Renderer
Description
Section titled “Description”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.
Example
Section titled “Example”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 windowlet window = fragmentcolor::headless_window([800, 600]);
// Create a Target from itlet target = renderer.create_target(window).await?;let texture_target = renderer.create_texture_target([16, 16]).await?;
// RENDERINGrenderer.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()) }import { Shader, Renderer } from "fragmentcolor";
const renderer = new Renderer();
// Use your platform's windowing system to create a windowconst canvas = document.createElement('canvas');
// Create a Target from itconst target = await renderer.createTarget(canvas);const texture_target = await renderer.createTextureTarget([16, 16]);
// RENDERINGrenderer.render(new Shader(""), texture_target);
// That's it. Welcome to FragmentColor!from rendercanvas.auto import RenderCanvas, loop
from fragmentcolor import Shader, Renderer
renderer = Renderer()
# Use your platform's windowing system to create a windowcanvas = RenderCanvas(size=(800, 600))
# Create a Target from ittarget = renderer.create_target(canvas)texture_target = renderer.create_texture_target([16, 16])
# RENDERINGrenderer.render(Shader(""), texture_target)
# That's it. Welcome to FragmentColor!import FragmentColor
let renderer = Renderer()
// Use your platform's windowing system to create a window// iOS: window/canvas provided by CAMetalLayer at runtime
// Create a Target from itlet target = try await renderer.createTextureTarget([800, 600])let texture_target = try await renderer.createTextureTarget([16, 16])
// RENDERINGtry renderer.render(Shader(""), texture_target)
// That's it. Welcome to FragmentColor!import org.fragmentcolor.*
val renderer = Renderer()
// Use your platform's windowing system to create a window// HEADLESS: canvas creation not needed on Android
// Create a Target from itval target = renderer.createTextureTarget(800u, 600u)val texture_target = renderer.createTextureTarget(16u, 16u)
// RENDERINGrenderer.render(Shader(""), texture_target)
// That's it. Welcome to FragmentColor!Methods
Section titled “Methods”Renderer::new()
Section titled “Renderer::new()”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, orrenderer.create_texture_target([w, h])for offscreen rendering.
Example
Section titled “Example”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()) }import { Renderer } from "fragmentcolor";
const renderer = new Renderer();const texture_target = await renderer.createTextureTarget([16, 16]);from fragmentcolor import Renderer
renderer = Renderer()texture_target = renderer.create_texture_target([16, 16])import FragmentColor
let renderer = Renderer()let texture_target = try await renderer.createTextureTarget([16, 16])import org.fragmentcolor.*
val renderer = Renderer()val texture_target = renderer.createTextureTarget(16u, 16u)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.
Example
Section titled “Example”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()) }import { Renderer } from "fragmentcolor";
const renderer = new Renderer();
// Use your platform's windowing system to create a window.const canvas = document.createElement('canvas');
const target = await renderer.createTarget(canvas);from rendercanvas.auto import RenderCanvas, loop
from fragmentcolor import Renderer
renderer = Renderer()
# Use your platform's windowing system to create a window.canvas = RenderCanvas(size=(800, 600))
target = renderer.create_target(canvas)import FragmentColor
let renderer = Renderer()
// Use your platform's windowing system to create a window.// iOS: window/canvas provided by CAMetalLayer at runtime
let target = try await renderer.createTextureTarget([800, 600])import org.fragmentcolor.*
val renderer = Renderer()
// Use your platform's windowing system to create a window.// HEADLESS: canvas creation not needed on Android
val target = renderer.createTextureTarget(800u, 600u)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.
Example
Section titled “Example”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 imagelet image = target.get_image().await;
5 collapsed lines
// RGBA8assert_eq!(image.len(), 64 * 64 * 4);Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Renderer, Shader } from "fragmentcolor";const renderer = new Renderer();
// Create an offscreen texture target with a size of 64x64 pixels.const target = await renderer.createTextureTarget([64, 64]);
renderer.render(new Shader(""), target);
// get the rendered imageconst image = await target.getImage();from rendercanvas.auto import RenderCanvas, loop
from fragmentcolor import Renderer, Shaderrenderer = Renderer()
# Create an offscreen texture target with a size of 64x64 pixels.target = renderer.create_texture_target([64, 64])
renderer.render(Shader(""), target)
# get the rendered imageimage = target.get_image()import FragmentColorlet renderer = Renderer()
// Create an offscreen texture target with a size of 64x64 pixels.let target = try await renderer.createTextureTarget([64, 64])
try renderer.render(Shader(""), target)
// get the rendered imagelet image = try await target.getImage()import org.fragmentcolor.*val renderer = Renderer()
// Create an offscreen texture target with a size of 64x64 pixels.val target = renderer.createTextureTarget(64u, 64u)
renderer.render(Shader(""), target)
// get the rendered imageval image = target.getImage()Renderer::create_texture
Section titled “Renderer::create_texture”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.
Per-language shapes
Section titled “Per-language shapes”- Rust:
create_texture(input).inputaccepts bare bytes,(bytes, [w, h]),(bytes, format),(bytes, options), a path, a URL, aTextureMipChain, or a KTX2 input. - JS:
await renderer.createTexture(input, options?).inputisUint8Array/ArrayBuffer/ URL string / CSS selector /HTMLImageElement/HTMLCanvasElement/OffscreenCanvas/ImageData/ aTextureMipChainhandle.optionsis{ size?, format?, mipmaps? }; whensizeis set,inputis read as raw pixel bytes. - Python:
renderer.create_texture(input, size=None, format=None, mipmaps=None).inputisbytes/list[int]/str(path) / numpyndarray[H, W, C]/TextureMipChain. Numpy arrays fill insizefor you. - Swift / Kotlin:
try await renderer.createTexture(bytes)orrenderer.createTexture(chain). Overloads in the binding wrap the underlying enum, so you write the natural call.
How the discriminator works
Section titled “How the discriminator works”| Form | Treatment |
|---|---|
bytes (no size) | Encoded image; decoded internally (PNG, JPEG, BMP, HDR, etc.). |
(bytes, size) / options with size set | Raw pixel bytes; bpp(format) * width * height long, no decode. |
(bytes, format) / options with format set | Encoded image reinterpreted as format (e.g. Rgba8Unorm for normal-map data, R16Unorm for 16-bit grayscale). |
path / URL | File or HTTP fetch, then decoded. |
TextureMipChain | Pre-built CPU mip chain; GPU-only upload. Build via TextureMipChain::prepare on a worker thread. |
Ktx2Bytes / Ktx2Path / Ktx2Url | KTX2 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.
Example
Section titled “Example”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()) }import { Renderer } from "fragmentcolor";const image = "/healthcheck/public/favicon.png";const renderer = new Renderer();const tex = await renderer.createTexture(image);// use in a shader uniform// shader.set("tex", tex);from fragmentcolor import Renderer# Python binding: renderer.create_texture(input)# See main create_texture docs for examples.import FragmentColorlet renderer = Renderer()let image = "/healthcheck/public/favicon.png"let tex = try await renderer.createTexture(image)import org.fragmentcolor.*val renderer = Renderer()val image = "/healthcheck/public/favicon.png"val tex = renderer.createTexture(TextureInputMobile.Path(image), null)Renderer::create_storage_texture
Section titled “Renderer::create_storage_texture”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 withbytes.- A full
TextureInput { data, options }literal â passoptions.usage(viaTextureOptions::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.
Example
Section titled “Example”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()) }import { Renderer, TextureFormat } from "fragmentcolor";
const r = new Renderer();
// Empty storage texture.const tex = await r.createStorageTexture([64, 64], TextureFormat.Rgba);
// Pre-seeded with bytes.const pixels = Array(64 * 64 * 4).fill(0);const tex2 = await r.createStorageTexture([64, 64], TextureFormat.Rgba, pixels);from fragmentcolor import Renderer, TextureFormat
r = Renderer()# Empty storage texture -- positional args: (size, format).tex = r.create_storage_texture([64, 64], TextureFormat.Rgba)
# Pre-seeded with bytes -- same method, optional data kwarg.pixels = [0] * (64 * 64 * 4)tex2 = r.create_storage_texture([64, 64], TextureFormat.Rgba, data=pixels)import FragmentColor
let r = Renderer()
// Empty storage texture.let tex = try await r.createStorageTexture(([64, 64], TextureFormat.rgba))
// Pre-seeded with bytes.let pixels = Array(repeating: 0, count: 64 * 64 * 4)let tex2 = try await r.createStorageTexture(([64, 64], TextureFormat.rgba, pixels))import org.fragmentcolor.*
val r = Renderer()
// Empty storage texture.val tex = r.createStorageTexture(Size(width=64u, height=64u, depth=null), TextureFormat.RGBA, null, null)
// Pre-seeded with bytes.val pixels = ByteArray(64 * 64 * 4)val tex2 = r.createStorageTexture(Size(width=64u, height=64u, depth=null), TextureFormat.RGBA, pixels, null)Renderer::create_depth_texture
Section titled “Renderer::create_depth_texture”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.
Example
Section titled “Example”use fragmentcolor::Renderer;let r = Renderer::new();let depth = r.create_depth_texture([800, 600]);import { Renderer } from "fragmentcolor";const r = new Renderer();const depth = r.createDepthTexture([800, 600]);from fragmentcolor import Rendererr = Renderer()depth = r.create_depth_texture([800, 600])import FragmentColorlet r = Renderer()let depth = try await r.createDepthTexture([800, 600])import org.fragmentcolor.*val r = Renderer()val depth = r.createDepthTexture(800u, 600u)Renderer::unregister_texture(id)
Section titled “Renderer::unregister_texture(id)”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.
Example
Section titled “Example”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()) }import { Renderer, TextureFormat } from "fragmentcolor";
const renderer = new Renderer();const texture = await renderer.createStorageTexture([16, 16], TextureFormat.Rgba, null);const id = texture.id();
renderer.unregisterTexture(id);from fragmentcolor import Renderer, TextureFormat
renderer = Renderer()texture = renderer.create_storage_texture([16, 16], TextureFormat.Rgba)id = texture.id()
renderer.unregister_texture(id)import FragmentColorlet renderer = Renderer()let texture = try await renderer.createStorageTexture(([16, 16], TextureFormat.rgba))let id = texture.id()
try renderer.unregisterTexture(id)import org.fragmentcolor.*val renderer = Renderer()val texture = renderer.createStorageTexture(Size(width=16u, height=16u, depth=null), TextureFormat.RGBA, null, null)val id = texture.id()
renderer.unregisterTexture(id)Renderer::read_texture(texture_id)
Section titled “Renderer::read_texture(texture_id)”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 theTextureIdaround. - The texture must have
COPY_SRCusage. Creation helpers likecreate_storage_textureenable it by default.
Example
Section titled “Example”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()) }import { Renderer, TextureFormat } from "fragmentcolor";const renderer = new Renderer();const texture = await renderer.createStorageTexture([64, 64], TextureFormat.Rgba);texture.write(new Uint8Array(64 * 64 * 4));
const bytes = await renderer.readTexture(texture.id());from fragmentcolor import Renderer, TextureFormatrenderer = Renderer()texture = renderer.create_storage_texture([64, 64], TextureFormat.Rgba)texture.write([0] * (64 * 64 * 4))
bytes = renderer.read_texture(texture.id())import FragmentColorlet renderer = Renderer()let texture = try await renderer.createStorageTexture(([64, 64], TextureFormat.rgba))try texture.write(Array(repeating: 0, count: 64 * 64 * 4))
let bytes = try await renderer.readTexture(texture.id())import org.fragmentcolor.*val renderer = Renderer()val texture = renderer.createStorageTexture(Size(width=64u, height=64u, depth=null), TextureFormat.RGBA, null, null)texture.write(ByteArray(64 * 64 * 4))
val bytes = renderer.readTexture(texture.id())Renderer::create_external_texture(source)
Section titled “Renderer::create_external_texture(source)”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 rawUInt64pointer over the uniffi boundary). - Android: a
SurfaceTexturehandle (passed as a rawULongpointer).
Returns an error on every platform today â the API surface is in place, the implementation is a follow-up.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::Renderer;let renderer = Renderer::new();// platform-specific source handle passed here3 collapsed lines
Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Renderer } from "fragmentcolor";
const renderer = new Renderer();const element = document.getElementById("video");
if (element instanceof HTMLVideoElement) {const handle = renderer.createExternalTexture(element);void handle;}# Python: decode video frames host-side and upload via Texture.write().# A native external-texture source equivalent is not exposed on this binding.import FragmentColor// Once supported:// let renderer = Renderer()// let pixelBuffer: CVPixelBuffer = /* from AVPlayerItemVideoOutput */// let ptr = UInt64(UInt(bitPattern: Unmanaged.passUnretained(pixelBuffer).toOpaque()))// let handle = try renderer.createExternalTexture(sourcePtr: ptr)import org.fragmentcolor.*// Once supported:// val renderer = Renderer()// val surfaceTexture: SurfaceTexture = /* from MediaCodec / Camera2 */// val ptr: ULong = surfaceTexture.nativeHandle() // hypothetical helper// val handle = renderer.createExternalTexture(ptr)Renderer::render(renderable, target)
Section titled “Renderer::render(renderable, target)”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.
Example
Section titled “Example”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()) }import { Renderer, Shader } from "fragmentcolor";
const renderer = new Renderer();const target = await renderer.createTextureTarget([10, 10]);const shader = Shader.default();
renderer.render(shader, target);from fragmentcolor import Renderer, Shader
renderer = Renderer()target = renderer.create_texture_target([10, 10])shader = Shader.default()
renderer.render(shader, target)import FragmentColor
let renderer = Renderer()let target = try await renderer.createTextureTarget([10, 10])let shader = Shader.default()
try renderer.render(shader, target)import org.fragmentcolor.*
val renderer = Renderer()val target = renderer.createTextureTarget(10u, 10u)val shader = Shader.default()
renderer.render(shader, target)