Minimal version for which text approximately works

Francesco Pasa 10 months ago

CMakeLists.txt   M +2 -2

 3 set(CMAKE_C_STANDARD 11)
 4 
 5 # LTO
 6-set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
 7-set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
 8+#set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
 9+#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
10 
11 ## Disable system libraries
12 #set(CMAKE_FIND_USE_PACKAGE_ROOT_PATH FALSE)

examples/shapes.c   M +1 -1

26     uint32_t indices[] = {0, 1, 2, 0, 3, 4};
27     draw_triangles_indexed(window, 5, tri_vertices, attributes, 6, indices);
28 
29-    const AlbaColor blue = {0.0, 0.4, 1.0, 1.0};
30+    const AlbaColor blue = {0, 0.4, 1, 1};
31     draw_rect_aa(window, 350, 100, 500, 150, blue);
32 
33     const float rect_vertices[] = {350, 300, 500, 350, 450, 400, 300, 350};

examples/text.c   M +5 -3

1 #include "alba.h"
2+#include "../src/internal.h"
3 
4 int main()
5 {
 9     options.clear_color.b = 0.02;
10     AlbaWindow* window = create_window(&options);
11 
12-    draw_text(window, 0, 0, 0, "7 lm ß 🍔");
13-    draw_text(window, 100, 100, 0,
14-              "DESCRIPTION        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+    draw_text(window, (AlbaVector){100, 100}, (AlbaColor)YELLOW, 0, "7 lm francesco");
16+    // draw_text(window, (AlbaVector){100, 200}, (AlbaColor)WHITE, 0, "ß 🍔");
17+    draw_text(window, (AlbaVector){100, 200}, (AlbaColor)CYAN, 0,
18+              "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");
19 
20     while (!window_should_close(window))
21     {

include/alba.h   M +37 -2

30     float r, g, b, a;
31 } AlbaColor;
32 
33+#define RED {1, 0, 0, 1}
34+#define GREEN {0, 1, 0, 1}
35+#define BLUE {0, 0, 1, 1}
36+#define BLACK {0, 0, 0, 1}
37+#define WHITE {1, 1, 1, 1}
38+#define TRANSPARENT {0, 0, 0, 1}
39+#define YELLOW {1, 1, 0, 1}
40+#define MAGENTA {1, 0, 1, 1}
41+#define CYAN {0, 1, 1, 1}
42+
43 void color_print(const AlbaColor* color);
44 
45 typedef struct
49 
50 void vector_print(const AlbaVector* vector);
51 
52+inline AlbaVector vector_add(const AlbaVector a, const AlbaVector b)
53+{
54+    return (AlbaVector){a.x + b.x, a.y + b.y};
55+}
56+
57+inline AlbaVector vector_sub(const AlbaVector a, const AlbaVector b)
58+{
59+    return (AlbaVector){a.x - b.x, a.y - b.y};
60+}
61+
62+inline AlbaVector vector_mul(const AlbaVector a, const AlbaVector b)
63+{
64+    return (AlbaVector){a.x * b.x, a.y * b.y};
65+}
66+
67+inline AlbaVector vector_div(const AlbaVector a, const AlbaVector b)
68+{
69+    return (AlbaVector){a.x / b.x, a.y / b.y};
70+}
71+
72 typedef struct
73 {
74     AlbaColor color;
108 {
109     AlbaWindowOptions options;
110     GLFWwindow* glfw_window;
111-    uint32_t width, height;
112+    AlbaVector size;
113+    AlbaVector scale;
114     WGPUInstance instance;
115     WGPUSurface surface;
116     WGPUAdapter adapter;
161 void draw_circle(AlbaWindow* window, float x, float y, float r, AlbaColor color);
162 void draw_rounded_rect_aa(AlbaWindow* window, float x0, float y0, float x1, float y1, float r, AlbaColor color);
163 
164-void draw_text(AlbaWindow* window, const float x, const float y, uint64_t length, const char* text);
165+void draw_text(
166+    AlbaWindow* window,
167+    AlbaVector pos, AlbaColor color,
168+    uint64_t length, const char* text
169+);
170 
171 #endif // ALBA_H

src/atlas.c   M +39 -28

 2 
 3 #include <png.h>
 4 
 5-typedef struct
 6-{
 7-    uint32_t character;
 8-    FT_Long glyph_index;
 9-    FT_Bitmap bitmap;
10-    uint32_t x0, y0, x1, y1;
11-} Glyph;
12-
13 uint64_t hash_glyph(const void* item, const uint64_t seed0, const uint64_t seed1)
14 {
15-    return hashmap_murmur(&((Glyph*)item)->character, sizeof(FT_Long), seed0, seed1);
16+    return hashmap_murmur((FT_Long*)item, sizeof(FT_Long), seed0, seed1);
17 }
18 
19 int compare_glyph(const void* va, const void* vb, void* udata)
14     return a->character - b->character;
15 }
16 
17-void free_glyph(const void* vglyph)
18+void free_glyph(void* vglyph)
19 {
20     const Glyph* glyph = vglyph;
21     free(glyph->bitmap.buffer);
44     Glyph glyph = {
45         .character = character,
46         .glyph_index = glyph_index,
47+        .metrics = face->glyph->metrics,
48         .bitmap = *bitmap,
49     };
50 
52     glyph.bitmap.buffer = malloc(bitmap->rows * bitmap->pitch);
53     memcpy(glyph.bitmap.buffer, bitmap->buffer, bitmap->rows * bitmap->pitch);
54 
55-    const uint32_t hash = hash_glyph(&glyph, 0, 0); // Execute hash only once
56-    if (hashmap_get_with_hash(atlas->glyphs, &glyph, hash) == NULL)
57+    if (hashmap_get(atlas->glyphs, &glyph.character) == NULL)
58     {
59-        hashmap_set_with_hash(atlas->glyphs, &glyph, hash);
60+        hashmap_set(atlas->glyphs, &glyph);
61         atlas->dirty = 1;
62     }
63 }
70 {
71     for (uint32_t i = 0; i < src.rows; i++)
72     {
73-        memcpy(dst + x + (y + i) * dst_row_stride, src.buffer + i * src.pitch, sizeof(uint8_t) * src.pitch);
74+        // memcpy(dst + x + (y + i) * dst_row_stride, src.buffer + i * src.pitch, sizeof(uint8_t) * src.pitch);
75+        for (uint32_t j = 0; j < src.width; j++)
76+        {
77+            const uint32_t src_index = j + i * src.pitch;
78+            const uint32_t dst_index = (x + j + (y + i) * dst_row_stride) * 4;
79+            ((uint8_t*)dst)[dst_index] = ((uint8_t*)src.buffer)[src_index];
80+            ((uint8_t*)dst)[dst_index + 1] = ((uint8_t*)src.buffer)[src_index];
81+            ((uint8_t*)dst)[dst_index + 2] = ((uint8_t*)src.buffer)[src_index];
82+            ((uint8_t*)dst)[dst_index + 3] = ((uint8_t*)src.buffer)[src_index];
83+        }
84     }
85 }
86 
 88     FILE* fp = fopen(output_file_path, "wb");
 89     if (fp == NULL)
 90     {
 91-        printf("error: opening file '%s'.", output_file_path);
 92+        fprintf(stderr, "error: opening file '%s'.", output_file_path);
 93         exit(1);
 94     }
 95 
 96     png_structp write = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
 97     if (write == NULL)
 98     {
 99-        printf("error: creating png write struct.");
100+        fprintf(stderr, "error: creating png write struct.");
101         exit(1);
102     }
103     png_infop info = png_create_info_struct(write);
104     if (info == NULL)
105     {
106         png_destroy_write_struct(&write, (png_infopp)NULL);
107-        printf("error: creating png info struct.");
108+        fprintf(stderr, "error: creating png info struct.");
109         exit(1);
110     }
111 
112     png_init_io(write, fp);
113     png_set_IHDR(
114-        write, info, width, height, depth,
115+        write, info, width, height, 8,
116         depth == 8 ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGBA,
117         PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
118     );
116     const png_bytepp row_pointers = (png_bytepp)png_malloc(write, sizeof(png_bytepp) * height);
117     for (int i = 0; i < height; i++)
118     {
119-        row_pointers[i] = (png_bytep)png_malloc(write, width);
120+        row_pointers[i] = (png_bytep)png_malloc(write, png_get_rowbytes(write, info));
121     }
122     for (int hi = 0; hi < height; hi++)
123     {
124         for (int wi = 0; wi < width; wi++)
125         {
126-            row_pointers[hi][wi] = ((unsigned char*)data)[(wi + width * hi) * depth / 8];
127+            for (int di = 0; di < depth / 8; di++)
128+            {
129+                row_pointers[hi][wi * depth / 8 + di] = ((unsigned char*)data)[(wi + width * hi) * depth / 8 + di];
130+            }
131         }
132     }
133 
196         exit(1);
197     }
198 
199-    atlas->width = image_size;
200-    atlas->height = image_size;
201-    atlas->image = reallocarray(atlas->image, atlas->width * atlas->height, sizeof(uint8_t));
202-    memset(atlas->image, 0, atlas->width * atlas->height * sizeof(uint8_t));
203+    atlas->size = (AlbaVector){image_size, image_size};
204+    atlas->image = reallocarray(atlas->image, atlas->size.x * atlas->size.y * 4, sizeof(uint8_t));
205+    memset(atlas->image, 0, atlas->size.x * atlas->size.y * 4 * sizeof(uint8_t));
206 
207     const uint32_t stride = image_size;
208     uint32_t x = 0;
219         }
220 
221         copy_bitmap(glyph->bitmap, atlas->image, x, y, stride);
222-        glyph->x0 = x;
223-        glyph->y0 = y;
224-        glyph->x1 = x + glyph->bitmap.width;
225-        glyph->y1 = y + glyph->bitmap.rows;
226+        glyph->start = (AlbaVector){
227+            ((float)x) / image_size,
228+            ((float)y) / image_size,
229+        };
230+        glyph->end = (AlbaVector){
231+            ((float)(x + glyph->bitmap.width)) / image_size,
232+            ((float)(y + glyph->bitmap.rows)) / image_size,
233+        };
234 
235         x += glyph->bitmap.width;
236     }
237 
238+    // write_png("test.png", image_size, image_size, 32, atlas->image);
239+
240     dynarray_release(&glyphs);
241+
242+    atlas->dirty = 0;
243 }
244 
245 void atlas_release(const Atlas* atlas)

