
Welcome to FragmentColor
Easy GPU Programming for Rust, Javascript, Python, Swift, and Kotlin.
pip install fragmentcolor rendercanvas glfw
from fragmentcolor import Renderer, Shader, Pass, Frame 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, you can combine multiple passes in a Frame frame = Frame() frame.add_pass(rpass) frame.add_pass(Pass("GUI pass")) renderer.render(frame, target)
# To animate, simply update the uniforms in a loop @canvas.request_draw def animate(): circle.set("position", [0.0, 0.0]) renderer.render(frame, target)
loop.run()
npm install fragmentcolor
import init, { Renderer, Shader, Pass, Frame } 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, you can combine multiple passes in a Frameconst frame = new Frame();frame.addPass(rpass);frame.addPass(new Pass("GUI pass"));renderer.render(frame, target);
// To animate, simply update the uniforms in a loopfunction animate() { circle.set("position", [0.0, 0.0]); renderer.render(frame, target); requestAnimationFrame(animate);}animate();}
start();
use fragmentcolor::{Renderer, Shader, Pass, Frame};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 mut rpass = Pass::new("single pass"); rpass.add_shader(&circle); rpass.add_shader(&triangle); rpass.add_shader(&shader); renderer.render(&rpass, &target)?;
// Finally, you can combine multiple passes in a Frame let mut frame = Frame::new(); frame.add_pass(rpass); frame.add_pass(Pass::new("GUI pass")); renderer.render(&frame, &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(&frame, &target)?; }
Ok(()) })}
import 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) {}
}
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) }
}