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 const WGPUBlendState blend_state = {
131 .color = {
132 .operation = WGPUBlendOperation_Add,
133 .srcFactor = WGPUBlendFactor_SrcAlpha,
134 .dstFactor = WGPUBlendFactor_OneMinusSrcAlpha,
135 },
136 .alpha = {
137 .operation = WGPUBlendOperation_Add,
138 .srcFactor = WGPUBlendFactor_Zero,
139 .dstFactor = WGPUBlendFactor_One,
140 }
141 };
142 WGPUColorTargetState color_state = {0};
143 color_state.format = WGPUTextureFormat_BGRA8UnormSrgb; // TODO: avoid hardcoding
144 color_state.blend = &blend_state;
145 color_state.writeMask = WGPUColorWriteMask_All;
146 WGPUFragmentState fragment_state = {0};
147 fragment_state.module = window->shaders;
148 fragment_state.entryPoint = "fragment_shader";
149 fragment_state.targetCount = 1;
150 fragment_state.targets = &color_state;
151 pipleine_options.fragment = &fragment_state;
152
153 window->pipeline = wgpuDeviceCreateRenderPipeline(window->device, &pipleine_options);
154}
155
156AlbaWindow* create_window(const AlbaWindowOptions* options)
157{
158 const AlbaWindowOptions default_options = {0};
159 if (options == NULL)
160 {
161 options = &default_options;
162 }
163
164 AlbaWindow* window = malloc(sizeof(AlbaWindow));
165 memset(window, 0, sizeof(AlbaWindow));
166 window->options = *options;
167
168 if (!glfwInit())
169 {
170 printf("fatal error: initializeing GLFW failed");
171 exit(1);
172 }
173
174 // GLFW window
175 glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
176 window->glfw_window = glfwCreateWindow(
177 options->initial_width == 0 ? 800 : options->initial_width,
178 options->initial_height == 0 ? 600 : options->initial_height,
179 options->title == NULL ? "Alba" : options->title,
180 NULL,
181 NULL
182 );
183
184 // Instance & surface initialization
185 window->instance = wgpuCreateInstance(NULL);
186 window->surface = get_window_surface(window->instance, window);
187
188 // Adapter
189 WGPURequestAdapterOptions adapter_options = {0};
190 adapter_options.compatibleSurface = window->surface;
191 wgpuInstanceRequestAdapter(
192 window->instance,
193 &adapter_options,
194 on_receive_adapter,
195 &window->adapter
196 );
197
198 // Device
199 WGPUDeviceDescriptor device_options = {0};
200 device_options.deviceLostCallback = on_device_lost_error;
201 wgpuAdapterRequestDevice(
202 window->adapter,
203 &device_options,
204 on_receive_device,
205 &window->device
206 );
207
208 // Errors
209 wgpuDeviceSetUncapturedErrorCallback(window->device, on_error, NULL);
210 // Queue
211 window->queue = wgpuDeviceGetQueue(window->device);
212
213 // Surface configuration
214 configure_surface(window);
215
216 // Load and configure shaders
217 configure_pipeline(window);
218
219 // This is necessary to initialize buffers
220 window->dirty = 1;
221
222 return window;
223}
224
225uint32_t window_should_close(const AlbaWindow* window)
226{
227 const int should_close = glfwWindowShouldClose(window->glfw_window);
228 if (!should_close)
229 {
230 glfwWaitEventsTimeout(0.01);
231 }
232 return should_close;
233}
234
235void window_release(AlbaWindow* window)
236{
237 wgpuBufferDestroy(window->vertices);
238 wgpuBufferRelease(window->vertices);
239 float_array_release(&window->new_vertices);
240
241 wgpuBufferDestroy(window->attributes);
242 wgpuBufferRelease(window->attributes);
243 float_array_release(&window->new_attributes);
244
245 wgpuBufferDestroy(window->indices);
246 wgpuBufferRelease(window->indices);
247 uint32_array_release(&window->new_indices);
248
249 wgpuShaderModuleRelease(window->shaders);
250 wgpuRenderPipelineRelease(window->pipeline);
251 wgpuQueueRelease(window->queue);
252 wgpuDeviceRelease(window->device);
253 wgpuAdapterRelease(window->adapter);
254 wgpuSurfaceRelease(window->surface);
255 wgpuInstanceRelease(window->instance);
256
257 glfwDestroyWindow(window->glfw_window);
258 free(window);
259 glfwTerminate();
260}
261
262uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
263{
264 switch (frame.status)
265 {
266 case WGPUSurfaceGetCurrentTextureStatus_Success:
267 break;
268 case WGPUSurfaceGetCurrentTextureStatus_Timeout:
269 case WGPUSurfaceGetCurrentTextureStatus_Outdated:
270 case WGPUSurfaceGetCurrentTextureStatus_Lost:
271 return 0;
272 case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
273 printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
274 frame.status);
275 case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
276 printf("fatal error: device lost (%d)\n", frame.status);
277 case WGPUSurfaceGetCurrentTextureStatus_Force32:
278 printf("fatal error: force 32 error (%d)\n", frame.status);
279 exit(1);
280 }
281
282 return 1;
283}
284
285void copy_color(const AlbaColor src, WGPUColor* dst)
286{
287 dst->r = src.r;
288 dst->g = src.g;
289 dst->b = src.b;
290 dst->a = src.a;
291}
292
293WGPUBuffer create_buffer(const AlbaWindow* window, const uint64_t size, const void* data, WGPUBufferUsageFlags flags)
294{
295 WGPUBufferDescriptor buffer_options = {0};
296 buffer_options.usage = WGPUBufferUsage_CopyDst | flags;
297 buffer_options.size = size;
298 const WGPUBuffer buffer = wgpuDeviceCreateBuffer(window->device, &buffer_options);
299 wgpuQueueWriteBuffer(window->queue, buffer, 0, data, size);
300 return buffer;
301}
302
303void clear_buffer(const WGPUBuffer buffer)
304{
305 if (buffer != NULL)
306 {
307 wgpuBufferDestroy(buffer);
308 wgpuBufferRelease(buffer);
309 }
310}
311
312WGPUBuffer float_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, FloatArray data)
313{
314 // Deallocate old buffer if any
315 clear_buffer(buffer);
316 // Copy data to new buffer
317 buffer = create_buffer(window, data.length * sizeof(float), data.data, WGPUBufferUsage_Vertex);
318 // Clear local copy for next frame
319 float_array_clear(&data);
320 return buffer;
321}
322
323WGPUBuffer uint32_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, Uint32Array data)
324{
325 // Deallocate old buffer if any
326 clear_buffer(buffer);
327 // Copy data to new buffer
328 buffer = create_buffer(window, data.length * sizeof(uint32_t), data.data, WGPUBufferUsage_Index);
329 // Clear local copy for next frame
330 uint32_array_clear(&data);
331 return buffer;
332}
333
334void window_render(AlbaWindow* window)
335{
336 if (window->dirty)
337 {
338 window->vertices = float_update_buffer(window, window->vertices, window->new_vertices);
339 window->attributes = float_update_buffer(window, window->attributes, window->new_attributes);
340 window->indices = uint32_update_buffer(window, window->indices, window->new_indices);
341 window->dirty = 0;
342 }
343
344 WGPUSurfaceTexture frame;
345 wgpuSurfaceGetCurrentTexture(window->surface, &frame);
346 if (!frame_status_is_valid(window, frame))
347 {
348 // Re-configure surface and skip frame
349 if (frame.texture != NULL)
350 {
351 wgpuTextureRelease(frame.texture);
352 }
353
354 configure_surface(window);
355 return;
356 }
357
358 const WGPUTextureView view = wgpuTextureCreateView(frame.texture, NULL);
359 if (view == NULL)
360 {
361 printf("warning: could not get frame view, dropping frame (%d)\n", frame.status);
362 return;
363 }
364
365 const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
366
367 // Configures rendering
368 WGPURenderPassColorAttachment render_pass_attachment_options = {0};
369 render_pass_attachment_options.view = view;
370 render_pass_attachment_options.loadOp = WGPULoadOp_Clear;
371 render_pass_attachment_options.storeOp = WGPUStoreOp_Store;
372 copy_color(window->options.clear_color, &render_pass_attachment_options.clearValue);
373
374 WGPURenderPassDescriptor render_pass_options = {0};
375 render_pass_options.colorAttachmentCount = 1;
376 render_pass_options.colorAttachments = &render_pass_attachment_options;
377
378 // Draw calls
379 const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
380 encoder, &render_pass_options);
381 wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
382
383 const uint32_t vertex_size = wgpuBufferGetSize(window->vertices);
384 const uint32_t attributes_size = wgpuBufferGetSize(window->attributes);
385 const uint32_t indices_size = wgpuBufferGetSize(window->indices);
386 if (vertex_size > 0)
387 {
388 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, window->vertices, 0, vertex_size);
389 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 1, window->attributes, 0, attributes_size);
390 wgpuRenderPassEncoderSetIndexBuffer(render_pass, window->indices, WGPUIndexFormat_Uint32, 0, indices_size);
391 wgpuRenderPassEncoderDrawIndexed(render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
392 }
393 wgpuRenderPassEncoderEnd(render_pass);
394
395 // Encode render command and send to GPU
396 const WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL);
397 wgpuQueueSubmit(window->queue, 1, &command);
398
399 // Update screen
400 wgpuSurfacePresent(window->surface);
401
402 wgpuCommandBufferRelease(command);
403 wgpuRenderPassEncoderRelease(render_pass);
404 wgpuCommandEncoderRelease(encoder);
405 wgpuTextureViewRelease(view);
406 wgpuTextureRelease(frame.texture);
407}