src/drawing.c   M +41 -38

 1+#include "alba.h"
 2+#include "internal.h"
 3+
 4 #include <math.h>
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 
 8-#include "alba.h"
 9-
10 #define PI 3.14159265358979323846
11 #define MAX_ERROR 0.25
12 
13-AlbaVector NO_TEXTURE = {-1, -1};
14-
15-void draw_triangles_indexed(
16-    AlbaWindow* window,
17+void draw_triangles_indexed_render_pass(
18+    RenderPassData* data,
19     const uint32_t num_vertices,
20     const AlbaVector* vertices,
21     const AlbaAttribute* attributes,
17     uint32_t* indices
18 )
19 {
20-    const uint32_t offset = window->drawing.new_vertices.length / 2;
21+    const uint32_t offset = data->new_vertices.length;
22 
23-    dynarray_extend(&window->drawing.new_vertices, num_vertices * 2, vertices);
24-    dynarray_extend(&window->drawing.new_attributes, num_vertices * 6, attributes);
25+    dynarray_extend(&data->new_vertices, num_vertices, vertices);
26+    dynarray_extend(&data->new_attributes, num_vertices, attributes);
27 
28     if (offset > 0)
29     {
30             indices[i] += offset;
31         }
32     }
33-    dynarray_extend(&window->drawing.new_indices, num_indices, indices);
34+    dynarray_extend(&data->new_indices, num_indices, indices);
35+
36+    data->dirty = 1;
37+}
38 
39-    window->drawing.dirty = 1;
40+void draw_triangles_indexed(
41+    AlbaWindow* window,
42+    const uint32_t num_vertices,
43+    const AlbaVector* vertices,
44+    const AlbaAttribute* attributes,
45+    const uint32_t num_indices,
46+    uint32_t* indices
47+)
48+{
49+    draw_triangles_indexed_render_pass(&window->drawing, num_vertices, vertices, attributes, num_indices, indices);
50 }
51 
52 void draw_triangles(
77 void draw_triangle(AlbaWindow* window, const AlbaVector vertices[3], const AlbaColor color)
78 {
79     const AlbaAttribute attributes[] = {
80-        color.r, color.g, color.b, color.a, NO_TEXTURE,
81-        color.r, color.g, color.b, color.a, NO_TEXTURE,
82-        color.r, color.g, color.b, color.a, NO_TEXTURE,
83+        color, NO_TEXTURE,
84+        color, NO_TEXTURE,
85+        color, NO_TEXTURE,
86     };
87     // Does not try to consider winding order
88     uint32_t indices[] = {0, 1, 2};
 89 void draw_quad(AlbaWindow* window, const AlbaVector vertices[4], const AlbaColor color)
 90 {
 91     const AlbaAttribute attributes[] = {
 92-        color.r, color.g, color.b, color.a, NO_TEXTURE,
 93-        color.r, color.g, color.b, color.a, -1, -1,
 94-        color.r, color.g, color.b, color.a, -1, -1,
 95-        color.r, color.g, color.b, color.a, -1, -1,
 96+        color, NO_TEXTURE,
 97+        color, NO_TEXTURE,
 98+        color, NO_TEXTURE,
 99+        color, NO_TEXTURE,
100     };
101     // Does not try to consider winding order
102     uint32_t indices[] = {0, 1, 2, 0, 2, 3};
107 {
108     if (x0 > x1)
109     {
110-        printf("error: invalid rect coordinates, x0 > x1: %f > %f\n", x0, x1);
111+        fprintf(stderr, "error: invalid rect coordinates, x0 > x1: %f > %f\n", x0, x1);
112         exit(1);
113     }
114     if (y0 > y1)
115     {
116-        printf("error: invalid rect coordinates, y0 > y1: %f > %f\n", y0, y1);
117+        fprintf(stderr, "error: invalid rect coordinates, y0 > y1: %f > %f\n", y0, y1);
118         exit(1);
119     }
120 
171     for (; i < num_sides_per_corner + 1; i++)
172     {
173         dynarray_append(
174-            array,
175-            &(AlbaVector){
176+            array, &(AlbaVector){
177                 x0 + r - r * sin(step_angle * i),
178                 y0 + r - r * cos(step_angle * i)
179-            }
180-        );
181+            });
182     }
183     i -= 1;
184 
185     for (; i < 2 * num_sides_per_corner + 1; i++)
186     {
187         dynarray_append(
188-            array,
189-            &(AlbaVector){
190+            array, &(AlbaVector){
191                 x0 + r - r * sin(step_angle * i),
192                 y1 - r - r * cos(step_angle * i)
193-            }
194-        );
195+            });
196     }
197     i -= 1;
198 
199     for (; i < 3 * num_sides_per_corner + 1; i++)
200     {
201         dynarray_append(
202-            array,
203-            &(AlbaVector){
204+            array, &(AlbaVector){
205                 x1 - r - r * sin(step_angle * i),
206                 y1 - r - r * cos(step_angle * i)
207-            }
208-        );
209+            });
210     }
211     i -= 1;
212 
213     for (; i < 4 * num_sides_per_corner + 1; i++)
214     {
215         dynarray_append(
216-            array,
217-            &(AlbaVector){
218+            array, &(AlbaVector){
219                 x1 - r - r * sin(step_angle * i),
220                 y0 + r - r * cos(step_angle * i)
221-            }
222-        );
223+            });
224     }
225 }
226 
245 {
246     if (num_sides <= 2)
247     {
248-        printf("error: invalid number of sides in call to draw_regular_polygon: %d. "
249-               "A polygon must have at least 3 sides\n", num_sides);
250+        fprintf(stderr, "error: invalid number of sides in call to draw_regular_polygon: %d. "
251+                "A polygon must have at least 3 sides\n", num_sides);
252         exit(1);
253     }
254 

src/dynarray.c   M +11 -1

11     array.element_size = element_size;
12     if (capacity > 0)
13     {
14-        dynarray_reserve(&array, capacity);
15+        array.capacity = capacity;
16+        array.data = reallocarray(array.data, capacity, element_size);
17     }
18     return array;
19 }
 99     quicksort(array->element_size, array->length, array->data, is_before_pivot, temp);
