- 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:
parent
83c0371dcb
commit
18f4fc8123
18 changed files with 3276 additions and 621 deletions
8
build.c
8
build.c
|
@ -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
|
@ -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.type == QUAD_TYPE_REGULAR) {
|
||||||
if (input.texture_index >= 0) {
|
if (input.texture_index >= 0) {
|
||||||
return sample_texture(input.texture_index, input.uv);
|
return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
|
||||||
} else {
|
} else {
|
||||||
return input.color;
|
return input.color;
|
||||||
}
|
}
|
||||||
|
} else if (input.type == QUAD_TYPE_TEXT) {
|
||||||
|
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);
|
||||||
}
|
}
|
|
@ -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})
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
74
oogabooga/examples/text_rendering.c
Normal file
74
oogabooga/examples/text_rendering.c
Normal 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
361
oogabooga/font.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
|
BOOL ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
|
||||||
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
|
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
|
||||||
|
|
||||||
u32 actual_x = update_rect.left;
|
u32 actual_x = update_rect.left;
|
||||||
u32 actual_y = update_rect.top;
|
u32 actual_y = update_rect.top;
|
||||||
u32 actual_width = update_rect.right-update_rect.left;
|
u32 actual_width = update_rect.right - update_rect.left;
|
||||||
u32 actual_height = update_rect.bottom-update_rect.top;
|
u32 actual_height = update_rect.bottom - update_rect.top;
|
||||||
|
|
||||||
SetWindowPos(window._os_handle, 0, actual_x, actual_y, actual_width, actual_height, 0);
|
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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
Reference in a new issue