Make string work with the new draw-call setup!
Francesco Pasa 10 months ago
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
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
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