????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????

This commit is contained in:
Charlie 2024-07-09 18:51:15 +02:00
commit cc74dc68e4
22 changed files with 2564 additions and 1963 deletions

View file

@ -29,6 +29,7 @@ typedef struct Context_Extra {
#include "oogabooga/examples/minimal_game_loop.c"
// #include "oogabooga/examples/text_rendering.c"
// #include "oogabooga/examples/custom_logger.c"
// #include "oogabooga/examples/renderer_stress_test.c"
// This is where you swap in your own project!

View file

@ -1,3 +1,66 @@
## v0.00.004 - Custom logging, more concurrency & bugfixing
Concurrency:
- Refactored spinlock out of OS api (deprecated old procs)
- Concurrency utilites:
void spinlock_init(Spinlock *l);
void spinlock_acquire_or_wait(Spinlock* l);
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds);
void spinlock_release(Spinlock* l);
void mutex_init(Mutex *m);
void mutex_destroy(Mutex *m);
void mutex_acquire_or_wait(Mutex *m);
void mutex_release(Mutex *m);
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state);
void binary_semaphore_destroy(Binary_Semaphore *sem);
void binary_semaphore_wait(Binary_Semaphore *sem);
void binary_semaphore_signal(Binary_Semaphore *sem);
Macro MEMORY_BARRIER
- Concurrency tests
Docs:
- custom_logger.c example to show how one can make a custom logger and have logs displayed in-game
Utility:
- draw_text_and_measure() which is just an overload of draw_text but it also does a measure and returns it
Misc:
- Added u64 thread_id to global context. This is set in main() for main thread and in thread startup when you dispatch a Thread
- Fixed a bug where plain rects would be drawn with the incorrect color
- Fixed a bug where quads from earlier frames would be drawn
## v0.00.003 - Fixes
Random:
- get_random_float64()
- get_random_float32_in_range()
- get_random_float64_in_range()
- get_random_int_in_range()
Third party:
- Added 3 minimal libraries for audio decoding
- dr_mp3.h: MP3 decoding
- dr_wav.h: WAV decoding
- stb_vorbis.c: OGG decoding
- 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:
- 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
- Portable DEPRECATED macro
- Deprecate os_compare_and_swap and replace with more portable compare_and_swap
- Fixed a bug where the wrong image would be drawn
## v0.00.003 - Fixes
Random:

View file

@ -65,7 +65,9 @@ typedef enum Log_Level {
LOG_ERROR,
LOG_INFO,
LOG_WARNING,
LOG_VERBOSE
LOG_VERBOSE,
LOG_LEVEL_COUNT,
} Log_Level;
@ -80,6 +82,8 @@ Allocator get_heap_allocator();
typedef struct Context {
void *logger; // void(*Logger_Proc)(Log_Level level, string fmt, ...)
u64 thread_id;
CONTEXT_EXTRA extra;
} Context;

27
oogabooga/bucket_array.c Normal file
View file

@ -0,0 +1,27 @@
typedef struct Bucket_Array {
u64 _block_size;
u64 _bucket_count;
} Bucket_Array;
typedef struct Bucket_Array_Free_Node {
struct Bucket_Array_Free_Node *next;
} Bucket_Array_Free_Node;
typedef struct Bucket_Array_Bucket {
Bucket_Array_Free_Node *first_free;
void *data;
Bucket *next;
} Bucket_Array_Bucket;
Bucket_Array make_bucket_array(u64 block_size, u64 bucket_count) {
Bucket_Array ba;
ba._block_size = block_size;
ba._bucket_count = bucket_count;
}

153
oogabooga/concurrency.c Normal file
View file

