- Text rendering

- Font loading
	- Measuring for formatting & justification
	- Utf8 Glyph walking
	- Commented example in oogabooga/examples/text_rendering.c
- Small 2D renderer refactor
	- 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
- 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 (scaled_width vs pixel_width)
- in minimal example, renamed hammer_xform -> rect_xform
This commit is contained in:
Charlie 2024-07-07 20:27:34 +02:00
parent 83c0371dcb
commit 18f4fc8123
18 changed files with 3276 additions and 621 deletions

View file

@ -3,8 +3,8 @@
/// ///
// Build config stuff // Build config stuff
#define RUN_TESTS 1
#define RUN_TESTS 0
// This is only for people developing oogabooga! // This is only for people developing oogabooga!
#define OOGABOOGA_DEV 1 #define OOGABOOGA_DEV 1
@ -37,10 +37,12 @@ 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"
// An engine dev stress test for rendering // 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 // Randy's example game that he's building out as a tutorial for using the engine
// #include "entry_randygame.c" // #include "entry_randygame.c"

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,11 @@ struct VS_INPUT
float4 position : POSITION; float4 position : POSITION;
float2 uv : TEXCOORD; float2 uv : TEXCOORD;
float4 color : COLOR; float4 color : COLOR;
int texture_index: TEXTURE_INDEX; int data1: DATA1_;
// s8 texture_index
// u8 type
// u8 sampler_index
// u8
}; };
struct PS_INPUT struct PS_INPUT
@ -12,6 +16,8 @@ struct PS_INPUT
float2 uv : TEXCOORD0; float2 uv : TEXCOORD0;
float4 color : COLOR; float4 color : COLOR;
int texture_index: TEXTURE_INDEX; int texture_index: TEXTURE_INDEX;
int type: TYPE;
int sampler_index: SAMPLER_INDEX;
}; };
PS_INPUT vs_main(VS_INPUT input) PS_INPUT vs_main(VS_INPUT input)
@ -20,56 +26,178 @@ PS_INPUT vs_main(VS_INPUT input)
output.position = input.position; output.position = input.position;
output.uv = input.uv; output.uv = input.uv;
output.color = input.color; 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; return output;
} }
// #Magicvalue // #Magicvalue
Texture2D textures[32] : register(t0); 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, 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);
}
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);
return float4(1.0, 0.0, 0.0, 1.0); 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 float4 ps_main(PS_INPUT input) : SV_TARGET
{ {
if (input.texture_index >= 0) { if (input.type == QUAD_TYPE_REGULAR) {
return sample_texture(input.texture_index, input.uv); if (input.texture_index >= 0) {
} else { return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
return 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,9 +44,24 @@ Usage:
// If you ever need to free the image: // If you ever need to free the image:
delete_image(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);
*/
@ -62,6 +77,11 @@ typedef struct Draw_Quad {
// x1, y1, x2, y2 // x1, y1, x2, y2
Vector4 uv; Vector4 uv;
u8 type;
Gfx_Filter_Mode image_min_filter;
Gfx_Filter_Mode image_mag_filter;
} Draw_Quad; } Draw_Quad;
@ -106,6 +126,9 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
quad.top_right = m4_transform(world_to_clip, v4(v2_expand(quad.top_right), 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.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; if (!draw_frame.current) draw_frame.current = &first_block;
if (draw_frame.current == &first_block) draw_frame.num_blocks = 1; if (draw_frame.current == &first_block) draw_frame.num_blocks = 1;
@ -131,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]; return &draw_frame.current->quad_buffer[draw_frame.current->num_quads-1];
} }
Draw_Quad *draw_quad(Draw_Quad quad) { 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) { Draw_Quad *draw_quad_xform(Draw_Quad quad, Matrix4 xform) {
Matrix4 world_to_clip = m4_scalar(1.0); Matrix4 world_to_clip = m4_scalar(1.0);
world_to_clip = m4_multiply(world_to_clip, draw_frame.projection); world_to_clip = m4_mul(world_to_clip, draw_frame.projection);
world_to_clip = m4_multiply(world_to_clip, m4_inverse(draw_frame.view)); world_to_clip = m4_mul(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, xform);
return draw_quad_projected(quad, world_to_clip); return draw_quad_projected(quad, world_to_clip);
} }
@ -155,6 +178,7 @@ Draw_Quad *draw_rect(Vector2 position, Vector2 size, Vector4 color) {
q.bottom_right = v2(right, bottom); q.bottom_right = v2(right, bottom);
q.color = color; q.color = color;
q.image = 0; q.image = 0;
q.type = QUAD_TYPE_REGULAR;
return draw_quad(q); return draw_quad(q);
} }
@ -166,6 +190,7 @@ Draw_Quad *draw_rect_xform(Matrix4 xform, Vector2 size, Vector4 color) {
q.bottom_right = v2(size.x, 0); q.bottom_right = v2(size.x, 0);
q.color = color; q.color = color;
q.image = 0; q.image = 0;
q.type = QUAD_TYPE_REGULAR;
return draw_quad_xform(q, xform); return draw_quad_xform(q, xform);
} }
@ -186,6 +211,52 @@ Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vecto
return q; 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_RED ((Vector4){1.0, 0.0, 0.0, 1.0})
#define COLOR_GREEN ((Vector4){0.0, 1.0, 0.0, 1.0}) #define COLOR_GREEN ((Vector4){0.0, 1.0, 0.0, 1.0})

View file

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

View file

@ -23,6 +23,11 @@ int entry(int argc, char **argv) {
} }
gfx_set_image_data(my_image, 0, 0, 16, 16, my_data); 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(); seed_for_random = os_get_current_cycle_count();
const float64 fps_limit = 69000; const float64 fps_limit = 69000;
@ -102,7 +107,7 @@ int entry(int argc, char **argv) {
Matrix4 hammer_xform = m4_scalar(1.0); Matrix4 hammer_xform = m4_scalar(1.0);
hammer_xform = m4_rotate_z(hammer_xform, (f32)now); hammer_xform = m4_rotate_z(hammer_xform, (f32)now);
hammer_xform = m4_translate(hammer_xform, v3(-.25f, -.25f, 0)); hammer_xform = m4_translate(hammer_xform, v3(-.25f, -.25f, 0));
draw_image_xform(my_image, hammer_xform, v2(.5f, .5f), COLOR_RED); draw_image_xform(hammer_image, hammer_xform, v2(.5f, .5f), COLOR_RED);
Vector2 hover_position = v2_rotate_point_around_pivot(v2(-.5, -.5), v2(0, 0), (f32)now); Vector2 hover_position = v2_rotate_point_around_pivot(v2(-.5, -.5), v2(0, 0), (f32)now);
Vector2 local_pivot = v2(.125f, .125f); Vector2 local_pivot = v2(.125f, .125f);
@ -110,8 +115,18 @@ 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); draw_image(bush_image, v2(0.65, 0.65), v2(0.2*sin(now), 0.2*sin(now)), COLOR_WHITE);
//draw_text(font, "I am text", v2(0.1, 0.6), COLOR_BLACK); u32 atlas_index = 0;
//draw_text(font, "I am text", v2(0.09, 0.61), COLOR_WHITE); 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") { tm_scope_cycles("gfx_update") {
gfx_update(); gfx_update();

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

