[RELEASE] u22-v03

This version is submitted to U22 breau.
This commit is contained in:
2020-09-14 00:00:00 +00:00
parent 360595de37
commit 84c3a02b9a
357 changed files with 29223 additions and 0 deletions

12
util/CMakeLists.txt Normal file
View File

@@ -0,0 +1,12 @@
add_subdirectory(chaos)
add_subdirectory(coly2d)
add_subdirectory(container)
add_subdirectory(conv)
add_subdirectory(dictres)
add_subdirectory(gleasy)
add_subdirectory(glyphas)
add_subdirectory(jukebox)
add_subdirectory(math)
add_subdirectory(memory)
add_subdirectory(mpkutil)
add_subdirectory(parsarg)

View File

@@ -0,0 +1,7 @@
add_library(chaos
abchash.c
xorshift.c
)
target_link_libraries(chaos
math
)

32
util/chaos/abchash.c Normal file
View File

@@ -0,0 +1,32 @@
#include "./abchash.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define CHAOS_ABCHASH_GET_CODE(c) ( \
('0' <= c && c <= '9')? c-'0': \
('a' <= c && c <= 'z')? c-'a'+10: \
(c == '_') ? 36: UINT8_MAX)
#define CHAOS_ABCHASH_MAX_CODE 36
uint64_t chaos_abchash(const char* str, size_t len) {
assert(chaos_abchash_validate(str, len));
uint64_t ans = 0;
for (size_t i = 0; i < len; ++i) {
ans = ans*(CHAOS_ABCHASH_MAX_CODE+1) + CHAOS_ABCHASH_GET_CODE(str[i]);
}
return ans;
}
bool chaos_abchash_validate(const char* str, size_t len) {
for (size_t i = 0; i < len; ++i) {
if (CHAOS_ABCHASH_GET_CODE(str[i]) > CHAOS_ABCHASH_MAX_CODE) {
return false;
}
}
return len > 0;
}

19
util/chaos/abchash.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* DON'T EXPECT THIS ALGORITHM TO BE ONE-WAY */
uint64_t
chaos_abchash(
const char* str,
size_t len
);
bool
chaos_abchash_validate(
const char* str,
size_t len
);

19
util/chaos/xorshift.c Normal file
View File

@@ -0,0 +1,19 @@
#include "./xorshift.h"
#include <stddef.h>
#include <stdint.h>
uint64_t chaos_xorshift(uint64_t seed) {
seed = seed ^ (seed << 13);
seed = seed ^ (seed >> 7);
return seed ^ (seed << 17);
}
float chaos_xorshift_fract(uint64_t seed, uint64_t* next_seed) {
static const uint64_t period = 10000;
seed = chaos_xorshift(seed);
if (next_seed != NULL) *next_seed = seed;
return seed%period*1.f/period;
}

14
util/chaos/xorshift.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
uint64_t
chaos_xorshift(
uint64_t seed
);
float
chaos_xorshift_fract(
uint64_t seed,
uint64_t* next_seed /* NULLABLE */
);

View File

@@ -0,0 +1,7 @@
add_library(coly2d
hittest.c
shape.c
)
target_link_libraries(coly2d
math
)

4
util/coly2d/README.md Normal file
View File

@@ -0,0 +1,4 @@
coly
====
simplest 2D collision calculation library for C

171
util/coly2d/hittest.c Normal file
View File

@@ -0,0 +1,171 @@
#include "./hittest.h"
#include <assert.h>
#include <stdbool.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
bool coly2d_hittest_point_and_rect(
const vec2_t* pos1, const vec2_t* pos2, const vec2_t* size2) {
assert(vec2_valid(pos1));
assert(vec2_valid(pos2));
assert(vec2_valid(size2));
vec2_t rpos;
vec2_sub(&rpos, pos2, pos1);
return
MATH_ABS(rpos.x) < size2->x &&
MATH_ABS(rpos.y) < size2->y;
}
bool coly2d_hittest_point_and_triangle(
const vec2_t* pos1,
const vec2_t* pos2_a,
const vec2_t* pos2_b,
const vec2_t* pos2_c) {
assert(vec2_valid(pos1));
assert(vec2_valid(pos2_a));
assert(vec2_valid(pos2_b));
assert(vec2_valid(pos2_c));
vec2_t v1, v2;
vec2_sub(&v1, pos1, pos2_a);
vec2_sub(&v2, pos2_b, pos2_a);
float a = vec2_cross(&v1, &v2);
vec2_sub(&v1, pos1, pos2_b);
vec2_sub(&v2, pos2_c, pos2_b);
if (a * vec2_cross(&v1, &v2) <= 0) return false;
vec2_sub(&v1, pos1, pos2_c);
vec2_sub(&v2, pos2_a, pos2_c);
return a*vec2_cross(&v1, &v2) > 0;
}
bool coly2d_hittest_lineseg_and_lineseg(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2_st,
const vec2_t* pos2_ed) {
assert(vec2_valid(pos1_st));
assert(vec2_valid(pos1_ed));
assert(vec2_valid(pos2_st));
assert(vec2_valid(pos2_ed));
vec2_t x;
vec2_sub(&x, pos1_ed, pos1_st);
vec2_t a;
vec2_sub(&a, pos2_st, pos1_st);
vec2_t b;
vec2_sub(&b, pos2_ed, pos1_st);
vec2_t y;
vec2_sub(&y, pos2_ed, pos2_st);
vec2_t c;
vec2_sub(&c, pos1_st, pos2_st);
vec2_t d;
vec2_sub(&d, pos1_ed, pos2_st);
return
vec2_cross(&x, &a) * vec2_cross(&x, &b) < 0 &&
vec2_cross(&y, &c) * vec2_cross(&y, &d) < 0;
}
bool coly2d_hittest_lineseg_and_rect(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2,
const vec2_t* size2) {
assert(vec2_valid(pos1_st));
assert(vec2_valid(pos1_ed));
assert(vec2_valid(pos2));
assert(vec2_valid(size2));
if (coly2d_hittest_point_and_rect(pos1_st, pos2, size2) ||
coly2d_hittest_point_and_rect(pos1_ed, pos2, size2)) {
return true;
}
const float left = pos2->x - size2->x;
const float right = pos2->x + size2->x;
const float top = pos2->y + size2->y;
const float bottom = pos2->y - size2->y;
const vec2_t v1 = vec2(left, top);
const vec2_t v2 = vec2(left, bottom);
const vec2_t v3 = vec2(right, bottom);
const vec2_t v4 = vec2(right, top);
return
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v1, &v2) ||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v2, &v3) ||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v3, &v4) ||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, &v4, &v1);
}
bool coly2d_hittest_lineseg_and_ellipse(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2,
const vec2_t* size2) {
assert(vec2_valid(pos1_st));
assert(vec2_valid(pos1_ed));
assert(vec2_valid(pos2));
assert(vec2_valid(size2));
vec2_t p2 = *pos2;
p2.x /= size2->x;
p2.y /= size2->y;
vec2_t p1st = *pos1_st;
p1st.x /= size2->x;
p1st.y /= size2->y;
vec2_t c1;
vec2_sub(&c1, &p2, &p1st);
if (vec2_pow_length(&c1) < 1) return true;
vec2_t p1ed = *pos1_ed;
p1ed.x /= size2->x;
p1ed.y /= size2->y;
vec2_t c2;
vec2_sub(&c2, &p2, &p1ed);
if (vec2_pow_length(&c2) < 1) return true;
vec2_t ed;
vec2_sub(&ed, &p1ed, &p1st);
const float cross = vec2_cross(&ed, &c1);
if (MATH_ABS(cross) >= vec2_length(&ed)) return false;
vec2_t st;
vec2_sub(&st, &p1st, &p1ed);
return vec2_dot(&c1, &ed)*vec2_dot(&c2, &st) >= 0;
}
bool coly2d_hittest_lineseg_and_triangle(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2_a,
const vec2_t* pos2_b,
const vec2_t* pos2_c) {
assert(vec2_valid(pos1_st));
assert(vec2_valid(pos1_ed));
assert(vec2_valid(pos2_a));
assert(vec2_valid(pos2_b));
assert(vec2_valid(pos2_c));
/* TODO(catfoot): Tomas Moller's algorithm may make this function faster. */
if (coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_a, pos2_b) ||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_b, pos2_c) ||
coly2d_hittest_lineseg_and_lineseg(pos1_st, pos1_ed, pos2_c, pos2_a)) {
return true;
}
return
coly2d_hittest_point_and_triangle(pos1_st, pos2_a, pos2_b, pos2_c) ||
coly2d_hittest_point_and_triangle(pos1_ed, pos2_a, pos2_b, pos2_c);
}

53
util/coly2d/hittest.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <stdbool.h>
#include "util/math/vector.h"
bool
coly2d_hittest_point_and_rect(
const vec2_t* pos1,
const vec2_t* pos2,
const vec2_t* size2
);
bool
coly2d_hittest_point_and_triangle(
const vec2_t* pos1,
const vec2_t* pos2_a,
const vec2_t* pos2_b,
const vec2_t* pos2_c
);
bool
coly2d_hittest_lineseg_and_lineseg(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2_st,
const vec2_t* pos2_ed
);
bool
coly2d_hittest_lineseg_and_rect(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2,
const vec2_t* size2
);
bool
coly2d_hittest_lineseg_and_ellipse(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2,
const vec2_t* size2
);
bool
coly2d_hittest_lineseg_and_triangle(
const vec2_t* pos1_st,
const vec2_t* pos1_ed,
const vec2_t* pos2_a,
const vec2_t* pos2_b,
const vec2_t* pos2_c
);

54
util/coly2d/shape.c Normal file
View File

@@ -0,0 +1,54 @@
#include "./shape.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "./hittest.h"
bool coly2d_shape_valid(const coly2d_shape_t* shape) {
return
shape != NULL &&
vec2_valid(&shape->size) &&
MATH_FLOAT_VALID(shape->angle);
}
bool coly2d_shape_hittest_lineseg(
const coly2d_shape_t* shape, const vec2_t* st, const vec2_t* ed) {
assert(coly2d_shape_valid(shape));
assert(vec2_valid(st));
assert(vec2_valid(ed));
static const vec2_t origin = vec2(0, 0);
const float s = -sin(shape->angle);
const float c = cos(shape->angle);
const vec2_t st_ = vec2(st->x*c-st->y*s, st->x*s+st->y*c);
const vec2_t ed_ = vec2(ed->x*c-ed->y*s, ed->x*s+ed->y*c);
switch (shape->type) {
case COLY2D_SHAPE_TYPE_LINE:
return coly2d_hittest_lineseg_and_lineseg(
&st_, &ed_, &origin, &shape->size);
case COLY2D_SHAPE_TYPE_RECT:
return coly2d_hittest_lineseg_and_rect(
&st_, &ed_, &origin, &shape->size);
case COLY2D_SHAPE_TYPE_TRIANGLE:
return coly2d_hittest_lineseg_and_triangle(
&st_, &ed_,
&vec2( shape->size.x, 0),
&vec2(-shape->size.x, shape->size.y),
&vec2(-shape->size.x, -shape->size.y));
case COLY2D_SHAPE_TYPE_ELLIPSE:
return coly2d_hittest_lineseg_and_ellipse(
&st_, &ed_, &origin, &shape->size);
}
assert(false);
return false;
}

51
util/coly2d/shape.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <stdbool.h>
#include "util/math/vector.h"
typedef enum {
COLY2D_SHAPE_TYPE_LINE,
COLY2D_SHAPE_TYPE_RECT,
COLY2D_SHAPE_TYPE_TRIANGLE,
COLY2D_SHAPE_TYPE_ELLIPSE,
} coly2d_shape_type_t;
typedef struct {
coly2d_shape_type_t type;
vec2_t size;
float angle;
} coly2d_shape_t;
#define coly2d_shape_line(ed, a) ((coly2d_shape_t) { \
.type = COLY2D_SHAPE_TYPE_LINE, \
.size = ed, \
.angle = a, \
})
#define coly2d_shape_rect(sz, a) ((coly2d_shape_t) { \
.type = COLY2D_SHAPE_TYPE_RECT, \
.size = sz, \
.angle = a, \
})
#define coly2d_shape_triangle(sz, a) ((coly2d_shape_t) { \
.type = COLY2D_SHAPE_TYPE_TRIANGLE, \
.size = sz, \
.angle = a, \
})
#define coly2d_shape_ellipse(sz, a) ((coly2d_shape_t) { \
.type = COLY2D_SHAPE_TYPE_ELLIPSE, \
.size = sz, \
.angle = a, \
})
bool
coly2d_shape_valid(
const coly2d_shape_t* shape
);
bool
coly2d_shape_hittest_lineseg(
const coly2d_shape_t* shape,
const vec2_t* st,
const vec2_t* ed
);

View File

@@ -0,0 +1,11 @@
add_library(container
array.c
)
target_link_libraries(container memory)
if (BUILD_TESTING)
add_executable(container-test test.c)
target_link_libraries(container-test container)
add_test(container-test container-test)
endif()

90
util/container/array.c Normal file
View File

@@ -0,0 +1,90 @@
#include "./array.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "util/memory/memory.h"
typedef struct {
size_t length;
size_t reserved;
} container_array_header_t;
void container_array_delete(void* array) {
if (array == NULL) return;
memory_delete(array - sizeof(container_array_header_t));
}
void container_array_reserve_(void** array, size_t elmlen, size_t len) {
assert(array != NULL);
if (len == 0) return;
container_array_header_t* ptr;
if (*array == NULL) {
ptr = memory_new(sizeof(*ptr) + len*elmlen);
*ptr = (typeof(*ptr)) {
.length = 0,
.reserved = len,
};
} else {
ptr = *array - sizeof(*ptr);
if (ptr->reserved >= len) return;
ptr = memory_resize(ptr, sizeof(*ptr) + len*elmlen);
ptr->reserved = len;
}
*array = ptr + 1;
}
void container_array_resize_(void** array, size_t elmlen, size_t len) {
assert(array != NULL);
container_array_reserve_(array, elmlen, len);
if (*array == NULL) return;
container_array_header_t* ptr = *array - sizeof(*ptr);
ptr->length = len;
}
void container_array_insert_(void** array, size_t elmlen, size_t index) {
assert(array != NULL);
const size_t len = container_array_get_length(*array) + 1;
assert(index < len);
container_array_resize_(array, elmlen, len);
uint8_t* src = *array + (index)*elmlen;
memmove(src + elmlen, src, (len-index-1)*elmlen);
}
void container_array_remove_(void** array, size_t elmlen, size_t index) {
const size_t oldlen = container_array_get_length(*array);
assert(index < oldlen);
const size_t len = oldlen-1;
uint8_t* dst = *array + index*elmlen;
memmove(dst, dst + elmlen, (len-index)*elmlen);
container_array_resize_(array, elmlen, len);
}
void* container_array_duplicate_(const void* array, size_t elmlen) {
if (array == NULL) return NULL;
const container_array_header_t* ptr = array - sizeof(*ptr);
const size_t sz = sizeof(*ptr) + ptr->length*elmlen;
container_array_header_t* ptrdup = memory_new(sz);
memcpy(ptrdup, ptr, sz);
ptrdup->reserved = ptr->length;
return (void*) (ptrdup + 1);
}
size_t container_array_get_length_(const void* array) {
if (array == NULL) return 0;
const container_array_header_t* ptr = array - sizeof(*ptr);
return ptr->length;
}

31
util/container/array.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stddef.h>
#define CONTAINER_ARRAY
void container_array_delete(void* array);
#define container_array_reserve(array, len) \
container_array_reserve_((void**) &array, sizeof(*array), len)
void container_array_reserve_(void** array, size_t elmlen, size_t len);
#define container_array_resize(array, len) \
container_array_resize_((void**) &array, sizeof(*array), len)
void container_array_resize_(void** array, size_t elmlen, size_t len);
#define container_array_insert(array, index) \
container_array_insert_((void**) &array, sizeof(*array), index)
void container_array_insert_(void** array, size_t elmlen, size_t index);
#define container_array_remove(array, index) \
container_array_remove_((void**) &array, sizeof(*array), index)
void container_array_remove_(void** array, size_t elmlen, size_t index);
#define container_array_duplicate(array) \
(typeof(array)) container_array_duplicate_( \
(const void*) array, sizeof(*array))
void* container_array_duplicate_(const void* array, size_t elmlen);
#define container_array_get_length(array) container_array_get_length_(array)
size_t container_array_get_length_(const void* array);

48
util/container/test.c Normal file
View File

@@ -0,0 +1,48 @@
#undef NDEBUG
#include <assert.h>
#include <stdint.h>
#include "./array.h"
static void test_array() {
int32_t* numbers = NULL;
assert(container_array_get_length(numbers) == 0);
container_array_reserve(numbers, 10);
for (int32_t i = 50; i < 100; ++i) {
container_array_insert(numbers, i-50);
numbers[i-50] = i;
}
container_array_reserve(numbers, 10);
for (int32_t i = 0; i < 50; ++i) {
container_array_insert(numbers, i);
numbers[i] = i;
}
for (int32_t i = 0; i < 100; ++i) {
assert(numbers[i] == i);
}
for (int32_t i = 99; i >= 0; i-=2) {
container_array_remove(numbers, i);
}
for (int32_t i = 0; i < 100; i+=2) {
assert(numbers[i/2] == i);
}
assert(container_array_get_length(numbers) == 50);
container_array_resize(numbers, 25);
assert(container_array_get_length(numbers) == 25);
int32_t* dup = container_array_duplicate(numbers);
for (int32_t i = 0; i < 25; ++i) {
assert(numbers[i] == dup[i]);
}
container_array_delete(dup);
container_array_delete(numbers);
}
int main(void) {
test_array();
return 0;
}

3
util/conv/CMakeLists.txt Normal file
View File

@@ -0,0 +1,3 @@
add_library(conv
charcode.c
)

36
util/conv/charcode.c Normal file
View File

@@ -0,0 +1,36 @@
#include "./charcode.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
size_t conv_charcode_utf8_to_utf32(uint32_t* c, const char* s, size_t len) {
assert(c != NULL);
assert(s != NULL);
if ((*s & 0x80) == 0x00) {
if (len < 1) return 0;
*c = s[0];
return 1;
}
if ((*s & 0xE0) == 0xC0) {
if (len < 2) return 0;
*c = (s[0] & 0x1F) << 6 | (s[1] & 0x3F);
return 2;
}
if ((*s & 0xF0) == 0xE0) {
if (len < 3) return 0;
*c = (s[0] & 0x0F) << 12 | (s[1] & 0x3F) << 6 | (s[2] & 0x3F);
return 3;
}
if ((*s & 0xF8) == 0xF0) {
if (len < 4) return 0;
*c =
(s[0] & 0x07) << 18 |
(s[1] & 0x3F) << 12 |
(s[2] & 0x3F) << 6 |
(s[3] & 0x3F);
return 4;
}
return 0;
}

