src/window.c
1#include "alba.h"
2
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include "GLFW/glfw3.h"
8#include "webgpu.h"
9#include "../cmake-build-debug/_deps/glfw-build/src/fractional-scale-v1-client-protocol.h"
10
11extern char _binary_shaders_wgsl_start[];
12
13// forward-declare
14WGPUSurface get_window_surface(WGPUInstance instance, AlbaWindow* window);
15
16void on_receive_adapter(
17 const WGPURequestAdapterStatus status,
18 const WGPUAdapter adapter,
19 char const* message,
20 void* data
21)
22{
23 if (status != WGPURequestAdapterStatus_Success)
24 {
25 printf("fatal error: requesting adapter failed: %s", message);
26 exit(1);
27 }
28
29 *((WGPUAdapter*)data) = adapter;
30}
31
32void on_device_lost_error(const WGPUDeviceLostReason reason, char const* message, void* userdata)
33{
34 printf("fatal device lost error (%d): %s", reason, message);
35 exit(1);
36}
37
38void on_receive_device(
39 const WGPURequestDeviceStatus status,
40 const WGPUDevice device,
41 char const* message,
42 void* data
43)
44{
45 if (status != WGPURequestDeviceStatus_Success)
46 {
47 printf("fatal error: requesting device failed: %s", message);
48 exit(1);
49 }
50
51 *((WGPUDevice*)data) = device;
52}
53
54void on_error(const WGPUErrorType type, char const* message, void* data)
55{
56 if (type == WGPUErrorType_NoError)
57 {
58 return;
59 }
60 printf("error (%d): %s", type, message);
61}
62
63WGPUPipelineLayout configure_uniforms(AlbaWindow* window)
64{
65 WGPUBufferDescriptor uniform_options = {0};
66 uniform_options.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
67 uniform_options.size = 2 * sizeof(float);
68 window->uniforms = wgpuDeviceCreateBuffer(window->device, &uniform_options);
69
70 // @binding(0)
71 WGPUBindGroupLayoutEntry bind_group_layout_entry = {0};
72 bind_group_layout_entry.binding = 0;
73 bind_group_layout_entry.visibility = WGPUShaderStage_Vertex;
74 bind_group_layout_entry.buffer.type = WGPUBufferBindingType_Uniform;
75
76 WGPUBindGroupLayoutDescriptor bind_group_layout_options = {0};
77 bind_group_layout_options.entryCount = 1;
78 bind_group_layout_options.entries = &bind_group_layout_entry;
79 const WGPUBindGroupLayout bind_group_layout = wgpuDeviceCreateBindGroupLayout(
80 window->device, &bind_group_layout_options);
81
82 // @group(0)
83 WGPUBindGroupEntry bind_group_entry = {0};
84 bind_group_entry.binding = 0;
85 bind_group_entry.buffer = window->uniforms;
86 bind_group_entry.size = uniform_options.size;
87
88 WGPUBindGroupDescriptor binding_group_options = {0};
89 binding_group_options.layout = bind_group_layout;
90 binding_group_options.entryCount = 1;
91 binding_group_options.entries = &bind_group_entry;
92
93 window->bind_group = wgpuDeviceCreateBindGroup(window->device, &binding_group_options);
94
95 // A pipeline can have multiple bind groups
96 WGPUPipelineLayoutDescriptor pipeline_layout_options = {0};
97 pipeline_layout_options.bindGroupLayoutCount = 1;
98 pipeline_layout_options.bindGroupLayouts = &bind_group_layout;
99 const WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(
100 window->device, &pipeline_layout_options);
101
102 wgpuBindGroupLayoutRelease(bind_group_layout);
103
104 return pipeline_layout;
105}
106
107void configure_surface(AlbaWindow* window)
108{
109 int width, height;
110 float x_scale, y_scale;
111 glfwGetWindowSize(window->glfw_window, &width, &height);
112 glfwGetWindowContentScale(window->glfw_window, &x_scale, &y_scale);
113
114 if (width == 0 || height == 0) return;
115
116 window->width = width;
117 window->height = height;
118
119 // Update uniforms
120 const float scale[2] = {width / (2. * x_scale), height / (2. * y_scale)};
121 wgpuQueueWriteBuffer(window->queue, window->uniforms, 0, &scale, 2 * sizeof(uint32_t));
122
123 const WGPUTextureFormat format = wgpuSurfaceGetPreferredFormat(window->surface, window->adapter);
124
125 WGPUSurfaceConfiguration surface_options = {0};
126 surface_options.device = window->device;
127 surface_options.format = format;
128 surface_options.usage = WGPUTextureUsage_RenderAttachment;
129 surface_options.presentMode = WGPUPresentMode_Fifo;
130 surface_options.width = width;
131 surface_options.height = height;
132 wgpuSurfaceConfigure(window->surface, &surface_options);
133}
134
135
136void configure_pipeline(AlbaWindow* window)
137{
138 WGPUShaderModuleWGSLDescriptor shader_options = {0};
139 shader_options.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
140 shader_options.code = _binary_shaders_wgsl_start;
141
142 WGPUShaderModuleDescriptor shader_loader_options = {0};
143 shader_loader_options.nextInChain = (WGPUChainedStruct*)&shader_options;
144
145 window->shaders = wgpuDeviceCreateShaderModule(window->device, &shader_loader_options);
146
147 // Configure render pipeline
148 WGPURenderPipelineDescriptor pipleine_options = {0};
149 // Rendering settings
150 pipleine_options.primitive.topology = WGPUPrimitiveTopology_TriangleList;
151 pipleine_options.primitive.frontFace = WGPUFrontFace_CCW; // counter clockwise
152 pipleine_options.multisample.count = 4;
153 pipleine_options.multisample.mask = 0xFFFFFFFF;
154
155 // Vertex shader
156 pipleine_options.vertex.module = window->shaders;
157 pipleine_options.vertex.entryPoint = "vertex_shader";
158 pipleine_options.vertex.bufferCount = 2;
159 pipleine_options.vertex.buffers = (WGPUVertexBufferLayout []){
160 // Position
161 {
162 .arrayStride = 2 * sizeof(float),
163 .stepMode = WGPUVertexStepMode_Vertex,
164 .attributeCount = 1,
165 .attributes = &(WGPUVertexAttribute){
166 .format = WGPUVertexFormat_Float32x2,
167 .offset = 0,
168 .shaderLocation = 0,
169 },
170 },
171 // Color
172 {
173 .arrayStride = 4 * sizeof(float),
174 .stepMode = WGPUVertexStepMode_Vertex,
175 .attributeCount = 1,
176 .attributes = &(WGPUVertexAttribute){
177 .format = WGPUVertexFormat_Float32x4,
178 .offset = 0,
179 .shaderLocation = 1,
180 },
181 },
182 };
183
184 // Fragment shader
185 const WGPUBlendState blend_state = {
186 .color = {
187 .operation = WGPUBlendOperation_Add,
188 .srcFactor = WGPUBlendFactor_SrcAlpha,
189 .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha,
190 },
191 .alpha = {
192 .operation = WGPUBlendOperation_Add,
193 .srcFactor = WGPUBlendFactor_Zero,
194 .dstFactor = WGPUBlendFactor_One,
195 }
196 };
197 WGPUColorTargetState color_state = {0};
198 color_state.format = WGPUTextureFormat_BGRA8UnormSrgb; // TODO: avoid hardcoding
199 color_state.blend = &blend_state;
200 color_state.writeMask = WGPUColorWriteMask_All;
201 WGPUFragmentState fragment_state = {0};
202 fragment_state.module = window->shaders;
203 fragment_state.entryPoint = "fragment_shader";
204 fragment_state.targetCount = 1;
205 fragment_state.targets = &color_state;
206 pipleine_options.fragment = &fragment_state;
207
208 // Uniform buffer
209 pipleine_options.layout = configure_uniforms(window);
210
211 window->pipeline = wgpuDeviceCreateRenderPipeline(window->device, &pipleine_options);
212
213 wgpuPipelineLayoutRelease(pipleine_options.layout);
214}
215
216AlbaWindow* create_window(const AlbaWindowOptions* options)
217{
218 const AlbaWindowOptions default_options = {0};
219 if (options == NULL)
220 {
221 options = &default_options;
222 }
223
224 AlbaWindow* window = malloc(sizeof(AlbaWindow));
225 memset(window, 0, sizeof(AlbaWindow));
226 window->options = *options;
227
228 if (!glfwInit())
229 {
230 printf("fatal error: initializeing GLFW failed");
231 exit(1);
232 }
233
234 // GLFW window
235 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
236 window->glfw_window = glfwCreateWindow(
237 options->initial_width == 0 ? 800 : options->initial_width,
238 options->initial_height == 0 ? 600 : options->initial_height,
239 options->title == NULL ? "Alba" : options->title,
240 NULL,
241 NULL
242 );
243
244 // Instance & surface initialization
245 window->instance = wgpuCreateInstance(NULL);
246 window->surface = get_window_surface(window->instance, window);
247
248 // Adapter
249 WGPURequestAdapterOptions adapter_options = {0};
250 adapter_options.compatibleSurface = window->surface;
251 wgpuInstanceRequestAdapter(
252 window->instance,
253 &adapter_options,
254 on_receive_adapter,
255 &window->adapter
256 );
257
258 // Device
259 WGPUDeviceDescriptor device_options = {0};
260 device_options.deviceLostCallback = on_device_lost_error;
261 wgpuAdapterRequestDevice(
262 window->adapter,
263 &device_options,
264 on_receive_device,
265 &window->device
266 );
267
268 // Errors
269 wgpuDeviceSetUncapturedErrorCallback(window->device, on_error, NULL);
270 // Queue
271 window->queue = wgpuDeviceGetQueue(window->device);
272
273 configure_pipeline(window);
274 configure_surface(window);
275
276 // This is necessary to initialize buffers
277 window->dirty = 1;
278
279 return window;
280}
281
282uint32_t window_should_close(const AlbaWindow* window)
283{
284 const int should_close = glfwWindowShouldClose(window->glfw_window);
285 if (!should_close)
286 {
287 glfwWaitEventsTimeout(0.01);
288 }
289 return should_close;
290}
291
292void window_release(AlbaWindow* window)
293{
294 wgpuBufferDestroy(window->uniforms);
295 wgpuBufferRelease(window->uniforms);
296
297 wgpuBufferDestroy(window->vertices);
298 wgpuBufferRelease(window->vertices);
299 float_array_release(&window->new_vertices);
300
301 wgpuBufferDestroy(window->attributes);
302 wgpuBufferRelease(window->attributes);
303 float_array_release(&window->new_attributes);
304
305 wgpuBufferDestroy(window->indices);
306 wgpuBufferRelease(window->indices);
307 uint32_array_release(&window->new_indices);
308
309 wgpuBindGroupRelease(window->bind_group);
310 wgpuShaderModuleRelease(window->shaders);
311 wgpuRenderPipelineRelease(window->pipeline);
312 wgpuQueueRelease(window->queue);
313 wgpuDeviceRelease(window->device);
314 wgpuAdapterRelease(window->adapter);
315 wgpuSurfaceRelease(window->surface);
316 wgpuInstanceRelease(window->instance);
317
318 glfwDestroyWindow(window->glfw_window);
319 free(window);
320 glfwTerminate();
321}
322
323uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
324{
325 switch (frame.status)
326 {
327 case WGPUSurfaceGetCurrentTextureStatus_Success:
328 break;
329 case WGPUSurfaceGetCurrentTextureStatus_Timeout:
330 case WGPUSurfaceGetCurrentTextureStatus_Outdated:
331 case WGPUSurfaceGetCurrentTextureStatus_Lost:
332 return 0;
333 case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
334 printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
335 frame.status);
336 case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
337 printf("fatal error: device lost (%d)\n", frame.status);
338 case WGPUSurfaceGetCurrentTextureStatus_Force32:
339 printf("fatal error: force 32 error (%d)\n", frame.status);
340 exit(1);
341 }
342
343 return 1;
344}
345
346void copy_color(const AlbaColor src, WGPUColor* dst)
347{
348 dst->r = src.r;
349 dst->g = src.g;
350 dst->b = src.b;
351 dst->a = src.a;
352}
353
354WGPUBuffer create_buffer(const AlbaWindow* window, const uint64_t size, const void* data, WGPUBufferUsageFlags flags)
355{
356 WGPUBufferDescriptor buffer_options = {0};
357 buffer_options.usage = WGPUBufferUsage_CopyDst | flags;
358 buffer_options.size = size;
359 const WGPUBuffer buffer = wgpuDeviceCreateBuffer(window->device, &buffer_options);
360 wgpuQueueWriteBuffer(window->queue, buffer, 0, data, size);
361 return buffer;
362}
363
364void clear_buffer(const WGPUBuffer buffer)
365{
366 if (buffer != NULL)
367 {
368 wgpuBufferDestroy(buffer);
369 wgpuBufferRelease(buffer);
370 }
371}
372
373WGPUBuffer float_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, FloatArray data)
374{
375 // Deallocate old buffer if any
376 clear_buffer(buffer);
377 // Copy data to new buffer
378 buffer = create_buffer(window, data.length * sizeof(float), data.data, WGPUBufferUsage_Vertex);
379 // Clear local copy for next frame
380 float_array_clear(&data);
381 return buffer;
382}
383
384WGPUBuffer uint32_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, Uint32Array data)
385{
386 // Deallocate old buffer if any
387 clear_buffer(buffer);
388 // Copy data to new buffer
389 buffer = create_buffer(window, data.length * sizeof(uint32_t), data.data, WGPUBufferUsage_Index);
390 // Clear local copy for next frame
391 uint32_array_clear(&data);
392 return buffer;
393}
394
395void window_render(AlbaWindow* window)
396{
397 if (window->dirty)
398 {
399 window->vertices = float_update_buffer(window, window->vertices, window->new_vertices);
400 window->attributes = float_update_buffer(window, window->attributes, window->new_attributes);
401 window->indices = uint32_update_buffer(window, window->indices, window->new_indices);
402 window->dirty = 0;
403 }
404
405
406 WGPUSurfaceTexture frame;
407 wgpuSurfaceGetCurrentTexture(window->surface, &frame);
408 if (!frame_status_is_valid(window, frame))
409 {
410 // Re-configure surface and skip frame
411 if (frame.texture != NULL)
412 {
413 wgpuTextureRelease(frame.texture);
414 }
415
416 configure_surface(window);
417 return;
418 }
419
420 WGPUTextureDescriptor texture_options = {0};
421 texture_options.usage = WGPUTextureUsage_RenderAttachment;
422 texture_options.format = WGPUTextureFormat_BGRA8UnormSrgb; // TODO: avoid hardcoding
423 texture_options.dimension = WGPUTextureDimension_2D;
424 texture_options.size.width = window->width;
425 texture_options.size.height = window->height;
426 texture_options.size.depthOrArrayLayers = 1;
427 texture_options.sampleCount = 4;
428 texture_options.mipLevelCount = 1;
429 const WGPUTexture texture = wgpuDeviceCreateTexture(window->device, &texture_options);
430
431 const WGPUTextureView frame_view = wgpuTextureCreateView(frame.texture, NULL);
432 if (frame_view == NULL)
433 {
434 printf("warning: could not get frame view, dropping frame (%d)\n", frame.status);
435 return;
436 }
437
438 const WGPUTextureView texture_view = wgpuTextureCreateView(texture, NULL);
439 if (texture_view == NULL)
440 {
441 printf("warning: could not get texture view, dropping frame\n");
442 return;
443 }
444
445 const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
446
447 // Configures rendering
448 WGPURenderPassColorAttachment render_pass_attachment_options = {0};
449 render_pass_attachment_options.view = texture_view;
450 render_pass_attachment_options.loadOp = WGPULoadOp_Clear;
451 render_pass_attachment_options.storeOp = WGPUStoreOp_Store;
452 render_pass_attachment_options.resolveTarget = frame_view;
453 copy_color(window->options.clear_color, &render_pass_attachment_options.clearValue);
454
455 WGPURenderPassDescriptor render_pass_options = {0};
456 render_pass_options.colorAttachmentCount = 1;
457 render_pass_options.colorAttachments = &render_pass_attachment_options;
458
459 // Draw calls
460 const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
461 encoder, &render_pass_options);
462
463 wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
464 wgpuRenderPassEncoderSetBindGroup(render_pass, 0, window->bind_group, 0, NULL);
465
466 const uint32_t vertex_size = wgpuBufferGetSize(window->vertices);
467 const uint32_t attributes_size = wgpuBufferGetSize(window->attributes);
468 const uint32_t indices_size = wgpuBufferGetSize(window->indices);
469 if (vertex_size > 0)
470 {
471 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, window->vertices, 0, vertex_size);
472 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 1, window->attributes, 0, attributes_size);
473 wgpuRenderPassEncoderSetIndexBuffer(render_pass, window->indices, WGPUIndexFormat_Uint32, 0, indices_size);
474 wgpuRenderPassEncoderDrawIndexed(render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
475 }
476
477 wgpuRenderPassEncoderEnd(render_pass);
478
479 // Encode render command and send to GPU
480 const WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL);
481 wgpuQueueSubmit(window->queue, 1, &command);
482
483 // Update screen
484 wgpuSurfacePresent(window->surface);
485
486 wgpuCommandBufferRelease(command);
487 wgpuRenderPassEncoderRelease(render_pass);
488 wgpuCommandEncoderRelease(encoder);
489 wgpuTextureViewRelease(texture_view);
490 wgpuTextureViewRelease(frame_view);
491 wgpuTextureRelease(texture);
492 wgpuTextureRelease(frame.texture);
493}
494
495void window_get_size(const AlbaWindow* window, float* width, float* height)
496{
497 int raw_width, raw_height;
498 float x_scale, y_scale;
499 glfwGetWindowSize(window->glfw_window, &raw_width, &raw_height);
500 glfwGetWindowContentScale(window->glfw_window, &x_scale, &y_scale);
501
502 *width = raw_width / x_scale;
503 *height = raw_height / y_scale;
504}