@ -0,0 +1,153 @@
typedef struct Spinlock Spinlock;
typedef struct Mutex Mutex;
typedef struct Binary_Semaphore Binary_Semaphore;
///
// Spinlock "primitive"
// Like a mutex but it eats up the entire core while waiting.
// Beneficial if contention is low or sync speed is important
void spinlock_init(Spinlock *l);
void spinlock_acquire_or_wait(Spinlock* l);
// This returns true if successfully acquired or false if timeout reached.
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds);
void spinlock_release(Spinlock* l);
///
// High-level mutex primitive (short spinlock then OS mutex lock)
// Just spins for a few (configurable) microseconds with a spinlock,
// and if acquiring fails it falls back to a OS mutex.
void mutex_init(Mutex *m);
void mutex_destroy(Mutex *m);
void mutex_acquire_or_wait(Mutex *m);
void mutex_release(Mutex *m);
///
// Binary semaphore
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state);
void binary_semaphore_destroy(Binary_Semaphore *sem);
void binary_semaphore_wait(Binary_Semaphore *sem);
void binary_semaphore_signal(Binary_Semaphore *sem);
typedef struct Spinlock {
bool locked;
} Spinlock;
void spinlock_init(Spinlock *l) {
memset(l, 0, sizeof(*l));
}
void spinlock_acquire_or_wait(Spinlock* l) {
while (true) {
bool expected = false;
if (compare_and_swap_bool(&l->locked, true, expected)) {
MEMORY_BARRIER;
return;
}
while (l->locked) {
// spinny boi
}
}
}
// Returns true on aquired, false if timeout seconds reached
bool spinlock_acquire_or_wait_timeout(Spinlock* l, f64 timeout_seconds) {
f64 start = os_get_current_time_in_seconds();
while (true) {
bool expected = false;
if (compare_and_swap_bool(&l->locked, true, expected)) {
MEMORY_BARRIER;
return true;
}
while (l->locked) {
// spinny boi
if ((os_get_current_time_in_seconds()-start) >= timeout_seconds) return false;
}
}
return true;
}
void spinlock_release(Spinlock* l) {
bool expected = true;
bool success = compare_and_swap_bool(&l->locked, false, expected);
assert(success, "This thread should have acquired the spinlock but compare_and_swap failed");
MEMORY_BARRIER;
}
///
// High-level mutex primitive (short spinlock then OS mutex lock)
#define MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS 100
typedef struct Mutex {
Spinlock spinlock;
f64 spin_time_microseconds;
Mutex_Handle os_handle;
volatile bool spinlock_acquired;
volatile u64 acquiring_thread;
} Mutex;
void mutex_init(Mutex *m) {
spinlock_init(&m->spinlock);
m->spin_time_microseconds = MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS;
m->os_handle = os_make_mutex();
m->spinlock_acquired = false;
m->acquiring_thread = 0;
}
void mutex_destroy(Mutex *m) {
os_destroy_mutex(m->os_handle);
}
void mutex_acquire_or_wait(Mutex *m) {
if (spinlock_acquire_or_wait_timeout(&m->spinlock, m->spin_time_microseconds / 1000000.0)) {
assert(!m->spinlock_acquired, "Internal sync error in Mutex");
m->spinlock_acquired = true;
MEMORY_BARRIER;
}
os_lock_mutex(m->os_handle);
assert(!m->acquiring_thread, "Internal sync error in Mutex: Multiple threads acquired");
m->acquiring_thread = context.thread_id;
MEMORY_BARRIER;
}
void mutex_release(Mutex *m) {
assert(m->acquiring_thread != 0, "Tried to release a mutex which is not acquired");
assert(m->acquiring_thread == context.thread_id, "Non-owning thread tried to release mutex");
m->acquiring_thread = 0;
MEMORY_BARRIER;
bool was_spinlock_acquired = m->spinlock_acquired;
m->spinlock_acquired = false;
MEMORY_BARRIER;
os_unlock_mutex(m->os_handle);
MEMORY_BARRIER;
if (was_spinlock_acquired) {
spinlock_release(&m->spinlock);
MEMORY_BARRIER;
}
MEMORY_BARRIER;
}
typedef struct Binary_Semaphore {
bool signaled;
Mutex mutex;
} Binary_Semaphore;
void binary_semaphore_init(Binary_Semaphore *sem, bool initial_state) {
sem->signaled = initial_state;
mutex_init(&sem->mutex);
}
void binary_semaphore_destroy(Binary_Semaphore *sem) {
mutex_destroy(&sem->mutex);
}
void binary_semaphore_wait(Binary_Semaphore *sem) {
mutex_acquire_or_wait(&sem->mutex);
while (!sem->signaled) {
mutex_release(&sem->mutex);
os_yield_thread();
mutex_acquire_or_wait(&sem->mutex);
}
sem->signaled = false;
mutex_release(&sem->mutex);
}
void binary_semaphore_signal(Binary_Semaphore *sem) {
mutex_acquire_or_wait(&sem->mutex);
sem->signaled = true;
mutex_release(&sem->mutex);
}

View file

