Merge branch 'dev'

This commit is contained in:
Charlie 2024-07-07 20:45:45 +02:00
commit 8d081c1afe
22 changed files with 3823 additions and 685 deletions

17
build.c
View file

@ -3,17 +3,6 @@
///
// Build config stuff
#define RUN_TESTS 0
// This is only for people developing oogabooga!
#define OOGABOOGA_DEV 1
#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 1
#define INITIAL_PROGRAM_MEMORY_SIZE MB(5)
typedef struct Context_Extra {
@ -37,13 +26,15 @@ typedef struct Context_Extra {
//
// 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"
// An engine dev stress test for rendering
// #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"
// #include "entry_randygame.c"
// This is where you swap in your own project!
// #include "entry_yourepicgamename.c"

40
changelog.txt Normal file
View file

@ -0,0 +1,40 @@
## v0.00.002 - Text rendering, image manipulation, hash tables
Renderer:
- Pass 8-bit integers "type" and "sampler_index" to shader
- Sample texture differently depending on "type" (text or regular quad)
- Sample with nearest/linear min/mag depending on sampler_index
- Set min/mag filtering in Draw_Quad
- Images are now created and deleted directly with gfx calls rather than deferring it for gfx_update.
- We can now set image sub data with gfx_set_image_data()
- Images are no longer hard coded to 4 channels
Text/Fonts:
- Added font loading with lazy initialization/rasterization when it's needed
- Load & rasterize any fonts with load_font_from_disk()
- Fonts automatically rasterize glyphs into atlases and generate metrics on the fly
- Measure text for very_useful metrics with measure_text()
- Walk over glyphs and their metrics with walk_glyphs()
- Implemented simple but powerful text rendering
- Draw text with draw_text() or draw_text_xform()
- See oogabooga/examples/text_rendering.c for more
Other:
- Added 3 simple hash functions:
- 64-bit xx_hash(): Very fast hash for 64-bit values
- 64-bit city_hash(): Very fast hash for short strings
- 64-bit djb2_hash(): Fast hash for strings longer than 32 bytes
- Made a generic get_hash() macro to detect the type you are trying to hash and call the approriate procedure
- Introduced a simple (kinda slow) hash table with following procedures:
- hash_table_add
- hash_table_find
- hash_table_contains
- hash_table_set
See hash_table.c for more information.
- Utf8 utility:
- utf8_to_utf32(): convert utf8 bytes to a single u32 codepoint
- next_utf8(): Convert first utf8 character in a string to a u32 codepoint and advance the passed string to the next unicode
- Renamed m4_multiply -> m4_mul for consistency
- Refactored os window to be DPI aware
- Window now has scaled_width, scaled_height which is the DPI-scaled size. Previous window.width & window.height are now aliased as window.pixel_height and window.pixel_width for clarity
- in minimal example, renamed hammer_xform -> rect_xform

View file

@ -118,3 +118,19 @@ void pop_context() {
}
u64 get_next_power_of_two(u64 x) {
if (x == 0) {
return 1;
}
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,11 @@ struct VS_INPUT
float4 position : POSITION;
float2 uv : TEXCOORD;
float4 color : COLOR;
int texture_index: TEXTURE_INDEX;
int data1: DATA1_;
// s8 texture_index
// u8 type
// u8 sampler_index
// u8
};
struct PS_INPUT
@ -12,6 +16,8 @@ struct PS_INPUT
float2 uv : TEXCOORD0;
float4 color : COLOR;
int texture_index: TEXTURE_INDEX;
int type: TYPE;
int sampler_index: SAMPLER_INDEX;
};
PS_INPUT vs_main(VS_INPUT input)
@ -20,56 +26,178 @@ PS_INPUT vs_main(VS_INPUT input)
output.position = input.position;
output.uv = input.uv;
output.color = input.color;
output.texture_index = input.texture_index;
output.texture_index = (input.data1) & 0xFF;
output.type = (input.data1 >> 8) & 0xFF;
output.sampler_index = (input.data1 >> 16) & 0xFF;
return output;
}
// #Magicvalue
Texture2D textures[32] : register(t0);
SamplerState image_sampler : register(s0);
SamplerState image_sampler_0 : register(s0);
SamplerState image_sampler_1 : register(s1);
SamplerState image_sampler_2 : register(s2);
SamplerState image_sampler_3 : register(s3);
float4 sample_texture(int texture_index, float2 uv) {
// #Portability sigh
if (texture_index == 0) return textures[0].Sample(image_sampler, uv);
else if (texture_index == 1) return textures[1].Sample(image_sampler, uv);
else if (texture_index == 2) return textures[2].Sample(image_sampler, uv);
else if (texture_index == 3) return textures[3].Sample(image_sampler, uv);
else if (texture_index == 4) return textures[4].Sample(image_sampler, uv);
else if (texture_index == 5) return textures[5].Sample(image_sampler, uv);
else if (texture_index == 6) return textures[6].Sample(image_sampler, uv);
else if (texture_index == 7) return textures[7].Sample(image_sampler, uv);
else if (texture_index == 8) return textures[8].Sample(image_sampler, uv);
else if (texture_index == 9) return textures[9].Sample(image_sampler, uv);
else if (texture_index == 10) return textures[10].Sample(image_sampler, uv);
else if (texture_index == 11) return textures[11].Sample(image_sampler, uv);
else if (texture_index == 12) return textures[12].Sample(image_sampler, uv);
else if (texture_index == 13) return textures[13].Sample(image_sampler, uv);
else if (texture_index == 14) return textures[14].Sample(image_sampler, uv);
else if (texture_index == 15) return textures[15].Sample(image_sampler, uv);
else if (texture_index == 16) return textures[16].Sample(image_sampler, uv);
else if (texture_index == 17) return textures[17].Sample(image_sampler, uv);
else if (texture_index == 18) return textures[18].Sample(image_sampler, uv);
else if (texture_index == 19) return textures[19].Sample(image_sampler, uv);
else if (texture_index == 20) return textures[20].Sample(image_sampler, uv);
else if (texture_index == 21) return textures[21].Sample(image_sampler, uv);
else if (texture_index == 22) return textures[22].Sample(image_sampler, uv);
else if (texture_index == 23) return textures[23].Sample(image_sampler, uv);
else if (texture_index == 24) return textures[24].Sample(image_sampler, uv);
else if (texture_index == 25) return textures[25].Sample(image_sampler, uv);
else if (texture_index == 26) return textures[26].Sample(image_sampler, uv);
else if (texture_index == 27) return textures[27].Sample(image_sampler, uv);
else if (texture_index == 28) return textures[28].Sample(image_sampler, uv);
else if (texture_index == 29) return textures[29].Sample(image_sampler, uv);
else if (texture_index == 30) return textures[30].Sample(image_sampler, uv);
else if (texture_index == 31) return textures[31].Sample(image_sampler, uv);
float4 sample_texture(int texture_index, int sampler_index, float2 uv) {
// I love hlsl
if (sampler_index == 0) {
if (texture_index == 0) return textures[0].Sample(image_sampler_0, uv);
else if (texture_index == 1) return textures[1].Sample(image_sampler_0, uv);
else if (texture_index == 2) return textures[2].Sample(image_sampler_0, uv);
else if (texture_index == 3) return textures[3].Sample(image_sampler_0, uv);
else if (texture_index == 4) return textures[4].Sample(image_sampler_0, uv);
else if (texture_index == 5) return textures[5].Sample(image_sampler_0, uv);
else if (texture_index == 6) return textures[6].Sample(image_sampler_0, uv);
else if (texture_index == 7) return textures[7].Sample(image_sampler_0, uv);
else if (texture_index == 8) return textures[8].Sample(image_sampler_0, uv);
else if (texture_index == 9) return textures[9].Sample(image_sampler_0, uv);
else if (texture_index == 10) return textures[10].Sample(image_sampler_0, uv);
else if (texture_index == 11) return textures[11].Sample(image_sampler_0, uv);
else if (texture_index == 12) return textures[12].Sample(image_sampler_0, uv);
else if (texture_index == 13) return textures[13].Sample(image_sampler_0, uv);
else if (texture_index == 14) return textures[14].Sample(image_sampler_0, uv);
else if (texture_index == 15) return textures[15].Sample(image_sampler_0, uv);
else if (texture_index == 16) return textures[16].Sample(image_sampler_0, uv);
else if (texture_index == 17) return textures[17].Sample(image_sampler_0, uv);
else if (texture_index == 18) return textures[18].Sample(image_sampler_0, uv);
else if (texture_index == 19) return textures[19].Sample(image_sampler_0, uv);
else if (texture_index == 20) return textures[20].Sample(image_sampler_0, uv);
else if (texture_index == 21) return textures[21].Sample(image_sampler_0, uv);
else if (texture_index == 22) return textures[22].Sample(image_sampler_0, uv);
else if (texture_index == 23) return textures[23].Sample(image_sampler_0, uv);
else if (texture_index == 24) return textures[24].Sample(image_sampler_0, uv);
else if (texture_index == 25) return textures[25].Sample(image_sampler_0, uv);
else if (texture_index == 26) return textures[26].Sample(image_sampler_0, uv);
else if (texture_index == 27) return textures[27].Sample(image_sampler_0, uv);
else if (texture_index == 28) return textures[28].Sample(image_sampler_0, uv);
else if (texture_index == 29) return textures[29].Sample(image_sampler_0, uv);
else if (texture_index == 30) return textures[30].Sample(image_sampler_0, uv);
else if (texture_index == 31) return textures[31].Sample(image_sampler_0, uv);
} else if (sampler_index == 1) {
if (texture_index == 0) return textures[0].Sample(image_sampler_1, uv);
else if (texture_index == 1) return textures[1].Sample(image_sampler_1, uv);
else if (texture_index == 2) return textures[2].Sample(image_sampler_1, uv);
else if (texture_index == 3) return textures[3].Sample(image_sampler_1, uv);
else if (texture_index == 4) return textures[4].Sample(image_sampler_1, uv);
else if (texture_index == 5) return textures[5].Sample(image_sampler_1, uv);
else if (texture_index == 6) return textures[6].Sample(image_sampler_1, uv);
else if (texture_index == 7) return textures[7].Sample(image_sampler_1, uv);
else if (texture_index == 8) return textures[8].Sample(image_sampler_1, uv);
else if (texture_index == 9) return textures[9].Sample(image_sampler_1, uv);
else if (texture_index == 10) return textures[10].Sample(image_sampler_1, uv);
else if (texture_index == 11) return textures[11].Sample(image_sampler_1, uv);
else if (texture_index == 12) return textures[12].Sample(image_sampler_1, uv);
else if (texture_index == 13) return textures[13].Sample(image_sampler_1, uv);
else if (texture_index == 14) return textures[14].Sample(image_sampler_1, uv);
else if (texture_index == 15) return textures[15].Sample(image_sampler_1, uv);
else if (texture_index == 16) return textures[16].Sample(image_sampler_1, uv);
else if (texture_index == 17) return textures[17].Sample(image_sampler_1, uv);
else if (texture_index == 18) return textures[18].Sample(image_sampler_1, uv);
else if (texture_index == 19) return textures[19].Sample(image_sampler_1, uv);
else if (texture_index == 20) return textures[20].Sample(image_sampler_1, uv);
else if (texture_index == 21) return textures[21].Sample(image_sampler_1, uv);
else if (texture_index == 22) return textures[22].Sample(image_sampler_1, uv);
else if (texture_index == 23) return textures[23].Sample(image_sampler_1, uv);
else if (texture_index == 24) return textures[24].Sample(image_sampler_1, uv);
else if (texture_index == 25) return textures[25].Sample(image_sampler_1, uv);
else if (texture_index == 26) return textures[26].Sample(image_sampler_1, uv);
else if (texture_index == 27) return textures[27].Sample(image_sampler_1, uv);
else if (texture_index == 28) return textures[28].Sample(image_sampler_1, uv);
else if (texture_index == 29) return textures[29].Sample(image_sampler_1, uv);
else if (texture_index == 30) return textures[30].Sample(image_sampler_1, uv);
else if (texture_index == 31) return textures[31].Sample(image_sampler_1, uv);
} else if (sampler_index == 2) {
if (texture_index == 0) return textures[0].Sample(image_sampler_2, uv);
else if (texture_index == 1) return textures[1].Sample(image_sampler_2, uv);
else if (texture_index == 2) return textures[2].Sample(image_sampler_2, uv);
else if (texture_index == 3) return textures[3].Sample(image_sampler_2, uv);
else if (texture_index == 4) return textures[4].Sample(image_sampler_2, uv);
else if (texture_index == 5) return textures[5].Sample(image_sampler_2, uv);
else if (texture_index == 6) return textures[6].Sample(image_sampler_2, uv);
else if (texture_index == 7) return textures[7].Sample(image_sampler_2, uv);
else if (texture_index == 8) return textures[8].Sample(image_sampler_2, uv);
else if (texture_index == 9) return textures[9].Sample(image_sampler_2, uv);
else if (texture_index == 10) return textures[10].Sample(image_sampler_2, uv);
else if (texture_index == 11) return textures[11].Sample(image_sampler_2, uv);
else if (texture_index == 12) return textures[12].Sample(image_sampler_2, uv);
else if (texture_index == 13) return textures[13].Sample(image_sampler_2, uv);
else if (texture_index == 14) return textures[14].Sample(image_sampler_2, uv);
else if (texture_index == 15) return textures[15].Sample(image_sampler_2, uv);
else if (texture_index == 16) return textures[16].Sample(image_sampler_2, uv);
else if (texture_index == 17) return textures[17].Sample(image_sampler_2, uv);
else if (texture_index == 18) return textures[18].Sample(image_sampler_2, uv);
else if (texture_index == 19) return textures[19].Sample(image_sampler_2, uv);
else if (texture_index == 20) return textures[20].Sample(image_sampler_2, uv);
else if (texture_index == 21) return textures[21].Sample(image_sampler_2, uv);
else if (texture_index == 22) return textures[22].Sample(image_sampler_2, uv);
else if (texture_index == 23) return textures[23].Sample(image_sampler_2, uv);
else if (texture_index == 24) return textures[24].Sample(image_sampler_2, uv);
else if (texture_index == 25) return textures[25].Sample(image_sampler_2, uv);
else if (texture_index == 26) return textures[26].Sample(image_sampler_2, uv);
else if (texture_index == 27) return textures[27].Sample(image_sampler_2, uv);
else if (texture_index == 28) return textures[28].Sample(image_sampler_2, uv);
else if (texture_index == 29) return textures[29].Sample(image_sampler_2, uv);
else if (texture_index == 30) return textures[30].Sample(image_sampler_2, uv);
else if (texture_index == 31) return textures[31].Sample(image_sampler_2, uv);
} else if (sampler_index == 3) {
if (texture_index == 0) return textures[0].Sample(image_sampler_3, uv);
else if (texture_index == 1) return textures[1].Sample(image_sampler_3, uv);
else if (texture_index == 2) return textures[2].Sample(image_sampler_3, uv);
else if (texture_index == 3) return textures[3].Sample(image_sampler_3, uv);
else if (texture_index == 4) return textures[4].Sample(image_sampler_3, uv);
else if (texture_index == 5) return textures[5].Sample(image_sampler_3, uv);
else if (texture_index == 6) return textures[6].Sample(image_sampler_3, uv);
else if (texture_index == 7) return textures[7].Sample(image_sampler_3, uv);
else if (texture_index == 8) return textures[8].Sample(image_sampler_3, uv);
else if (texture_index == 9) return textures[9].Sample(image_sampler_3, uv);
else if (texture_index == 10) return textures[10].Sample(image_sampler_3, uv);
else if (texture_index == 11) return textures[11].Sample(image_sampler_3, uv);
else if (texture_index == 12) return textures[12].Sample(image_sampler_3, uv);
else if (texture_index == 13) return textures[13].Sample(image_sampler_3, uv);
else if (texture_index == 14) return textures[14].Sample(image_sampler_3, uv);
else if (texture_index == 15) return textures[15].Sample(image_sampler_3, uv);
else if (texture_index == 16) return textures[16].Sample(image_sampler_3, uv);
else if (texture_index == 17) return textures[17].Sample(image_sampler_3, uv);
else if (texture_index == 18) return textures[18].Sample(image_sampler_3, uv);
else if (texture_index == 19) return textures[19].Sample(image_sampler_3, uv);
else if (texture_index == 20) return textures[20].Sample(image_sampler_3, uv);
else if (texture_index == 21) return textures[21].Sample(image_sampler_3, uv);
else if (texture_index == 22) return textures[22].Sample(image_sampler_3, uv);
else if (texture_index == 23) return textures[23].Sample(image_sampler_3, uv);
else if (texture_index == 24) return textures[24].Sample(image_sampler_3, uv);
else if (texture_index == 25) return textures[25].Sample(image_sampler_3, uv);
else if (texture_index == 26) return textures[26].Sample(image_sampler_3, uv);
else if (texture_index == 27) return textures[27].Sample(image_sampler_3, uv);
else if (texture_index == 28) return textures[28].Sample(image_sampler_3, uv);
else if (texture_index == 29) return textures[29].Sample(image_sampler_3, uv);
else if (texture_index == 30) return textures[30].Sample(image_sampler_3, uv);
else if (texture_index == 31) return textures[31].Sample(image_sampler_3, uv);
}
return float4(1.0, 0.0, 0.0, 1.0);
}
#define QUAD_TYPE_REGULAR 0
#define QUAD_TYPE_TEXT 1
float4 ps_main(PS_INPUT input) : SV_TARGET
{
if (input.texture_index >= 0) {
return sample_texture(input.texture_index, input.uv);
} else {
return input.color;
if (input.type == QUAD_TYPE_REGULAR) {
if (input.texture_index >= 0) {
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) {
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 {
return input.color;
}
}
return float4(0.0, 1.0, 0.0, 1.0);
}

View file

@ -44,6 +44,22 @@ Usage:
// If you ever need to free the image:
delete_image(image);
API:
Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip);
Draw_Quad *draw_quad(Draw_Quad quad);
Draw_Quad *draw_quad_xform(Draw_Quad quad, Matrix4 xform);
Draw_Quad *draw_rect(Vector2 position, Vector2 size, Vector4 color);
Draw_Quad *draw_rect_xform(Matrix4 xform, Vector2 size, Vector4 color);
Draw_Quad *draw_image(Gfx_Image *image, Vector2 position, Vector2 size, Vector4 color);
Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vector4 color);
// raster_height is the pixel height that the text will be rasterized at. If text is blurry,
// you can try to increase raster_height and lower scale.
void draw_text(Gfx_Font *font, string text, u32 raster_height, Vector2 position, Vector2 scale, Vector4 color);
void draw_text_xform(Gfx_Font *font, string text, u32 raster_height, Matrix4 xform, Vector4 color);
*/
@ -52,7 +68,6 @@ Usage:
#define QUADS_PER_BLOCK 1024
typedef struct Draw_Quad {
Vector2 bottom_left, top_left, top_right, bottom_right;
@ -62,6 +77,11 @@ typedef struct Draw_Quad {
// x1, y1, x2, y2
Vector4 uv;
u8 type;
Gfx_Filter_Mode image_min_filter;
Gfx_Filter_Mode image_mag_filter;
} Draw_Quad;
@ -83,9 +103,6 @@ typedef struct Draw_Frame {
Matrix4 projection;
Matrix4 view;
Gfx_Handle garbage_stack[4096];
u64 garbage_stack_count;
} Draw_Frame;
// This frame is passed to the platform layer and rendered in os_update.
// Resets every frame.
@ -108,6 +125,9 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
quad.top_left = m4_transform(world_to_clip, v4(v2_expand(quad.top_left), 0, 1)).xy;
quad.top_right = m4_transform(world_to_clip, v4(v2_expand(quad.top_right), 0, 1)).xy;
quad.bottom_right = m4_transform(world_to_clip, v4(v2_expand(quad.bottom_right), 0, 1)).xy;
quad.image_min_filter = GFX_FILTER_MODE_NEAREST;
quad.image_min_filter = GFX_FILTER_MODE_NEAREST;
if (!draw_frame.current) draw_frame.current = &first_block;
@ -134,14 +154,14 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
return &draw_frame.current->quad_buffer[draw_frame.current->num_quads-1];
}
Draw_Quad *draw_quad(Draw_Quad quad) {
return draw_quad_projected(quad, m4_multiply(draw_frame.projection, m4_inverse(draw_frame.view)));
return draw_quad_projected(quad, m4_mul(draw_frame.projection, m4_inverse(draw_frame.view)));
}
Draw_Quad *draw_quad_xform(Draw_Quad quad, Matrix4 xform) {
Matrix4 world_to_clip = m4_scalar(1.0);
world_to_clip = m4_multiply(world_to_clip, draw_frame.projection);
world_to_clip = m4_multiply(world_to_clip, m4_inverse(draw_frame.view));
world_to_clip = m4_multiply(world_to_clip, xform);
world_to_clip = m4_mul(world_to_clip, draw_frame.projection);
world_to_clip = m4_mul(world_to_clip, m4_inverse(draw_frame.view));
world_to_clip = m4_mul(world_to_clip, xform);
return draw_quad_projected(quad, world_to_clip);
}
@ -158,6 +178,7 @@ Draw_Quad *draw_rect(Vector2 position, Vector2 size, Vector4 color) {
q.bottom_right = v2(right, bottom);
q.color = color;
q.image = 0;
q.type = QUAD_TYPE_REGULAR;
return draw_quad(q);
}
@ -169,6 +190,7 @@ Draw_Quad *draw_rect_xform(Matrix4 xform, Vector2 size, Vector4 color) {
q.bottom_right = v2(size.x, 0);
q.color = color;
q.image = 0;
q.type = QUAD_TYPE_REGULAR;
return draw_quad_xform(q, xform);
}
@ -189,6 +211,52 @@ Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vecto
return q;
}
typedef struct {
Gfx_Font *font;
string text;
u32 raster_height;
Matrix4 xform;
Vector2 scale;
Vector4 color;
} Draw_Text_Callback_Params;
bool draw_text_callback(Gfx_Glyph glyph, Gfx_Font_Atlas *atlas, float glyph_x, float glyph_y, void *ud) {
u32 codepoint = glyph.codepoint;
Draw_Text_Callback_Params *params = (Draw_Text_Callback_Params*)ud;
Vector2 size = v2(glyph.width*params->scale.x, glyph.height*params->scale.y);
Matrix4 glyph_xform = m4_translate(params->xform, v3(glyph_x, glyph_y, 0));
Draw_Quad *q = draw_image_xform(atlas->image, glyph_xform, size, params->color);
q->uv = glyph.uv;
q->type = QUAD_TYPE_TEXT;
q->image_min_filter = GFX_FILTER_MODE_LINEAR;
q->image_mag_filter = GFX_FILTER_MODE_LINEAR;
return true;
}
void draw_text_xform(Gfx_Font *font, string text, u32 raster_height, Matrix4 xform, Vector2 scale, Vector4 color) {
Draw_Text_Callback_Params p;
p.font = font;
p.text = text;
p.raster_height = raster_height;
p.xform = xform;
p.scale = scale;
p.color = color;
walk_glyphs((Walk_Glyphs_Spec){font, text, raster_height, scale, true, &p}, draw_text_callback);
}
void draw_text(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);
}
#define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0})
#define COLOR_GREEN ((Vector4){0.0, 1.0, 0.0, 1.0})
@ -196,40 +264,3 @@ Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vecto
#define COLOR_WHITE ((Vector4){1.0, 1.0, 1.0, 1.0})
#define COLOR_BLACK ((Vector4){0.0, 0.0, 0.0, 1.0})
Gfx_Image *load_image_from_disk(string path, Allocator allocator) {
string png;
bool ok = os_read_entire_file(path, &png, allocator);
if (!ok) return 0;
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image));
// Use stb_image to load and decode the PNG
int width, height, channels;
stbi_set_flip_vertically_on_load(1); // stb_image can flip the image on load
unsigned char* stb_data = stbi_load_from_memory(png.data, png.count, &width, &height, &channels, STBI_rgb_alpha);
if (!stb_data) {
dealloc(allocator, image);
dealloc_string(allocator, png);
return 0;
}
image->data = stb_data;
image->width = width;
image->height = height;
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
image->allocator = allocator;
dealloc_string(allocator, png);
return image;
}
void delete_image(Gfx_Image *image) {
stbi_image_free(image->data); // Free the image data allocated by stb_image
image->width = 0;
image->height = 0;
draw_frame.garbage_stack[draw_frame.garbage_stack_count] = image->gfx_handle;
draw_frame.garbage_stack_count += 1;
dealloc(image->allocator, image);
}

