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
209 return window;
210}
211
212uint32_t window_should_close(const AlbaWindow* window)
213{
214 const int should_close = glfwWindowShouldClose(window->glfw_window);
215 if (!should_close)
216 {
217 glfwWaitEventsTimeout(0.01);
218 }
219 return should_close;
220}
221
222void window_release(AlbaWindow* window)
223{
224 wgpuBufferDestroy(window->vertices);
225 wgpuBufferRelease(window->vertices);
226 float_array_release(&window->new_vertices);
227
228 wgpuBufferDestroy(window->attributes);
229 wgpuBufferRelease(window->attributes);
230 float_array_release(&window->new_attributes);
231
232 wgpuBufferDestroy(window->indices);
233 wgpuBufferRelease(window->indices);
234 uint32_array_release(&window->new_indices);
235
236 wgpuShaderModuleRelease(window->shaders);
237 wgpuRenderPipelineRelease(window->pipeline);
238 wgpuQueueRelease(window->queue);
239 wgpuDeviceRelease(window->device);
240 wgpuAdapterRelease(window->adapter);
241 wgpuSurfaceRelease(window->surface);
242 wgpuInstanceRelease(window->instance);
243
244 glfwDestroyWindow(window->glfw_window);
245 free(window);
246 glfwTerminate();
247}
248
249uint32_t frame_status_is_valid(const AlbaWindow* window, const WGPUSurfaceTexture frame)
250{
251 switch (frame.status)
252 {
253 case WGPUSurfaceGetCurrentTextureStatus_Success:
254 break;
255 case WGPUSurfaceGetCurrentTextureStatus_Timeout:
256 case WGPUSurfaceGetCurrentTextureStatus_Outdated:
257 case WGPUSurfaceGetCurrentTextureStatus_Lost:
258 return 0;
259 case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory:
260 printf("fatal error: frame allocation failed due to insufficient memory (%d)\n",
261 frame.status);
262 case WGPUSurfaceGetCurrentTextureStatus_DeviceLost:
263 printf("fatal error: device lost (%d)\n", frame.status);
264 case WGPUSurfaceGetCurrentTextureStatus_Force32:
265 printf("fatal error: force 32 error (%d)\n", frame.status);
266 exit(1);
267 }
268
269 return 1;
270}
271
272void copy_color(const AlbaColor src, WGPUColor* dst)
273{
274 dst->r = src.r;
275 dst->g = src.g;
276 dst->b = src.b;
277 dst->a = src.a;
278}
279
280WGPUBuffer create_buffer(const AlbaWindow* window, const uint64_t size, const void* data, WGPUBufferUsageFlags flags)
281{
282 WGPUBufferDescriptor buffer_options = {0};
283 buffer_options.usage = WGPUBufferUsage_CopyDst | flags;
284 buffer_options.size = size;
285 const WGPUBuffer buffer = wgpuDeviceCreateBuffer(window->device, &buffer_options);
286 wgpuQueueWriteBuffer(window->queue, buffer, 0, data, size);
287 return buffer;
288}
289
290void clear_buffer(const WGPUBuffer buffer)
291{
292 if (buffer != NULL)
293 {
294 wgpuBufferDestroy(buffer);
295 wgpuBufferRelease(buffer);
296 }
297}
298
299WGPUBuffer float_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, FloatArray data)
300{
301 // Deallocate old buffer if any
302 clear_buffer(buffer);
303 // Copy data to new buffer
304 buffer = create_buffer(window, data.length * sizeof(float), data.data, WGPUBufferUsage_Vertex);
305 // Clear local copy for next frame
306 float_array_clear(&data);
307 return buffer;
308}
309
310WGPUBuffer uint32_update_buffer(const AlbaWindow* window, WGPUBuffer buffer, Uint32Array data)
311{
312 // Deallocate old buffer if any
313 clear_buffer(buffer);
314 // Copy data to new buffer
315 buffer = create_buffer(window, data.length * sizeof(uint32_t), data.data, WGPUBufferUsage_Index);
316 // Clear local copy for next frame
317 uint32_array_clear(&data);
318 return buffer;
319}
320
321void window_render(AlbaWindow* window)
322{
323 if (window->dirty)
324 {
325 window->vertices = float_update_buffer(window, window->vertices, window->new_vertices);
326 window->attributes = float_update_buffer(window, window->attributes, window->new_attributes);
327 window->indices = uint32_update_buffer(window, window->indices, window->new_indices);
328 window->dirty = 0;
329 }
330
331 WGPUSurfaceTexture frame;
332 wgpuSurfaceGetCurrentTexture(window->surface, &frame);
333 if (!frame_status_is_valid(window, frame))
334 {
335 // Re-configure surface and skip frame
336 if (frame.texture != NULL)
337 {
338 wgpuTextureRelease(frame.texture);
339 }
340
341 configure_surface(window);
342 return;
343 }
344
345 const WGPUTextureView view = wgpuTextureCreateView(frame.texture, NULL);
346 if (view == NULL)
347 {
348 printf("warning: could not get frame view, dropping frame (%d)\n", frame.status);
349 return;
350 }
351
352 const WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(window->device, NULL);
353
354 // Configures rendering
355 WGPURenderPassColorAttachment render_pass_attachment_options = {0};
356 render_pass_attachment_options.view = view;
357 render_pass_attachment_options.loadOp = WGPULoadOp_Clear;
358 render_pass_attachment_options.storeOp = WGPUStoreOp_Store;
359 copy_color(window->options.clear_color, &render_pass_attachment_options.clearValue);
360
361 WGPURenderPassDescriptor render_pass_options = {0};
362 render_pass_options.colorAttachmentCount = 1;
363 render_pass_options.colorAttachments = &render_pass_attachment_options;
364
365 // Draw calls
366 const WGPURenderPassEncoder render_pass = wgpuCommandEncoderBeginRenderPass(
367 encoder, &render_pass_options);
368 wgpuRenderPassEncoderSetPipeline(render_pass, window->pipeline);
369
370 const uint32_t vertex_size = wgpuBufferGetSize(window->vertices);
371 const uint32_t attributes_size = wgpuBufferGetSize(window->attributes);
372 const uint32_t indices_size = wgpuBufferGetSize(window->indices);
373 if (vertex_size > 0)
374 {
375 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 0, window->vertices, 0, vertex_size);
376 wgpuRenderPassEncoderSetVertexBuffer(render_pass, 1, window->attributes, 0, attributes_size);
377 wgpuRenderPassEncoderSetIndexBuffer(render_pass, window->indices, WGPUIndexFormat_Uint32, 0, indices_size);
378 wgpuRenderPassEncoderDrawIndexed(render_pass, indices_size / sizeof(uint32_t), 1, 0, 0, 0);
379 }
380 wgpuRenderPassEncoderEnd(render_pass);
381
382 // Encode render command and send to GPU
383 const WGPUCommandBuffer command = wgpuCommandEncoderFinish(encoder, NULL);
384 wgpuQueueSubmit(window->queue, 1, &command);
385
386 // Update screen
387 wgpuSurfacePresent(window->surface);
388
389 wgpuCommandBufferRelease(command);
390 wgpuRenderPassEncoderRelease(render_pass);
391 wgpuCommandEncoderRelease(encoder);
392 wgpuTextureViewRelease(view);
393 wgpuTextureRelease(frame.texture);
394}