11
util/conv/charcode.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
size_t
conv_charcode_utf8_to_utf32(
uint32_t* c,
const char* s,
size_t len
);

View File

@@ -0,0 +1,11 @@
add_library(dictres
dictres.c
)
target_link_libraries(dictres
chaos
)
if (BUILD_TESTING)
add_executable(dictres-test test.c)
target_link_libraries(dictres-test dictres)
endif()

87
util/dictres/dictres.c Normal file
View File

@@ -0,0 +1,87 @@
#include "./dictres.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "util/chaos/abchash.h"
static void dictres_calculate_hash_(dictres_item_t* items, size_t length) {
assert(items != NULL || length == 0);
for (size_t i = 0; i < length; ++i) {
dictres_item_t* item = &items[i];
const size_t len = strlen(item->key);
if (!chaos_abchash_validate(item->key, len)) {
fprintf(stderr, "dictress: invalid key, '%s'\n", item->key);
abort();
}
item->hash = chaos_abchash(item->key, len);
}
}
static void dictres_sort_(dictres_item_t* items, size_t length) {
assert(items != NULL || length == 0);
for (size_t i = 0; i < length; ++i) {
for (size_t j = 1; j < length-i; ++j) {
if (items[j-1].hash > items[j].hash) {
/* Speed of swapping can be improved but simple is best. :) */
const dictres_item_t temp = items[j-1];
items[j-1] = items[j];
items[j] = temp;
}
}
}
}
static void dictres_validate_(dictres_item_t* items, size_t length) {
assert(items != NULL || length == 0);
for (size_t i = 1; i < length; ++i) {
if (items[i-1].hash == items[i].hash) {
fprintf(stderr,
"dictres: detected hash collision between '%s' and '%s'\n",
items[i-1].key, items[i].key);
}
}
}
void dictres_optimize_(dictres_item_t* items, size_t length) {
assert(items != NULL || length == 0);
dictres_calculate_hash_(items, length);
dictres_sort_(items, length);
dictres_validate_(items, length);
}
const char* dictres_find_(
const dictres_item_t* items, size_t length, const char* key) {
assert(items != NULL || length == 0);
const size_t keylen = strlen(key);
assert(chaos_abchash_validate(key, keylen));
const uint64_t hash = chaos_abchash(key, keylen);
size_t l = 0, r = length-1;
while (l <= r && r < length) {
const size_t i = (l+r)/2;
if (items[i].hash == hash) {
return items[i].value;
}
if (items[i].hash < hash) {
l = i+1;
} else {
if (i == 0) break;
r = i-1;
}
}
fprintf(stderr, "dictres: cannot find key, '%s'\n", key);
abort();
}

27
util/dictres/dictres.h Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct {
const char* key;
const char* value;
uint64_t hash;
} dictres_item_t;
void
dictres_optimize_(
dictres_item_t* items,
size_t length
);
#define dictres_optimize(items) \
dictres_optimize_(items, sizeof(items)/sizeof(items[0]))
const char*
dictres_find_(
const dictres_item_t* items,
size_t length,
const char* key
);
#define dictres_find(items, key) \
dictres_find_(items, sizeof(items)/sizeof(items[0]), key)

27
util/dictres/test.c Normal file
View File

@@ -0,0 +1,27 @@
#undef NDEBUG
#include <stdio.h>
#include <stdlib.h>
#include "./dictres.h"
static dictres_item_t dictres_[] = {
{"app_name",
"dictres-test"},
{"description",
"dictres provides a static dictionary type which finds value from key fast."},
{"bye",
"see you!"},
};
int main(void) {
dictres_optimize(dictres_);
printf("%s\n", dictres_find(dictres_, "app_name"));
printf("====\n");
printf("%s\n", dictres_find(dictres_, "description"));
printf("%s\n", dictres_find(dictres_, "bye"));
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,14 @@
add_library(gleasy
atlas.c
framebuffer.c
program.c
shader.c
)
target_link_libraries(gleasy
GLEW::GLEW
OpenGL::GL
container
math
memory
)

194
util/gleasy/atlas.c Normal file
View File

@@ -0,0 +1,194 @@
#include "./atlas.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <GL/glew.h>
#include "util/container/array.h"
#include "util/math/algorithm.h"
#include "util/memory/memory.h"
#include "./misc.h"
#include "./texture.h"
struct gleasy_atlas_t {
int32_t width;
int32_t height;
int32_t consumed_y;
int32_t consumed_x;
int32_t line_height;
gleasy_texture_2d_t tex;
CONTAINER_ARRAY uint8_t* resize_buffer;
};
static void gleasy_atlas_resize_bitmap_(
gleasy_atlas_t* atlas,
gleasy_atlas_bitmap_t* out,
const gleasy_atlas_bitmap_t* in) {
assert(atlas != NULL);
assert(out != NULL);
assert(in != NULL);
*out = (typeof(*out)) {
.width = math_int32_next_power2(MATH_MAX(in->width, 4)),
.height = math_int32_next_power2(MATH_MAX(in->height, 4)),
.format = in->format,
.type = in->type,
.buffer = in->buffer,
};
if (out->width == in->width && out->height == in->height) return;
const size_t type = GLEASY_GET_BYTE_SIZE_OF_TYPE(out->type);
const size_t fmt = GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(out->format);
const size_t pixel = type*fmt;
assert(pixel > 0);
container_array_resize(atlas->resize_buffer, out->width*out->height*pixel);
out->buffer = atlas->resize_buffer;
const int32_t ymax = out->height * pixel;
const int32_t xmax = out->width * pixel;
const uint8_t* src = in->buffer;
uint8_t* dst = atlas->resize_buffer;
for (int32_t y = 0; y < ymax; ++y) {
for (int32_t x = 0; x < xmax; ++x) {
if (x < in->width && y < in->height) {
*dst = *(src++);
} else {
*dst = 0;
}
++dst;
}
}
}
static bool gleasy_atlas_allocate_area_(
gleasy_atlas_t* atlas,
int32_t* x,
int32_t* y,
int32_t width,
int32_t height,
int32_t actual_width,
int32_t actual_height) {
assert(atlas != NULL);
assert(x != NULL);
assert(y != NULL);
assert(width > 0);
assert(height > 0);
assert(actual_width > 0);
assert(actual_height > 0);
if (atlas->consumed_x + actual_width > atlas->width) {
atlas->consumed_x = 0;
atlas->consumed_y += atlas->line_height+1;
atlas->line_height = 0;
}
if (atlas->consumed_y + actual_height > atlas->height) {
return false;
}
*x = atlas->consumed_x;
*y = atlas->consumed_y;
atlas->consumed_x += width+1;
atlas->line_height = MATH_MAX(atlas->line_height, height);
return true;
}
gleasy_atlas_t* gleasy_atlas_new(
GLenum format, int32_t width, int32_t height, bool aa) {
assert(width > 0);
assert(height > 0);
gleasy_atlas_t* atlas = memory_new(sizeof(*atlas));
*atlas = (typeof(*atlas)) {
.width = math_int32_next_power2(width),
.height = math_int32_next_power2(height),
};
glGenTextures(1, &atlas->tex);
glBindTexture(GL_TEXTURE_2D, atlas->tex);
const GLenum filter = aa? GL_LINEAR: GL_NEAREST;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexImage2D(GL_TEXTURE_2D, 0, format,
atlas->width, atlas->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
assert(glGetError() == GL_NO_ERROR);
container_array_reserve(atlas->resize_buffer, width*height*4);
return atlas;
}
void gleasy_atlas_delete(gleasy_atlas_t* atlas) {
if (atlas == NULL) return;
glDeleteTextures(1, &atlas->tex);
container_array_delete(atlas->resize_buffer);
memory_delete(atlas);
}
void gleasy_atlas_clear(gleasy_atlas_t* atlas) {
assert(atlas != NULL);
atlas->consumed_y = 0;
atlas->consumed_x = 0;
atlas->line_height = 0;
}
bool gleasy_atlas_add(
gleasy_atlas_t* atlas,
gleasy_atlas_geometry_t* geo,
const gleasy_atlas_bitmap_t* bitmap) {
assert(atlas != NULL);
assert(geo != NULL);
assert(bitmap != NULL);
gleasy_atlas_bitmap_t resized;
gleasy_atlas_resize_bitmap_(atlas, &resized, bitmap);
int32_t x, y;
if (!gleasy_atlas_allocate_area_(atlas,
&x, &y, bitmap->width, bitmap->height, resized.width, resized.height)) {
return false;
}
*geo = (typeof(*geo)) {
.left = x*1.0f / atlas->width,
.right = (x+bitmap->width)*1.0f / atlas->width,
.top = y*1.0f / atlas->height,
.bottom = (y+bitmap->height)*1.0f / atlas->height,
};
glBindTexture(GL_TEXTURE_2D, atlas->tex);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y,
resized.width,
resized.height,
resized.format,
resized.type,
resized.buffer);
assert(glGetError() == GL_NO_ERROR);
return true;
}
gleasy_texture_2d_t gleasy_atlas_get_texture(const gleasy_atlas_t* atlas) {
assert(atlas != NULL);
return atlas->tex;
}

56
util/gleasy/atlas.h Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <GL/glew.h>
#include "./texture.h"
struct gleasy_atlas_t;
typedef struct gleasy_atlas_t gleasy_atlas_t;
typedef struct {
float left;
float right;
float top;
float bottom;
} gleasy_atlas_geometry_t;
typedef struct {
int32_t width;
int32_t height;
GLenum format;
GLenum type;
const uint8_t* buffer;
} gleasy_atlas_bitmap_t;
gleasy_atlas_t*
gleasy_atlas_new(
GLenum format,
int32_t width,
int32_t height,
bool aa
);
void
gleasy_atlas_delete(
gleasy_atlas_t* atlas
);
void
gleasy_atlas_clear(
gleasy_atlas_t* atlas
);
bool
gleasy_atlas_add(
gleasy_atlas_t* atlas,
gleasy_atlas_geometry_t* geo,
const gleasy_atlas_bitmap_t* bitmap
);
gleasy_texture_2d_t
gleasy_atlas_get_texture(
const gleasy_atlas_t* atlas
);

7
util/gleasy/buffer.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
#include <GL/glew.h>
typedef GLuint gleasy_buffer_array_t;
typedef GLuint gleasy_buffer_element_array_t;
typedef GLuint gleasy_buffer_uniform_t;

96
util/gleasy/framebuffer.c Normal file
View File

@@ -0,0 +1,96 @@
#include "./framebuffer.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include "./texture.h"
void gleasy_framebuffer_initialize(
gleasy_framebuffer_t* fb, int32_t width, int32_t height, int32_t samples) {
assert(fb != NULL);
assert(width > 0);
assert(height > 0);
assert(0 < samples && samples < GL_MAX_SAMPLES);
*fb = (typeof(*fb)) {
.width = width,
.height = height,
};
glGenRenderbuffers(1, &fb->colorbuf_msaa);
glBindRenderbuffer(GL_RENDERBUFFER, fb->colorbuf_msaa);
glRenderbufferStorageMultisample(
GL_RENDERBUFFER, samples, GL_RGBA, width, height);
assert(glGetError() == GL_NO_ERROR);
glGenTextures(1, &fb->colorbuf);
glBindTexture(GL_TEXTURE_2D, fb->colorbuf);
glTexImage2D(GL_TEXTURE_2D,
0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
assert(glGetError() == GL_NO_ERROR);
GLuint fbo[2];
glGenFramebuffers(2, fbo);
fb->id_msaa = fbo[0];
fb->id = fbo[1];
glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa);
glFramebufferRenderbuffer(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb->colorbuf_msaa);
assert(glGetError() == GL_NO_ERROR);
glBindFramebuffer(GL_FRAMEBUFFER, fb->id);
glFramebufferTexture2D(GL_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->colorbuf, 0);
assert(glGetError() == GL_NO_ERROR);
}
void gleasy_framebuffer_deinitialize(gleasy_framebuffer_t* fb) {
assert(fb != NULL);
GLuint fbo[] = {fb->id_msaa, fb->id};
glDeleteFramebuffers(2, fbo);
glDeleteTextures(1, &fb->colorbuf);
glDeleteRenderbuffers(1, &fb->colorbuf_msaa);
}
void gleasy_framebuffer_bind(const gleasy_framebuffer_t* fb) {
assert(fb != NULL);
glBindFramebuffer(GL_FRAMEBUFFER, fb->id_msaa);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
fprintf(stderr, "gleasy: framebuffer is in invalid state\n");
abort();
}
}
void gleasy_framebuffer_flush(const gleasy_framebuffer_t* fb) {
assert(fb != NULL);
gleasy_framebuffer_flush_to_other(fb, fb->id);
}
void gleasy_framebuffer_flush_to_other(const gleasy_framebuffer_t* fb, GLuint id) {
assert(fb != NULL);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, id);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fb->id_msaa);
glBlitFramebuffer(
0, 0, fb->width, fb->height,
0, 0, fb->width, fb->height,
GL_COLOR_BUFFER_BIT,
GL_NEAREST);
assert(glGetError() == GL_NO_ERROR);
}

47
util/gleasy/framebuffer.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include <stdint.h>
#include <GL/glew.h>
#include "./texture.h"
typedef struct {
int32_t width;
int32_t height;
GLuint id_msaa;
GLuint colorbuf_msaa;
GLuint id;
gleasy_texture_2d_t colorbuf;
} gleasy_framebuffer_t;
void
gleasy_framebuffer_initialize(
gleasy_framebuffer_t* fb,
int32_t width,
int32_t height,
int32_t samples
);
void
gleasy_framebuffer_deinitialize(
gleasy_framebuffer_t* fb
);
void
gleasy_framebuffer_bind(
const gleasy_framebuffer_t* fb
);
void
gleasy_framebuffer_flush(
const gleasy_framebuffer_t* fb
);
void
gleasy_framebuffer_flush_to_other(
const gleasy_framebuffer_t* fb,
GLuint id
);

18
util/gleasy/misc.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <GL/glew.h>
#define GLEASY_GET_BYTE_SIZE_OF_TYPE(t) ( \
(t) == GL_UNSIGNED_BYTE? 1: \
(t) == GL_BYTE? 1: \
(t) == GL_SHORT? 2: \
(t) == GL_UNSIGNED_SHORT? 2: \
(t) == GL_INT? 4: \
(t) == GL_UNSIGNED_INT? 4: \
(t) == GL_FLOAT? 4: 0)
#define GLEASY_GET_CHANNELS_OF_TEXTURE_FORMAT(f) ( \
(f) == GL_RGBA? 4: \
(f) == GL_RGB? 3: \
(f) == GL_RG? 2: \
(f) == GL_RED? 1: 0)

66
util/gleasy/program.c Normal file
View File

@@ -0,0 +1,66 @@
#include "./program.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include "util/math/algorithm.h"
#include "./shader.h"
gleasy_program_t gleasy_program_new(
const char* header, size_t header_len,
const char* vsrc, size_t vsrc_len,
const char* fsrc, size_t fsrc_len) {
assert(header != NULL || header_len == 0);
assert(vsrc != NULL || vsrc_len == 0);
assert(fsrc != NULL || fsrc_len == 0);
gleasy_program_t program = glCreateProgram();
if (program == 0) {
fprintf(stderr, "failed to create program");
abort();
}
const gleasy_shader_t vshader =
gleasy_shader_new(GL_VERTEX_SHADER, header, header_len, vsrc, vsrc_len);
const gleasy_shader_t fshader =
gleasy_shader_new(GL_FRAGMENT_SHADER, header, header_len, fsrc, fsrc_len);
glAttachShader(program, vshader);
glDeleteShader(vshader);
glAttachShader(program, fshader);
glDeleteShader(fshader);
glLinkProgram(program);
GLint ok;
glGetProgramiv(program, GL_LINK_STATUS, &ok);
if (ok == GL_FALSE) {
char log[1024];
const int len =
gleasy_program_get_log(program, log, sizeof(log)/sizeof(log[0]));
fprintf(stderr, "failed to link program\n%.*s\n", len, log);
abort();
}
return program;
}
size_t gleasy_program_get_log(
gleasy_program_t program, char* dst, size_t maxlen) {
assert(program != 0);
GLint len;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &len);
if (dst == NULL) return len;
len = MATH_MIN(len, (GLint) maxlen);
if (len == 0) return 0;
glGetProgramInfoLog(program, len, &len, (GLchar*) dst);
return len;
}

24
util/gleasy/program.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <stddef.h>
#include <GL/glew.h>
typedef GLuint gleasy_program_t;
gleasy_program_t /* OWNERSHIP */
gleasy_program_new(
const char* header,
size_t header_len,
const char* vsrc,
size_t vsrc_len,
const char* fsrc,
size_t fsrc_len
);
size_t
gleasy_program_get_log(
gleasy_program_t program,
char* dst, /* when NULL, returns actual size */
size_t maxlen
);

57
util/gleasy/shader.c Normal file
View File

@@ -0,0 +1,57 @@
#include "./shader.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <GL/glew.h>
#include "util/math/algorithm.h"
gleasy_shader_t gleasy_shader_new(
GLenum type,
const char* header, size_t header_len,
const char* src, size_t src_len) {
assert(header != NULL || header_len == 0);
assert(src != NULL || src_len == 0);
const GLuint shader = glCreateShader(type);
if (shader == 0) {
fprintf(stderr, "failed to create shader\n");
abort();
}
const GLchar* srcs[] = { header, src, };
const GLint lens[] = { header_len, src_len, };
const size_t offset = (header_len == 0? 1: 0);
glShaderSource(shader, 2-offset, srcs+offset, lens+offset);
glCompileShader(shader);
GLint ok;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (ok == GL_FALSE) {
char log[1024];
const int loglen =
gleasy_shader_get_log(shader, log, sizeof(log)/sizeof(log[0]));
fprintf(stderr, "failed to compile shader\n%.*s\n", loglen, log);
abort();
}
return shader;
}
size_t gleasy_shader_get_log(
gleasy_shader_t shader, char* dst, size_t maxlen) {
assert(shader != 0);
GLint len;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
if (dst == NULL) return len;
len = MATH_MIN(len, (GLint) maxlen);
if (len == 0) return 0;
glGetShaderInfoLog(shader, len, &len, (GLchar*) dst);
return len;
}

23
util/gleasy/shader.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
#include <GL/glew.h>
typedef GLuint gleasy_shader_t;
gleasy_shader_t /* OWNERSHIP */
gleasy_shader_new(
GLenum type,
const char* header,
size_t header_len,
const char* src,
size_t src_len
);
size_t
gleasy_shader_get_log(
gleasy_shader_t shader,
char* dst, /* when NULL, returns actual size */
size_t maxlen
);

5
util/gleasy/texture.h Normal file
View File

@@ -0,0 +1,5 @@
#pragma once
#include <GL/glew.h>
typedef GLuint gleasy_texture_2d_t;

View File

@@ -0,0 +1,33 @@
add_library(glyphas
aligner.c
block.c
cache.c
context.c
drawer.c
face.c
glyph.c
)
target_any_sources(glyphas
drawer.vshader
drawer.fshader
)
target_link_libraries(glyphas
Freetype::Freetype
GLEW::GLEW
OpenGL::GL
chaos
container
conv
gleasy
math
memory
)
if (BUILD_TESTING)
add_executable(glyphas-test test.c)
target_link_libraries(glyphas-test
SDL2::SDL2
glyphas)
endif()

4
util/glyphas/README.md Normal file
View File

@@ -0,0 +1,4 @@
glyphas
====
GLYPH texture atlAS library wrapping FreeType

78
util/glyphas/aligner.c Normal file
View File

@@ -0,0 +1,78 @@
#include "./aligner.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "util/gleasy/atlas.h"
#include "util/math/algorithm.h"
#include "./cache.h"
void glyphas_aligner_initialize(
glyphas_aligner_t* aligner,
glyphas_aligner_direction_t dir,
int32_t lineheight,
int32_t maxpos) {
assert(aligner != NULL);
assert(maxpos > 0);
*aligner = (typeof(*aligner)) {
.dir = dir,
.lineheight = lineheight,
.maxpos = maxpos,
.line = lineheight,
};
}
void glyphas_aligner_deinitialize(glyphas_aligner_t* aligner) {
assert(aligner != NULL);
}
void glyphas_aligner_reset(glyphas_aligner_t* aligner) {
assert(aligner != NULL);
aligner->line = aligner->lineheight;
aligner->pos = 0;
}
void glyphas_aligner_push_character(
glyphas_aligner_t* aligner,
int32_t* x,
int32_t* y,
const glyphas_cache_glyph_t* g) {
assert(aligner != NULL);
assert(x != NULL);
assert(y != NULL);
assert(g != NULL);
switch (aligner->dir) {
case GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL:
if (aligner->pos + g->hmetrics.advance > aligner->maxpos) {
glyphas_aligner_break_line(aligner);
}
*x = aligner->pos + g->hmetrics.bear_x;
*y = g->hmetrics.bear_y + aligner->line;
aligner->pos += g->hmetrics.advance;
break;
case GLYPHAS_ALIGNER_DIRECTION_VERTICAL:
if (aligner->pos - g->vmetrics.advance < -aligner->maxpos) {
glyphas_aligner_break_line(aligner);
}
*x = aligner->pos - g->vmetrics.bear_x + aligner->line;
*y = -g->vmetrics.bear_y;
aligner->pos -= g->vmetrics.advance;
break;
default:
assert(false);
}
}
void glyphas_aligner_break_line(glyphas_aligner_t* aligner) {
assert(aligner != NULL);
aligner->pos = 0;
aligner->line += aligner->lineheight;
}

52
util/glyphas/aligner.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "./cache.h"
typedef enum {
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL,
GLYPHAS_ALIGNER_DIRECTION_VERTICAL,
} glyphas_aligner_direction_t;
typedef struct {
glyphas_aligner_direction_t dir;
int32_t lineheight;
int32_t maxpos;
int32_t pos;
int32_t line;
} glyphas_aligner_t;
void
glyphas_aligner_initialize(
glyphas_aligner_t* aligner,
glyphas_aligner_direction_t dir,
int32_t lineheight,
int32_t maxpos
);
void
glyphas_aligner_deinitialize(
glyphas_aligner_t* aligner
);
void
glyphas_aligner_reset(
glyphas_aligner_t* aligner
);
void
glyphas_aligner_push_character(
glyphas_aligner_t* aligner,
int32_t* x,
int32_t* y,
const glyphas_cache_glyph_t* g
);
void
glyphas_aligner_break_line(
glyphas_aligner_t* aligner
);

216
util/glyphas/block.c Normal file
View File

@@ -0,0 +1,216 @@
#include "./block.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/chaos/xorshift.h"
#include "util/conv/charcode.h"
#include "util/math/algorithm.h"
#include "util/math/vector.h"
#include "util/gleasy/atlas.h"
#include "util/memory/memory.h"
#include "./cache.h"
struct glyphas_block_t {
glyphas_aligner_t aligner;
size_t reserve;
size_t length;
glyphas_block_item_t items[1];
};
glyphas_block_t* glyphas_block_new(
glyphas_aligner_direction_t dir,
int32_t lineheight,
int32_t maxpos,
size_t reserve) {
assert(maxpos > 0);
assert(reserve > 0);
glyphas_block_t* block =
memory_new(sizeof(*block) + (reserve-1)*sizeof(block->items[0]));
*block = (typeof(*block)) {
.reserve = reserve,
};
glyphas_aligner_initialize(&block->aligner, dir, lineheight, maxpos);
return block;
}
void glyphas_block_delete(glyphas_block_t* block) {
if (block == NULL) return;
glyphas_aligner_deinitialize(&block->aligner);
memory_delete(block);
}
void glyphas_block_clear(glyphas_block_t* block) {
assert(block != NULL);
glyphas_aligner_reset(&block->aligner);
block->length = 0;
}
void glyphas_block_add_character(
glyphas_block_t* block,
const glyphas_cache_glyph_t* glyph,
const vec4_t* color) {
assert(block != NULL);
assert(glyph != NULL);
assert(vec4_valid(color));
if (block->length >= block->reserve) {
fprintf(stderr, "glyphas block overflow\n");
abort();
}
int32_t x, y;
glyphas_aligner_push_character(&block->aligner, &x, &y, glyph);
if (glyph->geometry.left >= glyph->geometry.right) return;
block->items[block->length++] = (glyphas_block_item_t) {
.pos = vec2(x, y),
.size = vec2(glyph->width, glyph->height),
.color = *color,
.uv = glyph->geometry,
};
}
void glyphas_block_add_characters(
glyphas_block_t* block,
glyphas_cache_t* cache,
const vec4_t* color,
const char* str,
size_t len) {
assert(block != NULL);
assert(cache != NULL);
assert(vec4_valid(color));
const char* end = str + len;
while (str < end) {
uint32_t c;
const size_t consumed = conv_charcode_utf8_to_utf32(&c, str, end-str);
str += MATH_MAX(consumed, 1);
if (consumed == 0) continue;
if (c == '\n') {
glyphas_block_break_line(block);
continue;
}
const glyphas_cache_glyph_t* g = glyphas_cache_add_glyph(cache, c);
if (g == NULL) continue;
glyphas_block_add_character(block, g, color);
}
}
void glyphas_block_break_line(glyphas_block_t* block) {
assert(block != NULL);
glyphas_aligner_break_line(&block->aligner);
}
void glyphas_block_scale(glyphas_block_t* block, const vec2_t* s) {
assert(block != NULL);
assert(vec2_valid(s));
for (size_t i = 0; i < block->length; ++i) {
vec2_t* pos = &block->items[i].pos;
vec2_t* sz = &block->items[i].size;
pos->x *= s->x;
pos->y *= s->y;
sz->x *= s->x;
sz->y *= s->y;
}
}
void glyphas_block_translate(glyphas_block_t* block, const vec2_t* v) {
assert(block != NULL);
assert(vec2_valid(v));
for (size_t i = 0; i < block->length; ++i) {
vec2_addeq(&block->items[i].pos, v);
}
}
void glyphas_block_set_origin(glyphas_block_t* block, const vec2_t* r) {
assert(block !=NULL);
assert(vec2_valid(r));
vec2_t offset, size;
glyphas_block_calculate_geometry(block, &offset, &size);
size.x *= r->x;
size.y *= r->y;
vec2_addeq(&offset, &size);
vec2_muleq(&offset, -1);
glyphas_block_translate(block, &offset);
}
void glyphas_block_set_alpha(glyphas_block_t* block, float a) {
assert(block != NULL);
for (size_t i = 0; i < block->length; ++i) {
block->items[i].color.w = a;
}
}
void glyphas_block_make_glitched(glyphas_block_t* block, uint64_t seed) {
assert(block != NULL);
# define randf() (seed = chaos_xorshift(seed), seed%1000/1000.f)
for (size_t i = 0; i < block->length; ++i) {
const vec2_t* sz = &block->items[i].size;
const vec2_t shift = vec2((randf()-.5f)*sz->x*.5f, (randf()-.5f)*sz->y*.5f);
const float scale = randf()*.4f + .8f;
vec2_addeq(&block->items[i].pos, &shift);
vec2_muleq(&block->items[i].size, scale);
}
# undef randf
}
void glyphas_block_calculate_geometry(
const glyphas_block_t* block, vec2_t* offset, vec2_t* size) {
assert(block != NULL);
assert(offset != NULL);
assert(size != NULL);
vec2_t lb = vec2(0, 0), rt = vec2(0, 0);
for (size_t i = 0; i < block->length; ++i) {
const glyphas_block_item_t* item = &block->items[i];
const float l = item->pos.x;
const float r = l + item->size.x;
const float t = item->pos.y;
const float b = t - item->size.y;
lb.x = MATH_MIN(lb.x, l);
lb.y = MATH_MIN(lb.y, b);
rt.x = MATH_MAX(rt.x, r);
rt.y = MATH_MAX(rt.y, t);
}
*offset = vec2(lb.x, rt.y);
vec2_sub(size, &rt, &lb);
}
const glyphas_block_item_t* glyphas_block_get_items(
const glyphas_block_t* block, size_t* len) {
assert(block != NULL);
assert(len != NULL);
*len = block->length;
return block->items;
}

102
util/glyphas/block.h Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "util/math/vector.h"
#include "util/gleasy/atlas.h"
#include "./aligner.h"
#include "./cache.h"
struct glyphas_block_t;
typedef struct glyphas_block_t glyphas_block_t;
typedef struct {
vec2_t pos;
vec2_t size;
vec4_t color;
gleasy_atlas_geometry_t uv;
} glyphas_block_item_t;
glyphas_block_t*
glyphas_block_new(
glyphas_aligner_direction_t dir,
int32_t lineheight,
int32_t maxpos,
size_t reserve
);
void
glyphas_block_delete(
glyphas_block_t* block
);
void
glyphas_block_clear(
glyphas_block_t* block
);
void
glyphas_block_add_character(
glyphas_block_t* block,
const glyphas_cache_glyph_t* glyph,
const vec4_t* color
);
void
glyphas_block_add_characters(
glyphas_block_t* block,
glyphas_cache_t* cache,
const vec4_t* color,
const char* str,
size_t len
);
void
glyphas_block_break_line(
glyphas_block_t* block
);
void
glyphas_block_scale(
glyphas_block_t* block,
const vec2_t* translation
);
void
glyphas_block_translate(
glyphas_block_t* block,
const vec2_t* translation
);
void
glyphas_block_set_origin(
glyphas_block_t* block,
const vec2_t* r
);
void
glyphas_block_set_alpha(
glyphas_block_t* block,
float a
);
void
glyphas_block_make_glitched(
glyphas_block_t* block,
uint64_t seed
);
void
glyphas_block_calculate_geometry(
const glyphas_block_t* block,
vec2_t* offset,
vec2_t* size
);
const glyphas_block_item_t*
glyphas_block_get_items(
const glyphas_block_t* block,
size_t* len
);

111
util/glyphas/cache.c Normal file
View File

@@ -0,0 +1,111 @@
#include "./cache.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <GL/glew.h>
#include "util/container/array.h"
#include "util/gleasy/atlas.h"
#include "util/memory/memory.h"
#include "./face.h"
struct glyphas_cache_t {
gleasy_atlas_t* atlas;
glyphas_face_t* face;
int32_t char_width;
int32_t char_height;
/* TODO(catfoot): linear search isn't efficient. :( */
CONTAINER_ARRAY glyphas_cache_glyph_t* items;
};
glyphas_cache_t* glyphas_cache_new(
gleasy_atlas_t* atlas,
glyphas_face_t* face,
int32_t char_width,
int32_t char_height) {
assert(atlas != NULL);
assert(face != NULL);
assert(char_width > 0);
assert(char_height > 0);
glyphas_cache_t* cache = memory_new(sizeof(*cache));
*cache = (typeof(*cache)) {
.atlas = atlas,
.face = face,
.char_width = char_width,
.char_height = char_height,
};
return cache;
}
void glyphas_cache_delete(glyphas_cache_t* cache) {
if (cache == NULL) return;
container_array_delete(cache->items);
memory_delete(cache);
}
const glyphas_cache_glyph_t* glyphas_cache_add_glyph(
glyphas_cache_t* cache, uint32_t unicode) {
assert(cache != NULL);
const glyphas_cache_glyph_t* found =
glyphas_cache_lookup_glyph(cache, unicode);
if (found != NULL) return found;
if (!glyphas_face_set_pixel_size(
cache->face, cache->char_width, cache->char_height)) {
return NULL;
}
if (!glyphas_face_render_glyph(cache->face, unicode)) {
return NULL;
}
gleasy_atlas_geometry_t geo = {0};
const glyphas_glyph_t* rendered = &cache->face->glyph;
if (rendered->bitmap.width > 0) {
const gleasy_atlas_bitmap_t bmp = {
.width = rendered->bitmap.width,
.height = rendered->bitmap.height,
.buffer = rendered->bitmap.buffer,
.format = GL_RED,
.type = GL_UNSIGNED_BYTE,
};
if (!gleasy_atlas_add(cache->atlas, &geo, &bmp)) {
return NULL;
}
}
const size_t index = container_array_get_length(cache->items);
container_array_insert(cache->items, index);
glyphas_cache_glyph_t* g = &cache->items[index];
*g = (typeof(*g)) {
.unicode = unicode,
.width = rendered->bitmap.width,
.height = rendered->bitmap.height,
.geometry = geo,
.hmetrics = rendered->hmetrics,
.vmetrics = rendered->vmetrics,
};
return g;
}
const glyphas_cache_glyph_t* glyphas_cache_lookup_glyph(
const glyphas_cache_t* cache, uint32_t unicode) {
assert(cache != NULL);
const size_t len = container_array_get_length(cache->items);
for (size_t i = 0; i < len; ++i) {
const glyphas_cache_glyph_t* g = &cache->items[i];
if (g->unicode == unicode) return g;
}
return NULL;
}

47
util/glyphas/cache.h Normal file
View File

@@ -0,0 +1,47 @@
#pragma once
#include <stdint.h>
#include "util/gleasy/atlas.h"
#include "./face.h"
#include "./glyph.h"
typedef struct {
uint32_t unicode;
int32_t width;
int32_t height;
gleasy_atlas_geometry_t geometry;
glyphas_glyph_metrics_t hmetrics;
glyphas_glyph_metrics_t vmetrics;
} glyphas_cache_glyph_t;
struct glyphas_cache_t;
typedef struct glyphas_cache_t glyphas_cache_t;
glyphas_cache_t*
glyphas_cache_new(
gleasy_atlas_t* atlas,
glyphas_face_t* face,
int32_t char_width,
int32_t char_height
);
void
glyphas_cache_delete(
glyphas_cache_t* cache
);
const glyphas_cache_glyph_t* /* NULLABLE */
glyphas_cache_add_glyph(
glyphas_cache_t* cache,
uint32_t unicode
);
const glyphas_cache_glyph_t* /* NULLABLE */
glyphas_cache_lookup_glyph(
const glyphas_cache_t* cache,
uint32_t unicode
);

21
util/glyphas/context.c Normal file
View File

@@ -0,0 +1,21 @@
#include "./context.h"
#include <assert.h>
#include <stddef.h>
#include <ft2build.h>
#include FT_FREETYPE_H
void glyphas_context_initialize(glyphas_context_t* ctx) {
assert(ctx != NULL);
*ctx = (typeof(*ctx)) {0};
FT_Init_FreeType(&ctx->ft);
}
void glyphas_context_deinitialize(glyphas_context_t* ctx) {
assert(ctx != NULL);
FT_Done_FreeType(ctx->ft);
}

18
util/glyphas/context.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
typedef struct {
FT_Library ft;
} glyphas_context_t;
void
glyphas_context_initialize(
glyphas_context_t* ctx
);
void
glyphas_context_deinitialize(
glyphas_context_t* ctx
);

164
util/glyphas/drawer.c Normal file
View File