View file

@ -2,8 +2,8 @@
int entry(int argc, char **argv) {
window.title = STR("Minimal Game Example");
window.width = 1280;
window.height = 720;
window.scaled_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI)
window.scaled_height = 720;
window.x = 200;
window.y = 200;
window.clear_color = hex_to_rgba(0x6495EDff);
@ -14,10 +14,10 @@ int entry(int argc, char **argv) {
os_update();
float64 now = os_get_current_time_in_seconds();
Matrix4 hammer_xform = m4_scalar(1.0);
hammer_xform = m4_rotate_z(hammer_xform, (f32)now);
hammer_xform = m4_translate(hammer_xform, v3(-.25f, -.25f, 0));
draw_rect_xform(hammer_xform, v2(.5f, .5f), COLOR_RED);
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);
gfx_update();
}

View file

@ -15,6 +15,19 @@ int entry(int argc, char **argv) {
Gfx_Image *hammer_image = load_image_from_disk(STR("oogabooga/examples/hammer.png"), get_heap_allocator());
assert(hammer_image, "Failed loading hammer.png");
void *my_data = alloc(get_heap_allocator(), 32*32*4);
memset(my_data, 0xffffffff, 32*32*4);
Gfx_Image *my_image = make_image(32, 32, 4, my_data, get_heap_allocator());
for (int *c = (int*)my_data; c < (int*)my_data+16*16; c += 1) {
*c = 0xff0000ff;
}
gfx_set_image_data(my_image, 0, 0, 16, 16, my_data);
Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator());
assert(font, "Failed loading arial.ttf, %d", GetLastError());
render_atlas_if_not_yet_rendered(font, 32, 'A');
seed_for_random = os_get_current_cycle_count();
const float64 fps_limit = 69000;
@ -76,7 +89,7 @@ int entry(int argc, char **argv) {
draw_frame.view = camera_view;
seed_for_random = 69;
for (u64 i = 0; i < 100000; i++) {
for (u64 i = 0; i < 10000; i++) {
float32 aspect = (float32)window.width/(float32)window.height;
float min_x = -aspect;
float max_x = aspect;
@ -102,11 +115,23 @@ int entry(int argc, char **argv) {
draw_image(bush_image, v2(0.65, 0.65), v2(0.2*sin(now), 0.2*sin(now)), COLOR_WHITE);
u32 atlas_index = 0;
Gfx_Font_Atlas *atlas = (Gfx_Font_Atlas*)hash_table_find(&font->variations[32].atlases, atlas_index);
draw_text(font, STR("I am text"), 128, v2(sin(now), -0.61), v2(0.001, 0.001), COLOR_BLACK);
draw_text(font, STR("I am text"), 128, v2(sin(now)-0.01, -0.6), v2(0.001, 0.001), COLOR_WHITE);
draw_text(font, STR("Hello jje\nnew line"), 128, v2(-1, 0.5), v2(0.001, 0.001), COLOR_WHITE);
local_persist bool show = false;
if (is_key_just_pressed('T')) show = !show;
if (show) draw_image(atlas->image, v2(-1.6, -1), v2(4, 4), COLOR_WHITE);
tm_scope_cycles("gfx_update") {
gfx_update();
}
if (is_key_just_released('E')) {
log("FPS: %.2f", 1.0 / delta);
log("ms: %.2f", delta*1000.0);

View file

@ -0,0 +1,74 @@
int entry(int argc, char **argv) {
window.title = STR("OGB Text Rendering Example");
window.scaled_width = 1280;
window.scaled_height = 720;
window.x = 200;
window.y = 200;
window.clear_color = hex_to_rgba(0x6495EDff);
Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator());
assert(font, "Failed loading arial.ttf, %d", GetLastError());
const u32 font_height = 48;
while (!window.should_close) tm_scope_cycles("Frame") {
reset_temporary_storage();
// Text is easiest to deal with if our projection matches window pixel size, because
// then the rasterization height will match the screen pixel height (unless scaling).
// The best way to make the text look good is to draw it at the exact same pixel height
// 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);
// 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(0, 0), v2(1, 1), COLOR_WHITE);
float now = (float)os_get_current_time_in_seconds();
float animated_x = sin(now*0.1)*(window.width*0.5);
draw_text(font, STR("I am text"), font_height, v2(animated_x-2, 2), v2(1, 1), COLOR_BLACK);
draw_text(font, STR("I am text"), font_height, v2(animated_x, 0), v2(1, 1), COLOR_WHITE);
// New lines are handled when drawing text
string hello_str = STR("Hello,\nTTTT New line\nAnother line");
// To align/justify text we need to measure it.
Gfx_Text_Metrics hello_metrics = measure_text(font, hello_str, font_height, v2(1, 1));
// This is where we want the bottom left of the text to be...
Vector2 bottom_left = v2(-window.width/2+20, -window.height/2+20);
// ... So we have to justify that bottom_left according to text metrics
Vector2 justified = v2_sub(bottom_left, hello_metrics.functional_pos_min);
draw_text(font, hello_str, font_height, justified, v2(1, 1), COLOR_WHITE);
// If for example we wanted to center the text, we would do the same but then add
// the text size divided by two:
// justified = v2_add(justified, v2_subf(hello_metrics.functional_size, 2.0));
local_persist bool show_bounds = false;
if (is_key_just_pressed('E')) show_bounds = !show_bounds;
string long_text = STR("Jaunty jackrabbits juggle quaint TTT quilts and quirky quinces, \nquickly queuing up for a jubilant, jazzy jamboree in the jungle.\nLorem ipsilum ");
if (show_bounds) {
// Visualize the bounds we get from metrics
Gfx_Text_Metrics m = measure_text(font, long_text, font_height, v2(1, 1));
draw_rect(v2_add(v2(-600, -200), m.visual_pos_min), m.visual_size, v4(.1, .1, .1, .2));
draw_rect(v2_add(v2(-600, -200), m.functional_pos_min), m.functional_size, v4(1, .1, .1, .2));
}
draw_text(font, long_text, font_height, v2(-600, -200), v2(1, 1), COLOR_WHITE);
os_update();
gfx_update();
}
return 0;
}

361
oogabooga/font.c Normal file
View file

@ -0,0 +1,361 @@
// See oogabooga/examples/text_rendering.c for usage
/*
TODO:
- Justify rows in walk_glyphs
*/
// #Memory #Speed
// This is terruble and huge waste of video memory. We should have a constant codepoint range
// with arbitrary atlas sizes instead.
#define FONT_ATLAS_WIDTH 2048
#define FONT_ATLAS_HEIGHT 2048
#define MAX_FONT_HEIGHT 512
typedef struct Gfx_Font Gfx_Font;
typedef struct Gfx_Text_Metrics {
// "functional" is what you would use for example for text editing for constistent
// placements.
// To draw text with it's origin at the bottom left, you need to sub this from the bottom
// left. I.e:
// Vector2 justified_pos = v2_sub(bottom_left, metrics.functional_pos_min);
// If you want to center, you have to first justify to bottom left and then add half of
// metrics.functional_size (or visual_size if you want perfect alignment and the text
// is static).
Vector2 functional_pos_min;
Vector2 functional_pos_max;
Vector2 functional_size;
// The visual bounds for the text.
Vector2 visual_pos_min;
Vector2 visual_pos_max;
Vector2 visual_size;
} Gfx_Text_Metrics;
typedef struct Gfx_Font_Metrics {
float latin_ascent;
float latin_descent;
float max_ascent;
float max_descent;
float line_spacing;
} Gfx_Font_Metrics;
typedef struct Gfx_Glyph {
u32 codepoint;
float xoffset, yoffset;
float advance;
float width, height;
Vector4 uv;
} Gfx_Glyph;
typedef struct Gfx_Font_Atlas {
Gfx_Image *image;
u32 first_codepoint;
Gfx_Glyph *glyphs; // first_codepoint + index == the codepoint
} Gfx_Font_Atlas;
typedef struct Gfx_Font_Variation {
Gfx_Font *font;
u32 height;
Gfx_Font_Metrics metrics;
float scale;
u32 codepoint_range_per_atlas;
Hash_Table atlases; // u32 atlas_index, Gfx_Font_Atlas
bool initted;
} Gfx_Font_Variation;
typedef struct Gfx_Font {
stbtt_fontinfo stbtt_handle;
string raw_font_data;
Gfx_Font_Variation variations[MAX_FONT_HEIGHT]; // Variation per font height
Allocator allocator;
} Gfx_Font;
Gfx_Font *load_font_from_disk(string path, Allocator allocator) {
string font_data;
bool read_ok = os_read_entire_file(path, &font_data, allocator);
if (!read_ok) return 0;
stbtt_fontinfo stbtt_handle;
int result = stbtt_InitFont(&stbtt_handle, font_data.data, stbtt_GetFontOffsetForIndex(font_data.data, 0));
if (result == 0) return 0;
Gfx_Font *font = alloc(allocator, sizeof(Gfx_Font));
memset(font, 0, sizeof(Gfx_Font));
font->stbtt_handle = stbtt_handle;
font->raw_font_data = font_data;
font->allocator = allocator;
return font;
}
void destroy_font(Gfx_Font *font) {
for (u64 i = 0; i < MAX_FONT_HEIGHT; i++) {
Gfx_Font_Variation *variation = &font->variations[i];
if (!variation->initted) continue;
for (u64 j = 0; j < variation->atlases.count; j++) {
Gfx_Font_Atlas *atlas = (Gfx_Font_Atlas*)hash_table_get_nth_value(&variation->atlases, j);
delete_image(atlas->image);
dealloc(font->allocator, atlas->glyphs);
}
hash_table_destroy(&variation->atlases);
}
dealloc_string(font->allocator, font->raw_font_data);
dealloc(font->allocator, font);
}
void font_variation_init(Gfx_Font_Variation *variation, Gfx_Font *font, u32 font_height) {
variation->font = font;
variation->height = font_height;
u32 x_range = FONT_ATLAS_WIDTH / font_height;
u32 y_range = FONT_ATLAS_HEIGHT / font_height;
variation->codepoint_range_per_atlas = x_range*y_range;
variation->atlases = make_hash_table(u32, Gfx_Font_Atlas, font->allocator);
variation->scale = stbtt_ScaleForPixelHeight(&font->stbtt_handle, (float)font_height);
// Unscaled !
int ascent, descent, line_gap;
stbtt_GetFontVMetrics(&font->stbtt_handle, &ascent, &descent, &line_gap);
variation->metrics.max_ascent = ((float)ascent*variation->scale);
variation->metrics.max_descent = ((float)descent*variation->scale);
variation->metrics.line_spacing = ((float)line_gap*variation->scale);
variation->metrics.latin_descent = 9999999;
for (u32 c = 'a'; c <= 'z'; c++) {
// This one is bottom-top as opposed to normally in stbtt where it's top-bottom
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&font->stbtt_handle, (int)c, variation->scale, variation->scale, &x0, &y0, &x1, &y1);
float c_descent = -(float)y1;
if (c_descent < variation->metrics.latin_descent) {
variation->metrics.latin_descent = c_descent;
}
}
for (u32 c = 'A'; c <= 'Z'; c++) {
// This one is bottom-top as opposed to normally in stbtt where it's top-bottom
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&font->stbtt_handle, (int)c, variation->scale, variation->scale, &x0, &y0, &x1, &y1);
float c_ascent = (float)(y1-y0); // #Bugprone #Cleanup I am not at all sure about this!
if (c_ascent > variation->metrics.latin_ascent)
variation->metrics.latin_ascent = c_ascent;
}
variation->initted = true;
}
void font_atlas_init(Gfx_Font_Atlas *atlas, Gfx_Font_Variation *variation, u32 first_codepoint) {
stbtt_fontinfo stbtt_handle = variation->font->stbtt_handle;
atlas->first_codepoint = first_codepoint;
atlas->image = make_image(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, 1, 0, variation->font->allocator);
atlas->glyphs = alloc(variation->font->allocator, variation->codepoint_range_per_atlas*sizeof(Gfx_Glyph));
u32 cursor_x = 0;
u32 cursor_y = 0;
// Used for flipping bitmaps
u8 *temp_row = (u8 *)talloc(variation->height);
for (u32 c = first_codepoint; c < first_codepoint + variation->codepoint_range_per_atlas; c++) {
u32 i = c-first_codepoint;
Gfx_Glyph *glyph = &atlas->glyphs[i];
glyph->codepoint = c;
int w, h, x, y;
void *bitmap = stbtt_GetCodepointBitmap(&stbtt_handle, variation->scale, variation->scale, (int)c, &w, &h, &x, &y);
if (cursor_x+w > FONT_ATLAS_WIDTH) {
cursor_x = 0;
cursor_y += variation->height;
}
if (bitmap) {
// #Speed #Loadtimes
for (int row = 0; row < h; ++row) {
gfx_set_image_data(atlas->image, cursor_x, cursor_y + (h - 1 - row), w, 1, bitmap + (row * w));
}
stbtt_FreeBitmap(bitmap, 0);
}
glyph->xoffset = (float)x;
glyph->yoffset = variation->height - (float)y - (float)h - variation->metrics.max_ascent+variation->metrics.max_descent; // Adjusted yoffset for bottom-up rendering
glyph->width = (float)w;
glyph->height = (float)h;
int advance, left_side_bearing;
stbtt_GetCodepointHMetrics(&stbtt_handle, c, &advance, &left_side_bearing);
glyph->advance = (float)advance*variation->scale;
//glyph->xoffset += (float)left_side_bearing*variation->scale;
glyph->uv.x1 = (float)cursor_x/(float)FONT_ATLAS_WIDTH;
glyph->uv.y1 = (float)cursor_y/(float)FONT_ATLAS_HEIGHT;
glyph->uv.x2 = ((float)cursor_x+glyph->width)/(float)FONT_ATLAS_WIDTH;
glyph->uv.y2 = ((float)cursor_y+glyph->height)/(float)FONT_ATLAS_HEIGHT;
cursor_x += w;
}
}
void render_atlas_if_not_yet_rendered(Gfx_Font *font, u32 font_height, u32 codepoint) {
assert(font_height <= MAX_FONT_HEIGHT, "Font height too large; maximum of %d is allowed.", MAX_FONT_HEIGHT);
Gfx_Font_Variation *variation = &font->variations[font_height];
if (!variation->initted) {
font_variation_init(variation, font, font_height);
}
u32 atlas_index = codepoint / variation->codepoint_range_per_atlas;
if (!hash_table_contains(&variation->atlases, atlas_index)) {
Gfx_Font_Atlas atlas = ZERO(Gfx_Font_Atlas);
font_atlas_init(&atlas, variation, atlas_index*variation->codepoint_range_per_atlas);
hash_table_add(&variation->atlases, atlas_index, atlas);
}
}
typedef bool(*Walk_Glyphs_Callback_Proc)(Gfx_Glyph glyph, Gfx_Font_Atlas *atlas, float glyph_x, float glyph_y, void *ud);
typedef struct {
Gfx_Font *font;
string text;
u32 raster_height;
Vector2 scale;
bool ignore_control_codes;
void *ud;
} Walk_Glyphs_Spec;
void walk_glyphs(Walk_Glyphs_Spec spec, Walk_Glyphs_Callback_Proc proc) {
Gfx_Font_Variation *variation = &spec.font->variations[spec.raster_height];
float x = 0;
float y = 0;
u32 last_c = 0;
u32 c = next_utf8(&spec.text);
while (c != 0) {
render_atlas_if_not_yet_rendered(spec.font, spec.raster_height, c);
if (c == '\n') {
x = 0;
y -= (variation->metrics.latin_ascent-variation->metrics.latin_descent+variation->metrics.line_spacing)*spec.scale.y;
last_c = 0;
}
if (c < 32 && spec.ignore_control_codes) {
c = next_utf8(&spec.text);
continue;
}
u32 atlas_index = c/variation->codepoint_range_per_atlas;
Gfx_Font_Atlas *atlas = (Gfx_Font_Atlas*)hash_table_find(&variation->atlases, atlas_index);
Gfx_Glyph glyph = atlas->glyphs[c-atlas->first_codepoint];
float glyph_x = x+glyph.xoffset*spec.scale.x;
float glyph_y = y+(glyph.yoffset)*spec.scale.y;
bool should_continue = proc(glyph, atlas, glyph_x, glyph_y, spec.ud);
if (!should_continue) break;
// #Incomplete kerning
x += glyph.advance*spec.scale.x;
if (last_c != 0) {
int kerning_unscaled = stbtt_GetCodepointKernAdvance(&spec.font->stbtt_handle, last_c, c);
float kerning_scaled_to_font_height = kerning_unscaled * variation->scale;
x += kerning_scaled_to_font_height*spec.scale.x;
}
last_c = c;
c = next_utf8(&spec.text);
}
}
Gfx_Font_Metrics get_font_metrics(Gfx_Font *font, u32 raster_height) {
Gfx_Font_Variation *variation = &font->variations[raster_height];
if (!variation->initted) {
font_variation_init(variation, font, raster_height);
}
return variation->metrics;
}
Gfx_Font_Metrics get_font_metrics_scaled(Gfx_Font *font, u32 raster_height, Vector2 scale) {
Gfx_Font_Metrics metrics = get_font_metrics(font, raster_height);
metrics.latin_ascent *= scale.x;
metrics.latin_descent *= scale.x;
metrics.max_ascent *= scale.x;
metrics.max_descent *= scale.x;
metrics.line_spacing *= scale.x;
return metrics;
}
typedef struct {
Gfx_Text_Metrics m;
Vector2 scale;
} Measure_Text_Walk_Glyphs_Context;
bool measure_text_glyph_callback(Gfx_Glyph glyph, Gfx_Font_Atlas *atlas, float glyph_x, float glyph_y, void *ud) {
Measure_Text_Walk_Glyphs_Context *c = (Measure_Text_Walk_Glyphs_Context*)ud;
float functional_left = glyph_x-glyph.xoffset*c->scale.x;
float functional_bottom = glyph_y-glyph.yoffset*c->scale.y;
float functional_right = functional_left + glyph.width*c->scale.x;
float functional_top = functional_bottom + glyph.height*c->scale.y;
c->m.functional_pos_min.x = min(c->m.functional_pos_min.x, functional_left);
c->m.functional_pos_min.y = min(c->m.functional_pos_min.y, functional_bottom);
c->m.functional_pos_max.x = max(c->m.functional_pos_max.x, functional_right);
c->m.functional_pos_max.y = max(c->m.functional_pos_max.y, functional_top);
float visual_left = glyph_x;
float visual_bottom = glyph_y;
float visual_right = visual_left + glyph.width*c->scale.x;
float visual_top = visual_bottom + glyph.height*c->scale.y;
c->m.visual_pos_min.x = min(c->m.visual_pos_min.x, visual_left);
c->m.visual_pos_min.y = min(c->m.visual_pos_min.y, visual_bottom);
c->m.visual_pos_max.x = max(c->m.visual_pos_max.x, visual_right);
c->m.visual_pos_max.y = max(c->m.visual_pos_max.y, visual_top);
return true;
}
Gfx_Text_Metrics measure_text(Gfx_Font *font, string text, u32 raster_height, Vector2 scale) {
Measure_Text_Walk_Glyphs_Context c = ZERO(Measure_Text_Walk_Glyphs_Context);
c.scale = scale;
walk_glyphs((Walk_Glyphs_Spec){font, text, raster_height, scale, true, &c}, measure_text_glyph_callback);
c.m.functional_size = v2_sub(c.m.functional_pos_max, c.m.functional_pos_min);
c.m.visual_size = v2_sub(c.m.visual_pos_max, c.m.visual_pos_min);
return c.m;
}

View file

@ -13,17 +13,27 @@ const Gfx_Handle GFX_INVALID_HANDLE = 0;
string temp_win32_null_terminated_wide_to_fixed_utf8(const u16 *utf16);
typedef struct alignat(16) D3D11_Vertex {
typedef struct alignat(16) D3D11_Vertex {
Vector4 color;
Vector4 position;
Vector2 uv;
int texture_index;
union {
s32 data1;
struct {
s8 texture_index;
u8 type;
u8 sampler;
u8 padding;
};
};
} D3D11_Vertex;
ID3D11Debug *d3d11_debug = 0;
ID3D11Device* d3d11_device = 0;
ID3D11DeviceContext* d3d11_context = 0;
ID3D11Device *d3d11_device = 0;
ID3D11DeviceContext *d3d11_context = 0;
ID3D11RenderTargetView *d3d11_window_render_target_view = 0;
ID3D11Texture2D *d3d11_back_buffer = 0;
D3D_DRIVER_TYPE d3d11_driver_type = 0;
@ -36,7 +46,10 @@ u32 d3d11_swap_chain_height = 0;
ID3D11BlendState *d3d11_blend_state = 0;
ID3D11RasterizerState *d3d11_rasterizer = 0;
ID3D11SamplerState *d3d11_image_sampler = 0;
ID3D11SamplerState *d3d11_image_sampler_np_fp = 0;
ID3D11SamplerState *d3d11_image_sampler_nl_fl = 0;
ID3D11SamplerState *d3d11_image_sampler_np_fl = 0;
ID3D11SamplerState *d3d11_image_sampler_nl_fp = 0;
ID3D11VertexShader *d3d11_image_vertex_shader = 0;
ID3D11PixelShader *d3d11_image_pixel_shader = 0;
@ -358,7 +371,21 @@ void gfx_init() {
sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.ComparisonFunc = D3D11_COMPARISON_NEVER;
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler);
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_np_fp);
win32_check_hr(hr);
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_nl_fl);
win32_check_hr(hr);
sd.Filter = D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT;
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_np_fl);
win32_check_hr(hr);
sd.Filter = D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_nl_fp);
win32_check_hr(hr);
}
@ -461,11 +488,11 @@ void gfx_init() {
layout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
layout[2].InstanceDataStepRate = 0;
layout[3].SemanticName = "TEXTURE_INDEX";
layout[3].SemanticName = "DATA1_";
layout[3].SemanticIndex = 0;
layout[3].Format = DXGI_FORMAT_R32_SINT;
layout[3].InputSlot = 0;
layout[3].AlignedByteOffset = offsetof(D3D11_Vertex, texture_index);
layout[3].AlignedByteOffset = offsetof(D3D11_Vertex, data1);
layout[3].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
layout[3].InstanceDataStepRate = 0;
@ -501,7 +528,10 @@ void d3d11_draw_call(int number_of_rendered_quads, ID3D11ShaderResourceView **te
VTABLE(VSSetShader, d3d11_context, d3d11_image_vertex_shader, NULL, 0);
VTABLE(PSSetShader, d3d11_context, d3d11_image_pixel_shader, NULL, 0);
VTABLE(PSSetSamplers, d3d11_context, 0, 1, &d3d11_image_sampler);
VTABLE(PSSetSamplers, d3d11_context, 0, 1, &d3d11_image_sampler_np_fp);
VTABLE(PSSetSamplers, d3d11_context, 1, 1, &d3d11_image_sampler_nl_fl);
VTABLE(PSSetSamplers, d3d11_context, 2, 1, &d3d11_image_sampler_np_fl);
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);
@ -518,23 +548,6 @@ void gfx_update() {
HRESULT hr;
tm_scope_cycles("Frame setup") {
///
// purge garbage
for (u64 i = 0; i < draw_frame.garbage_stack_count; i++) {
ID3D11ShaderResourceView *view = draw_frame.garbage_stack[i];
ID3D11Resource *resource = 0;
VTABLE(GetResource, view, &resource);
ID3D11Texture2D *texture = 0;
hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
if (SUCCEEDED(hr)) {
D3D11Release(view);
D3D11Release(texture);
log("Destroyed an image");
} else {
panic("Unhandled D3D11 resource deletion");
}
}
///
// Maybe resize swap chain
@ -592,44 +605,17 @@ void gfx_update() {
Draw_Quad *q = &block->quad_buffer[i];
int texture_index = -1;
s8 texture_index = -1;
if (q->image) {
if (!q->image->gfx_handle) {
D3D11_TEXTURE2D_DESC desc = ZERO(D3D11_TEXTURE2D_DESC);
desc.Width = q->image->width;
desc.Height = q->image->height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
desc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA data = ZERO(D3D11_SUBRESOURCE_DATA);
data.pSysMem = q->image->data;
data.SysMemPitch = q->image->width * 4; // #Magicvalue assuming 4 channels
ID3D11Texture2D* texture = 0;
HRESULT hr = VTABLE(CreateTexture2D, d3d11_device, &desc, &data, &texture);
win32_check_hr(hr);
hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &q->image->gfx_handle);
win32_check_hr(hr);
log_verbose("Created an image of width %d and height %d.", q->image->width, q->image->height);
}
if (last_texture == q->image->gfx_handle) {
texture_index = (int)(num_textures-1);
texture_index = (s8)(num_textures-1);
} else {
// First look if texture is already bound
for (u64 j = 0; j < num_textures; j++) {
if (textures[j] == q->image->gfx_handle) {
texture_index = (int)j;
texture_index = (s8)j;
break;
}
}
@ -648,7 +634,7 @@ void gfx_update() {
number_of_rendered_quads = 0;
pointer = head;
} else {
texture_index = (int)num_textures;
texture_index = (s8)num_textures;
num_textures += 1;
}
}
@ -657,6 +643,20 @@ void gfx_update() {
last_texture = q->image->gfx_handle;
}
if (q->type == QUAD_TYPE_TEXT) {
float pixel_width = 2.0/(float)window.width;
float pixel_height = 2.0/(float)window.height;
q->bottom_left.x = round(q->bottom_left.x / pixel_width) * pixel_width;
q->bottom_left.y = round(q->bottom_left.y / pixel_height) * pixel_height;
q->top_left.x = round(q->top_left.x / pixel_width) * pixel_width;
q->top_left.y = round(q->top_left.y / pixel_height) * pixel_height;
q->top_right.x = round(q->top_right.x / pixel_width) * pixel_width;
q->top_right.y = round(q->top_right.y / pixel_height) * pixel_height;
q->bottom_right.x = round(q->bottom_right.x / pixel_width) * pixel_width;
q->bottom_right.y = round(q->bottom_right.y / pixel_height) * pixel_height;
}
// We will write to 6 vertices for the one quad (two tris)
{
@ -681,6 +681,24 @@ void gfx_update() {
BL->color = TL->color = TR->color = BR->color = q->color;
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;
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
sampler = 0;
if (q->image_min_filter == GFX_FILTER_MODE_LINEAR
&& q->image_mag_filter == GFX_FILTER_MODE_LINEAR)
sampler = 1;
if (q->image_min_filter == GFX_FILTER_MODE_LINEAR
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
sampler = 2;
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
&& q->image_mag_filter == GFX_FILTER_MODE_LINEAR)
sampler = 3;
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
BL->sampler=TL->sampler=TR->sampler=BR->sampler = (u8)sampler;
*BL2 = *BL;
*TR2 = *TR;
@ -742,3 +760,92 @@ void gfx_update() {
reset_draw_frame(&draw_frame);
}
void gfx_init_image(Gfx_Image *image, void *initial_data) {
void *data = initial_data;
if (!initial_data){
// #Incomplete 8 bit width assumed
data = alloc(image->allocator, image->width*image->height*image->channels);
memset(data, 0, image->width*image->height*image->channels);
}
assert(image->channels > 0 && image->channels <= 4 && image->channels != 3, "Only 1, 2 or 4 channels allowed on images. Got %d", image->channels);
D3D11_TEXTURE2D_DESC desc = ZERO(D3D11_TEXTURE2D_DESC);
desc.Width = image->width;
desc.Height = image->height;
desc.MipLevels = 1;
desc.ArraySize = 1;
switch (image->channels) {
case 1: desc.Format = DXGI_FORMAT_R8_UNORM; break;
case 2: desc.Format = DXGI_FORMAT_R8G8_UNORM; break;
case 4: desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; break;
default: panic("You should not be here");
}
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
desc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA data_desc = ZERO(D3D11_SUBRESOURCE_DATA);
data_desc.pSysMem = data;
data_desc.SysMemPitch = image->width * image->channels;
ID3D11Texture2D* texture = 0;
HRESULT hr = VTABLE(CreateTexture2D, d3d11_device, &desc, &data_desc, &texture);
win32_check_hr(hr);
hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &image->gfx_handle);
win32_check_hr(hr);
if (!initial_data) {
dealloc(image->allocator, data);
}
log_verbose("Created a D3D11 image of width %d and height %d.", image->width, image->height);
}
void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data) {
assert(image && data, "Bad parameters passed to gfx_set_image_data");
ID3D11ShaderResourceView *view = image->gfx_handle;
ID3D11Resource *resource = NULL;
VTABLE(GetResource, view, &resource);
assert(resource, "Invalid image passed to gfx_set_image_data");
assert(x+w <= image->width && y+h <= image->height, "Specified subregion in image is out of bounds");
ID3D11Texture2D *texture = NULL;
HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
assert(SUCCEEDED(hr), "Expected gfx resource to be a texture but it wasn't");
D3D11_BOX destBox;
destBox.left = x;
destBox.right = x + w;
destBox.top = y;
destBox.bottom = y + h;
destBox.front = 0;
destBox.back = 1;
// #Incomplete bit-width 8 assumed
VTABLE(UpdateSubresource, d3d11_context, (ID3D11Resource*)texture, 0, &destBox, data, w * image->channels, 0);
}
void gfx_deinit_image(Gfx_Image *image) {
ID3D11ShaderResourceView *view = image->gfx_handle;
ID3D11Resource *resource = 0;
VTABLE(GetResource, view, &resource);
ID3D11Texture2D *texture = 0;
HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
if (SUCCEEDED(hr)) {
D3D11Release(view);
D3D11Release(texture);
log("Destroyed an image");
} else {
panic("Unhandled D3D11 resource deletion");
}
}

View file

@ -16,17 +16,82 @@
#endif
forward_global const Gfx_Handle GFX_INVALID_HANDLE;
#define QUAD_TYPE_REGULAR 0
#define QUAD_TYPE_TEXT 1
typedef enum Gfx_Filter_Mode {
GFX_FILTER_MODE_NEAREST,
GFX_FILTER_MODE_LINEAR,
} Gfx_Filter_Mode;
typedef struct Gfx_Image {
u32 width, height;
u8 *data;
u32 width, height, channels;
Gfx_Handle gfx_handle;
Allocator allocator;
} Gfx_Image;
typedef struct Gfx_Font {
u32 width, height;
u8 *data;
Gfx_Handle gfx_handle;
Allocator allocator;
} Gfx_Font;
Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator);
Gfx_Image *load_image_from_disk(string path, Allocator allocator);
void delete_image(Gfx_Image *image);
// Implemented per renderer
void gfx_init_image(Gfx_Image *image, void *data);
void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data);
void gfx_deinit_image(Gfx_Image *image);
// initial_data can be null to leave image data uninitialized
Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator) {
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image) + width*height*channels);
assert(channels > 0 && channels <= 4, "Only 1, 2, 3 or 4 channels allowed on images. Got %d", channels);
image->width = width;
image->height = height;
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
image->allocator = allocator;
image->channels = channels;
gfx_init_image(image, initial_data);
return image;
}
Gfx_Image *load_image_from_disk(string path, Allocator allocator) {
string png;
bool ok = os_read_entire_file(path, &png, allocator);
if (!ok) return 0;
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image));
int width, height, channels;
stbi_set_flip_vertically_on_load(1);
unsigned char* stb_data = stbi_load_from_memory(png.data, png.count, &width, &height, &channels, STBI_rgb_alpha);
if (!stb_data) {
dealloc(allocator, image);
dealloc_string(allocator, png);
return 0;
}
image->width = width;
image->height = height;
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
image->allocator = allocator;
image->channels = 4;
dealloc_string(allocator, png);
gfx_init_image(image, stb_data);
stbi_image_free(stb_data);
return image;
}
void delete_image(Gfx_Image *image) {
// Free the image data allocated by stb_image
image->width = 0;
image->height = 0;
gfx_deinit_image(image);
dealloc(image->allocator, image);
}

