Compare commits

..

35 Commits

Author SHA1 Message Date
7e409fc14a work towards DeferredCommandDraw being universal, support for DeferredCommandDepthRange, rework of cirlce mesh (has a bug still), get_quad_element_buffer() now more general, as it should be with gl_any 2024-10-17 21:01:35 +03:00
aa3cab87d2 skip switching texture modes when they're the same as the last used 2024-10-16 22:52:10 +03:00
1dc0dea762 no need for packed types no more 2024-10-16 03:31:02 +03:00
7f56ed8421 remove alignas for twn_types.h, it looks to be just as performant and even more in cases i looked at 2024-10-16 02:32:04 +03:00
119b706638 minor optimizations over sprite path 2024-10-15 19:32:42 +03:00
f2bbc1863e cache sprite srcrects 2024-10-15 18:43:02 +03:00
768daf1f54 move pipelines and texture modes to deferred commands 2024-10-15 18:35:08 +03:00
139394c6de partial implementation of double buffered render 2024-10-15 15:29:45 +03:00
446402c2e0 don't write unused color to vertices, utilize flat shading for only 2 important edges 2024-10-14 21:23:44 +03:00
f7a718003e send vertex data packed 2024-10-14 20:19:18 +03:00
f087bf1f7f fix depth clearing, ghostly modes not using depth layers, ortho with 0..1 2024-10-14 16:00:27 +03:00
19bf88d44e finally properly implemented depth optimization for 2d 2024-10-14 15:31:56 +03:00
3535a185df don't use depth range hack in rect case 2024-10-14 12:16:23 +03:00
d34516c4ee Merge branch 'main' of https://git.poto.cafe/wanp/townengine 2024-10-14 11:48:46 +03:00
b295c5920c rendering: use sprite batching techniques for rect primitives, unite their render path 2024-10-14 11:46:07 +03:00
f7f27119e1 use static, fixed arrays for circle geometry data instead of allocating for each one 2024-10-13 22:32:59 -03:00
ffab6a3924 cache font _files_ as well to avoid duplicate buffers 2024-10-13 21:34:05 -03:00
82bad550e5 CMakeLists.txt: fixes 2024-10-13 23:43:00 +03:00
19b9812b3e /bin/twn: load symbols from libgame.so 2024-10-13 22:51:48 +03:00
c8a65f2894 only do _GNU_SOURCE on linux 2024-10-13 21:44:32 +03:00
f0d3f6778c fix dynamic link issue 2024-10-13 21:36:01 +03:00
da98c0941b effort to have no warnings once again 2024-10-13 21:32:31 +03:00
d884cd45d9 CMakeLists.txt: visibility=hidden for add targets 2024-10-13 19:42:36 +03:00
d2422735e6 /third-party/physfs/: remove unused archivers, actually make cmake options pass, duh 2024-10-13 19:17:26 +03:00
ed93072371 twn_amalgam.c: a way for single unit compilation, controlled with -DTWN_USE_AMALGAM in cmake 2024-10-13 19:04:23 +03:00
9329d3c2be CMakeLists.txt: use visibility=hidden by default, so that unneeded libtownengine.so symbols are not leaked 2024-10-13 19:03:29 +03:00
ef5d609f4a /bin/build.sh: search for ninja and use it by default 2024-10-13 19:02:27 +03:00
64433cbe18 CMakeLists.txt: precompile physfs.h header 2024-10-13 18:17:32 +03:00
f96d521af2 CMakeLists.txt: don't precompile stb_ds.h, precompile SDL.h 2024-10-13 18:14:36 +03:00
1a7322dccf twn_util.h: separate internal things away, remove indirect includes in places 2024-10-12 21:16:25 +03:00
e70366f82f rework to context: now there's engine and user code copies, renaming of fields, most things that shouldn't be there are hidden 2024-10-12 20:24:47 +03:00
7886650339 /docs/getting_and_compiling.txt: add a note on git lfs install 2024-10-12 19:27:29 +03:00
3a57833ac1 /docs/getting_and_compiling.txt: update to changes 2024-10-12 19:12:20 +03:00
667b599c19 remove /include/twn_config.h, move defauls to relevant headers 2024-10-11 20:21:02 +03:00
cfc9ac9583 fix common-data pack reference for newly places demos 2024-10-11 19:31:30 +03:00
53 changed files with 1324 additions and 621 deletions

View File

@ -9,11 +9,11 @@ if(NOT EMSCRIPTEN)
endif() endif()
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified # CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
if(NOT CMAKE_BUILD_TYPE) if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
if(NOT TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug) if(NOT DEFINED TWN_SANITIZE AND CMAKE_BUILD_TYPE MATCHES Debug)
set(TWN_SANITIZE ON) set(TWN_SANITIZE ON)
endif() endif()
@ -24,6 +24,7 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
# feature configuration, set them with -DFEATURE=ON/OFF in cli # feature configuration, set them with -DFEATURE=ON/OFF in cli
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON) option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit" ON)
# todo: figure out how to compile for dynamic linking instead # todo: figure out how to compile for dynamic linking instead
if(EMSCRIPTEN) if(EMSCRIPTEN)
@ -41,19 +42,21 @@ if(HAIKU)
endif() endif()
# add -fPIC globally so that it's linked well # add -fPIC globally so that it's linked well
add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>) add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>
-fvisibility=hidden)
set(PHYSFS_BUILD_SHARED FALSE) set(PHYSFS_BUILD_SHARED FALSE CACHE INTERNAL "")
set(PHYSFS_DISABLE_INSTALL TRUE) set(PHYSFS_DISABLE_INSTALL TRUE CACHE INTERNAL "")
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall") set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall" CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_GRP OFF) set(PHYSFS_ARCHIVE_GRP OFF CACHE BOOL "")
set(PHYSFS_ARCHIVE_WAD OFF) set(PHYSFS_ARCHIVE_WAD OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_HOG OFF) set(PHYSFS_ARCHIVE_HOG OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_MVL OFF) set(PHYSFS_ARCHIVE_MVL OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_QPAK OFF) set(PHYSFS_ARCHIVE_QPAK OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_SLB OFF) set(PHYSFS_ARCHIVE_SLB OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_ISO9660 OFF) set(PHYSFS_ARCHIVE_ISO9660 OFF CACHE INTERNAL "")
set(PHYSFS_ARCHIVE_VDF OFF) set(PHYSFS_ARCHIVE_VDF OFF CACHE INTERNAL "")
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)
@ -88,7 +91,8 @@ set(TWN_THIRD_PARTY_SOURCE_FILES
third-party/tomlc99/toml.c third-party/tomlc99/toml.c
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/src/glad.c>) $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/src/glad.c>)
set(TWN_SOURCE_FILES set(TWN_NONOPT_SOURCE_FILES
src/twn_stb.c
src/twn_loop.c src/twn_loop.c
src/twn_main.c src/twn_main.c
src/twn_context.c include/twn_context.h src/twn_context.c include/twn_context.h
@ -100,11 +104,15 @@ set(TWN_SOURCE_FILES
src/rendering/twn_draw.c src/rendering/twn_draw_c.h src/rendering/twn_draw.c src/rendering/twn_draw_c.h
src/rendering/twn_sprites.c src/rendering/twn_sprites.c
src/rendering/twn_rects.c
src/rendering/twn_text.c src/rendering/twn_text.c
src/rendering/twn_triangles.c src/rendering/twn_triangles.c
src/rendering/twn_circles.c src/rendering/twn_circles.c
src/rendering/twn_skybox.c src/rendering/twn_skybox.c
src/rendering/twn_fog.c src/rendering/twn_fog.c)
set(TWN_SOURCE_FILES
$<IF:$<BOOL:${TWN_USE_AMALGAM}>,src/twn_amalgam.c src/twn_stb.c,${TWN_NONOPT_SOURCE_FILES}>
# for dynamic load based solution main is compiled in a separate target # for dynamic load based solution main is compiled in a separate target
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:src/twn_main.c $<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:src/twn_main.c
@ -113,6 +121,7 @@ set(TWN_SOURCE_FILES
${SYSTEM_SOURCE_FILES}) ${SYSTEM_SOURCE_FILES})
list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/) list(TRANSFORM TWN_SOURCE_FILES PREPEND ${TWN_ROOT_DIR}/)
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_NONOPT_SOURCE_FILES})
add_library(twn_third_parties STATIC ${TWN_THIRD_PARTY_SOURCE_FILES}) add_library(twn_third_parties STATIC ${TWN_THIRD_PARTY_SOURCE_FILES})
@ -123,8 +132,6 @@ else()
add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES} ${twn_third_parties}) add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES} ${twn_third_parties})
endif() endif()
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES})
set_target_properties(${TWN_TARGET} PROPERTIES set_target_properties(${TWN_TARGET} PROPERTIES
C_STANDARD 11 C_STANDARD 11
C_STANDARD_REQUIRED ON C_STANDARD_REQUIRED ON
@ -133,7 +140,8 @@ 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>
third-party/stb/stb_ds.h) ${SDL2_INCLUDE_DIR}/SDL.h
third-party/physfs/src/physfs.h)
function(give_options_without_warnings target) function(give_options_without_warnings target)
@ -168,20 +176,19 @@ function(give_options_without_warnings target)
target_compile_options(${target} PUBLIC target_compile_options(${target} PUBLIC
${BUILD_FLAGS} ${BUILD_FLAGS}
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}> $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}> $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
target_link_options(${target} PUBLIC target_link_options(${target} PUBLIC
${BUILD_FLAGS} ${BUILD_FLAGS}
# -Wl,--no-undefined # TODO: use later for implementing no-libc # -Wl,--no-undefined # TODO: use later for implementing no-libc
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}> $<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}> $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>
-Bsymbolic-functions -Bsymbolic-functions
-Wl,--hash-style=gnu) $<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
target_compile_definitions(${target} PRIVATE target_compile_definitions(${target} PRIVATE
$<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>) $<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:TWN_FEATURE_DYNLIB_GAME>
$<$<BOOL:${LINUX}>:_GNU_SOURCE>)
endfunction() endfunction()
@ -193,7 +200,8 @@ function(give_options target)
-Wno-padded -Wno-padded
-Wno-declaration-after-statement -Wno-declaration-after-statement
-Wno-unsafe-buffer-usage -Wno-unsafe-buffer-usage
-Wno-unused-command-line-argument) -Wno-unused-command-line-argument
-Wno-covered-switch-default)
set(WARNING_FLAGS set(WARNING_FLAGS
-Wall -Wall
@ -257,6 +265,8 @@ function(use_townengine target sources output_directory)
# launcher binary, loads game and engine shared library # launcher binary, loads game and engine shared library
add_executable(${target} ${TWN_ROOT_DIR}/src/twn_main.c) add_executable(${target} ${TWN_ROOT_DIR}/src/twn_main.c)
target_link_options(${target} PRIVATE $<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
# todo: copy instead? # todo: copy instead?
# put libtownengine.so alongside the binary # put libtownengine.so alongside the binary
set_target_properties(${TWN_TARGET} PROPERTIES set_target_properties(${TWN_TARGET} PROPERTIES

View File

@ -13,11 +13,11 @@
#define RIGHT_CLICK_ADD 500 #define RIGHT_CLICK_ADD 500
void handle_input(void) static void handle_input(void)
{ {
State *state = ctx.udata; State *state = ctx.udata;
if (ctx.mouse_window_position.y <= 60) if (ctx.mouse_position.y <= 60)
return; return;
if (input_is_action_pressed("add_a_bit")) if (input_is_action_pressed("add_a_bit"))
@ -27,10 +27,12 @@ void handle_input(void)
if (state->bunniesCount < MAX_BUNNIES) if (state->bunniesCount < MAX_BUNNIES)
{ {
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit"); state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_bit");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255}; (Color){(uint8_t)(state->bunniesCount % 190 + 50),
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }
@ -43,10 +45,12 @@ void handle_input(void)
if (state->bunniesCount < MAX_BUNNIES) if (state->bunniesCount < MAX_BUNNIES)
{ {
state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot"); state->bunnies[state->bunniesCount].position = input_get_action_position("add_a_lot");
state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.x = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0; state->bunnies[state->bunniesCount].speed.y = (float)(rand() % 500 - 250) / 60.0f;
state->bunnies[state->bunniesCount].color = state->bunnies[state->bunniesCount].color =
(Color){rand() % 190 + 50, rand() % 160 + 80, rand() % 140 + 100, 255}; (Color){(uint8_t)(state->bunniesCount % 190 + 50),
(uint8_t)((state->bunniesCount + 120) % 160 + 80),
(uint8_t)((state->bunniesCount + 65) % 140 + 100), 255};
state->bunniesCount++; state->bunniesCount++;
} }
} }
@ -73,32 +77,29 @@ void game_tick(void)
State *state = ctx.udata; State *state = ctx.udata;
const double delta =
(double)(ctx.delta_time) / 1000.0; // Receiving floating point delta value (diving by 1000 based on vibe)
for (int i = 0; i < state->bunniesCount; i++) for (int i = 0; i < state->bunniesCount; i++)
{ {
state->bunnies[i].position.x += state->bunnies[i].speed.x; state->bunnies[i].position.x += state->bunnies[i].speed.x;
state->bunnies[i].position.y += state->bunnies[i].speed.y; state->bunnies[i].position.y += state->bunnies[i].speed.y;
if (((state->bunnies[i].position.x + BUNNY_W / 2) > ctx.base_draw_w) || if (((state->bunnies[i].position.x + (float)BUNNY_W / 2) > (float)ctx.resolution.x) ||
((state->bunnies[i].position.x + BUNNY_W / 2) < 0)) ((state->bunnies[i].position.x + (float)BUNNY_W / 2) < 0))
state->bunnies[i].speed.x *= -1; state->bunnies[i].speed.x *= -1;
if (((state->bunnies[i].position.y + BUNNY_H / 2) > ctx.base_draw_h) || if (((state->bunnies[i].position.y + (float)BUNNY_H / 2) > (float)ctx.resolution.y) ||
((state->bunnies[i].position.y + BUNNY_H / 2 - 60) < 0)) ((state->bunnies[i].position.y + (float)BUNNY_H / 2 - 60) < 0))
state->bunnies[i].speed.y *= -1; state->bunnies[i].speed.y *= -1;
} }
handle_input(); handle_input();
// Clear window with Gray color (set the background color this way) // Clear window with Gray color (set the background color this way)
draw_rectangle((Rect){0, 0, ctx.base_draw_w, ctx.base_draw_h}, GRAY); draw_rectangle((Rect){0, 0, (float)ctx.resolution.x, (float)ctx.resolution.y}, GRAY);
for (int i = 0; i < state->bunniesCount; i++) for (int i = 0; i < state->bunniesCount; i++)
{ // Draw each bunny based on their position and color, also scale accordingly { // Draw each bunny based on their position and color, also scale accordingly
m_sprite(m_set(path, "wabbit_alpha.png"), m_sprite(m_set(path, "wabbit_alpha.png"),
m_set(rect, ((Rect){.x = (int)state->bunnies[i].position.x, m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
.y = (int)state->bunnies[i].position.y, .y = state->bunnies[i].position.y,
.w = BUNNY_W * SPRITE_SCALE, .w = BUNNY_W * SPRITE_SCALE,
.h = BUNNY_H * SPRITE_SCALE})), .h = BUNNY_H * SPRITE_SCALE})),
m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), ); m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), );

View File

@ -3,7 +3,7 @@
#include "twn_game_api.h" #include "twn_game_api.h"
#define MAX_BUNNIES 100000 // 100K bunnies limit #define MAX_BUNNIES 500000 // 100K bunnies limit
#define BUNNY_W 26 #define BUNNY_W 26
#define BUNNY_H 37 #define BUNNY_H 37
#define SPRITE_SCALE 1 #define SPRITE_SCALE 1

View File

@ -1,3 +1,3 @@
[[deps]] [[deps]]
source = "../../common-data" # where does it come from, might be an url source = "../../../common-data" # where does it come from, might be an url
name = "common-data" # should be globally unique name = "common-data" # should be globally unique

View File

@ -19,15 +19,13 @@ static void title_tick(State *state) {
return; return;
} }
m_sprite("/assets/title.png", ((Rect) { m_sprite("/assets/title.png", ((Rect) {
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */ /* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len); char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); snprintf(text_str, text_str_len, "%lu", state->ctx->frame_number);
const char *font = "fonts/kenney-pixel.ttf"; const char *font = "fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;

View File

@ -1,3 +1,3 @@
[[deps]] [[deps]]
source = "../../common-data" # where does it come from, might be an url source = "../../../common-data" # where does it come from, might be an url
name = "common-data" # should be globally unique name = "common-data" # should be globally unique

View File

@ -17,8 +17,8 @@ static void ingame_tick(State *state) {
if (input_is_mouse_captured()) { if (input_is_mouse_captured()) {
const float sensitivity = 0.6f; /* TODO: put this in a better place */ const float sensitivity = 0.6f; /* TODO: put this in a better place */
scn->yaw += (float)ctx.mouse_relative_position.x * sensitivity; scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
scn->pitch -= (float)ctx.mouse_relative_position.y * sensitivity; scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f); scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
const float yaw_rad = scn->yaw * (float)DEG2RAD; const float yaw_rad = scn->yaw * (float)DEG2RAD;
@ -62,8 +62,8 @@ static void ingame_tick(State *state) {
for (int ly = 64; ly--;) { for (int ly = 64; ly--;) {
for (int lx = 64; lx--;) { for (int lx = 64; lx--;) {
float x = SDL_truncf(scn->cam.pos.x + 32 - lx); float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
float y = SDL_truncf(scn->cam.pos.z + 32 - ly); float y = SDL_truncf(scn->cam.pos.z + 32 - (float)ly);
float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; float d0 = stb_perlin_noise3((float)x * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6; float d1 = stb_perlin_noise3((float)(x + 1) * TERRAIN_FREQUENCY, (float)y * TERRAIN_FREQUENCY, 0, 0, 0, 0) * 3 - 6;
@ -89,7 +89,7 @@ static void ingame_tick(State *state) {
} }
draw_skybox("/assets/miramar/miramar_*.tga"); draw_skybox("/assets/miramar/miramar_*.tga");
draw_fog(0.9, 1.0, 0.05, (Color){ 140, 147, 160, 255 }); draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 });
} }

View File

@ -16,15 +16,13 @@ static void title_tick(State *state) {
return; return;
} }
m_sprite("/assets/title.png", ((Rect) { m_sprite("/assets/title.png", ((Rect) {
((float)ctx.base_draw_w / 2) - ((float)320 / 2), 64, 320, 128 })); ((float)ctx.resolution.x / 2) - ((float)320 / 2), 64, 320, 128 }));
/* draw the tick count as an example of dynamic text */ /* draw the tick count as an example of dynamic text */
size_t text_str_len = snprintf(NULL, 0, "%lu", state->ctx->tick_count) + 1; size_t text_str_len = snprintf(NULL, 0, "%llu", state->ctx->frame_number) + 1;
char *text_str = cmalloc(text_str_len); char *text_str = cmalloc(text_str_len);
snprintf(text_str, text_str_len, "%lu", state->ctx->tick_count); snprintf(text_str, text_str_len, "%llu", state->ctx->frame_number);
const char *font = "/fonts/kenney-pixel.ttf"; const char *font = "/fonts/kenney-pixel.ttf";
int text_h = 32; int text_h = 32;
@ -39,8 +37,8 @@ static void title_tick(State *state) {
}, },
(Color) { 0, 0, 0, 255 } (Color) { 0, 0, 0, 255 }
); );
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
draw_text(text_str, (Vec2){ 0, 0 }, text_h, (Color) { 255, 255, 255, 255 }, font);
free(text_str); free(text_str);
} }

