Make string work with the new draw-call setup!

Francesco Pasa 10 months ago

CMakeLists.txt   M +1

131         src/glfw_surface.c src/window.c src/array.c src/drawing.c src/text.c
132         src/atlas.c ${hashmap_SOURCE_DIR}/hashmap.c src/helpers.c
133         src/draw_call.c
134+        src/string.c
135 )
136 target_include_directories(alba PRIVATE src)
137 target_link_libraries(alba PRIVATE glfw wgpu freetype harfbuzz)

examples/shapes.c   M +1 -1

29     const AlbaColor blue = {0, 0.4, 1, 1};
30     alba_draw_rect_aa(window, 350, 100, 500, 150, blue);
31 
32-    const float rect_vertices[] = {350, 300, 500, 350, 450, 400, 300, 350};
33+    const AlbaVector rect_vertices[] = {350, 300, 500, 350, 450, 400, 300, 350};
34     alba_draw_quad(window, rect_vertices, blue);
35 
36     alba_draw_regular_polygon(window, 5, 320, 240, 20, blue);

examples/text.c   M +13 -5

1 #include "alba.h"
2-#include "../src/internal.h"
3 
4 int main()
5 {
 8     options.clear_color.b = 0.02;
 9     AlbaWindow* window = alba_create_window(&options);
10 
11-    alba_draw_text(window, (AlbaVector){100, 100}, (AlbaColor)YELLOW, 0, "7 lm francesco");
12-    // draw_text(window, (AlbaVector){100, 200}, (AlbaColor)WHITE, 0, "ß 🍔");
13-    alba_draw_text(window, (AlbaVector){100, 200}, (AlbaColor)CYAN, 0,
14-              "DESCRIPTION\n       The malloc() function allocates size bytes and returns a pointer to the allocated memory.  The memory is not initialized.  If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().         The free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc().  Otherwise, or if free(ptr) has already been called before, undefined behavior occurs.  If ptr is NULL,        no operation is performed.         The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory.  The memory is set to zero.  If nmemb or size is 0, then calloc() returns either NULL, or a  unique  pointer  value        that can later be successfully passed to free().  If the multiplication of nmemb and size would result in integer overflow, then calloc() returns an error.  By contrast, an integer overflow would not be detected in the following call to malloc(), with        the result that an incorrectly sized block of memory would be allocated:             malloc(nmemb * size);         The realloc() function changes the size of the memory block pointed to by ptr to size bytes.  The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes.  If the new size is larger than the  old        size, the added memory will not be initialized.  If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr).  Unless ptr is NULL, it must        have been returned by an earlier call to malloc(), calloc(), or realloc().  If the area pointed to was moved, a free(ptr) is done.         The reallocarray() function changes the size of the memory block pointed to by ptr to be large enough for an array of nmemb elements, each of which is size bytes.  It is equivalent to the call                 realloc(ptr, nmemb * size);         However, unlike that realloc() call, reallocarray() fails safely in the case where the multiplication would overflow.  If such an overflow occurs, reallocarray() returns NULL, sets errno to ENOMEM, and leaves the original block of memory unchanged.  RETURN VALUE        The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type.  On error, these functions return NULL.  N");
15+    const AlbaString text1 = alba_create_string("7 lm francesco", 0);
16+    alba_draw_text(window, (AlbaVector){100, 100}, (AlbaColor)YELLOW, text1);
17+
18+    // alba_draw_text(window, (AlbaVector){100, 150}, (AlbaColor)WHITE, 0, "ß 🍔");
19+
20+    const AlbaString text2 = alba_create_string(
21+        "DESCRIPTION\n       The malloc() function allocates size bytes and returns a pointer to the allocated memory.  The memory is not initialized.  If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().         The free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc().  Otherwise, or if free(ptr) has already been called before, undefined behavior occurs.  If ptr is NULL,        no operation is performed.         The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory.  The memory is set to zero.  If nmemb or size is 0, then calloc() returns either NULL, or a  unique  pointer  value        that can later be successfully passed to free().  If the multiplication of nmemb and size would result in integer overflow, then calloc() returns an error.  By contrast, an integer overflow would not be detected in the following call to malloc(), with        the result that an incorrectly sized block of memory would be allocated:             malloc(nmemb * size);         The realloc() function changes the size of the memory block pointed to by ptr to size bytes.  The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes.  If the new size is larger than the  old        size, the added memory will not be initialized.  If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr).  Unless ptr is NULL, it must        have been returned by an earlier call to malloc(), calloc(), or realloc().  If the area pointed to was moved, a free(ptr) is done.         The reallocarray() function changes the size of the memory block pointed to by ptr to be large enough for an array of nmemb elements, each of which is size bytes.  It is equivalent to the call                 realloc(ptr, nmemb * size);         However, unlike that realloc() call, reallocarray() fails safely in the case where the multiplication would overflow.  If such an overflow occurs, reallocarray() returns NULL, sets errno to ENOMEM, and leaves the original block of memory unchanged.  RETURN VALUE        The malloc() and calloc() functions return a pointer to the allocated memory, which is suitably aligned for any built-in type.  On error, these functions return NULL.  N",
22+        0
23+    );
24+    alba_draw_text(window, (AlbaVector){100, 200}, (AlbaColor)CYAN, text2);
25 
26     while (!alba_window_should_close(window))
27     {
28         alba_window_render(window);
29     }
30 
31+    alba_string_release(&text1);
32+    alba_string_release(&text2);
33+
34     alba_window_release(window);
35     alba_release();
36 }

