Merge branch 'dev'
This commit is contained in:
commit
8d081c1afe
22 changed files with 3823 additions and 685 deletions
17
build.c
17
build.c
|
@ -3,17 +3,6 @@
|
|||
///
|
||||
// Build config stuff
|
||||
|
||||
#define RUN_TESTS 0
|
||||
|
||||
// This is only for people developing oogabooga!
|
||||
#define OOGABOOGA_DEV 1
|
||||
|
||||
#define ENABLE_PROFILING 0
|
||||
|
||||
// ENABLE_SIMD Requires CPU to support at least SSE1 but I will be very surprised if you find a system today which doesn't
|
||||
#define ENABLE_SIMD 1
|
||||
|
||||
|
||||
#define INITIAL_PROGRAM_MEMORY_SIZE MB(5)
|
||||
|
||||
typedef struct Context_Extra {
|
||||
|
@ -37,13 +26,15 @@ typedef struct Context_Extra {
|
|||
//
|
||||
|
||||
// this is a minimal starting point for new projects. Copy & rename to get started
|
||||
// #include "oogabooga/examples/minimal_game_loop.c"
|
||||
#include "oogabooga/examples/minimal_game_loop.c"
|
||||
|
||||
// #include "oogabooga/examples/text_rendering.c"
|
||||
|
||||
// An engine dev stress test for rendering
|
||||
// #include "oogabooga/examples/renderer_stress_test.c"
|
||||
|
||||
// Randy's example game that he's building out as a tutorial for using the engine
|
||||
#include "entry_randygame.c"
|
||||
// #include "entry_randygame.c"
|
||||
|
||||
// This is where you swap in your own project!
|
||||
// #include "entry_yourepicgamename.c"
|
||||
|
|
40
changelog.txt
Normal file
40
changelog.txt
Normal file
|
@ -0,0 +1,40 @@
|
|||
## v0.00.002 - Text rendering, image manipulation, hash tables
|
||||
|
||||
Renderer:
|
||||
- Pass 8-bit integers "type" and "sampler_index" to shader
|
||||
- Sample texture differently depending on "type" (text or regular quad)
|
||||
- Sample with nearest/linear min/mag depending on sampler_index
|
||||
- Set min/mag filtering in Draw_Quad
|
||||
- Images are now created and deleted directly with gfx calls rather than deferring it for gfx_update.
|
||||
- We can now set image sub data with gfx_set_image_data()
|
||||
- Images are no longer hard coded to 4 channels
|
||||
|
||||
Text/Fonts:
|
||||
- Added font loading with lazy initialization/rasterization when it's needed
|
||||
- Load & rasterize any fonts with load_font_from_disk()
|
||||
- Fonts automatically rasterize glyphs into atlases and generate metrics on the fly
|
||||
- Measure text for very_useful metrics with measure_text()
|
||||
- Walk over glyphs and their metrics with walk_glyphs()
|
||||
- Implemented simple but powerful text rendering
|
||||
- Draw text with draw_text() or draw_text_xform()
|
||||
- See oogabooga/examples/text_rendering.c for more
|
||||
|
||||
Other:
|
||||
- Added 3 simple hash functions:
|
||||
- 64-bit xx_hash(): Very fast hash for 64-bit values
|
||||
- 64-bit city_hash(): Very fast hash for short strings
|
||||
- 64-bit djb2_hash(): Fast hash for strings longer than 32 bytes
|
||||
- Made a generic get_hash() macro to detect the type you are trying to hash and call the approriate procedure
|
||||
- Introduced a simple (kinda slow) hash table with following procedures:
|
||||
- hash_table_add
|
||||
- hash_table_find
|
||||
- hash_table_contains
|
||||
- hash_table_set
|
||||
See hash_table.c for more information.
|
||||
- Utf8 utility:
|
||||
- utf8_to_utf32(): convert utf8 bytes to a single u32 codepoint
|
||||
- next_utf8(): Convert first utf8 character in a string to a u32 codepoint and advance the passed string to the next unicode
|
||||
- Renamed m4_multiply -> m4_mul for consistency
|
||||
- Refactored os window to be DPI aware
|
||||
- Window now has scaled_width, scaled_height which is the DPI-scaled size. Previous window.width & window.height are now aliased as window.pixel_height and window.pixel_width for clarity
|
||||
- in minimal example, renamed hammer_xform -> rect_xform
|
|
@ -118,3 +118,19 @@ void pop_context() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
u64 get_next_power_of_two(u64 x) {
|
||||
if (x == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
x--;
|
||||
x |= x >> 1;
|
||||
x |= x >> 2;
|
||||
x |= x >> 4;
|
||||
x |= x >> 8;
|
||||
x |= x >> 16;
|
||||
x |= x >> 32;
|
||||
|
||||
return x + 1;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,11 @@ struct VS_INPUT
|
|||
float4 position : POSITION;
|
||||
float2 uv : TEXCOORD;
|
||||
float4 color : COLOR;
|
||||
int texture_index: TEXTURE_INDEX;
|
||||
int data1: DATA1_;
|
||||
// s8 texture_index
|
||||
// u8 type
|
||||
// u8 sampler_index
|
||||
// u8
|
||||
};
|
||||
|
||||
struct PS_INPUT
|
||||
|
@ -12,6 +16,8 @@ struct PS_INPUT
|
|||
float2 uv : TEXCOORD0;
|
||||
float4 color : COLOR;
|
||||
int texture_index: TEXTURE_INDEX;
|
||||
int type: TYPE;
|
||||
int sampler_index: SAMPLER_INDEX;
|
||||
};
|
||||
|
||||
PS_INPUT vs_main(VS_INPUT input)
|
||||
|
@ -20,56 +26,178 @@ PS_INPUT vs_main(VS_INPUT input)
|
|||
output.position = input.position;
|
||||
output.uv = input.uv;
|
||||
output.color = input.color;
|
||||
output.texture_index = input.texture_index;
|
||||
output.texture_index = (input.data1) & 0xFF;
|
||||
output.type = (input.data1 >> 8) & 0xFF;
|
||||
output.sampler_index = (input.data1 >> 16) & 0xFF;
|
||||
return output;
|
||||
}
|
||||
|
||||
// #Magicvalue
|
||||
Texture2D textures[32] : register(t0);
|
||||
SamplerState image_sampler : register(s0);
|
||||
SamplerState image_sampler_0 : register(s0);
|
||||
SamplerState image_sampler_1 : register(s1);
|
||||
SamplerState image_sampler_2 : register(s2);
|
||||
SamplerState image_sampler_3 : register(s3);
|
||||
|
||||
float4 sample_texture(int texture_index, float2 uv) {
|
||||
// #Portability sigh
|
||||
if (texture_index == 0) return textures[0].Sample(image_sampler, uv);
|
||||
else if (texture_index == 1) return textures[1].Sample(image_sampler, uv);
|
||||
else if (texture_index == 2) return textures[2].Sample(image_sampler, uv);
|
||||
else if (texture_index == 3) return textures[3].Sample(image_sampler, uv);
|
||||
else if (texture_index == 4) return textures[4].Sample(image_sampler, uv);
|
||||
else if (texture_index == 5) return textures[5].Sample(image_sampler, uv);
|
||||
else if (texture_index == 6) return textures[6].Sample(image_sampler, uv);
|
||||
else if (texture_index == 7) return textures[7].Sample(image_sampler, uv);
|
||||
else if (texture_index == 8) return textures[8].Sample(image_sampler, uv);
|
||||
else if (texture_index == 9) return textures[9].Sample(image_sampler, uv);
|
||||
else if (texture_index == 10) return textures[10].Sample(image_sampler, uv);
|
||||
else if (texture_index == 11) return textures[11].Sample(image_sampler, uv);
|
||||
else if (texture_index == 12) return textures[12].Sample(image_sampler, uv);
|
||||
else if (texture_index == 13) return textures[13].Sample(image_sampler, uv);
|
||||
else if (texture_index == 14) return textures[14].Sample(image_sampler, uv);
|
||||
else if (texture_index == 15) return textures[15].Sample(image_sampler, uv);
|
||||
else if (texture_index == 16) return textures[16].Sample(image_sampler, uv);
|
||||
else if (texture_index == 17) return textures[17].Sample(image_sampler, uv);
|
||||
else if (texture_index == 18) return textures[18].Sample(image_sampler, uv);
|
||||
else if (texture_index == 19) return textures[19].Sample(image_sampler, uv);
|
||||
else if (texture_index == 20) return textures[20].Sample(image_sampler, uv);
|
||||
else if (texture_index == 21) return textures[21].Sample(image_sampler, uv);
|
||||
else if (texture_index == 22) return textures[22].Sample(image_sampler, uv);
|
||||
else if (texture_index == 23) return textures[23].Sample(image_sampler, uv);
|
||||
else if (texture_index == 24) return textures[24].Sample(image_sampler, uv);
|
||||
else if (texture_index == 25) return textures[25].Sample(image_sampler, uv);
|
||||
else if (texture_index == 26) return textures[26].Sample(image_sampler, uv);
|
||||
else if (texture_index == 27) return textures[27].Sample(image_sampler, uv);
|
||||
else if (texture_index == 28) return textures[28].Sample(image_sampler, uv);
|
||||
else if (texture_index == 29) return textures[29].Sample(image_sampler, uv);
|
||||
else if (texture_index == 30) return textures[30].Sample(image_sampler, uv);
|
||||
else if (texture_index == 31) return textures[31].Sample(image_sampler, uv);
|
||||
float4 sample_texture(int texture_index, int sampler_index, float2 uv) {
|
||||
// I love hlsl
|
||||
if (sampler_index == 0) {
|
||||
if (texture_index == 0) return textures[0].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 1) return textures[1].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 2) return textures[2].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 3) return textures[3].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 4) return textures[4].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 5) return textures[5].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 6) return textures[6].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 7) return textures[7].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 8) return textures[8].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 9) return textures[9].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 10) return textures[10].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 11) return textures[11].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 12) return textures[12].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 13) return textures[13].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 14) return textures[14].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 15) return textures[15].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 16) return textures[16].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 17) return textures[17].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 18) return textures[18].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 19) return textures[19].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 20) return textures[20].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 21) return textures[21].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 22) return textures[22].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 23) return textures[23].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 24) return textures[24].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 25) return textures[25].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 26) return textures[26].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 27) return textures[27].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 28) return textures[28].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 29) return textures[29].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 30) return textures[30].Sample(image_sampler_0, uv);
|
||||
else if (texture_index == 31) return textures[31].Sample(image_sampler_0, uv);
|
||||
} else if (sampler_index == 1) {
|
||||
if (texture_index == 0) return textures[0].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 1) return textures[1].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 2) return textures[2].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 3) return textures[3].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 4) return textures[4].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 5) return textures[5].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 6) return textures[6].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 7) return textures[7].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 8) return textures[8].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 9) return textures[9].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 10) return textures[10].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 11) return textures[11].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 12) return textures[12].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 13) return textures[13].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 14) return textures[14].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 15) return textures[15].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 16) return textures[16].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 17) return textures[17].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 18) return textures[18].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 19) return textures[19].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 20) return textures[20].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 21) return textures[21].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 22) return textures[22].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 23) return textures[23].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 24) return textures[24].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 25) return textures[25].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 26) return textures[26].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 27) return textures[27].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 28) return textures[28].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 29) return textures[29].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 30) return textures[30].Sample(image_sampler_1, uv);
|
||||
else if (texture_index == 31) return textures[31].Sample(image_sampler_1, uv);
|
||||
} else if (sampler_index == 2) {
|
||||
if (texture_index == 0) return textures[0].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 1) return textures[1].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 2) return textures[2].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 3) return textures[3].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 4) return textures[4].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 5) return textures[5].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 6) return textures[6].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 7) return textures[7].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 8) return textures[8].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 9) return textures[9].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 10) return textures[10].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 11) return textures[11].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 12) return textures[12].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 13) return textures[13].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 14) return textures[14].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 15) return textures[15].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 16) return textures[16].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 17) return textures[17].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 18) return textures[18].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 19) return textures[19].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 20) return textures[20].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 21) return textures[21].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 22) return textures[22].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 23) return textures[23].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 24) return textures[24].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 25) return textures[25].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 26) return textures[26].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 27) return textures[27].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 28) return textures[28].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 29) return textures[29].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 30) return textures[30].Sample(image_sampler_2, uv);
|
||||
else if (texture_index == 31) return textures[31].Sample(image_sampler_2, uv);
|
||||
} else if (sampler_index == 3) {
|
||||
if (texture_index == 0) return textures[0].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 1) return textures[1].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 2) return textures[2].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 3) return textures[3].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 4) return textures[4].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 5) return textures[5].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 6) return textures[6].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 7) return textures[7].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 8) return textures[8].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 9) return textures[9].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 10) return textures[10].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 11) return textures[11].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 12) return textures[12].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 13) return textures[13].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 14) return textures[14].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 15) return textures[15].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 16) return textures[16].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 17) return textures[17].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 18) return textures[18].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 19) return textures[19].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 20) return textures[20].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 21) return textures[21].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 22) return textures[22].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 23) return textures[23].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 24) return textures[24].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 25) return textures[25].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 26) return textures[26].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 27) return textures[27].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 28) return textures[28].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 29) return textures[29].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 30) return textures[30].Sample(image_sampler_3, uv);
|
||||
else if (texture_index == 31) return textures[31].Sample(image_sampler_3, uv);
|
||||
}
|
||||
|
||||
return float4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define QUAD_TYPE_REGULAR 0
|
||||
#define QUAD_TYPE_TEXT 1
|
||||
|
||||
float4 ps_main(PS_INPUT input) : SV_TARGET
|
||||
{
|
||||
if (input.texture_index >= 0) {
|
||||
return sample_texture(input.texture_index, input.uv);
|
||||
} else {
|
||||
return input.color;
|
||||
if (input.type == QUAD_TYPE_REGULAR) {
|
||||
if (input.texture_index >= 0) {
|
||||
return sample_texture(input.texture_index, input.sampler_index, input.uv)*input.color;
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
} else if (input.type == QUAD_TYPE_TEXT) {
|
||||
if (input.texture_index >= 0) {
|
||||
float alpha = sample_texture(input.texture_index, input.sampler_index, input.uv).x;
|
||||
return float4(1.0, 1.0, 1.0, alpha)*input.color;
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
}
|
||||
return float4(0.0, 1.0, 0.0, 1.0);
|
||||
}
|
|
@ -44,6 +44,22 @@ Usage:
|
|||
|
||||
// If you ever need to free the image:
|
||||
delete_image(image);
|
||||
|
||||
|
||||
API:
|
||||
|
||||
Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip);
|
||||
Draw_Quad *draw_quad(Draw_Quad quad);
|
||||
Draw_Quad *draw_quad_xform(Draw_Quad quad, Matrix4 xform);
|
||||
Draw_Quad *draw_rect(Vector2 position, Vector2 size, Vector4 color);
|
||||
Draw_Quad *draw_rect_xform(Matrix4 xform, Vector2 size, Vector4 color);
|
||||
Draw_Quad *draw_image(Gfx_Image *image, Vector2 position, Vector2 size, Vector4 color);
|
||||
Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vector4 color);
|
||||
// raster_height is the pixel height that the text will be rasterized at. If text is blurry,
|
||||
// you can try to increase raster_height and lower scale.
|
||||
void draw_text(Gfx_Font *font, string text, u32 raster_height, Vector2 position, Vector2 scale, Vector4 color);
|
||||
void draw_text_xform(Gfx_Font *font, string text, u32 raster_height, Matrix4 xform, Vector4 color);
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
@ -52,7 +68,6 @@ Usage:
|
|||
|
||||
|
||||
|
||||
|
||||
#define QUADS_PER_BLOCK 1024
|
||||
typedef struct Draw_Quad {
|
||||
Vector2 bottom_left, top_left, top_right, bottom_right;
|
||||
|
@ -62,6 +77,11 @@ typedef struct Draw_Quad {
|
|||
|
||||
// x1, y1, x2, y2
|
||||
Vector4 uv;
|
||||
u8 type;
|
||||
|
||||
Gfx_Filter_Mode image_min_filter;
|
||||
Gfx_Filter_Mode image_mag_filter;
|
||||
|
||||
} Draw_Quad;
|
||||
|
||||
|
||||
|
@ -83,9 +103,6 @@ typedef struct Draw_Frame {
|
|||
|
||||
Matrix4 projection;
|
||||
Matrix4 view;
|
||||
|
||||
Gfx_Handle garbage_stack[4096];
|
||||
u64 garbage_stack_count;
|
||||
} Draw_Frame;
|
||||
// This frame is passed to the platform layer and rendered in os_update.
|
||||
// Resets every frame.
|
||||
|
@ -108,6 +125,9 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
|||
quad.top_left = m4_transform(world_to_clip, v4(v2_expand(quad.top_left), 0, 1)).xy;
|
||||
quad.top_right = m4_transform(world_to_clip, v4(v2_expand(quad.top_right), 0, 1)).xy;
|
||||
quad.bottom_right = m4_transform(world_to_clip, v4(v2_expand(quad.bottom_right), 0, 1)).xy;
|
||||
|
||||
quad.image_min_filter = GFX_FILTER_MODE_NEAREST;
|
||||
quad.image_min_filter = GFX_FILTER_MODE_NEAREST;
|
||||
|
||||
if (!draw_frame.current) draw_frame.current = &first_block;
|
||||
|
||||
|
@ -134,14 +154,14 @@ Draw_Quad *draw_quad_projected(Draw_Quad quad, Matrix4 world_to_clip) {
|
|||
return &draw_frame.current->quad_buffer[draw_frame.current->num_quads-1];
|
||||
}
|
||||
Draw_Quad *draw_quad(Draw_Quad quad) {
|
||||
return draw_quad_projected(quad, m4_multiply(draw_frame.projection, m4_inverse(draw_frame.view)));
|
||||
return draw_quad_projected(quad, m4_mul(draw_frame.projection, m4_inverse(draw_frame.view)));
|
||||
}
|
||||
|
||||
Draw_Quad *draw_quad_xform(Draw_Quad quad, Matrix4 xform) {
|
||||
Matrix4 world_to_clip = m4_scalar(1.0);
|
||||
world_to_clip = m4_multiply(world_to_clip, draw_frame.projection);
|
||||
world_to_clip = m4_multiply(world_to_clip, m4_inverse(draw_frame.view));
|
||||
world_to_clip = m4_multiply(world_to_clip, xform);
|
||||
world_to_clip = m4_mul(world_to_clip, draw_frame.projection);
|
||||
world_to_clip = m4_mul(world_to_clip, m4_inverse(draw_frame.view));
|
||||
world_to_clip = m4_mul(world_to_clip, xform);
|
||||
return draw_quad_projected(quad, world_to_clip);
|
||||
}
|
||||
|
||||
|
@ -158,6 +178,7 @@ Draw_Quad *draw_rect(Vector2 position, Vector2 size, Vector4 color) {
|
|||
q.bottom_right = v2(right, bottom);
|
||||
q.color = color;
|
||||
q.image = 0;
|
||||
q.type = QUAD_TYPE_REGULAR;
|
||||
|
||||
return draw_quad(q);
|
||||
}
|
||||
|
@ -169,6 +190,7 @@ Draw_Quad *draw_rect_xform(Matrix4 xform, Vector2 size, Vector4 color) {
|
|||
q.bottom_right = v2(size.x, 0);
|
||||
q.color = color;
|
||||
q.image = 0;
|
||||
q.type = QUAD_TYPE_REGULAR;
|
||||
|
||||
return draw_quad_xform(q, xform);
|
||||
}
|
||||
|
@ -189,6 +211,52 @@ Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vecto
|
|||
return q;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Gfx_Font *font;
|
||||
string text;
|
||||
u32 raster_height;
|
||||
Matrix4 xform;
|
||||
Vector2 scale;
|
||||
Vector4 color;
|
||||
} Draw_Text_Callback_Params;
|
||||
bool draw_text_callback(Gfx_Glyph glyph, Gfx_Font_Atlas *atlas, float glyph_x, float glyph_y, void *ud) {
|
||||
|
||||
u32 codepoint = glyph.codepoint;
|
||||
|
||||
Draw_Text_Callback_Params *params = (Draw_Text_Callback_Params*)ud;
|
||||
|
||||
Vector2 size = v2(glyph.width*params->scale.x, glyph.height*params->scale.y);
|
||||
|
||||
Matrix4 glyph_xform = m4_translate(params->xform, v3(glyph_x, glyph_y, 0));
|
||||
|
||||
Draw_Quad *q = draw_image_xform(atlas->image, glyph_xform, size, params->color);
|
||||
q->uv = glyph.uv;
|
||||
q->type = QUAD_TYPE_TEXT;
|
||||
q->image_min_filter = GFX_FILTER_MODE_LINEAR;
|
||||
q->image_mag_filter = GFX_FILTER_MODE_LINEAR;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void draw_text_xform(Gfx_Font *font, string text, u32 raster_height, Matrix4 xform, Vector2 scale, Vector4 color) {
|
||||
|
||||
Draw_Text_Callback_Params p;
|
||||
p.font = font;
|
||||
p.text = text;
|
||||
p.raster_height = raster_height;
|
||||
p.xform = xform;
|
||||
p.scale = scale;
|
||||
p.color = color;
|
||||
|
||||
walk_glyphs((Walk_Glyphs_Spec){font, text, raster_height, scale, true, &p}, draw_text_callback);
|
||||
}
|
||||
void draw_text(Gfx_Font *font, string text, u32 raster_height, Vector2 position, Vector2 scale, Vector4 color) {
|
||||
Matrix4 xform = m4_scalar(1.0);
|
||||
xform = m4_translate(xform, v3(position.x, position.y, 0));
|
||||
|
||||
draw_text_xform(font, text, raster_height, xform, scale, color);
|
||||
}
|
||||
|
||||
|
||||
#define COLOR_RED ((Vector4){1.0, 0.0, 0.0, 1.0})
|
||||
#define COLOR_GREEN ((Vector4){0.0, 1.0, 0.0, 1.0})
|
||||
|
@ -196,40 +264,3 @@ Draw_Quad *draw_image_xform(Gfx_Image *image, Matrix4 xform, Vector2 size, Vecto
|
|||
#define COLOR_WHITE ((Vector4){1.0, 1.0, 1.0, 1.0})
|
||||
#define COLOR_BLACK ((Vector4){0.0, 0.0, 0.0, 1.0})
|
||||
|
||||
Gfx_Image *load_image_from_disk(string path, Allocator allocator) {
|
||||
string png;
|
||||
bool ok = os_read_entire_file(path, &png, allocator);
|
||||
if (!ok) return 0;
|
||||
|
||||
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image));
|
||||
|
||||
// Use stb_image to load and decode the PNG
|
||||
int width, height, channels;
|
||||
stbi_set_flip_vertically_on_load(1); // stb_image can flip the image on load
|
||||
unsigned char* stb_data = stbi_load_from_memory(png.data, png.count, &width, &height, &channels, STBI_rgb_alpha);
|
||||
|
||||
if (!stb_data) {
|
||||
dealloc(allocator, image);
|
||||
dealloc_string(allocator, png);
|
||||
return 0;
|
||||
}
|
||||
|
||||
image->data = stb_data;
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
|
||||
image->allocator = allocator;
|
||||
|
||||
dealloc_string(allocator, png);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void delete_image(Gfx_Image *image) {
|
||||
stbi_image_free(image->data); // Free the image data allocated by stb_image
|
||||
image->width = 0;
|
||||
image->height = 0;
|
||||
draw_frame.garbage_stack[draw_frame.garbage_stack_count] = image->gfx_handle;
|
||||
draw_frame.garbage_stack_count += 1;
|
||||
dealloc(image->allocator, image);
|
||||
}
|
|
@ -2,8 +2,8 @@
|
|||
int entry(int argc, char **argv) {
|
||||
|
||||
window.title = STR("Minimal Game Example");
|
||||
window.width = 1280;
|
||||
window.height = 720;
|
||||
window.scaled_width = 1280; // We need to set the scaled size if we want to handle system scaling (DPI)
|
||||
window.scaled_height = 720;
|
||||
window.x = 200;
|
||||
window.y = 200;
|
||||
window.clear_color = hex_to_rgba(0x6495EDff);
|
||||
|
@ -14,10 +14,10 @@ int entry(int argc, char **argv) {
|
|||
os_update();
|
||||
|
||||
float64 now = os_get_current_time_in_seconds();
|
||||
Matrix4 hammer_xform = m4_scalar(1.0);
|
||||
hammer_xform = m4_rotate_z(hammer_xform, (f32)now);
|
||||
hammer_xform = m4_translate(hammer_xform, v3(-.25f, -.25f, 0));
|
||||
draw_rect_xform(hammer_xform, v2(.5f, .5f), COLOR_RED);
|
||||
Matrix4 rect_xform = m4_scalar(1.0);
|
||||
rect_xform = m4_rotate_z(rect_xform, (f32)now);
|
||||
rect_xform = m4_translate(rect_xform, v3(-.25f, -.25f, 0));
|
||||
draw_rect_xform(rect_xform, v2(.5f, .5f), COLOR_RED);
|
||||
|
||||
gfx_update();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,19 @@ int entry(int argc, char **argv) {
|
|||
Gfx_Image *hammer_image = load_image_from_disk(STR("oogabooga/examples/hammer.png"), get_heap_allocator());
|
||||
assert(hammer_image, "Failed loading hammer.png");
|
||||
|
||||
void *my_data = alloc(get_heap_allocator(), 32*32*4);
|
||||
memset(my_data, 0xffffffff, 32*32*4);
|
||||
Gfx_Image *my_image = make_image(32, 32, 4, my_data, get_heap_allocator());
|
||||
for (int *c = (int*)my_data; c < (int*)my_data+16*16; c += 1) {
|
||||
*c = 0xff0000ff;
|
||||
}
|
||||
gfx_set_image_data(my_image, 0, 0, 16, 16, my_data);
|
||||
|
||||
Gfx_Font *font = load_font_from_disk(STR("C:/windows/fonts/arial.ttf"), get_heap_allocator());
|
||||
assert(font, "Failed loading arial.ttf, %d", GetLastError());
|
||||
|
||||
render_atlas_if_not_yet_rendered(font, 32, 'A');
|
||||
|
||||
seed_for_random = os_get_current_cycle_count();
|
||||
|
||||
const float64 fps_limit = 69000;
|
||||
|
@ -76,7 +89,7 @@ int entry(int argc, char **argv) {
|
|||
draw_frame.view = camera_view;
|
||||
|
||||
seed_for_random = 69;
|
||||
for (u64 i = 0; i < 100000; i++) {
|
||||
for (u64 i = 0; i < 10000; i++) {
|
||||
float32 aspect = (float32)window.width/(float32)window.height;
|
||||
float min_x = -aspect;
|
||||
float max_x = aspect;
|
||||
|
@ -102,11 +115,23 @@ int entry(int argc, char **argv) {
|
|||
|
||||
draw_image(bush_image, v2(0.65, 0.65), v2(0.2*sin(now), 0.2*sin(now)), COLOR_WHITE);
|
||||
|
||||
u32 atlas_index = 0;
|
||||
Gfx_Font_Atlas *atlas = (Gfx_Font_Atlas*)hash_table_find(&font->variations[32].atlases, atlas_index);
|
||||
|
||||
draw_text(font, STR("I am text"), 128, v2(sin(now), -0.61), v2(0.001, 0.001), COLOR_BLACK);
|
||||
draw_text(font, STR("I am text"), 128, v2(sin(now)-0.01, -0.6), v2(0.001, 0.001), COLOR_WHITE);
|
||||
|
||||
draw_text(font, STR("Hello jje\nnew line"), 128, v2(-1, 0.5), v2(0.001, 0.001), COLOR_WHITE);
|
||||
|
||||
local_persist bool show = false;
|
||||
if (is_key_just_pressed('T')) show = !show;
|
||||
|
||||
if (show) draw_image(atlas->image, v2(-1.6, -1), v2(4, 4), COLOR_WHITE);
|
||||
|
||||
tm_scope_cycles("gfx_update") {
|
||||
gfx_update();
|
||||
}
|
||||
|
||||
|
||||
if (is_key_just_released('E')) {
|
||||
log("FPS: %.2f", 1.0 / delta);
|
||||
log("ms: %.2f", delta*1000.0);
|
||||
|
|
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;
|
||||
}
|
||||
|
|
@ -13,17 +13,27 @@ const Gfx_Handle GFX_INVALID_HANDLE = 0;
|
|||
|
||||
string temp_win32_null_terminated_wide_to_fixed_utf8(const u16 *utf16);
|
||||
|
||||
typedef struct alignat(16) D3D11_Vertex {
|
||||
typedef struct alignat(16) D3D11_Vertex {
|
||||
|
||||
|
||||
Vector4 color;
|
||||
Vector4 position;
|
||||
Vector2 uv;
|
||||
int texture_index;
|
||||
union {
|
||||
s32 data1;
|
||||
struct {
|
||||
s8 texture_index;
|
||||
u8 type;
|
||||
u8 sampler;
|
||||
u8 padding;
|
||||
};
|
||||
};
|
||||
} D3D11_Vertex;
|
||||
|
||||
ID3D11Debug *d3d11_debug = 0;
|
||||
|
||||
ID3D11Device* d3d11_device = 0;
|
||||
ID3D11DeviceContext* d3d11_context = 0;
|
||||
ID3D11Device *d3d11_device = 0;
|
||||
ID3D11DeviceContext *d3d11_context = 0;
|
||||
ID3D11RenderTargetView *d3d11_window_render_target_view = 0;
|
||||
ID3D11Texture2D *d3d11_back_buffer = 0;
|
||||
D3D_DRIVER_TYPE d3d11_driver_type = 0;
|
||||
|
@ -36,7 +46,10 @@ u32 d3d11_swap_chain_height = 0;
|
|||
|
||||
ID3D11BlendState *d3d11_blend_state = 0;
|
||||
ID3D11RasterizerState *d3d11_rasterizer = 0;
|
||||
ID3D11SamplerState *d3d11_image_sampler = 0;
|
||||
ID3D11SamplerState *d3d11_image_sampler_np_fp = 0;
|
||||
ID3D11SamplerState *d3d11_image_sampler_nl_fl = 0;
|
||||
ID3D11SamplerState *d3d11_image_sampler_np_fl = 0;
|
||||
ID3D11SamplerState *d3d11_image_sampler_nl_fp = 0;
|
||||
|
||||
ID3D11VertexShader *d3d11_image_vertex_shader = 0;
|
||||
ID3D11PixelShader *d3d11_image_pixel_shader = 0;
|
||||
|
@ -358,7 +371,21 @@ void gfx_init() {
|
|||
sd.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sd.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sd.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
||||
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler);
|
||||
|
||||
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
||||
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_np_fp);
|
||||
win32_check_hr(hr);
|
||||
|
||||
sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_nl_fl);
|
||||
win32_check_hr(hr);
|
||||
|
||||
sd.Filter = D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT;
|
||||
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_np_fl);
|
||||
win32_check_hr(hr);
|
||||
|
||||
sd.Filter = D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR;
|
||||
hr = VTABLE(CreateSamplerState, d3d11_device, &sd, &d3d11_image_sampler_nl_fp);
|
||||
win32_check_hr(hr);
|
||||
}
|
||||
|
||||
|
@ -461,11 +488,11 @@ void gfx_init() {
|
|||
layout[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
|
||||
layout[2].InstanceDataStepRate = 0;
|
||||
|
||||
layout[3].SemanticName = "TEXTURE_INDEX";
|
||||
layout[3].SemanticName = "DATA1_";
|
||||
layout[3].SemanticIndex = 0;
|
||||
layout[3].Format = DXGI_FORMAT_R32_SINT;
|
||||
layout[3].InputSlot = 0;
|
||||
layout[3].AlignedByteOffset = offsetof(D3D11_Vertex, texture_index);
|
||||
layout[3].AlignedByteOffset = offsetof(D3D11_Vertex, data1);
|
||||
layout[3].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
|
||||
layout[3].InstanceDataStepRate = 0;
|
||||
|
||||
|
@ -501,7 +528,10 @@ void d3d11_draw_call(int number_of_rendered_quads, ID3D11ShaderResourceView **te
|
|||
VTABLE(VSSetShader, d3d11_context, d3d11_image_vertex_shader, NULL, 0);
|
||||
VTABLE(PSSetShader, d3d11_context, d3d11_image_pixel_shader, NULL, 0);
|
||||
|
||||
VTABLE(PSSetSamplers, d3d11_context, 0, 1, &d3d11_image_sampler);
|
||||
VTABLE(PSSetSamplers, d3d11_context, 0, 1, &d3d11_image_sampler_np_fp);
|
||||
VTABLE(PSSetSamplers, d3d11_context, 1, 1, &d3d11_image_sampler_nl_fl);
|
||||
VTABLE(PSSetSamplers, d3d11_context, 2, 1, &d3d11_image_sampler_np_fl);
|
||||
VTABLE(PSSetSamplers, d3d11_context, 3, 1, &d3d11_image_sampler_nl_fp);
|
||||
VTABLE(PSSetShaderResources, d3d11_context, 0, num_textures, textures);
|
||||
|
||||
VTABLE(ClearRenderTargetView, d3d11_context, d3d11_window_render_target_view, (float*)&window.clear_color);
|
||||
|
@ -518,23 +548,6 @@ void gfx_update() {
|
|||
HRESULT hr;
|
||||
|
||||
tm_scope_cycles("Frame setup") {
|
||||
///
|
||||
// purge garbage
|
||||
for (u64 i = 0; i < draw_frame.garbage_stack_count; i++) {
|
||||
ID3D11ShaderResourceView *view = draw_frame.garbage_stack[i];
|
||||
ID3D11Resource *resource = 0;
|
||||
VTABLE(GetResource, view, &resource);
|
||||
|
||||
ID3D11Texture2D *texture = 0;
|
||||
hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
|
||||
if (SUCCEEDED(hr)) {
|
||||
D3D11Release(view);
|
||||
D3D11Release(texture);
|
||||
log("Destroyed an image");
|
||||
} else {
|
||||
panic("Unhandled D3D11 resource deletion");
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
// Maybe resize swap chain
|
||||
|
@ -592,44 +605,17 @@ void gfx_update() {
|
|||
|
||||
Draw_Quad *q = &block->quad_buffer[i];
|
||||
|
||||
int texture_index = -1;
|
||||
s8 texture_index = -1;
|
||||
|
||||
if (q->image) {
|
||||
if (!q->image->gfx_handle) {
|
||||
D3D11_TEXTURE2D_DESC desc = ZERO(D3D11_TEXTURE2D_DESC);
|
||||
desc.Width = q->image->width;
|
||||
desc.Height = q->image->height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.CPUAccessFlags = 0;
|
||||
desc.MiscFlags = 0;
|
||||
|
||||
D3D11_SUBRESOURCE_DATA data = ZERO(D3D11_SUBRESOURCE_DATA);
|
||||
data.pSysMem = q->image->data;
|
||||
data.SysMemPitch = q->image->width * 4; // #Magicvalue assuming 4 channels
|
||||
|
||||
ID3D11Texture2D* texture = 0;
|
||||
HRESULT hr = VTABLE(CreateTexture2D, d3d11_device, &desc, &data, &texture);
|
||||
win32_check_hr(hr);
|
||||
|
||||
hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &q->image->gfx_handle);
|
||||
win32_check_hr(hr);
|
||||
|
||||
log_verbose("Created an image of width %d and height %d.", q->image->width, q->image->height);
|
||||
}
|
||||
|
||||
if (last_texture == q->image->gfx_handle) {
|
||||
texture_index = (int)(num_textures-1);
|
||||
texture_index = (s8)(num_textures-1);
|
||||
} else {
|
||||
// First look if texture is already bound
|
||||
for (u64 j = 0; j < num_textures; j++) {
|
||||
if (textures[j] == q->image->gfx_handle) {
|
||||
texture_index = (int)j;
|
||||
texture_index = (s8)j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -648,7 +634,7 @@ void gfx_update() {
|
|||
number_of_rendered_quads = 0;
|
||||
pointer = head;
|
||||
} else {
|
||||
texture_index = (int)num_textures;
|
||||
texture_index = (s8)num_textures;
|
||||
num_textures += 1;
|
||||
}
|
||||
}
|
||||
|
@ -657,6 +643,20 @@ void gfx_update() {
|
|||
last_texture = q->image->gfx_handle;
|
||||
}
|
||||
|
||||
if (q->type == QUAD_TYPE_TEXT) {
|
||||
float pixel_width = 2.0/(float)window.width;
|
||||
float pixel_height = 2.0/(float)window.height;
|
||||
|
||||
q->bottom_left.x = round(q->bottom_left.x / pixel_width) * pixel_width;
|
||||
q->bottom_left.y = round(q->bottom_left.y / pixel_height) * pixel_height;
|
||||
q->top_left.x = round(q->top_left.x / pixel_width) * pixel_width;
|
||||
q->top_left.y = round(q->top_left.y / pixel_height) * pixel_height;
|
||||
q->top_right.x = round(q->top_right.x / pixel_width) * pixel_width;
|
||||
q->top_right.y = round(q->top_right.y / pixel_height) * pixel_height;
|
||||
q->bottom_right.x = round(q->bottom_right.x / pixel_width) * pixel_width;
|
||||
q->bottom_right.y = round(q->bottom_right.y / pixel_height) * pixel_height;
|
||||
}
|
||||
|
||||
// We will write to 6 vertices for the one quad (two tris)
|
||||
{
|
||||
|
||||
|
@ -681,6 +681,24 @@ void gfx_update() {
|
|||
BL->color = TL->color = TR->color = BR->color = q->color;
|
||||
|
||||
BL->texture_index=TL->texture_index=TR->texture_index=BR->texture_index = texture_index;
|
||||
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
|
||||
|
||||
u8 sampler = 0;
|
||||
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
|
||||
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
|
||||
sampler = 0;
|
||||
if (q->image_min_filter == GFX_FILTER_MODE_LINEAR
|
||||
&& q->image_mag_filter == GFX_FILTER_MODE_LINEAR)
|
||||
sampler = 1;
|
||||
if (q->image_min_filter == GFX_FILTER_MODE_LINEAR
|
||||
&& q->image_mag_filter == GFX_FILTER_MODE_NEAREST)
|
||||
sampler = 2;
|
||||
if (q->image_min_filter == GFX_FILTER_MODE_NEAREST
|
||||
&& q->image_mag_filter == GFX_FILTER_MODE_LINEAR)
|
||||
sampler = 3;
|
||||
|
||||
BL->type=TL->type=TR->type=BR->type = (u8)q->type;
|
||||
BL->sampler=TL->sampler=TR->sampler=BR->sampler = (u8)sampler;
|
||||
|
||||
*BL2 = *BL;
|
||||
*TR2 = *TR;
|
||||
|
@ -742,3 +760,92 @@ void gfx_update() {
|
|||
reset_draw_frame(&draw_frame);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void gfx_init_image(Gfx_Image *image, void *initial_data) {
|
||||
|
||||
void *data = initial_data;
|
||||
if (!initial_data){
|
||||
// #Incomplete 8 bit width assumed
|
||||
data = alloc(image->allocator, image->width*image->height*image->channels);
|
||||
memset(data, 0, image->width*image->height*image->channels);
|
||||
}
|
||||
|
||||
assert(image->channels > 0 && image->channels <= 4 && image->channels != 3, "Only 1, 2 or 4 channels allowed on images. Got %d", image->channels);
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc = ZERO(D3D11_TEXTURE2D_DESC);
|
||||
desc.Width = image->width;
|
||||
desc.Height = image->height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
switch (image->channels) {
|
||||
case 1: desc.Format = DXGI_FORMAT_R8_UNORM; break;
|
||||
case 2: desc.Format = DXGI_FORMAT_R8G8_UNORM; break;
|
||||
case 4: desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; break;
|
||||
default: panic("You should not be here");
|
||||
}
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
|
||||
desc.CPUAccessFlags = 0;
|
||||
desc.MiscFlags = 0;
|
||||
|
||||
D3D11_SUBRESOURCE_DATA data_desc = ZERO(D3D11_SUBRESOURCE_DATA);
|
||||
data_desc.pSysMem = data;
|
||||
data_desc.SysMemPitch = image->width * image->channels;
|
||||
|
||||
ID3D11Texture2D* texture = 0;
|
||||
HRESULT hr = VTABLE(CreateTexture2D, d3d11_device, &desc, &data_desc, &texture);
|
||||
win32_check_hr(hr);
|
||||
|
||||
hr = VTABLE(CreateShaderResourceView, d3d11_device, (ID3D11Resource*)texture, 0, &image->gfx_handle);
|
||||
win32_check_hr(hr);
|
||||
|
||||
if (!initial_data) {
|
||||
dealloc(image->allocator, data);
|
||||
}
|
||||
|
||||
log_verbose("Created a D3D11 image of width %d and height %d.", image->width, image->height);
|
||||
}
|
||||
void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data) {
|
||||
assert(image && data, "Bad parameters passed to gfx_set_image_data");
|
||||
|
||||
ID3D11ShaderResourceView *view = image->gfx_handle;
|
||||
ID3D11Resource *resource = NULL;
|
||||
VTABLE(GetResource, view, &resource);
|
||||
|
||||
assert(resource, "Invalid image passed to gfx_set_image_data");
|
||||
|
||||
assert(x+w <= image->width && y+h <= image->height, "Specified subregion in image is out of bounds");
|
||||
|
||||
ID3D11Texture2D *texture = NULL;
|
||||
HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
|
||||
assert(SUCCEEDED(hr), "Expected gfx resource to be a texture but it wasn't");
|
||||
|
||||
D3D11_BOX destBox;
|
||||
destBox.left = x;
|
||||
destBox.right = x + w;
|
||||
destBox.top = y;
|
||||
destBox.bottom = y + h;
|
||||
destBox.front = 0;
|
||||
destBox.back = 1;
|
||||
|
||||
// #Incomplete bit-width 8 assumed
|
||||
VTABLE(UpdateSubresource, d3d11_context, (ID3D11Resource*)texture, 0, &destBox, data, w * image->channels, 0);
|
||||
}
|
||||
void gfx_deinit_image(Gfx_Image *image) {
|
||||
ID3D11ShaderResourceView *view = image->gfx_handle;
|
||||
ID3D11Resource *resource = 0;
|
||||
VTABLE(GetResource, view, &resource);
|
||||
|
||||
ID3D11Texture2D *texture = 0;
|
||||
HRESULT hr = VTABLE(QueryInterface, resource, &IID_ID3D11Texture2D, (void**)&texture);
|
||||
if (SUCCEEDED(hr)) {
|
||||
D3D11Release(view);
|
||||
D3D11Release(texture);
|
||||
log("Destroyed an image");
|
||||
} else {
|
||||
panic("Unhandled D3D11 resource deletion");
|
||||
}
|
||||
}
|
|
@ -16,17 +16,82 @@
|
|||
#endif
|
||||
|
||||
forward_global const Gfx_Handle GFX_INVALID_HANDLE;
|
||||
#define QUAD_TYPE_REGULAR 0
|
||||
#define QUAD_TYPE_TEXT 1
|
||||
|
||||
typedef enum Gfx_Filter_Mode {
|
||||
GFX_FILTER_MODE_NEAREST,
|
||||
GFX_FILTER_MODE_LINEAR,
|
||||
} Gfx_Filter_Mode;
|
||||
|
||||
typedef struct Gfx_Image {
|
||||
u32 width, height;
|
||||
u8 *data;
|
||||
u32 width, height, channels;
|
||||
Gfx_Handle gfx_handle;
|
||||
Allocator allocator;
|
||||
} Gfx_Image;
|
||||
|
||||
typedef struct Gfx_Font {
|
||||
u32 width, height;
|
||||
u8 *data;
|
||||
Gfx_Handle gfx_handle;
|
||||
Allocator allocator;
|
||||
} Gfx_Font;
|
||||
Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator);
|
||||
Gfx_Image *load_image_from_disk(string path, Allocator allocator);
|
||||
void delete_image(Gfx_Image *image);
|
||||
|
||||
// Implemented per renderer
|
||||
void gfx_init_image(Gfx_Image *image, void *data);
|
||||
void gfx_set_image_data(Gfx_Image *image, u32 x, u32 y, u32 w, u32 h, void *data);
|
||||
void gfx_deinit_image(Gfx_Image *image);
|
||||
|
||||
// initial_data can be null to leave image data uninitialized
|
||||
Gfx_Image *make_image(u32 width, u32 height, u32 channels, void *initial_data, Allocator allocator) {
|
||||
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image) + width*height*channels);
|
||||
|
||||
assert(channels > 0 && channels <= 4, "Only 1, 2, 3 or 4 channels allowed on images. Got %d", channels);
|
||||
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
|
||||
image->allocator = allocator;
|
||||
image->channels = channels;
|
||||
|
||||
gfx_init_image(image, initial_data);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
Gfx_Image *load_image_from_disk(string path, Allocator allocator) {
|
||||
string png;
|
||||
bool ok = os_read_entire_file(path, &png, allocator);
|
||||
if (!ok) return 0;
|
||||
|
||||
Gfx_Image *image = alloc(allocator, sizeof(Gfx_Image));
|
||||
|
||||
int width, height, channels;
|
||||
stbi_set_flip_vertically_on_load(1);
|
||||
unsigned char* stb_data = stbi_load_from_memory(png.data, png.count, &width, &height, &channels, STBI_rgb_alpha);
|
||||
|
||||
if (!stb_data) {
|
||||
dealloc(allocator, image);
|
||||
dealloc_string(allocator, png);
|
||||
return 0;
|
||||
}
|
||||
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
image->gfx_handle = GFX_INVALID_HANDLE; // This is handled in gfx
|
||||
image->allocator = allocator;
|
||||
image->channels = 4;
|
||||
|
||||
dealloc_string(allocator, png);
|
||||
|
||||
gfx_init_image(image, stb_data);
|
||||
|
||||
stbi_image_free(stb_data);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void delete_image(Gfx_Image *image) {
|
||||
// Free the image data allocated by stb_image
|
||||
image->width = 0;
|
||||
image->height = 0;
|
||||
gfx_deinit_image(image);
|
||||
dealloc(image->allocator, image);
|
||||
}
|
||||
|
|
84
oogabooga/hash.c
Normal file
84
oogabooga/hash.c
Normal file
|
@ -0,0 +1,84 @@
|
|||
|
||||
|
||||
#define PRIME64_1 11400714785074694791ULL
|
||||
#define PRIME64_2 14029467366897019727ULL
|
||||
#define PRIME64_3 1609587929392839161ULL
|
||||
#define PRIME64_4 9650029242287828579ULL
|
||||
#define PRIME64_5 2870177450012600261ULL
|
||||
|
||||
static inline u64 xx_hash(u64 x) {
|
||||
u64 h64 = PRIME64_5 + 8;
|
||||
h64 += x * PRIME64_3;
|
||||
h64 = ((h64 << 23) | (h64 >> (64 - 23))) * PRIME64_2 + PRIME64_4;
|
||||
h64 ^= h64 >> 33;
|
||||
h64 *= PRIME64_2;
|
||||
h64 ^= h64 >> 29;
|
||||
h64 *= PRIME64_3;
|
||||
h64 ^= h64 >> 32;
|
||||
return h64;
|
||||
}
|
||||
|
||||
static inline u64 city_hash(string s) {
|
||||
const u64 k = 0x9ddfea08eb382d69ULL;
|
||||
u64 a = s.count;
|
||||
u64 b = s.count * 5;
|
||||
u64 c = 9;
|
||||
u64 d = b;
|
||||
|
||||
if (s.count <= 16) {
|
||||
memcpy(&a, s.data, sizeof(u64));
|
||||
memcpy(&b, s.data + s.count - 8, sizeof(u64));
|
||||
} else {
|
||||
memcpy(&a, s.data, sizeof(u64));
|
||||
memcpy(&b, s.data + 8, sizeof(u64));
|
||||
memcpy(&c, s.data + s.count - 8, sizeof(u64));
|
||||
memcpy(&d, s.data + s.count - 16, sizeof(u64));
|
||||
}
|
||||
|
||||
a += b;
|
||||
a = (a << 43) | (a >> (64 - 43));
|
||||
a += c;
|
||||
a = a * 5 + 0x52dce729;
|
||||
d ^= a;
|
||||
d = (d << 44) | (d >> (64 - 44));
|
||||
d += b;
|
||||
|
||||
return d * k;
|
||||
}
|
||||
|
||||
u64 djb2_hash(string s) {
|
||||
u64 hash = 5381;
|
||||
for (u64 i = 0; i < s.count; i++) {
|
||||
hash = ((hash << 5) + hash) + s.data[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
u64 string_get_hash(string s) {
|
||||
if (s.count > 32) return djb2_hash(s);
|
||||
return city_hash(s);
|
||||
}
|
||||
u64 pointer_get_hash(void *p) {
|
||||
return xx_hash((u64)p);
|
||||
}
|
||||
u64 float64_get_hash(float64 x) {
|
||||
return xx_hash(*(u64*)&x);
|
||||
}
|
||||
u64 float32_get_hash(float32 x) {
|
||||
return float64_get_hash((float64)x);
|
||||
}
|
||||
|
||||
#define get_hash(x) _Generic((x), \
|
||||
string: string_get_hash, \
|
||||
s8: xx_hash, \
|
||||
u8: xx_hash, \
|
||||
s16: xx_hash, \
|
||||
u16: xx_hash, \
|
||||
s32: xx_hash, \
|
||||
u32: xx_hash, \
|
||||
s64: xx_hash, \
|
||||
u64: xx_hash, \
|
||||
f32: float32_get_hash, \
|
||||
f64: float64_get_hash, \
|
||||
default: pointer_get_hash \
|
||||
)(x)
|
218
oogabooga/hash_table.c
Normal file
218
oogabooga/hash_table.c
Normal file
|
@ -0,0 +1,218 @@
|
|||
|
||||
// Very naive implementation but it should be very cache efficient so it's alright
|
||||
// for non-excessive use for now.
|
||||
|
||||
/*
|
||||
|
||||
Example Usage:
|
||||
|
||||
|
||||
// Make a table with key type 'string' and value type 'int', allocated on the heap
|
||||
Hash_Table table = make_hash_table(string, int, get_heap_allocator());
|
||||
|
||||
// Set key "Key string" to integer value 69. This returns whether or not key was newly added.
|
||||
string key = STR("Key string");
|
||||
bool newly_added = hash_table_set(&table, key, 69);
|
||||
|
||||
// Find value associated with given key. Returns pointer to that value.
|
||||
string other_key = STR("Some other key");
|
||||
int* value = hash_table_find(&table, other_key);
|
||||
|
||||
if (value) {
|
||||
// Pointer is OK, item with key exists
|
||||
} else {
|
||||
// Pointer is null, item with key does NOT exist
|
||||
}
|
||||
|
||||
// Same as hash_table_find() != NULL
|
||||
string another_key = STR("Another key");
|
||||
if (hash_table_contains(&table, another_key)) {
|
||||
|
||||
}
|
||||
|
||||
// Reset all entries (but keep allocated memory)
|
||||
hash_table_reset(&table);
|
||||
|
||||
// Free allocated entries in hash table
|
||||
hash_table_destroy(&table);
|
||||
|
||||
|
||||
Limitations:
|
||||
- Key can only be a base type or pointer
|
||||
- Key and value passed to the following function needs to be lvalues (we need to be able to take their addresses with '&'):
|
||||
- hash_table_add
|
||||
- hash_table_find
|
||||
- hash_table_contains
|
||||
- hash_table_set
|
||||
|
||||
Example:
|
||||
|
||||
hash_table_set(&table, my_key+5, my_value+3); // ERROR
|
||||
|
||||
int key = my_key+5;
|
||||
int value = my_value+3;
|
||||
hash_table_set(&table, key, value); // OK
|
||||
|
||||
|
||||
*/
|
||||
|
||||
typedef struct Hash_Table Hash_Table;
|
||||
|
||||
// API:
|
||||
#define make_hash_table_reserve(Key_Type, Value_Type, capacity_count, allocator) \
|
||||
make_hash_table_reserve(sizeof(Key_Type), sizeof(Value_Type), capacity_count, allocator)
|
||||
|
||||
#define make_hash_table(Key_Type, Value_Type, allocator) \
|
||||
make_hash_table_raw(sizeof(Key_Type), sizeof(Value_Type), allocator)
|
||||
|
||||
#define hash_table_add(table_ptr, key, value) \
|
||||
hash_table_add_raw((table_ptr), get_hash(key), &(key), &(value), sizeof(key), sizeof(value))
|
||||
|
||||
#define hash_table_find(table_ptr, key) \
|
||||
hash_table_find_raw((table_ptr), get_hash(key))
|
||||
|
||||
#define hash_table_contains(table_ptr, key) \
|
||||
hash_table_contains_raw((table_ptr), get_hash(key))
|
||||
|
||||
#define hash_table_set(table_ptr, key, value) \
|
||||
hash_table_set_raw((table_ptr), get_hash(key), &key, &value, sizeof(key), sizeof(value))
|
||||
|
||||
void hash_table_reserve(Hash_Table *t, u64 required_count);
|
||||
|
||||
|
||||
typedef struct Hash_Table {
|
||||
|
||||
// Each entry is hash-key-value
|
||||
// Hash is sizeof(u64) bytes, key is _key_size bytes and value is _value_size bytes
|
||||
void *entries;
|
||||
|
||||
u64 count; // Number of valid entries
|
||||
u64 capacity_count; // Number of allocated entries
|
||||
|
||||
u64 _key_size;
|
||||
u64 _value_size;
|
||||
|
||||
Allocator allocator;
|
||||
} Hash_Table;
|
||||
|
||||
Hash_Table make_hash_table_reserve_raw(u64 key_size, u64 value_size, u64 capacity_count, Allocator allocator) {
|
||||
|
||||
capacity_count = min(capacity_count, 8);
|
||||
|
||||
Hash_Table t = ZERO(Hash_Table);
|
||||
|
||||
t._key_size = key_size;
|
||||
t._value_size = value_size;
|
||||
t.allocator = allocator;
|
||||
|
||||
u64 entry_size = value_size+sizeof(u64);
|
||||
t.entries = alloc(t.allocator, entry_size*capacity_count);
|
||||
memset(t.entries, 0, entry_size*capacity_count);
|
||||
t.capacity_count = capacity_count;
|
||||
|
||||
return t;
|
||||
}
|
||||
inline Hash_Table make_hash_table_raw(u64 key_size, u64 value_size, Allocator allocator) {
|
||||
return make_hash_table_reserve_raw(key_size, value_size, 128, allocator);
|
||||
}
|
||||
|
||||
void hash_table_reset(Hash_Table *t) {
|
||||
t->count = 0;
|
||||
}
|
||||
void hash_table_destroy(Hash_Table *t) {
|
||||
dealloc(t->allocator, t->entries);
|
||||
|
||||
t->entries = 0;
|
||||
t->count = 0;
|
||||
t->capacity_count = 0;
|
||||
}
|
||||
|
||||
void hash_table_reserve(Hash_Table *t, u64 required_count) {
|
||||
u64 entry_size = t->_value_size+sizeof(u64);
|
||||
|
||||
u64 required_size = required_count*entry_size;
|
||||
|
||||
u64 current_size = t->capacity_count*entry_size;
|
||||
|
||||
if (current_size >= required_size) return;
|
||||
|
||||
u64 new_count = get_next_power_of_two(required_count);
|
||||
u64 new_size = new_count*entry_size;
|
||||
|
||||
void *new_entries = alloc(t->allocator, new_size);
|
||||
memcpy(new_entries, t->entries, current_size);
|
||||
|
||||
dealloc(t->allocator, t->entries);
|
||||
|
||||
t->entries = new_entries;
|
||||
t->capacity_count = new_count;
|
||||
}
|
||||
|
||||
// This can add multiple entries of same hash, beware!
|
||||
void hash_table_add_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) {
|
||||
|
||||
assert(t->_key_size == key_size, "Key type size does not match hash table initted key type size");
|
||||
assert(t->_value_size == value_size, "Value type size does not match hash table initted value type size");
|
||||
|
||||
hash_table_reserve(t, t->count+1);
|
||||
|
||||
u64 entry_size = t->_value_size+sizeof(u64);
|
||||
|
||||
u64 index = entry_size*t->count;
|
||||
t->count += 1;
|
||||
|
||||
u64 hash_offset = 0;
|
||||
u64 value_offset = hash_offset + sizeof(u64);
|
||||
|
||||
memcpy((u8*)t->entries+index+hash_offset, &hash, sizeof(u64));
|
||||
memcpy((u8*)t->entries+index+value_offset, v, value_size);
|
||||
}
|
||||
|
||||
void *hash_table_find_raw(Hash_Table *t, u64 hash) {
|
||||
|
||||
// #Speed #Incomplete
|
||||
// Do quadratic probe 'triangular numbers'
|
||||
|
||||
u64 entry_size = t->_value_size+sizeof(u64);
|
||||
u64 hash_offset = 0;
|
||||
u64 value_offset = hash_offset + sizeof(u64);
|
||||
|
||||
for (u64 i = 0; i < t->count; i += 1) {
|
||||
u64 existing_hash = *(u64*)((u8*)t->entries+i*entry_size+hash_offset);
|
||||
if (existing_hash == hash) {
|
||||
void *value = ((u8*)t->entries+i*entry_size+value_offset);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *hash_table_get_nth_value(Hash_Table *t, u64 n) {
|
||||
assert(n < t->count, "Hash table n is out of range");
|
||||
|
||||
u64 entry_size = t->_value_size+sizeof(u64);
|
||||
u64 hash_offset = 0;
|
||||
u64 value_offset = hash_offset + sizeof(u64);
|
||||
|
||||
return (u8*)t->entries+entry_size*n+value_offset;
|
||||
}
|
||||
|
||||
bool hash_table_contains_raw(Hash_Table *t, u64 hash) {
|
||||
return hash_table_find_raw(t, hash) != 0;
|
||||
}
|
||||
|
||||
// Returns true if key was newly added or false if it already existed
|
||||
bool hash_table_set_raw(Hash_Table *t, u64 hash, void *k, void *v, u64 key_size, u64 value_size) {
|
||||
bool newly_added = true;
|
||||
|
||||
if (hash_table_contains_raw(t, hash)) newly_added = false;
|
||||
|
||||
if (newly_added) {
|
||||
hash_table_add_raw(t, hash, k, v, key_size, value_size);
|
||||
} else {
|
||||
memcpy(hash_table_find_raw(t, hash), v, value_size);
|
||||
}
|
||||
|
||||
return newly_added;
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ Matrix4 m4_make_scale(Vector3 scale) {
|
|||
return m;
|
||||
}
|
||||
|
||||
Matrix4 m4_multiply(Matrix4 a, Matrix4 b) {
|
||||
Matrix4 m4_mul(Matrix4 a, Matrix4 b) {
|
||||
Matrix4 result;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
|
@ -234,21 +234,21 @@ Matrix4 m4_multiply(Matrix4 a, Matrix4 b) {
|
|||
|
||||
inline Matrix4 m4_translate(Matrix4 m, Vector3 translation) {
|
||||
Matrix4 translation_matrix = m4_make_translation(translation);
|
||||
return m4_multiply(m, translation_matrix);
|
||||
return m4_mul(m, translation_matrix);
|
||||
}
|
||||
|
||||
inline Matrix4 m4_rotate(Matrix4 m, Vector3 axis, float32 radians) {
|
||||
Matrix4 rotation_matrix = m4_make_rotation(axis, radians);
|
||||
return m4_multiply(m, rotation_matrix);
|
||||
return m4_mul(m, rotation_matrix);
|
||||
}
|
||||
inline Matrix4 m4_rotate_z(Matrix4 m, float32 radians) {
|
||||
Matrix4 rotation_matrix = m4_make_rotation(v3(0, 0, 1), radians);
|
||||
return m4_multiply(m, rotation_matrix);
|
||||
return m4_mul(m, rotation_matrix);
|
||||
}
|
||||
|
||||
inline Matrix4 m4_scale(Matrix4 m, Vector3 scale) {
|
||||
Matrix4 scale_matrix = m4_make_scale(scale);
|
||||
return m4_multiply(m, scale_matrix);
|
||||
return m4_mul(m, scale_matrix);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ Heap_Block *make_heap_block(Heap_Block *parent, u64 size) {
|
|||
// #Speed #Cleanup
|
||||
if (((u8*)block)+size >= ((u8*)program_memory)+program_memory_size) {
|
||||
u64 minimum_size = ((u8*)block+size) - (u8*)program_memory + 1;
|
||||
u64 new_program_size = max((cast(u64)(minimum_size*2)), program_memory_size*2);
|
||||
u64 new_program_size = get_next_power_of_two(minimum_size);
|
||||
assert(new_program_size >= minimum_size, "Bröd");
|
||||
const u64 ATTEMPTS = 1000;
|
||||
for (u64 i = 0; i <= ATTEMPTS; i++) {
|
||||
|
|
|
@ -105,6 +105,12 @@
|
|||
|
||||
*/
|
||||
|
||||
#define OGB_VERSION_MAJOR 0
|
||||
#define OGB_VERSION_MINOR 0
|
||||
#define OGB_VERSION_PATCH 2
|
||||
|
||||
#define OGB_VERSION (OGB_VERSION_MAJOR*1000000+OGB_VERSION_MINOR*1000+OGB_VERSION_PATCH)
|
||||
|
||||
#include <stdint.h>
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
|
@ -253,11 +259,15 @@ typedef u8 bool;
|
|||
#include "string.c"
|
||||
#include "unicode.c"
|
||||
#include "string_format.c"
|
||||
#include "hash.c"
|
||||
#include "path_utils.c"
|
||||
#include "linmath.c"
|
||||
|
||||
#include "hash_table.c"
|
||||
|
||||
#include "os_interface.c"
|
||||
#include "gfx_interface.c"
|
||||
#include "font.c"
|
||||
|
||||
#include "profiling.c"
|
||||
#include "random.c"
|
||||
|
@ -291,6 +301,8 @@ typedef u8 bool;
|
|||
|
||||
#include "tests.c"
|
||||
|
||||
#define malloc please_use_alloc_for_memory_allocations_instead_of_malloc
|
||||
#define free please_use_dealloc_for_memory_deallocations_instead_of_free
|
||||
|
||||
void oogabooga_init(u64 program_memory_size) {
|
||||
context.logger = default_logger;
|
||||
|
@ -315,8 +327,9 @@ int ENTRY_PROC(int argc, char **argv);
|
|||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
printf("Ooga booga program started\n");
|
||||
print("Ooga booga program started\n");
|
||||
oogabooga_init(INITIAL_PROGRAM_MEMORY_SIZE);
|
||||
log_info("Ooga booga version is %d.%02d.%03d", OGB_VERSION_MAJOR, OGB_VERSION_MINOR, OGB_VERSION_PATCH);
|
||||
|
||||
assert(main != ENTRY_PROC, "You've ooga'd your last booga");
|
||||
|
||||
|
|
|
@ -141,6 +141,8 @@ void os_init(u64 program_memory_size) {
|
|||
#if CONFIGURATION == RELEASE
|
||||
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
|
||||
#endif
|
||||
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
|
@ -625,7 +627,7 @@ File os_file_open_s(string path, Os_Io_Open_Flags flags) {
|
|||
|
||||
u16 *wide = temp_win32_fixed_utf8_to_null_terminated_wide(path);
|
||||
|
||||
return CreateFileW(wide, access, 0, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
return CreateFileW(wide, access, FILE_SHARE_READ, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
}
|
||||
|
||||
void os_file_close(File f) {
|
||||
|
@ -935,31 +937,40 @@ void* os_get_stack_limit() {
|
|||
|
||||
void os_update() {
|
||||
|
||||
UINT dpi = GetDpiForWindow(window._os_handle);
|
||||
float dpi_scale_factor = dpi / 96.0f;
|
||||
|
||||
local_persist Os_Window last_window;
|
||||
|
||||
if (!strings_match(last_window.title, window.title)) {
|
||||
SetWindowText(window._os_handle, temp_convert_to_null_terminated_string(window.title));
|
||||
}
|
||||
|
||||
if (last_window.scaled_width != window.scaled_width || last_window.scaled_height != window.scaled_height) {
|
||||
window.width = window.scaled_width*dpi_scale_factor;
|
||||
window.height = window.scaled_height*dpi_scale_factor;
|
||||
}
|
||||
|
||||
BOOL ok;
|
||||
int screen_height = GetSystemMetrics(SM_CYSCREEN);
|
||||
DWORD style = (DWORD)GetWindowLong(window._os_handle, GWL_STYLE);
|
||||
DWORD ex_style = (DWORD)GetWindowLong(window._os_handle, GWL_EXSTYLE);
|
||||
if (last_window.x != window.x || last_window.y != window.y || last_window.width != window.width || last_window.y != window.y) {
|
||||
RECT update_rect;
|
||||
update_rect.left = window.x;
|
||||
update_rect.right = window.x + window.width;
|
||||
update_rect.bottom = screen_height-(window.y);
|
||||
update_rect.top = screen_height-(window.y+window.height);
|
||||
ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
|
||||
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
|
||||
|
||||
u32 actual_x = update_rect.left;
|
||||
u32 actual_y = update_rect.top;
|
||||
u32 actual_width = update_rect.right-update_rect.left;
|
||||
u32 actual_height = update_rect.bottom-update_rect.top;
|
||||
|
||||
SetWindowPos(window._os_handle, 0, actual_x, actual_y, actual_width, actual_height, 0);
|
||||
if (last_window.x != window.x || last_window.y != window.y || last_window.width != window.width || last_window.height != window.height) {
|
||||
RECT update_rect;
|
||||
update_rect.left = window.x;
|
||||
update_rect.right = window.x + window.width;
|
||||
update_rect.top = window.y;
|
||||
update_rect.bottom = window.y + window.height;
|
||||
|
||||
BOOL ok = AdjustWindowRectEx(&update_rect, style, FALSE, ex_style);
|
||||
assert(ok != 0, "AdjustWindowRectEx failed with error code %lu", GetLastError());
|
||||
|
||||
u32 actual_x = update_rect.left;
|
||||
u32 actual_y = update_rect.top;
|
||||
u32 actual_width = update_rect.right - update_rect.left;
|
||||
u32 actual_height = update_rect.bottom - update_rect.top;
|
||||
|
||||
SetWindowPos(window._os_handle, NULL, actual_x, actual_y, actual_width, actual_height, SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
RECT client_rect;
|
||||
|
@ -983,8 +994,11 @@ void os_update() {
|
|||
|
||||
window.x = (u32)top_left.x;
|
||||
window.y = (u32)(screen_height-bottom_right.y);
|
||||
window.width = (u32)(client_rect.right - client_rect.left);
|
||||
window.height = (u32)(client_rect.bottom - client_rect.top);
|
||||
window.pixel_width = (u32)(client_rect.right - client_rect.left);
|
||||
window.pixel_height = (u32)(client_rect.bottom - client_rect.top);
|
||||
|
||||
window.scaled_width = (u32)((client_rect.right - client_rect.left) * dpi_scale_factor);
|
||||
window.scaled_height = (u32)((client_rect.bottom - client_rect.top) * dpi_scale_factor);
|
||||
|
||||
last_window = window;
|
||||
|
||||
|
@ -1016,8 +1030,8 @@ void os_update() {
|
|||
GetCursorPos(&p);
|
||||
ScreenToClient(window._os_handle, &p);
|
||||
p.y = window.height - p.y;
|
||||
input_frame.mouse_x = (float32)p.x;
|
||||
input_frame.mouse_y = (float32)p.y;
|
||||
input_frame.mouse_x = p.x;
|
||||
input_frame.mouse_y = p.y;
|
||||
|
||||
if (window.should_close) {
|
||||
win32_window_proc(window._os_handle, WM_CLOSE, 0, 0);
|
||||
|
|
|
@ -298,10 +298,12 @@ typedef struct Os_Window {
|
|||
|
||||
// Keep in mind that setting these in runtime is potentially slow!
|
||||
string title;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 x;
|
||||
u32 y;
|
||||
union { s32 width; s32 pixel_width; };
|
||||
union { s32 height; s32 pixel_height; };
|
||||
s32 scaled_width;
|
||||
s32 scaled_height;
|
||||
s32 x;
|
||||
s32 y;
|
||||
Vector4 clear_color;
|
||||
bool enable_vsync;
|
||||
|
||||
|
|
|
@ -36,6 +36,8 @@ void log_heap() {
|
|||
|
||||
void test_allocator(bool do_log_heap) {
|
||||
|
||||
u64 h = get_hash((string*)69);
|
||||
|
||||
Allocator heap = get_heap_allocator();
|
||||
|
||||
// Basic allocation and free
|
||||
|
@ -881,8 +883,8 @@ void test_linmath() {
|
|||
// Test matrix multiplication
|
||||
Matrix4 m5 = m4_scalar(1.0f);
|
||||
m5.m[0][3] = 1.0f; m5.m[1][3] = 2.0f; m5.m[2][3] = 3.0f;
|
||||
Matrix4 result_matrix = m4_multiply(m2, m5);
|
||||
assert(result_matrix.m[0][3] == 2.0f && result_matrix.m[1][3] == 4.0f && result_matrix.m[2][3] == 6.0f, "m4_multiply incorrect");
|
||||
Matrix4 result_matrix = m4_mul(m2, m5);
|
||||
assert(result_matrix.m[0][3] == 2.0f && result_matrix.m[1][3] == 4.0f && result_matrix.m[2][3] == 6.0f, "m4_mul incorrect");
|
||||
|
||||
// Test matrix inverse
|
||||
Matrix4 identity = m4_scalar(1.0f);
|
||||
|
@ -1018,6 +1020,42 @@ void test_linmath() {
|
|||
assert(floats_roughly_match(v3_dot, 38), "Failed: v3_dot_product");
|
||||
assert(floats_roughly_match(v4_dot, 30), "Failed: v4_dot_product");
|
||||
}
|
||||
void test_hash_table() {
|
||||
Hash_Table table = make_hash_table(string, int, get_heap_allocator());
|
||||
|
||||
string key1 = STR("Key string");
|
||||
int value1 = 69;
|
||||
bool newly_added = hash_table_set(&table, key1, value1);
|
||||
assert(newly_added == true, "Failed: Key should be newly added");
|
||||
|
||||
int* found_value = hash_table_find(&table, key1);
|
||||
assert(found_value != NULL, "Failed: Key should exist in hash table");
|
||||
assert(*found_value == 69, "Failed: Value should be 69, got %i", *found_value);
|
||||
|
||||
int new_value1 = 70;
|
||||
newly_added = hash_table_set(&table, key1, new_value1);
|
||||
assert(newly_added == false, "Failed: Key should not be newly added");
|
||||
|
||||
found_value = hash_table_find(&table, key1);
|
||||
assert(found_value != NULL, "Failed: Key should exist in hash table");
|
||||
assert(*found_value == 70, "Failed: Value should be 70, got %i", *found_value);
|
||||
|
||||
bool contains = hash_table_contains(&table, key1);
|
||||
assert(contains == true, "Failed: Hash table should contain key1");
|
||||
|
||||
string key2 = STR("Non-existing key");
|
||||
contains = hash_table_contains(&table, key2);
|
||||
assert(contains == false, "Failed: Hash table should not contain key2");
|
||||
|
||||
hash_table_reset(&table);
|
||||
found_value = hash_table_find(&table, key1);
|
||||
assert(found_value == NULL, "Failed: Hash table should be empty after reset");
|
||||
|
||||
hash_table_destroy(&table);
|
||||
assert(table.entries == NULL, "Failed: Hash table entries should be NULL after destroy");
|
||||
assert(table.count == 0, "Failed: Hash table count should be 0 after destroy");
|
||||
assert(table.capacity_count == 0, "Failed: Hash table capacity count should be 0 after destroy");
|
||||
}
|
||||
void oogabooga_run_tests() {
|
||||
|
||||
|
||||
|
@ -1033,17 +1071,19 @@ void oogabooga_run_tests() {
|
|||
test_strings();
|
||||
print("OK!\n");
|
||||
|
||||
|
||||
//print("Thread bombing allocator... ");
|
||||
//Thread* threads[100];
|
||||
//for (int i = 0; i < 100; i++) {
|
||||
// threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
|
||||
// os_start_thread(threads[i]);
|
||||
//}
|
||||
//for (int i = 0; i < 100; i++) {
|
||||
// os_join_thread(threads[i]);
|
||||
//}
|
||||
//print("OK!\n");
|
||||
|
||||
#if CONFIGURATION != RELEASE
|
||||
print("Thread bombing allocator... ");
|
||||
Thread* threads[100];
|
||||
for (int i = 0; i < 100; i++) {
|
||||
threads[i] = os_make_thread(test_allocator_threaded, get_heap_allocator());
|
||||
os_start_thread(threads[i]);
|
||||
}
|
||||
for (int i = 0; i < 100; i++) {
|
||||
os_join_thread(threads[i]);
|
||||
}
|
||||
print("OK!\n");
|
||||
#endif
|
||||
|
||||
print("Testing file IO... ");
|
||||
test_file_io();
|
||||
|
@ -1057,4 +1097,8 @@ void oogabooga_run_tests() {
|
|||
test_simd();
|
||||
print("OK!\n");
|
||||
|
||||
print("Testing hash table... ");
|
||||
test_hash_table();
|
||||
print("OK!\n");
|
||||
|
||||
}
|
|
@ -40,6 +40,9 @@ typedef signed short s16;
|
|||
typedef unsigned int u32;
|
||||
typedef signed int s32;
|
||||
|
||||
// #Temporary #Incomplete #Memory
|
||||
// This should use the allocator we pass to the gfx font system.
|
||||
// Probably just do a thread local global here
|
||||
void *stbtt_malloc(size_t size) {
|
||||
if (!size) return 0;
|
||||
return alloc(get_heap_allocator(), size);
|
||||
|
|
|
@ -35,4 +35,77 @@ int utf16_to_utf32(const u16 *utf16, u64 length, u32 *utf32) {
|
|||
*utf32 = first;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Yoinked from jai Unicode.jai
|
||||
|
||||
#define UNI_REPLACEMENT_CHAR 0x0000FFFD
|
||||
#define UNI_MAX_UTF32 0x7FFFFFFF
|
||||
#define UNI_MAX_UTF16 0x0010FFFF
|
||||
#define SURROGATES_START 0xD800
|
||||
#define SURROGATES_END 0xDFFF
|
||||
|
||||
typedef struct {
|
||||
u32 utf32;
|
||||
s64 continuation_bytes;
|
||||
bool reached_end;
|
||||
bool error;
|
||||
} Utf8_To_Utf32_Result;
|
||||
|
||||
const u8 trailing_bytes_for_utf8[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
|
||||
};
|
||||
const u8 utf8_inital_byte_mask[] = { 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
|
||||
|
||||
Utf8_To_Utf32_Result utf8_to_utf32(u8 *s, s64 source_length, bool strict) {
|
||||
s64 continuation_bytes = trailing_bytes_for_utf8[s[0]];
|
||||
|
||||
if (continuation_bytes + 1 > source_length) {
|
||||
return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, source_length, true, true};
|
||||
}
|
||||
|
||||
u32 ch = s[0] & utf8_inital_byte_mask[continuation_bytes];
|
||||
|
||||
for (u64 i = 1; i <= continuation_bytes; i++) { // Do nothing if it is 0.
|
||||
ch = ch << 6;
|
||||
if (strict) if ((s[i] & 0xC0) != 0x80) return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, i - 1, true, true};
|
||||
ch |= s[i] & 0x3F;
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
if (ch > UNI_MAX_UTF16 ||
|
||||
(SURROGATES_START <= ch && ch <= SURROGATES_END) ||
|
||||
(ch <= 0x0000007F && continuation_bytes != 0) ||
|
||||
(ch <= 0x000007FF && continuation_bytes != 1) ||
|
||||
(ch <= 0x0000FFFF && continuation_bytes != 2) ||
|
||||
continuation_bytes > 3) {
|
||||
return (Utf8_To_Utf32_Result){UNI_REPLACEMENT_CHAR, continuation_bytes+1, true, true};
|
||||
}
|
||||
}
|
||||
|
||||
if (ch > UNI_MAX_UTF32) {
|
||||
ch = UNI_REPLACEMENT_CHAR;
|
||||
}
|
||||
|
||||
return (Utf8_To_Utf32_Result){ ch, continuation_bytes+1, false, false };
|
||||
}
|
||||
|
||||
// Returns 0 on fail
|
||||
u32 next_utf8(string *s) {
|
||||
Utf8_To_Utf32_Result result = utf8_to_utf32(s->data, s->count, false);
|
||||
|
||||
s->data += result.continuation_bytes;
|
||||
s->count -= result.continuation_bytes;
|
||||
assert(s->count >= 0);
|
||||
|
||||
if (result.error) return 0;
|
||||
|
||||
return result.utf32;
|
||||
}
|
Reference in a new issue