载入中...
搜索中...
未找到
GraphicsGpuDriven.cpp
浏览该文件的文档.
1#include "common/Exception.h"
3
4#include "graphics/Material.h"
7
8#include <algorithm>
9#include <cmath>
10#include <cstring>
11#include <map>
12#include <numeric>
13#include <string>
14#include <utility>
15
16#include <glm/gtc/matrix_access.hpp>
17
18namespace eve::graphics::webgpu {
19namespace {
20
21WGPUStringView label(const char* s) {
22 WGPUStringView out{};
23 out.data = s;
24 out.length = WGPU_STRLEN;
25 return out;
26}
27
28wgpu::ShaderModule shaderModule(wgpu::Device device, const char* source) {
29 WGPUShaderSourceWGSL wgsl{};
30 wgsl.chain.sType = WGPUSType_ShaderSourceWGSL;
31 wgsl.code = label(source);
32 WGPUShaderModuleDescriptor desc{};
33 desc.nextInChain = &wgsl.chain;
34 return device.CreateShaderModule(reinterpret_cast<const wgpu::ShaderModuleDescriptor*>(&desc));
35}
36
37wgpu::ShaderModule shaderModule(wgpu::Device device, const std::string& source) {
38 WGPUShaderSourceWGSL wgsl{};
39 wgsl.chain.sType = WGPUSType_ShaderSourceWGSL;
40 wgsl.code.data = source.data();
41 wgsl.code.length = source.size();
42 WGPUShaderModuleDescriptor desc{};
43 desc.nextInChain = &wgsl.chain;
44 return device.CreateShaderModule(reinterpret_cast<const wgpu::ShaderModuleDescriptor*>(&desc));
45}
46
47struct CullInput {
48 glm::mat4 model{1.f};
49 glm::vec4 bounds{0.f};
50 glm::vec4 color{1.f};
51 glm::vec4 terrainWave{};
52 glm::vec4 terrainWaveTint{1.f};
53 uint32_t bucket = 0;
54 uint32_t outputBase = 0;
55 uint32_t pad[2]{};
56};
57static_assert(sizeof(CullInput) == 144);
58
59using VisibleInstance = GpuInstance;
60static_assert(sizeof(VisibleInstance) == 208);
61
62struct CullParams {
63 glm::mat4 viewProj{1.f};
64 glm::vec4 planes[6]{};
65 glm::vec4 cameraPos{};
66 glm::vec4 screen{};
67 glm::vec4 clipNearFar{};
68 glm::vec4 hzbInfo{};
69 glm::uvec4 counts{};
70};
71static_assert(sizeof(CullParams) == 240);
72
73struct VisIndirectCommand {
74 uint32_t vertexCount = 0;
75 uint32_t instanceCount = 0;
76 uint32_t firstVertex = 0;
77 uint32_t firstInstance = 0;
78};
79static_assert(sizeof(VisIndirectCommand) == 16);
80
81struct HzbBuildParams {
82 glm::uvec4 info{}; // mip, width, height, previous-mip word offset
83 glm::uvec4 source{}; // previous width, previous height
84};
85static_assert(sizeof(HzbBuildParams) == 32);
86
87constexpr uint32_t kMaxHzbMips = 16;
88constexpr uint32_t kHzbHeaderWords = 16;
89
90constexpr const char* kHzbBuildWgsl = R"wgsl(
91struct BuildParams { info: vec4u, source: vec4u };
92@group(0) @binding(0) var<uniform> params: BuildParams;
93@group(0) @binding(1) var sourceDepth: texture_depth_2d;
94@group(0) @binding(2) var<storage, read_write> hzb: array<u32>;
95
96fn readHzb(base: u32, width: u32, height: u32, p: vec2i) -> f32 {
97 let q = clamp(p, vec2i(0), vec2i(i32(width) - 1, i32(height) - 1));
98 return bitcast<f32>(hzb[base + u32(q.y) * width + u32(q.x)]);
99}
100
101@compute @workgroup_size(8, 8)
102fn cs_main(@builtin(global_invocation_id) gid: vec3u) {
103 let mip = params.info.x;
104 let width = params.info.y;
105 let height = params.info.z;
106 if (gid.x >= width || gid.y >= height) { return; }
107 var depth: f32;
108 if (mip == 0u) {
109 depth = textureLoad(sourceDepth, vec2i(gid.xy), 0);
110 } else {
111 let previousWidth = params.source.x;
112 let previousHeight = params.source.y;
113 let previousBase = 16u + params.info.w;
114 let p = vec2i(gid.xy * 2u);
115 depth = max(readHzb(previousBase, previousWidth, previousHeight, p),
116 max(readHzb(previousBase, previousWidth, previousHeight, p + vec2i(1, 0)),
117 max(readHzb(previousBase, previousWidth, previousHeight, p + vec2i(0, 1)),
118 readHzb(previousBase, previousWidth, previousHeight, p + vec2i(1, 1)))));
119 }
120 let outputBase = 16u + hzb[mip];
121 hzb[outputBase + gid.y * width + gid.x] = bitcast<u32>(depth);
122}
123)wgsl";
124
125constexpr const char* kCullWgsl = R"wgsl(
126struct CullInput {
127 model: mat4x4f,
128 bounds: vec4f,
129 color: vec4f,
130 terrainWave: vec4f,
131 terrainWaveTint: vec4f,
132 bucket: u32,
133 outputBase: u32,
134 pad1: u32,
135 pad2: u32,
136};
137struct VisibleInstance {
138 model: mat4x4f,
139 meshId: u32,
140 materialId: u32,
141 flags: u32,
142 lodGroupId: u32,
143 reflectionProbeSlots: vec4u,
144 reflectionProbeCenter: array<vec4f, 2>,
145 reflectionProbeExtent: array<vec4f, 2>,
146 color: vec4f,
147 terrainWave: vec4f,
148 terrainWaveTint: vec4f,
149};
150struct CullParams {
151 viewProj: mat4x4f,
152 planes: array<vec4f, 6>,
153 cameraPos: vec4f,
154 screen: vec4f,
155 clipNearFar: vec4f,
156 hzbInfo: vec4f,
157 counts: vec4u,
158};
159struct IndirectCommand {
160 indexCount: u32,
161 instanceCount: atomic<u32>,
162 firstIndex: u32,
163 baseVertex: u32,
164 firstInstance: u32,
165};
166struct VisIndirectCommand {
167 vertexCount: u32,
168 instanceCount: atomic<u32>,
169 firstVertex: u32,
170 firstInstance: u32,
171};
172@group(0) @binding(0) var<uniform> params: CullParams;
173@group(0) @binding(1) var<storage, read> inputs: array<CullInput>;
174@group(0) @binding(2) var<storage, read_write> visibleInstances: array<VisibleInstance>;
175@group(0) @binding(3) var<storage, read_write> commands: array<IndirectCommand>;
176@group(0) @binding(4) var<storage, read_write> visCommands: array<VisIndirectCommand>;
177@group(0) @binding(5) var<storage, read> hzb: array<u32>;
178
179fn remainsVisibleAgainstDepth(center: vec3f, radius: f32) -> bool {
180 if (params.clipNearFar.w < 0.5) { return true; }
181 let clip = params.viewProj * vec4f(center, 1.0);
182 if (clip.w <= params.clipNearFar.x) { return true; }
183 let ndc = clip.xyz / clip.w;
184 if (abs(ndc.x) > 1.0 || abs(ndc.y) > 1.0 || ndc.z > 1.0) { return true; }
185
186 let toEye = normalize(params.cameraPos.xyz - center);
187 let frontClip = params.viewProj * vec4f(center + toEye * radius, 1.0);
188 let frontZ = frontClip.z / max(frontClip.w, 1e-6);
189 let viewDepth = max(length(center - params.cameraPos.xyz), 1e-4);
190 let radiusPx = radius * params.clipNearFar.z / viewDepth;
191 let diameterPx = max(radiusPx * 2.0, 1.0);
192 let mip = min(u32(ceil(log2(diameterPx))), u32(params.hzbInfo.x));
193 let width = max(u32(params.hzbInfo.z) >> mip, 1u);
194 let height = max(u32(params.hzbInfo.w) >> mip, 1u);
195 let uv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
196 let p0 = clamp(vec2i(uv * vec2f(f32(width), f32(height))) - vec2i(1),
197 vec2i(0), vec2i(i32(width) - 1, i32(height) - 1));
198 let p1 = min(p0 + vec2i(1), vec2i(i32(width) - 1, i32(height) - 1));
199 let base = 16u + hzb[mip];
200 let a = bitcast<f32>(hzb[base + u32(p0.y) * width + u32(p0.x)]);
201 let b = bitcast<f32>(hzb[base + u32(p0.y) * width + u32(p1.x)]);
202 let c = bitcast<f32>(hzb[base + u32(p1.y) * width + u32(p0.x)]);
203 let d = bitcast<f32>(hzb[base + u32(p1.y) * width + u32(p1.x)]);
204 let blocker = min(min(a, b), min(c, d));
205 // All conservative samples must contain geometry closer than the sphere.
206 // The epsilon prevents the object's own previous-frame depth from culling it.
207 return blocker >= 0.9999 || frontZ <= blocker + 0.002;
208}
209
210@compute @workgroup_size(64)
211fn cs_main(@builtin(global_invocation_id) gid: vec3u) {
212 let index = gid.x;
213 if (index >= params.counts.x) { return; }
214 let input = inputs[index];
215 let center = (input.model * vec4f(input.bounds.xyz, 1.0)).xyz;
216 let scale = max(length(input.model[0].xyz),
217 max(length(input.model[1].xyz), length(input.model[2].xyz)));
218 let radius = input.bounds.w * scale;
219 for (var p = 0u; p < 6u; p = p + 1u) {
220 let plane = params.planes[p];
221 if (dot(plane.xyz, center) + plane.w < -radius) { return; }
222 }
223 if (!remainsVisibleAgainstDepth(center, radius)) { return; }
224 let local = atomicAdd(&commands[input.bucket].instanceCount, 1u);
225 atomicAdd(&visCommands[input.bucket].instanceCount, 1u);
226 var visible: VisibleInstance;
227 visible.model = input.model;
228 visible.meshId = 0u;
229 visible.materialId = 0u;
230 visible.flags = 0u;
231 visible.lodGroupId = 0u;
232 visible.reflectionProbeSlots = vec4u(0u);
233 visible.reflectionProbeCenter[0] = vec4f(0.0);
234 visible.reflectionProbeCenter[1] = vec4f(0.0);
235 visible.reflectionProbeExtent[0] = vec4f(0.0);
236 visible.reflectionProbeExtent[1] = vec4f(0.0);
237 visible.color = input.color;
238 visible.terrainWave = input.terrainWave;
239 visible.terrainWaveTint = input.terrainWaveTint;
240 visibleInstances[input.outputBase + local] = visible;
241}
242)wgsl";
243
244constexpr const char* kGpuDrivenVertWgsl = R"wgsl(
245struct Light3D { posRadius: vec4f, color: vec4f };
246struct Frame {
247 mvp: mat4x4f,
248 model: mat4x4f,
249 lightDir: vec4f,
250 lightColor: vec4f,
251 tint: vec4f,
252 cameraPos: vec4f,
253 ambient: vec4f,
254 lights: array<Light3D, 8>,
255 texBomb: vec4f,
256 parallax: vec4f,
257 surface: vec4f,
258 view: mat4x4f,
259 clipInfo: vec4f,
260 cloud: vec4f,
261 cloudWind: vec4f,
262};
263struct VSIn {
264 @location(0) pos: vec3f,
265 @location(1) normal: vec3f,
266 @location(2) uv: vec2f,
267};
268struct VSOut {
269 @builtin(position) pos: vec4f,
270 @location(0) vNormal: vec3f,
271 @location(1) vUV: vec2f,
272 @location(2) vTint: vec4f,
273 @location(3) vWorldPos: vec3f,
274 @location(4) vCameraPos: vec3f,
275 @location(5) vViewPos: vec3f,
276};
277@group(0) @binding(0) var<uniform> ubo: Frame;
278struct VisibleInstance {
279 model: mat4x4f,
280 meshId: u32,
281 materialId: u32,
282 flags: u32,
283 lodGroupId: u32,
284 reflectionProbeSlots: vec4u,
285 reflectionProbeCenter: array<vec4f, 2>,
286 reflectionProbeExtent: array<vec4f, 2>,
287 color: vec4f,
288 terrainWave: vec4f,
289 terrainWaveTint: vec4f,
290};
291@group(1) @binding(0) var<storage, read> visibleInstances: array<VisibleInstance>;
292
293fn inverse3x3(m: mat3x3f) -> mat3x3f {
294 let a = m[0].x; let b = m[1].x; let c = m[2].x;
295 let d = m[0].y; let e = m[1].y; let f = m[2].y;
296 let g = m[0].z; let h = m[1].z; let i = m[2].z;
297 let det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
298 return mat3x3f(
299 vec3f((e * i - f * h) / det, (f * g - d * i) / det, (d * h - e * g) / det),
300 vec3f((c * h - b * i) / det, (a * i - c * g) / det, (b * g - a * h) / det),
301 vec3f((b * f - c * e) / det, (c * d - a * f) / det, (a * e - b * d) / det));
302}
303
304fn terrainFastSinCos(value: vec4f) -> vec4f {
305 var x = fract(value * 0.15915494309189535);
306 x = x * 2.0 - 1.0;
307 let x2 = x * x;
308 let sine = x * (7.61 - 35.2 * x2) / (1.0 + x2 * (11.2 + 3.6 * x2));
309 return sine * sine;
310}
311
312fn terrainDetailWave(worldPosition: ptr<function, vec3f>, heightMask: f32,
313 terrainWave: vec4f, terrainWaveTint: vec4f) -> vec3f {
314 if (terrainWave.w < 0.5) { return vec3f(1.0); }
315 let waveXSize = vec4f(0.012, 0.02, 0.06, 0.024) * terrainWave.y;
316 let waveZSize = vec4f(0.006, 0.02, 0.02, 0.05) * terrainWave.y;
317 var waves = (*worldPosition).x * waveXSize + (*worldPosition).z * waveZSize;
318 waves += terrainWave.x * vec4f(0.3, 0.5, 0.4, 1.2) * 4.0;
319 waves = terrainFastSinCos(waves);
320 let lighting = dot(waves, vec4f(0.6742, 0.6742, 0.2697, 0.1349)) * 0.7;
321 let displacement = vec2f(dot(waves, vec4f(0.024, 0.04, -0.12, 0.096)),
322 dot(waves, vec4f(0.006, 0.02, -0.02, 0.1))) * heightMask;
323 let offset = displacement * terrainWave.z;
324 *worldPosition = vec3f((*worldPosition).x - offset.x, (*worldPosition).y,
325 (*worldPosition).z - offset.y);
326 return 2.0 * mix(vec3f(0.5), terrainWaveTint.rgb, vec3f(lighting));
327}
328
329@vertex
330fn vs_main(in: VSIn, @builtin(instance_index) instanceIndex: u32) -> VSOut {
331 let sourceModel = visibleInstances[instanceIndex].model;
332 let instanceColor = visibleInstances[instanceIndex].color;
333 let terrainWave = visibleInstances[instanceIndex].terrainWave;
334 let terrainWaveTint = visibleInstances[instanceIndex].terrainWaveTint;
335 var model = sourceModel;
336 if (ubo.surface.w > 0.5) {
337 let origin = sourceModel[3].xyz;
338 var forward = vec3f(ubo.cameraPos.x - origin.x, 0.0, ubo.cameraPos.z - origin.z);
339 forward = select(vec3f(0, 0, 1), normalize(forward), length(forward) > 1e-6);
340 let right = vec3f(forward.z, 0, -forward.x);
341 model = mat4x4f(vec4f(right * length(sourceModel[0].xyz), 0),
342 vec4f(0, length(sourceModel[1].xyz), 0, 0),
343 vec4f(forward * length(sourceModel[2].xyz), 0), sourceModel[3]);
344 }
345 var worldPosition = (model * vec4f(in.pos, 1.0)).xyz;
346 let waveTint = terrainDetailWave(&worldPosition, in.uv.y, terrainWave, terrainWaveTint);
347 let world = vec4f(worldPosition, 1.0);
348 var out: VSOut;
349 out.pos = ubo.mvp * world;
350 out.pos.y = -out.pos.y;
351 out.vWorldPos = world.xyz;
352 out.vViewPos = (ubo.view * world).xyz;
353 let normalMatrix = transpose(inverse3x3(mat3x3f(model[0].xyz, model[1].xyz,
354 model[2].xyz)));
355 out.vNormal = normalize(normalMatrix * in.normal);
356 out.vUV = in.uv;
357 out.vTint = ubo.tint * instanceColor * vec4f(waveTint, 1.0);
358 out.vCameraPos = ubo.cameraPos.xyz;
359 return out;
360}
361)wgsl";
362
363uint64_t grownCapacity(uint64_t current, uint64_t required) {
364 uint64_t value = std::max<uint64_t>(current, 256);
365 while (value < required) value *= 2;
366 return value;
367}
368
369} // namespace
370
372 if (!mesh || !mesh->gpuHandle) return kInvalidGpuDrivenSlot;
373 auto found = gpuDrivenMeshIds_.find(mesh);
374 if (found != gpuDrivenMeshIds_.end()) return found->second;
375 const uint32_t id = static_cast<uint32_t>(gpuDrivenMeshes_.size());
376 gpuDrivenMeshes_.push_back(mesh);
377 gpuDrivenMeshIds_.emplace(mesh, id);
378 return id;
379}
380
383 auto found = gpuDrivenMaterialIds_.find(material);
384 if (found != gpuDrivenMaterialIds_.end()) return found->second;
385 uint32_t id = 0;
386 if (!gpuDrivenMaterialFree_.empty()) {
387 id = gpuDrivenMaterialFree_.back();
388 gpuDrivenMaterialFree_.pop_back();
389 gpuDrivenMaterials_[id] = material;
390 } else {
391 id = static_cast<uint32_t>(gpuDrivenMaterials_.size());
392 gpuDrivenMaterials_.push_back(material);
393 }
394 gpuDrivenMaterialIds_.emplace(material, id);
395 return id;
396}
397
399 if (!material || material->virtualTextureMode() == MaterialVirtualTextureMode::AtlasPageTable) return false;
400 if (material->surfaceMode() == SurfaceMode::Transparent || material->hasPbrSurface() ||
401 material->effectiveShader() != nullptr || material->isTransparentHair())
402 return false;
403 return material->getShadingModel() == "pbr" && material->getReceiveLight();
404}
405
407 if (!material)
409 "cannot release a null GPU-driven material"));
410 const auto found = gpuDrivenMaterialIds_.find(material);
411 if (found == gpuDrivenMaterialIds_.end()) return Result<void>::success();
412 const uint32_t slot = found->second;
413 gpuDrivenMaterialIds_.erase(found);
414 gpuDrivenMaterials_[slot] = nullptr;
415 gpuDrivenMaterialFree_.push_back(slot);
416 return Result<void>::success();
417}
418
420 if (!gpuDrivenEnabled_ || !device || gpuDrivenComputePending_ || gpuDrivenDrawPending_ || !instances ||
421 instanceCount == 0)
422 return false;
423 using BucketKey = std::pair<uint32_t, uint32_t>;
424 std::map<BucketKey, std::vector<const GpuInstance*>> grouped;
425 for (uint32_t i = 0; i < instanceCount; ++i) {
426 const GpuInstance& instance = instances[i];
427 if (instance.meshId >= gpuDrivenMeshes_.size() || instance.materialId >= gpuDrivenMaterials_.size())
428 return false;
429 Mesh* mesh = gpuDrivenMeshes_[instance.meshId];
430 Material* material = gpuDrivenMaterials_[instance.materialId];
431 if (!mesh || !mesh->gpuHandle || !material || !gpuDrivenMaterialUsable(material)) return false;
432 grouped[{instance.meshId, instance.materialId}].push_back(&instance);
433 }
434
435 const uint32_t paddedCount = std::accumulate(
436 grouped.begin(), grouped.end(), 0u, [](uint32_t count, const auto& entry) {
437 return count + ((static_cast<uint32_t>(entry.second.size()) + 15u) & ~uint32_t(15u));
438 });
439 ensureGpuDrivenResources(paddedCount, static_cast<uint32_t>(grouped.size()));
440 if (!gpuDrivenRenderPipeline_ || !gpuDrivenVisibleBuffer_ || !gpuDrivenIndirectBuffer_) return false;
441
442 std::vector<VisibleInstance> visible(paddedCount);
443 std::vector<GpuIndirectCommand> commands;
444 commands.reserve(grouped.size());
445 gpuDrivenBuckets_.clear();
446 uint32_t outputBase = 0;
447 for (const auto& [key, bucketInstances] : grouped) {
448 Mesh* mesh = gpuDrivenMeshes_[key.first];
449 Material* material = gpuDrivenMaterials_[key.second];
450 auto* gpu = static_cast<GpuMesh*>(mesh->gpuHandle);
451 auto bound = material->bind(*this);
452 if (!bound) throw Exception("%s", bound.error()->message().c_str());
453 GpuIndirectCommand command{};
454 command.indexCount = gpu->indexCount;
455 command.instanceCount = static_cast<uint32_t>(bucketInstances.size());
456 commands.push_back(command);
457 gpuDrivenBuckets_.push_back(
458 {mesh, material, outputBase, static_cast<uint32_t>(bucketInstances.size())});
459 for (uint32_t i = 0; i < bucketInstances.size(); ++i) {
460 const GpuInstance& source = *bucketInstances[i];
462 }
463 outputBase += (static_cast<uint32_t>(bucketInstances.size()) + 15u) & ~uint32_t(15u);
464 }
465 queue.WriteBuffer(gpuDrivenVisibleBuffer_, 0, visible.data(), visible.size() * sizeof(VisibleInstance));
466 queue.WriteBuffer(gpuDrivenIndirectBuffer_, 0, commands.data(), commands.size() * sizeof(GpuIndirectCommand));
467 gpuDrivenDrawPending_ = true;
468 frameHad3DThisFrame = true;
469 frameHad3D = true;
470 return true;
471}
472
474 if (!gpuDrivenEnabled_ || !device || gpuDrivenComputePending_ || gpuDrivenDrawPending_)
478 batch.buffer.strideBytes != sizeof(GpuInstance) || !batch.buckets || batch.bucketCount == 0 ||
479 batch.instanceCount == 0)
481 const uint64_t required = batch.buffer.offsetBytes + uint64_t(batch.instanceCount) * sizeof(GpuInstance);
482 if (required < batch.buffer.offsetBytes || required > batch.buffer.sizeBytes)
484
485 std::vector<GpuIndirectCommand> commands(batch.bucketCount);
486 std::vector<GpuDrivenBucket> buckets(batch.bucketCount);
487 uint64_t coveredInstances = 0;
488 for (uint32_t i = 0; i < batch.bucketCount; ++i) {
489 const GpuResidentInstanceBucket& bucket = batch.buckets[i];
490 const uint64_t end = uint64_t(bucket.firstInstance) + bucket.instanceCount;
491 if (bucket.instanceCount == 0 || bucket.firstInstance != coveredInstances || end > batch.instanceCount ||
492 bucket.meshId >= gpuDrivenMeshes_.size() || bucket.materialId >= gpuDrivenMaterials_.size())
494 Mesh* mesh = gpuDrivenMeshes_[bucket.meshId];
495 Material* material = gpuDrivenMaterials_[bucket.materialId];
496 if (!mesh || !mesh->gpuHandle || !gpuDrivenMaterialUsable(material))
498 auto* gpu = static_cast<GpuMesh*>(mesh->gpuHandle);
499 commands[i] = {gpu->indexCount, bucket.instanceCount, 0, 0, bucket.firstInstance};
500 buckets[i] = {mesh, material, bucket.firstInstance, bucket.instanceCount};
501 coveredInstances = end;
502 }
503 if (coveredInstances != batch.instanceCount) return GpuResidentSubmitStatus::InvalidArgument;
504
505 ensureGpuDrivenResources(batch.instanceCount, batch.bucketCount);
506 if (!gpuDrivenResidentRenderPipeline_ || !gpuDrivenIndirectBuffer_)
508 queue.WriteBuffer(gpuDrivenIndirectBuffer_, 0, commands.data(), commands.size() * sizeof(GpuIndirectCommand));
509 WGPUBuffer rawBuffer{};
510 static_assert(sizeof(rawBuffer) <= sizeof(batch.buffer.nativeHandle));
511 std::memcpy(&rawBuffer, &batch.buffer.nativeHandle, sizeof(rawBuffer));
513 gpuDrivenBuckets_ = std::move(buckets);
514 gpuDrivenResidentBuffer_ = rawBuffer;
515 gpuDrivenResidentOffset_ = batch.buffer.offsetBytes;
516 gpuDrivenResidentSize_ = uint64_t(batch.instanceCount) * sizeof(GpuInstance);
517 gpuDrivenResidentDrawPending_ = true;
518 gpuDrivenDrawPending_ = true;
519 frameHad3DThisFrame = true;
520 frameHad3D = true;
522}
523
524void Graphics::ensureGpuDrivenResources(uint32_t instanceCount, uint32_t bucketCount) {
525 if (!gpuDrivenCullPipeline_) {
526 WGPUBindGroupLayoutEntry computeEntries[6]{};
527 computeEntries[0].binding = 0;
528 computeEntries[0].visibility = WGPUShaderStage_Compute;
529 computeEntries[0].buffer.type = WGPUBufferBindingType_Uniform;
530 computeEntries[0].buffer.minBindingSize = sizeof(CullParams);
531 for (uint32_t i = 1; i < 5; ++i) {
532 computeEntries[i].binding = i;
533 computeEntries[i].visibility = WGPUShaderStage_Compute;
534 computeEntries[i].buffer.type =
535 i == 1 ? WGPUBufferBindingType_ReadOnlyStorage : WGPUBufferBindingType_Storage;
536 }
537 computeEntries[1].buffer.minBindingSize = sizeof(CullInput);
538 computeEntries[2].buffer.minBindingSize = sizeof(VisibleInstance);
539 computeEntries[3].buffer.minBindingSize = sizeof(uint32_t) * 5;
540 computeEntries[4].buffer.minBindingSize = sizeof(VisIndirectCommand);
541 WGPUBindGroupLayoutDescriptor cbgl{};
542 cbgl.label = label("eve_gpu_driven_compute_bgl");
543 computeEntries[5].binding = 5;
544 computeEntries[5].visibility = WGPUShaderStage_Compute;
545 computeEntries[5].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
546 cbgl.entryCount = 6;
547 cbgl.entries = computeEntries;
548 gpuDrivenComputeSetLayout_ =
549 device.CreateBindGroupLayout(reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*>(&cbgl));
550
551 WGPUBindGroupLayoutEntry renderEntry{};
552 renderEntry.binding = 0;
553 renderEntry.visibility = WGPUShaderStage_Vertex;
554 renderEntry.buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
555 renderEntry.buffer.minBindingSize = sizeof(VisibleInstance);
556 WGPUBindGroupLayoutDescriptor rbgl{};
557 rbgl.label = label("eve_gpu_driven_render_bgl");
558 rbgl.entryCount = 1;
559 rbgl.entries = &renderEntry;
560 gpuDrivenRenderSetLayout_ =
561 device.CreateBindGroupLayout(reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*>(&rbgl));
562
563 WGPUBindGroupLayout computeLayout = gpuDrivenComputeSetLayout_.Get();
564 WGPUPipelineLayoutDescriptor cpl{};
565 cpl.label = label("eve_gpu_driven_compute_layout");
566 cpl.bindGroupLayoutCount = 1;
567 cpl.bindGroupLayouts = &computeLayout;
568 gpuDrivenComputePipelineLayout_ =
569 device.CreatePipelineLayout(reinterpret_cast<const wgpu::PipelineLayoutDescriptor*>(&cpl));
570
571 WGPUBindGroupLayout renderLayouts[2] = {mesh3dSetLayout.Get(), gpuDrivenRenderSetLayout_.Get()};
572 WGPUPipelineLayoutDescriptor rpl{};
573 rpl.label = label("eve_gpu_driven_render_layout");
574 rpl.bindGroupLayoutCount = 2;
575 rpl.bindGroupLayouts = renderLayouts;
576 gpuDrivenRenderPipelineLayout_ =
577 device.CreatePipelineLayout(reinterpret_cast<const wgpu::PipelineLayoutDescriptor*>(&rpl));
578
579 wgpu::ShaderModule cullModule = shaderModule(device, kCullWgsl);
580 WGPUComputePipelineDescriptor cpd{};
581 cpd.label = label("eve_gpu_driven_cull");
582 cpd.layout = gpuDrivenComputePipelineLayout_.Get();
583 cpd.compute.module = cullModule.Get();
584 cpd.compute.entryPoint = label("cs_main");
585 gpuDrivenCullPipeline_ =
586 device.CreateComputePipeline(reinterpret_cast<const wgpu::ComputePipelineDescriptor*>(&cpd));
587
588 WGPUBindGroupLayoutEntry hzbEntries[3]{};
589 hzbEntries[0].binding = 0;
590 hzbEntries[0].visibility = WGPUShaderStage_Compute;
591 hzbEntries[0].buffer.type = WGPUBufferBindingType_Uniform;
592 hzbEntries[0].buffer.hasDynamicOffset = true;
593 hzbEntries[0].buffer.minBindingSize = sizeof(HzbBuildParams);
594 hzbEntries[1].binding = 1;
595 hzbEntries[1].visibility = WGPUShaderStage_Compute;
596 hzbEntries[1].texture.sampleType = WGPUTextureSampleType_Depth;
597 hzbEntries[1].texture.viewDimension = WGPUTextureViewDimension_2D;
598 hzbEntries[2].binding = 2;
599 hzbEntries[2].visibility = WGPUShaderStage_Compute;
600 hzbEntries[2].buffer.type = WGPUBufferBindingType_Storage;
601 WGPUBindGroupLayoutDescriptor hzbBgl{};
602 hzbBgl.label = label("eve_gpu_driven_hzb_bgl");
603 hzbBgl.entryCount = 3;
604 hzbBgl.entries = hzbEntries;
605 gpuDrivenHzbSetLayout_ =
606 device.CreateBindGroupLayout(reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*>(&hzbBgl));
607 WGPUBindGroupLayout hzbLayout = gpuDrivenHzbSetLayout_.Get();
608 WGPUPipelineLayoutDescriptor hzbPl{};
609 hzbPl.bindGroupLayoutCount = 1;
610 hzbPl.bindGroupLayouts = &hzbLayout;
611 gpuDrivenHzbPipelineLayout_ =
612 device.CreatePipelineLayout(reinterpret_cast<const wgpu::PipelineLayoutDescriptor*>(&hzbPl));
613 wgpu::ShaderModule hzbModule = shaderModule(device, kHzbBuildWgsl);
614 WGPUComputePipelineDescriptor hzbPd{};
615 hzbPd.label = label("eve_gpu_driven_hzb_build");
616 hzbPd.layout = gpuDrivenHzbPipelineLayout_.Get();
617 hzbPd.compute.module = hzbModule.Get();
618 hzbPd.compute.entryPoint = label("cs_main");
619 gpuDrivenHzbPipeline_ =
620 device.CreateComputePipeline(reinterpret_cast<const wgpu::ComputePipelineDescriptor*>(&hzbPd));
621
622 WGPUVertexAttribute attrs[3]{};
623 attrs[0].format = WGPUVertexFormat_Float32x3;
624 attrs[0].offset = 0;
625 attrs[0].shaderLocation = 0;
626 attrs[1].format = WGPUVertexFormat_Float32x3;
627 attrs[1].offset = 12;
628 attrs[1].shaderLocation = 1;
629 attrs[2].format = WGPUVertexFormat_Float32x2;
630 attrs[2].offset = 24;
631 attrs[2].shaderLocation = 2;
632 WGPUVertexBufferLayout vb{};
633 vb.arrayStride = 32;
634 vb.stepMode = WGPUVertexStepMode_Vertex;
635 vb.attributeCount = 3;
636 vb.attributes = attrs;
637 WGPUDepthStencilState depth{};
638 depth.format = WGPUTextureFormat_Depth32Float;
639 depth.depthWriteEnabled = WGPUOptionalBool_True;
640 depth.depthCompare = WGPUCompareFunction_Less;
641 WGPUColorTargetState target{};
642 target.format = sceneColorFormat;
643 target.writeMask = WGPUColorWriteMask_All;
644 wgpu::ShaderModule vertModule = shaderModule(device, kGpuDrivenVertWgsl);
645 std::string residentVertSource = kGpuDrivenVertWgsl;
646 const std::string modelsDecl =
647 "struct VisibleInstance {\n"
648 " model: mat4x4f,\n"
649 " meshId: u32,\n"
650 " materialId: u32,\n"
651 " flags: u32,\n"
652 " lodGroupId: u32,\n"
653 " reflectionProbeSlots: vec4u,\n"
654 " reflectionProbeCenter: array<vec4f, 2>,\n"
655 " reflectionProbeExtent: array<vec4f, 2>,\n"
656 " color: vec4f,\n"
657 " terrainWave: vec4f,\n"
658 " terrainWaveTint: vec4f,\n"
659 "};\n"
660 "@group(1) @binding(0) var<storage, read> visibleInstances: array<VisibleInstance>;";
661 const std::string residentDecl =
662 "struct ResidentInstance { model: mat4x4f, meshId: u32, materialId: u32, "
663 "flags: u32, lodGroupId: u32, reflectionProbeSlots: vec4u, "
664 "reflectionProbeCenter: array<vec4f, 2>, reflectionProbeExtent: array<vec4f, 2>, color: vec4f, "
665 "terrainWave: vec4f, terrainWaveTint: vec4f };\n"
666 "@group(1) @binding(0) var<storage, read> residentInstances: "
667 "array<ResidentInstance>;";
668 residentVertSource.replace(residentVertSource.find(modelsDecl), modelsDecl.size(), residentDecl);
669 const std::string modelRead =
670 "let sourceModel = visibleInstances[instanceIndex].model;\n"
671 " let instanceColor = visibleInstances[instanceIndex].color;\n"
672 " let terrainWave = visibleInstances[instanceIndex].terrainWave;\n"
673 " let terrainWaveTint = visibleInstances[instanceIndex].terrainWaveTint;";
674 residentVertSource.replace(residentVertSource.find(modelRead), modelRead.size(),
675 "let sourceModel = residentInstances[instanceIndex].model;\n"
676 " let instanceColor = residentInstances[instanceIndex].color;\n"
677 " let terrainWave = residentInstances[instanceIndex].terrainWave;\n"
678 " let terrainWaveTint = residentInstances[instanceIndex].terrainWaveTint;");
679 wgpu::ShaderModule residentVertModule = shaderModule(device, residentVertSource);
680 wgpu::ShaderModule fragModule = shaderModule(device, kMesh3DFragWgsl);
681 WGPUFragmentState fs{};
682 fs.module = fragModule.Get();
683 fs.entryPoint = label("fs_main");
684 fs.targetCount = 1;
685 fs.targets = &target;
686 WGPURenderPipelineDescriptor rpd{};
687 rpd.label = label("eve_gpu_driven_render");
688 rpd.layout = gpuDrivenRenderPipelineLayout_.Get();
689 rpd.vertex.module = vertModule.Get();
690 rpd.vertex.entryPoint = label("vs_main");
691 rpd.vertex.bufferCount = 1;
692 rpd.vertex.buffers = &vb;
693 rpd.fragment = &fs;
694 rpd.primitive.topology = WGPUPrimitiveTopology_TriangleList;
695 rpd.primitive.frontFace = WGPUFrontFace_CW;
696 rpd.primitive.cullMode = WGPUCullMode_None;
697 rpd.depthStencil = &depth;
698 rpd.multisample.count = sceneColorSamples;
699 rpd.multisample.mask = 0xFFFFFFFFu;
700 gpuDrivenRenderPipeline_ =
701 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
702 rpd.label = label("eve_gpu_driven_resident_render");
703 rpd.vertex.module = residentVertModule.Get();
704 gpuDrivenResidentRenderPipeline_ =
705 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
706 rpd.label = label("eve_gpu_driven_canvas");
707 rpd.vertex.module = vertModule.Get();
708 target.format = WGPUTextureFormat_RGBA8Unorm;
709 rpd.multisample.count = 1;
710 gpuDrivenCanvasPipeline_ =
711 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
712 rpd.label = label("eve_gpu_driven_resident_canvas");
713 rpd.vertex.module = residentVertModule.Get();
714 gpuDrivenResidentCanvasPipeline_ =
715 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
716 rpd.label = label("eve_gpu_driven_hdr_canvas");
717 rpd.vertex.module = vertModule.Get();
718 target.format = WGPUTextureFormat_RGBA16Float;
719 gpuDrivenHdrCanvasPipeline_ =
720 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
721 rpd.label = label("eve_gpu_driven_resident_hdr_canvas");
722 rpd.vertex.module = residentVertModule.Get();
723 gpuDrivenResidentHdrCanvasPipeline_ =
724 device.CreateRenderPipeline(reinterpret_cast<const wgpu::RenderPipelineDescriptor*>(&rpd));
725
726 WGPUBufferDescriptor pbd{};
727 pbd.label = label("eve_gpu_driven_params");
728 pbd.size = sizeof(CullParams);
729 pbd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
730 gpuDrivenParamsBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&pbd));
731 pbd.label = label("eve_gpu_driven_hzb_params");
732 pbd.size = kMaxHzbMips * 256u;
733 gpuDrivenHzbParamsBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&pbd));
734 }
735
736 bool recreateGroups = false;
737 const uint32_t hzbWidth = std::max(gbufferWidth, 1);
738 const uint32_t hzbHeight = std::max(gbufferHeight, 1);
739 if (!gpuDrivenHzbBuffer_ || gpuDrivenHzbWidth_ != hzbWidth || gpuDrivenHzbHeight_ != hzbHeight) {
740 gpuDrivenHzbWidth_ = hzbWidth;
741 gpuDrivenHzbHeight_ = hzbHeight;
742 gpuDrivenHzbOffsets_.clear();
743 uint32_t words = kHzbHeaderWords;
744 for (uint32_t w = hzbWidth, h = hzbHeight; gpuDrivenHzbOffsets_.size() < kMaxHzbMips;
745 w = std::max(w >> 1u, 1u), h = std::max(h >> 1u, 1u)) {
746 gpuDrivenHzbOffsets_.push_back(words - kHzbHeaderWords);
747 words += w * h;
748 if (w == 1 && h == 1) break;
749 }
750 WGPUBufferDescriptor hzbBd{};
751 hzbBd.label = label("eve_gpu_driven_hzb");
752 hzbBd.size = uint64_t(words) * sizeof(uint32_t);
753 gpuDrivenHzbCapacity_ = hzbBd.size;
754 hzbBd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Storage;
755 gpuDrivenHzbBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&hzbBd));
756 queue.WriteBuffer(gpuDrivenHzbBuffer_, 0, gpuDrivenHzbOffsets_.data(),
757 gpuDrivenHzbOffsets_.size() * sizeof(uint32_t));
758 recreateGroups = true;
759 }
760
761 const uint64_t inputBytes = uint64_t(instanceCount) * sizeof(CullInput);
762 if (!gpuDrivenInputBuffer_ || gpuDrivenInputCapacity_ < inputBytes) {
763 gpuDrivenInputCapacity_ = grownCapacity(gpuDrivenInputCapacity_, inputBytes);
764 WGPUBufferDescriptor bd{};
765 bd.label = label("eve_gpu_driven_inputs");
766 bd.size = gpuDrivenInputCapacity_;
767 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Storage;
768 gpuDrivenInputBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
769 recreateGroups = true;
770 }
771 const uint64_t visibleBytes = uint64_t(instanceCount) * sizeof(VisibleInstance);
772 if (!gpuDrivenVisibleBuffer_ || gpuDrivenVisibleCapacity_ < visibleBytes) {
773 gpuDrivenVisibleCapacity_ = grownCapacity(gpuDrivenVisibleCapacity_, visibleBytes);
774 WGPUBufferDescriptor bd{};
775 bd.label = label("eve_gpu_driven_visible");
776 bd.size = gpuDrivenVisibleCapacity_;
777 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Storage;
778 gpuDrivenVisibleBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
779 recreateGroups = true;
780 }
781 const uint64_t indirectBytes = uint64_t(bucketCount) * sizeof(GpuIndirectCommand);
782 if (!gpuDrivenIndirectBuffer_ || gpuDrivenIndirectCapacity_ < indirectBytes) {
783 gpuDrivenIndirectCapacity_ = grownCapacity(gpuDrivenIndirectCapacity_, indirectBytes);
784 WGPUBufferDescriptor bd{};
785 bd.label = label("eve_gpu_driven_indirect");
786 bd.size = gpuDrivenIndirectCapacity_;
787 bd.usage =
788 WGPUBufferUsage_CopyDst | WGPUBufferUsage_CopySrc | WGPUBufferUsage_Storage | WGPUBufferUsage_Indirect;
789 gpuDrivenIndirectBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
790 recreateGroups = true;
791 }
792 const uint64_t visIndirectBytes = uint64_t(bucketCount) * sizeof(VisIndirectCommand);
793 if (!gpuDrivenVisIndirectBuffer_ || gpuDrivenVisIndirectCapacity_ < visIndirectBytes) {
794 gpuDrivenVisIndirectCapacity_ = grownCapacity(gpuDrivenVisIndirectCapacity_, visIndirectBytes);
795 WGPUBufferDescriptor bd{};
796 bd.label = label("eve_gpu_driven_vis_indirect");
797 bd.size = gpuDrivenVisIndirectCapacity_;
798 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Storage | WGPUBufferUsage_Indirect;
799 gpuDrivenVisIndirectBuffer_ = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
800 recreateGroups = true;
801 }
802 // The previous-frame depth view rotates with the frame slot, so refresh
803 // the compute group even when the storage-buffer capacities are unchanged.
804 recreateGroups = true;
805 if (recreateGroups || !gpuDrivenComputeBindGroup_) {
806 WGPUBindGroupEntry entries[6]{};
807 entries[0].binding = 0;
808 entries[0].buffer = gpuDrivenParamsBuffer_.Get();
809 entries[0].size = sizeof(CullParams);
810 entries[1].binding = 1;
811 entries[1].buffer = gpuDrivenInputBuffer_.Get();
812 entries[1].size = gpuDrivenInputCapacity_;
813 entries[2].binding = 2;
814 entries[2].buffer = gpuDrivenVisibleBuffer_.Get();
815 entries[2].size = gpuDrivenVisibleCapacity_;
816 entries[3].binding = 3;
817 entries[3].buffer = gpuDrivenIndirectBuffer_.Get();
818 entries[3].size = gpuDrivenIndirectCapacity_;
819 entries[4].binding = 4;
820 entries[4].buffer = gpuDrivenVisIndirectBuffer_.Get();
821 entries[4].size = gpuDrivenVisIndirectCapacity_;
822 entries[5].binding = 5;
823 const uint32_t depthSlot = gbufferDepthValid_ ? lastGbufferSlot : currentFrameSlot();
824 entries[5].buffer = gpuDrivenHzbBuffer_.Get();
825 entries[5].size = gpuDrivenHzbCapacity_;
826 WGPUBindGroupDescriptor bgd{};
827 bgd.label = label("eve_gpu_driven_compute_bg");
828 bgd.layout = gpuDrivenComputeSetLayout_.Get();
829 bgd.entryCount = 6;
830 bgd.entries = entries;
831 gpuDrivenComputeBindGroup_ = device.CreateBindGroup(reinterpret_cast<const wgpu::BindGroupDescriptor*>(&bgd));
832
833 WGPUBindGroupEntry renderEntry{};
834 renderEntry.binding = 0;
835 renderEntry.buffer = gpuDrivenVisibleBuffer_.Get();
836 renderEntry.size = gpuDrivenVisibleCapacity_;
837 WGPUBindGroupDescriptor rbgd{};
838 rbgd.label = label("eve_gpu_driven_render_bg");
839 rbgd.layout = gpuDrivenRenderSetLayout_.Get();
840 rbgd.entryCount = 1;
841 rbgd.entries = &renderEntry;
842 gpuDrivenRenderBindGroup_ = device.CreateBindGroup(reinterpret_cast<const wgpu::BindGroupDescriptor*>(&rbgd));
843
844 WGPUBindGroupEntry hzbEntries[3]{};
845 hzbEntries[0].binding = 0;
846 hzbEntries[0].buffer = gpuDrivenHzbParamsBuffer_.Get();
847 hzbEntries[0].size = sizeof(HzbBuildParams);
848 hzbEntries[1].binding = 1;
849 hzbEntries[1].textureView = gbufferSlots[depthSlot].depthView.Get();
850 hzbEntries[2].binding = 2;
851 hzbEntries[2].buffer = gpuDrivenHzbBuffer_.Get();
852 hzbEntries[2].size = gpuDrivenHzbCapacity_;
853 WGPUBindGroupDescriptor hzbBg{};
854 hzbBg.label = label("eve_gpu_driven_hzb_bg");
855 hzbBg.layout = gpuDrivenHzbSetLayout_.Get();
856 hzbBg.entryCount = 3;
857 hzbBg.entries = hzbEntries;
858 gpuDrivenHzbBindGroup_ = device.CreateBindGroup(reinterpret_cast<const wgpu::BindGroupDescriptor*>(&hzbBg));
859 }
860}
861
863 if (!gpuDrivenEnabled_ || !instances || instanceCount == 0) return false;
864 for (uint32_t i = 0; i < instanceCount; ++i) {
865 if (instances[i].meshId >= gpuDrivenMeshes_.size()) return false;
866 auto* gpu = static_cast<GpuMesh*>(gpuDrivenMeshes_[instances[i].meshId]->gpuHandle);
867 if (!gpu || !gpu->indexBuffer) return false;
868 }
869 gpuDrivenPending_.assign(instances, instances + instanceCount);
870 gpuDrivenVisible_.clear();
871 gpuDrivenBuckets_.clear();
872 gpuDrivenComputePending_ = false;
873 gpuDrivenDrawPending_ = false;
874 return true;
875}
876
877void Graphics::gpuDrivenCullEmit(const glm::mat4& viewProj, const glm::vec3& eye, float fovYDeg, float nearZ,
878 float farZ) {
879 if (sceneColorWidth > 0 && sceneColorHeight > 0) createSceneColorResources(sceneColorWidth, sceneColorHeight);
880 if (gbufferSlots.empty() && sceneColorWidth > 0 && sceneColorHeight > 0)
881 createGbufferResources(sceneColorWidth, sceneColorHeight);
882 CullParams params{};
883 params.viewProj = viewProj;
884 params.planes[0] = glm::row(viewProj, 3) + glm::row(viewProj, 0);
885 params.planes[1] = glm::row(viewProj, 3) - glm::row(viewProj, 0);
886 params.planes[2] = glm::row(viewProj, 3) + glm::row(viewProj, 1);
887 params.planes[3] = glm::row(viewProj, 3) - glm::row(viewProj, 1);
888 params.planes[4] = glm::row(viewProj, 2);
889 params.planes[5] = glm::row(viewProj, 3) - glm::row(viewProj, 2);
890 for (glm::vec4& plane : params.planes) {
891 const float length = glm::length(glm::vec3(plane));
892 if (length > 1e-6f) plane /= length;
893 }
894 params.cameraPos = glm::vec4(eye, 1.f);
895 params.screen =
896 glm::vec4(float(gbufferWidth), float(gbufferHeight), gbufferWidth > 0 ? 1.f / float(gbufferWidth) : 0.f,
897 gbufferHeight > 0 ? 1.f / float(gbufferHeight) : 0.f);
898 const float projScaleY = float(gbufferHeight) / (2.f * std::tan(glm::radians(fovYDeg) * 0.5f));
899 params.clipNearFar = glm::vec4(nearZ, farZ, projScaleY, gbufferDepthValid_ ? 1.f : 0.f);
900 params.hzbInfo =
901 glm::vec4(float(gpuDrivenHzbOffsets_.size() - 1u), 0.f, float(gpuDrivenHzbWidth_), float(gpuDrivenHzbHeight_));
902 uint32_t mipWidth = gpuDrivenHzbWidth_;
903 uint32_t mipHeight = gpuDrivenHzbHeight_;
904 uint32_t previousWidth = mipWidth;
905 uint32_t previousHeight = mipHeight;
906 for (uint32_t mip = 0; mip < gpuDrivenHzbOffsets_.size(); ++mip) {
907 HzbBuildParams build{};
908 build.info = glm::uvec4(mip, mipWidth, mipHeight, mip == 0 ? 0u : gpuDrivenHzbOffsets_[mip - 1]);
909 build.source = glm::uvec4(previousWidth, previousHeight, 0u, 0u);
910 queue.WriteBuffer(gpuDrivenHzbParamsBuffer_, uint64_t(mip) * 256u, &build, sizeof(build));
911 previousWidth = mipWidth;
912 previousHeight = mipHeight;
913 mipWidth = std::max(mipWidth >> 1u, 1u);
914 mipHeight = std::max(mipHeight >> 1u, 1u);
915 }
916
917 using BucketKey = std::pair<uint32_t, uint32_t>;
918 std::map<BucketKey, std::vector<const GpuInstance*>> grouped;
919 for (const GpuInstance& instance : gpuDrivenPending_)
920 grouped[{instance.meshId, instance.materialId}].push_back(&instance);
921
922 std::vector<CullInput> inputs;
923 std::vector<GpuIndirectCommand> commands;
924 std::vector<VisIndirectCommand> visCommands;
925 inputs.reserve(gpuDrivenPending_.size());
926 commands.reserve(grouped.size());
927 visCommands.reserve(grouped.size());
928 uint32_t outputBase = 0;
929 for (const auto& [key, bucketInstances] : grouped) {
930 Mesh* mesh = gpuDrivenMeshes_[key.first];
931 Material* material = gpuDrivenMaterials_[key.second];
932 auto* gpu = static_cast<GpuMesh*>(mesh->gpuHandle);
933 const uint32_t bucketIndex = static_cast<uint32_t>(gpuDrivenBuckets_.size());
934 gpuDrivenBuckets_.push_back({mesh, material, outputBase, static_cast<uint32_t>(bucketInstances.size())});
935 GpuIndirectCommand command{};
936 command.indexCount = gpu->indexCount;
937 // Keep firstInstance zero: it is optional in WebGPU and requires the
938 // IndirectFirstInstance feature on adapters that expose it. The
939 // bucket's compacted model range is selected through the storage
940 // binding offset when drawing.
941 command.firstInstance = 0;
942 commands.push_back(command);
943 VisIndirectCommand visCommand{};
944 visCommand.vertexCount = gpu->indexCount;
945 visCommand.firstInstance = 0;
946 visCommands.push_back(visCommand);
947 for (const GpuInstance* instance : bucketInstances) {
948 CullInput input{};
949 input.model = instance->model;
950 input.color = instance->color;
951 input.terrainWave = instance->terrainWave;
952 input.terrainWaveTint = instance->terrainWaveTint;
953 input.bounds = mesh->hasBounds()
954 ? glm::vec4(mesh->boundsCx, mesh->boundsCy, mesh->boundsCz, mesh->boundsRadius)
955 : glm::vec4(0.f, 0.f, 0.f, 1e20f);
956 input.bucket = bucketIndex;
957 input.outputBase = outputBase;
958 inputs.push_back(input);
959
960 const glm::vec3 center = glm::vec3(input.model * glm::vec4(glm::vec3(input.bounds), 1.f));
961 const float scale =
962 std::max({glm::length(glm::vec3(input.model[0])), glm::length(glm::vec3(input.model[1])),
963 glm::length(glm::vec3(input.model[2]))});
964 bool visible = true;
965 for (const glm::vec4& plane : params.planes) {
966 if (glm::dot(glm::vec3(plane), center) + plane.w < -input.bounds.w * scale) {
967 visible = false;
968 break;
969 }
970 }
971 if (visible) gpuDrivenVisible_.push_back(*instance);
972 }
973 // Storage-buffer binding offsets are aligned to WebGPU's common
974 // 256-byte limit. Sixteen 208-byte records advance by 3328 bytes,
975 // keeping every bucket offset aligned.
976 outputBase += (static_cast<uint32_t>(bucketInstances.size()) + 15u) & ~uint32_t(15u);
977 }
978
979 ensureGpuDrivenResources(outputBase, static_cast<uint32_t>(commands.size()));
980 params.counts.x = static_cast<uint32_t>(inputs.size());
981 queue.WriteBuffer(gpuDrivenParamsBuffer_, 0, &params, sizeof(params));
982 queue.WriteBuffer(gpuDrivenInputBuffer_, 0, inputs.data(), inputs.size() * sizeof(CullInput));
983 queue.WriteBuffer(gpuDrivenIndirectBuffer_, 0, commands.data(), commands.size() * sizeof(GpuIndirectCommand));
984 queue.WriteBuffer(gpuDrivenVisIndirectBuffer_, 0, visCommands.data(),
985 visCommands.size() * sizeof(VisIndirectCommand));
986 gpuDrivenDispatchCount_ = static_cast<uint32_t>(inputs.size());
987 gpuDrivenLastBucketCount_ = static_cast<uint32_t>(commands.size());
988 gpuDrivenComputePending_ = !inputs.empty() && !commands.empty();
989}
990
992#if defined(__EMSCRIPTEN__)
993 return static_cast<uint32_t>(gpuDrivenVisible_.size());
994#else
995 if (!gpuDrivenIndirectBuffer_ || gpuDrivenLastBucketCount_ == 0) return 0;
996 const uint64_t size = uint64_t(gpuDrivenLastBucketCount_) * sizeof(GpuIndirectCommand);
997 WGPUBufferDescriptor bd{};
998 bd.label = label("eve_gpu_driven_debug_readback");
999 bd.size = size;
1000 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
1001 wgpu::Buffer dst = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
1002 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
1003 encoder.CopyBufferToBuffer(gpuDrivenIndirectBuffer_, 0, dst, 0, size);
1004 wgpu::CommandBuffer command = encoder.Finish();
1005 queue.Submit(1, &command);
1006
1007 struct MapState {
1008 bool ok = false;
1009 } state;
1010 WGPUBufferMapCallbackInfo callback{};
1011 callback.mode = WGPUCallbackMode_WaitAnyOnly;
1012 callback.callback = [](WGPUMapAsyncStatus status, WGPUStringView, void* userdata1, void*) {
1013 static_cast<MapState*>(userdata1)->ok = status == WGPUMapAsyncStatus_Success;
1014 };
1015 callback.userdata1 = &state;
1016 WGPUFuture future = wgpuBufferMapAsync(dst.Get(), WGPUMapMode_Read, 0, size, callback);
1017 WGPUFutureWaitInfo wait{};
1018 wait.future = future;
1019 (void)wgpuInstanceWaitAny(instance.Get(), 1, &wait, UINT64_MAX);
1020 if (!state.ok) return 0;
1021 const auto* commands = static_cast<const GpuIndirectCommand*>(dst.GetConstMappedRange(0, size));
1022 uint32_t visible = 0;
1023 if (commands) {
1024 for (uint32_t i = 0; i < gpuDrivenLastBucketCount_; ++i) visible += commands[i].instanceCount;
1025 }
1026 dst.Unmap();
1027 return visible;
1028#endif
1029}
1030
1032 if (!gpuDrivenComputePending_ || gpuDrivenBuckets_.empty()) return;
1033 frameHad3DThisFrame = true;
1034 frameHad3D = true;
1035 gpuDrivenDrawPending_ = true;
1036 gpuDrivenPending_.clear();
1037}
1038
1039void Graphics::recordGpuDrivenCompute(wgpu::CommandEncoder encoder) {
1040 if ((gpuDrivenComputePending_ || gpuDrivenVgComputePending_) && gbufferDepthValid_ && gpuDrivenHzbPipeline_ &&
1041 gpuDrivenHzbBindGroup_) {
1042 uint32_t width = gpuDrivenHzbWidth_;
1043 uint32_t height = gpuDrivenHzbHeight_;
1044 for (uint32_t mip = 0; mip < gpuDrivenHzbOffsets_.size(); ++mip) {
1045 wgpu::ComputePassEncoder hzbPass = encoder.BeginComputePass();
1046 hzbPass.SetPipeline(gpuDrivenHzbPipeline_);
1047 const uint32_t dynamicOffset = mip * 256u;
1048 hzbPass.SetBindGroup(0, gpuDrivenHzbBindGroup_, 1, &dynamicOffset);
1049 hzbPass.DispatchWorkgroups((width + 7u) / 8u, (height + 7u) / 8u, 1);
1050 hzbPass.End();
1051 width = std::max(width >> 1u, 1u);
1052 height = std::max(height >> 1u, 1u);
1053 }
1054 }
1055 if (gpuDrivenComputePending_ && gpuDrivenCullPipeline_ && gpuDrivenComputeBindGroup_) {
1056 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
1057 pass.SetPipeline(gpuDrivenCullPipeline_);
1058 pass.SetBindGroup(0, gpuDrivenComputeBindGroup_, 0, nullptr);
1059 pass.DispatchWorkgroups((gpuDrivenDispatchCount_ + 63u) / 64u, 1, 1);
1060 pass.End();
1061 gpuDrivenComputePending_ = false;
1062 }
1063 recordGpuDrivenVgCompute(encoder);
1064}
1065
1066void Graphics::flushGpuDrivenDraws(wgpu::RenderPassEncoder pass, bool canvasTarget, bool hdrCanvas) {
1067 if (!gpuDrivenDrawPending_ ||
1068 (gpuDrivenResidentDrawPending_ ? !gpuDrivenResidentBuffer_ : !gpuDrivenVisibleBuffer_))
1069 return;
1070 auto& arena = currentUboArena();
1071 ensureUboArena(arena, arena.used + gpuDrivenBuckets_.size() * 2048);
1072 const wgpu::RenderPipeline& pipeline =
1073 gpuDrivenResidentDrawPending_
1074 ? (canvasTarget ? (hdrCanvas ? gpuDrivenResidentHdrCanvasPipeline_
1075 : gpuDrivenResidentCanvasPipeline_)
1076 : gpuDrivenResidentRenderPipeline_)
1077 : (canvasTarget ? (hdrCanvas ? gpuDrivenHdrCanvasPipeline_ : gpuDrivenCanvasPipeline_)
1078 : gpuDrivenRenderPipeline_);
1079 if (!pipeline) return;
1080 pass.SetPipeline(pipeline);
1081 gpuDrivenLastIndirectDrawCount_ = 0;
1082
1083 for (uint32_t i = 0; i < gpuDrivenBuckets_.size(); ++i) {
1084 const GpuDrivenBucket& bucket = gpuDrivenBuckets_[i];
1085 auto* gpu = static_cast<GpuMesh*>(bucket.mesh->gpuHandle);
1086 Material* material = bucket.material;
1087 if (!gpu || !gpu->vertexBuffer || !gpu->indexBuffer || !material) continue;
1088
1089 Mesh3DUBO ubo{};
1090 ubo.mvp = mesh3dViewProj;
1091 ubo.model = glm::mat4(1.f);
1092 ubo.lightDir = glm::vec4(glm::vec3(mesh3dLighting.lights[0].posRadius), float(mesh3dLighting.count));
1093 ubo.lightColor = mesh3dLighting.lights[0].color;
1094 ubo.lightColor.w = mesh3dEnvIntensity;
1095 ubo.tint = glm::vec4(material->getTintR(), material->getTintG(), material->getTintB(), material->getTintA());
1096 ubo.cameraPos = glm::vec4(mesh3dCameraPos, material->getRoughness());
1097 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.ambient), material->getMetallic());
1098 for (int light = 0; light < Lighting3DPack::kMaxLights; ++light)
1099 ubo.lights[light] = mesh3dLighting.lights[light];
1100 ubo.texBomb = glm::vec4(material->getTexCellBombScale(), material->getTexCellBombStrength(),
1101 material->getTexCellBombRotation(), 0.f);
1102 ubo.parallax = glm::vec4(material->getParallaxScale(), material->getParallaxMinLayers(),
1103 material->getParallaxMaxLayers(), 0.f);
1104 const float ao = renderControl_ && renderControl_->isEnabled("ao") ? mesh3dSsaoIntensity : 0.f;
1105 const float surfaceCode = material->surfaceMode() == SurfaceMode::Masked ? 1.f : 0.f;
1106 ubo.surface = glm::vec4(surfaceCode, material->getAlphaCutoff(), ao,
1107 material->getCameraFacing() ? 1.f : 0.f);
1108 ubo.view = mesh3dView;
1109 ubo.clipInfo = glm::vec4(mesh3dNear, mesh3dFar, 0.f, 0.f);
1110 ubo.cloud = mesh3dCloud;
1111 ubo.cloudWind = mesh3dCloudWind;
1112 ubo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
1113 ubo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
1114 for (int probeIndex = 0; probeIndex < ReflectionProbeUpload::kMaxProbes; ++probeIndex) {
1115 if (probeIndex >= mesh3dReflectionProbes.count) continue;
1116 const auto& probe = mesh3dReflectionProbes.probes[probeIndex];
1117 GpuTexture* gpuProbe = gpuForTexture(probe.cubemap);
1118 if (!gpuProbe || !gpuProbe->isCube) continue;
1119 ubo.reflectionProbeCenter[probeIndex] = glm::vec4(probe.center, probe.intensity);
1120 ubo.reflectionProbeExtent[probeIndex] = glm::vec4(probe.extent, probe.blendDistance);
1121 }
1122
1123 const uint32_t frameOffset = arena.alloc(sizeof(Mesh3DUBO), 256);
1124 const uint32_t shadowOffset = arena.alloc(sizeof(ShadowUBO), 256);
1125 queue.WriteBuffer(arena.buffer, frameOffset, &ubo, sizeof(ubo));
1126 ShadowUBO shadow = mesh3dShadows.ubo;
1127 if (!mesh3dShadows.active || !material->getReceiveShadow()) shadow.bias.y = 0.f;
1128 queue.WriteBuffer(arena.buffer, shadowOffset, &shadow, sizeof(shadow));
1129
1130 GpuTexture* depth = mesh3dSceneDepthTexture ? gpuForTexture(mesh3dSceneDepthTexture) : flatDepthTexture3D;
1131 wgpu::BindGroup bindGroup =
1132 makeMeshBindGroup(gpuForTexture(material->getAlbedoTexture()), gpuForTexture(material->getNormalTexture()),
1133 gpuForTexture(mesh3dEnvTexture), gpuForTexture(material->getHeightTexture()), depth,
1134 nullptr, frameOffset, shadowOffset, 0, uploadSkinPalette(nullptr));
1135 const uint32_t offsets[3] = {frameOffset, shadowOffset, 0};
1136 pass.SetBindGroup(0, bindGroup, 3, offsets);
1137 WGPUBindGroupEntry modelEntry{};
1138 modelEntry.binding = 0;
1139 modelEntry.buffer = gpuDrivenResidentDrawPending_ ? gpuDrivenResidentBuffer_ : gpuDrivenVisibleBuffer_.Get();
1140 modelEntry.offset =
1141 gpuDrivenResidentDrawPending_ ? gpuDrivenResidentOffset_ :
1142 uint64_t(bucket.outputBase) * sizeof(VisibleInstance);
1143 modelEntry.size =
1144 gpuDrivenResidentDrawPending_ ? gpuDrivenResidentSize_ :
1145 uint64_t(bucket.inputCount) * sizeof(VisibleInstance);
1146 WGPUBindGroupDescriptor modelDesc{};
1147 modelDesc.layout = gpuDrivenRenderSetLayout_.Get();
1148 modelDesc.entryCount = 1;
1149 modelDesc.entries = &modelEntry;
1150 wgpu::BindGroup modelGroup =
1151 device.CreateBindGroup(reinterpret_cast<const wgpu::BindGroupDescriptor*>(&modelDesc));
1152 pass.SetBindGroup(1, modelGroup, 0, nullptr);
1153 pass.SetVertexBuffer(0, gpu->vertexBuffer, 0, gpu->vertexCount * 32ull);
1154 const uint64_t indexBytes = gpu->indexFormat == wgpu::IndexFormat::Uint16 ? 2u : 4u;
1155 pass.SetIndexBuffer(gpu->indexBuffer, gpu->indexFormat, 0, uint64_t(gpu->indexCount) * indexBytes);
1156 pass.DrawIndexedIndirect(gpuDrivenIndirectBuffer_, uint64_t(i) * sizeof(GpuIndirectCommand));
1157 ++gpuDrivenLastIndirectDrawCount_;
1158 }
1159 gpuDrivenDrawPending_ = false;
1160 gpuDrivenResidentDrawPending_ = false;
1161 gpuDrivenResidentBuffer_ = nullptr;
1162 gpuDrivenResidentOffset_ = 0;
1163 gpuDrivenResidentSize_ = 0;
1164 gpuDrivenBuckets_.clear();
1165}
1166
1167} // namespace eve::graphics::webgpu
LogicalId target
double value
float w
Definition AnimClip.cpp:738
const std::string & s
std::unordered_map< std::string, QuestRuntime > entries
std::vector< BuildingInstanceSnapshot > instances
float length
Definition CaveMesh.cpp:94
float planes[6][4]
std::string label
scene::NodeDesc desc
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t vertexCount
vkb::Device & device
std::uint32_t firstVertex
std::uint32_t firstInstance
std::uint32_t paddedCount
std::uint32_t instanceCount
std::uint32_t key
wgpu::PopErrorScopeStatus status
float u
Definition Grass.cpp:233
int inputs
Definition GridGraph.cpp:23
int h
std::array< float, 3 > scale
bool required
std::string id
Definition PlayHost.cpp:108
glm::vec3 eye
Mesh * mesh
glm::mat4 viewProj
glm::mat4 model
Material * material
bool found
double current
Light3D::Data * light
std::uint32_t count
bool visible
float size
Definition TreeMesh.cpp:156
const UnitySourceAsset & source
std::uint32_t depth
std::vector< VegetationPresetCommand > commands
ViewPreparation callback
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
std::unique_ptr< RenderControl > renderControl_
Definition Graphics.h:2264
Packages shading method + surface parameters into one attachable asset.
Definition Material.h:35
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
void gpuDrivenCullEmit(const glm::mat4 &viewProj, const glm::vec3 &eye, float fovYDeg, float nearZ, float farZ) override
Gpu driven cull emit.
void gpuDrivenDrawOpaque() override
Gpu driven draw opaque.
uint32_t gpuDrivenMaterialRecord(Material *material) override
Gpu driven material record.
bool gpuDrivenCullBegin(const GpuInstance *instances, uint32_t instanceCount) override
Gpu driven cull begin.
bool gpuDrivenMaterialUsable(Material *material) override
Gpu driven material usable.
uint32_t gpuDrivenMeshRecord(Mesh *mesh) override
Gpu driven mesh record.
Result< void > gpuDrivenReleaseMaterialRecord(Material *material) override
Gpu driven release material record.
GpuResidentSubmitStatus gpuDrivenSubmitResident(const GpuResidentInstanceBatch &batch) override
Gpu driven submit resident.
bool gpuDrivenSubmitOpaque(const GpuInstance *instances, uint32_t instanceCount) override
Gpu driven submit opaque.
uint32_t debugGpuDrivenGpuVisibleCount()
Read back the last GPU-written indirect instance total (native tests).
std::vector< ParamSpec > params
eve::graphics::GpuIndirectCommand GpuIndirectCommand
Definition GpuDriven.h:18
constexpr uint32_t kMaxHzbMips
Definition GpuDriven.h:25
eve::graphics::GpuInstance GpuInstance
Definition GpuDriven.h:17
const char * kMesh3DFragWgsl
constexpr uint32_t kInvalidGpuDrivenSlot
GPU-driven rendering shared constants + std430 GPU layouts.
GpuResidentSubmitStatus
Structured result for direct resident-instance submission.
std::vector< int64_t > attrs(const Node &n, const char *key, std::vector< int64_t > fallback)
Attrs.
constexpr uint64_t kGpuResidentStorageOffsetAlignment
Portable alignment required for resident storage-buffer slice offsets.
Indirect draw command; layout identical to VkDrawIndexedIndirectCommand.
Per-instance GPU record (std430). Mirrors GLSL GpuInstance.
Direct-render description for a GPU-authored array of GpuInstance records. @ownership buckets and buf...
const GpuResidentInstanceBucket * buckets
One contiguous mesh/material bucket in a sorted resident instance buffer.
static constexpr int kMaxLights
Definition Light.h:173
Light3DGpu lights[kMaxLights]
Definition Light.h:175
Vertex/index buffers for one mesh.
Definition Graphics.h:153
std::future< vkb::Instance > future
Definition Graphics.cpp:182
glm::vec4 cameraPos
uint32_t outputBase
uint32_t bucket
glm::vec4 hzbInfo
glm::vec4 terrainWaveTint
glm::uvec4 counts
glm::vec4 screen
glm::vec4 bounds
glm::uvec4 info
glm::vec4 clipNearFar
uint32_t pad[2]
glm::vec4 color
glm::vec4 terrainWave