include/alba.h   M +10 -2

1 #ifndef ALBA_H
2 #define ALBA_H
3 
4-#include "GLFW/glfw3.h"
5 #include "webgpu.h"
6 #include "ft2build.h"
7 #include FT_FREETYPE_H
21 void alba_array_release(const AlbaArray* array);
22 void alba_array_clear(AlbaArray* array);
23 void alba_array_sort(const AlbaArray* array, uint32_t (*is_before_pivot)(void* pivot, void* elem));
24+void* alba_array_last(const AlbaArray* array);
25 void alba_array_print(const AlbaArray* array, void (*print_element)(void* element));
26 
27+// String
28+typedef AlbaArray AlbaString;
29+
30+AlbaString alba_create_string(const char* text, uint64_t length);
31+char* alba_string_to_char(const AlbaString* string);
32+void alba_string_extend(AlbaString* string, const char* text, uint64_t length);
33+static void alba_string_release(const AlbaString* string) { alba_array_release(string); }
34+
35 // Helper structures
36 typedef struct
37 {
142 void alba_draw_text(
143     AlbaWindow* window,
144     AlbaVector pos, AlbaColor color,
145-    uint64_t length, const char* text
146+    AlbaString string
147 );
148 
149 #endif // ALBA_H

src/array.c   M +5

108         print_element(array->data + i * array->element_size);
109     }
110 }
111+
112+void* alba_array_last(const AlbaArray* array)
113+{
114+    return array->data + array->length - 1;
115+}

src/atlas.c   M +6 -6

