Generated OpenCL bindings for the V programming language.
Available as antono2.opencl on VPM.
The bindings are generated from Khronos' canonical OpenCL XML registry by
antono2/v_opencl_bindings.
REGISTRY_COMMIT and HEADERS_COMMIT record the immutable Khronos inputs used
for this release. GENERATOR_COMMIT identifies the exact canonical generator
revision from which the published module was synchronized.
Install the native OpenCL development prerequisites and this V module:
v run setup.vshWhen running from an installed module, use
v run ~/.vmodules/antono2/opencl/setup.vsh. Ubuntu and Debian, Fedora, Arch,
openSUSE, macOS, and Windows are supported. v run setup.vsh --check performs
a read-only diagnostic pass. On Windows, the script installs the Khronos loader
and headers through vcpkg; the current GPU vendor driver still supplies the
actual OpenCL implementation. macOS uses its built-in OpenCL framework.
| Platform | V compiler | C compiler | Validation level |
|---|---|---|---|
| Ubuntu 24.04 | V 0.5.2 | GCC | Runtime kernels, images, SVM, and Vulkan-particle validation smoke tests |
| Ubuntu 24.04 | V 0.5.2 | TinyCC | Vulkan-particle compile and zero-copy headless smoke test |
| macOS 14 | V 0.5.2 | Clang | OpenCL framework ABI compilation |
| Windows Server 2022 | V 0.5.2 | MSVC | OpenCL loader ABI compilation |
| Ubuntu 24.04 | Current V master | GCC | Advisory runtime compatibility lane |
V 0.5.2 is the supported baseline. A successful build confirms loader ABI compatibility; availability of devices and optional features is determined by the installed OpenCL implementation at runtime.
Install the module from VPM:
v install antono2.openclimport antono2.opencl as cl
fn main() {
mut count := u32(0)
result := cl.get_platform_ids(0, unsafe { nil }, &count)
if result != cl.success {
panic('clGetPlatformIDs failed: ${result}')
}
println('OpenCL platforms: ${count}')
}The generated functions remain available as the complete low-level API. An opt-in, hand-written layer adds typed errors and safe discovery helpers without hiding native OpenCL handles:
for platform in cl.platforms()! {
println(cl.platform_info_string(platform, cl.platform_name)!)
for device in cl.devices(platform, cl.device_type_all)! {
println(' ${cl.device_info_string(device, cl.device_name)!}')
}
}Contexts and queues use explicit, idempotent cleanup:
mut context := cl.new_context(device)!
defer { context.close() or {} }
mut queue := context.command_queue(device, cl.CommandQueueProperties(0))!
defer { queue.close() or {} }
mut buffer := cl.new_buffer[f32](&context, cl.mem_read_write, 1024)!
defer { buffer.close() or {} }
buffer.write(&queue, 0, []f32{len: 1024, init: f32(index)})!The element type used by Buffer[T], typed transfers, and kernel arguments
must be a plain C-layout value without V-managed references such as strings,
maps, or slices. Element-count multiplication is checked for overflow before
an OpenCL allocation or transfer call.
Source compilation preserves compiler diagnostics through ProgramBuildError. Owned
kernels support typed scalar and buffer arguments plus one-dimensional dispatch:
mut program := cl.build_source_program(&context, device, source, '')!
defer { program.close() or {} }
mut kernel := program.kernel('transform')!
defer { kernel.close() or {} }
kernel.set_buffer_arg(0, buffer.handle)!
kernel.set_slice_arg(1, [f32(0.5), 1.0])! // e.g. an OpenCL float2
kernel.enqueue_1d(&queue, usize(buffer.count), 0)!Non-blocking transfers and dispatch return owned events and accept native event dependency lists. Host slices must remain alive until their transfer event completes:
mut uploaded := buffer.write_async(&queue, 0, values, []cl.Event{})!
mut dispatched := kernel.enqueue_1d_after(&queue, usize(buffer.count), 0,
[uploaded.handle])!
mut downloaded := buffer.read_async(&queue, 0, mut result, [dispatched.handle])!
downloaded.wait()!
profile := downloaded.profile()! // queue must use cl.queue_profiling_enable
downloaded.close()!
dispatched.close()!
uploaded.close()!For multidimensional kernels, enqueue_nd_after() accepts one to three global
dimensions and either a matching local-size slice or an empty slice for an
implementation-selected work-group size.
Typed 2D images validate that T represents one complete pixel, provide checked
full-image and region transfers, and bind directly to kernels alongside owned
samplers:
format := cl.ImageFormat{
image_channel_order: cl.rgba
image_channel_data_type: cl.unorm_int8
}
mut image := cl.new_image_2d[u32](&context, cl.mem_read_write, format, 64, 64)!
defer { image.close() or {} }
mut sampler := cl.new_sampler(&context, false, cl.address_clamp_to_edge,
cl.filter_nearest)!
defer { sampler.close() or {} }
image.write(&queue, pixels)!
image.set_kernel_arg(&kernel, 0)!
kernel.set_sampler_arg(1, &sampler)!Shared virtual memory is similarly typed and capability-gated. Coarse-grained allocations can use checked copies or explicit map/unmap transitions, and can be bound directly to a kernel:
svm_capabilities := cl.device_svm_support(device)!
if svm_capabilities & (cl.device_svm_coarse_grain_buffer |
cl.device_svm_fine_grain_buffer) != 0 {
mut shared := cl.new_svm[u32](&context, cl.mem_read_write, 1024, 0)!
defer { shared.close() }
shared.write(&queue, 0, values)!
shared.set_kernel_arg(&kernel, 0)!
}Apple's OpenCL 1.2 framework does not expose SVM entry points, so SVM capability discovery reports the feature as unavailable on macOS. Image support remains available according to the selected device's advertised formats.
Optional features can be discovered once without substring matching or unsafe UUID buffers:
capabilities := cl.device_capabilities(device)!
if capabilities.has_all(['cl_khr_external_memory',
'cl_khr_external_memory_opaque_fd']) && capabilities.device_uuid {
device_uuid := capabilities.uuid()!
}Opaque-FD external objects use the same explicit ownership and event model. File descriptors are obtained from the exporting API; its handle-ownership rules still apply:
memory_interop := cl.load_external_memory_interop(platform, capabilities)!
mut shared := memory_interop.import_opaque_fd_buffer[f32](&context, memory_fd,
element_count, cl.mem_read_write)!
defer { shared.close() or {} }
semaphore_interop := cl.load_external_semaphore_interop(platform, capabilities)!
mut ready := semaphore_interop.import_opaque_fd(&context, semaphore_fd)!
defer { ready.close() or {} }
mut waited := ready.wait(&queue, [])!
mut acquired := memory_interop.acquire(&queue, [shared.handle], [waited.handle])!
defer { acquired.close() or {} }
defer { waited.close() or {} }Owned events expose explicit wait lists without manual reference counting:
mut uploaded := queue.marker([]cl.Event{})!
defer { uploaded.close() or {} }
mut ready := queue.barrier([uploaded.handle])!
defer { ready.close() or {} }
ready.wait()!See API_DESIGN.md for the conventions shared with the companion
Vulkan convenience layer.
See OWNERSHIP.md for the current copy and cleanup rules.
examples/vector_add is a compact introduction to the
owned convenience API. It runs asynchronous buffer uploads, a kernel, profiled
readback, and explicit cleanup.
examples/image_svm copies a typed RGBA image through an
image kernel and owned sampler, then executes a second kernel directly over a
typed SVM allocation when the selected device advertises buffer SVM support.
examples/vulkan_particles is an interactive particle-galaxy
example that combines OpenCL compute with Vulkan presentation. On UUID-matched devices it imports
one exported Vulkan allocation into OpenCL and synchronizes access with reusable opaque-FD
semaphores. It also includes a portable host-staged fallback, swapchain recreation, velocity
trails, interactive controls, and display-independent interoperability smoke tests.
The example is a separate nested V module, so its vulkan and glfw dependencies are not
dependencies of applications that only import opencl.
The module exposes all 114 cumulative OpenCL 1.0 through 3.0 commands and 14
portable Khronos extension entry points with V-style snake-case wrappers,
including platform and device discovery, contexts, queues, memory and images,
programs, kernels, events, profiling, synchronization, and object lifecycle.
The bindings generator reads command prototypes, types, pointer depth, and all
OpenCL 1.0 through 3.0 core constants from Khronos' XML registry. Constants are
exposed using their corresponding OpenCL typedefs.
Core command callbacks use named V function types, allowing callback signatures
to be checked at compile time while optional callbacks still accept unsafe { nil }.
The initial extension set covers cl_khr_il_program,
cl_khr_create_command_queue, cl_khr_subgroups, and
cl_khr_suggested_local_work_size.
Zero-copy synchronization support covers cl_khr_semaphore,
cl_khr_external_semaphore, and cl_khr_external_memory, including opaque-FD,
DMA-BUF, and sync-file handle variants.
cl_khr_device_uuid provides UUID, LUID, and node-mask device queries for
matching an OpenCL device with another compute or graphics API.
Optional extension commands are resolved through the ICD at runtime instead of
being required linker symbols, so applications that do not use them can still
build against older OpenCL loaders.
CI exercises complete typed buffer, image/sampler, and SVM kernel paths, OpenCL
1.1 user events, an OpenCL 1.2 marker-with-wait-list dependency, and OpenCL 2.0
property-list queue creation and SVM allocation on PoCL.
OpenCL 2.1 coverage additionally checks synchronized device and host timer
queries; IL programs, kernel cloning, subgroup queries, and SVM migration are
present in the generated API.
OpenCL 2.2 adds program specialization constants and program-release callbacks.
OpenCL 3.0 adds numeric version helpers, NameVersion, context destructor
callbacks, and property-based buffer and image creation.