Skip to content

wgpu

stable

GPU rendering plugin built on wgpu exposing adapter selection, device and queue management, render pipelines, buffers, textures, and a full render-pass API for cross-platform GPU graphics.

use plugin wgpu::{wgpu.request_adapter, wgpu.enumerate_adapters, wgpu.create_surface, …}
38 functions Graphics
/ filter jk navigate Esc clear
Functions (38)
  1. wgpu.request_adapter Select a GPU adapter, optionally for a surface
  2. wgpu.enumerate_adapters List every available GPU adapter
  3. wgpu.create_surface Create a rendering surface from a native window handle
  4. wgpu.handle_count Count live GPU object handles (leak probe)
  5. GpuAdapter.name Human-readable name of the adapter
  6. GpuAdapter.request_device Open a device and queue from the adapter
  7. GpuDevice.create_shader Compile a WGSL shader module
  8. GpuDevice.create_render_pipeline Build a render pipeline from shaders
  9. GpuDevice.create_buffer Allocate a GPU buffer
  10. GpuDevice.create_command_encoder Begin recording GPU commands
  11. GpuDevice.create_pipeline_layout Create an empty pipeline layout
  12. GpuDevice.create_depth_view Create a depth texture view
  13. GpuDevice.create_bind_group Bind buffers, views, and samplers to a layout
  14. GpuDevice.create_texture Allocate a 2D or array texture
  15. GpuDevice.create_sampler Create a texture sampler
  16. GpuDevice.destroy Release the device handle
  17. GpuQueue.submit Submit command buffers for execution
  18. GpuQueue.write_buffer Write raw bytes into a buffer
  19. GpuQueue.write_floats Write a table of floats into a buffer
  20. GpuQueue.write_texture Upload pixel data into a texture
  21. GpuQueue.write_bc1_as_rgba8 Decode BC1/BC2/BC3 blocks and upload as RGBA8
  22. GpuSurface.configure Configure the surface for a size and device
  23. GpuSurface.format Get the surface's chosen texture format
  24. GpuSurface.get_current_texture Acquire the next swap-chain texture
  25. GpuSurface.save_screenshot Read back the surface and save it as PNG
  26. GpuSurfaceTexture.create_view Create a view of the surface texture
  27. GpuSurfaceTexture.present Present the rendered frame to the screen
  28. GpuTexture.create_view Create a view of a texture
  29. GpuTextureView.destroy Release a texture view handle
  30. GpuRenderPipeline.get_bind_group_layout Fetch an auto-generated bind group layout
  31. GpuCommandEncoder.begin_render_pass Start recording a render pass
  32. GpuCommandEncoder.finish Finish recording and produce a command buffer
  33. RenderPassState.set_pipeline Bind a render pipeline for the pass
  34. RenderPassState.set_bind_group Bind a bind group at an index
  35. RenderPassState.draw Record a draw call
  36. RenderPassState.draw_batch Record many draws in one FFI crossing
  37. RenderPassState.end Replay recorded commands and end the pass
  38. GpuBuffer.destroy Release a buffer handle

Overview

wgpu is a thin, handle-based binding over the wgpu GPU API, mirroring the WebGPU shape used by the JS, Rust, Python, Go, and Zig bindings. Every GPU object — adapters, devices, queues, surfaces, buffers, textures, pipelines, and render passes — is an opaque handle returned by a factory call, and you drive it with method-style calls (device.create_buffer(...), queue.submit(...), tex.present()). The module-level entry points (wgpu.request_adapter, wgpu.enumerate_adapters, wgpu.create_surface, wgpu.handle_count) sit under the lowercase wgpu namespace; everything else is reached by calling methods on the handles those entry points hand back.

The canonical flow is: create a surface from a native window, request an adapter compatible with it, ask the adapter for a {device, queue} pair, configure the surface, then each frame acquire a surface texture, record a command encoder with a render pass, submit the resulting command buffer to the queue, and present. Handles are stateful and own real GPU resources, so release them with .destroy() (often via defer) and consume surface textures / command buffers exactly once. Use this plugin when you need direct, cross-platform GPU rendering rather than a higher-level scene API.

Common patterns

Bootstrap a renderer from a winit window — surface first, then an adapter compatible with it, then a device and queue:

use plugin winit::{Window, EventLoop}
use plugin wgpu::{wgpu}