@ -33,6 +33,8 @@ typedef struct Cpu_Capabilities {
__debugbreak();
volatile int *a = 0;
*a = 5;
a = (int*)0xDEADBEEF;
*a = 5;
}
#include <intrin.h>
#pragma intrinsic(__rdtsc)
@ -95,6 +97,8 @@ typedef struct Cpu_Capabilities {
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
}
#define MEMORY_BARRIER _ReadWriteBarrier()
#elif COMPILER_GCC || COMPILER_CLANG
#define inline __attribute__((always_inline)) inline
#define alignat(x) __attribute__((aligned(x)))
@ -103,6 +107,8 @@ typedef struct Cpu_Capabilities {
__builtin_trap();
volatile int *a = 0;
*a = 5;
a = (int*)0xDEADBEEF;
*a = 5;
}
inline u64 rdtsc() {
unsigned int lo, hi;
@ -194,6 +200,8 @@ typedef struct Cpu_Capabilities {
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
}
#define MEMORY_BARRIER __asm__ __volatile__("" ::: "memory")
#else
#define inline inline
#define COMPILER_HAS_MEMCPY_INTRINSICS 0
@ -207,6 +215,8 @@ typedef struct Cpu_Capabilities {
#define deprecated(msg)
#define MEMORY_BARRIER
#warning "Compiler is not explicitly supported, some things will probably not work as expected"
#endif

File diff suppressed because it is too large Load diff

View file

@ -186,13 +186,13 @@ float4 sample_texture(int texture_index, int sampler_index, float2 uv) {
float4 ps_main(PS_INPUT input) : SV_TARGET
{
if (input.type == QUAD_TYPE_REGULAR) {
if (input.texture_index >= 0) {
if (input.texture_index >= 0 && input.texture_index < 32 && input.sampler_index >= 0 && input.sampler_index <= 3) {
return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
} else {
return input.color;
}
} else if (input.type == QUAD_TYPE_TEXT) {
if (input.texture_index >= 0) {
if (input.texture_index >= 0 && input.texture_index < 32 && input.sampler_index >= 0 && input.sampler_index <= 3) {
float alpha = sample_texture(input.texture_index, input.sampler_index, input.uv).x;
return float4(1.0, 1.0, 1.0, alpha)*input.color;
} else {

View file

@ -117,13 +117,14 @@ Draw_Frame draw_frame = ZERO(Draw_Frame);
void reset_draw_frame(Draw_Frame *frame) {
*frame = (Draw_Frame){0};
frame->current = &first_block;
frame->current->num_quads = 0;
frame->current = 0;
float32 aspect = (float32)window.width/(float32)window.height;
frame->projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10);
frame->view = m4_scalar(1.0);
frame->num_blocks = 0;
}
Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
@ -139,10 +140,11 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
draw_frame.current = &first_block;
draw_frame.current->low_z = F32_MAX;
draw_frame.current->high_z = F32_MIN;
draw_frame.current->num_quads = 0;
memset(draw_frame.current->quad_buffer, 0, sizeof(draw_frame.current->quad_buffer)); // #Temporary
draw_frame.num_blocks = 1;
}
if (draw_frame.current == &first_block) draw_frame.num_blocks = 1;
assert(draw_frame.current->num_quads <= QUADS_PER_BLOCK);
if (draw_frame.current->num_quads == QUADS_PER_BLOCK) {
@ -154,11 +156,11 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
draw_frame.current = draw_frame.current->next;
draw_frame.current->num_quads = 0;
draw_frame.current->low_z = F32_MAX;
draw_frame.current->high_z = F32_MIN;
draw_frame.num_blocks += 1;
draw_frame.current->low_z = F32_MAX;
draw_frame.current->high_z = F32_MIN;
}
draw_frame.current->quad_buffer[draw_frame.current->num_quads] = quad;
@ -269,6 +271,14 @@ void draw_text(Gfx_Font *font, string text, u32 raster_height, Vector2 position,
draw_text_xform(font, text, raster_height, xform, scale, color);
}
Gfx_Text_Metrics draw_text_and_measure(Gfx_Font *font, string text, u32 raster_height, Vector2 position, Vector2 scale, Vector4 color) {
Matrix4 xform = m4_scalar(1.0);
xform = m4_translate(xform, v3(position.x, position.y, 0));
draw_text_xform(font, text, raster_height, xform, scale, color);
return measure_text(font, text, raster_height, scale);
}
#define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0})

View file

@ -0,0 +1,190 @@
/*
This is an example showcasing how we can make a custom logger to log both to stdout and
and in-game logger.
We also have log levels to be able to disable/enable the respective levels.
*/
// start all log levels enabled
bool log_level_enabled_flags[LOG_LEVEL_COUNT] = {1, 1, 1, 1};
// We delete old messages when this overflows
#define MAX_LOG_MESSAGES 50
typedef struct Log_Message {
string message;
Log_Level level;
} Log_Message;
Log_Message log_messages[MAX_LOG_MESSAGES];
s64 num_log_messages = 0;
string get_log_level_name(Log_Level level) {
switch (level) {
case LOG_VERBOSE: return STR("Verbose");
case LOG_INFO: return STR("Info");
case LOG_WARNING: return STR("Warning");
case LOG_ERROR: return STR("Error");
default: return STR("");
}
}
void my_logger(Log_Level level, string s) {
string prefix = STR("[INVALID LOG LEVEL]");
if (level >= 0 && level < LOG_LEVEL_COUNT) {
// if log level is disabled, we just leave
if (!log_level_enabled_flags[level]) {
return;
}
prefix = tprint("[%s]", get_log_level_name(level));
}
// Format the final string.
// Since we will be storing it in log_message, we need to use "sprint" rather than
// "tprint" because it tprint uses the temp allocator which gets reset each frame.
// In a real world scenario we would probably have a dedicated allocator for these
// strings rather than heap allocating all of them.
string message = sprint(get_heap_allocator(), "%s %s\n", prefix, s);
// Output the final string to stdout
print(message);
// Also add to in-game log messages
Log_Message msg = (Log_Message){message, level};
if (num_log_messages < MAX_LOG_MESSAGES) {
log_messages[num_log_messages] = msg;
num_log_messages += 1;
} else {
// Shift memory down by one to make space for the next message and deleting the first.
memcpy(log_messages, &log_messages[1], sizeof(log_messages)-sizeof(Log_Message));
log_messages[num_log_messages-1] = msg;
}
}
#define FONT_HEIGHT 38
Gfx_Font *font;
void draw_log(float x, float y);
int entry(int argc, char **argv) {
// Window setup
window.title = STR("Minimal Game Example");
window.scaled_width = 1280;
window.scaled_height = 720;
window.x = 200;
window.y = 200;
window.clear_color = hex_to_rgba(0x6495EDff);
// Load a font to draw the logs with
font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator());
assert(font, "Failed loading arial.ttf, %d", GetLastError());
// This is where we set the logger we want all logs to go through
context.logger = my_logger;
while (!window.should_close) {
reset_temporary_storage();
os_update();
gfx_update();
// pixel-aligned projection
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);
float x = -window.width/2+60;
float y = window.height/2-FONT_HEIGHT/2-30;
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);
draw_text(font, STR("Left-click to toggle, right-click to send log message"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE);
y -= FONT_HEIGHT*1.3;
// Loop through all levels to draw their state and act on input
for (Log_Level level = 0; level < LOG_LEVEL_COUNT; level += 1) {
bool enabled = log_level_enabled_flags[level];
string s = tprint("%s: %s", get_log_level_name(level), enabled ? STR("on") : STR("off"));
Gfx_Text_Metrics m = measure_text(font, s, FONT_HEIGHT, v2(1, 1));
Vector4 color = COLOR_WHITE;
Vector2 bottom_left = v2_sub(v2(x, y), m.functional_pos_min);
float L = bottom_left.x;
float R = L + m.visual_size.x;
float B = bottom_left.y;
float T = B + m.visual_size.y;
float mx = input_frame.mouse_x - window.width/2;
float my = input_frame.mouse_y - window.height/2;
bool hovered = mx >= L && mx < R && my >= B && my < T;
if (hovered) color = v4(.8, .8, .8, 1.0);
if (hovered && (is_key_down(MOUSE_BUTTON_LEFT) || is_key_down(MOUSE_BUTTON_RIGHT)))
color = v4(.6, .6, .6, 1.0);
if (hovered && is_key_just_released(MOUSE_BUTTON_LEFT))
log_level_enabled_flags[level] = !log_level_enabled_flags[level];
if (hovered && is_key_just_released(MOUSE_BUTTON_RIGHT)) {
if (level == LOG_VERBOSE) log_verbose("This is a log message");
if (level == LOG_INFO) log_info("This is a log message");
if (level == LOG_WARNING) log_warning("This is a log message");
if (level == LOG_ERROR) log_error("This is a log message");
}
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));
draw_text(font, s, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
draw_text(font, s, FONT_HEIGHT, v2(x, y), v2(1, 1), color);
y -= FONT_HEIGHT*1.3;
}
y -= FONT_HEIGHT*1.3;
draw_log(x, y);
}
return 0;
}
void draw_log(float x, float y) {
draw_text(font, STR("In-game log:"), FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
Gfx_Text_Metrics m = draw_text_and_measure(font, STR("In-game log:"), FONT_HEIGHT, v2(x, y), v2(1, 1), COLOR_WHITE);
y -= m.functional_size.y+20;
// Here we draw each entry in the log that we can fit on screen, starting from the top
// so we see the last log message first.
for (s64 i = num_log_messages-1; i >= 0; i--) {
Log_Level level = log_messages[i].level;
if (level >= 0 && level < LOG_LEVEL_COUNT && !log_level_enabled_flags[level]) {
// If it's disabled, skip it
continue;
}
// Set color reflecting log level
Vector4 color = COLOR_WHITE;
if (level == LOG_VERBOSE) color = v4(.6, .6, 1, 1);
else if (level == LOG_INFO) color = v4(.3, 1, .4, 1);
else if (level == LOG_WARNING) color = v4(.8, .8, 1, 1);
else if (level == LOG_ERROR) color = v4(1, .2, .2, 1);
draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x-1, y+1), v2(1, 1), COLOR_BLACK);
draw_text(font, log_messages[i].message, FONT_HEIGHT, v2(x, y), v2(1, 1), color);
y -= FONT_HEIGHT * 1.3;
if (y+FONT_HEIGHT < -window.height/2) break; // Occlude text outside of view
}
}

View file

@ -17,7 +17,7 @@ int entry(int argc, char **argv) {
Matrix4 rect_xform = m4_scalar(1.0);
rect_xform = m4_rotate_z(rect_xform, (f32)now);
rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0));
draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_RED);
draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_GREEN);
gfx_update();
}

