//! A native webgpu.h implementation of the gpu.Interface
const std = @import("std");
const c = @import("c.zig").c;

const Interface = @import("Interface.zig");
const RequestAdapterOptions = Interface.RequestAdapterOptions;
const RequestAdapterErrorCode = Interface.RequestAdapterErrorCode;
const RequestAdapterError = Interface.RequestAdapterError;
const RequestAdapterCallback = Interface.RequestAdapterCallback;
const RequestAdapterResponse = Interface.RequestAdapterResponse;

const Adapter = @import("Adapter.zig");
const RequestDeviceErrorCode = Adapter.RequestDeviceErrorCode;
const RequestDeviceError = Adapter.RequestDeviceError;
const RequestDeviceCallback = Adapter.RequestDeviceCallback;
const RequestDeviceResponse = Adapter.RequestDeviceResponse;

const Limits = @import("data.zig").Limits;
const Color = @import("data.zig").Color;
const Extent3D = @import("data.zig").Extent3D;

const Device = @import("Device.zig");
const Surface = @import("Surface.zig");
const Queue = @import("Queue.zig");
const CommandBuffer = @import("CommandBuffer.zig");
const ShaderModule = @import("ShaderModule.zig");
const SwapChain = @import("SwapChain.zig");
const TextureView = @import("TextureView.zig");
const Texture = @import("Texture.zig");
const Sampler = @import("Sampler.zig");
const RenderPipeline = @import("RenderPipeline.zig");
const RenderPassEncoder = @import("RenderPassEncoder.zig");
const RenderBundleEncoder = @import("RenderBundleEncoder.zig");
const RenderBundle = @import("RenderBundle.zig");
const QuerySet = @import("QuerySet.zig");
const PipelineLayout = @import("PipelineLayout.zig");
const ExternalTexture = @import("ExternalTexture.zig");
const BindGroup = @import("BindGroup.zig");
const BindGroupLayout = @import("BindGroupLayout.zig");
const Buffer = @import("Buffer.zig");
const CommandEncoder = @import("CommandEncoder.zig");
const ComputePassEncoder = @import("ComputePassEncoder.zig");
const ComputePipeline = @import("ComputePipeline.zig");

const PresentMode = @import("enums.zig").PresentMode;
const IndexFormat = @import("enums.zig").IndexFormat;
const ErrorType = @import("enums.zig").ErrorType;
const ErrorFilter = @import("enums.zig").ErrorFilter;
const LoggingType = @import("enums.zig").LoggingType;
const Feature = @import("enums.zig").Feature;

const ImageCopyBuffer = @import("structs.zig").ImageCopyBuffer;
const ImageCopyTexture = @import("structs.zig").ImageCopyTexture;
const ErrorCallback = @import("structs.zig").ErrorCallback;
const LoggingCallback = @import("structs.zig").LoggingCallback;

const NativeInstance = @This();

/// The WGPUInstance that is wrapped by this native instance.
instance: c.WGPUInstance,

/// Wraps a native WGPUInstance to provide an implementation of the gpu.Interface.
pub fn wrap(instance: *anyopaque) NativeInstance {
    return .{ .instance = @ptrCast(c.WGPUInstance, instance) };
}

const interface_vtable = Interface.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            const native = @ptrCast(*NativeInstance, @alignCast(std.meta.alignment(*NativeInstance), ptr));
            c.wgpuInstanceReference(native.instance);
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            const native = @ptrCast(*NativeInstance, @alignCast(std.meta.alignment(*NativeInstance), ptr));
            c.wgpuInstanceRelease(native.instance);
        }
    }).release,
    .requestAdapter = (struct {
        pub fn requestAdapter(
            ptr: *anyopaque,
            options: *const RequestAdapterOptions,
            callback: *RequestAdapterCallback,
        ) void {
            const native = @ptrCast(*NativeInstance, @alignCast(std.meta.alignment(*NativeInstance), ptr));

            const opt = c.WGPURequestAdapterOptions{
                .nextInChain = null,
                .compatibleSurface = if (options.compatible_surface) |surface| @ptrCast(c.WGPUSurface, surface.ptr) else null,
                .powerPreference = @enumToInt(options.power_preference),
                .forceFallbackAdapter = options.force_fallback_adapter,
            };

            const cCallback = (struct {
                pub fn cCallback(status: c.WGPURequestAdapterStatus, adapter: c.WGPUAdapter, message: [*c]const u8, userdata: ?*anyopaque) callconv(.C) void {
                    const callback_info = @ptrCast(*RequestAdapterCallback, @alignCast(std.meta.alignment(*RequestAdapterCallback), userdata.?));

                    // Store the response into a field on the native instance for later reading.
                    const response = if (status == c.WGPURequestAdapterStatus_Success) RequestAdapterResponse{
                        .adapter = wrapAdapter(adapter.?),
                    } else RequestAdapterResponse{
                        .err = Interface.RequestAdapterError{
                            .message = std.mem.span(message),
                            .code = switch (status) {
                                c.WGPURequestAdapterStatus_Unavailable => RequestAdapterErrorCode.Unavailable,
                                c.WGPURequestAdapterStatus_Error => RequestAdapterErrorCode.Error,
                                c.WGPURequestAdapterStatus_Unknown => RequestAdapterErrorCode.Unknown,
                                else => unreachable,
                            },
                        },
                    };

                    callback_info.type_erased_callback(callback_info.type_erased_ctx, response);
                }
            }).cCallback;

            c.wgpuInstanceRequestAdapter(native.instance, &opt, cCallback, callback);
        }
    }).requestAdapter,
};

/// Returns the gpu.Interface for interacting with this native instance.
pub fn interface(native: *NativeInstance) Interface {
    return .{
        .ptr = native,
        .vtable = &interface_vtable,
    };
}

pub fn createSurface(native: *const NativeInstance, descriptor: *const Surface.Descriptor) Surface {
    const surface = switch (descriptor.*) {
        .metal_layer => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromMetalLayer = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromMetalLayer;
            desc.layer = src.layer;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
        .windows_hwnd => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromWindowsHWND = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromWindowsHWND;
            desc.hinstance = src.hinstance;
            desc.hwnd = src.hwnd;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
        .windows_core_window => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromWindowsCoreWindow = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromWindowsCoreWindow;
            desc.coreWindow = src.core_window;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
        .windows_swap_chain_panel => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromWindowsSwapChainPanel = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromWindowsSwapChainPanel;
            desc.swapChainPanel = src.swap_chain_panel;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
        .xlib => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromXlibWindow = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromXlibWindow;
            desc.display = src.display;
            desc.window = src.window;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
        .canvas_html_selector => |src| blk: {
            var desc: c.WGPUSurfaceDescriptorFromCanvasHTMLSelector = undefined;
            desc.chain.next = null;
            desc.chain.sType = c.WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector;
            desc.selector = src.selector;
            break :blk c.wgpuInstanceCreateSurface(native.instance, &c.WGPUSurfaceDescriptor{
                .nextInChain = @ptrCast(*c.WGPUChainedStruct, &desc),
                .label = if (src.label) |l| l else null,
            });
        },
    };

    return Surface{
        .ptr = surface.?,
        .vtable = &surface_vtable,
    };
}

const surface_vtable = Surface.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuSurfaceReference(@ptrCast(c.WGPUSurface, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuSurfaceRelease(@ptrCast(c.WGPUSurface, ptr));
        }
    }).release,
};

pub inline fn fromWGPUAdapter(adapter: *anyopaque) Adapter {
    return wrapAdapter(@ptrCast(c.WGPUAdapter, adapter));
}

pub fn wrapAdapter(adapter: c.WGPUAdapter) Adapter {
    var c_props: c.WGPUAdapterProperties = undefined;
    c.wgpuAdapterGetProperties(adapter, &c_props);
    const properties = Adapter.Properties{
        .vendor_id = c_props.vendorID,
        .device_id = c_props.deviceID,
        .name = std.mem.span(c_props.name),
        .driver_description = std.mem.span(c_props.driverDescription),
        .adapter_type = @intToEnum(Adapter.Type, c_props.adapterType),
        .backend_type = @intToEnum(Adapter.BackendType, c_props.backendType),
    };

    var supported_limits: c.WGPUSupportedLimits = undefined;
    supported_limits.nextInChain = null;
    if (!c.wgpuAdapterGetLimits(adapter.?, &supported_limits)) @panic("failed to get adapter limits (this is a bug in mach/gpu)");

    var wrapped = Adapter{
        .features = undefined,
        .limits = @bitCast(Limits, supported_limits.limits),
        .properties = properties,

        // TODO: why is fallback not queryable on Dawn?
        .fallback = false,

        .ptr = adapter.?,
        .vtable = &adapter_vtable,
    };

    const features_len = c.wgpuAdapterEnumerateFeatures(adapter.?, @ptrCast([*]c.WGPUFeatureName, &wrapped._features));
    wrapped.features = wrapped._features[0..features_len];
    return wrapped;
}

const adapter_vtable = Adapter.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuAdapterReference(@ptrCast(c.WGPUAdapter, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuAdapterRelease(@ptrCast(c.WGPUAdapter, ptr));
        }
    }).release,
    .requestDevice = (struct {
        pub fn requestDevice(
            ptr: *anyopaque,
            descriptor: *const Device.Descriptor,
            callback: *RequestDeviceCallback,
        ) void {
            const adapter = @ptrCast(c.WGPUAdapter, @alignCast(@alignOf(c.WGPUAdapter), ptr));

            const required_limits = if (descriptor.required_limits) |l| c.WGPURequiredLimits{
                .nextInChain = null,
                .limits = @bitCast(c.WGPULimits, l),
            } else null;

            const desc = c.WGPUDeviceDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .requiredFeaturesCount = if (descriptor.required_features) |f| @intCast(u32, f.len) else 0,
                .requiredFeatures = if (descriptor.required_features) |f| @ptrCast([*]const c_uint, f.ptr) else null,
                .requiredLimits = if (required_limits) |*l| l else null,
                .defaultQueue = if (descriptor.default_queue) |q| .{ .nextInChain = null, .label = q.label } else .{ .nextInChain = null, .label = null },
            };

            const cCallback = (struct {
                pub fn cCallback(status: c.WGPURequestDeviceStatus, device: c.WGPUDevice, message: [*c]const u8, userdata: ?*anyopaque) callconv(.C) void {
                    const callback_info = @ptrCast(*RequestDeviceCallback, @alignCast(std.meta.alignment(*RequestDeviceCallback), userdata.?));

                    const response = if (status == c.WGPURequestDeviceStatus_Success) RequestDeviceResponse{
                        .device = wrapDevice(device.?),
                    } else RequestDeviceResponse{
                        .err = Adapter.RequestDeviceError{
                            .message = std.mem.span(message),
                            .code = switch (status) {
                                c.WGPURequestDeviceStatus_Error => RequestDeviceErrorCode.Error,
                                c.WGPURequestDeviceStatus_Unknown => RequestDeviceErrorCode.Unknown,
                                else => unreachable,
                            },
                        },
                    };

                    callback_info.type_erased_callback(callback_info.type_erased_ctx, response);
                }
            }).cCallback;

            c.wgpuAdapterRequestDevice(adapter, &desc, cCallback, callback);
        }
    }).requestDevice,
};

