├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE.md ├── README.md ├── build.bat ├── build.c ├── build.sh ├── build_release.bat ├── changelog.txt ├── oogabooga.code-workspace └── oogabooga ├── audio.c ├── base.c ├── color.c ├── concurrency.c ├── cpu.c ├── drawing.c ├── examples ├── audio_test.c ├── berry_bush.png ├── block.wav ├── bloom.c ├── bloom.hlsl ├── bloom_light.hlsl ├── bloom_map.hlsl ├── bruh.wav ├── custom_logger.c ├── custom_shader.c ├── custom_shader.hlsl ├── growing_array_example.c ├── hammer.png ├── hotload │ ├── README.md │ ├── build_engine.c │ ├── build_game.c │ ├── build_launcher.c │ ├── hotload_build_all.bat │ └── hotload_build_game.bat ├── input_example.c ├── male_animation.png ├── minimal_game_loop.c ├── offscreen_drawing.c ├── particles_example.c ├── player.png ├── renderer_stress_test.c ├── sanity_tests.c ├── song1.ogg ├── song2.ogg ├── song3.ogg ├── sprite_animation.c ├── text_rendering.c ├── threaded_drawing.c └── window_test.c ├── ext_particles.c ├── extensions.c ├── font.c ├── gfx_impl_d3d11.c ├── gfx_interface.c ├── growing_array.c ├── hash.c ├── hash_table.c ├── input.c ├── linmath.c ├── memory.c ├── oogabooga.c ├── os_impl_windows.c ├── os_interface.c ├── path_utils.c ├── profiling.c ├── random.c ├── simd.c ├── string.c ├── string_format.c ├── tests.c ├── third_party.c ├── third_party ├── stb_image.h ├── stb_truetype.h └── stb_vorbis.c ├── unicode.c └── utility.c /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.raddbgi 3 | **/worlds 4 | data 5 | uid 6 | *.rdbg 7 | *.exe 8 | *.pdb 9 | .build/ 10 | *.sublime-workspace 11 | *.sublime-project 12 | *.vs 13 | *.sln 14 | *.vcxproj 15 | *.vcxproj.* 16 | *.log 17 | *.ini 18 | *.VC.db 19 | *.obj 20 | *.ilk 21 | *.spall 22 | release/ 23 | output.txt 24 | *.blend1 25 | renderdoc.cap 26 | *.abs 27 | *.asf 28 | env.jai 29 | save.txt 30 | settings.txt 31 | *_BRAIN 32 | *_PLAYTEST 33 | *_SYNC 34 | *_TAG_LEGEND 35 | *_LOCAL_CHANGES 36 | *charlie.code-workspace 37 | !proj/arcane/windows_glslc.exe 38 | !proj/arcane/linux_glslc 39 | !proj/arcane/macos_glslc 40 | *TRACES/ 41 | 42 | fetch.bat 43 | thing.bat 44 | *crash-* 45 | *.dmp 46 | *crash.txt 47 | *crash-uid.txt 48 | *crash-version.txt 49 | output2.txt 50 | test_doc.vkn 51 | 52 | *.focus-config 53 | 54 | *keybinds 55 | *.rdi 56 | 57 | google_trace.json 58 | 59 | build/* 60 | 61 | *google_trace* -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Win32", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": ["DEBUG"], 9 | "forcedInclude": [ 10 | "build.c" 11 | ], 12 | "intelliSenseMode": "clang-x64", 13 | "compilerPath": "C:/Program Files/LLVM/bin/clang", 14 | "cStandard": "c11" 15 | } 16 | ], 17 | "version": 4 18 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Debug with MSVC Debugger", 6 | "type": "cppvsdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/build/cgame.exe", // Run the output executable after compile 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceFolder}", 12 | "environment": [], 13 | "console":"integratedTerminal", 14 | "preLaunchTask": "Compile" 15 | }, 16 | { 17 | "name": "Launch Release with MSVC Debugger", 18 | "type": "cppvsdbg", 19 | "request": "launch", 20 | "program": "${workspaceFolder}/build/release/cgame.exe", // Run the output executable after compile 21 | "args": [], 22 | "stopAtEntry": false, 23 | "cwd": "${workspaceFolder}", 24 | "environment": [], 25 | "console":"integratedTerminal", 26 | "preLaunchTask": "Compile Release" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.errorSquiggles": "disabled" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Compile", 6 | "type": "shell", 7 | "command": "${workspaceFolder}\\build", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "problemMatcher": ["$gcc"], 13 | "presentation": { 14 | "clear": true, 15 | // "revealProblems": "onProblem", 16 | // "close": false, 17 | // "showReuseMessage": true, 18 | } 19 | }, 20 | { 21 | "label": "Compile Release", 22 | "type": "shell", 23 | "command": "${workspaceFolder}\\build_release", 24 | "group": { 25 | "kind": "build" 26 | }, 27 | "problemMatcher": ["$gcc"], 28 | "presentation": { 29 | "clear": true, 30 | // "revealProblems": "onProblem", 31 | // "close": false, 32 | // "showReuseMessage": true, 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Educational License Agreement for Oggabooga 2 | 3 | This license agreement ("Agreement") applies only to non-commercial, educational use of Oggabooga ("Software") by schools, universities, non-profit institutions, and individuals for personal learning and development purposes. 4 | 5 | Grant of License 6 | 7 | Subject to the terms of this Agreement, RANDY.GG hereby grants to the User a non-exclusive, non-transferable, royalty-free license to use the Software for educational purposes. This license does not permit commercial use of any kind. 8 | 9 | Restrictions 10 | 11 | 1. Redistribution: The User may modify and redistribute the Software for educational purposes only, provided that: 12 | - All redistributed versions are also bound by the same educational use conditions. 13 | - Any redistributed version must include an acknowledgement of RANDY.GG as the original source of the Software. 14 | - Redistribution for commercial purposes is strictly prohibited without explicit permission from RANDY.GG. 15 | 16 | 2. Modification: Users are allowed to modify the Software for educational purposes. Any modifications must also be made available under the same educational license terms if redistributed. 17 | 18 | Termination 19 | 20 | This Agreement is effective until terminated. The license granted by this Agreement will terminate automatically if the User fails to comply with any of the terms and conditions hereof. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ooga booga 3 | 4 | ## TOC 5 | - [What is ooga booga?](#what-is-ooga-booga) 6 | - [A new C Standard](#a-new-c-standard) 7 | - [SIMPLICITY IS KING](#simplicity-is-king) 8 | - [The "Build System"](#the-build-system) 9 | - [Course: From Scratch to Steam](#course-from-scratch-to-steam) 10 | - [Quickstart](#quickstart) 11 | - [Examples & Documentation](#examples--documentation) 12 | - [Known bugs](#known-bugs) 13 | - [Licensing](#licensing) 14 | - [Contributions](#contributions) 15 | 16 | ## What is ooga booga? 17 | 18 | Ooga booga, often referred to as a *game engine* for simplicity, is more so designed to be a new C Standard, i.e. a new way to develop software from scratch in C. Other than `` we don't include a single C std header, but are instead writing a better standard library heavily optimized for developing games. Except for some image & audio file decoding, Ooga booga does not rely on any other third party code. 19 | 20 | ### A new C Standard 21 | 22 | Let's face it. The C standard is terrible. Don't even get me started on `string.h`. To be fair, any mainstream language standard is terrible. 23 | 24 | So what if we could strip out the nonsense standard of C and slap on something that's specifically made for video games, prioritizing speed and *simplicity*? 25 | 26 | That's exactly what oogabooga sets out to do. 27 | 28 | ### SIMPLICITY IS KING 29 | 30 | Ooga booga is designed to keep things simple, and let you solve video game problems the simplest way possible. 31 | 32 | What we mean by simple, is twofold: 33 | 34 | 1. Simple to use
35 | Performing SIMPLE and TRIVIAL tasks should be ... SIMPLE. If you want to draw a rectangle, there should be a single procedure to draw a rectangle. If you want to play an audio clip, there should be a single procedure to play an audio clip. Etc. This is something OS & Graphics API's tend to be fascinatingly terrible at even for the most trivial of tasks, and that is a big chunk of what we set out to solve. 36 | 37 | 2. Simple to understand
38 | When you need to do something more complicated, you need to understand the library you're working with. For some reason, it seems like it's a standard for libraries today to obscure the implementation details as much as possible spread out in layers and layers of procedure calls and abstractions. This is terrible. 39 | In Oogabooga, there is none of that. We WANT you to delve into our implementations and see exactly what we do. We do not hide ANYTHING from you. We do not impose RESTRICTIONS on how you solve problems. If you need to know what a procedure does, you search for the symbol and look at the implementation code. That's it. 40 | 41 | 42 | ### The "Build System" 43 | 44 | Our build system is a build.c and a build.bat which invokes the clang compiler on build.c. That's it. And we highly discourage anyone from introducing unnecessary complexity like a third party build system (cmake, premake) or to use header files at all whatsoever. 45 | 46 | This might sound like we are breaking some law, but we're not. We're using a compiler to compile a file which includes all the other files, it doesn't get simpler. We are NOT using third party software to run the same compiler to compile the same files over and over again and write it all to disk to then try and link it together. That's what we call silly business (and unreasonably slow compile times, without any real benefit). 47 | 48 | Oogabooga is made to be used in Unity builds. The idea is that you only include oogabooga.c somewhere in your project, specify the entry (see build.c) and now it's a Oogabooga project. Oogabooga is meant to replace the C standard, so it is not tested with projects which include standard C headers, so that will probably cause issues. 49 | 50 | ## Course: From Scratch to Steam 51 | 52 | This project was started to be used in a course detailing the full ride from starting out making a game to publishing it to Steam. If you're keen on going all-in on getting a small game published to steam within 2-3 months, then check it out for free in our [Skool Community](https://www.skool.com/game-dev). 53 | 54 | ## Quickstart 55 | Currently, we only support Windows x64 systems. 56 | 1. Make sure Windows SDK is installed 57 | 2. Install clang, add to path 58 | 2. Clone repo to 59 | 3. Make a file my_file.c in 60 | ``` 61 | int entry(int argc, char **argv) { 62 | 63 | window.title = STR("Minimal Game Example"); 64 | window.scaled_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI) 65 | window.scaled_height = 720; 66 | window.x = 200; 67 | window.y = 90; 68 | window.clear_color = hex_to_rgba(0x6495EDff); 69 | 70 | while (!window.should_close) { 71 | reset_temporary_storage(); 72 | 73 | os_update(); 74 | gfx_update(); 75 | } 76 | 77 | return 0; 78 | } 79 | ``` 80 | 4. in build.c add this line to the bottom 81 | ``` 82 | #include "my_file.c" 83 | ``` 84 | 5. Run `build.bat` 85 | 6. Run build/cgame.exe 86 | 7. profit 87 | 88 | ## Examples & Documentation 89 | 90 | In general, we try to leave a nice chunk of documentation in a comment at the top of the source code files when needed. 91 | An example would be: If you want to understand how to draw things, go to drawing.c and read the comment at the top of the file. 92 | This is however a WIP and probably not very well-maintained. 93 | 94 | The goal is however to have the main form of documentation be in the form of [examples](oogabooga/examples). Seeing things in practice is generally much more informative than theory. 95 | 96 | Simply add `#include "oogabooga/examples/some_example.c"` to build.c and compile & run to see the example code in action. 97 | 98 | Other than the top-of-file documentation and examples, we have tried to write code that's easy to read & understand i.e. self-documenting. Ideally, a good way of finding what you need is to use your text editor to do a workspace-search for terms related to what you're trying to do and finding related functions/files/documentation. 99 | 100 | ## Known bugs & issues 101 | - If DPI changes in runtime, updating window position or size will be a bit weird 102 | - Converting 24-bit audio files doesn't really work 103 | - Compiling with msys, cygwin, mingw etc fails 104 | 105 | ## Licensing 106 | By default, the repository has an educational license that makes the engine free to use for personal projects. 107 | 108 | [Educational license terms](https://github.com/alpinestudios/oogabooga/blob/master/LICENSE.md) 109 | 110 | You can obtain the full commercial license by being an active member of the community and making your first game. 111 | 112 | [Learn more here](https://www.skool.com/game-dev) 113 | 114 | ## Contributions 115 | - Open PR's with `dev` as the base branch 116 | - Keep it simple, no multi-layer abstractions 117 | - Keep the implementation code readable, comment confusing code 118 | - If you're introducing a new file/module, document the API and how to use it at the top of the file 119 | - Add tests in tests.c if it makes sense to test 120 | - Run tests (#define RUN_TESTS 1) before submitting PR 121 | - Don't submit PR's for: 122 | - the sake of submitting PR's 123 | - Small polishing/tweaks that doesn't really affect the people making games 124 | - When you submit a PR, please answer these prompts (if you're submitting a bugfix then you can skip this): 125 | - What feature/bugfix does this PR implement? 126 | - Why do we need this? 127 | - Describe at least one specific and practical problem this solves for people developing a game 128 | - Does this add complexity/friction for people making games? If so, how do you justify that? -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not exist build ( 3 | mkdir build 4 | ) 5 | 6 | pushd build 7 | 8 | clang -g -fuse-ld=lld -o cgame.exe ../build.c -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -lkernel32 -lgdi32 -luser32 -lruntimeobject -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -lole32 -lshcore -lavrt -lksuser -ldbghelp -femit-all-decls 9 | 10 | popd -------------------------------------------------------------------------------- /build.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | /// 4 | // Build config stuff 5 | 6 | // To enable extensions: 7 | // #define OOGABOOGA_ENABLE_EXTENSIONS 1 8 | // #define OOGABOOGA_EXTENSION_PARTICLES 1 9 | 10 | #define INITIAL_PROGRAM_MEMORY_SIZE MB(5) 11 | 12 | // You might want to increase this if you get a log warning saying the temporary storage was overflown. 13 | // In many cases, overflowing the temporary storage should be fine since it just wraps back around and 14 | // allocations made way earlier in the frame are likely not used anymore. 15 | // This might however not always be the case, so it's probably a good idea to make sure you always have 16 | // enough temporary storage for your game. 17 | #define TEMPORARY_STORAGE_SIZE MB(2) 18 | 19 | // Enable VERY_DEBUG if you are having memory bugs to detect things like heap corruption earlier. 20 | // #define VERY_DEBUG 1 21 | 22 | typedef struct Context_Extra { 23 | int monkee; 24 | } Context_Extra; 25 | // This needs to be defined before oogabooga if we want extra stuff in context 26 | #define CONTEXT_EXTRA Context_Extra 27 | 28 | // This defaults to "entry", but we can set it to anything (except "main" or other existing proc names" 29 | #define ENTRY_PROC entry 30 | 31 | // Ooga booga needs to be included AFTER configuration and BEFORE the program code 32 | #include "oogabooga/oogabooga.c" 33 | 34 | 35 | // 36 | // Comment & Uncomment these to swap projects (only include one at a time) 37 | // 38 | 39 | // This is a minimal starting point for new projects. Copy & rename to get started 40 | // #include "oogabooga/examples/minimal_game_loop.c" 41 | 42 | // #include "oogabooga/examples/text_rendering.c" 43 | // #include "oogabooga/examples/custom_logger.c" 44 | // #include "oogabooga/examples/renderer_stress_test.c" 45 | // #include "oogabooga/examples/audio_test.c" 46 | // #include "oogabooga/examples/custom_shader.c" 47 | // #include "oogabooga/examples/growing_array_example.c" 48 | // #include "oogabooga/examples/input_example.c" 49 | // #include "oogabooga/examples/sprite_animation.c" 50 | // #include "oogabooga/examples/window_test.c" 51 | // #include "oogabooga/examples/offscreen_drawing.c" 52 | // #include "oogabooga/examples/threaded_drawing.c" 53 | #include "oogabooga/examples/bloom.c" 54 | 55 | // These examples require some extensions to be enabled. See top respective files for more info. 56 | // #include "oogabooga/examples/particles_example.c" // Requires OOGABOOGA_EXTENSION_PARTICLES 57 | 58 | // #include "oogabooga/examples/sanity_tests.c" 59 | 60 | // This is where you swap in your own project! 61 | // #include "entry_yourepicgamename.c" 62 | 63 | // #include "entry_randygame.c" -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CC=x86_64-w64-mingw32-gcc 4 | CFLAGS="-g -O0 -std=c11 --static -D_CRT_SECURE_NO_WARNINGS 5 | -Wextra -Wno-sign-compare -Wno-unused-parameter 6 | -lkernel32 -lgdi32 -luser32 -lruntimeobject 7 | -lwinmm -ld3d11 -ldxguid -ld3dcompiler 8 | -lshlwapi -lole32 -lavrt -lksuser -ldbghelp 9 | -lshcore" 10 | SRC=../build.c 11 | EXENAME=game.exe 12 | 13 | mkdir -p build 14 | cd build 15 | $CC $SRC -o $EXENAME $CFLAGS 16 | cd .. -------------------------------------------------------------------------------- /build_release.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not exist build ( 3 | mkdir build 4 | ) 5 | if not exist "build\release" ( 6 | mkdir build\release 7 | ) 8 | 9 | 10 | pushd build 11 | pushd release 12 | 13 | clang -o cgame.exe ../../build.c -Ofast -DNDEBUG -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -Wno-deprecated-declarations -lkernel32 -lgdi32 -luser32 -lruntimeobject -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -lole32 -lshcore -lavrt -lksuser -finline-functions -finline-hint-functions -ffast-math -fno-math-errno -funsafe-math-optimizations -freciprocal-math -ffinite-math-only -fassociative-math -fno-signed-zeros -fno-trapping-math -ftree-vectorize -fomit-frame-pointer -funroll-loops -fno-rtti -fno-exceptions 14 | 15 | popd 16 | popd -------------------------------------------------------------------------------- /oogabooga.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "algorithm": "c" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /oogabooga/base.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #define local_persist static 5 | 6 | #define forward_global extern 7 | 8 | #define alignas _Alignas 9 | 10 | #define null 0 11 | 12 | void 13 | printf(const char* fmt, ...); 14 | 15 | void 16 | dump_stack_trace(); 17 | 18 | #define ASSERT_STR_HELPER(x) #x 19 | #define ASSERT_STR(x) ASSERT_STR_HELPER(x) 20 | #define assert_line(line, cond, ...) {if(!(cond)) { printf("\nAssertion failed in file " __FILE__ " on line " ASSERT_STR(line) "\n\nFailed Condition: " #cond ". Message: " __VA_ARGS__); printf("\n"); dump_stack_trace(); crash(); }} 21 | #define assert(cond, ...) {assert_line(__LINE__, cond, __VA_ARGS__)} 22 | 23 | #define DEFER(start, end) for(int _i_ = ((start), 0); _i_ == 0; _i_ += 1, (end)) 24 | 25 | #define RAW_STRING(...) (#__VA_ARGS__) 26 | 27 | #if CONFIGURATION == RELEASE 28 | #undef assert 29 | #define assert(x, ...) (void)(x) 30 | #endif 31 | 32 | #define panic(...) { print(__VA_ARGS__); crash(); } 33 | 34 | #define cast(t) (t) 35 | 36 | #define ZERO(t) (t){0} 37 | 38 | 39 | 40 | #define FIRST_ARG(arg1, ...) arg1 41 | #define SECOND_ARG(arg1, arg2, ...) arg2 42 | #define print(...) _Generic((FIRST_ARG(__VA_ARGS__)), \ 43 | string: prints, \ 44 | default: printf \ 45 | )(__VA_ARGS__) 46 | #define sprint(...) _Generic((SECOND_ARG(__VA_ARGS__)), \ 47 | string: sprints, \ 48 | default: sprintf \ 49 | )(__VA_ARGS__) 50 | #define tprint(...) _Generic((FIRST_ARG(__VA_ARGS__)), \ 51 | string: tprints, \ 52 | default: tprintf \ 53 | )(__VA_ARGS__) 54 | 55 | typedef struct Nothing {int nothing;} Nothing; 56 | 57 | #ifndef CONTEXT_EXTRA 58 | #define CONTEXT_EXTRA Nothing 59 | #endif 60 | 61 | typedef enum Allocator_Message { 62 | ALLOCATOR_ALLOCATE, 63 | ALLOCATOR_DEALLOCATE, 64 | ALLOCATOR_REALLOCATE, 65 | } Allocator_Message; 66 | typedef void*(*Allocator_Proc)(u64, void*, Allocator_Message, void*); 67 | 68 | typedef enum Log_Level { 69 | LOG_ERROR, 70 | LOG_INFO, 71 | LOG_WARNING, 72 | LOG_VERBOSE, 73 | 74 | LOG_LEVEL_COUNT, 75 | } Log_Level; 76 | 77 | 78 | 79 | typedef struct Allocator { 80 | Allocator_Proc proc; 81 | void *data; 82 | } Allocator; 83 | 84 | Allocator 85 | get_heap_allocator(); 86 | 87 | ogb_instance Allocator 88 | get_temporary_allocator(); 89 | 90 | typedef struct Context { 91 | void *logger; // void(*Logger_Proc)(Log_Level level, string fmt, ...) 92 | 93 | u64 thread_id; 94 | 95 | CONTEXT_EXTRA extra; 96 | } Context; 97 | 98 | #define CONTEXT_STACK_MAX 512 99 | 100 | // 101 | // #Global 102 | //thread_local ogb_instance Context context; 103 | //thread_local ogb_instance Context context_stack[CONTEXT_STACK_MAX]; 104 | //thread_local ogb_instance u64 num_contexts; 105 | ogb_instance 106 | Context get_context(); 107 | 108 | ogb_instance void* 109 | alloc(Allocator allocator, u64 size); 110 | 111 | ogb_instance void* 112 | alloc_uninitialized(Allocator allocator, u64 size); 113 | 114 | ogb_instance void 115 | dealloc(Allocator allocator, void *p); 116 | 117 | ogb_instance void 118 | push_context(Context c); 119 | 120 | ogb_instance void 121 | pop_context(); 122 | // 123 | // 124 | 125 | 126 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 127 | 128 | thread_local Context context; 129 | thread_local Context context_stack[CONTEXT_STACK_MAX]; 130 | thread_local u64 num_contexts = 0; 131 | 132 | void* 133 | alloc(Allocator allocator, u64 size) { 134 | assert(size > 0, "You requested an allocation of zero bytes. I'm not sure what you want with that."); 135 | void *p = allocator.proc(size, 0, ALLOCATOR_ALLOCATE, allocator.data); 136 | #if DO_ZERO_INITIALIZATION 137 | memset(p, 0, size); 138 | #endif 139 | return p; 140 | } 141 | 142 | void* 143 | alloc_uninitialized(Allocator allocator, u64 size) { 144 | assert(size > 0, "You requested an allocation of zero bytes. I'm not sure what you want with that."); 145 | return allocator.proc(size, 0, ALLOCATOR_ALLOCATE, allocator.data); 146 | } 147 | 148 | void 149 | dealloc(Allocator allocator, void *p) { 150 | assert(p != 0, "You tried to deallocate a pointer at adress 0. That doesn't make sense!"); 151 | allocator.proc(0, p, ALLOCATOR_DEALLOCATE, allocator.data); 152 | } 153 | 154 | void 155 | push_context(Context c) { 156 | assert(num_contexts < CONTEXT_STACK_MAX, "Context stack overflow"); 157 | 158 | context_stack[num_contexts] = context; 159 | context = c; 160 | num_contexts += 1; 161 | } 162 | void 163 | pop_context() { 164 | assert(num_contexts > 0, "No contexts to pop!"); 165 | num_contexts -= 1; 166 | context = context_stack[num_contexts]; 167 | } 168 | 169 | ogb_instance 170 | Context get_context() { 171 | return context; 172 | } 173 | 174 | #endif // NOT OOGABOOGA_LINK_EXTERNAL_INSTANCE 175 | 176 | u64 177 | get_next_power_of_two(u64 x) { 178 | if (x == 0) { 179 | return 1; 180 | } 181 | 182 | x--; 183 | x |= x >> 1; 184 | x |= x >> 2; 185 | x |= x >> 4; 186 | x |= x >> 8; 187 | x |= x >> 16; 188 | x |= x >> 32; 189 | 190 | return x + 1; 191 | } 192 | 193 | #define align_next(x, a) ((u64)((x)+(a)-1ULL) & (u64)~((a)-1ULL)) 194 | #define align_previous(x, a) ((u64)(x) & (u64)~((a) - 1ULL)) -------------------------------------------------------------------------------- /oogabooga/color.c: -------------------------------------------------------------------------------- 1 | 2 | // #Cleanup 3 | 4 | // usage example: hex_to_rgba(0x2a2d3aff); 5 | Vector4 hex_to_rgba(s64 hex) { 6 | u8 r = (hex>>24) & 0x000000FF; 7 | u8 g = (hex>>16) & 0x000000FF; 8 | u8 b = (hex>>8) & 0x000000FF; 9 | u8 a = (hex>>0) & 0x000000FF; 10 | return (Vector4){r/255.0, g/255.0, b/255.0, a/255.0}; 11 | } 12 | 13 | // todo - hsv conversion stuff when it's needed -------------------------------------------------------------------------------- /oogabooga/concurrency.c: -------------------------------------------------------------------------------- 1 | 2 | typedef struct Spinlock Spinlock; 3 | typedef struct Mutex Mutex; 4 | typedef struct Binary_Semaphore Binary_Semaphore; 5 | 6 | // These are probably your best friend for sync-free multi-processing. 7 | inline bool compare_and_swap_8(volatile uint8_t *a, uint8_t b, uint8_t old); 8 | inline bool compare_and_swap_16(volatile uint16_t *a, uint16_t b, uint16_t old); 9 | inline bool compare_and_swap_32(volatile uint32_t *a, uint32_t b, uint32_t old); 10 | inline bool compare_and_swap_64(volatile uint64_t *a, uint64_t b, uint64_t old); 11 | inline bool compare_and_swap_bool(volatile bool *a, bool b, bool old); 12 | 13 | /// 14 | // Spinlock "primitive" 15 | // Like a mutex but it eats up the entire core while waiting. 16 | // Beneficial if contention is low or sync speed is important 17 | typedef struct Spinlock { 18 | volatile bool locked; 19 | } Spinlock; 20 | 21 | void ogb_instance 22 | spinlock_init(Spinlock *l); 23 | 24 | void ogb_instance 25 | spinlock_acquire_or_wait(Spinlock* l); 26 | 27 | // This returns true if successfully acquired or false if timeout reached. 28 | bool ogb_instance 29 | spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds); 30 | 31 | void ogb_instance 32 | spinlock_release(Spinlock* l); 33 | 34 | 35 | /// 36 | // High-level mutex primitive (short spinlock then OS mutex lock) 37 | // Just spins for a few (configurable) microseconds with a spinlock, 38 | // and if acquiring fails it falls back to a OS mutex. 39 | #define MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS 100 40 | typedef struct Mutex { 41 | Spinlock spinlock; 42 | f64 spin_time_microseconds; 43 | Mutex_Handle os_handle; 44 | volatile bool spinlock_acquired; 45 | volatile u64 acquiring_thread; 46 | } Mutex; 47 | 48 | void ogb_instance 49 | mutex_init(Mutex *m); 50 | 51 | void ogb_instance 52 | mutex_destroy(Mutex *m); 53 | 54 | void ogb_instance 55 | mutex_acquire_or_wait(Mutex *m); 56 | 57 | void ogb_instance 58 | mutex_release(Mutex *m); 59 | 60 | 61 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 62 | 63 | void spinlock_init(Spinlock *l) { 64 | memset(l, 0, sizeof(*l)); 65 | } 66 | void spinlock_acquire_or_wait(Spinlock* l) { 67 | while (true) { 68 | bool expected = false; 69 | if (compare_and_swap_bool(&l->locked, true, expected)) { 70 | return; 71 | } 72 | while (l->locked) { 73 | // spinny boi 74 | } 75 | } 76 | } 77 | // Returns true on aquired, false if timeout seconds reached 78 | bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds) { 79 | f64 start = os_get_elapsed_seconds(); 80 | while (true) { 81 | bool expected = false; 82 | if (compare_and_swap_bool(&l->locked, true, expected)) { 83 | return true; 84 | } 85 | while (l->locked) { 86 | // spinny boi 87 | if ((os_get_elapsed_seconds()-start) >= timeout_seconds) return false; 88 | } 89 | } 90 | return true; 91 | } 92 | void spinlock_release(Spinlock* l) { 93 | bool expected = true; 94 | bool success = compare_and_swap_bool(&l->locked, false, expected); 95 | assert(success, "This thread should have acquired the spinlock but compare_and_swap failed"); 96 | } 97 | 98 | 99 | /// 100 | // High-level mutex primitive (short spinlock then OS mutex lock) 101 | 102 | void mutex_init(Mutex *m) { 103 | spinlock_init(&m->spinlock); 104 | m->spin_time_microseconds = MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS; 105 | m->os_handle = os_make_mutex(); 106 | m->spinlock_acquired = false; 107 | m->acquiring_thread = 0; 108 | } 109 | void mutex_destroy(Mutex *m) { 110 | os_destroy_mutex(m->os_handle); 111 | } 112 | void mutex_acquire_or_wait(Mutex *m) { 113 | if (spinlock_acquire_or_wait_timeout(&m->spinlock, m->spin_time_microseconds / 1000000.0)) { 114 | assert(!m->spinlock_acquired, "Internal sync error in Mutex"); 115 | m->spinlock_acquired = true; 116 | } 117 | os_lock_mutex(m->os_handle); 118 | 119 | assert(!m->acquiring_thread, "Internal sync error in Mutex: Multiple threads acquired"); 120 | m->acquiring_thread = context.thread_id; 121 | } 122 | void mutex_release(Mutex *m) { 123 | assert(m->acquiring_thread != 0, "Tried to release a mutex which is not acquired"); 124 | assert(m->acquiring_thread == context.thread_id, "Non-owning thread tried to release mutex"); 125 | m->acquiring_thread = 0; 126 | bool was_spinlock_acquired = m->spinlock_acquired; 127 | m->spinlock_acquired = false; 128 | os_unlock_mutex(m->os_handle); 129 | if (was_spinlock_acquired) { 130 | spinlock_release(&m->spinlock); 131 | } 132 | } 133 | 134 | #endif -------------------------------------------------------------------------------- /oogabooga/cpu.c: -------------------------------------------------------------------------------- 1 | // #Portability rip ARM 2 | typedef struct Cpu_Info_X86 { 3 | u32 eax; 4 | u32 ebx; 5 | u32 ecx; 6 | u32 edx; 7 | } Cpu_Info_X86; 8 | 9 | typedef struct Cpu_Capabilities { 10 | bool sse1; 11 | bool sse2; 12 | bool sse3; 13 | bool ssse3; 14 | bool sse41; 15 | bool sse42; 16 | bool any_sse; 17 | bool avx; 18 | bool avx2; 19 | bool avx512; 20 | 21 | } Cpu_Capabilities; 22 | 23 | // I think this is the standard? (sse1) 24 | #define COMPILER_CAN_DO_SSE 1 25 | 26 | /// 27 | // Compiler specific stuff 28 | #if COMPILER_MVSC 29 | #define inline __forceinline 30 | #define alignat(x) __declspec(align(x)) 31 | #define noreturn __declspec(noreturn) 32 | #define COMPILER_HAS_MEMCPY_INTRINSICS 1 33 | inline void 34 | crash() noreturn { 35 | __debugbreak(); 36 | volatile int *a = 0; 37 | *a = 5; 38 | a = (volatile int*)0xDEADBEEF; 39 | *a = 5; 40 | } 41 | #include 42 | #pragma intrinsic(__rdtsc) 43 | inline u64 44 | rdtsc() { 45 | return __rdtsc(); 46 | } 47 | inline Cpu_Info_X86 cpuid(u32 function_id) { 48 | Cpu_Info_X86 i; 49 | __cpuid((int*)&i, function_id); 50 | return i; 51 | } 52 | 53 | #if _M_IX86_FP >= 2 54 | #define COMPILER_CAN_DO_SSE2 1 55 | #define COMPILER_CAN_DO_SSE41 1 56 | #else 57 | #define COMPILER_CAN_DO_SSE2 0 58 | #define COMPILER_CAN_DO_SSE41 0 59 | #endif 60 | #ifdef __AVX__ 61 | #define COMPILER_CAN_DO_AVX 1 62 | #else 63 | #define COMPILER_CAN_DO_AVX 0 64 | #endif 65 | #ifdef __AVX2__ 66 | #define COMPILER_CAN_DO_AVX2 1 67 | #else 68 | #define COMPILER_CAN_DO_AVX2 0 69 | #endif 70 | #ifdef __AVX512F__ 71 | #define COMPILER_CAN_DO_AVX512 1 72 | #else 73 | #define COMPILER_CAN_DO_AVX512 0 74 | #endif 75 | 76 | #define DEPRECATED(proc, msg) __declspec(deprecated(msg)) func 77 | 78 | #pragma intrinsic(_InterlockedCompareExchange8) 79 | #pragma intrinsic(_InterlockedCompareExchange16) 80 | #pragma intrinsic(_InterlockedCompareExchange) 81 | #pragma intrinsic(_InterlockedCompareExchange64) 82 | 83 | inline bool 84 | compare_and_swap_8(volatile uint8_t *a, uint8_t b, uint8_t old) { 85 | return _InterlockedCompareExchange8((volatile char*)a, (char)b, (char)old) == old; 86 | } 87 | 88 | inline bool 89 | compare_and_swap_16(volatile uint16_t *a, uint16_t b, uint16_t old) { 90 | return _InterlockedCompareExchange16((volatile short*)a, (short)b, (short)old) == old; 91 | } 92 | 93 | inline bool 94 | compare_and_swap_32(volatile uint32_t *a, uint32_t b, uint32_t old) { 95 | return _InterlockedCompareExchange((volatile long*)a, (long)b, (long)old) == old; 96 | } 97 | 98 | inline bool 99 | compare_and_swap_64(volatile uint64_t *a, uint64_t b, uint64_t old) { 100 | return _InterlockedCompareExchange64((volatile long long*)a, (long long)b, (long long)old) == old; 101 | } 102 | 103 | inline bool 104 | compare_and_swap_bool(volatile bool *a, bool b, bool old) { 105 | return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old); 106 | } 107 | 108 | #define MEMORY_BARRIER _ReadWriteBarrier() 109 | 110 | #define thread_local __declspec(thread) 111 | 112 | #define SHARED_EXPORT __declspec(dllexport) 113 | #define SHARED_IMPORT __declspec(dllimport) 114 | 115 | #elif COMPILER_GCC || COMPILER_CLANG 116 | #define inline __attribute__((always_inline)) inline 117 | #define alignat(x) __attribute__((aligned(x))) 118 | #define noreturn __attribute__((noreturn)) 119 | #define COMPILER_HAS_MEMCPY_INTRINSICS 1 120 | 121 | inline void noreturn 122 | crash() { 123 | __builtin_trap(); 124 | volatile int *a = 0; 125 | *a = 5; 126 | a = (int*)0xDEADBEEF; 127 | *a = 5; 128 | } 129 | 130 | inline u64 131 | rdtsc() { 132 | unsigned int lo, hi; 133 | __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); 134 | return ((u64)hi << 32) | lo; 135 | } 136 | 137 | inline 138 | Cpu_Info_X86 cpuid(u32 function_id) { 139 | Cpu_Info_X86 info; 140 | __asm__ __volatile__( 141 | "cpuid" 142 | : "=a"(info.eax), "=b"(info.ebx), "=c"(info.ecx), "=d"(info.edx) 143 | : "a"(function_id), "c"(0)); 144 | return info; 145 | } 146 | 147 | #ifdef __SSE2__ 148 | #define COMPILER_CAN_DO_SSE2 1 149 | #else 150 | #define COMPILER_CAN_DO_SSE2 0 151 | #endif 152 | #ifdef __SSE4_1__ 153 | #define COMPILER_CAN_DO_SSE41 1 154 | #else 155 | #define COMPILER_CAN_DO_SSE41 0 156 | #endif 157 | #ifdef __AVX__ 158 | #define COMPILER_CAN_DO_AVX 1 159 | #else 160 | #define COMPILER_CAN_DO_AVX 0 161 | #endif 162 | #ifdef __AVX2__ 163 | #define COMPILER_CAN_DO_AVX2 1 164 | #else 165 | #define COMPILER_CAN_DO_AVX2 0 166 | #endif 167 | #ifdef __AVX512F__ 168 | #define COMPILER_CAN_DO_AVX512 1 169 | #else 170 | #define COMPILER_CAN_DO_AVX512 0 171 | #endif 172 | 173 | #define DEPRECATED(proc, msg) __attribute__((deprecated(msg))) proc 174 | 175 | inline bool 176 | compare_and_swap_8(volatile uint8_t *a, uint8_t b, uint8_t old) { 177 | unsigned char result; 178 | __asm__ __volatile__( 179 | "lock; cmpxchgb %2, %1" 180 | : "=a" (result), "=m" (*a) 181 | : "r" (b), "m" (*a), "a" (old) 182 | : "memory" 183 | ); 184 | return result == old; 185 | } 186 | 187 | inline bool 188 | compare_and_swap_16(volatile uint16_t *a, uint16_t b, uint16_t old) { 189 | unsigned short result; 190 | __asm__ __volatile__( 191 | "lock; cmpxchgw %2, %1" 192 | : "=a" (result), "=m" (*a) 193 | : "r" (b), "m" (*a), "a" (old) 194 | : "memory" 195 | ); 196 | return result == old; 197 | } 198 | 199 | inline bool 200 | compare_and_swap_32(volatile uint32_t *a, uint32_t b, uint32_t old) { 201 | unsigned int result; 202 | __asm__ __volatile__( 203 | "lock; cmpxchgl %2, %1" 204 | : "=a" (result), "=m" (*a) 205 | : "r" (b), "m" (*a), "a" (old) 206 | : "memory" 207 | ); 208 | return result == old; 209 | } 210 | 211 | inline bool 212 | compare_and_swap_64(volatile uint64_t *a, uint64_t b, uint64_t old) { 213 | unsigned long long result; 214 | __asm__ __volatile__( 215 | "lock; cmpxchgq %2, %1" 216 | : "=a" (result), "=m" (*a) 217 | : "r" (b), "m" (*a), "a" (old) 218 | : "memory" 219 | ); 220 | return result == old; 221 | } 222 | 223 | inline bool 224 | compare_and_swap_bool(volatile bool *a, bool b, bool old) { 225 | return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old); 226 | } 227 | 228 | #define MEMORY_BARRIER {__asm__ __volatile__("" ::: "memory");__sync_synchronize();} 229 | 230 | #define thread_local __thread 231 | 232 | #if TARGET_OS == WINDOWS 233 | #define SHARED_EXPORT __attribute__((visibility("default"))) __declspec(dllexport) 234 | #define SHARED_IMPORT __declspec(dllimport) 235 | #else 236 | #define SHARED_EXPORT __attribute__((visibility("default"))) 237 | #define SHARED_IMPORT 238 | #endif 239 | 240 | #else 241 | #define inline inline 242 | #define COMPILER_HAS_MEMCPY_INTRINSICS 0 243 | 244 | inline u64 245 | rdtsc() { return 0; } 246 | inline Cpu_Info_X86 cpuid(u32 function_id) {return (Cpu_Info_X86){0};} 247 | #define COMPILER_CAN_DO_SSE2 0 248 | #define COMPILER_CAN_DO_AVX 0 249 | #define COMPILER_CAN_DO_AVX2 0 250 | #define COMPILER_CAN_DO_AVX512 0 251 | 252 | #define DEPRECATED(proc, msg) 253 | 254 | #define MEMORY_BARRIER 255 | 256 | #warning "Compiler is not explicitly supported, some things will probably not work as expected" 257 | #endif 258 | 259 | 260 | 261 | Cpu_Capabilities 262 | query_cpu_capabilities() { 263 | Cpu_Capabilities result = {0}; 264 | 265 | Cpu_Info_X86 info = cpuid(1); 266 | 267 | result.sse1 = (info.edx & (1 << 25)) != 0; 268 | result.sse2 = (info.edx & (1 << 26)) != 0; 269 | result.sse3 = (info.ecx & (1 << 0)) != 0; 270 | result.ssse3 = (info.ecx & (1 << 9)) != 0; 271 | result.sse41 = (info.ecx & (1 << 19)) != 0; 272 | result.sse42 = (info.ecx & (1 << 20)) != 0; 273 | result.any_sse = result.sse1 || result.sse2 || result.sse3 || result.ssse3 || result.sse41 || result.sse42; 274 | 275 | result.avx = (info.ecx & (1 << 28)) != 0; 276 | 277 | Cpu_Info_X86 ext_info = cpuid(7); 278 | result.avx2 = (ext_info.ebx & (1 << 5)) != 0; 279 | 280 | result.avx512 = (ext_info.ebx & (1 << 16)) != 0; 281 | 282 | return result; 283 | } 284 | 285 | -------------------------------------------------------------------------------- /oogabooga/examples/audio_test.c: -------------------------------------------------------------------------------- 1 | #define FONT_HEIGHT 48 2 | 3 | Gfx_Font *font; 4 | 5 | bool button(string label, Vector2 pos, Vector2 size, bool enabled); 6 | 7 | /* 8 | NOTE: 9 | 10 | In most cases you will probably just want to call play_one_clip(). 11 | Using Audio Players is for when you need more control over the playback. 12 | 13 | */ 14 | 15 | int entry(int argc, char **argv) { 16 | 17 | window.title = STR("Audio test"); 18 | window.point_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI) 19 | window.point_height = 720; 20 | window.x = 200; 21 | window.y = 90; 22 | window.clear_color = hex_to_rgba(0x6495EDff); 23 | 24 | Allocator heap = get_heap_allocator(); 25 | 26 | font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), heap); 27 | assert(font, "Failed loading arial.ttf"); 28 | 29 | #define NUM_SONGS 3 30 | 31 | Audio_Source bruh; 32 | Audio_Source songs[NUM_SONGS]; 33 | 34 | bool bruh_ok = audio_open_source_load(&bruh, STR("oogabooga/examples/bruh.wav"), heap); 35 | assert(bruh_ok, "Could not load bruh.wav"); 36 | 37 | for (u64 i = 0; i < NUM_SONGS; i += 1) { 38 | bool song_ok = audio_open_source_stream(&songs[i], tprint("oogabooga/examples/song%d.ogg", i+1), heap); 39 | assert(song_ok, "Could not load song%d.ogg", i+1); 40 | } 41 | 42 | // By default, audio sources will be converted to the same format as the output buffer. 43 | // However, if you want it to be a specific format (or something smaller than the 44 | // output format), then you can call: 45 | // audio_open_source_load_format() 46 | // audio_open_source_stream_format() 47 | 48 | Audio_Player *clip_player = audio_player_get_one(); 49 | Audio_Player *song_player = audio_player_get_one(); 50 | 51 | // If you ever need it, you can give the player back to be reused somewhere else. 52 | // audio_player_release(clip_player); 53 | // But this is probably only something you would need to care about if you had a very 54 | // complicated audio system. 55 | 56 | audio_player_set_source(clip_player, bruh); 57 | audio_player_set_source(song_player, songs[0]); 58 | 59 | audio_player_set_state(clip_player, AUDIO_PLAYER_STATE_PAUSED); 60 | audio_player_set_state(song_player, AUDIO_PLAYER_STATE_PAUSED); 61 | 62 | audio_player_set_looping(clip_player, true); 63 | //play_one_audio_clip(STR("oogabooga/examples/block.wav")); 64 | 65 | u64 song_index = 0; 66 | 67 | while (!window.should_close) { 68 | reset_temporary_storage(); 69 | 70 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 71 | 72 | if (is_key_just_pressed(MOUSE_BUTTON_RIGHT)) { 73 | // Easy mode (when you don't care and just want to play a clip) 74 | play_one_audio_clip(STR("oogabooga/examples/block.wav")); 75 | } 76 | 77 | 78 | 79 | Vector4 rect; 80 | rect.x = -window.width/2+40; 81 | rect.y = window.height/2-FONT_HEIGHT-40; 82 | rect.z = FONT_HEIGHT*8; 83 | rect.w = FONT_HEIGHT*1.5; 84 | 85 | bool clip_playing = clip_player->state == AUDIO_PLAYER_STATE_PLAYING; 86 | bool song_playing = song_player->state == AUDIO_PLAYER_STATE_PLAYING; 87 | 88 | if (button(STR("Song"), rect.xy, rect.zw, song_playing)) { 89 | if (song_playing) audio_player_set_state(song_player, AUDIO_PLAYER_STATE_PAUSED); 90 | else audio_player_set_state(song_player, AUDIO_PLAYER_STATE_PLAYING); 91 | } 92 | 93 | rect.y -= FONT_HEIGHT*1.8; 94 | if (button(STR("Loop Bruh"), rect.xy, rect.zw, clip_playing)) { 95 | if (clip_playing) audio_player_set_state(clip_player, AUDIO_PLAYER_STATE_PAUSED); 96 | else audio_player_set_state(clip_player, AUDIO_PLAYER_STATE_PLAYING); 97 | } 98 | rect.y -= FONT_HEIGHT*1.8; 99 | if (button(STR("One Bruh"), rect.xy, rect.zw, false)) { 100 | Audio_Playback_Config config = {0}; 101 | config.volume = 1.0; 102 | config.playback_speed = get_random_float32_in_range(0.8, 1.2); 103 | config.enable_spacialization = true; 104 | config.position = v3( 105 | get_random_float32_in_range(-window.width/2, window.width/2), 106 | get_random_float32_in_range(-window.height/2, window.height/2), 107 | 0 108 | ); 109 | config.spacial_distance_min = 400; 110 | config.spacial_distance_max = 600; 111 | config.spacial_projection = draw_frame.projection; 112 | config.spacial_listener_xform = draw_frame.camera_xform; 113 | play_one_audio_clip_with_config(STR("oogabooga/examples/bruh.wav"), config); 114 | } 115 | rect.y -= FONT_HEIGHT*3; 116 | if (button(STR("Reset song"), rect.xy, rect.zw, false)) { 117 | audio_player_set_progression_factor(song_player, 0); 118 | } 119 | 120 | rect.y = window.height/2-FONT_HEIGHT-40; 121 | rect.x += rect.z + FONT_HEIGHT; 122 | if (button(STR("Song vol up"), rect.xy, rect.zw, false)) { 123 | song_player->config.volume += 0.05; 124 | } 125 | rect.y -= FONT_HEIGHT*1.8; 126 | if (button(STR("Song vol down"), rect.xy, rect.zw, false)) { 127 | song_player->config.volume -= 0.05; 128 | } 129 | song_player->config.volume = clamp(song_player->config.volume, 0, 20); 130 | rect.x += rect.z + FONT_HEIGHT; 131 | draw_text(font, tprint("Song volume: %d%%", (s64)round(song_player->config.volume*100)), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK); 132 | draw_text(font, tprint("Song volume: %d%%", (s64)round(song_player->config.volume*100)), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE); 133 | rect.x -= rect.z + FONT_HEIGHT; 134 | 135 | rect.y -= FONT_HEIGHT*5; 136 | if (button(STR("Speed up"), rect.xy, rect.zw, false)) { 137 | song_player->config.playback_speed += 0.05; 138 | } 139 | rect.y -= FONT_HEIGHT*1.8; 140 | if (button(STR("Speed down"), rect.xy, rect.zw, false)) { 141 | song_player->config.playback_speed -= 0.05; 142 | } 143 | rect.y -= FONT_HEIGHT*1.8; 144 | if (button(STR("Next song"), rect.xy, rect.zw, false)) { 145 | song_index += 1; 146 | if (song_index >= NUM_SONGS) song_index = 0; 147 | audio_player_transition_to_source(song_player, songs[song_index], 4); 148 | } 149 | song_player->config.playback_speed = clamp(song_player->config.playback_speed, 0, 20); 150 | rect.x += rect.z + FONT_HEIGHT; 151 | draw_text(font, tprint("Speed: %d%%", (s64)round(song_player->config.playback_speed*100)), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK); 152 | draw_text(font, tprint("Speed: %d%%", (s64)round(song_player->config.playback_speed*100)), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE); 153 | 154 | 155 | rect.y -= FONT_HEIGHT*3; 156 | 157 | draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, v2_sub(rect.xy, v2(2, -2)), v2(1, 1), COLOR_BLACK); 158 | draw_text(font, STR("Right-click for thing"), FONT_HEIGHT, rect.xy, v2(1, 1), COLOR_WHITE); 159 | 160 | os_update(); 161 | gfx_update(); 162 | } 163 | 164 | // Don't actually do this on exit!! 165 | // Your OS will clean up everything when program exits, so this is only slowing down the time it takes for the program to exit. 166 | // This is just for testing purposes. 167 | audio_source_destroy(&bruh); 168 | for (u64 i = 0; i < NUM_SONGS; i += 1) { 169 | audio_source_destroy(&songs[i]); 170 | } 171 | 172 | return 0; 173 | } 174 | 175 | bool button(string label, Vector2 pos, Vector2 size, bool enabled) { 176 | 177 | Vector4 color = v4(.25, .25, .25, 1); 178 | 179 | float L = pos.x; 180 | float R = L + size.x; 181 | float B = pos.y; 182 | float T = B + size.y; 183 | 184 | float mx = input_frame.mouse_x - window.width/2; 185 | float my = input_frame.mouse_y - window.height/2; 186 | 187 | bool pressed = false; 188 | 189 | if (mx >= L && mx < R && my >= B && my < T) { 190 | color = v4(.15, .15, .15, 1); 191 | if (is_key_down(MOUSE_BUTTON_LEFT)) { 192 | color = v4(.05, .05, .05, 1); 193 | } 194 | 195 | pressed = is_key_just_released(MOUSE_BUTTON_LEFT); 196 | } 197 | 198 | if (enabled) { 199 | color = v4_add(color, v4(.1, .1, .1, 0)); 200 | } 201 | 202 | draw_rect(pos, size, color); 203 | 204 | Gfx_Text_Metrics m = measure_text(font, label, FONT_HEIGHT, v2(1, 1)); 205 | 206 | Vector2 bottom_left = v2_sub(pos, m.functional_pos_min); 207 | bottom_left.x += size.x/2; 208 | bottom_left.x -= m.functional_size.x/2; 209 | 210 | bottom_left.y += size.y/2; 211 | bottom_left.y -= m.functional_size.y/2; 212 | 213 | draw_text(font, label, FONT_HEIGHT, bottom_left, v2(1, 1), COLOR_WHITE); 214 | 215 | return pressed; 216 | } -------------------------------------------------------------------------------- /oogabooga/examples/berry_bush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/berry_bush.png -------------------------------------------------------------------------------- /oogabooga/examples/block.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/block.wav -------------------------------------------------------------------------------- /oogabooga/examples/bloom.c: -------------------------------------------------------------------------------- 1 | /* 2 | Screen-space bloom example. 3 | 4 | How it works: 5 | 6 | 1. Draw game to an image (game_image) 7 | 8 | 2. Draw game to another image, but with a shader that only lets through color values above 1.0 (bloom_map) 9 | 10 | 3. Draw game_image to a third image (final_image), binding bloom_map to a shader which samples surrounding pixels from the bloom map to create the bloom effect. 11 | 12 | 4. Draw final_image to the window 13 | */ 14 | 15 | 16 | 17 | Gfx_Shader_Extension load_shader(string file, int cbuffer_size); 18 | void draw_game(Draw_Frame *frame); 19 | bool button(string label, Vector2 pos, Vector2 size, bool enabled); 20 | 21 | typedef enum View_Mode { 22 | VIEW_GAME_AFTER_POSTPROCESS, 23 | VIEW_GAME_BEFORE_POSTPROCESS, 24 | VIEW_BLOOM_MAP, 25 | 26 | VIEW_MODE_MAX 27 | } View_Mode; 28 | 29 | string view_mode_stringify(View_Mode vm) { 30 | switch (vm) { 31 | case VIEW_GAME_AFTER_POSTPROCESS: 32 | return STR("VIEW_GAME_AFTER_POSTPROCESS"); 33 | case VIEW_GAME_BEFORE_POSTPROCESS: 34 | return STR("VIEW_GAME_BEFORE_POSTPROCESS"); 35 | case VIEW_BLOOM_MAP: 36 | return STR("VIEW_BLOOM_MAP"); 37 | default: return STR(""); 38 | } 39 | } 40 | 41 | // BEWARE std140 packing: 42 | // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules 43 | typedef struct Scene_Cbuffer { 44 | Vector2 mouse_pos_screen; // We use this to make a light around the mouse cursor 45 | Vector2 window_size; // We only use this to revert the Y in the shader because for some reason d3d11 inverts it. 46 | } Scene_Cbuffer; 47 | 48 | Gfx_Font *font; 49 | u32 font_height = 28; 50 | int entry(int argc, char **argv) { 51 | 52 | window.title = STR("Bloom example"); 53 | 54 | font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 55 | assert(font != 0, "Failed loading arial.ttf"); 56 | 57 | // regular shader + point light which makes things extra bright 58 | Gfx_Shader_Extension light_shader = load_shader(STR("oogabooga/examples/bloom_light.hlsl"), sizeof(Scene_Cbuffer)); 59 | // shader used to generate bloom map. Very simple: It takes the output color -1 on all channels 60 | // so all we have left is how much bloom there should be 61 | Gfx_Shader_Extension bloom_map_shader = load_shader(STR("oogabooga/examples/bloom_map.hlsl"), sizeof(Scene_Cbuffer)); 62 | // postprocess shader where the bloom happens. It samples from the generated bloom_map. 63 | Gfx_Shader_Extension postprocess_bloom_shader = load_shader(STR("oogabooga/examples/bloom.hlsl"), sizeof(Scene_Cbuffer)); 64 | 65 | Gfx_Image *bloom_map = 0; 66 | Gfx_Image *game_image = 0; 67 | Gfx_Image *final_image = 0; 68 | 69 | View_Mode view = VIEW_GAME_AFTER_POSTPROCESS; 70 | 71 | Draw_Frame offscreen_draw_frame; 72 | draw_frame_init(&offscreen_draw_frame); 73 | 74 | Scene_Cbuffer scene_cbuffer; 75 | 76 | // Window width and height may be 0 before first call to os_update(), and we base render target sizes of window size. 77 | // This is an Oogabooga quirk which might get fixed at some point. 78 | os_update(); 79 | 80 | while (!window.should_close) { 81 | reset_temporary_storage(); 82 | 83 | 84 | /// 85 | // Create bloom map and game image when window size changes (or first time) 86 | local_persist Os_Window last_window; 87 | if ((last_window.width != window.width || last_window.height != window.height || !game_image) && window.width > 0 && window.height > 0) { 88 | if (bloom_map) delete_image(bloom_map); 89 | if (game_image) delete_image(game_image); 90 | if (final_image) delete_image(final_image); 91 | 92 | bloom_map = make_image_render_target(window.width, window.height, 4, 0, get_heap_allocator()); 93 | game_image = make_image_render_target(window.width, window.height, 4, 0, get_heap_allocator()); 94 | final_image = make_image_render_target(window.width, window.height, 4, 0, get_heap_allocator()); 95 | } 96 | last_window = window; 97 | 98 | // Set stuff in cbuffer which we need to pass to shaders 99 | scene_cbuffer.mouse_pos_screen = v2(input_frame.mouse_x, window.height-input_frame.mouse_y); 100 | scene_cbuffer.window_size = v2(window.width, window.height); 101 | 102 | /// 103 | // Draw game with light shader to game_image 104 | 105 | // Reset draw frame & clear the image with a clear color 106 | draw_frame_reset(&offscreen_draw_frame); 107 | gfx_clear_render_target(game_image, v4(.7, .7, .7, 1.0)); 108 | 109 | // Draw game things to offscreen Draw_Frame 110 | draw_game(&offscreen_draw_frame); 111 | 112 | // Set the shader & cbuffer before the render call 113 | offscreen_draw_frame.shader_extension = light_shader; 114 | offscreen_draw_frame.cbuffer = &scene_cbuffer; 115 | 116 | // Render Draw_Frame to the image 117 | ///// NOTE: Drawing to one frame like this will wait for the gpu to finish the last draw call. If this becomes 118 | // a performance bottleneck, you would have more frames "in flight" which you cycle through. 119 | gfx_render_draw_frame(&offscreen_draw_frame, game_image); 120 | 121 | 122 | 123 | /// 124 | // Draw game with bloom map shader to the bloom map 125 | 126 | // Reset draw frame & clear the image 127 | draw_frame_reset(&offscreen_draw_frame); 128 | gfx_clear_render_target(bloom_map, COLOR_BLACK); 129 | 130 | 131 | // Draw game things to offscreen Draw_Frame 132 | draw_game(&offscreen_draw_frame); 133 | 134 | // Set the shader & cbuffer before the render call 135 | offscreen_draw_frame.shader_extension = bloom_map_shader; 136 | offscreen_draw_frame.cbuffer = &scene_cbuffer; 137 | 138 | // Render Draw_Frame to the image 139 | ///// NOTE: Drawing to one frame like this will wait for the gpu to finish the last draw call. If this becomes 140 | // a performance bottleneck, you would have more frames "in flight" which you cycle through. 141 | gfx_render_draw_frame(&offscreen_draw_frame, bloom_map); 142 | 143 | /// 144 | // Draw game image into final image, using the bloom shader which samples from the bloom_map 145 | 146 | draw_frame_reset(&offscreen_draw_frame); 147 | gfx_clear_render_target(final_image, COLOR_BLACK); 148 | 149 | // To sample from another image in the shader, we must bind it to a specific slot. 150 | draw_frame_bind_image_to_shader(&offscreen_draw_frame, bloom_map, 0); 151 | 152 | // Draw the game the final image, but now with the post process shader 153 | draw_image_in_frame(game_image, v2(-window.width/2, -window.height/2), v2(window.width, window.height), COLOR_WHITE, &offscreen_draw_frame); 154 | 155 | offscreen_draw_frame.shader_extension = postprocess_bloom_shader; 156 | offscreen_draw_frame.cbuffer = &scene_cbuffer; 157 | 158 | gfx_render_draw_frame(&offscreen_draw_frame, final_image); 159 | 160 | 161 | switch (view) { 162 | case VIEW_GAME_AFTER_POSTPROCESS: 163 | Draw_Quad *q = draw_image(final_image, v2(-window.width/2, -window.height/2), v2(window.width, window.height), COLOR_WHITE); 164 | // The draw image will be flipped on y, so we want to draw it "upside down" 165 | swap(q->uv.y, q->uv.w, float); 166 | break; 167 | case VIEW_GAME_BEFORE_POSTPROCESS: 168 | draw_image(game_image, v2(-window.width/2, -window.height/2), v2(window.width, window.height), COLOR_WHITE); 169 | break; 170 | case VIEW_BLOOM_MAP: 171 | draw_image(bloom_map, v2(-window.width/2, -window.height/2), v2(window.width, window.height), COLOR_WHITE); 172 | break; 173 | default: break; 174 | } 175 | 176 | for (int i = 0; i < VIEW_MODE_MAX; i += 1) { 177 | if (button(view_mode_stringify(i), v2(-window.width/2+40, window.height/2-100-i*60), v2(500, 50), i == view)) { 178 | view = i; 179 | } 180 | } 181 | 182 | os_update(); 183 | gfx_update(); 184 | } 185 | 186 | return 0; 187 | } 188 | 189 | Gfx_Shader_Extension load_shader(string file_path, int cbuffer_size) { 190 | string source; 191 | 192 | bool ok = os_read_entire_file(file_path, &source, get_heap_allocator()); 193 | assert(ok, "Could not read %s", file_path); 194 | 195 | Gfx_Shader_Extension shader; 196 | ok = gfx_compile_shader_extension(source, cbuffer_size, &shader); 197 | assert(ok, "Failed compiling shader extension"); 198 | 199 | return shader; 200 | } 201 | 202 | void draw_game(Draw_Frame *frame) { 203 | // Draw a background 204 | draw_rect_in_frame(v2(-window.width/2, -window.height/2), v2(window.width, window.height), v4(.2, .2, .2, 1), frame); 205 | 206 | // Draw some random things, with same seed each time so it looks like persistent things 207 | seed_for_random = 69; 208 | for (int i = 0; i < 1000; i += 1) { 209 | 210 | // Bias towards dark colors, so the bright bloom will stand out more 211 | bool dark = get_random_float32_in_range(0, 1) < 0.9; 212 | 213 | int bright_channels = 0; 214 | 215 | if (!dark) { 216 | if (get_random_int_in_range(0, 2)) bright_channels |= (1 << 1); 217 | if (get_random_int_in_range(0, 2)) bright_channels |= (1 << 2); 218 | if (get_random_int_in_range(0, 2)) bright_channels |= (1 << 3); 219 | } 220 | 221 | draw_rect_in_frame(v2( // Random pos 222 | get_random_float32_in_range(-window.width/2, window.width/2), 223 | get_random_float32_in_range(-window.height/2, window.height/2) 224 | ), v2( // Random size 225 | get_random_float32_in_range(40, 100), 226 | get_random_float32_in_range(40, 100) 227 | ), v4( // Random color 228 | (bright_channels & (1 << 1)) ? get_random_float32_in_range(0.95, 1.0) : get_random_float32_in_range(0.0, 0.3), 229 | (bright_channels & (1 << 2)) ? get_random_float32_in_range(0.95, 1.0) : get_random_float32_in_range(0.0, 0.3), 230 | (bright_channels & (1 << 3)) ? get_random_float32_in_range(0.95, 1.0) : get_random_float32_in_range(0.0, 0.3), 231 | 1 232 | ), frame); 233 | 234 | } 235 | } 236 | 237 | bool button(string label, Vector2 pos, Vector2 size, bool enabled) { 238 | 239 | Vector4 color = v4(.45, .45, .45, 1); 240 | 241 | float L = pos.x; 242 | float R = L + size.x; 243 | float B = pos.y; 244 | float T = B + size.y; 245 | 246 | float mx = input_frame.mouse_x - window.width/2; 247 | float my = input_frame.mouse_y - window.height/2; 248 | 249 | bool pressed = false; 250 | 251 | if (mx >= L && mx < R && my >= B && my < T) { 252 | color = v4(.15, .15, .15, 1); 253 | if (is_key_down(MOUSE_BUTTON_LEFT)) { 254 | color = v4(.05, .05, .05, 1); 255 | } 256 | 257 | pressed = is_key_just_released(MOUSE_BUTTON_LEFT); 258 | } 259 | 260 | if (enabled) { 261 | color = v4_sub(color, v4(.2, .2, .2, 0)); 262 | } 263 | 264 | draw_rect(pos, size, color); 265 | 266 | Gfx_Text_Metrics m = measure_text(font, label, font_height, v2(1, 1)); 267 | 268 | Vector2 bottom_left = v2_sub(pos, m.functional_pos_min); 269 | bottom_left.x += size.x/2; 270 | bottom_left.x -= m.functional_size.x/2; 271 | 272 | bottom_left.y += size.y/2; 273 | bottom_left.y -= m.functional_size.y/2; 274 | 275 | draw_text(font, label, font_height, bottom_left, v2(1, 1), COLOR_WHITE); 276 | 277 | return pressed; 278 | } -------------------------------------------------------------------------------- /oogabooga/examples/bloom.hlsl: -------------------------------------------------------------------------------- 1 | 2 | // This is copypasted in bloom.hlsl and bloom_map.hlsl. 3 | // You can do #include in hlsl shaders, but I wanted this example to be very simple to look at. 4 | cbuffer some_cbuffer : register(b0) { 5 | float2 mouse_pos_screen; // In pixels 6 | float2 window_size; 7 | } 8 | 9 | Texture2D bloom_map: register(t0); // 0 because we bound to 0 in bloom.c 10 | 11 | /* 12 | Samplers: 13 | 14 | image_sampler_0 // near POINT, far POINT 15 | image_sampler_1 // near LINEAR, far LINEAR 16 | image_sampler_2 // near POINT, far LINEAR 17 | image_sampler_3 // near LINEAR, far POINT 18 | 19 | 20 | This is a oogabooga quirk at the moment. May get a better API at some point. 21 | 22 | */ 23 | 24 | float4 pixel_shader_extension(PS_INPUT input, float4 color) { 25 | const int BLUR_SAMPLE_RADIUS_PIXELS = 5; 26 | const float SAMPLES_PER_PIXEL = 1.0; 27 | const int BLUR_SAMPLE_RADIUS = BLUR_SAMPLE_RADIUS_PIXELS*SAMPLES_PER_PIXEL; 28 | 29 | // This makes d3d compiler very slow because dynamic length of loops. Ideally you would bake the window size into the shader source as a constant and recompile each time the window resized 30 | const float2 BLUR_OFFSET = float2(1.0 / window_size.x, 1.0/window_size.y) * 1.0/SAMPLES_PER_PIXEL; 31 | 32 | float4 total_bloom = float4(0, 0, 0, 0); 33 | 34 | float total_weight = 0.0; 35 | float radius_sq = float(BLUR_SAMPLE_RADIUS * BLUR_SAMPLE_RADIUS); 36 | 37 | for(int x = -BLUR_SAMPLE_RADIUS; x <= BLUR_SAMPLE_RADIUS; x++) { 38 | for(int y = -BLUR_SAMPLE_RADIUS; y <= BLUR_SAMPLE_RADIUS; y++) { 39 | float dist = (float)(x*x + y*y); 40 | 41 | if (dist < radius_sq) { 42 | float2 sample_offset = float2(x, y) * BLUR_OFFSET; 43 | float4 bloom = bloom_map.Sample(image_sampler_1, input.uv + sample_offset); // See comment on top of file for explanation of image_sampler_1 44 | total_bloom += bloom; 45 | total_weight += 1.0; 46 | } 47 | 48 | } 49 | } 50 | 51 | total_bloom /= total_weight; 52 | 53 | return color + total_bloom; 54 | } -------------------------------------------------------------------------------- /oogabooga/examples/bloom_light.hlsl: -------------------------------------------------------------------------------- 1 | // This is copypasted in bloom.hlsl and bloom_map.hlsl. 2 | // You can do #include in hlsl shaders, but I wanted this example to be very simple to look at. 3 | cbuffer some_cbuffer : register(b0) { 4 | float2 mouse_pos_screen; // In pixels 5 | float2 window_size; 6 | } 7 | float4 get_light_contribution(PS_INPUT input) { 8 | 9 | const float light_distance = 500; // We could pass this with userdata 10 | const float intensity = 0.75; 11 | float2 vertex_pos = input.position_screen.xy; // In pixels 12 | vertex_pos.y = window_size.y-vertex_pos.y; // For some reason d3d11 inverts the Y here so we need to revert it 13 | 14 | // Simple linear attenuation based on distance 15 | float attenuation = ((1.0 - (length(mouse_pos_screen - vertex_pos) / light_distance)))*intensity; 16 | 17 | return float4(attenuation, attenuation, attenuation, 1.0); 18 | } 19 | 20 | float4 pixel_shader_extension(PS_INPUT input, float4 color) { 21 | return color + get_light_contribution(input); 22 | } -------------------------------------------------------------------------------- /oogabooga/examples/bloom_map.hlsl: -------------------------------------------------------------------------------- 1 | 2 | // This is copypasted in bloom.hlsl and bloom_map.hlsl. 3 | // You can do #include in hlsl shaders, but I wanted this example to be very simple to look at. 4 | cbuffer some_cbuffer : register(b0) { 5 | float2 mouse_pos_screen; // In pixels 6 | float2 window_size; 7 | } 8 | float4 get_light_contribution(PS_INPUT input) { 9 | 10 | const float light_distance = 500; // We could pass this with userdata 11 | const float intensity = 0.75; 12 | float2 vertex_pos = input.position_screen.xy; // In pixels 13 | vertex_pos.y = window_size.y-vertex_pos.y; // For some reason d3d11 inverts the Y here so we need to revert it 14 | 15 | // Simple linear attenuation based on distance 16 | float attenuation = ((1.0 - (length(mouse_pos_screen - vertex_pos) / light_distance)))*intensity; 17 | 18 | return float4(attenuation, attenuation, attenuation, 1.0); 19 | } 20 | 21 | float4 pixel_shader_extension(PS_INPUT input, float4 color) { 22 | // We want to output everything above 1.0 23 | 24 | color = color + get_light_contribution(input); 25 | 26 | return float4( 27 | max(color.r-1.0, 0), 28 | max(color.g-1.0, 0), 29 | max(color.b-1.0, 0), 30 | color.a 31 | ); 32 | } -------------------------------------------------------------------------------- /oogabooga/examples/bruh.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/bruh.wav -------------------------------------------------------------------------------- /oogabooga/examples/custom_logger.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This is an example showcasing how we can make a custom logger to log both to stdout and 5 | and in-game logger. 6 | 7 | We also have log levels to be able to disable/enable the respective levels. 8 | 9 | BEWARE!! 10 | This logger is not thread-safe. If multiple threads call log(), then nobody knows 11 | what might happen. If you need to make it thread-safe, check out concurrency.c. 12 | 13 | */ 14 | 15 | // start all log levels enabled 16 | bool log_level_enabled_flags[LOG_LEVEL_COUNT] = {1, 1, 1, 1}; 17 | 18 | // We delete old messages when this overflows 19 | #define MAX_LOG_MESSAGES 50 20 | typedef struct Log_Message { 21 | string message; 22 | Log_Level level; 23 | } Log_Message; 24 | Log_Message log_messages[MAX_LOG_MESSAGES]; 25 | s64 num_log_messages = 0; 26 | 27 | string get_log_level_name(Log_Level level) { 28 | switch (level) { 29 | case LOG_VERBOSE: return STR("Verbose"); 30 | case LOG_INFO: return STR("Info"); 31 | case LOG_WARNING: return STR("Warning"); 32 | case LOG_ERROR: return STR("Error"); 33 | default: return STR(""); 34 | } 35 | } 36 | 37 | void my_logger(Log_Level level, string s) { 38 | 39 | 40 | string prefix = STR("[INVALID LOG LEVEL]"); 41 | if (level >= 0 && level < LOG_LEVEL_COUNT) { 42 | // if log level is disabled, we just leave 43 | if (!log_level_enabled_flags[level]) { 44 | return; 45 | } 46 | 47 | prefix = tprint("[%s]", get_log_level_name(level)); 48 | } 49 | 50 | 51 | // Format the final string. 52 | // Since we will be storing it in log_message, we need to use "sprint" rather than 53 | // "tprint" because it tprint uses the temp allocator which gets reset each frame. 54 | // In a real world scenario we would probably have a dedicated allocator for these 55 | // strings rather than heap allocating all of them. 56 | string message = sprint(get_heap_allocator(), "%s %s\n", prefix, s); 57 | 58 | // Output the final string to stdout 59 | print(message); 60 | 61 | 62 | // Also add to in-game log messages 63 | Log_Message msg = (Log_Message){message, level}; 64 | 65 | if (num_log_messages < MAX_LOG_MESSAGES) { 66 | log_messages[num_log_messages] = msg; 67 | num_log_messages += 1; 68 | } else { 69 | // Shift memory down by one to make space for the next message and deleting the first. 70 | memcpy(log_messages, &log_messages[1], sizeof(log_messages)-sizeof(Log_Message)); 71 | log_messages[num_log_messages-1] = msg; 72 | } 73 | } 74 | 75 | #define FONT_HEIGHT 38 76 | Gfx_Font *font; 77 | 78 | void draw_log(float x, float y); 79 | 80 | int entry(int argc, char **argv) { 81 | 82 | // Window setup 83 | window.title = STR("Minimal Game Example"); 84 | window.point_width = 1280; 85 | window.point_height = 720; 86 | window.x = 200; 87 | window.y = 200; 88 | window.clear_color = hex_to_rgba(0x6495EDff); 89 | 90 | // Load a font to draw the logs with 91 | font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 92 | assert(font, "Failed loading arial.ttf, %d", GetLastError()); 93 | 94 | // This is where we set the logger we want all logs to go through 95 | context.logger = my_logger; 96 | 97 | while (!window.should_close) { 98 | reset_temporary_storage(); 99 | 100 | os_update(); 101 | gfx_update(); 102 | 103 | // pixel-aligned projection 104 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 105 | 106 | float x = -window.width/2+60; 107 | float y = window.height/2-FONT_HEIGHT/2-30; 108 | 109 | draw_text(font, STR("Left-click to toggle, right-click to send log message"), FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK); 110 | draw_text(font, STR("Left-click to toggle, right-click to send log message"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE); 111 | 112 | y -= FONT_HEIGHT*1.3; 113 | 114 | // Loop through all levels to draw their state and act on input 115 | for (Log_Level level = 0; level < LOG_LEVEL_COUNT; level += 1) { 116 | bool enabled = log_level_enabled_flags[level]; 117 | string s = tprint("%s: %s", get_log_level_name(level), enabled ? STR("on") : STR("off")); 118 | Gfx_Text_Metrics m = measure_text(font, s, FONT_HEIGHT, v2(1, 1)); 119 | 120 | Vector4 color = COLOR_WHITE; 121 | 122 | Vector2 bottom_left = v2_sub(v2(x, y), m.functional_pos_min); 123 | 124 | float L = bottom_left.x; 125 | float R = L + m.visual_size.x; 126 | float B = bottom_left.y; 127 | float T = B + m.visual_size.y; 128 | 129 | float mx = input_frame.mouse_x - window.width/2; 130 | float my = input_frame.mouse_y - window.height/2; 131 | 132 | bool hovered = mx >= L && mx < R && my >= B && my < T; 133 | if (hovered) color = v4(.8, .8, .8, 1.0); 134 | if (hovered && (is_key_down(MOUSE_BUTTON_LEFT) || is_key_down(MOUSE_BUTTON_RIGHT))) 135 | color = v4(.6, .6, .6, 1.0); 136 | 137 | if (hovered && is_key_just_released(MOUSE_BUTTON_LEFT)) 138 | log_level_enabled_flags[level] = !log_level_enabled_flags[level]; 139 | if (hovered && is_key_just_released(MOUSE_BUTTON_RIGHT)) { 140 | if (level == LOG_VERBOSE) log_verbose("This is a log message"); 141 | if (level == LOG_INFO) log_info("This is a log message"); 142 | if (level == LOG_WARNING) log_warning("This is a log message"); 143 | if (level == LOG_ERROR) log_error("This is a log message"); 144 | } 145 | 146 | draw_rect(v2_sub(bottom_left, v2(8, 8)), v2_add(m.functional_size, v2(16, 16)), v4_mul(v4(.3, .3, .3, 1), color)); 147 | draw_text(font, s, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK); 148 | draw_text(font, s, FONT_HEIGHT, v2(x, y), v2(1, 1), color); 149 | 150 | y -= FONT_HEIGHT*1.3; 151 | } 152 | 153 | y -= FONT_HEIGHT*1.3; 154 | 155 | draw_log(x, y); 156 | } 157 | 158 | return 0; 159 | } 160 | 161 | void draw_log(float x, float y) { 162 | 163 | 164 | draw_text(font, STR("In-game log:"), FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK); 165 | Gfx_Text_Metrics m = draw_text_and_measure(font, STR("In-game log:"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE); 166 | 167 | y -= m.functional_size.y+20; 168 | 169 | // Here we draw each entry in the log that we can fit on screen, starting from the top 170 | // so we see the last log message first. 171 | for (s64 i = num_log_messages-1; i >= 0; i--) { 172 | 173 | Log_Level level = log_messages[i].level; 174 | 175 | if (level >= 0 && level < LOG_LEVEL_COUNT && !log_level_enabled_flags[level]) { 176 | // If it's disabled, skip it 177 | continue; 178 | } 179 | 180 | // Set color reflecting log level 181 | Vector4 color = COLOR_WHITE; 182 | if (level == LOG_VERBOSE) color = v4(.6, .6, 1, 1); 183 | else if (level == LOG_INFO) color = v4(.3, 1, .4, 1); 184 | else if (level == LOG_WARNING) color = v4(.8, .8, 1, 1); 185 | else if (level == LOG_ERROR) color = v4(1, .2, .2, 1); 186 | 187 | draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK); 188 | draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x, y), v2(1, 1), color); 189 | 190 | y -= FONT_HEIGHT * 1.3; 191 | 192 | if (y+FONT_HEIGHT < -window.height/2) break; // Occlude text outside of view 193 | } 194 | } -------------------------------------------------------------------------------- /oogabooga/examples/custom_shader.c: -------------------------------------------------------------------------------- 1 | 2 | // BEWARE std140 packing: 3 | // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules 4 | typedef struct My_Cbuffer { 5 | Vector2 mouse_pos_screen; // We use this to make a light around the mouse cursor 6 | Vector2 window_size; // We only use this to revert the Y in the shader because for some reason d3d11 inverts it. 7 | } My_Cbuffer; 8 | 9 | 10 | // We implement these details which we implement in the shader 11 | #define DETAIL_TYPE_ROUNDED_CORNERS 1 12 | #define DETAIL_TYPE_OUTLINED 2 13 | #define DETAIL_TYPE_OUTLINED_CIRCLE 3 14 | 15 | // With custom shading we can extend the rendering library! 16 | Draw_Quad *draw_rounded_rect(Vector2 p, Vector2 size, Vector4 color, float radius); 17 | Draw_Quad *draw_rounded_rect_xform(Matrix4 xform, Vector2 size, Vector4 color, float radius); 18 | Draw_Quad *draw_outlined_rect(Vector2 p, Vector2 size, Vector4 color, float line_width_pixels); 19 | Draw_Quad *draw_outlined_rect_xform(Matrix4 xform, Vector2 size, Vector4 color, float line_width_pixels); 20 | Draw_Quad *draw_outlined_circle(Vector2 p, Vector2 size, Vector4 color, float line_width_pixels); 21 | Draw_Quad *draw_outlined_circle_xform(Matrix4 xform, Vector2 size, Vector4 color, float line_width_pixels); 22 | 23 | int entry(int argc, char **argv) { 24 | 25 | window.title = STR("Custom Shader Example"); 26 | window.point_width = 1280; 27 | window.point_height = 720; 28 | window.x = 200; 29 | window.y = 90; 30 | window.clear_color = hex_to_rgba(0x6495EDff); 31 | 32 | string source; 33 | bool ok = os_read_entire_file("oogabooga/examples/custom_shader.hlsl", &source, get_heap_allocator()); 34 | assert(ok, "Could not read oogabooga/examples/custom_shader.hlsl"); 35 | 36 | Gfx_Shader_Extension shader; 37 | // This is slow and needs to recompile the shader. However, it should probably only happen once (or each hot reload) 38 | // If it fails, it will return false and return to whatever shader it was before. 39 | ok = gfx_compile_shader_extension(source, sizeof(My_Cbuffer), &shader); 40 | assert(ok, "Failed compiling shader extension"); 41 | 42 | dealloc_string(get_heap_allocator(), source); 43 | 44 | // This memory needs to stay alive throughout the frame because we pass the pointer to it in draw_frame.cbuffer. 45 | // If this memory is invalidated before gfx_update after setting draw_frame.cbuffer, then gfx_update will copy 46 | // memory from an invalid address. 47 | My_Cbuffer cbuffer; 48 | 49 | float64 last_time = os_get_elapsed_seconds(); 50 | while (!window.should_close) { 51 | 52 | float64 now = os_get_elapsed_seconds(); 53 | if ((int)now != (int)last_time) { 54 | log("%.2f FPS\n%.2fms", 1.0/(now-last_time), (now-last_time)*1000); 55 | } 56 | last_time = now; 57 | 58 | reset_temporary_storage(); 59 | 60 | float32 aspect = (float32)window.width/(float32)window.height; 61 | 62 | draw_frame.projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10); 63 | 64 | // The shader is used when rendering, which happens in gfx_update() for everything drawn to the Draw_Frame. 65 | draw_frame.shader_extension = shader; 66 | 67 | cbuffer.mouse_pos_screen = v2(input_frame.mouse_x, input_frame.mouse_y); 68 | cbuffer.window_size = v2(window.width, window.height); 69 | draw_frame.cbuffer = &cbuffer; 70 | 71 | // Just draw a big rect to cover background, so our lighting shader will apply to background 72 | draw_rect(v2(-5, -5), v2(10, 10), v4(.4, .4, .4, 1.0)); 73 | 74 | Matrix4 rect_xform = m4_scalar(1.0); 75 | rect_xform = m4_rotate_z(rect_xform, (f32)now); 76 | rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0)); 77 | Draw_Quad *q = draw_rounded_rect_xform(rect_xform, v2(.5f, .5f), COLOR_GREEN, 0.1); 78 | 79 | draw_outlined_rect(v2(sin(now), -.8), v2(.5, .25), COLOR_RED, 2); 80 | 81 | draw_outlined_circle(v2(-sin(now), -.8), v2(.6, .6), COLOR_BLUE, 2); 82 | 83 | 84 | // Shader hot reloading 85 | if (is_key_just_pressed('R')) { 86 | ok = os_read_entire_file("oogabooga/examples/custom_shader.hlsl", &source, get_heap_allocator()); 87 | assert(ok, "Could not read oogabooga/examples/custom_shader.hlsl"); 88 | Gfx_Shader_Extension old_shader = shader; // Store previous shader in case this one fails to compile 89 | ok = gfx_compile_shader_extension(source, sizeof(My_Cbuffer), &shader); 90 | if (!ok) shader = old_shader; 91 | else gfx_destroy_shader_extension(old_shader); 92 | dealloc_string(get_heap_allocator(), source); 93 | } 94 | 95 | os_update(); 96 | gfx_update(); 97 | } 98 | 99 | return 0; 100 | } 101 | 102 | Vector2 world_to_screen(Vector2 p) { 103 | Vector4 in_cam_space = m4_transform(draw_frame.camera_xform, v4(p.x, p.y, 0.0, 1.0)); 104 | Vector4 in_clip_space = m4_transform(draw_frame.projection, in_cam_space); 105 | 106 | Vector4 ndc = { 107 | .x = in_clip_space.x / in_clip_space.w, 108 | .y = in_clip_space.y / in_clip_space.w, 109 | .z = in_clip_space.z / in_clip_space.w, 110 | .w = in_clip_space.w 111 | }; 112 | 113 | return v2( 114 | (ndc.x + 1.0f) * 0.5f * (f32)window.width, 115 | (ndc.y + 1.0f) * 0.5f * (f32)window.height 116 | ); 117 | } 118 | Vector2 world_size_to_screen_size(Vector2 s) { 119 | Vector2 origin = v2(0, 0); 120 | 121 | Vector2 screen_origin = world_to_screen(origin); 122 | Vector2 screen_size_point = world_to_screen(s); 123 | 124 | return v2( 125 | screen_size_point.x - screen_origin.x, 126 | screen_size_point.y - screen_origin.y 127 | ); 128 | } 129 | 130 | Draw_Quad *draw_rounded_rect(Vector2 p, Vector2 size, Vector4 color, float radius) { 131 | Draw_Quad *q = draw_rect(p, size, color); 132 | // detail_type 133 | q->userdata[0].x = DETAIL_TYPE_ROUNDED_CORNERS; 134 | // corner_radius 135 | q->userdata[0].y = radius; 136 | return q; 137 | } 138 | Draw_Quad *draw_rounded_rect_xform(Matrix4 xform, Vector2 size, Vector4 color, float radius) { 139 | Draw_Quad *q = draw_rect_xform(xform, size, color); 140 | // detail_type 141 | q->userdata[0].x = DETAIL_TYPE_ROUNDED_CORNERS; 142 | // corner_radius 143 | q->userdata[0].y = radius; 144 | return q; 145 | } 146 | Draw_Quad *draw_outlined_rect(Vector2 p, Vector2 size, Vector4 color, float line_width_pixels) { 147 | Draw_Quad *q = draw_rect(p, size, color); 148 | // detail_type 149 | q->userdata[0].x = DETAIL_TYPE_OUTLINED; 150 | // line_width_pixels 151 | q->userdata[0].y = line_width_pixels; 152 | // rect_size 153 | q->userdata[0].zw = world_size_to_screen_size(size); 154 | return q; 155 | } 156 | Draw_Quad *draw_outlined_rect_xform(Matrix4 xform, Vector2 size, Vector4 color, float line_width_pixels) { 157 | Draw_Quad *q = draw_rect_xform(xform, size, color); 158 | // detail_type 159 | q->userdata[0].x = DETAIL_TYPE_OUTLINED; 160 | // line_width_pixels 161 | q->userdata[0].y = line_width_pixels; 162 | // rect_size 163 | q->userdata[0].zw = world_size_to_screen_size(size); 164 | return q; 165 | } 166 | Draw_Quad *draw_outlined_circle(Vector2 p, Vector2 size, Vector4 color, float line_width_pixels) { 167 | Draw_Quad *q = draw_rect(p, size, color); 168 | // detail_type 169 | q->userdata[0].x = DETAIL_TYPE_OUTLINED_CIRCLE; 170 | // line_width_pixels 171 | q->userdata[0].y = line_width_pixels; 172 | // rect_size_pixels 173 | q->userdata[0].zw = world_size_to_screen_size(size); // Transform world space to screen space 174 | return q; 175 | } 176 | Draw_Quad *draw_outlined_circle_xform(Matrix4 xform, Vector2 size, Vector4 color, float line_width_pixels) { 177 | Draw_Quad *q = draw_rect_xform(xform, size, color); 178 | // detail_type 179 | q->userdata[0].x = DETAIL_TYPE_OUTLINED_CIRCLE; 180 | // line_width_pixels 181 | q->userdata[0].y = line_width_pixels; 182 | // rect_size_pixels 183 | q->userdata[0].zw = world_size_to_screen_size(size); // Transform world space to screen space 184 | 185 | return q; 186 | } -------------------------------------------------------------------------------- /oogabooga/examples/custom_shader.hlsl: -------------------------------------------------------------------------------- 1 | 2 | // PS_INPUT is defined in the default shader in gfx_impl_d3d11.c at the bottom of the file 3 | 4 | // BEWARE std140 packing: 5 | // https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules 6 | cbuffer some_cbuffer : register(b0) { 7 | float2 mouse_pos_screen; // In pixels 8 | float2 window_size; 9 | } 10 | 11 | #define DETAIL_TYPE_ROUNDED_CORNERS 1 12 | #define DETAIL_TYPE_OUTLINED 2 13 | #define DETAIL_TYPE_OUTLINED_CIRCLE 3 14 | 15 | float4 get_light_contribution(PS_INPUT input) { 16 | 17 | const float light_distance = 500; // We could pass this with userdata 18 | 19 | float2 vertex_pos = input.position_screen.xy; // In pixels 20 | vertex_pos.y = window_size.y-vertex_pos.y; // For some reason d3d11 inverts the Y here so we need to revert it 21 | 22 | // Simple linear attenuation based on distance 23 | float attenuation = 1.0 - (length(mouse_pos_screen - vertex_pos) / light_distance); 24 | 25 | return float4(attenuation, attenuation, attenuation, 1.0); 26 | } 27 | 28 | // This procedure is the "entry" of our extension to the shader 29 | // It basically just takes in the resulting color and input from vertex shader, for us to transform it 30 | // however we want. 31 | float4 pixel_shader_extension(PS_INPUT input, float4 color) { 32 | float detail_type = input.userdata[0].x; 33 | 34 | // Assumes rect with 90deg corners 35 | float2 rect_size_pixels = input.userdata[0].zw; 36 | 37 | if (detail_type == DETAIL_TYPE_ROUNDED_CORNERS) { 38 | float corner_radius = input.userdata[0].y; 39 | 40 | float2 pos = input.self_uv - float2(0.5, 0.5); 41 | 42 | float2 corner_distance = abs(pos) - (float2(0.5, 0.5) - corner_radius); 43 | 44 | float dist = length(max(corner_distance, 0.0)) - corner_radius; 45 | float smoothing = 0.01; 46 | float mask = 1.0-smoothstep(0.0, smoothing, dist); 47 | 48 | color *= mask; 49 | } else if (detail_type == DETAIL_TYPE_OUTLINED) { 50 | float line_width_pixels = input.userdata[0].y; 51 | 52 | float2 pixel_pos = round(input.self_uv*rect_size_pixels); 53 | 54 | float xcenter = rect_size_pixels.x/2; 55 | float ycenter = rect_size_pixels.y/2; 56 | 57 | float xedge = pixel_pos.x < xcenter ? 0.0 : rect_size_pixels.x; 58 | float yedge = pixel_pos.y < ycenter ? 0.0 : rect_size_pixels.y; 59 | 60 | float xdist = abs(xedge-pixel_pos.x); 61 | float ydist = abs(yedge-pixel_pos.y); 62 | 63 | if (xdist > line_width_pixels && ydist > line_width_pixels) { 64 | discard; 65 | } 66 | } else if (detail_type == DETAIL_TYPE_OUTLINED_CIRCLE) { 67 | float line_width_pixels = input.userdata[0].y; 68 | float2 rect_size_pixels = input.userdata[0].zw; 69 | float line_width_uv = line_width_pixels / min(rect_size_pixels.x, rect_size_pixels.y); 70 | 71 | // For some simple anti-aliasing, we add a little bit of padding around the outline 72 | // and fade that padding outwards with a smooth curve towards 0. 73 | // Very arbitrary smooth equation that I got from just testing different sizes of the circle. 74 | // It's kinda meh. 75 | float smooth = ((4.0/line_width_pixels)*6.0)/window_size.x; 76 | 77 | float2 center = float2(0.5, 0.5); 78 | float dist = length(input.self_uv - center); 79 | 80 | float mask; 81 | if (dist > 0.5-smooth) { 82 | mask = 1.0-lerp(0, 1.0, max(dist-0.5+smooth, 0.0)/smooth); 83 | } else if (dist < 0.5-line_width_uv+smooth) { 84 | mask = smoothstep(0, 1.0, max(dist-0.5+line_width_uv+smooth, 0.0)/smooth); 85 | } 86 | if (mask <= 0) discard; 87 | color *= mask; 88 | } 89 | 90 | float4 light = get_light_contribution(input); 91 | 92 | return color * light; 93 | } -------------------------------------------------------------------------------- /oogabooga/examples/growing_array_example.c: -------------------------------------------------------------------------------- 1 | 2 | typedef struct Circle { 3 | Vector2 pos; 4 | float radius; 5 | } Circle; 6 | 7 | int entry(int argc, char **argv) { 8 | 9 | //window.enable_vsync = true; 10 | 11 | window.title = STR("Minimal Game Example"); 12 | window.point_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI) 13 | window.point_height = 720; 14 | window.x = 200; 15 | window.y = 90; 16 | window.clear_color = hex_to_rgba(0x6495EDff); 17 | 18 | Circle *circles; 19 | growing_array_init((void**)&circles, sizeof(Circle), get_heap_allocator()); 20 | 21 | const int num_circles = 10000; 22 | const float radius_min = 8.0; 23 | const float radius_max = 32.0; 24 | 25 | const float hover_radius = 200; 26 | 27 | os_update(); // We set scaled window size, os_update updates the pixel window size values for us 28 | 29 | for (int i = 0; i < num_circles; i++) { 30 | float32 r = get_random_float32_in_range(radius_min, radius_max); 31 | Vector2 p = v2( 32 | get_random_float32_in_range(-(f32)window.width/2.0+r, (f32)window.width/2.0-r), 33 | get_random_float32_in_range(-(f32)window.height/2.0+r, (f32)window.height/2.0-r) 34 | ); 35 | // &(Circle){p, r} will only compile in true C, not in a C++ compiler 36 | growing_array_add((void**)&circles, &(Circle){p, r}); 37 | } 38 | 39 | float64 last_time = os_get_elapsed_seconds(); 40 | while (!window.should_close) { 41 | float64 now = os_get_elapsed_seconds(); 42 | if ((int)now != (int)last_time) log("%.2f FPS\n%.2fms", 1.0/(now-last_time), (now-last_time)*1000); 43 | last_time = now; 44 | 45 | reset_temporary_storage(); 46 | 47 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 48 | 49 | 50 | float mx = input_frame.mouse_x - window.width/2; 51 | float my = input_frame.mouse_y - window.height/2; 52 | 53 | // We build an array on the fly each frame for all the hovered circles 54 | Circle *circles_hovered; 55 | growing_array_init_reserve((void**)&circles_hovered, sizeof(Circle), num_circles, get_temporary_allocator()); 56 | for (int i = 0; i < num_circles; i++) { 57 | float distance = v2_length(v2_sub(v2(mx, my), circles[i].pos)); 58 | if (distance <= hover_radius) { 59 | growing_array_add((void**)&circles_hovered, &circles[i]); 60 | } 61 | } 62 | 63 | 64 | 65 | for (int i = 0; i < growing_array_get_valid_count(circles_hovered); i++) { 66 | Circle c = circles_hovered[i]; 67 | draw_circle(v2_sub(c.pos, v2(c.radius, c.radius)), v2(c.radius, c.radius), COLOR_GREEN); 68 | } 69 | 70 | os_update(); 71 | gfx_update(); 72 | } 73 | 74 | return 0; 75 | } -------------------------------------------------------------------------------- /oogabooga/examples/hammer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/hammer.png -------------------------------------------------------------------------------- /oogabooga/examples/hotload/README.md: -------------------------------------------------------------------------------- 1 | # !!! THIS VERY EXPERIMENTAL !!! 2 | 3 | 4 | This is an example how how we can set up a oogabooga project where some code can be recompiled while the engine is still running. 5 | 6 | To try this: 7 | 1. Copy the files in this directory into the root project directory 8 | 2. Compile with `hotload_build_all.bat` 9 | 3. Run `build/launcher.exe` 10 | 4. Modify `build_game.c` 11 | 5. Recompile the game code only with `hotload_build_game.bat` 12 | 6. Go back to the application and press 'R' -------------------------------------------------------------------------------- /oogabooga/examples/hotload/build_engine.c: -------------------------------------------------------------------------------- 1 | 2 | /// 3 | // Build config stuff 4 | 5 | #define OOGABOOGA_BUILD_SHARED_LIBRARY 1 6 | 7 | #include "oogabooga/oogabooga.c" 8 | 9 | /// 10 | /// 11 | // This is the "engine" part of your game, which will call into your game.dll 12 | 13 | 14 | typedef void (*Game_Update_Proc)(f64); 15 | Game_Update_Proc game_update; 16 | 17 | Dynamic_Library_Handle dll = 0; 18 | 19 | void load_game_dll(char **argv) { 20 | 21 | // Here we load all the game symbols 22 | 23 | if (dll) { 24 | os_unload_dynamic_library(dll); 25 | } 26 | 27 | string exe_path = STR(argv[0]); 28 | string exe_dir = get_directory_of(exe_path); 29 | 30 | // We need to copy the original and open the copy, so we can recompile the original and then close & replace 31 | // the copy. 32 | string dll_path = string_concat(exe_dir, STR("/game.dll"), get_temporary_allocator()); 33 | string used_dll_path = string_concat(exe_dir, STR("/game-in-use.dll"), get_temporary_allocator()); 34 | 35 | bool ok = os_file_copy(dll_path, used_dll_path, true); 36 | assert(ok, "Could not copy %s to %s", dll_path, used_dll_path); 37 | 38 | dll = os_load_dynamic_library(used_dll_path); 39 | assert(dll, "Failed loading game dll"); 40 | 41 | game_update = os_dynamic_library_load_symbol(dll, STR("game_update")); 42 | assert(game_update, "game is missing game_update()"); 43 | 44 | log("Loaded game procedures"); 45 | } 46 | 47 | int entry(int argc, char **argv) { 48 | 49 | load_game_dll(argv); 50 | 51 | window.title = STR("Minimal Game Example"); 52 | window.point_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI) 53 | window.point_height = 720; 54 | window.x = 200; 55 | window.y = 90; 56 | window.clear_color = hex_to_rgba(0x6495EDff); 57 | 58 | float64 last_time = os_get_elapsed_seconds(); 59 | while (!window.should_close) { 60 | float64 now = os_get_elapsed_seconds(); 61 | float64 delta = now-last_time; 62 | if ((int)now != (int)last_time) log("%.2f FPS\n%.2fms", 1.0/(delta), (delta)*1000); 63 | last_time = now; 64 | 65 | reset_temporary_storage(); 66 | 67 | game_update(delta); 68 | 69 | if (is_key_just_pressed('R')) { 70 | load_game_dll(argv); 71 | play_one_audio_clip(STR("oogabooga/examples/bruh.wav")); 72 | } 73 | 74 | os_update(); 75 | gfx_update(); 76 | } 77 | 78 | return 0; 79 | } -------------------------------------------------------------------------------- /oogabooga/examples/hotload/build_game.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | // !!!!!!!! BUILD CONFIG SHOULD BE DONE IN build_engine.c 4 | 5 | #define OOGABOOGA_LINK_EXTERNAL_INSTANCE 1 6 | #include "oogabooga/oogabooga.c" 7 | 8 | /// 9 | /// 10 | // This is the game module which is what can be recompiled in the engine runtime 11 | 12 | // For the engine to be able to detect a symbol, it needs to be marked with SHARED_EXPORT 13 | void SHARED_EXPORT 14 | game_update(f64 delta_time) { 15 | 16 | float64 now = os_get_elapsed_seconds(); 17 | 18 | Matrix4 rect_xform = m4_scalar(1.0); 19 | rect_xform = m4_rotate_z(rect_xform, (f32)now); 20 | rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0)); 21 | draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_GREEN); 22 | 23 | draw_rect(v2(sin(now), -.8), v2(.5, .25), COLOR_RED); 24 | 25 | float aspect = (f32)window.width/(f32)window.height; 26 | float mx = (input_frame.mouse_x/(f32)window.width * 2.0 - 1.0)*aspect; 27 | float my = input_frame.mouse_y/(f32)window.height * 2.0 - 1.0; 28 | 29 | draw_line(v2(-.75, -.75), v2(mx, my), 0.005, COLOR_WHITE); 30 | } -------------------------------------------------------------------------------- /oogabooga/examples/hotload/build_launcher.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | // !!!!!!!! BUILD CONFIG SHOULD BE DONE IN build_engine.c 4 | 5 | #define OOGABOOGA_LINK_EXTERNAL_INSTANCE 1 6 | #include "oogabooga/oogabooga.c" 7 | 8 | 9 | // All we do with the launcher is to launch the engine 10 | // We need to be careful to use oogabooga things because it has not yet been initialized. 11 | // We can use get_temporary_allocator() because that actually gives us the initialization allocator. 12 | // We cannot use log() but we can use print() 13 | int main(int argc, char **argv) { 14 | string exe_path = STR(argv[0]); 15 | string exe_dir = get_directory_of(exe_path); 16 | 17 | Allocator a = get_initialization_allocator(); 18 | string dll_path = string_concat(exe_dir, STR("/engine.dll"), a); 19 | 20 | Dynamic_Library_Handle dll = os_load_dynamic_library(dll_path); 21 | if (!dll) { 22 | os_write_string_to_stdout(STR("Failed loading engine dll from ")); 23 | os_write_string_to_stdout(dll_path); 24 | os_write_string_to_stdout(STR("\n")); 25 | return -1; 26 | } 27 | 28 | int (*engine_main)(int, char**) = os_dynamic_library_load_symbol(dll, STR("main")); 29 | if (!engine_main) { 30 | os_write_string_to_stdout(STR("Failed loading engine main\n")); 31 | return -1; 32 | } 33 | 34 | print("Launcher found engine main(), running...\n"); 35 | return engine_main(argc, argv); 36 | } -------------------------------------------------------------------------------- /oogabooga/examples/hotload/hotload_build_all.bat: -------------------------------------------------------------------------------- 1 | @echo on 2 | if exist build ( 3 | rmdir /s /q build 4 | ) 5 | mkdir build 6 | 7 | pushd build 8 | 9 | clang ../build_engine.c -g -shared -o engine.dll -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -fuse-ld=lld -lkernel32 -lgdi32 -luser32 -lruntimeobject -lwinmm -ld3d11 -ldxguid -ld3dcompiler -lshlwapi -lole32 -lavrt -lksuser -ldbghelp -femit-all-decls -Xlinker /IMPLIB:engine.lib -Xlinker /MACHINE:X64 -Xlinker /SUBSYSTEM:CONSOLE 10 | 11 | clang ../build_launcher.c -g -o launcher.exe -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -femit-all-decls -luser32 -fuse-ld=lld -L. -lengine -Xlinker /SUBSYSTEM:CONSOLE 12 | 13 | clang ../build_game.c -g -shared -o game.dll -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -femit-all-decls -luser32 -fuse-ld=lld -L. -lengine -Xlinker /SUBSYSTEM:CONSOLE 14 | 15 | 16 | popd -------------------------------------------------------------------------------- /oogabooga/examples/hotload/hotload_build_game.bat: -------------------------------------------------------------------------------- 1 | @echo on 2 | 3 | pushd build 4 | 5 | clang ../build_game.c -g -shared -o game.dll -O0 -std=c11 -D_CRT_SECURE_NO_WARNINGS -Wextra -Wno-incompatible-library-redeclaration -Wno-sign-compare -Wno-unused-parameter -Wno-builtin-requires-header -femit-all-decls -luser32 -fuse-ld=lld -L. -lengine -Xlinker /SUBSYSTEM:CONSOLE 6 | 7 | 8 | popd -------------------------------------------------------------------------------- /oogabooga/examples/input_example.c: -------------------------------------------------------------------------------- 1 | 2 | // This example is kinda dumb for now, I just log stuff to console. 3 | 4 | 5 | #define MAX_KEYS_PER_BINDING 3 6 | 7 | typedef enum Action { 8 | ACTION_DASH, 9 | ACTION_SHOOT, 10 | 11 | ACTION_MAX 12 | } Action; 13 | 14 | typedef struct Key_Bind { 15 | Input_Key_Code codes[MAX_KEYS_PER_BINDING]; 16 | } Key_Bind; 17 | 18 | // Index with action into key bind 19 | Key_Bind key_binds[ACTION_MAX] = {0}; 20 | 21 | bool is_action_just_pressed(Action action) { 22 | for (u64 i = 0; i < MAX_KEYS_PER_BINDING; i++) { 23 | Input_Key_Code code = key_binds[action].codes[i]; 24 | if (code == 0) continue; 25 | 26 | if (is_key_just_pressed(code)) return true; 27 | } 28 | return false; 29 | } 30 | 31 | int entry(int argc, char **argv) { 32 | 33 | window.title = STR("Input example"); 34 | window.point_width = 1280; 35 | window.point_height = 720; 36 | window.x = 200; 37 | window.y = 90; 38 | window.clear_color = hex_to_rgba(0x6495EDff); 39 | 40 | key_binds[ACTION_DASH].codes[0] = KEY_SPACEBAR; 41 | key_binds[ACTION_DASH].codes[1] = GAMEPAD_A; 42 | 43 | key_binds[ACTION_SHOOT].codes[0] = MOUSE_BUTTON_LEFT; 44 | key_binds[ACTION_SHOOT].codes[1] = GAMEPAD_RIGHT_BUMPER; 45 | 46 | float64 last_time = os_get_elapsed_seconds(); 47 | while (!window.should_close) { 48 | reset_temporary_storage(); 49 | 50 | if (is_key_just_pressed(GAMEPAD_LEFT_TRIGGER)) { 51 | log("Left trigger"); 52 | } 53 | if (is_key_just_pressed(GAMEPAD_B)) { 54 | log("B"); 55 | } 56 | 57 | if (is_action_just_pressed(ACTION_DASH)) log("DASH"); 58 | if (is_action_just_pressed(ACTION_SHOOT)) log("PEW PEW"); 59 | 60 | // Vibrate depending on how far pushed the triggers are 61 | set_gamepad_vibration(input_frame.left_trigger, input_frame.right_trigger); 62 | 63 | // Example to retrieve axes for multiple gamepads 64 | for (u64 i = 0; i < input_frame.number_of_events; i++) { 65 | Input_Event e = input_frame.events[i]; 66 | 67 | switch (e.kind) { 68 | case INPUT_EVENT_GAMEPAD_AXIS: { 69 | 70 | if (e.axes_changed & INPUT_AXIS_LEFT_STICK) log("Gamepad %d left stick: %f %f", e.gamepad_index, e.left_stick.x, e.left_stick.y); 71 | if (e.axes_changed & INPUT_AXIS_RIGHT_STICK) log("Gamepad %d right stick: %f %f", e.gamepad_index, e.right_stick.x, e.right_stick.y); 72 | if (e.axes_changed & INPUT_AXIS_LEFT_TRIGGER) log("Gamepad %d left trigger: %f", e.gamepad_index, e.left_trigger); 73 | if (e.axes_changed & INPUT_AXIS_RIGHT_TRIGGER) log("Gamepad %d right trigger: %f", e.gamepad_index, e.right_trigger); 74 | 75 | break; 76 | } 77 | default: break; 78 | } 79 | } 80 | 81 | os_update(); 82 | gfx_update(); 83 | } 84 | 85 | return 0; 86 | } -------------------------------------------------------------------------------- /oogabooga/examples/male_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/male_animation.png -------------------------------------------------------------------------------- /oogabooga/examples/minimal_game_loop.c: -------------------------------------------------------------------------------- 1 | 2 | int entry(int argc, char **argv) { 3 | 4 | // This is how we (optionally) configure the window. 5 | // To see all the settable window properties, ctrl+f "struct Os_Window" in os_interface.c 6 | window.title = STR("Minimal Game Example"); 7 | 8 | while (!window.should_close) { 9 | reset_temporary_storage(); 10 | 11 | float64 now = os_get_elapsed_seconds(); 12 | 13 | Matrix4 rect_xform = m4_scalar(1.0); 14 | rect_xform = m4_rotate_z(rect_xform, (f32)now); 15 | rect_xform = m4_translate(rect_xform, v3(-125, -125, 0)); 16 | draw_rect_xform(rect_xform, v2(250, 250), COLOR_GREEN); 17 | 18 | draw_rect(v2(sin(now)*window.width*0.4-60, -60), v2(120, 120), COLOR_RED); 19 | 20 | os_update(); 21 | gfx_update(); 22 | } 23 | 24 | return 0; 25 | } -------------------------------------------------------------------------------- /oogabooga/examples/offscreen_drawing.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | In this example we make use of a separate Draw_Frame to render it to a render target image. 5 | 6 | The global draw_frame is rendered to window and cleared each gfx_update(). 7 | 8 | So if we want more control to be able to do offscreen rendering, we need to make a separate Draw_Frame 9 | and manually reset it & gfx_clear it before we draw to it and finally render it to a render target. 10 | 11 | To showcase this, I generate 3 worlds (immediate-mode generated with a consistent base seed for random), 12 | and draw each of them to their own world image which lets us easily hop between worlds and draw the 13 | final world image of the current world to the window, as well as draw the whole world image in a minimap 14 | in the top left. 15 | The minimaps also has a red blip showing the player position. 16 | 17 | WASD - Move the player 18 | 19 | Left & Right arrow keys - hop between worlds 20 | 21 | */ 22 | 23 | typedef enum World { 24 | WORLD1, 25 | WORLD2, 26 | WORLD3, 27 | 28 | WORLD_MAX, 29 | } World; 30 | 31 | int entry(int argc, char **argv) { 32 | 33 | window.title = STR("Offscreen Rendering Example"); 34 | 35 | // Configuration of world generation 36 | u64 number_of_things_per_world = 3000; 37 | Vector2 world_min = v2(-3000, -3000); 38 | Vector2 world_max = v2( 3000, 3000); 39 | Vector2 world_size = v2(ceil(world_max.x-world_min.x), ceil(world_max.y-world_min.y)); 40 | Vector4 terrain_colors[WORLD_MAX] = { 41 | v4(.5, .5, .5, 1), 42 | COLOR_BLACK, 43 | COLOR_WHITE, 44 | }; 45 | Vector4 thing_colors[WORLD_MAX] = { 46 | v4(0, 0, 1, 1), 47 | COLOR_WHITE, 48 | COLOR_BLACK, 49 | }; 50 | 51 | // Init world images & world draw frame 52 | Gfx_Image *world_images[WORLD_MAX]; 53 | Draw_Frame world_draw_frame; 54 | 55 | for (World world = WORLD1; world < WORLD_MAX; world += 1) { 56 | world_images[world] = make_image_render_target(world_size.x, world_size.y, 4, 0, get_heap_allocator()); 57 | } 58 | 59 | draw_frame_init_reserve(&world_draw_frame, number_of_things_per_world); 60 | 61 | // Load player sprite 62 | Gfx_Image *player_sprite = load_image_from_disk(STR("oogabooga/examples/player.png"), get_heap_allocator()); 63 | assert(player_sprite, "Failed loading player sprite"); 64 | 65 | 66 | // Randomize the base seed with current system cycle count 67 | u64 base_seed = rdtsc(); 68 | 69 | Vector2 player_pos = v2(0, 0); 70 | Vector2 camera_pos = v2(0, 0); 71 | World current_world = WORLD1; 72 | 73 | float64 last_time = os_get_elapsed_seconds(); 74 | while (!window.should_close) { 75 | reset_temporary_storage(); 76 | float64 now = os_get_elapsed_seconds(); 77 | float64 delta_time = now-last_time; 78 | last_time = now; 79 | 80 | /// 81 | // Update each world 82 | // 83 | for (World world = WORLD1; world < WORLD_MAX; world += 1) { 84 | 85 | u64 seed = base_seed + world; 86 | 87 | Vector4 clear_color = terrain_colors[world]; 88 | Vector4 thing_color = thing_colors[world]; 89 | 90 | // Reset draw frame and clear the render target 91 | draw_frame_reset(&world_draw_frame); 92 | gfx_clear_render_target(world_images[world], clear_color); 93 | 94 | // We are drawing the whole world into one big texture, so the orthographic projection should be 95 | // the same size as the world. 96 | world_draw_frame.projection 97 | = m4_make_orthographic_projection(-world_size.x/2, world_size.x/2, -world_size.y/2, world_size.y/2, -1, 10); 98 | 99 | // Start at the first seed for this world (this seed advances for each get_random_xxx call you make. 100 | seed_for_random = seed; 101 | 102 | // 103 | // Draw all the things in the world to the world image 104 | for (u64 i = 0; i < number_of_things_per_world; i += 1) { 105 | 106 | Vector2 pos = v2( 107 | get_random_float32_in_range(world_min.x, world_max.x), 108 | get_random_float32_in_range(world_min.y, world_max.y) 109 | ); 110 | 111 | Vector2 size = v2( 112 | get_random_float32_in_range(16, 64), 113 | get_random_float32_in_range(16, 64) 114 | ); 115 | 116 | switch (world) { 117 | // In world 1 and 3 the things are rectangles 118 | case WORLD1: 119 | case WORLD3: 120 | draw_rect_in_frame(pos, size, thing_color, &world_draw_frame); break; 121 | 122 | // In world 2 the shapes are circles 123 | case WORLD2: 124 | draw_circle_in_frame(pos, v2(size.x, size.x), thing_color, &world_draw_frame); break; 125 | 126 | case WORLD_MAX: panic(""); 127 | } 128 | } 129 | 130 | // Since we have draw to a custom Draw_Frame (not the global one), we need to manually specify 131 | // when to render it and where to render it. Here we render it to the world image. 132 | // You can also render Draw_Frame's to the window directly with gfx_render_draw_frame_to_window() 133 | gfx_render_draw_frame(&world_draw_frame, world_images[world]); 134 | } 135 | 136 | // 137 | // Determine what the next and previous worlds are depending on the current 138 | World next_world; 139 | World previous_world; 140 | 141 | switch (current_world) { 142 | case WORLD1: { 143 | previous_world = WORLD3; 144 | next_world = WORLD2; 145 | break; 146 | } 147 | case WORLD2: { 148 | previous_world = WORLD1; 149 | next_world = WORLD3; 150 | break; 151 | } 152 | case WORLD3: { 153 | previous_world = WORLD2; 154 | next_world = WORLD1; 155 | break; 156 | } 157 | 158 | case WORLD_MAX: panic(""); 159 | } 160 | if (is_key_just_pressed(KEY_ARROW_LEFT)) current_world = previous_world; 161 | if (is_key_just_pressed(KEY_ARROW_RIGHT)) current_world = next_world; 162 | 163 | 164 | /// 165 | // Basic player and camera movement 166 | // 167 | 168 | camera_pos = v2_smerp(camera_pos, player_pos, 0.07); 169 | 170 | camera_pos.x = min(camera_pos.x, world_max.x-window.width /2.0); 171 | camera_pos.x = max(camera_pos.x, world_min.x+window.width /2.0); 172 | camera_pos.y = min(camera_pos.y, world_max.y-window.height/2.0); 173 | camera_pos.y = max(camera_pos.y, world_min.y+window.height/2.0); 174 | 175 | draw_frame.camera_xform = m4_make_translation(v3(v2_expand(camera_pos), 0.0)); 176 | 177 | // Draw the current world in the center of everything 178 | draw_image(world_images[current_world], v2(-world_size.x/2, -world_size.y/2), v2(world_size.x, world_size.y), COLOR_WHITE); 179 | 180 | Vector2 move_axes = v2(0, 0); 181 | if (is_key_down('A')) move_axes.x -= 1.0; 182 | if (is_key_down('D')) move_axes.x += 1.0; 183 | if (is_key_down('S')) move_axes.y -= 1.0; 184 | if (is_key_down('W')) move_axes.y += 1.0; 185 | 186 | move_axes = v2_normalize(move_axes); 187 | 188 | player_pos = v2_add(player_pos, v2_mulf(move_axes, 200*delta_time)); 189 | 190 | // Draw the player 191 | draw_image(player_sprite, player_pos, v2(player_sprite->width*4, player_sprite->height*4), COLOR_WHITE); 192 | 193 | 194 | // Reset camera for rendering the minimaps, so they are always the same position in the window. 195 | draw_frame.camera_xform = m4_identity(); 196 | 197 | /// 198 | // Draw the minimaps 199 | // 200 | for (World world = WORLD1; world < WORLD_MAX; world += 1) { 201 | float32 minimap_w = 256; 202 | float32 minimap_h = 256; 203 | float32 minimap_x = -window.width/2+40 + world*(minimap_w+30); 204 | float32 minimap_y = window.height/2-minimap_h-40; 205 | 206 | // Red Background (borders for current world) 207 | if (world == current_world) 208 | draw_rect(v2(minimap_x-10, minimap_y-10), v2(minimap_w+20, minimap_h+20), COLOR_RED); 209 | 210 | // The minimap 211 | draw_image(world_images[world], v2(minimap_x, minimap_y), v2(minimap_w, minimap_h), COLOR_WHITE); 212 | 213 | // Draw a blip representing the player on the minimap for the current world 214 | if (world == current_world) { 215 | float32 minimap_center_x = minimap_x + minimap_w/2; 216 | float32 minimap_center_y = minimap_y + minimap_h/2; 217 | float32 normx = player_pos.x/((world_max.x-world_min.x)/2); 218 | float32 normy = player_pos.y/((world_max.y-world_min.y)/2); 219 | Vector2 pos_on_minimap = v2(minimap_center_x+normx*(minimap_w/2.0), minimap_center_y+normy*(minimap_h/2.0)); 220 | draw_circle(v2_sub(pos_on_minimap, v2(3, 3)), v2(6, 6), COLOR_RED); 221 | } 222 | } 223 | 224 | 225 | os_update(); 226 | gfx_update(); 227 | } 228 | 229 | return 0; 230 | } -------------------------------------------------------------------------------- /oogabooga/examples/particles_example.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #if !OOGABOOGA_ENABLE_EXTENSIONS 4 | #error particles_example.c requires OOGABOOGA_ENABLE_EXTENSIONS to be enabled 5 | #endif 6 | #if !OOGABOOGA_EXTENSION_PARTICLES 7 | #error particles_example.c requires OOGABOOGA_EXTENSION_PARTICLES to be enabled 8 | #endif 9 | 10 | 11 | #if OOGABOOGA_ENABLE_EXTENSIONS && OOGABOOGA_EXTENSION_PARTICLES 12 | 13 | Emission_Config emission_rain; 14 | Emission_Config emission_poof; 15 | void setup_emission_configs(); 16 | 17 | int entry(int argc, char **argv) { 18 | 19 | // This is how we (optionally) configure the window. 20 | // To see all the settable window properties, ctrl+f "struct Os_Window" in os_interface.c 21 | window.title = STR("Particles example"); 22 | 23 | setup_emission_configs(); 24 | 25 | Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 26 | assert(font, "Failed loading arial.ttf"); 27 | 28 | // Keep a stack of the looped emissions so we can pop them 29 | Emission_Handle *emission_stack = 0; 30 | growing_array_init((void**)&emission_stack, sizeof(Emission_Handle), get_heap_allocator()); 31 | 32 | // Particles spawn in contiguously meaning vbo will initially grow a little bit at a time 33 | // as we start drawing more quads, so let's just reserve a lot of bytes now instead. 34 | gfx_reserve_vbo_bytes(MB(16)); 35 | 36 | float64 last_time = os_get_elapsed_seconds(); 37 | while (!window.should_close) { 38 | reset_temporary_storage(); 39 | 40 | float64 now = os_get_elapsed_seconds(); 41 | float64 delta_time = now-last_time; 42 | 43 | last_time = now; 44 | 45 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 46 | 47 | float mx = input_frame.mouse_x - window.width/2; 48 | float my = input_frame.mouse_y - window.height/2; 49 | 50 | // Left click: Emit a rain thingy and add to stack of rain emissions 51 | if (is_key_just_pressed(MOUSE_BUTTON_LEFT)) { 52 | Emission_Handle h = emit_particles(emission_rain, v2(mx, my)); 53 | growing_array_add((void**)&emission_stack, &h); 54 | } 55 | 56 | u64 num_emissions = growing_array_get_valid_count(emission_stack); 57 | // Pop emissions on right click 58 | if (is_key_just_pressed(MOUSE_BUTTON_RIGHT) && num_emissions > 0) { 59 | Emission_Handle h = emission_stack[num_emissions-1]; 60 | 61 | emission_release(h); 62 | growing_array_pop((void**)&emission_stack); 63 | } 64 | 65 | // Emit poof with spacebar, but just use one instance which is reset with a new seed. 66 | // It's completely fine to just call emit_particles() and do nothing else each time, 67 | // this is just to prove that you can have one resetting emission. 68 | if (is_key_just_pressed(KEY_SPACEBAR)) { 69 | 70 | local_persist Emission_Handle inst = ZERO(Emission_Handle); 71 | local_persist bool inst_set = false; 72 | 73 | if (!inst_set) { 74 | inst = emit_particles(emission_poof, v2(mx, my)); 75 | inst_set = true; 76 | } else { 77 | emission_poof.seed = get_random(); 78 | emission_set_config(inst, emission_poof); 79 | emission_reset(inst); 80 | emission_set_position(inst, v2(mx, my)); 81 | } 82 | } 83 | 84 | 85 | 86 | ext_update(delta_time); 87 | ext_draw(); 88 | 89 | draw_text(font, tprint("FPS: %.2f", 1.0/delta_time), 32, v2(-window.width/2+30, window.height/2-60), v2(1, 1), COLOR_WHITE); 90 | 91 | os_update(); 92 | gfx_update(); 93 | } 94 | 95 | return 0; 96 | } 97 | 98 | 99 | void setup_emission_configs() { 100 | 101 | Gfx_Image *img = load_image_from_disk(STR("oogabooga/examples/berry_bush.png"), get_heap_allocator()); 102 | assert(img, "Failed loading berry_bush.png"); 103 | 104 | // 105 | // Rain 106 | 107 | emission_rain.kind_pool[0] = PARTICLE_KIND_RECTANGLE; 108 | emission_rain.kind_pool[1] = PARTICLE_KIND_CIRCLE; 109 | emission_rain.kind_pool[2] = PARTICLE_KIND_IMAGE; 110 | emission_rain.number_of_kinds = 3; 111 | 112 | emission_rain.image_pool[0] = img; 113 | emission_rain.number_of_images = 1; 114 | 115 | emission_rain.loop = true; // Loop means first particle starts over after last particle is emitted 116 | // If we want to loop without pauses, then number_of_particles needs to be at least 117 | // life_time*emissions_per_second 118 | emission_rain.number_of_particles = 600; 119 | emission_rain.emissions_per_second = 100; 120 | 121 | emission_rain.life_time.flat_f32 = 0.6; 122 | 123 | // Color and size must be set for particles to be visible at all 124 | 125 | emission_rain.color.mode = EMISSION_PROPERTY_MODE_INTERPOLATE; 126 | emission_rain.color.interp_kind = EMISSION_INTERPOLATION_SMOOTH; 127 | emission_rain.color.min_v4 = v4(0.85, 0.5, 0.5, 1.0); 128 | emission_rain.color.max_v4 = v4(0.85, 0.5, 0.5, 0.0); // Fade out 129 | 130 | emission_rain.size.flat_v2 = v2(16, 16); 131 | 132 | // start_position is relative to the position which you emit at 133 | emission_rain.start_position.mode = EMISSION_PROPERTY_MODE_RANDOM; 134 | emission_rain.start_position.min_v2 = v2(-16, -16); 135 | emission_rain.start_position.max_v2 = v2(16, 16); 136 | 137 | 138 | emission_rain.velocity.mode = EMISSION_PROPERTY_MODE_RANDOM; 139 | emission_rain.velocity.min_v2 = v2(-1000, -1000); 140 | emission_rain.velocity.max_v2 = v2( 1000, 1000); 141 | 142 | emission_rain.acceleration.flat_v2 = v2(0, -2500); 143 | 144 | // 145 | // Poof 146 | emission_poof.kind_pool[0] = PARTICLE_KIND_RECTANGLE; 147 | emission_poof.number_of_kinds = 1; 148 | 149 | emission_poof.number_of_particles = 10; 150 | emission_poof.emissions_per_second = 100; 151 | emission_poof.loop = false; 152 | 153 | // If we don't loop, the emission will get released when done. 154 | // Alternatilvely, we set persist to true so we can keep it around to reset 155 | // and potentially release manually with emission_release() 156 | emission_poof.persist = true; 157 | 158 | emission_poof.life_time.flat_f32 = 1.6; 159 | 160 | emission_poof.velocity.mode = EMISSION_PROPERTY_MODE_RANDOM; 161 | emission_poof.velocity.min_v2 = v2(-300, -300); 162 | emission_poof.velocity.max_v2 = v2( 300, 300); 163 | 164 | emission_poof.rotational_acceleration.mode = EMISSION_PROPERTY_MODE_RANDOM; 165 | emission_poof.rotational_acceleration.min_f32 = -TAU32; 166 | emission_poof.rotational_acceleration.max_f32 = TAU32; 167 | 168 | // Fade out 169 | emission_poof.color.mode = EMISSION_PROPERTY_MODE_INTERPOLATE; 170 | emission_poof.color.min_v4 = COLOR_WHITE; 171 | emission_poof.color.max_v4 = v4_zero; 172 | 173 | // Shrink until death 174 | emission_poof.size.mode = EMISSION_PROPERTY_MODE_INTERPOLATE; 175 | emission_poof.size.interp_kind = EMISSION_INTERPOLATION_SMOOTH; 176 | emission_poof.size.min_v2 = v2(16, 16); 177 | emission_poof.size.max_v2 = v2(0, 0); 178 | 179 | // For centered rotations, the pivot should interpolate same as size (but half) 180 | emission_poof.pivot.mode = EMISSION_PROPERTY_MODE_INTERPOLATE; 181 | emission_poof.pivot.interp_kind = EMISSION_INTERPOLATION_SMOOTH; 182 | emission_poof.pivot.min_v2 = v2(8, 8); 183 | emission_poof.pivot.max_v2 = v2(0, 0); 184 | } 185 | 186 | #endif // OOGABOOGA_ENABLE_EXTENSIONS && OOGABOOGA_EXTENSION_PARTICLES 187 | -------------------------------------------------------------------------------- /oogabooga/examples/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/player.png -------------------------------------------------------------------------------- /oogabooga/examples/renderer_stress_test.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | int entry(int argc, char **argv) { 4 | 5 | window.title = STR("My epic game"); 6 | window.point_width = 1280; 7 | window.point_height = 720; 8 | window.x = 200; 9 | window.y = 200; 10 | 11 | window.clear_color = hex_to_rgba(0x2a2d3aff); 12 | 13 | Gfx_Image *bush_image = load_image_from_disk(STR("oogabooga/examples/berry_bush.png"), get_heap_allocator()); 14 | assert(bush_image, "Failed loading berry_bush.png"); 15 | Gfx_Image *hammer_image = load_image_from_disk(STR("oogabooga/examples/hammer.png"), get_heap_allocator()); 16 | assert(hammer_image, "Failed loading hammer.png"); 17 | 18 | Custom_Mouse_Pointer hammer_pointer 19 | = os_make_custom_mouse_pointer_from_file(STR("oogabooga/examples/hammer.png"), 16, 16, get_heap_allocator()); 20 | assert(hammer_pointer != 0, "Could not load hammer pointer"); 21 | 22 | 23 | void *my_data = alloc(get_heap_allocator(), 32*32*4); 24 | memset(my_data, 0xffffffff, 32*32*4); 25 | Gfx_Image *my_image = make_image(32, 32, 4, my_data, get_heap_allocator()); 26 | for (int *c = (int*)my_data; c < (int*)my_data+16*16; c += 1) { 27 | *c = 0xff0000ff; 28 | } 29 | gfx_set_image_data(my_image, 0, 0, 16, 16, my_data); 30 | 31 | Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 32 | assert(font, "Failed loading arial.ttf, %d", GetLastError()); 33 | 34 | // This makes sure atlas is rendered for ascii. 35 | // You might want to do this if your game lags the first time you render text because it renders 36 | // the atlas on the fly. 37 | render_atlas_if_not_yet_rendered(font, 32, 'A'); 38 | 39 | seed_for_random = rdtsc(); 40 | 41 | const float64 fps_limit = 69000; 42 | const float64 min_frametime = 1.0 / fps_limit; 43 | 44 | Matrix4 camera_xform = m4_scalar(1.0); 45 | 46 | float64 last_time = os_get_elapsed_seconds(); 47 | while (!window.should_close) tm_scope("Frame") { 48 | reset_temporary_storage(); 49 | 50 | float64 now = os_get_elapsed_seconds(); 51 | float64 delta = now - last_time; 52 | if (delta < min_frametime) { 53 | os_high_precision_sleep((min_frametime-delta)*1000.0); 54 | now = os_get_elapsed_seconds(); 55 | delta = now - last_time; 56 | } 57 | last_time = now; 58 | 59 | float32 aspect = (float32)window.width/(float32)window.height; 60 | 61 | draw_frame.projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10); 62 | 63 | const float32 cam_move_speed = 4.0; 64 | Vector2 cam_move_axis = v2(0, 0); 65 | if (is_key_down('A')) { 66 | cam_move_axis.x -= 1.0; 67 | } 68 | if (is_key_down('D')) { 69 | cam_move_axis.x += 1.0; 70 | } 71 | if (is_key_down('S')) { 72 | cam_move_axis.y -= 1.0; 73 | } 74 | if (is_key_down('W')) { 75 | cam_move_axis.y += 1.0; 76 | } 77 | 78 | Vector2 cam_move = v2_mulf(cam_move_axis, delta * cam_move_speed); 79 | camera_xform = m4_translate(camera_xform, v3(v2_expand(cam_move), 0)); 80 | draw_frame.camera_xform = camera_xform; 81 | 82 | seed_for_random = 69; 83 | for (u64 i = 0; i < 150000; i++) { 84 | float32 aspect = (float32)window.width/(float32)window.height; 85 | float min_x = -aspect; 86 | float max_x = aspect; 87 | float min_y = -1; 88 | float max_y = 1; 89 | 90 | float x = get_random_float32() * (max_x-min_x) + min_x; 91 | float y = get_random_float32() * (max_y-min_y) + min_y; 92 | 93 | draw_image(bush_image, v2(x, y), v2(0.1, 0.1), COLOR_WHITE); 94 | } 95 | 96 | if (is_key_just_released('E')) { 97 | log("FPS: %.2f", 1.0 / delta); 98 | log("ms: %.2f", delta*1000.0); 99 | } 100 | 101 | gfx_update(); 102 | os_update(); 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /oogabooga/examples/sanity_tests.c: -------------------------------------------------------------------------------- 1 | 2 | int entry(int argc, char **argv) { 3 | 4 | 5 | u32 w = 128; 6 | u32 h = 128; 7 | u32 tile_w = w/4; 8 | u32 tile_h = w/4; 9 | 10 | u8 *pixels = (u8*)alloc(get_heap_allocator(), w * h * 4); 11 | 12 | for (u32 y = 0; y < h; y += 1) { 13 | for (u32 x = 0; x < w; x += 1) { 14 | u32 tile_x = x / tile_w; 15 | u32 tile_y = y / tile_h; 16 | 17 | bool is_black_tile = (tile_x % 2 == tile_y % 2); 18 | 19 | for (u32 c = 0; c < 4; c += 1) { 20 | if (is_black_tile) { 21 | pixels[(y * w + x) * 4 + c] = 127; 22 | } else { 23 | pixels[(y * w + x) * 4 + c] = 255; 24 | } 25 | } 26 | } 27 | } 28 | 29 | Gfx_Image *img = make_image(w, h, 4, pixels, get_heap_allocator()); 30 | 31 | u8 *read_pixels = (u8*)alloc(get_heap_allocator(), w*h*4); 32 | 33 | gfx_read_image_data(img, 0, 0, w, h, read_pixels); 34 | 35 | gfx_set_image_data(img, 0, 0, w, h, read_pixels); 36 | 37 | assert(memcmp(read_pixels, pixels, w*h*4) == 0); 38 | 39 | float64 last_time = os_get_elapsed_seconds(); 40 | while (!window.should_close) { 41 | reset_temporary_storage(); 42 | 43 | float64 now = os_get_elapsed_seconds(); 44 | if ((int)now != (int)last_time) log("%.2f FPS\n%.2fms", 1.0/(now-last_time), (now-last_time)*1000); 45 | last_time = now; 46 | 47 | Matrix4 rect_xform = m4_scalar(1.0); 48 | rect_xform = m4_rotate_z(rect_xform, (f32)now); 49 | rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0)); 50 | draw_image_xform(img, rect_xform, v2(.5f, .5f), COLOR_GREEN); 51 | 52 | draw_rect(v2(sin(now), -.8), v2(.5, .25), COLOR_RED); 53 | 54 | float aspect = (f32)window.width/(f32)window.height; 55 | float mx = (input_frame.mouse_x/(f32)window.width * 2.0 - 1.0)*aspect; 56 | float my = input_frame.mouse_y/(f32)window.height * 2.0 - 1.0; 57 | 58 | draw_line(v2(-.75, -.75), v2(mx, my), 0.005, COLOR_WHITE); 59 | 60 | if (is_key_just_pressed('F')) { 61 | window.fullscreen = !window.fullscreen; 62 | } 63 | 64 | os_update(); 65 | gfx_update(); 66 | } 67 | 68 | return 0; 69 | } -------------------------------------------------------------------------------- /oogabooga/examples/song1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/song1.ogg -------------------------------------------------------------------------------- /oogabooga/examples/song2.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/song2.ogg -------------------------------------------------------------------------------- /oogabooga/examples/song3.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baldgg/oogabooga/9bb5c16e0d363b1158ba74e94a016ff2b05fbdf8/oogabooga/examples/song3.ogg -------------------------------------------------------------------------------- /oogabooga/examples/sprite_animation.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | int entry(int argc, char **argv) { 5 | 6 | window.title = STR("Sprite animation example"); 7 | 8 | Gfx_Image *anim_sheet = load_image_from_disk(STR("oogabooga/examples/male_animation.png"), get_heap_allocator()); 9 | assert(anim_sheet, "Could not open oogabooga/examples/male_animation.png"); 10 | 11 | // Configure information about the whole image as a sprite sheet 12 | u32 number_of_columns = 10; 13 | u32 number_of_rows = 6; 14 | u32 total_number_of_frames = number_of_rows * number_of_columns; 15 | 16 | u32 anim_frame_width = anim_sheet->width / number_of_columns; 17 | u32 anim_frame_height = anim_sheet->height / number_of_rows; 18 | 19 | // Configure the animation by setting the start & end frames in the grid of frames 20 | // (Inspect sheet image and count the frame indices you want) 21 | // In sprite sheet animations, it usually goes down. So Y 0 is actuall the top of the 22 | // sprite sheet, and +Y is down on the sprite sheet. 23 | u32 anim_start_frame_x = 2; 24 | u32 anim_start_frame_y = 1; 25 | u32 anim_end_frame_x = 6; 26 | u32 anim_end_frame_y = 2; 27 | u32 anim_start_index = anim_start_frame_y * number_of_columns + anim_start_frame_x; 28 | u32 anim_end_index = anim_end_frame_y * number_of_columns + anim_end_frame_x; 29 | u32 anim_number_of_frames = max(anim_end_index, anim_start_index)-min(anim_end_index, anim_start_index)+1; 30 | 31 | // Sanity check configuration 32 | assert(anim_end_index > anim_start_index, "The last frame must come before the first frame"); 33 | assert(anim_start_frame_x < number_of_columns, "anim_start_frame_x is out of bounds"); 34 | assert(anim_start_frame_y < number_of_rows, "anim_start_frame_y is out of bounds"); 35 | assert(anim_end_frame_x < number_of_columns, "anim_end_frame_x is out of bounds"); 36 | assert(anim_end_frame_y < number_of_rows, "anim_end_frame_y is out of bounds"); 37 | 38 | // Calculate duration per frame in seconds 39 | float32 playback_fps = 4; 40 | float32 anim_time_per_frame = 1.0 / playback_fps; 41 | float32 anim_duration = anim_time_per_frame * (float32)anim_number_of_frames; 42 | 43 | float32 anim_start_time = os_get_elapsed_seconds(); 44 | 45 | float64 last_time = os_get_elapsed_seconds(); 46 | while (!window.should_close) { 47 | reset_temporary_storage(); 48 | 49 | 50 | float64 now = os_get_elapsed_seconds(); 51 | float64 delta = now-last_time; 52 | last_time = now; 53 | 54 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 55 | 56 | // Float modulus to "loop" around the timer over the anim duration 57 | float32 anim_elapsed = fmodf(now - anim_start_time, anim_duration); 58 | 59 | // Get current progression in animation from 0.0 to 1.0 60 | float32 anim_progression_factor = anim_elapsed / anim_duration; 61 | 62 | u32 anim_current_index = anim_number_of_frames * anim_progression_factor; 63 | u32 anim_absolute_index_in_sheet = anim_start_index + anim_current_index; 64 | 65 | u32 anim_index_x = anim_absolute_index_in_sheet % number_of_columns; 66 | u32 anim_index_y = anim_absolute_index_in_sheet / number_of_columns + 1; 67 | 68 | u32 anim_sheet_pos_x = anim_index_x * anim_frame_width; 69 | u32 anim_sheet_pos_y = (number_of_rows - anim_index_y) * anim_frame_height; // Remember, Y inverted. 70 | 71 | // Draw the sprite sheet, with the uv box for the current frame. 72 | // Uv box is a Vector4 of x1, y1, x2, y2 where each value is a percentage value 0.0 to 1.0 73 | // from left to right / bottom to top in the texture. 74 | Draw_Quad *quad = draw_image(anim_sheet, v2(0, 0), v2(anim_frame_width*4, anim_frame_height*4), COLOR_WHITE); 75 | quad->uv.x1 = (float32)(anim_sheet_pos_x)/(float32)anim_sheet->width; 76 | quad->uv.y1 = (float32)(anim_sheet_pos_y)/(float32)anim_sheet->height; 77 | quad->uv.x2 = (float32)(anim_sheet_pos_x+anim_frame_width) /(float32)anim_sheet->width; 78 | quad->uv.y2 = (float32)(anim_sheet_pos_y+anim_frame_height)/(float32)anim_sheet->height; 79 | 80 | 81 | // Visualize sprite sheet animation 82 | Vector2 sheet_pos = v2(-window.width/2+40, -window.height/2+40); 83 | Vector2 sheet_size = v2(anim_sheet->width, anim_sheet->height); 84 | Vector2 frame_pos_in_sheet = v2(anim_sheet_pos_x, anim_sheet_pos_y); 85 | Vector2 frame_size = v2(anim_frame_width, anim_frame_height); 86 | draw_rect(sheet_pos, sheet_size, COLOR_BLACK); // Draw black background 87 | draw_rect(v2_add(sheet_pos, frame_pos_in_sheet), frame_size, COLOR_WHITE); // Draw white rect on current frame 88 | draw_image(anim_sheet, sheet_pos, sheet_size, COLOR_WHITE); // Draw the seet 89 | 90 | os_update(); 91 | gfx_update(); 92 | } 93 | 94 | return 0; 95 | } -------------------------------------------------------------------------------- /oogabooga/examples/text_rendering.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | int entry(int argc, char **argv) { 4 | 5 | window.title = STR("OGB Text Rendering Example"); 6 | window.point_width = 1280; 7 | window.point_height = 720; 8 | window.x = 200; 9 | window.y = 200; 10 | window.clear_color = hex_to_rgba(0x6495EDff); 11 | 12 | Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 13 | assert(font, "Failed loading arial.ttf"); 14 | 15 | const u32 font_height = 48; 16 | 17 | seed_for_random = rdtsc(); 18 | u64 gunk_seed = get_random(); 19 | 20 | while (!window.should_close) tm_scope("Frame") { 21 | reset_temporary_storage(); 22 | 23 | // Text is easiest to deal with if our projection matches window pixel size, because 24 | // then the rasterization height will match the screen pixel height (unless scaling). 25 | // The best way to make the text look good is to draw it at the exact same pixel height 26 | // as it was rasterized at with no down- or up-scaling. 27 | // It's fairly common in video games to render the UI with a separate projection for this 28 | // very reason. 29 | draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.pixel_height * -0.5, window.pixel_height * 0.5, -1, 10); 30 | 31 | // Easy drop shadow: Just draw the same text underneath with a slight offset 32 | draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK); 33 | draw_text(font, STR("I am text"), font_height, v2(0, 0), v2(1, 1), COLOR_WHITE); 34 | 35 | draw_text(font, tprint("Time: %f", os_get_elapsed_seconds()), font_height, v2(0, 100), v2(1, 1), COLOR_WHITE); 36 | 37 | float now = (float)os_get_elapsed_seconds(); 38 | float animated_x = sin(now*0.1)*(window.width*0.5); 39 | 40 | // UTF-8 ! 41 | draw_text(font, STR("Привет"), font_height, v2(animated_x-2, 2), v2(1, 1), COLOR_BLACK); 42 | draw_text(font, STR("Привет"), font_height, v2(animated_x, 0), v2(1, 1), COLOR_WHITE); 43 | 44 | // New lines are handled when drawing text 45 | string hello_str = STR("Hello,\nTTTT New line\nAnother line"); 46 | 47 | // To align/justify text we need to measure it. 48 | Gfx_Text_Metrics hello_metrics = measure_text(font, hello_str, font_height, v2(1, 1)); 49 | 50 | // This is where we want the bottom left of the text to be... 51 | Vector2 bottom_left = v2(-window.width/2+20, -window.height/2+20); 52 | 53 | // ... So we have to justify that bottom_left according to text metrics 54 | Vector2 justified = v2_sub(bottom_left, hello_metrics.functional_pos_min); 55 | 56 | // If we wanted to center it: 57 | // justified = v2_sub(justified, v2_divf(hello_metrics.functional_size, 2)); 58 | 59 | draw_text(font, hello_str, font_height, justified, v2(1, 1), COLOR_WHITE); 60 | 61 | 62 | local_persist bool show_bounds = false; 63 | if (is_key_just_pressed('E')) show_bounds = !show_bounds; 64 | 65 | 66 | 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 "); 67 | 68 | // Generate some random gunk to add to the long text 69 | u64 n = ((sin(now)+1)/2.0)*100; 70 | seed_for_random = gunk_seed; 71 | if (n > 0) { 72 | string gunk = talloc_string(n); 73 | for (u64 i = 0; i < n; i++) { 74 | if (get_random_float32() < 0.2) { 75 | gunk.data[i] = ' '; 76 | continue; 77 | } 78 | gunk.data[i] = get_random_int_in_range('a', 'z'); 79 | } 80 | 81 | long_text = string_concat(long_text, gunk, get_temporary_allocator()); 82 | } 83 | 84 | if (show_bounds) { 85 | // Visualize the bounds we get from metrics 86 | Gfx_Text_Metrics m = measure_text(font, long_text, font_height, v2(1, 1)); 87 | draw_rect(v2_add(v2(-600, -200), m.visual_pos_min), m.visual_size, v4(.1, .1, .1, .2)); 88 | draw_rect(v2_add(v2(-600, -200), m.functional_pos_min), m.functional_size, v4(1, .1, .1, .2)); 89 | } 90 | draw_text(font, long_text, font_height, v2(-600, -200), v2(1, 1), COLOR_WHITE); 91 | 92 | // Wrap text and draw each returned line 93 | string *long_text_wrapped = split_text_to_lines_with_wrapping(long_text, window.width/2*0.9, font, font_height, v2_one, true); 94 | float32 y = 200; 95 | for (u64 i = 0; i < growing_array_get_valid_count(long_text_wrapped); i += 1) { 96 | string line = long_text_wrapped[i]; 97 | Gfx_Font_Metrics m = get_font_metrics(font, font_height); 98 | 99 | draw_text(font, line, font_height, v2(-window.width/2+20, y), v2(1, 1), COLOR_WHITE); 100 | 101 | y -= m.new_line_offset; 102 | } 103 | 104 | os_update(); 105 | gfx_update(); 106 | 107 | } 108 | 109 | return 0; 110 | } -------------------------------------------------------------------------------- /oogabooga/examples/threaded_drawing.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | In this example we utilize separate draw frames and threading to split up the task of computing each 5 | quad. 6 | 7 | Note that the computed Draw_Frame's all need to be translated to vertices & copied to gpu on the main 8 | thread. 9 | 10 | So what we do is that we split the total work (draw X sprites) up for a certain amount of thread, each 11 | which has it's own Draw_Frame. 12 | 13 | We use Binary_Semaphore's per thread to notify 1. When draw thread can start drawing, after main thread 14 | has finished rendering the result draw_frames and 2. When draw thread is done, which the main thread needs 15 | to wait for before using the potentially unfinished result Draw_Frame's for rendering. 16 | 17 | If your computer has at lest 5-6 logical processors, that seems to split the time it takes to draw in 18 | about 1/3 (at least on my computer). 19 | 20 | Unfortunately, since the backend is using d3d11, the copying of vertices to gpu is very slow and can't 21 | really be mutlithreaded so that's really where the bottleneck is in this case. But offloading the Draw_Frame 22 | computations to separate threads definitely proved non-trivial. 23 | 24 | */ 25 | 26 | // Context per thread 27 | typedef struct Draw_Context { 28 | Draw_Frame frame; 29 | u64 index; 30 | Gfx_Image *sprite; 31 | Binary_Semaphore draw_thread_start_sem; 32 | Binary_Semaphore draw_thread_done_sem; 33 | u64 number_of_sprites; 34 | Vector4 color; 35 | 36 | u64 frame_count; 37 | float64 accum_seconds; 38 | } Draw_Context; 39 | 40 | void draw_thread(Thread *t); 41 | 42 | int entry(int argc, char **argv) { 43 | window.title = STR("Threaded Drawing Example"); 44 | 45 | Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 46 | 47 | Gfx_Image *sprite = load_image_from_disk(STR("oogabooga/examples/berry_bush.png"), get_heap_allocator()); 48 | assert(sprite, "Could not load 'oogabooga/examples/berry_bush.png'"); 49 | 50 | // This is overkill af on my computer with 32 logical processors, in fact 5-6 seems to peek in 51 | // performance and after that there's no difference. 52 | // You could however imagine the threads doing a lot more work. 53 | u64 number_of_threads = os_get_number_of_logical_processors(); 54 | 55 | u64 total_number_of_sprites = 150000; 56 | 57 | Thread *threads = (Thread*)alloc(get_heap_allocator(), number_of_threads*sizeof(Thread)); 58 | Draw_Context *draw_contexts = (Draw_Context*)alloc(get_heap_allocator(), number_of_threads*sizeof(Draw_Context)); 59 | 60 | // Initialize each thread and the respective draw context, and start the threads 61 | for (u64 i = 0; i < number_of_threads; i += 1) { 62 | Thread *t = threads + i; 63 | Draw_Context *draw_context = draw_contexts + i; 64 | 65 | os_thread_init(t, draw_thread); 66 | t->data = draw_context; 67 | 68 | draw_frame_init(&draw_context->frame); 69 | os_binary_semaphore_init(&draw_context->draw_thread_start_sem, false); 70 | os_binary_semaphore_init(&draw_context->draw_thread_done_sem, false); 71 | draw_context->index = i; 72 | draw_context->sprite = sprite; 73 | draw_context->number_of_sprites = total_number_of_sprites/number_of_threads; 74 | // Draw threads can start right away. Also if we don't do this, we will deadlock since draw thread will wait 75 | // for this signal, but we will wait for draw thread to signal being done. 76 | os_binary_semaphore_signal(&draw_context->draw_thread_start_sem); 77 | draw_context->color = v4( 78 | get_random_float32_in_range(0, 1), 79 | get_random_float32_in_range(0, 1), 80 | get_random_float32_in_range(0, 1), 81 | 1 82 | ); 83 | 84 | os_thread_start(t); 85 | } 86 | 87 | int tick = 0; 88 | 89 | float64 last_time = os_get_elapsed_seconds(); 90 | while (!window.should_close) tm_scope("Update") { 91 | reset_temporary_storage(); 92 | 93 | float64 now = os_get_elapsed_seconds(); 94 | if ((int)now != (int)last_time) log("%.2f FPS\n%.2fms", 1.0/(now-last_time), (now-last_time)*1000); 95 | last_time = now; 96 | 97 | for (u64 i = 0; i < number_of_threads; i += 1) { 98 | Draw_Context *draw_context = draw_contexts + i; 99 | 100 | // Wait for draw thread to be done 101 | os_binary_semaphore_wait(&draw_context->draw_thread_done_sem); 102 | 103 | // Render the result Draw_Frame 104 | gfx_render_draw_frame_to_window(&draw_context->frame); 105 | 106 | // Signal the draw thread that it can start drawing the next Draw_Frame 107 | os_binary_semaphore_signal(&draw_context->draw_thread_start_sem); 108 | } 109 | 110 | os_update(); 111 | gfx_update(); 112 | 113 | } 114 | 115 | return 0; 116 | } 117 | 118 | void draw_thread(Thread *t) { 119 | Draw_Context *draw_context = (Draw_Context*)t->data; 120 | 121 | u64 my_seed = rdtsc() + t->id; 122 | 123 | float32 sprite_width = 8; 124 | float32 sprite_height = 8; 125 | 126 | while (!window.should_close) tm_scope("Thread frame") { 127 | reset_temporary_storage(); 128 | 129 | float64 now = os_get_elapsed_seconds(); 130 | 131 | os_binary_semaphore_wait(&draw_context->draw_thread_start_sem); 132 | 133 | tm_scope("Thread draw") { 134 | draw_frame_reset(&draw_context->frame); 135 | 136 | // Remember, seed_for_random is thread_local 137 | seed_for_random = my_seed; 138 | 139 | for (u64 i = 0; i < draw_context->number_of_sprites; i += 1) { 140 | draw_image_in_frame( 141 | draw_context->sprite, 142 | v2( 143 | get_random_float32_in_range(-window.width/2, window.width/2) - sprite_width/2, 144 | get_random_float32_in_range(-window.height/2, window.height/2) - sprite_height/2 145 | ), 146 | v2(sprite_width, sprite_height), 147 | draw_context->color, 148 | &draw_context->frame 149 | ); 150 | } 151 | 152 | float64 duration = os_get_elapsed_seconds() - now; 153 | 154 | draw_context->accum_seconds += duration; 155 | draw_context->frame_count += 1; 156 | } 157 | 158 | os_binary_semaphore_signal(&draw_context->draw_thread_done_sem); 159 | } 160 | } -------------------------------------------------------------------------------- /oogabooga/examples/window_test.c: -------------------------------------------------------------------------------- 1 | 2 | bool button(string label, Vector2 pos, Vector2 size, bool enabled); 3 | 4 | Gfx_Font *font; 5 | u32 font_height = 28; 6 | 7 | int entry(int argc, char **argv) { 8 | 9 | font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator()); 10 | 11 | window.title = STR("Minimal Game Example"); 12 | window.point_width = 900; 13 | window.point_height = 600; 14 | window.point_x = 100; 15 | window.point_y = 150; 16 | 17 | float64 last_time = os_get_elapsed_seconds(); 18 | while (!window.should_close) { 19 | reset_temporary_storage(); 20 | 21 | float64 now = os_get_elapsed_seconds(); 22 | float64 delta_time = now - last_time; 23 | last_time = now; 24 | 25 | Matrix4 rect_xform = m4_scalar(1.0); 26 | rect_xform = m4_rotate_z(rect_xform, (f32)now); 27 | rect_xform = m4_translate(rect_xform, v3(-125, -125, 0)); 28 | draw_rect_xform(rect_xform, v2(250, 250), COLOR_GREEN); 29 | 30 | draw_rect(v2(sin(now)*window.width*0.4-60, -60), v2(120, 120), COLOR_RED); 31 | 32 | float32 x = -window.width/2 + 40; 33 | float32 y = window.height/2 - 40 - font_height; 34 | 35 | draw_text(font, tprint("FPS: %.2f", 1.0/delta_time), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 36 | y -= font_height*1.2; 37 | draw_text(font, tprint("px width: %d", window.pixel_width), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 38 | y -= font_height*1.2; 39 | draw_text(font, tprint("px height: %d", window.pixel_height), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 40 | y -= font_height*1.2; 41 | draw_text(font, tprint("px x: %d", window.x), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 42 | y -= font_height*1.2; 43 | draw_text(font, tprint("px y: %d", window.y), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 44 | y -= font_height*1.2; 45 | draw_text(font, tprint("pt width: %d", window.point_width), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 46 | y -= font_height*1.2; 47 | draw_text(font, tprint("pt height: %d", window.point_height), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 48 | y -= font_height*1.2; 49 | draw_text(font, tprint("pt x: %d", window.point_x), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 50 | y -= font_height*1.2; 51 | draw_text(font, tprint("pt y: %d", window.point_y), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 52 | y -= font_height*1.5; 53 | draw_text(font, tprint("Fullscreen: %b", window.fullscreen), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 54 | y -= font_height*1.2; 55 | draw_text(font, tprint("Mouse: %v2", v2(input_frame.mouse_x, input_frame.mouse_y)), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 56 | y -= font_height*1.2; 57 | draw_text(font, tprint("DPI: %d", window.dpi), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 58 | y -= font_height*1.2; 59 | draw_text(font, tprint("Point size in pixels: %.3f", window.point_size_in_pixels), font_height, v2(x, y), v2(1, 1), v4(.9, .9, .9, 1.0)); 60 | y -= font_height*1.2; 61 | 62 | x = 0; 63 | y = window.height/2 - 40 - font_height; 64 | 65 | float32 w = 200; 66 | float32 h = font_height*1.5; 67 | 68 | if (button(STR("Fullscreen"), v2(x-w/2, y-h/2), v2(w, h), window.fullscreen)) window.fullscreen = !window.fullscreen; 69 | y -= h*1.3; 70 | if (button(STR("Vsync"), v2(x-w/2, y-h/2), v2(w, h), window.enable_vsync)) window.enable_vsync = !window.enable_vsync; 71 | y -= h*1.3; 72 | if (button(STR("Allow resize"), v2(x-w/2, y-h/2), v2(w, h), window.allow_resize)) window.allow_resize = !window.allow_resize; 73 | y -= h*1.3; 74 | if (button(STR("Topmost"), v2(x-w/2, y-h/2), v2(w, h), window.force_topmost)) window.force_topmost = !window.force_topmost; 75 | y -= h*1.3; 76 | if (button(STR("Move right"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_x += 20; 77 | y -= h*1.3; 78 | if (button(STR("Move left"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_x -= 20; 79 | y -= h*1.3; 80 | if (button(STR("Move up"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_y += 20; 81 | y -= h*1.3; 82 | if (button(STR("Move down"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_y -= 20; 83 | y -= h*1.3; 84 | if (button(STR("+ width"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_width += 20; 85 | y -= h*1.3; 86 | if (button(STR("- width"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_width -= 20; 87 | y -= h*1.3; 88 | if (button(STR("+ height"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_height += 20; 89 | y -= h*1.3; 90 | if (button(STR("- height"), v2(x-w/2, y-h/2), v2(w, h), false)) window.point_height -= 20; 91 | y -= h*1.3; 92 | 93 | os_update(); 94 | gfx_update(); 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | 101 | bool button(string label, Vector2 pos, Vector2 size, bool enabled) { 102 | 103 | Vector4 color = v4(.45, .45, .45, 1); 104 | 105 | float L = pos.x; 106 | float R = L + size.x; 107 | float B = pos.y; 108 | float T = B + size.y; 109 | 110 | float mx = input_frame.mouse_x - window.width/2; 111 | float my = input_frame.mouse_y - window.height/2; 112 | 113 | bool pressed = false; 114 | 115 | if (mx >= L && mx < R && my >= B && my < T) { 116 | color = v4(.15, .15, .15, 1); 117 | if (is_key_down(MOUSE_BUTTON_LEFT)) { 118 | color = v4(.05, .05, .05, 1); 119 | } 120 | 121 | pressed = is_key_just_released(MOUSE_BUTTON_LEFT); 122 | } 123 | 124 | if (enabled) { 125 | color = v4_sub(color, v4(.2, .2, .2, 0)); 126 | } 127 | 128 | draw_rect(pos, size, color); 129 | 130 | Gfx_Text_Metrics m = measure_text(font, label, font_height, v2(1, 1)); 131 | 132 | Vector2 bottom_left = v2_sub(pos, m.functional_pos_min); 133 | bottom_left.x += size.x/2; 134 | bottom_left.x -= m.functional_size.x/2; 135 | 136 | bottom_left.y += size.y/2; 137 | bottom_left.y -= m.functional_size.y/2; 138 | 139 | draw_text(font, label, font_height, bottom_left, v2(1, 1), COLOR_WHITE); 140 | 141 | return pressed; 142 | } -------------------------------------------------------------------------------- /oogabooga/extensions.c: -------------------------------------------------------------------------------- 1 | 2 | #if OOGABOOGA_EXTENSION_PARTICLES 3 | #include "ext_particles.c" 4 | #endif 5 | 6 | void ext_init() { 7 | #if OOGABOOGA_EXTENSION_PARTICLES 8 | particles_init(); 9 | #endif 10 | } 11 | 12 | void ext_update(float32 delta_time) { 13 | #if OOGABOOGA_EXTENSION_PARTICLES 14 | particles_update(); 15 | #endif 16 | } 17 | 18 | void ext_draw() { 19 | #if OOGABOOGA_EXTENSION_PARTICLES 20 | particles_draw(); 21 | #endif 22 | } -------------------------------------------------------------------------------- /oogabooga/gfx_interface.c: -------------------------------------------------------------------------------- 1 | #if GFX_RENDERER == GFX_RENDERER_D3D11 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | typedef ID3D11ShaderResourceView * Gfx_Handle; 9 | typedef ID3D11RenderTargetView * Gfx_Render_Target_Handle; 10 | 11 | typedef struct { ID3D11PixelShader *ps; ID3D11Buffer *cbuffer; u64 cbuffer_size; } Gfx_Shader_Extension; 12 | 13 | #elif GFX_RENDERER == GFX_RENDERER_VULKAN 14 | #error "We only have a D3D11 renderer at the moment" 15 | #elif GFX_RENDERER == GFX_RENDERER_METAL 16 | #error "We only have a D3D11 renderer at the moment" 17 | #else 18 | #error "Unknown renderer GFX_RENDERER defined" 19 | #endif 20 | 21 | 22 | #ifdef VERTEX_2D_USER_DATA_COUNT 23 | #error VERTEX_2D_USER_DATA_COUNT has been renamed to VERTEX_USER_DATA_COUNT, please use that instead 24 | #endif 25 | #ifndef VERTEX_USER_DATA_COUNT 26 | #define VERTEX_USER_DATA_COUNT 1 27 | #endif 28 | 29 | ogb_instance const Gfx_Handle GFX_INVALID_HANDLE; 30 | // #Volatile reflected in 2D batch shader 31 | #define QUAD_TYPE_REGULAR 0 32 | #define QUAD_TYPE_TEXT 1 33 | #define QUAD_TYPE_CIRCLE 2 34 | 35 | typedef enum Gfx_Filter_Mode { 36 | GFX_FILTER_MODE_NEAREST, 37 | GFX_FILTER_MODE_LINEAR, 38 | } Gfx_Filter_Mode; 39 | 40 | typedef struct Gfx_Image { 41 | u32 width, height, channels; 42 | Gfx_Handle gfx_handle; 43 | Gfx_Render_Target_Handle gfx_render_target; 44 | Allocator allocator; 45 | } Gfx_Image; 46 | 47 | typedef struct Draw_Frame Draw_Frame; 48 | 49 | // Implemented per renderer 50 | 51 | ogb_instance void 52 | gfx_render_draw_frame(Draw_Frame *frame, Gfx_Image *render_target); 53 | 54 | ogb_instance void 55 | gfx_render_draw_frame_to_window(Draw_Frame *frame); 56 | 57 | ogb_instance void 58 | gfx_init_image(Gfx_Image *image, void *data, bool render_target); 59 | 60 | ogb_instance void 61 | gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data); 62 | 63 | ogb_instance void 64 | gfx_read_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *output); 65 | 66 | ogb_instance void 67 | gfx_deinit_image(Gfx_Image *image); 68 | 69 | ogb_instance void 70 | gfx_init(); 71 | 72 | ogb_instance void 73 | gfx_update(); 74 | 75 | ogb_instance void 76 | gfx_reserve_vbo_bytes(u64 number_of_bytes); 77 | 78 | ogb_instance bool 79 | gfx_compile_shader_extension(string ext_source, u64 cbuffer_size, Gfx_Shader_Extension *result); 80 | 81 | ogb_instance void 82 | gfx_destroy_shader_extension(Gfx_Shader_Extension shader_extension); 83 | 84 | // Deprecated @ 25th of September 2024 #Cleanup 85 | DEPRECATED(ogb_instance bool gfx_shader_recompile_with_extension(string ext_source, u64 cbuffer_size), "The shader extension system has been reworked and this function will no longer do anything. See custom_shader.c or bloom.c in oogabooga/examples."); 86 | 87 | 88 | // initial_data can be null to leave image data uninitialized 89 | Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator) { 90 | // This is annoying but I did this long ago because stuff was a bit different and now I can't really change it :( 91 | Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image)); 92 | 93 | assert(channels > 0 && channels <= 4, "Only 1, 2, 3 or 4 channels allowed on images. Got %d", channels); 94 | 95 | image->width = width; 96 | image->height = height; 97 | image->allocator = allocator; 98 | image->channels = channels; 99 | 100 | gfx_init_image(image, initial_data, false); 101 | 102 | return image; 103 | } 104 | 105 | Gfx_Image *make_image_render_target(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator) { 106 | // This is annoying but I did this long ago because stuff was a bit different and now I can't really change it :( 107 | Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image)); 108 | 109 | assert(channels > 0 && channels <= 4, "Only 1, 2, 3 or 4 channels allowed on images. Got %d", channels); 110 | 111 | image->width = width; 112 | image->height = height; 113 | image->allocator = allocator; 114 | image->channels = channels; 115 | 116 | gfx_init_image(image, initial_data, true); 117 | 118 | return image; 119 | } 120 | 121 | Gfx_Image *load_image_from_disk(string path, Allocator allocator) { 122 | string png; 123 | bool ok = os_read_entire_file(path, &png, allocator); 124 | if (!ok) return 0; 125 | 126 | Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image)); 127 | 128 | int width, height, channels; 129 | stbi_set_flip_vertically_on_load(1); 130 | third_party_allocator = allocator; 131 | unsigned char* stb_data = stbi_load_from_memory(png.data, png.count, &width, &height, &channels, STBI_rgb_alpha); 132 | 133 | 134 | if (!stb_data) { 135 | dealloc(allocator, image); 136 | dealloc_string(allocator, png); 137 | return 0; 138 | } 139 | 140 | image->width = width; 141 | image->height = height; 142 | image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx 143 | image->allocator = allocator; 144 | image->channels = 4; 145 | 146 | dealloc_string(allocator, png); 147 | 148 | gfx_init_image(image, stb_data, false); 149 | 150 | stbi_image_free(stb_data); 151 | 152 | third_party_allocator = ZERO(Allocator); 153 | 154 | return image; 155 | } 156 | 157 | void 158 | delete_image(Gfx_Image *image) { 159 | // Free the image data allocated by stb_image 160 | image->width = 0; 161 | image->height = 0; 162 | gfx_deinit_image(image); 163 | dealloc(image->allocator, image); 164 | } 165 | -------------------------------------------------------------------------------- /oogabooga/growing_array.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | Full API: 5 | 6 | void growing_array_init_reserve(void **array, u64 block_size_in_bytes, u64 count_to_reserve, Allocator allocator); 7 | void growing_array_init(void **array, u64 block_size_in_bytes, Allocator allocator); 8 | void growing_array_deinit(void **array); 9 | 10 | void *growing_array_add_empty(void **array); 11 | void *growing_array_add_multiple_empty(void **array); 12 | void growing_array_add(void **array, void *item); 13 | void growing_array_add_multiple(void **array, void *items, u64 count); 14 | 15 | void growing_array_reserve(void **array, u64 count_to_reserve); 16 | void growing_array_resize(void **array, u64 new_count); 17 | void growing_array_pop(void **array); 18 | void growing_array_clear(void **array); 19 | 20 | // Returns -1 if not found 21 | s32 growing_array_find_index_from_left_by_pointer(void **array, void *p); 22 | s32 growing_array_find_index_from_left_by_value(void **array, void *p); 23 | 24 | void growing_array_ordered_remove_by_index(void **array, u32 index); 25 | void growing_array_unordered_remove_by_index(void **array, u32 index); 26 | bool growing_array_ordered_remove_by_pointer(void **array, void *p); 27 | bool growing_array_unordered_remove_by_pointer(void **array, void *p); 28 | bool growing_array_ordered_remove_one_by_value(void **array, void *p); 29 | bool growing_array_unordered_remove_one_by_value(void **array, void *p); 30 | 31 | u32 growing_array_get_valid_count(void *array); 32 | u32 growing_array_get_allocated_count(void *array); 33 | 34 | Usage: 35 | 36 | Thing *things; 37 | growing_array_init(&things, sizeof(Thing)); 38 | 39 | growing_array_deinit(&things); 40 | 41 | Thing new_thing; 42 | growing_array_add(&things, &new_thing); // 'thing' is copied 43 | 44 | Thing *nth_thing = &things[n]; 45 | 46 | growing_array_reserve_count(&things, 690); 47 | growing_array_resize_count(&things, 69); 48 | 49 | // "Slow", but stuff in the array keeps the same order 50 | growing_array_ordered_remove_by_index(&things, i); 51 | 52 | // Fast, but will not keep stuff ordered 53 | growing_array_unordered_remove_by_index(&things, i); 54 | 55 | growing_array_ordered_remove_by_pointer(&things, nth_thing); 56 | growing_array_unordered_remove_by_pointer(&things, nth_thing); 57 | 58 | Thing thing_prototype; 59 | growing_array_ordered_remove_one_by_value(&things, thing_prototype); 60 | growing_array_unordered_remove_one_by_value(&things, thing_prototype); 61 | growing_array_ordered_remove_all_by_value(&things, thing_prototype); 62 | growing_array_unordered_remove_all_by_value(&things, thing_prototype); 63 | 64 | growing_array_get_valid_count(&things); 65 | growing_array_get_allocated_count(&things); 66 | 67 | */ 68 | 69 | #define GROWING_ARRAY_SIGNATURE 2224364215 70 | 71 | typedef struct Growing_Array_Header { 72 | u32 signature; 73 | u32 valid_count; 74 | u32 allocated_count; 75 | u32 block_size_in_bytes; 76 | Allocator allocator; 77 | } Growing_Array_Header; 78 | 79 | bool 80 | check_growing_array_signature(void **array) { 81 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 82 | if (header->signature != GROWING_ARRAY_SIGNATURE) return false; 83 | return true; 84 | } 85 | 86 | void 87 | growing_array_init_reserve(void **array, u64 block_size_in_bytes, u64 count_to_reserve, Allocator allocator) { 88 | 89 | count_to_reserve = get_next_power_of_two(count_to_reserve); 90 | u64 bytes_to_allocate = count_to_reserve*block_size_in_bytes + sizeof(Growing_Array_Header); 91 | 92 | Growing_Array_Header *header = (Growing_Array_Header*)alloc(allocator, bytes_to_allocate); 93 | 94 | header->allocator = allocator; 95 | header->block_size_in_bytes = block_size_in_bytes; 96 | header->valid_count = 0; 97 | header->allocated_count = count_to_reserve; 98 | header->signature = GROWING_ARRAY_SIGNATURE; 99 | 100 | *array = header+1; 101 | } 102 | void 103 | growing_array_init(void **array, u64 block_size_in_bytes, Allocator allocator) { 104 | growing_array_init_reserve(array, block_size_in_bytes, 8, allocator); 105 | } 106 | void 107 | growing_array_deinit(void **array) { 108 | assert(check_growing_array_signature(array), "Not a valid growing array"); 109 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 110 | dealloc(header->allocator, header); 111 | } 112 | 113 | void 114 | growing_array_reserve(void **array, u64 count_to_reserve) { 115 | assert(check_growing_array_signature(array), "Not a valid growing array"); 116 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 117 | 118 | if (header->allocated_count >= count_to_reserve) return; 119 | 120 | u64 old_allocated_bytes = header->allocated_count*header->block_size_in_bytes+sizeof(Growing_Array_Header); 121 | count_to_reserve = get_next_power_of_two(count_to_reserve); 122 | u64 bytes_to_allocate = count_to_reserve*header->block_size_in_bytes+sizeof(Growing_Array_Header); 123 | Growing_Array_Header *new_header = (Growing_Array_Header*)alloc(header->allocator, bytes_to_allocate); 124 | 125 | memcpy(new_header, header, old_allocated_bytes); 126 | 127 | 128 | *array = new_header+1; 129 | 130 | new_header->allocated_count = count_to_reserve; 131 | 132 | dealloc(header->allocator, header); 133 | } 134 | 135 | void* 136 | growing_array_add_empty(void **array) { 137 | assert(check_growing_array_signature(array), "Not a valid growing array"); 138 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 139 | growing_array_reserve(array, header->valid_count+1); 140 | 141 | // Pointer might have been invalidated after reserve 142 | header = ((Growing_Array_Header*)*array) - 1; 143 | 144 | void *item = (u8*)*array + header->valid_count*header->block_size_in_bytes; 145 | 146 | header->valid_count += 1; 147 | 148 | return item; 149 | } 150 | void* 151 | growing_array_add_multiple_empty(void **array, u64 count) { 152 | assert(check_growing_array_signature(array), "Not a valid growing array"); 153 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 154 | growing_array_reserve(array, header->valid_count+count); 155 | 156 | // Pointer might have been invalidated after reserve 157 | header = ((Growing_Array_Header*)*array) - 1; 158 | 159 | void *start = (u8*)*array + header->valid_count*header->block_size_in_bytes; 160 | 161 | header->valid_count += count; 162 | 163 | return start; 164 | } 165 | void 166 | growing_array_add(void **array, void *item) { 167 | 168 | void *new = growing_array_add_empty(array); 169 | 170 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 171 | 172 | memcpy(new, item, header->block_size_in_bytes); 173 | } 174 | void 175 | growing_array_add_multiple(void **array, void *items, u64 count) { 176 | 177 | void *start = growing_array_add_multiple_empty(array, count); 178 | 179 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 180 | 181 | memcpy(start, items, header->block_size_in_bytes*count); 182 | } 183 | 184 | void growing_array_resize(void **array, u64 new_count) { 185 | growing_array_reserve(array, new_count); 186 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 187 | header->valid_count = new_count; 188 | } 189 | 190 | void growing_array_pop(void **array) { 191 | assert(check_growing_array_signature(array), "Not a valid growing array"); 192 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 193 | assert(header->valid_count > 0, "No items to pop in growing array"); 194 | header->valid_count -= 1; 195 | } 196 | 197 | void growing_array_clear(void **array) { 198 | assert(check_growing_array_signature(array), "Not a valid growing array"); 199 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 200 | header->valid_count = 0; 201 | } 202 | 203 | void 204 | growing_array_ordered_remove_by_index(void **array, u32 index) { 205 | assert(check_growing_array_signature(array), "Not a valid growing array"); 206 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 207 | assert(index < header->valid_count, "Growing array index out of range"); 208 | 209 | if (index == header->valid_count-1) { 210 | growing_array_pop(array); 211 | return; 212 | } 213 | 214 | u64 byte_index = header->block_size_in_bytes*index; 215 | 216 | memcpy( 217 | (u8*)*array + byte_index, 218 | (u8*)*array + byte_index + header->block_size_in_bytes, 219 | (header->valid_count-index-1)*header->block_size_in_bytes 220 | ); 221 | header->valid_count -= 1; 222 | } 223 | void 224 | growing_array_unordered_remove_by_index(void **array, u32 index) { 225 | assert(check_growing_array_signature(array), "Not a valid growing array"); 226 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 227 | assert(index < header->valid_count, "Growing array index out of range"); 228 | 229 | if (index == header->valid_count-1) { 230 | growing_array_pop(array); 231 | return; 232 | } 233 | 234 | u64 byte_index = header->block_size_in_bytes*index; 235 | u64 last_item_index = header->block_size_in_bytes*header->valid_count-header->block_size_in_bytes; 236 | 237 | memcpy( 238 | (u8*)*array + byte_index, 239 | (u8*)*array + last_item_index, 240 | header->block_size_in_bytes 241 | ); 242 | header->valid_count -= 1; 243 | } 244 | 245 | s32 246 | growing_array_find_index_from_left_by_pointer(void **array, void *p) { 247 | assert(check_growing_array_signature(array), "Not a valid growing array"); 248 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 249 | 250 | for (u32 i = 0; i < header->valid_count; i++) { 251 | void *next = (u8*)*array + i*header->block_size_in_bytes; 252 | 253 | if (next == p) { 254 | return i; 255 | } 256 | } 257 | return -1; 258 | } 259 | s32 260 | growing_array_find_index_from_left_by_value(void **array, void *p) { 261 | assert(check_growing_array_signature(array), "Not a valid growing array"); 262 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 263 | 264 | for (u32 i = 0; i < header->valid_count; i++) { 265 | void *next = (u8*)*array + i*header->block_size_in_bytes; 266 | 267 | if (bytes_match(next, p, header->block_size_in_bytes)) { 268 | return i; 269 | } 270 | } 271 | return -1; 272 | } 273 | 274 | bool 275 | growing_array_ordered_remove_by_pointer(void **array, void *p) { 276 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 277 | 278 | s32 i = growing_array_find_index_from_left_by_pointer(array, p); 279 | 280 | if (i < 0) return false; 281 | 282 | growing_array_ordered_remove_by_index(array, i); 283 | 284 | return true; 285 | } 286 | bool 287 | growing_array_unordered_remove_by_pointer(void **array, void *p) { 288 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 289 | 290 | s32 i = growing_array_find_index_from_left_by_pointer(array, p); 291 | 292 | if (i < 0) return false; 293 | 294 | growing_array_unordered_remove_by_index(array, i); 295 | 296 | return true; 297 | } 298 | bool 299 | growing_array_ordered_remove_one_by_value(void **array, void *p) { 300 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 301 | 302 | s32 i = growing_array_find_index_from_left_by_value(array, p); 303 | 304 | if (i < 0) return false; 305 | 306 | growing_array_ordered_remove_by_index(array, i); 307 | 308 | return true; 309 | } 310 | bool 311 | growing_array_unordered_remove_one_by_value(void **array, void *p) { 312 | Growing_Array_Header *header = ((Growing_Array_Header*)*array) - 1; 313 | 314 | s32 i = growing_array_find_index_from_left_by_value(array, p); 315 | 316 | if (i < 0) return false; 317 | 318 | growing_array_unordered_remove_by_index(array, i); 319 | 320 | return true; 321 | } 322 | 323 | // #Incomplete 324 | // s32 growing_array_ordered_remove_one_by_value(void **array, void *p) 325 | // s32 growing_array_unordered_remove_one_by_value(void **array, void *p) 326 | 327 | u32 328 | growing_array_get_valid_count(void *array) { 329 | assert(check_growing_array_signature(&array), "Not a valid growing array"); 330 | Growing_Array_Header *header = ((Growing_Array_Header*)array) - 1; 331 | return header->valid_count; 332 | } 333 | u32 334 | growing_array_get_allocated_count(void *array) { 335 | assert(check_growing_array_signature(&array), "Not a valid growing array"); 336 | Growing_Array_Header *header = ((Growing_Array_Header*)array) - 1; 337 | return header->allocated_count; 338 | } -------------------------------------------------------------------------------- /oogabooga/hash.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define PRIME64_1 11400714785074694791ULL 4 | #define PRIME64_2 14029467366897019727ULL 5 | #define PRIME64_3 1609587929392839161ULL 6 | #define PRIME64_4 9650029242287828579ULL 7 | #define PRIME64_5 2870177450012600261ULL 8 | 9 | static inline u64 xx_hash(u64 x) { 10 | u64 h64 = PRIME64_5 + 8; 11 | h64 += x * PRIME64_3; 12 | h64 = ((h64 << 23) | (h64 >> (64 - 23))) * PRIME64_2 + PRIME64_4; 13 | h64 ^= h64 >> 33; 14 | h64 *= PRIME64_2; 15 | h64 ^= h64 >> 29; 16 | h64 *= PRIME64_3; 17 | h64 ^= h64 >> 32; 18 | return h64; 19 | } 20 | 21 | static inline u64 city_hash(string s) { 22 | const u64 k = 0x9ddfea08eb382d69ULL; 23 | u64 a = s.count; 24 | u64 b = s.count * 5; 25 | u64 c = 9; 26 | u64 d = b; 27 | 28 | if (s.count <= 16) { 29 | memcpy(&a, s.data, sizeof(u64)); 30 | memcpy(&b, s.data + s.count - 8, sizeof(u64)); 31 | } else { 32 | memcpy(&a, s.data, sizeof(u64)); 33 | memcpy(&b, s.data + 8, sizeof(u64)); 34 | memcpy(&c, s.data + s.count - 8, sizeof(u64)); 35 | memcpy(&d, s.data + s.count - 16, sizeof(u64)); 36 | } 37 | 38 | a += b; 39 | a = (a << 43) | (a >> (64 - 43)); 40 | a += c; 41 | a = a * 5 + 0x52dce729; 42 | d ^= a; 43 | d = (d << 44) | (d >> (64 - 44)); 44 | d += b; 45 | 46 | return d * k; 47 | } 48 | 49 | u64 djb2_hash(string s) { 50 | u64 hash = 5381; 51 | for (u64 i = 0; i < s.count; i++) { 52 | hash = ((hash << 5) + hash) + s.data[i]; 53 | } 54 | return hash; 55 | } 56 | 57 | u64 string_get_hash(string s) { 58 | if (s.count > 32) return djb2_hash(s); 59 | return city_hash(s); 60 | } 61 | u64 pointer_get_hash(void *p) { 62 | return xx_hash((u64)p); 63 | } 64 | u64 float64_get_hash(float64 x) { 65 | return xx_hash(*(u64*)&x); 66 | } 67 | u64 float32_get_hash(float32 x) { 68 | return float64_get_hash((float64)x); 69 | } 70 | 71 | #define get_hash(x) _Generic((x), \ 72 | string: string_get_hash, \ 73 | s8: xx_hash, \ 74 | u8: xx_hash, \ 75 | s16: xx_hash, \ 76 | u16: xx_hash, \ 77 | s32: xx_hash, \ 78 | u32: xx_hash, \ 79 | s64: xx_hash, \ 80 | u64: xx_hash, \ 81 | f32: float32_get_hash, \ 82 | f64: float64_get_hash, \ 83 | default: pointer_get_hash \ 84 | )(x) -------------------------------------------------------------------------------- /oogabooga/hash_table.c: -------------------------------------------------------------------------------- 1 | 2 | // Very naive implementation but it should be very cache efficient so it's alright 3 | // for non-excessive use for now. 4 | 5 | /* 6 | 7 | Example Usage: 8 | 9 | 10 | // Make a table with key type 'string' and value type 'int', allocated on the heap 11 | Hash_Table table = make_hash_table(string, int, get_heap_allocator()); 12 | 13 | // Set key "Key string" to integer value 69. This returns whether or not key was newly added. 14 | string key = STR("Key string"); 15 | bool newly_added = hash_table_set(&table, key, 69); 16 | 17 | // Find value associated with given key. Returns pointer to that value. 18 | string other_key = STR("Some other key"); 19 | int* value = hash_table_find(&table, other_key); 20 | 21 | if (value) { 22 | // Pointer is OK, item with key exists 23 | } else { 24 | // Pointer is null, item with key does NOT exist 25 | } 26 | 27 | // Same as hash_table_find() != NULL 28 | string another_key = STR("Another key"); 29 | if (hash_table_contains(&table, another_key)) { 30 | 31 | } 32 | 33 | // Reset all entries (but keep allocated memory) 34 | hash_table_reset(&table); 35 | 36 | // Free allocated entries in hash table 37 | hash_table_destroy(&table); 38 | 39 | 40 | Limitations: 41 | - Key can only be a base type or pointer 42 | - Key and value passed to the following function needs to be lvalues (we need to be able to take their addresses with '&'): 43 | - hash_table_add 44 | - hash_table_find 45 | - hash_table_contains 46 | - hash_table_set 47 | 48 | Example: 49 | 50 | hash_table_set(&table, my_key+5, my_value+3); // ERROR 51 | 52 | int key = my_key+5; 53 | int value = my_value+3; 54 | hash_table_set(&table, key, value); // OK 55 | 56 | 57 | */ 58 | 59 | typedef struct Hash_Table Hash_Table; 60 | 61 | // API: 62 | #define make_hash_table_reserve(Key_Type, Value_Type, capacity_count, allocator) \ 63 | make_hash_table_reserve(sizeof(Key_Type), sizeof(Value_Type), capacity_count, allocator) 64 | 65 | #define make_hash_table(Key_Type, Value_Type, allocator) \ 66 | make_hash_table_raw(sizeof(Key_Type), sizeof(Value_Type), allocator) 67 | 68 | #define hash_table_add(table_ptr, key, value) \ 69 | hash_table_add_raw((table_ptr), get_hash(key), &(key), &(value), sizeof(key), sizeof(value)) 70 | 71 | #define hash_table_find(table_ptr, key) \ 72 | hash_table_find_raw((table_ptr), get_hash(key)) 73 | 74 | #define hash_table_contains(table_ptr, key) \ 75 | hash_table_contains_raw((table_ptr), get_hash(key)) 76 | 77 | #define hash_table_set(table_ptr, key, value) \ 78 | hash_table_set_raw((table_ptr), get_hash(key), &key, &value, sizeof(key), sizeof(value)) 79 | 80 | void hash_table_reserve(Hash_Table *t, u64 required_count); 81 | 82 | 83 | typedef struct Hash_Table { 84 | 85 | // Each entry is hash-key-value 86 | // Hash is sizeof(u64) bytes, key is _key_size bytes and value is _value_size bytes 87 | void *entries; 88 | 89 | u64 count; // Number of valid entries 90 | u64 capacity_count; // Number of allocated entries 91 | 92 | u64 _key_size; 93 | u64 _value_size; 94 | 95 | Allocator allocator; 96 | } Hash_Table; 97 | 98 | Hash_Table make_hash_table_reserve_raw(u64 key_size, u64 value_size, u64 capacity_count, Allocator allocator) { 99 | 100 | capacity_count = min(capacity_count, 8); 101 | 102 | Hash_Table t = ZERO(Hash_Table); 103 | 104 | t._key_size = key_size; 105 | t._value_size = value_size; 106 | t.allocator = allocator; 107 | 108 | u64 entry_size = value_size+sizeof(u64); 109 | t.entries = alloc(t.allocator, entry_size*capacity_count); 110 | memset(t.entries, 0, entry_size*capacity_count); 111 | t.capacity_count = capacity_count; 112 | 113 | return t; 114 | } 115 | inline Hash_Table make_hash_table_raw(u64 key_size, u64 value_size, Allocator allocator) { 116 | return make_hash_table_reserve_raw(key_size, value_size, 128, allocator); 117 | } 118 | 119 | void hash_table_reset(Hash_Table *t) { 120 | t->count = 0; 121 | } 122 | void hash_table_destroy(Hash_Table *t) { 123 | dealloc(t->allocator, t->entries); 124 | 125 | t->entries = 0; 126 | t->count = 0; 127 | t->capacity_count = 0; 128 | } 129 | 130 | void hash_table_reserve(Hash_Table *t, u64 required_count) { 131 | u64 entry_size = t->_value_size+sizeof(u64); 132 | 133 | u64 required_size = required_count*entry_size; 134 | 135 | u64 current_size = t->capacity_count*entry_size; 136 | 137 | if (current_size >= required_size) return; 138 | 139 | u64 new_count = get_next_power_of_two(required_count); 140 | u64 new_size = new_count*entry_size; 141 | 142 | void *new_entries = alloc(t->allocator, new_size); 143 | memcpy(new_entries, t->entries, current_size); 144 | 145 | dealloc(t->allocator, t->entries); 146 | 147 | t->entries = new_entries; 148 | t->capacity_count = new_count; 149 | } 150 | 151 | // This can add multiple entries of same hash, beware! 152 | void hash_table_add_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) { 153 | 154 | assert(t->_key_size == key_size, "Key type size does not match hash table initted key type size"); 155 | assert(t->_value_size == value_size, "Value type size does not match hash table initted value type size"); 156 | 157 | hash_table_reserve(t, t->count+1); 158 | 159 | u64 entry_size = t->_value_size+sizeof(u64); 160 | 161 | u64 index = entry_size*t->count; 162 | t->count += 1; 163 | 164 | u64 hash_offset = 0; 165 | u64 value_offset = hash_offset + sizeof(u64); 166 | 167 | memcpy((u8*)t->entries+index+hash_offset, &hash, sizeof(u64)); 168 | memcpy((u8*)t->entries+index+value_offset, v, value_size); 169 | } 170 | 171 | void *hash_table_find_raw(Hash_Table *t, u64 hash) { 172 | 173 | // #Speed #Incomplete 174 | // Do quadratic probe 'triangular numbers' 175 | 176 | u64 entry_size = t->_value_size+sizeof(u64); 177 | u64 hash_offset = 0; 178 | u64 value_offset = hash_offset + sizeof(u64); 179 | 180 | for (u64 i = 0; i < t->count; i += 1) { 181 | u64 existing_hash = *(u64*)((u8*)t->entries+i*entry_size+hash_offset); 182 | if (existing_hash == hash) { 183 | void *value = ((u8*)t->entries+i*entry_size+value_offset); 184 | return value; 185 | } 186 | } 187 | return 0; 188 | } 189 | 190 | void *hash_table_get_nth_value(Hash_Table *t, u64 n) { 191 | assert(n < t->count, "Hash table n is out of range"); 192 | 193 | u64 entry_size = t->_value_size+sizeof(u64); 194 | u64 hash_offset = 0; 195 | u64 value_offset = hash_offset + sizeof(u64); 196 | 197 | return (u8*)t->entries+entry_size*n+value_offset; 198 | } 199 | 200 | bool hash_table_contains_raw(Hash_Table *t, u64 hash) { 201 | return hash_table_find_raw(t, hash) != 0; 202 | } 203 | 204 | // Returns true if key was newly added or false if it already existed 205 | bool hash_table_set_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) { 206 | bool newly_added = true; 207 | 208 | if (hash_table_contains_raw(t, hash)) newly_added = false; 209 | 210 | if (newly_added) { 211 | hash_table_add_raw(t, hash, k, v, key_size, value_size); 212 | } else { 213 | memcpy(hash_table_find_raw(t, hash), v, value_size); 214 | } 215 | 216 | return newly_added; 217 | } 218 | 219 | -------------------------------------------------------------------------------- /oogabooga/input.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Main input juice: 4 | 5 | bool is_key_down(Input_Key_Code code) 6 | bool is_key_up(Input_Key_Code code) 7 | bool is_key_just_pressed(Input_Key_Code code) 8 | bool is_key_just_released(Input_Key_Code code) 9 | 10 | bool consume_key_down(Input_Key_Code code) 11 | bool consume_key_just_pressed(Input_Key_Code code) 12 | bool consume_key_just_released(Input_Key_Code code) 13 | 14 | To loop through events this frame: 15 | 16 | for (u64 i = 0; i < input_frame.number_of_events; i++) { 17 | Input_Event e = input_frame.events[i]; 18 | 19 | switch (e.kind) { 20 | case INPUT_EVENT_KEY: ...; break; 21 | case INPUT_EVENT_SCROLL: ...; break; 22 | case INPUT_EVENT_TEXT: ...; break; 23 | case INPUT_EVENT_GAMEPAD_AXIS: ...; break; 24 | } 25 | } 26 | 27 | Gamepad input: 28 | 29 | (!! Only Xbox gamepads are natively supported. Steam will map playstation controllers to xbox, but you need third party software to map playstation controllers to xbox. See DS4Windows. !!) 30 | 31 | Vector2 input_frame.left_stick 32 | Vector2 input_frame.right_stick 33 | float32 input_frame.left_trigger 34 | float32 input_frame.right_trigger 35 | 36 | Gamepad buttons are treated as regular keys. 37 | 38 | Example: 39 | 40 | is_key_just_pressed(GAMEPAD_A); 41 | is_key_down(GAMEPAD_DPAD_LEFT); 42 | 43 | To handle multiple gamepads, you need to poll input events and use the event.gamepad_index. 44 | 45 | For gamepad buttons: INPUT_EVENT_KEY 46 | For gamepad axes: INPUT_EVENT_GAMEPAD_AXIS 47 | 48 | Set axis deadzones with global variables: 49 | Vector2 deadzone_left_stick 50 | Vector2 deadzone_right_stick 51 | float32 deadzone_left_trigger 52 | float32 deadzone_right_trigger 53 | 54 | Set vibration with: 55 | set_gamepad_vibration(float32 left, float32 right) 56 | set_specific_gamepad_vibration(int gamepad_index, float32 left, float32 right) 57 | 58 | see input_example.c for example code in practice 59 | */ 60 | 61 | // #Global 62 | forward_global const u64 MAX_NUMBER_OF_GAMEPADS; // Defined in os layer 63 | 64 | typedef enum Input_Event_Kind { 65 | INPUT_EVENT_KEY, 66 | INPUT_EVENT_SCROLL, 67 | INPUT_EVENT_TEXT, 68 | INPUT_EVENT_GAMEPAD_AXIS, 69 | } Input_Event_Kind; 70 | 71 | typedef enum Input_Key_Code { 72 | KEY_UNKNOWN = 0, 73 | 74 | // Non-textual keys that have placements in the ASCII table 75 | // (and thus in Unicode): 76 | 77 | KEY_BACKSPACE = 8, 78 | KEY_TAB = 9, 79 | KEY_ENTER = 13, 80 | KEY_ESCAPE = 27, 81 | KEY_SPACEBAR = 32, 82 | 83 | // The letters A-Z live in here as well and may be returned 84 | // by keyboard events. 85 | 86 | KEY_DELETE = 127, 87 | 88 | KEY_ARROW_UP = 128, 89 | KEY_ARROW_DOWN = 129, 90 | KEY_ARROW_LEFT = 130, 91 | KEY_ARROW_RIGHT = 131, 92 | 93 | KEY_PAGE_UP = 132, 94 | KEY_PAGE_DOWN = 133, 95 | 96 | KEY_HOME = 134, 97 | KEY_END = 135, 98 | 99 | KEY_INSERT = 136, 100 | 101 | KEY_PAUSE = 137, 102 | KEY_SCROLL_LOCK = 138, 103 | 104 | KEY_ALT, 105 | KEY_CTRL, 106 | KEY_SHIFT, 107 | KEY_CMD, 108 | KEY_META = KEY_CMD, 109 | 110 | KEY_F1, 111 | KEY_F2, 112 | KEY_F3, 113 | KEY_F4, 114 | KEY_F5, 115 | KEY_F6, 116 | KEY_F7, 117 | KEY_F8, 118 | KEY_F9, 119 | KEY_F10, 120 | KEY_F11, 121 | KEY_F12, 122 | 123 | KEY_PRINT_SCREEN, 124 | 125 | GAMEPAD_DPAD_UP, 126 | GAMEPAD_DPAD_RIGHT, 127 | GAMEPAD_DPAD_DOWN, 128 | GAMEPAD_DPAD_LEFT, 129 | 130 | GAMEPAD_A, 131 | GAMEPAD_X, 132 | GAMEPAD_Y, 133 | GAMEPAD_B, 134 | 135 | GAMEPAD_START, 136 | GAMEPAD_BACK, 137 | 138 | GAMEPAD_LEFT_STICK, 139 | GAMEPAD_RIGHT_STICK, 140 | 141 | GAMEPAD_LEFT_BUMPER, 142 | GAMEPAD_RIGHT_BUMPER, 143 | GAMEPAD_LEFT_TRIGGER, 144 | GAMEPAD_RIGHT_TRIGGER, 145 | 146 | MOUSE_BUTTON_LEFT, 147 | MOUSE_BUTTON_MIDDLE, 148 | MOUSE_BUTTON_RIGHT, 149 | 150 | GAMEPAD_FIRST = GAMEPAD_START, 151 | GAMEPAD_LAST = GAMEPAD_RIGHT_TRIGGER, 152 | 153 | MOUSE_FIRST = MOUSE_BUTTON_LEFT, 154 | MOUSE_LAST = MOUSE_BUTTON_RIGHT, 155 | 156 | INPUT_KEY_CODE_COUNT 157 | } Input_Key_Code; 158 | 159 | typedef enum Input_State_Flags { 160 | INPUT_STATE_DOWN = 1<<0, 161 | INPUT_STATE_JUST_PRESSED = 1<<1, 162 | INPUT_STATE_JUST_RELEASED = 1<<2, 163 | INPUT_STATE_REPEAT = 1<<3, 164 | } Input_State_Flags; 165 | 166 | typedef enum Input_Axis_Flags { 167 | INPUT_AXIS_LEFT_STICK = 1<<0, 168 | INPUT_AXIS_RIGHT_STICK = 1<<1, 169 | INPUT_AXIS_LEFT_TRIGGER = 1<<2, 170 | INPUT_AXIS_RIGHT_TRIGGER = 1<<3, 171 | } Input_Axis_Flags; 172 | 173 | typedef struct Input_Event { 174 | Input_Event_Kind kind; 175 | 176 | // For INPUT_EVENT_KEY 177 | Input_Key_Code key_code; 178 | Input_State_Flags key_state; 179 | // Also for INPUT_EVENT_GAMEPAD_AXIS 180 | s64 gamepad_index; // -1 if key is not from a gamepad 181 | 182 | // For INPUT_EVENT_SCROLL 183 | float64 xscroll; 184 | float64 yscroll; 185 | 186 | // For INPUT_EVENT_TEXT_INPUT 187 | union { u32 utf32; char ascii; }; 188 | 189 | // For INPUT_EVENT_GAMEPAD_AXIS 190 | // Only the changed axes have valid values 191 | Input_Axis_Flags axes_changed; 192 | Vector2 left_stick; 193 | Vector2 right_stick; 194 | float32 left_trigger; 195 | float32 right_trigger; 196 | 197 | } Input_Event; 198 | 199 | Input_Key_Code os_key_to_key_code(void* os_key); 200 | void* key_code_to_os_key(Input_Key_Code key_code); 201 | 202 | #define MAX_EVENTS_PER_FRAME 10000 203 | typedef struct Input_Frame { 204 | Input_Event events[MAX_EVENTS_PER_FRAME]; 205 | u64 number_of_events; 206 | 207 | float32 mouse_x; 208 | float32 mouse_y; 209 | 210 | // Gamepad 211 | // If you need to deal with more than 1 gamepad, use INPUT_EVENT_GAMEPAD_AXIS 212 | Vector2 left_stick; 213 | Vector2 right_stick; 214 | float32 left_trigger; 215 | float32 right_trigger; 216 | 217 | Input_State_Flags key_states[INPUT_KEY_CODE_COUNT]; 218 | 219 | } Input_Frame; 220 | 221 | // #Global 222 | ogb_instance Input_Frame input_frame; 223 | ogb_instance Vector2 deadzone_left_stick; 224 | ogb_instance Vector2 deadzone_right_stick; 225 | ogb_instance float32 deadzone_left_trigger; 226 | ogb_instance float32 deadzone_right_trigger; 227 | 228 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 229 | Input_Frame input_frame = ZERO(Input_Frame); 230 | Vector2 deadzone_left_stick = {0.2, 0.2}; 231 | Vector2 deadzone_right_stick = {0.2, 0.2}; 232 | float32 deadzone_left_trigger = {0.07}; 233 | float32 deadzone_right_trigger = {0.07}; 234 | #endif 235 | 236 | // 0.0 for no vibration, 1.0 for max vibration 237 | void set_gamepad_vibration(float32 left, float32 right); 238 | void set_specific_gamepad_vibration(u64 gamepad_index, float32 left, float32 right); 239 | 240 | bool has_key_state(Input_Key_Code code, Input_State_Flags flags) { 241 | assert(code > 0 && code < INPUT_KEY_CODE_COUNT, "Invalid key code %d!", code); 242 | Input_State_Flags state = input_frame.key_states[code]; 243 | 244 | #if CONFIGURATION == DEBUG 245 | { 246 | Input_State_Flags impossible = (INPUT_STATE_JUST_RELEASED | INPUT_STATE_DOWN); 247 | assert((impossible & state) != impossible, "Key state for key '%d' is corrupt!", code); 248 | impossible = (INPUT_STATE_JUST_RELEASED | INPUT_STATE_JUST_PRESSED); 249 | assert((impossible & state) != impossible, "Key state for key '%d' is corrupt!", code); 250 | } 251 | #endif 252 | return (state & flags) == flags; 253 | } 254 | bool is_key_down(Input_Key_Code code) { 255 | return has_key_state(code, INPUT_STATE_DOWN); 256 | } 257 | bool is_key_up(Input_Key_Code code) { 258 | return input_frame.key_states[code] == 0 || has_key_state(code, INPUT_STATE_JUST_RELEASED); 259 | } 260 | bool is_key_just_pressed(Input_Key_Code code) { 261 | return has_key_state(code, INPUT_STATE_JUST_PRESSED); 262 | } 263 | bool is_key_just_released(Input_Key_Code code) { 264 | return has_key_state(code, INPUT_STATE_JUST_RELEASED); 265 | } 266 | 267 | bool consume_key_down(Input_Key_Code code) { 268 | bool result = is_key_down(code); 269 | input_frame.key_states[code] &= ~(INPUT_STATE_DOWN); 270 | return result; 271 | } 272 | bool consume_key_just_pressed(Input_Key_Code code) { 273 | bool result = is_key_just_pressed(code); 274 | input_frame.key_states[code] &= ~(INPUT_STATE_JUST_PRESSED); 275 | return result; 276 | } 277 | bool consume_key_just_released(Input_Key_Code code) { 278 | bool result = is_key_just_released(code); 279 | input_frame.key_states[code] &= ~(INPUT_STATE_JUST_RELEASED); 280 | return result; 281 | } -------------------------------------------------------------------------------- /oogabooga/oogabooga.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | CONFIGURATION: 4 | 5 | #define these before including oogabooga.c to configure 6 | 7 | All configuration properties has default values if you do not explicitly #define them. 8 | 9 | - OOGABOOGA_ENABLE_EXTENSIONS 10 | Enable oogabooga extensions. 11 | 12 | 0: Disable 13 | 1: Enable 14 | 15 | Example: 16 | 17 | #define OOGABOOGA_ENABLE_EXTENSIONS 1 18 | 19 | Note: 20 | Your program needs to call ext_update_and_draw() each frame. 21 | 22 | - OOGABOOGA_EXTENSION_PARTICLES 23 | Enable the 'particles' oogabooga extension to use particle emitters in your game. 24 | 25 | 0: Disable 26 | 1: Enable 27 | 28 | Example: 29 | 30 | #define OOGABOOGA_EXTENSION_PARTICLES 1 31 | 32 | 33 | - ENTRY_PROC 34 | Define this as whatever the entry procedure of your program should be. 35 | 36 | Example: 37 | 38 | #define ENTRY_PROC my_entry 39 | void entry_proc(int, char**) { 40 | // ... 41 | } 42 | 43 | Note: 44 | void entry_proc(int, char**); 45 | 46 | - DO_ZERO_INITIALIZATION 47 | 48 | 0: Disable zero initialization 49 | 1: Enable zero initialization 50 | 51 | Example: 52 | 53 | // Disable zero initialization 54 | #define DO_ZERO_INITIALIZATION 0 55 | 56 | Note: 57 | Zero initialization only happens to memory allocated with the alloc() procedure. 58 | 59 | - ENABLE_SIMD 60 | 0: Disable SIMD 61 | 1: Enable SIMD 62 | 63 | Example: 64 | 65 | // Disable simd 66 | #define ENABLE_SIMD 0 67 | 68 | - Simd Extensions 69 | 0: Disable 70 | 1: Enable 71 | 72 | Possible extensions: 73 | SIMD_ENABLE_SSE2 74 | SIMD_ENABLE_SSE41 75 | SIMD_ENABLE_AVX 76 | SIMD_ENABLE_AVX2 77 | SIMD_ENABLE_AVX512 78 | 79 | Example: 80 | // Enable SSE2 Extension 81 | #define SIMD_ENABLE_SSE2 1 82 | 83 | Note: 84 | I recommend that you do not touch this unless you know what you're doing. 85 | These may require you to pass the respective instruction set flag to your 86 | compiler. 87 | For compatilibility reasons, all simd extensions are disabled by default. 88 | 89 | - INITIAL_PROGRAM_MEMORY_SIZE 90 | Defines this as the size in number of bytes you want the initial allocation for 91 | your program memory to be. 92 | This will grow dynamically as needed. 93 | You can use helper macros KB, MB, GB. 94 | 95 | Example: 96 | #define INITIAL_PROGRAM_MEMORY_SIZE (MB(10)) 97 | 98 | Note: 99 | This is not guaranteed to be exactly what you set it to because we have 100 | minimum requirements for example to fit the temporary storage in program 101 | memory. It's more of a rough guideline. 102 | 103 | - RUN_TESTS 104 | Run ooga booga tests. 105 | 106 | 0: Disable 107 | 1: Enable 108 | 109 | Example: 110 | 111 | #define RUN_TESTS 1 112 | 113 | - ENABLE_PROFILING 114 | Enable time profiling which will be dumped to google_trace.json. 115 | 116 | 0: Disable 117 | 1: Enable 118 | 119 | Example: 120 | 121 | #define ENABLE_PROFILING 1 122 | 123 | Note: 124 | See timing macros in profile.c 125 | tm_scope 126 | tm_scope_var 127 | tm_scope_accum 128 | 129 | - OOGABOOGA_HEADLESS 130 | Run oogabooga in headless mode, i.e. no window, no graphics, no audio. 131 | Useful if you only need the oogabooga standard library for something like a game server. 132 | 133 | 0: Disable 134 | 1: Enable 135 | 136 | Example: 137 | 138 | #define OOGABOOGA_HEADLESS 1 139 | */ 140 | 141 | #define OGB_VERSION_MAJOR 0 142 | #define OGB_VERSION_MINOR 1 143 | #define OGB_VERSION_PATCH 9 144 | 145 | #define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH) 146 | 147 | #include 148 | #include 149 | #include 150 | #include 151 | 152 | typedef uint8_t u8; 153 | typedef uint16_t u16; 154 | typedef uint32_t u32; 155 | typedef uint64_t u64; 156 | typedef int8_t s8; 157 | typedef int16_t s16; 158 | typedef int32_t s32; 159 | typedef int64_t s64; 160 | 161 | typedef u8 uint8; 162 | typedef s8 int8; 163 | typedef u16 uint16; 164 | typedef s16 int16; 165 | typedef u32 uint32; 166 | typedef s32 int32; 167 | typedef u64 uint64; 168 | typedef s64 int64; 169 | 170 | typedef float f32; 171 | typedef double f64; 172 | typedef f32 float32; 173 | typedef f64 float64; 174 | 175 | #define F32_MAX 3.402823466e+38F 176 | #define F32_MIN 1.175494351e-38F 177 | 178 | typedef u8 bool; 179 | #define false 0 180 | #define true 1 181 | 182 | 183 | // Determine what compiler we are on 184 | #ifdef __clang__ 185 | #define COMPILER_CLANG 1 186 | #elif defined(__GNUC__) || defined(__GNUG__) 187 | #define COMPILER_GCC 1 188 | #elif defined(_MSC_VER) 189 | #define COMPILER_MSVC 1 190 | #else 191 | #define COMPILER_UNKNOWN 1 192 | #warning "Compiler is not explicitly supported, some things will probably not work as expected" 193 | #endif 194 | 195 | #define DEBUG 0 196 | #define RELEASE 2 197 | 198 | #if defined(NDEBUG) 199 | #define CONFIGURATION RELEASE 200 | #else 201 | #define CONFIGURATION DEBUG 202 | #endif 203 | 204 | 205 | #include "cpu.c" 206 | 207 | 208 | 209 | 210 | #ifndef ENTRY_PROC 211 | #define ENTRY_PROC entry 212 | #endif 213 | 214 | #ifndef DO_ZERO_INITIALIZATION 215 | #define DO_ZERO_INITIALIZATION 1 216 | #endif 217 | 218 | #ifndef ENABLE_SIMD 219 | #define ENABLE_SIMD 1 220 | #endif 221 | 222 | #ifndef INITIAL_PROGRAM_MEMORY_SIZE 223 | #define INITIAL_PROGRAM_MEMORY_SIZE MB(5) 224 | #endif 225 | 226 | #if ENABLE_SIMD && !defined(SIMD_ENABLE_SSE2) 227 | #if COMPILER_CAN_DO_SSE2 228 | #define SIMD_ENABLE_SSE2 1 229 | #else 230 | #define SIMD_ENABLE_SSE2 0 231 | #endif 232 | #endif 233 | #if ENABLE_SIMD && !defined(SIMD_ENABLE_SSE41) 234 | #define SIMD_ENABLE_SSE41 0 235 | #endif 236 | #if ENABLE_SIMD && !defined(SIMD_ENABLE_AVX) 237 | #define SIMD_ENABLE_AVX 0 238 | #endif 239 | #if ENABLE_SIMD && !defined(SIMD_ENABLE_AVX2) 240 | #define SIMD_ENABLE_AVX2 0 241 | #endif 242 | #if ENABLE_SIMD && !defined(SIMD_ENABLE_AVX512) 243 | #define SIMD_ENABLE_AVX512 0 244 | #endif 245 | 246 | 247 | #define WINDOWS 0 248 | #define LINUX 1 249 | #define MACOS 2 250 | 251 | #ifdef _WIN32 252 | #define COBJMACROS 253 | #undef noreturn 254 | #include 255 | #if CONFIGURATION == DEBUG 256 | #include 257 | #endif 258 | #define TARGET_OS WINDOWS 259 | #define OS_PATHS_HAVE_BACKSLASH 1 260 | #elif defined(__linux__) 261 | // Include whatever #Incomplete #Portability 262 | #define TARGET_OS LINUX 263 | #error "Linux is not supported yet"; 264 | #define OS_PATHS_HAVE_BACKSLASH 0 265 | #elif defined(__APPLE__) && defined(__MACH__) 266 | // Include whatever #Incomplete #Portability 267 | #define TARGET_OS MACOS 268 | #error "Macos is not supported yet"; 269 | #define OS_PATHS_HAVE_BACKSLASH 1 270 | #else 271 | #error "Current OS not supported!"; 272 | #endif 273 | 274 | #if OOGABOOGA_LINK_EXTERNAL_INSTANCE 275 | #define ogb_instance SHARED_IMPORT extern 276 | #elif OOGABOOGA_BUILD_SHARED_LIBRARY 277 | #define ogb_instance SHARED_EXPORT 278 | #else 279 | #define ogb_instance 280 | #endif 281 | 282 | 283 | // This needs to be included before dependencies 284 | #include "base.c" 285 | 286 | #include "simd.c" 287 | 288 | // #Incomplete 289 | // We might want to make this configurable ? 290 | #define GFX_RENDERER_D3D11 0 291 | #define GFX_RENDERER_VULKAN 1 292 | #define GFX_RENDERER_METAL 2 293 | #ifndef GFX_RENDERER 294 | // #Portability 295 | #if TARGET_OS == WINDOWS 296 | #define GFX_RENDERER GFX_RENDERER_D3D11 297 | #elif TARGET_OS == LINUX 298 | #define GFX_RENDERER GFX_RENDERER_VULKAN 299 | #elif TARGET_OS == MACOS 300 | #define GFX_RENDERER GFX_RENDERER_METAL 301 | #endif 302 | #endif 303 | 304 | 305 | #include "string.c" 306 | #include "unicode.c" 307 | #include "string_format.c" 308 | #include "hash.c" 309 | #include "path_utils.c" 310 | #include "utility.c" 311 | #include "linmath.c" 312 | 313 | #include "hash_table.c" 314 | #include "growing_array.c" 315 | 316 | #include "os_interface.c" 317 | 318 | /// 319 | /// 320 | // Dependencies 321 | /// 322 | // The reason dependencies are compiled here is because we need to modify third party code 323 | // to use the oogabooga standard where they use the C standard. 324 | 325 | #include "third_party.c" 326 | 327 | ///// 328 | 329 | #include "concurrency.c" 330 | 331 | #include "profiling.c" 332 | #include "random.c" 333 | #include "color.c" 334 | #include "memory.c" 335 | #include "input.c" 336 | 337 | #ifndef OOGABOOGA_HEADLESS 338 | 339 | #include "gfx_interface.c" 340 | 341 | #include "font.c" 342 | 343 | #include "drawing.c" 344 | 345 | #include "audio.c" 346 | #endif 347 | 348 | #if OOGABOOGA_ENABLE_EXTENSIONS 349 | 350 | #include "extensions.c" 351 | #endif 352 | 353 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 354 | 355 | #if TARGET_OS == WINDOWS 356 | #include "os_impl_windows.c" 357 | #elif TARGET_OS == LINUX 358 | #include "os_impl_linux.c" 359 | #elif TARGET_OS == MACOS 360 | #error "Macos is not supported yet" 361 | #else 362 | #error "Current OS is not supported" 363 | #endif 364 | 365 | #ifndef OOGABOOGA_HEADLESS 366 | // #Portability 367 | #if GFX_RENDERER == GFX_RENDERER_D3D11 368 | #include "gfx_impl_d3d11.c" 369 | #elif GFX_RENDERER == GFX_RENDERER_VULKAN 370 | #error "We only have a D3D11 renderer at the moment" 371 | #elif GFX_RENDERER == GFX_RENDERER_METAL 372 | #error "We only have a D3D11 renderer at the moment" 373 | #else 374 | #error "Unknown renderer GFX_RENDERER defined" 375 | #endif 376 | #endif 377 | 378 | #endif // NOT OOGABOOGA_LINK_EXTERNAL_INSTANCE 379 | 380 | #include "tests.c" 381 | 382 | #define malloc please_use_alloc_for_memory_allocations_instead_of_malloc 383 | #define free please_use_dealloc_for_memory_deallocations_instead_of_free 384 | 385 | Mutex _default_logger_mutex; 386 | bool _default_logger_mutex_initted = false; 387 | void default_logger(Log_Level level, string s) { 388 | 389 | if (!_default_logger_mutex_initted) { 390 | mutex_init(&_default_logger_mutex); 391 | _default_logger_mutex_initted = true; 392 | } 393 | 394 | mutex_acquire_or_wait(&_default_logger_mutex); 395 | switch (level) { 396 | case LOG_VERBOSE: print("[VERBOSE]: %s\n", s); break; 397 | case LOG_INFO: print("[INFO]: %s\n", s); break; 398 | case LOG_WARNING: print("[WARNING]: %s\n", s); break; 399 | case LOG_ERROR: print("[ERROR]: %s\n", s); break; 400 | case LOG_LEVEL_COUNT: break; 401 | } 402 | mutex_release(&_default_logger_mutex); 403 | } 404 | 405 | ogb_instance void oogabooga_init(u64 program_memory_size); 406 | 407 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 408 | void oogabooga_init(u64 program_memory_size) { 409 | seed_for_random = rdtsc(); 410 | 411 | context.logger = default_logger; 412 | temp_allocator = get_initialization_allocator(); 413 | Cpu_Capabilities features = query_cpu_capabilities(); 414 | os_init(program_memory_size); 415 | heap_init(); 416 | temporary_storage_init(TEMPORARY_STORAGE_SIZE); 417 | log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH); 418 | #ifndef OOGABOOGA_HEADLESS 419 | gfx_init(); 420 | #else 421 | log_info("Headless mode on"); 422 | #endif 423 | 424 | #if OOGABOOGA_ENABLE_EXTENSIONS 425 | ext_init(); 426 | #endif 427 | 428 | 429 | log_verbose("CPU has sse1: %cs", features.sse1 ? "true" : "false"); 430 | log_verbose("CPU has sse2: %cs", features.sse2 ? "true" : "false"); 431 | log_verbose("CPU has sse3: %cs", features.sse3 ? "true" : "false"); 432 | log_verbose("CPU has ssse3: %cs", features.ssse3 ? "true" : "false"); 433 | log_verbose("CPU has sse41: %cs", features.sse41 ? "true" : "false"); 434 | log_verbose("CPU has sse42: %cs", features.sse42 ? "true" : "false"); 435 | log_verbose("CPU has avx: %cs", features.avx ? "true" : "false"); 436 | log_verbose("CPU has avx2: %cs", features.avx2 ? "true" : "false"); 437 | log_verbose("CPU has avx512: %cs", features.avx512 ? "true" : "false"); 438 | 439 | Os_Monitor *m = os.primary_monitor; 440 | log_verbose("Primary Monitor:\n\t%s\n\t%dhz\n\t%dx%d\n\tdpi: %d", m->name, m->refresh_rate, m->resolution_x, m->resolution_y, m->dpi); 441 | } 442 | #endif 443 | 444 | int ENTRY_PROC(int argc, char **argv); 445 | 446 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 447 | 448 | #if OOGABOOGA_BUILD_SHARED_LIBRARY 449 | int SHARED_EXPORT main(int argc, char **argv) { 450 | #else 451 | int main(int argc, char **argv) { 452 | #endif 453 | 454 | 455 | print("Ooga booga program started\n"); 456 | oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE); 457 | 458 | assert(sizeof(Vector3) == 12, "%d", sizeof(Vector3)); 459 | assert(sizeof(Vector2) == 8 , "%d", sizeof(Vector2)); 460 | assert(sizeof(Vector4) == 16, "%d", sizeof(Vector4)); 461 | 462 | assert(main != ENTRY_PROC, "You've ooga'd your last booga"); 463 | 464 | #if RUN_TESTS 465 | oogabooga_run_tests(); 466 | #endif 467 | 468 | int code = ENTRY_PROC(argc, argv); 469 | 470 | #if ENABLE_PROFILING 471 | 472 | dump_profile_result(); 473 | 474 | #endif 475 | 476 | // This is so any threads waiting for window to close will close on exit 477 | window.should_close = true; 478 | 479 | printf("Ooga booga program exit with code %i\n", code); 480 | 481 | return code; 482 | } 483 | #endif 484 | 485 | 486 | 487 | 488 | // I hope whoever caused this @ microsoft is fired. 489 | #ifdef near 490 | #undef near 491 | #endif 492 | #ifdef far 493 | #undef far 494 | #endif -------------------------------------------------------------------------------- /oogabooga/path_utils.c: -------------------------------------------------------------------------------- 1 | 2 | // in "dir/file.ext" this returns "ext". Returns an empty string if there is no extension 3 | string get_file_extension(string path) { 4 | 5 | if (path.count <= 0) return ZERO(string); 6 | 7 | for (s64 i = path.count-1; i >= 0; i--) { 8 | 9 | if (path.data[i] == '/' || path.data[i] == '\\' || path.data[i] == ':') { 10 | return ZERO(string); 11 | } 12 | 13 | if (path.data[i] == '.') { 14 | string ext = ZERO(string); 15 | ext.count = path.count-i; 16 | if (ext.count > 0) ext.data = path.data+i; 17 | return ext; 18 | } 19 | } 20 | 21 | return ZERO(string); 22 | } 23 | 24 | string get_file_name_including_extension(string file_path) { 25 | if (file_path.count <= 0) return ZERO(string); 26 | 27 | s64 last_separator = -1; 28 | for (s64 i = file_path.count - 1; i >= 0; i--) { 29 | if (file_path.data[i] == '/' || file_path.data[i] == '\\' || file_path.data[i] == ':') { 30 | last_separator = i; 31 | break; 32 | } 33 | } 34 | 35 | string file_name = ZERO(string); 36 | if (last_separator != -1 && last_separator < file_path.count - 1) { 37 | file_name.data = file_path.data + last_separator + 1; 38 | file_name.count = file_path.count - last_separator - 1; 39 | } else { 40 | file_name = file_path; // If no separator was found, assume entire path is a file name. 41 | } 42 | 43 | return file_name; 44 | } 45 | string get_file_name_excluding_extension(string file_path) { 46 | string file_name = get_file_name_including_extension(file_path); 47 | 48 | for (s64 i = file_name.count-1; i >= 1; i--) { 49 | if (file_name.data[i] == '.') { 50 | return string_view(file_name, 0, i); 51 | } 52 | } 53 | return file_name; 54 | } 55 | 56 | string get_directory_of(string path) { 57 | if (path.count <= 0) return ZERO(string); 58 | 59 | for (u64 i = path.count; i >= 0; i--) { 60 | if (path.data[i] == '/' || path.data[i] == '\\' || path.data[i] == ':') { 61 | return string_view(path, 0, i); 62 | } 63 | } 64 | 65 | return ZERO(string); 66 | } -------------------------------------------------------------------------------- /oogabooga/profiling.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | // #Global 4 | ogb_instance String_Builder _profile_output; 5 | ogb_instance bool profiler_initted; 6 | ogb_instance Spinlock _profiler_lock; 7 | 8 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 9 | String_Builder _profile_output = {0}; 10 | bool profiler_initted = false; 11 | Spinlock _profiler_lock; 12 | #endif 13 | 14 | void dump_profile_result() { 15 | File file = os_file_open("google_trace.json", O_CREATE | O_WRITE); 16 | 17 | os_file_write_string(file, STR("[")); 18 | os_file_write_string(file, _profile_output.result); 19 | os_file_write_string(file, STR("{}]")); 20 | 21 | os_file_close(file); 22 | 23 | log_verbose("Wrote profiling result to google_trace.json"); 24 | } 25 | void _profiler_report_time(string name, f64 count, f64 start) { 26 | if (!profiler_initted) { 27 | spinlock_init(&_profiler_lock); 28 | profiler_initted = true; 29 | 30 | string_builder_init_reserve(&_profile_output, 1024*1000, get_heap_allocator()); 31 | 32 | } 33 | 34 | spinlock_acquire_or_wait(&_profiler_lock); 35 | 36 | string fmt = STR("{\"cat\":\"function\",\"dur\":%.3f,\"name\":\"%s\",\"ph\":\"X\",\"pid\":0,\"tid\":%zu,\"ts\":%.3f},"); 37 | string_builder_print( 38 | &_profile_output, 39 | fmt, 40 | (float64)(count * 1000000), 41 | name, 42 | get_context().thread_id, 43 | start * 1000000 44 | ); 45 | spinlock_release(&_profiler_lock); 46 | } 47 | #if ENABLE_PROFILING 48 | #define tm_scope(name) \ 49 | for (f64 start_time = os_get_elapsed_seconds(), end_time = start_time, elapsed_time = 0; \ 50 | elapsed_time == 0; \ 51 | elapsed_time = (end_time = os_get_elapsed_seconds()) - start_time, _profiler_report_time(STR(name), elapsed_time, start_time)) 52 | #define tm_scope_var(name, var) \ 53 | for (f64 start_time = os_get_elapsed_seconds(), end_time = start_time, elapsed_time = 0; \ 54 | elapsed_time == 0; \ 55 | elapsed_time = (end_time = os_get_elapsed_seconds()) - start_time, var=elapsed_time) 56 | #define tm_scope_accum(name, var) \ 57 | for (f64 start_time = os_get_elapsed_seconds(), end_time = start_time, elapsed_time = 0; \ 58 | elapsed_time == 0; \ 59 | elapsed_time = (end_time = os_get_elapsed_seconds()) - start_time, var+=elapsed_time) 60 | #else 61 | #define tm_scope(...) 62 | #define tm_scope_var(...) 63 | #define tm_scope_accum(...) 64 | #endif -------------------------------------------------------------------------------- /oogabooga/random.c: -------------------------------------------------------------------------------- 1 | // LCG 2 | // Meh distribution, but good enough for general purposes 3 | 4 | #define RAND_MAX_64 0xFFFFFFFFFFFFFFFFull 5 | #define MULTIPLIER 6364136223846793005ull 6 | #define INCREMENT 1442695040888963407ull 7 | 8 | // #Global 9 | // set this to something like rtdsc() for very randomized seed 10 | ogb_instance thread_local u64 seed_for_random; 11 | 12 | #if !OOGABOOGA_LINK_EXTERNAL_INSTANCE 13 | thread_local u64 seed_for_random = 1; 14 | #endif 15 | 16 | // Like get_random but it doesn't advance the seed 17 | u64 peek_random() { 18 | return seed_for_random * MULTIPLIER + INCREMENT; 19 | } 20 | 21 | u64 get_random() { 22 | seed_for_random = peek_random(); 23 | return seed_for_random; 24 | } 25 | 26 | f32 get_random_float32() { 27 | return (float32)get_random()/(float32)UINT64_MAX; 28 | } 29 | 30 | f64 get_random_float64() { 31 | return (float64)get_random()/(float64)UINT64_MAX; 32 | } 33 | 34 | 35 | f32 get_random_float32_in_range(f32 min, f32 max) { 36 | return (max-min)*get_random_float32()+min; 37 | } 38 | f64 get_random_float64_in_range(f64 min, f64 max) { 39 | return (max-min)*get_random_float64()+min; 40 | } 41 | 42 | // in_range max is --->INCLUSIVE<--- 43 | s64 get_random_int_in_range(s64 min, s64 max) { 44 | if (min == max) return 0; 45 | if (min > max) swap(min, max, s64); 46 | 47 | return min + (s64)(get_random() % (max - min + 1)); 48 | } -------------------------------------------------------------------------------- /oogabooga/string.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | 5 | 6 | */ 7 | 8 | ogb_instance void* talloc(u64); 9 | 10 | typedef struct string { 11 | u64 count; 12 | u8 *data; 13 | } string; 14 | 15 | const string null_string = {0, 0}; 16 | 17 | #define fixed_string STR 18 | #define STR(s) ((string){ length_of_null_terminated_string((const char*)s), (u8*)s }) 19 | 20 | inline u64 21 | length_of_null_terminated_string(const char* cstring) { 22 | u64 len = 0; 23 | while (*cstring != 0) { 24 | len += 1; 25 | cstring += 1; 26 | } 27 | return len; 28 | } 29 | 30 | string 31 | alloc_string(Allocator allocator, u64 count) { 32 | string s; 33 | s.count = count; 34 | s.data = cast(u8*)alloc(allocator, count); 35 | return s; 36 | } 37 | void 38 | dealloc_string(Allocator allocator, string s) { 39 | assert(s.count > 0 && s.data, "You tried to deallocate an empty string. That's doesn't make sense."); 40 | dealloc(allocator, s.data); 41 | } 42 | string 43 | talloc_string(u64 count) { 44 | string s = alloc_string(get_temporary_allocator(), count); 45 | return s; 46 | } 47 | 48 | string 49 | string_concat(const string left, const string right, Allocator allocator) { 50 | 51 | if (right.count + left.count == 0) return null_string; 52 | if (left.count == 0) return right; 53 | if (right.count == 0) return left; 54 | 55 | string result; 56 | result.count = left.count + right.count; 57 | result.data = cast(u8*)alloc(allocator, result.count); 58 | memcpy(result.data, left.data, left.count); 59 | memcpy(result.data+left.count, right.data, right.count); 60 | return result; 61 | } 62 | char * 63 | convert_to_null_terminated_string(const string s, Allocator allocator) { 64 | char *cstring = cast(char*)alloc(allocator, s.count+1); 65 | memcpy(cstring, s.data, s.count); 66 | cstring[s.count] = 0; 67 | return cstring; 68 | } 69 | 70 | char * 71 | temp_convert_to_null_terminated_string(const string s) { 72 | char *c = convert_to_null_terminated_string(s, get_temporary_allocator()); 73 | return c; 74 | } 75 | bool 76 | strings_match(string a, string b) { 77 | if (a.count != b.count) return false; 78 | 79 | // Count match, pointer match: they are the same 80 | if (a.data == b.data) return true; 81 | 82 | return memcmp(a.data, b.data, a.count) == 0; 83 | } 84 | 85 | string 86 | string_view(string s, u64 start_index, u64 count) { 87 | if (count == 0) return null_string; 88 | 89 | assert(start_index < s.count, "string_view start_index % out of range for string count %", start_index, s.count); 90 | assert(count > 0, "string_view count must be more than 0"); 91 | assert(start_index + count <= s.count, "string_view start_index + count is out of range"); 92 | 93 | 94 | string result; 95 | result.data = s.data+start_index; 96 | result.count = count; 97 | 98 | return result; 99 | } 100 | 101 | // Returns first index from left where "sub" matches in "s". Returns -1 if no match is found. 102 | s64 103 | string_find_from_left(string s, string sub) { 104 | for (s64 i = 0; i <= s.count-sub.count; i++) { 105 | if (strings_match(string_view(s, i, sub.count), sub)) { 106 | return i; 107 | } 108 | } 109 | 110 | return -1; 111 | } 112 | 113 | // Returns first index from right where "sub" matches in "s" Returns -1 if no match is found. 114 | s64 115 | string_find_from_right(string s, string sub) { 116 | for (s64 i = s.count-sub.count; i >= 0 ; i--) { 117 | if (strings_match(string_view(s, i, sub.count), sub)) { 118 | return i; 119 | } 120 | } 121 | 122 | return -1; 123 | } 124 | 125 | bool 126 | string_starts_with(string s, string sub) { 127 | if (s.count < sub.count) return false; 128 | 129 | s.count = sub.count; 130 | 131 | return strings_match(s, sub); 132 | } 133 | 134 | string 135 | string_copy(string s, Allocator allocator) { 136 | string c = alloc_string(allocator, s.count); 137 | memcpy(c.data, s.data, s.count); 138 | return c; 139 | } 140 | 141 | 142 | typedef struct String_Builder { 143 | union { 144 | struct {u64 count;u8 *buffer;}; 145 | string result; 146 | }; 147 | u64 buffer_capacity; 148 | Allocator allocator; 149 | } String_Builder; 150 | 151 | 152 | void 153 | string_builder_reserve(String_Builder *b, u64 required_capacity) { 154 | if (b->buffer_capacity >= required_capacity) return; 155 | 156 | u64 new_capacity = max(b->buffer_capacity*2, (u64)(required_capacity*1.5)); 157 | u8 *new_buffer = alloc(b->allocator, new_capacity); 158 | if (b->buffer) { 159 | memcpy(new_buffer, b->buffer, b->count); 160 | dealloc(b->allocator, b->buffer); 161 | } 162 | b->buffer = new_buffer; 163 | b->buffer_capacity = new_capacity; 164 | } 165 | void 166 | string_builder_init_reserve(String_Builder *b, u64 reserved_capacity, Allocator allocator) { 167 | reserved_capacity = max(reserved_capacity, 128); 168 | b->allocator = allocator; 169 | b->buffer_capacity = 0; 170 | b->buffer = 0; 171 | string_builder_reserve(b, reserved_capacity); 172 | b->count = 0; 173 | } 174 | void 175 | string_builder_init(String_Builder *b, Allocator allocator) { 176 | string_builder_init_reserve(b, 128, allocator); 177 | } 178 | void 179 | string_builder_deinit(String_Builder *b) { 180 | dealloc(b->allocator, b->buffer); 181 | } 182 | void 183 | string_builder_append(String_Builder *b, string s) { 184 | assert(b->allocator.proc, "String_Builder is missing allocator"); 185 | string_builder_reserve(b, b->count+s.count); 186 | 187 | memcpy(b->buffer+b->count, s.data, s.count); 188 | b->count += s.count; 189 | } 190 | string 191 | string_builder_get_string(String_Builder b) { 192 | return b.result; 193 | } 194 | 195 | 196 | string 197 | string_replace_all(string s, string old, string new, Allocator allocator) { 198 | 199 | if (!s.data || !s.count) return string_copy(null_string, allocator); 200 | 201 | String_Builder builder; 202 | string_builder_init_reserve(&builder, s.count, allocator); 203 | 204 | while (s.count > 0) { 205 | if (s.count >= old.count && strings_match(string_view(s, 0, old.count), old)) { 206 | if (new.count != 0) string_builder_append(&builder, new); 207 | s.data += old.count; 208 | s.count -= old.count; 209 | } else { 210 | string_builder_append(&builder, string_view(s, 0, 1)); 211 | s.data += 1; 212 | s.count -= 1; 213 | } 214 | } 215 | 216 | return string_builder_get_string(builder); 217 | } 218 | 219 | string 220 | string_trim_left(string s) { 221 | while (s.count > 0 && *s.data == ' ') { 222 | s.data += 1; 223 | s.count -= 1; 224 | } 225 | return s; 226 | } 227 | string 228 | string_trim_right(string s) { 229 | 230 | while (s.count > 0 && s.data[s.count-1] == ' ') { 231 | s.count -= 1; 232 | } 233 | return s; 234 | } 235 | string 236 | string_trim(string s) { 237 | s = string_trim_left(s); 238 | return string_trim_right(s); 239 | } -------------------------------------------------------------------------------- /oogabooga/third_party.c: -------------------------------------------------------------------------------- 1 | 2 | // YOINKED FROM https://gist.github.com/LingDong-/7e4c4cae5cbbc44400a05fba65f06f23 3 | // (stb_vorbis uses math.h log() but that collides with oogabooga log() procedure) 4 | ///////////////////////////////////////////////////////////// 5 | // ln.c 6 | // 7 | // simple, fast, accurate natural log approximation 8 | // when without 9 | 10 | // featuring * floating point bit level hacking, 11 | // * x=m*2^p => ln(x)=ln(m)+ln(2)p, 12 | // * Remez algorithm 13 | 14 | // by Lingdong Huang, 2020. Public domain. 15 | 16 | // ============================================ 17 | 18 | float ln(float x) { 19 | unsigned int bx = * (unsigned int *) (&x); 20 | unsigned int ex = bx >> 23; 21 | signed int t = (signed int)ex-(signed int)127; 22 | unsigned int s = (t < 0) ? (-t) : t; 23 | bx = 1065353216 | (bx & 8388607); 24 | x = * (float *) (&bx); 25 | return -1.49278+(2.11263+(-0.729104+0.10969*x)*x)*x+0.6931471806*t; 26 | } 27 | // done. 28 | 29 | 30 | 31 | 32 | 33 | 34 | // ============================================ 35 | // Exact same function, with added comments: 36 | 37 | float natural_log(float x) { 38 | 39 | // ASSUMING: 40 | // - non-denormalized numbers i.e. x > 2^−126 41 | // - integer is 32 bit. float is IEEE 32 bit. 42 | 43 | // INSPIRED BY: 44 | // - https://stackoverflow.com/a/44232045 45 | // - http://mathonweb.com/help_ebook/html/algorithms.htm#ln 46 | // - https://en.wikipedia.org/wiki/Fast_inverse_square_root 47 | 48 | // FORMULA: 49 | // x = m * 2^p => 50 | // ln(x) = ln(m) + ln(2)p, 51 | 52 | // first normalize the value to between 1.0 and 2.0 53 | // assuming normalized IEEE float 54 | // sign exp frac 55 | // 0b 0 [00000000] 00000000000000000000000 56 | // value = (-1)^s * M * 2 ^ (exp-127) 57 | // 58 | // exp = 127 for x = 1, 59 | // so 2^(exp-127) is the multiplier 60 | 61 | // evil floating point bit level hacking 62 | unsigned int bx = * (unsigned int *) (&x); 63 | 64 | // extract exp, since x>0, sign bit must be 0 65 | unsigned int ex = bx >> 23; 66 | signed int t = (signed int)ex-(signed int)127; 67 | unsigned int s = (t < 0) ? (-t) : t; 68 | 69 | // reinterpret back to float 70 | // 127 << 23 = 1065353216 71 | // 0b11111111111111111111111 = 8388607 72 | bx = 1065353216 | (bx & 8388607); 73 | x = * (float *) (&bx); 74 | 75 | 76 | // use remez algorithm to find approximation between [1,2] 77 | // - see this answer https://stackoverflow.com/a/44232045 78 | // - or this usage of C++/boost's remez implementation 79 | // https://computingandrecording.wordpress.com/2017/04/24/ 80 | // e.g. 81 | // boost::math::tools::remez_minimax approx( 82 | // [](const double& x) { return log(x); }, 83 | // 4, 0, 1, 2, false, 0, 0, 64); 84 | // 85 | // 4th order is: 86 | // { -1.74178, 2.82117, -1.46994, 0.447178, -0.0565717 } 87 | // 88 | // 3rd order is: 89 | // { -1.49278, 2.11263, -0.729104, 0.10969 } 90 | 91 | return 92 | 93 | /* less accurate */ 94 | -1.49278+(2.11263+(-0.729104+0.10969*x)*x)*x 95 | 96 | /* OR more accurate */ 97 | // -1.7417939+(2.8212026+(-1.4699568+(0.44717955-0.056570851*x)*x)*x)*x 98 | 99 | /* compensate for the ln(2)s. ln(2)=0.6931471806 */ 100 | + 0.6931471806*t; 101 | } 102 | 103 | 104 | ////////////////////////////////////////////////////////////////////////////// 105 | 106 | 107 | #define STB_TRUETYPE_IMPLEMENTATION 108 | #define STB_IMAGE_IMPLEMENTATION 109 | 110 | typedef unsigned char u8; 111 | typedef signed char s8; 112 | typedef unsigned short u16; 113 | typedef signed short s16; 114 | typedef unsigned int u32; 115 | typedef signed int s32; 116 | 117 | thread_local Allocator third_party_allocator = {0}; 118 | void *third_party_malloc(size_t size) { 119 | assert(third_party_allocator.proc, "No third party allocator was set, but it was used!"); 120 | if (!size) return 0; 121 | return alloc(third_party_allocator, size); 122 | } 123 | void *third_party_realloc(void *p, size_t size) { 124 | assert(third_party_allocator.proc, "No third party allocator was set, but it was used!"); 125 | if (!size) return 0; 126 | if (!p) return third_party_malloc(size); 127 | return third_party_allocator.proc(size, p, ALLOCATOR_REALLOCATE, 0); 128 | } 129 | void third_party_free(void *p) { 130 | assert(third_party_allocator.proc, "No third party allocator was set, but it was used!"); 131 | if (!p) return; 132 | dealloc(third_party_allocator, p); 133 | } 134 | 135 | #define STBTT_malloc(x,u) ((void)(u),third_party_malloc(x)) 136 | #define STBTT_free(x,u) ((void)(u),third_party_free(x)) 137 | #define STBTT_assert(x) assert(x) 138 | size_t stbtt_strlen(const char* str) { 139 | size_t count = 0; 140 | while (str[count] != 0) count += 1; 141 | return count; 142 | } 143 | #define STBTT_strlen(x) stbtt_strlen(x) 144 | #define STBTT_memcpy memcpy 145 | #define STBTT_memset memset 146 | #include "third_party/stb_truetype.h" 147 | 148 | 149 | #define STBI_NO_STDIO 150 | #define STBI_ASSERT(x) {if (!(x)) *(volatile char*)0 = 0;} 151 | #define STBI_MALLOC(sz) third_party_malloc(sz) 152 | #define STBI_REALLOC(p,newsz) third_party_allocator.proc(newsz, p, ALLOCATOR_REALLOCATE, 0) 153 | #define STBI_FREE(p) third_party_free(p) 154 | #include "third_party/stb_image.h" 155 | 156 | #define STB_VORBIS_NO_CRT 157 | #include "third_party/stb_vorbis.c" 158 | // Why Sean ????? 159 | #undef R 160 | #undef C 161 | #undef L 162 | 163 | -------------------------------------------------------------------------------- /oogabooga/unicode.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #define UTF16_SURROGATE_HIGH_START 0xD800 4 | #define UTF16_SURROGATE_HIGH_END 0xDBFF 5 | #define UTF16_SURROGATE_LOW_START 0xDC00 6 | #define UTF16_SURROGATE_LOW_END 0xDFFF 7 | #define UTF16_SURROGATE_OFFSET 0x10000 8 | #define UTF16_SURROGATE_MASK 0x3FF 9 | 10 | // Returns how many utf16 units were converted 11 | int utf16_to_utf32(const u16 *utf16, u64 length, u32 *utf32) { 12 | if (length == 0 || utf16 == NULL || utf32 == NULL) { 13 | return -1; 14 | } 15 | 16 | u16 first = utf16[0]; 17 | 18 | if (first >= UTF16_SURROGATE_HIGH_START && first <= UTF16_SURROGATE_HIGH_END) { 19 | if (length < 2) { 20 | return -1; 21 | } 22 | 23 | u16 second = utf16[1]; 24 | if (second >= UTF16_SURROGATE_LOW_START && second <= UTF16_SURROGATE_LOW_END) { 25 | *utf32 = ((first - UTF16_SURROGATE_HIGH_START) << 10) 26 | + (second - UTF16_SURROGATE_LOW_START) 27 | + UTF16_SURROGATE_OFFSET; 28 | return 2; 29 | } else { 30 | return -1; 31 | } 32 | } else if (first >= UTF16_SURROGATE_LOW_START && first <= UTF16_SURROGATE_LOW_END) { 33 | return -1; 34 | } else { 35 | *utf32 = first; 36 | return 1; 37 | } 38 | } 39 | 40 | // Yoinked from jai Unicode.jai 41 | 42 | #define UNI_REPLACEMENT_CHAR 0x0000FFFD 43 | #define UNI_MAX_UTF32 0x7FFFFFFF 44 | #define UNI_MAX_UTF16 0x0010FFFF 45 | #define SURROGATES_START 0xD800 46 | #define SURROGATES_END 0xDFFF 47 | 48 | typedef struct { 49 | u32 utf32; 50 | s64 continuation_bytes; 51 | bool reached_end; 52 | bool error; 53 | } Utf8_To_Utf32_Result; 54 | 55 | const u8 trailing_bytes_for_utf8[] = { 56 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 57 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 58 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 59 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 60 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 61 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 62 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 63 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 64 | }; 65 | const u8 utf8_inital_byte_mask[] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; 66 | 67 | Utf8_To_Utf32_Result utf8_to_utf32(u8 *s, s64 source_length, bool strict) { 68 | s64 continuation_bytes = trailing_bytes_for_utf8[s[0]]; 69 | 70 | if (continuation_bytes + 1 > source_length) { 71 | return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, source_length, true, true}; 72 | } 73 | 74 | u32 ch = s[0] & utf8_inital_byte_mask[continuation_bytes]; 75 | 76 | for (u64 i = 1; i <= continuation_bytes; i++) { // Do nothing if it is 0. 77 | ch = ch << 6; 78 | if (strict) if ((s[i] & 0xC0) != 0x80) return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, i - 1, true, true}; 79 | ch |= s[i] & 0x3F; 80 | } 81 | 82 | if (strict) { 83 | if (ch > UNI_MAX_UTF16 || 84 | (SURROGATES_START <= ch && ch <= SURROGATES_END) || 85 | (ch <= 0x0000007F && continuation_bytes != 0) || 86 | (ch <= 0x000007FF && continuation_bytes != 1) || 87 | (ch <= 0x0000FFFF && continuation_bytes != 2) || 88 | continuation_bytes > 3) { 89 | return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, continuation_bytes+1, true, true}; 90 | } 91 | } 92 | 93 | if (ch > UNI_MAX_UTF32) { 94 | ch = UNI_REPLACEMENT_CHAR; 95 | } 96 | 97 | return (Utf8_To_Utf32_Result){ ch, continuation_bytes+1, false, false }; 98 | } 99 | 100 | // Returns 0 on fail 101 | u32 next_utf8(string *s) { 102 | Utf8_To_Utf32_Result result = utf8_to_utf32(s->data, s->count, false); 103 | 104 | s->data += result.continuation_bytes; 105 | s->count -= result.continuation_bytes; 106 | assert(s->count >= 0); 107 | 108 | if (result.error) return 0; 109 | 110 | return result.utf32; 111 | } 112 | 113 | u64 utf8_index_to_byte_index(string str, u64 index) { 114 | u64 byte_index = 0; 115 | u64 utf8_index = 0; 116 | while (utf8_index < index && str.count != 0) { 117 | string last_str = str; 118 | u32 codepoint = next_utf8(&str); 119 | if (!codepoint) break; 120 | 121 | u64 byte_diff = ((u8*)str.data)-((u8*)last_str.data); 122 | assert(byte_diff != 0); 123 | byte_index += byte_diff; 124 | utf8_index += 1; 125 | } 126 | return byte_index; 127 | } 128 | string utf8_slice(string str, u64 index, u64 count) { 129 | u64 byte_index = utf8_index_to_byte_index(str, index); 130 | u64 byte_end_index = utf8_index_to_byte_index(str, index+count); 131 | u64 byte_count = byte_end_index - byte_index; 132 | 133 | return string_view(str, byte_index, byte_count); 134 | } 135 | -------------------------------------------------------------------------------- /oogabooga/utility.c: -------------------------------------------------------------------------------- 1 | 2 | #define PI32 3.14159265359f 3 | #define PI64 3.14159265358979323846 4 | #define TAU32 (2.0f * PI32) 5 | #define TAU64 (2.0 * PI64) 6 | #define RAD_PER_DEG (PI64 / 180.0) 7 | #define DEG_PER_RAD (180.0 / PI64) 8 | 9 | #define to_radians(degrees) ((degrees)*RAD_PER_DEG) 10 | #define to_degrees(radians) ((radians)*DEG_PER_RAD) 11 | 12 | 13 | 14 | // This is a very niche sort algorithm. 15 | // I use it for Z sorting quads. 16 | // help_buffer should be same size as collection. 17 | // This only works with integers, and it will use the first number_of_bits in the integer 18 | // at sort_value_offset_in_item for sorting. 19 | // There is a cost of memory as we need to double the buffer we're sorting BUT the performance 20 | // gain is very promising. 21 | // At 21 bits I'm able to sort a completely randomized collection of 100k integers at around 22 | // 8m cycles (or 2.5-2.6ms on my shitty laptop i5-11300H) 23 | void radix_sort(void *collection, void *help_buffer, u64 item_count, u64 item_size, u64 sort_value_offset_in_item, u64 number_of_bits) { 24 | local_persist const int RADIX = 256; 25 | local_persist const int BITS_PER_PASS = 8; 26 | 27 | const int PASS_COUNT = ((number_of_bits + BITS_PER_PASS - 1) / BITS_PER_PASS); 28 | const u64 HALF_RANGE_OF_VALUE_BITS = 1ULL << (number_of_bits - 1); 29 | 30 | u64 count[RADIX]; 31 | u64 prefix_sum[RADIX]; 32 | 33 | for (u32 pass = 0; pass < PASS_COUNT; ++pass) { 34 | u32 shift = pass * BITS_PER_PASS; 35 | 36 | memset(count, 0, sizeof(count)); 37 | 38 | for (u64 i = 0; i < item_count; ++i) { 39 | u8 *item = (u8*)collection + i * item_size; 40 | 41 | u64 sort_value = *(u64*)(item + sort_value_offset_in_item); 42 | sort_value += HALF_RANGE_OF_VALUE_BITS; // We treat the value as a signed integer 43 | 44 | u32 digit = (sort_value >> shift) & (RADIX-1); 45 | ++count[digit]; 46 | } 47 | 48 | prefix_sum[0] = 0; 49 | for (u32 i = 1; i < RADIX; ++i) { 50 | prefix_sum[i] = prefix_sum[i - 1] + count[i - 1]; 51 | } 52 | 53 | for (u64 i = 0; i < item_count; ++i) { 54 | u8 *item = (u8*)collection + i * item_size; 55 | 56 | u64 sort_value = *(u64*)(item + sort_value_offset_in_item); 57 | sort_value += HALF_RANGE_OF_VALUE_BITS; // We treat the value as a signed integer 58 | 59 | u32 digit = (sort_value >> shift) & (RADIX-1); 60 | memcpy((u8*)help_buffer + prefix_sum[digit] * item_size, item, item_size); 61 | ++prefix_sum[digit]; 62 | } 63 | 64 | memcpy(collection, help_buffer, item_count * item_size); 65 | } 66 | } 67 | 68 | void merge_sort(void *collection, void *help_buffer, u64 item_count, u64 item_size, int (*compare)(const void *, const void *)) { 69 | u8 *items = (u8 *)collection; 70 | u8 *buffer = (u8 *)help_buffer; 71 | 72 | for (u64 width = 1; width < item_count; width *= 2) { 73 | for (u64 i = 0; i < item_count; i += 2 * width) { 74 | u64 left = i; 75 | u64 right = (i + width < item_count) ? (i + width) : item_count; 76 | u64 end = (i + 2 * width < item_count) ? (i + 2 * width) : item_count; 77 | 78 | u64 left_index = left; 79 | u64 right_index = right; 80 | u64 k = left; 81 | 82 | while (left_index < right && right_index < end) { 83 | if (compare(items + left_index * item_size, items + right_index * item_size) <= 0) { 84 | memcpy(buffer + k * item_size, items + left_index * item_size, item_size); 85 | left_index++; 86 | } else { 87 | memcpy(buffer + k * item_size, items + right_index * item_size, item_size); 88 | right_index++; 89 | } 90 | k++; 91 | } 92 | 93 | while (left_index < right) { 94 | memcpy(buffer + k * item_size, items + left_index * item_size, item_size); 95 | left_index++; 96 | k++; 97 | } 98 | 99 | while (right_index < end) { 100 | memcpy(buffer + k * item_size, items + right_index * item_size, item_size); 101 | right_index++; 102 | k++; 103 | } 104 | 105 | for (u64 j = left; j < end; j++) { 106 | memcpy(items + j * item_size, buffer + j * item_size, item_size); 107 | } 108 | } 109 | } 110 | } 111 | 112 | inline bool bytes_match(void *a, void *b, u64 count) { return memcmp(a, b, count) == 0; } 113 | 114 | #define swap(a, b, type) { type t = a; a = b; b = t; } 115 | 116 | 117 | // This isn't really linmath but just putting it here for now 118 | #define clamp(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x))) 119 | 120 | #define lerpf lerpf32 121 | f32 lerpf32(f32 from, f32 to, f32 x) { 122 | return (to-from)*x+from; 123 | } 124 | f64 lerpf64(f64 from, f64 to, f64 x) { 125 | return (to-from)*x+from; 126 | } 127 | s64 lerpi(s64 from, s64 to, f64 x) { 128 | return (s64)((round((f64)to-(f64)from)*x)+from); 129 | } 130 | 131 | #define smerpf smerpf32 132 | f32 smerpf32(f32 from, f32 to, f32 t) { 133 | float32 smooth = t * t * (3.0 - 2.0 * t); 134 | return lerpf(from, to, smooth); 135 | } 136 | f64 smerpf64(f64 from, f64 to, f64 t) { 137 | float64 smooth = t * t * (3.0 - 2.0 * t); 138 | return lerpf(from, to, smooth); 139 | } 140 | s64 smerpi(s64 from, s64 to, f64 t) { 141 | float64 smooth = t * t * (3.0 - 2.0 * t); 142 | return lerpi(from, to, smooth); 143 | } 144 | // I don't know how to describe this one I just made this in desmos and it has been useful for cool stuff: 145 | // https://www.desmos.com/calculator/r2etlhi2ej 146 | float32 sine_oscillate_n_waves_normalized(float32 v, float32 n) { 147 | return (sin((n*2*PI32*((v)-(1/(n*4))))+1))/2; 148 | } 149 | 150 | 151 | float64 string_to_float(string s, bool *success) { 152 | 153 | *success = true; 154 | float64 result = 0.0; 155 | float64 sign = 1.0; 156 | bool has_decimal = false; 157 | float64 decimal_place = 0.1; 158 | 159 | u64 i = 0; 160 | // Skip leading spaces 161 | while (i < s.count && (s.data[i] == ' ' || s.data[i] == '\t')) { 162 | i++; 163 | } 164 | 165 | // Handle optional sign 166 | if (i < s.count && s.data[i] == '-') { 167 | sign = -1.0; 168 | i++; 169 | } else if (i < s.count && s.data[i] == '+') { 170 | i++; 171 | } 172 | 173 | while (i < s.count) { 174 | if (s.data[i] >= '0' && s.data[i] <= '9') { 175 | if (has_decimal) { 176 | result += (s.data[i] - '0') * decimal_place; 177 | decimal_place *= 0.1; 178 | } else { 179 | result = result * 10.0 + (s.data[i] - '0'); 180 | } 181 | } else if (s.data[i] == '.') { 182 | if (has_decimal) { 183 | // Multiple decimal points are invalid 184 | *success = false; 185 | return 0.0; 186 | } 187 | has_decimal = true; 188 | } else { 189 | *success = false; 190 | return 0.0; 191 | } 192 | i++; 193 | } 194 | 195 | return result * sign; 196 | } 197 | 198 | s64 string_to_int(string s, bool *success) { 199 | *success = true; 200 | s64 result = 0; 201 | s64 sign = 1; 202 | 203 | u64 i = 0; 204 | // Skip leading spaces 205 | while (i < s.count && (s.data[i] == ' ' || s.data[i] == '\t')) { 206 | i++; 207 | } 208 | 209 | // Handle optional sign 210 | if (i < s.count && s.data[i] == '-') { 211 | sign = -1; 212 | i++; 213 | } else if (i < s.count && s.data[i] == '+') { 214 | i++; 215 | } 216 | 217 | while (i < s.count) { 218 | if (s.data[i] >= '0' && s.data[i] <= '9') { 219 | result = result * 10 + (s.data[i] - '0'); 220 | } else { 221 | *success = false; 222 | return 0; 223 | } 224 | i++; 225 | } 226 | 227 | return result * sign; 228 | } --------------------------------------------------------------------------------