Skip to content

Renderer

The Renderer is the main entry point for FragmentColor and normally the first object you create.

It is used to render Shaders and Passes (single passes or any iterable of them) to a Target (canvas, window, or texture).

The Renderer internals are lazily initialized when the user creates a Target.

See the constructor Renderer::new() description below for details.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Shader, Renderer, Target};
let renderer = Renderer::new();
// Use your platform's windowing system to create a window
let window = fragmentcolor::headless_window([800, 600]);
// Create a Target from it
let target = renderer.create_target(window).await?;
let texture_target = renderer.create_texture_target([16, 16]).await?;
// RENDERING
renderer.render(&Shader::default(), &texture_target)?;
// That's it. Welcome to FragmentColor!
7 collapsed lines
let s = target.size();
assert_eq!([s.width, s.height], [800, 600]);
let s2 = texture_target.size();
assert_eq!([s2.width, s2.height], [16, 16]);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Create a new Renderer.

The renderer’s GPU adapter and device are initialized lazily on the first target you create, so the same Renderer works whether you end up rendering offscreen or attaching it to a window. By the time render() is called the GPU resources are already in place — render requires a Target, and the only way to build one is through this renderer:

  • renderer.create_target(Window) for an on-screen target, or
  • renderer.create_texture_target([w, h]) for offscreen rendering.
1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Target};
let renderer = Renderer::new();
let texture_target = renderer.create_texture_target([16, 16]).await?;
5 collapsed lines
let s = texture_target.size();
assert_eq!([s.width, s.height], [16, 16]);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Renderer::create_target(target: Canvas | Window)

Section titled “Renderer::create_target(target: Canvas | Window)”

Creates a Target attached to a platform-specific canvas or window.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Target};
let renderer = Renderer::new();
// Use your platform's windowing system to create a window.
// We officially support Winit. Check the examples folder for details.
let window = fragmentcolor::headless_window([800, 600]);
let target = renderer.create_target(window).await?;
5 collapsed lines
let s = target.size();
assert_eq!([s.width, s.height], [800, 600]);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Renderer::create_texture_target(size: [u32; 2])

Section titled “Renderer::create_texture_target(size: [u32; 2])”

Render to an offscreen texture without a Window or Canvas.

This is useful for tests, server-side rendering, or running examples in CI.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Shader, Target};
let renderer = Renderer::new();
// Create an offscreen texture target with a size of 64x64 pixels.
let target = renderer.create_texture_target([64, 64]).await?;
renderer.render(&Shader::default(), &target)?;
// get the rendered image
let image = target.get_image().await;
5 collapsed lines
// RGBA8
assert_eq!(image.len(), 64 * 64 * 4);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Create a Texture from bytes, a file path, a URL, a KTX2 container, or a pre-built TextureMipChain.

When you pass a size, the bytes are treated as raw pixels in the chosen format. Without a size, the bytes (or the file/URL contents) are decoded as PNG, JPEG, BMP, HDR, etc.

Decode and mipmap generation run on a background worker on native platforms. On the web they run inline on the caller’s thread; move heavy decode into a Web Worker if you need parallelism.

  • Rust: create_texture(input). input accepts bare bytes, (bytes, [w, h]), (bytes, format), (bytes, options), a path, a URL, a TextureMipChain, or a KTX2 input.
  • JS: await renderer.createTexture(input, options?). input is Uint8Array / ArrayBuffer / URL string / CSS selector / HTMLImageElement / HTMLCanvasElement / OffscreenCanvas / ImageData / a TextureMipChain handle. options is { size?, format?, mipmaps? }; when size is set, input is read as raw pixel bytes.
  • Python: renderer.create_texture(input, size=None, format=None, mipmaps=None). input is bytes / list[int] / str (path) / numpy ndarray[H, W, C] / TextureMipChain. Numpy arrays fill in size for you.
  • Swift / Kotlin: try await renderer.createTexture(bytes) or renderer.createTexture(chain). Overloads in the binding wrap the underlying enum, so you write the natural call.
FormTreatment
bytes (no size)Encoded image; decoded internally (PNG, JPEG, BMP, HDR, etc.).
(bytes, size) / options with size setRaw pixel bytes; bpp(format) * width * height long, no decode.
(bytes, format) / options with format setEncoded image reinterpreted as format (e.g. Rgba8Unorm for normal-map data, R16Unorm for 16-bit grayscale).
path / URLFile or HTTP fetch, then decoded.
TextureMipChainPre-built CPU mip chain; GPU-only upload. Build via TextureMipChain::prepare on a worker thread.
Ktx2Bytes / Ktx2Path / Ktx2UrlKTX2 container (BC / ETC2 / ASTC / uncompressed); the file’s declared format and pre-baked mip chain win.

