OpenCL bindings for the V programming language, with the complete OpenCL 1.0 through 3.0 core API, selected Khronos extensions, and optional helpers for device discovery and explicit resource ownership.
Available on VPM · Release notes · Project portfolio
Install the release described by this README:
v install antono2.opencl@v1.0.1Then install or verify native prerequisites using the setup script in the installed module. At the default V module location:
v run "$HOME/.vmodules/antono2/opencl/setup.vsh"From a source checkout, use v run setup.vsh. The script supports Ubuntu and
Debian, Fedora, Arch, openSUSE, macOS, and Windows. It reuses an existing V module.
For diagnostics without installing packages, add --check to either invocation.
On Linux, setup installs OpenCL headers, the ICD loader, and PoCL for CPU-based development. On Windows, it installs headers and the Khronos loader through vcpkg; the GPU vendor driver supplies the OpenCL implementation. macOS uses its built-in OpenCL framework. Device and optional-feature availability is always determined by the installed implementation at runtime.
For other package versions, see the releases. The package version is separate from the OpenCL API versions it exposes.
Save this as main.v, then run v run main.v:
import 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 expose the complete raw API. Optional helpers add typed errors, device discovery, and explicit resource ownership. The snippets below show individual operations; see the examples for complete programs.
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()!Owned contexts, queues, buffers, images, samplers, programs, kernels, events,
and external semaphores are @[nocopy], preventing accidental double release.
Constructors return owned pointers; pass them directly without adding another
&. When two independently closable owners are required,
call clone_ref(); it performs the matching OpenCL retain operation. SVM
allocations cannot be retained and therefore always have one unique owner.
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 ownership 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 the complete OpenCL 1.0 through 3.0 core API and selected
portable Khronos extensions 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.
CI pins the release compiler and a V3 compiler revision. The exact compiler, runner, and dependency versions are recorded in the test workflow.
| Platform | Compiler lane | Validation |
|---|---|---|
| Linux | Pinned release V, GCC | Kernels, images, SVM, and Vulkan-particle validation smoke tests |
| Linux | Pinned release V, TinyCC | Vulkan-particle compile and headless smoke test |
| Linux | Pinned V3, TinyCC | Required frontend checks and module/vector-add runtime smoke tests |
| macOS | Pinned release V, Clang | OpenCL framework ABI compilation |
| Windows | Pinned release V, MSVC | OpenCL loader ABI compilation |
| Linux | Current V master, GCC | Advisory runtime compatibility checks |
The pinned V3 frontend checks also cover the particle example and ABI probe. Windows and macOS compilation confirms the loader ABI; it does not establish that every device supports every optional feature. The moving V-master and Vulkan/GLFW-master lanes report compatibility regressions without blocking releases on an unrelated upstream change.
The canonical sources, helpers, examples, and this README are maintained in
antono2/v_opencl_bindings.
Submit changes there; its publication workflow updates this module.
Bindings are generated from Khronos' OpenCL XML registry. REGISTRY_COMMIT and
HEADERS_COMMIT identify the immutable Khronos inputs, GENERATOR_COMMIT
identifies the generator revision, and VERSION records the package version.
The publisher fills this README's installation command from VERSION and
checks documentation drift together with the other distribution files.