84
oogabooga/hash.c Normal file
View file

@ -0,0 +1,84 @@
#define PRIME64_1 11400714785074694791ULL
#define PRIME64_2 14029467366897019727ULL
#define PRIME64_3 1609587929392839161ULL
#define PRIME64_4 9650029242287828579ULL
#define PRIME64_5 2870177450012600261ULL
static inline u64 xx_hash(u64 x) {
u64 h64 = PRIME64_5 + 8;
h64 += x * PRIME64_3;
h64 = ((h64 << 23) | (h64 >> (64 - 23))) * PRIME64_2 + PRIME64_4;
h64 ^= h64 >> 33;
h64 *= PRIME64_2;
h64 ^= h64 >> 29;
h64 *= PRIME64_3;
h64 ^= h64 >> 32;
return h64;
}
static inline u64 city_hash(string s) {
const u64 k = 0x9ddfea08eb382d69ULL;
u64 a = s.count;
u64 b = s.count * 5;
u64 c = 9;
u64 d = b;
if (s.count <= 16) {
memcpy(&a, s.data, sizeof(u64));
memcpy(&b, s.data + s.count - 8, sizeof(u64));
} else {
memcpy(&a, s.data, sizeof(u64));
memcpy(&b, s.data + 8, sizeof(u64));
memcpy(&c, s.data + s.count - 8, sizeof(u64));
memcpy(&d, s.data + s.count - 16, sizeof(u64));
}
a += b;
a = (a << 43) | (a >> (64 - 43));
a += c;
a = a * 5 + 0x52dce729;
d ^= a;
d = (d << 44) | (d >> (64 - 44));
d += b;
return d * k;
}
u64 djb2_hash(string s) {
u64 hash = 5381;
for (u64 i = 0; i < s.count; i++) {
hash = ((hash << 5) + hash) + s.data[i];
}
return hash;
}
u64 string_get_hash(string s) {
if (s.count > 32) return djb2_hash(s);
return city_hash(s);
}
u64 pointer_get_hash(void *p) {
return xx_hash((u64)p);
}
u64 float64_get_hash(float64 x) {
return xx_hash(*(u64*)&x);
}
u64 float32_get_hash(float32 x) {
return float64_get_hash((float64)x);
}
#define get_hash(x) _Generic((x), \
string: string_get_hash, \
s8: xx_hash, \
u8: xx_hash, \
s16: xx_hash, \
u16: xx_hash, \
s32: xx_hash, \
u32: xx_hash, \
s64: xx_hash, \
u64: xx_hash, \
f32: float32_get_hash, \
f64: float64_get_hash, \
default: pointer_get_hash \
)(x)