View File

@ -1,7 +1,12 @@
#!/bin/env sh #!/bin/env sh
if [ "$1" = "web" ]; then # check whether ninja is around (you better start running)
emcmake cmake -B .build-web "${@:2}" && cmake --build .build-web --parallel if [ -x "$(command -v ninja)" ]; then
else generator="-G Ninja"
cmake -B .build "$@" && cmake --build .build --parallel fi
if [ "$1" = "web" ]; then
emcmake cmake $generator -B .build-web "${@:2}" && cmake --build .build-web --parallel
else
cmake $generator -B .build "$@" && cmake --build .build --parallel
fi fi

View File

@ -16,7 +16,7 @@ case "$1" in
;; ;;
gdb ) unset DEBUGINFOD_URLS gdb ) unset DEBUGINFOD_URLS
$0 build && gdb -ex run --args "$(basename $PWD)" "${@:2}" $0 build && gdb --se=libgame.so -ex run --args "$(basename $PWD)" "${@:2}"
;; ;;
apitrace ) case "$2" in apitrace ) case "$2" in

View File

@ -1,6 +1,7 @@
firstly, make sure to pull git-lfs references with: :git lfs fetch origin main: firstly, make sure to pull git-lfs references with: :git lfs fetch origin main:
you might have to run `git lfs install` before that
install cmake and sdl2-dev packages in manner suitable to your distribution install cmake and sdl2-dev packages in manner suitable to your distribution
add /tools/ directory to your $PATH, for example, with :source hooks: add /bin/ directory to your $PATH, you can do it cleanly with :source hooks: command
navigate to one of /apps/ folders for demos navigate to one of /apps/ folders for demos
run `twn build` to build run `twn build` to build
runnable apps should have the same name as app folder it is in runnable apps should have the same name as app folder it is in

View File

@ -4,6 +4,7 @@ for that certain steps are taken:
* number of public api calls is kept at the minimum * number of public api calls is kept at the minimum
* procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike, * procedure signatures can only use basic types, no aggregates, with exception of Vec/Matrix types and alike,
with no expectation on new additions (see /include/twn_types.h) with no expectation on new additions (see /include/twn_types.h)
* optionals can be expressed via pointer passage of value primitives, with NULL expressive default
* /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling * /include/twn_game_api.json file is hand-kept with a schema to aid automatic generation and other tooling
one of main inspirations for that is opengl model one of main inspirations for that is opengl model

View File

@ -1,7 +1,7 @@
#ifndef TWN_CAMERA_H #ifndef TWN_CAMERA_H
#define TWN_CAMERA_H #define TWN_CAMERA_H
#include "twn_util.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
/* TODO: make it cached? */ /* TODO: make it cached? */

View File

@ -1,25 +0,0 @@
#ifndef TWN_CONFIG_H
#define TWN_CONFIG_H
/* TODO: consider if it's still necessary to keep these in one place */
#define PACKAGE_EXTENSION "btw"
#define TICKS_PER_SECOND_DEFAULT 60
#define BASE_RENDER_WIDTH_DEFAULT 640
#define BASE_RENDER_HEIGHT_DEFAULT 360
#define TEXTURE_ATLAS_SIZE_DEFAULT 2048
#define TEXTURE_ATLAS_BIT_DEPTH 32
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
#define KEYBIND_SLOTS_DEFAULT 3
#define AUDIO_FREQUENCY 48000
#define TEXT_FONT_TEXTURE_SIZE_DEFAULT 2048
#define TEXT_FONT_OVERSAMPLING_DEFAULT 4
#define TEXT_FONT_FILTERING_DEFAULT TEXTURE_FILTER_LINEAR
#endif

View File

@ -1,47 +1,46 @@
#ifndef TWN_CONTEXT_H #ifndef TWN_CONTEXT_H
#define TWN_CONTEXT_H #define TWN_CONTEXT_H
#include "twn_input.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
/* context that is valid for current frame */ /* context that is only valid for current frame */
/* changes to it should not have an effect, unless specified */ /* any changes to it will be dropped, unless explicitly stated */
/* TODO: ensure the statement above */
typedef struct Context { typedef struct Context {
/* you may read from and write to these from game code */ /* you may read from and write to this one from game code */
/* it is ensured to persist between initializations */
void *udata; void *udata;
/* TODO: is it what we actually want? */ /* which frame is it, starting from 0 at startup */
int64_t delta_time; /* preserves real time frame delta with no manipilation */ uint64_t frame_number;
uint64_t tick_count;
Vec2i mouse_window_position; /* real time spent on one frame (in seconds) */
Vec2i mouse_relative_position; /* townengine is fixed step based, so you don't have */
/* TODO: actually set it */
float frame_duration;
/* set just once on startup */ /* resolution is set from config and dictates both logical and drawing space, as they're related */
/* even if scaling is done, game logic should never change over that */
Vec2i resolution;
Vec2i mouse_position;
Vec2i mouse_movement;
/* is set on startup, should be used as source of randomness */
uint64_t random_seed; uint64_t random_seed;
/* this should be a multiple of the current ticks per second */ /* whether debugging logic should be enabled in user code */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
unsigned int update_multiplicity;
/* TODO: use Vec2i? */
int window_w;
int window_h;
int base_draw_w;
int base_draw_h;
bool debug; bool debug;
bool is_running;
bool window_size_has_changed; /* is set to true when state is invalidated and needs to be rebuilt */
/* watch for it and handle properly! */
bool initialization_needed; bool initialization_needed;
} Context; } Context;
/* when included after twn_engine_context there's an 'ctx' defined already */
#ifndef TWN_ENGINE_CONTEXT_C_H #ifndef TWN_ENGINE_CONTEXT_C_H
TWN_API extern Context ctx; TWN_API extern Context ctx;
#endif #endif

View File

