LuisaCompute Documentation

repository·stable·Indexed 21 days ago

https://github.com/luisagroup/luisacompute

A high-performance, cross-platform computing framework for graphics and general-purpose stream processing. It features a C++ embedded DSL for kernel programming, a unified runtime for resource management and command scheduling, and supports multiple backends including CUDA, DirectX, Metal, and CPU. The framework provides a Python frontend via the luisa-python package.

Tokens
64.1K
Snippets
195
Records
242
Agent score
77%

What's inside LuisaCompute

  1. What is LuisaCompute

    stable

    LuisaCompute is a high-performance, cross-platform computing framework designed for graphics and general-purpose stream-processing tasks. It aims to unify programmability and performance through three core components:

    1. Embedded Domain-Specific Language (DSL): A C++-based DSL for authoring kernels. It uses meta-programming and operator overloading to trace user-defined kernels into an Abstract Syntax Tree (AST) without requiring custom preprocessing or compiler extensions.
    2. Unified Runtime: An abstract runtime layer that provides high-level resource wrappers (strongly and statically typed C++ objects). It manages cross-platform resources and command scheduling, automatically probing dependencies to optimize hardware utilization.
    3. Multiple Backends: Optimized backends that translate ASTs into platform-specific shader source code and execute them. Supported backends include CUDA, DirectX, Metal, and a CPU backend (implemented in Rust for debugging/fallback).

    Note: LuisaCompute is a framework for building applications, not a standalone renderer. For a ready-to-use Monte Carlo renderer, use LuisaRender.

  2. Summary of LuisaCompute Tutorial Capabilities

    stable

    LuisaCompute supports a wide range of GPU computing tasks across different domains:

    Visual Effects & Rendering

    • Mandelbrot: Iterative complex number math.
    • Path Tracer: Physically-based global illumination.
    • Voxel Ray Tracer: Real-time ray marching through 3D grids.
    • Black Hole: Relativistic ray tracing and gravitational lensing.

    Physics Simulations

    • MPM (Material Point Method): Fluid and solid simulations using multiple kernels and atomic operations.
    • Wave Equation: PDE solving for interactive water ripples.
    • N-Body: Large-scale particle simulations (e.g., 4,000+ particles) using tile-based optimization.

    Pattern Formation & Cellular Automata

    • Game of Life: Cellular automata using ping-pong buffering.
    • Reaction-Diffusion: Coupled PDEs for chemical pattern simulation.

    Image Processing & Particles

    • Image Processing: Multi-pass pipelines (e.g., Gaussian blur, edge detection).
    • Fire Particles: Large-scale particle systems with lifecycle management and procedural noise.
  3. Explore LuisaCompute rendering examples

    stable

    LuisaCompute provides several rendering implementations that can be used as templates for high-performance graphics applications. Key examples include:

    • Path Tracing: Monte Carlo path tracing, HDR with tone mapping, depth of field (camera model), alpha-tested cutout geometry, nested callable composition, ray visibility masks, and spectral rendering.
    • Photon Mapping: Global illumination with caustics.
    • SDF Renderer: Signed distance field rendering using ray marching.
    • Voxel Ray Tracer: Real-time voxel rendering.
    • Procedural Rendering: ShaderToy-style procedural effects and procedural geometry.
    • Specialized Effects: Black hole simulation with gravitational lensing.

    Source code for these examples is located in examples/rendering/.

  4. Explore LuisaCompute simulation examples

    stable

    LuisaCompute can be used for various physical and mathematical simulations. Reference implementations are found in examples/simulation/:

    • Fluid Simulation: Material Point Method (MPM88).
    • Cellular Automata: Conway's Game of Life.
    • PDE Solvers: Wave equation solver.
    • Particle Systems: Physics-based fire simulation and gravitational N-body simulation.
  5. Overview of the LuisaCompute DSL

    stable

    LuisaCompute features an embedded Domain Specific Language (DSL) that allows you to write GPU kernels directly in modern C++. It uses C++ metaprogramming to trace kernel code and build an Abstract Syntax Tree (AST), which is then compiled for various backends.

    Key features include:

    • Type-safe device variables via Var<T>.
    • Vector and matrix types optimized for graphics computing.
    • GPU-executed control flow using $-prefixed macros.
    • Built-in functions for math, texture sampling, and ray tracing.
    • Automatic differentiation for gradient computation.
  6. Understand XIR control-flow representations

    stable

    XIR (the Intermediate Representation) uses two distinct types of control-flow terminators. It is critical to distinguish between them when writing optimizations or transforms:

    1. Structured Terminators: These retain lexical information required by source-oriented code generators. They include:

      • IfInst
      • SwitchInst
      • LoopInst
      • SimpleLoopInst
      • BreakInst
      • ContinueInst
    2. Raw CFG Terminators: These are used by analyses and transforms that operate directly on graph edges. They include:

      • BranchInst
      • ConditionalBranchInst
      • IndexedBranchInst

    Safety Rule: An optimization must not silently cross this boundary. Only passes explicitly designed for CFG lowering are permitted to convert structured constructs into plain CFG.

  7. How the embedded DSL works

    stable

    The DSL allows you to write both host-side logic and device-side kernels in modern C++. It uses meta-programming to simulate syntax and function/operator overloading to dynamically trace kernels. During tracing, an AST (Intermediate Representation) is constructed, which backends then use to generate concrete, platform-dependent shader code.

    Example of a kernel using the DSL:

    Callable to_srgb = [](Float3 x) {
        $if (x <= 0.00031308f) {
            x = 12.92f * x;
        } $else {
            x = 1.055f * pow(x, 1.f / 2.4f) - .055f;
        };
        return x;
    };
    
    Kernel2D fill = [&](ImageFloat image) {
        auto coord = dispatch_id().xy();
        auto size = make_float2(dispatch_size().xy());
        auto rg = make_float2(coord) / size;
        // invoke the callable
        auto srgb = to_srgb(make_float3(rg, 1.f));
        image.write(coord, make_float4(srgb, 1.f));
    };
    Callable to_srgb = [](Float3 x) {
        $if (x <= 0.00031308f) {
            x = 12.92f * x;
        } $else {
            x = 1.055f * pow(x, 1.f / 2.4f) - .055f;
        };
        return x;
    };
    Kernel2D fill = [&](ImageFloat image) {
        auto coord = dispatch_id().xy();
        auto size = make_float2(dispatch_size().xy());
        auto rg = make_float2(coord) / size;
        // invoke the callable
        auto srgb = to_srgb(make_float3(rg, 1.f));
        image.write(coord, make_float4(srgb, 1.f));
    };
  8. Use the LuisaCompute Embedded DSL for GPU Kernels

    stable

    The Embedded DSL allows you to write GPU kernels directly in C++.

    Variables and Types

    Use Var<T> (or shorthand like Float, Int) for device-side variables. Supports swizzling for vectors.

    Var<float> x = 1.0f;           // Scalar
    Float2 pos = make_float2(1.0f, 2.0f); // Vector
    
    // Swizzling
    Float2 xy = pos.xy();          
    Float3 xyz = rgba.xyz();       
    Float4 repeated = pos.xxxx();  

    Control Flow

    Use $-prefixed macros for device-side control flow. Note that native C++ if is for host-side compile-time decisions.

    $if (condition) {
        // GPU-side branch
    } $else {
        // Default
    };
    
    $while (condition) { /* GPU loop */ };
    
    $for (i, 0, 100) { /* Loop from 0 to 99 */ };
    $for (i, 0, 100, 2) { /* Loop with step 2 */ };

    Kernels and Callables

    A Callable is a reusable device function. A Kernel is the entry point for GPU execution.

    // A reusable device function
    Callable lerp = [](Float a, Float b, Float t) noexcept {
        return a * (1.0f - t) + b * t;
    };
    
    // A 2D kernel
    Kernel2D render_kernel = [&](ImageFloat image, BufferFloat4 colors) noexcept {
        Var coord = dispatch_id().xy();
        Var index = coord.y * dispatch_size().x + coord.x;
        Float4 color = colors.read(index);
        image->write(coord, color);
    };
    
    // Compile and dispatch
    auto shader = device.compile(render_kernel);
    stream << shader(image, colors).dispatch(1024, 1024);
    Kernel2D render_kernel = [&](ImageFloat image, BufferFloat4 colors) noexcept {
        Var coord = dispatch_id().xy();
        image->write(coord, colors.read(0));
    };
    auto shader = device.compile(render_kernel);
    stream << shader(image, colors).dispatch(1024, 1024);
  9. Manage resource lifetimes with RAII and Move Semantics

    stable

    LuisaCompute uses RAII (Resource Acquisition Is Initialization) for all resources like Buffer, Image, and Volume. Resources are automatically released when they go out of scope.

    Important Constraints:

    • Move-only: Resources cannot be copied; they must be moved using std::move.
    • Lifetime Safety: You must ensure that resources (like buffers) outlive the commands they are used in. If a resource is destroyed before the stream has finished executing the command that uses it, it will lead to errors.
    {
        // Resource created
        Buffer<float> buffer = device.create_buffer<float>(1000);
        Image<float> image = device.create_image<float>(PixelStorage::BYTE4, 1024, 1024);
        
        // Use resources...
    } // Resources automatically released here
    
    // Move semantics example
    Buffer<float> buf1 = device.create_buffer<float>(1000);
    Buffer<float> buf2 = std::move(buf1); // Valid
    // Buffer<float> buf3 = buf2;          // Error: copy not allowed
  10. Optimize Memory with Structure-of-Arrays (SOA)

    stable

    The SOA<T> layout improves GPU memory coalescing by splitting composite types (vectors, matrices, structs) into separate contiguous arrays for each component. This ensures that adjacent threads access adjacent memory addresses.

    Key Features:

    • Creation: SOA<T> name{device, count};.
    • Component Access: You can read or write individual components directly (e.g., positions.x.read(idx)) without touching other components.
    • Subviews: SOAView<T> provides a non-owning view into a subset of an SOA's elements using .subview(offset, count).

    When to use: Use SOA instead of Buffer<float3> or similar when you want to maximize memory bandwidth for parallel workloads.

    // Create SOA storage for 1024 float3 elements
    SOA<float3> positions{device, 1024};
    
    // Reading/Writing in a kernel
    Kernel1D update = [&](Var<SOA<float3>> pos, Float dt) noexcept {
        Var idx = dispatch_id().x;
        Float3 p = pos.read(idx);
        p += make_float3(0.0f, -9.8f, 0.0f) * dt;
        pos.write(idx, p);
    };
    
    // Component-level access
    Kernel1D kernel = [&](Var<SOA<float3>> positions) noexcept {
        Var idx = dispatch_id().x;
        Float x = positions.x.read(idx);
        positions.y.write(idx, x + 1.0f);
    };
    
    // Subviews
    auto sub = positions.subview(100, 200);
    stream << shader(sub, dt).dispatch(200);
  11. Use device-side control flows in the DSL

    stable

    LuisaCompute provides a Domain-Specific Language (DSL) for device-side control flows using special macros prefixed with $. These macros allow for conditional logic and loops within kernels and callables.

    Supported Control Flows:

    • Conditionals: $if (cond) { ... }, $if (cond) { ... } $else { ... }, $if (cond) { ... } $elif (cond2) { ... }, and $if (cond) { ... } $elif (cond2) { ... } $else { ... }.
    • Loops: $while (cond) { ... }, $for (variable, n) { ... }, $for (variable, begin, end) { ... }, $for (variable, begin, end, step) { ... }, and $loop { ... } (infinite loop unless $break is used).
    • Switch Statements: $switch (variable) { $case (value) { ... }; $default { ... }; }. Note that $break is automatically added after $case and $default blocks.
    • Jump Statements: $break; and $continue;.

    Note on Native C++ Control Flow: You can still use standard C++ if, while, etc. (without the $ prefix). These act as meta-stage control flows that determine how the kernels/callables are generated during compilation, enabling multi-stage programming patterns.

    $if (cond) {
        /*...*/
    } $else {
        /*...*/
    };
    
    $for (i, 0, 10, 1) {
        /*...*/
    };