A full mipmap chain is generated for source images by default (options.mipmaps = true). Set to false to skip the CPU work for textures that won’t be sampled at distance.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Renderer;
let renderer = Renderer::new();
let image = std::fs::read("logo.png")?;
let tex = renderer.create_texture(&image[..]).await?;
4 collapsed lines
_ = tex.size();
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Create a storage-class texture for compute shaders, image store/load, or as a render target. The input shapes mirror Renderer::create_texture and TextureMipChain::prepare.

Common forms:

  • (size, format) — empty storage texture, no initial data.
  • (size, format, bytes) — storage texture pre-seeded with bytes.
  • A full TextureInput { data, options } literal — pass options.usage (via TextureOptions::with_usage(...)) for non-default usage flags.

size is required; storage textures have no source to infer dimensions from. Missing it returns TextureError::InvalidInput.

options.usage overrides the default mask of STORAGE | TEXTURE | COPY_SRC | COPY_DST. The bindings expose it as a u32 bitmask, so the same flag values work in every language.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, TextureFormat};
let r = Renderer::new();
// Empty storage texture.
let tex = r.create_storage_texture(([64, 64], TextureFormat::Rgba)).await?;
1 collapsed line
_ = tex;
// Pre-seeded with bytes.
let pixels = vec![0u8; 64 * 64 * 4];
let tex2 = r
.create_storage_texture(([64, 64], TextureFormat::Rgba, pixels))
.await?;
4 collapsed lines
_ = tex2;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Create a depth texture using Depth32Float.

The created depth texture inherits the renderer’s current sample count:

  • If you called create_target(window) (surface-backed), it matches the negotiated MSAA (e.g., 2×/4×) for that surface.
  • If you are rendering offscreen via create_texture_target, it defaults to 1.

This ensures the depth attachment sample_count matches the pass sample_count. If you attach a depth texture with a different sample_count than the pass, rendering will return a descriptive validation error.

use fragmentcolor::Renderer;
let r = Renderer::new();
let depth = r.create_depth_texture([800, 600]);

Explicitly remove a texture from the renderer’s registry.

  • Call this when you replace a texture, stop a video stream, or tear down a scene, to release GPU memory.
  • If the texture is still referenced elsewhere, it will remain alive until all strong references are dropped.
1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, TextureFormat};
let renderer = Renderer::new();
let texture = renderer.create_storage_texture(([16, 16], TextureFormat::Rgba)).await?;
let id = *texture.id();
renderer.unregister_texture(id)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Read back the mip-0 contents of a registered texture as tightly-packed bytes in the texture’s native format.

  • Works on native and on the web; the call awaits the GPU readback mapping.
  • Equivalent to calling [Texture::get_image] on the texture handle. Use this entry point when you only kept the TextureId around.
  • The texture must have COPY_SRC usage. Creation helpers like create_storage_texture enable it by default.
1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, TextureFormat};
let renderer = Renderer::new();
let texture = renderer.create_storage_texture(([64, 64], TextureFormat::Rgba)).await?;
texture.write(&vec![0u8; 64 * 64 * 4])?;
let bytes = renderer.read_texture(*texture.id()).await?;
4 collapsed lines
assert_eq!(bytes.len(), 64 * 64 * 4);
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Wrap a native platform video-frame source as an external texture so a WGSL shader can sample it directly via texture_external / textureSampleBaseClampToEdge, without an intermediate CPU upload.

The source argument is platform-specific:

  • Web: HTMLVideoElement (or anything that decodes into one).
  • iOS: a CVPixelBuffer-backed handle (passed as a raw UInt64 pointer over the uniffi boundary).
  • Android: a SurfaceTexture handle (passed as a raw ULong pointer).

Returns an error on every platform today — the API surface is in place, the implementation is a follow-up.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::Renderer;
let renderer = Renderer::new();
// platform-specific source handle passed here
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }

Renders the given object to the given Target.

renderable can be a Shader, a Pass, or any iterable of Pass (Vec<Pass>, &[Pass], &[&Pass]). Passes in an iterable are rendered in order.

1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {
use fragmentcolor::{Renderer, Shader};
let renderer = Renderer::new();
let target = renderer.create_texture_target([10, 10]).await?;
let shader = Shader::default();
renderer.render(&shader, &target)?;
3 collapsed lines
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }