Bölüm 01
Temeller ve Yapı
WGSL sadece shader sözdizimi değildir. Çalışan bir WGSL modülü, browser tarafındaki WebGPU kodu, WebGPU doğrulayıcısı ve GPU pipeline'ı arasında yapılan açık bir sözleşmedir. Bu bölüm o sözleşmeyi gözle okunur hale getirir.
Önce modülün tamamını oku
Bir WGSL dosyası bir modüldür. İçinde struct, const, override, module-scope variable, helper function ve entry point bulunabilir. Pratik okuma sırası şu: önce kaynaklar, sonra yardımcı fonksiyonlar, en son entry point.
@group(0) @binding(0)
var<storage, read_write> data : array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3u) {
let i = id.x;
data[i] = sin(f32(i) * 0.0625);
}
API sınırından hangi veri geçiyor?
Burada data adlı tek storage buffer var. Reflection onu group 0, binding 0 olarak göstermeli.
Pipeline nereden başlıyor?
@compute, main fonksiyonunu compute entry point yapar.
Kaç lane birlikte çalışıyor?
@workgroup_size(64), x boyutunda 64 local invocation'lık workgroup tanımlar.
Her invocation ne yazıyor?
global_invocation_id.x array index olur; lane 0 data[0], lane 1
data[1] yazar.
Attribute süs değildir
WGSL'de attribute'lar sıradan görünen declaration'ları GPU pipeline anlamına bağlar. Aynı fonksiyon gövdesi, önündeki attribute'a göre helper function da olabilir, entry point de.
@group(0) @binding(0)
var<storage, read_write> data : array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3u) {
data[id.x] = 1.0;
}
@group ve @binding host tarafındaki slot'u belirtir. @compute shader
aşamasını seçer. @workgroup_size yerel paralel şekli sabitler. @builtin ise değerin
buffer'dan değil WebGPU tarafından geleceğini söyler.
@binding(0) satırını sil ve Diagnostics'e bak. Sonra geri koyup
Reflection'da resource'un tekrar göründüğünü kontrol et.
Binding ve address space host sözleşmesidir
Module-scope var declaration'ları shader dışındaki veriyi tarif eder. Address space verinin nerede
yaşadığını, access mode ise shader'ın onu nasıl kullanacağını gösterir.
struct Params {
scale : f32,
offset : f32,
};
@group(0) @binding(0)
var<uniform> params : Params;
@group(0) @binding(1)
var<storage, read_write> data : array<f32>;
uniform küçük, paylaşılan ve read-only ayarlar için iyidir: time, resolution, camera matrix gibi.
storage ise büyük array'ler, compute çıktıları, particle state veya readback için daha doğaldır.
Entry point girdileri tahmin edilmez
Entry point yalnızca senin yazdığın parametreleri alır. Compute tarafında ilk öğrenilecek builtin
global_invocation_id, fragment tarafında ise position olur.
struct Frame {
resolution : vec2f,
time : f32,
};
@group(0) @binding(0)
var<uniform> frame : Frame;
@fragment
fn fs(@builtin(position) frag : vec4f) -> @location(0) vec4f {
let uv = frag.xy / frame.resolution;
return vec4f(uv.x, uv.y, 0.5, 1.0);
}
İlk görsel hamle şu: piksel pozisyonunu çözünürlüğe böl, UV koordinatı elde et, sonra UV'yi renge yaz. Böylece koordinat modelini doğrudan ekranda görürsün.
Dispatch modeli
Compute shader bir kez çalışmaz; çok sayıda invocation olarak planlanır. JavaScript tarafı kaç workgroup dispatch edileceğini seçer, WGSL tarafı da her workgroup içindeki local invocation sayısını söyler.
@group(0) @binding(0)
var<storage, read_write> data : array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id : vec3u) {
let i = id.x;
if (i >= arrayLength(&data)) {
return;
}
data[i] = sin(f32(i) * 0.0625);
}
Küçük görsel merdiven
Compute veri akışını öğretir; fragment shader uzamsal sezgiyi öğretir. UV, time, distance field ve smoothstep aynı dersin görsel tarafıdır.
struct Frame {
resolution : vec2f,
time : f32,
};
@group(0) @binding(0)
var<uniform> frame : Frame;
@fragment
fn fs(@builtin(position) frag : vec4f) -> @location(0) vec4f {
let aspect = frame.resolution.x / frame.resolution.y;
let uv = (frag.xy / frame.resolution - vec2f(0.5, 0.5)) * vec2f(aspect, 1.0);
let radius = 0.28 + 0.06 * sin(frame.time * 1.8);
let d = length(uv) - radius;
let aa = 1.5 / frame.resolution.y;
let inside = 1.0 - smoothstep(-aa, aa, d);
return vec4f(mix(vec3f(0.05, 0.07, 0.10), vec3f(0.13, 0.83, 0.93), inside), 1.0);
}
Bilinçli hata denemeleri
@binding(0) sil
Beklenen sonuç: resource slot eksik olduğu için diagnostics konuşur.
data[i] = id.x; yaz
Beklenen sonuç: data f32, id.x ise u32.
Çalışma döngüsü
- Fonksiyon gövdesinden önce declaration'ları oku; kaynakları ve entry point'leri adlandır.
- Studio'da diagnostics, reflection ve bind-group layout'un tahmininle uyuştuğunu kontrol et.
- Tek invocation trace et; hangi değer lane'e özel, hangi değer paylaşılıyor ayır.
- WebGPU'da çalıştır; readback veya preview sonucunu tahmininle karşılaştır.
- Bir şeyi bilerek boz; compiler'ın hangi sınırı tarif ettiğini oku.