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}