Welcome to FragmentColor!
FragmentColor is a universal GPU abstraction library for Rust, Python, JavaScript, Swift, and Kotlin, where you can simply write some WGSL or GLSL code, forget about manual pipeline setup, set uniforms by key-value, and it will render on every device in existence:
let shader = Shader::new("shader source defining my_uniform");shader.set("my_uniform", 0.5); // reflected from sourcerenderer.render(shader);And that’s it. The library builds on top of wgpu , so it directly targets each platform’s native graphics API:
Vulkan, Metal, DirectX, OpenGL, WebGL, or WebGPU.
Check the Documentation and the API Reference for more information.
Follow the project on GitHub to stay tuned on the latest updates.
See Platform Support for details.
Example
Section titled “Example”npm install fragmentcolorimport init, { Renderer, Shader, Pass } from "fragmentcolor";
async function start() {// Initializes the WASM moduleawait init();
// Initializes a renderer and a target compatible with the given canvasconst canvas = document.getElementById("my-canvas");const renderer = new Renderer();const target = await renderer.createTarget(canvas);
// You can pass the shader as a source string, file path, or URL:const circle = new Shader("./path/to/circle.wgsl");const triangle = new Shader("https://fragmentcolor.org/shaders/triangle.wgsl");const shader = new Shader(`28 collapsed lines
struct VertexOutput { @builtin(position) coords: vec4<f32>, }
struct MyStruct { my_field: vec3<f32>, }
@group(0) @binding(0) var<uniform> my_struct: MyStruct;
@group(0) @binding(1) var<uniform> my_vec2: vec2<f32>;
@vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput { const vertices = array( vec2( -1., -1.), vec2( 3., -1.), vec2( -1., 3.) ); return VertexOutput(vec4<f32>(vertices[in_vertex_index], 0.0, 1.0)); }
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(my_struct.my_field, 1.0); }`);
// The library binds and updates the uniforms automaticallyshader.set("my_struct.my_field", [0.1, 0.8, 0.9]);shader.set("my_vec2", [1.0, 1.0]);
// One shader is all you need to renderrenderer.render(circle, target);
// But you can also combine multiple shaders in a render Passconst rpass = new Pass("single pass");rpass.addShader(circle);rpass.addShader(triangle);rpass.addShader(shader);renderer.render(rpass, target);
// Finally, multiple passes can run in one frame: pass an array, or use// Pass.require(...) to declare dependencies and let the renderer order them.const guiPass = new Pass("GUI pass");renderer.render([rpass, guiPass], target);
// To animate, simply update the uniforms in a loopfunction animate() { circle.set("position", [0.0, 0.0]); renderer.render([rpass, guiPass], target); requestAnimationFrame(animate);}animate();}
start();pip install fragmentcolor rendercanvas glfw from fragmentcolor import Renderer, Shader, Pass from rendercanvas.auto import RenderCanvas, loop
# Initializes a renderer and a target compatible with the given canvas canvas = RenderCanvas(size=(800, 600)) renderer = Renderer() target = renderer.create_target(canvas)
# You can pass the shader as a source string, file path, or URL: circle = Shader("./path/to/circle.wgsl") triangle = Shader("https://fragmentcolor.org/shaders/triangle.wgsl") shader = Shader("""28 collapsed lines
struct VertexOutput { @builtin(position) coords: vec4<f32>, }
struct MyStruct { my_field: vec3<f32>, }
@group(0) @binding(0) var<uniform> my_struct: MyStruct;
@group(0) @binding(1) var<uniform> my_vec2: vec2<f32>;
@vertex fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput { const vertices = array( vec2( -1., -1.), vec2( 3., -1.), vec2( -1., 3.) ); return VertexOutput(vec4<f32>(vertices[in_vertex_index], 0.0, 1.0)); }
@fragment fn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(my_struct.my_field, 1.0); } """)
# The library binds and updates the uniforms automatically shader.set("my_struct.my_field", [0.1, 0.8, 0.9]) shader.set("my_vec2", [1.0, 1.0])
# One shader is all you need to render renderer.render(shader, target)
# But you can also combine multiple shaders in a render Pass rpass = Pass("single pass") rpass.add_shader(circle) rpass.add_shader(triangle) rpass.add_shader(shader) renderer.render(rpass, target)
# Finally, multiple passes can run in one frame: pass a list, or use # Pass.require(...) to declare dependencies and let the renderer order them. gui_pass = Pass("GUI pass") renderer.render([rpass, gui_pass], target)
# To animate, simply update the uniforms in a loop @canvas.request_draw def animate(): circle.set("position", [0.0, 0.0]) renderer.render([rpass, gui_pass], target)
loop.run()cargo add fragmentcoloruse fragmentcolor::{Renderer, Shader, Pass};use pollster::block_on;
fn main() -> Result<(), Box<dyn std::error::Error>> { block_on(async { // Initializes a renderer and a target (offscreen example here) let renderer = Renderer::new(); let target = renderer.create_texture_target([800, 600]).await?;
// You can pass the shader as a source string, file path, or URL: let circle = Shader::new("./path/to/circle.wgsl")?; let triangle = Shader::new("https://fragmentcolor.org/shaders/triangle.wgsl")?; let wgsl = r#"28 collapsed lines
struct VertexOutput { @builtin(position) coords: vec4<f32>,}
struct MyStruct { my_field: vec3<f32>,}
@group(0) @binding(0)var<uniform> my_struct: MyStruct;
@group(0) @binding(1)var<uniform> my_vec2: vec2<f32>;
@vertexfn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> VertexOutput { const vertices = array( vec2( -1., -1.), vec2( 3., -1.), vec2( -1., 3.) ); return VertexOutput(vec4<f32>(vertices[in_vertex_index], 0.0, 1.0));}
@fragmentfn fs_main() -> @location(0) vec4<f32> { return vec4<f32>(my_struct.my_field, 1.0);}"#; let mut shader = Shader::new(wgsl)?;
// The library binds and updates the uniforms automatically shader.set("my_struct.my_field", [0.1f32, 0.8, 0.9])?; shader.set("my_vec2", [1.0f32, 1.0])?;
// One shader is all you need to render renderer.render(&shader, &target)?;
// But you can also combine multiple shaders in a render Pass let rpass = Pass::new("single pass"); rpass.add_shader(&circle); rpass.add_shader(&triangle); rpass.add_shader(&shader); renderer.render(&rpass, &target)?;
// Finally, multiple passes can run in one frame: pass a slice, or use // Pass::require(...) to declare dependencies and let the renderer order them. let gui_pass = Pass::new("GUI pass"); let passes = [rpass.clone(), gui_pass.clone()]; renderer.render(&passes[..], &target)?;
// To animate, simply update the uniforms in a loop for i in 0..60 { circle.set("position", [0.0f32, i as f32])?; renderer.render(&passes[..], &target)?; }
Ok(()) })}swift package add https://github.com/vista-art/fragmentcolorimport FragmentColorimport SwiftUI
struct ShaderView: View {
@State private var mousePos = CGPoint(x: 0, y: 0) let renderer: FragmentColor.Renderer let target: FragmentColor.Target let circle: Shader
init() { let canvas = MetalCanvasView() // Your Metal-backed view
renderer = FragmentColor.Renderer() target = renderer.createTarget(canvas)
circle = Shader("circle.wgsl")! circle.setUniform("circle.radius", 200.0) circle.setUniform("circle.color", SIMD4<Float>(1.0, 0.0, 0.0, 0.8)) circle.setUniform("circle.border", 20.0) }
var body: some View { CanvasRepresentation() .gesture(DragGesture(minimumDistance: 0) .onChanged { value in mousePos = value.location } ) .onAppear(perform: animate) }
func animate() { circle.setUniform("circle.position", SIMD2<Float>( Float(mousePos.x), Float(mousePos.y) )) renderer.render(circle, to: target)
DispatchQueue.main.asyncAfter(deadline: .now() + 1/60) { animate() } }
}
struct CanvasRepresentation: NSViewRepresentable {
func makeNSView(context: Context) -> some NSView { let view = MetalCanvasView() view.wantsLayer = true return view }
func updateNSView(_ nsView: NSViewType, context: Context) {}
}implementation("org.fragmentcolor:fragmentcolor:0.11.0")import androidx.compose.foundation.Canvasimport androidx.compose.foundation.gestures.detectDragGesturesimport androidx.compose.runtime.*import androidx.compose.ui.Modifierimport fragmentcolor.FragmentColorimport fragmentcolor.Shader
@Composablefun ShaderCanvas() { var mousePos by remember { mutableStateOf(Offset.Zero) } val renderer = remember { FragmentColor.Renderer() } val target = remember { renderer.createTarget(canvas) } val circle = remember { Shader("circle.wgsl") }.apply { setUniform("circle.radius", 200.0f) setUniform("circle.color", floatArrayOf(1.0f, 0.0f, 0.0f, 0.8f)) setUniform("circle.border", 20.0f) }
LaunchedEffect(Unit) { while (true) { withFrameNanos { circle.setUniform("circle.position", floatArrayOf( mousePos.x.toFloat(), mousePos.y.toFloat() )) renderer.render(circle, target) } } }
Canvas( modifier = Modifier.detectDragGestures { change, _ -> mousePos = change.position } ) { FragmentColor.init(this, renderer, target) }
}