let win = Window.new(#{"title": "Triangle", "width": 800, "height": 600})
let el = EventLoop.new()

el.run(fn(event) {
  if event["event"] == "window_created" {
    let surface = wgpu.create_surface(win.native_handle())
    let adapter = wgpu.request_adapter(#{"compatible_surface": surface})
    let dq = adapter.request_device(#{"label": "main"})
    surface.configure(adapter, dq["device"], 800, 600)
    print("using adapter: {adapter.name()}")
  }
})

Render one frame: acquire the surface texture, clear it in a render pass, draw, finish the encoder, submit, and present:

use plugin wgpu::{wgpu}

fn draw_frame(surface, device, queue, pipeline) {
  let frame = surface.get_current_texture()
  if frame == nil { return }
  let view = frame.create_view()

  let encoder = device.create_command_encoder(#{})
  let pass = encoder.begin_render_pass(#{
    "color_attachments": #{
      "1": #{
        "view": view,
        "load": "clear",
        "clear_color": #{"r": 0.1, "g": 0.2, "b": 0.3, "a": 1.0},
      },
    },
  })
  pass.set_pipeline(pipeline)
  pass.draw(3, 1)
  pass.end()

  queue.submit(#{"1": encoder.finish()})
  frame.present()
}

Upload uniform data into a buffer and bind it for a draw:

use plugin wgpu::{wgpu}

fn upload_uniforms(device, queue, pipeline, values) {
  let buf = device.create_buffer(#{"size": 64, "usage": "uniform|copy_dst"})
  queue.write_floats(buf, 0, values)

  let layout = pipeline.get_bind_group_layout(0)
  let bg = device.create_bind_group(layout, #{"0": #{"buffer": buf}})
  return bg
}

Select a GPU adapter, optionally for a surface

Requests a GPU adapter. Pass a config table with optional power_preference ("high", "low", or "none") and a compatible_surface handle so the adapter is chosen to support that surface. Returns a GpuAdapter handle, or errors if no suitable GPU is found.

use plugin wgpu::{wgpu}

let adapter = wgpu.request_adapter(#{"power_preference": "high"})
print(adapter.name())

When you already have a surface, pass it so adapter selection guarantees compatibility:

use plugin wgpu::{wgpu}

fn pick_adapter(surface) {
  return wgpu.request_adapter(#{"compatible_surface": surface})
}

List every available GPU adapter

Returns a table listing every available GPU adapter. Each entry is a table with name, backend, and device_type string fields.

use plugin wgpu::{wgpu}

let adapters = wgpu.enumerate_adapters()
print("found {#adapters} adapters")

Inspect the backend and type of each detected GPU:

use plugin wgpu::{wgpu}

for entry in wgpu.enumerate_adapters() {
  print("{entry["name"]} — {entry["backend"]} ({entry["device_type"]})")
}

Create a rendering surface from a native window handle

Creates a wgpu rendering surface from a native window handle. Pass either a window.native_handle() table (cross-platform: win32, xlib, xcb, wayland, appkit) or, on Windows only, a raw Win32 HWND integer. Create the surface before requesting a compatible adapter.

use plugin winit::{Window, EventLoop}
use plugin wgpu::{wgpu}

let win = Window.new(#{"title": "Demo", "width": 800, "height": 600})
let el = EventLoop.new()

el.run(fn(event) {
  if event["event"] == "window_created" {
    let surface = wgpu.create_surface(win.native_handle())
    let adapter = wgpu.request_adapter(#{"compatible_surface": surface})
    let dq = adapter.request_device(#{})
    surface.configure(adapter, dq["device"], 800, 600)
  }
})

Count live GPU object handles (leak probe)

Returns the number of live entries in the plugin handle table. Useful for detecting GPU object leaks — this value should be stable across frames in a steady-state render loop, and growth signals a handle that was created without a matching .destroy() or consume step.

use plugin wgpu::{wgpu}

print("live handles: {wgpu.handle_count()}")

Human-readable name of the adapter

Returns the human-readable name of the adapter, e.g. the GPU model string.

use plugin wgpu::{wgpu}

let adapter = wgpu.request_adapter(#{})
print("GPU: {adapter.name()}")

Open a device and queue from the adapter

Opens a logical device and queue from the adapter. Pass an optional config table with a label string. Returns a table with device (a GpuDevice) and queue (a GpuQueue). BC texture compression is enabled automatically when the adapter supports it.

use plugin wgpu::{wgpu}

let adapter = wgpu.request_adapter(#{})
let dq = adapter.request_device(#{"label": "renderer"})
let device = dq["device"]
let queue = dq["queue"]

Compile a WGSL shader module

Compiles a WGSL shader module. The config table requires a source string (the WGSL code) and accepts an optional label. Returns a GpuShader handle to use as a vertex or fragment stage in a render pipeline.

use plugin wgpu::{wgpu}

fn make_shader(device, wgsl) {
  return device.create_shader(#{"label": "main", "source": wgsl})
}

Build a render pipeline from shaders

Builds a render pipeline. The config table requires vertex_shader (a GpuShader handle) and accepts fragment_shader, vertex_entry (default "vs_main"), fragment_entry (default "fs_main"), color_format (default "bgra8unorm"), depth_format (presence enables a Depth32Float depth test), layout, and label. Returns a GpuRenderPipeline handle.

use plugin wgpu::{wgpu}

fn make_pipeline(device, vs, fs, fmt) {
  return device.create_render_pipeline(#{
    "vertex_shader": vs,
    "fragment_shader": fs,
    "color_format": fmt,
  })
}

Add a depth_format to opt into depth testing for 3D scenes:

use plugin wgpu::{wgpu}

fn make_3d_pipeline(device, vs, fs, fmt) {
  return device.create_render_pipeline(#{
    "vertex_shader": vs,
    "fragment_shader": fs,
    "color_format": fmt,
    "depth_format": "depth32float",
  })
}

Allocate a GPU buffer

Allocates a GPU buffer. The config table requires a size in bytes and accepts a usage string (a |-separated list of vertex, index, uniform, storage, copy_src, copy_dst, indirect; default "copy_dst|copy_src") plus an optional label. Returns a GpuBuffer handle.

use plugin wgpu::{wgpu}

fn make_vertex_buffer(device, byte_size) {
  return device.create_buffer(#{"size": byte_size, "usage": "vertex|copy_dst"})
}

Begin recording GPU commands

Begins recording GPU commands. Pass an optional config table with a label. Returns a GpuCommandEncoder to open render passes on; calling finish() consumes it.

use plugin wgpu::{wgpu}

let encoder = device.create_command_encoder(#{"label": "frame"})

Create an empty pipeline layout

Creates a pipeline layout (currently with no explicit bind group layouts or push constant ranges). Pass an optional config table with a label. Returns a GpuPipelineLayout handle.

use plugin wgpu::{wgpu}

let layout = device.create_pipeline_layout(#{"label": "empty"})

Create a depth texture view

Creates a Depth32Float depth texture of the given size and returns a GpuTextureView over it, ready to use as the depth_view of a render pass. Match its size to the surface so the depth test aligns with the color target.

use plugin wgpu::{wgpu}

let depth = device.create_depth_view(800, 600)

Bind buffers, views, and samplers to a layout

Binds resources to a GpuBindGroupLayout. Pass the layout handle plus an entries table keyed by binding index (as string keys), where each value is a table holding a buffer, texture_view, or sampler handle. A legacy buffer-only form create_bind_group(layout, buffer, binding) is also accepted. Returns a GpuBindGroup handle.

use plugin wgpu::{wgpu}

fn make_bind_group(device, pipeline, buf, view, sampler) {
  let layout = pipeline.get_bind_group_layout(0)
  return device.create_bind_group(layout, #{
    "0": #{"buffer": buf},
    "1": #{"texture_view": view},
    "2": #{"sampler": sampler},
  })
}

Allocate a 2D or array texture

Allocates a 2D (or array) texture. The config table accepts width (default 256), height (default 256), array_layers (default 1), format (default "bgra8unorm"; also supports rgba8unorm, srgb variants, rgba16float, r32float, and BC1/BC2/BC3 names), usage (a |-separated list of texture_binding, copy_dst, copy_src, render_attachment, storage_binding; default "texture_binding|copy_dst"), and an optional label. Returns a GpuTexture handle.

use plugin wgpu::{wgpu}

fn make_texture(device, w, h) {
  return device.create_texture(#{
    "width": w,
    "height": h,
    "format": "rgba8unorm",
    "usage": "texture_binding|copy_dst",
  })
}

Create a texture sampler

Creates a texture sampler. Pass an optional config table with address_mode_u, address_mode_v, address_mode_w ("clamp", "repeat", "mirror", "border"), mag_filter, min_filter, mipmap_filter ("linear" or "nearest"), and an optional label. A bare create_sampler(...) with no config yields clamp-to-edge + linear defaults. Returns a GpuSampler handle.

use plugin wgpu::{wgpu}

fn make_sampler(device) {
  return device.create_sampler(#{
    "address_mode_u": "repeat",
    "address_mode_v": "repeat",
    "mag_filter": "nearest",
  })
}

Release the device handle

Releases the device handle and frees its GPU resources. Pairs well with defer for scope-bound cleanup.

use plugin wgpu::{wgpu}

let dq = wgpu.request_adapter(#{}).request_device(#{})
let device = dq["device"]
defer device.destroy()

Submit command buffers for execution

Submits a table of GpuCommandBuffer handles (produced by encoder.finish()) for execution on the GPU. Each command buffer is consumed and its handle is freed, so submit each buffer exactly once.

use plugin wgpu::{wgpu}

let cmd = encoder.finish()
queue.submit(#{"1": cmd})

Write raw bytes into a buffer

Writes raw bytes into a GpuBuffer at a byte offset. The data argument is a byte string or byte value; for tables of numbers use write_floats instead.

use plugin wgpu::{wgpu}

queue.write_buffer(index_buffer, 0, index_bytes)

Write a table of floats into a buffer

Writes a table of numbers into a GpuBuffer as tightly-packed little-endian f32 values, starting at byte offset. Convenient for uniforms and vertex data built up in Zolo as a list of numbers.

use plugin wgpu::{wgpu}

let mvp = #{"1": 1.0, "2": 0.0, "3": 0.0, "4": 0.0}
queue.write_floats(uniform_buffer, 0, mvp)

Upload pixel data into a texture

Uploads pixel data into a GpuTexture. Pass the pixel bytes, the texture width and height, and an optional layer index as a fifth argument for array textures. Strides are computed automatically, including block alignment for BC formats.

use plugin wgpu::{wgpu}

queue.write_texture(tex, rgba_bytes, 256, 256)

Target a specific layer of a 2D-array texture with the optional layer argument:

use plugin wgpu::{wgpu}

queue.write_texture(atlas, face_bytes, 64, 64, 3)

Decode BC1/BC2/BC3 blocks and upload as RGBA8

Software-decodes block-compressed data and uploads it as RGBA8. Defaults to BC1 (DXT1); pass an optional sixth layer argument (target array layer, default 0) and a seventh compression argument of 3 for BC2 (DXT3) or 5 for BC3 (DXT5). Use this when a GPU's hardware BC decoder misbehaves.

use plugin wgpu::{wgpu}

queue.write_bc1_as_rgba8(tex, dxt1_bytes, 256, 256)

Decode a DXT5 (BC3) block instead by passing the layer and compression mode:

use plugin wgpu::{wgpu}

queue.write_bc1_as_rgba8(tex, dxt5_bytes, 256, 256, 0, 5)

Configure the surface for a size and device

Configures the surface's swap chain for the given adapter, device, and pixel size. Picks an sRGB format when available, prefers a low-latency present mode, and enables COPY_SRC so save_screenshot can read the surface back. Re-run this on window resize.

use plugin wgpu::{wgpu}

surface.configure(adapter, device, 1280, 720)

Get the surface's chosen texture format

Returns the texture format the surface was configured with as a string (e.g. "bgra8unorm-srgb"). Pass this to create_render_pipeline's color_format so the pipeline output matches the surface.

use plugin wgpu::{wgpu}

let fmt = surface.format()
let pipeline = device.create_render_pipeline(#{
  "vertex_shader": vs,
  "fragment_shader": fs,
  "color_format": fmt,
})

Acquire the next swap-chain texture

Acquires the next swap-chain texture to render into. Returns a GpuSurfaceTexture, or nil when the surface is outdated or lost (reconfigure and retry next frame). Create a view from it, render, then present() it.

use plugin wgpu::{wgpu}

let frame = surface.get_current_texture()
if frame == nil { return }
let view = frame.create_view()

Read back the surface and save it as PNG

Copies the current surface texture into a CPU-mappable buffer, swizzles BGRA to RGBA, and writes it to path as a PNG. Call this BEFORE present() — after present the swap-chain image is consumed.

use plugin wgpu::{wgpu}

let frame = surface.get_current_texture()
if frame != nil {
  surface.save_screenshot(device, queue, frame, "frame.png")
  frame.present()
}

Create a view of the surface texture

Creates a GpuTextureView over the surface texture, suitable as a render-pass color attachment. The view is owned by the swap-chain image and becomes invalid once the texture is presented (the plugin reaps it automatically).

use plugin wgpu::{wgpu}

let frame = surface.get_current_texture()
let view = frame.create_view()

Present the rendered frame to the screen

Presents the rendered frame to the screen and consumes the surface texture (its handle and any derived views are freed). Call once per acquired frame, after submitting your command buffers.

use plugin wgpu::{wgpu}

queue.submit(#{"1": encoder.finish()})
frame.present()

Create a view of a texture

Creates a GpuTextureView over a texture. Pass an optional config table with a dimension ("2d", "2d-array", "cube", "cube-array", "3d") to override wgpu's default view dimension. Returns a GpuTextureView handle for binding.

use plugin wgpu::{wgpu}

let view = tex.create_view(#{})

View an array texture as a cube map:

use plugin wgpu::{wgpu}

let cube_view = cube_tex.create_view(#{"dimension": "cube"})

Release a texture view handle

Releases a texture view handle. Use for views you created from regular textures; surface-texture views are reaped automatically on present.

use plugin wgpu::{wgpu}

defer view.destroy()

Fetch an auto-generated bind group layout

Returns the auto-generated GpuBindGroupLayout at the given group index of the pipeline. Feed it to device.create_bind_group to bind resources matching the shader's declared bindings.

use plugin wgpu::{wgpu}

let layout = pipeline.get_bind_group_layout(0)
let bg = device.create_bind_group(layout, #{"0": #{"buffer": uniforms}})

Start recording a render pass

Starts recording a render pass. The config table holds color_attachments (a table keyed by index, each with a view handle plus optional load ("clear"/"load"), clear_color #{r, g, b, a}, and store ("store"/"discard")), an optional depth_view handle, and an optional depth_load. Returns a RenderPassState you record draws into; the work is replayed when you call end().

use plugin wgpu::{wgpu}

let pass = encoder.begin_render_pass(#{
  "color_attachments": #{
    "1": #{
      "view": view,
      "load": "clear",
      "clear_color": #{"r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0},
    },
  },
})

Attach a depth view for a 3D pass:

use plugin wgpu::{wgpu}

let pass = encoder.begin_render_pass(#{
  "color_attachments": #{
    "1": #{"view": view, "load": "clear", "clear_color": #{"r": 0.1, "g": 0.1, "b": 0.1, "a": 1.0}},
  },
  "depth_view": depth_view,
  "depth_load": "clear",
})

Finish recording and produce a command buffer

Finishes recording and produces a GpuCommandBuffer ready for queue.submit. Consumes the encoder, so its handle is freed and cannot be reused.

use plugin wgpu::{wgpu}

let cmd = encoder.finish()
queue.submit(#{"1": cmd})

Bind a render pipeline for the pass

Binds a GpuRenderPipeline for subsequent draws in the pass. The binding is recorded and applied when end() replays the pass.

use plugin wgpu::{wgpu}

pass.set_pipeline(pipeline)

Bind a bind group at an index

Records binding a GpuBindGroup at group index. Bind-group sets and draws are replayed in the exact order you issued them, so you can interleave per-draw bind groups with draws.

use plugin wgpu::{wgpu}

pass.set_bind_group(0, bind_group)
pass.draw(3, 1)

Record a draw call

Records a draw call for vertex_count vertices and instance_count instances, with optional first_vertex and first_instance offsets (both default 0). The draw is replayed in order at end().

use plugin wgpu::{wgpu}

pass.draw(3, 1)

Draw many instances from a vertex offset:

use plugin wgpu::{wgpu}

pass.draw(6, 100, 0, 0)

Record many draws in one FFI crossing

Records many draw calls in a single Lua↔Rust crossing. Pass a flat table whose length is a multiple of 4 — [vertex_count, instance_count, first_vertex, first_instance, ...] per draw. A large win over per-draw draw() calls when rendering thousands of slots per frame.

use plugin wgpu::{wgpu}

let draws = #{
  "1": 3, "2": 1, "3": 0, "4": 0,
  "5": 6, "6": 1, "7": 3, "8": 0,
}
pass.draw_batch(draws)

Replay recorded commands and end the pass

Replays the recorded pipeline, bind-group, and draw commands against the encoder, ends the render pass, and frees the pass handle. Call once after recording all draws for the pass.

use plugin wgpu::{wgpu}

pass.set_pipeline(pipeline)
pass.draw(3, 1)
pass.end()

Release a buffer handle

Releases a buffer handle and frees its GPU memory. Use defer to release buffers that outlive a single frame.

use plugin wgpu::{wgpu}

let buf = device.create_buffer(#{"size": 1024, "usage": "vertex|copy_dst"})
defer buf.destroy()
enespt-br