@@ -0,0 +1,164 @@
#include "./drawer.h"
#include <assert.h>
#include <stddef.h>
#include <GL/glew.h>
#include "util/gleasy/buffer.h"
#include "util/gleasy/texture.h"
#include "util/gleasy/program.h"
#include "util/math/vector.h"
#include "util/memory/memory.h"
#include "./block.h"
/* resources */
#include "anysrc/drawer.vshader.h"
#include "anysrc/drawer.fshader.h"
#define GLYPHAS_DRAWER_UNIFORM_TEX 0
#define GLYPHAS_DRAWER_VSHADER_IN_POS 0
#define GLYPHAS_DRAWER_VSHADER_IN_SIZE 1
#define GLYPHAS_DRAWER_VSHADER_IN_UV_POS 2
#define GLYPHAS_DRAWER_VSHADER_IN_UV_SIZE 3
#define GLYPHAS_DRAWER_VSHADER_IN_COLOR 4
struct glyphas_drawer_t {
gleasy_texture_2d_t tex;
GLuint vao;
gleasy_buffer_array_t instances;
size_t instances_reserved;
size_t instances_length;
};
#pragma pack(push, 1)
typedef struct {
vec2_t pos;
vec2_t size;
vec2_t uv_pos;
vec2_t uv_size;
vec4_t color;
} glyphas_drawer_instance_t;
#pragma pack(pop)
static void glyphas_drawer_setup_vao_(
gleasy_buffer_array_t instances) {
assert(instances != 0);
glBindBuffer(GL_ARRAY_BUFFER, instances);
# define enable_attrib_(NAME, name, dim, type) do { \
glEnableVertexAttribArray(GLYPHAS_DRAWER_VSHADER_IN_##NAME); \
glVertexAttribPointer( \
GLYPHAS_DRAWER_VSHADER_IN_##NAME, dim, type, GL_FALSE, \
sizeof(glyphas_drawer_instance_t), \
NULL + offsetof(glyphas_drawer_instance_t, name)); \
glVertexAttribDivisor(GLYPHAS_DRAWER_VSHADER_IN_##NAME, 1); \
} while (0)
enable_attrib_(POS, pos, 2, GL_FLOAT);
enable_attrib_(SIZE, size, 2, GL_FLOAT);
enable_attrib_(UV_POS, uv_pos, 2, GL_FLOAT);
enable_attrib_(UV_SIZE, uv_size, 2, GL_FLOAT);
enable_attrib_(COLOR, color, 4, GL_FLOAT);
# undef enable_attrib_
}
gleasy_program_t glyphas_drawer_create_default_program(void) {
return gleasy_program_new("", 0,
glyphas_drawer_vshader_, sizeof(glyphas_drawer_vshader_),
glyphas_drawer_fshader_, sizeof(glyphas_drawer_fshader_));
}
glyphas_drawer_t* glyphas_drawer_new(void) {
glyphas_drawer_t* drawer = memory_new(sizeof(*drawer));
*drawer = (typeof(*drawer)) {0};
glCreateVertexArrays(1, &drawer->vao);
glBindVertexArray(drawer->vao);
glGenBuffers(1, &drawer->instances);
glyphas_drawer_setup_vao_(drawer->instances);
return drawer;
}
void glyphas_drawer_delete(glyphas_drawer_t* drawer) {
if (drawer == NULL) return;
glDeleteBuffers(1, &drawer->instances);
glDeleteVertexArrays(1, &drawer->vao);
memory_delete(drawer);
}
void glyphas_drawer_clear(
glyphas_drawer_t* drawer, gleasy_texture_2d_t tex, size_t reserve) {
assert(drawer != NULL);
assert(tex != 0);
assert(reserve > 0);
drawer->tex = tex;
drawer->instances_length = 0;
if (drawer->instances_reserved < reserve) {
glBindBuffer(GL_ARRAY_BUFFER, drawer->instances);
glBufferData(GL_ARRAY_BUFFER,
reserve * sizeof(glyphas_drawer_instance_t),
NULL, GL_DYNAMIC_DRAW);
drawer->instances_reserved = reserve;
}
}
void glyphas_drawer_add_block(
glyphas_drawer_t* drawer, const glyphas_block_t* block) {
assert(drawer != NULL);
assert(block != NULL);
size_t len;
const glyphas_block_item_t* items = glyphas_block_get_items(block, &len);
for (size_t i = 0; i < len; ++i) {
glyphas_drawer_add_block_item(drawer, &items[i]);
}
}
void glyphas_drawer_add_block_item(
glyphas_drawer_t* drawer, const glyphas_block_item_t* item) {
assert(drawer != NULL);
assert(item != NULL);
const glyphas_drawer_instance_t insta = {
.pos = item->pos,
.size = item->size,
.uv_pos = vec2(item->uv.left, item->uv.top),
.uv_size = vec2(item->uv.right-item->uv.left, item->uv.top-item->uv.bottom),
.color = item->color,
};
const size_t offset = drawer->instances_length * sizeof(insta);
glBindBuffer(GL_ARRAY_BUFFER, drawer->instances);
glBufferSubData(GL_ARRAY_BUFFER, offset, sizeof(insta), &insta);
++drawer->instances_length;
}
void glyphas_drawer_draw(const glyphas_drawer_t* drawer) {
assert(drawer != NULL);
if (drawer->instances_length == 0) return;
glBindVertexArray(drawer->vao);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, drawer->tex);
glUniform1i(GLYPHAS_DRAWER_UNIFORM_TEX, 0);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, drawer->instances_length);
}

View File

@@ -0,0 +1,14 @@
#version 330
#extension GL_ARB_explicit_uniform_location : enable
layout (location = 0) uniform sampler2D u_tex;
in vec2 v_uv;
in vec4 v_color;
out vec4 o_color;
void main(void) {
float a = texture(u_tex, v_uv).r;
o_color = vec4(v_color.rgb, v_color.a*a);
}

51
util/glyphas/drawer.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include "util/gleasy/texture.h"
#include "util/gleasy/program.h"
#include "./block.h"
struct glyphas_drawer_t;
typedef struct glyphas_drawer_t glyphas_drawer_t;
gleasy_program_t
glyphas_drawer_create_default_program(
void
);
glyphas_drawer_t*
glyphas_drawer_new(
void
);
void
glyphas_drawer_delete(
glyphas_drawer_t* drawer
);
void
glyphas_drawer_clear(
glyphas_drawer_t* drawer,
gleasy_texture_2d_t tex,
size_t reserve
);
void
glyphas_drawer_add_block(
glyphas_drawer_t* drawer,
const glyphas_block_t* block
/* The drawer doesn't hold the reference. */
);
void
glyphas_drawer_add_block_item(
glyphas_drawer_t* drawer,
const glyphas_block_item_t* item
/* The drawer doesn't hold the reference. */
);
/* bind the program before drawing */
void
glyphas_drawer_draw(
const glyphas_drawer_t* drawer
);

View File

@@ -0,0 +1,25 @@
#version 330
layout (location = 0) in vec2 i_pos;
layout (location = 1) in vec2 i_size;
layout (location = 2) in vec2 i_uv_pos;
layout (location = 3) in vec2 i_uv_size;
layout (location = 4) in vec4 i_color;
out vec2 v_uv;
out vec4 v_color;
void main(void) {
vec2 p =
gl_VertexID == 0? vec2(0., 0.):
gl_VertexID == 1? vec2(0., -1.):
gl_VertexID == 2? vec2(1., -1.):
gl_VertexID == 3? vec2(0., 0.):
gl_VertexID == 4? vec2(1., -1.):
gl_VertexID == 5? vec2(1., 0.):
vec2(0.);
gl_Position = vec4(p*i_size+i_pos, 0, 1);
v_uv = p*i_uv_size + i_uv_pos;
v_color = i_color;
}

78
util/glyphas/face.c Normal file
View File

@@ -0,0 +1,78 @@
#include "./face.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <ft2build.h>
#include FT_ERRORS_H
#include FT_FREETYPE_H
#include "./context.h"
void glyphas_face_initialize_from_file(
glyphas_face_t* face,
const glyphas_context_t* ctx,
const char* path,
size_t index) {
assert(face != NULL);
assert(ctx != NULL);
*face = (typeof(*face)) {0};
const FT_Error err = FT_New_Face(ctx->ft, path, index, &face->ft);
if (err != FT_Err_Ok) {
fprintf(stderr,
"failed to load font file '%s': %s\n", path, FT_Error_String(err));
abort();
}
}
void glyphas_face_initialize_from_buffer(
glyphas_face_t* face,
const glyphas_context_t* ctx,
const void* data,
size_t length,
size_t index) {
assert(face != NULL);
assert(ctx != NULL);
*face = (typeof(*face)) {0};
const FT_Error err =
FT_New_Memory_Face(ctx->ft, data, length, index, &face->ft);
if (err != FT_Err_Ok) {
fprintf(stderr,
"failed to load font on memory: %s\n", FT_Error_String(err));
abort();
}
}
void glyphas_face_deinitialize(glyphas_face_t* face) {
assert(face != NULL);
FT_Done_Face(face->ft);
}
bool glyphas_face_set_pixel_size(
glyphas_face_t* face, int32_t width, int32_t height) {
assert(face != NULL);
assert(width > 0);
assert(height > 0);
const FT_Error err = FT_Set_Pixel_Sizes(face->ft, width, height);
return err == FT_Err_Ok;
}
bool glyphas_face_render_glyph(glyphas_face_t* face, uint64_t unicode) {
assert(face != NULL);
const FT_UInt index = FT_Get_Char_Index(face->ft, unicode);
if (index == 0) return false;
const FT_Error err = FT_Load_Glyph(face->ft, index, FT_LOAD_RENDER);
if (err != FT_Err_Ok) return false;
return glyphas_glyph_reset_from_ft_glyph_slot(&face->glyph, face->ft->glyph);
}

52
util/glyphas/face.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "./context.h"
#include "./glyph.h"
typedef struct {
FT_Face ft;
glyphas_glyph_t glyph;
} glyphas_face_t;
void
glyphas_face_initialize_from_file(
glyphas_face_t* face,
const glyphas_context_t* ctx,
const char* path,
size_t index
);
void
glyphas_face_initialize_from_buffer(
glyphas_face_t* face,
const glyphas_context_t* ctx,
const void* data,
size_t length,
size_t index
);
void
glyphas_face_deinitialize(
glyphas_face_t* face
);
bool
glyphas_face_set_pixel_size(
glyphas_face_t* face,
int32_t width,
int32_t height
);
/* Set sizes before rendering glyphs. */
bool
glyphas_face_render_glyph(
glyphas_face_t* face,
uint64_t unicode
);

36
util/glyphas/glyph.c Normal file
View File

@@ -0,0 +1,36 @@
#include "./glyph.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
bool glyphas_glyph_reset_from_ft_glyph_slot(
glyphas_glyph_t* glyph, FT_GlyphSlot slot) {
assert(glyph != NULL);
assert(slot != NULL);
if (slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY) return false;
*glyph = (typeof(*glyph)) {
.bitmap = {
.width = slot->bitmap.width,
.height = slot->bitmap.rows,
.buffer = slot->bitmap.buffer,
},
.hmetrics = {
.bear_x = slot->metrics.horiBearingX/64,
.bear_y = slot->metrics.horiBearingY/64,
.advance = slot->metrics.horiAdvance /64,
},
.vmetrics = {
.bear_x = slot->metrics.vertBearingX/64,
.bear_y = slot->metrics.vertBearingY/64,
.advance = slot->metrics.vertAdvance /64,
},
};
return true;
}

32
util/glyphas/glyph.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <ft2build.h>
#include FT_FREETYPE_H
typedef struct {
int32_t width;
int32_t height;
uint8_t* buffer; /* gray-scale bitmap */
} glyphas_glyph_bitmap_t;
typedef struct {
int32_t bear_x;
int32_t bear_y;
int32_t advance;
} glyphas_glyph_metrics_t;
typedef struct {
glyphas_glyph_bitmap_t bitmap;
glyphas_glyph_metrics_t vmetrics;
glyphas_glyph_metrics_t hmetrics;
} glyphas_glyph_t;
/* The glyph's lifetime must be shorter than the slot. */
bool
glyphas_glyph_reset_from_ft_glyph_slot(
glyphas_glyph_t* glyph,
FT_GlyphSlot slot
);

179
util/glyphas/test.c Normal file
View File

@@ -0,0 +1,179 @@
/* An expression in assert() can be expected to be executed absolutely. */
#undef NDEBUG
#define SDL_MAIN_HANDLED
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <SDL2/SDL.h>
#include "util/conv/charcode.h"
#include "util/gleasy/buffer.h"
#include "util/gleasy/program.h"
#include "util/math/vector.h"
#include "./cache.h"
#include "./context.h"
#include "./drawer.h"
#include "./face.h"
#include "./glyph.h"
typedef struct {
glyphas_context_t glyphas;
glyphas_face_t face;
gleasy_atlas_t* atlas;
glyphas_cache_t* cache;
gleasy_program_t prog;
glyphas_drawer_t* drawer;
} context_t;
#define SCALE 0.01f
static void align_text_(context_t* ctx, const char* str) {
assert(ctx != NULL);
const size_t len = strlen(str);
glyphas_block_t* block = glyphas_block_new(
GLYPHAS_ALIGNER_DIRECTION_HORIZONTAL, -20, 400, len);
static vec4_t color = vec4(0, 0, 0, 1);
glyphas_block_add_characters(block, ctx->cache, &color, str, len);
vec2_t offset, size;
glyphas_block_calculate_geometry(block, &offset, &size);
offset = vec2(-offset.x, 0);
glyphas_block_translate(block, &offset);
size = vec2(1.f/200, 1.f/200);
glyphas_block_scale(block, &size);
static const vec2_t origin = vec2(.5f, -.5f);
glyphas_block_set_origin(block, &origin);
glyphas_drawer_clear(ctx->drawer, gleasy_atlas_get_texture(ctx->atlas), len);
glyphas_drawer_add_block(ctx->drawer, block);
glyphas_block_delete(block);
}
static void initialize_(context_t* ctx, const char* path, const char* str) {
assert(ctx != NULL);
*ctx = (typeof(*ctx)) {0};
glyphas_context_initialize(&ctx->glyphas);
glyphas_face_initialize_from_file(&ctx->face, &ctx->glyphas, path, 0);
ctx->atlas = gleasy_atlas_new(GL_RED, 256, 256, false /* anti-alias */);
ctx->cache = glyphas_cache_new(ctx->atlas, &ctx->face, 16, 16);
ctx->prog = glyphas_drawer_create_default_program();
ctx->drawer = glyphas_drawer_new();
assert(glGetError() == GL_NO_ERROR);
align_text_(ctx, str);
assert(glGetError() == GL_NO_ERROR);
}
static void draw_(const context_t* ctx) {
assert(ctx != NULL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(ctx->prog);
glyphas_drawer_draw(ctx->drawer);
}
static void deinitialize_(context_t* ctx) {
assert(ctx != NULL);
glyphas_drawer_delete(ctx->drawer);
glDeleteProgram(ctx->prog);
glyphas_cache_delete(ctx->cache);
gleasy_atlas_delete(ctx->atlas);
glyphas_face_deinitialize(&ctx->face);
glyphas_context_deinitialize(&ctx->glyphas);
}
typedef struct {
SDL_Window* win;
SDL_GLContext gl;
} libs_t;
static void initialize_libraries_(libs_t* libs) {
assert(libs != NULL);
SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0");
assert(SDL_Init(SDL_INIT_VIDEO) == 0);
SDL_GL_SetAttribute(
SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(
SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
static const uint32_t win_flags =
SDL_WINDOW_ALLOW_HIGHDPI |
SDL_WINDOW_OPENGL |
SDL_WINDOW_SHOWN;
libs->win = SDL_CreateWindow(
"glyphas-test", /* title */
SDL_WINDOWPOS_CENTERED, /* X position */
SDL_WINDOWPOS_CENTERED, /* Y position */
400,
400,
win_flags);
assert(libs->win != NULL);
libs->gl = SDL_GL_CreateContext(libs->win);
glewExperimental = 1;
assert(glewInit() == GLEW_OK);
}
static void deinitialize_libraries_(libs_t* libs) {
assert(libs != NULL);
SDL_GL_DeleteContext(libs->gl);
SDL_DestroyWindow(libs->win);
SDL_Quit();
}
int main(int argc, char** argv) {
assert(argc == 3);
libs_t libs;
initialize_libraries_(&libs);
context_t ctx;
initialize_(&ctx, argv[1], argv[2]);
bool alive = true;
while (alive) {
SDL_Delay(30);
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) alive = false;
}
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
draw_(&ctx);
SDL_GL_SwapWindow(libs.win);
}
deinitialize_(&ctx);
deinitialize_libraries_(&libs);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,23 @@
add_library(jukebox
amp.c
beep.c
composite.c
decoder.c
delay.c
effect.c
format.c
lowpass.c
mixer.c
sound.c
)
target_link_libraries(jukebox
miniaudio
math
memory
)
if (BUILD_TESTING)
add_executable(jukebox-test test.c)
target_link_libraries(jukebox-test jukebox)
endif()

83
util/jukebox/amp.c Normal file
View File

@@ -0,0 +1,83 @@
#include "./amp.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "./effect.h"
#include "./format.h"
/* FIXME: Whether this solution works fine,
depends on how long the operation is done. */
static void jukebox_amp_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_amp_t* amp = (typeof(amp)) effect;
const uint64_t easedur = atomic_load(&amp->ease_duration);
const uint64_t elapsed = atomic_load(&amp->elapsed);
const float prev = atomic_load(&amp->prev_amount)/1000.0f;
const float next = atomic_load(&amp->next_amount)/1000.0f;
float* ptr = pcm->ptr;
for (int32_t i = 0; i < pcm->frames; ++i) {
float a = next;
if (elapsed < easedur) {
a = (prev-next) * (easedur-elapsed) / easedur + next;
}
for (int32_t j = 0; j < amp->format.channels; ++j) {
*(ptr++) *= a;
}
}
atomic_fetch_add(&amp->elapsed, pcm->frames);
}
void jukebox_amp_initialize(
jukebox_amp_t* amp, const jukebox_format_t* format) {
assert(amp != NULL);
assert(jukebox_format_valid(format));
*amp = (typeof(*amp)) {
.super = {
.vtable = {
.affect = jukebox_amp_affect_,
},
},
.format = *format,
};
}
void jukebox_amp_deinitialize(jukebox_amp_t* amp) {
assert(amp != NULL);
}
void jukebox_amp_change_volume(
jukebox_amp_t* amp, float amount, const rational_t* duration) {
assert(amp != NULL);
assert(MATH_FLOAT_VALID(amount));
assert(amount >= 0);
const float srate = amp->format.sample_rate;
atomic_store(&amp->ease_duration, 0);
atomic_store(&amp->elapsed, 0);
atomic_store(&amp->prev_amount, amp->next_amount);
rational_t dur = rational(1, 100);
if (rational_valid(duration)) rational_addeq(&dur, duration);
rational_normalize(&dur, srate);
atomic_store(&amp->next_amount, (uint16_t) (amount*1000));
atomic_store(&amp->ease_duration, dur.num);
}

38
util/jukebox/amp.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <stdatomic.h>
#include "util/math/rational.h"
#include "./effect.h"
#include "./format.h"
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_uint_least64_t elapsed;
atomic_uint_least64_t ease_duration;
atomic_uint_least16_t prev_amount; /* [1/1000] */
atomic_uint_least16_t next_amount; /* [1/1000] */
} jukebox_amp_t;
void
jukebox_amp_initialize(
jukebox_amp_t* amp,
const jukebox_format_t* format
);
void
jukebox_amp_deinitialize(
jukebox_amp_t* amp
);
void
jukebox_amp_change_volume(
jukebox_amp_t* amp,
float amount,
const rational_t* duration /* NULLABLE */
);

77
util/jukebox/beep.c Normal file
View File

@@ -0,0 +1,77 @@
#include "./beep.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "util/math/constant.h"
#include "./effect.h"
#include "./format.h"
static void jukebox_beep_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_beep_t* b = (typeof(b)) effect;
if (!atomic_load(&b->playing)) return;
const int32_t sample_rate = b->format.sample_rate;
const int32_t channels = b->format.channels;
const float omega = 2 * MATH_PI * b->hz;
float* ptr = pcm->ptr;
for (int32_t i = 0; i < pcm->frames; ++i) {
const float a = fmod(i*omega/sample_rate, 2*MATH_PI);
const float x = sin(a + b->phase) * b->amp;
for (int32_t j = 0; j < channels; ++j) {
*(ptr++) += x;
}
}
b->phase += pcm->frames*1.0f/sample_rate * omega;
b->phase = fmod(b->phase, 2*MATH_PI);
}
void jukebox_beep_initialize(jukebox_beep_t* beep, const jukebox_format_t* format) {
assert(beep != NULL);
assert(jukebox_format_valid(format));
*beep = (typeof(*beep)) {
.super = {
.vtable = {
.affect = jukebox_beep_affect_,
},
},
.format = *format,
};
}
void jukebox_beep_deinitialize(jukebox_beep_t* beep) {
assert(beep != NULL);
}
void jukebox_beep_play(jukebox_beep_t* beep, float hz, float amp) {
assert(beep != NULL);
assert(MATH_FLOAT_VALID(hz) && hz > 0);
assert(MATH_FLOAT_VALID(amp) && amp > 0);
if (beep->playing) return;
beep->hz = hz;
beep->amp = amp;
beep->phase = 0;
atomic_store(&beep->playing, true);
}
void jukebox_beep_stop(jukebox_beep_t* beep) {
assert(beep != NULL);
if (!beep->playing) return;
atomic_store(&beep->playing, false);
}