20     free(glyph->bitmap.buffer);
21 }
22 
23-AlbaAtlas create_atlas(const uint32_t capacity)
24+Atlas create_atlas(const uint32_t capacity)
25 {
26-    AlbaAtlas atlas = {0};
27+    Atlas atlas = {0};
28     atlas.glyphs = hashmap_new(sizeof(AlbaGlyph), capacity, 0, 0, hash_glyph, compare_glyph, free_glyph, NULL);
29     if (capacity != 0)
30     {
31     return atlas;
32 }
33 
34-void atlas_reserve(AlbaAtlas* atlas, const uint32_t capacity)
35+void atlas_reserve(Atlas* atlas, const uint32_t capacity)
36 {
37     alba_array_reserve(&atlas->glyph_sizes, capacity * 4);
38 }
39 
40-void atlas_append(AlbaAtlas* atlas, const FT_Face face, const uint32_t character)
41+void atlas_append(Atlas* atlas, const FT_Face face, const uint32_t character)
42 {
43     const FT_Long glyph_index = FT_Load_Char(face, character, FT_LOAD_RENDER);
44 
138 }
139 
140 // TODO: mutex?
141-void atlas_generate_image(AlbaAtlas* atlas)
142+void atlas_generate_image(Atlas* atlas)
143 {
144     if (atlas->dirty == 0) return;
145 
238     atlas->dirty = 0;
239 }
240 
241-void atlas_release(const AlbaAtlas* atlas)
242+void atlas_release(const Atlas* atlas)
243 {
244     hashmap_free(atlas->glyphs);
245     alba_array_release(&atlas->glyph_sizes);

src/draw_call.c   M +105 -4

  1 #include "alba.h"
  2 #include "internal.h"
  3 
  4-void create_draw_call(DrawCall* draw_call)
  5+DrawCall* window_create_draw_call(AlbaWindow* window)
  6 {
  7-    draw_call->new_vertices = alba_create_array(sizeof(AlbaVector), 0);
  8-    draw_call->new_attributes = alba_create_array(sizeof(AlbaAttribute), 0);
  9-    draw_call->new_indices = alba_create_array(sizeof(uint32_t), 0);
 10+    DrawCall draw_call = {0};
 11+    draw_call.buffers_dirty = 1;
 12+    draw_call.bind_group_dirty = 1;
 13+    draw_call.new_vertices = alba_create_array(sizeof(AlbaVector), 0);
 14+    draw_call.new_attributes = alba_create_array(sizeof(AlbaAttribute), 0);
 15+    draw_call.new_indices = alba_create_array(sizeof(uint32_t), 0);
 16+    draw_call.new_texts = alba_create_array(sizeof(Text), 0);
 17+    alba_array_append(&window->draw_calls, &draw_call);
 18+    return alba_array_last(&window->draw_calls);
 19+}
 20+
 21+void window_set_texture(AlbaWindow* window, const WGPUTexture texture)
 22+{
 23+    DrawCall* draw_call = alba_array_last(&window->draw_calls);
 24+    if (draw_call->texture_set)
 25+    {
 26+        draw_call = window_create_draw_call(window);
 27+    }
 28+    draw_call_set_texture(draw_call, texture);
 29+    draw_call->texture_set = 1;
 30+}
 31+
 32+void window_clear(AlbaWindow* window)
 33+{
 34+    alba_array_clear(&window->draw_calls);
 35+}
 36+
 37+void clear_texture_bind_group(const DrawCall* draw_call)
 38+{
 39+    wgpuBindGroupRelease(draw_call->bind_group);
 40+}
 41+
 42+void draw_call_set_texture(DrawCall* draw_call, const WGPUTexture texture)
 43+{
 44+    if (draw_call->view != NULL)
 45+    {
 46+        wgpuTextureViewRelease(draw_call->view);
 47+    }
 48+    if (draw_call->texture != NULL)
 49+    {
 50+        wgpuTextureDestroy(draw_call->texture);
 51+        wgpuTextureRelease(draw_call->texture);
 52+    }
 53+
 54+    draw_call->texture = texture;
 55+    draw_call->view = wgpuTextureCreateView(draw_call->texture, NULL);
 56+    draw_call->bind_group_dirty = 1;
 57+}
 58+
 59+void draw_call_update_bind_group(const AlbaWindow* window, DrawCall* draw_call)
 60+{
 61+    WGPUBindGroupEntry bind_group_entries[2] = {0};
 62+
 63+    // Texture
 64+    if (draw_call->texture == NULL)
 65+    {
 66+        draw_call_set_texture(
 67+            draw_call,
 68+            // 1x1 white texture
 69+            create_texture(
 70+                window,
 71+                WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
 72+                (AlbaVector){1, 1},
 73+                WGPUTextureFormat_RGBA8UnormSrgb, 1,
 74+                &(AlbaColor)WHITE
 75+            )
 76+        );
 77+    }
 78+
 79+    // @binding(1)
 80+    bind_group_entries[0].binding = 0; // @group(1) @binding(0)
 81+    bind_group_entries[0].textureView = draw_call->view;
 82+
 83+    // Sampler
 84+    if (draw_call->sampler == NULL)
 85+    {
 86+        WGPUSamplerDescriptor sampler_options = {0};
 87+        sampler_options.addressModeU = WGPUAddressMode_ClampToEdge;
 88+        sampler_options.addressModeV = WGPUAddressMode_ClampToEdge;
 89+        sampler_options.addressModeW = WGPUAddressMode_ClampToEdge;
 90+        sampler_options.magFilter = WGPUFilterMode_Linear;
 91+        sampler_options.minFilter = WGPUFilterMode_Linear;
 92+        sampler_options.mipmapFilter = WGPUMipmapFilterMode_Linear;
 93+        sampler_options.lodMinClamp = 0;
 94+        sampler_options.lodMaxClamp = 1;
 95+        sampler_options.maxAnisotropy = 1;
 96+        draw_call->sampler = wgpuDeviceCreateSampler(window->device, &sampler_options);
 97+    }
 98+
 99+    // @binding(2)
100+    bind_group_entries[1].binding = 1; // @group(1) @binding(1)
101+    bind_group_entries[1].sampler = draw_call->sampler;
102+
103+    WGPUBindGroupDescriptor binding_group_options = {0};
104+    binding_group_options.layout = window->texture_bind_group_layout;
105+    binding_group_options.entryCount = 2;
106+    binding_group_options.entries = bind_group_entries;
107+
108+    if (draw_call->bind_group != NULL)
109+    {
110+        wgpuBindGroupRelease(draw_call->bind_group);
111+    }
112+    draw_call->bind_group = wgpuDeviceCreateBindGroup(window->device, &binding_group_options);
113 }
114 
115 void draw_call_release(const DrawCall* draw_call)
125     wgpuTextureViewRelease(draw_call->view);
126     wgpuTextureDestroy(draw_call->texture);
127     wgpuTextureRelease(draw_call->texture);
128+    wgpuSamplerRelease(draw_call->sampler);
129     wgpuBindGroupRelease(draw_call->bind_group);
130 }

src/drawing.c   M +11 -10

 8 #define PI 3.14159265358979323846
 9 #define MAX_ERROR 0.25
10 
11-void draw_triangles_indexed_render_pass(
12-    DrawCall* data,
13+void draw_triangles_indexed_draw_call(
14+    DrawCall* draw_call,
15     const uint32_t num_vertices,
16     const AlbaVector* vertices,
17     const AlbaAttribute* attributes,
17     uint32_t* indices
18 )
19 {
20-    const uint32_t offset = data->new_vertices.length;
21+    const uint32_t offset = draw_call->new_vertices.length;
22 
23-    alba_array_extend(&data->new_vertices, num_vertices, vertices);
24-    alba_array_extend(&data->new_attributes, num_vertices, attributes);
25+    alba_array_extend(&draw_call->new_vertices, num_vertices, vertices);
26+    alba_array_extend(&draw_call->new_attributes, num_vertices, attributes);
27 
28     if (offset > 0)
29     {
30             indices[i] += offset;
31         }
32     }
33-    alba_array_extend(&data->new_indices, num_indices, indices);
34+    alba_array_extend(&draw_call->new_indices, num_indices, indices);
35 
36-    data->dirty = 1;
37+    draw_call->buffers_dirty = 1;
38 }
39 
40 void alba_draw_triangles_indexed(
44     uint32_t* indices
45 )
46 {
47-    draw_triangles_indexed_render_pass(
48-        &window->layers[0].drawing,
49+    draw_triangles_indexed_draw_call(
50+        alba_array_last(&window->draw_calls),
51         num_vertices, vertices, attributes,
52         num_indices, indices
53     );
272 void alba_draw_rounded_rect_aa(
273     AlbaWindow* window,
274     const float x0, const float y0, const float x1, const float y1,
275-    const float r, const AlbaColor color)
276+    const float r, const AlbaColor color
277+)
278 {
279     if (r == 0)
280     {

src/helpers.c   M +1

1 #include "alba.h"
2+#include "internal.h"
3 
4 #include <stdio.h>
5 

src/internal.h   M +37 -15

 5 
 6 #include <pthread.h>
 7 
 8+#include "GLFW/glfw3.h"
 9 #include "hashmap.h"
10 
11 #define NO_TEXTURE {-1, -1}
13 // Window
14 typedef struct
15 {
16-    // if set, the next frame will update the buffers
17-    int dirty;
18+    AlbaString string;
19+    AlbaVector position;
20+    AlbaColor color;
21+    FT_Face face;
22+} Text;
23+
24+typedef struct
25+{
26+    int buffers_dirty;
27     AlbaArray new_vertices;
28     AlbaArray new_attributes;
29     AlbaArray new_indices;
30     WGPUBuffer vertices;
31     WGPUBuffer attributes;
32     WGPUBuffer indices;
33+
34+    int text_dirty;
35+    AlbaArray new_texts;
36+
37+    int bind_group_dirty;
38+    int texture_set;
39     WGPUTexture texture;
40     WGPUTextureView view;
41+    WGPUSampler sampler;
42     WGPUBindGroup bind_group;
43 } DrawCall;
44 
45-void create_draw_call();
46+void draw_call_generate_text(DrawCall* draw_call);
47+void draw_call_set_texture(DrawCall* draw_call, WGPUTexture texture);
48+void draw_call_update_bind_group(const AlbaWindow* window, DrawCall* draw_call);
49 void draw_call_release(const DrawCall* draw_call);
50 
51 typedef struct AlbaWindowImpl
61     WGPURenderPipeline pipeline;
62     WGPUBuffer uniforms;
63     WGPUBindGroup uniforms_bind_group;
64+    WGPUBindGroupLayout texture_bind_group_layout;
65     AlbaArray draw_calls;
66 } AlbaWindow;
67 
68+DrawCall* window_create_draw_call(AlbaWindow* window);
69+void window_set_texture(AlbaWindow* window, WGPUTexture texture);
70+void window_clear(AlbaWindow* window);
71+
72 // Atlas
73 typedef struct
74 {
 87     AlbaArray glyph_sizes; // in w, h order
 88     AlbaVector size;
 89     uint8_t* image;
 90-} AlbaAtlas;
 91+} Atlas;
 92 
 93-AlbaAtlas create_atlas(uint32_t capacity);
 94-void atlas_reserve(AlbaAtlas* atlas, uint32_t capacity);
 95-void atlas_append(AlbaAtlas* atlas, FT_Face face, uint32_t character);
 96-void atlas_generate_image(AlbaAtlas* atlas);
 97-void atlas_release(const AlbaAtlas* atlas);
 98+Atlas create_atlas(uint32_t capacity);
 99+void atlas_reserve(Atlas* atlas, uint32_t capacity);
100+void atlas_append(Atlas* atlas, FT_Face face, uint32_t character);
101+void atlas_generate_image(Atlas* atlas);
102+void atlas_release(const Atlas* atlas);
103 
104 // Helpers
105 WGPUSurface get_window_surface(WGPUInstance instance, AlbaWindow* window);
118 void release_texture(WGPUTexture texture);
119 
120 // Global variables
121-static int glfw_initialized = 0;
122-static pthread_mutex_t glfw_mutex = PTHREAD_MUTEX_INITIALIZER;
123+extern int glfw_initialized;
124+extern pthread_mutex_t glfw_mutex;
125 
126-static FT_Library freetype = NULL;
127-static FT_Face roboto = NULL;
128-static AlbaAtlas atlas;
129-static pthread_mutex_t freetype_mutex = PTHREAD_MUTEX_INITIALIZER;
130+extern FT_Library freetype;
131+extern FT_Face roboto;
132+extern Atlas atlas;
133+extern pthread_mutex_t freetype_mutex;
134 
135 #endif // INTERNAL_H

src/shaders.wgsl   M +4 -2

 1+// uniforms
 2 @group(0) @binding(0) var<uniform> scale: vec2f;
 3-@group(0) @binding(1) var texture: texture_2d<f32>;
 4-@group(0) @binding(2) var texture_sampler: sampler;
 5+// draw call
 6+@group(1) @binding(0) var texture: texture_2d<f32>;
 7+@group(1) @binding(1) var texture_sampler: sampler;
 8 
 9 struct VertexInput {
10     @location(0) position: vec2f,

src/string.c   A +28

 1+#include "alba.h"
 2+
 3+#include <string.h>
 4+
 5+AlbaString alba_create_string(const char* text, uint64_t length)
 6+{
 7+    if (length == 0)
 8+    {
 9+        length = strlen(text);
10+    }
11+    AlbaArray array = alba_create_array(sizeof(char), length);
12+    alba_array_extend(&array, length, text);
13+    return array;
14+}
15+
16+char* alba_string_to_char(const AlbaString* string)
17+{
18+    return string->data;
19+}
20+
21+void alba_string_extend(AlbaString* string, const char* text, uint64_t length)
22+{
23+    if (length == 0)
24+    {
25+        length = strlen(text);
26+    }
27+    alba_array_extend(string, length, text);
28+}

src/text.c   M +45 -38

 6 extern char _binary_Roboto_Regular_ttf_start[];
 7 extern char _binary_Roboto_Regular_ttf_end[];
 8 
 9+FT_Library freetype = NULL;
10+FT_Face roboto = NULL;
11+Atlas atlas;
12+pthread_mutex_t freetype_mutex = PTHREAD_MUTEX_INITIALIZER;
13+
14 void ensure_freetype_initialized()
15 {
16     // already initialized
46     pthread_mutex_unlock(&freetype_mutex);
47 }
48 
49-void atlas_add_chars(AlbaAtlas* atlas, uint64_t length, const char* text)
50+void atlas_add_chars(Atlas* atlas, const AlbaString string)
51 {
52-    for (uint64_t i = 0; i < length; i++)
53+    const char* text = alba_string_to_char(&string);
54+    for (uint64_t i = 0; i < string.length; i++)
55     {
56         atlas_append(atlas, roboto, text[i]); // TODO: unicode
57     }
61 }
62 
63 void atlas_generate_text_geometry(
64-    const AlbaAtlas* atlas,
65+    const Atlas* atlas,
66     const AlbaVector pos, const AlbaColor color,
67-    const uint64_t length, const char* text,
68+    const AlbaString string,
69     AlbaArray* vertices, AlbaArray* attributes, AlbaArray* indices
70 )
71 {
72+    alba_array_reserve(vertices, vertices->length + string.length * 4);
73+    alba_array_reserve(attributes, attributes->length + string.length * 4);
74+    alba_array_reserve(indices, indices->length + string.length * 6);
75+
76+    const char* text = alba_string_to_char(&string);
77     AlbaVector current = pos;
78-    for (uint64_t i = 0; i < length; i++)
79+    for (uint64_t i = 0; i < string.length; i++)
80     {
81         // TODO: mutex?
82         const FT_Long character = text[i]; // TODO: unicode
123     }
124 }
125 
126+void draw_call_generate_text(DrawCall* draw_call)
127+{
128+    for (uint64_t i = 0; i < draw_call->new_texts.length; i++)
129+    {
130+        const Text* text = draw_call->new_texts.data + i * draw_call->new_texts.element_size;
131+        atlas_generate_text_geometry(
132+            &atlas,
133+            text->position, text->color, text->string,
134+            &draw_call->new_vertices, &draw_call->new_attributes, &draw_call->new_indices
135+        );
136+    }
137+}
138+
139 void alba_draw_text(
140     AlbaWindow* window,
141     const AlbaVector pos, const AlbaColor color,
142-    uint64_t length, const char* text
143+    const AlbaString string
144 )
145 {
146     ensure_freetype_initialized();
147 
148-    if (length == 0)
149-    {
150-        length = strlen(text);
151-    }
152-
153-
154     pthread_mutex_lock(&freetype_mutex);
155-    atlas_add_chars(&atlas, length, text);
156-    atlas_generate_image(&atlas);
157+    atlas_add_chars(&atlas, string);
158     pthread_mutex_unlock(&freetype_mutex);
159 
160-    // TODO: do this like buffers lazily
161-    window->layers[0].text.texture = create_texture(
162-        window,
163-        WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
164-        atlas.size,
165-        WGPUTextureFormat_BGRA8UnormSrgb, 1, // TODO: do not assume format
166-        atlas.image
167-    );
168-    window->layers[0].text.dirty = 1;
169-
170-    AlbaArray vertices = alba_create_array(sizeof(AlbaVector), length * 4);
171-    AlbaArray attributes = alba_create_array(sizeof(AlbaAttribute), length * 4);
172-    AlbaArray indices = alba_create_array(sizeof(uint32_t), length * 6);
173-
174-    atlas_generate_text_geometry(
175-        &atlas,
176-        pos, color, length, text,
177-        &vertices, &attributes, &indices
178-    );
179+    const Text text_obj = {
180+        .string = string,
181+        .position = pos,
182+        .color = color,
183+        .face = roboto,
184+    };
185 
186-    draw_triangles_indexed_render_pass(
187-        &window->layers[0].text,
188-        length * 4, vertices.data, attributes.data,
189-        length * 6, indices.data
190-    );
191+    DrawCall* draw_call = alba_array_last(&window->draw_calls);
192+    if (draw_call->texture_set)
193+    {
194+        draw_call = window_create_draw_call(window);
195+    }
196+    alba_array_append(&draw_call->new_texts, &text_obj);
197+    draw_call->text_dirty = 1;
198 }
199+

src/window.c   M +95 -87

11 
12 extern char _binary_shaders_wgsl_start[];
13 
14+int glfw_initialized = 0;
15+pthread_mutex_t glfw_mutex = PTHREAD_MUTEX_INITIALIZER;
16+
17 void on_receive_adapter(
18     const WGPURequestAdapterStatus status,
19     const WGPUAdapter adapter,
62 }
63 
64 
65-// TODO: reorganize code
66-WGPUPipelineLayout configure_bind_group_layout(const AlbaWindow* window)
67+void create_uniform_bind_group(AlbaWindow* window, const WGPUBindGroupLayout bind_group_layout)
68+{
69+    const float uniforms_default_value[2] = {1, 1};
70+    window->uniforms = create_buffer(
71+        window,
72+        sizeof(uniforms_default_value), uniforms_default_value,
73+        WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform
74+    );
75+
76+    WGPUBindGroupEntry bind_group_entries[1] = {0};
77+    bind_group_entries[0].binding = 0; // @group(0) @binding(0)
78+    bind_group_entries[0].buffer = window->uniforms;
79+    bind_group_entries[0].size = sizeof(uniforms_default_value);
80+
81+    WGPUBindGroupDescriptor binding_group_options = {0};
82+    binding_group_options.layout = bind_group_layout;
83+    binding_group_options.entryCount = 1;
84+    binding_group_options.entries = bind_group_entries;
85+
86+    window->uniforms_bind_group = wgpuDeviceCreateBindGroup(window->device, &binding_group_options);
87+}
88+
89+WGPUPipelineLayout configure_bind_group_layout(AlbaWindow* window)
90 {
91     WGPUBindGroupLayout bind_groups[2];
92 
129     const WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(
130         window->device, &pipeline_layout_options);
131 
132+    // Create bind group for uniforms
133+    create_uniform_bind_group(window, bind_groups[0]);
134+
135     wgpuBindGroupLayoutRelease(bind_groups[0]);
136-    wgpuBindGroupLayoutRelease(bind_groups[1]);
137+    window->texture_bind_group_layout = bind_groups[1];
138 
139     return pipeline_layout;
140-
141-    // // --------------------------
142-    // WGPUBindGroupEntry bind_group_entries[3] = {0};
143-    //
144-    // // Uniform (scale)
145-    // WGPUBufferDescriptor uniform_options = {0};
146-    // uniform_options.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
147-    // uniform_options.size = 2 * sizeof(float);
148-    // if (data->uniforms == NULL)
149-    // {
150-    //     data->uniforms = wgpuDeviceCreateBuffer(window->device, &uniform_options);
151-    // }
152-    //
153-    // // @binding(0)
154-    // bind_group_entries[0].binding = 0;
155-    // bind_group_entries[0].buffer = data->uniforms;
156-    // bind_group_entries[0].size = uniform_options.size;
157-    //
158-    // // Texture
159-    // if (data->texture == NULL)
160-    // {
161-    //     // 1x1 transparent texture
162-    //     data->texture = create_texture(
163-    //         window,
164-    //         WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
165-    //         (AlbaVector){1, 1},
166-    //         WGPUTextureFormat_RGBA8UnormSrgb, 1,
167-    //         &(AlbaColor)WHITE
168-    //     );
169-    // }
170-    // // @binding(1)
171-    // bind_group_entries[1].binding = 1;
172-    // bind_group_entries[1].textureView = wgpuTextureCreateView(data->texture, NULL);
173-    //
174-    // // Sampler
175-    // WGPUSamplerDescriptor sampler_options = {0};
176-    // sampler_options.addressModeU = WGPUAddressMode_ClampToEdge;
177-    // sampler_options.addressModeV = WGPUAddressMode_ClampToEdge;
178-    // sampler_options.addressModeW = WGPUAddressMode_ClampToEdge;
179-    // sampler_options.magFilter = WGPUFilterMode_Linear;
180-    // sampler_options.minFilter = WGPUFilterMode_Linear;
181-    // sampler_options.mipmapFilter = WGPUMipmapFilterMode_Linear;
182-    // sampler_options.lodMinClamp = 0;
183-    // sampler_options.lodMaxClamp = 1;
184-    // sampler_options.maxAnisotropy = 1;
185-    //
186-    // // @binding(2)
187-    // bind_group_entries[2].binding = 2;
188-    // // TODO: free previous
189-    // bind_group_entries[2].sampler = wgpuDeviceCreateSampler(window->device, &sampler_options);
190-    //
191-    // WGPUBindGroupDescriptor binding_group_options = {0};
192-    // binding_group_options.layout = bind_group_layout;
193-    // binding_group_options.entryCount = 3;
194-    // binding_group_options.entries = bind_group_entries;
195-    //
196-    // // TODO: free previous
197-    // data->bind_group = wgpuDeviceCreateBindGroup(window->device, &binding_group_options);
198 }
199 
200+
201 void configure_surface(AlbaWindow* window)
202 {
203     int width, height;
327     configure_surface(window);
328 
329     window->draw_calls = alba_create_array(sizeof(DrawCall), 0);
330+    window_create_draw_call(window);
331 
332     return window;
333 }
365     free(window);
366 }
367 
368-uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
369+uint32_t frame_status_is_valid(const WGPUSurfaceTexture frame)
370 {
371     switch (frame.status)
372     {
397     dst->a = src.a;
398 }
399 
400+void ensure_draw_call_updated(
401+    const AlbaWindow* window,
402+    DrawCall* draw_call
403+)
404+{
405+    if (draw_call->text_dirty)
406+    {
407+        pthread_mutex_lock(&freetype_mutex);
408+        atlas_generate_image(&atlas);
409+        pthread_mutex_unlock(&freetype_mutex);
410+        draw_call_set_texture(
411+            draw_call,
412+            create_texture(
413+                window,
414+                WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
415+                atlas.size,
416+                WGPUTextureFormat_BGRA8UnormSrgb, 1, // TODO: do not assume format
417+                atlas.image
418+            )
419+        );
420+        draw_call->texture_set = 1;
421+        draw_call_generate_text(draw_call);
422+        draw_call->buffers_dirty = 1;
423+        draw_call->text_dirty = 0;
424+    }
425+
426+    if (draw_call->buffers_dirty)
427+    {
428+        draw_call->vertices = update_buffer(
429+            window, draw_call->vertices, WGPUBufferUsage_Vertex, draw_call->new_vertices);
430+        draw_call->attributes = update_buffer(
431+            window, draw_call->attributes, WGPUBufferUsage_Vertex, draw_call->new_attributes);
432+        draw_call->indices = update_buffer(
433+            window, draw_call->indices, WGPUBufferUsage_Index, draw_call->new_indices);
434+
435+        draw_call->buffers_dirty = 0;
436+    }
437+
438+    if (draw_call->bind_group_dirty)
439+    {
440+        draw_call_update_bind_group(window, draw_call);
441+        draw_call->bind_group_dirty = 0;
442+    }
443+}
444 
445 void draw_objects(
446     const AlbaWindow* window,
447+    const WGPUCommandEncoder command_encoder,
448     const WGPURenderPassEncoder render_pass
449 )
450 {
451     wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
452+    wgpuRenderPassEncoderSetBindGroup(render_pass, 0, window->uniforms_bind_group, 0, NULL);
453+
454     for (uint64_t i = 0; i < window->draw_calls.length; i++)
455     {
456         DrawCall* draw_call = window->draw_calls.data + i;
457 
458-        wgpuRenderPassEncoderSetBindGroup(render_pass, 0, draw_call->bind_group, 0, NULL);
459-
460-        if (draw_call->dirty)
461-        {
462-            // // TODO: optimize (and only recreate the necessary pipeline)
463-            // configure_pipeline(window);
464-            draw_call->vertices = update_buffer(
465-                window, draw_call->vertices, WGPUBufferUsage_Vertex, draw_call->new_vertices);
466-            draw_call->attributes = update_buffer(
467-                window, draw_call->attributes, WGPUBufferUsage_Vertex, draw_call->new_attributes);
468-            draw_call->indices = update_buffer(
469-                window, draw_call->indices, WGPUBufferUsage_Index, draw_call->new_indices);
470-            draw_call->dirty = 0;
471-        }
472+        ensure_draw_call_updated(window, draw_call);
473+        wgpuRenderPassEncoderSetBindGroup(render_pass, 1, draw_call->bind_group, 0, NULL);
474 
475         const uint32_t vertex_size = wgpuBufferGetSize(draw_call->vertices);
476         const uint32_t attributes_size = wgpuBufferGetSize(draw_call->attributes);
472             wgpuRenderPassEncoderDrawIndexed(
473                 render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
474         }
475-
476-        // Encode render command and send to GPU
477-        const WGPUCommandBuffer command = wgpuCommandEncoderFinish(window->command_encoder, NULL);
478-        wgpuQueueSubmit(window->queue, 1, &command);
479-        wgpuCommandBufferRelease(command);
480     }
481+
482+    wgpuRenderPassEncoderEnd(render_pass);
483+
484+    // Encode render command and send to GPU
485+    const WGPUCommandBuffer command = wgpuCommandEncoderFinish(command_encoder, NULL);
486+    wgpuQueueSubmit(window->queue, 1, &command);
487+    wgpuCommandBufferRelease(command);
488 }
489 
490 void alba_window_render(AlbaWindow* window)
488     // --------------------------------------------
489     WGPUSurfaceTexture frame;
490     wgpuSurfaceGetCurrentTexture(window->surface, &frame);
491-    if (!frame_status_is_valid(window, frame))
492+    if (!frame_status_is_valid(frame))
493     {
494         // Re-configure surface and skip frame
495         if (frame.texture != NULL)
524 
525     // Draw commands
526     // --------------------------------------------
527-    const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
528+    const WGPUCommandEncoder command_encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
529 
530     // Configures rendering
531     WGPURenderPassColorAttachment render_pass_attachment_options = {0};
539     render_pass_options.colorAttachments = &render_pass_attachment_options;
540 
541     const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
542-        encoder, &render_pass_options);
543+        command_encoder, &render_pass_options);
544 
545     // Draw calls
546-    draw_objects(window, render_pass);
547+    draw_objects(window, command_encoder, render_pass);
548 
549     // Update screen & cleanup
550     // --------------------------------------------
551     wgpuSurfacePresent(window->surface);
552 
553-    wgpuRenderPassEncoderEnd(render_pass);
554     wgpuRenderPassEncoderRelease(render_pass);
555+    wgpuCommandEncoderRelease(command_encoder);
556 
557     wgpuTextureViewRelease(render_target_view);
558     release_texture(render_target);
559     wgpuTextureViewRelease(frame_view);
560-    release_texture(frame.texture);
561+    // release_texture(frame.texture);
562 }
563 
564