lc_native_shader
Skill1.1k starsChanged 35 days agoApache-2.0View on GitHub
Compile hand-written native compute shaders and dispatch them from a Stream through NativeShaderExt — HLSL on dx/vk (DXC), GLSL on vk (glslang), CUDA C++ on cuda (NVRTC) — covering compile/reflect/load, NativeShaderLauncher binding by index/register+space/positional, uniforms (root constants / push constants / scalar kernel params), the per-argument Usage contract, include dirs, and lifetime.
Trending
Quiet since July.thin dataabout the whole repository
Who's talking about LuisaGroup/LuisaCompute
- Stack Overflow“c++ error: ‘struct std::atomic_flag’ has no member named ‘test’”Stack Overflow · Dec 26, 2022 · 1 votes
- Tencent/ncnn“optimize multi-head attention (increased shared-memory tile size, add fused kernel)”GitHub · Jul 10, 2026
- bazelbuild/bazel-central-registry“wanted: [LuisaGroup/LuisaCompute]”GitHub · Nov 8, 2025
- MuGdxy/SymEigen“Try to add support for luisa-compute”GitHub · Sep 15, 2025
- LuisaGroup/LuisaRender“Remove `util/u64.h` and use `Var<ulong>` instead”GitHub · Jun 2, 2024
The biggest 5 of 5 mentions linking to the repository that our crawl found. Each line is their own headline.
In this repository22 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- astSkill
- backend-architectureSkill
- cmakeSkill
- cpp-styleSkill
- debugSkill
- gitSkill
- glslangSkill
- hlslSkill
- ir-pipelineSkill
- lc_apiSkill
- lc_optimizeSkill
- llvm-spirvSkill
- lspSkill
- project_structureSkill
- renderdocSkill
- spirv_codegenSkill
- spv-optSkill
- testSkill
- xir-passesSkill
- xmakeSkill
- yyjsonSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
Only buffers and uniform blocks.
- Very specific: line 161
Every reflected binding must be supplied exactly once.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Native Shader Injection (NativeShaderExt)
NativeShaderExt (in include/luisa/backends/ext/native_shader_ext.h, the canonical
reference) hands native source (HLSL / GLSL / CUDA C++) to the backend's own compiler,
reflects its bindings, creates a backend compute-shader instance, and dispatches it from
a Stream. It bypasses the DSL/AST/XIR path entirely.
- Compute only, JIT only (no
ShaderSerializer/AOT, noLUISA_DUMP_SOURCE). - Only buffers and uniform blocks; textures/samplers/accel reflect but are rejected at
load(). - The per-argument
Usageyou declare is the command-reorder synchronization contract. - Destroy a
NativeShaderbefore theDevicethat created it.
Backend support matrix
NativeShaderLanguage | dx | vk | cuda |
HLSL | DXIL via DXC | SPIR-V via DXC | rejected |
GLSL | rejected | SPIR-V via glslang | rejected |
CUDA_NVRTC | rejected | rejected | PTX via NVRTC |
compile() fails closed (ok() false, binary empty, error names the reason).
Workflow
#include <luisa/backends/ext/native_shader_ext.h>
// ... runtime/context.h, runtime/device.h, runtime/stream.h, runtime/command_list.h ...
auto ext = device.extension<NativeShaderExt>();
if (ext == nullptr) { /* backend has no NativeShaderExt */ }
// 1. compile source -> bytecode + reflection
NativeShaderCompileInfo info;
info.language = NativeShaderLanguage::HLSL; // or GLSL / CUDA_NVRTC
info.source = source; // string_view: must outlive compile()
info.entry_point = "CSMain"; // "main" for GLSL
info.push_constant_size = 2u * sizeof(float); // uniform bytes; 0 = reflect
// info.block_size for CUDA when the kernel has no __launch_bounds__
// info.source_type = NativeShaderSourceType::FilePath; info.include_dirs = {...}
// info.shader_model, info.optimize, info.enable_fast_math, info.enable_debug_info
auto result = ext->compile(info);
if (!result.ok()) { /* result.error */ }
// 2. load bytecode -> backend shader instance + resolved binding table
auto metadata = ext->load(result); // optional usage_override span
if (!metadata.valid()) { /* contract violation, LUISA_WARNING names it */ }
// 3. RAII owner (destroys the instance before the device)
NativeShader shader{*ext, std::move(metadata)};
// or one-shot: auto shader = ext->create_shader(info);
// 4. bind + dispatch
auto launcher = shader.launcher(); // handle + block size + bindings
launcher.add_buffer_by_index(0u, src.view(), Usage::READ)
.add_buffer_by_index(1u, dst.view(), Usage::WRITE)
.add_uniform(k)
.add_uniform(c);
auto cmd = std::move(launcher).build(uint3{thread_count, 1u, 1u});
stream << std::move(cmd) << synchronize();build(thread_count) takes the total thread count (grid × block), not a grid size;
the block size comes from the launcher (the shader's reflected workgroup size). Use
launcher.validate() for a soft error string; build() asserts the same plan.
Shader sources
HLSL on dx — free entry-point name; [numthreads] drives the reflected block size:
StructuredBuffer<float> src : register(t0); // SRV
RWStructuredBuffer<float> dst : register(u0); // UAV
cbuffer Uniforms : register(b0) { float k; float c; }; // uniform block (root constants)
[numthreads(64, 1, 1)]
void CSMain(uint3 tid : SV_DispatchThreadID) { dst[tid.x] = src[tid.x] * k + c; }The b0 cbuffer is the launcher's uniform block, fed by add_uniform values and removed
from load()'s binding table (compile() still reports it). Root-constant limit: 64 ×
32-bit; push_constant_size must be a multiple of 4.
HLSL on vk — SPIR-V decorations are the authoritative reflection table:
struct Uniforms { float k; float c; };
[[vk::binding(0, 0)]] StructuredBuffer<float> src;
[[vk::binding(1, 0)]] RWStructuredBuffer<float> dst;
[[vk::push_constant]] ConstantBuffer<Uniforms> uniforms;
[numthreads(64, 1, 1)]
void CSMain(uint3 tid : SV_DispatchThreadID) { dst[tid.x] = src[tid.x] * uniforms.k + uniforms.c; }Plain registers also work (t<n>/u<n> → binding n, space 0); use
[[vk::binding(set, binding)]] for a specific set/binding.
GLSL on vk — single entry point named main:
#version 450
layout(local_size_x = 64) in;
layout(set = 0, binding = 0) readonly buffer A { float a[]; } src; // SRV
layout(set = 0, binding = 1) buffer B { float b[]; } dst; // UAV
layout(push_constant) uniform Push { float k; float c; } uniforms;
void main() { uint i = gl_GlobalInvocationID.x; dst.b[i] = src.a[i] * uniforms.k + uniforms.c; }readonly → StructuredBuffer, otherwise RWStructuredBuffer. layout(push_constant) is
the uniform block (size read back from the module when push_constant_size == 0).
Push-constant size must be a multiple of 4. For #include, add
#extension GL_GOOGLE_include_directive : require after #version.
CUDA C++ on cuda — the kernel signature is the binding declaration:
extern "C" __global__ void scale(const float *src, float *dst, float k, float c) {
auto i = blockIdx.x * blockDim.x + threadIdx.x;
dst[i] = src[i] * k + c;
}extern "C"keeps the name unmangled;__global__must not be templated/qualified.- Pointer params are buffer bindings in order, each bound as the buffer's 64-bit device
address:
const T *→StructuredBuffer/READ;T *→RWStructuredBuffer/READ_WRITE. - Non-pointer params are scalar kernel params fed by
add_uniformin order; there is no push-constant block, "uniform bytes" is just their total size (push_constant_sizecross-checks, 0 = reflect it). A by-value struct is valid (add_uniformusessizeof). entry_pointselects the__global__(defaultmain= "the only kernel").block_sizemust be supplied unless the kernel declares__launch_bounds__(N).
Reflection & binding
result.bindings / shader.bindings() are reported in canonical order
(space_index, register_index, kind); the launcher's positional overload and reorder order
follow it too.
struct NativeShaderResourceBinding {
NativeShaderResourceKind kind; // StructuredBuffer, RWStructuredBuffer, ConstantBuffer, ...
uint register_index; // HLSL register / GLSL binding
uint space_index; // HLSL space / GLSL set
uint array_size; // 1 == non-array
Usage usage; // effective usage
uint32_t stride; // element stride when known
uint32_t size_bytes; // constant-buffer / block size when known
};Supported kinds at load(): dx ConstantBuffer, StructuredBuffer/RWStructuredBuffer,
ByteAddressBuffer/RWByteAddressBuffer; vk the same plus TypedBuffer/RWTypedBuffer;
cuda buffers only. Default usages: CBV/UBO and SRV classes → READ; UAV classes →
READ_WRITE.
The three bind overloads (prefer index when HLSL namespaces collide — register(t0) and
register(b0) are both bind point 0):
add_buffer_by_index(index, view, usage)— index into the reflection table (recommended).add_buffer(register, space, view, usage)— usage disambiguates a shared (register, space).add_buffer(view, usage)— positional, canonical order.
Every reflected binding must be supplied exactly once; missing/duplicate/extra args and null
handles are rejected by validate()/build().
Launcher & dispatch
template<typename T> NativeShaderLauncher &add_uniform(const T &value);
NativeShaderLauncher &add_uniform(const void *data, size_t size, size_t alignment);
auto cmd = std::move(launcher).build(uint3{thread_count, 1u, 1u});
// or: std::move(launcher).build(shader_handle, thread_count, block_size);Uniforms are packed into a blob (offsets shifted past the argument header; alignment
clamped to ≤ 16). NativeShaderDispatchCommand is a CustomDispatchCommand
(CustomCommandUUID::NATIVE_SHADER_DISPATCH, 0x0600) tagged StreamTag::COMPUTE; enqueue
it like any other command.
Usage contract
The declared per-argument usage is the synchronization contract: READ (mergeable with
other readers), WRITE (exclusive), READ_WRITE (both); NONE is never valid. build()
(and the backend at load()) cross-check it against the reflected class:
- An SRV/CBV bound as
WRITEis always rejected. - A UAV bound as
READis rejected by default; allow it withlauncher.set_allow_usage_override(true).
Declaring READ for a resource the shader writes silently breaks synchronization.
load(result, usage_override) can override usages up-front (one entry per reflected
binding; a const T * binding cannot be declared writable).
Include dirs & file sources
NativeShaderCompileInfo carries include_dirs; a FilePath source also resolves relative
to its own directory:
info.source_type = NativeShaderSourceType::FilePath;
info.source = luisa::string_view{source_path}; // must outlive compile()
info.include_dirs = { scratch_dir };Includes go through DXC / glslang's includer / NVRTC -I respectively. A bogus include
directory fails closed on every route: the standalone luisa_nvrtc child prints the
compiler log to its stderr, exits non-zero (it never aborts), and the parent surfaces
the log plus the exit code in result.error.
Lifetime
{
NativeShader shader{*ext, std::move(metadata)}; // RAII; move-only
...
} // destroy_shader() here
// or: ext->destroy_shader(handle); / shader.reset();Synchronize dispatches before destruction. A leftover instance is released at extension teardown with a warning, never silently.
Pitfalls
info.source/info.entry_pointarestring_view— the memory must outlivecompile().build(thread_count)is a thread count, not a grid size (1024 threads @ block 64 →build(uint3{1024,1,1})).- On dx, bind by the
load()/metadata.bindingstable (compile()reports the raw DXC table including the b0 cbuffer). - Block size must match the reflected workgroup size, or
build()asserts. dx/vk derive it from[numthreads]/local_size_*; cuda requires it (or__launch_bounds__). - UAV-as-
READneedsset_allow_usage_override(true); SRV-as-WRITEis never allowed. - Destroy the shader before the device and before its dispatches are torn down.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

