Commit Graph
4 Commits
Author SHA1 Message Date
西风逍遥游 d82d246465 gpu-driven stage 2: GPU frustum/HZB cull + GPU-written indirect draws
Add the stage-2 compute chain to the main frame:
- ComputePass wrapper + compile script for compute SPIR-V (hzb_build /
  gpu_cull / gpu_emit); bindless set extended with cull UBO, visible flags,
  compacted instances, indirect commands, HZB buffer and GBuffer depth.
- HZB: R32F max-downsampled mip chain per frame slot built from the
  GBuffer depth after the GBuffer pass (buffer-based; offsets header at slot
  start). Same-frame self-occlusion handled by testing the bounding sphere's
  FRONT depth, and near-zero depth (uninitialized/corner garbage) never
  occludes.
- Cull: per-instance Gribb-Hartmann frustum test + HZB occlusion test, flags
  written per instance; emit compacts visible instances per CPU-provided
  bucket layout and fills per-bucket indirect commands with atomic counters.
- Frame wiring: begin3DFrame defers opening the scene color pass when the
  cull chain is live, so the compute section records before the opaque
  draws; RenderSystem3D uploads sorted instances, runs cull+emit, opens the
  pass and draws with GPU-written commands (per-bucket, empty buckets no-op).
  Fallback to stage 1 when compute caps are missing. gpuDriven now forces
  the GBuffer pass (HZB source).
- Known limitation: HZB is built from the same-frame GBuffer, so objects
  already in the GBuffer cannot be occlusion-culled until the GBuffer itself
  is culled (stage 3 vis buffer); the chain is verified with frustum culling
  (2 of 5 instances culled) and pixel parity.

Tests: GpuDriven.opaqueForwardCullParity (5 spheres, 2 outside frustum ->
3 visible / 3 non-empty buckets, pixel parity < 0.03); all 6 GpuDriven
tests pass (integrated GPU; the AMD driver intermittently crashes at
vkCreateImage during init - environmental).
2026-08-21 15:09:39 +08:00
西风逍遥游 454c4b5504 gpu-driven: fix indirect instance indexing and bind per-mesh buffers
Stage 1 (CPU indirect draw) produced no geometry for the second draw:
- mesh3d_gpudriven.vert added pc.firstInstance on top of gl_InstanceIndex,
  which already includes VkDrawIndexedIndirectCommand.firstInstance per the
  Vulkan spec; the second command read instance 2 instead of 1 and sampled
  garbage matrices. Drop the push-constant indirection entirely.
- gpuDrivenSubmitOpaque never bound vertex/index buffers; each GpuMesh owns
  its own buffer pair (vertexBase/firstIndex are 0), so draws ran against
  whatever the legacy path left bound. Track meshRecordOwners_ parallel to
  the GPU mesh table and bind per-mesh at bucket boundaries.

Test: GpuDriven.opaqueForwardParityMultiTexture now uses a distinct bindless
slot-1 albedo on the second sphere; all 5 GpuDriven tests + IndirectBuilder
tests pass (integrated GPU). Also add the shared Windows crash-backtrace
handler (CrashHandler.h) to unit_test and document the repo debugging
playbook (incl. stale-object/layout pitfalls with unscanned Ninja deps) in
AGENTS.md.
2026-08-21 13:35:57 +08:00
西风逍遥游 d72b292d47 fix(graphics): GPU-driven shading parity + AMD driver workarounds
End-to-end parity testing (gpuDriven on/off pixel comparison) exposed and
fixed several shading-path bugs, and documented two hard AMD driver
limitations on this machine (Radeon AI PRO R9700):

1. Missing ambient in the GPU-driven frame UBO: the per-frame UBO only set
   ambient.w; ambient.rgb came from stale state. Now mirrors the legacy
   draw path (mesh3dLighting.ambient).
2. Normal-map fallback: materials without a normal texture clamped to the
   bindless slot 0, which is the WHITE placeholder - the legacy path uses a
   (128,128,255) flat-normal texture. The fragment shader now skips
   applyNormalMap when the material has no normal texture.
3. AMD driver limitation A: vkUpdateDescriptorSets on the sampler array
   binding hangs for writes of >= 4 elements; placeholder fill uses
   single-element writes (~1.1k calls at init, ~3 ms).
4. AMD driver limitation B: descriptor-array dynamic indexing only returns
   correct results for element 0. Scenes with more than one distinct
   texture therefore fall back to the legacy path via the new
   gpuDrivenMaterialUsable() gate (RenderSystem3D eligibility check). The
   proper fix (texture-array/atlas with layer indexing) is Phase 3 work.

Adds GpuDriven.opaqueForwardParity: renders the same single-texture scene
with the legacy and GPU-driven paths and asserts per-pixel luma agreement
(max delta < 0.03). Verified passing in a clean process; the AMD driver
wedges intermittently under repeated test runs on this machine, which is
tracked separately.

The gpuDriven feature remains opt-in (RenderControl, default OFF), so
default rendering is unchanged.
2026-08-21 11:21:02 +08:00
西风逍遥游 674643c2da feat(graphics): GPU-driven phase 0 - bindless set, GPU tables, per-frame arena
- GpuDrivenCaps: probe + enable Vulkan 1.2 core features (drawIndirectCount,
  descriptor indexing, non-uniform indexing, update-after-bind) at device
  creation; the toolchain's Vulkan headers omit the core drawIndirect bit so
  it is assumed supported and left untouched.
- Bindless Set1: sampler2D array (4096) + samplerCube array (64) + SSBO table
  array (64) with PARTIALLY_BOUND | UPDATE_AFTER_BIND; textures register on
  creation, placeholder-filled with the 1x1 white texture/cubemap.
- GPU tables: GpuMeshRecord (bounds + index/vertex ranges) filled at mesh
  upload and GpuMaterialRecord built from Material params, uploaded to
  host-visible SSBOs with fixed capacity.
- FrameArena: per-frame-slot host-visible ring allocator, reset each
  begin3DFrame; unused until phase 1 (indirect draw).
- Tests: GpuDriven.capsAvailable / bindlessTextureRegistration /
  meshTableRegistration (GPU runtime verification pending driver recovery).

Rendering is unchanged: nothing consumes the new infrastructure yet.
2026-08-20 21:17:17 +08:00