From f66c1618d21c62b7bf54038cefada6f36a0f5d07 Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Sun, 27 Jul 2025 10:51:33 +0300 Subject: [PATCH] Added a function to generate texture templates. Drawn some stupid textures for my stupid cylinders --- .gitignore | 8 +- CMakeLists.txt | 3 + src/l1/core/VecSpan_int_primitives.h | 9 +- src/l1/core/int_primitives.h | 2 + src/l1/gen/geom_and_textures.h | 772 ++++++++++++++++++ src/l1/main.c | 573 +------------ src/l2/{margaret => core}/stringop.h | 2 - src/l2/margaret/graphics_geom.h | 1 - src/l2/margaret/margaret.h | 102 ++- src/l2/tests/r0.c | 170 ++-- src/l2/tests/r0_assets.h | 422 ++++++---- src/l2/tests/r0_scene.h | 109 +++ src/l2/tests/r0_tex_init_prep.c | 9 + src/l2/tests/test_shader_compile.sh | 10 + src/l2/tests/test_shaders/glsl/0/0.frag | 26 +- src/l2/tests/test_shaders/glsl/0/0.vert | 8 +- src/l2/tests/test_shaders/glsl/0b/0b.frag | 0 src/l2/tests/test_shaders/glsl/0b/0b.vert | 0 .../tests/test_textures/bitmap_converter.py | 119 +++ src/l2/tests/test_textures/log_10_2_6.png | Bin 0 -> 16484 bytes .../test_textures/log_10_2_6_TEMPLATE.png | Bin 0 -> 1084 bytes src/l2/tests/test_textures/log_10_2_6_v2.png | Bin 0 -> 18647 bytes 22 files changed, 1500 insertions(+), 845 deletions(-) create mode 100644 src/l1/gen/geom_and_textures.h rename src/l2/{margaret => core}/stringop.h (95%) create mode 100644 src/l2/tests/r0_scene.h create mode 100644 src/l2/tests/r0_tex_init_prep.c create mode 100644 src/l2/tests/test_shaders/glsl/0b/0b.frag create mode 100644 src/l2/tests/test_shaders/glsl/0b/0b.vert create mode 100755 src/l2/tests/test_textures/bitmap_converter.py create mode 100644 src/l2/tests/test_textures/log_10_2_6.png create mode 100644 src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png create mode 100644 src/l2/tests/test_textures/log_10_2_6_v2.png diff --git a/.gitignore b/.gitignore index 9b8c381..4306bd3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,9 @@ cmake-build-debug/ .idea/ vgcore.* -gen/ -*.pdf \ No newline at end of file +/gen/ +*.pdf +*.r8g8b8a8 +*.r8b8g8 +*.r8 +*.xcf diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ea6104..fb83dc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ add_executable(1_test src/l1/tests/t1.c) add_executable(0_render_test src/l2/tests/r0.c) target_link_libraries(0_render_test -lvulkan -lX11 -lm) +add_executable(0_render_test_tex_init_prep src/l2/tests/r0_tex_init_prep.c) +target_link_libraries(0_render_test_tex_init_prep -lm) + add_executable(0_play_test src/l3/tests/p0.c) target_link_libraries(0_play_test -lncurses) diff --git a/src/l1/core/VecSpan_int_primitives.h b/src/l1/core/VecSpan_int_primitives.h index f04494a..0dc731e 100644 --- a/src/l1/core/VecSpan_int_primitives.h +++ b/src/l1/core/VecSpan_int_primitives.h @@ -21,9 +21,16 @@ ConstSpanU8 ConstSpanU8_from_cstr(const char* dc) { } #define cstr(dc) ConstSpanU8_from_cstr(dc) +/* Not thread safe (for stdout) !*/ void ConstSpanU8_print(ConstSpanU8 str) { for (size_t i = 0; i < str.len; i++) - putchar((int)*ConstSpanU8_at(str, i)); + putc((int)*ConstSpanU8_at(str, i), stdout); +} + +/* Not thread safe (for `stream`) ! */ +void ConstSpanU8_fprint( ConstSpanU8 str, FILE* stream) { + for (size_t i = 0; i < str.len; i++) + putc((int)*ConstSpanU8_at(str, i), stream); } SpanT_VecT_trivmove_COMPLETE_Definition(U16) diff --git a/src/l1/core/int_primitives.h b/src/l1/core/int_primitives.h index 3e4e7ca..c0cb9bd 100644 --- a/src/l1/core/int_primitives.h +++ b/src/l1/core/int_primitives.h @@ -57,5 +57,7 @@ T MAX_##T (T a, T b){ return a < b ? b : a; } int_minmax_function_Definition(U8) int_minmax_function_Definition(U32) int_minmax_function_Definition(U64) +int_minmax_function_Definition(float) +int_minmax_function_Definition(double) #endif diff --git a/src/l1/gen/geom_and_textures.h b/src/l1/gen/geom_and_textures.h new file mode 100644 index 0000000..5666c6f --- /dev/null +++ b/src/l1/gen/geom_and_textures.h @@ -0,0 +1,772 @@ +#ifndef PROTOTYPE_SRC_L1_GEN_GEOM_AND_TEXTURES_H +#define PROTOTYPE_SRC_L1_GEN_GEOM_AND_TEXTURES_H + +#include + +#include "../system/fileio.h" +#include "../core/VecU8_format.h" + +NODISCARD VecU8 begin_header(ConstSpanU8 guard) { + VecU8 res = VecU8_new(); + VecU8_append_span(&res, cstr("#ifndef ")); + VecU8_append_span(&res, guard); + VecU8_append_span(&res, cstr("\n#define ")); + VecU8_append_span(&res, guard); + VecU8_append_span(&res, cstr("\n/* Automatically generated file. Do not edit it. */\n\n")); + return res; +} + +/* Codegen script's working directory should be `gen` */ +void finish_header(VecU8 text_before_endif, const char* filename) { + VecU8_append_span(&text_before_endif, cstr("#endif\n")); + write_whole_file_or_abort(filename, VecU8_to_ConstSpanU8(&text_before_endif)); + VecU8_drop(text_before_endif); +} + +#define SPACE4 " " +#define SPACE8 " " +#define SPACE12 " " + +void string_append_vec_field_name(VecU8* str, int ci) { + assert(0 <= ci && ci < 4); + + VecU8_append(str, ci == 3 ? 'w' : 'x' + ci); +} + +void string_append_xvecy(VecU8* str, ConstSpanU8 xvec, int cc) { + VecU8_append_span(str, xvec); + VecU8_append(str, '0' + cc); +} + +NODISCARD VecU8 generate_xvecy_struct_definition(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + assert(2 <= cc && cc <= 4); + VecU8 res = VecU8_new(); + VecU8_append_span(&res, cstr("typedef struct {\n")); + + for (int ci = 0; ci < cc; ci++) { + VecU8_append_span(&res, cstr(SPACE4)); + VecU8_append_span(&res, member); + VecU8_append(&res, ' '); + string_append_vec_field_name(&res, ci); + VecU8_append_span(&res, cstr(";\n")); + } + + VecU8_append_span(&res, cstr("} ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(";\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvecy_method_add_xvecy(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_add_")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("){ ")); + for (int ci = 0; ci < cc; ci++) { + if (ci) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, cstr("A.")); + string_append_vec_field_name(&res, ci); + VecU8_append_span(&res, cstr(" + B.")); + string_append_vec_field_name(&res, ci); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvecy_method_minus_xvecy(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_minus_")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("){ ")); + for (int ci = 0; ci < cc; ci++) { + if (ci) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, cstr("A.")); + string_append_vec_field_name(&res, ci); + VecU8_append_span(&res, cstr(" - B.")); + string_append_vec_field_name(&res, ci); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + + +NODISCARD VecU8 generate_xvecy_method_minus(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_minus(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("){ ")); + for (int ci = 0; ci < cc; ci++) { + if (ci) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, cstr("-A.")); + string_append_vec_field_name(&res, ci); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvecy_method_mul_scal(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_mul_scal(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" A, ")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("){ ")); + for (int ci = 0; ci < cc; ci++) { + if (ci) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, cstr("A.")); + string_append_vec_field_name(&res, ci); + VecU8_append_span(&res, cstr(" * B")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvecy_method_div_by_scal(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_div_by_scal(")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr(" A, ")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(" B){\n" SPACE4 "return ")); + string_append_xvecy(&res, xvec, cc); + VecU8_append_span(&res, cstr("_mul_scal(A, 1/B);\n}\n\n")); + return res; +} + +void string_append_xmatnm(VecU8* str, ConstSpanU8 xmat, int cols, int rows) { + VecU8_append_span(str, xmat); + VecU8_append(str, '0' + cols); + if (rows != cols) { + VecU8_append(str, 'x'); + VecU8_append(str, '0' + rows); + } +} + +/* With columns padded to 16 bytes (for std140, std140 is our everything) */ +NODISCARD VecU8 generate_xmatnm_structure_definition(ConstSpanU8 xmat, ConstSpanU8 xvec, int cols, int rows, int sizeof_member) { + int sv = (rows * sizeof_member) % 16; + VecU8 res = VecU8_from_cstr("typedef struct {\n"); + for (int x = 0; x < cols; x++) { + VecU8_append_span(&res, cstr(SPACE4)); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr(" ")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(";\n")); + if (sv) { + VecU8_append_vec(&res, VecU8_format(SPACE4 "char _padding_%d[%d];\n", x, 16 - sv)); + } + } + VecU8_append_span(&res, cstr("} ")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(";\n\n")); + return res; +} + +void string_append_mat_col_definition(VecU8* str, int ci) { + VecU8_append(str, '.'); + string_append_vec_field_name(str, ci); + VecU8_append_span(str, cstr(" = ")); +} + +void string_append_mat_el_access(VecU8* str, int x, int y) { + VecU8_append(str, '.'); + string_append_vec_field_name(str, x); + VecU8_append(str, '.'); + string_append_vec_field_name(str, y); +} + +NODISCARD VecU8 generate_xmatnm_method_new(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_new(")); + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + if (x > 0 || y > 0) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, member); + VecU8_append(&res, ' '); + string_append_vec_field_name(&res, x); + string_append_vec_field_name(&res, y); + } + } + VecU8_append_span(&res, cstr(") {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("){ ")); + for (int x = 0; x < cols; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + VecU8_append_span(&res, cstr("{ ")); + for (int y = 0; y < rows; y++) { + if (y) + VecU8_append_span(&res, cstr(", ")); + string_append_vec_field_name(&res, x); + string_append_vec_field_name(&res, y); + } + VecU8_append_span(&res, cstr(" }")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_square_xmatnn_E_definition(ConstSpanU8 xmat, int n) { + VecU8 res = VecU8_from_cstr("const "); + string_append_xmatnm(&res, xmat, n, n); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, n, n); + VecU8_append_span(&res, cstr("_E = { ")); + for (int x = 0; x < n; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + VecU8_append_span(&res, cstr("{ ")); + for (int y = 0; y < n; y++) { + if (y) + VecU8_append_span(&res, cstr(", ")); + VecU8_append(&res, '0' + (x == y)); + } + VecU8_append_span(&res, cstr(" }")); + } + VecU8_append_span(&res, cstr(" };\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_add_xmatnm(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_add_")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("(")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("){ ")); + for (int x = 0; x < cols; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("_add_")); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("(A.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(", B.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(")")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_minus_xmatnm(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_minus_")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("(")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("){ ")); + for (int x = 0; x < cols; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("_minus_")); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("(A.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(", B.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(")")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_minus(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_minus(")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("){ ")); + for (int x = 0; x < cols; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("_minus(A.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(")")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_mul_scal(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_mul_scal(")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" A, ")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("){ ")); + for (int x = 0; x < cols; x++) { + if (x) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, x); + string_append_xvecy(&res, xvec, rows); + VecU8_append_span(&res, cstr("_mul_scal(A.")); + string_append_vec_field_name(&res, x); + VecU8_append_span(&res, cstr(", B)")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_div_by_scal(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_div_by_scal(")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr(" A, ")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); + string_append_xmatnm(&res, xmat, cols, rows); + VecU8_append_span(&res, cstr("_mul_scal(A, 1/B);\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvecn_method_dot_xvecn(ConstSpanU8 xvec, ConstSpanU8 member, int n) { + VecU8 res = VecU8_from_span(member); + VecU8_append(&res, ' '); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr("_dot_")); + string_append_xvecy(&res, xvec, n); + VecU8_append(&res, '('); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); + for (int i = 0; i < n; i++) { + if (i) + VecU8_append_span(&res, cstr(" + ")); + VecU8_append_span(&res, cstr("A.")); + string_append_vec_field_name(&res, i); + VecU8_append_span(&res, cstr(" * B.")); + string_append_vec_field_name(&res, i); + } + VecU8_append_span(&res, cstr(";\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_mul_xvecn(ConstSpanU8 xmat, ConstSpanU8 xvec, int n, int m) { + VecU8 res = VecU8_new(); + string_append_xvecy(&res, xvec, m); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr("_mul_")); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr("(")); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xvecy(&res, xvec, n); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xvecy(&res, xvec, m); + VecU8_append_span(&res, cstr("){")); + for (int ci = 0; ci < m; ci++) { + if (ci) + VecU8_append(&res, ','); + VecU8_append_span(&res, cstr("\n" SPACE8)); + for (int x = 0; x < n; x++) { + if (x) + VecU8_append_span(&res, cstr(" + ")); + VecU8_append_span(&res, cstr("A")); + string_append_mat_el_access(&res, x, ci); + VecU8_append_span(&res, cstr(" * B.")); + string_append_vec_field_name(&res, x); + } + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xmatnm_method_mul_xmatkn(ConstSpanU8 xmat, int n, int m, int k) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, k, m); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr("_mul_")); + string_append_xmatnm(&res, xmat, k, n); + VecU8_append_span(&res, cstr("(")); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr(" A, ")); + string_append_xmatnm(&res, xmat, k, n); + VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, k, m); + VecU8_append_span(&res, cstr("){")); + for (int x = 0; x < k; x++) { + if (x) + VecU8_append_span(&res, cstr(",")); + VecU8_append_span(&res, cstr("\n" SPACE8)); + string_append_mat_col_definition(&res, x); + VecU8_append_span(&res, cstr("{ ")); + for (int y = 0; y < m; y++) { + if (y) + VecU8_append_span(&res, cstr(", ")); + for (int z = 0; z < n; z++) { + if (z) + VecU8_append_span(&res, cstr(" + ")); + VecU8_append_span(&res, cstr("A")); + string_append_mat_el_access(&res, z, y); + VecU8_append_span(&res, cstr(" * B")); + string_append_mat_el_access(&res, x, z); + } + } + VecU8_append_span(&res, cstr(" }")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +VecU8 generate_xmatnm_method_transpose(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int n, int m) { + VecU8 res = VecU8_new(); + string_append_xmatnm(&res, xmat, m, n); + VecU8_append(&res, ' '); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr("_transpose(")); + string_append_xmatnm(&res, xmat, n, m); + VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return (")); + string_append_xmatnm(&res, xmat, m, n); + VecU8_append_span(&res, cstr("){ ")); + for (int bx = 0; bx < m; bx++) { + if (bx) + VecU8_append_span(&res, cstr(", ")); + string_append_mat_col_definition(&res, bx); + VecU8_append_span(&res, cstr("{ ")); + for (int by = 0; by < n; by++) { + if (by) + VecU8_append_span(&res, cstr(", ")); + VecU8_append_span(&res, cstr("A")); + string_append_mat_el_access(&res, by, bx); + } + VecU8_append_span(&res, cstr(" }")); + } + VecU8_append_span(&res, cstr(" };\n}\n\n")); + return res; +} + +NODISCARD VecU8 generate_xvec234_structs_and_methods(ConstSpanU8 xvec, ConstSpanU8 member) { + VecU8 res = VecU8_new(); + for (int cc = 2; cc <= 4; cc++) { + VecU8_append_vec(&res, generate_xvecy_struct_definition(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_add_xvecy(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_minus_xvecy(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_minus(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_mul_scal(xvec, member, cc)); + VecU8_append_vec(&res, generate_xvecy_method_div_by_scal(xvec, member, cc)); + } + return res; +} + +NODISCARD VecU8 generate_xmat234x234_structs_and_methods(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int sizeof_member) { + VecU8 res = VecU8_new(); + for (int cols = 2; cols <= 4; cols++) { + for (int rows = 2; rows <= 4; rows++) { + VecU8_append_vec(&res, generate_xmatnm_structure_definition(xmat, xvec, cols, rows, sizeof_member)); + } + } + for (int cols = 2; cols <= 4; cols++) { + for (int rows = 2; rows <= 4; rows++) { + VecU8_append_vec(&res, generate_xmatnm_method_new(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_add_xmatnm(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_minus_xmatnm(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_minus(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_mul_scal(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_div_by_scal(xmat, xvec, member, cols, rows)); + VecU8_append_vec(&res, generate_xmatnm_method_transpose(xmat, xvec, member, cols, rows)); + } + } + for (int n = 2; n <= 4; n++) { + VecU8_append_vec(&res, generate_square_xmatnn_E_definition(xmat, n)); + } + for (int n = 2; n <= 4; n++) { + for (int m = 2; m <= 4; m++) { + VecU8_append_vec(&res, generate_xmatnm_method_mul_xvecn(xmat, xvec, n, m)); + } + } + for (int n = 2; n <= 4; n++) { + for (int m = 2; m <= 4; m++) { + for (int k = 2; k <= 4; k++) { + VecU8_append_vec(&res, generate_xmatnm_method_mul_xmatkn(xmat, n, m, k)); + } + } + } + return res; +} + +void generate_geom_header() { + VecU8 res = begin_header(cstr("PROTOTYPE1_GEN_GEOM")); + VecU8_append_span(&res, cstr("#include \"../src/l1/core/int_primitives.h\"\n\n")); + VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("cvec"), cstr("U8"))); + VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("ivec"), cstr("S32"))); + + VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("vec"), cstr("float"))); + VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("dvec"), cstr("double"))); + VecU8_append_vec(&res, generate_xmat234x234_structs_and_methods(cstr("mat"), cstr("vec"), cstr("float"), sizeof(float))); + VecU8_append_vec(&res, generate_xmat234x234_structs_and_methods(cstr("dmat"), cstr("dvec"), cstr("double"), sizeof(double))); + finish_header(res, "geom.h"); +} + +VecU8 generate_type_triv_methods_and_vec(ConstSpanU8 member) { + VecU8 res = VecU8_from_cstr("#define "); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr("_drop(x) {}\n#define ")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr("_clone(xp) (*(xp))\n\n")); + VecU8_append_span(&res, cstr("VecT_trivmove_struct_Definition(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(")\nVecT_trivmove_method_Definition(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(")\nVecT_primitive_zeroinit_method_Definition(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(")\n\n")); + return res; +} + +/* code generation function. We don't append vector data to vecu8, we append vector name to string in VecU8 */ +void VecU8_append_vecoft(VecU8* str, ConstSpanU8 t) { + VecU8_append_span(str, cstr("Vec")); + VecU8_append_span(str, t); +} + +void VecU8_append_resoftexdatat(VecU8* str, ConstSpanU8 texdatat) { + VecU8_append_span(str, cstr("Result")); + VecU8_append_span(str, texdatat); + VecU8_append_span(str, cstr("OrConstSpanU8")); +} + +/* Used to generate both _at() and _cat() methods */ +VecU8 generate_texture_data_method_at(ConstSpanU8 texdatat, ConstSpanU8 member, bool const_access) { + VecU8 res = VecU8_from_span(member); + VecU8_append_span(&res, cstr("* ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, const_access ? cstr("_cat") : cstr("_at")); + VecU8_append_span(&res, cstr("(")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("* self, size_t x, size_t y) {\n" SPACE4 "assert(x < self->width);\n" + SPACE4 "return ")); + VecU8_append_vecoft(&res, member); + VecU8_append_span(&res, const_access ? cstr("_cat") : cstr("_at")); + VecU8_append_span(&res, cstr("(&self->pixels, x + y * self->width);\n}\n\n")); + return res; +} + +VecU8 generate_texture_data_struct_and_necc_methods(ConstSpanU8 texdatat, ConstSpanU8 member) { + VecU8 res = VecU8_from_cstr("typedef struct {\n" SPACE4); + VecU8_append_vecoft(&res, member); + VecU8_append_span(&res, cstr(" pixels;\n" SPACE4 "size_t width;\n} ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr(";\n\n")); + /* Method _new() */ + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr(" ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_new(U32 width, U32 height) {\n" + SPACE4 "assert(!(SIZE_MAX / width / height < 100 || UINT32_MAX / width < 10 || UINT32_MAX / height < 10));\n" + SPACE4 "return (")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("){.pixels = ")); + VecU8_append_vecoft(&res, member); + VecU8_append_span(&res, cstr("_new_zeroinit((size_t)width * height), .width = width};\n}\n\n")); + /* Method _drop() */ + VecU8_append_span(&res, cstr("void ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_drop(")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr(" self) {\n" SPACE4)); + VecU8_append_vecoft(&res, member); + VecU8_append_span(&res, cstr("_drop(self.pixels);\n}\n\n")); + /* Method _get_height() */ + VecU8_append_span(&res, cstr("size_t ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_get_height(const ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("* self) {\n" SPACE4 "return self->pixels.len / self->width;\n}\n\n")); + /* Methods _at and _cat */ + VecU8_append_vec(&res, generate_texture_data_method_at(texdatat, member, false)); + VecU8_append_span(&res, cstr("const ")); + VecU8_append_vec(&res, generate_texture_data_method_at(texdatat, member, true)); + /* Method _get_size_in_bytes */ + VecU8_append_span(&res, cstr("size_t ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_get_size_in_bytes(const ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("* self) {\n" SPACE4 "return self->pixels.len * sizeof(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(");\n}\n\n")); + /* Method _to_bitmap_text() + * We use the assumption that bytes in type member are tightly packed */ + VecU8_append_span(&res, cstr("VecU8 ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_to_bitmap_text(const ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("* self) {\n" + SPACE4 "assert(SIZE_MAX / self->pixels.len >= 100);\n" + SPACE4 "size_t len = self->pixels.len * sizeof(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(");\n" + SPACE4 "VecU8 res = VecU8_new_zeroinit(8 + len);\n" + SPACE4 "size_t width = self->width;\n" + SPACE4 "size_t height = self->pixels.len / self->width;\n" + SPACE4 "assert(UINT32_MAX / width >= 10 && UINT32_MAX / height >= 10);\n" + SPACE4 "for (int i = 0; i < 4; i++)\n" + SPACE4 SPACE4 "*VecU8_at(&res, 0 + i) = (width >> (8 * i)) & 0xff;\n" + SPACE4 "for (int i = 0; i < 4; i++)\n" + SPACE4 SPACE4 "*VecU8_at(&res, 4 + i) = (height >> (8 * i)) & 0xff;\n" + SPACE4 "memcpy(res.buf + 8, self->pixels.buf, len);\n" + SPACE4 "return res;\n" + "}\n\n")); + /* Method _write_to_file + * Aborts on failure */ + VecU8_append_span(&res, cstr("void ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_write_to_file(const ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("* self, const char* path) {\n" SPACE4 "VecU8 data = ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_to_bitmap_text(self);\n" + SPACE4 "write_whole_file_or_abort(path, VecU8_to_ConstSpanU8(&data));\n" + SPACE4 "VecU8_drop(data);\n" + "}\n\n")); + /* Result stucture */ + VecU8_append_span(&res, cstr("typedef struct {\n" SPACE4 "Result_variant variant;\n" SPACE4 "union {\n" SPACE8 )); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr(" ok;\n" SPACE4 SPACE4 "ConstSpanU8 err;\n" SPACE4 "};\n} ")); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr(";\n\n")); + /* Method _from_bitmap_text() + * We assume that bytes are tightly packed in member type + */ + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append(&res, ' '); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_from_bitmap_text(ConstSpanU8 text) {\n" + SPACE4 "if (text.len < 8)\n" + SPACE4 SPACE4 "return (")); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr("){.variant = Result_Err, " + ".err = cstr(\"No header *crying emoji*\")};\n" + SPACE4 "size_t width = 0, height = 0;\n" + SPACE4 "for (int i = 0; i < 4; i++)\n" + SPACE4 SPACE4 "width |= (((size_t)*ConstSpanU8_at(text, 0 + i)) << (8 * i));\n" + SPACE4 "for (int i = 0; i < 4; i++)\n" + SPACE4 SPACE4 "height |= (((size_t)*ConstSpanU8_at(text, 4 + i)) << (8 * i));\n" + SPACE4 "if (SIZE_MAX / width / height < 100 || UINT32_MAX / width < 10 || UINT32_MAX / height < 10)\n" + SPACE4 SPACE4 "return (")); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr("){.variant = Result_Err, " + ".err = cstr(\"Image is too big\")};\n" + SPACE4 "size_t len = width * height * sizeof(")); + VecU8_append_span(&res, member); + VecU8_append_span(&res, cstr(");\n" + SPACE4 "if (text.len < 8 + len)\n" + SPACE4 SPACE4 "return (")); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr("){.variant = Result_Err, " + ".err = cstr(\"Texture size and file size mismatch\")};\n" SPACE4)); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr(" res = ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_new(width, height);\n" + SPACE4 "memcpy(res.pixels.buf, text.data + 8, len);\n" + SPACE4 "return (")); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr("){.variant = Result_Ok, .ok = res};\n}\n\n")); + /* Method _read_from_file */ + VecU8_append_span(&res, texdatat); + VecU8_append(&res, ' '); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_read_from_file(const char* path) {\n" + SPACE4 "VecU8 data = read_whole_file_or_abort(path);\n" SPACE4)); + VecU8_append_resoftexdatat(&res, texdatat); + VecU8_append_span(&res, cstr(" res = ")); + VecU8_append_span(&res, texdatat); + VecU8_append_span(&res, cstr("_from_bitmap_text(VecU8_to_ConstSpanU8(&data));\n" + SPACE4 "if (res.variant != Result_Ok) {\n" + SPACE8 "fprintf(stderr, \"Tried loading bitmap texture from file, but encountered decoding error: \");\n" + SPACE8 "ConstSpanU8_fprint(res.err, stderr);\n" + SPACE8 "abortf(\"\\n\");\n" SPACE4 "}\n" SPACE4 "VecU8_drop(data);\n" SPACE4 "return res.ok;\n}\n\n")); + return res; +} + +void generate_pixel_masses_header() { + VecU8 res = begin_header(cstr("PROTOTYPE1_GEN_PIXEL_MASSES")); + VecU8_append_span(&res, cstr("#include \"geom.h\"\n\n")); + VecU8_append_span(&res, cstr("#include \"../src/l1/core/VecSpan_int_primitives.h\"\n\n")); + VecU8_append_span(&res, cstr("#include \"../src/l1/system/fileio.h\"\n\n")); + VecU8_append_vec(&res, generate_type_triv_methods_and_vec(cstr("cvec3"))); + VecU8_append_vec(&res, generate_type_triv_methods_and_vec(cstr("cvec4"))); + VecU8_append_vec(&res, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8"), cstr("U8"))); + VecU8_append_vec(&res, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8G8B8"), cstr("cvec3"))); + VecU8_append_vec(&res, generate_texture_data_struct_and_necc_methods(cstr("TextureDataR8G8B8A8"), cstr("cvec4"))); + finish_header(res, "pixel_masses.h"); +} + +#endif diff --git a/src/l1/main.c b/src/l1/main.c index e7e633c..ea6e3a5 100644 --- a/src/l1/main.c +++ b/src/l1/main.c @@ -1,573 +1,6 @@ -#include - -#include "system/fileio.h" -#include "core/VecU8_format.h" - -NODISCARD VecU8 begin_header(ConstSpanU8 guard) { - VecU8 res = VecU8_new(); - VecU8_append_span(&res, cstr("#ifndef ")); - VecU8_append_span(&res, guard); - VecU8_append_span(&res, cstr("\n#define ")); - VecU8_append_span(&res, guard); - VecU8_append_span(&res, cstr("\n/* Automatically generated file. Do not edit it. */\n\n" - "#include \"../src/l1/core/int_primitives.h\"\n\n")); - return res; -} - -void finish_header(VecU8 text_before_endif) { - VecU8_append_span(&text_before_endif, cstr("#endif\n")); - write_whole_file_or_abort("geom.h", VecU8_to_ConstSpanU8(&text_before_endif)); - VecU8_drop(text_before_endif); -} - -#define SPACE4 " " - -void string_append_vec_field_name(VecU8* str, int ci) { - assert(0 <= ci && ci < 4); - - VecU8_append(str, ci == 3 ? 'w' : 'x' + ci); -} - -void string_append_xvecy(VecU8* str, ConstSpanU8 xvec, int cc) { - VecU8_append_span(str, xvec); - VecU8_append(str, '0' + cc); -} - -NODISCARD VecU8 generate_xvecy_struct_definition(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - assert(2 <= cc && cc <= 4); - VecU8 res = VecU8_new(); - VecU8_append_span(&res, cstr("typedef struct {\n")); - - for (int ci = 0; ci < cc; ci++) { - VecU8_append_span(&res, cstr(SPACE4)); - VecU8_append_span(&res, member); - VecU8_append(&res, ' '); - string_append_vec_field_name(&res, ci); - VecU8_append_span(&res, cstr(";\n")); - } - - VecU8_append_span(&res, cstr("} ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(";\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvecy_method_add_xvecy(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_add_")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("){ ")); - for (int ci = 0; ci < cc; ci++) { - if (ci) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, cstr("A.")); - string_append_vec_field_name(&res, ci); - VecU8_append_span(&res, cstr(" + B.")); - string_append_vec_field_name(&res, ci); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvecy_method_minus_xvecy(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_minus_")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("){ ")); - for (int ci = 0; ci < cc; ci++) { - if (ci) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, cstr("A.")); - string_append_vec_field_name(&res, ci); - VecU8_append_span(&res, cstr(" - B.")); - string_append_vec_field_name(&res, ci); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - - -NODISCARD VecU8 generate_xvecy_method_minus(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_minus(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("){ ")); - for (int ci = 0; ci < cc; ci++) { - if (ci) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, cstr("-A.")); - string_append_vec_field_name(&res, ci); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvecy_method_mul_scal(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_mul_scal(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" A, ")); - VecU8_append_span(&res, member); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("){ ")); - for (int ci = 0; ci < cc; ci++) { - if (ci) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, cstr("A.")); - string_append_vec_field_name(&res, ci); - VecU8_append_span(&res, cstr(" * B")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvecy_method_div_by_scal(ConstSpanU8 xvec, ConstSpanU8 member, int cc) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_div_by_scal(")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr(" A, ")); - VecU8_append_span(&res, member); - VecU8_append_span(&res, cstr(" B){\n" SPACE4 "return ")); - string_append_xvecy(&res, xvec, cc); - VecU8_append_span(&res, cstr("_mul_scal(A, 1/B);\n}\n\n")); - return res; -} - -void string_append_xmatnm(VecU8* str, ConstSpanU8 xmat, int cols, int rows) { - VecU8_append_span(str, xmat); - VecU8_append(str, '0' + cols); - if (rows != cols) { - VecU8_append(str, 'x'); - VecU8_append(str, '0' + rows); - } -} - -/* With columns padded to 16 bytes (for std140, std140 is our everything) */ -NODISCARD VecU8 generate_xmatnm_structure_definition(ConstSpanU8 xmat, ConstSpanU8 xvec, int cols, int rows, int sizeof_member) { - int sv = (rows * sizeof_member) % 16; - VecU8 res = VecU8_from_cstr("typedef struct {\n"); - for (int x = 0; x < cols; x++) { - VecU8_append_span(&res, cstr(SPACE4)); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr(" ")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(";\n")); - if (sv) { - VecU8_append_vec(&res, VecU8_format(SPACE4 "char _padding_%d[%d];\n", x, 16 - sv)); - } - } - VecU8_append_span(&res, cstr("} ")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(";\n\n")); - return res; -} - -void string_append_mat_col_definition(VecU8* str, int ci) { - VecU8_append(str, '.'); - string_append_vec_field_name(str, ci); - VecU8_append_span(str, cstr(" = ")); -} - -void string_append_mat_el_access(VecU8* str, int x, int y) { - VecU8_append(str, '.'); - string_append_vec_field_name(str, x); - VecU8_append(str, '.'); - string_append_vec_field_name(str, y); -} - -NODISCARD VecU8 generate_xmatnm_method_new(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_new(")); - for (int y = 0; y < rows; y++) { - for (int x = 0; x < cols; x++) { - if (x > 0 || y > 0) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, member); - VecU8_append(&res, ' '); - string_append_vec_field_name(&res, x); - string_append_vec_field_name(&res, y); - } - } - VecU8_append_span(&res, cstr(") {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("){ ")); - for (int x = 0; x < cols; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - VecU8_append_span(&res, cstr("{ ")); - for (int y = 0; y < rows; y++) { - if (y) - VecU8_append_span(&res, cstr(", ")); - string_append_vec_field_name(&res, x); - string_append_vec_field_name(&res, y); - } - VecU8_append_span(&res, cstr(" }")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_square_xmatnn_E_definition(ConstSpanU8 xmat, int n) { - VecU8 res = VecU8_from_cstr("const "); - string_append_xmatnm(&res, xmat, n, n); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, n, n); - VecU8_append_span(&res, cstr("_E = { ")); - for (int x = 0; x < n; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - VecU8_append_span(&res, cstr("{ ")); - for (int y = 0; y < n; y++) { - if (y) - VecU8_append_span(&res, cstr(", ")); - VecU8_append(&res, '0' + (x == y)); - } - VecU8_append_span(&res, cstr(" }")); - } - VecU8_append_span(&res, cstr(" };\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_add_xmatnm(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_add_")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("(")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("){ ")); - for (int x = 0; x < cols; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("_add_")); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("(A.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(", B.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(")")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_minus_xmatnm(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_minus_")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("(")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("){ ")); - for (int x = 0; x < cols; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("_minus_")); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("(A.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(", B.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(")")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_minus(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_minus(")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("){ ")); - for (int x = 0; x < cols; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("_minus(A.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(")")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_mul_scal(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_mul_scal(")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" A, ")); - VecU8_append_span(&res, member); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("){ ")); - for (int x = 0; x < cols; x++) { - if (x) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, x); - string_append_xvecy(&res, xvec, rows); - VecU8_append_span(&res, cstr("_mul_scal(A.")); - string_append_vec_field_name(&res, x); - VecU8_append_span(&res, cstr(", B)")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_div_by_scal(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int cols, int rows) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_div_by_scal(")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr(" A, ")); - VecU8_append_span(&res, member); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); - string_append_xmatnm(&res, xmat, cols, rows); - VecU8_append_span(&res, cstr("_mul_scal(A, 1/B);\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvecn_method_dot_xvecn(ConstSpanU8 xvec, ConstSpanU8 member, int n) { - VecU8 res = VecU8_from_span(member); - VecU8_append(&res, ' '); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr("_dot_")); - string_append_xvecy(&res, xvec, n); - VecU8_append(&res, '('); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return ")); - for (int i = 0; i < n; i++) { - if (i) - VecU8_append_span(&res, cstr(" + ")); - VecU8_append_span(&res, cstr("A.")); - string_append_vec_field_name(&res, i); - VecU8_append_span(&res, cstr(" * B.")); - string_append_vec_field_name(&res, i); - } - VecU8_append_span(&res, cstr(";\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_mul_xvecn(ConstSpanU8 xmat, ConstSpanU8 xvec, int n, int m) { - VecU8 res = VecU8_new(); - string_append_xvecy(&res, xvec, m); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr("_mul_")); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr("(")); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xvecy(&res, xvec, n); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xvecy(&res, xvec, m); - VecU8_append_span(&res, cstr("){")); - for (int ci = 0; ci < m; ci++) { - if (ci) - VecU8_append(&res, ','); - VecU8_append_span(&res, cstr("\n" SPACE4 SPACE4)); - for (int x = 0; x < n; x++) { - if (x) - VecU8_append_span(&res, cstr(" + ")); - VecU8_append_span(&res, cstr("A")); - string_append_mat_el_access(&res, x, ci); - VecU8_append_span(&res, cstr(" * B.")); - string_append_vec_field_name(&res, x); - } - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xmatnm_method_mul_xmatkn(ConstSpanU8 xmat, int n, int m, int k) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, k, m); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr("_mul_")); - string_append_xmatnm(&res, xmat, k, n); - VecU8_append_span(&res, cstr("(")); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr(" A, ")); - string_append_xmatnm(&res, xmat, k, n); - VecU8_append_span(&res, cstr(" B) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, k, m); - VecU8_append_span(&res, cstr("){")); - for (int x = 0; x < k; x++) { - if (x) - VecU8_append_span(&res, cstr(",")); - VecU8_append_span(&res, cstr("\n" SPACE4 SPACE4)); - string_append_mat_col_definition(&res, x); - VecU8_append_span(&res, cstr("{ ")); - for (int y = 0; y < m; y++) { - if (y) - VecU8_append_span(&res, cstr(", ")); - for (int z = 0; z < n; z++) { - if (z) - VecU8_append_span(&res, cstr(" + ")); - VecU8_append_span(&res, cstr("A")); - string_append_mat_el_access(&res, z, y); - VecU8_append_span(&res, cstr(" * B")); - string_append_mat_el_access(&res, x, z); - } - } - VecU8_append_span(&res, cstr(" }")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -VecU8 generate_xmatnm_method_transpose(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int n, int m) { - VecU8 res = VecU8_new(); - string_append_xmatnm(&res, xmat, m, n); - VecU8_append(&res, ' '); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr("_transpose(")); - string_append_xmatnm(&res, xmat, n, m); - VecU8_append_span(&res, cstr(" A) {\n" SPACE4 "return (")); - string_append_xmatnm(&res, xmat, m, n); - VecU8_append_span(&res, cstr("){ ")); - for (int bx = 0; bx < m; bx++) { - if (bx) - VecU8_append_span(&res, cstr(", ")); - string_append_mat_col_definition(&res, bx); - VecU8_append_span(&res, cstr("{ ")); - for (int by = 0; by < n; by++) { - if (by) - VecU8_append_span(&res, cstr(", ")); - VecU8_append_span(&res, cstr("A")); - string_append_mat_el_access(&res, by, bx); - } - VecU8_append_span(&res, cstr(" }")); - } - VecU8_append_span(&res, cstr(" };\n}\n\n")); - return res; -} - -NODISCARD VecU8 generate_xvec234_structs_and_methods(ConstSpanU8 xvec, ConstSpanU8 member) { - VecU8 res = VecU8_new(); - for (int cc = 2; cc <= 4; cc++) { - VecU8_append_vec(&res, generate_xvecy_struct_definition(xvec, member, cc)); - VecU8_append_vec(&res, generate_xvecy_method_add_xvecy(xvec, member, cc)); - VecU8_append_vec(&res, generate_xvecy_method_minus_xvecy(xvec, member, cc)); - VecU8_append_vec(&res, generate_xvecy_method_minus(xvec, member, cc)); - VecU8_append_vec(&res, generate_xvecy_method_mul_scal(xvec, member, cc)); - VecU8_append_vec(&res, generate_xvecy_method_div_by_scal(xvec, member, cc)); - } - return res; -} - -NODISCARD VecU8 generate_xmat234x234_structs_and_methods(ConstSpanU8 xmat, ConstSpanU8 xvec, ConstSpanU8 member, int sizeof_member) { - VecU8 res = VecU8_new(); - for (int cols = 2; cols <= 4; cols++) { - for (int rows = 2; rows <= 4; rows++) { - VecU8_append_vec(&res, generate_xmatnm_structure_definition(xmat, xvec, cols, rows, sizeof_member)); - } - } - for (int cols = 2; cols <= 4; cols++) { - for (int rows = 2; rows <= 4; rows++) { - VecU8_append_vec(&res, generate_xmatnm_method_new(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_add_xmatnm(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_minus_xmatnm(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_minus(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_mul_scal(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_div_by_scal(xmat, xvec, member, cols, rows)); - VecU8_append_vec(&res, generate_xmatnm_method_transpose(xmat, xvec, member, cols, rows)); - } - } - for (int n = 2; n <= 4; n++) { - VecU8_append_vec(&res, generate_square_xmatnn_E_definition(xmat, n)); - } - for (int n = 2; n <= 4; n++) { - for (int m = 2; m <= 4; m++) { - VecU8_append_vec(&res, generate_xmatnm_method_mul_xvecn(xmat, xvec, n, m)); - } - } - for (int n = 2; n <= 4; n++) { - for (int m = 2; m <= 4; m++) { - for (int k = 2; k <= 4; k++) { - VecU8_append_vec(&res, generate_xmatnm_method_mul_xmatkn(xmat, n, m, k)); - } - } - } - return res; -} - -void generate_geometry_header() { - VecU8 res = begin_header(cstr("PROTOTYPE1_GEN_GEOM")); - VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("cvec"), cstr("U8"))); - VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("ivec"), cstr("S32"))); - - VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("vec"), cstr("float"))); - VecU8_append_vec(&res, generate_xvec234_structs_and_methods(cstr("dvec"), cstr("double"))); - VecU8_append_vec(&res, generate_xmat234x234_structs_and_methods(cstr("mat"), cstr("vec"), cstr("float"), sizeof(float))); - VecU8_append_vec(&res, generate_xmat234x234_structs_and_methods(cstr("dmat"), cstr("dvec"), cstr("double"), sizeof(double))); - finish_header(res); -} +#include "gen/geom_and_textures.h" int main() { - generate_geometry_header(); + generate_geom_header(); + generate_pixel_masses_header(); } \ No newline at end of file diff --git a/src/l2/margaret/stringop.h b/src/l2/core/stringop.h similarity index 95% rename from src/l2/margaret/stringop.h rename to src/l2/core/stringop.h index ec40af6..6f42ca9 100644 --- a/src/l2/margaret/stringop.h +++ b/src/l2/core/stringop.h @@ -3,8 +3,6 @@ #include "../../l1/core/VecSpan_int_primitives.h" -// todo: move this out of margaret (may still keep it in l2) - U8 U8_to_lowercase(U8 ch) { if ('A' <= ch && ch <= 'Z') return ch - 'A' + 'a'; diff --git a/src/l2/margaret/graphics_geom.h b/src/l2/margaret/graphics_geom.h index 82b174f..d4481c9 100644 --- a/src/l2/margaret/graphics_geom.h +++ b/src/l2/margaret/graphics_geom.h @@ -74,5 +74,4 @@ mat3 margaret_simple_camera_rot_m_basis_in_cols(float yaw, float pitch, float ro }; } - #endif diff --git a/src/l2/margaret/margaret.h b/src/l2/margaret/margaret.h index 1c71f50..a274929 100644 --- a/src/l2/margaret/margaret.h +++ b/src/l2/margaret/margaret.h @@ -8,11 +8,12 @@ #include #include #include -#include "stringop.h" +#include "../core/stringop.h" // #include #include // #include #include +#include "../../l1/system/fileio.h" typedef struct timespec margaret_ns_time; @@ -478,6 +479,59 @@ ResultMargaretChosenSwapchainDetailsOrConstSpanU8 margaret_choose_swapchain_deta }; } +SpanT_struct_Definition(VkFormat) +SpanT_method_Definition(VkFormat) + +OptionT_struct_Definition(VkFormat) +OptionT_method_Definition(VkFormat) + +OptionVkFormat margaret_find_supported_format_for_linear_tiling( + VkPhysicalDevice physical_device, ConstSpanVkFormat candidates, VkFormatFeatureFlags required_features + ) { + for (size_t i = 0; i < candidates.len; i++) { + VkFormat format = *ConstSpanVkFormat_at(candidates, i); + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); + if ((properties.linearTilingFeatures & required_features) == required_features) + return Some_VkFormat(format); + } + return None_VkFormat(); +} + +OptionVkFormat margaret_find_supported_format_for_optimal_tiling( + VkPhysicalDevice physical_device, ConstSpanVkFormat candidates, VkFormatFeatureFlags required_features + ) { + for (size_t i = 0; i < candidates.len; i++) { + VkFormat format = *ConstSpanVkFormat_at(candidates, i); + VkFormatProperties properties; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); + if ((properties.optimalTilingFeatures & required_features) == required_features) + return Some_VkFormat(format); + } + return None_VkFormat(); +} + +OptionVkFormat margaret_find_supported_zbuffer_format(VkPhysicalDevice physical_device) { + VkFormat candidates[3] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }; + return margaret_find_supported_format_for_optimal_tiling(physical_device, + (ConstSpanVkFormat){.data = candidates, .len = ARRAY_SIZE(candidates)}, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); +} + +OptionVkFormat margaret_find_supported_hdr_buffer_format(VkPhysicalDevice physical_device) { + // todo: use format without alpha component (even though they are probably not supported) + VkFormat candidates[] = { /*VK_FORMAT_R16G16B16_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT,*/ + VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT }; + return margaret_find_supported_format_for_optimal_tiling(physical_device, + (ConstSpanVkFormat){.data = candidates, .len = ARRAY_SIZE(candidates)}, + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT ); +} + +bool margaret_is_format_supported_for_textures(VkPhysicalDevice physical_device, VkFormat format) { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties(physical_device, format, &props); + return props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; +} + typedef struct { VkPhysicalDevice physical_device; S64 score; @@ -532,6 +586,14 @@ MargaretScoredPhysicalDevice margaret_score_physical_device( if (swapchain_details.variant == Result_Err) { return (MargaretScoredPhysicalDevice){dev, -1, cstr("Physical device lacks nice swapchain support")}; } + if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8_UNORM)) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8_UNORM format can't be used for optimal tiling of textures")}; + if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8G8B8A8_SRGB)) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8G8B8A8_SRGB format can't be used for optimal tiling of textures")}; + if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R8G8B8A8_UNORM)) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("R8G8B8A8_UNORM format can't be used for optimal tiling of textures")}; + if (!margaret_is_format_supported_for_textures(dev, VK_FORMAT_R16G16B16A16_SFLOAT)) + return (MargaretScoredPhysicalDevice){dev, -1, cstr("VK_FORMAT_R16G16B16A16_SFLOAT format can't be used for optimal tiling of textures")}; return (MargaretScoredPhysicalDevice){dev, score, ""}; } @@ -1271,44 +1333,6 @@ VkSampler margaret_create_sampler(VkPhysicalDevice physical_device, VkDevice dev return sampler; } -SpanT_struct_Definition(VkFormat) -SpanT_method_Definition(VkFormat) - -OptionT_struct_Definition(VkFormat) -OptionT_method_Definition(VkFormat) - -OptionVkFormat margaret_find_supported_format_for_linear_tiling( - VkPhysicalDevice physical_device, ConstSpanVkFormat candidates, VkFormatFeatureFlags required_features - ) { - for (size_t i = 0; i < candidates.len; i++) { - VkFormat format = *ConstSpanVkFormat_at(candidates, i); - VkFormatProperties properties; - vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); - if ((properties.linearTilingFeatures & required_features) == required_features) - return Some_VkFormat(format); - } - return None_VkFormat(); -} - -OptionVkFormat margaret_find_supported_format_for_optimal_tiling( - VkPhysicalDevice physical_device, ConstSpanVkFormat candidates, VkFormatFeatureFlags required_features - ) { - for (size_t i = 0; i < candidates.len; i++) { - VkFormat format = *ConstSpanVkFormat_at(candidates, i); - VkFormatProperties properties; - vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties); - if ((properties.optimalTilingFeatures & required_features) == required_features) - return Some_VkFormat(format); - } - return None_VkFormat(); -} - -OptionVkFormat margaret_find_supported_zbuffer_format(VkPhysicalDevice physical_device) { - VkFormat candidates[3] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }; - return margaret_find_supported_format_for_optimal_tiling(physical_device, - (ConstSpanVkFormat){.data = candidates, .len = ARRAY_SIZE(candidates)}, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); -} - #define VkDescriptorPoolSize_drop(v) {} #define VkDescriptorPoolSize_clone(p) (*(p)) diff --git a/src/l2/tests/r0.c b/src/l2/tests/r0.c index 7773066..dca27fc 100644 --- a/src/l2/tests/r0.c +++ b/src/l2/tests/r0.c @@ -3,9 +3,8 @@ #include #include "../../l1/system/fileio.h" #include -#include "r0_assets.h" -// Only for linux -#include +#include "r0_scene.h" +#include // Only for linux // todo: generate this class in l2 typedef struct { @@ -22,39 +21,57 @@ void destroy_graphics_pipeline_hands(VkDevice device, PipelineHands hands) { } // todo: generate this function in l2 -VkRenderPass create_render_pass_0(VkDevice logical_device, VkFormat image_format) { - // Color attachments array for our render pass - VkAttachmentDescription all_attachments[1] = { (VkAttachmentDescription){ - .format = image_format, - .samples = VK_SAMPLE_COUNT_1_BIT, - .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, - .storeOp = VK_ATTACHMENT_STORE_OP_STORE, - .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, - .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - } }; +VkRenderPass create_render_pass_0(VkDevice logical_device, VkFormat colorbuffer_format, VkFormat zbuffer_format) { + VkAttachmentDescription all_attachments[2] = { + { + .format = colorbuffer_format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + }, + { + .format = zbuffer_format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + } + }; // For our one single render subpass - VkAttachmentReference color_attachment_refs[1] = { (VkAttachmentReference){ - .attachment = 0, - .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VkAttachmentReference color_attachments_refs[1] = { + { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, } }; + VkAttachmentReference depth_attachment_ref = { + .attachment = 1, + .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, + }; + VkSubpassDescription subpasses_descr[1] = { (VkSubpassDescription){ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, - .colorAttachmentCount = ARRAY_SIZE(color_attachment_refs), - .pColorAttachments = color_attachment_refs, + .colorAttachmentCount = ARRAY_SIZE(color_attachments_refs), + .pColorAttachments = color_attachments_refs, + .pDepthStencilAttachment = &depth_attachment_ref, } }; VkSubpassDependency subpass_dependencies[1] = { // subpass_0_external (VkSubpassDependency) { .srcSubpass = VK_SUBPASS_EXTERNAL, - .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - .srcAccessMask = 0, .dstSubpass = 0, - .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, + .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, }}; @@ -136,8 +153,8 @@ PipelineHands create_graphics_pipeline_0( .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, - // .cullMode = VK_CULL_MODE_BACK_BIT, - .cullMode = VK_CULL_MODE_NONE, + .cullMode = VK_CULL_MODE_BACK_BIT, + // .cullMode = VK_CULL_MODE_NONE, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, @@ -156,6 +173,13 @@ PipelineHands create_graphics_pipeline_0( .alphaToOneEnable = VK_FALSE, }; + VkPipelineDepthStencilStateCreateInfo depth_stencil_state_crinfo = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_LESS + }; + // For one framebuffer VkPipelineColorBlendAttachmentState color_blend_attachments[1] = {(VkPipelineColorBlendAttachmentState){ .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, @@ -186,7 +210,7 @@ PipelineHands create_graphics_pipeline_0( .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, // our shader variable is not an array of descriptors, so this stays 1 .descriptorCount = 1, - .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, { .binding = 1, @@ -204,12 +228,18 @@ PipelineHands create_graphics_pipeline_0( if (vkCreateDescriptorSetLayout(device, &descriptor_set_layout_crinfo, NULL, &my_descriptor_set_layout) != VK_SUCCESS) abortf("vkCreateDescriptorSetLayout"); + VkPushConstantRange pc_ranges[] = { + { + .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, + .offset = 0, .size = sizeof(mat4) + }}; + VkPipelineLayoutCreateInfo layout_crinfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &my_descriptor_set_layout, - .pushConstantRangeCount = 0, - .pPushConstantRanges = NULL, + .pushConstantRangeCount = ARRAY_SIZE(pc_ranges), + .pPushConstantRanges = pc_ranges, }; VkPipelineLayout pipeline_layout; if (vkCreatePipelineLayout(device, &layout_crinfo, NULL, &pipeline_layout) != VK_SUCCESS) @@ -223,7 +253,7 @@ PipelineHands create_graphics_pipeline_0( .pViewportState = &viewport_state, .pRasterizationState = &rasterizer_crinfo, .pMultisampleState = &multisampling_crinfo, - .pDepthStencilState = NULL, + .pDepthStencilState = &depth_stencil_state_crinfo, .pColorBlendState = &color_blending_crinfo, .pDynamicState = &dynamic_state_crinfo, .layout = pipeline_layout, @@ -442,15 +472,16 @@ PipelineHands create_graphics_pipeline_1( } -VkFramebuffer create_IT1_framebuffer(VkDevice device, VkImageView IT1_view, VkRenderPass render_pass_0, VkExtent2D MAX_WIN_SIZE) { - VkImageView attachments[1] = {IT1_view}; +VkFramebuffer create_IT1_framebuffer(VkDevice device, VkImageView IT1_view, VkImageView zbuffer_view, VkRenderPass render_pass_0, + U32 MAX_WIN_WIDTH, U32 MAX_WIN_HEIGHT) { + VkImageView attachments[2] = {IT1_view, zbuffer_view}; VkFramebufferCreateInfo framebuffer_crinfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = render_pass_0, .attachmentCount = ARRAY_SIZE(attachments), .pAttachments = attachments, - .width = MAX_WIN_SIZE.width, - .height = MAX_WIN_SIZE.height, + .width = MAX_WIN_WIDTH, + .height = MAX_WIN_HEIGHT, .layers = 1 }; VkFramebuffer framebuffer; @@ -463,7 +494,7 @@ void reset_and_record_command_buffer_0( VkCommandBuffer command_buffer, VkRenderPass render_pass_0, const PipelineHands* pipeline_and_layout, VkFramebuffer swapchain_image_framebuffer, VkExtent2D image_extent, - const Scene* scene, VkDescriptorSet my_descriptor_set + const Scene* scene, VkDescriptorSet my_descriptor_set, mat4 t_mat ) { if (vkResetCommandBuffer(command_buffer, 0) != VK_SUCCESS) abortf("vkResetCommandBuffer"); @@ -471,15 +502,15 @@ void reset_and_record_command_buffer_0( if (vkBeginCommandBuffer(command_buffer, &info_begin) != VK_SUCCESS) abortf("vkBeginCommandBuffer"); - VkClearValue clear_color[1] = {{.color = scene->color}}; + VkClearValue clear_values[2] = {{.color = scene->color}, {.depthStencil = {.depth = 1, .stencil = 0}}}; VkRenderPassBeginInfo renderpass_begin = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = render_pass_0, .framebuffer = swapchain_image_framebuffer, .renderArea.offset = (VkOffset2D){0, 0}, .renderArea.extent = image_extent, - .clearValueCount = ARRAY_SIZE(clear_color), - .pClearValues = clear_color, + .clearValueCount = ARRAY_SIZE(clear_values), + .pClearValues = clear_values, }; vkCmdBeginRenderPass(command_buffer, &renderpass_begin, VK_SUBPASS_CONTENTS_INLINE); @@ -502,16 +533,19 @@ void reset_and_record_command_buffer_0( }; vkCmdSetScissor(command_buffer, 0, 1, &scissor); for (size_t i = 0; i < scene->models.len; i++) { - const ModelOnScene* models = VecModelOnScene_cat(&scene->models, i); - VkBuffer attached_buffers[1] = { models->vbo }; + const UsedModelOnScene* model = VecUsedModelOnScene_cat(&scene->models, i); + VkBuffer attached_buffers[1] = { model->model.vbo }; // We use our whole buffer, no need for offset VkDeviceSize offsets_in_buffers[1] = {0}; vkCmdBindVertexBuffers(command_buffer, 0, 1, attached_buffers, offsets_in_buffers); - vkCmdBindIndexBuffer(command_buffer, models->ebo, 0, VK_INDEX_TYPE_UINT32); + vkCmdBindIndexBuffer(command_buffer, model->model.ebo, 0, VK_INDEX_TYPE_UINT32); + mat4 tt = mat4_mul_mat4(t_mat, model->model_t); + vkCmdPushConstants(command_buffer, pipeline_and_layout->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, + 0, sizeof(mat4), &tt); vkCmdBindDescriptorSets( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_and_layout->pipeline_layout, 0, 1, &my_descriptor_set, 0, NULL); - vkCmdDrawIndexed(command_buffer, models->indexes, 1, 0, 0, 0); + vkCmdDrawIndexed(command_buffer, model->model.indexes, 1, 0, 0, 0); } vkCmdEndRenderPass(command_buffer); @@ -661,9 +695,15 @@ int main() { MargaretChosenSwapchainDetails swapchain_details = swapchain_details_res.ok; // We hope that the image format won't be changed even when window gets resized - // VkSurfaceFormatKHR image_format = choose_surface_format_i_want(swap_chain_support).value(); + // (swapchain_details.surface_format.format) + OptionVkFormat zbuffer_format = margaret_find_supported_zbuffer_format(physical_device); + if (zbuffer_format.variant != Option_Some) + abortf("Could not find supported zbuffer format\n"); + OptionVkFormat IT1_format = margaret_find_supported_hdr_buffer_format(physical_device); + if (IT1_format.variant != Option_Some) + abortf("Could not find supported hdr buffer format\n"); - VkRenderPass render_pass_0 = create_render_pass_0(device, swapchain_details.surface_format.format); + VkRenderPass render_pass_0 = create_render_pass_0(device, IT1_format.some, zbuffer_format.some); PipelineHands pipeline_hands_0 = create_graphics_pipeline_0(device, render_pass_0, 0); VkRenderPass render_pass_1 = create_render_pass_1(device, swapchain_details.surface_format.format); @@ -674,14 +714,15 @@ int main() { // Filling scene info ModelTopology cylinder_1 = generate_one_fourth_of_a_cylinder(10, 2, 6); ModelTopology cylinder_2 = generate_one_fourth_of_a_cylinder(5, 5, 10); - TextureDataR8G8B8A8 wood_texture_data = generate_wood_texture(); + // TextureDataR8G8B8A8 wood_texture_data = generate_texture_for_one_fourth_of_a_cylinder(20, 10, 2, 6); + TextureDataR8G8B8A8 wood_texture_data = TextureDataR8G8B8A8_read_from_file("test_textures/log_10_2_6.r8g8b8a8"); // We have only one staging buffer in host memory (because we don't really need more) MargaretBufferInMemoryInfo host_mem_buffer = (MargaretBufferInMemoryInfo){ .sz = MAX_U64(ModelTopology_get_space_needed_for_staging_buffer(&cylinder_1), MAX_U64(ModelTopology_get_space_needed_for_staging_buffer(&cylinder_2), MAX_U64(sizeof(Pipeline0UBO), - MAX_U64(TextureDataR8G8B8A8_get_space_needed_for_staging_buffer(&wood_texture_data), 0)))) + MAX_U64(TextureDataR8G8B8A8_get_size_in_bytes(&wood_texture_data), 0)))) , .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT }; VkDeviceMemory host_mem = margaret_initialize_buffers_and_images(physical_device, device, (SpanMargaretBufferInMemoryInfo){.data = &host_mem_buffer, .len = 1}, (SpanMargaretImageInMemoryInfo){ 0 }, @@ -697,7 +738,8 @@ int main() { MargaretImageInMemoryInfo device_mem_images[] = { margaret_prep_image_mem_info_of_gpu_texture_rgba(wood_texture_data.width, TextureDataR8G8B8A8_get_height(&wood_texture_data)), - margaret_prep_image_mem_info_of_colorbuffer(MAX_WIN_WIDTH, MAX_WIN_HEIGHT, swapchain_details.surface_format.format), + margaret_prep_image_mem_info_of_colorbuffer(MAX_WIN_WIDTH, MAX_WIN_HEIGHT, IT1_format.some), + margaret_prep_image_mem_info_of_zbuffer(MAX_WIN_WIDTH, MAX_WIN_HEIGHT, zbuffer_format.some), }; VkDeviceMemory device_mem = margaret_initialize_buffers_and_images(physical_device, device, (SpanMargaretBufferInMemoryInfo){ .data = device_mem_buffers, .len = ARRAY_SIZE(device_mem_buffers)}, @@ -710,6 +752,7 @@ int main() { MargaretBufferInMemoryInfo device_ubo_my_buffer = device_mem_buffers[4]; MargaretImageInMemoryInfo device_wood_texture = device_mem_images[0]; MargaretImageInMemoryInfo device_IT1_image = device_mem_images[1]; + MargaretImageInMemoryInfo device_zbuffer_image = device_mem_images[2]; VkCommandPool command_pool = margaret_create_resettable_command_pool(device, queue_fam.for_graphics); VkCommandBuffer rendering_command_buffer_0 = margaret_allocate_command_buffer(device, command_pool); @@ -748,25 +791,27 @@ int main() { device_ebo_2_buffer.buffer, host_mem_buffer.buffer, size); } memcpy(host_mem_buffer_mem, wood_texture_data.pixels.buf, - TextureDataR8G8B8A8_get_space_needed_for_staging_buffer(&wood_texture_data)); + TextureDataR8G8B8A8_get_size_in_bytes(&wood_texture_data)); margaret_copy_buffer_to_texture_for_frag_shader_imm(device, command_pool, graphics_queue, &device_wood_texture, host_mem_buffer.buffer); // We sent everything we needed. but host_mem_buffer_mem may be used later // My wood texture needs VkImageView VkImageView wood_texture_view = margaret_create_view_for_image(device, &device_wood_texture, VK_IMAGE_ASPECT_COLOR_BIT); - + // My zbuffer also needs a view + VkImageView zbuffer_view = margaret_create_view_for_image(device, &device_zbuffer_image, VK_IMAGE_ASPECT_DEPTH_BIT); /* Here we create an image view into a temporary IT1 texture and a framebuffer for scene rendering */ VkImageView IT1_view = margaret_create_view_for_image(device, &device_IT1_image, VK_IMAGE_ASPECT_COLOR_BIT); - VkFramebuffer IT1_framebuffer = create_IT1_framebuffer(device, IT1_view, render_pass_0, - (VkExtent2D){.width = MAX_WIN_WIDTH, .height = MAX_WIN_HEIGHT}); + VkFramebuffer IT1_framebuffer = create_IT1_framebuffer(device, IT1_view, zbuffer_view, render_pass_0, + MAX_WIN_WIDTH, MAX_WIN_HEIGHT); - Scene scene; - scene.models = VecModelOnScene_new_zeroinit(2); - *VecModelOnScene_at(&scene.models, 0) = (ModelOnScene){ - .vbo = device_vbo_1_buffer.buffer, .ebo = device_ebo_1_buffer.buffer, .indexes = cylinder_1.indexes.len }; - // *VecModelOnScene_at(&scene.models, 1) = (ModelOnScene){ - // .vbo = device_vbo_2_buffer.buffer, .ebo = device_ebo_2_buffer.buffer, .indexes = cylinder_2.indexes.len }; + Scene scene = Scene_new(); + VecUsedModelOnScene_append(&scene.models, (UsedModelOnScene){.model = { + .vbo = device_vbo_1_buffer.buffer, .ebo = device_ebo_1_buffer.buffer, .indexes = cylinder_1.indexes.len + }, .model_t = margaret_translation_mat4((vec3){1, -1, 5}) }); + VecUsedModelOnScene_append(&scene.models, (UsedModelOnScene){.model = { + .vbo = device_vbo_2_buffer.buffer, .ebo = device_ebo_2_buffer.buffer, .indexes = cylinder_2.indexes.len + }, .model_t = margaret_translation_mat4((vec3){6, -3, 7}) }); // Sampler is global for a lot of my future textures VkSampler my_texture_sampler = margaret_create_sampler(physical_device, device); @@ -902,9 +947,16 @@ int main() { mat4 camera_rotation_matrix = margaret_mat3_to_mat4_transposed(my_cam_control_info.cam_basis); mat4 camera_translation_matrix = margaret_translation_mat4(vec3_minus(my_cam_control_info.pos)); mat4 t_mat = mat4_mul_mat4(projection_matrix, mat4_mul_mat4(camera_rotation_matrix, camera_translation_matrix)); - // mat4 t_mat = mat4_mul_mat4(camera_rotation_matrix, camera_translation_matrix); + { - *(Pipeline0UBO*)host_mem_buffer_mem = (Pipeline0UBO){.t = t_mat}; + assert(scene.spotlights.len < pipeline_0_ubo_spotlight_max_count); + assert(scene.point_lights.len < pipeline_0_ubo_point_light_max_count); + Pipeline0UBO* subo = (Pipeline0UBO*)host_mem_buffer_mem; + // todo: speed it up a little + subo->spotlight_count = (int)scene.spotlights.len; + memcpy(&subo->spotlight_arr, scene.spotlights.buf, scene.spotlights.len * sizeof(Pipeline0Spotlight)); + subo->point_light_count = (int)scene.point_lights.len; + memcpy(&subo->point_light_arr, scene.point_lights.buf, scene.point_lights.len * sizeof(Pipeline0PointLight)); VkCommandBuffer command_buffers[1] = { uniform_transfer_command_buffer }; VkSemaphore signaling_semaphores[1] = { swfb.in_frame_transfer_complete }; VkSubmitInfo ubo_copying_cmd_buffer_submit = { @@ -918,7 +970,7 @@ int main() { } reset_and_record_command_buffer_0(rendering_command_buffer_0, render_pass_0, &pipeline_hands_0, - IT1_framebuffer, swfb.extent, &scene, descriptor_set_for_pipeline_0); + IT1_framebuffer, swfb.extent, &scene, descriptor_set_for_pipeline_0, t_mat); reset_and_record_command_buffer_1(rendering_command_buffer_1, render_pass_1, &pipeline_hands_1, *VecVkFramebuffer_cat(&swfb.framebuffers, ij), diff --git a/src/l2/tests/r0_assets.h b/src/l2/tests/r0_assets.h index 825010c..59524f2 100644 --- a/src/l2/tests/r0_assets.h +++ b/src/l2/tests/r0_assets.h @@ -3,27 +3,16 @@ #include "../margaret/graphics_geom.h" #include "../../l1/core/util.h" +#include "../../l1/core/VecSpan_int_primitives.h" +#include "../../l1/system/fileio.h" #include +#include "../../../gen/pixel_masses.h" typedef struct { vec3 pos; vec2 tex; } Vertex; -/* No offset yet */ -typedef struct { - VkBuffer vbo; - VkBuffer ebo; - size_t indexes; -} ModelOnScene; - -#define ModelOnScene_drop(vp) {} -#define ModelOnScene_clone(vp) (*(vp)) - -VecT_trivmove_struct_Definition(ModelOnScene) -VecT_trivmove_method_Definition(ModelOnScene) -VecT_primitive_zeroinit_method_Definition(ModelOnScene) - #define Vertex_drop(vp) {} #define Vertex_clone(vp) (*(vp)) @@ -44,62 +33,6 @@ void ModelTopology_drop(ModelTopology self) { VecU32_drop(self.indexes); } -ModelTopology generate_one_fourth_of_a_cylinder(float w, float r, U32 k) { - assert(k >= 1); - const float a = M_PI_2f / (float)k; - const float l = 2 * r * sin(M_PI_4f / (float)k); - const vec2 v0tex = {r / (2 * r + w), 1 / (2 + k * l)}; - const vec2 v1tex = {(r + w) / (2 * r + w), 1 / (2 + k * l)}; - const vec2 v2tex = {r / (2 * r + w), 2 / (2 + k * l)}; - const vec2 v3tex = {(r + w) / (2 * r + w), 2 / (2 + k * l)}; - VecVertex vertices = VecVertex_new(); // todo: reserve 4 * k + 6 - VecVertex_append(&vertices, (Vertex){.pos = {0, 0, 0}, .tex = v0tex}); - VecVertex_append(&vertices, (Vertex){.pos = {w, 0, 0}, .tex = v1tex}); - VecVertex_append(&vertices, (Vertex){.pos = {0, r, 0}, .tex = v2tex}); - VecVertex_append(&vertices, (Vertex){.pos = {w, r, 0}, .tex = v3tex}); - VecVertex_append(&vertices, (Vertex){.pos = {0, 0, r}, .tex = {r / (2 * r + w), 0}}); - VecVertex_append(&vertices, (Vertex){.pos = {w, 0, r}, .tex = {(r + w) / (2 * r + w), 0}}); - for (U32 i = 1; i <= k; i++) { - VecVertex_append(&vertices, (Vertex){ - .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, - .tex = vec2_add_vec2(v0tex, (vec2){r / (2 * r + w) * -sinf(a * i), 1 / (2 + k * l) * cos(a * i)}) - }); - } - for (U32 i = 1; i <= k; i++) { - VecVertex_append(&vertices, (Vertex){ - .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, - .tex = vec2_add_vec2(v1tex, (vec2){r / (2 * r + w) * sinf(a * i), 1 / (2 + k * l) * cos(a * i)}) - }); - } - for (U32 i = 1; i <= k; i++) { - VecVertex_append(&vertices, (Vertex){ - .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, - .tex = {v2tex.x, v2tex.y + i * l / (2 + k * l)} - }); - } - for (U32 i = 1; i <= k; i++) { - VecVertex_append(&vertices, (Vertex){ - .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, - .tex = {v3tex.x, v3tex.y + i * l / (2 + k * l)} - }); - } - VecU32 indexes = VecU32_new(); // todo: reserve 3 * (2+2+2*k+2*k)< - { - U32 _span_0[] = { 5, 0, 1, 5, 4, 0, 1, 0, 3, 3, 0, 2 }; - VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_0, .len = ARRAY_SIZE(_span_0)}); - } - for (U32 i = 1; i <= k; i++) { - U32 _span_1[] = { - 0, 5 + i, i > 1 ? 5 + i - 1 : 2, - 1, i > 1 ? 5 + k + i - 1 : 3, 5 + k + i, - i > 1 ? 5 + 2 * k + i - 1 : 2, 5 + 2 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, - 5 + 3 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, 5 + 2 * k + i - }; - VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_1, .len = ARRAY_SIZE(_span_1)}); - } - return (ModelTopology){.vertices = vertices, .indexes = indexes}; -} - typedef struct { vec2 win_scale; } Pipeline1PushRangeVertex; @@ -112,37 +45,163 @@ typedef struct { } Pipeline1PushRangeFragment; typedef struct { - mat4 t; -} Pipeline0UBO; + vec3 pos; + char _padding_0[4]; + vec3 dir; + char _padding_1[4]; + vec3 color; + char _padding_2[4]; + float d; + char _padding_3[12]; +} Pipeline0Spotlight; -#define cvec4_drop(vp) {} -#define cvec4_clone(vp) (*(vp)) +#define Pipeline0Spotlight_drop(x) {} +#define Pipeline0Spotlight_clone(x) (*(x)) -VecT_trivmove_struct_Definition(cvec4) -VecT_trivmove_method_Definition(cvec4) -VecT_primitive_zeroinit_method_Definition(cvec4) +VecT_trivmove_struct_Definition(Pipeline0Spotlight) +VecT_trivmove_method_Definition(Pipeline0Spotlight) +VecT_primitive_zeroinit_method_Definition(Pipeline0Spotlight) typedef struct { - // I hate this so much uuuh. Capacity is not actually useful here... - Veccvec4 pixels; - size_t width; -} TextureDataR8G8B8A8; + vec3 pos; + char _padding_0[4]; + vec3 color; + char _padding_1[4]; +} Pipeline0PointLight; -TextureDataR8G8B8A8 TextureDataR8G8B8A8_new(size_t width, size_t height) { - return (TextureDataR8G8B8A8){.pixels = Veccvec4_new_zeroinit(width * height), .width = width}; +#define Pipeline0PointLight_drop(x) {} +#define Pipeline0PointLight_clone(x) (*(x)) + +VecT_trivmove_struct_Definition(Pipeline0PointLight) +VecT_trivmove_method_Definition(Pipeline0PointLight) +VecT_primitive_zeroinit_method_Definition(Pipeline0PointLight) + +const size_t pipeline_0_ubo_point_light_max_count = 20; +const size_t pipeline_0_ubo_spotlight_max_count = 20; + +typedef struct { + int spotlight_count; + int point_light_count; + char _padding_1[8]; + Pipeline0PointLight point_light_arr[20]; + Pipeline0Spotlight spotlight_arr[120]; +} Pipeline0UBO; + +size_t ModelTopology_get_space_needed_for_staging_buffer(const ModelTopology* self) { + return MAX_U64(self->vertices.len * sizeof(Vertex), self->indexes.len * sizeof(U32)); } -size_t TextureDataR8G8B8A8_get_height(const TextureDataR8G8B8A8* self) { - return self->pixels.len / self->width; +void TextureDataR8_pixel_maxing(TextureDataR8* self, S32 x, S32 y, U8 val) { + if (x < 0 || y < 0 || x >= self->width) + return; + size_t p = (size_t)x + (size_t)y * self->width; + if (p >= self->pixels.len) + return; + U8 b = *TextureDataR8_at(self, x, y); + *TextureDataR8_at(self, x, y) = MAX_U8(b, val); } -void TextureDataR8G8B8A8_drop(TextureDataR8G8B8A8 self) { - Veccvec4_drop(self.pixels); +U8 a_small_cute_gradient(float r_cut, float r_decay, float dist) { + return dist > r_cut ? 0 : (dist < r_decay) ? 255 : (U8)roundf( 255.f * (r_cut - dist) / (r_cut - r_decay) ); } -cvec4* TextureDataR8G8B8A8_at(TextureDataR8G8B8A8* self, size_t x, size_t y) { - assert(x < self->width); - return Veccvec4_at(&self->pixels, x + y * self->width); +void TextureDataR8_seg_vertical_strip_maxing(TextureDataR8* self, S32 x, float y_real, float vert_r_cut, float vert_r_decay) { + S32 y = (S32)roundf(y_real - 0.5f); + S32 k = (S32)ceilf(vert_r_cut); + for (S32 j = -k+y; j <= y+k; j++) { + float dist = fabsf(.5f + (float)j - y_real); + TextureDataR8_pixel_maxing(self, x, j, a_small_cute_gradient(vert_r_cut, vert_r_decay, dist)); + } +} + +void TextureDataR8_seg_horizontal_strip_maxing(TextureDataR8* self, float x_real, S32 y, float hor_r_cut, float hor_r_decay) { + S32 x = (S32)roundf(x_real - 0.5f); + S32 k = (S32)ceilf(hor_r_cut); + for (S32 i = -k+x; i < x+k; i++) { + float dist = fabsf(.5f + (float)i - x_real); + TextureDataR8_pixel_maxing(self, i, y, a_small_cute_gradient(hor_r_cut, hor_r_decay, dist)); + } +} + +// todo: complete rewrite +/* abs(y2 - y1) < abs(x2 - x1) x1 <= x2 */ +void TextureDataR8_draw_horizontal_inner_line_maxing(TextureDataR8* self, + float x1, float y1, float x2, float y2, float r_cut, float r_decay) { + S32 sx1 = (S32)roundf(x1 - 0.5f); + S32 sx2 = (S32)roundf(x2 - 0.5f); + if (sx1 + 1 >= sx2) + return; + float dx = x2 - x1; + float dy = y2 - y1; + float xdds = 1 / dx; + float inv_sin_a = sqrtf(dx*dx + dy*dy) / dx; + float vert_r_cut = r_cut * inv_sin_a; + float vert_r_decay = r_decay * inv_sin_a; + for (S32 x = sx1 + 1; x < sx2; x++) { + float real_x = (float)x + 0.5f; + float real_y = y1 + dy * (real_x - x1) * xdds; + TextureDataR8_seg_vertical_strip_maxing(self, x, real_y, vert_r_cut, vert_r_decay); + } +} + +/* abs(x2 - x1) < abs(y2 - y1) y1 <= y2 */ +void TextureDataR8_draw_vertical_inner_line_maxing(TextureDataR8* self, + float x1, float y1, float x2, float y2, float r_cut, float r_decay) { + S32 sy1 = (S32)roundf(y1 - 0.5f); + S32 sy2 = (S32)roundf(y2 - 0.5f); + if (sy1 + 1 >= sy2) + return; + float dx = x2 - x1; + float dy = y2 - y1; + float ydds = 1 / dy; + float inv_sin_a = sqrtf(dx*dx + dy*dy) / dy; + float hor_r_cut = r_cut * inv_sin_a; + float hor_r_decay = r_decay * inv_sin_a; + for (S32 y = sy1 + 1; y < sy2; y++) { + float real_y = (float)y + 0.5f; + float real_x = x1 + dx * (real_y - y1) * ydds; + TextureDataR8_seg_horizontal_strip_maxing(self, real_x, y, hor_r_cut, hor_r_decay); + } +} + +void TextureDataR8_draw_inner_line_maxing(TextureDataR8* self, + float x1, float y1, float x2, float y2, float r_cut, float r_decay) { + float dx = fabsf(x2 - x1); + float dy = fabsf(y2 - y1); + if (dx > dy) { + if (x1 < x2) { + TextureDataR8_draw_horizontal_inner_line_maxing(self, x1, y1, x2, y2, r_cut, r_decay); + } else { + TextureDataR8_draw_horizontal_inner_line_maxing(self, x2, y2, x1, y1, r_cut, r_decay); + } + } else { + if (y1 < y2) { + TextureDataR8_draw_vertical_inner_line_maxing(self, x1, y1, x2, y2, r_cut, r_decay); + } else { + TextureDataR8_draw_vertical_inner_line_maxing(self, x2, y2, x1, y1, r_cut, r_decay); + } + } +} + +void TextureDataR8_draw_spot_maxing(TextureDataR8* self, float x, float y, float r_cut, float r_decay) { + S32 sx = (S32)roundf(x - .5f); + S32 sy = (S32)roundf(y - .5f); + S32 k = (S32)ceilf(r_cut); + for (S32 i = sx-k; i <= sx+k; i++) { + for (S32 j = sy-k; j <= sy+k; j++) { + float dx = 0.5f + (float)i - x; + float dy = 0.5f + (float)j - y; + float dist = sqrtf(dx*dx + dy*dy); + TextureDataR8_pixel_maxing(self, i, j, a_small_cute_gradient(r_cut, r_decay, dist)); + } + } +} + +void TextureDataR8G8B8A8_draw_one_segment_maxing_alpha(TextureDataR8* self, + float x1, float y1, float x2, float y2, float r_cut, float r_decay) { + TextureDataR8_draw_spot_maxing(self, x1, y1, r_cut, r_decay); + TextureDataR8_draw_spot_maxing(self, x2, y2, r_cut, r_decay); + TextureDataR8_draw_inner_line_maxing(self, x1, y1, x2, y2, r_cut, r_decay); } TextureDataR8G8B8A8 generate_wood_texture() { @@ -172,85 +231,114 @@ TextureDataR8G8B8A8 generate_wood_texture() { return res; } -size_t ModelTopology_get_space_needed_for_staging_buffer(const ModelTopology* self) { - return MAX_U64(self->vertices.len * sizeof(Vertex), self->indexes.len * sizeof(U32)); +ModelTopology generate_one_fourth_of_a_cylinder(float w, float r, U32 k) { + assert(k >= 1); + const float a = M_PI_2f / (float)k; + const float l = 2 * r * sin(M_PI_4f / (float)k); + const vec2 v0tex = {r / (2 * r + w), r / (2 * r + k * l)}; + const vec2 v1tex = {(r + w) / (2 * r + w), r / (2 * r + k * l)}; + const vec2 v2tex = {r / (2 * r + w), 2 * r / (2 * r + k * l)}; + const vec2 v3tex = {(r + w) / (2 * r + w), 2 * r / (2 * r + k * l)}; + VecVertex vertices = VecVertex_new(); // todo: reserve 4 * k + 6 + VecVertex_append(&vertices, (Vertex){.pos = {0, 0, 0}, .tex = v0tex}); + VecVertex_append(&vertices, (Vertex){.pos = {w, 0, 0}, .tex = v1tex}); + VecVertex_append(&vertices, (Vertex){.pos = {0, r, 0}, .tex = v2tex}); + VecVertex_append(&vertices, (Vertex){.pos = {w, r, 0}, .tex = v3tex}); + VecVertex_append(&vertices, (Vertex){.pos = {0, 0, r}, .tex = {r / (2 * r + w), 0}}); + VecVertex_append(&vertices, (Vertex){.pos = {w, 0, r}, .tex = {(r + w) / (2 * r + w), 0}}); + for (U32 i = 1; i <= k; i++) { + VecVertex_append(&vertices, (Vertex){ + .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, + .tex = vec2_add_vec2(v0tex, (vec2){r / (2 * r + w) * -sinf(a * i), r / (2 * r + k * l) * cos(a * i)}) + }); + } + for (U32 i = 1; i <= k; i++) { + VecVertex_append(&vertices, (Vertex){ + .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, + .tex = vec2_add_vec2(v1tex, (vec2){r / (2 * r + w) * sinf(a * i), r / (2*r + k * l) * cos(a * i)}) + }); + } + for (U32 i = 1; i <= k; i++) { + VecVertex_append(&vertices, (Vertex){ + .pos = {0, cosf(a * i) * r, sinf(a * i) * r}, + .tex = {v2tex.x, v2tex.y + i * l / (2*r + k * l)} + }); + } + for (U32 i = 1; i <= k; i++) { + VecVertex_append(&vertices, (Vertex){ + .pos = {w, cosf(a * i) * r, sinf(a * i) * r}, + .tex = {v3tex.x, v3tex.y + i * l / (2*r + k * l)} + }); + } + VecU32 indexes = VecU32_new(); // todo: reserve 3 * (2+2+2*k+2*k)< + { + U32 _span_0[] = { 5, 0, 1, 5, 4, 0, 1, 0, 3, 3, 0, 2 }; + VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_0, .len = ARRAY_SIZE(_span_0)}); + } + for (U32 i = 1; i <= k; i++) { + U32 _span_1[] = { + 0, 5 + i, i > 1 ? 5 + i - 1 : 2, + 1, i > 1 ? 5 + k + i - 1 : 3, 5 + k + i, + i > 1 ? 5 + 2 * k + i - 1 : 2, 5 + 2 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, + 5 + 3 * k + i, i > 1 ? 5 + 3 * k + i - 1 : 3, 5 + 2 * k + i + }; + VecU32_append_span(&indexes, (ConstSpanU32){.data = _span_1, .len = ARRAY_SIZE(_span_1)}); + } + return (ModelTopology){.vertices = vertices, .indexes = indexes}; } -size_t TextureDataR8G8B8A8_get_space_needed_for_staging_buffer(const TextureDataR8G8B8A8* self) { - return self->pixels.len * sizeof(cvec4); +#define vec2_drop(x) {} +#define vec2_clone(x) (*(x)) + +VecT_trivmove_struct_Definition(vec2) +VecT_trivmove_method_Definition(vec2) +VecT_primitive_zeroinit_method_Definition(vec2) + +TextureDataR8 generate_tex_template_for_one_fourth_of_a_cylinder(float s_resol, float w, float r, U32 k) { + assert(k >= 1); + assert(k >= 1); + const float a = M_PI_2f / (float)k; + const float l = 2 * r * sin(M_PI_4f / (float)k); + // We will multiply everything by s_resol at the end + const vec2 v0tex = {r, r}; + const vec2 v1tex = {r + w, r}; + const vec2 v2tex = {r, 2 * r}; + const vec2 v3tex = {r + w, 2 * r}; + TextureDataR8 res = TextureDataR8_new((size_t)ceilf((float)s_resol * (2 * r + w)), + (size_t)ceilf((float)s_resol * (2 * r + k * l))); + Vecvec2 P = Vecvec2_new(); // todo: reserve 6 + k * 4 + Vecvec2_append(&P, v0tex); + Vecvec2_append(&P, (vec2){r, 0}); // 4 + Vecvec2_append(&P, (vec2){r + w, 0}); // 5 + Vecvec2_append(&P, v1tex); + for (size_t i = k; i > 0; i--) { + Vecvec2_append(&P, (vec2){r + w + r * sinf(a * i), r + r * cos(a * i)}); + } + Vecvec2_append(&P, v3tex); + for (size_t i = 1; i <= k; i++) { + Vecvec2_append(&P, (vec2){r + w, 2 * r + i * l}); + } + for (size_t i = k; i > 0; i--) { + Vecvec2_append(&P, (vec2){r, 2 * r + i * l}); + } + Vecvec2_append(&P, v2tex); + for (size_t i = 1; i <= k; i++) { + Vecvec2_append(&P, (vec2){r - r * sinf(a * i), r + r * cos(a * i)}); + } + size_t S = 6 + 4 * k; + assert(P.len == S); + float r_cut = 2; + float r_decay = 1; + for (size_t i = 0; i < S; i++) { + vec2 p = vec2_mul_scal(*Vecvec2_at(&P, i), s_resol); + TextureDataR8_draw_spot_maxing(&res, p.x, p.y, r_cut, r_decay); + } + for (size_t i = 0; i < S; i++) { + vec2 pp = vec2_mul_scal(*Vecvec2_at(&P, i ? i - 1 : S - 1), s_resol); + vec2 p = vec2_mul_scal(*Vecvec2_at(&P, i), s_resol); + TextureDataR8_draw_inner_line_maxing(&res, pp.x, pp.y, p.x, p.y, r_cut, r_decay); + } + return res; } -typedef struct { - float fov; - mat3 cam_basis; - vec3 pos; - - float speed; - float sensitivity; - float pitch_cap; -} CamControlInfo; - -void CamControlInfo_forward(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.z, -self->speed * fl)); -} - -void CamControlInfo_backward(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.z, self->speed * fl)); -} - -void CamControlInfo_left(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.x, -self->speed * fl)); -} - -void CamControlInfo_right(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.x, self->speed * fl)); -} - -void CamControlInfo_down(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal((vec3){0, -1, 0}, self->speed * fl)); -} - -void CamControlInfo_up(CamControlInfo* self, float fl) { - self->pos = vec3_add_vec3(self->pos, vec3_mul_scal((vec3){0, 1, 0}, self->speed * fl)); -} - -CamControlInfo CamControlInfo_new() { - return (CamControlInfo){ - .fov = 1.5, .cam_basis = margaret_simple_camera_rot_m_basis_in_cols(0, 0, 0), .pos = {0, 0, 0}, - .speed = 2.7, .sensitivity = 0.5f * M_PIf / 180, .pitch_cap = M_PI * 0.49 - }; -} - -float clamp_float(float a, float l, float r) { - return (a < l) ? l : ((a <= r) ? a : r); -} - -void CamControlInfo_update_direction(CamControlInfo* self, int win_width, int win_height, int pointer_x, int pointer_y) { - float yaw = (float)(win_width / 2 - pointer_x) * self->sensitivity; - float pitch = clamp_float( - (float)(win_height / 2 - pointer_y) * self->sensitivity, - -self->pitch_cap, self->pitch_cap - ); - self->cam_basis = margaret_simple_camera_rot_m_basis_in_cols(yaw, pitch, 0); -} - -typedef struct { - VecModelOnScene models; - VkClearColorValue color; - float gamma_correction_factor; - float hdr_factor; - float lsd_factor; - float anim_time; // A timer, passed to functions that push push constants -} Scene; - -Scene Scene_new() { - return (Scene){.models = VecModelOnScene_new(), .color = {.float32 = {1, 0.5, 0.7}}, - .gamma_correction_factor = 2.2, .hdr_factor = 1, .lsd_factor = 0, .anim_time = 0}; -} - -void Scene_drop(Scene self) { - VecModelOnScene_drop(self.models); -} - - #endif diff --git a/src/l2/tests/r0_scene.h b/src/l2/tests/r0_scene.h new file mode 100644 index 0000000..511fc94 --- /dev/null +++ b/src/l2/tests/r0_scene.h @@ -0,0 +1,109 @@ +#ifndef PROTOTYPE1_SRC_L2_TESTS_R0_SCENE_H +#define PROTOTYPE1_SRC_L2_TESTS_R0_SCENE_H + +#include "r0_assets.h" + +/* No offset yet */ +typedef struct { + VkBuffer vbo; + VkBuffer ebo; + size_t indexes; +} ModelOnScene; + +#define ModelOnScene_drop(vp) {} +#define ModelOnScene_clone(vp) (*(vp)) + +VecT_trivmove_struct_Definition(ModelOnScene) +VecT_trivmove_method_Definition(ModelOnScene) +VecT_primitive_zeroinit_method_Definition(ModelOnScene) + +typedef struct { + ModelOnScene model; + mat4 model_t; +} UsedModelOnScene; + +#define UsedModelOnScene_drop(vp) {} +#define UsedModelOnScene_clone(vp) (*(vp)) + +VecT_trivmove_struct_Definition(UsedModelOnScene) +VecT_trivmove_method_Definition(UsedModelOnScene) +VecT_primitive_zeroinit_method_Definition(UsedModelOnScene) + + +typedef struct { + float fov; + mat3 cam_basis; + vec3 pos; + + float speed; + float sensitivity; + float pitch_cap; +} CamControlInfo; + +void CamControlInfo_forward(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.z, -self->speed * fl)); +} + +void CamControlInfo_backward(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.z, self->speed * fl)); +} + +void CamControlInfo_left(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.x, -self->speed * fl)); +} + +void CamControlInfo_right(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal(self->cam_basis.x, self->speed * fl)); +} + +void CamControlInfo_down(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal((vec3){0, -1, 0}, self->speed * fl)); +} + +void CamControlInfo_up(CamControlInfo* self, float fl) { + self->pos = vec3_add_vec3(self->pos, vec3_mul_scal((vec3){0, 1, 0}, self->speed * fl)); +} + +CamControlInfo CamControlInfo_new() { + return (CamControlInfo){ + .fov = 1.5, .cam_basis = margaret_simple_camera_rot_m_basis_in_cols(0, 0, 0), .pos = {0, 0, 0}, + .speed = 2.7, .sensitivity = 0.5f * M_PIf / 180, .pitch_cap = M_PI * 0.49 + }; +} + +float clamp_float(float a, float l, float r) { + return (a < l) ? l : ((a <= r) ? a : r); +} + +void CamControlInfo_update_direction(CamControlInfo* self, int win_width, int win_height, int pointer_x, int pointer_y) { + float yaw = (float)(win_width / 2 - pointer_x) * self->sensitivity; + float pitch = clamp_float( + (float)(win_height / 2 - pointer_y) * self->sensitivity, + -self->pitch_cap, self->pitch_cap + ); + self->cam_basis = margaret_simple_camera_rot_m_basis_in_cols(yaw, pitch, 0); +} + +typedef struct { + VecUsedModelOnScene models; + VkClearColorValue color; + float gamma_correction_factor; + float hdr_factor; + float lsd_factor; + float anim_time; // A timer, passed to functions that push push constants + VecPipeline0Spotlight spotlights; + VecPipeline0PointLight point_lights; +} Scene; + +Scene Scene_new() { + return (Scene){.models = VecUsedModelOnScene_new(), .color = {.float32 = {1, 0.5, 0.7}}, + .gamma_correction_factor = 2.2, .hdr_factor = 1, .lsd_factor = 0, .anim_time = 0, + .spotlights = VecPipeline0Spotlight_new(), .point_lights = VecPipeline0PointLight_new()}; +} + +void Scene_drop(Scene self) { + VecUsedModelOnScene_drop(self.models); +} + + +#endif diff --git a/src/l2/tests/r0_tex_init_prep.c b/src/l2/tests/r0_tex_init_prep.c new file mode 100644 index 0000000..0189340 --- /dev/null +++ b/src/l2/tests/r0_tex_init_prep.c @@ -0,0 +1,9 @@ +#include "r0_assets.h" +#include "../../l1/system/fileio.h" + +int main() { + TextureDataR8 tex_1 = generate_tex_template_for_one_fourth_of_a_cylinder(20, 10, 2, 6); + TextureDataR8_write_to_file(&tex_1, "log_10_2_6_TEMPLATE.r8"); + TextureDataR8_drop(tex_1); + return 0; +} diff --git a/src/l2/tests/test_shader_compile.sh b/src/l2/tests/test_shader_compile.sh index 916e835..65e2eb2 100755 --- a/src/l2/tests/test_shader_compile.sh +++ b/src/l2/tests/test_shader_compile.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash + +set +x cd test_shaders function compile(){ @@ -9,3 +11,11 @@ function compile(){ compile 0 compile 1 + +#cd ../test_textures +# +#function png_to_r8g8b8a8 { +# python bitmap_convert.py to_bmp "$1.png" "$1.r8g8b8" +#} +# +#png_to_r8g8b8a8 log_10_2_6 diff --git a/src/l2/tests/test_shaders/glsl/0/0.frag b/src/l2/tests/test_shaders/glsl/0/0.frag index d75e8d2..1ce099b 100644 --- a/src/l2/tests/test_shaders/glsl/0/0.frag +++ b/src/l2/tests/test_shaders/glsl/0/0.frag @@ -6,6 +6,30 @@ layout(location = 0) out vec4 fin_color; layout(binding = 1) uniform sampler2D color_tex; + +struct Pipeline0Spotlight +{ + vec3 pos; + vec3 dir; + vec3 colour; + float range; +}; + +struct Pipeline0PointLight +{ + vec3 pos; + vec3 colour; +}; + +layout(std140, binding = 0) uniform Pipeline0UBO +{ + int spotlight_count; + int point_light_count; + + Pipeline0PointLight point_light_arr[20]; + Pipeline0Spotlight spotlight_arr [120]; +}; + void main(){ fin_color = texture(color_tex, fsin_tex); -} \ No newline at end of file +} diff --git a/src/l2/tests/test_shaders/glsl/0/0.vert b/src/l2/tests/test_shaders/glsl/0/0.vert index 5f93605..d28b556 100644 --- a/src/l2/tests/test_shaders/glsl/0/0.vert +++ b/src/l2/tests/test_shaders/glsl/0/0.vert @@ -5,12 +5,14 @@ layout(location = 1) in vec2 tex; layout(location = 0) out vec2 vsout_tex; -layout(binding = 0) uniform ubo { +layout(push_constant, std430) uniform pc { + /* Individual transformation for a model. Fits in push constant range + Includes global perspective matrix and camera matrix, but for each model there is a distinct + transformation matrix. Right now I only have one instance for each model, + otherwise I would use per-instance vertex attribute */ mat4 t; }; -// todo: add my ubo (location = 0) into the mix -// todo: add my woiod_texture (location = 1) into the mix void main(){ vsout_tex = tex; gl_Position = t * vec4(pos, 1); diff --git a/src/l2/tests/test_shaders/glsl/0b/0b.frag b/src/l2/tests/test_shaders/glsl/0b/0b.frag new file mode 100644 index 0000000..e69de29 diff --git a/src/l2/tests/test_shaders/glsl/0b/0b.vert b/src/l2/tests/test_shaders/glsl/0b/0b.vert new file mode 100644 index 0000000..e69de29 diff --git a/src/l2/tests/test_textures/bitmap_converter.py b/src/l2/tests/test_textures/bitmap_converter.py new file mode 100755 index 0000000..b7dae80 --- /dev/null +++ b/src/l2/tests/test_textures/bitmap_converter.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +raw_png_conv.py +=============== + +Convert between custom bottom‑up raw files (.r8g8b8a8 / .r8b8g8 / .r8) +and normal PNG using Pillow. + +Format +------ +uint32 width (little‑endian) +uint32 height (little‑endian) +pixel data rows bottom‑first: + * .r8g8b8a8 : R G B A (4× uint8) + * .r8b8g8 : R G B (3× uint8) + * .r8 : R (1× uint8 <- grayscale) + +CLI +--- + raw -> png : python raw_png_conv.py to_png input.raw output.png + png -> raw : python raw_png_conv.py to_raw input.png output.raw +""" +import argparse +import struct +from pathlib import Path +from PIL import Image + +# --------------------------------------------------------------------- # +# Helpers +# --------------------------------------------------------------------- # + +RAW_FORMATS = { + ".r8g8b8a8": {"pix_size": 4, "mode": "RGBA"}, + ".r8b8g8": {"pix_size": 3, "mode": "RGB"}, + ".r8": {"pix_size": 1, "mode": "L"}, +} + + +def get_fmt(path: Path): + fmt = RAW_FORMATS.get(path.suffix.lower()) + if not fmt: + raise ValueError(f"Unknown raw extension: {path.suffix}") + return fmt + + +def read_raw(path: Path) -> Image.Image: + """Load any supported raw file -> Pillow Image.""" + spec = get_fmt(path) + with path.open("rb") as f: + header = f.read(8) + if len(header) != 8: + raise ValueError("File too short for header") + w, h = struct.unpack(" top‑down + return Image.frombytes(spec["mode"], (w, h), img_bytes) + + +def write_raw(img: Image.Image, path: Path) -> None: + """Write Pillow Image -> raw file chosen by path suffix.""" + spec = get_fmt(path) + # Convert to required mode + if img.mode != spec["mode"]: + if spec["mode"] == "L": + img = img.convert("L") + elif spec["mode"] == "RGB": + img = img.convert("RGB") + else: # RGBA + img = img.convert("RGBA") + + w, h = img.size + data = img.tobytes() + row_len = w * spec["pix_size"] + rows = [data[i : i + row_len] for i in range(0, len(data), row_len)] + bottom_first = b"".join(reversed(rows)) # top‑down -> bottom‑up + + with path.open("wb") as f: + f.write(struct.pack(" PNG") + sub = ap.add_subparsers(dest="cmd", required=True) + p_png = sub.add_parser("to_png", help="raw -> png") + p_png.add_argument("src", type=Path) + p_png.add_argument("dst", type=Path) + p_raw = sub.add_parser("to_raw", help="png -> raw") + p_raw.add_argument("src", type=Path) + p_raw.add_argument("dst", type=Path) + args = ap.parse_args() + + if args.cmd == "to_png": + to_png(args.src, args.dst) + else: + to_raw(args.src, args.dst) + + +if __name__ == "__main__": + main() diff --git a/src/l2/tests/test_textures/log_10_2_6.png b/src/l2/tests/test_textures/log_10_2_6.png new file mode 100644 index 0000000000000000000000000000000000000000..7c307a2c9f13aa0d5f44ab3df92a762c877b1af2 GIT binary patch literal 16484 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 literal 0 HcmV?d00001 diff --git a/src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png b/src/l2/tests/test_textures/log_10_2_6_TEMPLATE.png new file mode 100644 index 0000000000000000000000000000000000000000..fed645aed7c52a1c4c4c46ac76f75fdc40292ffa GIT binary patch literal 1084 zcmeAS@N?(olHy`uVBq!ia0y~yV3Yu|`#FFF1Ix$R@<5W=)5S5QV$R#UhW^p%5=TD@ zvuj44a8fQ!Fbv{MJ$0jzUqDfGqmiQVl!Mkn-UZ7Syh(lG5Z*t{YD3hfBk2XPC(BnV zP1xkiDdnuz0Iib zd_bozPaETf93OM9ASw5YQxDcXeQeNln1fH*qUQmKanRuUfv-92C*5o04;u709>3f_ zx$lQvjRq%N1)Oov;C`rfX#c5dq5clqC~5(8h|SQBQyKkmzYd%xXD3pqCL{cglMU!C92?>*PPREGc{?Iw7?|OdHEPuzUl$CPnGjgWC;oW~rO}~4ts_yhBW&w=f8ZIwt z_dKb&ft?{+#ru;*+8M2rrVQ&(hF#peWU1>uHUkCk4>bp3b0#t`a9gGR*lV@-wF`>R z|5R?>ZLMJaqh@{X|JM^mA1r!dc&V5%q+zAL@XYv`hkMLK=7)!r`eCL`x98t8eRpWj!96i^eth|6p1*&a_HV(5{-b*HUGZt%ujlF{l=~j%b%+1?|9himJykAb^AiWsHfrc5%~h1n8{}t{LIbH n&p4CQA#(Kane(Ny73CRsuZ%BPC8d`ODi%Cl{an^LB{Ts5`78-* literal 0 HcmV?d00001 diff --git a/src/l2/tests/test_textures/log_10_2_6_v2.png b/src/l2/tests/test_textures/log_10_2_6_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..615b8206d3ffbf45108c60f297db3cf878022491 GIT binary patch literal 18647 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