100     free(temp);
101 }
102+
103+void dynarray_print(const DynArray* array, void (*print_element)(void* element))
104+{
105+    printf("len: %lu\n", array->length);
106+    for (uint64_t i = 0; i < array->length; i++)
107+    {
108+        print_element(array->data + i * array->element_size);
109+    }
110+}

src/glfw_surface.c   M +1 -1

85 #endif
86 
87     default:
88-        printf("error: unsuppoerted platform: %d\n", glfwGetPlatform());
89+        fprintf(stderr, "error: unsuppoerted platform: %d\n", glfwGetPlatform());
90         exit(1);
91     }
92 }

src/internal.h   M +25 -4

 5 
 6 #include "hashmap.h"
 7 
 8+#define NO_TEXTURE {-1, -1}
 9+
10 // Atlas
11+typedef struct
12+{
13+    uint32_t character;
14+    FT_Long glyph_index;
15+    FT_Glyph_Metrics metrics;
16+    FT_Bitmap bitmap;
17+    AlbaVector start;
18+    AlbaVector end;
19+} Glyph;
20+
21 typedef struct
22 {
23     struct hashmap* glyphs;
24     int dirty;
25     DynArray glyph_sizes; // in w, h order
26-    uint32_t width;
27-    uint32_t height;
28+    AlbaVector size;
29     uint8_t* image;
30 } Atlas;
31 
36 // Helpers
37 WGPUTexture create_texture(
38     const AlbaWindow* window,
39-    const WGPUTextureUsage usage,
40-    uint64_t width, uint64_t height,
41+    WGPUTextureUsage usage,
42+    AlbaVector size,
43     WGPUTextureFormat format, uint8_t samples,
44     const void* data
45 );
46 
47+// Drawng
48+void draw_triangles_indexed_render_pass(
49+    RenderPassData* data,
50+    uint32_t num_vertices,
51+    const AlbaVector* vertices,
52+    const AlbaAttribute* attributes,
53+    uint32_t num_indices,
54+    uint32_t* indices
55+);
56+
57 #endif // INTERNAL_H