42
util/jukebox/beep.h Normal file
View File

@@ -0,0 +1,42 @@
#pragma once
#include <stdatomic.h>
#include "./effect.h"
#include "./format.h"
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_bool playing;
float hz;
float amp;
float phase;
} jukebox_beep_t;
void
jukebox_beep_initialize(
jukebox_beep_t* beep,
const jukebox_format_t* format
);
void
jukebox_beep_deinitialize(
jukebox_beep_t* beep
);
/* not thread-safe function */
void
jukebox_beep_play(
jukebox_beep_t* beep,
float hz,
float amp
);
/* not thread-safe function */
void
jukebox_beep_stop(
jukebox_beep_t* beep
);

113
util/jukebox/composite.c Normal file
View File

@@ -0,0 +1,113 @@
#include "./composite.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/math/algorithm.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_composite_t {
jukebox_effect_t super;
jukebox_format_t format;
atomic_bool playing;
size_t effects_reserved;
size_t effects_length;
jukebox_effect_t* effects[1];
};
static void jukebox_composite_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
# define CHUNK_SIZE 256
jukebox_composite_t* c = (typeof(c)) effect;
if (!atomic_load(&c->playing)) return;
const int32_t channels = c->format.channels;
const size_t chunk_frames = CHUNK_SIZE / channels;
uint64_t read = 0;
while (read < (uint64_t) pcm->frames) {
float chunk[CHUNK_SIZE] = {0};
jukebox_effect_pcm_t chunk_pcm = {
.ptr = chunk,
.frames = MATH_MIN(chunk_frames, pcm->frames - read),
};
for (size_t i = 0; i < c->effects_length; ++i) {
jukebox_effect_affect(c->effects[i], &chunk_pcm);
}
const size_t len = chunk_pcm.frames * channels;
const float* src = chunk;
float* dst = pcm->ptr + read * channels;
for (uint64_t i = 0; i < len; ++i) {
*(dst++) += *(src++);
}
read += chunk_pcm.frames;
}
# undef CHUNK_SIZE
}
jukebox_composite_t* jukebox_composite_new(
const jukebox_format_t* format, size_t reserve) {
assert(jukebox_format_valid(format));
assert(reserve > 0);
jukebox_composite_t* compo =
memory_new(sizeof(*compo) + (reserve-1)*sizeof(compo->effects[0]));
*compo = (typeof(*compo)) {
.super = {
.vtable = {
.affect = jukebox_composite_affect_,
},
},
.format = *format,
.effects_reserved = reserve,
};
return compo;
}
void jukebox_composite_delete(jukebox_composite_t* compo) {
if (compo == NULL) return;
memory_delete(compo);
}
void jukebox_composite_add_effect(
jukebox_composite_t* compo, jukebox_effect_t* effect) {
assert(compo != NULL);
assert(effect != NULL);
assert(!atomic_load(&compo->playing));
if (compo->effects_length >= compo->effects_reserved) {
fprintf(stderr, "jukebox: composite effect overflow\n");
abort();
}
compo->effects[compo->effects_length++] = effect;
}
void jukebox_composite_play(jukebox_composite_t* compo) {
assert(compo != NULL);
atomic_store(&compo->playing, true);
}
void jukebox_composite_stop(jukebox_composite_t* compo) {
assert(compo != NULL);
atomic_store(&compo->playing, false);
}

36
util/jukebox/composite.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <stddef.h>
#include "./effect.h"
#include "./format.h"
struct jukebox_composite_t;
typedef struct jukebox_composite_t jukebox_composite_t;
jukebox_composite_t* /* OWNERSHIP */
jukebox_composite_new(
const jukebox_format_t* format,
size_t reserve
);
void
jukebox_composite_delete(
jukebox_composite_t* compo /* OWNERSHIP */
);
void
jukebox_composite_add_effect(
jukebox_composite_t* compo, /* must be stopped */
jukebox_effect_t* effect /* must be alive while the comp is alive */
);
void
jukebox_composite_play(
jukebox_composite_t* compo
);
void
jukebox_composite_stop(
jukebox_composite_t* compo
);

217
util/jukebox/decoder.c Normal file
View File

@@ -0,0 +1,217 @@
#include "./decoder.h"
#include <assert.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
# define JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE 256
struct jukebox_decoder_t {
jukebox_effect_t super;
jukebox_format_t format;
ma_decoder ma;
atomic_bool playing;
atomic_int_least64_t duration; /* negative = 0 */
atomic_uint_least64_t current; /* main thread doesn't modify when playing*/
bool loop;
bool request_seek;
};
static void jukebox_decoder_seek_(jukebox_decoder_t* dec, uint64_t f) {
assert(dec != NULL);
assert(atomic_load(&dec->playing));
if (ma_decoder_seek_to_pcm_frame(&dec->ma, f) != MA_SUCCESS) {
fprintf(stderr, "jukebox: failed to seek ma_decoder\n");
abort();
}
atomic_store(&dec->current, f);
dec->request_seek = false;
}
static void jukebox_decoder_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_decoder_t* dec = (typeof(dec)) effect;
if (!atomic_load(&dec->playing)) return;
if (dec->request_seek) {
jukebox_decoder_seek_(dec, atomic_load(&dec->current));
}
const int32_t ch = dec->format.channels;
uint64_t read_frames = 0;
for (;;) {
const int64_t duration = atomic_load(&dec->duration);
const uint64_t requested_frames = pcm->frames - read_frames;
if (duration <= 0) {
atomic_store(&dec->playing, false);
break;
}
if (read_frames >= (uint64_t) pcm->frames) break;
uint64_t chunk_frames = JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE / ch;
if (chunk_frames > (uint64_t) duration) chunk_frames = duration;
if (chunk_frames > requested_frames) chunk_frames = requested_frames;
float chunk[JUKEBOX_DECODER_AFFECTION_CHUNK_SIZE];
const uint64_t decoded_frames =
ma_decoder_read_pcm_frames(&dec->ma, chunk, chunk_frames);
if (decoded_frames != chunk_frames) {
jukebox_decoder_seek_(dec, 0);
}
assert(read_frames+decoded_frames <= (uint64_t) pcm->frames);
for (size_t i = 0; i < decoded_frames*ch; ++i) {
pcm->ptr[read_frames*ch+i] += chunk[i];
}
read_frames += decoded_frames;
atomic_fetch_add(&dec->duration, -decoded_frames);
atomic_fetch_add(&dec->current, decoded_frames);
}
}
static jukebox_decoder_t* jukebox_decoder_new_(
const jukebox_format_t* format) {
assert(jukebox_format_valid(format));
jukebox_decoder_t* decoder = memory_new(sizeof(*decoder));
*decoder = (typeof(*decoder)) {
.super = {
.vtable = {
.affect = jukebox_decoder_affect_,
},
},
.format = *format,
};
return decoder;
}
jukebox_decoder_t* jukebox_decoder_new_from_file(
const jukebox_format_t* format, const char* path) {
assert(jukebox_format_valid(format));
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
if (ma_decoder_init_file(path, &cfg, &decoder->ma) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
abort();
}
return decoder;
}
jukebox_decoder_t* jukebox_decoder_new_from_memory_mp3(
const jukebox_format_t* format, const void* buf, size_t len) {
assert(jukebox_format_valid(format));
assert(buf != NULL || len == 0);
jukebox_decoder_t* decoder = jukebox_decoder_new_(format);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, decoder->format.channels, decoder->format.sample_rate);
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder->ma) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
abort();
}
return decoder;
}
void jukebox_decoder_delete(jukebox_decoder_t* decoder) {
if (decoder == NULL) return;
ma_decoder_uninit(&decoder->ma);
memory_delete(decoder);
}
void jukebox_decoder_play(
jukebox_decoder_t* decoder, const rational_t* st, bool loop) {
assert(decoder != NULL);
assert(rational_valid(st));
if (atomic_load(&decoder->playing)) return;
rational_t t = *st;
rational_normalize(&t, decoder->format.sample_rate);
atomic_store(&decoder->duration, INT64_MAX);
decoder->loop = loop;
decoder->request_seek = true;
atomic_store(&decoder->current, t.num);
atomic_store(&decoder->playing, true);
}
void jukebox_decoder_resume(jukebox_decoder_t* decoder, bool loop) {
assert(decoder != NULL);
if (atomic_load(&decoder->playing)) return;
atomic_store(&decoder->duration, INT64_MAX);
decoder->loop = loop;
atomic_store(&decoder->playing, true);
}
void jukebox_decoder_stop_after(
jukebox_decoder_t* decoder, const rational_t* dur) {
assert(decoder != NULL);
assert(rational_valid(dur));
if (!atomic_load(&decoder->playing)) return;
rational_t d = *dur;
rational_normalize(&d, decoder->format.sample_rate);
atomic_store(&decoder->duration, d.num);
}
void jukebox_decoder_get_seek_position(
const jukebox_decoder_t* decoder, rational_t* time) {
assert(decoder != NULL);
assert(time != NULL);
*time = (typeof(*time)) {
.num = atomic_load(&decoder->current),
.den = decoder->format.sample_rate,
};
}
void jukebox_decoder_get_duration(
const jukebox_decoder_t* decoder, rational_t* time) {
assert(decoder != NULL);
assert(time != NULL);
*time = rational(
ma_decoder_get_length_in_pcm_frames((ma_decoder*) &decoder->ma),
decoder->format.sample_rate);
}

71
util/jukebox/decoder.h Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_decoder_t;
typedef struct jukebox_decoder_t jukebox_decoder_t;
jukebox_decoder_t* /* OWNERSHIP */
jukebox_decoder_new_from_file(
const jukebox_format_t* format,
const char* path
);
jukebox_decoder_t* /* OWNERSHIP */
jukebox_decoder_new_from_memory_mp3(
const jukebox_format_t* format,
const void* buf,
size_t len
);
void
jukebox_decoder_delete(
jukebox_decoder_t* decoder /* OWNERSHIP */
);
void
jukebox_decoder_play(
jukebox_decoder_t* decoder,
const rational_t* st,
bool loop
);
void
jukebox_decoder_resume(
jukebox_decoder_t* decoder,
bool loop
);
void
jukebox_decoder_stop_after(
jukebox_decoder_t* decoder,
const rational_t* dur
);
void
jukebox_decoder_set_loop(
jukebox_decoder_t* decoder,
const rational_t* start, /* NULLABLE */
const rational_t* end /* NULLABLE */
);
void
jukebox_decoder_unset_loop(
jukebox_decoder_t* decoder
);
void
jukebox_decoder_get_seek_position(
const jukebox_decoder_t* decoder,
rational_t* time
);
void
jukebox_decoder_get_duration(
const jukebox_decoder_t* decoder,
rational_t* time
);

94
util/jukebox/delay.c Normal file
View File

@@ -0,0 +1,94 @@
#include "./delay.h"
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "util/math/algorithm.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_delay_t {
jukebox_effect_t super;
jukebox_format_t format;
float source;
float feedback;
size_t length;
size_t offset;
size_t cursor;
float buffer[1];
};
static void jukebox_delay_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_delay_t* d = (typeof(d)) effect;
const int32_t ch = d->format.channels;
const size_t pcmlen = pcm->frames*ch;
for (size_t i = 0; i < pcmlen; ++i) {
float* bufoff = &d->buffer[d->offset];
float* bufcur = &d->buffer[d->cursor];
float* pcmptr = &pcm->ptr[i];
*bufoff *= d->feedback;
*bufoff += *pcmptr * d->source;
*pcmptr += *bufcur;
if (MATH_ABS(*pcmptr) >= 1.0f) {
*pcmptr = MATH_SIGN(*pcmptr) * 0.99f;
}
if (++d->offset >= d->length) d->offset = 0;
if (++d->cursor >= d->length) d->cursor = 0;
}
}
jukebox_delay_t* jukebox_delay_new(
const jukebox_format_t* format,
const rational_t* duration,
float source_attenuation,
float feedback_attenuation) {
assert(jukebox_format_valid(format));
assert(rational_valid(duration));
assert(MATH_FLOAT_VALID(source_attenuation));
assert(MATH_FLOAT_VALID(feedback_attenuation));
rational_t dur_r = rational(format->channels*duration->num, duration->den);
rational_normalize(&dur_r, format->sample_rate);
const size_t length = dur_r.num;
jukebox_delay_t* d =
memory_new(sizeof(*d) + (length-1)*sizeof(d->buffer[0]));
*d = (typeof(*d)) {
.super = {
.vtable = {
.affect = jukebox_delay_affect_,
},
},
.format = *format,
.source = source_attenuation,
.feedback = feedback_attenuation,
.length = length,
.offset = dur_r.num,
.cursor = 0,
};
for (size_t i = 0; i < d->length; ++i) d->buffer[i] = 0;
return d;
}
void jukebox_delay_delete(jukebox_delay_t* delay) {
if (delay == NULL) return;
memory_delete(delay);
}

23
util/jukebox/delay.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_delay_t;
typedef struct jukebox_delay_t jukebox_delay_t;
jukebox_delay_t*
jukebox_delay_new(
const jukebox_format_t* format,
const rational_t* duration,
float source_attenuation,
float feedback_attenuation
);
void
jukebox_delay_delete(
jukebox_delay_t* delay
);

13
util/jukebox/effect.c Normal file
View File

@@ -0,0 +1,13 @@
#include "./effect.h"
#include <assert.h>
#include <stddef.h>
void jukebox_effect_affect(
jukebox_effect_t* snd, const jukebox_effect_pcm_t* pcm) {
assert(snd != NULL);
assert(pcm != NULL);
assert(snd->vtable.affect != NULL);
snd->vtable.affect(snd, pcm);
}

31
util/jukebox/effect.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdint.h>
#include "./format.h"
struct jukebox_effect_t;
typedef struct jukebox_effect_t jukebox_effect_t;
typedef struct {
float* ptr;
int32_t frames;
} jukebox_effect_pcm_t;
typedef struct {
void
(*affect)(
jukebox_effect_t* snd,
const jukebox_effect_pcm_t* pcm
);
} jukebox_effect_vtable_t;
struct jukebox_effect_t {
jukebox_effect_vtable_t vtable;
};
void
jukebox_effect_affect(
jukebox_effect_t* snd,
const jukebox_effect_pcm_t* pcm
);

9
util/jukebox/format.c Normal file
View File

@@ -0,0 +1,9 @@
#include "./format.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
bool jukebox_format_valid(const jukebox_format_t* format) {
return format != NULL && format->sample_rate > 0 && format->channels > 0;
}

14
util/jukebox/format.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct {
int32_t sample_rate;
int32_t channels;
} jukebox_format_t;
bool
jukebox_format_valid(
const jukebox_format_t* format
);

57
util/jukebox/lowpass.c Normal file
View File

@@ -0,0 +1,57 @@
#include "./lowpass.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/algorithm.h"
#include "./effect.h"
#include "./format.h"
static void jukebox_lowpass_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_lowpass_t* lp = (typeof(lp)) effect;
const size_t ch = lp->format.channels;
const float f = atomic_load(&lp->factor) * JUKEBOX_LOWPASS_FACTOR_UNIT;
assert(ch <= JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS);
const size_t pcmlen = pcm->frames * ch;
for (size_t i = 0; i < pcmlen; ++i) {
const float p = (ch+1 <= i? pcm->ptr[i-1-ch]: lp->prev[i]);
pcm->ptr[i] = (1.0f-f)*pcm->ptr[i] + f*p;
}
if (pcmlen < ch) return;
for (size_t i = 0; i < ch; ++i) {
lp->prev[i] = pcm->ptr[pcmlen-ch+i];
}
}
void jukebox_lowpass_initialize(
jukebox_lowpass_t* lowpass, const jukebox_format_t* format, float factor) {
assert(lowpass != NULL);
assert(jukebox_format_valid(format));
assert(MATH_FLOAT_VALID(factor));
*lowpass = (typeof(*lowpass)) {
.super = {
.vtable = {
.affect = jukebox_lowpass_affect_,
},
},
.format = *format,
.factor = factor*10000,
.prev = {0},
};
}
void jukebox_lowpass_deinitialize(jukebox_lowpass_t* lowpass) {
assert(lowpass != NULL);
}

31
util/jukebox/lowpass.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <stdatomic.h>
#include "./effect.h"
#include "./format.h"
#define JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS 16
#define JUKEBOX_LOWPASS_FACTOR_UNIT (1.0f/10000)
typedef struct {
jukebox_effect_t super;
jukebox_format_t format;
atomic_uint_least32_t factor;
float prev[JUKEBOX_LOWPASS_SUPPORT_MAX_CHANNELS];
} jukebox_lowpass_t;
void
jukebox_lowpass_initialize(
jukebox_lowpass_t* lowpass,
const jukebox_format_t* format,
float factor
);
void
jukebox_lowpass_deinitialize(
jukebox_lowpass_t* lowpass
);

98
util/jukebox/mixer.c Normal file
View File

