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
10extern char _binary_shaders_wgsl_start[];
11
12// forward-declare
13WGPUSurface get_window_surface(WGPUInstance instance, AlbaWindow* window);
14
15void on_receive_adapter(
16 const WGPURequestAdapterStatus status,
17 const WGPUAdapter adapter,
18 char const* message,
19 void* data
20)
21{
22 if (status != WGPURequestAdapterStatus_Success)
23 {
24 printf("fatal error: requesting adapter failed: %s", message);
25 exit(1);
26 }
27
28 *((WGPUAdapter*)data) = adapter;
29}
30
31void on_device_lost_error(const WGPUDeviceLostReason reason, char const* message, void* userdata)
32{
33 printf("fatal device lost error (%d): %s", reason, message);
34 exit(1);
35}
36
37void on_receive_device(
38 const WGPURequestDeviceStatus status,
39 const WGPUDevice device,
40 char const* message,
41 void* data
42)
43{
44 if (status != WGPURequestDeviceStatus_Success)
45 {
46 printf("fatal error: requesting device failed: %s", message);
47 exit(1);
48 }
49
50 *((WGPUDevice*)data) = device;
51}
52
53void on_error(const WGPUErrorType type, char const* message, void* data)
54{
55 if (type == WGPUErrorType_NoError)
56 {
57 return;
58 }
59 printf("error (%d): %s", type, message);
60}
61
62void configure_surface(const AlbaWindow* window)
63{
64 int width, height;
65 glfwGetWindowSize(window->glfw_window, &width, &height);
66
67 if (width == 0 || height == 0) return;
68
69 const WGPUTextureFormat format = wgpuSurfaceGetPreferredFormat(window->surface, window->adapter);
70
71 WGPUSurfaceConfiguration surface_options = {0};
72 surface_options.device = window->device;
73 surface_options.format = format;
74 surface_options.usage = WGPUTextureUsage_RenderAttachment;
75 surface_options.presentMode = WGPUPresentMode_Fifo;
76 surface_options.width = width;
77 surface_options.height = height;
78 wgpuSurfaceConfigure(window->surface, &surface_options);
79}
80
81void configure_pipeline(AlbaWindow* window)
82{
83 WGPUShaderModuleWGSLDescriptor shader_options = {0};
84 shader_options.chain.sType = WGPUSType_ShaderModuleWGSLDescriptor;
85 shader_options.code = _binary_shaders_wgsl_start;
86
87 WGPUShaderModuleDescriptor shader_loader_options = {0};
88 shader_loader_options.nextInChain = (WGPUChainedStruct*)&shader_options;
89
90 window->shaders = wgpuDeviceCreateShaderModule(window->device, &shader_loader_options);
91
92 // Configure render pipeline
93 WGPURenderPipelineDescriptor pipleine_options = {0};
94 // Rendering settings
95 pipleine_options.primitive.topology = WGPUPrimitiveTopology_TriangleList;
96 pipleine_options.primitive.frontFace = WGPUFrontFace_CCW; // counter clockwise
97 pipleine_options.multisample.count = 1; // no anti-aliasing
98 pipleine_options.multisample.mask = 0xFFFFFFFF;
99
100 // Vertex shader
101 pipleine_options.vertex.module = window->shaders;
102 pipleine_options.vertex.entryPoint = "vertex_shader";
103 pipleine_options.vertex.bufferCount = 2;
104 pipleine_options.vertex.buffers = (WGPUVertexBufferLayout []){
105 // Position
106 {
107 .arrayStride = 2 * sizeof(float),
108 .stepMode = WGPUVertexStepMode_Vertex,
109 .attributeCount = 1,
110 .attributes = &(WGPUVertexAttribute){
111 .format = WGPUVertexFormat_Float32x2,
112 .offset = 0,
113 .shaderLocation = 0,
114 },
115 },
116 // Color
117 {
118 .arrayStride = 4 * sizeof(float),
119 .stepMode = WGPUVertexStepMode_Vertex,
120 .attributeCount = 1,
121 .attributes = &(WGPUVertexAttribute){
122 .format = WGPUVertexFormat_Float32x4,
123 .offset = 0,
124 .shaderLocation = 1,
125 },
126 },
127 };
128
129 // Fragment shader
130 WGPUColorTargetState color_state = {0};
131 color_state.format = WGPUTextureFormat_BGRA8UnormSrgb;
132 color_state.writeMask = WGPUColorWriteMask_All;
133 WGPUFragmentState fragment_state = {0};
134 fragment_state.module = window->shaders;
135 fragment_state.entryPoint = "fragment_shader";
136 fragment_state.targetCount = 1;
137 fragment_state.targets = &color_state;
138 pipleine_options.fragment = &fragment_state;
139
140 window->pipeline = wgpuDeviceCreateRenderPipeline(window->device, &pipleine_options);
141}
142
143AlbaWindow* create_window(const AlbaWindowOptions* options)
144{
145 const AlbaWindowOptions default_options = {0};
146 if (options == NULL)
147 {
148 options = &default_options;
149 }
150
151 AlbaWindow* window = malloc(sizeof(AlbaWindow));
152 memset(window, 0, sizeof(AlbaWindow));
153 window->options = *options;
154
155 if (!glfwInit())
156 {
157 printf("fatal error: initializeing GLFW failed");
158 exit(1);
159 }
160
161 // GLFW window
162 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
163 window->glfw_window = glfwCreateWindow(
164 options->initial_width == 0 ? 800 : options->initial_width,
165 options->initial_height == 0 ? 600 : options->initial_height,
166 options->title == NULL ? "Alba" : options->title,
167 NULL,
168 NULL
169 );
170
171 // Instance & surface initialization
172 window->instance = wgpuCreateInstance(NULL);
173 window->surface = get_window_surface(window->instance, window);
174
175 // Adapter
176 WGPURequestAdapterOptions adapter_options = {0};
177 adapter_options.compatibleSurface = window->surface;
178 wgpuInstanceRequestAdapter(
179 window->instance,
180 &adapter_options,
181 on_receive_adapter,
182 &window->adapter
183 );
184
185 // Device
186 WGPUDeviceDescriptor device_options = {0};
187 device_options.deviceLostCallback = on_device_lost_error;
188 wgpuAdapterRequestDevice(
189 window->adapter,
190 &device_options,
191 on_receive_device,
192 &window->device
193 );
194
195 // Errors
196 wgpuDeviceSetUncapturedErrorCallback(window->device, on_error, NULL);
197 // Queue
198 window->queue = wgpuDeviceGetQueue(window->device);
199
200 // Surface configuration
201 configure_surface(window);
202
203 // Load and configure shaders
204 configure_pipeline(window);
205
206 // This is necessary to initialize buffers
207 window->dirty = 1;
208 float_array_append(&window->new_vertices, -0.5);
209 float_array_append(&window->new_vertices, -0.5);
210 float_array_append(&window->new_vertices, +0.5);
211 float_array_append(&window->new_vertices, -0.5);
212 float_array_append(&window->new_vertices, +0.5);
213 float_array_append(&window->new_vertices, +0.5);
214 for (int i = 0; i < 3; i++)
215 {
216 float_array_append(&window->new_attributes, i / 2);
217 float_array_append(&window->new_attributes, (2 - i) / 2);
218 float_array_append(&window->new_attributes, 0.0);
219 float_array_append(&window->new_attributes, 1.0);
220 }
221 uint32_array_append(&window->new_indices, 0);
222 uint32_array_append(&window->new_indices, 1);
223 uint32_array_append(&window->new_indices, 2);
224
225 return window;
226}
227
228uint32_t window_should_close(const AlbaWindow* window)
229{
230 const int should_close = glfwWindowShouldClose(window->glfw_window);
231 if (!should_close)
232 {
233 glfwWaitEventsTimeout(0.01);
234 }
235 return should_close;
236}
237
238void window_release(AlbaWindow* window)
239{
240 wgpuBufferDestroy(window->vertices);
241 wgpuBufferRelease(window->vertices);
242 float_array_release(&window->new_vertices);
243
244 wgpuBufferDestroy(window->attributes);
245 wgpuBufferRelease(window->attributes);
246 float_array_release(&window->new_attributes);
247
248 wgpuBufferDestroy(window->indices);
249 wgpuBufferRelease(window->indices);
250 uint32_array_release(&window->new_indices);
251
252 wgpuShaderModuleRelease(window->shaders);
253 wgpuRenderPipelineRelease(window->pipeline);
254 wgpuQueueRelease(window->queue);
255 wgpuDeviceRelease(window->device);
256 wgpuAdapterRelease(window->adapter);
257 wgpuSurfaceRelease(window->surface);
258 wgpuInstanceRelease(window->instance);
259
260 glfwDestroyWindow(window->glfw_window);
261 free(window);
262 glfwTerminate();
263}
264
265uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
266{
267 switch (frame.status)
268 {
269 case WGPUSurfaceGetCurrentTextureStatus_Success:
270 break;
271 case WGPUSurfaceGetCurrentTextureStatus_Timeout:
272 case WGPUSurfaceGetCurrentTextureStatus_Outdated:
273 case WGPUSurfaceGetCurrentTextureStatus_Lost:
274 return 0;
275 case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
276 printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
277 frame.status);
278 case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
279 printf("fatal error: device lost (%d)\n", frame.status);
280 case WGPUSurfaceGetCurrentTextureStatus_Force32:
281 printf("fatal error: force 32 error (%d)\n", frame.status);
282 exit(1);
283 }
284
285 return 1;
286}
287
288void copy_color(const AlbaColor src, WGPUColor* dst)
289{
290 dst->r = src.r;
291 dst->g = src.g;
292 dst->b = src.b;
293 dst->a = src.a;
294}
295
296WGPUBuffer create_buffer(const AlbaWindow* window, const uint64_t size, const void* data, WGPUBufferUsageFlags flags)
297{
298 WGPUBufferDescriptor buffer_options = {0};
299 buffer_options.usage = WGPUBufferUsage_CopyDst | flags;
300 buffer_options.size = size;
301 const WGPUBuffer buffer = wgpuDeviceCreateBuffer(window->device, &buffer_options);
302 wgpuQueueWriteBuffer(window->queue, buffer, 0, data, size);
303 return buffer;
304}
305
306void clear_buffer(const WGPUBuffer buffer)
307{
308 if (buffer != NULL)
309 {
310 wgpuBufferDestroy(buffer);
311 wgpuBufferRelease(buffer);
312 }
313}
314
315WGPUBuffer float_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, FloatArray data)
316{
317 // Deallocate old buffer if any
318 clear_buffer(buffer);
319 // Copy data to new buffer
320 buffer = create_buffer(window, data.length * sizeof(float), data.data, WGPUBufferUsage_Vertex);
321 // Clear local copy for next frame
322 float_array_clear(&data);
323 return buffer;
324}
325
326WGPUBuffer uint32_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, Uint32Array data)
327{
328 // Deallocate old buffer if any
329 clear_buffer(buffer);
330 // Copy data to new buffer
331 buffer = create_buffer(window, data.length * sizeof(uint32_t), data.data, WGPUBufferUsage_Index);
332 // Clear local copy for next frame
333 uint32_array_clear(&data);
334 return buffer;
335}
336
337void window_render(AlbaWindow* window)
338{
339 if (window->dirty)
340 {
341 window->vertices = float_update_buffer(window, window->vertices, window->new_vertices);
342 window->attributes = float_update_buffer(window, window->attributes, window->new_attributes);
343 window->indices = uint32_update_buffer(window, window->indices, window->new_indices);
344 window->dirty = 0;
345 }
346
347 WGPUSurfaceTexture frame;
348 wgpuSurfaceGetCurrentTexture(window->surface, &frame);
349 if (!frame_status_is_valid(window, frame))
350 {
351 // Re-configure surface and skip frame
352 if (frame.texture != NULL)
353 {
354 wgpuTextureRelease(frame.texture);
355 }
356
357 configure_surface(window);
358 return;
359 }
360
361 const WGPUTextureView view = wgpuTextureCreateView(frame.texture, NULL);
362 if (view == NULL)
363 {
364 printf("warning: could not get frame view, dropping frame (%d)\n", frame.status);
365 return;
366 }
367
368 const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
369
370 // Configures rendering
371 WGPURenderPassColorAttachment render_pass_attachment_options = {0};
372 render_pass_attachment_options.view = view;
373 render_pass_attachment_options.loadOp = WGPULoadOp_Clear;
374 render_pass_attachment_options.storeOp = WGPUStoreOp_Store;
375 copy_color(window->options.clear_color, &render_pass_attachment_options.clearValue);
376
377 WGPURenderPassDescriptor render_pass_options = {0};
378 render_pass_options.colorAttachmentCount = 1;
379 render_pass_options.colorAttachments = &render_pass_attachment_options;
380
381 // Draw calls
382 const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
383 encoder, &render_pass_options);
384 wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
385
386 const uint32_t vertex_size = wgpuBufferGetSize(window->vertices);
387 const uint32_t attributes_size = wgpuBufferGetSize(window->attributes);
388 const uint32_t indices_size = wgpuBufferGetSize(window->indices);
389 if (vertex_size > 0)
390 {
391 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, window->vertices, 0, vertex_size);
392 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 1, window->attributes, 0, attributes_size);
393 wgpuRenderPassEncoderSetIndexBuffer(render_pass, window->indices, WGPUIndexFormat_Uint32, 0, indices_size);
394 wgpuRenderPassEncoderDrawIndexed(render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
395 }
396 wgpuRenderPassEncoderEnd(render_pass);
397
398 // Encode render command and send to GPU
399 const WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL);
400 wgpuQueueSubmit(window->queue, 1, &command);
401
402 // Update screen
403 wgpuSurfacePresent(window->surface);
404
405 wgpuCommandBufferRelease(command);
406 wgpuRenderPassEncoderRelease(render_pass);
407 wgpuCommandEncoderRelease(encoder);
408 wgpuTextureViewRelease(view);
409 wgpuTextureRelease(frame.texture);
410}