fn wrapDevice(device: c.WGPUDevice) Device {
    var supported_limits: c.WGPUSupportedLimits = undefined;
    supported_limits.nextInChain = null;
    if (!c.wgpuDeviceGetLimits(device.?, &supported_limits)) @panic("failed to get device limits (this is a bug in mach/gpu)");

    var wrapped = Device{
        .features = undefined,
        .limits = @bitCast(Limits, supported_limits.limits),
        .ptr = device.?,
        .vtable = &device_vtable,
    };

    const features_len = c.wgpuDeviceEnumerateFeatures(device.?, @ptrCast([*]c.WGPUFeatureName, &wrapped._features));
    wrapped.features = wrapped._features[0..features_len];
    return wrapped;
}

const device_vtable = Device.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuDeviceReference(@ptrCast(c.WGPUDevice, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuDeviceRelease(@ptrCast(c.WGPUDevice, ptr));
        }
    }).release,
    .getQueue = (struct {
        pub fn getQueue(ptr: *anyopaque) Queue {
            return wrapQueue(c.wgpuDeviceGetQueue(@ptrCast(c.WGPUDevice, ptr)));
        }
    }).getQueue,
    .injectError = (struct {
        pub fn injectError(ptr: *anyopaque, typ: ErrorType, message: [*:0]const u8) void {
            c.wgpuDeviceInjectError(@ptrCast(c.WGPUDevice, ptr), @enumToInt(typ), message);
        }
    }).injectError,
    .loseForTesting = (struct {
        pub fn loseForTesting(ptr: *anyopaque) void {
            c.wgpuDeviceLoseForTesting(@ptrCast(c.WGPUDevice, ptr));
        }
    }).loseForTesting,
    .popErrorScope = (struct {
        pub fn popErrorScope(ptr: *anyopaque, callback: *ErrorCallback) bool {
            const cCallback = (struct {
                pub fn cCallback(
                    typ: c.WGPUErrorType,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*ErrorCallback, @alignCast(std.meta.alignment(*ErrorCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(ErrorType, typ),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            return c.wgpuDevicePopErrorScope(
                @ptrCast(c.WGPUDevice, ptr),
                cCallback,
                callback,
            );
        }
    }).popErrorScope,
    .createBindGroup = (struct {
        pub fn createBindGroup(ptr: *anyopaque, descriptor: *const BindGroup.Descriptor) BindGroup {
            var few_entries: [16]c.WGPUBindGroupEntry = undefined;
            const entries = if (descriptor.entries.len <= 8)
                few_entries[0..descriptor.entries.len]
            else
                std.heap.page_allocator.alloc(c.WGPUBindGroupEntry, descriptor.entries.len) catch unreachable;
            defer if (entries.len > 8) std.heap.page_allocator.free(entries);

            for (descriptor.entries) |entry, i| {
                entries[i] = c.WGPUBindGroupEntry{
                    .nextInChain = null,
                    .binding = entry.binding,
                    .buffer = if (entry.buffer) |buf|
                        @ptrCast(c.WGPUBuffer, buf.ptr)
                    else
                        null,
                    .offset = entry.offset,
                    .size = entry.size,
                    .sampler = if (entry.sampler) |samp|
                        @ptrCast(c.WGPUSampler, samp.ptr)
                    else
                        null,
                    .textureView = if (entry.texture_view) |tex|
                        @ptrCast(c.WGPUTextureView, tex.ptr)
                    else
                        null,
                };
            }

            const desc = c.WGPUBindGroupDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .layout = @ptrCast(c.WGPUBindGroupLayout, descriptor.layout.ptr),
                .entryCount = @intCast(u32, entries.len),
                .entries = entries.ptr,
            };

            return wrapBindGroup(c.wgpuDeviceCreateBindGroup(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createBindGroup,
    .pushErrorScope = (struct {
        pub fn pushErrorScope(ptr: *anyopaque, filter: ErrorFilter) void {
            c.wgpuDevicePushErrorScope(@ptrCast(c.WGPUDevice, ptr), @enumToInt(filter));
        }
    }).pushErrorScope,
    .setLostCallback = (struct {
        pub fn setLostCallback(ptr: *anyopaque, callback: *Device.LostCallback) void {
            const cCallback = (struct {
                pub fn cCallback(
                    reason: c.WGPUDeviceLostReason,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*Device.LostCallback, @alignCast(std.meta.alignment(*Device.LostCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(Device.LostReason, reason),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            c.wgpuDeviceSetDeviceLostCallback(
                @ptrCast(c.WGPUDevice, ptr),
                cCallback,
                callback,
            );
        }
    }).setLostCallback,
    .createBindGroupLayout = (struct {
        pub fn createBindGroupLayout(ptr: *anyopaque, descriptor: *const BindGroupLayout.Descriptor) BindGroupLayout {
            const desc = c.WGPUBindGroupLayoutDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .entryCount = @intCast(u32, descriptor.entries.len),
                .entries = @ptrCast([*]const c.WGPUBindGroupLayoutEntry, descriptor.entries.ptr),
            };
            return wrapBindGroupLayout(c.wgpuDeviceCreateBindGroupLayout(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createBindGroupLayout,
    .createSampler = (struct {
        pub fn createSampler(ptr: *anyopaque, descriptor: *const Sampler.Descriptor) Sampler {
            return wrapSampler(c.wgpuDeviceCreateSampler(
                @ptrCast(c.WGPUDevice, ptr),
                @ptrCast(*const c.WGPUSamplerDescriptor, descriptor),
            ));
        }
    }).createSampler,
    .createShaderModule = (struct {
        pub fn createShaderModule(ptr: *anyopaque, descriptor: *const ShaderModule.Descriptor) ShaderModule {
            switch (descriptor.code) {
                .wgsl => |wgsl| {
                    const wgsl_desc = c.WGPUShaderModuleWGSLDescriptor{
                        .chain = c.WGPUChainedStruct{
                            .next = null,
                            .sType = c.WGPUSType_ShaderModuleWGSLDescriptor,
                        },
                        .source = wgsl,
                    };
                    const desc = c.WGPUShaderModuleDescriptor{
                        .nextInChain = @ptrCast(*const c.WGPUChainedStruct, &wgsl_desc),
                        .label = if (descriptor.label) |l| l else null,
                    };
                    return wrapShaderModule(c.wgpuDeviceCreateShaderModule(@ptrCast(c.WGPUDevice, ptr), &desc));
                },
                .spirv => |spirv| {
                    const spirv_desc = c.WGPUShaderModuleSPIRVDescriptor{
                        .chain = c.WGPUChainedStruct{
                            .next = null,
                            .sType = c.WGPUSType_ShaderModuleSPIRVDescriptor,
                        },
                        .code = spirv.ptr,
                        .codeSize = @intCast(u32, spirv.len),
                    };
                    const desc = c.WGPUShaderModuleDescriptor{
                        .nextInChain = @ptrCast(*const c.WGPUChainedStruct, &spirv_desc),
                        .label = if (descriptor.label) |l| l else null,
                    };
                    return wrapShaderModule(c.wgpuDeviceCreateShaderModule(@ptrCast(c.WGPUDevice, ptr), &desc));
                },
            }
        }
    }).createShaderModule,
    .nativeCreateSwapChain = (struct {
        pub fn nativeCreateSwapChain(ptr: *anyopaque, surface: ?Surface, descriptor: *const SwapChain.Descriptor) SwapChain {
            const desc = c.WGPUSwapChainDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .usage = @bitCast(u32, descriptor.usage),
                .format = @enumToInt(descriptor.format),
                .width = descriptor.width,
                .height = descriptor.height,
                .presentMode = @enumToInt(descriptor.present_mode),
                .implementation = descriptor.implementation,
            };
            return wrapSwapChain(c.wgpuDeviceCreateSwapChain(
                @ptrCast(c.WGPUDevice, ptr),
                if (surface) |surf| @ptrCast(c.WGPUSurface, surf.ptr) else null,
                &desc,
            ));
        }
    }).nativeCreateSwapChain,
    .createTexture = (struct {
        pub fn createTexture(ptr: *anyopaque, descriptor: *const Texture.Descriptor) Texture {
            const desc = c.WGPUTextureDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .usage = @bitCast(u32, descriptor.usage),
                .dimension = @enumToInt(descriptor.dimension),
                .size = @bitCast(c.WGPUExtent3D, descriptor.size),
                .format = @enumToInt(descriptor.format),
                .mipLevelCount = descriptor.mip_level_count,
                .sampleCount = descriptor.sample_count,
                .viewFormatCount = if (descriptor.view_formats) |vf| @intCast(u32, vf.len) else 0,
                .viewFormats = if (descriptor.view_formats) |vf| @ptrCast([*]const c.WGPUTextureFormat, vf.ptr) else null,
            };
            return wrapTexture(c.wgpuDeviceCreateTexture(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createTexture,
    .destroy = (struct {
        pub fn destroy(ptr: *anyopaque) void {
            c.wgpuDeviceDestroy(@ptrCast(c.WGPUDevice, ptr));
        }
    }).destroy,
    .createBuffer = (struct {
        pub fn createBuffer(ptr: *anyopaque, descriptor: *const Buffer.Descriptor) Buffer {
            return wrapBuffer(c.wgpuDeviceCreateBuffer(
                @ptrCast(c.WGPUDevice, ptr),
                @ptrCast(*const c.WGPUBufferDescriptor, descriptor),
            ));
        }
    }).createBuffer,
    .createCommandEncoder = (struct {
        pub fn createCommandEncoder(ptr: *anyopaque, descriptor: ?*const CommandEncoder.Descriptor) CommandEncoder {
            const desc: ?*c.WGPUCommandEncoderDescriptor = if (descriptor) |d| &.{
                .nextInChain = null,
                .label = if (d.label) |l| l else "",
            } else null;
            return wrapCommandEncoder(c.wgpuDeviceCreateCommandEncoder(@ptrCast(c.WGPUDevice, ptr), desc));
        }
    }).createCommandEncoder,
    .createComputePipeline = (struct {
        pub fn createComputePipeline(
            ptr: *anyopaque,
            descriptor: *const ComputePipeline.Descriptor,
        ) ComputePipeline {
            const desc = convertComputePipelineDescriptor(descriptor);

            return wrapComputePipeline(c.wgpuDeviceCreateComputePipeline(
                @ptrCast(c.WGPUDevice, ptr),
                &desc,
            ));
        }
    }).createComputePipeline,
    .createComputePipelineAsync = (struct {
        pub fn createComputePipelineAsync(
            ptr: *anyopaque,
            descriptor: *const ComputePipeline.Descriptor,
            callback: *ComputePipeline.CreateCallback,
        ) void {
            const desc = convertComputePipelineDescriptor(descriptor);

            const cCallback = (struct {
                pub fn cCallback(
                    status: c.WGPUCreatePipelineAsyncStatus,
                    pipeline: c.WGPUComputePipeline,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*ComputePipeline.CreateCallback, @alignCast(std.meta.alignment(*ComputePipeline.CreateCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(ComputePipeline.CreateStatus, status),
                        wrapComputePipeline(pipeline),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            c.wgpuDeviceCreateComputePipelineAsync(
                @ptrCast(c.WGPUDevice, ptr),
                &desc,
                cCallback,
                callback,
            );
        }
    }).createComputePipelineAsync,
    .createErrorBuffer = (struct {
        pub fn createErrorBuffer(ptr: *anyopaque) Buffer {
            return wrapBuffer(c.wgpuDeviceCreateErrorBuffer(
                @ptrCast(c.WGPUDevice, ptr),
            ));
        }
    }).createErrorBuffer,
    .createExternalTexture = (struct {
        pub fn createExternalTexture(ptr: *anyopaque, descriptor: *const ExternalTexture.Descriptor) ExternalTexture {
            const desc = c.WGPUExternalTextureDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .plane0 = @ptrCast(c.WGPUTextureView, descriptor.plane0.ptr),
                .plane1 = @ptrCast(c.WGPUTextureView, descriptor.plane1.ptr),
                .colorSpace = @enumToInt(descriptor.color_space),
            };
            return wrapExternalTexture(c.wgpuDeviceCreateExternalTexture(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createExternalTexture,
    .createPipelineLayout = (struct {
        pub fn createPipelineLayout(ptr: *anyopaque, descriptor: *const PipelineLayout.Descriptor) PipelineLayout {
            var few_bind_group_layouts: [16]c.WGPUBindGroupLayout = undefined;
            const bind_group_layouts = if (descriptor.bind_group_layouts.len <= 16) blk: {
                for (descriptor.bind_group_layouts) |layout, i| {
                    few_bind_group_layouts[i] = @ptrCast(c.WGPUBindGroupLayout, layout.ptr);
                }
                break :blk few_bind_group_layouts[0..descriptor.bind_group_layouts.len];
            } else blk: {
                const mem = std.heap.page_allocator.alloc(c.WGPUBindGroupLayout, descriptor.bind_group_layouts.len) catch unreachable;
                for (descriptor.bind_group_layouts) |layout, i| {
                    mem[i] = @ptrCast(c.WGPUBindGroupLayout, layout.ptr);
                }
                break :blk mem;
            };
            defer if (descriptor.bind_group_layouts.len > 16) std.heap.page_allocator.free(descriptor.bind_group_layouts);

            const desc = c.WGPUPipelineLayoutDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .bindGroupLayoutCount = @intCast(u32, bind_group_layouts.len),
                .bindGroupLayouts = bind_group_layouts.ptr,
            };
            return wrapPipelineLayout(c.wgpuDeviceCreatePipelineLayout(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createPipelineLayout,
    .createQuerySet = (struct {
        pub fn createQuerySet(ptr: *anyopaque, descriptor: *const QuerySet.Descriptor) QuerySet {
            const desc = c.WGPUQuerySetDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .type = @enumToInt(descriptor.type),
                .count = descriptor.count,
                .pipelineStatistics = @ptrCast([*]const c.WGPUPipelineStatisticName, descriptor.pipeline_statistics.ptr),
                .pipelineStatisticsCount = @intCast(u32, descriptor.pipeline_statistics.len),
            };
            return wrapQuerySet(c.wgpuDeviceCreateQuerySet(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createQuerySet,
    .createRenderBundleEncoder = (struct {
        pub fn createRenderBundleEncoder(ptr: *anyopaque, descriptor: *const RenderBundleEncoder.Descriptor) RenderBundleEncoder {
            const desc = c.WGPURenderBundleEncoderDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
                .colorFormatsCount = @intCast(u32, descriptor.color_formats.len),
                .colorFormats = @ptrCast([*]const c.WGPUTextureFormat, descriptor.color_formats.ptr),
                .depthStencilFormat = @enumToInt(descriptor.depth_stencil_format),
                .sampleCount = descriptor.sample_count,
                .depthReadOnly = descriptor.depth_read_only,
                .stencilReadOnly = descriptor.stencil_read_only,
            };
            return wrapRenderBundleEncoder(c.wgpuDeviceCreateRenderBundleEncoder(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createRenderBundleEncoder,
    .createRenderPipeline = (struct {
        pub fn createRenderPipeline(ptr: *anyopaque, descriptor: *const RenderPipeline.Descriptor) RenderPipeline {
            var tmp_depth_stencil: c.WGPUDepthStencilState = undefined;
            var tmp_fragment_state: c.WGPUFragmentState = undefined;
            const desc = convertRenderPipelineDescriptor(descriptor, &tmp_depth_stencil, &tmp_fragment_state);
            return wrapRenderPipeline(c.wgpuDeviceCreateRenderPipeline(@ptrCast(c.WGPUDevice, ptr), &desc));
        }
    }).createRenderPipeline,
    .createRenderPipelineAsync = (struct {
        pub fn createRenderPipelineAsync(
            ptr: *anyopaque,
            descriptor: *const RenderPipeline.Descriptor,
            callback: *RenderPipeline.CreateCallback,
        ) void {
            var tmp_depth_stencil: c.WGPUDepthStencilState = undefined;
            var tmp_fragment_state: c.WGPUFragmentState = undefined;
            const desc = convertRenderPipelineDescriptor(descriptor, &tmp_depth_stencil, &tmp_fragment_state);

            const cCallback = (struct {
                pub fn cCallback(
                    status: c.WGPUCreatePipelineAsyncStatus,
                    pipeline: c.WGPURenderPipeline,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*RenderPipeline.CreateCallback, @alignCast(std.meta.alignment(*RenderPipeline.CreateCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(RenderPipeline.CreateStatus, status),
                        wrapRenderPipeline(pipeline),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            c.wgpuDeviceCreateRenderPipelineAsync(
                @ptrCast(c.WGPUDevice, ptr),
                &desc,
                cCallback,
                callback,
            );
        }
    }).createRenderPipelineAsync,
    .setUncapturedErrorCallback = (struct {
        pub fn setUncapturedErrorCallback(
            ptr: *anyopaque,
            callback: *ErrorCallback,
        ) void {
            const cCallback = (struct {
                pub fn cCallback(
                    typ: c.WGPUErrorType,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*ErrorCallback, @alignCast(std.meta.alignment(*ErrorCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(ErrorType, typ),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            return c.wgpuDeviceSetUncapturedErrorCallback(
                @ptrCast(c.WGPUDevice, ptr),
                cCallback,
                callback,
            );
        }
    }).setUncapturedErrorCallback,
    .setLoggingCallback = (struct {
        pub fn setLoggingCallback(
            ptr: *anyopaque,
            callback: *LoggingCallback,
        ) void {
            const cCallback = (struct {
                pub fn cCallback(
                    typ: c.WGPULoggingType,
                    message: [*c]const u8,
                    userdata: ?*anyopaque,
                ) callconv(.C) void {
                    const callback_info = @ptrCast(*LoggingCallback, @alignCast(std.meta.alignment(*LoggingCallback), userdata));
                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(LoggingType, typ),
                        std.mem.span(message),
                    );
                }
            }).cCallback;

            return c.wgpuDeviceSetLoggingCallback(
                @ptrCast(c.WGPUDevice, ptr),
                cCallback,
                callback,
            );
        }
    }).setLoggingCallback,
    .tick = (struct {
        pub fn tick(ptr: *anyopaque) void {
            c.wgpuDeviceTick(@ptrCast(c.WGPUDevice, ptr));
        }
    }.tick),
};

inline fn convertComputePipelineDescriptor(descriptor: *const ComputePipeline.Descriptor) c.WGPUComputePipelineDescriptor {
    return .{
        .nextInChain = null,
        .label = if (descriptor.label) |l| l else null,
        .layout = if (descriptor.layout) |l| @ptrCast(c.WGPUPipelineLayout, l.ptr) else null,
        .compute = c.WGPUProgrammableStageDescriptor{
            .nextInChain = null,
            .module = @ptrCast(c.WGPUShaderModule, descriptor.compute.module.ptr),
            .entryPoint = descriptor.compute.entry_point,
            .constantCount = if (descriptor.compute.constants) |v| @intCast(u32, v.len) else 0,
            .constants = if (descriptor.compute.constants) |v| @ptrCast([*]const c.WGPUConstantEntry, v.ptr) else null,
        },
    };
}

inline fn convertRenderPipelineDescriptor(
    d: *const RenderPipeline.Descriptor,
    tmp_depth_stencil: *c.WGPUDepthStencilState,
    tmp_fragment_state: *c.WGPUFragmentState,
) c.WGPURenderPipelineDescriptor {
    if (d.depth_stencil) |ds| {
        tmp_depth_stencil.* = c.WGPUDepthStencilState{
            .nextInChain = null,
            .format = @enumToInt(ds.format),
            .depthWriteEnabled = ds.depth_write_enabled,
            .depthCompare = @enumToInt(ds.depth_compare),
            .stencilFront = @bitCast(c.WGPUStencilFaceState, ds.stencil_front),
            .stencilBack = @bitCast(c.WGPUStencilFaceState, ds.stencil_back),
            .stencilReadMask = ds.stencil_read_mask,
            .stencilWriteMask = ds.stencil_write_mask,
            .depthBias = ds.depth_bias,
            .depthBiasSlopeScale = ds.depth_bias_slope_scale,
            .depthBiasClamp = ds.depth_bias_clamp,
        };
    }

    if (d.fragment) |frag| {
        tmp_fragment_state.* = c.WGPUFragmentState{
            .nextInChain = null,
            .module = @ptrCast(c.WGPUShaderModule, frag.module.ptr),
            .entryPoint = frag.entry_point,
            .constantCount = if (frag.constants) |v| @intCast(u32, v.len) else 0,
            .constants = if (frag.constants) |v| @ptrCast([*]const c.WGPUConstantEntry, v.ptr) else null,
            .targetCount = if (frag.targets) |v| @intCast(u32, v.len) else 0,
            .targets = if (frag.targets) |v| @ptrCast([*]const c.WGPUColorTargetState, v.ptr) else null,
        };
    }

    return c.WGPURenderPipelineDescriptor{
        .nextInChain = null,
        .label = if (d.label) |l| l else null,
        .layout = if (d.layout) |v| @ptrCast(c.WGPUPipelineLayout, v.ptr) else null,
        .vertex = c.WGPUVertexState{
            .nextInChain = null,
            .module = @ptrCast(c.WGPUShaderModule, d.vertex.module.ptr),
            .entryPoint = d.vertex.entry_point,
            .constantCount = if (d.vertex.constants) |v| @intCast(u32, v.len) else 0,
            .constants = if (d.vertex.constants) |v| @ptrCast([*]const c.WGPUConstantEntry, v.ptr) else null,
            .bufferCount = if (d.vertex.buffers) |v| @intCast(u32, v.len) else 0,
            .buffers = if (d.vertex.buffers) |v| @ptrCast([*]const c.WGPUVertexBufferLayout, v.ptr) else null,
        },
        .primitive = c.WGPUPrimitiveState{
            .nextInChain = null,
            .topology = @enumToInt(d.primitive.topology),
            .stripIndexFormat = @enumToInt(d.primitive.strip_index_format),
            .frontFace = @enumToInt(d.primitive.front_face),
            .cullMode = @enumToInt(d.primitive.cull_mode),
        },
        .depthStencil = if (d.depth_stencil != null) tmp_depth_stencil else null,
        .multisample = c.WGPUMultisampleState{
            .nextInChain = null,
            .count = d.multisample.count,
            .mask = d.multisample.mask,
            .alphaToCoverageEnabled = d.multisample.alpha_to_coverage_enabled,
        },
        .fragment = if (d.fragment != null) tmp_fragment_state else null,
    };
}

fn wrapQueue(queue: c.WGPUQueue) Queue {
    return .{
        .ptr = queue.?,
        .vtable = &queue_vtable,
    };
}

const queue_vtable = Queue.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuQueueReference(@ptrCast(c.WGPUQueue, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuQueueRelease(@ptrCast(c.WGPUQueue, ptr));
        }
    }).release,
    .submit = (struct {
        pub fn submit(queue: *Queue, cmds: []const CommandBuffer) void {
            const wgpu_queue = @ptrCast(c.WGPUQueue, queue.ptr);

            if (queue.on_submitted_work_done) |_| {
                // Note: signalValue is not available in the web API, and it's usage is undocumented
                // kainino says "It's basically reserved for future use, though it's been suggested
                // to remove it instead"
                const signal_value: u64 = 0;

                const cCallback = (struct {
                    pub fn cCallback(status: c.WGPUQueueWorkDoneStatus, userdata: ?*anyopaque) callconv(.C) void {
                        const callback_info = @ptrCast(*Queue.WorkDoneCallback, @alignCast(std.meta.alignment(*Queue.WorkDoneCallback), userdata));
                        callback_info.type_erased_callback(
                            callback_info.type_erased_ctx,
                            @intToEnum(Queue.WorkDoneStatus, status),
                        );
                    }
                }).cCallback;

                c.wgpuQueueOnSubmittedWorkDone(
                    wgpu_queue,
                    signal_value,
                    cCallback,
                    &queue.on_submitted_work_done,
                );
            }

            var few_commands: [16]c.WGPUCommandBuffer = undefined;
            const commands = if (cmds.len <= 16) blk: {
                for (cmds) |cmd, i| {
                    few_commands[i] = @ptrCast(c.WGPUCommandBuffer, cmd.ptr);
                }
                break :blk few_commands[0..cmds.len];
            } else blk: {
                const mem = std.heap.page_allocator.alloc(c.WGPUCommandBuffer, cmds.len) catch unreachable;
                for (cmds) |cmd, i| {
                    mem[i] = @ptrCast(c.WGPUCommandBuffer, cmd.ptr);
                }
                break :blk mem;
            };
            defer if (cmds.len > 16) std.heap.page_allocator.free(cmds);

            c.wgpuQueueSubmit(
                wgpu_queue,
                @intCast(u32, commands.len),
                @ptrCast([*]c.WGPUCommandBuffer, commands.ptr),
            );
        }
    }).submit,
    .writeBuffer = (struct {
        pub fn writeBuffer(ptr: *anyopaque, buffer: Buffer, buffer_offset: u64, data: *const anyopaque, size: u64) void {
            c.wgpuQueueWriteBuffer(
                @ptrCast(c.WGPUQueue, ptr),
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                buffer_offset,
                data,
                size,
            );
        }
    }).writeBuffer,
    .writeTexture = (struct {
        pub fn writeTexture(
            ptr: *anyopaque,
            destination: *const ImageCopyTexture,
            data: *const anyopaque,
            data_size: usize,
            data_layout: *const Texture.DataLayout,
            write_size: *const Extent3D,
        ) void {
            c.wgpuQueueWriteTexture(
                @ptrCast(c.WGPUQueue, ptr),
                &c.WGPUImageCopyTexture{
                    .nextInChain = null,
                    .texture = @ptrCast(c.WGPUTexture, destination.texture.ptr),
                    .mipLevel = destination.mip_level,
                    .origin = @bitCast(c.WGPUOrigin3D, destination.origin),
                    .aspect = @bitCast(c.WGPUTextureAspect, destination.aspect),
                },
                data,
                data_size,
                @ptrCast(*const c.WGPUTextureDataLayout, data_layout),
                @ptrCast(*const c.WGPUExtent3D, write_size),
            );
        }
    }).writeTexture,
};

fn wrapShaderModule(shader_module: c.WGPUShaderModule) ShaderModule {
    return .{
        .ptr = shader_module.?,
        .vtable = &shader_module_vtable,
    };
}

const shader_module_vtable = ShaderModule.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuShaderModuleReference(@ptrCast(c.WGPUShaderModule, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuShaderModuleRelease(@ptrCast(c.WGPUShaderModule, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuShaderModuleSetLabel(@ptrCast(c.WGPUShaderModule, ptr), label);
        }
    }).setLabel,
    .getCompilationInfo = (struct {
        pub fn getCompilationInfo(ptr: *anyopaque, callback: *ShaderModule.CompilationInfoCallback) void {
            const cCallback = (struct {
                pub fn cCallback(status: c.WGPUCompilationInfoRequestStatus, info: [*c]const c.WGPUCompilationInfo, userdata: ?*anyopaque) callconv(.C) void {
                    const callback_info = @ptrCast(*ShaderModule.CompilationInfoCallback, @alignCast(std.meta.alignment(*ShaderModule.CompilationInfoCallback), userdata.?));

                    callback_info.type_erased_callback(
                        callback_info.type_erased_ctx,
                        @intToEnum(ShaderModule.CompilationInfoRequestStatus, status),
                        &ShaderModule.CompilationInfo{
                            .messages = @bitCast([]const ShaderModule.CompilationMessage, info[0].messages[0..info[0].messageCount]),
                        },
                    );
                }
            }).cCallback;

            c.wgpuShaderModuleGetCompilationInfo(@ptrCast(c.WGPUShaderModule, ptr), cCallback, callback);
        }
    }).getCompilationInfo,
};

fn wrapSwapChain(swap_chain: c.WGPUSwapChain) SwapChain {
    return .{
        .ptr = swap_chain.?,
        .vtable = &swap_chain_vtable,
    };
}

const swap_chain_vtable = SwapChain.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuSwapChainReference(@ptrCast(c.WGPUSwapChain, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuSwapChainRelease(@ptrCast(c.WGPUSwapChain, ptr));
        }
    }).release,
    .configure = (struct {
        pub fn configure(ptr: *anyopaque, format: Texture.Format, allowed_usage: Texture.Usage, width: u32, height: u32) void {
            c.wgpuSwapChainConfigure(
                @ptrCast(c.WGPUSwapChain, ptr),
                @enumToInt(format),
                @bitCast(u32, allowed_usage),
                width,
                height,
            );
        }
    }).configure,
    .getCurrentTextureView = (struct {
        pub fn getCurrentTextureView(ptr: *anyopaque) TextureView {
            return wrapTextureView(c.wgpuSwapChainGetCurrentTextureView(@ptrCast(c.WGPUSwapChain, ptr)));
        }
    }).getCurrentTextureView,
    .present = (struct {
        pub fn present(ptr: *anyopaque) void {
            c.wgpuSwapChainPresent(@ptrCast(c.WGPUSwapChain, ptr));
        }
    }).present,
};

fn wrapTextureView(texture_view: c.WGPUTextureView) TextureView {
    return .{
        .ptr = texture_view.?,
        .vtable = &texture_view_vtable,
    };
}

const texture_view_vtable = TextureView.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuTextureViewReference(@ptrCast(c.WGPUTextureView, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuTextureViewRelease(@ptrCast(c.WGPUTextureView, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuTextureViewSetLabel(@ptrCast(c.WGPUTextureView, ptr), label);
        }
    }).setLabel,
};

fn wrapTexture(texture: c.WGPUTexture) Texture {
    return .{
        .ptr = texture.?,
        .vtable = &texture_vtable,
    };
}

const texture_vtable = Texture.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuTextureReference(@ptrCast(c.WGPUTexture, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuTextureRelease(@ptrCast(c.WGPUTexture, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuTextureSetLabel(@ptrCast(c.WGPUTexture, ptr), label);
        }
    }).setLabel,
    .destroy = (struct {
        pub fn destroy(ptr: *anyopaque) void {
            c.wgpuTextureDestroy(@ptrCast(c.WGPUTexture, ptr));
        }
    }).destroy,
    .createView = (struct {
        pub fn createView(ptr: *anyopaque, descriptor: *const TextureView.Descriptor) TextureView {
            const desc = c.WGPUTextureViewDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else "",
                .format = @enumToInt(descriptor.format),
                .dimension = @enumToInt(descriptor.dimension),
                .baseMipLevel = descriptor.base_mip_level,
                .mipLevelCount = descriptor.mip_level_count,
                .baseArrayLayer = descriptor.base_array_layer,
                .arrayLayerCount = descriptor.array_layer_count,
                .aspect = @enumToInt(descriptor.aspect),
            };
            return wrapTextureView(c.wgpuTextureCreateView(
                @ptrCast(c.WGPUTexture, ptr),
                &desc,
            ));
        }
    }).createView,
};

fn wrapSampler(sampler: c.WGPUSampler) Sampler {
    return .{
        .ptr = sampler.?,
        .vtable = &sampler_vtable,
    };
}

const sampler_vtable = Sampler.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuSamplerReference(@ptrCast(c.WGPUSampler, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuSamplerRelease(@ptrCast(c.WGPUSampler, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuSamplerSetLabel(@ptrCast(c.WGPUSampler, ptr), label);
        }
    }).setLabel,
};

fn wrapRenderPipeline(pipeline: c.WGPURenderPipeline) RenderPipeline {
    return .{
        .ptr = pipeline.?,
        .vtable = &render_pipeline_vtable,
    };
}

const render_pipeline_vtable = RenderPipeline.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuRenderPipelineReference(@ptrCast(c.WGPURenderPipeline, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuRenderPipelineRelease(@ptrCast(c.WGPURenderPipeline, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuRenderPipelineSetLabel(@ptrCast(c.WGPURenderPipeline, ptr), label);
        }
    }).setLabel,
    .getBindGroupLayout = (struct {
        pub fn getBindGroupLayout(ptr: *anyopaque, group_index: u32) BindGroupLayout {
            return wrapBindGroupLayout(c.wgpuRenderPipelineGetBindGroupLayout(
                @ptrCast(c.WGPURenderPipeline, ptr),
                group_index,
            ));
        }
    }).getBindGroupLayout,
};

fn wrapRenderPassEncoder(pass: c.WGPURenderPassEncoder) RenderPassEncoder {
    return .{
        .ptr = pass.?,
        .vtable = &render_pass_encoder_vtable,
    };
}

const render_pass_encoder_vtable = RenderPassEncoder.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuRenderPassEncoderReference(@ptrCast(c.WGPURenderPassEncoder, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuRenderPassEncoderRelease(@ptrCast(c.WGPURenderPassEncoder, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuRenderPassEncoderSetLabel(@ptrCast(c.WGPURenderPassEncoder, ptr), label);
        }
    }).setLabel,
    .setPipeline = (struct {
        pub fn setPipeline(ptr: *anyopaque, pipeline: RenderPipeline) void {
            c.wgpuRenderPassEncoderSetPipeline(@ptrCast(c.WGPURenderPassEncoder, ptr), @ptrCast(c.WGPURenderPipeline, pipeline.ptr));
        }
    }).setPipeline,
    .draw = (struct {
        pub fn draw(ptr: *anyopaque, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) void {
            c.wgpuRenderPassEncoderDraw(@ptrCast(c.WGPURenderPassEncoder, ptr), vertex_count, instance_count, first_vertex, first_instance);
        }
    }).draw,
    .drawIndexed = (struct {
        pub fn drawIndexed(
            ptr: *anyopaque,
            index_count: u32,
            instance_count: u32,
            first_index: u32,
            base_vertex: i32,
            first_instance: u32,
        ) void {
            c.wgpuRenderPassEncoderDrawIndexed(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                index_count,
                instance_count,
                first_index,
                base_vertex,
                first_instance,
            );
        }
    }).drawIndexed,
    .drawIndexedIndirect = (struct {
        pub fn drawIndexedIndirect(ptr: *anyopaque, indirect_buffer: Buffer, indirect_offset: u64) void {
            c.wgpuRenderPassEncoderDrawIndexedIndirect(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @ptrCast(c.WGPUBuffer, indirect_buffer.ptr),
                indirect_offset,
            );
        }
    }).drawIndexedIndirect,
    .drawIndirect = (struct {
        pub fn drawIndirect(ptr: *anyopaque, indirect_buffer: Buffer, indirect_offset: u64) void {
            c.wgpuRenderPassEncoderDrawIndexedIndirect(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @ptrCast(c.WGPUBuffer, indirect_buffer.ptr),
                indirect_offset,
            );
        }
    }).drawIndirect,
    .beginOcclusionQuery = (struct {
        pub fn beginOcclusionQuery(ptr: *anyopaque, query_index: u32) void {
            c.wgpuRenderPassEncoderBeginOcclusionQuery(@ptrCast(c.WGPURenderPassEncoder, ptr), query_index);
        }
    }).beginOcclusionQuery,
    .endOcclusionQuery = (struct {
        pub fn endOcclusionQuery(ptr: *anyopaque) void {
            c.wgpuRenderPassEncoderEndOcclusionQuery(@ptrCast(c.WGPURenderPassEncoder, ptr));
        }
    }).endOcclusionQuery,
    .end = (struct {
        pub fn end(ptr: *anyopaque) void {
            c.wgpuRenderPassEncoderEnd(@ptrCast(c.WGPURenderPassEncoder, ptr));
        }
    }).end,
    .executeBundles = (struct {
        pub fn executeBundles(ptr: *anyopaque, bundles: []RenderBundle) void {
            var few_bundles: [16]c.WGPURenderBundle = undefined;
            const c_bundles = if (bundles.len <= 8) blk: {
                for (bundles) |bundle, i| {
                    few_bundles[i] = @ptrCast(c.WGPURenderBundle, bundle.ptr);
                }
                break :blk few_bundles[0..bundles.len];
            } else blk: {
                const mem = std.heap.page_allocator.alloc(c.WGPURenderBundle, bundles.len) catch unreachable;
                for (bundles) |bundle, i| {
                    mem[i] = @ptrCast(c.WGPURenderBundle, bundle.ptr);
                }
                break :blk mem;
            };
            defer if (bundles.len > 8) std.heap.page_allocator.free(c_bundles);

            c.wgpuRenderPassEncoderExecuteBundles(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @intCast(u32, c_bundles.len),
                c_bundles.ptr,
            );
        }
    }).executeBundles,
    .insertDebugMarker = (struct {
        pub fn insertDebugMarker(ptr: *anyopaque, marker_label: [*:0]const u8) void {
            c.wgpuRenderPassEncoderInsertDebugMarker(@ptrCast(c.WGPURenderPassEncoder, ptr), marker_label);
        }
    }).insertDebugMarker,
    .popDebugGroup = (struct {
        pub fn popDebugGroup(ptr: *anyopaque) void {
            c.wgpuRenderPassEncoderPopDebugGroup(@ptrCast(c.WGPURenderPassEncoder, ptr));
        }
    }).popDebugGroup,
    .pushDebugGroup = (struct {
        pub fn pushDebugGroup(ptr: *anyopaque, group_label: [*:0]const u8) void {
            c.wgpuRenderPassEncoderPushDebugGroup(@ptrCast(c.WGPURenderPassEncoder, ptr), group_label);
        }
    }).pushDebugGroup,
    .setBindGroup = (struct {
        pub fn setBindGroup(
            ptr: *anyopaque,
            group_index: u32,
            group: BindGroup,
            dynamic_offsets: ?[]const u32,
        ) void {
            c.wgpuRenderPassEncoderSetBindGroup(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                group_index,
                @ptrCast(c.WGPUBindGroup, group.ptr),
                if (dynamic_offsets) |d| @intCast(u32, d.len) else 0,
                if (dynamic_offsets) |d| d.ptr else null,
            );
        }
    }).setBindGroup,
    .setBlendConstant = (struct {
        pub fn setBlendConstant(ptr: *anyopaque, color: *const Color) void {
            c.wgpuRenderPassEncoderSetBlendConstant(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @ptrCast(*const c.WGPUColor, color),
            );
        }
    }).setBlendConstant,
    .setIndexBuffer = (struct {
        pub fn setIndexBuffer(
            ptr: *anyopaque,
            buffer: Buffer,
            format: IndexFormat,
            offset: u64,
            size: u64,
        ) void {
            c.wgpuRenderPassEncoderSetIndexBuffer(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                @enumToInt(format),
                offset,
                size,
            );
        }
    }).setIndexBuffer,
    .setScissorRect = (struct {
        pub fn setScissorRect(ptr: *anyopaque, x: u32, y: u32, width: u32, height: u32) void {
            c.wgpuRenderPassEncoderSetScissorRect(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                x,
                y,
                width,
                height,
            );
        }
    }).setScissorRect,
    .setStencilReference = (struct {
        pub fn setStencilReference(ptr: *anyopaque, reference: u32) void {
            c.wgpuRenderPassEncoderSetStencilReference(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                reference,
            );
        }
    }).setStencilReference,
    .setVertexBuffer = (struct {
        pub fn setVertexBuffer(ptr: *anyopaque, slot: u32, buffer: Buffer, offset: u64, size: u64) void {
            c.wgpuRenderPassEncoderSetVertexBuffer(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                slot,
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                offset,
                size,
            );
        }
    }).setVertexBuffer,
    .setViewport = (struct {
        pub fn setViewport(
            ptr: *anyopaque,
            x: f32,
            y: f32,
            width: f32,
            height: f32,
            min_depth: f32,
            max_depth: f32,
        ) void {
            c.wgpuRenderPassEncoderSetViewport(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                x,
                y,
                width,
                height,
                min_depth,
                max_depth,
            );
        }
    }).setViewport,
    .writeTimestamp = (struct {
        pub fn writeTimestamp(ptr: *anyopaque, query_set: QuerySet, query_index: u32) void {
            c.wgpuRenderPassEncoderWriteTimestamp(
                @ptrCast(c.WGPURenderPassEncoder, ptr),
                @ptrCast(c.WGPUQuerySet, query_set.ptr),
                query_index,
            );
        }
    }).writeTimestamp,
};

fn wrapRenderBundleEncoder(enc: c.WGPURenderBundleEncoder) RenderBundleEncoder {
    return .{
        .ptr = enc.?,
        .vtable = &render_bundle_encoder_vtable,
    };
}

const render_bundle_encoder_vtable = RenderBundleEncoder.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuRenderBundleEncoderReference(@ptrCast(c.WGPURenderBundleEncoder, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuRenderBundleEncoderRelease(@ptrCast(c.WGPURenderBundleEncoder, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuRenderBundleEncoderSetLabel(@ptrCast(c.WGPURenderBundleEncoder, ptr), label);
        }
    }).setLabel,
    .setPipeline = (struct {
        pub fn setPipeline(ptr: *anyopaque, pipeline: RenderPipeline) void {
            c.wgpuRenderBundleEncoderSetPipeline(@ptrCast(c.WGPURenderBundleEncoder, ptr), @ptrCast(c.WGPURenderPipeline, pipeline.ptr));
        }
    }).setPipeline,
    .draw = (struct {
        pub fn draw(ptr: *anyopaque, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) void {
            c.wgpuRenderBundleEncoderDraw(@ptrCast(c.WGPURenderBundleEncoder, ptr), vertex_count, instance_count, first_vertex, first_instance);
        }
    }).draw,
    .drawIndexed = (struct {
        pub fn drawIndexed(
            ptr: *anyopaque,
            index_count: u32,
            instance_count: u32,
            first_index: u32,
            base_vertex: i32,
            first_instance: u32,
        ) void {
            c.wgpuRenderBundleEncoderDrawIndexed(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                index_count,
                instance_count,
                first_index,
                base_vertex,
                first_instance,
            );
        }
    }).drawIndexed,
    .drawIndexedIndirect = (struct {
        pub fn drawIndexedIndirect(ptr: *anyopaque, indirect_buffer: Buffer, indirect_offset: u64) void {
            c.wgpuRenderBundleEncoderDrawIndexedIndirect(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                @ptrCast(c.WGPUBuffer, indirect_buffer.ptr),
                indirect_offset,
            );
        }
    }).drawIndexedIndirect,
    .drawIndirect = (struct {
        pub fn drawIndirect(ptr: *anyopaque, indirect_buffer: Buffer, indirect_offset: u64) void {
            c.wgpuRenderBundleEncoderDrawIndexedIndirect(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                @ptrCast(c.WGPUBuffer, indirect_buffer.ptr),
                indirect_offset,
            );
        }
    }).drawIndirect,
    .finish = (struct {
        pub fn finish(ptr: *anyopaque, descriptor: *const RenderBundle.Descriptor) RenderBundle {
            const desc = c.WGPURenderBundleDescriptor{
                .nextInChain = null,
                .label = if (descriptor.label) |l| l else null,
            };
            return wrapRenderBundle(c.wgpuRenderBundleEncoderFinish(@ptrCast(c.WGPURenderBundleEncoder, ptr), &desc));
        }
    }).finish,
    .insertDebugMarker = (struct {
        pub fn insertDebugMarker(ptr: *anyopaque, marker_label: [*:0]const u8) void {
            c.wgpuRenderBundleEncoderInsertDebugMarker(@ptrCast(c.WGPURenderBundleEncoder, ptr), marker_label);
        }
    }).insertDebugMarker,
    .popDebugGroup = (struct {
        pub fn popDebugGroup(ptr: *anyopaque) void {
            c.wgpuRenderBundleEncoderPopDebugGroup(@ptrCast(c.WGPURenderBundleEncoder, ptr));
        }
    }).popDebugGroup,
    .pushDebugGroup = (struct {
        pub fn pushDebugGroup(ptr: *anyopaque, group_label: [*:0]const u8) void {
            c.wgpuRenderBundleEncoderPushDebugGroup(@ptrCast(c.WGPURenderBundleEncoder, ptr), group_label);
        }
    }).pushDebugGroup,
    .setBindGroup = (struct {
        pub fn setBindGroup(
            ptr: *anyopaque,
            group_index: u32,
            group: BindGroup,
            dynamic_offsets: ?[]const u32,
        ) void {
            c.wgpuRenderBundleEncoderSetBindGroup(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                group_index,
                @ptrCast(c.WGPUBindGroup, group.ptr),
                if (dynamic_offsets) |d| @intCast(u32, d.len) else 0,
                if (dynamic_offsets) |d| d.ptr else null,
            );
        }
    }).setBindGroup,
    .setIndexBuffer = (struct {
        pub fn setIndexBuffer(
            ptr: *anyopaque,
            buffer: Buffer,
            format: IndexFormat,
            offset: u64,
            size: u64,
        ) void {
            c.wgpuRenderBundleEncoderSetIndexBuffer(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                @enumToInt(format),
                offset,
                size,
            );
        }
    }).setIndexBuffer,
    .setVertexBuffer = (struct {
        pub fn setVertexBuffer(ptr: *anyopaque, slot: u32, buffer: Buffer, offset: u64, size: u64) void {
            c.wgpuRenderBundleEncoderSetVertexBuffer(
                @ptrCast(c.WGPURenderBundleEncoder, ptr),
                slot,
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                offset,
                size,
            );
        }
    }).setVertexBuffer,
};

fn wrapRenderBundle(bundle: c.WGPURenderBundle) RenderBundle {
    return .{
        .ptr = bundle.?,
        .vtable = &render_bundle_vtable,
    };
}

const render_bundle_vtable = RenderBundle.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuRenderBundleReference(@ptrCast(c.WGPURenderBundle, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuRenderBundleRelease(@ptrCast(c.WGPURenderBundle, ptr));
        }
    }).release,
};

fn wrapQuerySet(qset: c.WGPUQuerySet) QuerySet {
    return .{
        .ptr = qset.?,
        .vtable = &query_set_vtable,
    };
}

const query_set_vtable = QuerySet.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuQuerySetReference(@ptrCast(c.WGPUQuerySet, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuQuerySetRelease(@ptrCast(c.WGPUQuerySet, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuQuerySetSetLabel(@ptrCast(c.WGPUQuerySet, ptr), label);
        }
    }).setLabel,
    .destroy = (struct {
        pub fn destroy(ptr: *anyopaque) void {
            c.wgpuQuerySetDestroy(@ptrCast(c.WGPUQuerySet, ptr));
        }
    }).destroy,
};

fn wrapPipelineLayout(layout: c.WGPUPipelineLayout) PipelineLayout {
    return .{
        .ptr = layout.?,
        .vtable = &pipeline_layout_vtable,
    };
}

const pipeline_layout_vtable = PipelineLayout.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuPipelineLayoutReference(@ptrCast(c.WGPUPipelineLayout, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuPipelineLayoutRelease(@ptrCast(c.WGPUPipelineLayout, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuPipelineLayoutSetLabel(@ptrCast(c.WGPUPipelineLayout, ptr), label);
        }
    }).setLabel,
};

fn wrapExternalTexture(texture: c.WGPUExternalTexture) ExternalTexture {
    return .{
        .ptr = texture.?,
        .vtable = &external_texture_vtable,
    };
}

const external_texture_vtable = ExternalTexture.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuExternalTextureReference(@ptrCast(c.WGPUExternalTexture, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuExternalTextureRelease(@ptrCast(c.WGPUExternalTexture, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuExternalTextureSetLabel(@ptrCast(c.WGPUExternalTexture, ptr), label);
        }
    }).setLabel,
    .destroy = (struct {
        pub fn destroy(ptr: *anyopaque) void {
            c.wgpuExternalTextureDestroy(@ptrCast(c.WGPUExternalTexture, ptr));
        }
    }).destroy,
};

fn wrapBindGroup(group: c.WGPUBindGroup) BindGroup {
    return .{
        .ptr = group.?,
        .vtable = &bind_group_vtable,
    };
}

const bind_group_vtable = BindGroup.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuBindGroupReference(@ptrCast(c.WGPUBindGroup, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuBindGroupRelease(@ptrCast(c.WGPUBindGroup, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuBindGroupSetLabel(@ptrCast(c.WGPUBindGroup, ptr), label);
        }
    }).setLabel,
};

fn wrapBindGroupLayout(layout: c.WGPUBindGroupLayout) BindGroupLayout {
    return .{
        .ptr = layout.?,
        .vtable = &bind_group_layout_vtable,
    };
}

const bind_group_layout_vtable = BindGroupLayout.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuBindGroupLayoutReference(@ptrCast(c.WGPUBindGroupLayout, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuBindGroupLayoutRelease(@ptrCast(c.WGPUBindGroupLayout, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuBindGroupLayoutSetLabel(@ptrCast(c.WGPUBindGroupLayout, ptr), label);
        }
    }).setLabel,
};

fn wrapBuffer(buffer: c.WGPUBuffer) Buffer {
    return .{
        .ptr = buffer.?,
        .vtable = &buffer_vtable,
    };
}

const buffer_vtable = Buffer.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuBufferReference(@ptrCast(c.WGPUBuffer, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuBufferRelease(@ptrCast(c.WGPUBuffer, ptr));
        }
    }).release,
    .getConstMappedRange = (struct {
        pub fn getConstMappedRange(ptr: *anyopaque, offset: usize, size: usize) []const u8 {
            const range = c.wgpuBufferGetConstMappedRange(@ptrCast(c.WGPUBuffer, ptr), offset, size);
            return @ptrCast([*c]const u8, range.?)[0..size];
        }
    }).getConstMappedRange,
    .getMappedRange = (struct {
        pub fn getMappedRange(ptr: *anyopaque, offset: usize, size: usize) []u8 {
            const range = c.wgpuBufferGetMappedRange(@ptrCast(c.WGPUBuffer, ptr), offset, size);
            return @ptrCast([*c]u8, range.?)[0..size];
        }
    }).getMappedRange,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuBufferSetLabel(@ptrCast(c.WGPUBuffer, ptr), label);
        }
    }).setLabel,
    .destroy = (struct {
        pub fn destroy(ptr: *anyopaque) void {
            c.wgpuBufferDestroy(@ptrCast(c.WGPUBuffer, ptr));
        }
    }).destroy,
    .mapAsync = (struct {
        pub fn mapAsync(
            ptr: *anyopaque,
            mode: Buffer.MapMode,
            offset: usize,
            size: usize,
            callback: *Buffer.MapCallback,
        ) void {
            const cCallback = (struct {
                pub fn cCallback(status: c.WGPUBufferMapAsyncStatus, userdata: ?*anyopaque) callconv(.C) void {
                    const callback_info = @ptrCast(*Buffer.MapCallback, @alignCast(std.meta.alignment(*Buffer.MapCallback), userdata.?));
                    callback_info.type_erased_callback(callback_info.type_erased_ctx, @intToEnum(Buffer.MapAsyncStatus, status));
                }
            }).cCallback;
            c.wgpuBufferMapAsync(@ptrCast(c.WGPUBuffer, ptr), @enumToInt(mode), offset, size, cCallback, callback);
        }
    }).mapAsync,
    .unmap = (struct {
        pub fn unmap(ptr: *anyopaque) void {
            c.wgpuBufferUnmap(@ptrCast(c.WGPUBuffer, ptr));
        }
    }).unmap,
};

fn wrapCommandBuffer(buffer: c.WGPUCommandBuffer) CommandBuffer {
    return .{
        .ptr = buffer.?,
        .vtable = &command_buffer_vtable,
    };
}

const command_buffer_vtable = CommandBuffer.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuCommandBufferReference(@ptrCast(c.WGPUCommandBuffer, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuCommandBufferRelease(@ptrCast(c.WGPUCommandBuffer, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuCommandBufferSetLabel(@ptrCast(c.WGPUCommandBuffer, ptr), label);
        }
    }).setLabel,
};

fn wrapCommandEncoder(enc: c.WGPUCommandEncoder) CommandEncoder {
    return .{
        .ptr = enc.?,
        .vtable = &command_encoder_vtable,
    };
}

const command_encoder_vtable = CommandEncoder.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuCommandEncoderReference(@ptrCast(c.WGPUCommandEncoder, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuCommandEncoderRelease(@ptrCast(c.WGPUCommandEncoder, ptr));
        }
    }).release,
    .finish = (struct {
        pub fn finish(ptr: *anyopaque, descriptor: ?*const CommandBuffer.Descriptor) CommandBuffer {
            const desc: ?*c.WGPUCommandBufferDescriptor = if (descriptor) |d| &.{
                .nextInChain = null,
                .label = if (d.label) |l| l else "",
            } else null;
            return wrapCommandBuffer(c.wgpuCommandEncoderFinish(@ptrCast(c.WGPUCommandEncoder, ptr), desc));
        }
    }).finish,
    .injectValidationError = (struct {
        pub fn injectValidationError(ptr: *anyopaque, message: [*:0]const u8) void {
            c.wgpuCommandEncoderInjectValidationError(@ptrCast(c.WGPUCommandEncoder, ptr), message);
        }
    }).injectValidationError,
    .insertDebugMarker = (struct {
        pub fn insertDebugMarker(ptr: *anyopaque, marker_label: [*:0]const u8) void {
            c.wgpuCommandEncoderInsertDebugMarker(@ptrCast(c.WGPUCommandEncoder, ptr), marker_label);
        }
    }).insertDebugMarker,
    .resolveQuerySet = (struct {
        pub fn resolveQuerySet(
            ptr: *anyopaque,
            query_set: QuerySet,
            first_query: u32,
            query_count: u32,
            destination: Buffer,
            destination_offset: u64,
        ) void {
            c.wgpuCommandEncoderResolveQuerySet(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                @ptrCast(c.WGPUQuerySet, query_set.ptr),
                first_query,
                query_count,
                @ptrCast(c.WGPUBuffer, destination.ptr),
                destination_offset,
            );
        }
    }).resolveQuerySet,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuCommandEncoderSetLabel(@ptrCast(c.WGPUCommandEncoder, ptr), label);
        }
    }).setLabel,
    .beginComputePass = (struct {
        pub fn beginComputePass(ptr: *anyopaque, descriptor: ?*const ComputePassEncoder.Descriptor) ComputePassEncoder {
            if (descriptor) |d| {
                var few_timestamp_writes: [8]c.WGPUComputePassTimestampWrite = undefined;
                const timestamp_writes = if (d.timestamp_writes.len <= 8) blk: {
                    for (d.timestamp_writes) |v, i| {
                        few_timestamp_writes[i] = c.WGPUComputePassTimestampWrite{
                            .querySet = @ptrCast(c.WGPUQuerySet, v.query_set.ptr),
                            .queryIndex = v.query_index,
                            .location = @enumToInt(v.location),
                        };
                    }
                    break :blk few_timestamp_writes[0..d.timestamp_writes.len];
                } else blk: {
                    const mem = std.heap.page_allocator.alloc(c.WGPUComputePassTimestampWrite, d.timestamp_writes.len) catch unreachable;
                    for (d.timestamp_writes) |v, i| {
                        mem[i] = c.WGPUComputePassTimestampWrite{
                            .querySet = @ptrCast(c.WGPUQuerySet, v.query_set.ptr),
                            .queryIndex = v.query_index,
                            .location = @enumToInt(v.location),
                        };
                    }
                    break :blk mem;
                };
                defer if (d.timestamp_writes.len > 8) std.heap.page_allocator.free(timestamp_writes);

                const desc = c.WGPUComputePassDescriptor{
                    .nextInChain = null,
                    .label = if (d.label) |l| l else null,
                    .timestampWriteCount = @intCast(u32, timestamp_writes.len),
                    .timestampWrites = @ptrCast([*]const c.WGPUComputePassTimestampWrite, timestamp_writes.ptr),
                };
                return wrapComputePassEncoder(c.wgpuCommandEncoderBeginComputePass(@ptrCast(c.WGPUCommandEncoder, ptr), &desc));
            } else {
                return wrapComputePassEncoder(c.wgpuCommandEncoderBeginComputePass(@ptrCast(c.WGPUCommandEncoder, ptr), null));
            }
        }
    }).beginComputePass,
    .beginRenderPass = (struct {
        pub fn beginRenderPass(ptr: *anyopaque, d: *const RenderPassEncoder.Descriptor) RenderPassEncoder {
            var few_color_attachments: [8]c.WGPURenderPassColorAttachment = undefined;
            const color_attachments = if (d.color_attachments.len <= 8) blk: {
                for (d.color_attachments) |v, i| {
                    few_color_attachments[i] = c.WGPURenderPassColorAttachment{
                        .view = @ptrCast(c.WGPUTextureView, v.view.ptr),
                        .resolveTarget = if (v.resolve_target) |t| @ptrCast(c.WGPUTextureView, t.ptr) else null,
                        .loadOp = @enumToInt(v.load_op),
                        .storeOp = @enumToInt(v.store_op),
                        .clearValue = @bitCast(c.WGPUColor, v.clear_value),
                        // deprecated:
                        .clearColor = c.WGPUColor{
                            .r = std.math.nan(f32),
                            .g = std.math.nan(f32),
                            .b = std.math.nan(f32),
                            .a = std.math.nan(f32),
                        },
                    };
                }
                break :blk few_color_attachments[0..d.color_attachments.len];
            } else blk: {
                const mem = std.heap.page_allocator.alloc(c.WGPURenderPassColorAttachment, d.color_attachments.len) catch unreachable;
                for (d.color_attachments) |v, i| {
                    mem[i] = c.WGPURenderPassColorAttachment{
                        .view = @ptrCast(c.WGPUTextureView, v.view.ptr),
                        .resolveTarget = if (v.resolve_target) |t| @ptrCast(c.WGPUTextureView, t.ptr) else null,
                        .loadOp = @enumToInt(v.load_op),
                        .storeOp = @enumToInt(v.store_op),
                        .clearValue = @bitCast(c.WGPUColor, v.clear_value),
                        // deprecated:
                        .clearColor = c.WGPUColor{
                            .r = std.math.nan(f32),
                            .g = std.math.nan(f32),
                            .b = std.math.nan(f32),
                            .a = std.math.nan(f32),
                        },
                    };
                }
                break :blk mem;
            };
            defer if (d.color_attachments.len > 8) std.heap.page_allocator.free(color_attachments);

            var few_timestamp_writes: [8]c.WGPURenderPassTimestampWrite = undefined;
            const timestamp_writes = if (d.timestamp_writes) |writes| blk: {
                if (writes.len <= 8) {
                    for (writes) |v, i| {
                        few_timestamp_writes[i] = c.WGPURenderPassTimestampWrite{
                            .querySet = @ptrCast(c.WGPUQuerySet, v.query_set.ptr),
                            .queryIndex = v.query_index,
                            .location = @enumToInt(v.location),
                        };
                    }
                    break :blk few_timestamp_writes[0..writes.len];
                } else {
                    const mem = std.heap.page_allocator.alloc(c.WGPURenderPassTimestampWrite, writes.len) catch unreachable;
                    for (writes) |v, i| {
                        mem[i] = c.WGPURenderPassTimestampWrite{
                            .querySet = @ptrCast(c.WGPUQuerySet, v.query_set.ptr),
                            .queryIndex = v.query_index,
                            .location = @enumToInt(v.location),
                        };
                    }
                    break :blk mem;
                }
            } else null;
            defer if (timestamp_writes != null and timestamp_writes.?.len > 8) std.heap.page_allocator.free(timestamp_writes.?);

            const desc = c.WGPURenderPassDescriptor{
                .nextInChain = null,
                .label = if (d.label) |l| l else null,
                .colorAttachmentCount = @intCast(u32, color_attachments.len),
                .colorAttachments = color_attachments.ptr,
                .depthStencilAttachment = if (d.depth_stencil_attachment) |v| &c.WGPURenderPassDepthStencilAttachment{
                    .view = @ptrCast(c.WGPUTextureView, v.view.ptr),
                    .depthLoadOp = @enumToInt(v.depth_load_op),
                    .depthStoreOp = @enumToInt(v.depth_store_op),
                    .clearDepth = v.clear_depth,
                    .depthClearValue = v.depth_clear_value,
                    .depthReadOnly = v.depth_read_only,
                    .stencilLoadOp = @enumToInt(v.stencil_load_op),
                    .stencilStoreOp = @enumToInt(v.stencil_store_op),
                    .clearStencil = v.clear_stencil,
                    .stencilClearValue = v.stencil_clear_value,
                    .stencilReadOnly = v.stencil_read_only,
                } else null,
                .occlusionQuerySet = if (d.occlusion_query_set) |v| @ptrCast(c.WGPUQuerySet, v.ptr) else null,
                .timestampWriteCount = if (timestamp_writes) |v| @intCast(u32, v.len) else 0,
                .timestampWrites = if (timestamp_writes) |v| @ptrCast([*]const c.WGPURenderPassTimestampWrite, v.ptr) else null,
            };
            return wrapRenderPassEncoder(c.wgpuCommandEncoderBeginRenderPass(@ptrCast(c.WGPUCommandEncoder, ptr), &desc));
        }
    }).beginRenderPass,
    .clearBuffer = (struct {
        pub fn clearBuffer(ptr: *anyopaque, buffer: Buffer, offset: u64, size: u64) void {
            c.wgpuCommandEncoderClearBuffer(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                offset,
                size,
            );
        }
    }).clearBuffer,
    .copyBufferToBuffer = (struct {
        pub fn copyBufferToBuffer(
            ptr: *anyopaque,
            source: Buffer,
            source_offset: u64,
            destination: Buffer,
            destination_offset: u64,
            size: u64,
        ) void {
            c.wgpuCommandEncoderCopyBufferToBuffer(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                @ptrCast(c.WGPUBuffer, source.ptr),
                source_offset,
                @ptrCast(c.WGPUBuffer, destination.ptr),
                destination_offset,
                size,
            );
        }
    }).copyBufferToBuffer,
    .copyBufferToTexture = (struct {
        pub fn copyBufferToTexture(
            ptr: *anyopaque,
            source: *const ImageCopyBuffer,
            destination: *const ImageCopyTexture,
            copy_size: *const Extent3D,
        ) void {
            c.wgpuCommandEncoderCopyBufferToTexture(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                &convertImageCopyBuffer(source),
                &convertImageCopyTexture(destination),
                @ptrCast(*const c.WGPUExtent3D, copy_size),
            );
        }
    }).copyBufferToTexture,
    .copyTextureToBuffer = (struct {
        pub fn copyTextureToBuffer(
            ptr: *anyopaque,
            source: *const ImageCopyTexture,
            destination: *const ImageCopyBuffer,
            copy_size: *const Extent3D,
        ) void {
            c.wgpuCommandEncoderCopyTextureToBuffer(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                &convertImageCopyTexture(source),
                &convertImageCopyBuffer(destination),
                @ptrCast(*const c.WGPUExtent3D, copy_size),
            );
        }
    }).copyTextureToBuffer,
    .copyTextureToTexture = (struct {
        pub fn copyTextureToTexture(
            ptr: *anyopaque,
            source: *const ImageCopyTexture,
            destination: *const ImageCopyTexture,
            copy_size: *const Extent3D,
        ) void {
            c.wgpuCommandEncoderCopyTextureToTexture(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                &convertImageCopyTexture(source),
                &convertImageCopyTexture(destination),
                @ptrCast(*const c.WGPUExtent3D, copy_size),
            );
        }
    }).copyTextureToTexture,
    .popDebugGroup = (struct {
        pub fn popDebugGroup(ptr: *anyopaque) void {
            c.wgpuCommandEncoderPopDebugGroup(@ptrCast(c.WGPUCommandEncoder, ptr));
        }
    }).popDebugGroup,
    .pushDebugGroup = (struct {
        pub fn pushDebugGroup(ptr: *anyopaque, group_label: [*:0]const u8) void {
            c.wgpuCommandEncoderPushDebugGroup(@ptrCast(c.WGPUCommandEncoder, ptr), group_label);
        }
    }).pushDebugGroup,
    .writeBuffer = (struct {
        pub fn writeBuffer(ptr: *anyopaque, buffer: Buffer, buffer_offset: u64, data: [*]const u8, size: u64) void {
            c.wgpuCommandEncoderWriteBuffer(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                @ptrCast(c.WGPUBuffer, buffer.ptr),
                buffer_offset,
                data,
                size,
            );
        }
    }).writeBuffer,
    .writeTimestamp = (struct {
        pub fn writeTimestamp(ptr: *anyopaque, query_set: QuerySet, query_index: u32) void {
            c.wgpuCommandEncoderWriteTimestamp(
                @ptrCast(c.WGPUCommandEncoder, ptr),
                @ptrCast(c.WGPUQuerySet, query_set.ptr),
                query_index,
            );
        }
    }).writeTimestamp,
};

inline fn convertImageCopyBuffer(v: *const ImageCopyBuffer) c.WGPUImageCopyBuffer {
    return .{
        .nextInChain = null,
        .layout = convertTextureDataLayout(v.layout),
        .buffer = @ptrCast(c.WGPUBuffer, v.buffer.ptr),
    };
}

inline fn convertImageCopyTexture(v: *const ImageCopyTexture) c.WGPUImageCopyTexture {
    return .{
        .nextInChain = null,
        .texture = @ptrCast(c.WGPUTexture, v.texture.ptr),
        .mipLevel = v.mip_level,
        .origin = @bitCast(c.WGPUOrigin3D, v.origin),
        .aspect = @enumToInt(v.aspect),
    };
}

inline fn convertTextureDataLayout(v: Texture.DataLayout) c.WGPUTextureDataLayout {
    return .{
        .nextInChain = null,
        .offset = v.offset,
        .bytesPerRow = v.bytes_per_row,
        .rowsPerImage = v.rows_per_image,
    };
}

fn wrapComputePassEncoder(enc: c.WGPUComputePassEncoder) ComputePassEncoder {
    return .{
        .ptr = enc.?,
        .vtable = &compute_pass_encoder_vtable,
    };
}

const compute_pass_encoder_vtable = ComputePassEncoder.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuComputePassEncoderReference(@ptrCast(c.WGPUComputePassEncoder, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuComputePassEncoderRelease(@ptrCast(c.WGPUComputePassEncoder, ptr));
        }
    }).release,
    .dispatch = (struct {
        pub fn dispatch(
            ptr: *anyopaque,
            workgroup_count_x: u32,
            workgroup_count_y: u32,
            workgroup_count_z: u32,
        ) void {
            c.wgpuComputePassEncoderDispatch(
                @ptrCast(c.WGPUComputePassEncoder, ptr),
                workgroup_count_x,
                workgroup_count_y,
                workgroup_count_z,
            );
        }
    }).dispatch,
    .dispatchIndirect = (struct {
        pub fn dispatchIndirect(
            ptr: *anyopaque,
            indirect_buffer: Buffer,
            indirect_offset: u64,
        ) void {
            c.wgpuComputePassEncoderDispatchIndirect(
                @ptrCast(c.WGPUComputePassEncoder, ptr),
                @ptrCast(c.WGPUBuffer, indirect_buffer.ptr),
                indirect_offset,
            );
        }
    }).dispatchIndirect,
    .end = (struct {
        pub fn end(ptr: *anyopaque) void {
            c.wgpuComputePassEncoderEnd(@ptrCast(c.WGPUComputePassEncoder, ptr));
        }
    }).end,
    .setBindGroup = (struct {
        pub fn setBindGroup(
            ptr: *anyopaque,
            group_index: u32,
            group: BindGroup,
            dynamic_offsets: ?[]const u32,
        ) void {
            c.wgpuComputePassEncoderSetBindGroup(
                @ptrCast(c.WGPUComputePassEncoder, ptr),
                group_index,
                @ptrCast(c.WGPUBindGroup, group.ptr),
                if (dynamic_offsets) |d| @intCast(u32, d.len) else 0,
                if (dynamic_offsets) |d| d.ptr else null,
            );
        }
    }).setBindGroup,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuComputePassEncoderSetLabel(@ptrCast(c.WGPUComputePassEncoder, ptr), label);
        }
    }).setLabel,
    .insertDebugMarker = (struct {
        pub fn insertDebugMarker(ptr: *anyopaque, marker_label: [*:0]const u8) void {
            c.wgpuComputePassEncoderInsertDebugMarker(@ptrCast(c.WGPUComputePassEncoder, ptr), marker_label);
        }
    }).insertDebugMarker,
    .popDebugGroup = (struct {
        pub fn popDebugGroup(ptr: *anyopaque) void {
            c.wgpuComputePassEncoderPopDebugGroup(@ptrCast(c.WGPUComputePassEncoder, ptr));
        }
    }).popDebugGroup,
    .pushDebugGroup = (struct {
        pub fn pushDebugGroup(ptr: *anyopaque, group_label: [*:0]const u8) void {
            c.wgpuComputePassEncoderPushDebugGroup(@ptrCast(c.WGPUComputePassEncoder, ptr), group_label);
        }
    }).pushDebugGroup,
    .setPipeline = (struct {
        pub fn setPipeline(ptr: *anyopaque, pipeline: ComputePipeline) void {
            c.wgpuComputePassEncoderSetPipeline(@ptrCast(c.WGPUComputePassEncoder, ptr), @ptrCast(c.WGPUComputePipeline, pipeline.ptr));
        }
    }).setPipeline,
    .writeTimestamp = (struct {
        pub fn writeTimestamp(ptr: *anyopaque, query_set: QuerySet, query_index: u32) void {
            c.wgpuComputePassEncoderWriteTimestamp(
                @ptrCast(c.WGPUComputePassEncoder, ptr),
                @ptrCast(c.WGPUQuerySet, query_set.ptr),
                query_index,
            );
        }
    }).writeTimestamp,
};

fn wrapComputePipeline(pipeline: c.WGPUComputePipeline) ComputePipeline {
    return .{
        .ptr = pipeline.?,
        .vtable = &compute_pipeline_vtable,
    };
}

const compute_pipeline_vtable = ComputePipeline.VTable{
    .reference = (struct {
        pub fn reference(ptr: *anyopaque) void {
            c.wgpuComputePipelineReference(@ptrCast(c.WGPUComputePipeline, ptr));
        }
    }).reference,
    .release = (struct {
        pub fn release(ptr: *anyopaque) void {
            c.wgpuComputePipelineRelease(@ptrCast(c.WGPUComputePipeline, ptr));
        }
    }).release,
    .setLabel = (struct {
        pub fn setLabel(ptr: *anyopaque, label: [:0]const u8) void {
            c.wgpuComputePipelineSetLabel(@ptrCast(c.WGPUComputePipeline, ptr), label);
        }
    }).setLabel,
    .getBindGroupLayout = (struct {
        pub fn getBindGroupLayout(ptr: *anyopaque, group_index: u32) BindGroupLayout {
            return wrapBindGroupLayout(c.wgpuComputePipelineGetBindGroupLayout(
                @ptrCast(c.WGPUComputePipeline, ptr),
                group_index,
            ));
        }
    }).getBindGroupLayout,
};

test {
    _ = wrap;
    _ = interface_vtable;
    _ = interface;
    _ = createSurface;
    _ = surface_vtable;
    _ = adapter_vtable;
    _ = wrapDevice;
    _ = device_vtable;
    _ = wrapQueue;
    _ = wrapShaderModule;
    _ = wrapSwapChain;
    _ = wrapTextureView;
    _ = wrapTexture;
    _ = wrapSampler;
    _ = wrapRenderPipeline;
    _ = wrapRenderPassEncoder;
    _ = wrapRenderBundleEncoder;
    _ = wrapRenderBundle;
    _ = wrapQuerySet;
    _ = wrapPipelineLayout;
    _ = wrapExternalTexture;
    _ = wrapBindGroup;
    _ = wrapBindGroupLayout;
    _ = wrapBuffer;
    _ = wrapCommandBuffer;
    _ = wrapCommandEncoder;
    _ = wrapComputePassEncoder;
    _ = wrapComputePipeline;
}
