Compare commits
37 Commits
9c01264fd0
...
double
Author | SHA1 | Date | |
---|---|---|---|
7e409fc14a | |||
aa3cab87d2 | |||
1dc0dea762 | |||
7f56ed8421 | |||
119b706638 | |||
f2bbc1863e | |||
768daf1f54 | |||
139394c6de | |||
446402c2e0 | |||
f7a718003e | |||
f087bf1f7f | |||
19bf88d44e | |||
3535a185df | |||
d34516c4ee | |||
b295c5920c | |||
f7f27119e1 | |||
ffab6a3924 | |||
82bad550e5 | |||
19b9812b3e | |||
c8a65f2894 | |||
f0d3f6778c | |||
da98c0941b | |||
d884cd45d9 | |||
d2422735e6 | |||
ed93072371 | |||
9329d3c2be | |||
ef5d609f4a | |||
64433cbe18 | |||
f96d521af2 | |||
1a7322dccf | |||
e70366f82f | |||
7886650339 | |||
3a57833ac1 | |||
667b599c19 | |||
cfc9ac9583 | |||
b566cf20b5 | |||
4ac87b3021 |
@ -9,11 +9,11 @@ if(NOT EMSCRIPTEN)
|
||||
endif()
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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
|
||||
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
|
||||
if(EMSCRIPTEN)
|
||||
@ -41,19 +42,21 @@ if(HAIKU)
|
||||
endif()
|
||||
|
||||
# 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_DISABLE_INSTALL TRUE)
|
||||
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall")
|
||||
set(PHYSFS_ARCHIVE_GRP OFF)
|
||||
set(PHYSFS_ARCHIVE_WAD OFF)
|
||||
set(PHYSFS_ARCHIVE_HOG OFF)
|
||||
set(PHYSFS_ARCHIVE_MVL OFF)
|
||||
set(PHYSFS_ARCHIVE_QPAK OFF)
|
||||
set(PHYSFS_ARCHIVE_SLB OFF)
|
||||
set(PHYSFS_ARCHIVE_ISO9660 OFF)
|
||||
set(PHYSFS_ARCHIVE_VDF OFF)
|
||||
set(PHYSFS_BUILD_SHARED FALSE CACHE INTERNAL "")
|
||||
set(PHYSFS_DISABLE_INSTALL TRUE CACHE INTERNAL "")
|
||||
set(PHYSFS_TARGETNAME_UNINSTALL "physfs_uninstall" CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_GRP OFF CACHE BOOL "")
|
||||
set(PHYSFS_ARCHIVE_WAD OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_HOG OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_MVL OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_QPAK OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_SLB OFF CACHE INTERNAL "")
|
||||
set(PHYSFS_ARCHIVE_ISO9660 OFF CACHE INTERNAL "")
|
||||
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/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM)
|
||||
|
||||
@ -88,7 +91,8 @@ set(TWN_THIRD_PARTY_SOURCE_FILES
|
||||
third-party/tomlc99/toml.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_main.c
|
||||
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_sprites.c
|
||||
src/rendering/twn_rects.c
|
||||
src/rendering/twn_text.c
|
||||
src/rendering/twn_triangles.c
|
||||
src/rendering/twn_circles.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
|
||||
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:src/twn_main.c
|
||||
@ -113,6 +121,7 @@ set(TWN_SOURCE_FILES
|
||||
${SYSTEM_SOURCE_FILES})
|
||||
|
||||
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})
|
||||
|
||||
@ -123,8 +132,6 @@ else()
|
||||
add_library(${TWN_TARGET} STATIC ${TWN_SOURCE_FILES} ${twn_third_parties})
|
||||
endif()
|
||||
|
||||
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES})
|
||||
|
||||
set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_STANDARD_REQUIRED ON
|
||||
@ -133,7 +140,8 @@ set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
# precompile commonly used not-so-small headers
|
||||
target_precompile_headers(${TWN_TARGET} PRIVATE
|
||||
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
|
||||
third-party/stb/stb_ds.h)
|
||||
${SDL2_INCLUDE_DIR}/SDL.h
|
||||
third-party/physfs/src/physfs.h)
|
||||
|
||||
|
||||
function(give_options_without_warnings target)
|
||||
@ -168,20 +176,19 @@ function(give_options_without_warnings target)
|
||||
target_compile_options(${target} PUBLIC
|
||||
${BUILD_FLAGS}
|
||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
||||
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
|
||||
|
||||
target_link_options(${target} PUBLIC
|
||||
${BUILD_FLAGS}
|
||||
# -Wl,--no-undefined # TODO: use later for implementing no-libc
|
||||
$<$<CONFIG:Release>:${BUILD_FLAGS_RELEASE}>
|
||||
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
|
||||
$<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>
|
||||
-Bsymbolic-functions
|
||||
-Wl,--hash-style=gnu)
|
||||
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@ -193,7 +200,8 @@ function(give_options target)
|
||||
-Wno-padded
|
||||
-Wno-declaration-after-statement
|
||||
-Wno-unsafe-buffer-usage
|
||||
-Wno-unused-command-line-argument)
|
||||
-Wno-unused-command-line-argument
|
||||
-Wno-covered-switch-default)
|
||||
|
||||
set(WARNING_FLAGS
|
||||
-Wall
|
||||
@ -257,6 +265,8 @@ function(use_townengine target sources output_directory)
|
||||
# launcher binary, loads game and engine shared library
|
||||
add_executable(${target} ${TWN_ROOT_DIR}/src/twn_main.c)
|
||||
|
||||
target_link_options(${target} PRIVATE $<$<BOOL:${LINUX}>:-Wl,-rpath,$ORIGIN/>)
|
||||
|
||||
# todo: copy instead?
|
||||
# put libtownengine.so alongside the binary
|
||||
set_target_properties(${TWN_TARGET} PROPERTIES
|
||||
|
@ -13,11 +13,11 @@
|
||||
#define RIGHT_CLICK_ADD 500
|
||||
|
||||
|
||||
void handle_input(void)
|
||||
static void handle_input(void)
|
||||
{
|
||||
State *state = ctx.udata;
|
||||
|
||||
if (ctx.mouse_window_position.y <= 60)
|
||||
if (ctx.mouse_position.y <= 60)
|
||||
return;
|
||||
|
||||
if (input_is_action_pressed("add_a_bit"))
|
||||
@ -27,10 +27,12 @@ void handle_input(void)
|
||||
if (state->bunniesCount < MAX_BUNNIES)
|
||||
{
|
||||
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.y = (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.0f;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
@ -43,10 +45,12 @@ void handle_input(void)
|
||||
if (state->bunniesCount < MAX_BUNNIES)
|
||||
{
|
||||
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.y = (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.0f;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
@ -73,32 +77,29 @@ void game_tick(void)
|
||||
|
||||
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++)
|
||||
{
|
||||
state->bunnies[i].position.x += state->bunnies[i].speed.x;
|
||||
state->bunnies[i].position.y += state->bunnies[i].speed.y;
|
||||
|
||||
if (((state->bunnies[i].position.x + BUNNY_W / 2) > ctx.window_w) ||
|
||||
((state->bunnies[i].position.x + BUNNY_W / 2) < 0))
|
||||
if (((state->bunnies[i].position.x + (float)BUNNY_W / 2) > (float)ctx.resolution.x) ||
|
||||
((state->bunnies[i].position.x + (float)BUNNY_W / 2) < 0))
|
||||
state->bunnies[i].speed.x *= -1;
|
||||
if (((state->bunnies[i].position.y + BUNNY_H / 2) > ctx.window_h) ||
|
||||
((state->bunnies[i].position.y + BUNNY_H / 2 - 60) < 0))
|
||||
if (((state->bunnies[i].position.y + (float)BUNNY_H / 2) > (float)ctx.resolution.y) ||
|
||||
((state->bunnies[i].position.y + (float)BUNNY_H / 2 - 60) < 0))
|
||||
state->bunnies[i].speed.y *= -1;
|
||||
}
|
||||
|
||||
handle_input();
|
||||
|
||||
// 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++)
|
||||
{ // Draw each bunny based on their position and color, also scale accordingly
|
||||
m_sprite(m_set(path, "wabbit_alpha.png"),
|
||||
m_set(rect, ((Rect){.x = (int)state->bunnies[i].position.x,
|
||||
.y = (int)state->bunnies[i].position.y,
|
||||
m_set(rect, ((Rect){.x = state->bunnies[i].position.x,
|
||||
.y = state->bunnies[i].position.y,
|
||||
.w = BUNNY_W * SPRITE_SCALE,
|
||||
.h = BUNNY_H * SPRITE_SCALE})),
|
||||
m_opt(color, (state->bunnies[i].color)), m_opt(stretch, true), );
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#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_H 37
|
||||
#define SPRITE_SCALE 1
|
||||
|
3
apps/demos/platformer/data/packs/data.toml
Normal file
3
apps/demos/platformer/data/packs/data.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -19,15 +19,13 @@ static void title_tick(State *state) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
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 */
|
||||
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);
|
||||
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";
|
||||
int text_h = 32;
|
3
apps/demos/scenery/data/packs/data.toml
Normal file
3
apps/demos/scenery/data/packs/data.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[[deps]]
|
||||
source = "../../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -17,8 +17,8 @@ static void ingame_tick(State *state) {
|
||||
|
||||
if (input_is_mouse_captured()) {
|
||||
const float sensitivity = 0.6f; /* TODO: put this in a better place */
|
||||
scn->yaw += (float)ctx.mouse_relative_position.x * sensitivity;
|
||||
scn->pitch -= (float)ctx.mouse_relative_position.y * sensitivity;
|
||||
scn->yaw += (float)ctx.mouse_movement.x * sensitivity;
|
||||
scn->pitch -= (float)ctx.mouse_movement.y * sensitivity;
|
||||
scn->pitch = clampf(scn->pitch, -89.0f, 89.0f);
|
||||
|
||||
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 lx = 64; lx--;) {
|
||||
float x = SDL_truncf(scn->cam.pos.x + 32 - lx);
|
||||
float y = SDL_truncf(scn->cam.pos.z + 32 - ly);
|
||||
float x = SDL_truncf(scn->cam.pos.x + 32 - (float)lx);
|
||||
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 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_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 });
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,13 @@ static void title_tick(State *state) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
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 */
|
||||
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);
|
||||
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";
|
||||
int text_h = 32;
|
||||
@ -39,8 +37,8 @@ static void title_tick(State *state) {
|
||||
},
|
||||
(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);
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
@ -1,3 +0,0 @@
|
||||
[[deps]]
|
||||
source = "../../common-data" # where does it come from, might be an url
|
||||
name = "common-data" # should be globally unique
|
13
bin/build.sh
13
bin/build.sh
@ -1,7 +1,12 @@
|
||||
#!/bin/env sh
|
||||
|
||||
if [ "$1" = "web" ]; then
|
||||
emcmake cmake -B .build-web "${@:2}" && cmake --build .build-web --parallel
|
||||
else
|
||||
cmake -B .build "$@" && cmake --build .build --parallel
|
||||
# check whether ninja is around (you better start running)
|
||||
if [ -x "$(command -v ninja)" ]; then
|
||||
generator="-G Ninja"
|
||||
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
|
||||
|
2
bin/twn
2
bin/twn
@ -16,7 +16,7 @@ case "$1" in
|
||||
;;
|
||||
|
||||
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
|
||||
|
@ -1,6 +1,7 @@
|
||||
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
|
||||
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
|
||||
run `twn build` to build
|
||||
runnable apps should have the same name as app folder it is in
|
||||
|
@ -4,6 +4,7 @@ for that certain steps are taken:
|
||||
* 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,
|
||||
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
|
||||
|
||||
one of main inspirations for that is opengl model
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef TWN_CAMERA_H
|
||||
#define TWN_CAMERA_H
|
||||
|
||||
#include "twn_util.h"
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
|
||||
/* TODO: make it cached? */
|
||||
|
@ -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
|
@ -1,47 +1,46 @@
|
||||
#ifndef TWN_CONTEXT_H
|
||||
#define TWN_CONTEXT_H
|
||||
|
||||
#include "twn_input.h"
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
/* context that is valid for current frame */
|
||||
/* changes to it should not have an effect, unless specified */
|
||||
/* TODO: ensure the statement above */
|
||||
/* context that is only valid for current frame */
|
||||
/* any changes to it will be dropped, unless explicitly stated */
|
||||
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;
|
||||
|
||||
/* TODO: is it what we actually want? */
|
||||
int64_t delta_time; /* preserves real time frame delta with no manipilation */
|
||||
uint64_t tick_count;
|
||||
/* which frame is it, starting from 0 at startup */
|
||||
uint64_t frame_number;
|
||||
|
||||
Vec2i mouse_window_position;
|
||||
Vec2i mouse_relative_position;
|
||||
/* real time spent on one frame (in seconds) */
|
||||
/* 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;
|
||||
|
||||
/* 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 */
|
||||
unsigned int update_multiplicity;
|
||||
|
||||
/* TODO: use Vec2i? */
|
||||
int window_w;
|
||||
int window_h;
|
||||
int base_draw_w;
|
||||
int base_draw_h;
|
||||
|
||||
/* whether debugging logic should be enabled in user code */
|
||||
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;
|
||||
} Context;
|
||||
|
||||
/* when included after twn_engine_context there's an 'ctx' defined already */
|
||||
#ifndef TWN_ENGINE_CONTEXT_C_H
|
||||
TWN_API extern Context ctx;
|
||||
#endif
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef TWN_DRAW_H
|
||||
#define TWN_DRAW_H
|
||||
|
||||
#include "twn_util.h"
|
||||
#include "twn_types.h"
|
||||
#include "twn_option.h"
|
||||
#include "twn_camera.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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* TODO: have font optional, with something minimal coming embedded */
|
||||
|
@ -4,7 +4,7 @@
|
||||
#if defined(__WIN32)
|
||||
#define TWN_API __declspec(dllexport)
|
||||
#else
|
||||
#define TWN_API
|
||||
#define TWN_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -1,15 +1,15 @@
|
||||
/* include this header in game code to get the usable parts of the engine */
|
||||
#ifndef GAME_API_H
|
||||
#define GAME_API_H
|
||||
|
||||
#ifndef TWN_GAME_API_H
|
||||
#define TWN_GAME_API_H
|
||||
|
||||
#include "twn_input.h"
|
||||
#include "twn_context.h"
|
||||
#include "twn_draw.h"
|
||||
#include "twn_audio.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_input.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_util.h"
|
||||
|
||||
#ifndef TWN_NOT_C
|
||||
|
||||
/* sole game logic and display function.
|
||||
all state must be used from and saved to supplied state pointer. */
|
||||
@ -18,5 +18,6 @@ TWN_API extern void game_tick(void);
|
||||
/* called when application is closing. */
|
||||
TWN_API extern void game_end(void);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
@ -1,7 +1,6 @@
|
||||
#ifndef TWN_INPUT_H
|
||||
#define TWN_INPUT_H
|
||||
|
||||
#include "twn_config.h"
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_control.h"
|
||||
|
@ -1,6 +1,9 @@
|
||||
#ifndef 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 */
|
||||
typedef enum TextureMode {
|
||||
TEXTURE_MODE_OPAQUE, /* all pixels are solid */
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
/* a point in some space (integer) */
|
||||
typedef struct Vec2i {
|
||||
_Alignas(8)
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
} Vec2i;
|
||||
@ -16,7 +15,6 @@ _Alignas(8)
|
||||
|
||||
/* a point in some space (floating point) */
|
||||
typedef struct Vec2 {
|
||||
_Alignas(8)
|
||||
float x;
|
||||
float y;
|
||||
} Vec2;
|
||||
@ -25,7 +23,6 @@ _Alignas(8)
|
||||
/* a point in some three dimension space (floating point) */
|
||||
/* y goes up, x goes to the right */
|
||||
typedef struct Vec3 {
|
||||
_Alignas(16)
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
@ -35,7 +32,6 @@ _Alignas(16)
|
||||
/* a point in some three dimension space (floating point) */
|
||||
/* y goes up, x goes to the right */
|
||||
typedef struct Vec4 {
|
||||
_Alignas(16)
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
@ -45,7 +41,6 @@ _Alignas(16)
|
||||
|
||||
/* 32-bit color data */
|
||||
typedef struct Color {
|
||||
_Alignas(4)
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
@ -55,7 +50,6 @@ _Alignas(4)
|
||||
|
||||
/* a rectangle with the origin at the upper left (integer) */
|
||||
typedef struct Recti {
|
||||
_Alignas(16)
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t w;
|
||||
@ -65,7 +59,6 @@ _Alignas(16)
|
||||
|
||||
/* a rectangle with the origin at the upper left (floating point) */
|
||||
typedef struct Rect {
|
||||
_Alignas(16)
|
||||
float x;
|
||||
float y;
|
||||
float w;
|
||||
|
@ -1,23 +1,15 @@
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
#ifndef TWN_UTIL_H
|
||||
#define TWN_UTIL_H
|
||||
|
||||
#include "twn_types.h"
|
||||
#include "twn_engine_api.h"
|
||||
#include "twn_types.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.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
|
||||
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
|
||||
#endif
|
||||
@ -26,16 +18,13 @@
|
||||
#define DEG2RAD (M_PI / 180)
|
||||
#define RAD2DEG (180 / M_PI)
|
||||
|
||||
#ifndef TWN_NOT_C
|
||||
|
||||
/* */
|
||||
/* GENERAL UTILITIES */
|
||||
/* */
|
||||
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);
|
||||
|
||||
TWN_API 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()))
|
||||
#endif /* TWN_NOT_C */
|
||||
|
||||
|
||||
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, ...);
|
||||
|
||||
|
||||
/* 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 */
|
||||
TWN_API double clamp(double d, double min, double max);
|
||||
TWN_API float clampf(float f, float min, float max);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "twn_game_object_c.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
#include <x-watcher.h>
|
||||
#include <SDL2/SDL.h>
|
||||
@ -57,7 +58,7 @@ static void load_game_object(void) {
|
||||
|
||||
handle = new_handle;
|
||||
|
||||
if (ctx.game.tick_count != 0)
|
||||
if (ctx.game.frame_number != 0)
|
||||
log_info("Game object was reloaded\n");
|
||||
|
||||
return;
|
||||
@ -82,13 +83,20 @@ static void watcher_callback(XWATCHER_FILE_EVENT event,
|
||||
(void)data;
|
||||
|
||||
switch(event) {
|
||||
case XWATCHER_FILE_CREATED:
|
||||
case XWATCHER_FILE_MODIFIED:
|
||||
SDL_LockMutex(lock);
|
||||
last_tick_modified = ctx.game.tick_count;
|
||||
last_tick_modified = ctx.game.frame_number;
|
||||
loaded_after_modification = false;
|
||||
SDL_UnlockMutex(lock);
|
||||
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:
|
||||
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 */
|
||||
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) {
|
||||
load_game_object();
|
||||
loaded_after_modification = true;
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "twn_game_object_c.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
#include <errhandlingapi.h>
|
||||
#include <libloaderapi.h>
|
||||
@ -48,7 +49,7 @@ static void load_game_object(void) {
|
||||
|
||||
handle = new_handle;
|
||||
|
||||
if (ctx.game.tick_count != 0)
|
||||
if (ctx.game.frame_number != 0)
|
||||
log_info("Game object was reloaded\n");
|
||||
|
||||
return;
|
||||
|
@ -1,4 +1,3 @@
|
||||
#include "twn_util.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_draw.h"
|
||||
@ -22,28 +21,17 @@ void draw_circle(Vec2 position, float radius, Color color) {
|
||||
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,
|
||||
Color color,
|
||||
float radius,
|
||||
size_t num_vertices,
|
||||
SDL_Vertex **vertices_out,
|
||||
int **indices_out)
|
||||
Vec2 vertices[])
|
||||
{
|
||||
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 */
|
||||
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
|
||||
|
||||
vertices[0].position.x = (float)position.x;
|
||||
vertices[0].position.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 };
|
||||
vertices[0].x = (float)position.x;
|
||||
vertices[0].y = (float)position.y;
|
||||
|
||||
/* this point will rotate around the center */
|
||||
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) {
|
||||
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
|
||||
|
||||
vertices[i].position.x =
|
||||
cosf(final_seg_rotation_angle) * start_x -
|
||||
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;
|
||||
float c, s;
|
||||
sincosf(final_seg_rotation_angle, &s, &c);
|
||||
|
||||
vertices[i].position.x += position.x;
|
||||
vertices[i].position.y += position.y;
|
||||
vertices[i].x = c * start_x - s * start_y;
|
||||
vertices[i].y = c * start_y + s * start_x;
|
||||
|
||||
vertices[i].color.r = color.r;
|
||||
vertices[i].color.g = color.g;
|
||||
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[i].x += position.x;
|
||||
vertices[i].y += position.y;
|
||||
}
|
||||
|
||||
*vertices_out = vertices;
|
||||
*indices_out = indices;
|
||||
}
|
||||
|
@ -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) {
|
||||
const float bt = (float)border_thickness; /* i know! */
|
||||
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);
|
||||
|
||||
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) {
|
||||
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) {
|
||||
case PRIMITIVE_2D_SPRITE: {
|
||||
const struct SpriteBatch batch =
|
||||
const struct QuadBatch batch =
|
||||
collect_sprite_batch(current, render_queue_len - i);
|
||||
|
||||
/* TODO: what's even the point? just use OR_EQUAL comparison */
|
||||
set_depth_range((double)batch_count / UINT16_MAX, 1.0);
|
||||
render_sprites(current, batch);
|
||||
struct Render2DInvocation const invocation = {
|
||||
.primitive = current,
|
||||
.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;
|
||||
}
|
||||
case PRIMITIVE_2D_RECT:
|
||||
render_rectangle(¤t->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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
case PRIMITIVE_2D_TEXT: {
|
||||
struct Render2DInvocation const invocation = {
|
||||
.primitive = current,
|
||||
.layer = layer,
|
||||
};
|
||||
arrput(ghostly_invocations, invocation);
|
||||
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(¤t->circle);
|
||||
render_circle(&invocation.primitive->circle);
|
||||
break;
|
||||
case PRIMITIVE_2D_TEXT:
|
||||
render_text(¤t->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);
|
||||
|
||||
/* fit rendering context onto the resizable screen */
|
||||
if (ctx.game.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) {
|
||||
float ratio = (float)ctx.game.window_h / (float)ctx.base_render_height;
|
||||
if (ctx.window_size_has_changed) {
|
||||
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.window_dims.y / (float)ctx.base_render_height;
|
||||
int w = (int)((float)ctx.base_render_width * ratio);
|
||||
setup_viewport(
|
||||
ctx.game.window_w / 2 - w / 2,
|
||||
ctx.window_dims.x / 2 - w / 2,
|
||||
0,
|
||||
w,
|
||||
ctx.game.window_h
|
||||
ctx.window_dims.y
|
||||
);
|
||||
} 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);
|
||||
setup_viewport(
|
||||
0,
|
||||
ctx.game.window_h / 2 - h / 2,
|
||||
ctx.game.window_w,
|
||||
ctx.window_dims.y / 2 - h / 2,
|
||||
ctx.window_dims.x,
|
||||
h
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
start_render_frame(); {
|
||||
render_space();
|
||||
render_skybox(); /* after space, as to use depth buffer for early rejection */
|
||||
render_skybox(); /* after space, as to use depth buffer for early z rejection */
|
||||
render_2d();
|
||||
swap_buffers();
|
||||
clear_draw_buffer();
|
||||
} end_render_frame();
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,7 +20,10 @@
|
||||
extern Matrix4 camera_projection_matrix;
|
||||
extern Matrix4 camera_look_at_matrix;
|
||||
|
||||
|
||||
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
|
||||
#define CIRCLE_VERTICES_MAX 2048
|
||||
#define CIRCLE_ELEMENT_BUFFER_LENGTH (CIRCLE_VERTICES_MAX * 3)
|
||||
|
||||
|
||||
typedef GLuint VertexBuffer;
|
||||
@ -126,22 +129,25 @@ void render(void);
|
||||
/* clears all render queues */
|
||||
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,
|
||||
Color color,
|
||||
float radius,
|
||||
size_t num_vertices,
|
||||
SDL_Vertex **vertices_out,
|
||||
int **indices_out);
|
||||
Vec2 vertices[]);
|
||||
|
||||
struct SpriteBatch {
|
||||
struct QuadBatch {
|
||||
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 repeat; /* whether repeat is needed */
|
||||
} collect_sprite_batch(const Primitive2D primitives[], size_t len);
|
||||
|
||||
void render_sprites(const Primitive2D primitives[],
|
||||
const struct SpriteBatch batch);
|
||||
bool textured;
|
||||
} collect_quad_batch(const Primitive2D primitives[], size_t len);
|
||||
void render_quad_batch(const Primitive2D primitives[], struct QuadBatch 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,
|
||||
TextureKey texture_key);
|
||||
@ -160,16 +166,18 @@ void text_cache_reset_arena(TextCache *cache);
|
||||
|
||||
VertexBuffer create_vertex_buffer(void);
|
||||
|
||||
VertexBuffer get_scratch_vertex_array(void);
|
||||
|
||||
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 */
|
||||
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 */
|
||||
bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
||||
void *bytes,
|
||||
void const *bytes,
|
||||
size_t size);
|
||||
|
||||
/* state */
|
||||
@ -182,7 +190,9 @@ void swap_buffers(void);
|
||||
|
||||
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);
|
||||
|
||||
@ -194,13 +204,13 @@ void use_2d_pipeline(void);
|
||||
|
||||
void use_texture_mode(TextureMode mode);
|
||||
|
||||
void finally_render_sprites(Primitive2D const primitives[],
|
||||
struct SpriteBatch batch,
|
||||
void finally_render_quads(Primitive2D const primitives[],
|
||||
struct QuadBatch batch,
|
||||
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,
|
||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||
@ -233,4 +243,8 @@ void pop_fog(void);
|
||||
|
||||
void finally_pop_fog(void);
|
||||
|
||||
void start_render_frame(void);
|
||||
|
||||
void end_render_frame(void);
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "twn_gpu_texture_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
|
||||
GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps) {
|
||||
|
@ -1,17 +1,17 @@
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
#include "twn_config.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_text_c.h"
|
||||
#include "twn_types.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 */
|
||||
/* 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 {
|
||||
/* upper-left */
|
||||
Vec2 v0;
|
||||
@ -31,7 +31,6 @@ typedef struct ElementIndexedQuad {
|
||||
Color c3;
|
||||
} ElementIndexedQuad;
|
||||
|
||||
|
||||
typedef struct ElementIndexedQuadWithoutColor {
|
||||
/* upper-left */
|
||||
Vec2 v0;
|
||||
@ -48,6 +47,75 @@ typedef struct 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 {
|
||||
PIPELINE_NO,
|
||||
PIPELINE_SPACE,
|
||||
@ -55,10 +123,266 @@ typedef enum {
|
||||
} 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;
|
||||
|
||||
|
||||
/* 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) {
|
||||
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)
|
||||
return;
|
||||
|
||||
@ -91,11 +415,22 @@ void use_space_pipeline(void) {
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
|
||||
|
||||
texture_mode_last_used = -1;
|
||||
pipeline_last_used = PIPELINE_SPACE;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
glFlush();
|
||||
@ -127,80 +462,30 @@ void use_2d_pipeline(void) {
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
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);
|
||||
glLoadIdentity();
|
||||
|
||||
texture_mode_last_used = -1;
|
||||
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) {
|
||||
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;
|
||||
if (!lists) {
|
||||
lists = glGenLists(3);
|
||||
@ -209,7 +494,7 @@ void use_texture_mode(TextureMode mode) {
|
||||
glNewList(lists + 0, GL_COMPILE); {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDepthFunc(GL_ALWAYS);
|
||||
glDepthFunc(GL_LESS);
|
||||
glDepthMask(GL_FALSE);
|
||||
glDisable(GL_ALPHA_TEST);
|
||||
} glEndList();
|
||||
@ -217,7 +502,7 @@ void use_texture_mode(TextureMode mode) {
|
||||
/* seethrough */
|
||||
glNewList(lists + 1, GL_COMPILE); {
|
||||
glDisable(GL_BLEND);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glDepthFunc(GL_LESS);
|
||||
glDepthMask(GL_TRUE);
|
||||
glEnable(GL_ALPHA_TEST);
|
||||
glAlphaFunc(GL_EQUAL, 1.0f);
|
||||
@ -239,6 +524,8 @@ void use_texture_mode(TextureMode mode) {
|
||||
} else {
|
||||
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,
|
||||
void *bytes, size_t size) {
|
||||
void const *bytes, size_t size) {
|
||||
if (builder->bytes_left == 0)
|
||||
return false;
|
||||
|
||||
SDL_memcpy(builder->mapping, bytes, size);
|
||||
memcpy(builder->mapping, bytes, size);
|
||||
builder->bytes_left -= size;
|
||||
|
||||
/* trigger data send */
|
||||
@ -277,92 +564,107 @@ bool push_to_vertex_buffer_builder(VertexBufferBuilder *builder,
|
||||
}
|
||||
|
||||
|
||||
void finally_render_sprites(const Primitive2D primitives[],
|
||||
const struct SpriteBatch batch,
|
||||
void finally_render_quads(const Primitive2D primitives[],
|
||||
const struct QuadBatch batch,
|
||||
const VertexBuffer buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
DeferredCommandDraw command = {0};
|
||||
|
||||
GLsizei off;
|
||||
GLsizei voff;
|
||||
GLsizei uvoff;
|
||||
GLsizei off = 0, voff = 0, uvoff = 0, coff = 0;
|
||||
|
||||
if (!batch.constant_colored) {
|
||||
if (!batch.constant_colored && batch.textured) {
|
||||
off = offsetof(ElementIndexedQuad, v1);
|
||||
voff = offsetof(ElementIndexedQuad, v0);
|
||||
uvoff = offsetof(ElementIndexedQuad, uv0);
|
||||
} else {
|
||||
coff = offsetof(ElementIndexedQuad, c0);
|
||||
} else if (batch.constant_colored && batch.textured) {
|
||||
off = offsetof(ElementIndexedQuadWithoutColor, v1);
|
||||
voff = offsetof(ElementIndexedQuadWithoutColor, v0);
|
||||
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 */
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2,
|
||||
GL_FLOAT,
|
||||
off,
|
||||
(void *)(size_t)voff);
|
||||
command.vertices = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = off,
|
||||
.offset = voff,
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glClientActiveTexture(GL_TEXTURE0);
|
||||
glTexCoordPointer(2,
|
||||
GL_FLOAT,
|
||||
off,
|
||||
(void *)(size_t)uvoff);
|
||||
if (batch.textured)
|
||||
command.texcoords = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = off,
|
||||
.offset = uvoff,
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
if (!batch.constant_colored) {
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glColorPointer(4,
|
||||
GL_UNSIGNED_BYTE,
|
||||
off,
|
||||
(void *)offsetof(ElementIndexedQuad, c0));
|
||||
} else
|
||||
glColor4ub(primitives[0].sprite.color.r,
|
||||
primitives[0].sprite.color.g,
|
||||
primitives[0].sprite.color.b,
|
||||
primitives[0].sprite.color.a);
|
||||
command.colors = (AttributeArrayPointer) {
|
||||
.arity = 4,
|
||||
.type = GL_UNSIGNED_BYTE,
|
||||
.stride = off,
|
||||
.offset = coff,
|
||||
.buffer = buffer
|
||||
};
|
||||
} else {
|
||||
command.constant_colored = true;
|
||||
command.color = primitives[0].sprite.color;
|
||||
}
|
||||
|
||||
if (!batch.repeat)
|
||||
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
else
|
||||
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key);
|
||||
if (batch.textured) {
|
||||
/* TODO: bad, don't */
|
||||
command.textured = true;
|
||||
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 */
|
||||
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
DeferredCommand final_command = {
|
||||
.type = DEFERRED_COMMAND_TYPE_DRAW,
|
||||
.draw = command
|
||||
};
|
||||
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
if (!batch.constant_colored)
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
arrpush(deferred_commands, final_command);
|
||||
}
|
||||
|
||||
|
||||
size_t get_sprite_payload_size(struct SpriteBatch batch) {
|
||||
if (batch.constant_colored)
|
||||
size_t get_quad_payload_size(struct QuadBatch batch) {
|
||||
if (batch.constant_colored && batch.textured)
|
||||
return sizeof (ElementIndexedQuadWithoutColor);
|
||||
else
|
||||
else if (!batch.constant_colored && batch.textured)
|
||||
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,
|
||||
Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3,
|
||||
Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3,
|
||||
Color color)
|
||||
{
|
||||
if (!batch.constant_colored) {
|
||||
ElementIndexedQuad buffer_element = {
|
||||
if (!batch.constant_colored && batch.textured) {
|
||||
ElementIndexedQuad const buffer_element = {
|
||||
.v0 = v0,
|
||||
.v1 = v1,
|
||||
.v2 = v2,
|
||||
@ -375,15 +677,15 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
||||
|
||||
/* equal for all (flat shaded) */
|
||||
.c0 = color,
|
||||
.c1 = color,
|
||||
// .c1 = color,
|
||||
.c2 = color,
|
||||
.c3 = color,
|
||||
// .c3 = color,
|
||||
};
|
||||
|
||||
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
|
||||
|
||||
} else {
|
||||
ElementIndexedQuadWithoutColor buffer_element = {
|
||||
} else if (batch.constant_colored && batch.textured) {
|
||||
ElementIndexedQuadWithoutColor const buffer_element = {
|
||||
.v0 = v0,
|
||||
.v1 = v1,
|
||||
.v2 = v2,
|
||||
@ -395,8 +697,37 @@ bool push_sprite_payload_to_vertex_buffer_builder(struct SpriteBatch batch,
|
||||
.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);
|
||||
}
|
||||
|
||||
SDL_assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -465,44 +796,46 @@ void finally_draw_text(FontData const *font_data,
|
||||
Color color,
|
||||
VertexBuffer buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
DeferredCommandDraw command = {0};
|
||||
|
||||
/* vertex specification */
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer(2,
|
||||
GL_FLOAT,
|
||||
offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, v0));
|
||||
command.vertices = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||
.offset = offsetof(ElementIndexedQuadWithoutColor, v0),
|
||||
.buffer = buffer
|
||||
};
|
||||
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glClientActiveTexture(GL_TEXTURE0);
|
||||
glTexCoordPointer(2,
|
||||
GL_FLOAT,
|
||||
offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||
(void *)(size_t)offsetof(ElementIndexedQuadWithoutColor, uv0));
|
||||
command.texcoords = (AttributeArrayPointer) {
|
||||
.arity = 2,
|
||||
.type = GL_FLOAT,
|
||||
.stride = offsetof(ElementIndexedQuadWithoutColor, v1),
|
||||
.offset = 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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
arrpush(deferred_commands, final_command);
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
static GLuint cubemap = 0;
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
#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);
|
||||
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
|
||||
}
|
||||
|
||||
|
||||
void bind_quad_element_buffer(void) {
|
||||
static GLuint buffer = 0;
|
||||
VertexBuffer get_quad_element_buffer(void) {
|
||||
static VertexBuffer buffer = 0;
|
||||
|
||||
/* it's only generated once at runtime */
|
||||
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
|
||||
if (buffer == 0) {
|
||||
glGenBuffers(1, &buffer);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
QUAD_ELEMENT_BUFFER_LENGTH * 6 * sizeof(uint16_t),
|
||||
NULL,
|
||||
GL_STATIC_DRAW);
|
||||
buffer = create_vertex_buffer();
|
||||
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
|
||||
|
||||
uint16_t *const indices = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
GL_WRITE_ONLY);
|
||||
if (!indices)
|
||||
CRY("Quad indices generation", "glMapBuffer() failed");
|
||||
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
|
||||
GLshort indices[6];
|
||||
indices[0] = (GLshort)(i * 4 + 0);
|
||||
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) {
|
||||
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);
|
||||
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
||||
}
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
|
||||
SDL_assert_always(buffer);
|
||||
|
||||
} else
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
void clear_draw_buffer(void) {
|
||||
/* TODO: we can optimize a rectangle drawn over whole window to a clear color call*/
|
||||
glClearColor((1.0f / 255) * 230,
|
||||
(1.0f / 255) * 230,
|
||||
(1.0f / 255) * 230, 1);
|
||||
VertexBuffer get_circle_element_buffer(void) {
|
||||
static VertexBuffer buffer = 0;
|
||||
|
||||
/* TODO: don't clear color when skybox is applied? */
|
||||
/* for that window should match framebuffer */
|
||||
/* also it is driver dependent, from what i can gather */
|
||||
glClear(GL_COLOR_BUFFER_BIT |
|
||||
GL_DEPTH_BUFFER_BIT |
|
||||
GL_STENCIL_BUFFER_BIT);
|
||||
if (buffer == 0) {
|
||||
buffer = create_vertex_buffer();
|
||||
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * CIRCLE_ELEMENT_BUFFER_LENGTH);
|
||||
|
||||
for (size_t i = 1; i < CIRCLE_VERTICES_MAX; ++i) {
|
||||
/* first one is center point index, always zero */
|
||||
GLshort indices[3];
|
||||
|
||||
indices[0] = 0;
|
||||
|
||||
/* generated point index */
|
||||
indices[1] = (GLshort)i;
|
||||
|
||||
size_t index = (i + 1) % (CIRCLE_VERTICES_MAX - 1);
|
||||
if (index == 0) /* don't use center for outer ring */
|
||||
index = (CIRCLE_VERTICES_MAX - 1);
|
||||
indices[2] = (GLshort)index;
|
||||
|
||||
push_to_vertex_buffer_builder(&builder, indices, sizeof indices);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_assert_always(buffer);
|
||||
|
||||
void swap_buffers(void) {
|
||||
SDL_GL_SwapWindow(ctx.window);
|
||||
}
|
||||
|
||||
|
||||
void set_depth_range(double low, double high) {
|
||||
glDepthRange(low, high);
|
||||
return buffer;
|
||||
}
|
||||
|
30
src/rendering/twn_quads.c
Normal file
30
src/rendering/twn_quads.c
Normal 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
102
src/rendering/twn_rects.c
Normal 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 *)¤t->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);
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
char *paths_in_use;
|
||||
static char *paths_in_use;
|
||||
|
||||
void draw_skybox(const char *paths) {
|
||||
if (paths_in_use && SDL_strcmp(paths, paths_in_use) == 0)
|
||||
|
@ -60,15 +60,18 @@ void draw_sprite_args(const DrawSpriteArgs args) {
|
||||
}
|
||||
|
||||
|
||||
struct SpriteBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
|
||||
/* assumes that first primitive is already a sprite */
|
||||
struct QuadBatch collect_sprite_batch(const Primitive2D primitives[], size_t len) {
|
||||
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 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),
|
||||
.constant_colored = true,
|
||||
.repeat = primitives[0].sprite.repeat,
|
||||
.textured = true,
|
||||
};
|
||||
|
||||
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 */
|
||||
void render_sprites(const Primitive2D primitives[],
|
||||
const struct SpriteBatch batch)
|
||||
void render_sprite_batch(const Primitive2D primitives[],
|
||||
const struct QuadBatch batch)
|
||||
{
|
||||
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
|
||||
static VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
SDL_assert(primitives && batch.size != 0);
|
||||
SDL_assert(primitives[0].type == PRIMITIVE_2D_SPRITE);
|
||||
|
||||
use_texture_mode(batch.mode);
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
const Rect dims =
|
||||
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 */
|
||||
{
|
||||
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) {
|
||||
/* 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 SpritePrimitive sprite = primitives[cur].sprite;
|
||||
|
||||
/* TODO: try caching it */
|
||||
const Rect srcrect =
|
||||
textures_get_srcrect(&ctx.texture_cache, primitives[cur].sprite.texture_key);
|
||||
if (primitives[cur].sprite.texture_key.id != cached_srcrect_key.id) {
|
||||
cached_srcrect = 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;
|
||||
|
||||
if (!sprite.repeat) {
|
||||
if (!batch.repeat) {
|
||||
const float wr = srcrect.w / dims.w;
|
||||
const float hr = srcrect.h / dims.h;
|
||||
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 };
|
||||
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) {
|
||||
/* rotated square case */
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
const Vec2 c = frect_center(sprite.rect);
|
||||
const Vec2 t = fast_cossine(sprite.rotation + (float)M_PI_4);
|
||||
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 };
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
#include "twn_draw_c.h"
|
||||
#include "twn_draw.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_config.h"
|
||||
#include "twn_util_c.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
|
||||
#include <stb_truetype.h>
|
||||
|
||||
#include <stb_ds.h>
|
||||
|
||||
#define ASCII_START 32
|
||||
#define ASCII_END 128
|
||||
@ -31,6 +31,19 @@ typedef struct StringArena {
|
||||
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) {
|
||||
arena->head = cmalloc(sizeof *arena->head);
|
||||
arena->current_block = arena->head;
|
||||
@ -103,12 +116,22 @@ static FontData *text_load_font_data(const char *path, int height_px) {
|
||||
|
||||
{
|
||||
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));
|
||||
|
||||
/* 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);
|
||||
stbtt_GetFontVMetrics(
|
||||
&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) {
|
||||
SDL_free(font_data->file_bytes);
|
||||
delete_gpu_texture(font_data->texture);
|
||||
SDL_free(font_data);
|
||||
}
|
||||
|
||||
|
||||
static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color color) {
|
||||
VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
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) {
|
||||
/* HACK: stupid, bad, don't do this */
|
||||
/* HACK: don't */
|
||||
bool is_cached = false;
|
||||
for (size_t i = 0; i < arrlenu(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) {
|
||||
arrsetlen(cache->data, 0);
|
||||
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]);
|
||||
}
|
||||
|
||||
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);
|
||||
arrfree(cache->data);
|
||||
}
|
||||
|
||||
|
||||
|
@ -12,14 +12,16 @@
|
||||
#define ASCII_END 128
|
||||
#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 {
|
||||
stbtt_packedchar char_data[NUM_DISPLAY_ASCII];
|
||||
stbtt_fontinfo info;
|
||||
|
||||
const char *file_path;
|
||||
unsigned char *file_bytes;
|
||||
size_t file_bytes_len;
|
||||
|
||||
GPUTexture texture;
|
||||
|
||||
|
@ -35,7 +35,7 @@ void draw_triangle(const char *path,
|
||||
.uv2 = uv2,
|
||||
}};
|
||||
|
||||
union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)batch_p->value.primitives;
|
||||
union UncoloredSpaceTriangle *triangles = (union UncoloredSpaceTriangle *)(void *)batch_p->value.primitives;
|
||||
|
||||
arrpush(triangles, triangle);
|
||||
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,
|
||||
TextureKey texture_key)
|
||||
{
|
||||
static VertexBuffer vertex_array = 0;
|
||||
if (vertex_array == 0)
|
||||
vertex_array = create_vertex_buffer();
|
||||
VertexBuffer const vertex_array = get_scratch_vertex_array();
|
||||
|
||||
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 */
|
||||
for (size_t i = 0; i < primitives_len; ++i) {
|
||||
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.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
|
||||
|
@ -7,7 +7,6 @@
|
||||
#include <sys/auxv.h>
|
||||
#include <elf.h>
|
||||
#include <linux/limits.h>
|
||||
#define __USE_GNU
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
20
src/twn_amalgam.c
Normal file
20
src/twn_amalgam.c
Normal 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"
|
@ -1,7 +1,7 @@
|
||||
#include "twn_audio_c.h"
|
||||
#include "twn_config.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
|
||||
#include <SDL2/SDL.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:
|
||||
CRY("Audio error", "Unhandled audio format (in init)");
|
||||
return (union AudioContext){0};
|
||||
@ -147,6 +149,8 @@ static void repeat_audio(AudioChannel *channel) {
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIO_FILE_TYPE_UNKNOWN:
|
||||
case AUDIO_FILE_TYPE_COUNT:
|
||||
default:
|
||||
CRY("Audio error", "Unhandled audio format (in repeat)");
|
||||
break;
|
||||
@ -219,6 +223,8 @@ TWN_API void audio_set(const char *channel, AudioParam param, float value) {
|
||||
}
|
||||
pair->value.panning = value;
|
||||
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,
|
||||
size_t frames)
|
||||
{
|
||||
float *const sa = (float *)a;
|
||||
float *const sb = (float *)b;
|
||||
float *const sa = (float *)(void *)a;
|
||||
float *const sb = (float *)(void *)b;
|
||||
|
||||
const float left_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;
|
||||
}
|
||||
|
||||
case AUDIO_FILE_TYPE_UNKNOWN:
|
||||
case AUDIO_FILE_TYPE_COUNT:
|
||||
default:
|
||||
CRY("Audio error", "Unhandled audio format (in sampling)");
|
||||
break;
|
||||
|
@ -13,6 +13,9 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#define AUDIO_FREQUENCY 48000
|
||||
|
||||
|
||||
typedef enum AudioFileType {
|
||||
AUDIO_FILE_TYPE_OGG,
|
||||
AUDIO_FILE_TYPE_XM,
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "twn_camera.h"
|
||||
#include "twn_config.h"
|
||||
#include "twn_vec.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
typedef struct EngineContext {
|
||||
/* user code facing context */
|
||||
Context game_copy;
|
||||
/* engine-side context, copied to `game_copy` before every frame */
|
||||
Context game;
|
||||
|
||||
InputState input;
|
||||
@ -27,6 +29,8 @@ typedef struct EngineContext {
|
||||
/* where the app was run from, used as the root for packs */
|
||||
char *base_dir;
|
||||
|
||||
Vec2i window_dims;
|
||||
|
||||
/* configuration */
|
||||
toml_table_t *config_table;
|
||||
int64_t base_render_width;
|
||||
@ -52,6 +56,7 @@ typedef struct EngineContext {
|
||||
uint8_t audio_stream_channel_count;
|
||||
|
||||
/* main loop machinery */
|
||||
int64_t delta_time; /* preserves real time frame delta with no manipilation */
|
||||
int64_t clocks_per_second;
|
||||
int64_t prev_frame_time;
|
||||
int64_t desired_frametime; /* how long one tick should be */
|
||||
@ -59,12 +64,20 @@ typedef struct EngineContext {
|
||||
int64_t delta_averager_residual;
|
||||
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_Window *window;
|
||||
uint32_t window_id;
|
||||
|
||||
bool is_running;
|
||||
bool window_size_has_changed;
|
||||
bool resync_flag;
|
||||
bool was_successful;
|
||||
bool render_double_buffered;
|
||||
} EngineContext;
|
||||
|
||||
/* TODO: does it need to be marked with TWN_API? */
|
||||
|
@ -15,6 +15,12 @@ static void update_action_pressed_state(InputState *input, Action *action) {
|
||||
switch (action->bindings[i].source) {
|
||||
case BUTTON_SOURCE_NOT_SET:
|
||||
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:
|
||||
/* not pressed */
|
||||
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:
|
||||
is_already_bound = binding->code.mouse_button == code.mouse_button;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
}
|
||||
|
||||
if (is_already_bound) {
|
||||
@ -156,6 +164,8 @@ static void input_unbind_code_from_action(InputState *input,
|
||||
case BUTTON_SOURCE_MOUSE:
|
||||
is_bound = binding->code.mouse_button == code.mouse_button;
|
||||
break;
|
||||
default:
|
||||
SDL_assert(false);
|
||||
}
|
||||
|
||||
/* stop early, this won't change */
|
||||
@ -194,8 +204,8 @@ void input_state_update(InputState *input) {
|
||||
SDL_GetRelativeMouseState(&input->mouse_relative_position.x,
|
||||
&input->mouse_relative_position.y);
|
||||
|
||||
ctx.game.mouse_window_position = input->mouse_window_position;
|
||||
ctx.game.mouse_relative_position = input->mouse_relative_position;
|
||||
ctx.game.mouse_position = input->mouse_window_position;
|
||||
ctx.game.mouse_movement = input->mouse_relative_position;
|
||||
|
||||
for (size_t i = 0; i < shlenu(input->action_hash); ++i) {
|
||||
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,
|
||||
action_name,
|
||||
BUTTON_SOURCE_MOUSE,
|
||||
(union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)});
|
||||
(union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
|
||||
} else
|
||||
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,
|
||||
action_name,
|
||||
BUTTON_SOURCE_MOUSE,
|
||||
(union ButtonCode) { .mouse_button = SDL_BUTTON(mouse_button)});
|
||||
(union ButtonCode) { .mouse_button = (uint8_t)SDL_BUTTON(mouse_button)});
|
||||
} else
|
||||
log_warn("(%s) Invalid control value given: %i.", __func__, control);
|
||||
}
|
||||
|
@ -7,6 +7,9 @@
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
#define KEYBIND_SLOTS_DEFAULT 3
|
||||
|
||||
|
||||
union ButtonCode {
|
||||
SDL_Scancode scancode;
|
||||
SDL_Keycode keycode;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "twn_engine_context_c.h"
|
||||
#include "twn_input_c.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
#include "twn_game_object_c.h"
|
||||
#include "twn_audio_c.h"
|
||||
#include "twn_textures_c.h"
|
||||
@ -22,6 +23,10 @@
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
#define TICKS_PER_SECOND_DEFAULT 60
|
||||
#define PACKAGE_EXTENSION "btw"
|
||||
|
||||
|
||||
static int event_callback(void *userdata, SDL_Event *event) {
|
||||
(void)userdata;
|
||||
|
||||
@ -32,8 +37,8 @@ static int event_callback(void *userdata, SDL_Event *event) {
|
||||
|
||||
switch (event->window.event) {
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
ctx.game.window_w = event->window.data1;
|
||||
ctx.game.window_h = event->window.data2;
|
||||
ctx.window_dims.x = event->window.data1;
|
||||
ctx.window_dims.y = event->window.data2;
|
||||
ctx.resync_flag = true;
|
||||
break;
|
||||
|
||||
@ -55,12 +60,12 @@ static int event_callback(void *userdata, SDL_Event *event) {
|
||||
static void poll_events(void) {
|
||||
SDL_Event e;
|
||||
|
||||
ctx.game.window_size_has_changed = false;
|
||||
ctx.window_size_has_changed = false;
|
||||
|
||||
while (SDL_PollEvent(&e)) {
|
||||
switch (e.type) {
|
||||
case SDL_QUIT:
|
||||
ctx.game.is_running = false;
|
||||
ctx.is_running = false;
|
||||
break;
|
||||
|
||||
case SDL_WINDOWEVENT:
|
||||
@ -69,7 +74,7 @@ static void poll_events(void) {
|
||||
|
||||
switch (e.window.event) {
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
ctx.game.window_size_has_changed = true;
|
||||
ctx.window_size_has_changed = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -107,6 +112,11 @@ static void APIENTRY opengl_log(GLenum source,
|
||||
#endif
|
||||
|
||||
|
||||
static void preserve_persistent_ctx_fields(void) {
|
||||
ctx.game.udata = ctx.game_copy.udata;
|
||||
}
|
||||
|
||||
|
||||
static void main_loop(void) {
|
||||
/*
|
||||
if (!ctx.is_running) {
|
||||
@ -120,7 +130,7 @@ static void main_loop(void) {
|
||||
int64_t current_frame_time = SDL_GetPerformanceCounter();
|
||||
int64_t delta_time = current_frame_time - ctx.prev_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) */
|
||||
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.game_copy = ctx.game;
|
||||
|
||||
/* finally, let's get to work */
|
||||
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;
|
||||
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 ? */
|
||||
render_queue_clear();
|
||||
|
||||
poll_events();
|
||||
|
||||
input_state_update(&ctx.input);
|
||||
|
||||
game_object_tick();
|
||||
preserve_persistent_ctx_fields();
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -435,7 +444,7 @@ static bool initialize(void) {
|
||||
goto fail;
|
||||
}
|
||||
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");
|
||||
if (!datum_base_render_height.ok) {
|
||||
@ -443,7 +452,7 @@ static bool initialize(void) {
|
||||
goto fail;
|
||||
}
|
||||
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,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
@ -499,8 +508,8 @@ static bool initialize(void) {
|
||||
|
||||
/* TODO: */
|
||||
// SDL_GetRendererOutputSize(ctx.renderer, &ctx.window_w, &ctx.window_h);
|
||||
ctx.game.window_w = (int)ctx.base_render_width;
|
||||
ctx.game.window_h = (int)ctx.base_render_height;
|
||||
ctx.window_dims.x = (int)ctx.base_render_width;
|
||||
ctx.window_dims.y = (int)ctx.base_render_height;
|
||||
|
||||
/* add a watcher for immediate updates on window size */
|
||||
SDL_AddEventWatch(event_callback, NULL);
|
||||
@ -528,7 +537,7 @@ static bool initialize(void) {
|
||||
}
|
||||
|
||||
/* you could change this at runtime if you wanted */
|
||||
ctx.game.update_multiplicity = 1;
|
||||
ctx.update_multiplicity = 1;
|
||||
|
||||
#ifndef EMSCRIPTEN
|
||||
/* 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.clocks_per_second = SDL_GetPerformanceFrequency();
|
||||
ctx.prev_frame_time = SDL_GetPerformanceCounter();
|
||||
ctx.desired_frametime = ctx.clocks_per_second / ctx.ticks_per_second;
|
||||
ctx.frame_accumulator = 0;
|
||||
ctx.game.tick_count = 0;
|
||||
ctx.game.frame_number = 0;
|
||||
|
||||
/* delta time averaging */
|
||||
ctx.delta_averager_residual = 0;
|
||||
@ -649,6 +658,8 @@ static bool initialize(void) {
|
||||
}
|
||||
*/
|
||||
|
||||
ctx.render_double_buffered = true;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
@ -768,7 +779,7 @@ int enter_loop(int argc, char **argv) {
|
||||
ctx.was_successful = true;
|
||||
ctx.game.initialization_needed = true;
|
||||
|
||||
while (ctx.game.is_running) {
|
||||
while (ctx.is_running) {
|
||||
if (game_object_try_reloading()) {
|
||||
ctx.game.initialization_needed = true;
|
||||
reset_state();
|
||||
|
24
src/twn_stb.c
Normal file
24
src/twn_stb.c
Normal 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>
|
@ -1,5 +1,4 @@
|
||||
#include "twn_textures_c.h"
|
||||
#include "twn_config.h"
|
||||
#include "twn_util.h"
|
||||
#include "twn_util_c.h"
|
||||
#include "twn_engine_context_c.h"
|
||||
@ -9,8 +8,6 @@
|
||||
#include <physfsrwops.h>
|
||||
#include <stb_ds.h>
|
||||
#include <stb_rect_pack.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
@ -397,7 +394,7 @@ void textures_update_atlas(TextureCache *cache) {
|
||||
add_new_atlas(cache);
|
||||
++cache->atlas_index;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
update_texture_rects_in_atlas(cache, rects);
|
||||
recreate_current_atlas_texture(cache);
|
||||
|
@ -11,6 +11,11 @@
|
||||
#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 {
|
||||
Rect srcrect; /* position in atlas */
|
||||
SDL_Surface *data; /* original image data */
|
||||
@ -45,6 +50,7 @@ typedef struct TextureCache {
|
||||
typedef struct TextureKey { uint16_t id; } TextureKey;
|
||||
|
||||
/* 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)
|
||||
|
||||
void textures_cache_init(struct TextureCache *cache, SDL_Window *window);
|
||||
|
@ -4,23 +4,7 @@
|
||||
|
||||
#include <SDL2/SDL.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>
|
||||
#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>
|
||||
|
||||
@ -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) {
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
|
||||
SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION,
|
||||
priority,
|
||||
format,
|
||||
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) {
|
||||
SDL_RWops *handle = PHYSFSRWOPS_openRead(path);
|
||||
|
||||
@ -272,12 +261,14 @@ void tick_timer(int *value) {
|
||||
*value = MAX(*value - 1, 0);
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
*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) {
|
||||
*value += at;
|
||||
return true;
|
||||
|
@ -3,6 +3,23 @@
|
||||
|
||||
#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 */
|
||||
char *expand_asterisk(const char *mask, const char *to);
|
||||
|
||||
|
9
third-party/physfs/CMakeLists.txt
vendored
9
third-party/physfs/CMakeLists.txt
vendored
@ -85,16 +85,7 @@ set(PHYSFS_SRCS
|
||||
src/physfs_platform_android.c
|
||||
src/physfs_archiver_dir.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_slb.c
|
||||
src/physfs_archiver_iso9660.c
|
||||
src/physfs_archiver_vdf.c
|
||||
${PHYSFS_CPP_SRCS}
|
||||
${PHYSFS_M_SRCS}
|
||||
)
|
||||
|
52
third-party/stb/stb_ds.h
vendored
52
third-party/stb/stb_ds.h
vendored
@ -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
|
||||
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}'.
|
||||
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_arrfreef(void *a);
|
||||
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_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, 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 const*key, size_t keysize, ptrdiff_t *temp, int mode);
|
||||
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_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, 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 const*key, size_t keysize, size_t keyoffset, int mode);
|
||||
extern void * stbds_shmode_func(size_t elemsize, int mode);
|
||||
|
||||
#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_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_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_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)].value = (v))
|
||||
|
||||
@ -570,11 +570,11 @@ extern void * stbds_shmode_func(size_t elemsize, int mode);
|
||||
(t)[stbds_temp((t)-1)] = (s))
|
||||
|
||||
#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))
|
||||
|
||||
#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))
|
||||
|
||||
#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])
|
||||
|
||||
#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) \
|
||||
((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_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))
|
||||
|
||||
#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))
|
||||
|
||||
#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)].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) \
|
||||
((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))
|
||||
|
||||
#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))
|
||||
|
||||
#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))
|
||||
|
||||
#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)])
|
||||
|
||||
#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) \
|
||||
(((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) \
|
||||
((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) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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_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;
|
||||
while (*str)
|
||||
@ -1278,7 +1278,7 @@ static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t
|
||||
/* 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;
|
||||
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;
|
||||
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);
|
||||
|
||||
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;
|
||||
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
|
||||
{
|
||||
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 pos;
|
||||
ptrdiff_t tombstone = -1;
|
||||
@ -1469,7 +1469,7 @@ void * stbds_shmode_func(size_t elemsize, int mode)
|
||||
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) {
|
||||
return 0;
|
||||
|
Reference in New Issue
Block a user