Arsitektur bindless rendering pada API grafis modern seperti DirectX 12 dan Vulkan memindahkan manajemen resource dari CPU binding table ke global descriptor heap berukuran besar. Shader membaca resource melalui indeks numerik mentah yang diteruskan via Push Constants atau Root Constants. Kesalahan offset atau dynamic index yang tidak tervalidasi dapat memicu out-of-bounds (OOB) access, page fault pada GPU, hingga Device Lost (TDR). Panduan ini membahas implementasi CI linting shader bindless untuk validasi resource descriptor index secara statis sebelum aset masuk ke build release.
Tantangan Validasi Statis pada Bindless Rendering
Pada pipeline grafis tradisional berbasis slot binding (misalnya DirectX 11), compiler seperti DXC atau FXC memverifikasi kesesuaian register (t0, s0) saat kompilasi. Sebaliknya, bindless rendering memperlakukan heap resource sebagai array tanpa batas tetap (unbounded array):
// HLSL SM 6.6 Bindless Model
Texture2D g_Textures[] : register(t0, space0);
struct PushData {
uint materialTextureIndex;
uint samplerIndex;
};
ConstantBuffer<PushData> PushConstants : register(b0, space1);
float4 PSMain(float2 uv : TEXCOORD) : SV_Target {
// Risiko runtime crash jika index melebihi alokasi heap aplikasi
return g_Textures[PushConstants.materialTextureIndex].Sample(g_Samplers[PushConstants.samplerIndex], uv);
}Masalah utama yang harus dicegah pada level CI mencakup:
- Ketidaksesuaian Struct Push Constant: Perbedaan ukuran struct data antara kode C++ host engine dan layout shader.
- Batas Ukuran Array Unbounded: Batasan indeks statis yang melampaui alokasi maksimum descriptor heap target hardware (misalnya limit tier Vulkan atau D3D12 Tier 1/2 Resource Descriptor Heap).
- Akses Uninitialized Index: Eksekusi jalur kode yang menggunakan register index sebelum diinisialisasi.
Arsitektur CI Pipeline untuk Linting Shader
Pipeline validasi CI berjalan dalam dua tahap utama: Validasi Binary/IR menggunakan tool bawaan vendor (dxc dan spirv-val), dilanjutkan dengan Static AST Inspection menggunakan tool kustom berbasis CLI yang membandingkan metadata refleksi terhadap kontrak pipeline C++.
[Shader Source (.hlsl / .vert)]
|
(dxc / glslangValidator)
|
[SPIR-V / DXIL IR]
|------------------------------+
| |
[spirv-val] [Custom Reflector Tool]
(Validasi Standard IR) (Cek Binding & Push Constant)
| |
+--------------+---------------+
|
[Deterministik Exit Code]
(0 = Lolos, non-zero = Block)1. Validasi Intermediate Representation (IR) dengan spirv-val
Eksekusi kompilasi shader ke format target SPIR-V atau DXIL, lalu jalankan linter validasi byte-code. Untuk Vulkan/SPIR-V, gunakan flag spesifik untuk menguji kepatuhan kemampuan descriptor indexing:
# Kompilasi HLSL ke SPIR-V dengan ekstensi descriptor indexing
dxc -T ps_6_6 -E PSMain shader.hlsl -spirv -fspv-target-env=vulkan1.3 -Fo shader.spv
# Validasi struktur SPIR-V
spirv-val --target-env vulkan1.3 shader.spv2. Custom Static Checker: Validasi Root Signature dan Descriptor Bound
Compiler resmi tidak mengetahui konfigurasi memori runtime engine. Karena itu, CI memerlukan skrip CLI mandiri (misalnya ditulis dengan Python atau Rust) yang mengekstrak metadata refleksi shader menggunakan spirv-reflect atau dxc.exe -dumpbin -rootsig, lalu memverifikasinya terhadap kontrak engine (konstanta batas heap).
Berikut contoh skrip Python untuk memeriksa ukuran push constant block dan register space descriptor index:
#!/usr/bin/env python3
import sys
import json
import subprocess
MAX_ALLOWED_PUSH_CONSTANT_BYTES = 128
MAX_DESCRIPTOR_HEAP_INDEX = 1_000_000
def validate_shader(spv_path):
# Ekstrak refleksi via spirv-cross ke format JSON
cmd = ["spirv-cross", spv_path, "--reflect", "--output-type", "json"]
res = subprocess.run(cmd, capture_output=True, text=True)
if res.returncode != 0:
print(f"[ERROR] Refleksi gagal untuk {spv_path}: {res.stderr}")
return 1
data = json.loads(res.stdout)
# 1. Validasi Push Constant Size
push_constants = data.get("push_constants", [])
for pc in push_constants:
size = pc.get("block_size", 0)
if size > MAX_ALLOWED_PUSH_CONSTANT_BYTES:
print(f"[VIOLATION] Push constant pada {spv_path} berukuran {size}B (Maksimum {MAX_ALLOWED_PUSH_CONSTANT_BYTES}B)")
return 2
# 2. Validasi Binding Model
types = data.get("types", {})
textures = data.get("sampled_images", [])
for tex in textures:
# Cek unbounded array definition ([])
array_dims = tex.get("array", [])
if not array_dims or array_dims[0] == 0:
# Unbounded descriptor array terdeteksi
continue
if array_dims[0] > MAX_DESCRIPTOR_HEAP_INDEX:
print(f"[VIOLATION] Array descriptor index statis melebihi heap limit: {array_dims[0]}")
return 3
print(f"[PASS] {spv_path} valid.")
return 0
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit(1)
sys.exit(validate_shader(sys.argv[1]))Menangani Dynamic Indexing dan False Positive
Pada bindless architecture, indeks resource sering ditentukan secara dinamis (misalnya instanceID % totalMaterials). Static analysis murni tidak bisa memprediksi runtime value. Jika CI menolak dynamic expressions, durasi build terhambat oleh penyesuaian false positive.
Gunakan kombinasi pendekatan berikut pada level kode dan linter:
- Software-Enforced Clamping: Terapkan macro guard pada shader source untuk memastikan indeks tidak pernah melompati batas memori heap target secara spekulatif:
// Clamp macro untuk mencegah akses illegal memori shader core
#define SAFE_BINDLESS_FETCH(Heap, Index, MaxLimit) \
Heap[NonUniformResourceIndex(min(Index, MaxLimit - 1))]- Lint Exemption Annotations: Sediakan parser atribut khusus (misalnya
// [[ci::allow_dynamic_index]]) agar linter CI mengabaikan inspeksi batas statis pada baris yang telah diamankan dengan runtime branching logis.
Integrasi GitHub Actions Pipeline
Jalankan linting shader secara paralel sebelum tahap build binary engine. Pendekatan ini mempercepat siklus feedback jika terjadi breaking change pada shader interface.
name: Shader Bindless Lint Validation
on:
pull_request:
paths:
- 'shaders/**'
- 'engine/shaders/**'
jobs:
lint-bindless-shaders:
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v4
- name: Install Vulkan SDK and SPIRV Tools
run: |
sudo apt-get update
sudo apt-get install -y spirv-tools spirv-cross directx-shader-compiler
- name: Compile and Validate Shaders
run: |
EXIT_CODE=0
for shader in $(find shaders/ -name "*.hlsl"); do
echo "Linting: $shader"
# Kompilasi ke SPIR-V sementara
spv_out="${shader}.spv"
dxc -T ps_6_6 -E PSMain "$shader" -spirv -fspv-target-env=vulkan1.3 -Fo "$spv_out" || EXIT_CODE=1
# Validasi struktur SPIR-V
spirv-val --target-env vulkan1.3 "$spv_out" || EXIT_CODE=2
# Validasi heap bounds and layout contracts
python3 tools/ci/validate_bindless_layout.py "$spv_out" || EXIT_CODE=3
rm -f "$spv_out"
done
exit $EXIT_CODESkema Exit Code Deterministik
Untuk mempermudah investigasi pipeline pada CI runner, definisikan skema exit code standar untuk seluruh tools validasi:
0: Lolos validasi tanpa error.1: Kesalahan kompilasi dasar DXC (syntax error, unsupported target).2: Pelanggaran validasi SPIR-V (invalid memory model, malformed SSA structure viaspirv-val).3: Pelanggaran kontrak layout bindless (push constant size mismatch, heap space index overflow).
Penerapan automated linting pada pipeline CI memastikan error invalid pointer reference dan out-of-bounds descriptor indexing tertangkap sebelum mencapai fase integrasi gameplay maupun release runtime.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!