@ -1,7 +1,7 @@
#ifndef TWN_DRAW_H #ifndef TWN_DRAW_H
#define TWN_DRAW_H #define TWN_DRAW_H
#include "twn_util.h" #include "twn_types.h"
#include "twn_option.h" #include "twn_option.h"
#include "twn_camera.h" #include "twn_camera.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
@ -22,6 +22,7 @@ TWN_API void draw_sprite(char const *path,
TWN_API void draw_rectangle(Rect rect, Color color); TWN_API void draw_rectangle(Rect rect, Color color);
/* pushes a filled circle onto the circle render queue */ /* pushes a filled circle onto the circle render queue */
/* note that its edges may look jagged with a radius larger than 2048 */
TWN_API void draw_circle(Vec2 position, float radius, Color color); TWN_API void draw_circle(Vec2 position, float radius, Color color);
/* TODO: have font optional, with something minimal coming embedded */ /* TODO: have font optional, with something minimal coming embedded */

View File

@ -4,7 +4,7 @@
#if defined(__WIN32) #if defined(__WIN32)
#define TWN_API __declspec(dllexport) #define TWN_API __declspec(dllexport)
#else #else
#define TWN_API #define TWN_API __attribute__((visibility("default")))
#endif #endif
#endif #endif

View File

@ -1,22 +1,23 @@
/* include this header in game code to get the usable parts of the engine */ /* include this header in game code to get the usable parts of the engine */
#ifndef GAME_API_H #ifndef TWN_GAME_API_H
#define GAME_API_H #define TWN_GAME_API_H
#include "twn_input.h"
#include "twn_context.h" #include "twn_context.h"
#include "twn_draw.h" #include "twn_draw.h"
#include "twn_audio.h" #include "twn_audio.h"
#include "twn_util.h"
#include "twn_input.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include "twn_util.h"
#ifndef TWN_NOT_C
/* sole game logic and display function. /* sole game logic and display function.
all state must be used from and saved to supplied state pointer. */ all state must be used from and saved to supplied state pointer. */
TWN_API extern void game_tick(void); TWN_API extern void game_tick(void);
/* called when application is closing. */
TWN_API extern void game_end(void);
/* called when application is closing. */
TWN_API extern void game_end(void);
#endif
#endif #endif

View File

@ -1,7 +1,6 @@
#ifndef TWN_INPUT_H #ifndef TWN_INPUT_H
#define TWN_INPUT_H #define TWN_INPUT_H
#include "twn_config.h"
#include "twn_types.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include "twn_control.h" #include "twn_control.h"

View File

@ -1,6 +1,9 @@
#ifndef TWN_TEXTURES_MODES_H #ifndef TWN_TEXTURES_MODES_H
#define TWN_TEXTURES_MODES_H #define TWN_TEXTURES_MODES_H
/* TODO: rename, as it doesn't have to be about textures only, but blending */
/* TODO: move from public /include/ tree */
/* alpha channel information */ /* alpha channel information */
typedef enum TextureMode { typedef enum TextureMode {
TEXTURE_MODE_OPAQUE, /* all pixels are solid */ TEXTURE_MODE_OPAQUE, /* all pixels are solid */

View File

@ -8,7 +8,6 @@
/* a point in some space (integer) */ /* a point in some space (integer) */
typedef struct Vec2i { typedef struct Vec2i {
_Alignas(8)
int32_t x; int32_t x;
int32_t y; int32_t y;
} Vec2i; } Vec2i;
@ -16,7 +15,6 @@ _Alignas(8)
/* a point in some space (floating point) */ /* a point in some space (floating point) */
typedef struct Vec2 { typedef struct Vec2 {
_Alignas(8)
float x; float x;
float y; float y;
} Vec2; } Vec2;
@ -25,7 +23,6 @@ _Alignas(8)
/* a point in some three dimension space (floating point) */ /* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */ /* y goes up, x goes to the right */
typedef struct Vec3 { typedef struct Vec3 {
_Alignas(16)
float x; float x;
float y; float y;
float z; float z;
@ -35,7 +32,6 @@ _Alignas(16)
/* a point in some three dimension space (floating point) */ /* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */ /* y goes up, x goes to the right */
typedef struct Vec4 { typedef struct Vec4 {
_Alignas(16)
float x; float x;
float y; float y;
float z; float z;
@ -45,7 +41,6 @@ _Alignas(16)
/* 32-bit color data */ /* 32-bit color data */
typedef struct Color { typedef struct Color {
_Alignas(4)
uint8_t r; uint8_t r;
uint8_t g; uint8_t g;
uint8_t b; uint8_t b;
@ -55,7 +50,6 @@ _Alignas(4)
/* a rectangle with the origin at the upper left (integer) */ /* a rectangle with the origin at the upper left (integer) */
typedef struct Recti { typedef struct Recti {
_Alignas(16)
int32_t x; int32_t x;
int32_t y; int32_t y;
int32_t w; int32_t w;
@ -65,7 +59,6 @@ _Alignas(16)
/* a rectangle with the origin at the upper left (floating point) */ /* a rectangle with the origin at the upper left (floating point) */
typedef struct Rect { typedef struct Rect {
_Alignas(16)
float x; float x;
float y; float y;
float w; float w;

View File

@ -1,23 +1,15 @@
#ifndef UTIL_H #ifndef TWN_UTIL_H
#define UTIL_H #define TWN_UTIL_H
#include "twn_types.h" #include "twn_types.h"
#include "twn_engine_api.h" #include "twn_engine_api.h"
#include "twn_types.h"
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
#include <math.h> #include <math.h>
/* DON'T FORGET ABOUT DOUBLE EVALUATION */
/* YOU THINK YOU WON'T, AND THEN YOU DO */
/* C23's typeof could fix it, so i will */
/* leave them as macros to be replaced. */
/* use the macro in tgmath.h for floats */
#define MAX SDL_max
#define MIN SDL_min
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288 /**< pi */ #define M_PI 3.14159265358979323846264338327950288 /**< pi */
#endif #endif
@ -26,16 +18,13 @@
#define DEG2RAD (M_PI / 180) #define DEG2RAD (M_PI / 180)
#define RAD2DEG (180 / M_PI) #define RAD2DEG (180 / M_PI)
#ifndef TWN_NOT_C
/* */ TWN_API void *cmalloc(size_t size);
/* GENERAL UTILITIES */ TWN_API void *crealloc(void *ptr, size_t size);
/* */ TWN_API void *ccalloc(size_t num, size_t size);
TWN_API void cry_impl(const char *file, const int line, const char *title, const char *text); #endif /* TWN_NOT_C */
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
TWN_API void log_info(const char *restrict format, ...); TWN_API void log_info(const char *restrict format, ...);
@ -43,18 +32,6 @@ TWN_API void log_critical(const char *restrict format, ...);
TWN_API void log_warn(const char *restrict format, ...); TWN_API void log_warn(const char *restrict format, ...);
/* for when there's absolutely no way to continue */
TWN_API _Noreturn void die_abruptly(void);
/* "critical" allocation functions which will log and abort() on failure. */
/* if it is reasonable to handle this gracefully, use the standard versions. */
/* the stb implementations will be configured to use these */
TWN_API void *cmalloc(size_t size);
TWN_API void *crealloc(void *ptr, size_t size);
TWN_API void *ccalloc(size_t num, size_t size);
/* TODO: this is why generics were invented. sorry, i'm tired today */ /* TODO: this is why generics were invented. sorry, i'm tired today */
TWN_API double clamp(double d, double min, double max); TWN_API double clamp(double d, double min, double max);
TWN_API float clampf(float f, float min, float max); TWN_API float clampf(float f, float min, float max);

View File

@ -1,5 +1,6 @@
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include <x-watcher.h> #include <x-watcher.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
@ -38,7 +39,7 @@ static void load_game_object(void) {
goto ERR_OPENING_SO; goto ERR_OPENING_SO;
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic" #pragma GCC diagnostic ignored "-Wpedantic"
game_tick_callback = (void (*)(void))dlsym(new_handle, "game_tick"); game_tick_callback = (void (*)(void))dlsym(new_handle, "game_tick");
@ -57,7 +58,7 @@ static void load_game_object(void) {
handle = new_handle; handle = new_handle;
if (ctx.game.tick_count != 0) if (ctx.game.frame_number != 0)
log_info("Game object was reloaded\n"); log_info("Game object was reloaded\n");
return; return;
@ -82,13 +83,20 @@ static void watcher_callback(XWATCHER_FILE_EVENT event,
(void)data; (void)data;
switch(event) { switch(event) {
case XWATCHER_FILE_CREATED:
case XWATCHER_FILE_MODIFIED: case XWATCHER_FILE_MODIFIED:
SDL_LockMutex(lock); SDL_LockMutex(lock);
last_tick_modified = ctx.game.tick_count; last_tick_modified = ctx.game.frame_number;
loaded_after_modification = false; loaded_after_modification = false;
SDL_UnlockMutex(lock); SDL_UnlockMutex(lock);
break; break;
case XWATCHER_FILE_UNSPECIFIED:
case XWATCHER_FILE_REMOVED:
case XWATCHER_FILE_OPENED:
case XWATCHER_FILE_ATTRIBUTES_CHANGED:
case XWATCHER_FILE_NONE:
case XWATCHER_FILE_RENAMED:
default: default:
break; break;
} }
@ -129,7 +137,7 @@ bool game_object_try_reloading(void) {
/* only load the modified library after some time, as compilers make a lot of modifications */ /* only load the modified library after some time, as compilers make a lot of modifications */
SDL_LockMutex(lock); SDL_LockMutex(lock);
if (ctx.game.tick_count - last_tick_modified > MODIFIED_TICKS_MERGED && if (ctx.game.frame_number - last_tick_modified > MODIFIED_TICKS_MERGED &&
!loaded_after_modification) { !loaded_after_modification) {
load_game_object(); load_game_object();
loaded_after_modification = true; loaded_after_modification = true;

View File

@ -1,5 +1,6 @@
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util_c.h"
#include <errhandlingapi.h> #include <errhandlingapi.h>
#include <libloaderapi.h> #include <libloaderapi.h>
@ -48,7 +49,7 @@ static void load_game_object(void) {
handle = new_handle; handle = new_handle;
if (ctx.game.tick_count != 0) if (ctx.game.frame_number != 0)
log_info("Game object was reloaded\n"); log_info("Game object was reloaded\n");
return; return;

View File

@ -1,4 +1,3 @@
#include "twn_util.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_draw.h" #include "twn_draw.h"
@ -22,28 +21,17 @@ void draw_circle(Vec2 position, float radius, Color color) {
arrput(ctx.render_queue_2d, primitive); arrput(ctx.render_queue_2d, primitive);
} }
/* TODO: caching and reuse scheme */
/* vertices_out and indices_out MUST BE FREED */
void create_circle_geometry(Vec2 position, void create_circle_geometry(Vec2 position,
Color color,
float radius, float radius,
size_t num_vertices, size_t num_vertices,
SDL_Vertex **vertices_out, Vec2 vertices[])
int **indices_out)
{ {
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
/* the angle (in radians) to rotate by on each iteration */ /* the angle (in radians) to rotate by on each iteration */
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180); float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
vertices[0].position.x = (float)position.x; vertices[0].x = (float)position.x;
vertices[0].position.y = (float)position.y; vertices[0].y = (float)position.y;
vertices[0].color.r = color.r;
vertices[0].color.g = color.g;
vertices[0].color.b = color.b;
vertices[0].color.a = color.a;
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
/* this point will rotate around the center */ /* this point will rotate around the center */
float start_x = 0.0f - radius; float start_x = 0.0f - radius;
@ -52,37 +40,13 @@ void create_circle_geometry(Vec2 position,
for (size_t i = 1; i < num_vertices + 1; ++i) { for (size_t i = 1; i < num_vertices + 1; ++i) {
float final_seg_rotation_angle = (float)i * seg_rotation_angle; float final_seg_rotation_angle = (float)i * seg_rotation_angle;
vertices[i].position.x = float c, s;
cosf(final_seg_rotation_angle) * start_x - sincosf(final_seg_rotation_angle, &s, &c);
sinf(final_seg_rotation_angle) * start_y;
vertices[i].position.y =
cosf(final_seg_rotation_angle) * start_y +
sinf(final_seg_rotation_angle) * start_x;
vertices[i].position.x += position.x; vertices[i].x = c * start_x - s * start_y;
vertices[i].position.y += position.y; vertices[i].y = c * start_y + s * start_x;
vertices[i].color.r = color.r; vertices[i].x += position.x;
vertices[i].color.g = color.g; vertices[i].y += position.y;
vertices[i].color.b = color.b;
vertices[i].color.a = color.a;
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
size_t triangle_offset = 3 * (i - 1);
/* center point index */
indices[triangle_offset] = 0;
/* generated point index */
indices[triangle_offset + 1] = (int)i;
size_t index = (i + 1) % num_vertices;
if (index == 0)
index = num_vertices;
indices[triangle_offset + 2] = (int)index;
} }
*vertices_out = vertices;
*indices_out = indices;
} }

View File

@ -35,22 +35,6 @@ void render_queue_clear(void) {
} }
/* rectangle */
void draw_rectangle(Rect rect, Color color) {
RectPrimitive rectangle = {
.rect = rect,
.color = color,
};
Primitive2D primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
}
void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) { void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
const float bt = (float)border_thickness; /* i know! */ const float bt = (float)border_thickness; /* i know! */
const float bt2 = bt * 2; /* combined size of the two borders in an axis */ const float bt2 = bt * 2; /* combined size of the two borders in an axis */
@ -197,34 +181,152 @@ static void render_2d(void) {
const size_t render_queue_len = arrlenu(ctx.render_queue_2d); const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
size_t batch_count = 0; struct Render2DInvocation {
Primitive2D const *primitive;
double layer;
union {
struct QuadBatch quad_batch;
};
};
/* first, collect all invocations, while merging into batches where applicable */
/* we separate into opaque and transparent ones, as it presents optimization opportunities */
struct Render2DInvocation *opaque_invocations = NULL;
struct Render2DInvocation *ghostly_invocations = NULL;
arrsetcap(opaque_invocations, render_queue_len);
arrsetcap(ghostly_invocations, render_queue_len);
for (size_t i = 0; i < render_queue_len; ++i) { for (size_t i = 0; i < render_queue_len; ++i) {
const Primitive2D *current = &ctx.render_queue_2d[i]; const Primitive2D *current = &ctx.render_queue_2d[i];
// TODO: https://gamedev.stackexchange.com/questions/101136/using-full-resolution-of-depth-buffer-for-2d-rendering
double const layer = ((double)((render_queue_len + 1) - i) / (double)(render_queue_len + 1)) * 0.75;
switch (current->type) { switch (current->type) {
case PRIMITIVE_2D_SPRITE: { case PRIMITIVE_2D_SPRITE: {
const struct SpriteBatch batch = const struct QuadBatch batch =
collect_sprite_batch(current, render_queue_len - i); collect_sprite_batch(current, render_queue_len - i);
/* TODO: what's even the point? just use OR_EQUAL comparison */ struct Render2DInvocation const invocation = {
set_depth_range((double)batch_count / UINT16_MAX, 1.0); .primitive = current,
render_sprites(current, batch); .quad_batch = batch,
.layer = layer,
};
i += batch.size - 1; ++batch_count; if (batch.mode == TEXTURE_MODE_GHOSTLY)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
i += batch.size - 1;
break; break;
} }
case PRIMITIVE_2D_RECT:
render_rectangle(&current->rect); case PRIMITIVE_2D_RECT: {
const struct QuadBatch batch =
collect_rect_batch(current, render_queue_len - i);
struct Render2DInvocation const invocation = {
.primitive = current,
.quad_batch = batch,
.layer = layer,
};
if (batch.mode == TEXTURE_MODE_GHOSTLY)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
i += batch.size - 1;
break; break;
case PRIMITIVE_2D_CIRCLE: }
render_circle(&current->circle);
case PRIMITIVE_2D_CIRCLE: {
struct Render2DInvocation const invocation = {
.primitive = current,
.layer = layer,
};
if (current->circle.color.a != 255)
arrput(ghostly_invocations, invocation);
else
arrput(opaque_invocations, invocation);
break; break;
case PRIMITIVE_2D_TEXT: }
render_text(&current->text);
case PRIMITIVE_2D_TEXT: {
struct Render2DInvocation const invocation = {
.primitive = current,
.layer = layer,
};
arrput(ghostly_invocations, invocation);
break; break;
}
default:
SDL_assert(false);
} }
} }
/* first issue all opaque primitives, front-to-back */
for (size_t i = 0; i < arrlenu(opaque_invocations); ++i) {
struct Render2DInvocation const invocation = opaque_invocations[arrlenu(opaque_invocations) - 1 - i];
/* idea here is to set constant z write that moves further and further along */
/* with that every batch can early z reject against the previous */
/* additionally, it will also apply for future transparent passes, sandwitching in-between */
set_depth_range(invocation.layer, 1.0);
switch (invocation.primitive->type) {
case PRIMITIVE_2D_SPRITE: {
render_sprite_batch(invocation.primitive, invocation.quad_batch);
break;
}
case PRIMITIVE_2D_RECT: {
render_rect_batch(invocation.primitive, invocation.quad_batch);
break;
}
/* TODO: circle batching */
case PRIMITIVE_2D_CIRCLE:
render_circle(&invocation.primitive->circle);
break;
case PRIMITIVE_2D_TEXT:
default:
SDL_assert(false);
}
}
/* then issue all transparent primitives, back-to-front */
for (size_t i = 0; i < arrlenu(ghostly_invocations); ++i) {
struct Render2DInvocation const invocation = ghostly_invocations[i];
/* now we use it not for writing layers, but inferring ordering */
set_depth_range(invocation.layer, 1.0);
switch (invocation.primitive->type) {
case PRIMITIVE_2D_SPRITE: {
render_sprite_batch(invocation.primitive, invocation.quad_batch);
break;
}
case PRIMITIVE_2D_RECT: {
render_rect_batch(invocation.primitive, invocation.quad_batch);
break;
}
/* TODO: circle batching */
case PRIMITIVE_2D_CIRCLE:
render_circle(&invocation.primitive->circle);
break;
case PRIMITIVE_2D_TEXT:
render_text(&invocation.primitive->text);
break;
default:
SDL_assert(false);
}
}
arrfree(opaque_invocations);
arrfree(ghostly_invocations);
} }
@ -250,33 +352,33 @@ void render(void) {
textures_update_atlas(&ctx.texture_cache); textures_update_atlas(&ctx.texture_cache);
/* fit rendering context onto the resizable screen */ /* fit rendering context onto the resizable screen */
if (ctx.game.window_size_has_changed) { if (ctx.window_size_has_changed) {
if ((float)ctx.game.window_w / (float)ctx.game.window_h > (float)ctx.base_render_width / (float)ctx.base_render_height) { if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
float ratio = (float)ctx.game.window_h / (float)ctx.base_render_height; float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
int w = (int)((float)ctx.base_render_width * ratio); int w = (int)((float)ctx.base_render_width * ratio);
setup_viewport( setup_viewport(
ctx.game.window_w / 2 - w / 2, ctx.window_dims.x / 2 - w / 2,
0, 0,
w, w,
ctx.game.window_h ctx.window_dims.y
); );
} else { } else {
float ratio = (float)ctx.game.window_w / (float)ctx.base_render_width; float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
int h = (int)((float)ctx.base_render_height * ratio); int h = (int)((float)ctx.base_render_height * ratio);
setup_viewport( setup_viewport(
0, 0,
ctx.game.window_h / 2 - h / 2, ctx.window_dims.y / 2 - h / 2,
ctx.game.window_w, ctx.window_dims.x,
h h
); );
} }
} }
render_space(); start_render_frame(); {
render_skybox(); /* after space, as to use depth buffer for early rejection */ render_space();
render_2d(); render_skybox(); /* after space, as to use depth buffer for early z rejection */
swap_buffers(); render_2d();
clear_draw_buffer(); } end_render_frame();
} }

View File

@ -20,7 +20,10 @@
extern Matrix4 camera_projection_matrix; extern Matrix4 camera_projection_matrix;
extern Matrix4 camera_look_at_matrix; extern Matrix4 camera_look_at_matrix;
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6) #define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
#define CIRCLE_VERTICES_MAX 2048
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
typedef GLuint VertexBuffer; typedef GLuint VertexBuffer;
@ -126,22 +129,25 @@ void render(void);
/* clears all render queues */ /* clears all render queues */
void render_queue_clear(void); void render_queue_clear(void);
/* fills two existing arrays with the geometry data of a circle */
/* the size of indices must be at least 3 times the number of vertices */
void create_circle_geometry(Vec2 position, void create_circle_geometry(Vec2 position,
Color color,
float radius, float radius,
size_t num_vertices, size_t num_vertices,
SDL_Vertex **vertices_out, Vec2 vertices[]);
int **indices_out);
struct SpriteBatch { struct QuadBatch {
size_t size; /* how many primitives are in current batch */ size_t size; /* how many primitives are in current batch */
TextureMode mode; TextureMode mode; /* how color should be applied */
bool constant_colored; /* whether colored batch is uniformly colored */ bool constant_colored; /* whether colored batch is uniformly colored */
bool repeat; /* whether repeat is needed */ bool repeat; /* whether repeat is needed */
} collect_sprite_batch(const Primitive2D primitives[], size_t len); bool textured;
} collect_quad_batch(const Primitive2D primitives[], size_t len);
void render_sprites(const Primitive2D primitives[], void render_quad_batch(const Primitive2D primitives[], struct QuadBatch batch);
const struct SpriteBatch batch); struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len);
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);
void draw_uncolored_space_traingle_batch(MeshBatch *batch, void draw_uncolored_space_traingle_batch(MeshBatch *batch,
TextureKey texture_key); TextureKey texture_key);
@ -160,16 +166,18 @@ void text_cache_reset_arena(TextCache *cache);
VertexBuffer create_vertex_buffer(void); VertexBuffer create_vertex_buffer(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 *data, size_t bytes); void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
/* uses present in 1.5 buffer mapping feature */ /* uses present in 1.5 buffer mapping feature */
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes); VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
/* collects bytes for sending to the gpu until all is pushed, which is when false is returned */ /* collects bytes for sending to the gpu until all is pushed, which is when false is returned */
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder, bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
void *bytes, void const *bytes,
size_t size); size_t size);
/* state */ /* state */
@ -182,7 +190,9 @@ void swap_buffers(void);
void set_depth_range(double low, double high); void set_depth_range(double low, double high);
void bind_quad_element_buffer(void); VertexBuffer get_quad_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
void render_circle(const CirclePrimitive *circle); void render_circle(const CirclePrimitive *circle);
@ -194,17 +204,17 @@ void use_2d_pipeline(void);
void use_texture_mode(TextureMode mode); void use_texture_mode(TextureMode mode);
void finally_render_sprites(Primitive2D const primitives[], void finally_render_quads(Primitive2D const primitives[],
struct SpriteBatch batch, struct QuadBatch batch,
VertexBuffer buffer); VertexBuffer buffer);
size_t get_sprite_payload_size(struct SpriteBatch batch); size_t get_quad_payload_size(struct QuadBatch batch);
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch, bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
VertexBufferBuilder *builder, VertexBufferBuilder *builder,
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
Color color); Color color);
void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch, void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch,
TextureKey texture_key, TextureKey texture_key,
@ -233,4 +243,8 @@ void pop_fog(void);
void finally_pop_fog(void); void finally_pop_fog(void);
void start_render_frame(void);
void end_render_frame(void);
#endif #endif

View File

@ -1,5 +1,5 @@
#include "twn_gpu_texture_c.h" #include "twn_gpu_texture_c.h"
#include "twn_util.h" #include "twn_util_c.h"
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) { GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {

View File

@ -1,17 +1,17 @@
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h" #include "twn_util_c.h"
#include "twn_config.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_text_c.h" #include "twn_text_c.h"
#include "twn_types.h"
#include <glad/glad.h> #include <glad/glad.h>
#include <stb_ds.h>
/* TODO: try using the fact we utilize edge coloring and step virtual color attributes to bogus points */
/* this is only doable is we take out color attribute to separate array or a portion of it */
/* interleaved vertex array data */ /* interleaved vertex array data */
/* TODO: use int16_t for uvs */
/* TODO: use packed types? */
/* TODO: int16_t could be used for positioning, but we would need to have more CPU calcs */
typedef struct ElementIndexedQuad { typedef struct ElementIndexedQuad {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2 v0;
@ -31,7 +31,6 @@ typedef struct ElementIndexedQuad {
Color c3; Color c3;
} ElementIndexedQuad; } ElementIndexedQuad;
typedef struct ElementIndexedQuadWithoutColor { typedef struct ElementIndexedQuadWithoutColor {
/* upper-left */ /* upper-left */
Vec2 v0; Vec2 v0;
@ -48,6 +47,75 @@ typedef struct ElementIndexedQuadWithoutColor {
} ElementIndexedQuadWithoutColor; } ElementIndexedQuadWithoutColor;
typedef struct ElementIndexedQuadWithoutTexture {
/* upper-left */
Vec2 v0;
Color c0;
/* bottom-left */
Vec2 v1;
Color c1;
/* bottom-right */
Vec2 v2;
Color c2;
/* upper-right */
Vec2 v3;
Color c3;
} ElementIndexedQuadWithoutTexture;
typedef struct ElementIndexedQuadWithoutColorWithoutTexture {
/* upper-left */
Vec2 v0;
/* bottom-left */
Vec2 v1;
/* bottom-right */
Vec2 v2;
/* upper-right */
Vec2 v3;
} ElementIndexedQuadWithoutColorWithoutTexture;
typedef struct {
size_t offset;
GLenum type;
GLsizei stride;
GLuint buffer;
uint8_t arity; /* leave at 0 to signal pointer as unused */
} AttributeArrayPointer;
/* allows us to have generic way to issue draws as well as */
/* deferring new draw calls while previous frame is still being drawn */
typedef struct {
AttributeArrayPointer vertices;
AttributeArrayPointer texcoords;
bool constant_colored;
union {
AttributeArrayPointer colors;
Color color;
};
bool textured, texture_repeat, uses_gpu_key;
TextureKey texture_key;
GPUTexture gpu_texture;
GLuint element_buffer;
GLsizei element_count;
GLsizei range_start, range_end;
double depth_range_low, depth_range_high;
} DeferredCommandDraw;
typedef struct {
Color color;
bool clear_color;
bool clear_depth;
bool clear_stencil;
} DeferredCommandClear;
typedef enum { typedef enum {
PIPELINE_NO, PIPELINE_NO,
PIPELINE_SPACE, PIPELINE_SPACE,
@ -55,10 +123,266 @@ typedef enum {
} Pipeline; } Pipeline;
typedef struct {
Pipeline pipeline;
} DeferredCommandUsePipeline;
typedef struct {
TextureMode mode;
} DeferredCommandUseTextureMode;
typedef struct {
double low, high;
} DeferredCommandDepthRange;
typedef struct {
enum DeferredCommandType {
DEFERRED_COMMAND_TYPE_DRAW,
DEFERRED_COMMAND_TYPE_CLEAR,
DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
} type;
union {
DeferredCommandDraw draw;
DeferredCommandClear clear;
DeferredCommandUsePipeline use_pipeline;
DeferredCommandUseTextureMode use_texture_mode;
DeferredCommandDepthRange depth_range;
};
} DeferredCommand;
static TextureMode texture_mode_last_used = -1;
static Pipeline pipeline_last_used = PIPELINE_NO; static Pipeline pipeline_last_used = PIPELINE_NO;
/* potentially double buffered array of vertex array handles */
/* we assume they will be refilled fully each frame */
static size_t scratch_va_front_used, scratch_va_back_used;
static GLuint *front_scratch_vertex_arrays, *back_scratch_vertex_arrays;
static GLuint **current_scratch_vertex_array = &front_scratch_vertex_arrays;
static void restart_scratch_vertex_arrays(void) {
scratch_va_front_used = 0;
scratch_va_back_used = 0;
if (ctx.render_double_buffered) {
current_scratch_vertex_array = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
&back_scratch_vertex_arrays : &front_scratch_vertex_arrays;
}
}
GLuint get_scratch_vertex_array(void) {
size_t *used = current_scratch_vertex_array == &front_scratch_vertex_arrays ?
&scratch_va_front_used : &scratch_va_back_used;
if (arrlenu(*current_scratch_vertex_array) <= *used) {
GLuint handle;
glGenBuffers(1, &handle);
arrpush(*current_scratch_vertex_array, handle);
}
(*used)++;
return (*current_scratch_vertex_array)[*used - 1];
}
static void finally_use_2d_pipeline(void);
static void finally_use_space_pipeline(void);
static void finally_use_texture_mode(TextureMode mode);
static DeferredCommand *deferred_commands;
static void issue_deferred_draw_commands(void) {
for (size_t i = 0; i < arrlenu(deferred_commands); ++i) {
switch (deferred_commands[i].type) {
case DEFERRED_COMMAND_TYPE_DEPTH_RANGE: {
glDepthRange(deferred_commands[i].depth_range.low, deferred_commands[i].depth_range.high);
break;
}
case DEFERRED_COMMAND_TYPE_CLEAR: {
glClearColor((1.0f / 255) * deferred_commands[i].clear.color.r,
(1.0f / 255) * deferred_commands[i].clear.color.g,
(1.0f / 255) * deferred_commands[i].clear.color.b,
(1.0f / 255) * deferred_commands[i].clear.color.a);
/* needed as we might mess with it */
glDepthRange(0.0, 1.0);
glDepthMask(GL_TRUE);
glClear((deferred_commands[i].clear.clear_color ? GL_COLOR_BUFFER_BIT : 0) |
(deferred_commands[i].clear.clear_depth ? GL_DEPTH_BUFFER_BIT : 0) |
(deferred_commands[i].clear.clear_stencil ? GL_STENCIL_BUFFER_BIT : 0) );
break;
}
case DEFERRED_COMMAND_TYPE_DRAW: {
DeferredCommandDraw const command = deferred_commands[i].draw;
/* TODO: don't assume a single vertex array ? */
SDL_assert(command.vertices.arity != 0);
SDL_assert(command.vertices.buffer);
SDL_assert(command.element_count != 0);
SDL_assert(command.element_buffer);
glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(command.vertices.arity,
command.vertices.type,
command.vertices.stride,
(void *)command.vertices.offset);
if (command.texcoords.arity != 0) {
SDL_assert(command.texcoords.buffer == command.vertices.buffer);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(command.texcoords.arity,
command.texcoords.type,
command.texcoords.stride,
(void *)command.texcoords.offset);
}
if (command.colors.arity != 0) {
SDL_assert(command.colors.buffer == command.vertices.buffer);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(command.colors.arity,
command.colors.type,
command.colors.stride,
(void *)command.colors.offset);
} else if (command.constant_colored)
glColor4ub(command.color.r,
command.color.g,
command.color.b,
command.color.a);
if (command.textured) {
if (command.uses_gpu_key)
glBindTexture(GL_TEXTURE_2D, command.gpu_texture);
else if (command.texture_repeat)
textures_bind_repeating(&ctx.texture_cache, command.texture_key);
else
textures_bind(&ctx.texture_cache, command.texture_key);
}
if (command.range_start == command.range_end)
glDrawElements(GL_TRIANGLES, command.element_count, GL_UNSIGNED_SHORT, NULL);
else
glDrawRangeElements(GL_TRIANGLES,
command.range_start,
command.range_end,
command.element_count,
GL_UNSIGNED_SHORT,
NULL);
/* state clearing */
if (command.textured)
glBindTexture(GL_TEXTURE_2D, 0);
if (command.colors.arity != 0)
glDisableClientState(GL_COLOR_ARRAY);
if (command.texcoords.arity != 0)
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
break;
}
case DEFERRED_COMMAND_TYPE_USE_PIPIELINE: {
switch (deferred_commands[i].use_pipeline.pipeline) {
case PIPELINE_2D:
finally_use_2d_pipeline();
break;
case PIPELINE_SPACE:
finally_use_space_pipeline();
break;
case PIPELINE_NO:
default:
SDL_assert(false);
}
break;
}
case DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE: {
finally_use_texture_mode(deferred_commands[i].use_texture_mode.mode);
break;
}
default:
SDL_assert(false);
}
}
}
void clear_draw_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
DeferredCommand command = {
.type = DEFERRED_COMMAND_TYPE_CLEAR,
.clear = (DeferredCommandClear) {
.clear_color = true,
.clear_depth = true,
.clear_stencil = true,
.color = (Color) { 230, 230, 230, 1 }
}
};
arrpush(deferred_commands, command);
}
void start_render_frame(void) {
clear_draw_buffer();
}
void end_render_frame(void) {
if (!ctx.render_double_buffered || ctx.game.frame_number == 0) {
issue_deferred_draw_commands();
SDL_GL_SwapWindow(ctx.window);
arrsetlen(deferred_commands, 0);
restart_scratch_vertex_arrays();
} else {
/* instead of waiting for frame to finish for the swap, we continue */
/* while issuing new state for the next call, but deferring any fragment emitting calls for later */
/* actual swap will happen when next frame is fully finished, introducing a delay */
SDL_GL_SwapWindow(ctx.window);
issue_deferred_draw_commands();
restart_scratch_vertex_arrays();
glFlush();
arrsetlen(deferred_commands, 0);
}
}
void use_space_pipeline(void) { void use_space_pipeline(void) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
.use_pipeline = { PIPELINE_SPACE }
};
arrpush(deferred_commands, command);
}
static void finally_use_space_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) if (pipeline_last_used == PIPELINE_SPACE)
return; return;
@ -91,11 +415,22 @@ void use_space_pipeline(void) {
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&camera_look_at_matrix.row[0].x); glLoadMatrixf(&camera_look_at_matrix.row[0].x);
texture_mode_last_used = -1;
pipeline_last_used = PIPELINE_SPACE; pipeline_last_used = PIPELINE_SPACE;
} }
void use_2d_pipeline(void) { void use_2d_pipeline(void) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_PIPIELINE,
.use_pipeline = { PIPELINE_2D }
};
arrpush(deferred_commands, command);
}
static void finally_use_2d_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) { if (pipeline_last_used == PIPELINE_SPACE) {
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFlush(); glFlush();
@ -127,80 +462,30 @@ void use_2d_pipeline(void) {
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, -1, 1); glOrtho(0, (double)ctx.base_render_width, (double)ctx.base_render_height, 0, 0, 1);
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glLoadIdentity();
texture_mode_last_used = -1;
pipeline_last_used = PIPELINE_2D; pipeline_last_used = PIPELINE_2D;
} }
void upload_quad_vertices(Rect rect) {
/* client memory needs to be reachable on glDraw*, so */
static float vertices[6 * 2];
vertices[0] = rect.x; vertices[1] = rect.y;
vertices[2] = rect.x; vertices[3] = rect.y + rect.h;
vertices[4] = rect.x + rect.w; vertices[5] = rect.y + rect.h;
vertices[6] = rect.x + rect.w; vertices[7] = rect.y + rect.h;
vertices[8] = rect.x + rect.w; vertices[9] = rect.y;
vertices[10] = rect.x; vertices[11] = rect.y;
glVertexPointer(2, GL_FLOAT, 0, (void *)&vertices);
}
void render_rectangle(const RectPrimitive *rectangle) {
glColor4ub(rectangle->color.r, rectangle->color.g,
rectangle->color.b, rectangle->color.a);
glEnableClientState(GL_VERTEX_ARRAY);
upload_quad_vertices(rectangle->rect);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
}
void render_circle(const CirclePrimitive *circle) {
SDL_Vertex *vertices = NULL;
int *indices = NULL;
int num_vertices = (int)circle->radius;
create_circle_geometry(circle->position,
circle->color,
circle->radius,
num_vertices,
&vertices,
&indices);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
sizeof (SDL_Vertex),
&vertices->position);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
sizeof (SDL_Vertex),
&vertices->color);
glDrawElements(GL_TRIANGLES,
num_vertices * 3,
GL_UNSIGNED_INT,
indices);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
SDL_free(vertices);
SDL_free(indices);
}
void use_texture_mode(TextureMode mode) { void use_texture_mode(TextureMode mode) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_USE_TEXTURE_MODE,
.use_texture_mode = { mode }
};
arrpush(deferred_commands, command);
}
static void finally_use_texture_mode(TextureMode mode) {
if (texture_mode_last_used == mode)
return;
static GLuint lists = 0; static GLuint lists = 0;
if (!lists) { if (!lists) {
lists = glGenLists(3); lists = glGenLists(3);
@ -209,7 +494,7 @@ void use_texture_mode(TextureMode mode) {
glNewList(lists + 0, GL_COMPILE); { glNewList(lists + 0, GL_COMPILE); {
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS); glDepthFunc(GL_LESS);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST); glDisable(GL_ALPHA_TEST);
} glEndList(); } glEndList();
@ -217,7 +502,7 @@ void use_texture_mode(TextureMode mode) {
/* seethrough */ /* seethrough */
glNewList(lists + 1, GL_COMPILE); { glNewList(lists + 1, GL_COMPILE); {
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST); glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f); glAlphaFunc(GL_EQUAL, 1.0f);
@ -239,6 +524,8 @@ void use_texture_mode(TextureMode mode) {
} else { } else {
glCallList(lists + 2); glCallList(lists + 2);
} }
texture_mode_last_used = mode;
} }
@ -258,11 +545,11 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder, bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
void *bytes, size_t size) { void const *bytes, size_t size) {
if (builder->bytes_left == 0) if (builder->bytes_left == 0)
return false; return false;
SDL_memcpy(builder->mapping, bytes, size); memcpy(builder->mapping, bytes, size);
builder->bytes_left -= size; builder->bytes_left -= size;
/* trigger data send */ /* trigger data send */
@ -277,92 +564,107 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
} }
void finally_render_sprites(const Primitive2D primitives[], void finally_render_quads(const Primitive2D primitives[],
const struct SpriteBatch batch, const struct QuadBatch batch,
const VertexBuffer buffer) const VertexBuffer buffer)
{ {
(void)buffer; DeferredCommandDraw command = {0};
GLsizei off; GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
GLsizei voff;
GLsizei uvoff;
if (!batch.constant_colored) { if (!batch.constant_colored && batch.textured) {
off = offsetof(ElementIndexedQuad, v1); off = offsetof(ElementIndexedQuad, v1);
voff = offsetof(ElementIndexedQuad, v0); voff = offsetof(ElementIndexedQuad, v0);
uvoff = offsetof(ElementIndexedQuad, uv0); uvoff = offsetof(ElementIndexedQuad, uv0);
} else { coff = offsetof(ElementIndexedQuad, c0);
} else if (batch.constant_colored && batch.textured) {
off = offsetof(ElementIndexedQuadWithoutColor, v1); off = offsetof(ElementIndexedQuadWithoutColor, v1);
voff = offsetof(ElementIndexedQuadWithoutColor, v0); voff = offsetof(ElementIndexedQuadWithoutColor, v0);
uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0); uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0);
} else if (!batch.constant_colored && !batch.textured) {
off = offsetof(ElementIndexedQuadWithoutTexture, v1);
voff = offsetof(ElementIndexedQuadWithoutTexture, v0);
coff = offsetof(ElementIndexedQuad, c0);
} else if (batch.constant_colored && !batch.textured) {
off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1);
voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0);
} }
/* vertex specification */ command.vertices = (AttributeArrayPointer) {
glEnableClientState(GL_VERTEX_ARRAY); .arity = 2,
glVertexPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = off,
off, .offset = voff,
(void *)(size_t)voff); .buffer = buffer
};
glEnableClientState(GL_TEXTURE_COORD_ARRAY); if (batch.textured)
glClientActiveTexture(GL_TEXTURE0); command.texcoords = (AttributeArrayPointer) {
glTexCoordPointer(2, .arity = 2,
GL_FLOAT, .type = GL_FLOAT,
off, .stride = off,
(void *)(size_t)uvoff); .offset = uvoff,
.buffer = buffer
};
if (!batch.constant_colored) { if (!batch.constant_colored) {
glEnableClientState(GL_COLOR_ARRAY); command.colors = (AttributeArrayPointer) {
glColorPointer(4, .arity = 4,
GL_UNSIGNED_BYTE, .type = GL_UNSIGNED_BYTE,
off, .stride = off,
(void *)offsetof(ElementIndexedQuad, c0)); .offset = coff,
} else .buffer = buffer
glColor4ub(primitives[0].sprite.color.r, };
primitives[0].sprite.color.g, } else {
primitives[0].sprite.color.b, command.constant_colored = true;
primitives[0].sprite.color.a); command.color = primitives[0].sprite.color;
}
if (!batch.repeat) if (batch.textured) {
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key); /* TODO: bad, don't */
else command.textured = true;
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key); command.texture_key = primitives->sprite.texture_key;
command.texture_repeat = batch.repeat;
}
bind_quad_element_buffer(); command.element_buffer = get_quad_element_buffer();
command.element_count = 6 * (GLsizei)batch.size;
command.range_end = 6 * (GLsizei)batch.size;
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, GL_UNSIGNED_SHORT, NULL); use_texture_mode(batch.mode);
/* clear the state */ DeferredCommand final_command = {
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW); .type = DEFERRED_COMMAND_TYPE_DRAW,
glBindBuffer(GL_ARRAY_BUFFER, 0); .draw = command
};
glDisableClientState(GL_TEXTURE_COORD_ARRAY); arrpush(deferred_commands, final_command);
glDisableClientState(GL_VERTEX_ARRAY);
if (!batch.constant_colored)
glDisableClientState(GL_COLOR_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
} }
size_t get_sprite_payload_size(struct SpriteBatch batch) { size_t get_quad_payload_size(struct QuadBatch batch) {
if (batch.constant_colored) if (batch.constant_colored && batch.textured)
return sizeof (ElementIndexedQuadWithoutColor); return sizeof (ElementIndexedQuadWithoutColor);
else else if (!batch.constant_colored && batch.textured)
return sizeof (ElementIndexedQuad); return sizeof (ElementIndexedQuad);
else if (batch.constant_colored && !batch.textured)
return sizeof (ElementIndexedQuadWithoutTexture);
else if (!batch.constant_colored && !batch.textured)
return sizeof (ElementIndexedQuadWithoutColorWithoutTexture);
SDL_assert(false);
return 0;
} }
bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch, bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch,
VertexBufferBuilder *builder, VertexBufferBuilder *builder,
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
Color color) Color color)
{ {
if (!batch.constant_colored) { if (!batch.constant_colored && batch.textured) {
ElementIndexedQuad buffer_element = { ElementIndexedQuad const buffer_element = {
.v0 = v0, .v0 = v0,
.v1 = v1, .v1 = v1,
.v2 = v2, .v2 = v2,
@ -375,15 +677,15 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
/* equal for all (flat shaded) */ /* equal for all (flat shaded) */
.c0 = color, .c0 = color,
.c1 = color, // .c1 = color,
.c2 = color, .c2 = color,
.c3 = color, // .c3 = color,
}; };
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else { } else if (batch.constant_colored && batch.textured) {
ElementIndexedQuadWithoutColor buffer_element = { ElementIndexedQuadWithoutColor const buffer_element = {
.v0 = v0, .v0 = v0,
.v1 = v1, .v1 = v1,
.v2 = v2, .v2 = v2,
@ -395,8 +697,37 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
.uv3 = uv3, .uv3 = uv3,
}; };
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else if (!batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutTexture const buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
/* equal for all (flat shaded) */
.c0 = color,
// .c1 = color,
.c2 = color,
// .c3 = color,
};
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} else if (batch.constant_colored && !batch.textured) {
ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
};
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
} }
SDL_assert(false);
return false;
} }
@ -465,44 +796,46 @@ void finally_draw_text(FontData const *font_data,
Color color, Color color,
VertexBuffer buffer) VertexBuffer buffer)
{ {
(void)buffer; DeferredCommandDraw command = {0};
/* vertex specification */ command.vertices = (AttributeArrayPointer) {
glEnableClientState(GL_VERTEX_ARRAY); .arity = 2,
glVertexPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, v0),
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0)); .buffer = buffer
};
glEnableClientState(GL_TEXTURE_COORD_ARRAY); command.texcoords = (AttributeArrayPointer) {
glClientActiveTexture(GL_TEXTURE0); .arity = 2,
glTexCoordPointer(2, .type = GL_FLOAT,
GL_FLOAT, .stride = offsetof(ElementIndexedQuadWithoutColor, v1),
offsetof(ElementIndexedQuadWithoutColor, v1), .offset = offsetof(ElementIndexedQuadWithoutColor, uv0),
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0)); .buffer = buffer
};
bind_quad_element_buffer(); command.constant_colored = true;
command.color = color;
command.gpu_texture = font_data->texture;
command.uses_gpu_key = true;
command.textured = true;
command.element_buffer = get_quad_element_buffer();
command.element_count = 6 * (GLsizei)len;
command.range_end = 6 * (GLsizei)len;
use_texture_mode(TEXTURE_MODE_GHOSTLY); use_texture_mode(TEXTURE_MODE_GHOSTLY);
glBindTexture(GL_TEXTURE_2D, font_data->texture); DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
glColor4ub(color.r, color.g, color.b, color.a); arrpush(deferred_commands, final_command);
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)len, GL_UNSIGNED_SHORT, NULL);
/* clear the state */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
/* TODO: why doesn't it get restored if not placed here? */ /* TODO: why doesn't it get restored if not placed here? */
glDepthMask(GL_TRUE); // glDepthMask(GL_TRUE);
} }
@ -531,6 +864,47 @@ static void load_cubemap_side(const char *path, GLenum target) {
SDL_FreeSurface(surface); SDL_FreeSurface(surface);
} }
void render_circle(const CirclePrimitive *circle) {
static Vec2 vertices[CIRCLE_VERTICES_MAX];
int num_vertices = CIRCLE_VERTICES_MAX - 1;
create_circle_geometry(circle->position,
circle->radius,
num_vertices,
vertices);
VertexBuffer buffer = get_scratch_vertex_array();
specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);
DeferredCommandDraw command = {0};
command.vertices = (AttributeArrayPointer) {
.arity = 2,
.type = GL_FLOAT,
.stride = sizeof (Vec2),
.offset = 0,
.buffer = buffer
};
command.constant_colored = true;
command.color = circle->color;
command.element_buffer = get_circle_element_buffer();
command.element_count = num_vertices * 3;
command.range_end = num_vertices * 3;
use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY);
DeferredCommand final_command = {
.type = DEFERRED_COMMAND_TYPE_DRAW,
.draw = command
};
arrpush(deferred_commands, final_command);
}
void finally_render_skybox(char *paths) { void finally_render_skybox(char *paths) {
static GLuint cubemap = 0; static GLuint cubemap = 0;
static char *paths_cache = NULL; static char *paths_cache = NULL;
@ -697,3 +1071,16 @@ void finally_apply_fog(float start, float end, float density, Color color) {
void finally_pop_fog(void) { void finally_pop_fog(void) {
glDisable(GL_FOG); glDisable(GL_FOG);
} }
void set_depth_range(double low, double high) {
DeferredCommand const command = {
.type = DEFERRED_COMMAND_TYPE_DEPTH_RANGE,
.depth_range = {
.low = low,
.high = high
}
};
arrpush(deferred_commands, command);
}

View File

@ -1,6 +1,6 @@
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util.h" #include "twn_util_c.h"
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
@ -26,65 +26,66 @@ void delete_vertex_buffer(VertexBuffer buffer) {
} }
void specify_vertex_buffer(VertexBuffer buffer, void *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);
} }
void bind_quad_element_buffer(void) { VertexBuffer get_quad_element_buffer(void) {
static GLuint buffer = 0; static VertexBuffer 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) */
if (buffer == 0) { if (buffer == 0) {
glGenBuffers(1, &buffer); buffer = create_vertex_buffer();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
NULL,
GL_STATIC_DRAW);
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
GL_WRITE_ONLY); GLshort indices[6];
if (!indices) indices[0] = (GLshort)(i * 4 + 0);
CRY("Quad indices generation", "glMapBuffer() failed"); indices[1] = (GLshort)(i * 4 + 1);
indices[2] = (GLshort)(i * 4 + 2);
indices[3] = (GLshort)(i * 4 + 2);
indices[4] = (GLshort)(i * 4 + 3);
indices[5] = (GLshort)(i * 4 + 0);
for (uint16_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
indices[i * 6 + 0] = (uint16_t)(i * 4 + 0);
indices[i * 6 + 1] = (uint16_t)(i * 4 + 1);
indices[i * 6 + 2] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 3] = (uint16_t)(i * 4 + 2);
indices[i * 6 + 4] = (uint16_t)(i * 4 + 3);
indices[i * 6 + 5] = (uint16_t)(i * 4 + 0);
} }
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); SDL_assert_always(buffer);
} else return buffer;
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
} }
void clear_draw_buffer(void) { VertexBuffer get_circle_element_buffer(void) {
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/ static VertexBuffer buffer = 0;
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
/* TODO: don't clear color when skybox is applied? */ if (buffer == 0) {
/* for that window should match framebuffer */ buffer = create_vertex_buffer();
/* also it is driver dependent, from what i can gather */ VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT | for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
GL_STENCIL_BUFFER_BIT); /* first one is center point index, always zero */
} GLshort indices[3];
indices[0] = 0;
void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window); /* generated point index */
} indices[1] = (GLshort)i;
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
void set_depth_range(double low, double high) { if (index == 0) /* don't use center for outer ring */
glDepthRange(low, high); index = (CIRCLE_VERTICES_MAX - 1);
indices[2] = (GLshort)index;
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
}
}
SDL_assert_always(buffer);
return buffer;
} }

