partially done work on total source tree rework, separation of engine context and game context, generalization of renderer for different backends as well as web platform target

This commit is contained in:
veclav talica 2024-09-16 09:07:01 +03:00
parent ca0305feab
commit 551d60ef85
59 changed files with 2892 additions and 890 deletions

5
.gitignore vendored
View File

@ -5,6 +5,10 @@
!Makefile
**/*.exe
**/*.html
**/*.js
**/*.wasm
**/*.wasm.map
**/*.so
**/*.dll
**/*.7z
@ -16,6 +20,7 @@
.idea/
.cache/
.build/
.build-web/
build/
out/

View File

@ -3,7 +3,10 @@ cmake_minimum_required(VERSION 3.21)
project(townengine LANGUAGES C)
# SDL dependencies
find_package(SDL2 REQUIRED GLOBAL)
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
if(NOT EMSCRIPTEN)
find_package(SDL2 REQUIRED GLOBAL)
endif()
# CMake actually has no default configuration and it's toolchain file dependent, set debug if not specified
if(NOT CMAKE_BUILD_TYPE)
@ -19,6 +22,14 @@ set(TWN_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE INTERNAL "")
option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
option(TWN_ARCHIVE_DATA "Enable archival of assets" OFF)
# todo: figure out how to compile for dynamic linking instead
if(EMSCRIPTEN)
if(TWN_FEATURE_DYNLIB_GAME)
message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off")
set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "")
endif()
endif()
# add -fPIC globally so that it's linked well
add_compile_options($<$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>:-fPIC>)
@ -37,8 +48,8 @@ add_subdirectory(third-party/physfs SYSTEM)
add_subdirectory(third-party/libxm SYSTEM)
if(UNIX)
set(SYSTEM_SOURCE_FILES townengine/system/linux/elf.c)
if(LINUX)
set(SYSTEM_SOURCE_FILES src/system/linux/elf.c)
else()
set(SYSTEM_SOURCE_FILES)
endif()
@ -46,22 +57,22 @@ endif()
set(TWN_THIRD_PARTY_SOURCE_FILES
third-party/physfs/extras/physfsrwops.c
third-party/stb/stb_vorbis.c
third-party/glad/src/glad.c)
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/src/glad.c>)
set(TWN_SOURCE_FILES
townengine/twn_loop.c
townengine/twn_main.c
townengine/config.h
townengine/context/context.c townengine/context.h
townengine/audio/audio.c townengine/audio.h
townengine/util.c townengine/util.h
townengine/rendering.c townengine/rendering.h
townengine/input/input.c townengine/input.h
townengine/camera.c townengine/camera.h
townengine/textures/textures.c
townengine/twn_game_object.c
src/twn_loop.c
src/twn_main.c
src/twn_context.c include/twn_context.h
src/twn_audio.c include/twn_audio.h
src/twn_util.c include/twn_util.h
src/twn_rendering.c include/twn_rendering.h
src/twn_input.c include/twn_input.h
src/twn_camera.c include/twn_camera.h
src/twn_textures.c include/twn_textures.c
src/twn_game_object.c
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:townengine/twn_main.c>
# for dynamic load based solution main is compiled in a separate target
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:src/twn_main.c>
${SYSTEM_SOURCE_FILES})
@ -79,13 +90,13 @@ endif()
source_group(TREE ${TWN_ROOT_DIR} FILES ${TWN_SOURCE_FILES})
set_target_properties(${TWN_TARGET} PROPERTIES
C_STANDARD 11
C_STANDARD_REQUIRED ON
C_EXTENSIONS ON) # extensions are required by stb_ds.h
C_STANDARD 11
C_STANDARD_REQUIRED ON
C_EXTENSIONS ON) # extensions are required by stb_ds.h
# precompile commonly used not-so-small headers
target_precompile_headers(${TWN_TARGET} PRIVATE
third-party/glad/include/glad/glad.h
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
third-party/stb/stb_ds.h)
# distribution definitions, set them with -DX=X in cli
@ -104,7 +115,8 @@ function(give_options_without_warnings target)
-ffp-contract=fast
-fno-signed-zeros
-fno-trapping-math
-freciprocal-math)
-freciprocal-math
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
set(BUILD_FLAGS_RELEASE
-O3
@ -114,8 +126,7 @@ function(give_options_without_warnings target)
-fdata-sections
-ffunction-sections
-funroll-loops
-fomit-frame-pointer
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-fallow-store-data-races>)
-fomit-frame-pointer)
set(BUILD_FLAGS_DEBUG
-O0
@ -124,7 +135,8 @@ function(give_options_without_warnings target)
-fno-omit-frame-pointer
-fstack-protector-all
-fsanitize=undefined
-fsanitize=address)
-fsanitize=address
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
target_compile_options(${target} PRIVATE
${BUILD_FLAGS}
@ -179,9 +191,9 @@ function(include_deps target)
third-party/physfs/src
third-party/physfs/extras
third-party/libxm/include
third-party/glad/include
third-party/stb
third-party/x-watcher)
third-party/x-watcher
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include>)
list(TRANSFORM THIRD_PARTY_INCLUDES PREPEND ${TWN_ROOT_DIR}/)
target_include_directories(${target} SYSTEM PRIVATE ${THIRD_PARTY_INCLUDES})
@ -193,7 +205,7 @@ endfunction()
function(link_deps target)
target_link_libraries(${target} PUBLIC
SDL2::SDL2
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
physfs-static
xms)
endfunction()
@ -205,14 +217,14 @@ function(use_townengine target sources output_directory data_dir)
add_library(${target}_game SHARED ${sources})
give_options(${target}_game)
include_deps(${target}_game)
target_link_libraries(${target}_game PUBLIC SDL2::SDL2 ${TWN_TARGET})
target_link_libraries(${target}_game PUBLIC $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2> ${TWN_TARGET})
set_target_properties(${target}_game PROPERTIES
OUTPUT_NAME game
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
RUNTIME_OUTPUT_DIRECTORY ${output_directory})
# launcher binary, loads game and engine shared library
add_executable(${target}_app ${TWN_ROOT_DIR}/townengine/twn_main.c)
add_executable(${target}_app ${TWN_ROOT_DIR}/src/twn_main.c)
# todo: copy instead?
# put libtownengine.so alongside the binary
@ -225,7 +237,16 @@ function(use_townengine target sources output_directory data_dir)
set_target_properties(${target}_app PROPERTIES
OUTPUT_NAME launcher
LIBRARY_OUTPUT_DIRECTORY ${output_directory})
LIBRARY_OUTPUT_DIRECTORY ${output_directory}
RUNTIME_OUTPUT_DIRECTORY ${output_directory})
if(EMSCRIPTEN)
set_target_properties(${target}_app PROPERTIES
SUFFIX .html)
endif()
target_compile_options(${target}_app PRIVATE
$<$<BOOL:${EMSCRIPTEN}>:--shell-file ${TWN_ROOT_DIR}/shell_minimal.html>)
# system libraries
find_library(MATH_LIBRARY m)
@ -236,10 +257,8 @@ function(use_townengine target sources output_directory data_dir)
give_options(${target}_app)
include_deps(${target}_app)
link_deps(${target}_app)
target_link_libraries(${target}_app PUBLIC ${TWN_TARGET})
set_target_properties(${target}_app PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${output_directory})
target_link_libraries(${target}_app PUBLIC ${TWN_TARGET})
if(WIN32)
# copy dlls for baby windows
add_custom_command(TARGET ${target}_app POST_BUILD
@ -297,5 +316,5 @@ endif()
# move compie_commands.json into root directory so that it plays nicer with code editors without any configuration
add_custom_target(copy-compile-commands ALL
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
${TWN_ROOT_DIR})
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_BINARY_DIR}/compile_commands.json
${TWN_ROOT_DIR})

View File

@ -1,3 +1,7 @@
#!/bin/env sh
cmake -B .build "$@" && cmake --build .build
if [ $1 = "web" ]; then
emcmake cmake -B .build-web "${@:2}" && cmake --build .build-web
else
cmake -B .build "$@" && cmake --build .build
fi

View File

