GPU texture rendering surface.
Provides a CPU-side framebuffer that is uploaded to the GPU every frame and rendered as a fullscreen quad. This is useful for:
- Custom canvas rendering
- ML tensor visualization
- Game-like rendering
- Video processing
- Shader effects
The rendering pipeline:
- Elixir issues render commands (fill_rect, blit, etc.)
- Rust processes commands on a dedicated render thread
- CPU framebuffer is uploaded as GPU texture
- Fullscreen quad is rendered with the texture
Example
# Create a 256x256 GPU surface
{:ok, surface} = Dala.Gpu.create_surface(256, 256)
# Issue render commands
Dala.Gpu.clear(surface, :black)
Dala.Gpu.fill_rect(surface, 10, 10, 100, 100, :red)
Dala.Gpu.present(surface)
# Direct pixel access (for advanced use)
Dala.Gpu.with_pixels(surface, fn pixels ->
# pixels is a binary of RGBA8888 data
# modify directly for maximum performance
end)
Summary
Functions
Execute a batch of pre-encoded command binaries atomically.
Blit a loaded sprite at the given position.
Clear the entire surface with a solid color.
Create a GPU compute buffer.
Free a GPU compute buffer.
Free multiple GPU compute buffers.
Run a GPU compute pipeline.
Run a GPU compute kernel synchronously.
Create a new GPU surface with the given dimensions.
Destroy a GPU surface and free all associated GPU resources.
Dispatch a GPU compute shader on the surface.
Draw a circle outline.
Draw a loaded image onto the framebuffer at the given position and size.
Draw a line between two points with the given color.
Draw a rounded rectangle outline.
Draw a triangle outline from three points.
Fill a circle.
Fill a rectangle with a solid color.
Fill a rounded rectangle.
Fill a triangle.
Get the current pixel data as an RGBA8888 binary.
Get the height of the surface in pixels.
Load an image into the GPU texture pool.
Load or hot-reload a named shader.
Load a sprite into the texture atlas for later blitting.
Present the surface — flush the command queue and update the GPU texture.
Remove an image from the GPU texture pool.
Remove a sprite from the texture atlas.
Reset the clipping region to the full framebuffer.
Resize an existing GPU surface. This may reallocate the GPU texture.
Set the clipping rectangle. Pass enabled: false to disable clipping.
Set the pixel data directly from an RGBA8888 binary.
Set a uniform value on the current shader pipeline.
Check if the GPU backend supports compute shaders.
Get the width of the surface in pixels.
Modify pixels via a callback for maximum performance.
Types
@type color() :: Dala.Gpu.Command.color()
@type surface_pid() :: pid()
Functions
@spec batch(surface_pid(), [binary()]) :: :ok
Execute a batch of pre-encoded command binaries atomically.
@spec blit(surface_pid(), non_neg_integer(), integer(), integer()) :: :ok
Blit a loaded sprite at the given position.
@spec clear(surface_pid(), color()) :: :ok
Clear the entire surface with a solid color.
@spec compute_buffer(list(), tuple(), atom()) :: Dala.Gpu.Compute.Buffer.t()
Create a GPU compute buffer.
Delegates to Dala.Gpu.Compute.buffer/3.
@spec compute_free(Dala.Gpu.Compute.Buffer.t()) :: :ok
Free a GPU compute buffer.
Delegates to Dala.Gpu.Compute.free/1.
@spec compute_free_many([Dala.Gpu.Compute.Buffer.t()]) :: :ok
Free multiple GPU compute buffers.
Delegates to Dala.Gpu.Compute.free_many/1.
Run a GPU compute pipeline.
Delegates to Dala.Gpu.Compute.pipeline/0, Dala.Gpu.Compute.pipeline_add/2,
and Dala.Gpu.Compute.pipeline_run/1.
@spec compute_run( atom(), [Dala.Gpu.Compute.Buffer.t()], Dala.Gpu.Compute.Buffer.t(), map() ) :: :ok | {:error, term()}
Run a GPU compute kernel synchronously.
Delegates to Dala.Gpu.Compute.run_kernel/4.
@spec create_surface(non_neg_integer(), non_neg_integer()) :: {:ok, surface_pid()} | {:error, term()}
Create a new GPU surface with the given dimensions.
Returns {:ok, pid} where pid is the surface GenServer.
The surface is automatically cleaned up when the calling process exits
or when destroy_surface/1 is called.
@spec destroy_surface(surface_pid()) :: :ok
Destroy a GPU surface and free all associated GPU resources.
@spec dispatch_compute( surface_pid(), String.t(), binary(), {non_neg_integer(), non_neg_integer(), non_neg_integer()} ) :: :ok | {:error, term()}
Dispatch a GPU compute shader on the surface.
shader_source is the shader source code (MSL for Metal, GLSL for OpenGL ES).
params is a binary of parameter data passed to the shader.
workgroup_count is the number of threadgroups to dispatch {x, y, z}.
For filter presets, see Dala.Media.Filter.
@spec draw_circle(surface_pid(), integer(), integer(), non_neg_integer(), color()) :: :ok
Draw a circle outline.
@spec draw_image( surface_pid(), non_neg_integer(), integer(), integer(), non_neg_integer(), non_neg_integer() ) :: :ok
Draw a loaded image onto the framebuffer at the given position and size.
The image is scaled to fit the destination rectangle. For best quality, load the image at its native resolution and scale via the destination size.
Example
# Full-size render
Dala.Gpu.draw_image(surface, image_id, 0, 0, 640, 480)
# PiP mode: small overlay in the corner
Dala.Gpu.draw_image(surface, image_id, 540, 20, 120, 90)
Draw a line between two points with the given color.
@spec draw_round_rect( surface_pid(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), color() ) :: :ok
Draw a rounded rectangle outline.
@spec draw_triangle( surface_pid(), integer(), integer(), integer(), integer(), integer(), integer(), color() ) :: :ok
Draw a triangle outline from three points.
@spec fill_circle(surface_pid(), integer(), integer(), non_neg_integer(), color()) :: :ok
Fill a circle.
@spec fill_rect( surface_pid(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), color() ) :: :ok
Fill a rectangle with a solid color.
@spec fill_round_rect( surface_pid(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), color() ) :: :ok
Fill a rounded rectangle.
@spec fill_triangle( surface_pid(), integer(), integer(), integer(), integer(), integer(), integer(), color() ) :: :ok
Fill a triangle.
@spec get_pixels(surface_pid()) :: binary()
Get the current pixel data as an RGBA8888 binary.
The binary size is width * height * 4 bytes.
@spec height(surface_pid()) :: non_neg_integer()
Get the height of the surface in pixels.
@spec load_image( surface_pid(), non_neg_integer(), binary(), non_neg_integer(), non_neg_integer() ) :: :ok
Load an image into the GPU texture pool.
id is a unique non-negative integer identifying the image.
rgba_binary is the pixel data in RGBA8888 format.
The image can then be rendered with draw_image/6.
Example
# Load a PNG-decoded binary
{:ok, rgba_data} = Image.load("photo.png")
Dala.Gpu.load_image(surface, 1, rgba_data, 640, 480)
# Draw it at position (100, 200) scaled to 320x240
Dala.Gpu.draw_image(surface, 1, 100, 200, 320, 240)
@spec load_shader(surface_pid(), String.t(), String.t()) :: :ok | {:error, term()}
Load or hot-reload a named shader.
@spec load_sprite( surface_pid(), non_neg_integer(), binary(), non_neg_integer(), non_neg_integer() ) :: :ok
Load a sprite into the texture atlas for later blitting.
id is a unique non-negative integer identifying the sprite.
rgba_binary is the pixel data in RGBA8888 format.
@spec present(surface_pid()) :: :ok
Present the surface — flush the command queue and update the GPU texture.
@spec remove_image(surface_pid(), non_neg_integer()) :: :ok
Remove an image from the GPU texture pool.
@spec remove_sprite(surface_pid(), non_neg_integer()) :: :ok
Remove a sprite from the texture atlas.
@spec reset_clip(surface_pid()) :: :ok
Reset the clipping region to the full framebuffer.
@spec resize_surface(surface_pid(), non_neg_integer(), non_neg_integer()) :: :ok
Resize an existing GPU surface. This may reallocate the GPU texture.
@spec set_clip( surface_pid(), non_neg_integer(), non_neg_integer(), non_neg_integer(), non_neg_integer(), boolean() ) :: :ok
Set the clipping rectangle. Pass enabled: false to disable clipping.
@spec set_pixels(surface_pid(), binary()) :: :ok
Set the pixel data directly from an RGBA8888 binary.
The binary must be exactly width * height * 4 bytes.
@spec set_uniform(surface_pid(), String.t(), binary()) :: :ok | {:error, term()}
Set a uniform value on the current shader pipeline.
@spec supports_compute(surface_pid()) :: boolean()
Check if the GPU backend supports compute shaders.
@spec width(surface_pid()) :: non_neg_integer()
Get the width of the surface in pixels.
@spec with_pixels(surface_pid(), (binary() -> binary())) :: :ok
Modify pixels via a callback for maximum performance.
The callback receives the current RGBA8888 binary and must return
a new RGBA8888 binary of the same size. This avoids an extra
binary copy compared to get_pixels/set_pixels.
Example
Dala.Gpu.with_pixels(surface, fn pixels ->
# Set the first pixel to red
<<255, 0, 0, 255, rest::binary>> = pixels
rest
end)