The Power of Calibrated Timestamps: Empowering Vulkan and WebGPU

In the ever-growing world of graphics APIs, Vulkan and WebGPU stand out as two powerful platforms for unleashing the potential of modern graphics hardware. A key feature that enhances the efficiency and accuracy of these APIs is the calibrated timestamp. In this blog post, let’s explore the significance and advantages offered by calibrated timestamps in Vulkan and WebGPU, and how they contribute to improved performance and visual quality.

  1. What are Calibrated Timestamps? Timestamps are essential for gaining insights into the performance of graphics applications. They enable developers to measure various stages of rendering, such as the time taken for specific commands to execute or the synchronization between multiple GPU operations. Calibrated timestamps go a step further by providing not only time measurements but also accurate synchronization across different hardware devices.
  2. Benefits of Calibrated Timestamps: a. Accurate GPU Profiling: Calibrated timestamps enable precise profiling of GPU workloads, allowing developers to identify performance bottlenecks and optimize their applications accordingly. With accurate timestamps, developers can dive deep into the rendering pipeline, pinpointing areas where optimizations can be made for better frame rates and responsiveness.

b. Synchronization Across Devices: In multi-GPU setups or distributed rendering systems, calibrated timestamps play a vital role in synchronizing operations across different graphics devices. This synchronization ensures consistent and seamless rendering across multiple GPUs, reducing frame variability and potential visual artifacts.

c. Performance Analysis and Debugging: Calibrated timestamps enhance the ability to analyze performance issues and debugging with high precision. By comparing timestamps at different stages, developers can identify potential synchronization or dependency issues, allowing for efficient debugging and optimization.

  1. Vulkan and Calibrated Timestamps: Vulkan, a cross-platform graphics API, incorporates calibrated timestamps as part of its core functionality. Vulkan provides a mechanism to query for accurate hardware timestamps, enabling developers to measure GPU workloads precisely. This feature allows for detailed performance analysis, synchronization, and debugging within the Vulkan ecosystem.
  2. WebGPU: Bringing Calibrated Timestamps to the Web: WebGPU, the emerging web standard for low-level graphics and compute, aims to bridge the gap between native applications and the web. With its design influenced by Vulkan and Metal, WebGPU also embraces calibrated timestamps to cater to developers’ needs. Bringing calibrated timestamps to the browser opens up new possibilities for advanced web-based gaming, virtual reality experiences, and complex visualizations.
  3. Industry Adoption and Future Prospects: Calibrated timestamps have gained significant traction within the developer community due to the insights they provide into GPU workloads. Many game engines, graphics tools, and profiling frameworks have integrated support for calibrated timestamps, further propelling their adoption. As Vulkan and WebGPU continue to gain popularity, calibrated timestamps will play an indispensable role in maximizing graphic performance across various platforms and devices.

Samples

Vulkan

#include <stdio.h>
#include <stdlib.h>
#include <vulkan/vulkan.h>

int main() {
    VkInstance instance;
    VkPhysicalDevice physicalDevice;
    VkDevice device;

    // Define the required extension and enable it
    const char* const extensionName = "VK_EXT_calibrated_timestamps";

    VkInstanceCreateInfo instanceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .enabledExtensionCount = 1,
        .ppEnabledExtensionNames = &extensionName
    };

    vkCreateInstance(&instanceCreateInfo, NULL, &instance);

    // Query and select the physical device
    uint32_t deviceCount = 0;
    vkEnumeratePhysicalDevices(instance, &deviceCount, NULL);

    VkPhysicalDevice* devices = malloc(deviceCount * sizeof(VkPhysicalDevice));
    vkEnumeratePhysicalDevices(instance, &deviceCount, devices);

    physicalDevice = devices[0]; // Select the first device

    free(devices);

    // Create the logical device
    VkDeviceCreateInfo deviceCreateInfo = {
        .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
        .queueCreateInfoCount = 0,
        .enabledExtensionCount = 1,
        .ppEnabledExtensionNames = &extensionName
    };

    vkCreateDevice(physicalDevice, &deviceCreateInfo, NULL, &device);

    // Check if the extension is supported
    VkPhysicalDeviceProperties physicalDeviceProperties;
    vkGetPhysicalDeviceProperties(physicalDevice, &physicalDeviceProperties);

    if (physicalDeviceProperties.deviceExtensionProperties == NULL) {
        printf("VK_EXT_calibrated_timestamps not supported!\n");
        return 1;
    }

    // Use the VK_EXT_calibrated_timestamps extension
    // (perform your application-specific operations here)

    // Clean up resources
    vkDestroyDevice(device, NULL);
    vkDestroyInstance(instance, NULL);

    return 0;
}


WebGPU

// Create a GPUDevice and a GPUCanvasContext

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const context = canvas.getContext('gpupresent');

// Create a GPUBindGroupLayout, GPUPipelineLayout, and GPUComputePipeline

const bindGroupLayout = /* create GPUBindGroupLayout */;
const pipelineLayout = /* create GPUPipelineLayout */;
const computePipeline = device.createComputePipeline({
    layout: pipelineLayout,
    compute: {
        module: /* create GPUShaderModule */,
        entryPoint: 'main'
    }
});

// Create a timestamp query set

const timestampQuerySet = device.createQuerySet({
    type: 'timestamp',
    count: 2
});

// Create a buffer to store the results of the timestamp query

const timestampResultBuffer = device.createBuffer({
    size: 8,
    usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.QUERY_RESOLVE
});

// Encode commands

const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();

// Begin timestamp query

pass.writeTimestamp(timestampQuerySet, 0);

// Encode compute shader dispatch

pass.setPipeline(computePipeline);
pass.setBindGroup(0, /* create GPUBindGroup */);
pass.dispatch(/* compute dimensions */);

// End timestamp query

pass.writeTimestamp(timestampQuerySet, 1);

// Resolve timestamp query

pass.resolveQuerySet(timestampQuerySet, 0, 2, timestampResultBuffer, 0);

pass.endPass();

const cmdBuffer = encoder.finish();
const queue = device.defaultQueue;

// Submit commands

queue.submit([cmdBuffer]);

// Read the results of the timestamp query

const arrayBuffer = await timestampResultBuffer.mapReadAsync();
const uint32Array = new Uint32Array(arrayBuffer);
const startTime = uint32Array[0];
const endTime = uint32Array[1];
const elapsedTime = (endTime - startTime) / device.timestampPeriod;

console.log(`Elapsed time: ${elapsedTime.toFixed(2)} ms`);

Links
https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API
https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/calibrated_timestamps