30
src/rendering/twn_quads.c Normal file
View File

@ -0,0 +1,30 @@
#include "twn_draw_c.h"
#include <stddef.h>
struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) {
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
return collect_sprite_batch(primitives, len);
else if (primitives[0].type == PRIMITIVE_2D_RECT)
return collect_rect_batch(primitives, len);
else
SDL_assert(false);
return (struct QuadBatch){0};
}
/* assumes that orthogonal matrix setup is done already */
void render_quad_batch(const Primitive2D primitives[],
const struct QuadBatch batch)
{
if (primitives[0].type == PRIMITIVE_2D_SPRITE)
render_sprite_batch(primitives, batch);
else if (primitives[0].type == PRIMITIVE_2D_RECT)
render_rect_batch(primitives, batch);
else
SDL_assert(false);
return (struct QuadBatch){0};
}

102
src/rendering/twn_rects.c Normal file
View File

@ -0,0 +1,102 @@
#include "twn_draw.h"
#include "twn_draw_c.h"
#include "twn_engine_context_c.h"
#include "twn_util.h"
#include "twn_util_c.h"
#include "twn_textures_c.h"
#include "twn_option.h"
#include <stb_ds.h>
#include <stdbool.h>
#include <stddef.h>
void draw_rectangle(Rect rect, Color color) {
RectPrimitive rectangle = {
.rect = rect,
.color = color,
};
Primitive2D primitive = {
.type = PRIMITIVE_2D_RECT,
.rect = rectangle,
};
arrput(ctx.render_queue_2d, primitive);
}
struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len) {
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
SDL_assert(primitives && len != 0);
struct QuadBatch batch = {
.mode = primitives[0].rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY,
.constant_colored = true,
};
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].rect.color;
/* batch size is clamped so that reallocated short indices could be used */
if (len >= QUAD_ELEMENT_BUFFER_LENGTH)
len = QUAD_ELEMENT_BUFFER_LENGTH;
for (size_t i = 0; i < len; ++i) {
const Primitive2D *const current = &primitives[i];
/* don't touch things other than rectangles */
if (current->type != PRIMITIVE_2D_RECT)
break;
/* only collect the same blend modes */
if ((current->rect.color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY) != batch.mode)
break;
/* if all are modulated the same we can skip sending the color data */
if (*(const uint32_t *)&current->rect.color != uniform_color)
batch.constant_colored = false;
++batch.size;
}
return batch;
}
/* assumes that orthogonal matrix setup is done already */
void render_rect_batch(const Primitive2D primitives[],
const struct QuadBatch batch)
{
SDL_assert(primitives && batch.size != 0);
SDL_assert(primitives[0].type == PRIMITIVE_2D_RECT);
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
VertexBuffer const vertex_array = get_scratch_vertex_array();
use_texture_mode(batch.mode);
/* vertex population over a vertex buffer builder interface */
{
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back, to gain benefit of an early z rejection */
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const RectPrimitive rect = primitives[cur].rect;
Vec2 v0 = { rect.rect.x, rect.rect.y };
Vec2 v1 = { rect.rect.x, rect.rect.y + rect.rect.h };
Vec2 v2 = { rect.rect.x + rect.rect.w, rect.rect.y + rect.rect.h };
Vec2 v3 = { rect.rect.x + rect.rect.w, rect.rect.y };
push_quad_payload_to_vertex_buffer_builder(
batch, &payload,
v0, v1, v2, v3,
(Vec2){0}, (Vec2){0}, (Vec2){0}, (Vec2){0},
rect.color);
}
}
finally_render_quads(primitives, batch, vertex_array);
}