@@ -0,0 +1,98 @@
#include "./mixer.h"
#include <assert.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/container/array.h"
#include "util/memory/memory.h"
#include "./effect.h"
struct jukebox_mixer_t {
ma_device device;
jukebox_format_t format;
size_t effects_reserved;
size_t effects_length;
jukebox_effect_t* effects[1];
};
static void jukebox_mixer_device_callback_(
ma_device* device, void* out, const void* in, ma_uint32 frames) {
assert(device != NULL);
assert(out != NULL);
assert(frames > 0);
jukebox_mixer_t* mixer = (typeof(mixer)) device->pUserData;
assert(mixer != NULL);
const jukebox_effect_pcm_t pcm = {
.ptr = out,
.frames = frames,
};
for (size_t i = 0; i < mixer->effects_length; ++i) {
jukebox_effect_affect(mixer->effects[i], &pcm);
}
(void) in;
}
jukebox_mixer_t* jukebox_mixer_new(
const jukebox_format_t* format, size_t reserve) {
assert(jukebox_format_valid(format));
assert(reserve > 0);
jukebox_mixer_t* mixer = memory_new(
sizeof(*mixer) + (reserve-1)*sizeof(mixer->effects[0]));
*mixer = (typeof(*mixer)) {
.format = *format,
.effects_reserved = reserve,
};
ma_device_config config = ma_device_config_init(ma_device_type_playback);
config.playback.format = ma_format_f32;
config.playback.channels = mixer->format.channels;
config.sampleRate = mixer->format.sample_rate;
config.dataCallback = jukebox_mixer_device_callback_;
config.pUserData = mixer;
if (ma_device_init(NULL, &config, &mixer->device) != MA_SUCCESS) {
fprintf(stderr, "failed to open audio device\n");
abort();
}
if (ma_device_start(&mixer->device) != MA_SUCCESS) {
fprintf(stderr, "failed to start audio device\n");
ma_device_uninit(&mixer->device);
abort();
}
return mixer;
}
void jukebox_mixer_delete(jukebox_mixer_t* mixer) {
if (mixer == NULL) return;
ma_device_uninit(&mixer->device);
memory_delete(mixer);
}
void jukebox_mixer_add_effect(
jukebox_mixer_t* mixer, jukebox_effect_t* effect) {
assert(mixer != NULL);
assert(effect != NULL);
ma_mutex_lock(&mixer->device.lock);
if (mixer->effects_length >= mixer->effects_reserved) {
fprintf(stderr, "mixer effects overflow\n");
abort();
}
mixer->effects[mixer->effects_length++] = effect;
ma_mutex_unlock(&mixer->device.lock);
}

26
util/jukebox/mixer.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <stddef.h>
#include "./effect.h"
#include "./format.h"
struct jukebox_mixer_t;
typedef struct jukebox_mixer_t jukebox_mixer_t;
jukebox_mixer_t* /* OWNERSHIP */
jukebox_mixer_new(
const jukebox_format_t* format,
size_t reserve
);
void
jukebox_mixer_delete(
jukebox_mixer_t* mixer /* OWNERSHIP */
);
void
jukebox_mixer_add_effect(
jukebox_mixer_t* mixer,
jukebox_effect_t* effect /* must be alive while the mixer is alive */
);

180
util/jukebox/sound.c Normal file
View File

@@ -0,0 +1,180 @@
#include "./sound.h"
#include <assert.h>
#include <stdatomic.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "thirdparty/miniaudio/miniaudio.h"
#include "util/math/rational.h"
#include "util/memory/memory.h"
#include "./effect.h"
#include "./format.h"
struct jukebox_sound_buffer_t {
jukebox_format_t format;
uint64_t frames;
float ptr[1];
};
struct jukebox_sound_t {
jukebox_effect_t super;
const jukebox_sound_buffer_t* buffer;
atomic_bool stop_all;
size_t length;
atomic_uint_least64_t frames[1];
};
static void jukebox_sound_affect_(
jukebox_effect_t* effect, const jukebox_effect_pcm_t* pcm) {
assert(effect != NULL);
assert(pcm != NULL);
jukebox_sound_t* s = (typeof(s)) effect;
if (atomic_load(&s->stop_all)) {
for (size_t i = 0; i < s->length; ++i) {
atomic_store(&s->frames[i], s->buffer->frames);
}
atomic_store(&s->stop_all, false);
return;
}
const int32_t ch = s->buffer->format.channels;
for (size_t i = 0; i < s->length; ++i) {
const uint64_t frame = atomic_load(&s->frames[i]);
if (frame >= s->buffer->frames) continue;
const float* src = s->buffer->ptr;
float* dst = pcm->ptr;
const float* src_ed = src + s->buffer->frames*ch;
const float* dst_ed = dst + pcm->frames*ch;
src += frame*ch;
while (src < src_ed && dst < dst_ed) {
*(dst++) += *(src++);
}
atomic_fetch_add(&s->frames[i], (src - s->buffer->ptr)/ch - frame);
}
}
static jukebox_sound_buffer_t* jukebox_sound_buffer_new_(
const jukebox_format_t* format, ma_decoder* decoder) {
assert(format != NULL);
assert(decoder != NULL);
const uint64_t frames = ma_decoder_get_length_in_pcm_frames(decoder);
const size_t length = frames * format->channels;
jukebox_sound_buffer_t* buf =
memory_new(sizeof(*buf) + (length-1)*sizeof(buf->ptr[0]));
*buf = (typeof(*buf)) {
.format = *format,
.frames = frames,
};
const uint64_t read_frames =
ma_decoder_read_pcm_frames(decoder, buf->ptr, frames);
/* usually read_frames should be equal to frames */
buf->frames = read_frames;
return buf;
}
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_file(
const jukebox_format_t* format, const char* path) {
assert(jukebox_format_valid(format));
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, format->channels, format->sample_rate);
ma_decoder decoder;
if (ma_decoder_init_file(path, &cfg, &decoder) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid audio file '%s'\n", path);
abort();
}
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
ma_decoder_uninit(&decoder);
return sndbuf;
}
jukebox_sound_buffer_t* jukebox_sound_buffer_new_from_memory_mp3(
const jukebox_format_t* format, const void* buf, size_t len) {
assert(jukebox_format_valid(format));
assert(buf != NULL || len == 0);
const ma_decoder_config cfg = ma_decoder_config_init(
ma_format_f32, format->channels, format->sample_rate);
ma_decoder decoder;
if (ma_decoder_init_memory_mp3(buf, len, &cfg, &decoder) != MA_SUCCESS) {
fprintf(stderr, "jukebox: invalid mp3 buffer\n");
abort();
}
jukebox_sound_buffer_t* sndbuf = jukebox_sound_buffer_new_(format, &decoder);
ma_decoder_uninit(&decoder);
return sndbuf;
}
void jukebox_sound_buffer_delete(jukebox_sound_buffer_t* buf) {
if (buf == NULL) return;
memory_delete(buf);
}
jukebox_sound_t* jukebox_sound_new(
const jukebox_sound_buffer_t* buf, size_t max_concurrent) {
assert(buf != NULL);
assert(max_concurrent > 0);
jukebox_sound_t* sound =
memory_new(sizeof(*sound) + (max_concurrent-1)*sizeof(sound->frames[0]));
*sound = (typeof(*sound)) {
.super = {
.vtable = {
.affect = jukebox_sound_affect_,
},
},
.buffer = buf,
.length = max_concurrent,
};
for (size_t i = 0; i < sound->length; ++i) {
sound->frames[i] = sound->buffer->frames;
}
return sound;
}
void jukebox_sound_delete(jukebox_sound_t* sound) {
if (sound == NULL) return;
memory_delete(sound);
}
bool jukebox_sound_play(jukebox_sound_t* sound) {
assert(sound != NULL);
if (atomic_load(&sound->stop_all)) return false;
for (size_t i = 0; i < sound->length; ++i) {
if (atomic_load(&sound->frames[i]) >= sound->buffer->frames) {
atomic_store(&sound->frames[i], 0);
return true;
}
}
return false;
}
void jukebox_sound_stop_all(jukebox_sound_t* sound) {
assert(sound != NULL);
atomic_store(&sound->stop_all, true);
}

53
util/jukebox/sound.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <stdatomic.h>
#include <stddef.h>
#include "util/math/rational.h"
#include "./format.h"
struct jukebox_sound_buffer_t;
typedef struct jukebox_sound_buffer_t jukebox_sound_buffer_t;
struct jukebox_sound_t;
typedef struct jukebox_sound_t jukebox_sound_t;
jukebox_sound_buffer_t*
jukebox_sound_buffer_new_from_file(
const jukebox_format_t* format,
const char* path
);
jukebox_sound_buffer_t*
jukebox_sound_buffer_new_from_memory_mp3(
const jukebox_format_t* format,
const void* buf,
size_t len
);
void
jukebox_sound_buffer_delete(
jukebox_sound_buffer_t* buf
);
jukebox_sound_t*
jukebox_sound_new(
const jukebox_sound_buffer_t* buf,
size_t max_concurrent
);
void
jukebox_sound_delete(
jukebox_sound_t* sound
);
bool
jukebox_sound_play(
jukebox_sound_t* sound
);
void
jukebox_sound_stop_all(
jukebox_sound_t* sound
);

53
util/jukebox/test.c Normal file
View File

@@ -0,0 +1,53 @@
#undef NDEBUG
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "util/math/rational.h"
#include "./delay.h"
#include "./effect.h"
#include "./lowpass.h"
#include "./mixer.h"
#include "./sound.h"
int main(int argc, char** argv) {
assert(argc == 2);
static const jukebox_format_t format = {
.sample_rate = 48000,
.channels = 2,
};
jukebox_sound_buffer_t* sndbuf =
jukebox_sound_buffer_new_from_file(&format, argv[1]);
jukebox_sound_t* snd = jukebox_sound_new(sndbuf, 16);
static const rational_t delay_dur = rational(1, 10);
jukebox_delay_t* delay = jukebox_delay_new(&format, &delay_dur, 0.1f, 0.5f);
jukebox_lowpass_t lowpass = {0};
jukebox_lowpass_initialize(&lowpass, &format, 0.01f);
jukebox_mixer_t* mixer = jukebox_mixer_new(&format, 16);
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) snd);
jukebox_mixer_add_effect(mixer, (jukebox_effect_t*) delay);
jukebox_mixer_add_effect(mixer, &lowpass.super);
for (size_t i = 0; i < 32; ++i) {
jukebox_sound_play(snd);
printf("press enter to continue...\n");
getchar();
}
jukebox_sound_stop_all(snd);
jukebox_mixer_delete(mixer);
jukebox_lowpass_deinitialize(&lowpass);
jukebox_delay_delete(delay);
jukebox_sound_delete(snd);
jukebox_sound_buffer_delete(sndbuf);
return EXIT_SUCCESS;
}

16
util/math/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
add_library(math
rational.c
vector.c
)
target_source_of_source(math
algorithm.sos.c
matrix.sos.c
)
target_link_libraries(math m)
if (BUILD_TESTING)
add_executable(math-test test.c)
target_link_libraries(math-test math)
add_test(math-test math-test)
endif()

63
util/math/algorithm.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
#include <math.h>
#include <stdint.h>
#include "./constant.h"
#define MATH_ABS(a) ((a) > 0? (a): -(a))
#define MATH_MAX(a, b) ((a) > (b)? (a): (b))
#define MATH_MIN(a, b) ((a) < (b)? (a): (b))
#define MATH_DIFF(a, b) (MATH_MAX(a, b) - MATH_MIN(a, b))
#define MATH_CLAMP(x, min, max) ((x) < (min)? (min): (x) > (max)? (max): (x))
#define MATH_SIGN(a) (a < 0? -1: a > 0? 1: 0)
#define MATH_FLOAT_EQUAL(a, b) (MATH_ABS((a) - (b)) < MATH_EPSILON)
#define MATH_FLOAT_VALID(a) (!isnan(a) && !isinf(a))
#define decl_next_power2_(type) \
type##_t math_##type##_next_power2(type##_t x)
decl_next_power2_(int8);
decl_next_power2_(int16);
decl_next_power2_(int32);
decl_next_power2_(int64);
decl_next_power2_(uint8);
decl_next_power2_(uint16);
decl_next_power2_(uint32);
decl_next_power2_(uint64);
#undef decl_next_power2_
#define decl_gcd_(type) \
type##_t math_##type##_gcd(type##_t x, type##_t y)
decl_gcd_(int8);
decl_gcd_(int16);
decl_gcd_(int32);
decl_gcd_(int64);
decl_gcd_(uint8);
decl_gcd_(uint16);
decl_gcd_(uint32);
decl_gcd_(uint64);
#undef decl_gcd_
#define decl_lcm_(type) \
type##_t math_##type##_lcm(type##_t x, type##_t y)
decl_lcm_(int8);
decl_lcm_(int16);
decl_lcm_(int32);
decl_lcm_(int64);
decl_lcm_(uint8);
decl_lcm_(uint16);
decl_lcm_(uint32);
decl_lcm_(uint64);
#undef decl_lcm_

88
util/math/algorithm.sos.c Normal file
View File

@@ -0,0 +1,88 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
static void next_power2(const char* type, size_t N) {
assert(N > 0);
/* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 */
printf("%s%zu_t math_%s%zu_next_power2(%s%zu_t x) {", type, N, type, N, type, N);
printf("--x;");
for (size_t i = 1; i < N; i*=2) {
printf("x |= x >> %zu;", i);
}
printf("return ++x;");
printf("}\n");
}
static void gcd(const char* type, size_t N) {
assert(N > 0);
printf("%s%zu_t math_%s%zu_gcd(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N);
printf("assert(x > 0);");
printf("assert(y > 0);");
printf("if (x < y) return math_%s%zu_gcd(y, x);", type, N);
printf("%s%zu_t z;", type, N);
printf("while (y) {");
printf("z = x%%y;");
printf("x = y;");
printf("y = z;");
printf("}");
printf("return x;}\n");
}
static void lcm(const char* type, size_t N) {
assert(N > 0);
printf("%s%zu_t math_%s%zu_lcm(%s%zu_t x, %s%zu_t y) {", type, N, type, N, type, N, type, N);
printf("assert(x > 0);");
printf("assert(y > 0);");
printf("return x / math_%s%zu_gcd(x, y) * y;}\n", type, N);
}
int main(int argc, char** argv) {
(void) argc, (void) argv;
printf("#include \"./algorithm.h\"\n");
printf("#include <assert.h>\n");
printf("#include <stdint.h>\n");
next_power2("int", 8);
next_power2("int", 16);
next_power2("int", 32);
next_power2("int", 64);
next_power2("uint", 8);
next_power2("uint", 16);
next_power2("uint", 32);
next_power2("uint", 64);
gcd("int", 8);
gcd("int", 16);
gcd("int", 32);
gcd("int", 64);
gcd("uint", 8);
gcd("uint", 16);
gcd("uint", 32);
gcd("uint", 64);
lcm("int", 8);
lcm("int", 16);
lcm("int", 32);
lcm("int", 64);
lcm("uint", 8);
lcm("uint", 16);
lcm("uint", 32);
lcm("uint", 64);
return EXIT_SUCCESS;
}

8
util/math/constant.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
#include <math.h>
#include <float.h>
#define MATH_EPSILON FLT_EPSILON
#define MATH_INF 1e+10
#define MATH_PI M_PI

123
util/math/matrix.h Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "./vector.h"
typedef union {
vec2_t col[2];
float elm[2][2];
float ptr[4];
} mat2_t;
typedef union {
vec3_t col[3];
float elm[3][3];
float ptr[9];
} mat3_t;
typedef union {
vec4_t col[4];
float elm[4][4];
float ptr[16];
} mat4_t;
#define mat3_identity() (mat3_t) {{ \
vec3(1, 0, 0), \
vec3(0, 1, 0), \
vec3(0, 0, 1), \
}}
#define mat4_identity() (mat4_t) {{ \
vec4(1, 0, 0, 0), \
vec4(0, 1, 0, 0), \
vec4(0, 0, 1, 0), \
vec4(0, 0, 0, 1), \
}}
#define mat3_translation(x, y) (mat3_t) {{ \
vec3(1, 0, 0), \
vec3(0, 1, 0), \
vec3(x, y, 1), \
}}
#define mat4_translation(x, y, z) (mat4_t) {{ \
vec4(1, 0, 0, 0), \
vec4(0, 1, 0, 0), \
vec4(0, 0, 1, 0), \
vec4(x, y, z, 1), \
}}
#define mat3_scale(x, y) (mat3_t) {{ \
vec3(x, 0, 0), \
vec3(0, y, 0), \
vec3(0, 0, 1), \
}}
#define mat4_scale(x, y, z) (mat4_t) {{ \
vec4(x, 0, 0, 0), \
vec4(0, y, 0, 0), \
vec4(0, 0, z, 0), \
vec4(0, 0, 0, 1), \
}}
#define mat3_rotation_z(sin_theta, cos_theta) (mat3_t) {{ \
vec3( cos_theta, sin_theta, 0), \
vec3(-(sin_theta), cos_theta, 0), \
vec3( 0, 0, 1), \
}}
#define mat4_rotation_x(sin_theta, cos_theta) (mat4_t) {{ \
vec4(1, 0, 0, 0), \
vec4(0, cos_theta, sin_theta, 0), \
vec4(0, -(sin_theta), cos_theta, 0), \
vec4(0, 0, 0, 1), \
}}
#define mat4_rotation_y(sin_theta, cos_theta) (mat4_t) {{ \
vec4(cos_theta, 0, -(sin_theta), 0), \
vec4( 0, 1, 0, 0), \
vec4(sin_theta, 0, cos_theta, 0), \
vec4( 0, 0, 0, 1), \
}}
#define mat4_rotation_z(sin_theta, cos_theta) (mat4_t) {{ \
vec4( cos_theta, sin_theta, 0, 0), \
vec4(-(sin_theta), cos_theta, 0, 0), \
vec4( 0, 0, 1, 0), \
vec4( 0, 0, 0, 1), \
}}
bool mat2_valid(const mat2_t* x);
bool mat3_valid(const mat3_t* x);
bool mat4_valid(const mat4_t* x);
void mat2_add(mat2_t* x, const mat2_t* l, const mat2_t* r);
void mat3_add(mat3_t* x, const mat3_t* l, const mat3_t* r);
void mat4_add(mat4_t* x, const mat4_t* l, const mat4_t* r);
void mat2_addeq(mat2_t* x, const mat2_t* r);
void mat3_addeq(mat3_t* x, const mat3_t* r);
void mat4_addeq(mat4_t* x, const mat4_t* r);
void mat2_sub(mat2_t* x, const mat2_t* l, const mat2_t* r);
void mat3_sub(mat3_t* x, const mat3_t* l, const mat3_t* r);
void mat4_sub(mat4_t* x, const mat4_t* l, const mat4_t* r);
void mat2_subeq(mat2_t* x, const mat2_t* r);
void mat3_subeq(mat3_t* x, const mat3_t* r);
void mat4_subeq(mat4_t* x, const mat4_t* r);
void mat2_mul(mat2_t* x, const mat2_t* l, const mat2_t* r);
void mat3_mul(mat3_t* x, const mat3_t* l, const mat3_t* r);
void mat4_mul(mat4_t* x, const mat4_t* l, const mat4_t* r);
void mat2_mul_vec2(vec2_t* x, const mat2_t* l, const vec2_t* r);
void mat3_mul_vec3(vec3_t* x, const mat3_t* l, const vec3_t* r);
void mat4_mul_vec4(vec4_t* x, const mat4_t* l, const vec4_t* r);
float mat2_det(const mat2_t* x);
float mat3_det(const mat3_t* x);
float mat4_det(const mat4_t* x);
void mat3_cofactor(mat2_t* x, const mat3_t* r, size_t row, size_t col);
void mat4_cofactor(mat3_t* x, const mat4_t* r, size_t row, size_t col);
bool mat3_inv(mat3_t* x, const mat3_t* r);
bool mat4_inv(mat4_t* x, const mat4_t* r);

