diff --git a/README.md b/README.md index b66edbc..76229e1 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,10 @@ Simply add `#include "oogabooga/examples/some_example.c"` to build.c and compile Other than examples, a great way to learn is to delve into the code of whatever module you're using. The codebase is written with this in mind. -## Known bugs +## Known bugs & issues - Window positioning & sizing is fucky wucky - Converting 24-bit audio files doesn't really work +- Compiling with msys, cygwin, mingw etc fails ## Licensing By default, the repository has an educational license that makes the engine free to use for personal projects. diff --git a/TODO b/TODO deleted file mode 100644 index db313f5..0000000 --- a/TODO +++ /dev/null @@ -1,73 +0,0 @@ - -- Gamepad - -- Compile with msys2, cygwin, mingw64 - -- Audio - - Allow audio programming - - Inject mixer proc per player - - Inject a mixer proc before and after filling output buffer - - Better spacialization - - Fix vorbis >FILE< streaming (#StbVorbisFileStream) - - Handle audio sources which had their format defaulted to update when default device changes - For streamed audio sources this should be easy enough, because the conversion happens from raw format to source format as we stream it. - For loaded sources though, we would need to convert the source->pcm_frames. - Probably just dirty flag - - Optimize - - Spam simd - - Pool of intermediate buffers (2^) - - Bugs / Issues: - - Small fadeout on start/pause is sometimes noisy - - If audio starts/end abrubtly we will get noise. Maybe detect that on load and apply small fade in/out? - - Setting audio source to a format which differs from audio output format in both channels and bit_width at the same time will produce pure loud noise. - - 24-Bit audio conversion doesn't really work - -- General bugs & issues - - Window width&height is zero when minimized (and we make a 0x0 swap chain) - - Window positioning & sizing is fucky wucky - - Memory error messages are misleading when no VERY_DEBUG - -- Renderer - - API to pass constant values to shader (codegen #define's) - - Draw_Frame instances - - Or draw to a custom Quad_Buffer, then draw quad buffers ? - - draw_deferred ? - - Draw_Frame Render_Image - -- Fonts - - Atlases are way too big, render atlases with size depending on font_height (say, 128 codepoints per atlas) - -- OS - - Window::bool is_minimized - - don't set window.width & window.height to 0 - - Handle monitor change - - Sockets recv, send - - Mouse pointer - - Hide mouse pointer - -- Better hash table - -- Memory - - Arenas - -- Examples/Guides: - - Scaling text for pixel perfect rendering - - Z sorting - - Scissor boxing - - Concurrency - - strings - -- Rework profiler - - Store records and convert to google trace format on exit - - Measure both time and cycles, output a google_trace_cycles.json & google_trace_time.json - -- Concurrency - - Event objects for binary semaphore - -- Needs testing: - - Audio format channel conversions - - sample rate downsampling - - non stereo or mono audio - - Audio spacialization on anything that's not stereo - - Compiling with msvc - - Compiling with gcc \ No newline at end of file diff --git a/build.c b/build.c index 3469d6a..2101fec 100644 --- a/build.c +++ b/build.c @@ -35,14 +35,14 @@ typedef struct Context_Extra { // This is a minimal starting point for new projects. Copy & rename to get started // #include "oogabooga/examples/minimal_game_loop.c" -// #include "oogabooga/examples/text_rendering.c" +#include "oogabooga/examples/text_rendering.c" // #include "oogabooga/examples/custom_logger.c" // #include "oogabooga/examples/renderer_stress_test.c" // #include "oogabooga/examples/tile_game.c" // #include "oogabooga/examples/audio_test.c" // #include "oogabooga/examples/custom_shader.c" // #include "oogabooga/examples/growing_array_example.c" -#include "oogabooga/examples/input_example.c" +// #include "oogabooga/examples/input_example.c" // This is where you swap in your own project! // #include "entry_yourepicgamename.c" diff --git a/changelog.txt b/changelog.txt index 858e59a..10294a4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,8 +1,48 @@ ## v0.01.004 + - Input + - Added Gamepad support + - Gamepad keycodes for buttons & clicks + - input_frame.left_stick .right_stick .left_trigger .right_trigger + - event.gamepad_index for key inputs if it's from the gamepad + - INPUT_EVENT_GAMEPAD_AXIS for same axes but in events and with the event.gamepad_index set + - Deadzone globals: + Vector2 deadzone_left_stick + Vector2 deadzone_right_stick + float32 deadzone_left_trigger + float32 deadzone_right_trigger + - Vibration + set_gamepad_vibration(float32 left, float32 right) + set_specific_gamepad_vibration(int gamepad_index, float32 left, float32 right) + + - Text + - Implemented word-wrapping text into lines with + split_text_to_lines_with_wrapping() + - Added Gfx_Font_Metrics.new_line_offset for an exact value for how much you should decrement the Y on newline + + - strings + - Added some utf8 helpers + utf8_index_to_byte_index() + utf8_slice() + - Added whitespace trimming helpers + string_trim() (trim right & left) + string_trim_left() + string_trim_right() + - Misc + - input_example.c - Reworked os_get_current_time_in_seconds() -> os_get_elapsed_seconds() Now returns seconds sincs app init. - draw_frame.view -> draw_frame.camera_xform (deprecated .view) + - Unecessary refactoring in drawing.c + - Fixed temporary storage overflow warning (contributor kacpercwiklinski f9bf7ff) + - Added build.sh for building on linux (contributor 0xf0adc 90f00b) + - Added linmath constants + v2_one + v3_one + v4_one + v2_zero + v3_zero + v4_zero ## v0.01.003 - Mouse pointers, Audio improvement & features, bug fixes - Os layer diff --git a/oogabooga/examples/text_rendering.c b/oogabooga/examples/text_rendering.c index 3897443..cafbe06 100644 --- a/oogabooga/examples/text_rendering.c +++ b/oogabooga/examples/text_rendering.c @@ -14,6 +14,9 @@ int entry(int argc, char **argv) { const u32 font_height = 48; + seed_for_random = rdtsc(); + u64 gunk_seed = get_random(); + while (!window.should_close) tm_scope("Frame") { reset_temporary_storage(); @@ -29,6 +32,8 @@ int entry(int argc, char **argv) { draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK); draw_text(font, STR("I am text"), font_height, v2(0, 0), v2(1, 1), COLOR_WHITE); + draw_text(font, tprint("Time: %f", os_get_elapsed_seconds()), font_height, v2(0, 100), v2(1, 1), COLOR_WHITE); + float now = (float)os_get_elapsed_seconds(); float animated_x = sin(now*0.1)*(window.width*0.5); @@ -57,7 +62,25 @@ int entry(int argc, char **argv) { local_persist bool show_bounds = false; if (is_key_just_pressed('E')) show_bounds = !show_bounds; + string long_text = STR("Jaunty jackrabbits juggle quaint TTT quilts and quirky quinces, \nquickly queuing up for a jubilant, jazzy jamboree in the jungle.\nLorem ipsilum "); + + // Generate some random gunk to add to the long text + u64 n = ((sin(now)+1)/2.0)*100; + seed_for_random = gunk_seed; + if (n > 0) { + string gunk = talloc_string(n); + for (u64 i = 0; i < n; i++) { + if (get_random_float32() < 0.2) { + gunk.data[i] = ' '; + continue; + } + gunk.data[i] = get_random_int_in_range('a', 'z'); + } + + long_text = string_concat(long_text, gunk, get_temporary_allocator()); + } + if (show_bounds) { // Visualize the bounds we get from metrics Gfx_Text_Metrics m = measure_text(font, long_text, font_height, v2(1, 1)); @@ -66,6 +89,18 @@ int entry(int argc, char **argv) { } draw_text(font, long_text, font_height, v2(-600, -200), v2(1, 1), COLOR_WHITE); + // Wrap text and draw each returned line + string *long_text_wrapped = split_text_to_lines_with_wrapping(long_text, window.width/2*0.9, font, font_height, v2_one, true); + float32 y = 200; + for (u64 i = 0; i < growing_array_get_valid_count(long_text_wrapped); i += 1) { + string line = long_text_wrapped[i]; + Gfx_Font_Metrics m = get_font_metrics(font, font_height); + + draw_text(font, line, font_height, v2(-window.width/2+20, y), v2(1, 1), COLOR_WHITE); + + y -= m.new_line_offset; + } + os_update(); gfx_update(); diff --git a/oogabooga/font.c b/oogabooga/font.c index e0a5b7c..12510f6 100644 --- a/oogabooga/font.c +++ b/oogabooga/font.c @@ -75,6 +75,8 @@ typedef struct Gfx_Font_Metrics { float line_spacing; + float new_line_offset; + } Gfx_Font_Metrics; typedef struct Gfx_Glyph { u32 codepoint; @@ -196,6 +198,9 @@ void font_variation_init(Gfx_Font_Variation *variation, Gfx_Font *font, u32 font variation->metrics.latin_ascent = c_ascent; } + variation->metrics.new_line_offset + = (variation->metrics.latin_ascent-variation->metrics.latin_descent+variation->metrics.line_spacing); + variation->initted = true; } @@ -297,7 +302,7 @@ void walk_glyphs(Walk_Glyphs_Spec spec, Walk_Glyphs_Callback_Proc proc) { if (c == '\n') { x = 0; - y -= (variation->metrics.latin_ascent-variation->metrics.latin_descent+variation->metrics.line_spacing)*spec.scale.y; + y -= variation->metrics.new_line_offset*spec.scale.y; last_c = 0; } @@ -343,11 +348,12 @@ Gfx_Font_Metrics get_font_metrics(Gfx_Font *font, u32 raster_height) { Gfx_Font_Metrics get_font_metrics_scaled(Gfx_Font *font, u32 raster_height, Vector2 scale) { Gfx_Font_Metrics metrics = get_font_metrics(font, raster_height); - metrics.latin_ascent *= scale.x; - metrics.latin_descent *= scale.x; - metrics.max_ascent *= scale.x; - metrics.max_descent *= scale.x; - metrics.line_spacing *= scale.x; + metrics.latin_ascent *= scale.y; + metrics.latin_descent *= scale.y; + metrics.max_ascent *= scale.y; + metrics.max_descent *= scale.y; + metrics.line_spacing *= scale.y; + metrics.new_line_offset *= scale.y; return metrics; } @@ -405,3 +411,105 @@ Gfx_Text_Metrics measure_text(Gfx_Font *font, string text, u32 raster_height, Ve return c.m; } +typedef struct State_For_Glyph_Line_Break_Search { + u64 *line_break_indices; + u64 *glyph_count_per_line; + float32 width; + u64 line_num; + u64 start_index; + u64 index; + u64 last_space_index; + float32 line_start_x; + float32 last_space_x; // -elon + u64 count; + Vector2 scale; +} State_For_Glyph_Line_Break_Search; +bool text_line_wrapping_callback(Gfx_Glyph g, Gfx_Font_Atlas *atlas, float x, float y, void *ud) { + State_For_Glyph_Line_Break_Search *state = (State_For_Glyph_Line_Break_Search*)ud; + + bool is_newline = g.codepoint == '\n'; + if (g.codepoint < 32 && !is_newline) { + state->index += 1; + state->count += 1; + return true; + } + + + float32 glyph_right = x + g.width*state->scale.x; + + if (state->last_space_index == state->index) state->last_space_x = x; + + if ((g.codepoint != 32 && (glyph_right-state->line_start_x) > state->width) || is_newline) { + + bool do_break_at_last_space = state->last_space_index > state->start_index && !is_newline; + + u64 break_index; + if (do_break_at_last_space) break_index = state->last_space_index; + else break_index = state->index; + + growing_array_add((void**)&state->line_break_indices, &state->start_index); + + u64 count_from_start_to_space = break_index-state->start_index; + growing_array_add((void**)&state->glyph_count_per_line, &count_from_start_to_space); + + u64 count_from_space_to_now = state->index - break_index; + state->count = count_from_space_to_now; + + if (break_index == state->index && (is_newline || g.codepoint == ' ')) { + break_index += 1; // Skip \n and space if that's what we break on + } + state->start_index = break_index; + state->line_num += 1; + + if (do_break_at_last_space) state->line_start_x = state->last_space_x; + else state->line_start_x = x; + } else { + if (g.codepoint == ' ') { + state->last_space_index = state->index+1; + } + } + + state->index += 1; + state->count += 1; + + return true; +}; + +// Returns a Growing_Array of string, allocated with temp allocator +string *split_text_to_lines_with_wrapping(string str, float32 width, Gfx_Font *font, u32 raster_height, Vector2 scale, bool do_trim_lines) { + + Gfx_Font_Variation *variation = &font->variations[raster_height]; + + string text = str; + State_For_Glyph_Line_Break_Search result = ZERO(State_For_Glyph_Line_Break_Search); + result.width = width; + result.scale = scale; + growing_array_init((void**)&result.line_break_indices, sizeof(u64), get_temporary_allocator()); + growing_array_init((void**)&result.glyph_count_per_line, sizeof(u64), get_temporary_allocator()); + + walk_glyphs((Walk_Glyphs_Spec){font, text, raster_height, scale, false, &result}, text_line_wrapping_callback); + + string *lines; + growing_array_init((void**)&lines, sizeof(string), get_temporary_allocator()); + + for (u64 i = 0; i < growing_array_get_valid_count(result.line_break_indices); i += 1) { + u64 utf8_index = result.line_break_indices[i]; + u64 byte_index = utf8_index_to_byte_index(text, utf8_index); + u64 utf8_count = result.glyph_count_per_line[i]; + u64 byte_count = utf8_index_to_byte_index(text, utf8_index + utf8_count) - byte_index; + string line_str = string_view(text, byte_index, byte_count); + if (do_trim_lines) line_str = string_trim(line_str); + growing_array_add((void**)&lines, &line_str); + } + if (result.count > 0) { + u64 utf8_index = result.start_index; + u64 byte_index = utf8_index_to_byte_index(text, utf8_index); + u64 utf8_count = result.count; + u64 byte_count = utf8_index_to_byte_index(text, utf8_index + utf8_count) - byte_index; + string line_str = string_view(text, byte_index, byte_count); + if (do_trim_lines) line_str = string_trim(line_str); + growing_array_add((void**)&lines, &line_str); + } + + return lines; +} \ No newline at end of file diff --git a/oogabooga/linmath.c b/oogabooga/linmath.c index efd990d..2a263d6 100644 --- a/oogabooga/linmath.c +++ b/oogabooga/linmath.c @@ -43,6 +43,14 @@ typedef union Vector4 { inline Vector4 v4(float32 x, float32 y, float32 z, float32 w) { return (Vector4){x, y, z, w}; } #define v4_expand(v) (v).x, (v).y, (v).z, (v).w +const Vector2 v2_one = {1, 1}; +const Vector3 v3_one = {1, 1, 1}; +const Vector4 v4_one = {1, 1, 1, 1}; + +const Vector2 v2_zero = {0, 0}; +const Vector3 v3_zero = {0, 0, 0}; +const Vector4 v4_zero = {0, 0, 0, 0}; + inline Vector2 v2_add(Vector2 a, Vector2 b) { return v2(a.x+b.x, a.y+b.y); } diff --git a/oogabooga/string.c b/oogabooga/string.c index 8860b59..5917695 100644 --- a/oogabooga/string.c +++ b/oogabooga/string.c @@ -209,3 +209,24 @@ string_replace_all(string s, string old, string new, Allocator allocator) { return string_builder_get_string(builder); } +string +string_trim_left(string s) { + while (s.count > 0 && *s.data == ' ') { + s.data += 1; + s.count -= 1; + } + return s; +} +string +string_trim_right(string s) { + + while (s.count > 0 && s.data[s.count-1] == ' ') { + s.count -= 1; + } + return s; +} +string +string_trim(string s) { + s = string_trim_left(s); + return string_trim_right(s); +} \ No newline at end of file diff --git a/oogabooga/tests.c b/oogabooga/tests.c index e7a9e3a..1d688a2 100644 --- a/oogabooga/tests.c +++ b/oogabooga/tests.c @@ -442,8 +442,6 @@ void test_strings() { assert(memcmp(multi_append_builder.buffer, expected_result, multi_append_builder.count) == 0, "Failed: multiple appends"); dealloc(heap, multi_append_builder.buffer); - - string cheese_hello = STR("HeCHEESElloCHEESE, WorCHEESEld!"); string hello = string_replace_all(cheese_hello, STR("CHEESE"), STR(""), heap); assert(strings_match(hello, STR("Hello, World!")), "Failed: string_replace"); diff --git a/oogabooga/unicode.c b/oogabooga/unicode.c index 06fc90d..cb2ddcd 100644 --- a/oogabooga/unicode.c +++ b/oogabooga/unicode.c @@ -108,4 +108,27 @@ u32 next_utf8(string *s) { if (result.error) return 0; return result.utf32; -} \ No newline at end of file +} + +u64 utf8_index_to_byte_index(string str, u64 index) { + u64 byte_index = 0; + u64 utf8_index = 0; + while (utf8_index < index && str.count != 0) { + string last_str = str; + u32 codepoint = next_utf8(&str); + if (!codepoint) break; + + u64 byte_diff = ((u8*)str.data)-((u8*)last_str.data); + assert(byte_diff != 0); + byte_index += byte_diff; + utf8_index += 1; + } + return byte_index; +} +string utf8_slice(string str, u64 index, u64 count) { + u64 byte_index = utf8_index_to_byte_index(str, index); + u64 byte_end_index = utf8_index_to_byte_index(str, index+count); + u64 byte_count = byte_end_index - byte_index; + + return string_view(str, byte_index, byte_count); +}