View File

@ -3,7 +3,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
char *paths_in_use; static char *paths_in_use;
void draw_skybox(const char *paths) { void draw_skybox(const char *paths) {
if (paths_in_use && SDL_strcmp(paths, paths_in_use) == 0) if (paths_in_use && SDL_strcmp(paths, paths_in_use) == 0)

View File

@ -60,15 +60,18 @@ void draw_sprite_args(const DrawSpriteArgs args) {
} }
struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) { struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
/* assumes that first primitive is already a sprite */ SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
SDL_assert(primitives && len != 0);
const uint16_t texture_key_id = primitives[0].sprite.texture_key.id; const uint16_t texture_key_id = primitives[0].sprite.texture_key.id;
const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key); const int atlas_id = textures_get_atlas_id(&ctx.texture_cache, primitives[0].sprite.texture_key);
struct SpriteBatch batch = { struct QuadBatch batch = {
.mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key), .mode = textures_get_mode(&ctx.texture_cache, primitives[0].sprite.texture_key),
.constant_colored = true, .constant_colored = true,
.repeat = primitives[0].sprite.repeat, .repeat = primitives[0].sprite.repeat,
.textured = true,
}; };
const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color; const uint32_t uniform_color = *(const uint32_t *)&primitives[0].sprite.color;
@ -116,35 +119,40 @@ struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t l
/* assumes that orthogonal matrix setup is done already */ /* assumes that orthogonal matrix setup is done already */
void render_sprites(const Primitive2D primitives[], void render_sprite_batch(const Primitive2D primitives[],
const struct SpriteBatch batch) const struct QuadBatch batch)
{ {
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */ SDL_assert(primitives && batch.size != 0);
static VertexBuffer vertex_array = 0; SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
use_texture_mode(batch.mode); VertexBuffer const vertex_array = get_scratch_vertex_array();
const Rect dims = const Rect dims =
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key); textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
/* cached srcrect */
Rect cached_srcrect;
TextureKey cached_srcrect_key = TEXTURE_KEY_INVALID;
/* vertex population over a vertex buffer builder interface */ /* vertex population over a vertex buffer builder interface */
{ {
VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_sprite_payload_size(batch) * batch.size); VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_quad_payload_size(batch) * batch.size);
for (size_t i = 0; i < batch.size; ++i) { for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back */ /* render opaques front to back, to gain benefit of an early z rejection */
const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1; const size_t cur = batch.mode == TEXTURE_MODE_GHOSTLY ? i : batch.size - i - 1;
const SpritePrimitive sprite = primitives[cur].sprite; const SpritePrimitive sprite = primitives[cur].sprite;
/* TODO: try caching it */ if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) {
const Rect srcrect = cached_srcrect = textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key); cached_srcrect_key = primitives[cur].sprite.texture_key;
}
Rect const srcrect = cached_srcrect;
Vec2 uv0, uv1, uv2, uv3; Vec2 uv0, uv1, uv2, uv3;
if (!sprite.repeat) { if (!batch.repeat) {
const float wr = srcrect.w / dims.w; const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h; const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w; const float xr = srcrect.x / dims.w;
@ -199,9 +207,14 @@ void render_sprites(const Primitive2D primitives[],
v2 = (Vec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y + sprite.rect.h }; v2 = (Vec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y + sprite.rect.h };
v3 = (Vec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y }; v3 = (Vec2){ sprite.rect.x + sprite.rect.w, sprite.rect.y };
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
} else if (sprite.rect.w == sprite.rect.h) { } else if (sprite.rect.w == sprite.rect.h) {
/* rotated square case */ /* rotated square case */
#pragma GCC diagnostic pop
const Vec2 c = frect_center(sprite.rect); const Vec2 c = frect_center(sprite.rect);
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4); const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
const Vec2 d = { const Vec2 d = {
@ -228,9 +241,9 @@ void render_sprites(const Primitive2D primitives[],
v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y }; v3 = (Vec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
} }
push_sprite_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color); push_quad_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
} }
} }
finally_render_sprites(primitives, batch, vertex_array); finally_render_quads(primitives, batch, vertex_array);
} }