View file

@ -23,7 +23,7 @@ int entry(int argc, char **argv) {
// as it was rasterized at with no down- or up-scaling.
// It's fairly common in video games to render the UI with a separate projection for this
// very reason.
draw_frame.projection = m4_make_orthographic_projection(window.pixel_width * -0.5, window.pixel_width * 0.5, window.height * -0.5, window.height * 0.5, -1, 10);
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);
// Easy drop shadow: Just draw the same text underneath with a slight offset
draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK);

View file

@ -534,8 +534,6 @@ void d3d11_draw_call(int number_of_rendered_quads, ID3D11ShaderResourceView **te
VTABLE(PSSetSamplers, d3d11_context, 3, 1, &d3d11_image_sampler_nl_fp);
VTABLE(PSSetShaderResources, d3d11_context, 0, num_textures, textures);
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
VTABLE(Draw, d3d11_context, number_of_rendered_quads * 6, 0);
}
@ -543,6 +541,8 @@ void d3d11_process_draw_frame() {
HRESULT hr;
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
///
// Maybe grow quad vbo
u32 required_size = sizeof(D3D11_Vertex) * draw_frame.num_blocks*QUADS_PER_BLOCK*6;
@ -566,6 +566,7 @@ void d3d11_process_draw_frame() {
log_verbose("Grew quad vbo to %d bytes.", d3d11_quad_vbo_size);
}
memset(d3d11_staging_quad_buffer, 0, d3d11_quad_vbo_size);// #Temporary
if (draw_frame.num_blocks > 0) {
///
@ -583,7 +584,8 @@ void d3d11_process_draw_frame() {
Draw_Quad_Block *block = &first_block;
tm_scope_cycles("Quad processing") {
while (block != 0 && block->num_quads > 0) tm_scope_cycles("Quad block") {
u64 block_index = 0;
while (block != 0 && block->num_quads > 0 && block_index < draw_frame.num_blocks) tm_scope_cycles("Quad block") {
for (u64 i = 0; i < block->num_quads; i++) {
Draw_Quad *q = &block->quad_buffer[i];
@ -667,7 +669,7 @@ void d3d11_process_draw_frame() {
BL->texture_index=TL->texture_index=TR->texture_index=BR->texture_index = texture_index;
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
u8 sampler = 0;
u8 sampler = -1;
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
sampler = 0;
@ -691,7 +693,7 @@ void d3d11_process_draw_frame() {
}
}
block_index += 1;
block = block->next;
}
}
@ -721,7 +723,7 @@ void d3d11_process_draw_frame() {
void gfx_update() {
if (window.should_close) return;
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
HRESULT hr;
///
@ -739,6 +741,7 @@ void gfx_update() {
VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING);
#if CONFIGURATION == DEBUG
///
// Check debug messages, output to stdout

View file

@ -85,7 +85,7 @@ typedef struct {
Heap_Block *heap_head;
bool heap_initted = false;
Spinlock *heap_lock; // This is terrible but I don't care for now
Spinlock heap_lock; // This is terrible but I don't care for now
u64 get_heap_block_size_excluding_metadata(Heap_Block *block) {
@ -234,7 +234,7 @@ void heap_init() {
assert(sizeof(Heap_Allocation_Metadata) % HEAP_ALIGNMENT == 0);
heap_initted = true;
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
heap_lock = os_make_spinlock(get_initialization_allocator());
spinlock_init(&heap_lock);
}
@ -244,7 +244,9 @@ void *heap_alloc(u64 size) {
if (!heap_initted) heap_init();
// #Sync #Speed oof
os_spinlock_lock(heap_lock);
spinlock_acquire_or_wait(&heap_lock);
@ -344,7 +346,7 @@ void *heap_alloc(u64 size) {
#endif
// #Sync #Speed oof
os_spinlock_unlock(heap_lock);
spinlock_release(&heap_lock);
void *p = ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
@ -356,7 +358,7 @@ void heap_dealloc(void *p) {
if (!heap_initted) heap_init();
os_spinlock_lock(heap_lock);
spinlock_acquire_or_wait(&heap_lock);
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);
@ -428,7 +430,7 @@ void heap_dealloc(void *p) {
// #Sync #Speed oof
os_spinlock_unlock(heap_lock);
spinlock_release(&heap_lock);
}
void* heap_allocator_proc(u64 size, void *p, Allocator_Message message, void* data) {

View file

@ -107,7 +107,7 @@
#define OGB_VERSION_MAJOR 0
#define OGB_VERSION_MINOR 0
#define OGB_VERSION_PATCH 3
#define OGB_VERSION_PATCH 4
#define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH)
@ -265,11 +265,14 @@ typedef u8 bool;
#include "hash.c"
#include "path_utils.c"
#include "linmath.c"
#include "range.c"
#include "hash_table.c"
#include "os_interface.c"
#include "concurrency.c"
#include "gfx_interface.c"
#include "font.c"
#include "profiling.c"
@ -317,6 +320,7 @@ void oogabooga_init(u64 program_memory_size) {
os_init(program_memory_size);
heap_init();
temporary_storage_init();
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
gfx_init();
log_verbose("CPU has sse1: %cs", features.sse1 ? "true" : "false");
log_verbose("CPU has sse2: %cs", features.sse2 ? "true" : "false");
@ -335,7 +339,7 @@ int main(int argc, char **argv) {
print("Ooga booga program started\n");
oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE);
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
assert(main != ENTRY_PROC, "You've ooga'd your last booga");

View file

@ -135,6 +135,8 @@ LRESULT CALLBACK win32_window_proc(HWND passed_window, UINT message, WPARAM wpar
void os_init(u64 program_memory_size) {
context.thread_id = GetCurrentThreadId();
memset(&window, 0, sizeof(window));
timeBeginPeriod(1);
@ -370,6 +372,7 @@ DWORD WINAPI win32_thread_invoker(LPVOID param) {
Thread *t = (Thread*)param;
temporary_storage_init();
context = t->initial_context;
context.thread_id = GetCurrentThreadId();
t->proc(t);
return 0;
}
@ -379,9 +382,15 @@ Thread* os_make_thread(Thread_Proc proc, Allocator allocator) {
t->id = 0; // This is set when we start it
t->proc = proc;
t->initial_context = context;
t->allocator = allocator;
return t;
}
void os_destroy_thread(Thread *t) {
os_join_thread(t);
CloseHandle(t->os_handle);
dealloc(t->allocator, t);
}
void os_start_thread(Thread *t) {
t->os_handle = CreateThread(
0,
@ -396,14 +405,24 @@ void os_start_thread(Thread *t) {
}
void os_join_thread(Thread *t) {
WaitForSingleObject(t->os_handle, INFINITE);
CloseHandle(t->os_handle);
}
///
// Mutex primitive
Mutex_Handle os_make_mutex() {
return CreateMutex(0, FALSE, 0);
local_persist const int MAX_ATTEMPTS = 100;
HANDLE m = CreateMutexW(0, FALSE, 0);
int attempts = 1;
while (m == 0) {
assert(attempts <= MAX_ATTEMPTS, "Failed creating win32 mutex. error %d", GetLastError());
m = CreateMutex(0, FALSE, 0);
attempts += 1;
}
return m;
}
void os_destroy_mutex(Mutex_Handle m) {
CloseHandle(m);
@ -415,8 +434,8 @@ void os_lock_mutex(Mutex_Handle m) {
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
break;
//case WAIT_ABANDONED:
// break;
default:
assert(false, "Unexpected mutex lock result");
@ -425,7 +444,7 @@ void os_lock_mutex(Mutex_Handle m) {
}
void os_unlock_mutex(Mutex_Handle m) {
BOOL result = ReleaseMutex(m);
assert(result, "Unlock mutex failed");
assert(result, "Unlock mutex 0x%x failed with error %d", m, GetLastError());
}
///

View file

@ -94,32 +94,30 @@ typedef struct Thread {
void* data;
Thread_Proc proc;
Thread_Handle os_handle;
Allocator allocator;
} Thread;
///
// Thread primitive
Thread* os_make_thread(Thread_Proc proc, Allocator allocator);
void os_destroy_thread(Thread *t);
void os_start_thread(Thread* t);
void os_join_thread(Thread* t);
///
// Mutex primitive
// Low-level Mutex primitive. Mutex in concurrency.c is probably a better alternative.
Mutex_Handle os_make_mutex();
void os_destroy_mutex(Mutex_Handle m);
void os_lock_mutex(Mutex_Handle m);
void os_unlock_mutex(Mutex_Handle m);
///
// Spinlock "primitive"
typedef struct Spinlock {
bool locked;
} Spinlock;
Spinlock *os_make_spinlock(Allocator allocator);
void os_spinlock_lock(Spinlock* l);
void os_spinlock_unlock(Spinlock* l);
typedef struct Spinlock Spinlock;
// #Cleanup Moved to threading.c
DEPRECATED(Spinlock *os_make_spinlock(Allocator allocator), "use spinlock_init instead");
DEPRECATED(void os_spinlock_lock(Spinlock* l), "use spinlock_acquire_or_wait instead");
DEPRECATED(void os_spinlock_unlock(Spinlock* l), "use spinlock_release instead");
///
// Concurrency utilities
@ -130,6 +128,7 @@ void os_spinlock_unlock(Spinlock* l);
// instruction anyways, so may as well just inline asm it (or Win32
// if we're compiling with msvc) (LDREX/STREX on ARM)
// - CharlieM July 8th 2024
// compare_and_swap in cpu.c
DEPRECATED(bool os_compare_and_swap_8 (u8 *a, u8 b, u8 old), "use compare_and_swap instead");
DEPRECATED(bool os_compare_and_swap_16 (u16 *a, u16 b, u16 old), "use compare_and_swap instead");
DEPRECATED(bool os_compare_and_swap_32 (u32 *a, u32 b, u32 old), "use compare_and_swap instead");

View file

@ -1,6 +1,6 @@
String_Builder _profile_output = {0};
bool profiler_initted = false;
Spinlock *_profiler_lock = 0;
Spinlock _profiler_lock;
void dump_profile_result() {
File file = os_file_open("google_trace.json", O_CREATE | O_WRITE);
@ -14,19 +14,19 @@ void dump_profile_result() {
}
void _profiler_report_time_cycles(string name, u64 count, u64 start) {
if (!profiler_initted) {
_profiler_lock = os_make_spinlock(get_heap_allocator());
spinlock_init(&_profiler_lock);
profiler_initted = true;
string_builder_init_reserve(&_profile_output, 1024*1000, get_heap_allocator());
}
os_spinlock_lock(_profiler_lock);
spinlock_acquire_or_wait(&_profiler_lock);
string fmt = STR("{\"cat\":\"function\",\"dur\":%.3f,\"name\":\"%s\",\"ph\":\"X\",\"pid\":0,\"tid\":%zu,\"ts\":%lld},");
string_builder_print(&_profile_output, fmt, (float64)count*1000, name, GetCurrentThreadId(), start*1000);
os_spinlock_unlock(_profiler_lock);
spinlock_release(&_profiler_lock);
}
#if ENABLE_PROFILING
#define tm_scope_cycles(name) \

42
oogabooga/range.c Normal file
View file

@ -0,0 +1,42 @@
// randy: we might wanna remove the f by default, similar to the Vector2 ?
// I know that we'll have a Range2i at some point, so maybe it's better to be explicit for less confusion?
// I'll leave this decision up to u charlie just delete this whenever u see it
typedef struct Range1f {
float min;
float max;
} Range1f;
// ...
typedef struct Range2f {
Vector2 min;
Vector2 max;
} Range2f;
inline Range2f range2f_make(Vector2 min, Vector2 max) { return (Range2f) { min, max }; }
Range2f range2f_shift(Range2f r, Vector2 shift) {
r.min = v2_add(r.min, shift);
r.max = v2_add(r.max, shift);
return r;
}
Range2f range2f_make_bottom_center(Vector2 size) {
Range2f range = {0};
range.max = size;
range = range2f_shift(range, v2(size.x * -0.5, 0.0));
return range;
}
Vector2 range2f_size(Range2f range) {
Vector2 size = {0};
size = v2_sub(range.min, range.max);
size.x = fabsf(size.x);
size.y = fabsf(size.y);
return size;
}
bool range2f_contains(Range2f range, Vector2 v) {
return v.x >= range.min.x && v.x <= range.max.x && v.y >= range.min.y && v.y <= range.max.y;
}

View file

@ -229,6 +229,7 @@ void default_logger(Log_Level level, string s) {
case LOG_INFO: print("[INFO]: %s\n", s); break;
case LOG_WARNING: print("[WARNING]: %s\n", s); break;
case LOG_ERROR: print("[ERROR]: %s\n", s); break;
case LOG_LEVEL_COUNT: break;
}
}

View file

@ -5,7 +5,7 @@
///
void log_heap() {
os_spinlock_lock(heap_lock);
spinlock_acquire_or_wait(&heap_lock);
print("\nHEAP:\n");
Heap_Block *block = heap_head;
@ -31,7 +31,7 @@ void log_heap() {
block = block->next;
}
os_spinlock_unlock(heap_lock);
spinlock_release(&heap_lock);
}
void test_allocator(bool do_log_heap) {
@ -1080,6 +1080,67 @@ void test_random_distribution() {
print("Min: %d, max: %d\n", min_bin, max_bin);
}
#define MUTEX_TEST_TASK_COUNT 1000
typedef struct Mutex_Test_Shared_Data {
int counter;
bool any_active_thread;
Mutex mutex;
} Mutex_Test_Shared_Data;
void mutex_test_increment_counter(Thread* t) {
Mutex_Test_Shared_Data* data = (Mutex_Test_Shared_Data*)t->data;
for (int i = 0; i < MUTEX_TEST_TASK_COUNT; i++) {
mutex_acquire_or_wait(&data->mutex);
assert(!data->any_active_thread, "Failed: More than one thread is in critical section!");
data->any_active_thread = true;
data->counter++;
data->any_active_thread = false;
mutex_release(&data->mutex);
}
}
void test_mutex() {
Mutex m;
// Test initialization
mutex_init(&m);
assert(m.spin_time_microseconds == MUTEX_DEFAULT_SPIN_TIME_MICROSECONDS, "Failed: Default spin time incorrect");
assert(!m.spinlock_acquired, "Failed: Spinlock should not be acquired after initialization");
// Test acquire and release without contention
mutex_acquire_or_wait(&m);
assert(m.spinlock_acquired, "Failed: Mutex should be acquired after mutex_acquire_or_wait");
mutex_release(&m);
assert(!m.spinlock_acquired, "Failed: Spinlock should not be acquired after mutex_release");
// Clean up
mutex_destroy(&m);
Mutex_Test_Shared_Data data;
data.counter = 0;
data.any_active_thread = false;
mutex_init(&data.mutex);
Allocator allocator = get_heap_allocator();
const int num_threads = 100;
Thread **threads = alloc(allocator, sizeof(Thread*)*num_threads);
for (u64 i = 0; i < num_threads; i++) {
threads[i] = os_make_thread(mutex_test_increment_counter, allocator);
threads[i]->data = &data;
}
for (u64 i = 0; i < num_threads; i++) {
os_start_thread(threads[i]);
}
for (u64 i = 0; i < num_threads; i++) {
os_join_thread(threads[i]);
}
assert(data.counter == num_threads * MUTEX_TEST_TASK_COUNT, "Failed: Counter does not match expected value after threading tasks");
mutex_destroy(&data.mutex);
}
void oogabooga_run_tests() {
@ -1095,20 +1156,6 @@ void oogabooga_run_tests() {
test_strings();
print("OK!\n");
#if CONFIGURATION != RELEASE
print("Thread bombing allocator... ");
Thread* threads[300];
for (int i = 0; i < 300; i++) {
threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
os_start_thread(threads[i]);
}
for (int i = 0; i < 300; i++) {
os_join_thread(threads[i]);
}
print("OK!\n");
#endif
print("Testing file IO... ");
test_file_io();
print("OK!\n");
@ -1129,4 +1176,9 @@ void oogabooga_run_tests() {
test_random_distribution();
print("OK!\n");
print("Testing mutex... ");
test_mutex();
print("OK!\n");
}