@ -0,0 +1,20 @@
all source files are prefixed with 'twn_'
things are categorized into two main folders:
- 'include' for any definition that are usable from townengine library
- 'src' for any private implementations
internal headers have '_c.h' ending, so to not collide with our include style
example of a module:
src/twn_module.c -- internal implementations
src/twn_module_c.h -- private declarations
include/twn_module.h -- public declarations
both private and public headers are included via root:
#include "twn_module.h"
#include "twn_module_c.h"
additional subfolders in src/ are possible when implementation permutations are in place, build system will chose the appropriate ones
all procedures in include/ headers must have TWN_API macro applied

View File

@ -1,5 +1,5 @@
#ifndef AUDIO_H
#define AUDIO_H
#ifndef TWN_AUDIO_H
#define TWN_AUDIO_H
#include "twn_engine_api.h"

View File

@ -1,9 +1,10 @@
#ifndef CAMERA_H
#define CAMERA_H
#ifndef TWN_CAMERA_H
#define TWN_CAMERA_H
#include "util.h"
#include "twn_util.h"
#include "twn_engine_api.h"
/* TODO: make it cached */
/* TODO: make it cached? */
/* for example, perspective matrix only needs recaluclation on FOV change */
/* first person camera class */
@ -14,8 +15,8 @@ typedef struct camera {
float fov; /* field of view, in radians */
} t_camera;
t_matrix4 camera_look_at(const t_camera *camera);
TWN_API t_matrix4 camera_look_at(const t_camera *camera);
t_matrix4 camera_perspective(const t_camera *const camera);
TWN_API t_matrix4 camera_perspective(const t_camera *const camera);
#endif

View File

@ -1,9 +1,5 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <glad/glad.h>
#ifndef TWN_CONFIG_H
#define TWN_CONFIG_H
/*
* this file is for configuration values which are to be set at
@ -34,10 +30,6 @@
#define TEXT_FONT_TEXTURE_SIZE 1024
#define TEXT_FONT_OVERSAMPLING 4
#define TEXT_FONT_FILTERING GL_LINEAR
/* 1024 * 1024 */
/* #define UMKA_STACK_SIZE 1048576 */
#define TEXT_FONT_FILTERING TEXTURE_FILTER_LINEAR
#endif

38
include/twn_context.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef TWN_CONTEXT_H
#define TWN_CONTEXT_H
#include "twn_input.h"
#include "twn_engine_api.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct context {
struct input_state input;
int64_t delta_time; /* preserves real time frame delta with no manipilation */
uint64_t tick_count;
/* set just once on startup */
uint64_t random_seed;
/* this should be a multiple of 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;
int window_w;
int window_h;
/* you may read from and write to these from game code */
void *udata;
bool debug;
bool is_running;
bool window_size_has_changed;
bool initialization_needed;
} t_ctx;
TWN_API extern t_ctx ctx;
#endif

View File

@ -3,11 +3,11 @@
#define GAME_API_H
#include "townengine/context.h"
#include "townengine/rendering.h"
#include "townengine/audio.h"
#include "townengine/util.h"
#include "townengine/input.h"
#include "twn_context.h"
#include "twn_rendering.h"
#include "twn_audio.h"
#include "twn_util.h"
#include "twn_input.h"
#include "twn_engine_api.h"

View File

@ -1,9 +1,9 @@
#ifndef INPUT_H
#define INPUT_H
#ifndef TWN_INPUT_H
#define TWN_INPUT_H
#include "config.h"
#include "vec.h"
#include "util.h"
#include "twn_config.h"
#include "twn_vec.h"
#include "twn_util.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h>

View File

@ -1,5 +1,5 @@
#ifndef RENDERING_H
#define RENDERING_H
#ifndef TWN_RENDERING_H
#define TWN_RENDERING_H
#include "util.h"
#include "macros/option.h"
@ -35,8 +35,6 @@ TWN_API void push_rectangle(t_frect rect, t_color color);
/* pushes a filled circle onto the circle render queue */
TWN_API void push_circle(t_fvec2 position, float radius, t_color color);
TWN_API void text_cache_init(struct text_cache *cache);
TWN_API void text_cache_deinit(struct text_cache *cache);
TWN_API void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path);
TWN_API int get_text_width(char *string, int height_px, const char *font_path);
@ -44,13 +42,15 @@ TWN_API int get_text_width(char *string, int height_px, const char *font_path);
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */
TWN_API void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
// TODO: decide whether it's needed to begin with?
// intended usage for it is baked lighting, i would think.
/* pushes a colored textured 3d triangle onto the render queue */
// void unfurl_colored_triangle(const char *path,
// t_fvec3 v0,
@ -66,9 +66,9 @@ TWN_API void unfurl_triangle(const char *path,
// TODO:
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
// void unfurl_billboard(const char *path,
// t_fvec3 position,
// t_fvec2 scaling,
// t_frect uvs);
// t_fvec3 position,
// t_fvec2 scaling,
// t_frect uvs);
/* pushes a camera state to be used for all future unfurl_* commands */
TWN_API void set_camera(const t_camera *camera);

View File

@ -1,7 +1,7 @@
#ifndef UTIL_H
#define UTIL_H
#include "townengine/vec.h"
#include "twn_vec.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h>
@ -164,5 +164,4 @@ static inline t_fvec2 fast_cossine(float a) {
};
}
#endif

View File

@ -1,5 +1,5 @@
#ifndef VEC_H
#define VEC_H
#ifndef TWN_VEC_H
#define TWN_VEC_H
#include <stdint.h>
#include <math.h>

143
shell_minimal.html Normal file
View File