View File

@ -1,11 +1,11 @@
#include "twn_draw_c.h" #include "twn_draw_c.h"
#include "twn_draw.h" #include "twn_draw.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_config.h" #include "twn_util_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include <stb_truetype.h> #include <stb_truetype.h>
#include <stb_ds.h>
#define ASCII_START 32 #define ASCII_START 32
#define ASCII_END 128 #define ASCII_END 128
@ -31,6 +31,19 @@ typedef struct StringArena {
static StringArena string_arena; static StringArena string_arena;
typedef struct FontFileBuffer {
size_t len;
unsigned char *buffer;
} FontFileBuffer;
typedef struct FontFileCacheItem {
char *key;
FontFileBuffer value;
} FontFileCacheItem;
static FontFileCacheItem *font_file_cache_hash;
static void string_arena_init(StringArena *arena) { static void string_arena_init(StringArena *arena) {
arena->head = cmalloc(sizeof *arena->head); arena->head = cmalloc(sizeof *arena->head);
arena->current_block = arena->head; arena->current_block = arena->head;
@ -102,13 +115,23 @@ static FontData *text_load_font_data(const char *path, int height_px) {
unsigned char* bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1); unsigned char* bitmap = ccalloc(ctx.font_texture_size * ctx.font_texture_size, 1);
{ {
unsigned char *buf = NULL; unsigned char *buf = NULL;
int64_t buf_len = file_to_bytes(path, &buf); int64_t buf_len = 0;
/* if the file was already loaded just get it */
FontFileCacheItem *font_file_ptr = shgetp_null(font_file_cache_hash, path);
if (font_file_ptr != NULL) {
buf = font_file_ptr->value.buffer;
buf_len = font_file_ptr->value.len;
} else {
buf_len = file_to_bytes(path, &buf);
FontFileBuffer buffer = { buf_len, buf };
shput(font_file_cache_hash, path, buffer);
}
stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0)); stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
/* might as well get these now, for later */ /* might as well get these now, for later */
font_data->file_bytes = buf;
font_data->file_bytes_len = buf_len;
font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px); font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px);
stbtt_GetFontVMetrics( stbtt_GetFontVMetrics(
&font_data->info, &font_data->info,
@ -142,16 +165,13 @@ static FontData *text_load_font_data(const char *path, int height_px) {
static void text_destroy_font_data(FontData *font_data) { static void text_destroy_font_data(FontData *font_data) {
SDL_free(font_data->file_bytes);
delete_gpu_texture(font_data->texture); delete_gpu_texture(font_data->texture);
SDL_free(font_data); SDL_free(font_data);
} }
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) { static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
VertexBuffer vertex_array = 0; VertexBuffer const vertex_array = get_scratch_vertex_array();
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t len = SDL_strlen(text); const size_t len = SDL_strlen(text);
@ -192,7 +212,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color
static void ensure_font_cache(const char *font_path, int height_px) { static void ensure_font_cache(const char *font_path, int height_px) {
/* HACK: stupid, bad, don't do this */ /* HACK: don't */
bool is_cached = false; bool is_cached = false;
for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) { for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
FontData *font_data = ctx.text_cache.data[i]; FontData *font_data = ctx.text_cache.data[i];
@ -229,6 +249,8 @@ void render_text(const TextPrimitive *text) {
void text_cache_init(TextCache *cache) { void text_cache_init(TextCache *cache) {
arrsetlen(cache->data, 0); arrsetlen(cache->data, 0);
string_arena_init(&string_arena); string_arena_init(&string_arena);
sh_new_arena(font_file_cache_hash);
} }
@ -237,8 +259,13 @@ void text_cache_deinit(TextCache *cache) {
text_destroy_font_data(ctx.text_cache.data[i]); text_destroy_font_data(ctx.text_cache.data[i]);
} }
arrfree(cache->data); for (size_t i = 0; i < shlenu(font_file_cache_hash); ++i) {
SDL_free(font_file_cache_hash[i].value.buffer);
}
shfree(font_file_cache_hash);
string_arena_deinit(&string_arena); string_arena_deinit(&string_arena);
arrfree(cache->data);
} }

View File

@ -12,14 +12,16 @@
#define ASCII_END 128 #define ASCII_END 128
#define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1) #define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1)
#define TEXT_FONT_TEXTURE_SIZE_DEFAULT 2048
#define TEXT_FONT_OVERSAMPLING_DEFAULT 4
#define TEXT_FONT_FILTERING_DEFAULT TEXTURE_FILTER_LINEAR
typedef struct FontData { typedef struct FontData {
stbtt_packedchar char_data[NUM_DISPLAY_ASCII]; stbtt_packedchar char_data[NUM_DISPLAY_ASCII];
stbtt_fontinfo info; stbtt_fontinfo info;
const char *file_path; const char *file_path;
unsigned char *file_bytes;
size_t file_bytes_len;
GPUTexture texture; GPUTexture texture;

View File

@ -35,7 +35,7 @@ void draw_triangle(const char *path,
.uv2 = uv2, .uv2 = uv2,
}}; }};
union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)batch_p->value.primitives; union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
arrpush(triangles, triangle); arrpush(triangles, triangle);
batch_p->value.primitives = (uint8_t *)triangles; batch_p->value.primitives = (uint8_t *)triangles;
@ -45,9 +45,7 @@ void draw_triangle(const char *path,
void draw_uncolored_space_traingle_batch(struct MeshBatch *batch, void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
TextureKey texture_key) TextureKey texture_key)
{ {
static VertexBuffer vertex_array = 0; VertexBuffer const vertex_array = get_scratch_vertex_array();
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t primitives_len = arrlenu(batch->primitives); const size_t primitives_len = arrlenu(batch->primitives);
@ -66,7 +64,7 @@ void draw_uncolored_space_traingle_batch(struct MeshBatch *batch,
/* update pixel-based uvs to correspond with texture atlases */ /* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) { for (size_t i = 0; i < primitives_len; ++i) {
struct UncoloredSpaceTrianglePayload *payload = struct UncoloredSpaceTrianglePayload *payload =
&((union UncoloredSpaceTriangle *)batch->primitives)[i].payload; &((union UncoloredSpaceTriangle *)(void *)batch->primitives)[i].payload;
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr; payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr; payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;

View File

@ -7,7 +7,6 @@
#include <sys/auxv.h> #include <sys/auxv.h>
#include <elf.h> #include <elf.h>
#include <linux/limits.h> #include <linux/limits.h>
#define __USE_GNU
#include <dlfcn.h> #include <dlfcn.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>

20
src/twn_amalgam.c Normal file
View File

@ -0,0 +1,20 @@
/* a technique for faster compilation */
/* it includes all non-optional .c files directly in a single compilation unit */
#include "twn_audio.c"
#include "twn_camera.c"
#include "twn_context.c"
#include "twn_input.c"
#include "twn_loop.c"
#include "twn_main.c"
#include "twn_textures.c"
#include "twn_util.c"
#include "rendering/twn_circles.c"
#include "rendering/twn_draw.c"
#include "rendering/twn_fog.c"
#include "rendering/twn_skybox.c"
#include "rendering/twn_sprites.c"
#include "rendering/twn_rects.c"
#include "rendering/twn_text.c"
#include "rendering/twn_triangles.c"

View File

@ -1,7 +1,7 @@
#include "twn_audio_c.h" #include "twn_audio_c.h"
#include "twn_config.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h"
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <stb_ds.h> #include <stb_ds.h>
@ -126,6 +126,8 @@ static union AudioContext init_audio_context(const char *path, AudioFileType typ
}; };
} }
case AUDIO_FILE_TYPE_UNKNOWN:
case AUDIO_FILE_TYPE_COUNT:
default: default:
CRY("Audio error", "Unhandled audio format (in init)"); CRY("Audio error", "Unhandled audio format (in init)");
return (union AudioContext){0}; return (union AudioContext){0};
@ -147,6 +149,8 @@ static void repeat_audio(AudioChannel *channel) {
break; break;
} }
case AUDIO_FILE_TYPE_UNKNOWN:
case AUDIO_FILE_TYPE_COUNT:
default: default:
CRY("Audio error", "Unhandled audio format (in repeat)"); CRY("Audio error", "Unhandled audio format (in repeat)");
break; break;
@ -219,6 +223,8 @@ TWN_API void audio_set(const char *channel, AudioParam param, float value) {
} }
pair->value.panning = value; pair->value.panning = value;
break; break;
default:
CRY("Audio channel parameter setting failed", "Invalid parameter enum given");
} }
} }
@ -229,8 +235,8 @@ static void audio_mixin_streams(const AudioChannel *channel,
uint8_t *restrict b, uint8_t *restrict b,
size_t frames) size_t frames)
{ {
float *const sa = (float *)a; float *const sa = (float *)(void *)a;
float *const sb = (float *)b; float *const sb = (float *)(void *)b;
const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f); const float left_panning = fminf(fabsf(channel->panning - 1.0f), 1.0f);
const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f); const float right_panning = fminf(fabsf(channel->panning + 1.0f), 1.0f);
@ -321,6 +327,8 @@ static void audio_sample_and_mixin_channel(const AudioChannel *channel,
break; break;
} }
case AUDIO_FILE_TYPE_UNKNOWN:
case AUDIO_FILE_TYPE_COUNT:
default: default:
CRY("Audio error", "Unhandled audio format (in sampling)"); CRY("Audio error", "Unhandled audio format (in sampling)");
break; break;

