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
This commit is contained in:
parent
36c6ff5589
commit
b37652e1ce
20 changed files with 2490 additions and 1983 deletions
22
build.c
22
build.c
|
@ -3,18 +3,6 @@
|
||||||
///
|
///
|
||||||
// Build config stuff
|
// Build config stuff
|
||||||
|
|
||||||
#define OOGABOOGA_DEV 1
|
|
||||||
|
|
||||||
#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
|
|
||||||
#define ENABLE_SIMD 0
|
|
||||||
|
|
||||||
|
|
||||||
#define INITIAL_PROGRAM_MEMORY_SIZE MB(5)
|
#define INITIAL_PROGRAM_MEMORY_SIZE MB(5)
|
||||||
|
|
||||||
typedef struct Context_Extra {
|
typedef struct Context_Extra {
|
||||||
|
@ -38,15 +26,11 @@ typedef struct Context_Extra {
|
||||||
//
|
//
|
||||||
|
|
||||||
// this is a minimal starting point for new projects. Copy & rename to get started
|
// this is a minimal starting point for new projects. Copy & rename to get started
|
||||||
// #include "oogabooga/examples/minimal_game_loop.c"
|
#include "oogabooga/examples/minimal_game_loop.c"
|
||||||
|
|
||||||
// #include "oogabooga/examples/text_rendering.c"
|
// #include "oogabooga/examples/text_rendering.c"
|
||||||
|
// #include "oogabooga/examples/custom_logger.c"
|
||||||
// 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"
|
|
||||||
|
|
||||||
// This is where you swap in your own project!
|
// This is where you swap in your own project!
|
||||||
// #include "entry_yourepicgamename.c"
|
// #include "entry_yourepicgamename.c"
|
||||||
|
|
|
@ -1,3 +1,34 @@
|
||||||
|
## 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
|
## v0.00.003 - Fixes
|
||||||
|
|
||||||
Random:
|
Random:
|
||||||
|
|
|
@ -65,7 +65,9 @@ typedef enum Log_Level {
|
||||||
LOG_ERROR,
|
LOG_ERROR,
|
||||||
LOG_INFO,
|
LOG_INFO,
|
||||||
LOG_WARNING,
|
LOG_WARNING,
|
||||||
LOG_VERBOSE
|
LOG_VERBOSE,
|
||||||
|
|
||||||
|
LOG_LEVEL_COUNT,
|
||||||
} Log_Level;
|
} Log_Level;
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,6 +82,8 @@ Allocator get_heap_allocator();
|
||||||
typedef struct Context {
|
typedef struct Context {
|
||||||
void *logger; // void(*Logger_Proc)(Log_Level level, string fmt, ...)
|
void *logger; // void(*Logger_Proc)(Log_Level level, string fmt, ...)
|
||||||
|
|
||||||
|
u64 thread_id;
|
||||||
|
|
||||||
CONTEXT_EXTRA extra;
|
CONTEXT_EXTRA extra;
|
||||||
} Context;
|
} Context;
|
||||||
|
|
||||||
|
|
27
oogabooga/bucket_array.c
Normal file
27
oogabooga/bucket_array.c
Normal 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
153
oogabooga/concurrency.c
Normal 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);
|
||||||
|
}
|
|
@ -33,6 +33,8 @@ typedef struct Cpu_Capabilities {
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
volatile int *a = 0;
|
volatile int *a = 0;
|
||||||
*a = 5;
|
*a = 5;
|
||||||
|
a = (int*)0xDEADBEEF;
|
||||||
|
*a = 5;
|
||||||
}
|
}
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
#pragma intrinsic(__rdtsc)
|
#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);
|
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MEMORY_BARRIER _ReadWriteBarrier()
|
||||||
|
|
||||||
#elif COMPILER_GCC || COMPILER_CLANG
|
#elif COMPILER_GCC || COMPILER_CLANG
|
||||||
#define inline __attribute__((always_inline)) inline
|
#define inline __attribute__((always_inline)) inline
|
||||||
#define alignat(x) __attribute__((aligned(x)))
|
#define alignat(x) __attribute__((aligned(x)))
|
||||||
|
@ -103,6 +107,8 @@ typedef struct Cpu_Capabilities {
|
||||||
__builtin_trap();
|
__builtin_trap();
|
||||||
volatile int *a = 0;
|
volatile int *a = 0;
|
||||||
*a = 5;
|
*a = 5;
|
||||||
|
a = (int*)0xDEADBEEF;
|
||||||
|
*a = 5;
|
||||||
}
|
}
|
||||||
inline u64 rdtsc() {
|
inline u64 rdtsc() {
|
||||||
unsigned int lo, hi;
|
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);
|
return compare_and_swap_8((uint8_t*)a, (uint8_t)b, (uint8_t)old);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MEMORY_BARRIER __asm__ __volatile__("" ::: "memory")
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define inline inline
|
#define inline inline
|
||||||
#define COMPILER_HAS_MEMCPY_INTRINSICS 0
|
#define COMPILER_HAS_MEMCPY_INTRINSICS 0
|
||||||
|
@ -207,6 +215,8 @@ typedef struct Cpu_Capabilities {
|
||||||
|
|
||||||
#define deprecated(msg)
|
#define deprecated(msg)
|
||||||
|
|
||||||
|
#define MEMORY_BARRIER
|
||||||
|
|
||||||
#warning "Compiler is not explicitly supported, some things will probably not work as expected"
|
#warning "Compiler is not explicitly supported, some things will probably not work as expected"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -186,13 +186,13 @@ float4 sample_texture(int texture_index, int sampler_index, float2 uv) {
|
||||||
float4 ps_main(PS_INPUT input) : SV_TARGET
|
float4 ps_main(PS_INPUT input) : SV_TARGET
|
||||||
{
|
{
|
||||||
if (input.type == QUAD_TYPE_REGULAR) {
|
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;
|
return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
|
||||||
} else {
|
} else {
|
||||||
return input.color;
|
return input.color;
|
||||||
}
|
}
|
||||||
} else if (input.type == QUAD_TYPE_TEXT) {
|
} 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;
|
float alpha = sample_texture(input.texture_index, input.sampler_index, input.uv).x;
|
||||||
return float4(1.0, 1.0, 1.0, alpha)*input.color;
|
return float4(1.0, 1.0, 1.0, alpha)*input.color;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -117,13 +117,14 @@ Draw_Frame draw_frame = ZERO(Draw_Frame);
|
||||||
void reset_draw_frame(Draw_Frame *frame) {
|
void reset_draw_frame(Draw_Frame *frame) {
|
||||||
*frame = (Draw_Frame){0};
|
*frame = (Draw_Frame){0};
|
||||||
|
|
||||||
frame->current = &first_block;
|
frame->current = 0;
|
||||||
frame->current->num_quads = 0;
|
|
||||||
|
|
||||||
float32 aspect = (float32)window.width/(float32)window.height;
|
float32 aspect = (float32)window.width/(float32)window.height;
|
||||||
|
|
||||||
frame->projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10);
|
frame->projection = m4_make_orthographic_projection(-aspect, aspect, -1, 1, -1, 10);
|
||||||
frame->view = m4_scalar(1.0);
|
frame->view = m4_scalar(1.0);
|
||||||
|
|
||||||
|
frame->num_blocks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
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 = &first_block;
|
||||||
draw_frame.current->low_z = F32_MAX;
|
draw_frame.current->low_z = F32_MAX;
|
||||||
draw_frame.current->high_z = F32_MIN;
|
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);
|
assert(draw_frame.current->num_quads <= QUADS_PER_BLOCK);
|
||||||
|
|
||||||
if (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 = draw_frame.current->next;
|
||||||
draw_frame.current->num_quads = 0;
|
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.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;
|
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);
|
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})
|
#define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0})
|
||||||
|
|
190
oogabooga/examples/custom_logger.c
Normal file
190
oogabooga/examples/custom_logger.c
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ int entry(int argc, char **argv) {
|
||||||
Matrix4 rect_xform = m4_scalar(1.0);
|
Matrix4 rect_xform = m4_scalar(1.0);
|
||||||
rect_xform = m4_rotate_z(rect_xform, (f32)now);
|
rect_xform = m4_rotate_z(rect_xform, (f32)now);
|
||||||
rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0));
|
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();
|
gfx_update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ int entry(int argc, char **argv) {
|
||||||
// as it was rasterized at with no down- or up-scaling.
|
// 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
|
// It's fairly common in video games to render the UI with a separate projection for this
|
||||||
// very reason.
|
// 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
|
// 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);
|
draw_text(font, STR("I am text"), font_height, v2(-2, 2), v2(1, 1), COLOR_BLACK);
|
||||||
|
|
|
@ -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(PSSetSamplers, d3d11_context, 3, 1, &d3d11_image_sampler_nl_fp);
|
||||||
VTABLE(PSSetShaderResources, d3d11_context, 0, num_textures, textures);
|
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);
|
VTABLE(Draw, d3d11_context, number_of_rendered_quads * 6, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,6 +541,8 @@ void d3d11_process_draw_frame() {
|
||||||
|
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
|
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
||||||
|
|
||||||
///
|
///
|
||||||
// Maybe grow quad vbo
|
// Maybe grow quad vbo
|
||||||
u32 required_size = sizeof(D3D11_Vertex) * draw_frame.num_blocks*QUADS_PER_BLOCK*6;
|
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);
|
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) {
|
if (draw_frame.num_blocks > 0) {
|
||||||
///
|
///
|
||||||
|
@ -583,7 +584,8 @@ void d3d11_process_draw_frame() {
|
||||||
Draw_Quad_Block *block = &first_block;
|
Draw_Quad_Block *block = &first_block;
|
||||||
|
|
||||||
tm_scope_cycles("Quad processing") {
|
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++) {
|
for (u64 i = 0; i < block->num_quads; i++) {
|
||||||
|
|
||||||
Draw_Quad *q = &block->quad_buffer[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->texture_index=TL->texture_index=TR->texture_index=BR->texture_index = texture_index;
|
||||||
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
|
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
|
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
|
||||||
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
|
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
|
||||||
sampler = 0;
|
sampler = 0;
|
||||||
|
@ -691,7 +693,7 @@ void d3d11_process_draw_frame() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block_index += 1;
|
||||||
block = block->next;
|
block = block->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -721,7 +723,7 @@ void d3d11_process_draw_frame() {
|
||||||
void gfx_update() {
|
void gfx_update() {
|
||||||
if (window.should_close) return;
|
if (window.should_close) return;
|
||||||
|
|
||||||
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
|
||||||
|
|
||||||
HRESULT hr;
|
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);
|
VTABLE(Present, d3d11_swap_chain, window.enable_vsync, window.enable_vsync ? 0 : DXGI_PRESENT_ALLOW_TEARING);
|
||||||
|
|
||||||
|
|
||||||
#if CONFIGURATION == DEBUG
|
#if CONFIGURATION == DEBUG
|
||||||
///
|
///
|
||||||
// Check debug messages, output to stdout
|
// Check debug messages, output to stdout
|
||||||
|
|
|
@ -85,7 +85,7 @@ typedef struct {
|
||||||
|
|
||||||
Heap_Block *heap_head;
|
Heap_Block *heap_head;
|
||||||
bool heap_initted = false;
|
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) {
|
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);
|
assert(sizeof(Heap_Allocation_Metadata) % HEAP_ALIGNMENT == 0);
|
||||||
heap_initted = true;
|
heap_initted = true;
|
||||||
heap_head = make_heap_block(0, DEFAULT_HEAP_BLOCK_SIZE);
|
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,7 @@ void *heap_alloc(u64 size) {
|
||||||
if (!heap_initted) heap_init();
|
if (!heap_initted) heap_init();
|
||||||
|
|
||||||
// #Sync #Speed oof
|
// #Sync #Speed oof
|
||||||
os_spinlock_lock(heap_lock);
|
spinlock_acquire_or_wait(&heap_lock);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,7 +344,7 @@ void *heap_alloc(u64 size) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// #Sync #Speed oof
|
// #Sync #Speed oof
|
||||||
os_spinlock_unlock(heap_lock);
|
spinlock_release(&heap_lock);
|
||||||
|
|
||||||
|
|
||||||
void *p = ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
|
void *p = ((u8*)meta)+sizeof(Heap_Allocation_Metadata);
|
||||||
|
@ -356,7 +356,7 @@ void heap_dealloc(void *p) {
|
||||||
|
|
||||||
if (!heap_initted) heap_init();
|
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!");
|
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);
|
p = (u8*)p-sizeof(Heap_Allocation_Metadata);
|
||||||
|
@ -428,7 +428,7 @@ void heap_dealloc(void *p) {
|
||||||
|
|
||||||
|
|
||||||
// #Sync #Speed oof
|
// #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) {
|
void* heap_allocator_proc(u64 size, void *p, Allocator_Message message, void* data) {
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
|
|
||||||
#define OGB_VERSION_MAJOR 0
|
#define OGB_VERSION_MAJOR 0
|
||||||
#define OGB_VERSION_MINOR 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)
|
#define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH)
|
||||||
|
|
||||||
|
@ -270,7 +270,9 @@ typedef u8 bool;
|
||||||
#include "hash_table.c"
|
#include "hash_table.c"
|
||||||
|
|
||||||
#include "os_interface.c"
|
#include "os_interface.c"
|
||||||
|
#include "concurrency.c"
|
||||||
#include "gfx_interface.c"
|
#include "gfx_interface.c"
|
||||||
|
|
||||||
#include "font.c"
|
#include "font.c"
|
||||||
|
|
||||||
#include "profiling.c"
|
#include "profiling.c"
|
||||||
|
@ -318,6 +320,7 @@ void oogabooga_init(u64 program_memory_size) {
|
||||||
os_init(program_memory_size);
|
os_init(program_memory_size);
|
||||||
heap_init();
|
heap_init();
|
||||||
temporary_storage_init();
|
temporary_storage_init();
|
||||||
|
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
|
||||||
gfx_init();
|
gfx_init();
|
||||||
log_verbose("CPU has sse1: %cs", features.sse1 ? "true" : "false");
|
log_verbose("CPU has sse1: %cs", features.sse1 ? "true" : "false");
|
||||||
log_verbose("CPU has sse2: %cs", features.sse2 ? "true" : "false");
|
log_verbose("CPU has sse2: %cs", features.sse2 ? "true" : "false");
|
||||||
|
@ -336,7 +339,7 @@ int main(int argc, char **argv) {
|
||||||
|
|
||||||
print("Ooga booga program started\n");
|
print("Ooga booga program started\n");
|
||||||
oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE);
|
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");
|
assert(main != ENTRY_PROC, "You've ooga'd your last booga");
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,8 @@ LRESULT CALLBACK win32_window_proc(HWND passed_window, UINT message, WPARAM wpar
|
||||||
|
|
||||||
void os_init(u64 program_memory_size) {
|
void os_init(u64 program_memory_size) {
|
||||||
|
|
||||||
|
context.thread_id = GetCurrentThreadId();
|
||||||
|
|
||||||
memset(&window, 0, sizeof(window));
|
memset(&window, 0, sizeof(window));
|
||||||
|
|
||||||
timeBeginPeriod(1);
|
timeBeginPeriod(1);
|
||||||
|
@ -370,6 +372,7 @@ DWORD WINAPI win32_thread_invoker(LPVOID param) {
|
||||||
Thread *t = (Thread*)param;
|
Thread *t = (Thread*)param;
|
||||||
temporary_storage_init();
|
temporary_storage_init();
|
||||||
context = t->initial_context;
|
context = t->initial_context;
|
||||||
|
context.thread_id = GetCurrentThreadId();
|
||||||
t->proc(t);
|
t->proc(t);
|
||||||
return 0;
|
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->id = 0; // This is set when we start it
|
||||||
t->proc = proc;
|
t->proc = proc;
|
||||||
t->initial_context = context;
|
t->initial_context = context;
|
||||||
|
t->allocator = allocator;
|
||||||
|
|
||||||
return t;
|
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) {
|
void os_start_thread(Thread *t) {
|
||||||
t->os_handle = CreateThread(
|
t->os_handle = CreateThread(
|
||||||
0,
|
0,
|
||||||
|
@ -396,14 +405,24 @@ void os_start_thread(Thread *t) {
|
||||||
}
|
}
|
||||||
void os_join_thread(Thread *t) {
|
void os_join_thread(Thread *t) {
|
||||||
WaitForSingleObject(t->os_handle, INFINITE);
|
WaitForSingleObject(t->os_handle, INFINITE);
|
||||||
CloseHandle(t->os_handle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
// Mutex primitive
|
// Mutex primitive
|
||||||
|
|
||||||
Mutex_Handle os_make_mutex() {
|
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) {
|
void os_destroy_mutex(Mutex_Handle m) {
|
||||||
CloseHandle(m);
|
CloseHandle(m);
|
||||||
|
@ -415,8 +434,8 @@ void os_lock_mutex(Mutex_Handle m) {
|
||||||
case WAIT_OBJECT_0:
|
case WAIT_OBJECT_0:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WAIT_ABANDONED:
|
//case WAIT_ABANDONED:
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false, "Unexpected mutex lock result");
|
assert(false, "Unexpected mutex lock result");
|
||||||
|
@ -425,7 +444,7 @@ void os_lock_mutex(Mutex_Handle m) {
|
||||||
}
|
}
|
||||||
void os_unlock_mutex(Mutex_Handle m) {
|
void os_unlock_mutex(Mutex_Handle m) {
|
||||||
BOOL result = ReleaseMutex(m);
|
BOOL result = ReleaseMutex(m);
|
||||||
assert(result, "Unlock mutex failed");
|
assert(result, "Unlock mutex 0x%x failed with error %d", m, GetLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -947,8 +966,8 @@ void os_update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (last_window.scaled_width != window.scaled_width || last_window.scaled_height != window.scaled_height) {
|
if (last_window.scaled_width != window.scaled_width || last_window.scaled_height != window.scaled_height) {
|
||||||
window.width = window.scaled_width/dpi_scale_factor;
|
window.width = window.scaled_width*dpi_scale_factor;
|
||||||
window.height = window.scaled_height/dpi_scale_factor;
|
window.height = window.scaled_height*dpi_scale_factor;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL ok;
|
BOOL ok;
|
||||||
|
|
|
@ -94,32 +94,30 @@ typedef struct Thread {
|
||||||
void* data;
|
void* data;
|
||||||
Thread_Proc proc;
|
Thread_Proc proc;
|
||||||
Thread_Handle os_handle;
|
Thread_Handle os_handle;
|
||||||
|
Allocator allocator;
|
||||||
} Thread;
|
} Thread;
|
||||||
|
|
||||||
///
|
///
|
||||||
// Thread primitive
|
// Thread primitive
|
||||||
Thread* os_make_thread(Thread_Proc proc, Allocator allocator);
|
Thread* os_make_thread(Thread_Proc proc, Allocator allocator);
|
||||||
|
void os_destroy_thread(Thread *t);
|
||||||
void os_start_thread(Thread* t);
|
void os_start_thread(Thread* t);
|
||||||
void os_join_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();
|
Mutex_Handle os_make_mutex();
|
||||||
void os_destroy_mutex(Mutex_Handle m);
|
void os_destroy_mutex(Mutex_Handle m);
|
||||||
void os_lock_mutex(Mutex_Handle m);
|
void os_lock_mutex(Mutex_Handle m);
|
||||||
void os_unlock_mutex(Mutex_Handle m);
|
void os_unlock_mutex(Mutex_Handle m);
|
||||||
|
|
||||||
///
|
typedef struct Spinlock Spinlock;
|
||||||
// Spinlock "primitive"
|
// #Cleanup Moved to threading.c
|
||||||
typedef struct Spinlock {
|
DEPRECATED(Spinlock *os_make_spinlock(Allocator allocator), "use spinlock_init instead");
|
||||||
bool locked;
|
DEPRECATED(void os_spinlock_lock(Spinlock* l), "use spinlock_acquire_or_wait instead");
|
||||||
} Spinlock;
|
DEPRECATED(void os_spinlock_unlock(Spinlock* l), "use spinlock_release instead");
|
||||||
Spinlock *os_make_spinlock(Allocator allocator);
|
|
||||||
void os_spinlock_lock(Spinlock* l);
|
|
||||||
void os_spinlock_unlock(Spinlock* l);
|
|
||||||
|
|
||||||
///
|
///
|
||||||
// Concurrency utilities
|
// Concurrency utilities
|
||||||
|
@ -130,6 +128,7 @@ void os_spinlock_unlock(Spinlock* l);
|
||||||
// instruction anyways, so may as well just inline asm it (or Win32
|
// instruction anyways, so may as well just inline asm it (or Win32
|
||||||
// if we're compiling with msvc) (LDREX/STREX on ARM)
|
// if we're compiling with msvc) (LDREX/STREX on ARM)
|
||||||
// - CharlieM July 8th 2024
|
// - 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_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_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");
|
DEPRECATED(bool os_compare_and_swap_32 (u32 *a, u32 b, u32 old), "use compare_and_swap instead");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
String_Builder _profile_output = {0};
|
String_Builder _profile_output = {0};
|
||||||
bool profiler_initted = false;
|
bool profiler_initted = false;
|
||||||
Spinlock *_profiler_lock = 0;
|
Spinlock _profiler_lock;
|
||||||
void dump_profile_result() {
|
void dump_profile_result() {
|
||||||
File file = os_file_open("google_trace.json", O_CREATE | O_WRITE);
|
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) {
|
void _profiler_report_time_cycles(string name, u64 count, u64 start) {
|
||||||
if (!profiler_initted) {
|
if (!profiler_initted) {
|
||||||
_profiler_lock = os_make_spinlock(get_heap_allocator());
|
spinlock_init(&_profiler_lock);
|
||||||
profiler_initted = true;
|
profiler_initted = true;
|
||||||
|
|
||||||
string_builder_init_reserve(&_profile_output, 1024*1000, get_heap_allocator());
|
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 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);
|
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
|
#if ENABLE_PROFILING
|
||||||
#define tm_scope_cycles(name) \
|
#define tm_scope_cycles(name) \
|
||||||
|
|
|
@ -229,6 +229,7 @@ void default_logger(Log_Level level, string s) {
|
||||||
case LOG_INFO: print("[INFO]: %s\n", s); break;
|
case LOG_INFO: print("[INFO]: %s\n", s); break;
|
||||||
case LOG_WARNING: print("[WARNING]: %s\n", s); break;
|
case LOG_WARNING: print("[WARNING]: %s\n", s); break;
|
||||||
case LOG_ERROR: print("[ERROR]: %s\n", s); break;
|
case LOG_ERROR: print("[ERROR]: %s\n", s); break;
|
||||||
|
case LOG_LEVEL_COUNT: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
///
|
///
|
||||||
|
|
||||||
void log_heap() {
|
void log_heap() {
|
||||||
os_spinlock_lock(heap_lock);
|
spinlock_acquire_or_wait(&heap_lock);
|
||||||
print("\nHEAP:\n");
|
print("\nHEAP:\n");
|
||||||
|
|
||||||
Heap_Block *block = heap_head;
|
Heap_Block *block = heap_head;
|
||||||
|
@ -31,7 +31,7 @@ void log_heap() {
|
||||||
|
|
||||||
block = block->next;
|
block = block->next;
|
||||||
}
|
}
|
||||||
os_spinlock_unlock(heap_lock);
|
spinlock_release(&heap_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_allocator(bool do_log_heap) {
|
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);
|
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() {
|
void oogabooga_run_tests() {
|
||||||
|
|
||||||
|
|
||||||
|
@ -1095,20 +1156,6 @@ void oogabooga_run_tests() {
|
||||||
test_strings();
|
test_strings();
|
||||||
print("OK!\n");
|
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... ");
|
print("Testing file IO... ");
|
||||||
test_file_io();
|
test_file_io();
|
||||||
print("OK!\n");
|
print("OK!\n");
|
||||||
|
@ -1129,4 +1176,9 @@ void oogabooga_run_tests() {
|
||||||
test_random_distribution();
|
test_random_distribution();
|
||||||
print("OK!\n");
|
print("OK!\n");
|
||||||
|
|
||||||
|
print("Testing mutex... ");
|
||||||
|
test_mutex();
|
||||||
|
print("OK!\n");
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
Reference in a new issue