@ -14,10 +14,20 @@ const Gfx_Handle GFX_INVALID_HANDLE = 0;
string temp_win32_null_terminated_wide_to_fixed_utf8(const u16 *utf16); 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 color;
Vector4 position; Vector4 position;
Vector2 uv; Vector2 uv;
int texture_index; union {
s32 data1;
struct {
s8 texture_index;
u8 type;
u8 sampler;
u8 padding;
};
};
} D3D11_Vertex; } D3D11_Vertex;
ID3D11Debug *d3d11_debug = 0; ID3D11Debug *d3d11_debug = 0;
@ -36,7 +46,10 @@ u32 d3d11_swap_chain_height = 0;
ID3D11BlendState *d3d11_blend_state = 0; ID3D11BlendState *d3d11_blend_state = 0;
ID3D11RasterizerState *d3d11_rasterizer = 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; ID3D11VertexShader *d3d11_image_vertex_shader = 0;
ID3D11PixelShader *d3d11_image_pixel_shader = 0; ID3D11PixelShader *d3d11_image_pixel_shader = 0;
@ -358,7 +371,21 @@ void gfx_init() {
sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
sd.ComparisonFunc = D3D11_COMPARISON_NEVER; 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); win32_check_hr(hr);
} }
@ -461,11 +488,11 @@ void gfx_init() {
layout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA; layout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
layout[2].InstanceDataStepRate = 0; layout[2].InstanceDataStepRate = 0;
layout[3].SemanticName = "TEXTURE_INDEX"; layout[3].SemanticName = "DATA1_";
layout[3].SemanticIndex = 0; layout[3].SemanticIndex = 0;
layout[3].Format = DXGI_FORMAT_R32_SINT; layout[3].Format = DXGI_FORMAT_R32_SINT;
layout[3].InputSlot = 0; 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].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
layout[3].InstanceDataStepRate = 0; 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(VSSetShader, d3d11_context, d3d11_image_vertex_shader, NULL, 0);
VTABLE(PSSetShader, d3d11_context, d3d11_image_pixel_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(PSSetShaderResources, d3d11_context, 0, num_textures, textures);
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color); VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
@ -575,17 +605,17 @@ void gfx_update() {
Draw_Quad *q = &block->quad_buffer[i]; Draw_Quad *q = &block->quad_buffer[i];
int texture_index = -1; s8 texture_index = -1;
if (q->image) { if (q->image) {
if (last_texture == q->image->gfx_handle) { if (last_texture == q->image->gfx_handle) {
texture_index = (int)(num_textures-1); texture_index = (s8)(num_textures-1);
} else { } else {
// First look if texture is already bound // First look if texture is already bound
for (u64 j = 0; j < num_textures; j++) { for (u64 j = 0; j < num_textures; j++) {
if (textures[j] == q->image->gfx_handle) { if (textures[j] == q->image->gfx_handle) {
texture_index = (int)j; texture_index = (s8)j;
break; break;
} }
} }
@ -604,7 +634,7 @@ void gfx_update() {
number_of_rendered_quads = 0; number_of_rendered_quads = 0;
pointer = head; pointer = head;
} else { } else {
texture_index = (int)num_textures; texture_index = (s8)num_textures;
num_textures += 1; num_textures += 1;
} }
} }
@ -613,6 +643,20 @@ void gfx_update() {
last_texture = q->image->gfx_handle; 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) // We will write to 6 vertices for the one quad (two tris)
{ {
@ -637,6 +681,24 @@ void gfx_update() {
BL->color = TL->color = TR->color = BR->color = q->color; 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->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; *BL2 = *BL;
*TR2 = *TR; *TR2 = *TR;
@ -700,13 +762,28 @@ void gfx_update() {
} }
void gfx_init_image(Gfx_Image *image, void *data) { 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); D3D11_TEXTURE2D_DESC desc = ZERO(D3D11_TEXTURE2D_DESC);
desc.Width = image->width; desc.Width = image->width;
desc.Height = image->height; desc.Height = image->height;
desc.MipLevels = 1; desc.MipLevels = 1;
desc.ArraySize = 1; desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; 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.Count = 1;
desc.SampleDesc.Quality = 0; desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT; desc.Usage = D3D11_USAGE_DEFAULT;
@ -725,7 +802,11 @@ void gfx_init_image(Gfx_Image *image, void *data) {
hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &image->gfx_handle); hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &image->gfx_handle);
win32_check_hr(hr); win32_check_hr(hr);
log_verbose("Created an image of width %d and height %d.", image->width, image->height); 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) { 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"); assert(image && data, "Bad parameters passed to gfx_set_image_data");
@ -736,7 +817,7 @@ void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data
assert(resource, "Invalid image passed to gfx_set_image_data"); 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"); assert(x+w <= image->width && y+h <= image->height, "Specified subregion in image is out of bounds");
ID3D11Texture2D *texture = NULL; ID3D11Texture2D *texture = NULL;
HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture); HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
@ -751,9 +832,7 @@ void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data
destBox.back = 1; destBox.back = 1;
// #Incomplete bit-width 8 assumed // #Incomplete bit-width 8 assumed
VTABLE(UpdateSubresource, d3d11_context, resource, 0, &destBox, data, w * image->channels, 0); VTABLE(UpdateSubresource, d3d11_context, (ID3D11Resource*)texture, 0, &destBox, data, w * image->channels, 0);
log_verbose("Updated image data at region (%u, %u) with dimensions (%u, %u).", x, y, w, h);
} }
void gfx_deinit_image(Gfx_Image *image) { void gfx_deinit_image(Gfx_Image *image) {
ID3D11ShaderResourceView *view = image->gfx_handle; ID3D11ShaderResourceView *view = image->gfx_handle;

View file

@ -16,6 +16,13 @@
#endif #endif
forward_global const Gfx_Handle GFX_INVALID_HANDLE; 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 { typedef struct Gfx_Image {
u32 width, height, channels; u32 width, height, channels;
@ -27,44 +34,11 @@ Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, A
Gfx_Image *load_image_from_disk(string path, Allocator allocator); Gfx_Image *load_image_from_disk(string path, Allocator allocator);
void delete_image(Gfx_Image *image); void delete_image(Gfx_Image *image);
// Implemented per renderer
void gfx_init_image(Gfx_Image *image, void *data); 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_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data);
void gfx_deinit_image(Gfx_Image *image); void gfx_deinit_image(Gfx_Image *image);
#define FONT_ATLAS_WIDTH 4096
#define FONT_ATLAS_HEIGHT 4096
#define MAX_FONT_HEIGHT 256
typedef struct Gfx_Font_Metrics {
u32 latin_ascent;
u32 latin_descent;
u32 max_ascent;
u32 max_descent;
u32 line_height;
u32 line_spacing;
} Gfx_Font_Metrics;
typedef struct Gfx_Font_Atlas {
Gfx_Image image;
} Gfx_Font_Atlas;
typedef struct Gfx_Font_Variation {
u32 height;
Gfx_Font_Metrics metrics;
u32 codepoint_range_per_atlas;
Hash_Table atlases; // u32 atlas_index, Gfx_Font_Atlas
bool initted;
} Gfx_Font_Variation;
typedef struct Gfx_Font {
Gfx_Font_Variation variations[MAX_FONT_HEIGHT]; // Variation per font height
Allocator allocator;
} Gfx_Font;
// initial_data can be null to leave image data uninitialized // 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 *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator) {
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image) + width*height*channels); Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image) + width*height*channels);
@ -75,7 +49,7 @@ Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, A
image->height = height; image->height = height;
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
image->allocator = allocator; image->allocator = allocator;
image->channels = 4; image->channels = channels;
gfx_init_image(image, initial_data); gfx_init_image(image, initial_data);

