Minimal version for which text approximately works
Francesco Pasa 10 months ago
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
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
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);