@ -0,0 +1,143 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
</style>
</head>
<body>
<hr/>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<hr/>
<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)">
</div>
<hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var Module = {
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return (...args) => {
var text = args.join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
canvas: (() => {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", (e) => { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: (text) => {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: (left) => {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = () => {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>

View File

@ -1,16 +1,10 @@
/* a rendering.c mixin */
#ifndef CIRCLES_H
#define CIRCLES_H
#include "../util.h"
#include "townengine/util.h"
#include "townengine/context.h"
#include "internal_api.h"
#include "twn_rendering_c.h"
#include <SDL2/SDL.h>
#include <stb_ds.h>
#include <stdlib.h>
void push_circle(t_fvec2 position, float radius, t_color color) {
struct circle_primitive circle = {
@ -29,7 +23,7 @@ void push_circle(t_fvec2 position, float radius, t_color color) {
/* TODO: caching and reuse scheme */
/* vertices_out and indices_out MUST BE FREED */
static void create_circle_geometry(t_fvec2 position,
void create_circle_geometry(t_fvec2 position,
t_color color,
float radius,
size_t num_vertices,
@ -91,42 +85,3 @@ static void create_circle_geometry(t_fvec2 position,
*vertices_out = vertices;
*indices_out = indices;
}
static void render_circle(const struct circle_primitive *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);
free(vertices);
free(indices);
}
#endif

View File

@ -0,0 +1,28 @@
static gpu_texture new_gl_texture(void) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
#if !defined(EMSCRIPTEN)
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#endif
glBindTexture(GL_TEXTURE_2D, 0);
return create_gpu_texture(TEXTURE_FILTER_NEAREST, true);
}
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA8,
surface->w,
surface->h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface->pixels);

View File

@ -0,0 +1,429 @@
#include "twn_gl_15_rendering_c.h"
#include "twn_rendering_c.h"
#include "townengine/util.h"
#include "townengine/config.h"
#include "townengine/context.h"
#include "twn_text_c.h"
#include <glad/glad.h>
/* 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 */
struct element_indexed_quad {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
t_color c0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
t_color c1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
t_color c2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
t_color c3;
};
struct element_indexed_quad_without_color {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
};
typedef enum {
PIPELINE_NO,
PIPELINE_SPACE,
PIPELINE_2D,
} pipeline;
static pipeline pipeline_last_used = PIPELINE_NO;
void use_space_pipeline(void) {
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&camera_projection_matrix.row[0].x);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glDepthRange(0, 1);
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST); /* TODO: infer its usage? */
glAlphaFunc(GL_EQUAL, 1.0f);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
/* solid white, no modulation */
glColor4ub(255, 255, 255, 255);
pipeline_last_used = PIPELINE_SPACE;
}
void use_2d_pipeline(void) {
if (pipeline_last_used == PIPELINE_SPACE) {
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFlush();
}
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glShadeModel(GL_FLAT);
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
pipeline_last_used = PIPELINE_2D;
}
void upload_quad_vertices(t_frect 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 struct rect_primitive *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 struct circle_primitive *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);
free(vertices);
free(indices);
}
void use_texture_mode(enum texture_mode mode) {
if (mode == TEXTURE_MODE_GHOSTLY) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
} else if (mode == TEXTURE_MODE_SEETHROUGH) {
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
} else {
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
}
}
vertex_buffer_builder build_vertex_buffer(vertex_buffer buffer, size_t bytes) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if (!mapping)
CRY("build_vertex_buffer", "Error mapping a vertex array buffer");
return (vertex_buffer_builder) {
.mapping = mapping,
.bytes_left = bytes,
};
}
bool push_to_vertex_buffer_builder(vertex_buffer_builder *builder,
void *bytes, size_t size) {
if (builder->bytes_left == 0)
return false;
memcpy(builder->mapping, bytes, size);
builder->bytes_left -= size;
/* trigger data send */
if (builder->bytes_left == 0) {
glUnmapBuffer(GL_ARRAY_BUFFER);
return false;
}
return true;
}
void finally_render_sprites(const struct primitive_2d primitives[],
const struct sprite_batch batch,
const vertex_buffer vertex_buffer)
{
/* TODO: maybe do, dunno */
// glBindBuffer(GL_VERTEX_ARRAY, vertex_buffer);
(void)vertex_buffer;
GLsizei off;
GLsizei voff;
GLsizei uvoff;
if (!batch.constant_colored) {
off = offsetof(struct element_indexed_quad, v1);
voff = offsetof(struct element_indexed_quad, v0);
uvoff = offsetof(struct element_indexed_quad, uv0);
} else {
off = offsetof(struct element_indexed_quad_without_color, v1);
voff = offsetof(struct element_indexed_quad_without_color, v0);
uvoff = offsetof(struct element_indexed_quad_without_color, uv0);
}
/* vertex specification */
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
off,
(void *)(size_t)voff);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
off,
(void *)(size_t)uvoff);
if (!batch.constant_colored) {
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
off,
(void *)offsetof(struct element_indexed_quad, c0));
} else
glColor4ub(primitives[0].sprite.color.r,
primitives[0].sprite.color.g,
primitives[0].sprite.color.b,
primitives[0].sprite.color.a);
if (!batch.repeat)
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key);
else
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key);
bind_quad_element_buffer();
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, 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);
glDisableClientState(GL_COLOR_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
size_t get_sprite_payload_size(struct sprite_batch batch) {
if (batch.constant_colored)
return sizeof (struct element_indexed_quad_without_color);
else
return sizeof (struct element_indexed_quad);
}
bool push_sprite_payload_to_vertex_buffer_builder(struct sprite_batch batch,
vertex_buffer_builder *builder,
t_fvec2 v0, t_fvec2 v1, t_fvec2 v2, t_fvec2 v3,
t_fvec2 uv0, t_fvec2 uv1, t_fvec2 uv2, t_fvec2 uv3,
t_color color)
{
if (!batch.constant_colored) {
struct element_indexed_quad buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
/* 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 {
struct element_indexed_quad_without_color buffer_element = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
};
return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element);
}
}
void finally_draw_uncolored_space_traingle_batch(const struct mesh_batch *batch,
const t_texture_key texture_key,
const vertex_buffer vertex_buffer)
{
const size_t primitives_len = arrlenu(batch->primitives);
textures_bind(&ctx.texture_cache, texture_key);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
/* vertex specification*/
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, v0));
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, uv0));
/* commit for drawing */
glDrawArrays(GL_TRIANGLES, 0, 3 * (GLint)primitives_len);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
/* invalidate the buffer immediately */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
bool push_text_payload_to_vertex_buffer_builder(struct font_data const *font_data,
vertex_buffer_builder *builder,
stbtt_aligned_quad quad)
{
(void)font_data;
glTexCoord2f(quad.s0, quad.t0);
glVertex2f(quad.x0, quad.y0);
glTexCoord2f(quad.s1, quad.t0);
glVertex2f(quad.x1, quad.y0);
glTexCoord2f(quad.s1, quad.t1);
glVertex2f(quad.x1, quad.y1);
glTexCoord2f(quad.s0, quad.t1);
glVertex2f(quad.x0, quad.y1);
}
void finally_draw_text(struct font_data const *font_data,
size_t len,
t_color color,
vertex_buffer buffer)
{
use_texture_mode(TEXTURE_MODE_GHOSTLY);
glBindTexture(GL_TEXTURE_2D, font_data->texture);
glColor4ub(color.r, color.g, color.b, color.a);
}
size_t get_text_payload_size(void) {
return sizeof (struct element_indexed_quad_without_color);
}

View File

