Harbor

branch main
showing the latest snapshot on main
integration.md 4.5 KB · Markdown
shader/docs/integration.md 0644 Raw

gpu Shader Integration Guide

Luma is now gpu's built-in shader language/compiler. Most gpu code should use the easy shaderkit facade; compiler tools can import gpu/shader directly.

Basic Compilation

import shaderkit "path/to/gpu/shaderkit"

handle, ok := shaderkit.create_shader_from_source(
    "material.frag",
    .Fragment,
    source,
    {opt_level = .Basic},
)

The facade chooses the backend target from gpu.REQUIRED_SHADER_FORMAT, applies the D3D/HLSL defaults, logs diagnostics, creates the backend shader module, and destroys compiler output.

For compiler tools that need bytecode and reflection:

import shader "path/to/gpu/shader"

result := shader.compile(source, {target = .SPIR_V, stage = .Vertex}, "shader.luma")
defer shader.destroy_compile_result(&result)

bytecode := result.output
reflection := result.reflection

Compile_Result owns output, diagnostics, and reflection. They remain valid across later compiles and must be released with destroy_compile_result.

Pipeline Stages

Each stage is exposed independently for custom tooling:

// Parse only (AST inspection, syntax highlighting)
ast, diags := shader.compile_parse(source, "shader.luma")

// Parse + desugar + typecheck (IDE diagnostics)
ast, sema, diags := shader.compile_check(source, "shader.luma")

// Full lowering to IR (custom optimization passes)
ir_module, diags := shader.compile_lower(source, "shader.luma")

// Dump AST (parse + desugar only)
ast, diags := shader.compile_dump_ast(source, "shader.luma")

Reflection API

Standard Reflection

Extract binding layouts, I/O variables, and struct metadata:

result := shader.compile(source, {target = .SPIR_V, stage = .Fragment}, "shader.luma")
defer shader.destroy_compile_result(&result)

info := result.reflection
json := shader.reflect_to_json(info)

// Or access structured data directly:
for entry in info.entry_points {
    log.infof("Entry: %s (%v)", entry.name, entry.stage)
    for io in entry.inputs {
        log.infof("  in: %s @ location %d", io.name, io.location)
    }
}

for b in info.bindings {
    log.infof("Binding: %s group=%d binding=%d", b.name, b.group, b.binding)
}

SPIR-V Reflection

For Vulkan pipeline creation with descriptor set layouts:

info, diags := shader.compile_reflect_spirv(source, "shader.luma")
json := shader.reflect_spirv_to_json(info)

// Descriptor set layout creation
for ds in info.descriptor_sets {
    // ds.set is the descriptor set number
    for binding in ds.bindings {
        // binding.descriptor_type: "UNIFORM_BUFFER", "STORAGE_BUFFER",
        //                          "SAMPLED_IMAGE", "SAMPLER"
        // binding.binding: binding number
        // binding.stage_flags: ["VERTEX", "FRAGMENT", etc.]
        // binding.block_size: buffer size in bytes
        // binding.members: field-level layout with offsets
    }
}

Compile Options

shader.Compile_Options :: struct {
    target:       shader.Target,      // GLSL_450, GLSL_450_OPENGL, HLSL_SM6, MSL_2_4, WGSL, SPIR_V
    opt_level:    shader.Opt_Level,   // None, Basic, Aggressive
    stage:        shader.Shader_Stage,
    entry:        string,
    debug:        bool,             // Emit debug info (SPIR-V OpLine/OpSource)
    validate:     bool,
    include_dirs: []string,         // Include search paths
    file_reader:  shader.File_Reader, // Custom file reader for virtual filesystem
}

Custom File Reader

For virtual filesystems or asset pipelines, provide a custom file reader:

my_reader :: proc(path: string) -> (content: string, ok: bool) {
    // Read from your asset system
    data, read_ok := asset_system.read(path)
    return data, read_ok
}

result := shader.compile(source, shader.Compile_Options{
    target       = .SPIR_V,
    file_reader  = my_reader,
    include_dirs = []string{"shaders/include"},
})
defer shader.destroy_compile_result(&result)

Error Handling

Diagnostics include source location, severity, and message:

for d in result.diagnostics {
    switch d.level {
    case .Error:
        msg := shader.format_diagnostic(d)
        log.errorf("%s", msg)
        delete(msg)
    case .Warning:
        msg := shader.format_diagnostic(d)
        log.warnf("%s", msg)
        delete(msg)
    case .Note:
        msg := shader.format_diagnostic(d)
        log.infof("%s", msg)
        delete(msg)
    }
}

// Rich formatting with source context (for CLI tools)
for d in result.diagnostics {
    msg := shader.format_diagnostic_with_source(d, source)
    fmt.eprintln(msg)
    delete(msg)
}