View file

@ -187,6 +187,16 @@ void *hash_table_find_raw(Hash_Table *t, u64 hash) {
return 0; 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) { bool hash_table_contains_raw(Hash_Table *t, u64 hash) {
return hash_table_find_raw(t, hash) != 0; return hash_table_find_raw(t, hash) != 0;
} }
@ -205,3 +215,4 @@ bool hash_table_set_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size,
return newly_added; return newly_added;
} }

View file

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

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> #include <stdint.h>
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
@ -261,6 +267,7 @@ typedef u8 bool;
#include "os_interface.c" #include "os_interface.c"
#include "gfx_interface.c" #include "gfx_interface.c"
#include "font.c"
#include "profiling.c" #include "profiling.c"
#include "random.c" #include "random.c"
@ -320,8 +327,9 @@ int ENTRY_PROC(int argc, char **argv);
int main(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); 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");

View file

@ -142,6 +142,8 @@ void os_init(u64 program_memory_size) {
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
#endif #endif
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
SYSTEM_INFO si; SYSTEM_INFO si;
GetSystemInfo(&si); GetSystemInfo(&si);
os.granularity = cast(u64)si.dwAllocationGranularity; os.granularity = cast(u64)si.dwAllocationGranularity;
@ -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); 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) { void os_file_close(File f) {
@ -935,31 +937,40 @@ void* os_get_stack_limit() {
void os_update() { void os_update() {
UINT dpi = GetDpiForWindow(window._os_handle);
float dpi_scale_factor = dpi / 96.0f;
local_persist Os_Window last_window; local_persist Os_Window last_window;
if (!strings_match(last_window.title, window.title)) { if (!strings_match(last_window.title, window.title)) {
SetWindowText(window._os_handle, temp_convert_to_null_terminated_string(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; BOOL ok;
int screen_height = GetSystemMetrics(SM_CYSCREEN); int screen_height = GetSystemMetrics(SM_CYSCREEN);
DWORD style = (DWORD)GetWindowLong(window._os_handle, GWL_STYLE); DWORD style = (DWORD)GetWindowLong(window._os_handle, GWL_STYLE);
DWORD ex_style = (DWORD)GetWindowLong(window._os_handle, GWL_EXSTYLE); 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) { if (last_window.x != window.x || last_window.y != window.y || last_window.width != window.width || last_window.height != window.height) {
RECT update_rect; RECT update_rect;
update_rect.left = window.x; update_rect.left = window.x;
update_rect.right = window.x + window.width; update_rect.right = window.x + window.width;
update_rect.bottom = screen_height-(window.y); update_rect.top = window.y;
update_rect.top = screen_height-(window.y+window.height); update_rect.bottom = 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; BOOL ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
u32 actual_y = update_rect.top; assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
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); 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; RECT client_rect;
@ -983,8 +994,11 @@ void os_update() {
window.x = (u32)top_left.x; window.x = (u32)top_left.x;
window.y = (u32)(screen_height-bottom_right.y); window.y = (u32)(screen_height-bottom_right.y);
window.width = (u32)(client_rect.right - client_rect.left); window.pixel_width = (u32)(client_rect.right - client_rect.left);
window.height = (u32)(client_rect.bottom - client_rect.top); 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; last_window = window;
@ -1016,8 +1030,8 @@ void os_update() {
GetCursorPos(&p); GetCursorPos(&p);
ScreenToClient(window._os_handle, &p); ScreenToClient(window._os_handle, &p);
p.y = window.height - p.y; p.y = window.height - p.y;
input_frame.mouse_x = (float32)p.x; input_frame.mouse_x = p.x;
input_frame.mouse_y = (float32)p.y; input_frame.mouse_y = p.y;
if (window.should_close) { if (window.should_close) {
win32_window_proc(window._os_handle, WM_CLOSE, 0, 0); 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! // Keep in mind that setting these in runtime is potentially slow!
string title; string title;
u32 width; union { s32 width; s32 pixel_width; };
u32 height; union { s32 height; s32 pixel_height; };
u32 x; s32 scaled_width;
u32 y; s32 scaled_height;
s32 x;
s32 y;
Vector4 clear_color; Vector4 clear_color;
bool enable_vsync; bool enable_vsync;

View file

@ -883,8 +883,8 @@ void test_linmath() {
// Test matrix multiplication // Test matrix multiplication
Matrix4 m5 = m4_scalar(1.0f); Matrix4 m5 = m4_scalar(1.0f);
m5.m[0][3] = 1.0f; m5.m[1][3] = 2.0f; m5.m[2][3] = 3.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); 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_multiply incorrect"); 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 // Test matrix inverse
Matrix4 identity = m4_scalar(1.0f); Matrix4 identity = m4_scalar(1.0f);
@ -1021,45 +1021,36 @@ void test_linmath() {
assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product"); assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product");
} }
void test_hash_table() { void test_hash_table() {
// Initialize a hash table with key type 'string' and value type 'int'
Hash_Table table = make_hash_table(string, int, get_heap_allocator()); Hash_Table table = make_hash_table(string, int, get_heap_allocator());
// Test hash_table_set for adding new key-value pairs
string key1 = STR("Key string"); string key1 = STR("Key string");
int value1 = 69; int value1 = 69;
bool newly_added = hash_table_set(&table, key1, value1); bool newly_added = hash_table_set(&table, key1, value1);
assert(newly_added == true, "Failed: Key should be newly added"); assert(newly_added == true, "Failed: Key should be newly added");
// Test hash_table_find for existing key
int* found_value = hash_table_find(&table, key1); int* found_value = hash_table_find(&table, key1);
assert(found_value != NULL, "Failed: Key should exist in hash table"); assert(found_value != NULL, "Failed: Key should exist in hash table");
assert(*found_value == 69, "Failed: Value should be 69, got %i", *found_value); assert(*found_value == 69, "Failed: Value should be 69, got %i", *found_value);
// Test hash_table_set for updating existing key
int new_value1 = 70; int new_value1 = 70;
newly_added = hash_table_set(&table, key1, new_value1); newly_added = hash_table_set(&table, key1, new_value1);
assert(newly_added == false, "Failed: Key should not be newly added"); assert(newly_added == false, "Failed: Key should not be newly added");
// Test hash_table_find for updated value
found_value = hash_table_find(&table, key1); found_value = hash_table_find(&table, key1);
assert(found_value != NULL, "Failed: Key should exist in hash table"); assert(found_value != NULL, "Failed: Key should exist in hash table");
assert(*found_value == 70, "Failed: Value should be 70, got %i", *found_value); assert(*found_value == 70, "Failed: Value should be 70, got %i", *found_value);
// Test hash_table_contains for existing key
bool contains = hash_table_contains(&table, key1); bool contains = hash_table_contains(&table, key1);
assert(contains == true, "Failed: Hash table should contain key1"); assert(contains == true, "Failed: Hash table should contain key1");
// Test hash_table_contains for non-existing key
string key2 = STR("Non-existing key"); string key2 = STR("Non-existing key");
contains = hash_table_contains(&table, key2); contains = hash_table_contains(&table, key2);
assert(contains == false, "Failed: Hash table should not contain key2"); assert(contains == false, "Failed: Hash table should not contain key2");
// Test hash_table_reset
hash_table_reset(&table); hash_table_reset(&table);
found_value = hash_table_find(&table, key1); found_value = hash_table_find(&table, key1);
assert(found_value == NULL, "Failed: Hash table should be empty after reset"); assert(found_value == NULL, "Failed: Hash table should be empty after reset");
// Test hash_table_destroy
hash_table_destroy(&table); hash_table_destroy(&table);
assert(table.entries == NULL, "Failed: Hash table entries should be NULL after destroy"); 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.count == 0, "Failed: Hash table count should be 0 after destroy");

View file

@ -40,6 +40,9 @@ typedef signed short s16;
typedef unsigned int u32; typedef unsigned int u32;
typedef signed int s32; 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) { void *stbtt_malloc(size_t size) {
if (!size) return 0; if (!size) return 0;
return alloc(get_heap_allocator(), size); return alloc(get_heap_allocator(), size);

View file

@ -36,3 +36,76 @@ int utf16_to_utf32(const u16 *utf16, u64 length, u32 *utf32) {
return 1; 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;
}