diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c42d0e --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +ooga booga + +## TOC +- [Getting started](#getting-started) +- [What is ooga booga?](#what-is-ooga-booga) +- [Quickstart](#quickstart) +- [The "Build System"](#the-build-system) +- [Examples & Documentation](#examples--documentation) +- [Licensing](#licensing) + +## Getting started +If you'd like to learn how to use the engine to build a game, there's a completely free course in the [Skool community](https://www.skool.com/game-dev) + +You can find all tutorials and resources for getting started within the community. + +## What is ooga booga? + +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. + +## Quickstart +1. Install clang, add to path +2. Clone repo to +3. Make a file my_file.c in +``` +int entry(int argc, char **argv) { + print("Ooga, booga!\n"); +} +``` +4. in build.c add this line to the bottom +``` +#include "my_file.c" +``` +5. Run `build.bat` +6. Run build/cgame.exe +7. profit + +## The "Build System" + +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. + +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). +## Examples & Documentation + +Documentation will come in the form of a lot of examples because that's the best way to learn and understand how everything works. + +See [examples](oogabooga/examples). + +Simply add `#include "oogabooga/examples/some_example.c"` to build.c and compile & run to see the example code in action. + +Other than examples, a great way to learn is to delve into the code of whatever module you're using. The codebase is written with this in mind. + +## Licensing +By default, the repository has an educational license that makes the engine free to use for personal projects. + +[Educational license terms](https://github.com/alpinestudios/oogabooga/blob/master/LICENSE.md) + +When you're ready to take the next step and work on a commercial game, you can upgrade to the full commercial license. + +Here are the benefits of obtaining the full license: +- Permanent Ownership: You completely own the source code for life. +- No Recurring Fees or Royalties: Just an affordable one-time payment. +- It qualifies you to enter the private Skool community, where there's daily calls with Randy & Charlie, to help speedrun your game's development + +You can [contact us](https://randy.gg/contact) to find out more. \ No newline at end of file diff --git a/build.c b/build.c index 7ab91d4..ecad9f4 100644 --- a/build.c +++ b/build.c @@ -7,6 +7,8 @@ #define RUN_TESTS 0 +#define VERY_DEBUG 0 + #define ENABLE_PROFILING 0 // ENABLE_SIMD Requires CPU to support at least SSE1 but I will be very surprised if you find a system today which doesn't @@ -41,7 +43,7 @@ typedef struct Context_Extra { // #include "oogabooga/examples/text_rendering.c" // An engine dev stress test for rendering -#include "oogabooga/examples/renderer_stress_test.c" + #include "oogabooga/examples/renderer_stress_test.c" // Randy's example game that he's building out as a tutorial for using the engine // #include "entry_randygame.c" diff --git a/changelog.txt b/changelog.txt index cceac4d..e4c3914 100644 --- a/changelog.txt +++ b/changelog.txt @@ -14,8 +14,18 @@ Third party: - Made a global thread_local "third_party_allocator" which is set when third party libraries are used so all memory still goes through our *program_memory - Stripped all third party libraries of external dependencies (C headers) & noise +Memory: + - Improved assert messages to be more clear about what might be happening if they fail + - Added more checks in debug to detect heap corruption and what not + - Fixed a bug where the program would crash because a heap block was perfectly full + Misc: - - Fix typos in examples/text_rendering.c + - Fixed typos in examples/text_rendering.c + - Fixed Y placement of window when changing the window rect + - Fixed window sizing when setting scaled_width or scaled_height + - Updated readme + + ## v0.00.002 - Text rendering, image manipulation, hash tables diff --git a/oogabooga/drawing.c b/oogabooga/drawing.c index 291d299..6eb88c0 100644 --- a/oogabooga/drawing.c +++ b/oogabooga/drawing.c @@ -68,7 +68,7 @@ Usage: -#define QUADS_PER_BLOCK 1024 +#define QUADS_PER_BLOCK 16 typedef struct Draw_Quad { Vector2 bottom_left, top_left, top_right, bottom_right; // r, g, b, a diff --git a/oogabooga/examples/renderer_stress_test.c b/oogabooga/examples/renderer_stress_test.c index 0300d64..230b1af 100644 --- a/oogabooga/examples/renderer_stress_test.c +++ b/oogabooga/examples/renderer_stress_test.c @@ -6,7 +6,7 @@ int entry(int argc, char **argv) { window.width = 1280; window.height = 720; window.x = 200; - window.y = 0; + window.y = 200; window.clear_color = hex_to_rgba(0x2a2d3aff); @@ -56,11 +56,11 @@ int entry(int argc, char **argv) { } if (is_key_just_pressed(KEY_ARROW_LEFT)) { - window.width += 10; + window.scaled_width += 10; window.x -= 10; } if (is_key_just_pressed(KEY_ARROW_RIGHT)) { - window.width += 10; + window.scaled_width += 10; } if (is_key_just_released('Q')) { @@ -89,7 +89,7 @@ int entry(int argc, char **argv) { draw_frame.view = camera_view; seed_for_random = 69; - for (u64 i = 0; i < 10000; i++) { + for (u64 i = 0; i < 100000; i++) { float32 aspect = (float32)window.width/(float32)window.height; float min_x = -aspect; float max_x = aspect; diff --git a/oogabooga/gfx_impl_d3d11.c b/oogabooga/gfx_impl_d3d11.c index 6e2447d..9805b79 100644 --- a/oogabooga/gfx_impl_d3d11.c +++ b/oogabooga/gfx_impl_d3d11.c @@ -539,50 +539,32 @@ void d3d11_draw_call(int number_of_rendered_quads, ID3D11ShaderResourceView **te VTABLE(Draw, d3d11_context, number_of_rendered_quads * 6, 0); } -void gfx_update() { - if (window.should_close) return; - - VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color); - +void d3d11_process_draw_frame() { HRESULT hr; + + /// + // Maybe grow quad vbo + u32 required_size = sizeof(D3D11_Vertex) * draw_frame.num_blocks*QUADS_PER_BLOCK*6; - tm_scope_cycles("Frame setup") { - - /// - // Maybe resize swap chain - RECT client_rect; - bool ok = GetClientRect(window._os_handle, &client_rect); - assert(ok, "GetClientRect failed with error code %lu", GetLastError()); - u32 window_width = client_rect.right-client_rect.left; - u32 window_height = client_rect.bottom-client_rect.top; - if (window_width != d3d11_swap_chain_width || window_height != d3d11_swap_chain_height) { - d3d11_update_swapchain(); - } - - /// - // Maybe grow quad vbo - u32 required_size = sizeof(D3D11_Vertex) * draw_frame.num_blocks*QUADS_PER_BLOCK*6; - - if (required_size > d3d11_quad_vbo_size) { - if (d3d11_quad_vbo) { - D3D11Release(d3d11_quad_vbo); - dealloc(get_heap_allocator(), d3d11_staging_quad_buffer); - } - D3D11_BUFFER_DESC desc = ZERO(D3D11_BUFFER_DESC); - desc.Usage = D3D11_USAGE_DYNAMIC; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; - desc.ByteWidth = required_size; - desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; - HRESULT hr = VTABLE(CreateBuffer, d3d11_device, &desc, 0, &d3d11_quad_vbo); - assert(SUCCEEDED(hr), "CreateBuffer failed"); - d3d11_quad_vbo_size = required_size; - - d3d11_staging_quad_buffer = alloc(get_heap_allocator(), d3d11_quad_vbo_size); - assert((u64)d3d11_staging_quad_buffer%16 == 0); - - log_verbose("Grew quad vbo to %d bytes.", d3d11_quad_vbo_size); + if (required_size > d3d11_quad_vbo_size) { + if (d3d11_quad_vbo) { + D3D11Release(d3d11_quad_vbo); + dealloc(get_heap_allocator(), d3d11_staging_quad_buffer); } + D3D11_BUFFER_DESC desc = ZERO(D3D11_BUFFER_DESC); + desc.Usage = D3D11_USAGE_DYNAMIC; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + desc.ByteWidth = required_size; + desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + HRESULT hr = VTABLE(CreateBuffer, d3d11_device, &desc, 0, &d3d11_quad_vbo); + assert(SUCCEEDED(hr), "CreateBuffer failed"); + d3d11_quad_vbo_size = required_size; + + d3d11_staging_quad_buffer = alloc(get_heap_allocator(), d3d11_quad_vbo_size); + assert((u64)d3d11_staging_quad_buffer%16 == 0); + + log_verbose("Grew quad vbo to %d bytes.", d3d11_quad_vbo_size); } if (draw_frame.num_blocks > 0) { @@ -730,11 +712,30 @@ void gfx_update() { // Draw call tm_scope_cycles("Draw call") d3d11_draw_call(number_of_rendered_quads, textures, num_textures); } + + reset_draw_frame(&draw_frame); +} - tm_scope_cycles("Present") { - hr = VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING); - win32_check_hr(hr); - } +void gfx_update() { + if (window.should_close) return; + + VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color); + + HRESULT hr; + /// + // Maybe resize swap chain + RECT client_rect; + bool ok = GetClientRect(window._os_handle, &client_rect); + assert(ok, "GetClientRect failed with error code %lu", GetLastError()); + u32 window_width = client_rect.right-client_rect.left; + u32 window_height = client_rect.bottom-client_rect.top; + if (window_width != d3d11_swap_chain_width || window_height != d3d11_swap_chain_height) { + d3d11_update_swapchain(); + } + + d3d11_process_draw_frame(); + + VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING); #if CONFIGURATION == DEBUG /// @@ -757,8 +758,6 @@ void gfx_update() { } #endif - reset_draw_frame(&draw_frame); - } diff --git a/oogabooga/memory.c b/oogabooga/memory.c index d5e2f54..34e7a3c 100644 --- a/oogabooga/memory.c +++ b/oogabooga/memory.c @@ -73,9 +73,14 @@ typedef struct Heap_Block { // 32 bytes !! } Heap_Block; +#define HEAP_META_SIGNATURE 6969694206942069ull typedef struct { u64 size; Heap_Block *block; +#if CONFIGURATION == DEBUG + u64 signature; + u64 padding; +#endif } Heap_Allocation_Metadata; Heap_Block *heap_head; @@ -114,19 +119,24 @@ void santiy_check_free_node_tree(Heap_Block *block) { Heap_Free_Node *other_node = node->next; while (other_node != 0) { - assert(other_node != node, "Circular reference in heap free node tree. That's bad."); + assert(other_node != node, "Circular reference in heap free node tree. This is probably an internal error, or an extremely unlucky result from heap corruption."); other_node = other_node->next; } total_free += node->size; - assert(total_free <= block->size, "Free nodes are fucky wucky"); + assert(total_free <= block->size, "Free nodes are fucky wucky. This might be heap corruption, or possibly an internal error."); node = node->next; } } inline void check_meta(Heap_Allocation_Metadata *meta) { +#if CONFIGURATION == DEBUG + assert(meta->signature == HEAP_META_SIGNATURE, "Heap error. Either 1) You passed a bad pointer to dealloc or 2) You corrupted the heap."); +#endif // If > 256GB then prolly not legit lol - assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Garbage pointer passed to heap_dealloc !!! Or could be corrupted memory."); - assert(is_pointer_in_program_memory(meta->block), "Garbage pointer passed to heap_dealloc !!! Or could be corrupted memory."); + assert(meta->size < 1024ULL*1024ULL*1024ULL*256ULL, "Heap error. Either 1) You passed a bad pointer to dealloc or 2) You corrupted the heap."); + assert(is_pointer_in_program_memory(meta->block), "Heap error. Either 1) You passed a bad pointer to dealloc or 2) You corrupted the heap."); + + assert((u64)meta >= (u64)meta->block->start && (u64)meta < (u64)meta->block->start+meta->block->size, "Heap error: Pointer is not in it's metadata block. This could be heap corruption but it's more likely an internal error. That's not good."); } typedef struct { @@ -152,7 +162,7 @@ Heap_Search_Result search_heap_block(Heap_Block *block, u64 size) { result.best_fit = node; result.previous = previous; result.delta = 0; - assert(result.previous != result.best_fit); + assert(result.previous != result.best_fit, "Internal goof"); return result; } @@ -176,7 +186,7 @@ Heap_Search_Result search_heap_block(Heap_Block *block, u64 size) { result.best_fit = best_fit; result.previous = before_best_fit; result.delta = best_fit_delta; - assert(result.previous != result.best_fit); + assert(result.previous != result.best_fit, "Internal goof"); return result; } @@ -198,11 +208,11 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) { if (((u8*)block)+size >= ((u8*)program_memory)+program_memory_size) { u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1; u64 new_program_size = get_next_power_of_two(minimum_size); - assert(new_program_size >= minimum_size, "Bröd"); + assert(new_program_size >= minimum_size, "Internal goof"); const u64 ATTEMPTS = 1000; for (u64 i = 0; i <= ATTEMPTS; i++) { if (program_memory_size >= new_program_size) break; // Another thread might have resized already, causing it to fail here. - assert(i < ATTEMPTS, "OS is not letting us allocate more memory. Maybe we are out of memory?"); + assert(i < ATTEMPTS, "OS is not letting us allocate more memory. Maybe we are out of memory? You sure must be using a lot of memory then."); if (os_grow_program_memory(new_program_size)) break; } @@ -236,6 +246,8 @@ void *heap_alloc(u64 size) { // #Sync #Speed oof os_spinlock_lock(heap_lock); + + size += sizeof(Heap_Allocation_Metadata); size = (size+HEAP_ALIGNMENT) & ~(HEAP_ALIGNMENT-1); @@ -291,10 +303,7 @@ void *heap_alloc(u64 size) { } - // Ideally this should not be possible. - // If we run out of program_memory, we should just grow it and if that fails - // we crash because out of memory. - assert(best_fit != 0, "Internal heap allocation failed"); + assert(best_fit != 0, "Internal heap error"); Heap_Free_Node *new_free_node = 0; if (size != best_fit->size) { @@ -305,10 +314,10 @@ void *heap_alloc(u64 size) { } if (previous && new_free_node) { - assert(previous->next == best_fit, "Bro what"); + assert(previous->next == best_fit, "Internal heap error"); previous->next = new_free_node; } else if (previous) { - assert(previous->next == best_fit, "Bro what"); + assert(previous->next == best_fit, "Internal heap error"); previous->next = best_fit->next; } @@ -324,8 +333,13 @@ void *heap_alloc(u64 size) { Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)best_fit; meta->size = size; meta->block = best_fit_block; +#if CONFIGURATION == DEBUG + meta->signature = HEAP_META_SIGNATURE; +#endif -#if CONFIGURATION == VERY_DEBUG + check_meta(meta); + +#if VERY_DEBUG santiy_check_free_node_tree(meta->block); #endif @@ -334,7 +348,7 @@ void *heap_alloc(u64 size) { void *p = ((u8*)meta)+sizeof(Heap_Allocation_Metadata); - assert((u64)p % HEAP_ALIGNMENT == 0); + assert((u64)p % HEAP_ALIGNMENT == 0, "Internal heap error. Result pointer is not aligned to HEAP_ALIGNMENT"); return p; } void heap_dealloc(void *p) { @@ -344,17 +358,19 @@ void heap_dealloc(void *p) { os_spinlock_lock(heap_lock); - assert(is_pointer_in_program_memory(p), "Garbage pointer; out of program memory bounds!"); + assert(is_pointer_in_program_memory(p), "A bad pointer was passed tp heap_dealloc: it is out of program memory bounds!"); p = (u8*)p-sizeof(Heap_Allocation_Metadata); Heap_Allocation_Metadata *meta = (Heap_Allocation_Metadata*)(p); check_meta(meta); - - // Yoink meta data before we start overwriting it Heap_Block *block = meta->block; u64 size = meta->size; + #if VERY_DEBUG + santiy_check_free_node_tree(block); + #endif + Heap_Free_Node *new_node = cast(Heap_Free_Node*)p; new_node->size = size; @@ -368,38 +384,48 @@ void heap_dealloc(void *p) { block->free_head = new_node; } } else { - Heap_Free_Node *node = block->free_head; + if (!block->free_head) { + block->free_head = new_node; + new_node->next = 0; + } else { + Heap_Free_Node *node = block->free_head; - while (true) { - - assert(node != 0, "We didn't find where the free node should be! uh oh"); + while (true) { - if (new_node > node) { - u8* node_tail = (u8*)node + node->size; - if (cast(u8*)new_node == node_tail) { - node->size += new_node->size; - break; - } else { - new_node->next = node->next; - node->next = new_node; - - u8* new_node_tail = (u8*)new_node + new_node->size; - if (new_node->next && (u8*)new_node->next == new_node_tail) { - new_node->size += new_node->next->size; - new_node->next = new_node->next->next; + assert(node != 0, "We didn't find where the free node should be! This is likely heap corruption (or, hopefully not, an internal error)"); + + // In retrospect, I don't remember a good reason to care about where the + // free nodes are... maybe I'm just dumb right now? #Speed #Memory + // ... ACtually, it's probably to easily know when to merge free nodes. + // BUT. Maybe it's not worth the performance hit? Then again, if the heap + // allocator slows down your program you should rethink your memory management + // anyways... + + if (new_node >= node) { + u8* node_tail = (u8*)node + node->size; + if (cast(u8*)new_node == node_tail) { + node->size += new_node->size; + break; + } else { + new_node->next = node->next; + node->next = new_node; + + u8* new_node_tail = (u8*)new_node + new_node->size; + if (new_node->next && (u8*)new_node->next == new_node_tail) { + new_node->size += new_node->next->size; + new_node->next = new_node->next->next; + } + break; } - break; } + + node = node->next; } - - node = node->next; } } -#if CONFIGURATION == VERY_DEBUG - santiy_check_free_node_tree(block); -#endif + // #Sync #Speed oof os_spinlock_unlock(heap_lock); diff --git a/oogabooga/oogabooga.c b/oogabooga/oogabooga.c index 19906c5..9c79151 100644 --- a/oogabooga/oogabooga.c +++ b/oogabooga/oogabooga.c @@ -152,7 +152,6 @@ typedef u8 bool; #endif #define DEBUG 0 -#define VERY_DEBUG 1 #define RELEASE 2 #if defined(NDEBUG) diff --git a/oogabooga/os_impl_windows.c b/oogabooga/os_impl_windows.c index 714e294..8f8f65b 100644 --- a/oogabooga/os_impl_windows.c +++ b/oogabooga/os_impl_windows.c @@ -947,8 +947,8 @@ void os_update() { } if (last_window.scaled_width != window.scaled_width || last_window.scaled_height != window.scaled_height) { - window.width = window.scaled_width*dpi_scale_factor; - window.height = window.scaled_height*dpi_scale_factor; + window.width = window.scaled_width/dpi_scale_factor; + window.height = window.scaled_height/dpi_scale_factor; } BOOL ok; @@ -966,7 +966,7 @@ void os_update() { assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError()); u32 actual_x = update_rect.left; - u32 actual_y = update_rect.top; + u32 actual_y = screen_height - update_rect.top - window.height; u32 actual_width = update_rect.right - update_rect.left; u32 actual_height = update_rect.bottom - update_rect.top;