HLSL Code Generation

SkillAI & models

HLSL code generation, StringBuilder patterns, builtin headers, and DXIL embedding.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the HLSL Code Generation skill

What this skill tells your AI

The instructions your AI receives, as published by luisagroup/luisacompute in .agents/skills/hlsl/SKILL.md and read by ahel’s review.

Codegen utilities using vstd::StringBuilder and formatting helpers.

StringBuilder

#include <luisa/vstl/string_builder.h>

vstd::StringBuilder builder;
builder.clear();
size_t sz = builder.size();
vstd::string_view view = builder.view();
char* data = builder.data();

Appending

str.append("Hello");
str += "World"sv;
str.append(' ');
str << '\n';
str.append(other_builder);
str << "func" << '(' << ")";

Numbers

vstd::to_string(42, str);        // int (faster than format)
vstd::to_string(3.14f, str);     // float
str << luisa::format("{}", 42);
str << luisa::format("{}, {}!", "Hello", "World");
str << luisa::format("{:016X}", hash);

String View Literals

using namespace std::string_view_literals;
str += "void"sv;  // no allocation

Code Generation Patterns

Function Declaration

void GetFunctionDecl(Function func, vstd::StringBuilder &str) {
    vstd::StringBuilder data;
    if (func.return_type()) CodegenUtility::GetTypeName(*func.return_type(), data, Usage::READ);
    else data += "void"sv;
    data += " "sv;
    CodegenUtility::GetFunctionName(func, data);
    data += '(';
    for (auto &&arg : func.arguments()) {
        Usage usage = func.variable_usage(arg.uid());
        CodegenUtility::GetTypeName(*arg.type(), data, usage);
        data << ' ';
        vstd::StringBuilder varName;
        CodegenUtility::GetVariableName(func, arg, varName);
        data << varName << ',';
    }
    if (!func.arguments().empty()) data[data.size() - 1] = ')';
    else data += ')';
    str << '\n' << data;
}

Template Parameters

str << "template<"sv;
for (uint64 i = 0; i < tempIdx; ++i) {
    str << "typename T"sv; vstd::to_string(static_cast<int64_t>(i), str); str << ',';
}
if (tempIdx > 0) *(str.end() - 1) = '>';

Call Expression

str << "func_name"sv << '(';
if (!args.empty()) {
    for (size_t i = 0; i < args.size() - 1; ++i) { args[i]->accept(vis); str << ','; }
    args.back()->accept(vis);
}
str << ')';

Type-Aware Generation

if (t->is_texture() || t->is_buffer()) { str << 'T'; vstd::to_string(tempIdx++, str); }
else if (t->is_vector()) { str << "float"sv; vstd::to_string(t->dimension(), str); }
else if (t->is_matrix()) { auto n = vstd::to_string(t->dimension()); str << "_float"sv << n << 'x' << n; }

RAII Wrapping

struct CodeWrapper {
    vstd::StringBuilder *_result;
    CodeWrapper(vstd::StringBuilder *r, string_view open) : _result(r) { if (_result) *_result << open; }
    ~CodeWrapper() { if (_result) *_result << ')'; }
};
CodeWrapper wrapper{&str, "to_float4x4("};
// inner content generated; ')' appended automatically

Trailing Comma Fix

str << '(';
for (auto &&item : items) str << item << ',';
if (!items.empty()) str[str.size() - 1] = ')';
else str << ')';

Iteration & Hash

for (char c : str) {}
str.erase(str.begin() + 5);
vstd::hash<vstd::StringBuilder> hasher; size_t h = hasher(builder);
if (builder1 == builder2) {}
if (builder1 == "literal"sv) {}

Best Practices

  1. Use "text"sv literals to avoid allocations
  2. vstd::to_string() for integers (faster than luisa::format())
  3. builder.reserve(1024) when size known
  4. operator<< for chaining, operator+= for single items
  5. Check size() before modifying last character

HLSL Builtins

Located in src/backends/common/hlsl/builtin/. Access via:

#include <backends/common/hlsl/builtin/hlsl_builtin.hpp>
auto header = lc_hlsl::get_hlsl_builtin("hlsl_header");
std::string_view code(static_cast<const char*>(header.ptr), header.size);

Header Files (.bytes)

KeyDescription
hlsl_headerMain HLSL header
hlsl_header_fallbackFallback header
spv_aliasSPIR-V aliases
bindless_upload.bytes / bindless_upload_vk.bytesBindless upload helpers
accel_process.bytes / accel_process_vk.bytesAcceleration-structure processing
accel_process_vk_motion.bytesAcceleration-structure processing with motion blur
raytracing_motion_headerRay tracing motion blur
dx_linalgLinear algebra utils (DXIL)
vk_linalgLinear algebra utils (SPIR-V)
raytracing_headerRay tracing
tex2d_bindless / tex3d_bindlessBindless textures
compute_quadCompute quad ops
determinant / inverseMatrix ops
indirectIndirect dispatch/draw
resource_sizeResource queries
accel_headerAcceleration structures
copy_signSign ops
bindless_commonBindless utils
auto_diffAutodiff
reduceParallel reduction

DXIL Files (.dxil)

KeyDescription
accel_process_vk.dxilVulkan acceleration structures
load_bdls.dxil / load_bdls_vk.dxilBindless loading
set_accel4.dxilSet acceleration structure
bc6_encodeblock.dxil / bc6_trymodeg10.dxil / bc6_trymodele10.dxilBC6 compression
bc7_encodeblock.dxil / bc7_trymode02.dxil / bc7_trymode137.dxil / bc7_trymode456.dxilBC7 compression

Adding Builtins

  1. Add .bytes or .dxil to src/backends/common/hlsl/builtin/
  2. Declare: LC_HLSL_DECL_VARNAME(my_bytes)
  3. Register: LC_HLSL_INSERT_VARNAME(my_bytes, "my_key")

Build: .hlsl.bytes, shaders → .dxil, embedded via bin2obj.

Codegen Debug

Set env LUISA_DUMP_SOURCE=1 to dump generated HLSL to per-shader files. Output: hlsl_output_<shader_name>.hlsl in the working directory.

Naming priority depends on the backend and shader path:

DX compute / raster / save paths:

  1. ShaderOption::name / fileName — user-provided name
  2. Function::name() — kernel/callable debug name
  3. Function::hash() formatted as hex — fallback (e.g. hlsl_output_a1b2c3d4.hlsl)

VK internal builtin compute helpers only (ComputeShader::compile_builtin_hlsl_to_spirv):

  1. file_name — user-provided / cached name
  2. Generated HLSL MD5 — fallback

VK raster (VkRasterExt): ShaderOption::name is always required, so the dump file uses that name.

VK user compute shaders must use native SPIR-V codegen (LUISA_XIR_TO_SPIRV or LUISA_AST_LLVM_TO_SPIRV); the Vulkan Function compute path must not call HLSL/DXC.

Files are written with "wb" (overwrite) — no need to delete old files. Each shader gets its own file; no more single hlsl_output.hlsl with appended content.

Signals

GitHub stars
1k
Forks
108
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
hlsl
Source
github.com/luisagroup/luisacompute