240
util/math/matrix.sos.c Normal file
View File

@@ -0,0 +1,240 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#define SWAP(a, b) do { \
typeof(a) macro_SWAP_temp_ = a; \
a = b; \
b = macro_SWAP_temp_; \
} while (0)
static void next_permutation(size_t* p, size_t* c, size_t len) {
assert(p != NULL);
assert(c != NULL);
size_t i = 0;
while (i < len) {
if (c[i] < i) {
if (i%2 == 0) {
SWAP(p[0], p[i]);
} else {
SWAP(p[c[i]], p[i]);
}
++c[i];
return;
}
c[i] = 0;
++i;
}
}
static void valid(size_t dim) {
assert(dim >= 2);
printf("bool mat%zu_valid(const mat%zu_t* m) {", dim, dim);
printf("return m != NULL && MATH_FLOAT_VALID(m->ptr[0])");
for (size_t i = 1; i < dim*dim; ++i) {
printf("&& MATH_FLOAT_VALID(m->ptr[%zu])", i);
}
printf(";}\n");
}
static void each(size_t dim, const char* name, char op) {
assert(dim >= 2);
printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, name, dim, dim, dim);
printf("assert(x != NULL);");
printf("assert(mat%zu_valid(l));", dim);
printf("assert(mat%zu_valid(r));", dim);
for (size_t i = 0; i < dim*dim; ++i) {
printf("x->ptr[%zu] = l->ptr[%zu] %c r->ptr[%zu];", i, i, op, i);
}
printf("}\n");
}
static void eacheq(size_t dim, const char* name, char op) {
assert(dim >= 2);
printf("void mat%zu_%s(mat%zu_t* x, const mat%zu_t* r) {", dim, name, dim, dim);
printf("assert(mat%zu_valid(x));", dim);
printf("assert(mat%zu_valid(r));", dim);
for (size_t i = 0; i < dim*dim; ++i) {
printf("x->ptr[%zu] %c= r->ptr[%zu];", i, op, i);
}
printf("}\n");
}
static void mul(size_t dim) {
assert(dim >= 2);
printf("void mat%zu_mul(mat%zu_t* x, const mat%zu_t* l, const mat%zu_t* r) {", dim, dim, dim, dim);
printf("assert(x != NULL);");
printf("assert(mat%zu_valid(l));", dim);
printf("assert(mat%zu_valid(r));", dim);
for (size_t i = 0; i < dim; ++i) {
for (size_t j = 0; j < dim; ++j) {
printf("x->elm[%zu][%zu] = 0", j, i);
for (size_t k = 0; k < dim; ++k) {
printf("+ l->elm[%zu][%zu] * r->elm[%zu][%zu]", k, i, j, k);
}
printf(";");
}
}
printf("}\n");
}
static void mul_vec(size_t dim) {
assert(dim >= 2);
printf("void mat%zu_mul_vec%zu(vec%zu_t* x, const mat%zu_t* l, const vec%zu_t* r) {", dim, dim, dim, dim, dim);
printf("assert(x != NULL);");
printf("assert(mat%zu_valid(l));", dim);
printf("assert(vec%zu_valid(r));", dim);
for (size_t i = 0; i < dim; ++i) {
printf("x->ptr[%zu] = 0", i);
for (size_t j = 0; j < dim; ++j) {
printf("+ l->elm[%zu][%zu] * r->ptr[%zu]", j, i, j);
}
printf(";");
}
printf("}\n");
}
static void det(size_t dim) {
assert(dim >= 2);
size_t p[dim];
for (size_t i = 0; i < dim; ++i) p[i] = i;
size_t c[dim];
for (size_t i = 0; i < dim; ++i) c[i] = 0;
printf("float mat%zu_det(const mat%zu_t* x) {", dim, dim);
printf("assert(mat%zu_valid(x));", dim);
size_t f = 1;
for (size_t i = 1; i <= dim; ++i) f *= i;
printf("return 0");
bool positive = true;
for (size_t i = 0; i < f; ++i) {
printf("%c1", positive? '+': '-');
for (size_t j = 0; j < dim; ++j) {
printf("*x->elm[%zu][%zu]", p[j], j);
}
next_permutation(p, c, dim);
positive = !positive;
}
printf(";}\n");
}
static void cofactor(size_t dim) {
assert(dim >= 3);
printf("void mat%zu_cofactor(mat%zu_t* x, const mat%zu_t* r, size_t row, size_t col) {", dim, dim-1, dim);
printf("assert(x != NULL);");
printf("assert(mat%zu_valid(r));", dim);
printf("assert(row < %zu);", dim);
printf("assert(col < %zu);", dim);
for (size_t row = 0; row < dim; ++row) {
for (size_t col = 0; col < dim; ++col) {
printf("if (row == %zu && col == %zu) {", row, col);
for (size_t xi = 0; xi < dim-1; ++xi) {
const size_t ri = xi >= row? xi+1: xi;
for (size_t xj = 0; xj < dim-1; ++xj) {
const size_t rj = xj >= col? xj+1: xj;
printf("x->elm[%zu][%zu] = %s r->elm[%zu][%zu];", xj, xi, (xj+xi)%2 == 0? "": "-", rj, ri);
}
}
printf("return;}");
}
}
printf("}\n");
}
static void inv(size_t dim) {
assert(dim >= 3);
printf("bool mat%zu_inv(mat%zu_t* x, const mat%zu_t* r) {", dim, dim, dim);
printf("assert(x != NULL);");
printf("assert(mat%zu_valid(r));", dim);
printf("const float d = mat%zu_det(r);", dim);
printf("if (d == 0) return false;");
printf("mat%zu_t co;", dim-1);
for (size_t i = 0; i < dim; ++i) {
for (size_t j = 0; j < dim; ++j) {
printf("mat%zu_cofactor(&co, r, %zu, %zu);", dim, i, j);
printf("x->elm[%zu][%zu] = mat%zu_det(&co) / d;", j, i, dim-1);
}
}
printf("return true;");
printf("}\n");
}
int main(int argc, char** argv) {
(void) argc, (void) argv;
printf("#include \"./matrix.h\"\n");
printf("#include <assert.h>\n");
printf("#include <stdbool.h>\n");
printf("#include <stddef.h>\n");
printf("#include \"./algorithm.h\"\n");
printf("#include \"./vector.h\"\n");
valid(2);
valid(3);
valid(4);
each(2, "add", '+');
each(3, "add", '+');
each(4, "add", '+');
eacheq(2, "addeq", '+');
eacheq(3, "addeq", '+');
eacheq(4, "addeq", '+');
each(2, "sub", '-');
each(3, "sub", '-');
each(4, "sub", '-');
eacheq(2, "subeq", '-');
eacheq(3, "subeq", '-');
eacheq(4, "subeq", '-');
mul(2);
mul(3);
mul(4);
mul_vec(2);
mul_vec(3);
mul_vec(4);
det(2);
det(3);
det(4);
cofactor(3);
cofactor(4);
inv(3);
inv(4);
}

110
util/math/rational.c Normal file
View File

@@ -0,0 +1,110 @@
#include "./rational.h"
#include <assert.h>
#include <stddef.h>
#include "./algorithm.h"
bool rational_valid(const rational_t* x) {
return x != NULL && x->den != 0;
}
void rational_add(rational_t* x, const rational_t* a, const rational_t* b) {
assert(x != NULL);
assert(rational_valid(a));
assert(rational_valid(b));
const int64_t d = math_int64_lcm(a->den, b->den);
*x = (typeof(*x)) {
.num = a->num*(d/a->den) + b->num*(d/b->den),
.den = d,
};
}
void rational_addeq(rational_t* x, const rational_t* a) {
assert(rational_valid(x));
assert(rational_valid(a));
const int64_t d = math_int64_lcm(x->den, a->den);
x->num = x->num*(d/x->den) + a->num*(d/a->den);
x->den = d;
}
void rational_sub(rational_t* x, const rational_t* a, const rational_t* b) {
assert(x != NULL);
assert(rational_valid(a));
assert(rational_valid(b));
rational_t c = *b;
c.num *= -1;
rational_add(x, a, &c);
}
void rational_subeq(rational_t* x, const rational_t* a) {
assert(rational_valid(x));
assert(rational_valid(a));
rational_t c = *a;
c.num *= -1;
rational_addeq(x, &c);
}
void rational_mul(rational_t* x, const rational_t* a, const rational_t* b) {
assert(x != NULL);
assert(rational_valid(a));
assert(rational_valid(b));
*x = (typeof(*x)) {
.num = a->num*b->num,
.den = a->den*b->den,
};
}
void rational_muleq(rational_t* x, const rational_t* a) {
assert(rational_valid(x));
assert(rational_valid(a));
x->num *= a->num;
x->den *= a->den;
}
void rational_div(rational_t* x, const rational_t* a, const rational_t* b) {
assert(x != NULL);
assert(rational_valid(a));
assert(rational_valid(b));
assert(b->num != 0);
*x = (typeof(*x)) {
.num = a->num*b->den,
.den = a->den*b->num,
};
}
void rational_diveq(rational_t* x, const rational_t* a) {
assert(rational_valid(x));
assert(rational_valid(a));
assert(a->num != 0);
x->num *= a->den;
x->den *= a->num;
}
void rational_simplify(rational_t* x) {
assert(rational_valid(x));
const int64_t d =
x->num != 0? math_int64_gcd(MATH_ABS(x->num), MATH_ABS(x->den)): x->den;
x->num /= d;
x->den /= d;
}
void rational_normalize(rational_t* x, int64_t den) {
assert(rational_valid(x));
assert(den != 0);
x->num = x->num * den / x->den;
x->den = den;
}
float rational_calculate(const rational_t* x) {
assert(rational_valid(x));
return x->num*1.0f / x->den;
}

84
util/math/rational.h Normal file
View File

@@ -0,0 +1,84 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
typedef struct {
int64_t num;
int64_t den;
} rational_t;
#define rational(n, d) ((rational_t) { \
.num = n, \
.den = d, \
})
bool
rational_valid(
const rational_t* x
);
void
rational_add(
rational_t* x,
const rational_t* a,
const rational_t* b
);
void
rational_addeq(
rational_t* x,
const rational_t* a
);
void
rational_sub(
rational_t* x,
const rational_t* a,
const rational_t* b
);
void
rational_subeq(
rational_t* x,
const rational_t* a
);
void
rational_mul(
rational_t* x,
const rational_t* a,
const rational_t* b
);
void
rational_muleq(
rational_t* x,
const rational_t* a
);
void
rational_div(
rational_t* x,
const rational_t* a,
const rational_t* b
);
void
rational_diveq(
rational_t* x,
const rational_t* a
);
void
rational_simplify(
rational_t* x
);
/* Don't forget that this operation may cause a discrepancy. */
void
rational_normalize(
rational_t* x,
int64_t den
);
float
rational_calculate(
const rational_t* x
);

111
util/math/test.c Normal file
View File

@@ -0,0 +1,111 @@
#undef NDEBUG
#include <assert.h>
#include <math.h>
#include <float.h>
#include <stdbool.h>
#include <stdio.h>
#include "./algorithm.h"
#include "./matrix.h"
#include "./rational.h"
#include "./vector.h"
static bool vec4_is_equal(vec4_t v1, vec4_t v2) {
return
MATH_FLOAT_EQUAL(v1.x, v2.x) &&
MATH_FLOAT_EQUAL(v1.y, v2.y) &&
MATH_FLOAT_EQUAL(v1.z, v2.z) &&
MATH_FLOAT_EQUAL(v1.w, v2.w);
}
static void test_matrix() {
vec4_t v1, v2;
mat4_t m1, m2, m3;
v1 = vec4(1, 1, 1, 1);
m1 = mat4_identity();
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, v1));
v1 = vec4(1, 1, 1, 1);
m1 = mat4_translation(1, 1, 1);
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, vec4(2, 2, 2, 1)));
v1 = vec4(1, 1, 1, 1);
m1 = mat4_scale(.5f, 2, 1);
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, vec4(.5f, 2, 1, 1)));
v1 = vec4(0, 1, 0, 1);
m1 = mat4_rotation_x(-1 /* = sin -PI/2 */, 0 /* = cos -PI/2 */);
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
v1 = vec4(1, 0, 0, 1);
m1 = mat4_rotation_y(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
v1 = vec4(0, 0, -1, 1);
m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
mat4_mul_vec4(&v2, &m1, &v1);
assert(vec4_is_equal(v2, vec4(0, 0, -1, 1)));
v1 = vec4(-1, 1, 0, 1);
m1 = mat4_rotation_z(1 /* = sin PI/2 */, 0 /* = cos PI/2 */);
m2 = mat4_scale(.5f, .5f, .5f);
mat4_mul(&m3, &m2, &m1);
m1 = mat4_translation(1, 1, 0);
mat4_mul(&m2, &m1, &m3);
mat4_mul_vec4(&v2, &m2, &v1);
assert(vec4_is_equal(v2, vec4(.5f, .5f, 0, 1)));
}
static void test_algorithm(void) {
assert(math_int32_next_power2(8) == 8);
assert(math_int32_next_power2(20) == 32);
assert(math_int32_next_power2(200) == 256);
assert(math_int32_gcd(10, 5) == 5);
assert(math_int32_gcd(12, 6) == 6);
assert(math_int32_gcd(12, 9) == 3);
assert(math_int32_gcd(12, 11) == 1);
assert(math_int32_lcm(10, 5) == 10);
assert(math_int32_lcm(12, 10) == 60);
assert(math_int32_lcm(7, 8) == 56);
}
static void test_rational(void) {
rational_t x, y;
x = rational(1, 2);
y = rational(1, 4);
rational_addeq(&x, &y);
assert(x.num == 3 && x.den == 4);
x = rational(1, 2);
y = rational(1, 3);
rational_muleq(&x, &y);
assert(x.num == 1 && x.den == 6);
x = rational(10, 20);
rational_simplify(&x);
assert(x.num == 1 && x.den == 2);
x = rational(1, 2);
rational_normalize(&x, 100);
assert(x.num == 50 && x.den == 100);
x = rational(1, 2);
assert(rational_calculate(&x) == 1.0f/2.0f);
}
int main(void) {
test_matrix();
test_algorithm();
test_rational();
return 0;
}

275
util/math/vector.c Normal file
View File

@@ -0,0 +1,275 @@
#include "./vector.h"
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include "./algorithm.h"
bool vec2_valid(const vec2_t* x) {
return x != NULL &&
MATH_FLOAT_VALID(x->ptr[0]) &&
MATH_FLOAT_VALID(x->ptr[1]);
}
bool vec3_valid(const vec3_t* x) {
return x != NULL &&
MATH_FLOAT_VALID(x->ptr[0]) &&
MATH_FLOAT_VALID(x->ptr[1]) &&
MATH_FLOAT_VALID(x->ptr[2]);
}
bool vec4_valid(const vec4_t* x) {
return x != NULL &&
MATH_FLOAT_VALID(x->ptr[0]) &&
MATH_FLOAT_VALID(x->ptr[1]) &&
MATH_FLOAT_VALID(x->ptr[2]) &&
MATH_FLOAT_VALID(x->ptr[3]);
}
void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x + r->x;
x->y = l->y + r->y;
}
void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x + r->x;
x->y = l->y + r->y;
x->z = l->z + r->z;
}
void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x + r->x;
x->y = l->y + r->y;
x->z = l->z + r->z;
x->w = l->w + r->w;
}
void vec2_addeq(vec2_t* x, const vec2_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x += r->x;
x->y += r->y;
}
void vec3_addeq(vec3_t* x, const vec3_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x += r->x;
x->y += r->y;
x->z += r->z;
}
void vec4_addeq(vec4_t* x, const vec4_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x += r->x;
x->y += r->y;
x->z += r->z;
x->w += r->w;
}
void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x - r->x;
x->y = l->y - r->y;
}
void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x - r->x;
x->y = l->y - r->y;
x->z = l->z - r->z;
}
void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r) {
assert(x != NULL);
assert(l != NULL);
assert(r != NULL);
x->x = l->x - r->x;
x->y = l->y - r->y;
x->z = l->z - r->z;
x->w = l->w - r->w;
}
void vec2_subeq(vec2_t* x, const vec2_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x -= r->x;
x->y -= r->y;
}
void vec3_subeq(vec3_t* x, const vec3_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x -= r->x;
x->y -= r->y;
x->z -= r->z;
}
void vec4_subeq(vec4_t* x, const vec4_t* r) {
assert(x != NULL);
assert(r != NULL);
x->x -= r->x;
x->y -= r->y;
x->z -= r->z;
x->w -= r->w;
}
void vec2_mul(vec2_t* x, const vec2_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x * r;
x->y = l->y * r;
}
void vec3_mul(vec3_t* x, const vec3_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x * r;
x->y = l->y * r;
x->z = l->z * r;
}
void vec4_mul(vec4_t* x, const vec4_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x * r;
x->y = l->y * r;
x->z = l->z * r;
x->w = l->w * r;
}
void vec2_muleq(vec2_t* x, float r) {
assert(x != NULL);
x->x *= r;
x->y *= r;
}
void vec3_muleq(vec3_t* x, float r) {
assert(x != NULL);
x->x *= r;
x->y *= r;
x->z *= r;
}
void vec4_muleq(vec4_t* x, float r) {
assert(x != NULL);
x->x *= r;
x->y *= r;
x->z *= r;
x->w *= r;
}
void vec2_div(vec2_t* x, const vec2_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x / r;
x->y = l->y / r;
}
void vec3_div(vec3_t* x, const vec3_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x / r;
x->y = l->y / r;
x->z = l->z / r;
}
void vec4_div(vec4_t* x, const vec4_t* l, float r) {
assert(x != NULL);
assert(l != NULL);
x->x = l->x / r;
x->y = l->y / r;
x->z = l->z / r;
x->w = l->w / r;
}
void vec2_diveq(vec2_t* x, float r) {
assert(x != NULL);
x->x /= r;
x->y /= r;
}
void vec3_diveq(vec3_t* x, float r) {
assert(x != NULL);
x->x /= r;
x->y /= r;
x->z /= r;
}
void vec4_diveq(vec4_t* x, float r) {
assert(x != NULL);
x->x /= r;
x->y /= r;
x->z /= r;
x->w /= r;
}
float vec2_pow_length(const vec2_t* x) {
assert(x != NULL);
return x->x*x->x + x->y*x->y;
}
float vec3_pow_length(const vec3_t* x) {
assert(x != NULL);
return x->x*x->x + x->y*x->y + x->z*x->z;
}
float vec4_pow_length(const vec4_t* x) {
assert(x != NULL);
return x->x*x->x + x->y*x->y + x->z*x->z + x->w*x->w;
}
float vec2_length(const vec2_t* x) {
assert(x != NULL);
return sqrtf(vec2_pow_length(x));
}
float vec3_length(const vec3_t* x) {
assert(x != NULL);
return sqrtf(vec3_pow_length(x));
}
float vec4_length(const vec4_t* x) {
assert(x != NULL);
return sqrtf(vec4_pow_length(x));
}
float vec2_dot(const vec2_t* l, const vec2_t* r) {
assert(l != NULL);
assert(r != NULL);
return l->x*r->x + l->y*r->y;
}
float vec3_dot(const vec3_t* l, const vec3_t* r) {
assert(l != NULL);
assert(r != NULL);
return l->x*r->x + l->y*r->y + l->z*r->z;
}
float vec4_dot(const vec4_t* l, const vec4_t* r) {
assert(l != NULL);
assert(r != NULL);
return l->x*r->x + l->y*r->y + l->z*r->z + l->w*r->w;
}
float vec2_cross(const vec2_t* l, const vec2_t* r) {
assert(l != NULL);
assert(r != NULL);
return l->x*r->y - l->y*r->x;
}