src/shaders.wgsl   M +4 -3

1 @group(0) @binding(0) var<uniform> scale: vec2f;
2 @group(0) @binding(1) var texture: texture_2d<f32>;
3+@group(0) @binding(2) var texture_sampler: sampler;
4 
5 struct VertexInput {
6     @location(0) position: vec2f,
30 
31 @fragment
32 fn fragment_shader(in: VertexOutput) -> @location(0) vec4f {
33-    let texel_coords = vec2i(in.uv * vec2f(textureDimensions(texture)));
34-    let texture_color = textureLoad(texture, texel_coords, 0).rgba;
35+    let texture_color = textureSample(texture, texture_sampler, in.uv).rgba;
36+//    	return texture_color * in.color;
37     if any(in.uv < vec2f(0, 0)) {
38     	return in.color;
39     } else {
40-    	return texture_color;
41+    	return texture_color * in.color;
42     }
43 }

src/text.c   M +101 -23

 1 #include "alba.h"
 2+#include "internal.h"
 3 
 4 #include <pthread.h>
 5 
 6 #include "freetype/ftglyph.h"
 7 
 8-#include "internal.h"
 9-
10 extern char _binary_Roboto_Regular_ttf_start[];
11 extern char _binary_Roboto_Regular_ttf_end[];
12 
21     pthread_mutex_lock(&freetype_mutex);
22     if (FT_Init_FreeType(&freetype))
23     {
24-        printf("fatal error: initializeing FreeType failed");
25+        fprintf(stderr, "error: initializeing FreeType failed");
26         pthread_mutex_unlock(&freetype_mutex);
27         exit(1);
28     }
35     );
36     if (error)
37     {
38-        printf("error: cound not load built in Roboto font");
39+        fprintf(stderr, "error: cound not load built in Roboto font");
40     }
41     error = FT_Set_Pixel_Sizes(roboto, 0 /* width, same as height */, 18);
42     if (error)
43     {
44-        printf("error: cound not set Roboto face size");
45+        fprintf(stderr, "error: cound not set Roboto face size");
46     }
47 
48     atlas = atlas_new(1024);
 48     pthread_mutex_unlock(&freetype_mutex);
 49 }
 50 
 51+void atlas_add_chars(Atlas* atlas, uint64_t length, const char* text)
 52+{
 53+    for (uint64_t i = 0; i < length; i++)
 54+    {
 55+        atlas_append(atlas, roboto, text[i]); // TODO: unicode
 56+    }
 57+}
 58+
 59+uint64_t hash_glyph2(const void* item, const uint64_t seed0, const uint64_t seed1)
 60+{
 61+    return hashmap_murmur((FT_Long*)item, sizeof(FT_Long), seed0, seed1);
 62+}
 63+
 64+void atlas_generate_text_geometry(
 65+    const Atlas* atlas,
 66+    const AlbaVector pos, const AlbaColor color,
 67+    uint64_t length, const char* text,
 68+    DynArray* vertices, DynArray* attributes, DynArray* indices
 69+)
 70+{
 71+    AlbaVector current = pos;
 72+    for (uint64_t i = 0; i < length; i++)
 73+    {
 74+        // TODO: mutex?
 75+        const FT_Long character = text[i]; // TODO: unicode
 76+        const Glyph* glyph = hashmap_get(atlas->glyphs, &character);
 77+        if (glyph == NULL)
 78+        {
 79+            fprintf(stderr, "error: unable to find glyph for character '%s'\n", (char*)&character);
 80+            exit(1);
 81+        }
 82+        const FT_Glyph_Metrics metrics = glyph->metrics;
 83+
 84+        const AlbaVector origin = {
 85+            current.x + metrics.horiBearingX / 64,
 86+            current.y - metrics.horiBearingY / 64,
 87+        };
 88+        const uint32_t vertex_count = vertices->length;
 89+        dynarray_extend(
 90+            vertices, 4, (AlbaVector[]){
 91+                origin,
 92+                origin.x, origin.y + metrics.height / 64,
 93+                origin.x + metrics.width / 64, origin.y + metrics.height / 64,
 94+                origin.x + metrics.width / 64, origin.y,
 95+            });
 96+
 97+        dynarray_extend(
 98+            attributes, 4, (AlbaAttribute[]){
 99+                {color, glyph->start},
100+                {color, (AlbaVector){glyph->start.x, glyph->end.y}},
101+                {color, glyph->end},
102+                {color, (AlbaVector){glyph->end.x, glyph->start.y}},
103+            });
104+
105+        dynarray_extend(
106+            indices, 6, (uint32_t[]){
107+                // first triangle
108+                vertex_count,
109+                vertex_count + 1,
110+                vertex_count + 2,
111+                // second triangle
112+                vertex_count,
113+                vertex_count + 2,
114+                vertex_count + 3
115+            });
116+
117+        current.x += metrics.horiAdvance / 64;
118+        // current_y += metrics.vertAdvance / 64;
119+    }
120+}
121 
122-void draw_text(AlbaWindow* window, const float x, const float y, uint64_t length, const char* text)
123+void draw_text(
124+    AlbaWindow* window,
125+    const AlbaVector pos, const AlbaColor color,
126+    uint64_t length, const char* text
127+)
128 {
129     initialize_freetype();
130 
133     }
134 
135     pthread_mutex_lock(&freetype_mutex);
136-    for (uint64_t i = 0; i < length; i++)
137-    {
138-        atlas_append(&atlas, roboto, text[i]); // TODO: unicode
139-    }
140-    pthread_mutex_unlock(&freetype_mutex);
141-
142-    // FT_Error error = FT_Load_Char(roboto, '7', FT_LOAD_RENDER);
143-    // printf("%d\n", roboto->glyph->bitmap.width);
144-    // error = FT_Load_Char(roboto, 'l', FT_LOAD_RENDER);
145-    // printf("%d\n", roboto->glyph->bitmap.width);
146-    // error = FT_Load_Char(roboto, 'm', FT_LOAD_RENDER);
147-    // printf("%d\n", roboto->glyph->bitmap.width);
148-
149+    atlas_add_chars(&atlas, length, text);
150     atlas_generate_image(&atlas);
151+    pthread_mutex_unlock(&freetype_mutex);
152 
153-    WGPUTexture texture = create_texture(
154+    // TODO: do this like buffers lazily
155+    window->text.texture = create_texture(
156         window,
157         WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
158-        atlas.width, atlas.height,
159-        WGPUTextureFormat_RG8Unorm, 1, // TODO: do not assume format
160+        atlas.size,
161+        WGPUTextureFormat_BGRA8UnormSrgb, 1, // TODO: do not assume format
162         atlas.image
163     );
164+    window->text.dirty = 1;
165+
166+    DynArray vertices = dynarray_new(sizeof(AlbaVector), length * 4);
167+    DynArray attributes = dynarray_new(sizeof(AlbaAttribute), length * 4);
168+    DynArray indices = dynarray_new(sizeof(uint32_t), length * 6);
169 
170-    // window.
171+    atlas_generate_text_geometry(
172+        &atlas,
173+        pos, color, length, text,
174+        &vertices, &attributes, &indices
175+    );
176+
177+    draw_triangles_indexed_render_pass(
178+        &window->text,
179+        length * 4, vertices.data, attributes.data,
180+        length * 6, indices.data
181+    );
182 }

