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 Mipmap.
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, aMipmap, or a KTX2 input. - JS:
await renderer.createTexture(input, options?).inputisUint8Array/ArrayBuffer/ URL string / CSS selector /HTMLImageElement/HTMLCanvasElement/OffscreenCanvas/ImageData/ aMipmaphandle.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]/Mipmap. 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. |
Mipmap |
Pre-built CPU mip chain; GPU-only upload. Build via Mipmap::build 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 png: ByteArray = byteArrayOf()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 Mipmap::build.
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![0; 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)
val bytes: ByteArray = byteArrayOf()// 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![0; 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).
Currently returns an error on every platform. The public signature is
stable; native-side decode hookup is on the roadmap. Track support via
the typed RendererError you get back.
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::load
Section titled “Renderer::load”Realize every pending GPU upload referenced by a renderable. Material’s
texture setters (base_color_texture, metallic_roughness_texture, …)
accept paths, bytes, URLs, and DynamicImage values in addition to
already-uploaded Texture
handles; the path / bytes / URL variants are stored on the Material’s
Shader as pending uploads. load walks every Shader the renderable
visits, drains its pending list, calls
Renderer::create_texture
for each, and writes the resulting Texture into the matching uniform.
Renderer::render
calls load automatically the first time it sees a renderable with
pending uploads, so explicit load is optional. Reach for it when you
want to amortize the decode + upload cost outside the render loop, for
example in a loading screen that prepares the scene before the first
frame goes out.
Example
Section titled “Example”1 collapsed line
async fn run() -> Result<(), Box<dyn std::error::Error>> {use fragmentcolor::{Material, Mesh, Model, Renderer, Scene, Vertex};
let renderer = Renderer::new();let target = renderer.create_texture_target([64, 64]).await?;
let mesh = Mesh::new();mesh.add_vertex( Vertex::pbr([0.0, 0.5, 0.0]).set(Vertex::UV0, [0.5, 1.0]),);// Raw 2×2 RGBA pixel bytes — uploaded lazily by `Renderer::load` below.// In practice the loader hands the setter encoded PNG/JPEG bytes (from a// BIN chunk) or a file path (from a URI); the same `Into<TextureInput>`// vocabulary covers all of them.let red_pixels: Vec<u8> = vec![ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,];let red_tex = renderer.create_texture((red_pixels, [2, 2])).await?;let material = Material::pbr().base_color_texture(&red_tex);let model = Model::new(mesh, material);let scene = Scene::new();scene.add(&model)?;
// Eager prewarm — uploads the pending texture(s) so the next render is// GPU-only.renderer.load(&scene).await?;renderer.render(&scene, &target)?;3 collapsed lines
Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Material, Mesh, Model, Renderer, Scene, Vertex } from "fragmentcolor";
const renderer = new Renderer();const target = await renderer.createTextureTarget([64, 64]);
const mesh = new Mesh();mesh.addVertex( Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]), );// Raw 2×2 RGBA pixel bytes — uploaded lazily by `Renderer.load` below.// In practice the loader hands the setter encoded PNG/JPEG bytes (from a// BIN chunk) or a file path (from a URI); the same `Into<TextureInput>`// vocabulary covers all of them.const red_pixels = [ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255, ];const red_tex = await renderer.createTexture(red_pixels, [2, 2]);const material = Material.pbr().baseColorTexture(red_tex);const model = new Model(mesh, material);const scene = new Scene();scene.add(model);
// Eager prewarm — uploads the pending texture(s) so the next render is// GPU-only.await renderer.load(scene);renderer.render(scene, target);from fragmentcolor import Material, Mesh, Model, Renderer, Scene, Vertex
renderer = Renderer()target = renderer.create_texture_target([64, 64])
mesh = Mesh()mesh.add_vertex( Vertex.pbr([0.0, 0.5, 0.0]).set(Vertex.UV0, [0.5, 1.0]),)# Raw 2×2 RGBA pixel bytes — uploaded lazily by `Renderer.load` below.# In practice the loader hands the setter encoded PNG/JPEG bytes (from a# BIN chunk) or a file path (from a URI); the same `Into<TextureInput>`# vocabulary covers all of them.red_pixels = [ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,]red_tex = renderer.create_texture(red_pixels, size=[2, 2])material = Material.pbr().base_color_texture(red_tex)model = Model(mesh, material)scene = Scene()scene.add(model)
# Eager prewarm — uploads the pending texture(s) so the next render is# GPU-only.renderer.load(scene)renderer.render(scene, target)import FragmentColor
let renderer = Renderer()let target = try await renderer.createTextureTarget([64, 64])
let mesh = Mesh()try mesh.addVertex( try Vertex.pbr([0.0, 0.5, 0.0]).set("uv0", [0.5, 1.0]))// Raw 2×2 RGBA pixel bytes — uploaded lazily by """Renderer.load""" below.// In practice the loader hands the setter encoded PNG/JPEG bytes (from a// BIN chunk) or a file path (from a URI); the same """Into<TextureInput>"""// vocabulary covers all of them.let red_pixels = [ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,]let red_tex = try await renderer.createTexture((red_pixels, [2, 2]))let material = Material.pbr().baseColorTexture(red_tex)let model = Model(mesh, material)let scene = Scene()try scene.add(model)
// Eager prewarm — uploads the pending texture(s) so the next render is// GPU-only.try await renderer.load(scene)try renderer.render(scene, target)import org.fragmentcolor.*
val renderer = Renderer()val target = renderer.createTextureTarget(64u, 64u)
val mesh = Mesh()mesh.addVertex( Vertex.pbr(listOf(0.0f, 0.5f, 0.0f)).set("uv0", floatArrayOf(0.5f, 1.0f)), )val bytes: ByteArray = byteArrayOf()// Raw 2×2 RGBA pixel bytes — uploaded lazily by """Renderer.load""" below.
// vocabulary covers all of them.val red_pixels = listOf(255.0f, 0.0f, 0.0f, 255.0f, 0.0f, 255.0f, 0.0f, 255.0f, 0.0f, 0.0f, 255.0f, 255.0f, 255.0f, 255.0f, 255.0f, 255.0f, .0f)val red_tex = renderer.createTexture(TextureInputMobile.Bytes(red_pixels.let { ba -> ByteArray(ba.size) { i -> ba[i].toInt().and(0xFF).toByte() } }), null)val material = Material.pbr().baseColorTexture(red_tex)val model = Model(mesh, material)val scene = Scene()scene.add(model)
// Eager prewarm — uploads the pending texture(s) so the next render is// GPU-only.renderer.load(scene)renderer.render(scene, target)Renderer::read_storage
Section titled “Renderer::read_storage”Round-trip the GPU-side bytes of a Shader’s
storage binding back to the CPU. Submits any pending uploads, copies the
storage buffer into a pooled readback staging buffer, maps it, and returns
the bytes.
This is the only way to observe what a compute pass wrote into a storage
binding. The cheap CPU-mirror accessors
(Shader::get and
Shader::get_bytes)
reflect what was last set on the CPU side; GPU writes are not visible
through them.
read_storage is async on every platform because the underlying
map_async requires an awaited callback. On native the implementation
drives the device forward (device.poll(Wait)) before awaiting, so callers
don’t have to spin their own poll loop. On Web the browser schedules the
callback; the helper just awaits.
Returns
RendererError::StorageBindingNotFound
when the shader does not declare a storage binding called binding, or
when no render pass has yet materialised the GPU-side buffer (storage
buffers are allocated lazily on first bind).
Example
Section titled “Example”2 collapsed lines
async fn run() -> Result<(), Box<dyn std::error::Error>> {use bytemuck;use fragmentcolor::{Pass, Renderer, Shader};
let renderer = Renderer::new();let target = renderer.create_texture_target([16, 16]).await?;
let compute = Shader::new( r#" struct Out { values: array<f32, 4> }; @group(0) @binding(0) var<storage, read_write> out: Out; @compute @workgroup_size(1) fn main() { out.values[0] = 1.0; out.values[1] = 2.0; out.values[2] = 3.0; out.values[3] = 4.0; } "#,)?;
let pass = Pass::compute("seed");pass.set_compute_dispatch(1, 1, 1);pass.add_shader(&compute);renderer.render(&pass, &target)?;
let bytes = renderer.read_storage(&compute, "out").await?;5 collapsed lines
let values: &[f32] = bytemuck::cast_slice(&bytes);assert_eq!(values, &[1.0, 2.0, 3.0, 4.0]);Ok(())}fn main() -> Result<(), Box<dyn std::error::Error>> { pollster::block_on(run()) }import { Pass, Renderer, Shader } from "fragmentcolor";
const renderer = new Renderer();const target = await renderer.createTextureTarget([16, 16]);
const compute = new Shader(`
struct Out { values: array<f32, 4> }; @group(0) @binding(0) var<storage, read_write> out: Out; @compute @workgroup_size(1) fn main() { out.values[0] = 1.0; out.values[1] = 2.0; out.values[2] = 3.0; out.values[3] = 4.0; }
`);
const pass = Pass.compute("seed");pass.setComputeDispatch(1, 1, 1);pass.addShader(compute);renderer.render(pass, target);
const bytes = await renderer.readStorage(compute, "out");from fragmentcolor import Pass, Renderer, Shader
renderer = Renderer()target = renderer.create_texture_target([16, 16])
compute = Shader(""" struct Out { values: array<f32, 4> }; @group(0) @binding(0) var<storage, read_write> out: Out; @compute @workgroup_size(1) fn main() { out.values[0] = 1.0; out.values[1] = 2.0; out.values[2] = 3.0; out.values[3] = 4.0; }
""")
rpass = Pass.compute("seed")rpass.set_compute_dispatch(1, 1, 1)rpass.add_shader(compute)renderer.render(rpass, target)
bytes = renderer.read_storage(compute, "out")import FragmentColor
let renderer = Renderer()let target = try await renderer.createTextureTarget([16, 16])
let compute = try Shader(""" struct Out { values: array<f32, 4> } @group(0) @binding(0) var<storage, read_write> out: Out @compute @workgroup_size(1) fn main() { out.values[0] = 1.0 out.values[1] = 2.0 out.values[2] = 3.0 out.values[3] = 4.0 }
""")
let pass = Pass.compute("seed")pass.setComputeDispatch(1, 1, 1)pass.addShader(compute)try renderer.render(pass, target)
let bytes = try await renderer.readStorage(compute, "out")import org.fragmentcolor.*
val renderer = Renderer()val target = renderer.createTextureTarget(16u, 16u)
val compute = Shader(""" struct Out { values: array<f32, 4> } @group(0) @binding(0) var<storage, read_write> out: Out @compute @workgroup_size(1) fn main() { out.values[0] = 1.0 out.values[1] = 2.0 out.values[2] = 3.0 out.values[3] = 4.0 }
""")
val pass = Pass.compute("seed")pass.setComputeDispatch(1u,1u,1u)pass.addShader(compute)renderer.render(pass, target)
val bytes = renderer.readStorage(compute, "out")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)