218
oogabooga/hash_table.c Normal file
View file

@ -0,0 +1,218 @@
// Very naive implementation but it should be very cache efficient so it's alright
// for non-excessive use for now.
/*
Example Usage:
// Make a table with key type 'string' and value type 'int', allocated on the heap
Hash_Table table = make_hash_table(string, int, get_heap_allocator());
// Set key "Key string" to integer value 69. This returns whether or not key was newly added.
string key = STR("Key string");
bool newly_added = hash_table_set(&table, key, 69);
// Find value associated with given key. Returns pointer to that value.
string other_key = STR("Some other key");
int* value = hash_table_find(&table, other_key);
if (value) {
// Pointer is OK, item with key exists
} else {
// Pointer is null, item with key does NOT exist
}
// Same as hash_table_find() != NULL
string another_key = STR("Another key");
if (hash_table_contains(&table, another_key)) {
}
// Reset all entries (but keep allocated memory)
hash_table_reset(&table);
// Free allocated entries in hash table
hash_table_destroy(&table);
Limitations:
- Key can only be a base type or pointer
- Key and value passed to the following function needs to be lvalues (we need to be able to take their addresses with '&'):
- hash_table_add
- hash_table_find
- hash_table_contains
- hash_table_set
Example:
hash_table_set(&table, my_key+5, my_value+3); // ERROR
int key = my_key+5;
int value = my_value+3;
hash_table_set(&table, key, value); // OK
*/
typedef struct Hash_Table Hash_Table;
// API:
#define make_hash_table_reserve(Key_Type, Value_Type, capacity_count, allocator) \
make_hash_table_reserve(sizeof(Key_Type), sizeof(Value_Type), capacity_count, allocator)
#define make_hash_table(Key_Type, Value_Type, allocator) \
make_hash_table_raw(sizeof(Key_Type), sizeof(Value_Type), allocator)
#define hash_table_add(table_ptr, key, value) \
hash_table_add_raw((table_ptr), get_hash(key), &(key), &(value), sizeof(key), sizeof(value))
#define hash_table_find(table_ptr, key) \
hash_table_find_raw((table_ptr), get_hash(key))
#define hash_table_contains(table_ptr, key) \
hash_table_contains_raw((table_ptr), get_hash(key))
#define hash_table_set(table_ptr, key, value) \
hash_table_set_raw((table_ptr), get_hash(key), &key, &value, sizeof(key), sizeof(value))
void hash_table_reserve(Hash_Table *t, u64 required_count);
typedef struct Hash_Table {
// Each entry is hash-key-value
// Hash is sizeof(u64) bytes, key is _key_size bytes and value is _value_size bytes
void *entries;
u64 count; // Number of valid entries
u64 capacity_count; // Number of allocated entries
u64 _key_size;
u64 _value_size;
Allocator allocator;
} Hash_Table;
Hash_Table make_hash_table_reserve_raw(u64 key_size, u64 value_size, u64 capacity_count, Allocator allocator) {
capacity_count = min(capacity_count, 8);
Hash_Table t = ZERO(Hash_Table);
t._key_size = key_size;
t._value_size = value_size;
t.allocator = allocator;
u64 entry_size = value_size+sizeof(u64);
t.entries = alloc(t.allocator, entry_size*capacity_count);
memset(t.entries, 0, entry_size*capacity_count);
t.capacity_count = capacity_count;
return t;
}
inline Hash_Table make_hash_table_raw(u64 key_size, u64 value_size, Allocator allocator) {
return make_hash_table_reserve_raw(key_size, value_size, 128, allocator);
}
void hash_table_reset(Hash_Table *t) {
t->count = 0;
}
void hash_table_destroy(Hash_Table *t) {
dealloc(t->allocator, t->entries);
t->entries = 0;
t->count = 0;
t->capacity_count = 0;
}
void hash_table_reserve(Hash_Table *t, u64 required_count) {
u64 entry_size = t->_value_size+sizeof(u64);
u64 required_size = required_count*entry_size;
u64 current_size = t->capacity_count*entry_size;
if (current_size >= required_size) return;
u64 new_count = get_next_power_of_two(required_count);
u64 new_size = new_count*entry_size;
void *new_entries = alloc(t->allocator, new_size);
memcpy(new_entries, t->entries, current_size);
dealloc(t->allocator, t->entries);
t->entries = new_entries;
t->capacity_count = new_count;
}
// This can add multiple entries of same hash, beware!
void hash_table_add_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) {
assert(t->_key_size == key_size, "Key type size does not match hash table initted key type size");
assert(t->_value_size == value_size, "Value type size does not match hash table initted value type size");
hash_table_reserve(t, t->count+1);
u64 entry_size = t->_value_size+sizeof(u64);
u64 index = entry_size*t->count;
t->count += 1;
u64 hash_offset = 0;
u64 value_offset = hash_offset + sizeof(u64);
memcpy((u8*)t->entries+index+hash_offset, &hash, sizeof(u64));
memcpy((u8*)t->entries+index+value_offset, v, value_size);
}
void *hash_table_find_raw(Hash_Table *t, u64 hash) {
// #Speed #Incomplete
// Do quadratic probe 'triangular numbers'
u64 entry_size = t->_value_size+sizeof(u64);
u64 hash_offset = 0;
u64 value_offset = hash_offset + sizeof(u64);
for (u64 i = 0; i < t->count; i += 1) {
u64 existing_hash = *(u64*)((u8*)t->entries+i*entry_size+hash_offset);
if (existing_hash == hash) {
void *value = ((u8*)t->entries+i*entry_size+value_offset);
return value;
}
}
return 0;
}
void *hash_table_get_nth_value(Hash_Table *t, u64 n) {
assert(n < t->count, "Hash table n is out of range");
u64 entry_size = t->_value_size+sizeof(u64);
u64 hash_offset = 0;
u64 value_offset = hash_offset + sizeof(u64);
return (u8*)t->entries+entry_size*n+value_offset;
}
bool hash_table_contains_raw(Hash_Table *t, u64 hash) {
return hash_table_find_raw(t, hash) != 0;
}
// Returns true if key was newly added or false if it already existed
bool hash_table_set_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) {
bool newly_added = true;
if (hash_table_contains_raw(t, hash)) newly_added = false;
if (newly_added) {
hash_table_add_raw(t, hash, k, v, key_size, value_size);
} else {
memcpy(hash_table_find_raw(t, hash), v, value_size);
}
return newly_added;
}

View file

@ -219,7 +219,7 @@ Matrix4 m4_make_scale(Vector3 scale) {
return m;
}
Matrix4 m4_multiply(Matrix4 a, Matrix4 b) {
Matrix4 m4_mul(Matrix4 a, Matrix4 b) {
Matrix4 result;
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
@ -234,21 +234,21 @@ Matrix4 m4_multiply(Matrix4 a, Matrix4 b) {
inline Matrix4 m4_translate(Matrix4 m, Vector3 translation) {
Matrix4 translation_matrix = m4_make_translation(translation);
return m4_multiply(m, translation_matrix);
return m4_mul(m, translation_matrix);
}
inline Matrix4 m4_rotate(Matrix4 m, Vector3 axis, float32 radians) {
Matrix4 rotation_matrix = m4_make_rotation(axis, radians);
return m4_multiply(m, rotation_matrix);
return m4_mul(m, rotation_matrix);
}
inline Matrix4 m4_rotate_z(Matrix4 m, float32 radians) {
Matrix4 rotation_matrix = m4_make_rotation(v3(0, 0, 1), radians);
return m4_multiply(m, rotation_matrix);
return m4_mul(m, rotation_matrix);
}
inline Matrix4 m4_scale(Matrix4 m, Vector3 scale) {
Matrix4 scale_matrix = m4_make_scale(scale);
return m4_multiply(m, scale_matrix);
return m4_mul(m, scale_matrix);
}

View file

@ -197,7 +197,7 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) {
// #Speed #Cleanup
if (((u8*)block)+size >= ((u8*)program_memory)+program_memory_size) {
u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1;
u64 new_program_size = max((cast(u64)(minimum_size*2)), program_memory_size*2);
u64 new_program_size = get_next_power_of_two(minimum_size);
assert(new_program_size >= minimum_size, "Bröd");
const u64 ATTEMPTS = 1000;
for (u64 i = 0; i <= ATTEMPTS; i++) {

View file

@ -105,6 +105,12 @@
*/
#define OGB_VERSION_MAJOR 0
#define OGB_VERSION_MINOR 0
#define OGB_VERSION_PATCH 2
#define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH)
#include <stdint.h>
typedef uint8_t u8;
typedef uint16_t u16;
@ -253,11 +259,15 @@ typedef u8 bool;
#include "string.c"
#include "unicode.c"
#include "string_format.c"
#include "hash.c"
#include "path_utils.c"
#include "linmath.c"
#include "hash_table.c"
#include "os_interface.c"
#include "gfx_interface.c"
#include "font.c"
#include "profiling.c"
#include "random.c"
@ -291,6 +301,8 @@ typedef u8 bool;
#include "tests.c"
#define malloc please_use_alloc_for_memory_allocations_instead_of_malloc
#define free please_use_dealloc_for_memory_deallocations_instead_of_free
void oogabooga_init(u64 program_memory_size) {
context.logger = default_logger;
@ -315,8 +327,9 @@ int ENTRY_PROC(int argc, char **argv);
int main(int argc, char **argv) {
printf("Ooga booga program started\n");
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

@ -141,6 +141,8 @@ void os_init(u64 program_memory_size) {
#if CONFIGURATION == RELEASE
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
#endif
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
SYSTEM_INFO si;
GetSystemInfo(&si);
@ -625,7 +627,7 @@ File os_file_open_s(string path, Os_Io_Open_Flags flags) {
u16 *wide = temp_win32_fixed_utf8_to_null_terminated_wide(path);
return CreateFileW(wide, access, 0, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
return CreateFileW(wide, access, FILE_SHARE_READ, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
}
void os_file_close(File f) {
@ -935,31 +937,40 @@ void* os_get_stack_limit() {
void os_update() {
UINT dpi = GetDpiForWindow(window._os_handle);
float dpi_scale_factor = dpi / 96.0f;
local_persist Os_Window last_window;
if (!strings_match(last_window.title, window.title)) {
SetWindowText(window._os_handle, temp_convert_to_null_terminated_string(window.title));
}
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;
}
BOOL ok;
int screen_height = GetSystemMetrics(SM_CYSCREEN);
DWORD style = (DWORD)GetWindowLong(window._os_handle, GWL_STYLE);
DWORD ex_style = (DWORD)GetWindowLong(window._os_handle, GWL_EXSTYLE);
if (last_window.x != window.x || last_window.y != window.y || last_window.width != window.width || last_window.y != window.y) {
RECT update_rect;
update_rect.left = window.x;
update_rect.right = window.x + window.width;
update_rect.bottom = screen_height-(window.y);
update_rect.top = screen_height-(window.y+window.height);
ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
u32 actual_x = update_rect.left;
u32 actual_y = update_rect.top;
u32 actual_width = update_rect.right-update_rect.left;
u32 actual_height = update_rect.bottom-update_rect.top;
SetWindowPos(window._os_handle, 0, actual_x, actual_y, actual_width, actual_height, 0);
if (last_window.x != window.x || last_window.y != window.y || last_window.width != window.width || last_window.height != window.height) {
RECT update_rect;
update_rect.left = window.x;
update_rect.right = window.x + window.width;
update_rect.top = window.y;
update_rect.bottom = window.y + window.height;
BOOL ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
u32 actual_x = update_rect.left;
u32 actual_y = update_rect.top;
u32 actual_width = update_rect.right - update_rect.left;
u32 actual_height = update_rect.bottom - update_rect.top;
SetWindowPos(window._os_handle, NULL, actual_x, actual_y, actual_width, actual_height, SWP_NOZORDER | SWP_NOACTIVATE);
}
RECT client_rect;
@ -983,8 +994,11 @@ void os_update() {
window.x = (u32)top_left.x;
window.y = (u32)(screen_height-bottom_right.y);
window.width = (u32)(client_rect.right - client_rect.left);
window.height = (u32)(client_rect.bottom - client_rect.top);
window.pixel_width = (u32)(client_rect.right - client_rect.left);
window.pixel_height = (u32)(client_rect.bottom - client_rect.top);
window.scaled_width = (u32)((client_rect.right - client_rect.left) * dpi_scale_factor);
window.scaled_height = (u32)((client_rect.bottom - client_rect.top) * dpi_scale_factor);
last_window = window;
@ -1016,8 +1030,8 @@ void os_update() {
GetCursorPos(&p);
ScreenToClient(window._os_handle, &p);
p.y = window.height - p.y;
input_frame.mouse_x = (float32)p.x;
input_frame.mouse_y = (float32)p.y;
input_frame.mouse_x = p.x;
input_frame.mouse_y = p.y;
if (window.should_close) {
win32_window_proc(window._os_handle, WM_CLOSE, 0, 0);

View file

@ -298,10 +298,12 @@ typedef struct Os_Window {
// Keep in mind that setting these in runtime is potentially slow!
string title;
u32 width;
u32 height;
u32 x;
u32 y;
union { s32 width; s32 pixel_width; };
union { s32 height; s32 pixel_height; };
s32 scaled_width;
s32 scaled_height;
s32 x;
s32 y;
Vector4 clear_color;
bool enable_vsync;

View file

@ -36,6 +36,8 @@ void log_heap() {
void test_allocator(bool do_log_heap) {
u64 h = get_hash((string*)69);
Allocator heap = get_heap_allocator();
// Basic allocation and free
@ -881,8 +883,8 @@ void test_linmath() {
// Test matrix multiplication
Matrix4 m5 = m4_scalar(1.0f);
m5.m[0][3] = 1.0f; m5.m[1][3] = 2.0f; m5.m[2][3] = 3.0f;
Matrix4 result_matrix = m4_multiply(m2, m5);
assert(result_matrix.m[0][3] == 2.0f && result_matrix.m[1][3] == 4.0f && result_matrix.m[2][3] == 6.0f, "m4_multiply incorrect");
Matrix4 result_matrix = m4_mul(m2, m5);
assert(result_matrix.m[0][3] == 2.0f && result_matrix.m[1][3] == 4.0f && result_matrix.m[2][3] == 6.0f, "m4_mul incorrect");
// Test matrix inverse
Matrix4 identity = m4_scalar(1.0f);
@ -1018,6 +1020,42 @@ void test_linmath() {
assert(floats_roughly_match(v3_dot, 38), "Failed: v3_dot_product");
assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product");
}
void test_hash_table() {
Hash_Table table = make_hash_table(string, int, get_heap_allocator());
string key1 = STR("Key string");
int value1 = 69;
bool newly_added = hash_table_set(&table, key1, value1);
assert(newly_added == true, "Failed: Key should be newly added");
int* found_value = hash_table_find(&table, key1);
assert(found_value != NULL, "Failed: Key should exist in hash table");
assert(*found_value == 69, "Failed: Value should be 69, got %i", *found_value);
int new_value1 = 70;
newly_added = hash_table_set(&table, key1, new_value1);
assert(newly_added == false, "Failed: Key should not be newly added");
found_value = hash_table_find(&table, key1);
assert(found_value != NULL, "Failed: Key should exist in hash table");
assert(*found_value == 70, "Failed: Value should be 70, got %i", *found_value);
bool contains = hash_table_contains(&table, key1);
assert(contains == true, "Failed: Hash table should contain key1");
string key2 = STR("Non-existing key");
contains = hash_table_contains(&table, key2);
assert(contains == false, "Failed: Hash table should not contain key2");
hash_table_reset(&table);
found_value = hash_table_find(&table, key1);
assert(found_value == NULL, "Failed: Hash table should be empty after reset");
hash_table_destroy(&table);
assert(table.entries == NULL, "Failed: Hash table entries should be NULL after destroy");
assert(table.count == 0, "Failed: Hash table count should be 0 after destroy");
assert(table.capacity_count == 0, "Failed: Hash table capacity count should be 0 after destroy");
}
void oogabooga_run_tests() {
@ -1033,17 +1071,19 @@ void oogabooga_run_tests() {
test_strings();
print("OK!\n");
//print("Thread bombing allocator... ");
//Thread* threads[100];
//for (int i = 0; i < 100; i++) {
// threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
// os_start_thread(threads[i]);
//}
//for (int i = 0; i < 100; i++) {
// os_join_thread(threads[i]);
//}
//print("OK!\n");
#if CONFIGURATION != RELEASE
print("Thread bombing allocator... ");
Thread* threads[100];
for (int i = 0; i < 100; i++) {
threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
os_start_thread(threads[i]);
}
for (int i = 0; i < 100; i++) {
os_join_thread(threads[i]);
}
print("OK!\n");
#endif
print("Testing file IO... ");
test_file_io();
@ -1057,4 +1097,8 @@ void oogabooga_run_tests() {
test_simd();
print("OK!\n");
print("Testing hash table... ");
test_hash_table();
print("OK!\n");
}

View file

@ -40,6 +40,9 @@ typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
// #Temporary #Incomplete #Memory
// This should use the allocator we pass to the gfx font system.
// Probably just do a thread local global here
void *stbtt_malloc(size_t size) {
if (!size) return 0;
return alloc(get_heap_allocator(), size);

View file

@ -35,4 +35,77 @@ int utf16_to_utf32(const u16 *utf16, u64 length, u32 *utf32) {
*utf32 = first;
return 1;
}
}
// Yoinked from jai Unicode.jai
#define UNI_REPLACEMENT_CHAR 0x0000FFFD
#define UNI_MAX_UTF32 0x7FFFFFFF
#define UNI_MAX_UTF16 0x0010FFFF
#define SURROGATES_START 0xD800
#define SURROGATES_END 0xDFFF
typedef struct {
u32 utf32;
s64 continuation_bytes;
bool reached_end;
bool error;
} Utf8_To_Utf32_Result;
const u8 trailing_bytes_for_utf8[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
const u8 utf8_inital_byte_mask[] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
Utf8_To_Utf32_Result utf8_to_utf32(u8 *s, s64 source_length, bool strict) {
s64 continuation_bytes = trailing_bytes_for_utf8[s[0]];
if (continuation_bytes + 1 > source_length) {
return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, source_length, true, true};
}
u32 ch = s[0] & utf8_inital_byte_mask[continuation_bytes];
for (u64 i = 1; i <= continuation_bytes; i++) { // Do nothing if it is 0.
ch = ch << 6;
if (strict) if ((s[i] & 0xC0) != 0x80) return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, i - 1, true, true};
ch |= s[i] & 0x3F;
}
if (strict) {
if (ch > UNI_MAX_UTF16 ||
(SURROGATES_START <= ch && ch <= SURROGATES_END) ||
(ch <= 0x0000007F && continuation_bytes != 0) ||
(ch <= 0x000007FF && continuation_bytes != 1) ||
(ch <= 0x0000FFFF && continuation_bytes != 2) ||
continuation_bytes > 3) {
return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, continuation_bytes+1, true, true};
}
}
if (ch > UNI_MAX_UTF32) {
ch = UNI_REPLACEMENT_CHAR;
}
return (Utf8_To_Utf32_Result){ ch, continuation_bytes+1, false, false };
}
// Returns 0 on fail
u32 next_utf8(string *s) {
Utf8_To_Utf32_Result result = utf8_to_utf32(s->data, s->count, false);
s->data += result.continuation_bytes;
s->count -= result.continuation_bytes;
assert(s->count >= 0);
if (result.error) return 0;
return result.utf32;
}