src/window.c   M +72 -41

1 #include "alba.h"
2+#include "internal.h"
3 
4 #include <stdio.h>
5 #include <stdlib.h>
 9 #include "GLFW/glfw3.h"
10 #include "webgpu.h"
11 
12-#include "internal.h"
13-
14 extern char _binary_shaders_wgsl_start[];
15 
16 // forward-declare
29 {
30     if (status != WGPURequestAdapterStatus_Success)
31     {
32-        printf("fatal error: requesting adapter failed: %s", message);
33+        fprintf(stderr, "error: requesting adapter failed: %s", message);
34         exit(1);
35     }
36 
38 
39 void on_device_lost_error(const WGPUDeviceLostReason reason, char const* message, void* userdata)
40 {
41-    printf("fatal device lost error (%d): %s", reason, message);
42+    fprintf(stderr, "error: device lost error (%d): %s", reason, message);
43     exit(1);
44 }
45 
51 {
52     if (status != WGPURequestDeviceStatus_Success)
53     {
54-        printf("fatal error: requesting device failed: %s", message);
55+        fprintf(stderr, "error: requesting device failed: %s", message);
56         exit(1);
57     }
58 
64     {
65         return;
66     }
67-    printf("error (%d): %s", type, message);
68+    fprintf(stderr, "error (%d): %s", type, message);
69 }
70 
71 WGPUTexture create_texture(
72     const AlbaWindow* window,
73     const WGPUTextureUsage usage,
74-    const uint64_t width, const uint64_t height,
75+    const AlbaVector size,
76     const WGPUTextureFormat format, const uint8_t samples,
77     const void* data
78 )
79     texture_options.usage = usage;
80     texture_options.format = format;
81     texture_options.dimension = WGPUTextureDimension_2D;
82-    texture_options.size.width = width;
83-    texture_options.size.height = height;
84+    texture_options.size.width = size.x;
85+    texture_options.size.height = size.y;
86     texture_options.size.depthOrArrayLayers = 1;
87     texture_options.sampleCount = samples;
88     texture_options.mipLevelCount = 1;
 96         destination.aspect = WGPUTextureAspect_All;
 97 
 98         WGPUTextureDataLayout source = {0};
 99-        source.bytesPerRow = width * bpp;
100-        source.rowsPerImage = height;
101+        source.bytesPerRow = size.x * bpp;
102+        source.rowsPerImage = size.y;
103 
104         wgpuQueueWriteTexture(
105             window->queue,
106             &destination, data,
107-            width * height * bpp,
108+            size.x * size.y * bpp,
109             &source,
110             &texture_options.size
111         );
111     return texture;
112 }
113 
114-WGPUPipelineLayout configure_resources(AlbaWindow* window, RenderPassData* data)
115+// TODO: reorganize code
116+WGPUPipelineLayout configure_resources(const AlbaWindow* window, RenderPassData* data)
117 {
118-    WGPUBindGroupLayoutEntry bind_group_layout_entries[2] = {0};
119+    WGPUBindGroupLayoutEntry bind_group_layout_entries[3] = {0};
120 
121     // Uniform (scale)
122-    int entries = 1;
123-    WGPUBufferDescriptor uniform_options = {0};
124-    uniform_options.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
125-    uniform_options.size = 2 * sizeof(float);
126-    data->uniforms = wgpuDeviceCreateBuffer(window->device, &uniform_options);
127-
128     // @binding(0)
129     bind_group_layout_entries[0].binding = 0;
130     bind_group_layout_entries[0].visibility = WGPUShaderStage_Vertex;
129     bind_group_layout_entries[1].texture.sampleType = WGPUTextureSampleType_Float;
130     bind_group_layout_entries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
131 
132+    // Sampler
133+    // @binding(2)
134+    bind_group_layout_entries[2].binding = 2;
135+    bind_group_layout_entries[2].visibility = WGPUShaderStage_Fragment;
136+    bind_group_layout_entries[2].sampler.type = WGPUSamplerBindingType_Filtering;
137+
138     WGPUBindGroupLayoutDescriptor bind_group_layout_options = {0};
139-    bind_group_layout_options.entryCount = 2;
140+    bind_group_layout_options.entryCount = 3;
141     bind_group_layout_options.entries = bind_group_layout_entries;
142     const WGPUBindGroupLayout bind_group_layout = wgpuDeviceCreateBindGroupLayout(
143         window->device, &bind_group_layout_options);
144 
145-    WGPUBindGroupEntry bind_group_entries[2] = {0};
146+    // --------------------------
147+    WGPUBindGroupEntry bind_group_entries[3] = {0};
148 
149     // Uniform (scale)
150+    WGPUBufferDescriptor uniform_options = {0};
151+    uniform_options.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
152+    uniform_options.size = 2 * sizeof(float);
153+    if (data->uniforms == NULL)
154+    {
155+        data->uniforms = wgpuDeviceCreateBuffer(window->device, &uniform_options);
156+    }
157+
158     // @binding(0)
159     bind_group_entries[0].binding = 0;
160     bind_group_entries[0].buffer = data->uniforms;
161     bind_group_entries[0].size = uniform_options.size;
162 
163     // Texture
164-    // @binding(1)
165-    bind_group_entries[1].binding = 1;
166     if (data->texture == NULL)
167     {
168         // 1x1 transparent texture
169         data->texture = create_texture(
170             window,
171             WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst,
172-            1, 1,
173+            (AlbaVector){1, 1},
174             WGPUTextureFormat_RGBA8UnormSrgb, 1,
175-            (uint8_t[]){0, 0, 0, 0}
176+            &(AlbaColor)TRANSPARENT
177         );
178     }
179+    // @binding(1)
180+    bind_group_entries[1].binding = 1;
181     bind_group_entries[1].textureView = wgpuTextureCreateView(data->texture, NULL);
182 
183+    // Sampler
184+    WGPUSamplerDescriptor sampler_options = {0};
185+    sampler_options.addressModeU = WGPUAddressMode_ClampToEdge;
186+    sampler_options.addressModeV = WGPUAddressMode_ClampToEdge;
187+    sampler_options.addressModeW = WGPUAddressMode_ClampToEdge;
188+    sampler_options.magFilter = WGPUFilterMode_Linear;
189+    sampler_options.minFilter = WGPUFilterMode_Linear;
190+    sampler_options.mipmapFilter = WGPUMipmapFilterMode_Linear;
191+    sampler_options.lodMinClamp = 0;
192+    sampler_options.lodMaxClamp = 1;
193+    sampler_options.maxAnisotropy = 1;
194+
195+    // @binding(2)
196+    bind_group_entries[2].binding = 2;
197+    // TODO: free previous
198+    bind_group_entries[2].sampler = wgpuDeviceCreateSampler(window->device, &sampler_options);
199+
200     WGPUBindGroupDescriptor binding_group_options = {0};
201     binding_group_options.layout = bind_group_layout;
202-    binding_group_options.entryCount = 2;
203+    binding_group_options.entryCount = 3;
204     binding_group_options.entries = bind_group_entries;
205 
206+    // TODO: free previous
207     data->bind_group = wgpuDeviceCreateBindGroup(window->device, &binding_group_options);
208 
209     // A pipeline can have multiple bind groups
210     WGPUPipelineLayoutDescriptor pipeline_layout_options = {0};
211     pipeline_layout_options.bindGroupLayoutCount = 1;
212     pipeline_layout_options.bindGroupLayouts = &bind_group_layout;
213+    // TODO: free previous
214     const WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(
215         window->device, &pipeline_layout_options);
216 
221 
222     if (width == 0 || height == 0) return;
223 
224-    window->width = width;
225-    window->height = height;
226+    window->size = (AlbaVector){width, height};
227+    window->scale = (AlbaVector){width / (2 * x_scale), height / (2 * y_scale)};
228 
229     // Update uniforms
230-    const float scale[2] = {width / (2. * x_scale), height / (2. * y_scale)};
231-    wgpuQueueWriteBuffer(window->queue, window->drawing.uniforms, 0, &scale, 2 * sizeof(uint32_t));
232+    wgpuQueueWriteBuffer(window->queue, window->drawing.uniforms, 0, &window->scale, sizeof(AlbaVector));
233+    wgpuQueueWriteBuffer(window->queue, window->text.uniforms, 0, &window->scale, sizeof(AlbaVector));
234 
235     const WGPUTextureFormat format = wgpuSurfaceGetPreferredFormat(window->surface, window->adapter);
236 
335 
336 void init_render_pass_data(RenderPassData* data)
337 {
338-    data->new_vertices = dynarray_new(sizeof(float), 0);
339-    data->new_attributes = dynarray_new(sizeof(float), 0);
340+    data->new_vertices = dynarray_new(sizeof(AlbaVector), 0);
341+    data->new_attributes = dynarray_new(sizeof(AlbaAttribute), 0);
342     data->new_indices = dynarray_new(sizeof(uint32_t), 0);
343 }
344 
347     {
348         if (!glfwInit())
349         {
350-            printf("fatal error: initializeing GLFW failed");
351+            fprintf(stderr, "error: initializeing GLFW failed");
352             pthread_mutex_unlock(&glfw_mutex);
353             exit(1);
354         }
429 
430 void window_release(AlbaWindow* window)
431 {
432+    // TODO: destroy render pass
433     wgpuBufferDestroy(window->drawing.uniforms);
434     wgpuBufferRelease(window->drawing.uniforms);
435 
472     case WGPUSurfaceGetCurrentTextureStatus_Lost:
473         return 0;
474     case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
475-        printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
476-               frame.status);
477+        fprintf(stderr, "error: frame allocation failed due to insufficient memory (%d)\n",
478+                frame.status);
479     case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
480-        printf("fatal error: device lost (%d)\n", frame.status);
481+        fprintf(stderr, "error: device lost (%d)\n", frame.status);
482     case WGPUSurfaceGetCurrentTextureStatus_Force32:
483-        printf("fatal error: force 32 error (%d)\n", frame.status);
484+        fprintf(stderr, "error: force 32 error (%d)\n", frame.status);
485         exit(1);
486     }
487 
524 }
525 
526 void render_pass(
527-    const AlbaWindow* window,
528+    AlbaWindow* window,
529     const WGPUTextureView frame,
530     const WGPUTextureView render_target,
531-    int clear,
532+    const int clear,
533     RenderPassData* data
534 )
535 {
536     if (data->dirty)
537     {
538+        // TODO: optimize (and only recreate the necessary pipeline)
539+        configure_pipeline(window);
540         data->vertices = update_buffer(window, data->vertices, WGPUBufferUsage_Vertex, data->new_vertices);
541         data->attributes = update_buffer(window, data->attributes, WGPUBufferUsage_Vertex, data->new_attributes);
542         data->indices = update_buffer(window, data->indices, WGPUBufferUsage_Index, data->new_indices);
611     const WGPUTexture render_target = create_texture(
612         window,
613         WGPUTextureUsage_RenderAttachment,
614-        window->width, window->height,
615+        window->size,
616         WGPUTextureFormat_BGRA8UnormSrgb, 4, // TODO: do not assume format
617         NULL
618     );