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(const 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 // Update uniforms
117 const float scale[2] = {width / (2. * x_scale), height / (2. * y_scale)};
118 wgpuQueueWriteBuffer(window->queue, window->uniforms, 0, &scale, 2 * sizeof(uint32_t));
119
120 const WGPUTextureFormat format = wgpuSurfaceGetPreferredFormat(window->surface, window->adapter);
121
122 WGPUSurfaceConfiguration surface_options = {0};
123 surface_options.device = window->device;
124 surface_options.format = format;
125 surface_options.usage = WGPUTextureUsage_RenderAttachment;
126 surface_options.presentMode = WGPUPresentMode_Fifo;
127 surface_options.width = width;
128 surface_options.height = height;
129 wgpuSurfaceConfigure(window->surface, &surface_options);
130}
131
132
133void configure_pipeline(AlbaWindow* window)
134{
135 WGPUShaderModuleWGSLDescriptor shader_options = {0};
136 shader_options.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
137 shader_options.code = _binary_shaders_wgsl_start;
138
139 WGPUShaderModuleDescriptor shader_loader_options = {0};
140 shader_loader_options.nextInChain = (WGPUChainedStruct*)&shader_options;
141
142 window->shaders = wgpuDeviceCreateShaderModule(window->device, &shader_loader_options);
143
144 // Configure render pipeline
145 WGPURenderPipelineDescriptor pipleine_options = {0};
146 // Rendering settings
147 pipleine_options.primitive.topology = WGPUPrimitiveTopology_TriangleList;
148 pipleine_options.primitive.frontFace = WGPUFrontFace_CCW; // counter clockwise
149 pipleine_options.multisample.count = 4;
150 pipleine_options.multisample.mask = 0xFFFFFFFF;
151
152 // Vertex shader
153 pipleine_options.vertex.module = window->shaders;
154 pipleine_options.vertex.entryPoint = "vertex_shader";
155 pipleine_options.vertex.bufferCount = 2;
156 pipleine_options.vertex.buffers = (WGPUVertexBufferLayout []){
157 // Position
158 {
159 .arrayStride = 2 * sizeof(float),
160 .stepMode = WGPUVertexStepMode_Vertex,
161 .attributeCount = 1,
162 .attributes = &(WGPUVertexAttribute){
163 .format = WGPUVertexFormat_Float32x2,
164 .offset = 0,
165 .shaderLocation = 0,
166 },
167 },
168 // Color
169 {
170 .arrayStride = 4 * sizeof(float),
171 .stepMode = WGPUVertexStepMode_Vertex,
172 .attributeCount = 1,
173 .attributes = &(WGPUVertexAttribute){
174 .format = WGPUVertexFormat_Float32x4,
175 .offset = 0,
176 .shaderLocation = 1,
177 },
178 },
179 };
180
181 // Fragment shader
182 const WGPUBlendState blend_state = {
183 .color = {
184 .operation = WGPUBlendOperation_Add,
185 .srcFactor = WGPUBlendFactor_SrcAlpha,
186 .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha,
187 },
188 .alpha = {
189 .operation = WGPUBlendOperation_Add,
190 .srcFactor = WGPUBlendFactor_Zero,
191 .dstFactor = WGPUBlendFactor_One,
192 }
193 };
194 WGPUColorTargetState color_state = {0};
195 color_state.format = WGPUTextureFormat_BGRA8UnormSrgb; // TODO: avoid hardcoding
196 color_state.blend = &blend_state;
197 color_state.writeMask = WGPUColorWriteMask_All;
198 WGPUFragmentState fragment_state = {0};
199 fragment_state.module = window->shaders;
200 fragment_state.entryPoint = "fragment_shader";
201 fragment_state.targetCount = 1;
202 fragment_state.targets = &color_state;
203 pipleine_options.fragment = &fragment_state;
204
205 // Uniform buffer
206 pipleine_options.layout = configure_uniforms(window);
207
208 window->pipeline = wgpuDeviceCreateRenderPipeline(window->device, &pipleine_options);
209
210 wgpuPipelineLayoutRelease(pipleine_options.layout);
211}
212
213AlbaWindow* create_window(const AlbaWindowOptions* options)
214{
215 const AlbaWindowOptions default_options = {0};
216 if (options == NULL)
217 {
218 options = &default_options;
219 }
220
221 AlbaWindow* window = malloc(sizeof(AlbaWindow));
222 memset(window, 0, sizeof(AlbaWindow));
223 window->options = *options;
224
225 if (!glfwInit())
226 {
227 printf("fatal error: initializeing GLFW failed");
228 exit(1);
229 }
230
231 // GLFW window
232 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
233 window->glfw_window = glfwCreateWindow(
234 options->initial_width == 0 ? 800 : options->initial_width,
235 options->initial_height == 0 ? 600 : options->initial_height,
236 options->title == NULL ? "Alba" : options->title,
237 NULL,
238 NULL
239 );
240
241 // Instance & surface initialization
242 window->instance = wgpuCreateInstance(NULL);
243 window->surface = get_window_surface(window->instance, window);
244
245 // Adapter
246 WGPURequestAdapterOptions adapter_options = {0};
247 adapter_options.compatibleSurface = window->surface;
248 wgpuInstanceRequestAdapter(
249 window->instance,
250 &adapter_options,
251 on_receive_adapter,
252 &window->adapter
253 );
254
255 // Device
256 WGPUDeviceDescriptor device_options = {0};
257 device_options.deviceLostCallback = on_device_lost_error;
258 wgpuAdapterRequestDevice(
259 window->adapter,
260 &device_options,
261 on_receive_device,
262 &window->device
263 );
264
265 // Errors
266 wgpuDeviceSetUncapturedErrorCallback(window->device, on_error, NULL);
267 // Queue
268 window->queue = wgpuDeviceGetQueue(window->device);
269
270 configure_pipeline(window);
271 configure_surface(window);
272
273 // This is necessary to initialize buffers
274 window->dirty = 1;
275
276 return window;
277}
278
279uint32_t window_should_close(const AlbaWindow* window)
280{
281 const int should_close = glfwWindowShouldClose(window->glfw_window);
282 if (!should_close)
283 {
284 glfwWaitEventsTimeout(0.01);
285 }
286 return should_close;
287}
288
289void window_release(AlbaWindow* window)
290{
291 wgpuBufferDestroy(window->uniforms);
292 wgpuBufferRelease(window->uniforms);
293
294 wgpuBufferDestroy(window->vertices);
295 wgpuBufferRelease(window->vertices);
296 float_array_release(&window->new_vertices);
297
298 wgpuBufferDestroy(window->attributes);
299 wgpuBufferRelease(window->attributes);
300 float_array_release(&window->new_attributes);
301
302 wgpuBufferDestroy(window->indices);
303 wgpuBufferRelease(window->indices);
304 uint32_array_release(&window->new_indices);
305
306 wgpuBindGroupRelease(window->bind_group);
307 wgpuShaderModuleRelease(window->shaders);
308 wgpuRenderPipelineRelease(window->pipeline);
309 wgpuQueueRelease(window->queue);
310 wgpuDeviceRelease(window->device);
311 wgpuAdapterRelease(window->adapter);
312 wgpuSurfaceRelease(window->surface);
313 wgpuInstanceRelease(window->instance);
314
315 glfwDestroyWindow(window->glfw_window);
316 free(window);
317 glfwTerminate();
318}
319
320uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
321{
322 switch (frame.status)
323 {
324 case WGPUSurfaceGetCurrentTextureStatus_Success:
325 break;
326 case WGPUSurfaceGetCurrentTextureStatus_Timeout:
327 case WGPUSurfaceGetCurrentTextureStatus_Outdated:
328 case WGPUSurfaceGetCurrentTextureStatus_Lost:
329 return 0;
330 case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
331 printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
332 frame.status);
333 case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
334 printf("fatal error: device lost (%d)\n", frame.status);
335 case WGPUSurfaceGetCurrentTextureStatus_Force32:
336 printf("fatal error: force 32 error (%d)\n", frame.status);
337 exit(1);
338 }
339
340 return 1;
341}
342
343void copy_color(const AlbaColor src, WGPUColor* dst)
344{
345 dst->r = src.r;
346 dst->g = src.g;
347 dst->b = src.b;
348 dst->a = src.a;
349}
350
351WGPUBuffer create_buffer(const AlbaWindow* window, const uint64_t size, const void* data, WGPUBufferUsageFlags flags)
352{
353 WGPUBufferDescriptor buffer_options = {0};
354 buffer_options.usage = WGPUBufferUsage_CopyDst | flags;
355 buffer_options.size = size;
356 const WGPUBuffer buffer = wgpuDeviceCreateBuffer(window->device, &buffer_options);
357 wgpuQueueWriteBuffer(window->queue, buffer, 0, data, size);
358 return buffer;
359}
360
361void clear_buffer(const WGPUBuffer buffer)
362{
363 if (buffer != NULL)
364 {
365 wgpuBufferDestroy(buffer);
366 wgpuBufferRelease(buffer);
367 }
368}
369
370WGPUBuffer float_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, FloatArray data)
371{
372 // Deallocate old buffer if any
373 clear_buffer(buffer);
374 // Copy data to new buffer
375 buffer = create_buffer(window, data.length * sizeof(float), data.data, WGPUBufferUsage_Vertex);
376 // Clear local copy for next frame
377 float_array_clear(&data);
378 return buffer;
379}
380
381WGPUBuffer uint32_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, Uint32Array data)
382{
383 // Deallocate old buffer if any
384 clear_buffer(buffer);
385 // Copy data to new buffer
386 buffer = create_buffer(window, data.length * sizeof(uint32_t), data.data, WGPUBufferUsage_Index);
387 // Clear local copy for next frame
388 uint32_array_clear(&data);
389 return buffer;
390}
391
392void window_render(AlbaWindow* window)
393{
394 if (window->dirty)
395 {
396 window->vertices = float_update_buffer(window, window->vertices, window->new_vertices);
397 window->attributes = float_update_buffer(window, window->attributes, window->new_attributes);
398 window->indices = uint32_update_buffer(window, window->indices, window->new_indices);
399 window->dirty = 0;
400 }
401
402
403 WGPUSurfaceTexture frame;
404 wgpuSurfaceGetCurrentTexture(window->surface, &frame);
405 if (!frame_status_is_valid(window, frame))
406 {
407 // Re-configure surface and skip frame
408 if (frame.texture != NULL)
409 {
410 wgpuTextureRelease(frame.texture);
411 }
412
413 configure_surface(window);
414 return;
415 }
416
417 WGPUTextureDescriptor texture_options = {0};
418 texture_options.usage = WGPUTextureUsage_RenderAttachment;
419 texture_options.format = WGPUTextureFormat_BGRA8UnormSrgb; // TODO: avoid hardcoding
420 texture_options.dimension = WGPUTextureDimension_2D;
421 texture_options.size.width = 800;
422 texture_options.size.height = 600;
423 texture_options.size.depthOrArrayLayers = 1;
424 texture_options.sampleCount = 4;
425 texture_options.mipLevelCount = 1;
426 const WGPUTexture texture = wgpuDeviceCreateTexture(window->device, &texture_options);
427
428 const WGPUTextureView frame_view = wgpuTextureCreateView(frame.texture, NULL);
429 if (frame_view == NULL)
430 {
431 printf("warning: could not get frame view, dropping frame (%d)\n", frame.status);
432 return;
433 }
434
435 const WGPUTextureView texture_view = wgpuTextureCreateView(texture, NULL);
436 if (texture_view == NULL)
437 {
438 printf("warning: could not get texture view, dropping frame\n");
439 return;
440 }
441
442 const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
443
444 // Configures rendering
445 WGPURenderPassColorAttachment render_pass_attachment_options = {0};
446 render_pass_attachment_options.view = texture_view;
447 render_pass_attachment_options.loadOp = WGPULoadOp_Clear;
448 render_pass_attachment_options.storeOp = WGPUStoreOp_Store;
449 render_pass_attachment_options.resolveTarget = frame_view;
450 copy_color(window->options.clear_color, &render_pass_attachment_options.clearValue);
451
452 WGPURenderPassDescriptor render_pass_options = {0};
453 render_pass_options.colorAttachmentCount = 1;
454 render_pass_options.colorAttachments = &render_pass_attachment_options;
455
456 // Draw calls
457 const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
458 encoder, &render_pass_options);
459
460 wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
461 wgpuRenderPassEncoderSetBindGroup(render_pass, 0, window->bind_group, 0, NULL);
462
463 const uint32_t vertex_size = wgpuBufferGetSize(window->vertices);
464 const uint32_t attributes_size = wgpuBufferGetSize(window->attributes);
465 const uint32_t indices_size = wgpuBufferGetSize(window->indices);
466 if (vertex_size > 0)
467 {
468 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, window->vertices, 0, vertex_size);
469 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 1, window->attributes, 0, attributes_size);
470 wgpuRenderPassEncoderSetIndexBuffer(render_pass, window->indices, WGPUIndexFormat_Uint32, 0, indices_size);
471 wgpuRenderPassEncoderDrawIndexed(render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
472 }
473
474 wgpuRenderPassEncoderEnd(render_pass);
475
476 // Encode render command and send to GPU
477 const WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL);
478 wgpuQueueSubmit(window->queue, 1, &command);
479
480 // Update screen
481 wgpuSurfacePresent(window->surface);
482
483 wgpuCommandBufferRelease(command);
484 wgpuRenderPassEncoderRelease(render_pass);
485 wgpuCommandEncoderRelease(encoder);
486 wgpuTextureViewRelease(texture_view);
487 wgpuTextureViewRelease(frame_view);
488 wgpuTextureRelease(texture);
489 wgpuTextureRelease(frame.texture);
490}
491
492void window_get_size(const AlbaWindow* window, float* width, float* height)
493{
494 int raw_width, raw_height;
495 float x_scale, y_scale;
496 glfwGetWindowSize(window->glfw_window, &raw_width, &raw_height);
497 glfwGetWindowContentScale(window->glfw_window, &x_scale, &y_scale);
498
499 *width = raw_width / x_scale;
500 *height = raw_height / y_scale;
501}