@ -0,0 +1,65 @@
#ifndef TWN_GL_15_RENDERING_H
#define TWN_GL_15_RENDERING_H
/*
* OpenGL 1.5 and any 2.0+ compatibility version render implementation.
*/
#include "twn_rendering_c.h"
#include "twn_gl_any_rendering_c.h"
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stb_truetype.h>
void render_circle(const struct circle_primitive *circle);
void render_rectangle(const struct rect_primitive *rectangle);
void use_space_pipeline(void);
void use_2d_pipeline(void);
void use_texture_mode(enum texture_mode mode);
/* uses present in 1.5 buffer mapping feature */
vertex_buffer_builder build_vertex_buffer(vertex_buffer 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(vertex_buffer_builder *builder,
void *bytes,
size_t size);
void finally_render_sprites(struct primitive_2d const primitives[],
struct sprite_batch batch,
vertex_buffer buffer);
size_t get_sprite_payload_size(struct sprite_batch batch);
bool push_sprite_payload_to_vertex_buffer_builder(struct sprite_batch batch,
vertex_buffer_builder *builder,
t_fvec2 v0, t_fvec2 v1, t_fvec2 v2, t_fvec2 v3,
t_fvec2 uv0, t_fvec2 uv1, t_fvec2 uv2, t_fvec2 uv3,
t_color color);
void finally_draw_uncolored_space_traingle_batch(struct mesh_batch const *batch,
t_texture_key texture_key,
vertex_buffer buffer);
size_t get_text_payload_size(void);
bool push_text_payload_to_vertex_buffer_builder(struct font_data const *font_data,
vertex_buffer_builder *builder,
stbtt_aligned_quad quad);
void finally_draw_text(struct font_data const *font_data,
size_t len,
t_color color,
vertex_buffer buffer);
#endif

View File

@ -1,16 +1,41 @@
/* a rendering.c mixin */
#ifndef QUAD_ELEMENT_BUFFER_H
#define QUAD_ELEMENT_BUFFER_H
#include "twn_gl_any_rendering_c.h"
#include "townengine/context.h"
#include "townengine/util.h"
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stddef.h>
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
void setup_viewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
static void bind_quad_element_buffer(void) {
//////// VERTEX BUFFER ////////
vertex_buffer create_vertex_buffer(void) {
GLuint result;
glGenBuffers(1, &result);
return result;
}
void delete_vertex_buffer(vertex_buffer buffer) {
glDeleteBuffers(1, &buffer);
}
void specify_vertex_buffer(vertex_buffer buffer, void *data, size_t bytes) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
}
//////// END OF VERTEX BUFFER ////////
void bind_quad_element_buffer(void) {
static GLuint buffer = 0;
/* it's only generated once at runtime */
@ -42,4 +67,23 @@ static void bind_quad_element_buffer(void) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
}
#endif
void clear_draw_buffer(void) {
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
}
void swap_buffers(void) {
SDL_GL_SwapWindow(ctx.window);
}
void set_depth_range(double low, double high) {
glDepthRange(low, high);
}

View File

@ -0,0 +1,43 @@
#ifndef TWN_GL_ANY_RENDERING_H
#define TWN_GL_ANY_RENDERING_H
/*
* Any OpenGL version base render methods.
*/
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stdbool.h>
#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6)
typedef GLuint vertex_buffer;
typedef struct vertex_buffer_builder {
size_t bytes_left;
void *mapping;
} vertex_buffer_builder;
vertex_buffer create_vertex_buffer(void);
void delete_vertex_buffer(vertex_buffer buffer);
void specify_vertex_buffer(vertex_buffer buffer, void *data, size_t bytes);
void setup_viewport(int x, int y, int width, int height);
void bind_quad_element_buffer(void);
void clear_draw_buffer(void);
void swap_buffers(void);
void set_depth_range(double low, double high);
#endif

View File

@ -0,0 +1,29 @@
#ifndef TWN_GPU_TEXTURE_H
#define TWN_GPU_TEXTURE_H
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stdbool.h>
typedef GLuint gpu_texture;
enum texture_filter {
TEXTURE_FILTER_NEAREAST,
TEXTURE_FILTER_LINEAR,
};
gpu_texture create_gpu_texture(enum texture_filter filter, bool generate_mipmaps);
void delete_gpu_texture(gpu_texture texture);
void specify_gpu_texture(gpu_texture texture, void *pixels, int channels, int width, int height);
void bind_gpu_texture(gpu_texture texture);
#endif

View File

@ -1,22 +1,27 @@
#include "rendering/internal_api.h"
#include "rendering/sprites.h"
#include "rendering/triangles.h"
#include "rendering/circles.h"
#include "rendering/text.h"
#include "textures/internal_api.h"
#include "twn_rendering_c.h"
#include "townengine/twn_rendering.h"
#include "townengine/textures/internal_api.h"
#include "townengine/context.h"
#include "townengine/camera.h"
#include "twn_rendering_platform.h"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stb_ds.h>
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stddef.h>
#include <tgmath.h>
/* TODO: have a default initialized one */
static t_matrix4 camera_projection_matrix;
static t_matrix4 camera_look_at_matrix;
t_matrix4 camera_projection_matrix;
t_matrix4 camera_look_at_matrix;
void render_queue_clear(void) {
@ -54,38 +59,8 @@ void push_rectangle(t_frect rect, t_color color) {
}
static void upload_quad_vertices(t_frect 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);
}
static void render_rectangle(const struct rect_primitive *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);
}
static void render_2d(void) {
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
use_2d_pipeline();
const size_t render_queue_len = arrlenu(ctx.render_queue_2d);
@ -99,8 +74,8 @@ static void render_2d(void) {
const struct sprite_batch batch =
collect_sprite_batch(current, render_queue_len - i);
glDepthRange((double)batch_count / UINT16_MAX, 1.0);
/* 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);
i += batch.size - 1; ++batch_count;
@ -121,19 +96,12 @@ static void render_2d(void) {
static void render_space(void) {
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glDepthRange(0, 1);
glDisable(GL_BLEND);
glEnable(GL_ALPHA_TEST); /* TODO: infer its usage */
glAlphaFunc(GL_EQUAL, 1.0f);
glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
/* nothing to do, abort */
/* as space pipeline isn't used we can have fewer changes and initialization costs */
if (hmlenu(ctx.uncolored_mesh_batches) == 0)
return;
/* solid white, no modulation */
glColor4ub(255, 255, 255, 255);
use_space_pipeline();
for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
@ -150,7 +118,7 @@ void render(void) {
if ((float)ctx.window_w / (float)ctx.window_h > RENDER_BASE_RATIO) {
float ratio = (float)ctx.window_h / (float)RENDER_BASE_HEIGHT;
int w = (int)((float)RENDER_BASE_WIDTH * ratio);
glViewport(
setup_viewport(
ctx.window_w / 2 - w / 2,
0,
w,
@ -159,7 +127,7 @@ void render(void) {
} else {
float ratio = (float)ctx.window_w / (float)RENDER_BASE_WIDTH;
int h = (int)((float)RENDER_BASE_HEIGHT * ratio);
glViewport(
setup_viewport(
0,
ctx.window_h / 2 - h / 2,
ctx.window_w,
@ -168,57 +136,15 @@ void render(void) {
}
}
glClearColor((1.0f / 255) * 230,
(1.0f / 255) * 230,
(1.0f / 255) * 230, 1);
glClear(GL_COLOR_BUFFER_BIT |
GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_SMOOTH);
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
{
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&camera_projection_matrix.row[0].x);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&camera_look_at_matrix.row[0].x);
render_space();
}
/* TODO: only do it when transition between spaces is needed */
glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glFlush();
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glShadeModel(GL_FLAT);
/* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP);
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, RENDER_BASE_WIDTH, RENDER_BASE_HEIGHT, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
render_2d();
}
SDL_GL_SwapWindow(ctx.window);
clear_draw_buffer();
render_space();
render_2d();
swap_buffers();
}
void set_camera(const t_camera *const camera) {
/* TODO: skip recaulculating if it's the same? */
camera_projection_matrix = camera_perspective(camera);
camera_look_at_matrix = camera_look_at(camera);
camera_look_at_matrix = camera_look_at(camera);
}

View File

@ -6,10 +6,12 @@
#include "townengine/macros/option.h"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <stdbool.h>
extern t_matrix4 camera_projection_matrix;
extern t_matrix4 camera_look_at_matrix;
struct sprite_primitive {
t_frect rect;
t_color color;
@ -86,7 +88,6 @@ union uncolored_space_triangle {
/* batch of primitives with overlapping properties */
struct mesh_batch {
GLuint buffer; /* server side storage */
uint8_t *primitives;
};
@ -105,4 +106,40 @@ void render(void);
/* clears all render queues */
void render_queue_clear(void);
void push_circle(t_fvec2 position, float radius, t_color color);
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
void create_circle_geometry(t_fvec2 position,
t_color color,
float radius,
size_t num_vertices,
SDL_Vertex **vertices_out,
int **indices_out);
struct sprite_batch {
size_t size; /* how many primitives are in current batch */
enum texture_mode mode;
bool constant_colored; /* whether colored batch is uniformly colored */
bool repeat; /* whether repeat is needed */
} collect_sprite_batch(const struct primitive_2d primitives[], size_t len);
void render_sprites(const struct primitive_2d primitives[],
const struct sprite_batch batch);
void draw_uncolored_space_traingle_batch(struct mesh_batch *batch,
t_texture_key texture_key);
void render_text(const struct text_primitive *text);
void text_cache_init(struct text_cache *cache);
void text_cache_deinit(struct text_cache *cache);
#endif

View File

@ -0,0 +1,10 @@
#ifndef TWN_RENDERING_PLATFORM_H
#define TWN_RENDERING_PLATFORM_H
#ifdef EMSCRIPTEN
#include "twn_gl_es2_rendering_c.h"
#else
#include "twn_gl_15_rendering_c.h"
#endif
#endif

View File

@ -1,58 +1,15 @@
/* a rendering.c mixin */
#ifndef SPRITES_H
#define SPRITES_H
#include "../rendering.h"
#include "townengine/twn_rendering.h"
#include "twn_rendering_c.h"
#include "townengine/context.h"
#include "../util.h"
#include "../textures/internal_api.h"
#include "quad_element_buffer.h"
#include "internal_api.h"
#include "townengine/util.h"
#include "townengine/textures/internal_api.h"
#include "twn_rendering_platform.h"
#include <stb_ds.h>
#include <stdbool.h>
#include <stddef.h>
/* 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 */
struct sprite_primitive_payload {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
t_color c0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
t_color c1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
t_color c2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
t_color c3;
};
struct sprite_primitive_payload_without_color {
/* upper-left */
t_fvec2 v0;
t_fvec2 uv0;
/* bottom-left */
t_fvec2 v1;
t_fvec2 uv1;
/* bottom-right */
t_fvec2 v2;
t_fvec2 uv2;
/* upper-right */
t_fvec2 v3;
t_fvec2 uv3;
};
/*
* an implementation note:
* try to avoid doing expensive work in the push functions,
@ -82,12 +39,7 @@ void push_sprite(const t_push_sprite_args args) {
}
static struct sprite_batch {
size_t size; /* how many primitives are in current batch */
enum texture_mode mode;
bool constant_colored; /* whether colored batch is uniformly colored */
bool repeat; /* whether repeat is needed */
} collect_sprite_batch(const struct primitive_2d *primitives, size_t len) {
struct sprite_batch collect_sprite_batch(const struct primitive_2d primitives[], size_t len) {
/* assumes that first primitive is already a sprite */
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);
@ -143,52 +95,22 @@ static struct sprite_batch {
/* assumes that orthogonal matrix setup is done already */
static void render_sprites(const struct primitive_2d primitives[],
void render_sprites(const struct primitive_2d primitives[],
const struct sprite_batch batch)
{
/* single vertex array is used for every batch with NULL glBufferData() trick at the end */
static GLuint vertex_array = 0;
static vertex_buffer vertex_array = 0;
if (vertex_array == 0)
glGenBuffers(1, &vertex_array);
vertex_array = create_vertex_buffer();
if (batch.mode == TEXTURE_MODE_GHOSTLY) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
} else if (batch.mode == TEXTURE_MODE_SEETHROUGH) {
glDisable(GL_BLEND);
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
} else {
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
}
size_t payload_size;
if (!batch.constant_colored)
payload_size = sizeof (struct sprite_primitive_payload);
else
payload_size = sizeof (struct sprite_primitive_payload_without_color);
glBindBuffer(GL_ARRAY_BUFFER, vertex_array);
glBufferData(GL_ARRAY_BUFFER,
payload_size * batch.size,
NULL,
GL_STREAM_DRAW);
use_sprite_blendmode(batch.mode);
const t_frect dims =
textures_get_dims(&ctx.texture_cache, primitives->sprite.texture_key);
/* vertex population over a mapped buffer */
/* vertex population over a vertex buffer builder interface */
{
/* TODO: check errors, ensure alignment ? */
void *const payload = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
vertex_buffer_builder payload = build_vertex_buffer(vertex_array, get_sprite_payload_size(batch) * batch.size);
for (size_t i = 0; i < batch.size; ++i) {
/* render opaques front to back */
@ -275,100 +197,9 @@ static void render_sprites(const struct primitive_2d primitives[],
v3 = (t_fvec2){ c.x + t.x * +h.x - t.y * -h.y, c.y + t.y * +h.x + t.x * -h.y };
}
if (!batch.constant_colored)
((struct sprite_primitive_payload *)payload)[i] = (struct sprite_primitive_payload) {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
/* equal for all (flat shaded) */
.c0 = sprite.color,
.c1 = sprite.color,
.c2 = sprite.color,
.c3 = sprite.color,
};
else
((struct sprite_primitive_payload_without_color *)payload)[i] = (struct sprite_primitive_payload_without_color) {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.v3 = v3,
.uv0 = uv0,
.uv1 = uv1,
.uv2 = uv2,
.uv3 = uv3,
};
push_sprite_payload_to_vertex_buffer_builder(batch, &payload, v0, v1, v2, v3, uv0, uv1, uv2, uv3, sprite.color);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
}
GLsizei off;
GLsizei voff;
GLsizei uvoff;
if (!batch.constant_colored) {
off = offsetof(struct sprite_primitive_payload, v1);
voff = offsetof(struct sprite_primitive_payload, v0);
uvoff = offsetof(struct sprite_primitive_payload, uv0);
} else {
off = offsetof(struct sprite_primitive_payload_without_color, v1);
voff = offsetof(struct sprite_primitive_payload_without_color, v0);
uvoff = offsetof(struct sprite_primitive_payload_without_color, uv0);
}
/* vertex specification */
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2,
GL_FLOAT,
off,
(void *)(size_t)voff);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
off,
(void *)(size_t)uvoff);
if (!batch.constant_colored) {
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4,
GL_UNSIGNED_BYTE,
off,
(void *)offsetof(struct sprite_primitive_payload, c0));
} else
glColor4ub(primitives[0].sprite.color.r,
primitives[0].sprite.color.g,
primitives[0].sprite.color.b,
primitives[0].sprite.color.a);
if (!batch.repeat)
textures_bind(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D);
else
textures_bind_repeating(&ctx.texture_cache, primitives->sprite.texture_key, GL_TEXTURE_2D);
bind_quad_element_buffer();
glDrawElements(GL_TRIANGLES, 6 * (GLsizei)batch.size, 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);
glDisableClientState(GL_COLOR_ARRAY);
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
finally_render_sprites(primitives, batch, vertex_array);
}
#endif

View File

@ -1,13 +1,11 @@
/* a rendering.c mixin */
#ifndef TEXT_H
#define TEXT_H
#include "../util.h"
#include "twn_rendering_c.h"
#include "townengine/util.h"
#include "townengine/config.h"
#include "townengine/context.h"
#include "townengine/twn_rendering.h"
#include "twn_rendering_platform.h"
#include <glad/glad.h>
#include <stb_truetype.h>
@ -24,7 +22,7 @@ struct font_data {
unsigned char *file_bytes;
size_t file_bytes_len;
GLuint texture;
gpu_texture texture;
int height_px;
float scale_factor;
@ -67,24 +65,15 @@ static struct font_data *text_load_font_data(const char *path, int height_px) {
stbtt_PackEnd(&pctx);
}
glGenTextures(1, &font_data->texture);
glBindTexture(GL_TEXTURE_2D, font_data->texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_ALPHA,
font_data->texture = create_gpu_texture(TEXT_FONT_FILTERING, true);
specify_gpu_texture(
font_data->texture,
bitmap,
1,
TEXT_FONT_TEXTURE_SIZE,
TEXT_FONT_TEXTURE_SIZE,
0,
GL_ALPHA,
GL_UNSIGNED_BYTE,
bitmap
TEXT_FONT_TEXTURE_SIZE
);
free(bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TEXT_FONT_FILTERING);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TEXT_FONT_FILTERING);
glBindTexture(GL_TEXTURE_2D, 0);
return font_data;
}
@ -92,25 +81,22 @@ static struct font_data *text_load_font_data(const char *path, int height_px) {
static void text_destroy_font_data(struct font_data *font_data) {
free(font_data->file_bytes);
glDeleteTextures(1, &font_data->texture);
delete_gpu_texture(font_data->texture);
free(font_data);
}
static void text_draw_with(struct font_data* font_data, char* text, t_fvec2 position, t_color color) {
glBindTexture(GL_TEXTURE_2D, font_data->texture);
static vertex_buffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_ALWAYS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
const size_t len = SDL_strlen(text);
glColor4ub(color.r, color.g, color.b, color.a);
vertex_buffer_builder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len);
glBegin(GL_QUADS);
for (const char *p = text; *p != '\0'; ++p) {
const char c = *p;
for (size_t i = 0; i < len; ++i) {
const char c = text[i];
/* outside the range of what we want to display */
//if (c < ASCII_START || c > ASCII_END)
@ -136,20 +122,10 @@ static void text_draw_with(struct font_data* font_data, char* text, t_fvec2 posi
quad.y0 += (float)font_data->ascent;
quad.y1 += (float)font_data->ascent;
/* TODO: you know... */
glTexCoord2f(quad.s0, quad.t0);
glVertex2f(quad.x0, quad.y0);
glTexCoord2f(quad.s1, quad.t0);
glVertex2f(quad.x1, quad.y0);
glTexCoord2f(quad.s1, quad.t1);
glVertex2f(quad.x1, quad.y1);
glTexCoord2f(quad.s0, quad.t1);
glVertex2f(quad.x0, quad.y1);
push_text_payload_to_vertex_buffer_builder(font_data, &payload, quad);
}
glEnd();
glColor4ub(255, 255, 255, 255);
glBindTexture(GL_TEXTURE_2D, 0);
finally_draw_text(font_data, len, color, vertex_array);
}
@ -182,7 +158,7 @@ static struct font_data *get_font_data(const char *font_path, int height_px) {
}
static void render_text(const struct text_primitive *text) {
void render_text(const struct text_primitive *text) {
struct font_data *font_data = get_font_data(text->font, text->height_px);
text_draw_with(font_data, text->text, text->position, text->color);
}
@ -243,6 +219,3 @@ int get_text_width(char *string, int height_px, const char *font_path) {
return (int)((float)length * font_data->scale_factor);
}
#endif

View File

@ -0,0 +1,26 @@
#include "twn_rendering_platform.h"
#include <stb_truetype.h>
#define ASCII_START 32
#define ASCII_END 128
#define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1)
struct font_data {
stbtt_packedchar char_data[NUM_DISPLAY_ASCII];
stbtt_fontinfo info;
const char *file_path;
unsigned char *file_bytes;
size_t file_bytes_len;
gpu_texture texture;
int height_px;
float scale_factor;
int ascent;
int descent;
int line_gap;
};

View File

@ -0,0 +1,81 @@
#include "twn_rendering_c.h"
#include "twn_context.h"
#include "twn_textures_c.h"
#include "twn_rendering_platform.h"
#include <stb_ds.h>
/* TODO: automatic handling of repeating textures */
/* for that we could allocate a loner texture */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2)
{
const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path);
struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
if (!batch_p) {
struct mesh_batch item = {0};
hmput(ctx.uncolored_mesh_batches, texture_key, item);
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
}
union uncolored_space_triangle triangle = { .primitive = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.uv1 = m_to_fvec2(uv1),
.uv0 = m_to_fvec2(uv0),
.uv2 = m_to_fvec2(uv2),
}};
union uncolored_space_triangle *triangles = (union uncolored_space_triangle *)batch_p->value.primitives;
arrpush(triangles, triangle);
batch_p->value.primitives = (uint8_t *)triangles;
}
void draw_uncolored_space_traingle_batch(struct mesh_batch *batch,
t_texture_key texture_key)
{
static vertex_buffer vertex_array = 0;
if (vertex_array == 0)
vertex_array = create_vertex_buffer();
const size_t primitives_len = arrlenu(batch->primitives);
/* nothing to do */
if (primitives_len == 0)
return;
const t_frect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const t_frect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
/* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) {
struct uncolored_space_triangle_payload *payload =
&((union uncolored_space_triangle *)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;
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
}
specify_vertex_buffer(vertex_array, batch->primitives, primitives_len * sizeof (struct uncolored_space_triangle_payload));
finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array);
}

View File

@ -1,5 +1,5 @@
#ifndef ELF_H
#define ELF_H
#ifndef TWN_ELF_H
#define TWN_ELF_H
#include <stdbool.h>

View File

@ -1,7 +1,7 @@
#ifndef PRIVATE_AUDIO_H
#define PRIVATE_AUDIO_H
#ifndef TWN_AUDIO_C_H
#define TWN_AUDIO_C_H
#include "../audio.h"
#include "twn_audio.h"
#include <SDL2/SDL_audio.h>

View File

@ -1,5 +1,5 @@
#include "camera.h"
#include "townengine/context.h"
#include "twn_camera.h"
#include "twn_context.h"
#include <math.h>

View File

@ -1,5 +1,5 @@
#ifndef CONCATENATE_H
#define CONCATENATE_H
#ifndef TWN_CONCATENATE_H
#define TWN_CONCATENATE_H
#define m_concatenate(p_a, p_b) m_concatenate_(p_a, p_b)
#define m_concatenate_(p_a, p_b) m_concatenate__(p_a, p_b)

3
src/twn_context.c Normal file
View File

@ -0,0 +1,3 @@
#include "twn_engine_context_c.h"
t_engine_ctx ctx = {0};

View File

@ -1,11 +1,9 @@
#ifndef CONTEXT_H
#define CONTEXT_H
#ifndef TWN_ENGINE_CONTEXT_H
#define TWN_ENGINE_CONTEXT_H
#include "rendering/internal_api.h"
#include "twn_context.h"
#include "textures/internal_api.h"
#include "input.h"
#include "twn_engine_api.h"
#include "twn_input.h"
#include <SDL2/SDL.h>
@ -13,17 +11,17 @@
#include <stdint.h>
typedef struct context {
typedef struct engine_context {
t_ctx game_context;
/* the program's actual argc and argv */
int argc;
char **argv;
struct texture_cache texture_cache;
struct input_state input;
struct primitive_2d *render_queue_2d;
struct mesh_batch_item *uncolored_mesh_batches;
struct text_cache text_cache;
struct texture_cache texture_cache;
struct audio_channel_item *audio_channels;
SDL_AudioDeviceID audio_device;
@ -38,34 +36,15 @@ typedef struct context {
int64_t frame_accumulator;
int64_t delta_averager_residual;
int64_t time_averager[4];
int64_t delta_time; /* preserves real time frame delta with no manipilation */
uint64_t tick_count;
/* set just once on startup */
uint64_t random_seed;
/* this should be a multiple of 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;
SDL_GLContext *gl_context;
SDL_Window *window;
uint32_t window_id;
int window_w;
int window_h;
/* you may read from and write to these from game code */
void *udata;
bool debug;
bool is_running;
bool resync_flag;
bool was_successful;
bool window_size_has_changed;
bool initialization_needed;
} t_ctx;
} t_engine_ctx;
TWN_API extern t_ctx ctx;
extern t_engine_ctx ctx = ;
#endif

View File

@ -1,5 +1,5 @@
#ifndef GAME_OBJECT_H
#define GAME_OBJECT_H
#ifndef TWN_GAME_OBJECT_H
#define TWN_GAME_OBJECT_H
#include <stdbool.h>

View File

@ -9,10 +9,14 @@
#include "townengine/rendering/internal_api.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <physfs.h>
#include <stb_ds.h>
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif
#include <stdlib.h>
#include <stdbool.h>
@ -54,6 +58,8 @@ static void poll_events(void) {
}
#ifndef EMSCRIPTEN
static void APIENTRY opengl_log(GLenum source,
GLenum type,
GLuint id,
@ -71,6 +77,8 @@ static void APIENTRY opengl_log(GLenum source,
log_info("OpenGL: %.*s\n", length, message);
}
#endif
static void main_loop(void) {
/*
@ -185,7 +193,7 @@ static void main_loop(void) {
static bool initialize(void) {
if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
if (SDL_Init(SDL_INIT_EVERYTHING & ~SDL_INIT_HAPTIC) == -1) {
CRY_SDL("SDL initialization failed.");
return false;
}
@ -199,8 +207,20 @@ static bool initialize(void) {
ctx.debug = false;
#endif
#ifdef EMSCRIPTEN
/* emscripten interpretes those as GL ES version against WebGL */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
if (ctx.debug)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR);
#endif
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
@ -209,11 +229,6 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
if (ctx.debug)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR);
/* init got far enough to create a window */
ctx.window = SDL_CreateWindow("townengine",
SDL_WINDOWPOS_CENTERED,
@ -242,16 +257,20 @@ static bool initialize(void) {
if (SDL_GL_SetSwapInterval(-1))
SDL_GL_SetSwapInterval(1);
#ifndef EMSCRIPTEN
if (gladLoadGL() == 0) {
CRY("Init", "GLAD failed");
goto fail;
}
#endif
log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
#ifndef EMSCRIPTEN
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST);
#endif
/* might need this to have multiple windows */
ctx.window_id = SDL_GetWindowID(ctx.window);
@ -295,11 +314,13 @@ static bool initialize(void) {
/* you could change this at runtime if you wanted */
ctx.update_multiplicity = 1;
#ifndef EMSCRIPTEN
/* hook up opengl debugging callback */
if (ctx.debug) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL);
}
#endif
/* random seeding */
/* SDL_GetPerformanceCounter returns some platform-dependent number. */

View File

@ -1,8 +1,8 @@
#ifndef OPTION_H
#define OPTION_H
#ifndef TWN_OPTION_H
#define TWN_OPTION_H
#include "concatenate.h"
#include "varargcount.h"
#include "twn_concatenate_c.h"
#include "twn_varargcount_c.h"
#include <stdbool.h>

View File

@ -1,5 +1,5 @@
#ifndef TEXTURES_MODES_H
#define TEXTURES_MODES_H
#ifndef TWN_TEXTURES_MODES_H
#define TWN_TEXTURES_MODES_H
/* alpha channel information */
enum texture_mode {

View File

@ -81,24 +81,6 @@ ERR_CANNOT_OPEN_FILE:
}
static GLuint new_gl_texture(void) {
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBindTexture(GL_TEXTURE_2D, 0);
return texture;
}
static SDL_Surface *create_surface(int width, int height) {
Uint32 rmask, gmask, bmask, amask;
@ -134,28 +116,16 @@ static SDL_Surface *create_surface(int width, int height) {
static void add_new_atlas(struct texture_cache *cache) {
SDL_Surface *new_atlas = create_surface(TEXTURE_ATLAS_SIZE, TEXTURE_ATLAS_SIZE);
arrput(cache->atlas_surfaces, new_atlas);
arrput(cache->atlas_textures, new_gl_texture());
arrput(cache->atlas_textures, create_gpu_texture(TEXTURE_FILTER_NEAREAST, true));
}
static void upload_texture_from_surface(GLuint texture, SDL_Surface *surface) {
glBindTexture(GL_TEXTURE_2D, texture);
static void upload_texture_from_surface(gpu_texture texture, SDL_Surface *surface) {
SDL_LockSurface(surface);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RGBA8,
surface->w,
surface->h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
surface->pixels);
specify_gpu_texture(texture, surface->pixels, surface->w, surface->h);
SDL_UnlockSurface(surface);
glBindTexture(GL_TEXTURE_2D, 0);
}
@ -285,7 +255,7 @@ void textures_cache_init(struct texture_cache *cache, SDL_Window *window) {
void textures_cache_deinit(struct texture_cache *cache) {
/* free atlas textures */
for (size_t i = 0; i < arrlenu(cache->atlas_textures); ++i) {
glDeleteTextures(1, &cache->atlas_textures[i]);
delete_gpu_texture(cache->atlas_textures[i]);
}
arrfree(cache->atlas_textures);
@ -373,7 +343,7 @@ static t_texture_key textures_load(struct texture_cache *cache, const char *path
/* it's a "loner texture," it doesn't fit in an atlas so it's not in one */
if (surface->w >= TEXTURE_ATLAS_SIZE || surface->h >= TEXTURE_ATLAS_SIZE) {
new_texture.loner_texture = new_gl_texture();
new_texture.loner_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, true);
upload_texture_from_surface(new_texture.loner_texture, surface);
new_texture.srcrect = (t_frect) { .w = (float)surface->w, .h = (float)surface->h };
shput(cache->hash, path, new_texture);
@ -528,12 +498,12 @@ t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key)
}
void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target) {
void textures_bind(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
if (cache->hash[key.id].value.loner_texture == 0)
glBindTexture(target, cache->atlas_textures[cache->hash[key.id].value.atlas_index]);
bind_gpu_texture(cache->atlas_textures[cache->hash[key.id].value.atlas_index]);
else
glBindTexture(target, cache->hash[key.id].value.loner_texture);
bind_gpu_texture(cache->hash[key.id].value.loner_texture);
} else if (key.id == 0) {
CRY("Texture binding failed.",
"Tried to get texture that isn't loaded.");
@ -542,39 +512,33 @@ void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum
/* TODO: alternative schemes, such as: array texture, fragment shader and geometry division */
void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target) {
void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key) {
if (m_texture_key_is_valid(key)) {
if (cache->hash[key.id].value.loner_texture == 0) {
/* already allocated */
if (cache->hash[key.id].value.repeating_texture != 0) {
glBindTexture(target, cache->hash[key.id].value.repeating_texture);
bind_gpu_texture(cache->hash[key.id].value.repeating_texture);
return;
}
const struct texture texture = cache->hash[key.id].value;
const GLuint repeating_texture = new_gl_texture();
glBindTexture(target, repeating_texture);
const gpu_texture repeating_texture = create_gpu_texture(TEXTURE_FILTER_NEAREAST, false);
SDL_LockSurface(texture.data);
glTexImage2D(target,
0,
GL_RGBA8,
texture.data->w,
texture.data->h,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
texture.data->pixels);
specify_gpu_texture(repeating_texture,
texture.data->pixels,
texture.data->w,
texture.data->h);
SDL_UnlockSurface(texture.data);
cache->hash[key.id].value.repeating_texture = repeating_texture;
} else
glBindTexture(target, cache->hash[key.id].value.loner_texture);
bind_gpu_texture(cache->hash[key.id].value.loner_texture);
} else if (key.id == 0) {
CRY("Texture binding failed.",

View File

@ -1,13 +1,13 @@
#ifndef TEXTURES_INTERNAL_API_H
#define TEXTURES_INTERNAL_API_H
#ifndef TWN_TEXTURES_H
#define TWN_TEXTURES_H
#include "../util.h"
#include "../textures/modes.h"
#include "../twn_engine_api.h"
#include "twn_util.h"
#include "twn_texture_modes.h"
#include "twn_engine_api.h"
#include "twn_gpu_texture.h"
#include <SDL2/SDL.h>
#include <stb_rect_pack.h>
#include <glad/glad.h>
#include <stdbool.h>
@ -15,8 +15,8 @@ struct texture {
t_frect srcrect; /* position in atlas */
SDL_Surface *data; /* original image data */
int atlas_index;
GLuint loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */
GLuint repeating_texture; /* separately allocated texture, for loners == loner_texture */
gpu_texture loner_texture; /* stored directly for loners, == 0 means atlas_index should be used */
gpu_texture repeating_texture; /* separately allocated texture, for loners == loner_texture */
enum texture_mode mode;
};
@ -35,7 +35,7 @@ struct texture_cache {
stbrp_node *node_buffer; /* used internally by stb_rect_pack */
SDL_Surface **atlas_surfaces;
GLuint *atlas_textures; /* shared by atlas textures */
gpu_texture *atlas_textures; /* shared by atlas textures */
int atlas_index; /* atlas that is currently being built */
bool is_dirty; /* current atlas needs to be recreated */
@ -47,11 +47,11 @@ typedef struct { uint16_t id; } t_texture_key;
/* tests whether given key structure corresponds to any texture */
#define m_texture_key_is_valid(p_key) ((p_key).id != (uint16_t)-1)
TWN_API void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
TWN_API void textures_cache_deinit(struct texture_cache *cache);
void textures_cache_init(struct texture_cache *cache, SDL_Window *window);
void textures_cache_deinit(struct texture_cache *cache);
/* for debugging */
TWN_API void textures_dump_atlases(struct texture_cache *cache);
void textures_dump_atlases(struct texture_cache *cache);
/* loads an image if it isn't in the cache, otherwise a no-op. */
/* can be called from anywhere at any time after init, useful if you want to */
@ -61,34 +61,32 @@ TWN_API void textures_dump_atlases(struct texture_cache *cache);
/* repacks the current texture atlas based on the texture cache if needed */
/* any previously returned srcrect results are invalidated after that */
/* call it every time before rendering */
TWN_API void textures_update_atlas(struct texture_cache *cache);
void textures_update_atlas(struct texture_cache *cache);
/* returns a persistent handle to some texture in cache, loading it if needed */
/* check the result with m_texture_key_is_valid() */
TWN_API t_texture_key textures_get_key(struct texture_cache *cache, const char *path);
t_texture_key textures_get_key(struct texture_cache *cache, const char *path);
/* returns a rect in a texture cache of the given key */
TWN_API t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key);
t_frect textures_get_srcrect(const struct texture_cache *cache, t_texture_key key);
/* returns a rect of dimensions of the whole texture (whole atlas) */
TWN_API t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key);
t_frect textures_get_dims(const struct texture_cache *cache, t_texture_key key);
/* returns an identifier that is equal for all textures placed in the same atlas */
TWN_API int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key);
int32_t textures_get_atlas_id(const struct texture_cache *cache, t_texture_key key);
/* binds atlas texture in opengl state */
TWN_API void textures_bind(const struct texture_cache *cache, t_texture_key key, GLenum target);
void textures_bind(const struct texture_cache *cache, t_texture_key key);
/* binds texture in opengl state, ensuring that it's usable with texture repeat */
TWN_API void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key, GLenum target);
void textures_bind_repeating(const struct texture_cache *cache, t_texture_key key);
/* returns helpful information about contents of alpha channel in given texture */
TWN_API enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key);
enum texture_mode textures_get_mode(const struct texture_cache *cache, t_texture_key key);
/* returns the number of atlases in the cache */
TWN_API size_t textures_get_num_atlases(const struct texture_cache *cache);
size_t textures_get_num_atlases(const struct texture_cache *cache);
/* TODO: should recieve texture_cache, get_key optimization cache should be cleared some other way */
TWN_API void textures_reset_state(void);
void textures_reset_state(void);
#endif

View File

@ -1,5 +1,5 @@
#include "util.h"
#include "townengine/context.h"
#include "twn_util.h"
#include "twn_context.h"
#include <SDL2/SDL.h>
#include <physfsrwops.h>

View File

@ -1,5 +1,5 @@
#ifndef VARARGCOUNT_H
#define VARARGCOUNT_H
#ifndef TWN_VARARGCOUNT_H
#define TWN_VARARGCOUNT_H
#define m_narg(...) m_narg_(__VA_ARGS__, m_rseq_n_())
#define m_narg_(...) m_arg_n_(__VA_ARGS__)

View File

@ -1,3 +0,0 @@
#include "townengine/context.h"
t_ctx ctx = {0};

View File

@ -1,118 +0,0 @@
/* a rendering.c mixin */
#ifndef TRIANGLES_H
#define TRIANGLES_H
#include "townengine/context.h"
#include "internal_api.h"
#include "../textures/internal_api.h"
#include <stb_ds.h>
/* TODO: automatic handling of repeating textures */
/* for that we could allocate a loner texture */
void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2)
{
const t_texture_key texture_key = textures_get_key(&ctx.texture_cache, path);
struct mesh_batch_item *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key);
if (!batch_p) {
struct mesh_batch item = {0};
hmput(ctx.uncolored_mesh_batches, texture_key, item);
batch_p = &ctx.uncolored_mesh_batches[hmlenu(ctx.uncolored_mesh_batches) - 1]; /* TODO: can last index be used? */
}
union uncolored_space_triangle triangle = { .primitive = {
.v0 = v0,
.v1 = v1,
.v2 = v2,
.uv1 = m_to_fvec2(uv1),
.uv0 = m_to_fvec2(uv0),
.uv2 = m_to_fvec2(uv2),
}};
union uncolored_space_triangle *triangles =
(union uncolored_space_triangle *)batch_p->value.primitives;
arrpush(triangles, triangle);
batch_p->value.primitives = (uint8_t *)triangles;
}
static void draw_uncolored_space_traingle_batch(struct mesh_batch *batch,
t_texture_key texture_key)
{
size_t primitives_len = arrlenu(batch->primitives);
if (primitives_len == 0)
return;
/* create vertex array object */
if (batch->buffer == 0)
glGenBuffers(1, &batch->buffer);
/* TODO: try using mapped buffers while building batches instead? */
/* this way we could skip client side copy that is kept until commitment */
/* alternatively we could commit glBufferSubData based on a threshold */
/* update pixel-based uvs to correspond with texture atlases */
for (size_t i = 0; i < primitives_len; ++i) {
struct uncolored_space_triangle_payload *payload =
&((union uncolored_space_triangle *)batch->primitives)[i].payload;
const t_frect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key);
const t_frect dims = textures_get_dims(&ctx.texture_cache, texture_key);
const float wr = srcrect.w / dims.w;
const float hr = srcrect.h / dims.h;
const float xr = srcrect.x / dims.w;
const float yr = srcrect.y / dims.h;
payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr;
payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr;
payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr;
payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr;
payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr;
payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr;
}
textures_bind(&ctx.texture_cache, texture_key, GL_TEXTURE_2D);
glBindBuffer(GL_ARRAY_BUFFER, batch->buffer);
/* upload batched data */
glBufferData(GL_ARRAY_BUFFER,
primitives_len * sizeof (struct uncolored_space_triangle_payload),
batch->primitives,
GL_STREAM_DRAW);
/* vertex specification*/
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, v0));
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(2,
GL_FLOAT,
offsetof(struct uncolored_space_triangle_payload, v1),
(void *)offsetof(struct uncolored_space_triangle_payload, uv0));
/* commit for drawing */
glDrawArrays(GL_TRIANGLES, 0, 3 * (GLint)primitives_len);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
/* invalidate the buffer immediately */
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
#endif

View File

@ -1,139 +0,0 @@
#include "scripting.h"
#include "util.h"
#include "townengine/context.h"
#include <SDL2/SDL.h>
#include <umka_api.h>
#include <physfs.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
static void msgbox(UmkaStackSlot *params, UmkaStackSlot *result) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION,
params[1].ptrVal, params[0].ptrVal, NULL);
}
static void umka_log_info(UmkaStackSlot *params, UmkaStackSlot *result) {
log_info(params[0].ptrVal);
}
static void umka_log_critical(UmkaStackSlot *params, UmkaStackSlot *result) {
log_critical(params[0].ptrVal);
}
static void umka_log_warn(UmkaStackSlot *params, UmkaStackSlot *result) {
log_warn(params[0].ptrVal);
}
static void is_action_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_pressed(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void is_action_just_pressed(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_just_pressed(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void is_action_just_released(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[1].ptrVal;
t_ctx *ctx = state->hidden_ptr;
bool value = input_is_action_just_released(&ctx->input, params[0].ptrVal);
result->uintVal = value;
}
static void get_action_position(UmkaStackSlot *params, UmkaStackSlot *result) {
struct state *state = params[2].ptrVal;
t_ctx *ctx = state->hidden_ptr;
t_fvec2 *position = params[0].ptrVal;
*position = input_get_action_position(&ctx->input, params[1].ptrVal);
// the result is in a hidden result pointer allocated by Umka
result->ptrVal = params[0].ptrVal;
}
static void register_api(void *umka) {
umkaAddFunc(umka, "msgbox", msgbox);
umkaAddFunc(umka, "logInfo", umka_log_info);
umkaAddFunc(umka, "logCritical", umka_log_critical);
umkaAddFunc(umka, "logWarn", umka_log_warn);
umkaAddFunc(umka, "cImplIsActionPressed", is_action_pressed);
umkaAddFunc(umka, "cImplIsActionJustPressed", is_action_just_pressed);
umkaAddFunc(umka, "cImplIsActionJustReleased", is_action_just_released);
umkaAddFunc(umka, "getActionPosition", get_action_position);
}
bool scripting_init(t_ctx *ctx) {
if (!PHYSFS_exists("/scripts/main.um")) {
CRY("Failed to initialize scripting", "Could not find a main.um (we need it)");
return false;
}
ctx->umka = umkaAlloc();
char *main_script = file_to_str("/scripts/main.um");
bool umka_ok = umkaInit(ctx->umka,
"main.um",
main_script,
UMKA_STACK_SIZE,
NULL,
ctx->argc,
ctx->argv,
false,
false,
NULL);
free(main_script);
if (!umka_ok) {
CRY("Failed to initialize scripting", "Unknown Umka error");
return false;
}
/* all Umka files are compiled even if they're never used */
char **dir_file_names = PHYSFS_enumerateFiles("/scripts");
for (char **i = dir_file_names; *i != NULL; ++i) {
char *file_name = *i;
if (!strends(file_name, ".um"))
continue;
/* got this one already */
if (strcmp(file_name, "main.um") == 0)
continue;
/* need to figure out the actual path (as opposed to the lone file name) */
const char *path_prefix = "/scripts/";
size_t path_size = snprintf(NULL, 0, "%s%s", path_prefix, file_name) + 1;
char *path = cmalloc(path_size);
snprintf(path, path_size, "%s%s", path_prefix, file_name);
char *contents = file_to_str(path);
umkaAddModule(ctx->umka, file_name, contents);
free(path);
free(contents);
}
PHYSFS_freeList(dir_file_names);
register_api(ctx->umka);
if (!umkaCompile(ctx->umka)) {
cry_umka(ctx->umka);
return false;
}
return true;
}
void scripting_deinit(t_ctx *ctx) {
umkaFree(ctx->umka);
}

View File

@ -1,18 +0,0 @@
#ifndef SCRIPTING_H
#define SCRIPTING_H
#include <umka_api.h>
#include <stdint.h>
#include <stdbool.h>
typedef struct context t_ctx;
struct state {
t_ctx *hidden_ptr;
uint64_t tick_count;
};
bool scripting_init(void);
#endif

8
untitled.sublime-project Normal file
View File

@ -0,0 +1,8 @@
{
"folders":
[
{
"path": "."
}
]
}

1609
untitled.sublime-workspace Normal file

File diff suppressed because it is too large Load Diff