78
util/math/vector.h Normal file
View File

@@ -0,0 +1,78 @@
#pragma once
#include <stdbool.h>
typedef union {
struct { float x, y; };
float ptr[2];
} vec2_t;
typedef union {
struct { float x, y, z; };
float ptr[3];
vec2_t xy;
} vec3_t;
typedef union {
struct { float x, y, z, w; };
float ptr[4];
vec2_t xy;
vec3_t xyz;
} vec4_t;
#define vec2(x, y) (vec2_t) {{x, y}}
#define vec3(x, y, z) (vec3_t) {{x, y, z}}
#define vec4(x, y, z, w) (vec4_t) {{x, y, z, w}}
bool vec2_valid(const vec2_t* x);
bool vec3_valid(const vec3_t* x);
bool vec4_valid(const vec4_t* x);
void vec2_add(vec2_t* x, const vec2_t* l, const vec2_t* r);
void vec3_add(vec3_t* x, const vec3_t* l, const vec3_t* r);
void vec4_add(vec4_t* x, const vec4_t* l, const vec4_t* r);
void vec2_addeq(vec2_t* x, const vec2_t* r);
void vec3_addeq(vec3_t* x, const vec3_t* r);
void vec4_addeq(vec4_t* x, const vec4_t* r);
void vec2_sub(vec2_t* x, const vec2_t* l, const vec2_t* r);
void vec3_sub(vec3_t* x, const vec3_t* l, const vec3_t* r);
void vec4_sub(vec4_t* x, const vec4_t* l, const vec4_t* r);
void vec2_subeq(vec2_t* x, const vec2_t* r);
void vec3_subeq(vec3_t* x, const vec3_t* r);
void vec4_subeq(vec4_t* x, const vec4_t* r);
void vec2_mul(vec2_t* x, const vec2_t* l, float r);
void vec3_mul(vec3_t* x, const vec3_t* l, float r);
void vec4_mul(vec4_t* x, const vec4_t* l, float r);
void vec2_muleq(vec2_t* x, float r);
void vec3_muleq(vec3_t* x, float r);
void vec4_muleq(vec4_t* x, float r);
void vec2_div(vec2_t* x, const vec2_t* l, float r);
void vec3_div(vec3_t* x, const vec3_t* l, float r);
void vec4_div(vec4_t* x, const vec4_t* l, float r);
void vec2_diveq(vec2_t* x, float r);
void vec3_diveq(vec3_t* x, float r);
void vec4_diveq(vec4_t* x, float r);
float vec2_pow_length(const vec2_t* x);
float vec3_pow_length(const vec3_t* x);
float vec4_pow_length(const vec4_t* x);
float vec2_length(const vec2_t* x);
float vec3_length(const vec3_t* x);
float vec4_length(const vec4_t* x);
float vec2_dot(const vec2_t* l, const vec2_t* r);
float vec3_dot(const vec3_t* l, const vec3_t* r);
float vec4_dot(const vec4_t* l, const vec4_t* r);
float vec2_cross(const vec2_t* l, const vec2_t* r);
/* TODO(catfoot): add vec3_cross function */

View File

@@ -0,0 +1,10 @@
add_library(memory
memory.c
)
if (BUILD_TESTING)
add_executable(memory-test test.c)
target_link_libraries(memory-test memory)
add_test(memory-test memory-test)
endif()

81
util/memory/memory.c Normal file
View File

@@ -0,0 +1,81 @@
#include "./memory.h"
#include <assert.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef __linux__
# include <execinfo.h>
#endif
#define MEMORY_LOG_FILE "memory-trace"
void* memory_new(size_t size) {
if (size == 0) return NULL;
void* ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "failed to allocate %zu bytes memory", size);
abort();
}
memory_log("new 0x%tx (%zu bytes)", (ptrdiff_t) ptr, size);
return ptr;
}
void* memory_resize(void* ptr, size_t size) {
if (ptr == NULL) return memory_new(size);
if (size == 0) return ptr;
void* newptr = realloc(ptr, size);
if (newptr == NULL) {
fprintf(stderr,
"failed to allocate %zu bytes memory for resizing 0x%tx",
size, (ptrdiff_t) ptr);
abort();
}
memory_log("resize 0x%tx -> 0x%tx (%zu bytes)",
(ptrdiff_t) ptr, (ptrdiff_t) newptr, size);
return newptr;
}
void memory_delete(void* ptr) {
if (ptr == NULL) return;
free(ptr);
memory_log("delete 0x%tx", (ptrdiff_t) ptr);
}
#ifndef NDEBUG
void memory_log(const char* fmt, ...) {
static FILE* fp = NULL;
if (fp == NULL) {
fp = fopen(MEMORY_LOG_FILE, "w");
assert(fp != NULL);
}
va_list args;
va_start(args, fmt);
vfprintf(fp, fmt, args);
va_end(args);
fprintf(fp, "\n");
# if __linux__
void* callstack[32];
const size_t frames = backtrace(callstack, sizeof(callstack));
char** symbols = backtrace_symbols(callstack, frames);
for (size_t i = 0; i < frames; ++i) {
fprintf(fp, " %s\n", symbols[i]);
}
free(symbols);
# endif /* __linux__ */
fprintf(fp, "\n");
fflush(fp);
}
#endif /* NDEBUG */

29
util/memory/memory.h Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include <stddef.h>
void*
memory_new(
size_t size
);
void*
memory_resize(
void* ptr,
size_t size
);
void
memory_delete(
void* ptr
);
#ifndef NDEBUG
void
memory_log(
const char* fmt,
...
);
#else
# define memory_log(fmt, ...) (void) fmt
#endif /* NDEBUG */

8
util/memory/test.c Normal file
View File

@@ -0,0 +1,8 @@
#include "./memory.h"
int main(void) {
void* ptr = memory_new(0x1);
ptr = memory_resize(ptr, 0x2);
memory_delete(ptr);
return 0;
}

View File

@@ -0,0 +1,10 @@
add_library(mpkutil
file.c
get.c
pack.c
)
target_link_libraries(mpkutil
msgpackc
math
)

4
util/mpkutil/README.md Normal file
View File

@@ -0,0 +1,4 @@
mpkutil
====
utility library for msgpack-c

58
util/mpkutil/file.c Normal file
View File

@@ -0,0 +1,58 @@
#include "./file.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <msgpack.h>
bool mpkutil_file_unpack_with_unpacker(
msgpack_unpacked* obj,
FILE* fp,
msgpack_unpacker* unpacker) {
assert(obj != NULL);
assert(fp != NULL);
assert(unpacker != NULL);
for (;;) {
if (feof(fp)) return false;
const size_t maxlen = msgpack_unpacker_buffer_capacity(unpacker);
if (maxlen == 0) {
fprintf(stderr, "unpacker buffer overflow\n");
abort();
}
const size_t len = fread(
msgpack_unpacker_buffer(unpacker), 1, maxlen, fp);
if (ferror(fp)) return false;
msgpack_unpacker_buffer_consumed(unpacker, len);
size_t parsed_len;
switch (msgpack_unpacker_next_with_size(unpacker, obj, &parsed_len)) {
case MSGPACK_UNPACK_SUCCESS:
return fseek(fp, (long) parsed_len - len, SEEK_CUR) == 0;
case MSGPACK_UNPACK_CONTINUE:
break;
case MSGPACK_UNPACK_PARSE_ERROR:
default:
return false;
}
}
}
bool mpkutil_file_unpack(msgpack_unpacked* obj, FILE* fp) {
assert(fp != NULL);
assert(obj != NULL);
msgpack_unpacker unpacker;
if (!msgpack_unpacker_init(&unpacker, 1024)) return false;
const bool result = mpkutil_file_unpack_with_unpacker(obj, fp, &unpacker);
msgpack_unpacker_destroy(&unpacker);
return result;
}

19
util/mpkutil/file.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdbool.h>
#include <stdio.h>
#include <msgpack.h>
bool
mpkutil_file_unpack_with_unpacker(
msgpack_unpacked* obj,
FILE* fp,
msgpack_unpacker* unpacker
);
bool
mpkutil_file_unpack(
msgpack_unpacked* obj,
FILE* fp
);

143
util/mpkutil/get.c Normal file
View File

@@ -0,0 +1,143 @@
#include "./get.h"
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/algorithm.h"
#include "util/math/vector.h"
bool mpkutil_get_bool(const msgpack_object* obj, bool* b) {
if (obj == NULL) return false;
if (obj->type != MSGPACK_OBJECT_BOOLEAN) return false;
if (b != NULL) *b = obj->via.boolean;
return true;
}
bool mpkutil_get_int(const msgpack_object* obj, intmax_t* i) {
if (obj == NULL) return false;
if (obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER &&
obj->type != MSGPACK_OBJECT_NEGATIVE_INTEGER) {
return false;
}
if (i != NULL) *i = obj->via.i64;
return true;
}
#define define_mpkutil_get_intN_(N) \
bool mpkutil_get_int##N(const msgpack_object* obj, int##N##_t* i) { \
intmax_t temp; \
if (!mpkutil_get_int(obj, &temp) || \
temp < INT##N##_MIN || temp > INT##N##_MAX) { \
return false; \
} \
if (i != NULL) *i = temp; \
return true; \
}
define_mpkutil_get_intN_(8);
define_mpkutil_get_intN_(16);
define_mpkutil_get_intN_(32);
define_mpkutil_get_intN_(64);
#undef define_mpkutil_get_intN_
bool mpkutil_get_uint(const msgpack_object* obj, uintmax_t* i) {
if (obj == NULL || obj->type != MSGPACK_OBJECT_POSITIVE_INTEGER) {
return false;
}
if (i != NULL) *i = obj->via.u64;
return true;
}
#define define_mpkutil_get_uintN_(N) \
bool mpkutil_get_uint##N(const msgpack_object* obj, uint##N##_t* i) { \
uintmax_t temp; \
if (!mpkutil_get_uint(obj, &temp) || temp > UINT##N##_MAX) { \
return false; \
} \
if (i != NULL) *i = temp; \
return true; \
}
define_mpkutil_get_uintN_(8);
define_mpkutil_get_uintN_(16);
define_mpkutil_get_uintN_(32);
define_mpkutil_get_uintN_(64);
#undef define_mpkutil_get_uintN_
bool mpkutil_get_float(const msgpack_object* obj, float* f) {
if (obj == NULL || obj->type != MSGPACK_OBJECT_FLOAT64 ||
!MATH_FLOAT_VALID(obj->via.f64)) {
return false;
}
if (f != NULL) *f = obj->via.f64;
return true;
}
bool mpkutil_get_str(const msgpack_object* obj, const char** s, size_t* len) {
if (obj == NULL || obj->type != MSGPACK_OBJECT_STR) {
return false;
}
if (s != NULL) *s = obj->via.str.ptr;
if (len != NULL) *len = obj->via.str.size;
return true;
}
bool mpkutil_get_vec2(const msgpack_object* obj, vec2_t* v) {
const msgpack_object_array* array = mpkutil_get_array(obj);
if (array == NULL || array->size != 2) return false;
for (size_t i = 0; i < 2; ++i) {
if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false;
}
if (v != NULL) {
v->x = array->ptr[0].via.f64;
v->y = array->ptr[1].via.f64;
}
return true;
}
bool mpkutil_get_vec4(const msgpack_object* obj, vec4_t* v) {
const msgpack_object_array* array = mpkutil_get_array(obj);
if (array == NULL || array->size != 4) return false;
for (size_t i = 0; i < 4; ++i) {
if (array->ptr[i].type != MSGPACK_OBJECT_FLOAT64) return false;
}
if (v != NULL) {
v->x = array->ptr[0].via.f64;
v->y = array->ptr[1].via.f64;
v->y = array->ptr[2].via.f64;
v->y = array->ptr[3].via.f64;
}
return true;
}
const msgpack_object_array* mpkutil_get_array(const msgpack_object* obj) {
if (obj == NULL || obj->type != MSGPACK_OBJECT_ARRAY) {
return NULL;
}
return &obj->via.array;
}
const msgpack_object_map* mpkutil_get_map(const msgpack_object* obj) {
if (obj == NULL || obj->type != MSGPACK_OBJECT_MAP) {
return NULL;
}
return &obj->via.map;
}
const msgpack_object* mpkutil_get_map_item_by_str(
const msgpack_object_map* map, const char* name) {
if (map == NULL) return NULL;
for (size_t i = 0; i < map->size; ++i) {
const msgpack_object* key_obj = &map->ptr[i].key;
if (key_obj->type != MSGPACK_OBJECT_STR) continue;
const msgpack_object_str* key = &key_obj->via.str;
if (strncmp(key->ptr, name, key->size) == 0 && name[key->size] == 0) {
return &map->ptr[i].val;
}
}
return NULL;
}

92
util/mpkutil/get.h Normal file
View File

@@ -0,0 +1,92 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <msgpack.h>
#include "util/math/vector.h"
bool
mpkutil_get_bool(
const msgpack_object* obj, /* NULLABLE */
bool* b /* NULLABLE */
);
bool
mpkutil_get_int(
const msgpack_object* obj, /* NULLABLE */
intmax_t* i /* NULLABLE */
);
#define decl_mpkutil_get_intN_(N) \
bool \
mpkutil_get_int##N( \
const msgpack_object* obj, \
int##N##_t* i \
);
decl_mpkutil_get_intN_(8);
decl_mpkutil_get_intN_(16);
decl_mpkutil_get_intN_(32);
decl_mpkutil_get_intN_(64);
#undef decl_mpkutil_get_intN_
bool
mpkutil_get_uint(
const msgpack_object* obj, /* NULLABLE */
uintmax_t* i /* NULLABLE */
);
#define decl_mpkutil_get_uintN_(N) \
bool \
mpkutil_get_uint##N( \
const msgpack_object* obj, \
uint##N##_t* i \
);
decl_mpkutil_get_uintN_(8);
decl_mpkutil_get_uintN_(16);
decl_mpkutil_get_uintN_(32);
decl_mpkutil_get_uintN_(64);
#undef decl_mpkutil_get_uintN_
bool
mpkutil_get_float(
const msgpack_object* obj, /* NULLABLE */
float* f /* NULLABLE */
);
bool
mpkutil_get_str(
const msgpack_object* obj, /* NULLABLE */
const char** s, /* NULLABLE */
size_t* len /* NULLABLE */
);
bool
mpkutil_get_vec2(
const msgpack_object* obj, /* NULLABLE */
vec2_t* v /* NULLABLE */
);
bool
mpkutil_get_vec4(
const msgpack_object* obj, /* NULLABLE */
vec4_t* v /* NULLABLE */
);
const msgpack_object_array* /* NULLABLE */
mpkutil_get_array(
const msgpack_object* obj /* NULLABLE */
);
const msgpack_object_map* /* NULLABLE */
mpkutil_get_map(
const msgpack_object* obj /* NULLABLE */
);
const msgpack_object* /* NULLABLE */
mpkutil_get_map_item_by_str(
const msgpack_object_map* map, /* NULLABLE */
const char* name /* NULL-terminated */
);

49
util/mpkutil/pack.c Normal file
View File

@@ -0,0 +1,49 @@
#include "./pack.h"
#include <assert.h>
#include <stddef.h>
#include <msgpack.h>
#include "util/math/vector.h"
void mpkutil_pack_bool(msgpack_packer* packer, bool b) {
assert(packer != NULL);
(b? msgpack_pack_true: msgpack_pack_false)(packer);
}
void mpkutil_pack_str(msgpack_packer* packer, const char* str) {
assert(packer != NULL);
assert(str != NULL);
mpkutil_pack_strn(packer, str, strlen(str));
}
void mpkutil_pack_strn(msgpack_packer* packer, const char* str, size_t len) {
assert(packer != NULL);
assert(str != NULL);
msgpack_pack_str(packer, len);
msgpack_pack_str_body(packer, str, len);
}
void mpkutil_pack_vec2(msgpack_packer* packer, const vec2_t* v) {
assert(packer != NULL);
assert(v != NULL);
msgpack_pack_array(packer, 2);
msgpack_pack_double(packer, v->x);
msgpack_pack_double(packer, v->y);
}
void mpkutil_pack_vec4(msgpack_packer* packer, const vec4_t* v) {
assert(packer != NULL);
assert(v != NULL);
msgpack_pack_array(packer, 4);
msgpack_pack_double(packer, v->x);
msgpack_pack_double(packer, v->y);
msgpack_pack_double(packer, v->z);
msgpack_pack_double(packer, v->w);
}

39
util/mpkutil/pack.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <msgpack.h>
#include "util/math/vector.h"
void
mpkutil_pack_bool(
msgpack_packer* packer,
bool v
);
void
mpkutil_pack_str(
msgpack_packer* packer,
const char* str /* NULL-terminated */
);
void
mpkutil_pack_strn(
msgpack_packer* packer,
const char* str,
size_t len
);
void
mpkutil_pack_vec2(
msgpack_packer* packer,
const vec2_t* v
);
void
mpkutil_pack_vec4(
msgpack_packer* packer,
const vec4_t* v
);

View File

@@ -0,0 +1,12 @@
add_library(parsarg
parsarg.c
)
if (BUILD_TESTING)
add_executable(parsarg-test test.c)
target_link_libraries(parsarg-test
parsarg
conv
)
endif()

Some files were not shown because too many files have changed in this diff Show More