Fixed memory bug & window placement

- 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
- Fixed Y placement of window when changing the window rect
- Fixed window sizing when setting scaled_width or scaled_height
- Updated readme
This commit is contained in:
Charlie 2024-07-08 17:57:23 +02:00
parent 05fd88472b
commit e54897432b
9 changed files with 198 additions and 118 deletions

View file

@ -1,20 +1,64 @@
ooga booga
## 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.
## 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.
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 `<math.h>` 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 <project_dir>
3. Make a file my_file.c in <project_dir>
```
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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -152,7 +152,6 @@ typedef u8 bool;
#endif
#define DEBUG 0
#define VERY_DEBUG 1
#define RELEASE 2
#if defined(NDEBUG)

View file

@ -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;