View File

@ -13,6 +13,9 @@
#include <stdint.h> #include <stdint.h>
#define AUDIO_FREQUENCY 48000
typedef enum AudioFileType { typedef enum AudioFileType {
AUDIO_FILE_TYPE_OGG, AUDIO_FILE_TYPE_OGG,
AUDIO_FILE_TYPE_XM, AUDIO_FILE_TYPE_XM,

View File

@ -1,5 +1,4 @@
#include "twn_camera.h" #include "twn_camera.h"
#include "twn_config.h"
#include "twn_vec.h" #include "twn_vec.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"

View File

@ -17,6 +17,8 @@
typedef struct EngineContext { typedef struct EngineContext {
/* user code facing context */ /* user code facing context */
Context game_copy;
/* engine-side context, copied to `game_copy` before every frame */
Context game; Context game;
InputState input; InputState input;
@ -27,6 +29,8 @@ typedef struct EngineContext {
/* where the app was run from, used as the root for packs */ /* where the app was run from, used as the root for packs */
char *base_dir; char *base_dir;
Vec2i window_dims;
/* configuration */ /* configuration */
toml_table_t *config_table; toml_table_t *config_table;
int64_t base_render_width; int64_t base_render_width;
@ -52,6 +56,7 @@ typedef struct EngineContext {
uint8_t audio_stream_channel_count; uint8_t audio_stream_channel_count;
/* main loop machinery */ /* main loop machinery */
int64_t delta_time; /* preserves real time frame delta with no manipilation */
int64_t clocks_per_second; int64_t clocks_per_second;
int64_t prev_frame_time; int64_t prev_frame_time;
int64_t desired_frametime; /* how long one tick should be */ int64_t desired_frametime; /* how long one tick should be */
@ -59,12 +64,20 @@ typedef struct EngineContext {
int64_t delta_averager_residual; int64_t delta_averager_residual;
int64_t time_averager[4]; int64_t time_averager[4];
/* this should be a multiple of the current ticks per second */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
uint32_t update_multiplicity;
SDL_GLContext *gl_context; SDL_GLContext *gl_context;
SDL_Window *window; SDL_Window *window;
uint32_t window_id; uint32_t window_id;
bool is_running;
bool window_size_has_changed;
bool resync_flag; bool resync_flag;
bool was_successful; bool was_successful;
bool render_double_buffered;
} EngineContext; } EngineContext;
/* TODO: does it need to be marked with TWN_API? */ /* TODO: does it need to be marked with TWN_API? */

View File

@ -15,6 +15,12 @@ static void update_action_pressed_state(InputState *input, Action *action) {
switch (action->bindings[i].source) { switch (action->bindings[i].source) {
case BUTTON_SOURCE_NOT_SET: case BUTTON_SOURCE_NOT_SET:
break; break;
case BUTTON_SOURCE_KEYBOARD_CHARACTER:
CRY("Action pressed state updated failed", "BUTTON_SOURCE_KEYBOARD_CHARACTER isn't handled");
break;
case BUTTON_SOURCE_GAMEPAD:
CRY("Action pressed state updated failed", "BUTTON_SOURCE_GAMEPAD isn't handled");
break;
case BUTTON_SOURCE_KEYBOARD_PHYSICAL: case BUTTON_SOURCE_KEYBOARD_PHYSICAL:
/* not pressed */ /* not pressed */
if (input->keyboard_state[action->bindings[i].code.scancode] == 0) { if (input->keyboard_state[action->bindings[i].code.scancode] == 0) {
@ -98,6 +104,8 @@ static void input_bind_code_to_action(InputState *input,
case BUTTON_SOURCE_MOUSE: case BUTTON_SOURCE_MOUSE:
is_already_bound = binding->code.mouse_button == code.mouse_button; is_already_bound = binding->code.mouse_button == code.mouse_button;
break; break;
default:
SDL_assert(false);
} }
if (is_already_bound) { if (is_already_bound) {
@ -156,6 +164,8 @@ static void input_unbind_code_from_action(InputState *input,
case BUTTON_SOURCE_MOUSE: case BUTTON_SOURCE_MOUSE:
is_bound = binding->code.mouse_button == code.mouse_button; is_bound = binding->code.mouse_button == code.mouse_button;
break; break;
default:
SDL_assert(false);
} }
/* stop early, this won't change */ /* stop early, this won't change */
@ -194,8 +204,8 @@ void input_state_update(InputState *input) {
SDL_GetRelativeMouseState(&input->mouse_relative_position.x, SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
&input->mouse_relative_position.y); &input->mouse_relative_position.y);
ctx.game.mouse_window_position = input->mouse_window_position; ctx.game.mouse_position = input->mouse_window_position;
ctx.game.mouse_relative_position = input->mouse_relative_position; ctx.game.mouse_movement = input->mouse_relative_position;
for (size_t i = 0; i < shlenu(input->action_hash); ++i) { for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
Action *action = &input->action_hash[i].value; Action *action = &input->action_hash[i].value;
@ -220,7 +230,7 @@ void input_bind_action_control(char const *action_name,
input_bind_code_to_action(&ctx.input, input_bind_code_to_action(&ctx.input,
action_name, action_name,
BUTTON_SOURCE_MOUSE, BUTTON_SOURCE_MOUSE,
(union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)}); (union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
} else } else
log_warn("(%s) Invalid control value given: %i.", __func__, control); log_warn("(%s) Invalid control value given: %i.", __func__, control);
} }
@ -242,7 +252,7 @@ void input_unbind_action_control(char const *action_name,
input_unbind_code_from_action(&ctx.input, input_unbind_code_from_action(&ctx.input,
action_name, action_name,
BUTTON_SOURCE_MOUSE, BUTTON_SOURCE_MOUSE,
(union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)}); (union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
} else } else
log_warn("(%s) Invalid control value given: %i.", __func__, control); log_warn("(%s) Invalid control value given: %i.", __func__, control);
} }

View File

@ -7,6 +7,9 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#define KEYBIND_SLOTS_DEFAULT 3
union ButtonCode { union ButtonCode {
SDL_Scancode scancode; SDL_Scancode scancode;
SDL_Keycode keycode; SDL_Keycode keycode;

View File

@ -2,6 +2,7 @@
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#include "twn_input_c.h" #include "twn_input_c.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h"
#include "twn_game_object_c.h" #include "twn_game_object_c.h"
#include "twn_audio_c.h" #include "twn_audio_c.h"
#include "twn_textures_c.h" #include "twn_textures_c.h"
@ -22,6 +23,10 @@
#include <limits.h> #include <limits.h>
#define TICKS_PER_SECOND_DEFAULT 60
#define PACKAGE_EXTENSION "btw"
static int event_callback(void *userdata, SDL_Event *event) { static int event_callback(void *userdata, SDL_Event *event) {
(void)userdata; (void)userdata;
@ -32,8 +37,8 @@ static int event_callback(void *userdata, SDL_Event *event) {
switch (event->window.event) { switch (event->window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.game.window_w = event->window.data1; ctx.window_dims.x = event->window.data1;
ctx.game.window_h = event->window.data2; ctx.window_dims.y = event->window.data2;
ctx.resync_flag = true; ctx.resync_flag = true;
break; break;
@ -55,12 +60,12 @@ static int event_callback(void *userdata, SDL_Event *event) {
static void poll_events(void) { static void poll_events(void) {
SDL_Event e; SDL_Event e;
ctx.game.window_size_has_changed = false; ctx.window_size_has_changed = false;
while (SDL_PollEvent(&e)) { while (SDL_PollEvent(&e)) {
switch (e.type) { switch (e.type) {
case SDL_QUIT: case SDL_QUIT:
ctx.game.is_running = false; ctx.is_running = false;
break; break;
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
@ -69,7 +74,7 @@ static void poll_events(void) {
switch (e.window.event) { switch (e.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED: case SDL_WINDOWEVENT_SIZE_CHANGED:
ctx.game.window_size_has_changed = true; ctx.window_size_has_changed = true;
break; break;
default: default:
@ -107,6 +112,11 @@ static void APIENTRY opengl_log(GLenum source,
#endif #endif
static void preserve_persistent_ctx_fields(void) {
ctx.game.udata = ctx.game_copy.udata;
}
static void main_loop(void) { static void main_loop(void) {
/* /*
if (!ctx.is_running) { if (!ctx.is_running) {
@ -120,7 +130,7 @@ static void main_loop(void) {
int64_t current_frame_time = SDL_GetPerformanceCounter(); int64_t current_frame_time = SDL_GetPerformanceCounter();
int64_t delta_time = current_frame_time - ctx.prev_frame_time; int64_t delta_time = current_frame_time - ctx.prev_frame_time;
ctx.prev_frame_time = current_frame_time; ctx.prev_frame_time = current_frame_time;
ctx.game.delta_time = delta_time; ctx.delta_time = delta_time;
/* handle unexpected timer anomalies (overflow, extra slow frames, etc) */ /* handle unexpected timer anomalies (overflow, extra slow frames, etc) */
if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */ if (delta_time > ctx.desired_frametime * 8) { /* ignore extra-slow frames */
@ -184,24 +194,23 @@ static void main_loop(void) {
ctx.resync_flag = false; ctx.resync_flag = false;
} }
ctx.game_copy = ctx.game;
/* finally, let's get to work */ /* finally, let's get to work */
int frames = 0; int frames = 0;
while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.game.update_multiplicity) { while (ctx.frame_accumulator >= ctx.desired_frametime * ctx.update_multiplicity) {
frames += 1; frames += 1;
for (size_t i = 0; i < ctx.game.update_multiplicity; ++i) { for (size_t i = 0; i < ctx.update_multiplicity; ++i) {
/* TODO: disable rendering pushes on not-last ? */ /* TODO: disable rendering pushes on not-last ? */
render_queue_clear(); render_queue_clear();
poll_events(); poll_events();
input_state_update(&ctx.input); input_state_update(&ctx.input);
game_object_tick(); game_object_tick();
preserve_persistent_ctx_fields();
ctx.frame_accumulator -= ctx.desired_frametime; ctx.frame_accumulator -= ctx.desired_frametime;
ctx.game.tick_count = (ctx.game.tick_count % ULLONG_MAX) + 1; ctx.game.frame_number = (ctx.game.frame_number % ULLONG_MAX) + 1;
ctx.game.initialization_needed = false; ctx.game.initialization_needed = false;
} }
} }
@ -435,7 +444,7 @@ static bool initialize(void) {
goto fail; goto fail;
} }
ctx.base_render_width = datum_base_render_width.u.i; ctx.base_render_width = datum_base_render_width.u.i;
ctx.game.base_draw_w = (int)ctx.base_render_width; ctx.game.resolution.x = (int)ctx.base_render_width;
toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height"); toml_datum_t datum_base_render_height = toml_int_in(game, "base_render_height");
if (!datum_base_render_height.ok) { if (!datum_base_render_height.ok) {
@ -443,7 +452,7 @@ static bool initialize(void) {
goto fail; goto fail;
} }
ctx.base_render_height = datum_base_render_height.u.i; ctx.base_render_height = datum_base_render_height.u.i;
ctx.game.base_draw_h = (int)ctx.base_render_height; ctx.game.resolution.y = (int)ctx.base_render_height;
ctx.window = SDL_CreateWindow(datum_title.u.s, ctx.window = SDL_CreateWindow(datum_title.u.s,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
@ -499,8 +508,8 @@ static bool initialize(void) {
/* TODO: */ /* TODO: */
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h); // SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
ctx.game.window_w = (int)ctx.base_render_width; ctx.window_dims.x = (int)ctx.base_render_width;
ctx.game.window_h = (int)ctx.base_render_height; ctx.window_dims.y = (int)ctx.base_render_height;
/* add a watcher for immediate updates on window size */ /* add a watcher for immediate updates on window size */
SDL_AddEventWatch(event_callback, NULL); SDL_AddEventWatch(event_callback, NULL);
@ -528,7 +537,7 @@ static bool initialize(void) {
} }
/* you could change this at runtime if you wanted */ /* you could change this at runtime if you wanted */
ctx.game.update_multiplicity = 1; ctx.update_multiplicity = 1;
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
/* hook up opengl debugging callback */ /* hook up opengl debugging callback */
@ -557,13 +566,13 @@ static bool initialize(void) {
} }
} }
ctx.game.is_running = true; ctx.is_running = true;
ctx.resync_flag = true; ctx.resync_flag = true;
ctx.clocks_per_second = SDL_GetPerformanceFrequency(); ctx.clocks_per_second = SDL_GetPerformanceFrequency();
ctx.prev_frame_time = SDL_GetPerformanceCounter(); ctx.prev_frame_time = SDL_GetPerformanceCounter();
ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second; ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
ctx.frame_accumulator = 0; ctx.frame_accumulator = 0;
ctx.game.tick_count = 0; ctx.game.frame_number = 0;
/* delta time averaging */ /* delta time averaging */
ctx.delta_averager_residual = 0; ctx.delta_averager_residual = 0;
@ -649,6 +658,8 @@ static bool initialize(void) {
} }
*/ */
ctx.render_double_buffered = true;
return true; return true;
fail: fail:
@ -768,7 +779,7 @@ int enter_loop(int argc, char **argv) {
ctx.was_successful = true; ctx.was_successful = true;
ctx.game.initialization_needed = true; ctx.game.initialization_needed = true;
while (ctx.game.is_running) { while (ctx.is_running) {
if (game_object_try_reloading()) { if (game_object_try_reloading()) {
ctx.game.initialization_needed = true; ctx.game.initialization_needed = true;
reset_state(); reset_state();

24
src/twn_stb.c Normal file
View File

@ -0,0 +1,24 @@
/* single compilation unit for every stb implementation */
#define STB_DS_IMPLEMENTATION
#define STBDS_ASSERT SDL_assert
#define STBDS_REALLOC(context,ptr,size) ((void)(context), SDL_realloc(ptr, size))
#define STBDS_FREE(context,ptr) ((void)(context), SDL_free(ptr))
#include <stb_ds.h>
#define STB_RECT_PACK_IMPLEMENTATION
#define STBRP_SORT SDL_qsort
#define STBRP_ASSERT SDL_assert
#include <stb_rect_pack.h>
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_malloc(x,u) ((void)(u), SDL_malloc(x))
#define STBTT_free(x,u) ((void)(u), SDL_free(x))
#define STBTT_assert(x) SDL_assert(x)
#define STBTT_strlen(x) SDL_strlen(x)
#define STBTT_memcpy SDL_memcpy
#define STBTT_memset SDL_memset
#include <stb_truetype.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

View File

@ -1,5 +1,4 @@
#include "twn_textures_c.h" #include "twn_textures_c.h"
#include "twn_config.h"
#include "twn_util.h" #include "twn_util.h"
#include "twn_util_c.h" #include "twn_util_c.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
@ -9,8 +8,6 @@
#include <physfsrwops.h> #include <physfsrwops.h>
#include <stb_ds.h> #include <stb_ds.h>
#include <stb_rect_pack.h> #include <stb_rect_pack.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h> #include <stb_image.h>
#include <stdbool.h> #include <stdbool.h>
@ -397,7 +394,7 @@ void textures_update_atlas(TextureCache *cache) {
add_new_atlas(cache); add_new_atlas(cache);
++cache->atlas_index; ++cache->atlas_index;
} }
}; }
update_texture_rects_in_atlas(cache, rects); update_texture_rects_in_atlas(cache, rects);
recreate_current_atlas_texture(cache); recreate_current_atlas_texture(cache);

View File

@ -11,6 +11,11 @@
#include <stdbool.h> #include <stdbool.h>
#define TEXTURE_ATLAS_SIZE_DEFAULT 2048
#define TEXTURE_ATLAS_BIT_DEPTH 32
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
typedef struct Texture { typedef struct Texture {
Rect srcrect; /* position in atlas */ Rect srcrect; /* position in atlas */
SDL_Surface *data; /* original image data */ SDL_Surface *data; /* original image data */
@ -45,6 +50,7 @@ typedef struct TextureCache {
typedef struct TextureKey { uint16_t id; } TextureKey; typedef struct TextureKey { uint16_t id; } TextureKey;
/* tests whether given key structure corresponds to any texture */ /* tests whether given key structure corresponds to any texture */
#define TEXTURE_KEY_INVALID (TextureKey) { (uint16_t)-1 }
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1) #define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
void textures_cache_init(struct TextureCache *cache, SDL_Window *window); void textures_cache_init(struct TextureCache *cache, SDL_Window *window);

View File

@ -4,23 +4,7 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <physfsrwops.h> #include <physfsrwops.h>
#define STB_DS_IMPLEMENTATION
#define STBDS_ASSERT SDL_assert
#define STBDS_REALLOC(context,ptr,size) ((void)(context), SDL_realloc(ptr, size))
#define STBDS_FREE(context,ptr) ((void)(context), SDL_free(ptr))
#include <stb_ds.h> #include <stb_ds.h>
#define STB_RECT_PACK_IMPLEMENTATION
#define STBRP_SORT SDL_qsort
#define STBRP_ASSERT SDL_assert
#include <stb_rect_pack.h>
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_malloc(x,u) ((void)(u), SDL_malloc(x))
#define STBTT_free(x,u) ((void)(u), SDL_free(x))
#define STBTT_assert(x) SDL_assert(x)
#define STBTT_strlen(x) SDL_strlen(x)
#define STBTT_memcpy SDL_memcpy
#define STBTT_memset SDL_memset
#include <stb_truetype.h>
#include <stdarg.h> #include <stdarg.h>
@ -33,10 +17,16 @@ void cry_impl(const char *file, const int line, const char *title, const char *t
static void log_impl(const char *restrict format, va_list args, SDL_LogPriority priority) { static void log_impl(const char *restrict format, va_list args, SDL_LogPriority priority) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
priority, priority,
format, format,
args); args);
#pragma GCC diagnostic pop
} }
@ -125,7 +115,6 @@ int clampi(int i, int min, int max) {
} }
int64_t file_to_bytes(const char *path, unsigned char **buf_out) { int64_t file_to_bytes(const char *path, unsigned char **buf_out) {
SDL_RWops *handle = PHYSFSRWOPS_openRead(path); SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
@ -272,12 +261,14 @@ void tick_timer(int *value) {
*value = MAX(*value - 1, 0); *value = MAX(*value - 1, 0);
} }
void tick_ftimer(float *value) { void tick_ftimer(float *value) {
*value = MAX(*value - ((float)ctx.game.delta_time / (float)ctx.clocks_per_second), 0.0f); *value = MAX(*value - ((float)ctx.delta_time / (float)ctx.clocks_per_second), 0.0f);
} }
bool repeat_ftimer(float *value, float at) { bool repeat_ftimer(float *value, float at) {
*value -= (float)ctx.game.delta_time / (float)ctx.clocks_per_second; *value -= (float)ctx.delta_time / (float)ctx.clocks_per_second;
if (*value < 0.0f) { if (*value < 0.0f) {
*value += at; *value += at;
return true; return true;

View File

@ -3,6 +3,23 @@
#include "twn_types.h" #include "twn_types.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
#define MAX SDL_max
#define MIN SDL_min
void cry_impl(const char *file, const int line, const char *title, const char *text);
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
/* for when there's absolutely no way to continue */
_Noreturn void die_abruptly(void);
/* note: you must free the returned string */ /* note: you must free the returned string */
char *expand_asterisk(const char *mask, const char *to); char *expand_asterisk(const char *mask, const char *to);

View File

@ -85,16 +85,7 @@ set(PHYSFS_SRCS
src/physfs_platform_android.c src/physfs_platform_android.c
src/physfs_archiver_dir.c src/physfs_archiver_dir.c
src/physfs_archiver_unpacked.c src/physfs_archiver_unpacked.c
src/physfs_archiver_grp.c
src/physfs_archiver_hog.c
src/physfs_archiver_7z.c
src/physfs_archiver_mvl.c
src/physfs_archiver_qpak.c
src/physfs_archiver_wad.c
src/physfs_archiver_zip.c src/physfs_archiver_zip.c
src/physfs_archiver_slb.c
src/physfs_archiver_iso9660.c
src/physfs_archiver_vdf.c
${PHYSFS_CPP_SRCS} ${PHYSFS_CPP_SRCS}
${PHYSFS_M_SRCS} ${PHYSFS_M_SRCS}
) )

View File

@ -476,7 +476,7 @@ extern void stbds_rand_seed(size_t seed);
// these are the hash functions used internally if you want to test them or use them for other purposes // these are the hash functions used internally if you want to test them or use them for other purposes
extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
extern size_t stbds_hash_string(char *str, size_t seed); extern size_t stbds_hash_string(char const*str, size_t seed);
// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. // this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'.
typedef struct stbds_string_arena stbds_string_arena; typedef struct stbds_string_arena stbds_string_arena;
@ -494,11 +494,11 @@ extern void stbds_unit_tests(void);
extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
extern void stbds_arrfreef(void *a); extern void stbds_arrfreef(void *a);
extern void stbds_hmfree_func(void *p, size_t elemsize); extern void stbds_hmfree_func(void *p, size_t elemsize);
extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); extern void * stbds_hmget_key(void *a, size_t elemsize, void const*key, size_t keysize, int mode);
extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void const*key, size_t keysize, ptrdiff_t *temp, int mode);
extern void * stbds_hmput_default(void *a, size_t elemsize); extern void * stbds_hmput_default(void *a, size_t elemsize);
extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); extern void * stbds_hmput_key(void *a, size_t elemsize, void const*key, size_t keysize, int mode);
extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); extern void * stbds_hmdel_key(void *a, size_t elemsize, void const*key, size_t keysize, size_t keyoffset, int mode);
extern void * stbds_shmode_func(size_t elemsize, int mode); extern void * stbds_shmode_func(size_t elemsize, int mode);
#ifdef __cplusplus #ifdef __cplusplus
@ -531,7 +531,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) #define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var))
#define stbds_header(t) ((stbds_array_header *) (t) - 1) #define stbds_header(t) (((stbds_array_header *) (void *) (t)) - 1)
#define stbds_temp(t) stbds_header(t)->temp #define stbds_temp(t) stbds_header(t)->temp
#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) #define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table)
@ -561,7 +561,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) #define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c)))
#define stbds_hmput(t, k, v) \ #define stbds_hmput(t, k, v) \
((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void const*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \
(t)[stbds_temp((t)-1)].key = (k), \ (t)[stbds_temp((t)-1)].key = (k), \
(t)[stbds_temp((t)-1)].value = (v)) (t)[stbds_temp((t)-1)].value = (v))
@ -570,11 +570,11 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
(t)[stbds_temp((t)-1)] = (s)) (t)[stbds_temp((t)-1)] = (s))
#define stbds_hmgeti(t,k) \ #define stbds_hmgeti(t,k) \
((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void const*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \
stbds_temp((t)-1)) stbds_temp((t)-1))
#define stbds_hmgeti_ts(t,k,temp) \ #define stbds_hmgeti_ts(t,k,temp) \
((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void const*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \
(temp)) (temp))
#define stbds_hmgetp(t, k) \ #define stbds_hmgetp(t, k) \
@ -584,7 +584,7 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp])
#define stbds_hmdel(t,k) \ #define stbds_hmdel(t,k) \
(((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void const*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0)
#define stbds_hmdefault(t, v) \ #define stbds_hmdefault(t, v) \
((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v))
@ -603,28 +603,28 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) #define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)])
#define stbds_shput(t, k, v) \ #define stbds_shput(t, k, v) \
((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void const*) (k), sizeof (t)->key, STBDS_HM_STRING), \
(t)[stbds_temp((t)-1)].value = (v)) (t)[stbds_temp((t)-1)].value = (v))
#define stbds_shputi(t, k, v) \ #define stbds_shputi(t, k, v) \
((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void const*) (k), sizeof (t)->key, STBDS_HM_STRING), \
(t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1))
#define stbds_shputs(t, s) \ #define stbds_shputs(t, s) \
((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void const*) (s).key, sizeof (s).key, STBDS_HM_STRING), \
(t)[stbds_temp((t)-1)] = (s), \ (t)[stbds_temp((t)-1)] = (s), \
(t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally
#define stbds_pshput(t, p) \ #define stbds_pshput(t, p) \
((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void const*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \
(t)[stbds_temp((t)-1)] = (p)) (t)[stbds_temp((t)-1)] = (p))
#define stbds_shgeti(t,k) \ #define stbds_shgeti(t,k) \
((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void const*) (k), sizeof (t)->key, STBDS_HM_STRING), \
stbds_temp((t)-1)) stbds_temp((t)-1))
#define stbds_pshgeti(t,k) \ #define stbds_pshgeti(t,k) \
((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void const*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \
stbds_temp((t)-1)) stbds_temp((t)-1))
#define stbds_shgetp(t, k) \ #define stbds_shgetp(t, k) \
@ -634,9 +634,9 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)])
#define stbds_shdel(t,k) \ #define stbds_shdel(t,k) \
(((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void const*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0)
#define stbds_pshdel(t,k) \ #define stbds_pshdel(t,k) \
(((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void const*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0)
#define stbds_sh_new_arena(t) \ #define stbds_sh_new_arena(t) \
((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA))
@ -702,10 +702,10 @@ template<class T> static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, v
template<class T> static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { template<class T> static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) {
return (T*)stbds_hmput_default((void *)a, elemsize); return (T*)stbds_hmput_default((void *)a, elemsize);
} }
template<class T> static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { template<class T> static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void const*key, size_t keysize, int mode) {
return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode);
} }
template<class T> static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ template<class T> static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void const*key, size_t keysize, size_t keyoffset, int mode){
return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode);
} }
template<class T> static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { template<class T> static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) {
@ -1013,7 +1013,7 @@ static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_ind
#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) #define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n))))
#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) #define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n))))
size_t stbds_hash_string(char *str, size_t seed) size_t stbds_hash_string(char const*str, size_t seed)
{ {
size_t hash = seed; size_t hash = seed;
while (*str) while (*str)
@ -1278,7 +1278,7 @@ static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t
/* NOTREACHED */ /* NOTREACHED */
} }
void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) void * stbds_hmget_key_ts(void *a, size_t elemsize, void const*key, size_t keysize, ptrdiff_t *temp, int mode)
{ {
size_t keyoffset = 0; size_t keyoffset = 0;
if (a == NULL) { if (a == NULL) {
@ -1309,7 +1309,7 @@ void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, p
} }
} }
void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) void * stbds_hmget_key(void *a, size_t elemsize, void const*key, size_t keysize, int mode)
{ {
ptrdiff_t temp; ptrdiff_t temp;
void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode);
@ -1334,7 +1334,7 @@ void * stbds_hmput_default(void *a, size_t elemsize)
static char *stbds_strdup(char *str); static char *stbds_strdup(char *str);
void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) void *stbds_hmput_key(void *a, size_t elemsize, void const *key, size_t keysize, int mode)
{ {
size_t keyoffset=0; size_t keyoffset=0;
void *raw_a; void *raw_a;
@ -1370,7 +1370,7 @@ void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int m
// we iterate hash table explicitly because we want to track if we saw a tombstone // we iterate hash table explicitly because we want to track if we saw a tombstone
{ {
size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char const*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed);
size_t step = STBDS_BUCKET_LENGTH; size_t step = STBDS_BUCKET_LENGTH;
size_t pos; size_t pos;
ptrdiff_t tombstone = -1; ptrdiff_t tombstone = -1;
@ -1469,7 +1469,7 @@ void * stbds_shmode_func(size_t elemsize, int mode)
return STBDS_ARR_TO_HASH(a,elemsize); return STBDS_ARR_TO_HASH(a,elemsize);
} }
void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) void * stbds_hmdel_key(void *a, size_t elemsize, void const*key, size_t keysize, size_t keyoffset, int mode)
{ {
if (a == NULL) { if (a == NULL) {
return 0; return 0;