diff --git a/.claude/skills/add-render-pass/SKILL.md b/.claude/skills/add-render-pass/SKILL.md new file mode 100644 index 00000000..677e1404 --- /dev/null +++ b/.claude/skills/add-render-pass/SKILL.md @@ -0,0 +1,180 @@ +--- +name: add-render-pass +description: Add a new render or compute pass to the FrameGraph end to end — the .sig PassNode and PSO declaration, code generation, the setup/render implementation, the HLSL shader, and pipeline registration. Use this skill whenever adding a new rendering effect, post-process, compute dispatch, shadow, or GBuffer stage, whenever a new PassNode or ComputePSO/GraphicsPSO is needed, or when an existing pass needs new resource reads/writes wired through the FrameGraph. Also use it when a newly added pass never executes, since that is usually a pipeline registration or setup-return problem rather than a bug in the render body. +--- + +# Adding a FrameGraph pass + +A pass is declared in a `.sig` file and implemented in C++, with the generator +producing the glue between them. Getting the declaration right matters more +than the render body — the declaration is what the FrameGraph uses to schedule +the pass and to compute its barriers, so a wrong read/write flag produces +validation errors or corrupt results that look like shader bugs. + +Read `sources/RenderSystem/Effects/Sky.cpp` alongside `sources/SIGParser/sigs/sky.sig` +before starting. Between them they show both wiring styles, a graphics PSO and +several compute PSOs, resource creation, and per-mip view handling — it is the +best single reference in the tree. + +## 1. Declare in a `.sig` file + +Put the declaration in an existing `.sig` that matches the subsystem, or a new +one in `sources/SIGParser/sigs/`. + +**Binding struct** — the shader-visible parameters. `[Bind = DefaultLayout::InstanceN]` +selects the root-signature slot; distinct structs bound in the same pass need +distinct instance slots: + +``` +[Bind = DefaultLayout::Instance0] +struct MyEffectData +{ + float4 params; + Texture2D depthBuffer; + RWTexture2D result; +} +``` + +**PSO** — `compute = ` refers to `workdir/shaders/.hlsl`, and +`[EntryPoint = X]` selects the function within it. One shader file can back +several PSOs through different entry points: + +``` +ComputePSO MyEffectCompute +{ + root = DefaultLayout; + + [EntryPoint = CS] + compute = my_effect; +} +``` + +**PassNode** — the FrameGraph declaration. Each member is a resource the pass +touches; `[Write]` marks the ones it produces: + +``` +[Compute] +PassNode MyEffect +{ + Texture GBuffer_Depth; + [Write] Texture ResultTexture; +} +``` + +Resource names are matched **across passes by name** — that is how the graph +links a producer to its consumers. Use the existing name for an existing +resource; introducing a new spelling silently creates an unrelated resource. + +### PassNode flags, and the one that decides your implementation style + +- `[Compute]` — may run on the async compute queue. Whether it actually does is + decided by `[Async]` at the pipeline registration site, not here. +- `[Required]` — never culled, even if nothing consumes its output. +- `[Static]` — **this flag decides how you implement the pass.** It generates a + `PassDefault` specialization declaring `setup` and `render`, which + you then define out-of-line. Without `[Static]` no such specialization exists + and the pass must be wired at runtime by assigning `setup_func`/`render_func`. + See `sources/SIGParser/templates/cpp/pass_defaults.jinja` for the exact rule. + +Choose `[Static]` when the pass is self-contained. Choose runtime wiring when +the pass needs state owned by a C++ object — loaded textures, cached history +buffers, persistent settings. + +## 2. Regenerate + +Use the `sig-regen` skill. In short: run the generator from `sources/SIGParser`, +then run `generate_project.bat` because a new `PassNode` and PSO create new +files that the projects don't yet list. + +## 3. Implement setup and render + +**setup** declares resource intent and returns whether the pass should run this +frame. Returning `false` culls it — a pass that never executes is usually a +`setup` returning `false`, not a broken render body. + +```cpp +bool PassDefault::setup( + Passes::MyEffect::Context& data, TaskBuilder& builder) +{ + builder.need(data.GBuffer_Depth, ResourceFlags::ComputeRead); + builder.need(data.ResultTexture, ResourceFlags::UnorderedAccess); + return true; +} +``` + +- `builder.need(...)` declares use of a resource that already exists. +- `builder.create(handle, desc, flags)` declares a resource this pass produces. +- `ResourceFlags` are in `sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx`: + `PixelRead`, `ComputeRead`, `DSRead`, `UnorderedAccess`, `RenderTarget`, + `DepthStencil`, `CopyDest`, `CopySource`, plus `Static` (persists across + frames rather than being transient) and `Required`. + +The flags must match what the pass actually does. They are the input to barrier +computation, so declaring `ComputeRead` on something the shader writes produces +a validation error whose message points at the barrier, not at the declaration. + +**render** records the work: + +```cpp +void PassDefault::render( + Passes::MyEffect::Context& data, FrameContext& context) +{ + auto& compute = context.get_list()->get_compute(); + + context.graph->set_slot(SlotID::FrameInfo, compute); + + { + Slots::MyEffectData params; + params.GetDepthBuffer() = data.GBuffer_Depth->texture2D; + params.GetResult() = data.ResultTexture->rwTexture2D; + compute.set(params); + } + + compute.set_pipeline(); + compute.dispatch(context.graph->get_context().frame_size, ivec2{ 16, 16 }); +} +``` + +Add `PROFILE(L"my_effect_dispatch")` scopes around logically distinct phases — +setup versus dispatch versus per-mip loops — per the profiling convention in +`CLAUDE.md`. + +### A binding trap worth knowing + +`builder.need()` wires the owning pass's own `data.X` FrameGraph handle; +`pass_texture()` does not. Using a `pass_texture()`-declared handle through +`data.X` inside that same pass's `render()` dereferences null and crashes with +no D3D12 output at all. In that pass, use the owning `HAL::Texture` directly. + +## 4. Write the shader + +Create `workdir/shaders/.hlsl` matching the PSO's `compute =`/`vertex =`/ +`pixel =` value, with a function named by `[EntryPoint = ...]`. Include the +generated binding header so the struct layout stays in sync with the `.sig`. + +## 5. Register in a pipeline + +A declared pass does nothing until it appears in a `Pipeline` block. Order in +the block is execution order; `[Async]` before an entry lets it overlap on the +compute queue: + +``` +Pipeline AssetPipeline +{ + ... + [Async] + MyEffect; + ... +} +``` + +Add it to every pipeline that should run it — `AssetPipeline` and the pipeline +in `test.sig` are separate graphs and adding to one does not affect the other. +Then regenerate again, since the pipeline block changed. + +## 6. Verify + +Use the `build-and-validate` skill. Confirm both that the pass runs and that it +introduced no new D3D12 errors — a new pass with mismatched resource flags +typically shows up as a per-frame repeating barrier error rather than as +anything visible on screen. diff --git a/.claude/skills/barrier-triage/SKILL.md b/.claude/skills/barrier-triage/SKILL.md new file mode 100644 index 00000000..76a0f802 --- /dev/null +++ b/.claude/skills/barrier-triage/SKILL.md @@ -0,0 +1,94 @@ +--- +name: barrier-triage +description: Diagnose D3D12 barrier, resource-layout, and resource-state validation errors in the operation-based barrier system (CmdListOperation / TrackedResourceState / CommandListGroup). Use this skill whenever the debug layer reports a barrier, layout, sync, access, or discard error — especially message IDs #1334, #1417, and #1422 — or when a resource is in an unexpected layout, a transition is rejected, a GPU hang or TDR is suspected to come from a missing barrier, or work in HAL.CommandList, HAL.CommandListRecorder, HAL.ResourceStates, or HAL.Queue needs its barrier consequences reasoned through. +--- + +# Barrier triage + +Barriers here are not emitted where the code asks for them. Work is recorded +into `CmdListOperation` runs, and the barriers are *computed afterwards*, across +a whole `CommandListGroup`, by `compile_transitions`. So the fix for a barrier +error is almost never "insert a barrier at the call site" — it is a wrong +declared state, a wrong resting layout, or a group-boundary handoff that the +compiler was never told about. + +## Read the source before theorising + +The implementation is heavily commented and the comments carry the invariants, +including the specific validation IDs each one prevents. Read the relevant part +rather than reconstructing the design from the error text: + +| File | What it settles | +|---|---| +| `sources/HAL/HAL.ResourceStates.ixx` | `TrackedResourceState`, `CmdListOperation`, resting layout, `SubresRange` | +| `sources/HAL/HAL.CommandList.ixx` | `CommandListGroup`, `PlannedResource`, how barriers are computed across lists | +| `sources/HAL/HAL.CommandListRecorder.ixx` | How reserved barrier points map back to operations | +| `sources/HAL/HAL.Queue.cpp` | Submission ordering, `execute(group)` | +| `sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp` | Where the compiled barriers reach the API | + +## The core model + +- A resource at rest sits in its **resting layout**, derived from what it was + created for (`resting_layout`). Every group leaves a resource there when + finished, so the next group can assume it without cross-group bookkeeping. +- Within a group, lists hand a resource to each other **directly**. A resource + read by several consecutive lists does not bounce through the resting layout + at each list boundary. +- A resource whose previous state cannot be known group-locally must have an + **entry state** declared (`has_entry_state` / `entry_state`). The FrameGraph + knows the whole pass graph, so it declares this for resources crossing a group + boundary. Unset means "assume resting", which is correct only when the group + model owns the resource end to end. +- `compile_transitions` must run **exactly once per list**. A list processed + twice gets two independently computed barrier sets spliced into one command + stream, which produces contradictory `LayoutBefore` values. + +## Message IDs and what they actually mean + +**#1417 — `SyncBefore` is `NONE` on a resource that has already been accessed.** +`NONE` is only legal for a resource entering from the creation/undefined layout. +`CommandListGroup::plan_resources` decides this via `PlannedResource::from_undefined`. +If you see this, the group believes it is the first-ever toucher of a resource +that something already used — look at whether an earlier group touched it +without being accounted for, or whether the resource was reset/recreated without +its tracked state being reset. + +**#1422 — missing initializing discard on a resource's first write.** +The group holding the first-ever write owns the discard +(`PlannedResource::owns_discard`). This fires when the first write happens +somewhere the planner didn't identify as the first write — commonly a resource +written by a pass that isn't declared as a writer, or an aliased/transient +resource whose first use after aliasing wasn't treated as initializing. + +**#1334 — `LayoutBefore` doesn't match the resource's actual layout.** +The most common structural cause is a barrier group being emitted twice: the +second run declares a `LayoutBefore` the first already moved past. `CmdListOperation::closed` +exists specifically to prevent this — it guards against reserving +`barriers_after` a second time when something appends to a list after +`CommandList::end()` closed it. Also check for a resource used mid-frame in +`COMMON` because it was created or deserialized lazily during a render pass +rather than at init. + +## How to work a specific error + +1. Get the full message text, not just the ID — it names the resource and the + two layouts, which localises the problem far faster than the ID alone. +2. Identify which pass or list touches that resource around the failure. +3. Determine what the compiler *believed*: is this the group's first touch, is + there an entry state, is the resource declared as read or written by that + pass? +4. Compare against what actually happens. The mismatch is the bug. +5. Fix the declaration — the pass's read/write flags, the entry state, the + resting layout, or the planning decision — not the emitted barrier. + +## Verifying a fix + +Barrier reasoning is not self-verifying, so a fix is unproven until the debug +layer agrees. Use the `build-and-validate` skill to build `Debug-D3D12`, run, +and count errors by ID before and after. Report the counts. + +Be careful about one failure mode in particular: a change that suppresses an +error by making a resource stop being tracked (rather than by making its +transitions correct) shows up as a clean log while leaving a genuine missing +barrier. If an error count drops sharply, confirm the resource is still being +transitioned rather than merely no longer being visited. diff --git a/.claude/skills/build-and-validate/SKILL.md b/.claude/skills/build-and-validate/SKILL.md new file mode 100644 index 00000000..a3bdddc5 --- /dev/null +++ b/.claude/skills/build-and-validate/SKILL.md @@ -0,0 +1,106 @@ +--- +name: build-and-validate +description: Build Spectrum and run it to capture real D3D12 debug-layer output, turning "this should work" into a verified result. Use this skill whenever a change needs to be proven — after editing HAL, FrameGraph, barrier, resource-state, or render pass code, when asked to build, compile, run, or test the engine, when checking whether a fix actually removed validation errors, or when reporting how many D3D12 errors a change fixed or introduced. Prefer this over reasoning about whether code compiles or whether barriers are correct. +--- + +# Build and validate + +For barrier, resource-state, and FrameGraph work the D3D12 debug layer *is* +the test. Reasoning about whether a transition is legal is unreliable; running +the engine and counting what the validation layer says is not. This skill is +the loop that produces a trustworthy number. + +## Choosing a configuration + +Use **`Debug-D3D12`** for validation work. The plain `Debug` configuration and +the `-Vulkan` variants will not produce D3D12 debug-layer output, so a clean +result from them says nothing about barrier correctness. + +Available configurations are `Debug`, `Profile`, `Retail`, each also as +`-D3D12` and `-Vulkan`, all `x64`. + +Note that the output path is derived from the *mode* only +(`main.sharpmake.cs:202` uses `target.Mode`), so `Debug-D3D12` and +`Debug-Vulkan` both produce `bin/debug/spectrum.exe`. The binary on disk gives +no indication of which API it was built against, and a leftover Vulkan build +runs perfectly while emitting no D3D12 messages at all. The `D3D12 SETUP` line +described below is the only reliable way to tell them apart. + +## Building + +Locate MSBuild via vswhere rather than hardcoding a version, since the VS +install path moves between machines and updates: + +```bash +MSBUILD=$("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -latest -requires Microsoft.Component.MSBuild -find 'MSBuild\**\Bin\MSBuild.exe' | head -1) +"$MSBUILD" projects/spectrum.sln /p:Configuration=Debug-D3D12 /p:Platform=x64 /m /nologo /v:minimal +``` + +Building a single project (`projects/hal/hal.vcxproj`) is much faster and is +the right call when iterating inside one layer. Build the full solution before +reporting a change as done, because C++ module consumers frequently break in +ways a single-project build never surfaces. + +Build times are long — allow several minutes and set generous tool timeouts +rather than assuming a hang. + +## Running to capture validation output + +The engine registers an `ID3D12InfoQueue1` message callback that mirrors +debug-layer messages into the log (`sources/HAL/API/D3D12/HAL.D3D12.Device.cpp`), +so validation output is available from a plain console run without a debugger +attached. The working directory must be `workdir/`, which is where assets, +shaders, and the log live. + +Run it in the background and stop it after roughly 15 seconds. That is enough +to get through device creation, asset load, and a number of steady-state +frames, which is where essentially all validation errors appear. Waiting +minutes adds repeated copies of the same messages and nothing new. + +```bash +cd workdir && ../bin/Debug/spectrum.exe +``` + +Stop the process once it has run long enough, then read `workdir/log.txt`. + +## Reading the results + +Messages are written in a fixed shape, so counting is exact: + +```bash +grep -cE "^D3D12 (ERROR|CORRUPTION)" workdir/log.txt +grep -E "^D3D12 (ERROR|CORRUPTION|WARNING)" workdir/log.txt | sed -E 's/^(D3D12 [A-Z]+ #[0-9]+).*/\1/' | sort | uniq -c | sort -rn +``` + +The second command groups by severity and message ID, which is what makes a +large error count actionable — 85,000 errors is routinely three distinct IDs +repeating per frame, and the ID is what identifies the bug. + +## Guarding against a fake clean result + +A "zero errors" result is only meaningful if validation was actually running. +Before believing it, confirm all of the following: + +- **The callback registered.** `grep "D3D12 SETUP" workdir/log.txt` must show + `message callback registered`. If it reports `ID3D12InfoQueue1 unavailable`, + nothing was captured and the run proves nothing. +- **The log is from this run.** The engine appends across runs. Truncate or + delete `workdir/log.txt` before launching, or the counts mix old and new. +- **The build actually succeeded.** A failed build leaves the previous + executable in place, so the run silently tests stale code. Check the MSBuild + exit code, don't just skim the output. +- **The run got past device creation.** If the app crashed or exited in the + first second, few frames were submitted and most passes never ran. + +These are the ways this loop lies. Checking them costs seconds; skipping them +produces a confident and wrong "fixed it." + +## Reporting + +Give the before and after counts and the distinct message IDs, not a +qualitative summary. "Was 85518, now 0" and "the remaining 12 are all #1334 on +PSSM_Depths" are the useful forms. If errors remain, say so plainly with the +counts rather than describing the change as complete. + +To interpret a specific barrier or layout error once you have the ID, use the +`barrier-triage` skill. diff --git a/.claude/skills/sig-regen/SKILL.md b/.claude/skills/sig-regen/SKILL.md new file mode 100644 index 00000000..2f8962b4 --- /dev/null +++ b/.claude/skills/sig-regen/SKILL.md @@ -0,0 +1,76 @@ +--- +name: sig-regen +description: Regenerate C++ and HLSL binding code after editing any .sig file in sources/SIGParser/sigs/. Use this skill whenever you add, edit, or remove a struct, ComputePSO, GraphicsPSO, PassNode, or Pipeline entry in a .sig file, or whenever generated code under sources/HAL/autogen, sources/RenderSystem/FrameGraph/autogen, or workdir/shaders/autogen looks stale or out of sync with the .sig sources. Also use it when a build fails with unknown Slots::, PSOS::, or Passes:: identifiers, since that almost always means the .sig edit was never regenerated. +--- + +# Regenerating SIG code + +`.sig` files are the single source of truth for GPU/CPU shared structs, PSO +definitions, and FrameGraph pass declarations. Editing one changes nothing on +its own — the generated C++ and HLSL must be rebuilt from it. + +## The one thing that goes wrong + +`generate_sigs.bat` does **not** regenerate code from `.sig` files. It runs +ANTLR over `SIG.g4` to rebuild the *parser grammar*, which is only needed when +the grammar itself changes. Running it after a `.sig` edit appears to succeed +and produces no useful change — which is exactly why it's the trap. + +The actual generator is `bin/debug/sigparser.exe`. + +## Running the generator + +The generator resolves every path relative to its working directory — +`sigs/` for input, `../../sources/...` and `../../workdir/...` for output (see +`sources/SIGParser/Main.cpp:9-13`). Run it anywhere else and it either finds no +input or writes to the wrong place, so the working directory is not incidental: + +```bash +cd sources/SIGParser && ../../bin/debug/sigparser.exe +``` + +If `bin/debug/sigparser.exe` is missing or older than the `.sig` edits and the +grammar or generator sources changed, build the `sigparser` project first — +otherwise you are regenerating with a stale generator and the output won't +reflect template changes. + +## Deciding whether the project needs regenerating too + +Sharpmake enumerates source files at generation time, so the `.vcxproj` files +list generated sources explicitly. Modifying the *contents* of an existing +generated file is invisible to the build system, but a **new** generated file +will not compile until the projects know about it. + +After running the generator, check whether the file set changed: + +```bash +git status --short sources/HAL/autogen sources/RenderSystem/FrameGraph/autogen workdir/shaders/autogen +``` + +Lines starting with `??` (untracked) or `D` (deleted) mean the file set changed +— run `generate_project.bat`. Only `M` lines means contents changed in place and +the existing projects already cover it. + +Adding a new `struct`, `ComputePSO`/`GraphicsPSO`, or `PassNode` usually creates +new files, so a new declaration generally does need the project regenerated. + +## Full sequence + +1. Edit the `.sig` file under `sources/SIGParser/sigs/`. +2. `cd sources/SIGParser && ../../bin/debug/sigparser.exe` +3. `git status --short` the three autogen directories. +4. If any file was added or removed, run `generate_project.bat` from the repo root. +5. Build, and confirm the new `Slots::`/`PSOS::`/`Passes::` names resolve. + +## Reporting back + +Say which `.sig` files changed, what the generator wrote, and — explicitly — +whether `generate_project.bat` was needed. That last point is what the next +person (or the next session) needs in order to trust the result, since a +missing project regeneration produces a confusing "identifier not found" error +far away from its cause. + +Never hand-edit files under the autogen directories. They carry a +DO-NOT-EDIT banner and the next generator run silently discards the changes. +If generated output is wrong, fix the `.sig` file or the Jinja template in +`sources/SIGParser/templates/`. diff --git a/.gitignore b/.gitignore index 23cad42b..c655a27e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,12 @@ #Project-specific -.claude/ +# Ignore local Claude Code state (settings.local.json, caches) but keep the +# shared skills, which describe repo workflows and belong in version control. +.claude/* +!.claude/skills/ log.txt *.log +# Temporary debug logging — see "Temporary debug logging" in CLAUDE.md +*.temp projects/ diff --git a/CLAUDE.md b/CLAUDE.md index 68628ab0..8acfb364 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,3 +81,47 @@ When writing or editing any function that does non-trivial per-frame CPU work (l - Name markers by what they measure, not where they live (`vsm_hiz_copy`, not `loop1`) — this is what shows up in the profiler UI and needs to be legible next to unrelated markers from other systems. - Prefer several small, named scopes over one broad one when a function has multiple distinct phases (setup vs. dispatch vs. teardown) — this is what makes a profiler capture actionable instead of just "this function is slow somewhere." - This is the same convention already used throughout `HAL.CommandList.cpp` (`commit_tables`, `transitions`, `rt_transitions`) and `MipMapGeneration.cpp` — match that granularity when adding new instrumented code, don't invent a different style. + +## Temporary debug logging + +Debug logging added to chase a specific problem is scaffolding, not code. It +exists for one investigation and becomes noise the moment that investigation +ends — so it needs to be obvious on sight and trivial to remove. + +**Write temporary log output to files with a `.temp` extension**, never into +`log.txt` or the engine's normal logging path. `*.temp` is gitignored, so +scratch output can never be committed by accident, and the extension makes +every temporary artifact greppable as a set rather than something you have to +remember file by file. + +**When a debugging session ends, ask before cleaning up.** Once the bug is +found and fixed, list the temporary logging that was added — the call sites and +the `.temp` files — and ask whether to remove it. Don't strip it automatically: +a fix often needs one more round of verification, and silently deleting the +instrumentation that proved it means rebuilding it from scratch. Equally, don't +leave it behind unmentioned — untracked `PROFILE`-adjacent spam and stray +writes accumulate and are hard to attribute later. + +This is specifically about *temporary* instrumentation. Permanent diagnostics +that earn their place — the VSM active-window diag, the D3D12 message callback +— are normal code and stay. + +## Comments + +Comment sparingly. Prefer code that doesn't need explaining, and don't restate +in prose what the line below already says: `// increment the counter` above +`++counter` is pure cost, and comments that merely narrate drift out of sync +with the code and start actively lying. + +Write a comment when the reader would otherwise be right to think the code is +wrong: + +- A workaround for a driver, API, or compiler bug — say which, and what breaks + without it. +- A non-obvious ordering or lifetime requirement — why this call must precede + that one. +- A deliberate deviation from the obvious implementation, with the reason. +- A constant whose value came from measurement or a spec, not from choice. + +The test is whether someone competent would be tempted to "simplify" the code +and break it. If yes, explain why it is the way it is. If no, leave it alone. diff --git a/mermaid_edges.txt b/mermaid_edges.txt deleted file mode 100644 index 456fc39e..00000000 --- a/mermaid_edges.txt +++ /dev/null @@ -1,36 +0,0 @@ - BlueNoise --> VoxelScreen - CubeMapDownsample --> CubeMapEnviromentProcessor - CubeMapEnviromentProcessor --> Lighting - CubeMapEnviromentProcessor --> RTXColorPass - CubeSky --> CubeMapDownsample - Lighting --> Mipmapping - Mipmapping --> VoxelDebug - Mipmapping --> VoxelScreen - PSSM_Cascade --> PSSM_Cascade1 - PSSM_Cascade1 --> PSSM_Cascade2 - PSSM_Cascade2 --> PSSM_Cascade3 - PSSM_Cascade3 --> PSSM_Cascade4 - PSSM_Cascade4 --> PSSM_Cascade5 - PSSM_Cascade5 --> PSSM_GenerateMask - PSSM_Combine --> VoxelScreen - PSSM_GenerateMask --> PSSM_Combine - PSSM_Global --> Lighting - PreScene --> RTXColorPass - PreScene --> Scene - RTXShadow --> PSSM_Combine - ReflCombine --> CopyPrev - ReflCombine --> Sky - ReflectionDenoiser_Reproject --> ReflCombine - ResultCreation --> PSSM_Combine - SMAA --> FSR - Scene --> PSSM_GenerateMask - Scene --> RTXShadow - Scene --> VoxelDebug - ScreenReflection --> ReflectionDenoiser_Reproject - Sky --> stencil_renderer_after - VoxelCombine --> ReflCombine - VoxelDebug --> CopyPrev - VoxelScreen --> ScreenReflection - VoxelScreen --> VoxelCombine - Voxelize --> Lighting - stencil_renderer_after --> SMAA \ No newline at end of file diff --git a/mermaid_meta.txt b/mermaid_meta.txt deleted file mode 100644 index 54d0fef4..00000000 --- a/mermaid_meta.txt +++ /dev/null @@ -1,33 +0,0 @@ -PreScene|0|0|4|2 -BlueNoise|1|0|7|3 -Voxelize|0|0|5|1 -PSSM_Global|0|0|5|1 -PSSM_Cascade|0|0|0|7 -PSSM_Cascade1|0|1|0|6 -PSSM_Cascade2|0|2|0|5 -PSSM_Cascade3|0|3|0|4 -PSSM_Cascade4|0|4|0|3 -PSSM_Cascade5|0|5|0|2 -Scene|0|1|4|11 -stencil_renderer_before|0|0|15|0 -CubeSky|0|0|3|2 -CubeMapDownsample|1|1|3|6 -CubeMapEnviromentProcessor|0|2|3|5 -Lighting|1|3|3|4 -Mipmapping|1|4|3|3 -RTXShadow|1|2|4|2 -ResultCreation|0|0|6|8 -PSSM_GenerateMask|0|6|0|2 -PSSM_Combine|1|7|0|8 -VoxelScreen|0|8|0|8 -VoxelCombine|0|9|1|6 -ScreenReflection|1|9|0|3 -ReflectionDenoiser_Reproject|1|10|0|2 -ReflCombine|1|11|0|5 -VoxelDebug|0|5|9|1 -RTXColorPass|1|3|12|0 -Sky|1|12|0|3 -stencil_renderer_after|0|13|0|2 -SMAA|1|14|0|1 -FSR|1|15|0|0 -CopyPrev|1|12|3|0 \ No newline at end of file diff --git a/overview/Barriers.txt b/overview/Barriers.txt new file mode 100644 index 00000000..eb6412a4 --- /dev/null +++ b/overview/Barriers.txt @@ -0,0 +1,350 @@ +BARRIER SYSTEM — TECHNICAL REFERENCE +================================================================================ +How resource state transitions are decided and emitted. + +This is the operation-based system that replaced ResourceStateManager (2026-08). +Nothing tracks state "globally": every decision is made per command-list GROUP, +from data recorded per (resource, list), against a small set of invariants. + +Files: + HAL/HAL.ResourceStates.ixx/.cpp OperationUsage, TrackedResourceState, + CmdListOperation, Barriers, resting_layout, + state_at_rest + HAL/HAL.CommandList.ixx/.cpp Transitions (recording), CommandListGroup + (plan_resources / compile_transitions) + RenderSystem/FrameGraph/ + FrameGraph.cpp TaskBuilder::process_transitions (cross-pass + linking), the debug snapshot + + +================================================================================ + THE MODEL +================================================================================ + +A command list is a SEQUENCE OF OPERATIONS, not a flat command stream. + +CmdListOperation + One contiguous run of same-class work. begin_op(BarrierSync) starts a new one + only when the class differs from the one at the back, so a run of same-class + commands grows ONE operation instead of producing one per command. That is + what makes barriers batch. + + - type the BarrierSync the run was opened with (names the operation) + - index position in the list's `operations` (stable ordering key) + - barriers_before runs immediately before the operation's commands + - barriers_after runs immediately after them + - closed whether barriers_after already has its point in the stream + + `operations` is a std::deque, NOT a vector: the recorder holds + CmdListOperation* (DelayedCommandList::func_barrier), so entries must not move + as more are appended. + +OperationUsage + One place a resource was used inside one operation. Either: + - through a bound view (`info`), carrying a mip/array/plane range, or + - as a bare subresource index (`subres`), for uses with no descriptor. + Plus the ResourceState it is used in, and `step` — which command within the + operation recorded it. Two usages with different `step` are different + dispatches, which is how "bound twice for one dispatch" is told apart from + "written by one dispatch, then read by the next". + +TrackedResourceState (per resource, PER LIST — via ObjectState) + - listed resource has been added to this list's used_resources + - has_entry_state / entry_state what this list will FIND the resource in, + when only an outside scheduler can know + - operations map>, ordered + + NOTE `listed` is deliberately not TrackedObjectState::used. A resource carries + ONE state object for both purposes; track_object sets `used` first for + non-FrameGraph resources, so sharing the flag would leave use_resource never + listing the resource, and no barriers would be emitted at all. + +CommandListGroup + One or more lists submitted in ONE ExecuteCommandLists. Barriers are computed + per group, and a queue accepts only a group — never a bare list. + + +================================================================================ + LIFECYCLE OF A FRAME'S BARRIERS +================================================================================ + +1. RECORD (per list, during pass render) + add_resource_usage / commit_tables append OperationUsage entries to the + resource's per-list TrackedResourceState. Cheap by construction: a lookup + and a push_back. No state comparison, no barrier decision, no subresource + expansion — all of that happens later, once. + +2. LINK (TaskBuilder::process_transitions, after all passes recorded) + Cross-pass work for frame_graph_managed resources only. See below. + +3. PLAN (CommandListGroup::plan_resources) + ORDERED, on the submitting thread, in submission order. Resolves the + first-use questions and builds the group's resource list. + MUST NOT run in the parallel fan-out: it reads and mutates + Resource::virgin / Resource::initialized, which are per-RESOURCE and shared + by every group that touches them. + +4. COMPILE (CommandListGroup::compile_transitions) + Pure and parallel-safe: reads only group-local data (`planned`, `current`, + `wanted`) and fills Barriers owned by its own lists. Several groups compile + concurrently. + +5. REPLAY (CommandListGroup::compile) + Replays each list into its API command list; the reserved barrier points + are emitted with whatever compile_transitions put in them. + +6. SNAPSHOT (Dev only, FrameGraph.cpp) + Between compile() and execute(), resolves each Transition record's + barrier_point into plain data for the debugger. MUST be before execute: + Transitions::on_execute() clears `operations` on the submit thread, which + destroys the Barriers the records point into. + + +================================================================================ + THE INVARIANTS +================================================================================ + +RESTING LAYOUT + Every non-FrameGraph resource ENTERS a group at its resting layout and is LEFT + at its resting layout. That is what makes groups independent of the order they + are submitted in, and lets a group assume where a resource is without being + told. + + resting_layout(resource) -> TextureLayout + Swapchain -> PRESENT + ShaderResource -> SHADER_RESOURCE + UnorderedAccess -> UNORDERED_ACCESS + otherwise -> COPY_DEST (where an upload would put it) + + state_at_rest(layout) -> ResourceState + Fills the layout back out. Access follows from the layout; sync is ALL, + because a barrier against a resting resource crosses into a scope where no + specific producer/consumer stage is known. + + A Dev-only check at the end of compile_transitions asserts the group actually + converged. Every consumer of the resting assumption is silently wrong if it + does not. + +FRAME-GRAPH-MANAGED RESOURCES OPT OUT + Resource::frame_graph_managed means the FrameGraph owns the transitions. + Groups do NOT rest those — the graph knows the whole pass timeline and rests + them once, at the last pass that uses them, rather than converging at every + group boundary (which for the VSM pyramid costs a barrier per subresource + every time a fence splits its passes). + + The consequence: for those resources the entry state must come from + set_entry_state. Reaching the resting fallback with one means the graph left a + boundary unlinked and the group is about to guess. + +EXECUTECOMMANDLISTS SCOPE + A group is exactly one ECL scope. Per the D3D12 spec only the LAYOUT carries + across that boundary — there are no pending commands or cache flushes between + scopes. compile_transitions uses this: the group's first touch of a resource + whose layout already matches what it wants emits NO barrier at all. + + +================================================================================ + compile_transitions — HOW A BARRIER IS DECIDED +================================================================================ + +Per resource in `planned`, per list, per operation, in order: + + 1. Collapse every usage in the operation into ONE wanted state per subresource + (`want`). Read-only states combine via merge_state; anything else cannot be + satisfied by one barrier, so the later use wins. + + 2. Expand views (visit_subres) ONLY here — once, and only for resources that + turned out to need barriers at all. visit_subres also reports side + resources (a UAV counter buffer), so it is filtered to the resource in + hand. + + 3. emit(subres, after) resolves the before-state and decides: + + a. first write of a virgin resource -> DISCARD, whole resource + b. entry matches what we want (scope) -> no barrier [entry_known only] + c. read-after-read, same layout+access -> no barrier + d. before == after, UAV involved -> UAV ordering barrier + e. before == after otherwise -> no barrier + f. otherwise -> transition + + `current` maps subresource -> state for the group so far, CARRIED ACROSS LISTS + (the whole point: list N+1 diffs against where list N actually left it). The + ALL_SUBRESOURCES key is the uniform value standing in for every subresource + with no entry of its own, so a whole-resource lifetime costs exactly one entry. + + entry_known == !frame_graph_managed. Rule (b) writes `after` into `current` as + the group's belief; on a GUESSED entry that belief is unfounded and would + cascade through every later barrier, so it is only allowed where the resting + contract actually holds. + +FIRST USE — TWO SEPARATE FACTS + Resource::virgin no barrier has ever transitioned it, so its layout is + undefined. Decides the BEFORE-state. + Resource::initialized contents have been established by a write. Decides + whether the first write carries the DISCARD. + + They are distinct because a resource whose first touch is a READ gets a + defined layout from that point on but still holds nothing. + + alias_begin() must re-arm BOTH: aliased memory is another resource's data, so + it is both undefined AND uninitialized. + + +================================================================================ + process_transitions — CROSS-PASS LINKING +================================================================================ + +Only for frame_graph_managed resources; skips UPLOAD/READBACK heaps (CPU-visible, +no layout). Steps: + + 1. PRUNE passes whose list never actually touched the resource. + 2. MERGE readers of one version onto a single shared state, pushed back to + every reader, so only the first transitions and the rest are no-ops. + 3. LINK state N-1 -> state N via set_entry_state. + 4. REST transition_to_rest at the LAST-EXECUTING pass. + +CONVERGE AT THE PRODUCER, NOT THE FIRST CONSUMER + A phase's passes have no order among them. Telling all of them "you will find + it in the producer's exit state" is true for exactly one — the rest find it + already transitioned. So the producer is converged into a state they ALL agree + on (merged_read for readers, common first-use state otherwise), which costs the + consumers no barrier at all and makes their order irrelevant. + +ORDER PASSES BY call_id + ResourceRWState::passes is a concurrent_vector appended from several threads + during setup — its order is ARBITRARY. Anything that needs "the last pass" + must pick by call_id (see `last_of`). Using .back() put the return-to-rest on + an arbitrary pass of the phase, mid-frame. + +NOTHING IS CARRIED ACROSS FRAMES + The graph rests at the last pass, so the next frame's first pass needs no + entry state — the resting default is already right. Carrying the real end + state forward would be worse: a resource left in DEPTH_STENCIL_WRITE cannot be + barriered out of by a compute queue at all. + + +================================================================================ + WORK THE SYSTEM CANNOT SEE +================================================================================ + +Three cases where usage must be declared by hand, because nothing can derive it: + + EXTERNAL SDKs (Streamline/DLSS) + begin_external_op(BarrierSync) opens an operation that never merges into the + one in front of it, then the caller declares each resource the external call + touches. The barriers land in THAT operation's barriers_before and bracket + exactly the external work. See HAL::DLSS::upscale. + + GLOBAL SLOTS + A resource sampled through a global slot (FrameInfo) rather than a per-pass + binding has no ResourceInfo recorded, so its read must be declared. + + LISTING A RESOURCE AT ALL + Device::flush_uploads records a read use whose state IS the resting layout. + That looks redundant but is not: recording it is what puts the resource in + that list's used_resources, which is what makes the group transition it out + of COPY_DEST. + +Everything else should be derived. A manual add_resource_usage elsewhere is a +smell — it usually means a pass declares something it does not do, or does +something it does not declare. + + +================================================================================ + [Barrier = ALL] — SIG ANNOTATION +================================================================================ + +A .sig table member may be declared: + + [Barrier = ALL] RWTexture2DArray dst_mip; + +The bind then records ONE whole-resource use instead of expanding the view's +mip/array range. For a view narrowed to one mip of a large array (a Hi-Z level +over N slices) the range costs one barrier per slice per bind, while the whole +resource is being rewritten anyway. + +Chain: + table.jinja emits compiler.compile_whole(x) + -> Slot_Compiler::bind_whole_resource (scoped flag, so the scalar/array/ + vector overloads all inherit it) + -> HAL::BoundResource { info, whole_resource } + -> commit_tables -> add_resource_usage(info, op, whole) + -> records OperationUsage(ALL_SUBRESOURCES, state) instead of the view. + +No grammar change was needed: value_declaration already accepts option_block, and +options are exposed to Jinja generically. Regenerate with bin/debug/sigparser.exe +from sources/SIGParser — generate_sigs.bat only rebuilds the ANTLR grammar. + + +================================================================================ + DEBUGGING +================================================================================ + +The FrameGraph timeline debugger's "Transitions" panel shows, per resource: + + Pass + Operation N [class] + Pre barriers + Sync / Access / Layout before -> after [discard] + Post barriers + ... + + - Operation identity comes from the record (op_index, after_op, op_type), + captured when the barrier POINT is reserved — by snapshot time a barrier is + just an entry in a list. + - The Sync line is clickable: toggles a GPU-side breakpoint that fires only + when that exact before->after transition executes + (HAL::Debug::BarrierBreakpoints). + - Red rows are the panel's own validator. Its one rule for a first barrier: + claiming UNDEFINED without discarding is wrong; entering from a real layout + is fine for any resource. Continuity checks skip releases (alias_end) and + discards, since neither's `before` is a continuity claim. + + The panel reads Pass::debug_commands, which is filled at END of frame + (commit_command_lists) but copied by the debugger at START of one + (graph.on_compile) — so it shows the PREVIOUS frame's transitions. That is + also why Pass::reset_frame must NOT clear debug_commands. + + +================================================================================ + PITFALLS (each of these was a real bug — do not re-derive them) +================================================================================ + +SYNC_NONE WITH A REAL LAYOUT + {SYNC_NONE, ACCESS_NO_ACCESS, } does not take effect. D3D12 + rejects NO_ACCESS paired with a real layout (the after-side form of this is + #1331: "LayoutAfter must be UNDEFINED or SyncAfter must be SYNC_NONE when + AccessAfter is ACCESS_NO_ACCESS"). The barrier silently does nothing and the + NEXT one fails instead. Use state_at_rest. + +DISCARD MUST RIDE AN RT/DS BARRIER FOR RT/DS RESOURCES + #1422 counts a placed RENDER_TARGET/DEPTH_STENCIL resource as initialized only + when the DISCARD accompanies a barrier INTO an RT/DS layout. Moving the discard + onto an earlier SHADER_RESOURCE read costs 54x #1422. + +RESERVING A BARRIER POINT TWICE + end_op() must be idempotent. Reserving barriers_after twice emits the SAME + group twice; the second run's LayoutBefore is stale by definition (#1334). + This happens whenever something appends to a list after CommandList::end() + closed it — process_transitions does exactly that. + +BARRIERS THAT DISAGREE WITH `current` + Every emission path must resolve its before-state from the group's tracking. + The discard path once called from_undefined() outright, so after a read had + already moved the resource the barrier declared UNDEFINED while `current` said + SHADER_RESOURCE. D3D12 tolerates it (UNDEFINED means "don't care"), so it + shows up only as an inconsistency in the debugger. + +UNINITIALISED heap_type + ResourceAllocInfo::heap_type is assigned in create_resources(), which SKIPS + passed resources. Anything filtering on it silently dropped every passed-in + resource. Also: Resource::init(NativeImportHandle) never assigns a heap type, + so an imported resource (the swapchain) keeps the RESERVED sentinel — filter + on "is CPU-visible", not on "== DEFAULT". + +DEBUG-ONLY PASSES MUST BE TRANSPARENT + ExternalPass exists so the debugger can capture; it is not in any resource's + state timeline, and linking it would add a fence for a pass with no GPU work. + It must hand every resource it touched back at rest instead. + +================================================================================ diff --git a/overview/HAL.txt b/overview/HAL.txt index 2e6a71d0..70bad885 100644 --- a/overview/HAL.txt +++ b/overview/HAL.txt @@ -23,28 +23,29 @@ ResourceStates namespace — predefined constants: RAYTRACING_STRUCTURE, RAYTRACING_STRUCTURE_WRITE, INDIRECT_ARGUMENT, RENDER_TARGET, DEPTH_STENCIL, NO_ACCESS, UNKNOWN. -SubResourcesCPU - CPU-side per-subresource tracking: first/last usage, state history. - - merge_read_state() - - get_subres_state(int) - -ResourceStateManager - Manages D3D12 resource transitions across command lists. - - transition(Transitions*, ResourceState, uint subres) - - prepare_state() — merge GPU/CPU state at frame start - - process_transitions() — pre-transitions between frame boundaries - - connect() — connects transitions between command lists - - alias_begin/end() — aliasing barrier handling - Barriers - Accumulates resource barriers for batch submission. - - transition(from, to) - - validate() - - get_barriers() + Accumulates resource barriers for batch submission, and carries the counts + the D3D12 backend needs to size its two native arrays exactly. + - transition(resource, before, after, subres, flags) + - get_barriers(), get_buffer_count(), get_texture_count() + +CmdListOperation + One contiguous run of same-class work on a list, bracketed by barriers_before + and barriers_after. A list is a sequence of these. Transitions - Per-command-list resource usage tracking. - - add_usage(), get_last_usage_point(), track_object(), use_resource() + Per-command-list recording: begin_op / end_op / split_op, record_usage, + add_resource_usage, set_entry_state, get_exit_state, get_first_use_state, + transition_to / transition_to_rest, begin_external_op. + +CommandListGroup + One or more lists submitted as ONE ExecuteCommandLists. Owns the barrier + computation: plan_resources() (ordered) then compile_transitions() (parallel) + then compile(). A queue accepts only a group, never a bare list. + + >>> See overview/Barriers.txt for how transitions are actually decided: + the operation model, the resting-layout contract, cross-pass linking, + first-use/discard rules, and the pitfalls that produced them. ================================================================================ diff --git a/UIRefactor.md b/overview/UIRefactor.md similarity index 100% rename from UIRefactor.md rename to overview/UIRefactor.md diff --git a/sources/Core/Tree/Tree.cpp b/sources/Core/Tree/Tree.cpp index 65f8407f..552f9ba7 100644 --- a/sources/Core/Tree/Tree.cpp +++ b/sources/Core/Tree/Tree.cpp @@ -12,11 +12,23 @@ std::string VariableBase::get_name() return name; } +namespace +{ + // Per-thread so the (thread-confined, single-threaded-at-setup) push/pop + // discipline can't be corrupted by another thread constructing a named + // VariableContext concurrently. + thread_local std::vector g_context_scope_stack; +} + VariableContext::VariableContext() : name(L"global") {} VariableContext::VariableContext(std::wstring name) : name(name) { - Singleton::get().add_child(this); + VariableContext* parent = g_context_scope_stack.empty() + ? &Singleton::get() + : g_context_scope_stack.back(); + + parent->add_child(this); } VariableContext::~VariableContext() @@ -24,6 +36,22 @@ VariableContext::~VariableContext() remove_from_parent(); } +std::unique_ptr VariableContext::create(std::wstring name) +{ + return std::unique_ptr(new VariableContext(std::move(name))); +} + +VariableContext::Scope::Scope(VariableContext& context) : context(context) +{ + g_context_scope_stack.push_back(&context); +} + +VariableContext::Scope::~Scope() +{ + ASSERT(!g_context_scope_stack.empty() && g_context_scope_stack.back() == &context); + g_context_scope_stack.pop_back(); +} + std::wstring VariableContext::get_name() const { return name; diff --git a/sources/Core/Tree/Tree.ixx b/sources/Core/Tree/Tree.ixx index 7979c8d7..b320ffdb 100644 --- a/sources/Core/Tree/Tree.ixx +++ b/sources/Core/Tree/Tree.ixx @@ -3,6 +3,7 @@ export import :Singleton; export import :Events; export import stl.core; export import :Data; +import magic_enum; export class base_tree { @@ -243,6 +244,18 @@ public: VariableBase(std::string name); virtual ~VariableBase() = default; std::string get_name(); + + // Enum support: GUI code (ParameterWindow) needs to build a combo box for + // whichever concrete enum a Variable wraps, but Core can't depend on + // GUI, and there's no way to dynamic_cast to "some unknown enum type" the + // way it does for the known concrete types bool/float -- so the enum + // listing/get/set has to be exposed generically here instead, with + // Variable supplying the real implementation only when T is an enum. + // A non-enum Variable leaves these at their harmless defaults, which GUI + // code reads as "not an enum" (empty name list). + virtual std::vector get_enum_names() const { return {}; } + virtual int get_enum_index() const { return -1; } + virtual void set_enum_index(int index) {} }; export class VariableContext:public tree >, public Singleton @@ -250,18 +263,59 @@ export class VariableContext:public tree; std::wstring name; - - + + VariableContext(); protected: VariableContext(std::wstring name); - virtual ~VariableContext(); public: + // Public (not protected like the constructor above) so std::unique_ptr's + // default deleter -- used by create() below to hand out an independently- + // owned context -- can actually invoke it; the constructor stays + // protected to keep enforcing "always give a name; go through a class + // that derives VariableContext, or through create()". + virtual ~VariableContext(); + std::wstring get_name() const override; std::set variables; void add(VariableBase* v); void remove(VariableBase* v); + + // Constructs a new named VariableContext the caller owns (VariableContext's + // own constructor is protected, so it can't be constructed directly outside + // classes that derive from it -- this is the way to get one as a plain, + // independently-owned grouping node, e.g. a persistent member of a class + // that does NOT itself derive VariableContext). The tree stores raw + // VariableContext* internally, so the returned object must keep a stable + // address for as long as anything is parented under it -- store the + // unique_ptr, don't move/copy out of it. + static std::unique_ptr create(std::wstring name); + + // RAII scope: while alive, any VariableContext constructed with a name + // (mesh_renderer, VSM, ...) attaches under `context` instead of the global + // root -- lets call sites disambiguate otherwise identically-named siblings + // (e.g. multiple mesh_renderer instances) by where they're constructed, + // without threading a parent pointer through every constructor. Scopes + // nest: a Scope opened while another is already active attaches under + // that outer scope. Must be destroyed in strict LIFO order (ordinary + // stack-scoped RAII use satisfies this). + // + // Takes an EXISTING context by reference rather than constructing and + // owning its own: the things constructed under it (a mesh_renderer stored + // in a long-lived shared_ptr, say) usually outlive the single statement a + // Scope wraps, so the grouping context needs to outlive the Scope too -- + // pass a persistent context (a base-class `this`, or one obtained from + // create() above), not a temporary. A Scope that owned and destroyed its + // own context would remove that context -- orphaning everything nested + // under it -- the moment the construction statement it wraps finishes. + class Scope + { + VariableContext& context; + public: + explicit Scope(VariableContext& context); + ~Scope(); + }; }; export template @@ -269,27 +323,87 @@ class Variable:public VariableBase { T value; VariableContext* context; + + // Only meaningful when constrained is true (the range-taking constructor + // was used) -- an unconstrained Variable never reads these. + T range_min{}; + T range_max{}; + bool constrained = false; + + static T clamp(const T& v, const T& lo, const T& hi) + { + return v < lo ? lo : (v > hi ? hi : v); + } + public: Variable(const T& def, std::string name, VariableContext * context) :value(def), VariableBase(name), context(context) { context->add(this); } - + + // Constrained overload: value is clamped to [min, max] on construction and + // on every subsequent assignment (GUI slider drag or a plain `var = x` in + // code alike), so the constraint holds no matter which path writes it. + Variable(const T& def, std::string name, VariableContext* context, T min, T max) : + value(clamp(def, min, max)), VariableBase(name), context(context), + range_min(min), range_max(max), constrained(true) + { + context->add(this); + } + ~Variable() { context->remove(this); } - + operator T() const { return value; } - + const T operator=(const T& r) { - return value = r; + return value = constrained ? clamp(r, range_min, range_max) : r; + } + + bool has_range() const { return constrained; } + T get_min() const { return range_min; } + T get_max() const { return range_max; } + + // Real implementation only for enum T -- if constexpr discards the other + // branch entirely (not just at runtime), so this compiles for every T + // without needing magic_enum to accept non-enum types. + std::vector get_enum_names() const override + { + if constexpr (std::is_enum_v) + { + auto names = magic_enum::enum_names(); + return std::vector(names.begin(), names.end()); + } + else + return {}; } + int get_enum_index() const override + { + if constexpr (std::is_enum_v) + { + auto index = magic_enum::enum_index(value); + return index.has_value() ? (int)index.value() : -1; + } + else + return -1; + } + + void set_enum_index(int index) override + { + if constexpr (std::is_enum_v) + { + auto values = magic_enum::enum_values(); + if (index >= 0 && index < (int)values.size()) + *this = values[index]; + } + } }; diff --git a/sources/Core/patterns/Trackable.ixx b/sources/Core/patterns/Trackable.ixx index 48120abb..72ccf158 100644 --- a/sources/Core/patterns/Trackable.ixx +++ b/sources/Core/patterns/Trackable.ixx @@ -35,8 +35,18 @@ export }; + // Anything carrying per-context state that is (or derives from) + // TrackedObjectState. Stated as a requirement on get_state rather than as + // is_base_of, T>, because a type is free to + // extend the state with its own data -- e.g. HAL::Resource stores its + // per-command-list barrier tracking in a TrackedResourceState. Those give + // ObjectState, which is an unrelated type to the template + // machinery even though the state itself still IS-A TrackedObjectState. template - concept TrackableClass = std::is_base_of, T>::value; + concept TrackableClass = requires (T& obj, StateContext* context) + { + { obj.get_state(context) } -> std::convertible_to; + }; class TrackedObject { diff --git a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp index c0611f0c..14ab68e7 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.cpp @@ -413,9 +413,18 @@ namespace HAL void CommandList::transitions(const HAL::Barriers& _barriers) { auto& barriers = _barriers.get_barriers(); + if (barriers.empty()) return; - std::vector textures; - std::vector buffers; + // Exact sizes, counted as the barriers were recorded -- so neither + // array ever grows mid-fill, and after the first few frames the + // reserve is a no-op because the capacity is already there. + auto& textures = scratch_textures; + auto& buffers = scratch_buffers; + + textures.clear(); + buffers.clear(); + textures.reserve(_barriers.get_texture_count()); + buffers.reserve(_barriers.get_buffer_count()); for (auto& e : barriers) { @@ -434,7 +443,7 @@ namespace HAL buffers.emplace_back(barrier); } - if (e.resource->get_desc().is_texture()) + else if (e.resource->get_desc().is_texture()) { D3D12_TEXTURE_BARRIER barrier; @@ -446,7 +455,7 @@ namespace HAL barrier.LayoutAfter = to_native(e.after.get_layout()); barrier.pResource = e.resource->get_dx(); - if (e.subres == 0xffffffffu) // HAL::ALL_SUBRESOURCES + if (e.range.is_all()) { // Single barrier covering every subresource. D3D12 uses // IndexOrFirstMipLevel == 0xffffffff (== ALL_SUBRESOURCES) @@ -462,12 +471,15 @@ namespace HAL } else { - barrier.Subresources.IndexOrFirstMipLevel = e.resource->get_desc().as_texture().get_mip(e.subres); - barrier.Subresources.NumMipLevels = 1; - barrier.Subresources.FirstArraySlice = e.resource->get_desc().as_texture().get_array(e.subres); - barrier.Subresources.NumArraySlices = 1; - barrier.Subresources.FirstPlane = e.resource->get_desc().as_texture().get_plane(e.subres); - barrier.Subresources.NumPlanes = 1; + // Straight copy: SubresRange holds exactly these six fields, + // so a run of subresources merged at record time reaches the + // API as one barrier instead of one per subresource. + barrier.Subresources.IndexOrFirstMipLevel = e.range.first_mip; + barrier.Subresources.NumMipLevels = e.range.num_mips; + barrier.Subresources.FirstArraySlice = e.range.first_slice; + barrier.Subresources.NumArraySlices = e.range.num_slices; + barrier.Subresources.FirstPlane = e.range.first_plane; + barrier.Subresources.NumPlanes = e.range.num_planes; } barrier.Flags = D3D12_TEXTURE_BARRIER_FLAG_NONE; @@ -484,28 +496,30 @@ namespace HAL } } - std::vector native; + // At most one buffer group and one texture group, so a stack array + // rather than a vector -- this used to heap-allocate for two + // entries on every barrier point. + D3D12_BARRIER_GROUP native[2]; + UINT native_count = 0; if (!buffers.empty()) { - D3D12_BARRIER_GROUP group; + auto& group = native[native_count++]; group.Type = D3D12_BARRIER_TYPE_BUFFER; group.NumBarriers = uint(buffers.size()); group.pBufferBarriers = buffers.data(); - native.emplace_back(group); } if (!textures.empty()) { - D3D12_BARRIER_GROUP group; + auto& group = native[native_count++]; group.Type = D3D12_BARRIER_TYPE_TEXTURE; group.NumBarriers = uint(textures.size()); group.pTextureBarriers = textures.data(); - native.emplace_back(group); } - if (!native.empty()) - m_commandList->Barrier((UINT)native.size(), native.data()); + if (native_count) + m_commandList->Barrier(native_count, native); } } } diff --git a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.ixx b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.ixx index c036ec58..ac86e85c 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.CommandList.ixx +++ b/sources/HAL/API/D3D12/HAL.D3D12.CommandList.ixx @@ -22,6 +22,15 @@ export namespace HAL { D3D_PRIMITIVE_TOPOLOGY native_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; CommandListType type; Device* m_device = nullptr; + + // Scratch for transitions(), kept alive between calls so their + // capacity survives. Members rather than locals or thread_local: + // transitions() is called once per barrier point (hundreds of times + // per frame) and a fresh vector would malloc/free every time; a + // list is only ever replayed by one thread at a time, so per-list + // storage needs no synchronisation and costs no TLS lookup. + std::vector scratch_textures; + std::vector scratch_buffers; public: D3D::CommandList get_native() const { return m_commandList; diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp index 2a4d8aa9..4be0e788 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Queue.cpp @@ -7,6 +7,11 @@ import HAL; namespace HAL { + // A GPU-decompressed DirectStorage request whose compressed size approaches + // or exceeds the staging buffer silently decodes to all-zero (no error, no + // validation warning) instead of failing loudly - this is what caused the + // 8192^2-texture mip-0-black bug. Assert instead of letting that recur. + static constexpr UINT64 DS_STAGING_BUFFER_SIZE = 128 * 1024 * 1024; // NOTE: the shared HAL::Queue(CommandListType, Device&) constructor is // defined in the backend-neutral sources/HAL/HAL.Queue.cpp (it just calls @@ -106,7 +111,11 @@ namespace HAL TEST(device, DStorageGetFactory(IID_PPV_ARGS(&factory))); if constexpr (Debug::CheckErrors) factory->SetDebugFlags(DSTORAGE_DEBUG_BREAK_ON_ERROR | DSTORAGE_DEBUG_SHOW_ERRORS); - factory->SetStagingBufferSize(32 * 1024 * 1024); + // 32MB was silently dropping GPU-decompressed subresources whose request + // size approached/exceeded it (observed: an 8192^2 mip, ~21MB compressed / + // 64MB uncompressed, decoded to all-zero while every smaller subresource + // of the same texture was fine). 128MB gives headroom for large top mips. + factory->SetStagingBufferSize(static_cast(DS_STAGING_BUFFER_SIZE)); // Create a DirectStorage queue which will be used to load data into a // buffer on the GPU. @@ -184,6 +193,17 @@ namespace HAL ASSERT(request.Source.File.Size == srequest.size); ASSERT(request.UncompressedSize == srequest.uncompressed_size); + // A GPU-decompressed (compressed==true) request whose compressed size is + // at/above the staging buffer decodes to all-zero with no HRESULT error + // and no debug-layer warning - see DS_STAGING_BUFFER_SIZE above. Assert + // on the compressed size, since that's what's DMA'd into the staging + // buffer; if this trips for a request whose compressed size looks safely + // under the limit, the real constraint may be the uncompressed size or a + // multi-request-in-flight sum instead - check that before just raising + // the limit again. + if (srequest.compressed) + ASSERT(srequest.size < DS_STAGING_BUFFER_SIZE); + std::visit(overloaded{ [&](const StorageRequest::Buffer& buffer) { @@ -266,8 +286,8 @@ namespace HAL PROFILE(L"Queue::execute"); // Accumulate lists into one ExecuteCommandLists — no per-list flush. - // Legal because TaskBuilder::link_list_groups chains resource state - // across list boundaries within a group: D3D12 forbids, within one + // Legal because CommandListGroup::compile_transitions chains resource + // state across list boundaries within a group: D3D12 forbids, within one // scope, accessing a resource after a SyncAfter == SYNC_NONE barrier or // emitting a SyncBefore == SYNC_NONE barrier once it has been accessed // (#1417). gpu_wait/signal still flush at real sync points. diff --git a/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp b/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp index 71c2c56e..61ad5af0 100644 --- a/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp +++ b/sources/HAL/API/D3D12/HAL.D3D12.Resource.cpp @@ -139,13 +139,11 @@ namespace HAL initialLayout = TextureLayout::COPY_DEST; // probably update from CPU or copy } - if (check(_desc.Flags & ResFlags::DisableStateTracking)) - { - if (check(_desc.Flags & ResFlags::ShaderResource)) - initialLayout = TextureLayout::SHADER_RESOURCE;// | TextureLayout::COPY_SOURCE; - else - initialLayout = TextureLayout::COPY_SOURCE; - } + // (Used to force SHADER_RESOURCE / COPY_SOURCE for + // DisableStateTracking resources. That flag is gone, and its + // replacement -- frame_graph_managed -- is set after creation, so it + // cannot be consulted here. Nothing assumes the creation layout + // anyway: a resource's first-ever barrier enters from UNDEFINED.) } else @@ -206,7 +204,6 @@ namespace HAL else this->address = 0; - THIS->state_manager.init_subres(device.Subresources(THIS->get_desc()), layout); if (THIS->heap_type == HeapType::RESERVED) THIS->tiled_manager.init_tilings(); @@ -235,13 +232,13 @@ namespace HAL // resource creation. init(device, desc, address, initialLayout, clear_value); } - Resource::Resource(Device& device, const ResourceDesc& desc, HeapType heap_type, TextureLayout initialLayout, vec4 clear_value) :state_manager(this), tiled_manager(this) + Resource::Resource(Device& device, const ResourceDesc& desc, HeapType heap_type, TextureLayout initialLayout, vec4 clear_value) : tiled_manager(this) { _init(device, desc, heap_type, initialLayout, clear_value); } - Resource::Resource(Device& device, const ResourceDesc& desc, ResourceHandle handle, bool own) :state_manager(this), tiled_manager(this) + Resource::Resource(Device& device, const ResourceDesc& desc, ResourceHandle handle, bool own) :tiled_manager(this) { auto t = CounterManager::get().start_count(); m_device = &device; @@ -256,7 +253,7 @@ namespace HAL } } - Resource::Resource(Device& device, const ResourceDesc& desc, PlacementAddress address) :state_manager(this), tiled_manager(this) + Resource::Resource(Device& device, const ResourceDesc& desc, PlacementAddress address) : tiled_manager(this) { auto t = CounterManager::get().start_count(); m_device = &device; @@ -264,7 +261,7 @@ namespace HAL init(device, desc, address, TextureLayout::UNDEFINED); } - Resource::Resource(Device& device, const API::NativeImportHandle& handle, TextureLayout initialLayout) :state_manager(this), tiled_manager(this) + Resource::Resource(Device& device, const API::NativeImportHandle& handle, TextureLayout initialLayout) : tiled_manager(this) { native_resource = handle.resource; m_device = &device; diff --git a/sources/HAL/API/D3D12/HAL.Utils.cpp b/sources/HAL/API/D3D12/HAL.Utils.cpp index 197fedf1..0d57faee 100644 --- a/sources/HAL/API/D3D12/HAL.Utils.cpp +++ b/sources/HAL/API/D3D12/HAL.Utils.cpp @@ -723,6 +723,12 @@ D3D12_BARRIER_SYNC to_native(BarrierSync flags) result = D3D12_BARRIER_SYNC::D3D12_BARRIER_SYNC_SPLIT; } + if (check(flags & BarrierSync::ALL)) + { + ASSERT(flags == BarrierSync::ALL); + result = D3D12_BARRIER_SYNC::D3D12_BARRIER_SYNC_ALL; + } + return result; } diff --git a/sources/HAL/API/Vulkan/HAL.Vulkan.Resource.cpp b/sources/HAL/API/Vulkan/HAL.Vulkan.Resource.cpp index 5ae376ec..95f2821a 100644 --- a/sources/HAL/API/Vulkan/HAL.Vulkan.Resource.cpp +++ b/sources/HAL/API/Vulkan/HAL.Vulkan.Resource.cpp @@ -88,16 +88,10 @@ namespace HAL initialLayout = TextureLayout::COPY_DEST; } - if (check(_desc.Flags & ResFlags::DisableStateTracking)) - { - if (check(_desc.Flags & ResFlags::ShaderResource)) - initialLayout = TextureLayout::SHADER_RESOURCE; - else - initialLayout = TextureLayout::COPY_SOURCE; - } + // (See the D3D12 path: the DisableStateTracking override is gone + // with the flag, and frame_graph_managed is set post-creation.) } - THIS->state_manager.init_subres(device.Subresources(THIS->get_desc()), initialLayout); if (THIS->heap_type == HeapType::RESERVED) { @@ -288,7 +282,6 @@ namespace HAL if (layout == TextureLayout::PRESENT) THIS->desc.Flags |= ResFlags::Swapchain; - THIS->state_manager.init_subres(device.Subresources(THIS->get_desc()), layout); } } @@ -305,13 +298,13 @@ namespace HAL Resource::Resource(Device& device, const ResourceDesc& desc, HeapType heap_type, TextureLayout initialLayout, vec4 clear_value) - : state_manager(this), tiled_manager(this) + : tiled_manager(this) { _init(device, desc, heap_type, initialLayout, clear_value); } Resource::Resource(Device& device, const ResourceDesc& desc, ResourceHandle handle, bool own) - : state_manager(this), tiled_manager(this) + : tiled_manager(this) { m_device = &device; PlacementAddress address = { handle.get_heap().get(), handle.get_offset() }; @@ -321,14 +314,14 @@ namespace HAL } Resource::Resource(Device& device, const ResourceDesc& desc, PlacementAddress address) - : state_manager(this), tiled_manager(this) + : tiled_manager(this) { m_device = &device; init(device, desc, address, TextureLayout::UNDEFINED); } Resource::Resource(Device& device, const API::NativeImportHandle& handle, TextureLayout initialLayout) - : state_manager(this), tiled_manager(this) + : tiled_manager(this) { m_device = &device; init(handle, initialLayout, device); diff --git a/sources/HAL/API/Vulkan/HAL.Vulkan.Swapchain.cpp b/sources/HAL/API/Vulkan/HAL.Vulkan.Swapchain.cpp index a7d8478a..4a7ca193 100644 --- a/sources/HAL/API/Vulkan/HAL.Vulkan.Swapchain.cpp +++ b/sources/HAL/API/Vulkan/HAL.Vulkan.Swapchain.cpp @@ -601,14 +601,10 @@ namespace HAL pi.pWaitSemaphores = (wait_sem != VK_NULL_HANDLE) ? &wait_sem : nullptr; VkResult pr = api_queue.present(pi); - // 3) Re-assert initial_layout=PRESENT so next frame's compile_transitions - // emits a fresh PRESENT→COLOR_ATTACHMENT barrier. - if (presented_image < static_cast(frames.size()) && - frames[presented_image].m_renderTarget) - { - frames[presented_image].m_renderTarget->get_state_manager() - .init_subres(1, TextureLayout::PRESENT); - } + // 3) The back buffer is back at its resting layout (PRESENT) by + // construction now -- resting_layout() derives it from the + // Swapchain flag, and every list returns a resource to rest -- + // so nothing has to be re-asserted here. // 4) Acquire the NEXT image (free-running ring slot — NOT keyed by image // index) and wire its wait semaphore into the next render submit. diff --git a/sources/HAL/API/Vulkan/HAL.Vulkan.Utils.cpp b/sources/HAL/API/Vulkan/HAL.Vulkan.Utils.cpp index 869f5ca5..991d8599 100644 --- a/sources/HAL/API/Vulkan/HAL.Vulkan.Utils.cpp +++ b/sources/HAL/API/Vulkan/HAL.Vulkan.Utils.cpp @@ -267,7 +267,7 @@ uint32_t to_native_resource_state(TextureLayout layout) VkPipelineStageFlags2 to_native_stage(BarrierSync sync) { if (sync == BarrierSync::NONE) return VK_PIPELINE_STAGE_2_NONE; - //if (check(sync & BarrierSync::ALL)) return VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + if (check(sync & BarrierSync::ALL)) return VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; VkPipelineStageFlags2 result = VK_PIPELINE_STAGE_2_NONE; if (check(sync & BarrierSync::DRAW)) result |= VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; diff --git a/sources/HAL/HAL.CommandList.cpp b/sources/HAL/HAL.CommandList.cpp index 92acd9ea..35ada3d4 100644 --- a/sources/HAL/HAL.CommandList.cpp +++ b/sources/HAL/HAL.CommandList.cpp @@ -9,78 +9,84 @@ import Core; import HAL; // File-local template — full HAL::Resource available here, no std::function overhead. +// +// Reports each resource a view touches ONCE, with the SubresRange it covers, +// rather than calling back per subresource. A view already IS a range -- the +// mip/array/plane fields below are the same six numbers -- so shredding it here +// only to have the caller (and formerly the backend) put it back together was +// wasted work: a 256-slice view meant 256 callbacks, 256 shared_ptr passes and +// 256 identical resource comparisons in the caller's filter. +// +// SubresRange::all() is still preferred wherever the view covers the whole +// resource: D3D12 has a dedicated sentinel for that and it stays cheaper than an +// equivalent range all the way down. template static void visit_subres(const HAL::ResourceInfo& info, F&& f) { + using HAL::SubresRange; + std::visit(overloaded{ [&](const HAL::Views::ShaderResource& v) { std::visit(overloaded{ - [&](const HAL::Views::ShaderResource::Buffer&) { f(v.Resource, HAL::ALL_SUBRESOURCES); }, + [&](const HAL::Views::ShaderResource::Buffer&) { f(v.Resource, SubresRange::all()); }, [&](const HAL::Views::ShaderResource::Texture2D& t) { auto& desc = v.Resource->get_desc().as_texture(); if (t.MipLevels == desc.MipLevels && t.MostDetailedMip == 0 && desc.is2D()) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - for (auto mip = t.MostDetailedMip; mip < t.MostDetailedMip + t.MipLevels; mip++) - f(v.Resource, desc.CalcSubresource(mip, 0, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MostDetailedMip, t.MipLevels, 0, 1, t.PlaneSlice, 1 }); }, [&](const HAL::Views::ShaderResource::Texture2DArray& t) { auto& desc = v.Resource->get_desc().as_texture(); if (t.MipLevels == desc.MipLevels && t.MostDetailedMip == 0 && t.FirstArraySlice == 0 && t.ArraySize == desc.ArraySize && desc.is2D()) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - for (auto mip = t.MostDetailedMip; mip < t.MostDetailedMip + t.MipLevels; mip++) - for (auto arr = t.FirstArraySlice; arr < t.FirstArraySlice + t.ArraySize; arr++) - f(v.Resource, desc.CalcSubresource(mip, arr, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MostDetailedMip, t.MipLevels, t.FirstArraySlice, t.ArraySize, t.PlaneSlice, 1 }); }, [&](const HAL::Views::ShaderResource::Texture3D& t) { auto& desc = v.Resource->get_desc().as_texture(); if (t.MipLevels == desc.MipLevels && t.MostDetailedMip == 0) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - for (auto mip = t.MostDetailedMip; mip < t.MostDetailedMip + t.MipLevels; mip++) - f(v.Resource, desc.CalcSubresource(mip, 0, 0)); + f(v.Resource, SubresRange{ t.MostDetailedMip, t.MipLevels, 0, 1, 0, 1 }); }, [&](const HAL::Views::ShaderResource::Cube& t) { auto& desc = v.Resource->get_desc().as_texture(); if (t.MipLevels == desc.MipLevels && t.MostDetailedMip == 0) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - for (auto mip = t.MostDetailedMip; mip < t.MostDetailedMip + t.MipLevels; mip++) - for (auto arr = 0; arr < 6; arr++) - f(v.Resource, desc.CalcSubresource(mip, arr, 0)); + f(v.Resource, SubresRange{ t.MostDetailedMip, t.MipLevels, 0, 6, 0, 1 }); }, - [&](const HAL::Views::ShaderResource::Raytracing&) { f(v.Resource, HAL::ALL_SUBRESOURCES); }, + [&](const HAL::Views::ShaderResource::Raytracing&) { f(v.Resource, SubresRange::all()); }, [&](auto) { ASSERT(false); } }, v.View); }, [&](const HAL::Views::UnorderedAccess& v) { std::visit(overloaded{ [&](const HAL::Views::UnorderedAccess::Buffer& b) { - f(v.Resource, HAL::ALL_SUBRESOURCES); - if (b.CounterResource) f(b.CounterResource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); + if (b.CounterResource) f(b.CounterResource, SubresRange::all()); }, [&](const HAL::Views::UnorderedAccess::Texture2D& t) { auto& desc = v.Resource->get_desc().as_texture(); if (desc.MipLevels == 1 && desc.is2D()) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - f(v.Resource, desc.CalcSubresource(t.MipSlice, 0, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MipSlice, 1, 0, 1, t.PlaneSlice, 1 }); }, [&](const HAL::Views::UnorderedAccess::Texture2DArray& t) { auto& desc = v.Resource->get_desc().as_texture(); if (desc.MipLevels == 1 && desc.is2D() && desc.ArraySize == 1) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - for (auto arr = t.FirstArraySlice; arr < t.FirstArraySlice + t.ArraySize; arr++) - f(v.Resource, desc.CalcSubresource(t.MipSlice, arr, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MipSlice, 1, t.FirstArraySlice, t.ArraySize, t.PlaneSlice, 1 }); }, [&](const HAL::Views::UnorderedAccess::Texture3D& t) { auto& desc = v.Resource->get_desc().as_texture(); if (t.FirstWSlice == 0 && t.WSize == desc.Dimensions.z && desc.MipLevels == 1) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - f(v.Resource, desc.CalcSubresource(t.MipSlice, 0, 0)); + f(v.Resource, SubresRange{ t.MipSlice, 1, 0, 1, 0, 1 }); }, [&](auto) { ASSERT(false); } }, v.View); @@ -90,14 +96,12 @@ static void visit_subres(const HAL::ResourceInfo& info, F&& f) [&](const HAL::Views::RenderTarget::Texture2D& t) { auto& desc = v.Resource->get_desc().as_texture(); if (desc.MipLevels == 1 && desc.is2D() == 1) - f(v.Resource, HAL::ALL_SUBRESOURCES); + f(v.Resource, SubresRange::all()); else - f(v.Resource, desc.CalcSubresource(t.MipSlice, 0, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MipSlice, 1, 0, 1, t.PlaneSlice, 1 }); }, [&](const HAL::Views::RenderTarget::Texture2DArray& t) { - auto& desc = v.Resource->get_desc().as_texture(); - for (auto arr = t.FirstArraySlice; arr < t.FirstArraySlice + t.ArraySize; arr++) - f(v.Resource, desc.CalcSubresource(t.MipSlice, arr, t.PlaneSlice)); + f(v.Resource, SubresRange{ t.MipSlice, 1, t.FirstArraySlice, t.ArraySize, t.PlaneSlice, 1 }); }, [&](auto) { ASSERT(false); } }, v.View); @@ -105,18 +109,15 @@ static void visit_subres(const HAL::ResourceInfo& info, F&& f) [&](const HAL::Views::DepthStencil& v) { std::visit(overloaded{ [&](const HAL::Views::DepthStencil::Texture2D& t) { - auto& desc = v.Resource->get_desc().as_texture(); - f(v.Resource, desc.CalcSubresource(t.MipSlice, 0, 0)); + f(v.Resource, SubresRange{ t.MipSlice, 1, 0, 1, 0, 1 }); }, [&](const HAL::Views::DepthStencil::Texture2DArray& t) { - auto& desc = v.Resource->get_desc().as_texture(); - for (auto arr = t.FirstArraySlice; arr < t.FirstArraySlice + t.ArraySize; arr++) - f(v.Resource, desc.CalcSubresource(t.MipSlice, arr, 0)); + f(v.Resource, SubresRange{ t.MipSlice, 1, t.FirstArraySlice, t.ArraySize, 0, 1 }); }, [&](auto) { ASSERT(false); } }, v.View); }, - [&](const HAL::Views::ConstantBuffer& v) { f(v.Resource, HAL::ALL_SUBRESOURCES); }, + [&](const HAL::Views::ConstantBuffer& v) { f(v.Resource, SubresRange::all()); }, [&](auto) { ASSERT(false); } }, info.view); } @@ -305,6 +306,10 @@ namespace HAL { current_pipeline = nullptr; + // Reserve the last operation's barriers_after point -- nothing follows + // it to trigger the close in begin_op. + end_op(); + if (graphics) graphics->end(); if (compute) compute->end(); @@ -374,17 +379,20 @@ namespace HAL on_execute_funcs.emplace_back(f); } + // Compile a standalone list -- one that is not part of a FrameGraph batch. + // It still goes through a group (of one), because that is what computes the + // barriers and then replays the list; running either half directly here + // would be a second, independent computation over the same command stream. void Sendable::compile() { if (active) end(); - - dynamic_cast(this)->compile_transitions(); - - auto ca = frame_resources->get_ca(type); - compiler.compile(*ca); - frame_resources->free_ca(ca); + CommandListGroup group; + group.add(dynamic_cast(this)->get_ptr()); + group.plan_resources(); + group.compile_transitions(); + group.compile(); } FenceWaiter Sendable::execute(std::function f) @@ -396,9 +404,9 @@ namespace HAL on_execute_funcs.emplace_back(f); - std::list lists; - lists.emplace_back(dynamic_cast(this)->get_ptr()); - auto fence = dynamic_cast(this)->get_device().get_queue(type)->execute(lists); + CommandListGroup group; + group.add(dynamic_cast(this)->get_ptr()); + auto fence = dynamic_cast(this)->get_device().get_queue(type)->execute(group); return fence; } @@ -483,11 +491,17 @@ namespace HAL { PROFILE(L"pre_command"); base.pre_command(*this, BarrierSync::DRAW); } + // Changing the render target ends the batch: see Transitions::break_op. + // Must come after pre_command, which is what establishes the operation + // class -- breaking first would split the PREVIOUS class and leave an + // empty operation of it behind. + get_base().break_op(); + { PROFILE(L"rt_transitions"); for (uint i = 0; i < table_rtv.get_count(); i++) - get_base().transition(table_rtv[i].get_resource_info()); + get_base().add_resource_usage(table_rtv[i].get_resource_info()); if (table_dsv) - get_base().transition(table_dsv.get_resource_info()); + get_base().add_resource_usage(table_dsv.get_resource_info()); } if (check(options & RTOptions::SetHandles)) @@ -622,7 +636,7 @@ namespace HAL PROFILE_GPU(L"draw_indexed"); base.pre_command(*this, BarrierSync::DRAW); - get_base().transition(index.Resource, ResourceStates::INDEX_BUFFER); + get_base().add_resource_usage(index.Resource, ResourceStates::INDEX_BUFFER); list->set_index_buffer(index); list->draw_indexed(index_count, index_offset, vertex_offset, instance_count, instance_offset); @@ -698,7 +712,7 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(resource, ResourceStates::COPY_DEST); + base.add_resource_usage(resource, ResourceStates::COPY_DEST); auto info = base.place_data(size); std::memcpy(info.get_cpu_data(), data, size); @@ -718,7 +732,7 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(resource, ResourceStates::COPY_DEST, sub_resource); + base.add_resource_usage(resource, ResourceStates::COPY_DEST, sub_resource); auto layout = base.get_device().get_texture_layout(resource->get_desc(), sub_resource, box); auto info = base.place_data(layout.size, layout.alignment); @@ -778,7 +792,7 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(resource, ResourceStates::COPY_SOURCE, sub_resource); + base.add_resource_usage(resource, ResourceStates::COPY_SOURCE, sub_resource); auto layout = base.get_device().get_texture_layout(resource->get_desc(), sub_resource, box); auto info = base.read_data(layout.size, layout.alignment, static_cast(base.get_type())); @@ -802,7 +816,7 @@ namespace HAL std::function, texture_layout)> f) { base.pre_command(*this, BarrierSync::COPY); - base.transition(resource, ResourceStates::COPY_SOURCE, sub_resource); + base.add_resource_usage(resource, ResourceStates::COPY_SOURCE, sub_resource); auto layout = base.get_device().get_texture_layout(resource->get_desc(), sub_resource); auto info = base.read_data(layout.size, layout.alignment, static_cast(base.get_type())); @@ -837,7 +851,7 @@ namespace HAL base.pre_command(*this, BarrierSync::COPY); - base.transition(resource, ResourceStates::COPY_SOURCE); + base.add_resource_usage(resource, ResourceStates::COPY_SOURCE); auto info = base.read_data(size, GPUEntityStorageInterface::DEFAULT_ALIGN, static_cast(base.get_type())); list->copy_buffer(info.resource, info.resource_offset, resource, offset, size); @@ -899,369 +913,122 @@ namespace HAL } - void Transitions::create_usage_point(BarrierSync operation, bool end) - - { - PROFILE(L"create_usage_point"); - auto prev_point = usage_points.empty() ? nullptr : &usage_points.back(); - auto* point = &usage_points.emplace_back(type); - if (prev_point) prev_point->next_point = point; - point->prev_point = prev_point; point->cmd_list = this; point->start = !end; point->index = static_cast(usage_points.size()); point->operation = operation; - compiler.func_barrier(point); - } - - // Open a new batch of `op`, continue the current one if same, or close - // the previous (different class) and open a new one. + // Grow this list's operation sequence: a run of same-class work stays + // ONE CmdListOperation, so only a change of class appends a new one. + // + // Reserves the barrier points as it goes, which is what fixes their + // POSITION in the command stream (the groups themselves are filled + // in later): the outgoing operation's barriers_after closes right + // after its last command, then the new operation's barriers_before + // opens immediately ahead of its first. Recording a list therefore + // produces + // [op0.before] op0 cmds [op0.after] [op1.before] op1 cmds ... void Transitions::begin_op(BarrierSync op) { - if (HAL::Debug::EnableOpBatching && open_op == op) - return; // same class → keep open - if (open_op != BarrierSync::NONE) - create_usage_point(open_op, false); // close previous batch - create_usage_point(op); // open new batch - open_op = op; - current_batch_id++; // fresh batch id - } + // Counts commands, not operations -- a run of same-class work + // merges below but each call is still a distinct dispatch/draw. + op_step++; - // Close the currently open batch (list end / flush). - void Transitions::close_op() - { - if (open_op == BarrierSync::NONE) return; - create_usage_point(open_op, false); - open_op = BarrierSync::NONE; - } + if (!operations.empty() && operations.back().type == op) + return; // same class -> keep growing - // Accesses that write memory. A dependency on one of these needs - // ordering even when the state itself is unchanged. - static bool is_write_access(ResourceState s) - { - return check(s.access & (BarrierAccess::UNORDERED_ACCESS - | BarrierAccess::RENDER_TARGET - | BarrierAccess::DEPTH_STENCIL_WRITE - | BarrierAccess::COPY_DEST - | BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_WRITE)); + end_op(); + + auto& operation = operations.emplace_back(type, op, static_cast(operations.size())); + compiler.func_barrier(&operation.barriers_before, operation.index, false, operation.type); + op_first_step = op_step; } - // Split the open batch when this use needs a barrier against an - // earlier op in the same batch. Two independent reasons: - // 1. a real state change is required (different, non-mergeable), or - // 2. the state is unchanged but there is a data dependency on the - // same subresource -- RAW/WAW (earlier write) or WAR. - // (2) is what a state-only test misses: a mip chain / ping-pong / - // reduction stays UNORDERED_ACCESS throughout, so nothing "changes", - // yet each step must observe the previous one's writes. - void Transitions::batch_hazard_check(const HAL::Resource* resource, ResourceState to, UINT subres) + void Transitions::split_op() { - if (open_op == BarrierSync::NONE) return; - - auto& cpu = const_cast(resource)->get_state_manager().get_cpu_state(this); + if (operations.empty()) return; - const uint64 bit = (subres == ALL_SUBRESOURCES) ? ~0ull : (1ull << (subres & 63)); - const bool writes = is_write_access(to); + const BarrierSync op = operations.back().type; - if (cpu.batch_touch_id != current_batch_id) - { - // First touch this batch: nothing to conflict with. - cpu.batch_touch_id = current_batch_id; - cpu.batch_touch_state = to; - cpu.batch_write_mask = writes ? bit : 0; - cpu.batch_read_mask = writes ? 0 : bit; - return; - } + end_op(); - const bool state_hazard = !(cpu.batch_touch_state == to) && !merge_state(cpu.batch_touch_state, to); - const bool data_hazard = (cpu.batch_write_mask & bit) != 0 - || (writes && (cpu.batch_read_mask & bit) != 0); - - if (state_hazard || data_hazard) - { - // Split so this transition (and the ops after it) land in a new - // point positioned after the earlier op — the barrier must run - // between the two conflicting uses. current_batch_id is NOT - // bumped: it is the OP-CLASS batch epoch (advanced only by - // begin_op). A resource touched anywhere in the op-class batch - // must stay detectable across intra-batch splits — otherwise a - // later conflicting use (e.g. SMAA_blend written RT by one draw, - // read SR by the next) would miss its split after an unrelated - // split already occurred, mis-positioning the barrier. - create_usage_point(open_op, false); // close before the hazard - create_usage_point(open_op); // reopen same class after - - // Everything recorded so far is now ordered ahead of this - // use, so dependency tracking starts over from it. - cpu.batch_write_mask = 0; - cpu.batch_read_mask = 0; - } - - cpu.batch_touch_id = current_batch_id; - cpu.batch_touch_state = to; - if (writes) cpu.batch_write_mask |= bit; - else cpu.batch_read_mask |= bit; + auto& operation = operations.emplace_back(type, op, static_cast(operations.size())); + compiler.func_barrier(&operation.barriers_before, operation.index, false, operation.type); + op_first_step = op_step; } - void Transitions::compile_transitions() + void Transitions::break_op() { - if(transitions_compiled) - return; - - // Close any batch still open at list end so its resources are - // fully recorded before the usage points are walked below. - close_op(); - std::set non_tracked_resources; - - - for (auto& point : usage_points) - { - for (auto* u : point.usages) - { - auto& usage = *u; - auto prev_usage = usage.prev_usage; - - // A list-boundary marker bypassed by chain_lists: the - // neighbouring list is in this ExecuteCommandLists group, - // so state is handed over directly and this node must not - // publish a SYNC_NONE barrier (D3D12 #1417). It is also not - // an unsynchronized first touch, so keep it out of - // non_tracked_resources below. - if (usage.suppressed) continue; - - // ASSERT(!usage.debug); - if(!prev_usage) - { - bool is_automatic = check(usage.resource->get_desc().Flags &HAL::ResFlags::DisableStateTracking); - // ASSERT(!); - - if(!is_automatic&usage.resource->get_desc().is_texture()) - non_tracked_resources.insert(usage.resource); - continue; // needs to be synchronized between cmdlists. Can track here what resources needs to be checked - } - - - auto prev_state = prev_usage->wanted_state; - - // Phase 5 maximal split: the source list left this resource in a - // MIXED per-subresource state. Emit one barrier per subresource - // from its recorded real before-state; a single ALL barrier could - // only carry one before-layout and would mismatch the rest - // (#1334) while its decay stranded them (#1417). Subresources - // whose before already equals wanted (untouched, or the matching - // majority) emit nothing. After this the resource is uniform. - if (!usage.split_before.empty()) - { - for (UINT i = 0; i < usage.split_before.size(); i++) - { - auto sub_prev = usage.split_before[i]; - if (sub_prev == usage.wanted_state) continue; - - BarrierFlags sflags = BarrierFlags::NONE; - if (sub_prev == ResourceStates::UNKNOWN) - sflags |= BarrierFlags::DISCARD; - - ASSERT(sub_prev.is_valid(usage.resource->get_type())); - ASSERT(usage.wanted_state.is_valid(usage.resource->get_type())); - - point.transitions.transition(usage.resource, - sub_prev, - usage.wanted_state, - i, sflags); - } - continue; - } - - - if (prev_state == usage.wanted_state) - { - // No transition needed -- but if batch_hazard_check - // split these two uses of the same subresource apart - // (write dependency), execution/memory ordering still - // is. Under enhanced barriers a same-state barrier is - // exactly the UAV barrier that provides it. - if (usage.point != prev_usage->point && is_write_access(usage.wanted_state)) - point.transitions.transition(usage.resource, - prev_state, - usage.wanted_state, - usage.subres, BarrierFlags::NONE); - continue; - } - - - if (prev_state!=ResourceStates::UNKNOWN && usage.prev_usage->prev_usage) - { - ASSERT(prev_state.operation!=BarrierSync::NONE); - - } - ASSERT(prev_state.is_valid(usage.resource->get_type())); - ASSERT(usage.wanted_state.is_valid(usage.resource->get_type())); - - - auto a = prev_state.get_best_cmd_type(); - auto b = usage.wanted_state.get_best_cmd_type(); - - ASSERT(IsCompatible(type, a)); - ASSERT(IsCompatible(type, b)); - BarrierFlags flags = BarrierFlags::NONE; - - if (prev_state==ResourceStates::UNKNOWN) - flags |= BarrierFlags::DISCARD; - - auto prev_point = prev_usage ? prev_usage->point : nullptr; - if(prev_point)prev_point=prev_point->next_point; - - bool can_split = false;// usage.point->cmd_list!=prev_usage->point->cmd_list;//usage.debug; - - if(!point.start /*&& !usage.debug*/) can_split = false; // can split only between work i.e. only from start - if(prev_point==&point) can_split = false; // can't split if it's needed right now - if(usage.wanted_state==ResourceStates::UNKNOWN) can_split = false; // can't split if it's deactivated. probably some new resources will be activated shortly after. we dont know. we do not track it. we dont want to know. thats it. final. - if(usage.wanted_state.access==BarrierAccess::NO_ACCESS) can_split = false; // resource or it's memory will not be used anymore. There is no end point for that(or is it? Last one in the list?) - - - // if(usage.next_usage&& usage.point->cmd_list==usage.next_usage->point->cmd_list) - ASSERT(!(usage.next_usage&&usage.wanted_state.operation==BarrierSync::NONE)); - - if (usage.resource->debug_transitions) - Log::get() << "TRANSITION" << prev_state << " " << usage.wanted_state << "subres: " << usage.subres << Log::endl; - - HAL::Debug::BarrierBreakpoints::check_usage(usage.resource->name, usage.subres, usage.wanted_state); + // Nothing recorded into the current operation yet -- it already + // IS the fresh one the caller wants, and splitting would strand + // an empty operation with two reserved barrier points. + if (operations.empty() || op_step == op_first_step) return; - if (can_split) - { - ASSERT(prev_point->start); - auto sync_state = usage.wanted_state; - - sync_state.operation = BarrierSync::SPLIT; - - prev_point->transitions.transition(usage.resource, - prev_state, - sync_state, - usage.subres, flags); - - - point.transitions.transition(usage.resource, - sync_state, - usage.wanted_state, - usage.subres, flags); - - } - else - { - point.transitions.transition(usage.resource, - prev_state, - usage.wanted_state, - usage.subres, flags); - - } - - - } - } - - transitions_compiled = true; - - - for (auto& resource : non_tracked_resources) - { - auto& cpu_state = resource->get_state_manager().get_state(this); - - auto transition_one = [&](UINT subres) { - - auto& subres_cpu = cpu_state.get_subres_state(subres); - - if (!subres_cpu.used) return; - ASSERT(subres_cpu.used); - - // A later list in the same ExecuteCommandLists group still - // uses this subresource, and its state was chained across - // the boundary (chain_lists) — releasing it here to - // NO_ACCESS/SYNC_NONE would make it untouchable for the - // rest of the scope (D3D12 #1417). The group's last user - // releases it. Checked per subresource so that mips which - // were NOT carried forward still decay to initial_layout. - if (subres_cpu.skip_end_decay) return; - - - - auto target = ResourceStates::NO_ACCESS; - target.layout = resource->get_state_manager().initial_layout; - - auto end_point = subres_cpu.last_usage->point->next_point; - - // A bare transition-only list (built with the public - // transition() API, e.g. the upload/promote list) never opened - // an op batch, so close_op()'s open_op==NONE guard skipped the - // trailing point — the last usage sits at the final point with - // no next_point. Append one now (usage_points is a deque, so - // existing UsagePoint* stay valid) so the release lands AFTER - // the last usage. Op-batched lists already have this point. - if (!end_point) - { - create_usage_point(BarrierSync::NONE, false); - end_point = subres_cpu.last_usage->point->next_point; - } - - - if (resource->debug_transitions) - Log::get() << "TRANSITION" << subres_cpu.last_usage->wanted_state << " " << target << "subres: " << subres << Log::endl; + split_op(); + } + // Close the operation currently at the back, reserving the point its + // barriers_after will be emitted at. Idempotent-by-position: only the + // transition from one operation to the next (or to end of list) calls + // it, so a group is never reserved twice. + void Transitions::end_op() + { + if (operations.empty()) return; + + // Idempotent: reserving the same barriers_after twice emits that + // group twice, and the second run's LayoutBefore is stale by + // definition. CommandList::end() closes the final operation, and + // anything appending afterwards (process_transitions) would + // otherwise close it a second time. + auto& operation = operations.back(); + if (operation.closed) return; + operation.closed = true; + + compiler.func_barrier(&operation.barriers_after, operation.index, true, operation.type); + } - end_point->transitions.transition(resource, - subres_cpu.last_usage->wanted_state, - target, - subres, BarrierFlags::NONE); - }; + void Transitions::transition_present(const HAL::Resource* resource_ptr) + { + begin_op(BarrierSync::NONE); + add_resource_usage(resource_ptr, { BarrierSync::NONE, BarrierAccess::NO_ACCESS, TextureLayout::PRESENT }, ALL_SUBRESOURCES); + } - for (int i = 0; i < cpu_state.subres.size(); i++) - transition_one(i); - } - + void Transitions::begin_external_op(BarrierSync op) + { + // begin_op without its merge check: a run of same-class work + // merges by design, but external work must be its own operation + // or its barriers would be hoisted in front of unrelated commands + // that happen to share the class. + end_op(); + + auto& operation = operations.emplace_back(type, op, static_cast(operations.size())); + compiler.func_barrier(&operation.barriers_before, operation.index, false, operation.type); } - HAL::ResourceUsage* Transitions::add_usage(const HAL::Resource* resource, UINT subres, ResourceState state, HAL::TransitionType type) + void Transitions::transition_to(const HAL::Resource* resource, ResourceState state) { - HAL::UsagePoint* point = nullptr; - - if (type == HAL::TransitionType::LAST) point = &usage_points.back(); - if (type == HAL::TransitionType::ZERO) point = &usage_points.front(); - - HAL::ResourceUsage* usage_ptr; - if (pool_used < usage_pool.size()) - usage_ptr = &usage_pool[pool_used]; + // split_op, not begin_op: begin_op would merge into the last + // operation whenever the classes happen to match, and the target + // state would then be merged with that operation's real use + // instead of following it. + if (operations.empty()) + begin_op(BarrierSync::NONE); else - usage_pool.emplace_back(), usage_ptr = &usage_pool.back(); - pool_used++; - - HAL::ResourceUsage& usage = *usage_ptr; - point->usages.push_back(usage_ptr); + split_op(); - usage.resource = const_cast(resource); - usage.subres = subres; - usage.wanted_state = state; - usage.prev_usage = nullptr; - usage.next_usage = nullptr; - usage.point = point; - usage.debug = false; - usage.suppressed = false; - usage.split_before.clear(); + add_resource_usage(resource, state, ALL_SUBRESOURCES); - HAL::Debug::BarrierBreakpoints::check_usage(resource->name, subres, state); - - return usage_ptr; + // Close it here: this runs after CommandList::end() has already + // closed the list, so nothing else will reserve this operation's + // barriers_after, and any resource whose last use lands in it + // would lose its trailing barriers. end_op() is idempotent, so a + // later split_op() on the same list stays correct. + end_op(); } - - - void Transitions::transition_present(const HAL::Resource* resource_ptr) + void Transitions::transition_to_rest(const HAL::Resource* resource) { - - create_usage_point(BarrierSync::NONE); - - transition(resource_ptr, { BarrierSync::NONE, BarrierAccess::NO_ACCESS, TextureLayout::PRESENT }, ALL_SUBRESOURCES); - - create_usage_point(BarrierSync::NONE,false); + transition_to(resource, state_at_rest(resting_layout(resource))); } - void Transitions::transition(const ResourceInfo& info, BarrierSync operation ) + void Transitions::add_resource_usage(const ResourceInfo& info, BarrierSync operation, bool whole_resource ) { if (!info.is_valid()) return; ResourceState target_state;//= ResourceState::COMMON; @@ -1332,34 +1099,87 @@ namespace HAL if (resource->get_heap_type() == HeapType::DEFAULT || resource->get_heap_type() == HeapType::RESERVED) { use_resource(resource); - visit_subres(info, [&](const HAL::Resource::ptr&, UINT subres) { - use_resource(resource); - batch_hazard_check(resource, target_state, subres); - resource->get_state_manager().transition(this, target_state, subres); - }); + + // Record the VIEW, not its expanded subresource list. The view + // already names its mip/array/plane range, so expanding it here + // would pay a per-subresource cost on every bind for information + // that can be recovered later, once, only for resources that + // actually need a barrier. + // + // [Barrier = ALL] drops the view entirely and records a bare + // whole-resource use: there is then no range to expand at all, so + // the resource stays on compile_transitions' single + // current[ALL_SUBRESOURCES] entry instead of diverging into one + // per slice. + record_usage(resource, whole_resource + ? HAL::OperationUsage(ALL_SUBRESOURCES, target_state) + : HAL::OperationUsage(&info, target_state)); } } - void Transitions::transition(const Resource::ptr& resource, ResourceState to, UINT subres) + void Transitions::add_resource_usage(const Resource::ptr& resource, ResourceState to, UINT subres) { - transition(resource.get(), to, subres); + add_resource_usage(resource.get(), to, subres); } void Transitions::use_resource(const Resource* resource) { - auto& state = const_cast(resource)->get_state_manager().get_cpu_state(this); - if (!state.used) + auto& state = const_cast(resource)->get_state(this); + if (!state.listed) { - state.used = true; + state.listed = true; used_resources.emplace_back(const_cast(resource)); } } - void Transitions::free_resources() - { + // Append one use of `resource` to the operation currently being recorded. + // Cheap by construction: a lookup of this resource's per-list state and a + // push_back. No state comparison, no barrier decision, no subresource + // expansion -- all of that happens later, once, over the recorded + // operations. + void Transitions::record_usage(const Resource* resource, const HAL::OperationUsage& usage) + { + // A usage always belongs to an operation. Paths that transition a + // resource without issuing GPU work through pre_command -- the upload + // flush, and other direct add_resource_usage callers -- never open one, + // so open it here. Without this the usage is recorded against operation + // 0 while `operations` stays empty, and the barrier pass indexes off the + // end of it. + if (operations.empty()) + begin_op(BarrierSync::NONE); + + uint op_index = operations.back().index; + + auto& state = const_cast(resource)->get_state(this); + + // Unordered access is the one case where re-touching a resource inside + // a single operation is a real hazard: two dispatches writing the same + // UAV (or one writing and the next reading it) must be ordered, and the + // state does not change to signal it. Render-target and depth writes are + // ordered by the pipeline, so they are deliberately not included. + // + // `step` is what separates "bound twice for the same dispatch" -- which + // is fine, those binds merge -- from "touched again by a later dispatch + // in the same run", which needs the operation split so a barrier can sit + // between the two halves. + const bool uav_involved = check(usage.state.access & BarrierAccess::UNORDERED_ACCESS); + + for (const auto& prev : state.operations[op_index]) + { + if (prev.step == op_step) continue; // same dispatch, just another bind + if (!uav_involved && !check(prev.state.access & BarrierAccess::UNORDERED_ACCESS)) continue; + + split_op(); + op_index = operations.back().index; + break; + } + + auto& usages = state.operations[op_index]; + usages.push_back(usage); + usages.back().step = op_step; } - void Transitions::transition(const Resource* resource, ResourceState to, UINT subres) + void Transitions::add_resource_usage(const Resource* resource, ResourceState to, UINT subres) { if (!resource) return; @@ -1369,18 +1189,66 @@ namespace HAL if (resource->get_heap_type() == HeapType::DEFAULT || resource->get_heap_type() == HeapType::RESERVED) { use_resource(resource); - batch_hazard_check(resource, to, subres); - const_cast(resource)->get_state_manager().transition(this, to, subres); + + // No view here -- the subresource is named directly (copies, and + // anything else that transitions a resource without binding it). + record_usage(resource, HAL::OperationUsage(subres, to)); } } + void Transitions::set_entry_state(const HAL::Resource* resource, ResourceState state) + { + auto& tracked = const_cast(resource)->get_state(this); + tracked.has_entry_state = true; + tracked.entry_state = state; + } + + std::optional Transitions::get_exit_state(const HAL::Resource* resource) const + { + auto& tracked = const_cast(resource)->get_state(const_cast(this)); + if (tracked.operations.empty()) return std::nullopt; + + // operations is ordered by index, so the last entry is the last + // operation, and its last usage is the one that leaves the resource + // where it ends up. + const auto& usages = tracked.operations.rbegin()->second; + if (usages.empty()) return std::nullopt; + + return usages.back().state; + } + + std::optional Transitions::get_first_use_state(const HAL::Resource* resource) const + { + auto& tracked = const_cast(resource)->get_state(const_cast(this)); + if (tracked.operations.empty()) return std::nullopt; + + // Mirror of get_exit_state: ordered map, so the FIRST entry is the + // earliest operation, and its first usage is the state this list needs + // the resource to already be in when it starts. + const auto& usages = tracked.operations.begin()->second; + if (usages.empty()) return std::nullopt; + + return usages.front().state; + } + void Transitions::alias_begin(HAL::Resource* resource) { resource->get_state(this).alias_ended = false; track_object(*resource); - - const_cast(resource)->get_state_manager().alias_begin(this); + + // The aliased-into resource has no meaningful contents -- its first use + // is entered from UNKNOWN with a discard. compile_transitions already + // does that for a virgin resource, so re-arm the flag here. + // + // BOTH flags, because they gate different halves of that and only + // re-arming `virgin` produced a barrier from UNDEFINED with no DISCARD: + // `virgin` decides the before-state (from_undefined), while + // `initialized` decides whether the first write carries the discard. + // Left initialized, the resource claims UNDEFINED and then preserves + // contents that are another resource's memory. + resource->virgin = true; + resource->initialized = false; } void Transitions::alias_end(HAL::Resource* resource) @@ -1388,8 +1256,9 @@ namespace HAL track_object(*resource); - const_cast(resource)->get_state_manager().alias_end(this); - resource->get_state(this).alias_ended = true; + // End-of-aliasing barriers belong in the operation's barriers_after -- + // that group is reserved and waiting, not wired up yet. + resource->get_state(this).alias_ended = true; } @@ -1398,8 +1267,8 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(source, ResourceStates::COPY_SOURCE); - base.transition(dest, ResourceStates::COPY_DEST); + base.add_resource_usage(source, ResourceStates::COPY_SOURCE); + base.add_resource_usage(dest, ResourceStates::COPY_DEST); list->copy_buffer(dest, s_dest, source, s_source, size); @@ -1410,8 +1279,8 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(source, ResourceStates::COPY_SOURCE); - base.transition(dest, ResourceStates::COPY_DEST); + base.add_resource_usage(source, ResourceStates::COPY_SOURCE); + base.add_resource_usage(dest, ResourceStates::COPY_DEST); list->copy_resource(dest, source); base.post_command(*this, BarrierSync::COPY); } @@ -1426,8 +1295,8 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(source, ResourceStates::COPY_SOURCE, source_subres); - base.transition(dest, ResourceStates::COPY_DEST, dest_subres); + base.add_resource_usage(source, ResourceStates::COPY_SOURCE, source_subres); + base.add_resource_usage(dest, ResourceStates::COPY_DEST, dest_subres); list->copy_texture(dest, dest_subres, source, source_subres); if constexpr (Debug::CheckErrors) @@ -1440,8 +1309,8 @@ namespace HAL { base.pre_command(*this, BarrierSync::COPY); - base.transition(from, ResourceStates::COPY_SOURCE); - base.transition(to, ResourceStates::COPY_DEST); + base.add_resource_usage(from, ResourceStates::COPY_SOURCE); + base.add_resource_usage(to, ResourceStates::COPY_DEST); list->copy_texture(to, to_pos, from, from_pos, size); if constexpr (Debug::CheckErrors) TEST(base.get_device(), base.get_device().get_device_removed_reason()); @@ -1643,9 +1512,9 @@ namespace HAL base.pre_command(get_base().get_compute(), BarrierSync::COMPUTE_SHADING, &command_types.slots); } { PROFILE(L"transitions"); - if (command_buffer) get_base().transition(command_buffer, ResourceStates::INDIRECT_ARGUMENT); - if (counter_buffer) get_base().transition(counter_buffer, ResourceStates::INDIRECT_ARGUMENT); - get_base().transition(static_cast(index.Resource.get()), ResourceStates::INDEX_BUFFER); } + if (command_buffer) get_base().add_resource_usage(command_buffer, ResourceStates::INDIRECT_ARGUMENT); + if (counter_buffer) get_base().add_resource_usage(counter_buffer, ResourceStates::INDIRECT_ARGUMENT); + get_base().add_resource_usage(static_cast(index.Resource.get()), ResourceStates::INDEX_BUFFER); } list->set_index_buffer(index); @@ -1674,8 +1543,8 @@ namespace HAL base.pre_command(*this, BarrierSync::COMPUTE_SHADING); } { PROFILE(L"transitions"); - if (command_buffer) get_base().transition(command_buffer, ResourceStates::INDIRECT_ARGUMENT); - if (counter_buffer) get_base().transition(counter_buffer, ResourceStates::INDIRECT_ARGUMENT); } + if (command_buffer) get_base().add_resource_usage(command_buffer, ResourceStates::INDIRECT_ARGUMENT); + if (counter_buffer) get_base().add_resource_usage(counter_buffer, ResourceStates::INDIRECT_ARGUMENT); } list->execute_indirect( command_types, @@ -1696,22 +1565,22 @@ namespace HAL for (auto g : bottom.geometry) { - base.transition(g.IndexBuffer.resource, + base.add_resource_usage(g.IndexBuffer.resource, HAL::ResourceState(BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED)); - base.transition(g.VertexBuffer.resource, { + base.add_resource_usage(g.VertexBuffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); - base.transition(g.Transform3x4.resource, { + base.add_resource_usage(g.Transform3x4.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); } - base.transition(build_desc.DestAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); - base.transition(build_desc.SourceAccelerationStructureData.resource, + base.add_resource_usage(build_desc.DestAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); + base.add_resource_usage(build_desc.SourceAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); - base.transition(build_desc.ScratchAccelerationStructureData.resource, { + base.add_resource_usage(build_desc.ScratchAccelerationStructureData.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::UNORDERED_ACCESS, TextureLayout::UNDEFINED }); list->build_ras(build_desc, bottom); @@ -1725,14 +1594,14 @@ namespace HAL { base.pre_command(*this, BarrierSync::BUILD_RAYTRACING_ACCELERATION_STRUCTURE); - base.transition(build_desc.DestAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); - base.transition(build_desc.SourceAccelerationStructureData.resource, + base.add_resource_usage(build_desc.DestAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); + base.add_resource_usage(build_desc.SourceAccelerationStructureData.resource, ResourceStates::RAYTRACING_STRUCTURE_WRITE); - base.transition(build_desc.ScratchAccelerationStructureData.resource, { + base.add_resource_usage(build_desc.ScratchAccelerationStructureData.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::UNORDERED_ACCESS, TextureLayout::UNDEFINED }); - base.transition(top.instances.resource, { + base.add_resource_usage(top.instances.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); @@ -1748,23 +1617,17 @@ namespace HAL void Transitions::begin() { - transition_count = 0; - pool_used = 0; - open_op = BarrierSync::NONE; - current_batch_id = 0; + operations.clear(); + op_step = 0; tracked_resources.reserve(512); used_resources.reserve(256); - create_usage_point(BarrierSync::NONE, false); } void Transitions::on_execute() { - pool_used = 0; - usage_points.clear(); + operations.clear(); + op_step = 0; used_resources.clear(); - need_check_transitions.clear(); - transitions_compiled = false; - open_op = BarrierSync::NONE; } void SignatureDataSetter::commit_tables(BarrierSync operation, UsedSlots* slots) @@ -1777,8 +1640,8 @@ namespace HAL { { PROFILE(L"transitions"); - for (auto& resource_info : table.resources) - get_base().transition(*resource_info, operation); + for (auto& bound : table.resources) + get_base().add_resource_usage(*bound.info, operation, bound.whole_resource); } { PROFILE(L"set_cb"); @@ -1980,7 +1843,7 @@ namespace HAL void SignatureDataSetter::set_cb(UINT index, const Handles::CBV& cb, BarrierSync operation) { - get_base().transition(cb.get_resource_info(), operation); + get_base().add_resource_usage(cb.get_resource_info(), operation); set_const_buffer(index, 0, cb.get_offset()); } @@ -2056,17 +1919,17 @@ namespace HAL track_object(*(info.resource)); } - void CommandList::clear_uav(const Handles::UAV& h, vec4 ClearColor) + void CommandList::clear_uav(const Handles::UAV& h, vec4 ClearColor, bool whole_resource) { - create_usage_point(BarrierSync::CLEAR_UNORDERED_ACCESS_VIEW); - transition(h.get_resource_info(), BarrierSync::CLEAR_UNORDERED_ACCESS_VIEW); + begin_op(BarrierSync::CLEAR_UNORDERED_ACCESS_VIEW); + add_resource_usage(h.get_resource_info(), BarrierSync::CLEAR_UNORDERED_ACCESS_VIEW, whole_resource); compiler.clear_uav(h, ClearColor); - create_usage_point(BarrierSync::CLEAR_UNORDERED_ACCESS_VIEW, false); } - void CommandList::clear_dsv(const Handles::DSV& h, bool clear_depth, bool clear_stencil, float depth, UINT8 stencil) + void CommandList::clear_dsv(const Handles::DSV& h, bool clear_depth, bool clear_stencil, float depth, UINT8 stencil, + bool whole_resource) { - // begin_op (not a bracketing create_usage_point pair) so consecutive + // begin_op (not a bracketing barrier-point pair) so consecutive // clear_dsv calls against different subresources of the same // resource -- e.g. one per dirty VSM page slice -- automatically // merge into a single open batch instead of one barrier pair each. @@ -2074,7 +1937,7 @@ namespace HAL // list end, same as every draw/dispatch already does via // pre_command/post_command. begin_op(BarrierSync::DEPTH_STENCIL); - transition(h.get_resource_info(), BarrierSync::DEPTH_STENCIL); + add_resource_usage(h.get_resource_info(), BarrierSync::DEPTH_STENCIL, whole_resource); compiler.clear_depth_stencil(h, clear_depth, clear_stencil, depth, stencil); } @@ -2083,6 +1946,11 @@ namespace HAL return compiler.get_debug_records(); } + std::vector CommandList::take_debug_records() + { + return compiler.take_debug_records(); + } + DelayedCommandList* CommandListBase::get_native_list() { return &compiler; @@ -2091,4 +1959,468 @@ namespace HAL CopyContext::CopyContext(CommandList& base) : base(base), list(base.get_native_list()) {} const API::CommandList& TransitionCommandList::get_compiled() const { return compiler.get_list(); } + + // --- CommandListGroup --- + + // One linear pass per resource across the whole group. Cost is proportional + // to real activity, not to a resource's declared subresource count: a + // resource used whole stays a single ALL_SUBRESOURCES entry from start to + // finish and never pays per-mip anything. + void CommandListGroup::plan_resources() + { + PROFILE(L"plan_resources"); + + planned.clear(); + planned_built = true; + + for (auto& list : lists) + { + for (auto* resource : list->get_used_resources()) + { + // Dedup by linear scan rather than a hash set: a list names a + // resource at most once (TrackedResourceState::listed), so this + // only has to catch resources shared BETWEEN the group's lists, + // and `planned` is a short contiguous array of pointers. A set + // would trade that scan for an allocation per resource. + bool seen = false; + for (auto& p : planned) + if (p.resource == resource) { seen = true; break; } + if (seen) continue; + + PlannedResource p; + p.resource = resource; + p.from_undefined = resource->virgin; + + // Does this group write the resource anywhere? Only a write can + // establish contents, so only a write can own the discard. + bool writes = false; + if (!resource->initialized) + { + for (auto& l : lists) + { + auto& tracked = resource->get_state(l.get()); + for (auto& [op_index, usages] : tracked.operations) + { + for (auto& usage : usages) + if (usage.state.has_write_bits()) { writes = true; break; } + if (writes) break; + } + if (writes) break; + } + } + + if (writes) + { + p.owns_discard = true; + resource->initialized = true; + } + + resource->virgin = false; + planned.push_back(p); + } + } + } + + void CommandListGroup::compile_transitions() + { + PROFILE(L"compile_transitions"); + + // `planned` is built by plan_resources() on the submitting thread and + // is read-only here, which is what lets groups compile in parallel. It + // already holds every resource the group touches, deduplicated and in + // first-seen order, so there is no list to rebuild. + // + // Skipping plan_resources() would silently disable every first-use + // discard and enter every resource from the wrong state, so catch it. + ASSERT(planned_built); + + for (auto& res_plan : planned) + { + Resource* resource = res_plan.resource; + + // Both first-use facts are consumed ONCE within the group, so they + // are tracked group-locally rather than read back off the resource. + // The discard goes to the first write; SyncBefore=NONE goes to the + // genuinely first barrier and nothing after it, since D3D12 rejects + // NONE once the resource has been accessed (#1417). + bool discard_pending = res_plan.owns_discard; + bool undefined_pending = res_plan.from_undefined; + + // Where the group has left each subresource so far -- carried + // ACROSS lists, which is the whole point: list N+1 diffs against + // where list N actually left the resource, not against the resting + // layout. The ALL_SUBRESOURCES key is the uniform value standing in + // for every subresource with no entry of its own, so a + // whole-resource lifetime costs exactly one entry. + // Per-subresource state for this group, kept as RANGES. + // + // A view is a rectangle in (mip x slice x plane) space and so is a + // D3D12 barrier. Keying this per flat subresource forced both ends to + // be shredded and rebuilt -- one map entry and one barrier per + // subresource, thousands of them for an atlas or a Hi-Z pyramid. + // SubresRangeMap keeps the rectangle intact from the view all the way + // to D3D12_BARRIER_SUBRESOURCE_RANGE. + uint dim_mips = 1, dim_slices = 1, dim_planes = 1; + if (resource->get_desc().is_texture()) + { + const auto& tex = resource->get_desc().as_texture(); + dim_mips = std::max(1u, (uint)tex.MipLevels); + dim_slices = std::max(1u, (uint)tex.ArraySize); + dim_planes = std::max(1u, resource->get_device().Subresources(resource->get_desc()) + / (dim_mips * dim_slices)); + } + + // A bare subresource index (a use with no descriptor -- copies name + // one directly) as the rectangle it stands for. + auto index_to_range = [&](UINT subres) -> SubresRange + { + if (subres == ALL_SUBRESOURCES || !resource->get_desc().is_texture()) + return SubresRange::all(); + + const auto& tex = resource->get_desc().as_texture(); + return SubresRange::single(tex.get_mip(subres), tex.get_array(subres), tex.get_plane(subres)); + }; + + SubresRangeMap current; + current.reset(dim_mips, dim_slices, dim_planes); + + // The last operation in the group that used this resource -- its + // barriers_after is where the return to rest goes. + CmdListOperation* last_use = nullptr; + + for (auto& list : lists) + { + auto& tracked = resource->get_state(list.get()); + if (tracked.operations.empty()) continue; + + // Whoever schedules this resource may know where the previous + // group left it -- something this group cannot see for itself. + // Only meaningful before the group has touched it; once it has, + // its own tracking is authoritative. + if (current.empty() && tracked.has_entry_state) + current.assign(SubresRange::all(), tracked.entry_state); + + // tracked.operations is an ordered map, so this walks the + // operations in the order they were recorded. + for (auto& [op_index, usages] : tracked.operations) + { + // 1. Collapse everything this operation does to the resource + // into one wanted state per REGION. A resource bound + // several ways in one operation (sampled and written + // through different views) merges where the states allow. + SubresRangeMap wanted; + wanted.reset(dim_mips, dim_slices, dim_planes); + + std::vector> pending; + + auto want = [&](SubresRange range, ResourceState state) + { + // Read-only states combine; anything else cannot be + // satisfied by one barrier, so the later use wins and the + // operation runs in that state. + // + // Collected before assigning: assign() rewrites the very + // entries visit() is walking. + pending.clear(); + wanted.visit(range, [&](SubresRange piece, const ResourceState* cur) + { + ResourceState result = state; + if (cur) + { + auto merged = merge_state(*cur, state); + result = merged ? *merged : state; + } + pending.emplace_back(piece, result); + }); + + for (auto& pr : pending) wanted.assign(pr.first, pr.second); + }; + + for (auto& usage : usages) + { + if (usage.info) + { + // The view reports the rectangle it covers and it goes + // straight in -- no expansion into subresources at any + // point. visit_subres also reports side resources (a UAV + // counter buffer), so this still has to filter. + visit_subres(*usage.info, [&](const HAL::Resource::ptr& r, SubresRange range) + { + if (r.get() == resource) want(range, usage.state); + }); + } + else + { + want(index_to_range(usage.subres), usage.state); + } + } + + if (wanted.empty()) continue; + + auto& operation = list->operations[op_index]; + auto& barriers = operation.barriers_before; + + // Entering from the creation (undefined) layout. SyncBefore + // may only be NONE while D3D12 has genuinely never seen the + // resource -- once anything has touched it, NONE is rejected + // outright (#1417), so widen to ALL. AccessBefore stays + // NO_ACCESS either way: that is the only access D3D12 permits + // alongside LAYOUT_UNDEFINED. + auto from_undefined = [&]() -> ResourceState + { + if (undefined_pending) + { + undefined_pending = false; + return ResourceStates::UNKNOWN; // never seen: NONE is correct, and required + } + return ResourceState{ BarrierSync::ALL, BarrierAccess::NO_ACCESS, TextureLayout::UNDEFINED }; + }; + + auto emit = [&](SubresRange want_range, ResourceState after) + { + // The first WRITE establishes the resource's contents. Its + // memory is fresh (or freshly aliased), so discard rather + // than preserve -- and cover the WHOLE resource in one + // barrier, because D3D12 tracks initialization per + // subresource while this is a per-resource fact. A cubemap + // otherwise reports 42 uninitialized subresources (6 faces + // x 7 mips) after only the first one was discarded. + // + // Keyed on `initialized`, not `virgin`: a resource whose + // first-ever touch is a READ gets a defined layout from + // that point on but still holds nothing, so the discard has + // to wait for the write that actually fills it (#1422). + // + // Do NOT relax this to "discard on the first barrier" so + // that a read arriving before the first write gets one. + // Tried 2026-08-19 twice: unconditionally it costs 54 x + // #1422 (D3D12 counts a placed RT/DS resource as initialized + // only when the DISCARD rides a barrier INTO an RT/DS + // layout); restricted to non-RT/DS resources it "works", but + // only by making a read of never-written memory look + // legitimate. A read before the first write is a bug in the + // pass, and the debugger flagging it is the point. + if (discard_pending && after.has_write_bits()) + { + // Declare where the resource ACTUALLY is, not UNDEFINED + // unconditionally: a barrier must never disagree with + // the state we believe it starts from. Only a uniform + // whole-resource state can be used, since this barrier + // covers everything; if regions have diverged there is + // no single before-state to name and UNDEFINED (don't + // care) is the honest answer. + const ResourceState before = current.is_uniform() + ? current.uniform_state() + : from_undefined(); + + barriers.transition(resource, before, after, + SubresRange::all(), BarrierFlags::SINGLE | BarrierFlags::DISCARD); + + discard_pending = false; + + current.clear(); + current.assign(SubresRange::all(), after); + return; + } + + // True when nothing in this GROUP has touched the resource + // yet -- not merely this region. That is the exact condition + // the D3D12 spec attaches to SyncBefore = NONE: "there MUST + // have been no preceding barriers or accesses made to that + // resource in the same ExecuteCommandLists scope", and a + // group is submitted as exactly one such scope. + const bool group_first_touch = current.empty(); + + // Whether we actually KNOW where the resource is on entry, + // as opposed to guessing the resting layout. + // + // The rest block below deliberately does NOT rest + // frame_graph_managed resources -- the FrameGraph links + // those across passes itself and rests them once, at the + // last pass that uses them. So for those the resting layout + // is not where they are; the only trustworthy entry state is + // the one the FrameGraph declares via set_entry_state, and + // that has already been consumed into `current` above. + const bool entry_known = !resource->frame_graph_managed; + + // One pass over the region: `current` reports each part of + // it with the state that part is in, splitting wherever they + // differ. What used to need a per-subresource walk plus a + // "have they diverged?" special case is just this. + std::vector> updates; + + current.visit(want_range, [&](SubresRange piece, const ResourceState* tracked_before) + { + ResourceState before; + const bool was_tracked = tracked_before != nullptr; + + if (was_tracked) + { + before = *tracked_before; + } + else + { + // Untouched by this group so far. A resource no + // barrier has ever moved is still in its creation + // (undefined) layout; anything else was handed back + // at rest by whoever used it last. + // + // state_at_rest, NOT {SYNC_NONE, NO_ACCESS, rest}. + // The spec does license SyncBefore = NONE for the + // first touch in an ExecuteCommandLists scope, but + // NO_ACCESS paired with a REAL layout is the same + // combination D3D12 rejects on the after side + // (#1331). Such a barrier does not take effect, so + // the layout never moves and the NEXT barrier fails + // validation instead (#1334). It also bought + // nothing: the skip below keys on layout equality, + // which is identical under either form. + before = res_plan.from_undefined + ? from_undefined() + : state_at_rest(resting_layout(resource)); + } + + updates.emplace_back(piece, after); + + // Entering the scope in the layout we already want. + // Nothing to transition and nothing to synchronize + // against -- the scope boundary already guarantees prior + // work retired and caches flushed. + // + // Gated on entry_known. Skipping both emits no barrier + // AND records `after` as the group's belief; on a + // GUESSED entry that belief is unfounded and every later + // barrier is computed from a wrong base. Emitting keeps + // a wrong guess to one self-correcting transition. + if (entry_known && !was_tracked && group_first_touch + && before.layout == after.layout && !res_plan.from_undefined) + return; + + // Read-after-read, differing only in which shader stages + // read it. Neither side writes, so there is no hazard to + // order and nothing to make visible. Access must match + // exactly -- a different access needs the data made + // visible to it even when both are reads. No entry_known + // gate: this only fires on a state the group actually + // tracked, never on a guess. + if (was_tracked && before.layout == after.layout + && before.access == after.access + && !before.has_write_bits() && !after.has_write_bits()) + return; + + // Same state is not the same as no hazard. Two dispatches + // writing the same UAV need ordering even though nothing + // about the state changes -- the layouts match, so this + // is purely an execution/memory barrier, which is what a + // legacy UAV barrier was. Only unordered access needs it: + // render-target and depth writes are pipeline-ordered, + // and read-after-read needs nothing. + const bool uav_ordering = (before == after) + && check(after.access & BarrierAccess::UNORDERED_ACCESS) + && was_tracked; + + if (before == after && !uav_ordering) + return; + + barriers.transition(resource, before, after, piece, BarrierFlags::SINGLE); + }); + + for (auto& up : updates) current.assign(up.first, up.second); + }; + + // 2. Apply. Each region of the resource this operation wants a + // state for, with that state -- regions it does not touch + // report none and are skipped. + wanted.visit(SubresRange::all(), [&](SubresRange piece, const ResourceState* st) + { + if (st) emit(piece, *st); + }); + + last_use = &operation; + } + } + + // Hand the resource back at rest, right after the group's last use + // of it -- not at end of group, so it is released as early as it is + // actually free. A group therefore both finds and leaves such a + // resource in the same well-defined state, which is what makes + // groups independent of the order they are submitted in. + // + // FrameGraph-managed resources opt out: the FrameGraph knows the + // whole pass graph, so it links their usage across passes itself and + // rests them once, at the last pass that uses them -- rather than + // converging every group boundary. + if (!last_use) continue; + if (resource->frame_graph_managed) continue; + + const TextureLayout rest_layout = resting_layout(resource); + const ResourceState rest = state_at_rest(rest_layout); + auto& after_barriers = last_use->barriers_after; + + // Regions the group never touched report no state and are already at + // rest by the same contract, so only tracked ones can need a barrier. + // No subresource count anywhere in here. + current.visit(SubresRange::all(), [&](SubresRange piece, const ResourceState* st) + { + if (!st) return; + if (st->layout == rest_layout) return; + after_barriers.transition(resource, *st, rest, piece); + }); + + current.clear(); + current.assign(SubresRange::all(), rest); + + // The contract this whole design rests on: a non-FrameGraph resource + // enters a group at its resting layout and leaves at its resting + // layout, so groups are independent of the order they are submitted + // in and the next group can assume where it is without being told. + if constexpr (BuildOptions::Dev) + { + ASSERT(current.check_disjoint()); + current.visit(SubresRange::all(), [&](SubresRange, const ResourceState* st) + { + ASSERT(!st || st->layout == rest_layout); + }); + } + } + } + + // Replay every list into its API command list. Runs after + // compile_transitions -- compile() is what consumes the barrier groups, so + // doing it first would emit them empty. + // + // Lists compile independently of each other, so this fans out and joins + // before returning: whoever called us is about to submit, and every list has + // to be closed by then. + void CommandListGroup::compile() + { + PROFILE(L"group_compile"); + + if (lists.size() == 1) + { + // The common case (a standalone list, or a batch of one) -- not + // worth a task dispatch. + auto& list = lists.front(); + auto ca = list->frame_resources->get_ca(list->get_type()); + list->compiler.compile(*ca); + list->frame_resources->free_ca(ca); + return; + } + + std::vector> tasks; + tasks.reserve(lists.size()); + + for (auto& list : lists) + tasks.emplace_back(thread_pool::get().enqueue([list]() + { + PROFILE(((CommandListBase*)list.get())->get_name()); + auto ca = list->frame_resources->get_ca(list->get_type()); + + list->compiler.compile(*ca); + list->frame_resources->free_ca(ca); + })); + + for (auto& t : tasks) + t.wait(); + } } diff --git a/sources/HAL/HAL.CommandList.ixx b/sources/HAL/HAL.CommandList.ixx index f49cbb0b..0f6acef9 100644 --- a/sources/HAL/HAL.CommandList.ixx +++ b/sources/HAL/HAL.CommandList.ixx @@ -41,7 +41,12 @@ export{ template void track_object(T& obj) { - auto& state = obj.ObjectState::get_state(this); + // Unqualified: the state type is whatever this object stores per + // context -- TrackedObjectState for most things, a type derived + // from it where the object needs more (HAL::Resource keeps its + // per-list barrier tracking there). Naming the base explicitly + // would only compile for the former. + auto& state = obj.get_state(this); if (!state.used) { state.used = true; @@ -58,6 +63,11 @@ export{ virtual ~CommandListBase() = default; CommandListType get_type(); + + // The list's debug name -- the owning pass's name for FrameGraph + // lists. Used by diagnostics that need to say WHICH list a barrier + // belongs to; an index within a group is meaningless across groups. + LiteralWStr get_name() const { return name; } }; class TransitionCommandList; @@ -70,67 +80,149 @@ export{ friend class SignatureDataSetter; friend class Sendable; friend class Eventer; - friend class ResourceStateManager; + friend class CommandListGroup; protected: void begin(); void on_execute(); - std::deque usage_points; - std::deque usage_pool; - size_t pool_used = 0; - std::set need_check_transitions; - void create_usage_point(BarrierSync operation, bool end = true); - - // --- Operation batching ------------------------------------------- - // Consecutive ops of the same class share ONE usage point (one - // barrier group) instead of each bracketing itself. open_op is the - // currently open batch's class (NONE = none open). current_batch_id - // is the OP-CLASS batch epoch — advanced ONLY by begin_op (op-class - // change), NOT by intra-batch hazard splits, so a resource touched - // anywhere in the op-class batch stays detectable across splits. Each - // resource's per-list state stamps batch_touch_id/state, so the hazard - // check reads the resource directly (no parallel map): if it was - // already touched in THIS epoch with a different, non-mergeable state, - // the batch is split. A different op class closes the batch (begin_op); - // list end closes it (close_op). - BarrierSync open_op = BarrierSync::NONE; - uint current_batch_id = 0; + // This list's work, split into contiguous same-class runs. Appended + // to by begin_op() only when the operation class actually changes. + // + // deque, not vector: the recorder holds CmdListOperation* (see + // DelayedCommandList::func_barrier) so entries must not move as + // more operations are appended. + std::deque operations; + + // Start (or keep growing) the current operation. Consecutive calls + // with the same class are a no-op; a different class closes the + // previous operation and appends a new one, reserving both barrier + // points in the command stream as it goes. void begin_op(BarrierSync op); - void batch_hazard_check(const HAL::Resource* resource, ResourceState to, UINT subres); - public: - // Close the open op-batch. Called at list end (compile_transitions) - // and by the FrameGraph before it appends cross-pass transitions, so - // those land after the last op instead of inside its batch. - void close_op(); + + // Close the operation at the back, reserving the point for its + // barriers_after. Called by begin_op when the class changes, and + // once at end of recording for the final operation. + void end_op(); + + // Force a new operation of the SAME class. begin_op deliberately + // merges a run of same-class work into one operation, which is what + // makes barriers batch -- but two dispatches in that run that both + // touch the same UAV have to be ordered against each other, and a + // barrier can only sit between operations. record_usage calls this + // when it spots that case. + void split_op(); + + // Bumped on every begin_op call, including the ones that merge into + // the current operation. Stamped onto each recorded usage so + // record_usage can tell one dispatch's binds from the next's. + uint op_step = 0; + + // op_step at the moment the operation at the back was appended. When + // it still equals op_step the operation is empty, and forcing + // another one would leave a stray operation behind holding two + // reserved-but-unused barrier points. + uint op_first_step = 0; + protected: + // Append one use to the operation currently being recorded (the + // back of `operations`), on this resource's per-list + // TrackedResourceState. + void record_usage(const HAL::Resource* resource, const HAL::OperationUsage& usage); public://temporarily to allow transition into read mode - void transition(const HAL::Resource* resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); - void transition(const HAL::Resource::ptr& resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); + void add_resource_usage(const HAL::Resource* resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); + void add_resource_usage(const HAL::Resource::ptr& resource, ResourceState state, UINT subres = ALL_SUBRESOURCES); - void compile_transitions(); public: - void free_resources(); - bool transitions_compiled = false; - UINT transition_count = 0; - HAL::ResourceUsage* add_usage(const HAL::Resource* resource, UINT subres, ResourceState state, HAL::TransitionType type = HAL::TransitionType::LAST); + // End the current operation so the next command starts a fresh one, + // even though it is the same class. Unlike split_op this is a no-op + // on an operation nothing has recorded into yet, so it is safe to + // call unconditionally at the head of a command. + // + // Used by set_rtv: a run of draws merges into one operation, and one + // operation gets ONE set of entry barriers, so every resource it + // touches has to hold a single state throughout. That breaks the + // moment the render target changes -- a pass that renders to a + // texture and then samples it records RENDER_TARGET and + // SHADER_RESOURCE into the same operation, and the later use wins, + // so the clear/draw that needed RENDER_TARGET runs against + // SHADER_RESOURCE (#1334). Draws with different render targets can't + // overlap on the GPU anyway, so there is nothing to lose by not + // batching across the change. + void break_op(); void use_resource(const HAL::Resource* resource); - // Resources this list touched (populated by use_resource). Used by - // link_list_groups to find resources shared between lists that land in - // the same ExecuteCommandLists scope. + // Resources this list touched (populated by use_resource). This is the + // set CommandListGroup::compile_transitions walks -- a resource shared + // between lists in the group is diffed against where the previous list + // actually left it, not against the resting layout. const std::vector& get_used_resources() const { return used_resources; } public: + // Declare what state this list will find `resource` in. Only used + // when the list holds its group's FIRST touch of the resource -- + // otherwise the group has tracked it directly and knows better. + // Lets the FrameGraph link a resource across a group boundary, which + // the group cannot see over. + void set_entry_state(const HAL::Resource* resource, ResourceState state); + + // Open an operation for work this list cannot introspect -- an + // external SDK recording straight into the native command list + // (Streamline/DLSS). Unlike begin_op it never merges into the + // operation in front of it, so the states declared afterwards land + // in THIS operation's barriers_before and bracket exactly that work. + // + // The caller follows it with add_resource_usage for each resource the + // external call touches; nothing else can know those. + void begin_external_op(BarrierSync op); + + // Record a trailing use that leaves `resource` in `state`, in an + // operation of its own so the barrier lands AFTER the work already + // recorded rather than merging into its state. + // + // This is how the FrameGraph converges a resource at its PRODUCER + // rather than at whichever consumer happens to run first: several + // passes reading the same version each need the same "before", and + // only one of them could ever supply it. + void transition_to(const HAL::Resource* resource, ResourceState state); + + // transition_to() with the resource's resting state. CommandListGroup + // deliberately does not rest frame_graph_managed resources (a group + // is only part of the frame), so the FrameGraph calls this on the + // last pass that touches one. + void transition_to_rest(const HAL::Resource* resource); + + // The state this list leaves `resource` in: the state of its last + // recorded usage, or nullopt if it never touched it. The FrameGraph + // pairs this with set_entry_state on the consuming list to hand a + // resource from one pass to the next. + // + // Subresource-accurate only when the list used the resource + // uniformly; a list that touched subresources individually reports + // its last usage's state, which is why the FrameGraph declares a + // whole-resource state at the boundaries it links. + std::optional get_exit_state(const HAL::Resource* resource) const; + + // The state this list needs `resource` to already be in when it + // starts -- its first recorded usage. The counterpart of + // get_exit_state: the FrameGraph converges a producer INTO this so + // the consuming pass needs no barrier of its own, which is the only + // hand-off that works when a phase has several passes (none of them + // ordered against each other). + std::optional get_first_use_state(const HAL::Resource* resource) const; + void alias_begin(HAL::Resource*); void alias_end(HAL::Resource*); void transition_present(const HAL::Resource* resource_ptr); - void transition(const ResourceInfo& info, BarrierSync operation = BarrierSync::NONE); + // whole_resource: record the use as ALL_SUBRESOURCES instead of the + // mip/array range the view names. Set by a table member declared + // [Barrier = ALL] in its .sig -- see HAL::BoundResource. + void add_resource_usage(const ResourceInfo& info, BarrierSync operation = BarrierSync::NONE, bool whole_resource = false); }; @@ -288,7 +380,7 @@ export{ void post_command(T& context, BarrierSync operation) { // Leave the batch OPEN — it is closed lazily by the next op of a - // different class (begin_op) or at list end (close_op). A hazard + // different class (begin_op) or at list end. A hazard // on a shared resource splits it (see batch_hazard_check). if constexpr (Debug::GfxDebug) if constexpr (compute || graphics) print_debug(); @@ -309,6 +401,10 @@ export{ const std::vector& get_debug_records() const; + // Move the records out rather than copying them. Used by the + // FrameGraph debug snapshot, which rewrites what it takes. + std::vector take_debug_records(); + void discard(HAL::Resource* resource); CommandList(CommandListType, Device&); @@ -322,7 +418,12 @@ export{ void invalidate_state(); - void clear_uav(const Handles::UAV& h, vec4 ClearColor = vec4(0, 0, 0, 0)); + // whole_resource: see clear_dsv below. Declares the barrier over the + // entire resource instead of the subresources this view names, so a + // caller clearing selected slices/mips of a resource it then uses as + // a whole does not diverge the group's per-subresource tracking. + void clear_uav(const Handles::UAV& h, vec4 ClearColor = vec4(0, 0, 0, 0), + bool whole_resource = false); // Clears a DSV directly (ClearDepthStencilView-equivalent) without // binding it as the active render target the way set_rtv's @@ -330,7 +431,15 @@ export{ // full OM bind, resource-state transitions for every attachment, // and render-target-size bookkeeping, all irrelevant when the // caller only wants the clear. Mirrors clear_uav's shape. - void clear_dsv(const Handles::DSV& h, bool clear_depth = true, bool clear_stencil = false, float depth = 0, UINT8 stencil = 0); + // whole_resource: declare the barrier over the ENTIRE resource rather + // than the subresources this view names. For a caller that clears + // selected slices of an atlas and then binds the whole thing as a + // DSV anyway, that is both accurate and far cheaper -- per-slice + // declarations diverge the group's per-subresource tracking, and the + // next whole-resource use then has to reconcile every subresource + // individually. + void clear_dsv(const Handles::DSV& h, bool clear_depth = true, bool clear_stencil = false, float depth = 0, UINT8 stencil = 0, + bool whole_resource = false); }; @@ -428,7 +537,7 @@ export{ SlotID slot_id; Handles::CBV const_buffer; - std::vector resources; + std::vector resources; }; std::vector tables; @@ -637,15 +746,17 @@ export{ CommandList& get_base(); template - void clear(HAL::StructuredBufferView& view, vec4 ClearColor = vec4(0, 0, 0, 0)) + void clear(HAL::StructuredBufferView& view, vec4 ClearColor = vec4(0, 0, 0, 0), + bool whole_resource = false) { - get_base().clear_uav(view.rwRAW, ClearColor); + get_base().clear_uav(view.rwRAW, ClearColor, whole_resource); } template - void clear_counter(HAL::StructuredBufferView& view, vec4 ClearColor = vec4(0, 0, 0, 0)) + void clear_counter(HAL::StructuredBufferView& view, vec4 ClearColor = vec4(0, 0, 0, 0), + bool whole_resource = false) { - get_base().clear_uav(view.counter_view.rwRAW, ClearColor); + get_base().clear_uav(view.counter_view.rwRAW, ClearColor, whole_resource); } void dispatch(int = 1, int = 1, int = 1); @@ -677,9 +788,9 @@ export{ { base.pre_command(*this, BarrierSync::COMPUTE_SHADING); - base.transition(hit_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); - base.transition(miss_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); - base.transition(raygen_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); + base.add_resource_usage(hit_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); + base.add_resource_usage(miss_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); + base.add_resource_usage(raygen_buffer.resource, { BarrierSync::COMPUTE_SHADING, BarrierAccess::SHADER_RESOURCE, TextureLayout::UNDEFINED }); list->dispatch_rays(size, hit_buffer, hit_count, miss_buffer, miss_count, raygen_buffer); @@ -704,6 +815,92 @@ export{ }; + // A set of command lists that will be submitted together, in this order, + // as one ExecuteCommandLists batch. Everything reaches a queue through a + // group -- there is no way to submit a bare array of lists -- because + // barriers can only be computed correctly with the whole batch in view. + // + // Barriers are computed ACROSS the group, not per list: a resource is + // entered from its resting layout once, at the group's first use of it, + // and returned there once, after the group's last use. Between those, + // lists hand the resource to each other directly, so a resource read (or + // written) by several consecutive lists no longer bounces through the + // resting layout at every list boundary. + class CommandListGroup + { + std::vector lists; + + // One resource this group touches, plus what the group must do + // about its first-ever use. Decided by plan_resources() on the + // submitting thread, in submission order, so that + // compile_transitions() itself reads only group-local data and + // several groups can compile concurrently. + struct PlannedResource + { + Resource* resource = nullptr; + + // This group is the first ever to touch the resource, so it + // enters from the creation (undefined) layout with SyncBefore + // NONE. Otherwise something has already touched it and NONE is + // illegal (#1417). + bool from_undefined = false; + + // This group holds the resource's first-ever WRITE and therefore + // owns its initializing discard (#1422). + bool owns_discard = false; + }; + + // Every resource any list in the group touched, deduplicated, in + // first-seen order. Built ONCE by plan_resources and then just + // walked by compile_transitions -- which used to rebuild the same + // list through a std::set (a tree-node allocation per resource) and + // then pay a hash lookup per resource to find its plan. + std::vector planned; + + // Whether plan_resources() has run for this group. Not derivable + // from `planned` being non-empty: a group whose lists touched no + // resources at all plans to an empty list, which is legitimate. + bool planned_built = false; + + public: + void add(const CommandList::ptr& list) { lists.emplace_back(list); } + + bool empty() const { return lists.empty(); } + size_t size() const { return lists.size(); } + void clear() { lists.clear(); } + + const std::vector& get_lists() const { return lists; } + + // Compute every barrier for the group and fill the groups the + // reserved points in each list's command stream refer to. Runs after + // recording and before the lists are compiled -- compile() consumes + // the barrier groups, so filling them afterwards would emit nothing. + // + // Must run exactly once per list. A list processed by two groups (or + // by a group and again on its own) gets two independently computed + // barrier sets spliced into one command stream. + // Resolve the first-use questions for every resource this group + // touches. MUST run on the submitting thread, in submission order, + // BEFORE compile_transitions -- it is the only step that reads or + // writes the per-resource `virgin` / `initialized` flags, and "which + // group owns the first write" is meaningless out of order. + // + // Splitting it out is what makes compile_transitions pure: after + // this, a group's barrier computation touches nothing shared, so + // groups can be compiled in parallel. + void plan_resources(); + + void compile_transitions(); + + // Replay every list into its API command list. Must follow + // compile_transitions -- this is what consumes the barrier groups, + // so compiling first would emit them empty. Lists compile + // independently, so this fans out across the thread pool and joins + // before returning. + void compile(); + }; + + class TransitionCommandList : public Eventer, public Sendable { diff --git a/sources/HAL/HAL.CommandListRecorder.cpp b/sources/HAL/HAL.CommandListRecorder.cpp index 88261473..5c680097 100644 --- a/sources/HAL/HAL.CommandListRecorder.cpp +++ b/sources/HAL/HAL.CommandListRecorder.cpp @@ -43,11 +43,11 @@ namespace HAL { case CommandType::Transition: if constexpr (BuildOptions::Dev) - for (const auto& b : cmd.barrier->transitions.get_barriers()) + for (const auto& b : cmd.barrier->get_barriers()) HAL::Debug::BarrierBreakpoints::check_barrier( b.resource ? std::string_view{b.resource->name} : std::string_view{}, - b.subres, b.before, b.after); - list.transitions(cmd.barrier->transitions); + HAL::representative_subres(b.resource, b.range), b.before, b.after); + list.transitions(*cmd.barrier); break; case CommandType::Draw: @@ -145,11 +145,25 @@ namespace HAL // ── push methods ───────────────────────────────────────────────────────── - void DelayedCommandList::func_barrier(UsagePoint* point) + // Reserve this point in the command stream for a barrier group. The group + // is still empty at record time -- it is filled in later, once the barriers + // for the whole list are computed -- so what matters here is only the + // POSITION. Callers reserve one point per group they intend to emit + // (CmdListOperation brackets its work with barriers_before/barriers_after). + // `barriers` must outlive compile(); the groups are owned by the list's + // `operations` deque, whose entries never move. + void DelayedCommandList::func_barrier(Barriers* barriers, uint op_index, bool after_op, + HAL::BarrierSync op_type) { if constexpr (BuildOptions::Dev) - debug_recorder.push_back({CommandType::Transition, {}, point}); - Cmd cmd{}; cmd.type = CommandType::Transition; cmd.barrier = point; + { + CommandRecord rec{ CommandType::Transition, {}, barriers }; + rec.op_index = op_index; + rec.after_op = after_op; + rec.op_type = op_type; + debug_recorder.push_back(std::move(rec)); + } + Cmd cmd{}; cmd.type = CommandType::Transition; cmd.barrier = barriers; tasks.push_back(cmd); } @@ -558,4 +572,5 @@ namespace HAL const API::CommandList& DelayedCommandList::get_list() const { return list; } bool DelayedCommandList::is_compiled() const { return compiled; } const std::vector& DelayedCommandList::get_debug_records() const { return debug_recorder.get(); } + std::vector DelayedCommandList::take_debug_records() { return debug_recorder.take(); } } diff --git a/sources/HAL/HAL.CommandListRecorder.ixx b/sources/HAL/HAL.CommandListRecorder.ixx index 0688de30..cd6ad2d7 100644 --- a/sources/HAL/HAL.CommandListRecorder.ixx +++ b/sources/HAL/HAL.CommandListRecorder.ixx @@ -45,7 +45,11 @@ export namespace HAL std::string resource_name; HAL::ResourceState before; HAL::ResourceState after; + // Representative flat index (ALL_SUBRESOURCES when the barrier covers + // more than one) -- what the continuity tracking keys on. `range` is + // the exact extent, for display. uint subres = 0; + HAL::SubresRange range; HAL::BarrierFlags flags = HAL::BarrierFlags::NONE; // Opaque per-instance id (the source Resource address). A recreate() // makes a NEW Resource object with the SAME name, so the debugger uses @@ -56,8 +60,20 @@ export namespace HAL CommandType type = CommandType::Func; std::string description; - UsagePoint* barrier_point = nullptr; // non-null only until snapshot + Barriers* barrier_point = nullptr; // non-null only until snapshot std::vector barrier_details; // non-empty only for Transition records + + // Which CmdListOperation this barrier group belongs to, and which of its + // two groups it is. Recorded when the point is reserved, because that is + // the only moment the structure is known -- by snapshot time a barrier is + // just an entry in a list. Transition records only. + uint op_index = 0; + bool after_op = false; // false = barriers_before, true = barriers_after + + // The operation's class -- the BarrierSync a run of same-class work was + // opened with (begin_op). Names the operation in the debugger far better + // than its index: "COMPUTE_SHADING" says what the barriers bracket. + HAL::BarrierSync op_type = HAL::BarrierSync::NONE; }; class DelayedCommandList @@ -70,6 +86,15 @@ export namespace HAL void push_back(CommandRecord r) { records.push_back(std::move(r)); } void clear() { records.clear(); } const std::vector& get() const { return records; } + + // Hand the records over instead of lending them. The snapshot has + // to MUTATE what it takes (it resolves each Transition's + // barrier_point into plain data and nulls it), so copying was the + // only alternative -- and a CommandRecord owns a description string + // and a vector of details, making that a deep copy of every command + // on every pass, every frame. The recorder clears these on reuse + // anyway, so it has no use for them after the snapshot. + std::vector take() { return std::move(records); } }; struct NullRecorder { @@ -80,6 +105,7 @@ export namespace HAL static const std::vector s_empty; return s_empty; } + std::vector take() { return {}; } }; // Typed command — trivial data stored inline (no heap, no type-erasure). @@ -91,7 +117,7 @@ export namespace HAL union { - UsagePoint* barrier; + Barriers* barrier; struct { UINT vc, vo, ic, io; } draw; struct { UINT ic, ioff, vo, inst, io; } draw_indexed; @@ -131,7 +157,7 @@ export namespace HAL bool compiled = false; API::CommandList list; - std::wstring name; + std::wstring_view name; std::vector tasks; std::vector> fn_pool; // shared_ptr captures [[no_unique_address]] @@ -192,8 +218,10 @@ export namespace HAL void update_texture(HAL::Resource* resource, ivec3 offset, ivec3 box, UINT sub_resource, ResourceAddress address, texture_layout layout); void read_texture(const HAL::Resource* resource, ivec3 offset, ivec3 box, UINT sub_resource, ResourceAddress target, texture_layout layout); - void func_barrier(UsagePoint* point); + void func_barrier(Barriers* barriers, uint op_index = 0, bool after_op = false, + HAL::BarrierSync op_type = HAL::BarrierSync::NONE); const std::vector& get_debug_records() const; + std::vector take_debug_records(); template void dispatch_rays(ivec2 size, HAL::ResourceAddress hit_buffer, UINT hit_count, HAL::ResourceAddress miss_buffer, UINT miss_count, HAL::ResourceAddress raygen_buffer) { diff --git a/sources/HAL/HAL.DLSS.cpp b/sources/HAL/HAL.DLSS.cpp index 3281797a..0cde977c 100644 --- a/sources/HAL/HAL.DLSS.cpp +++ b/sources/HAL/HAL.DLSS.cpp @@ -91,6 +91,32 @@ namespace nvidia { if (!resolved || !frame.valid()) return; + // Streamline's plugins issue their own legacy ResourceBarrier calls on + // tagged resources internally, requiring legacy/enhanced barrier interop + // hand-off at COMMON (D3D12 #1350). NOT transition_present() -- that + // requests NO_ACCESS ("handed to the OS"), which is wrong here and trips + // a DEV-only assert. Sync is COMPUTE_SHADING, not NONE, since these + // resources are used again downstream. + static const HAL::ResourceState kTagged{ + HAL::BarrierSync::COMPUTE_SHADING, HAL::BarrierAccess::COMMON, HAL::TextureLayout::PRESENT }; + + // color_out is WRITTEN by SL's internal compute work, unlike the three + // read-only tags -- forcing it to COMMON tripped D3D12 #1334 on SL's own + // UAV clears, so it stays in UNORDERED_ACCESS. + static const HAL::ResourceState kWritten{ + HAL::BarrierSync::COMPUTE_SHADING, HAL::BarrierAccess::UNORDERED_ACCESS, HAL::TextureLayout::UNORDERED_ACCESS }; + + // Declare the states HERE rather than at the call site: which resources + // SL tags, and in what state each has to be, is a property of this call. + // The operation wraps exactly the deferred body below, so the barriers + // land immediately before it instead of merging into whatever compute + // work happened to precede the pass. + list.begin_external_op(HAL::BarrierSync::COMPUTE_SHADING); + list.add_resource_usage(color_in, kTagged); + list.add_resource_usage(depth, kTagged); + list.add_resource_usage(motion_vectors, kTagged); + list.add_resource_usage(color_out, kWritten); + list.compiler.func( [this, frame, constants, mode, hdr, viewport, color_in, depth, motion_vectors, color_out] (HAL::API::CommandList& native_list) diff --git a/sources/HAL/HAL.Debug.ixx b/sources/HAL/HAL.Debug.ixx index 8034c4fd..99792acc 100644 --- a/sources/HAL/HAL.Debug.ixx +++ b/sources/HAL/HAL.Debug.ixx @@ -15,6 +15,7 @@ export namespace HAL // identical; any difference is a hazard-detection bug. inline bool EnableOpBatching = true; + #ifdef DEV constexpr bool GfxDebug = !RunForPix&&false; constexpr bool ValidationErrors = !RunForPix&&true; diff --git a/sources/HAL/HAL.DescriptorHeap.ixx b/sources/HAL/HAL.DescriptorHeap.ixx index 8e390f6c..37bcdbed 100644 --- a/sources/HAL/HAL.DescriptorHeap.ixx +++ b/sources/HAL/HAL.DescriptorHeap.ixx @@ -29,6 +29,26 @@ export void for_each_subres(std::function&, UINT)> f) const; }; + // One view bound into a table slot, plus how the barrier system should + // scope it. Separate from ResourceInfo because the scope belongs to the + // BINDING, not the view: the same descriptor can be bound by one table + // that wants whole-resource barriers and another that does not. + struct BoundResource + { + ResourceInfo* info = nullptr; + + // [Barrier = ALL] on the .sig member. Transition the WHOLE resource + // instead of the mip/array range this view names. + // + // For a view narrowed to one mip of a big array (a Hi-Z pyramid + // level over N slices), the range costs one barrier per slice per + // bind while the neighbouring levels are being written anyway. One + // whole-resource entry is both cheaper and what the surrounding + // code actually wants -- which is why several call sites used to + // hand-write a bare add_resource_usage() next to the bind. + bool whole_resource = false; + }; + class DescriptorHeap : public SharedObject, public API::DescriptorHeap, public TypedObject { friend struct Handle; diff --git a/sources/HAL/HAL.Device.cpp b/sources/HAL/HAL.Device.cpp index 51141ff8..d78f2d69 100644 --- a/sources/HAL/HAL.Device.cpp +++ b/sources/HAL/HAL.Device.cpp @@ -47,27 +47,33 @@ namespace HAL auto list = get_upload_list(); - std::vector> promoted; - promoted.reserve(batch.size()); - for (auto& r : batch) { if (r->get_desc().is_buffer()) continue; // buffers carry no layout - auto desired = r->get_state_manager().get_desired_state(); + auto layout = resting_layout(r.get()); // Only promote resources that actually have a read state. - if (!check(desired.layout & (TextureLayout::SHADER_RESOURCE | TextureLayout::UNORDERED_ACCESS))) + if (!check(layout & (TextureLayout::SHADER_RESOURCE | TextureLayout::UNORDERED_ACCESS))) continue; - // The resource is physically in COMMON (its data upload already waited on - // the DStorage fence). Record COMMON -> read: the seed reads gpu_state - // (still COMMON) so a REAL transition is emitted. Set initial_layout to - // the read layout NOW so this list's own end-of-list decay is a no-op - // (it decays to initial_layout). gpu_state is pinned to the read state - // AFTER execute, once the seed has been consumed by compile. - list->transition(r.get(), desired); - r->get_state_manager().initial_layout = desired.layout; - promoted.push_back(r); + // Record the read use. This is not a redundant hint: recording it is + // what puts the resource in this list's used_resources at all, which + // is what makes the group transition it out of COPY_DEST. The state + // asked for is the resting layout, so the group's return-to-rest then + // has nothing left to do. + // + // Per-subresource, not ALL_SUBRESOURCES: this is the resource's first + // ever touch through the Transitions state tracker (its subresources + // were written via DirectStorage, which bypasses Transitions entirely), + // so an ALL_SUBRESOURCES call here hits the virgin/non_tracked_resources + // fallback in compile_transitions() and drops the promotion for some + // subresources (observed: subresource 0, the largest mip, left black). + auto desired = check(layout & TextureLayout::SHADER_RESOURCE) + ? ResourceStates::SHADER_RESOURCE + : ResourceStates::UNORDERED_ACCESS; + + for (UINT s = 0; s < r->get_desc().as_texture().Subresources(); s++) + list->add_resource_usage(r.get(), desired, s); } // Submit on the DIRECT queue and wait. This runs on the MAIN thread at @@ -76,9 +82,6 @@ namespace HAL // Waiting keeps the list alive until the GPU is done and orders the // transition ahead of this frame's rendering. list->execute_and_wait(); - - for (auto& r : promoted) - r->get_state_manager().set_resting_state(r->get_state_manager().get_desired_state().layout); } HAL::Queue::ptr& Device::get_queue(CommandListType type) diff --git a/sources/HAL/HAL.Queue.cpp b/sources/HAL/HAL.Queue.cpp index 0d7d6bd0..bceec3e8 100644 --- a/sources/HAL/HAL.Queue.cpp +++ b/sources/HAL/HAL.Queue.cpp @@ -106,7 +106,7 @@ namespace HAL return signal_internal(); } */ - void Queue::execute_internal(UINT64 fence_value, std::list lists) + void Queue::execute_internal(UINT64 fence_value, std::vector lists) { std::unique_lock lock(queue_mutex); @@ -265,17 +265,15 @@ namespace HAL result.wait(); } - HAL::FenceWaiter Queue::execute(std::list list) + HAL::FenceWaiter Queue::execute(const CommandListGroup& group) { std::unique_lock lock(submit_mutex); const UINT64 fence = ++m_fenceValue; // Move the batch through: caller -> executor closure -> execute_internal. - // Copying a std::list of shared_ptr costs a node allocation and an atomic - // refcount bump per element, per hop. - gpu_execute_thread.enqueue([this, list = std::move(list), fence]() mutable { - execute_internal(fence, std::move(list)); + gpu_execute_thread.enqueue([this, lists = group.get_lists(), fence]() mutable { + execute_internal(fence, std::move(lists)); }); return HAL::FenceWaiter(&commandListCounter, fence, type); diff --git a/sources/HAL/HAL.Queue.ixx b/sources/HAL/HAL.Queue.ixx index 150e8509..90fe08e0 100644 --- a/sources/HAL/HAL.Queue.ixx +++ b/sources/HAL/HAL.Queue.ixx @@ -48,7 +48,7 @@ export namespace HAL HAL::CommandListType type; - void execute_internal(UINT64 fence_value, std::list list); + void execute_internal(UINT64 fence_value, std::vector lists); uint64 frequency; public: @@ -69,7 +69,11 @@ export namespace HAL void signal_and_wait(); HAL::FenceWaiter signal(); - HAL::FenceWaiter execute(std::list list); + // Submit a group. There is deliberately no overload taking a bare + // array of lists: barriers are computed across a whole group (see + // CommandListGroup::compile_transitions), so a batch that never + // became a group would reach the GPU with its barriers unfilled. + HAL::FenceWaiter execute(const CommandListGroup& group); HAL::FenceWaiter signal(HAL::Fence& fence, UINT64 value); void gpu_wait(HAL::FenceWaiter waiter); diff --git a/sources/HAL/HAL.Resource.cpp b/sources/HAL/HAL.Resource.cpp index aecfae74..0e6c14b1 100644 --- a/sources/HAL/HAL.Resource.cpp +++ b/sources/HAL/HAL.Resource.cpp @@ -65,7 +65,7 @@ namespace HAL load_waiter.wait(); } - Resource::Resource() : state_manager(this), tiled_manager(this) {} + Resource::Resource() : tiled_manager(this) {} bool Resource::is_ready() const { @@ -85,14 +85,12 @@ namespace HAL return desc; } - ResourceStateManager& Resource::get_state_manager() - { - return state_manager; - } - uint32_t Resource::get_native_state() const { - TextureLayout layout = state_manager.copy_gpu().get_subres_state(0).layout; + // No per-list tracking is consulted here: this is asked at API + // boundaries that want the resource's steady state (e.g. Streamline's + // sl::Resource tag), and every list hands a resource back at rest. + TextureLayout layout = resting_layout(this); return to_native_resource_state(layout); } @@ -106,16 +104,6 @@ namespace HAL return get_ptr(); } - void Resource::disable_state_tracking() - { - desc.Flags |= ResFlags::DisableStateTracking; - } - - void Resource::enable_state_tracking() - { - desc.Flags &= ~ResFlags::DisableStateTracking; - } - HeapType Resource::get_heap_type() const { return heap_type; diff --git a/sources/HAL/HAL.Resource.ixx b/sources/HAL/HAL.Resource.ixx index 9d38d54a..d64dd3a1 100644 --- a/sources/HAL/HAL.Resource.ixx +++ b/sources/HAL/HAL.Resource.ixx @@ -134,7 +134,7 @@ export{ { - class Resource :public SharedObject, public ObjectState, public TrackedObject, public API::Resource, public TypedObject + class Resource :public SharedObject, public ObjectState, public TrackedObject, public API::Resource, public TypedObject { protected: bool serialize_from_derived = false; @@ -145,7 +145,6 @@ export{ Device* m_device = nullptr; protected: - ResourceStateManager state_manager; TiledResourceManager tiled_manager; void _init(Device& device, const ResourceDesc& desc, HeapType heap_type = HeapType::DEFAULT, TextureLayout initialLayout = TextureLayout::UNDEFINED, vec4 clear_value = vec4(0, 0, 0, 0)); @@ -159,12 +158,25 @@ export{ bool debug = false; bool debug_transitions = false; bool frame_graph_managed = false; + + // True until any barrier has transitioned this resource. While set, + // its layout is whatever it was created with (undefined), so a first + // touch enters from UNKNOWN rather than from the resting layout. + bool virgin = true; + + // True until the resource's contents have been established by a + // write. D3D12 requires a placed render-target/depth resource to be + // initialized by a Discard/Clear/Copy before it is used as one + // (#1422), and only a WRITE can do that -- so this is deliberately + // separate from `virgin`: a resource whose first-ever touch is a + // read has a defined layout from that point on, but is still + // uninitialized, and the discard has to wait for the first write. + bool initialized = false; bool is_ready() const; ResourceType get_type() const; const ResourceDesc& get_desc() const; ResourceHandle alloc_handle; - ResourceStateManager& get_state_manager(); // Backend-native resource-state value (D3D12_RESOURCE_STATES or // VkImageLayout, as uint32_t) for the currently tracked GPU state — @@ -176,8 +188,6 @@ export{ std::shared_ptr get_tracked(); - void disable_state_tracking(); - void enable_state_tracking(); ResourceAllocationInfo alloc_info; std::string name; void set_name(std::string name); @@ -215,19 +225,8 @@ export{ SERIALIZE() { - - desc.Flags &= ~ResFlags::DisableStateTracking; - if (check(desc.Flags & ResFlags::DisableStateTracking)) - { - ASSERT(false); - Log::get() << "AlARMA!!" << Log::endl; - } - ASSERT(!check(desc.Flags & ResFlags::DisableStateTracking)); - ASSERT(serialize_from_derived); ar& NVP(desc); - ASSERT(!check(desc.Flags & ResFlags::DisableStateTracking)); - } }; diff --git a/sources/HAL/HAL.ResourceStates.cpp b/sources/HAL/HAL.ResourceStates.cpp index 62d40f0f..e07bef92 100644 --- a/sources/HAL/HAL.ResourceStates.cpp +++ b/sources/HAL/HAL.ResourceStates.cpp @@ -8,6 +8,55 @@ namespace HAL // --- free functions --- + TextureLayout resting_layout(const Resource* resource) + { + auto flags = resource->get_desc().Flags; + + // A back buffer rests ready to present, not in a read layout -- and + // transition_present() records exactly that as its last use, so + // anything else here would emit a barrier undoing it. + if (check(flags & ResFlags::Swapchain)) return TextureLayout::PRESENT; + + if (check(flags & ResFlags::ShaderResource)) return TextureLayout::SHADER_RESOURCE; + if (check(flags & ResFlags::UnorderedAccess)) return TextureLayout::UNORDERED_ACCESS; + + // Nothing declares it readable -- leave it where an upload would put it. + return TextureLayout::COPY_DEST; + } + + ResourceState state_at_rest(TextureLayout layout) + { + if (check(layout & TextureLayout::SHADER_RESOURCE)) return { BarrierSync::ALL, BarrierAccess::SHADER_RESOURCE, layout }; + if (check(layout & TextureLayout::UNORDERED_ACCESS)) return { BarrierSync::ALL, BarrierAccess::UNORDERED_ACCESS, layout }; + if (check(layout & TextureLayout::RENDER_TARGET)) return { BarrierSync::ALL, BarrierAccess::RENDER_TARGET, layout }; + if (check(layout & TextureLayout::DEPTH_STENCIL_WRITE)) return { BarrierSync::ALL, BarrierAccess::DEPTH_STENCIL_WRITE, layout }; + if (check(layout & TextureLayout::DEPTH_STENCIL_READ)) return { BarrierSync::ALL, BarrierAccess::DEPTH_STENCIL_READ, layout }; + if (check(layout & TextureLayout::COPY_SOURCE)) return { BarrierSync::ALL, BarrierAccess::COPY_SOURCE, layout }; + if (check(layout & TextureLayout::COPY_DEST)) return { BarrierSync::ALL, BarrierAccess::COPY_DEST, layout }; + + // PRESENT / UNDEFINED / NONE -- nothing is accessing it. NO_ACCESS is + // also the only access D3D12 permits alongside LAYOUT_UNDEFINED. + // + // Sync NONE rather than ALL here, unlike every branch above: D3D12 + // rejects NO_ACCESS paired with any real sync unless the layout is + // UNDEFINED (#1331), and PRESENT maps to LAYOUT_COMMON. This is the + // same state transition_present() records, which is what a swapchain + // resource genuinely rests in. + return { BarrierSync::NONE, BarrierAccess::NO_ACCESS, layout }; + } + + uint representative_subres(const Resource* resource, const SubresRange& range) + { + if (range.is_all() || !resource->get_desc().is_texture()) + return ALL_SUBRESOURCES; + + if (range.num_mips != 1 || range.num_slices != 1 || range.num_planes != 1) + return ALL_SUBRESOURCES; + + return resource->get_desc().as_texture().CalcSubresource( + range.first_mip, range.first_slice, range.first_plane); + } + bool IsCompatible(CommandListType a, CommandListType b) { if (a == CommandListType::DIRECT) return true; @@ -83,6 +132,8 @@ namespace HAL void Barriers::clear() { barriers.clear(); + buffer_count = 0; + texture_count = 0; } const std::vector& Barriers::get_barriers() const @@ -94,990 +145,76 @@ namespace HAL { ASSERT(resource); - ASSERT(IsFullySupport(type, before)); - ASSERT(IsFullySupport(type, after)); - - if (check(after.access & (BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_WRITE | BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_READ))) - ASSERT(check(resource->get_desc().Flags & ResFlags::Raytracing)); - - barriers.emplace_back(Barrier{ const_cast(resource), before, after, subres, flags }); - } - - - // --- UsagePoint --- - - UsagePoint::UsagePoint(CommandListType type) : transitions(type) - { - } - - - // --- ResourceListStateCPU --- - - ResourceState ResourceListStateCPU::get_first_usage() - { - return first_usage->wanted_state; - } - - ResourceState ResourceListStateCPU::get_usage() - { - return last_usage->wanted_state; - } - - void ResourceListStateCPU::reset() - { - need_discard = false; - used = false; - skip_end_decay = false; - first_usage = nullptr; - last_usage = nullptr; - } - - void ResourceListStateCPU::check_valid(const Resource* resource) - { -#ifdef DEV - if (resource && check(resource->get_desc().Flags & ResFlags::DisableStateTracking)) return; - - auto usage = last_usage; - auto prev_usage = last_usage->prev_usage; - - if(prev_usage && prev_usage->prev_usage && prev_usage->wanted_state!=ResourceStates::UNKNOWN) - ASSERT( prev_usage->wanted_state.operation!=BarrierSync::NONE); -#endif - } - - ResourceUsage* ResourceListStateCPU::add_usage(ResourceUsage* usage) - { - // ASSERT(!last_usage || !(usage->wanted_state == last_usage->wanted_state && usage->wanted_state.access == ResourceStates::NO_ACCESS.access)); - // ASSERT(!last_usage || (last_usage->wanted_state.operation != ResourceStates::NO_ACCESS.operation)); - - // ASSERT(!(!check(usage->resource->get_desc().Flags & ResFlags::DisableStateTracking) && usage->wanted_state.access == ResourceStates::NO_ACCESS.access)); - ASSERT(!(check(usage->resource->get_desc().Flags & ResFlags::DisableStateTracking) && last_usage &&last_usage->prev_usage && last_usage->wanted_state.access == ResourceStates::NO_ACCESS.access)); - - auto prev = last_usage; - last_usage = usage; - last_usage->prev_usage = prev; - if (prev) prev->next_usage = usage; - - return usage; - } - - ResourceUsage* ResourceListStateCPU::set_zero_transition(ResourceUsage* usage) - { - // ASSERT(!(!check(usage->resource->get_desc().Flags & ResFlags::DisableStateTracking) && usage->wanted_state.access == ResourceStates::NO_ACCESS.access)); - - usage->next_usage = first_usage; - if (first_usage) first_usage->prev_usage = usage; - - if (!last_usage) last_usage = usage; - - first_usage = usage; - return first_usage; - } - - - // --- SubResourcesCPU --- - - ResourceListStateCPU& SubResourcesCPU::slot(UINT id) - { - return (uniform || id == ALL_SUBRESOURCES) ? uniform_state : subres[id]; - } - - const ResourceListStateCPU& SubResourcesCPU::slot(UINT id) const - { - return (uniform || id == ALL_SUBRESOURCES) ? uniform_state : subres[id]; - } - - void SubResourcesCPU::expand(UINT count) - { - if (!uniform) return; - - // Materialize per-subres slots from the shared uniform chain: each slot - // copies uniform_state's first/last_usage (sharing the history nodes, - // which carry subres == ALL_SUBRESOURCES). The next per-subres - // transition forks its own node after that shared history — correct, at - // the cost of one extra barrier for that subresource on the frame it - // first diverges (design A). - if (subres.size() < count) subres.resize(count); - for (UINT i = 0; i < count; i++) subres[i] = uniform_state; - uniform = false; - } - - bool SubResourcesCPU::assert_shared_state(const TiledResourceManager* mapped) - { - if (uniform) return false; - if (subres.empty()) return false; - - auto is_required = [&](UINT i) { - // A subresource with no tile currently mapped to real memory - // (reserved/Virtual resource only) is structurally never part of - // the working set -- e.g. VSM_Atlas declares MaxPhysicalSlots - // (2048) array slices but only physical_page_count (~256) are - // ever tile-mapped; the rest can never be `used` and must not - // block the fold. - return !mapped || mapped->is_subresource_mapped_flat(i); - }; - - // Find a representative required-and-used subresource to seed the - // fold from. This runs in BOTH Dev and Retail (unlike the verify loop - // below) -- without a real `rep` there is nothing valid to collapse - // to, in any build. - UINT rep_index = UINT(-1); - for (UINT i = 0; i < (UINT)subres.size(); i++) - { - if (!is_required(i)) continue; - if (!subres[i].used) return false; - rep_index = i; - break; - } - if (rep_index == UINT(-1)) return false; - - // uniform_state.first_usage is untouched by expand()/per-subres - // transitions — it still marks how this resource entered the list, so - // only last_usage needs to move forward to the (now shared-again) tail. - ResourceUsage* rep = subres[rep_index].last_usage; - -#ifdef DEV - for (UINT i = rep_index + 1; i < (UINT)subres.size(); i++) - { - if (!is_required(i)) continue; - ASSERT(subres[i].used); - ASSERT(subres[i].get_usage() == rep->wanted_state); - } -#endif - - uniform_state.last_usage = rep; - uniform = true; - return true; - } - - void SubResourcesCPU::reset() - { - used = false; - uniform = true; - batch_touch_id = 0; // stale batch id from a prior frame must not match - uniform_state.reset(); - for (auto& s : subres) - s.reset(); - } - - CommandListType SubResourcesCPU::get_best_list_type_last() - { - CommandListType type = CommandListType::COPY; - - auto one = [&](const ResourceListStateCPU& cpu) { - if (!cpu.used) return; - type = Merge(type, cpu.last_usage->wanted_state.get_best_cmd_type()); - }; - - if (uniform) one(uniform_state); - else for (auto& cpu : subres) one(cpu); - - return type; - } - - CommandListType SubResourcesCPU::get_best_list_type_first() - { - CommandListType type = CommandListType::COPY; - - auto one = [&](const ResourceListStateCPU& cpu) { - if (!cpu.used) return; - type = Merge(type, cpu.first_usage->wanted_state.get_best_cmd_type()); - }; - - if (uniform) one(uniform_state); - else for (auto& cpu : subres) one(cpu); - - return type; - } - - const ResourceListStateCPU& SubResourcesCPU::get_subres_state(UINT id) const - { - return slot(id); - } - - ResourceListStateCPU& SubResourcesCPU::get_subres_state(UINT id) - { - return slot(id); - } - - ResourceUsage* SubResourcesCPU::get_first_usage(UINT id) const - { - return slot(id).first_usage; - } - - ResourceUsage* SubResourcesCPU::get_last_usage(UINT id) const - { - return slot(id).last_usage; - } - - ResourceState SubResourcesCPU::get_first_state(UINT id) const - { - return get_first_usage(id)->wanted_state; - } - - ResourceState SubResourcesCPU::get_last_state(UINT id) const - { - return get_last_usage(id)->wanted_state; - } - - void SubResourcesCPU::merge_read_state(CommandListType type, SubResourcesGPU& state) - { - if (!used) return; + SubresRange range = SubresRange::all(); - for (int i = 0; i < subres.size(); i++) + if (subres != ALL_SUBRESOURCES && resource->get_desc().is_texture()) { - auto my_state = get_subres_state(i); - auto subres_layout = state.get_subres_state(i).layout; - - if (subres_layout == TextureLayout::NONE) - continue; - - auto merged = merge_layout(subres_layout, my_state.first_usage->wanted_state.layout); - - if (merged) - my_state.first_usage->wanted_state.layout = *merged; - else - ASSERT(false); - - my_state.check_valid(nullptr); - //ASSERT(my_state.first_usage == my_state.last_usage); + const auto& tex = resource->get_desc().as_texture(); + range = SubresRange::single(tex.get_mip(subres), tex.get_array(subres), tex.get_plane(subres)); } - } - - - // --- SubResourcesGPU --- - - bool SubResourcesGPU::is_valid() const - { - for (auto& s : subres) - return true; - return false; - } - void SubResourcesGPU::operator=(const TextureLayout& layout) - { - for (auto& s : subres) - s.layout = layout; + transition(resource, before, after, range, flags); } - CommandListType SubResourcesGPU::get_best_list_type() + void Barriers::transition(const Resource* resource, ResourceState before, ResourceState after, SubresRange range, BarrierFlags flags) { - CommandListType type = CommandListType::COPY; - - for (auto& s : subres) - { - if (s.layout != TextureLayout::NONE) - type = Merge(type, get_best_cmd_type(s.layout)); - } - - return type; - } - - void SubResourcesGPU::set_cpu_state(const SubResourcesCPU& cpu_state) - { - for (int i = 0; i < subres.size(); i++) - { - auto& gpu = get_subres_state(i); - auto& cpu = cpu_state.get_subres_state(i); - - if (!cpu.used) continue; - - auto last_usage = cpu_state.get_last_usage(i); - gpu.layout = last_usage->wanted_state.layout; - } - } - - void SubResourcesGPU::set_cpu_state_first(const SubResourcesCPU& cpu_state) - { - for (int i = 0; i < subres.size(); i++) - { - auto& gpu = get_subres_state(i); - auto& cpu = cpu_state.get_subres_state(i); - - if (!cpu.used) continue; - - auto first_usage = cpu_state.get_first_usage(i); - gpu.layout = first_usage->wanted_state.layout; - } - } - - const ResourceListStateGPU& SubResourcesGPU::get_subres_state(UINT id) const - { - // ALL_SUBRESOURCES resolves to subresource 0 as the representative — used - // by the uniform CPU fast path, where the GPU layout is uniform anyway. - return subres[id == ALL_SUBRESOURCES ? 0 : id]; - } - - ResourceListStateGPU& SubResourcesGPU::get_subres_state(UINT id) - { - return subres[id == ALL_SUBRESOURCES ? 0 : id]; - } - - void SubResourcesGPU::merge(SubResourcesCPU& other) - { - for (int i = 0; i < subres.size(); i++) - { - auto transition = other.get_first_usage(i); - - if (transition) - { - auto res = merge_layout(subres[i].layout, transition->wanted_state.layout); - if (res) - subres[i].layout = *res; - } - } - } - - - // --- ResourceStateManager --- - - ResourceStateManager::ResourceStateManager(const Resource* resource) : resource(resource) - { - } - - UINT ResourceStateManager::get_subres_count() - { - return static_cast(gpu_state.subres.size()); - } - - SubResourcesGPU ResourceStateManager::copy_gpu() const - { - return gpu_state; - } - - ResourceState ResourceStateManager::get_desired_state() const - { - auto flags = resource->get_desc().Flags; - if (check(flags & ResFlags::ShaderResource)) return ResourceStates::SHADER_RESOURCE; - if (check(flags & ResFlags::UnorderedAccess)) return ResourceStates::UNORDERED_ACCESS; - // No read usage declared — leave it in the copy-destination state. - return ResourceStates::COPY_DEST; - } - - void ResourceStateManager::set_resting_state(TextureLayout layout) - { - initial_layout = layout; - for (auto& e : gpu_state.subres) - e.layout = layout; - virgin = false; - } - - void ResourceStateManager::init_subres(int count, TextureLayout layout) - { - initial_layout = layout; - gpu_state.subres.resize(count); - for (auto& e : gpu_state.subres) - e.layout = layout; - - // Reset every per-command-list CPU state so that set_cpu_state_first() on - // the next barrier-compilation pass sees "never used" and falls back to the - // GPU state we just reset above, rather than overwriting it with a stale - // layout from a previous frame (e.g. COLOR_ATTACHMENT_OPTIMAL after present). - states.set_init_func([count](SubResourcesCPU& state) - { - state.used = false; - state.uniform = true; // start each list in the uniform fast path - state.batch_touch_id = 0; - state.uniform_state.reset(); - state.subres.resize(count); - for (auto& e : state.subres) - { - e.used = false; // prevents set_cpu_state_first from using stale layout - e.skip_end_decay = false; - } - }); - } - - bool ResourceStateManager::is_used(Transitions* list) const - { - SubResourcesCPU& s = get_state((list)); - return s.used; - } - - bool ResourceStateManager::assert_shared_state(Transitions* list) const - { - SubResourcesCPU& s = get_state((list)); - - const TiledResourceManager* mapped = resource->get_desc().is_virtual() - ? &const_cast(resource)->get_tiled_manager() - : nullptr; - - return s.assert_shared_state(mapped); - } - - SubResourcesCPU& ResourceStateManager::get_cpu_state(Transitions* list) const - { - auto& state = get_state((list)); - return state; - } - + ASSERT(resource); + ASSERT(IsFullySupport(type, before)); + ASSERT(IsFullySupport(type, after)); - void ResourceStateManager::transition(Transitions* list, ResourceState state, unsigned int s) const - { - if (check(state.access & (BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_WRITE | BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_READ))) + if (check(after.access & (BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_WRITE | BarrierAccess::RAYTRACING_ACCELERATION_STRUCTURE_READ))) ASSERT(check(resource->get_desc().Flags & ResFlags::Raytracing)); - if (check(resource->get_desc().Flags & ResFlags::Immutable)) - { - ASSERT(!state.has_write_bits() || state.has_copy_bits()); - - ASSERT(state.access != ResourceStates::NO_ACCESS.access); - } - - - auto& cpu_state = get_state((list)); - - bool need_add_uav = false; - - if (list->get_type() == CommandListType::COPY) - { - if (state.layout == TextureLayout::COPY_DEST || state.layout == TextureLayout::COPY_SOURCE) - state.layout = TextureLayout::COPY_QUEUE; - } - - ASSERT(state.is_valid(resource->get_type())); - - // Captured before the per-subres loop so every subresource of this - // transition sees the same virginity (the flag flips once, below). - const bool was_virgin = virgin; - - // `subres` here is the value stamped onto the emitted usage node: a real - // subresource index in expanded mode, or ALL_SUBRESOURCES in uniform mode - // (one node → one all-subresources barrier). - auto transition_one = [&](UINT subres) { - - auto& subres_cpu = cpu_state.slot(subres); - if (!subres_cpu.used) + // Merge into the previous barrier when this one continues it. + // + // The expand paths emit a run of subresources back to back with the same + // before/after, and flat subresource indexing is mip-major within a slice + // -- so consecutive indices are consecutive MIPS of one slice, and a + // whole mip chain collapses to a single range. A second case follows: + // once a slice's full mip chain is one range, the NEXT slice with the + // same mip span extends it along the slice axis. + // + // O(1) and order-preserving: only ever the immediately preceding entry is + // considered, so a barrier can never move past an unrelated one. + if (!barriers.empty() && !range.is_all()) + { + Barrier& prev = barriers.back(); + + if (prev.resource == resource && prev.before == before && prev.after == after + && prev.flags == flags && !prev.range.is_all() + && prev.range.first_plane == range.first_plane + && prev.range.num_planes == range.num_planes) { - if (!check(resource->get_desc().Flags & ResFlags::DisableStateTracking) && resource->get_desc().is_texture()) - { - // Seed node = the state the resource is IN at this list's first - // touch. Non-FG virgin resource whose first-ever use is a WRITE - // (incl. COPY_DEST upload) → UNKNOWN so compile_transitions - // emits a DISCARD activation (R4): contents are meaningless and - // about to be overwritten. A read-first virgin resource is - // initialized out-of-band, so must not be discarded. - // - // Otherwise seed from the TRACKED layout (gpu_state), not the - // creation-time initial_layout. initial_layout is only a guess - // and is wrong for any resource whose layout has since moved — - // e.g. an adopted history-prev, which carries the layout last - // frame's `current` ended in. Seeding the guess emitted barriers - // whose LayoutBefore disagreed with the real layout - // (D3D12 #1334 INCOMPATIBLE_BARRIER_LAYOUT). gpu_state equals - // initial_layout for a freshly created resource, so this is a - // strict improvement. - auto target = ResourceStates::NO_ACCESS; - if (!resource->frame_graph_managed && was_virgin && state.has_write_bits()) - target = ResourceStates::UNKNOWN; - else - target.layout = gpu_state.get_subres_state(subres).layout; - - subres_cpu.used = true; - subres_cpu.first_usage = subres_cpu.last_usage = (list)->add_usage((resource), subres, target); - - // ASSERT(!(!check(subres_cpu.first_usage->resource->get_desc().Flags & ResFlags::DisableStateTracking) && subres_cpu.first_usage->wanted_state.access == ResourceStates::NO_ACCESS.access)); - - auto transition = (list)->add_usage((resource), subres, state); - subres_cpu.add_usage(transition); - } - else + // Same slice, next mip. + if (prev.range.first_slice == range.first_slice + && prev.range.num_slices == range.num_slices + && prev.range.first_mip + prev.range.num_mips == range.first_mip) { - subres_cpu.used = true; - subres_cpu.first_usage = subres_cpu.last_usage = (list)->add_usage((resource), subres, state); + prev.range.num_mips += range.num_mips; + return; } - subres_cpu.check_valid(resource); - return; - } - { - auto last_state = subres_cpu.last_usage->wanted_state; - auto merged_state = merge_state(last_state, state); - if (merged_state) - { - subres_cpu.last_usage->wanted_state = *merged_state; - } - else + // Same mip span, next slice. + if (prev.range.first_mip == range.first_mip + && prev.range.num_mips == range.num_mips + && prev.range.first_slice + prev.range.num_slices == range.first_slice) { - auto transition = (list)->add_usage((resource), subres, state); - subres_cpu.add_usage(transition); + prev.range.num_slices += range.num_slices; + return; } - subres_cpu.check_valid(resource); } - }; - - - if (s != ALL_SUBRESOURCES) - { - // Subresource-scoped touch: leave the uniform mode (if any) and track - // this subresource independently from here on. - if (cpu_state.uniform) - cpu_state.expand((UINT)gpu_state.subres.size()); - transition_one(s); - } - else if (cpu_state.uniform) - { - // Whole-resource touch while still uniform: one shared node. - transition_one(ALL_SUBRESOURCES); - } - else - { - for (int i = 0; i < gpu_state.subres.size(); i++) - transition_one(i); } - // First use has now been recorded — later uses preserve contents - // (no more DISCARD). Re-discarding would only ever be safe anyway, but - // non-FG resources persist data across frames, so this must flip once. - virgin = false; - } - - - // optimization sector - void ResourceStateManager::prepare_state(Transitions* next_list, const SubResourcesGPU& gpu_state) const - { if (resource->get_desc().is_buffer()) - return; - - ASSERT(check(resource->get_desc().Flags & ResFlags::DisableStateTracking)); - - auto& cpu_state = get_state((next_list)); - - //if (!cpu_state.used) return; - - bool updated = false; - - auto merge_one = [&, this](UINT i) { - auto& gpu = gpu_state.get_subres_state(i); - auto& cpu = cpu_state.get_subres_state(i); - - if (!cpu.used) return; - - auto first_usage = cpu_state.get_first_usage(i); - - auto merged = merge_layout(gpu.layout, first_usage->wanted_state.layout); - if (merged) - first_usage->wanted_state.layout = *merged; - // ASSERT(first_usage->wanted_state.valid_begin()); - // ASSERT(first_usage->wanted_state.valid_begin()); - - cpu.check_valid(resource); - }; - - auto transition_one = [&](UINT i) { - auto& gpu = gpu_state.get_subres_state(i); - auto& cpu = cpu_state.get_subres_state(i); - - auto first_usage = cpu_state.get_first_usage(i); - - if (!cpu.used || gpu.layout != first_usage->wanted_state.layout) - { - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu.layout; - - auto point = (next_list)->add_usage((resource), i, target, TransitionType::ZERO); - cpu.set_zero_transition(point); - updated = true; - cpu.used = true; - cpu.check_valid(resource); - } - }; - - // Uniform CPU state → one pass stamping ALL_SUBRESOURCES; else per-subres. - if (cpu_state.uniform) - { - merge_one(ALL_SUBRESOURCES); - transition_one(ALL_SUBRESOURCES); - } + buffer_count++; else - { - for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); - for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); - } - - if (updated) - { - (next_list)->track_object(*(const_cast(resource))); - (next_list)->use_resource((resource)); - } - } - - void ResourceStateManager::prepare_state(Transitions* from, ResourceState wanted_state) const - { - if (resource->get_desc().is_buffer()) - return; - - ASSERT(check(resource->get_desc().Flags & ResFlags::DisableStateTracking)); - - auto& cpu_state = get_state((from)); - - bool updated = false; - - auto merge_one = [&, this](UINT i) { - auto& gpu = wanted_state; - auto& cpu = cpu_state.get_subres_state(i); + texture_count++; - if (!cpu.used) return; - - auto first_usage = cpu_state.get_first_usage(i); - - auto merged = merge_layout(gpu.layout, first_usage->wanted_state.layout); - if (merged) - first_usage->wanted_state.layout = *merged; - ASSERT(first_usage->wanted_state.valid_begin()); - ASSERT(first_usage->wanted_state.valid_begin()); - - cpu.check_valid(resource); - }; - - auto transition_one = [&](UINT i) { - auto& gpu = wanted_state; - auto& cpu = cpu_state.get_subres_state(i); - - if (!cpu.used) - { - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu.layout; + barriers.emplace_back(Barrier{ const_cast(resource), before, after, range, flags }); - auto point = (from)->add_usage((resource), i, target, TransitionType::ZERO); - cpu.set_zero_transition(point); - updated = true; - cpu.used = true; - - cpu.check_valid(resource); - return; - } - - auto first_usage = cpu_state.get_first_usage(i); - auto first_state = first_usage->wanted_state; - - if (gpu.layout != first_state.layout) - { - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu.layout; - - auto point = (from)->add_usage((resource), i, target, TransitionType::ZERO); - cpu.set_zero_transition(point); - updated = true; - } - - cpu.check_valid(resource); - }; - - // Uniform CPU state → one pass stamping ALL_SUBRESOURCES; else per-subres. - if (cpu_state.uniform) - { - merge_one(ALL_SUBRESOURCES); - transition_one(ALL_SUBRESOURCES); - } - else - { - for (int i = 0; i < gpu_state.subres.size(); i++) merge_one(i); - for (int i = 0; i < gpu_state.subres.size(); i++) transition_one(i); - } - - if (updated) - { - (from)->track_object(*(const_cast(resource))); - (from)->use_resource((resource)); - } - } - - void ResourceStateManager::alias_begin(Transitions* list) const - { - transition(list, ResourceStates::UNKNOWN, ALL_SUBRESOURCES); - } - - void ResourceStateManager::alias_end(Transitions* list) const - { - transition(list, ResourceStates::UNKNOWN, ALL_SUBRESOURCES); - } - - void ResourceStateManager::prepare_after_state(Transitions* from, const SubResourcesGPU& gpu_state) const - { - if (resource->get_desc().is_buffer()) - return; - - ASSERT(check(resource->get_desc().Flags & ResFlags::DisableStateTracking)); - - auto& cpu_state = get_state((from)); - - if (cpu_state.uniform) - { - // Uniform CPU state ⟺ uniform GPU handoff: subresource 0 represents - // all. One ALL_SUBRESOURCES transition keeps the resource uniform. - if (gpu_state.subres[0].layout != TextureLayout::NONE) - { - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu_state.subres[0].layout; - transition(from, target, ALL_SUBRESOURCES); - } - } - else - { - for (int i = 0; i < gpu_state.subres.size(); i++) - { - if (gpu_state.subres[i].layout == TextureLayout::NONE) continue; - - auto target = ResourceStates::NO_ACCESS; - target.layout = gpu_state.subres[i].layout; - transition(from, target, i); - } - } - - if (!cpu_state.used) - { - (from)->track_object(*(const_cast(resource))); - (from)->use_resource((resource)); - } - } - - void ResourceStateManager::chain_lists(Transitions* from, Transitions* to) const - { - if (resource->get_desc().is_buffer()) return; - - auto& prev = get_state(from); - auto& next = get_state(to); - - if (!prev.used || !next.used) return; - - // A SYNC_NONE / NO_ACCESS node: the first-touch seed planted by - // transition_one, a zero-transition prepended by prepare_state, or a - // release appended by prepare_after_state. A list can carry several of - // them at either end, around its real usages. - auto is_none = [](const ResourceUsage* u) - { - return u->wanted_state.operation == BarrierSync::NONE - && u->wanted_state.access == BarrierAccess::NO_ACCESS; - }; - - auto one = [&](UINT subres) - { - auto* last = prev.slot(subres).last_usage; - auto* first = next.slot(subres).first_usage; - - if (!last || !first) return; - - // Walk forward over `to`'s SYNC_NONE prefix to its first REAL usage. - auto* target = first; - while (is_none(target) && target->next_usage) - target = target->next_usage; - - // `to` has NO real usage — only SYNC_NONE reconciliation nodes - // (redundant seed + release from prepare_state/prepare_after_state). If - // `from` really accessed the resource, `to`'s first node re-declares the - // state with SyncBefore=NONE AFTER that access → #1417. Walk `from` back - // to its last REAL usage and hand it to `to`'s first node so the barrier - // carries a real SyncBefore; suppress `from`'s own end release (the - // resource stays live for `to`). The remaining `to` nodes now chain off a - // real predecessor instead of a SYNC_NONE seed. - if (is_none(target)) - { - while (is_none(last) && last->prev_usage) - last = last->prev_usage; - - if (!is_none(last)) - { - // `to`'s LAST node is its exit release; the earlier none nodes are - // redundant re-seeds. Suppress the seeds (so none emit a bare - // SyncBefore=NONE after the access) and hand the release `from`'s - // last real usage as predecessor so it carries real sync. - auto* lastnode = first; - while (lastnode->next_usage) lastnode = lastnode->next_usage; - - for (auto* u = first; u != lastnode; u = u->next_usage) - u->suppressed = true; - - if (!lastnode->prev_usage || is_none(lastnode->prev_usage)) - lastnode->prev_usage = last; - - // Suppress `from`'s OWN trailing release nodes for this subresource - // too — the resource stays live for `to` (and whatever real consumer - // follows it). Setting skip_end_decay alone only stops the generic - // transition_one end-decay; the explicit prepare_after_state release - // nodes appended between `last` and `from`'s recorded last_usage still - // publish DS_WRITE->NO_ACCESS (SyncAfter=NONE) unless suppressed here, - // which is exactly the #1417 seen on PSSM_Depths' non-rendered array - // slices (each cascade needs the whole array but renders one slice, so - // the other slices resolve to this reconciliation-only branch). - for (auto* u = prev.slot(subres).last_usage; u != last; u = u->prev_usage) - u->suppressed = true; - - prev.slot(subres).skip_end_decay = true; - } - return; - } - - // Walk back over `from`'s SYNC_NONE suffix to its last REAL usage. - // Chaining onto a release node would emit `SyncBefore == NONE`, which - // is precisely the thing being avoided. - while (is_none(last) && last->prev_usage) - last = last->prev_usage; - - if (is_none(last)) return; - - // Keep `from`'s subresource live past the boundary — suppress its - // trailing SYNC_NONE suffix and stop the generic end-of-list release. - // This runs UNCONDITIONALLY (before the predecessor guard below): when - // `to` expanded from a uniform `need`, several of its subresources - // resolve to ONE shared usage node. The first sibling chains that node - // and sets its prev_usage; without suppressing here first, every later - // sibling would hit the "predecessor already real" guard and return - // with `from`'s decay for THAT subresource still in place — the release - // publishes SyncAfter == NONE and `to` then touches the subresource in - // the same ECL scope (#1417). The decay must be dropped per subresource - // `to` actually uses; the shared node only needs one predecessor. - for (auto* u = prev.slot(subres).last_usage; u != last; u = u->prev_usage) - u->suppressed = true; - - prev.slot(subres).skip_end_decay = true; - - // Replace only a missing or SYNC_NONE predecessor; never clobber a - // real one (already chained by a sibling subresource sharing this node, - // or a genuine in-list transition). A leading SYNC_NONE prefix on `to` - // would publish SyncBefore == NONE after the resource was touched, so - // suppress it too — but only on the path that actually (re)chains. - if (target->prev_usage && !is_none(target->prev_usage)) return; - - // NOTE: this can drop a resource's initializing DISCARD. If one of the - // bypassed nodes is the alias_begin (state UNKNOWN) and `target` is then - // rewired to a real predecessor, compile_transitions no longer sees - // prev_state == UNKNOWN and never sets BarrierFlags::DISCARD — D3D12 then - // reports the placed resource as uninitialized (#1422). Reproduces with - // auto_async enabled, on sky_cubemap_filtered. Fixing it needs to tell a - // stale cross-frame tracked state on a freshly placed resource apart from - // a genuine initialise-and-write earlier in the same frame; blindly - // keeping the DISCARD would destroy the earlier list's writes. - for (auto* u = first; u != target; u = u->next_usage) - u->suppressed = true; - - target->prev_usage = last; - }; - - if (next.uniform) - { - if (prev.uniform) - { - // Both sides track the whole resource as one chain. - one(ALL_SUBRESOURCES); - } - else - { - // `next` reads the whole resource through a single shared node, but - // `prev` left it in per-subresource tracking. The one() loop cannot be - // used here: its first iteration chains the shared node, and every later - // iteration then hits the "predecessor already real" guard and returns - // BEFORE suppressing `prev`'s release for that subresource. Those - // releases publish SyncAfter == NONE, and the shared read node still - // touches subres 1..N in the same ECL scope (#1417), while their stale - // per-subres before-layout drives #1334. Suppress `prev`'s exit release - // for EVERY subresource it used, and hand the shared node one real - // predecessor (the resting read state is uniform across subresources, so - // any real last-usage is a valid before-state for the ALL barrier). - auto* first = next.uniform_state.first_usage; - if (first) - { - auto* target = first; - while (is_none(target) && target->next_usage) - target = target->next_usage; - - ResourceUsage* rep_last = nullptr; - - // Per-subresource before-state as seen at this boundary, defaulted - // to the read state so untouched / already-matching subresources - // become no-ops in the split path below. - std::vector subres_before(gpu_state.subres.size(), target->wanted_state); - - for (UINT i = 0; i < gpu_state.subres.size(); i++) - { - auto* last = prev.slot(i).last_usage; - if (!last) continue; - - while (is_none(last) && last->prev_usage) - last = last->prev_usage; - if (is_none(last)) continue; // `prev` never really touched subres i - - // Keep subres i live past the boundary: drop its trailing - // SYNC_NONE release and stop the generic end-of-list decay. - for (auto* u = prev.slot(i).last_usage; u != last; u = u->prev_usage) - u->suppressed = true; - prev.slot(i).skip_end_decay = true; - - subres_before[i] = last->wanted_state; // this subresource's real before-state - rep_last = last; - } - - if (rep_last) - { - if (is_none(target)) - { - // `next` carries only SYNC_NONE nodes — hand its exit release - // a real predecessor and suppress the redundant seeds. - auto* lastnode = first; - while (lastnode->next_usage) lastnode = lastnode->next_usage; - for (auto* u = first; u != lastnode; u = u->next_usage) - u->suppressed = true; - if (!lastnode->prev_usage || is_none(lastnode->prev_usage)) - lastnode->prev_usage = rep_last; - } - else - { - for (auto* u = first; u != target; u = u->next_usage) - u->suppressed = true; - if (!target->prev_usage || is_none(target->prev_usage)) - target->prev_usage = rep_last; - - // If `prev` left the subresources in DIFFERENT states, a single - // ALL barrier cannot express it (it would carry one before-layout - // and mismatch the rest -> #1334/#1417). Record the per-subres - // before-states so compile_transitions emits a split barrier; - // after it the resource is uniform in the read state again. When - // the states ARE uniform, keep the single ALL barrier (cheaper). - bool split = false; - for (UINT i = 1; i < subres_before.size(); i++) - if (!(subres_before[i] == subres_before[0])) { split = true; break; } - - if (split) - target->split_before = std::move(subres_before); - } - } - } - } - } - else - { - for (UINT i = 0; i < gpu_state.subres.size(); i++) - one(i); - } } - void ResourceStateManager::connect(Transitions* from, Transitions* to) - { - if (resource->get_desc().is_buffer()) - return; - - ASSERT(check(resource->get_desc().Flags & ResFlags::DisableStateTracking)); - - auto& prev_state = get_state((from)); - auto& next_state = get_state((to)); - - auto transit = [&](UINT i) - { - auto last_usage = prev_state.get_last_usage(i); - auto first_usage = next_state.get_first_usage(i); - ASSERT(!first_usage->prev_usage); - ASSERT(last_usage->point->cmd_list != first_usage->point->cmd_list); - - if (first_usage->wanted_state != last_usage->wanted_state) - { - first_usage->prev_usage = last_usage; - first_usage->debug = true; - } - }; - - for (int i = 0; i < gpu_state.subres.size(); i++) transit(i); - } } diff --git a/sources/HAL/HAL.ResourceStates.ixx b/sources/HAL/HAL.ResourceStates.ixx index 742ee640..b04280b2 100644 --- a/sources/HAL/HAL.ResourceStates.ixx +++ b/sources/HAL/HAL.ResourceStates.ixx @@ -6,6 +6,7 @@ export module HAL:ResourceStates; import Core; import :Types; +import :DescriptorHeap; // ResourceInfo, ALL_SUBRESOURCES using namespace HAL; @@ -18,12 +19,118 @@ export class TiledResourceManager; - enum class TransitionType :int + // Per-(resource, command list) state. Extends the generic tracked-object + // state (used / alias_ended, which the command list's track_object needs) + // with the resource-specific tracking the barrier system builds on, so a + // resource carries ONE per-list state object rather than a separate + // parallel store. + // + // reset() runs whenever a command list is reused for a new frame (see + // ObjectState::get_state) -- it must chain to the base explicitly, since + // declaring reset() here hides TrackedObjectState::reset(). + // One place a resource was used inside a single operation. A resource can + // be bound several times within one operation (several views, several + // copy regions), so an operation holds a list of these rather than a + // single entry. + // + // Two ways a use names its subresources: + // - through a bound view (`info`), which carries the mip/array/plane + // range the barrier system has to expand; + // - as a bare subresource index (`subres`), for uses that have no + // descriptor at all -- copies name a subresource directly. + // `info` is null in the second case. + struct OperationUsage { - ZERO, - LAST + const ResourceInfo* info = nullptr; + UINT subres = ALL_SUBRESOURCES; + + // How the resource is used here. Kept even though the operation + // already carries a BarrierSync: the sync class alone does not + // determine access/layout for a bare-subresource use -- a COPY + // operation is COPY_SOURCE for one resource and COPY_DEST for + // another, and nothing in `subres` distinguishes them. + ResourceState state; + + // Which command within the operation recorded this. An operation is + // a RUN of same-class work, so several dispatches share one -- and + // two usages with different `step` values are different dispatches, + // which is what distinguishes "bound twice for one dispatch" from + // "written by one dispatch, then touched by the next". + uint step = 0; + + OperationUsage() = default; + OperationUsage(const ResourceInfo* info, ResourceState state) : info(info), state(state) {} + OperationUsage(UINT subres, ResourceState state) : subres(subres), state(state) {} }; + struct TrackedResourceState : TrackedObjectState + { + // True once this resource has been added to the list's + // used_resources, i.e. the barrier pass will visit it. + // + // Deliberately NOT TrackedObjectState::used, which means "added to + // tracked_resources" (kept alive for the list's lifetime) and is set + // by CommandListBase::track_object. A resource now carries ONE state + // object for both, so a single flag cannot serve both: track_object + // runs first for non-FrameGraph resources, and use_resource would + // see `used` already set and never list the resource -- leaving + // compile_transitions with nothing to do and no barriers emitted. + bool listed = false; + + // The state this list will FIND the resource in, when the group has + // no way to know it -- i.e. this list holds the group's first touch + // of it. Declared by whoever owns the resource's scheduling: the + // FrameGraph knows the whole pass graph, so for its own resources it + // can say what the previous pass left behind, across a group + // boundary the group itself cannot see over. + // + // Unset means "assume the resting layout", which is correct for any + // resource the group model owns end to end (every group hands those + // back at rest). This is the replacement for the old + // prepare_state()/zero-transition seed node. + bool has_entry_state = false; + ResourceState entry_state; + + // Every operation on this list that touched this resource, keyed by + // CmdListOperation::index -> the places it was used in that + // operation. Ordered map: iteration is already in operation order, + // which is the order the barrier pass needs. + // + // Keyed by index, not pointer: the list's `operations` is a vector, + // so pointers into it would dangle as it grows -- and + // CmdListOperation isn't visible from here anyway (CommandList + // imports ResourceStates, not the reverse). + std::map> operations; + + void reset() + { + TrackedObjectState::reset(); + listed = false; + has_entry_state = false; + operations.clear(); + } + }; + + + // The LAYOUT a resource sits in whenever no command list is actively + // using it, derived from what it was created to be used for. Every list + // leaves a resource here when it is done with it, so the next list can + // assume it without any cross-list bookkeeping. + // + // Only a layout: sync and access describe a specific access at a + // specific point, and a resource at rest is by definition not being + // accessed by anyone. + TextureLayout resting_layout(const Resource* resource); + + // Fill a resting layout back out into the ResourceState a barrier needs. + // The access follows from the layout; the sync stays generic, because a + // barrier against a resting resource is crossing into (or out of) a + // scope where no specific producer/consumer stage is known -- and it + // must NOT be BarrierSync::NONE, which D3D12 rejects once a resource has + // been accessed. + ResourceState state_at_rest(TextureLayout layout); + + bool IsCompatible(CommandListType a, CommandListType b); bool IsFullySupport(CommandListType type, const ResourceState& states); CommandListType Merge(CommandListType a, CommandListType b); @@ -39,20 +146,68 @@ export DISCARD = 4 }; + // A rectangle in (mip x array-slice x plane) space -- the same six numbers + // D3D12_BARRIER_SUBRESOURCE_RANGE carries, and the same shape a VIEW + // already has. Tracking is still per flat subresource index; this is how + // a run of those is EXPRESSED once it reaches the API, so a Hi-Z level + // spanning 256 slices costs one barrier instead of 256. + // + // is_all() is the whole-resource form. It is not the same as a range that + // happens to cover everything: D3D12 has a dedicated sentinel for it and + // ignores the other fields. + struct SubresRange + { + uint first_mip = ALL_SUBRESOURCES; // sentinel == whole resource + uint num_mips = 0; + uint first_slice = 0; + uint num_slices = 0; + uint first_plane = 0; + uint num_planes = 0; + + bool is_all() const { return first_mip == ALL_SUBRESOURCES; } + + static SubresRange all() { return {}; } + + // One flat subresource index, decomposed. Flat indexing is + // mip-major within a slice (mip + slice*MipLevels + plane*...), so + // CONSECUTIVE indices are consecutive mips of one slice -- which is + // what makes the incremental merge in Barriers::transition work. + static SubresRange single(uint mip, uint slice, uint plane) + { + return SubresRange{ mip, 1, slice, 1, plane, 1 }; + } + + bool operator==(const SubresRange&) const = default; + }; + struct Barrier { Resource* resource; ResourceState before; ResourceState after; - UINT subres; + SubresRange range; BarrierFlags flags; }; + // A single flat subresource index standing in for a range, for consumers + // that key on one (breakpoints, the debugger's per-subresource + // continuity tracking). Exact when the range is one subresource; + // ALL_SUBRESOURCES otherwise, which is how those consumers already treat + // "covers more than one". + uint representative_subres(const Resource* resource, const SubresRange& range); class Barriers { std::vector barriers; + // How many of `barriers` target a buffer and how many a texture. + // D3D12 wants those split into two separate native arrays, and the + // resource desc is already in hand here at record time -- so the + // backend can size both arrays exactly instead of growing them and + // re-walking the list to find out how big they should have been. + uint buffer_count = 0; + uint texture_count = 0; + CommandListType type; public: @@ -61,288 +216,64 @@ export void clear(); const std::vector& get_barriers() const; - void transition(const Resource* resource, ResourceState before, ResourceState after, UINT subres, BarrierFlags flags = BarrierFlags::SINGLE); - - }; - - struct UsagePoint; - - struct ResourceUsage - { - Resource* resource = nullptr; - ResourceState wanted_state; - UINT subres = -1; - - ResourceUsage* prev_usage = nullptr; - ResourceUsage* next_usage = nullptr; - - UsagePoint* point = nullptr; - - bool debug = false; - - // Set by ResourceStateManager::chain_lists when this usage is a - // SYNC_NONE list-boundary marker (a seed, a prepare_state zero - // transition, or a prepare_after_state release) that got bypassed - // because the neighbouring list is in the same ExecuteCommandLists - // group. compile_transitions must not emit a barrier for it: inside - // one ECL scope a SyncBefore/SyncAfter of NONE is illegal once the - // resource has been touched (#1417). - bool suppressed = false; - - // Phase 5 maximal split: set by chain_lists when this is a uniform - // (ALL_SUBRESOURCES) usage whose source list left the resource in a - // MIXED per-subresource state (e.g. a producer that writes mip 0 as - // UAV but leaves the rest in SHADER_RESOURCE). A single ALL barrier - // can only carry one before-layout, so it mismatches whichever - // subresources differ (D3D12 #1334) and its SYNC_NONE decay makes them - // untouchable (#1417). When non-empty, compile_transitions emits one - // barrier per subresource using split_before[i] as that subresource's - // real before-state instead of the single ALL barrier; entries equal to - // wanted_state (untouched or already-matching subresources) emit - // nothing. After this usage the resource is uniform again. - std::vector split_before; - }; - - struct UsagePoint - { - uint index; - std::vector usages; - HAL::Barriers transitions; - bool start = false; - UsagePoint* prev_point = nullptr; - UsagePoint* next_point = nullptr; - Transitions* cmd_list; - BarrierSync operation; - UsagePoint(CommandListType type); - }; - - struct ResourceListStateCPU - { - ResourceUsage* first_usage = nullptr; - ResourceUsage* last_usage = nullptr; - - bool used = false; - bool need_discard = false; - - // A LATER list in the same ExecuteCommandLists group also uses THIS - // subresource, so its state was handed over directly (chain_lists) and - // this list must not emit the end-of-list release to - // NO_ACCESS/SYNC_NONE — inside one ECL - // scope that makes it untouchable afterwards (D3D12 #1417). Tracked - // per subresource: chaining is per subresource, and a resource whose - // mips are only partially carried forward must still release the rest, - // or it never returns to `initial_layout` and the next group's seed - // mismatches (D3D12 #1334). - bool skip_end_decay = false; - - ResourceState get_first_usage(); - ResourceState get_usage(); - void reset(); -#ifdef DEV - void check_valid(const Resource* resource); -#else - void check_valid(const Resource*) {} -#endif - ResourceUsage* add_usage(ResourceUsage* usage); - ResourceUsage* set_zero_transition(ResourceUsage* usage); - }; - - struct SubResourcesGPU; - struct SubResourcesCPU - { - std::vector subres; - - bool used = false; - - // Operation batching: the id of the batch this resource was last - // touched in, and the state it was transitioned to. The command - // list compares batch_touch_id against its monotonic current_batch_id - // to detect a same-batch reuse that needs a split. Reset per frame. - uint batch_touch_id = 0; - ResourceState batch_touch_state; - - // Which subresources were read/written so far in this batch, for - // detecting data dependencies that need ordering even when the - // state does not change (UAV->UAV). Subresource index hashed into - // 64 bits: exact for typical mip/slice counts, conservative past - // that (a false overlap costs a split, never misses a hazard). - uint64 batch_write_mask = 0; - uint64 batch_read_mask = 0; - - // Uniform fast path: while `uniform`, all subresources are tracked as - // one chain (`uniform_state`) whose usage nodes carry subres == - // ALL_SUBRESOURCES, so a full-resource transition costs one node/one - // barrier instead of one per mip/array/plane. The first subresource- - // scoped transition calls expand(): each per-subres slot is seeded - // from uniform_state (sharing the chain history) and the resource is - // per-subres for the rest of the list. slot() resolves either mode, so - // read helpers work unchanged; only mutating loops must run once in - // uniform mode (see transition/prepare_state). - bool uniform = true; - ResourceListStateCPU uniform_state; - - ResourceListStateCPU& slot(UINT id); - const ResourceListStateCPU& slot(UINT id) const; - void expand(UINT count); - - // Reverse of expand(): caller asserts every subresource has - // re-converged to the same state (e.g. a batched pass that touched - // every slice independently but landed them all back on - // SHADER_RESOURCE) and folds back to the uniform fast path so the - // NEXT full-resource transition costs one node/barrier again instead - // of one per subresource. DEV builds verify the claim (ASSERT per - // subresource); Retail trusts it unconditionally and collapses in - // O(1) — same contract as the standard library's assert(). Returns - // false only if there is nothing to collapse (already uniform, or - // never expanded/touched). - // - // The DEV verify is THIS LIST's local bookkeeping only (every - // subres[i].wanted_state agrees) — it is NOT proof the fold is - // safe, and the reason is a specific, known mechanism, not a vague - // one: once folded, slot(i) resolves to the single shared - // uniform_state for ANY index, including ones the fold's own - // verification never individually covered. SubResourcesGPU::merge() - // persists per-subresource layout ACROSS FRAMES (not just this - // list) by looping the full declared subresource count and calling - // other.get_first_usage(i) unconditionally, no `used` guard — post- - // fold that silently stamps the persistent layout for every - // declared subresource, including ones never actually verified, - // corrupting the record for whichever one the debug layer - // disagrees with later. Confirmed live twice (2026-08): VSM_PageHiZ - // (#1334, 3584x, its once-ever cold-start clear_uav path) and - // VSM_Atlas even WITH the mapped-subresource mask below applied - // correctly and the DEV assert passing (#1334, 72x) — see - // [[project-assert-shared-state-vsm-pagehiz]]. A real fix needs - // merge() (or an equivalent) to stop trusting slot(i) for indices - // the fold didn't individually prove; not attempted yet. Until - // then, treat "DEV assert passed" as necessary, not sufficient — - // any resource whose layout persists across frames via merge() - // (i.e. most things) is not provably safe to fold, live validation - // or not. - // - // `mapped` (optional): for a reserved/Virtual resource, pass its - // TiledResourceManager so subresources with no tile currently - // mapped to real memory are skipped entirely -- neither required - // to be `used` nor compared against the representative state. - // Without this a resource like VSM_Atlas (2048 declared array - // slices, ~256 ever tile-mapped) could never collapse: the - // declared-but-never-backed slices would always fail the "every - // subresource used" precondition. Mapped subresources still go - // through the exact same used/state checks as before. - bool assert_shared_state(const TiledResourceManager* mapped = nullptr); - - void reset(); - CommandListType get_best_list_type_last(); - CommandListType get_best_list_type_first(); - const ResourceListStateCPU& get_subres_state(UINT id) const; - ResourceListStateCPU& get_subres_state(UINT id); - ResourceUsage* get_first_usage(UINT id) const; - ResourceUsage* get_last_usage(UINT id) const; - ResourceState get_first_state(UINT id) const; - ResourceState get_last_state(UINT id) const; - void merge_read_state(CommandListType type, SubResourcesGPU& state); - }; + uint get_buffer_count() const { return buffer_count; } + uint get_texture_count() const { return texture_count; } + // Flat subresource index (or ALL_SUBRESOURCES). Decomposes into a + // range and merges with the previous barrier where possible. + void transition(const Resource* resource, ResourceState before, ResourceState after, UINT subres, BarrierFlags flags = BarrierFlags::SINGLE); - struct ResourceListStateGPU - { - TextureLayout layout; - }; + // Explicit range. Same merging. + void transition(const Resource* resource, ResourceState before, ResourceState after, SubresRange range, BarrierFlags flags = BarrierFlags::SINGLE); - struct SubResourcesGPU - { - std::vector subres; - - bool is_valid() const; - void operator=(const TextureLayout& layout); - CommandListType get_best_list_type(); - void set_cpu_state(const SubResourcesCPU& cpu_state); - void set_cpu_state_first(const SubResourcesCPU& cpu_state); - const ResourceListStateGPU& get_subres_state(UINT id) const; - ResourceListStateGPU& get_subres_state(UINT id); - void merge(SubResourcesCPU& other); }; - - - class ResourceStateManager : public ObjectState + // One contiguous run of same-class work on a command list. A list is a + // sequence of these: begin_op() starts a new one only when the incoming + // operation class differs from the one currently at the back, so a run + // of same-class commands grows a single CmdListOperation instead of + // producing one entry per command. + // + // Also owns the two barrier groups that bracket its work. Batching is + // therefore structural rather than a separate layer: everything that + // must happen ahead of this operation lands in the one `barriers_before` + // group, and the recorder emits each group at its own reserved point in + // the command stream (DelayedCommandList::func_barrier). + // + // Lives here rather than in :CommandList because the recorder has to + // name the type, and :CommandList imports :CommandListRecorder, not the + // other way round. + struct CmdListOperation { - const Resource* resource; - - protected: - mutable SubResourcesGPU gpu_state; - - - protected: - // Non-FG resources: true until their first-ever use. The first - // activation of a virgin resource discards (UNDEFINED->state) since - // its contents are meaningless (fresh/placed memory) — point R4. - // Flips false permanently after the first transition; later uses - // preserve contents. FG resources ignore this (own activation path). - mutable bool virgin = true; - - public: - virtual ~ResourceStateManager() = default; - TextureLayout initial_layout; - using ptr = std::unique_ptr; - UINT get_subres_count(); - ResourceStateManager(const Resource* resource); - SubResourcesGPU copy_gpu() const; - void init_subres(int count, TextureLayout layout); - - // The canonical READ state this resource should rest in after upload, - // derived from its declared usage flags (ShaderResource -> SHADER_RESOURCE, - // etc.). Used by the UploadManager to transition a freshly-uploaded - // resource out of COPY_DEST/COMMON into a strictly-defined read state. - ResourceState get_desired_state() const; - - // Pin the persistent resting state to `layout`. Called once by the - // UploadManager's post-upload transition so every later command list - // seeds this resource in its read state (a no-op) and never decays it - // back to COMMON. This is the strict replacement for the old - // gpu_state-as-first-list-link behaviour. - void set_resting_state(TextureLayout layout); - - SubResourcesCPU& get_cpu_state(Transitions* list) const; - - bool is_used(Transitions* list) const; - - // See SubResourcesCPU::assert_shared_state — collapses this - // resource's per-subresource tracking on `list` back to the uniform - // fast path if (DEV: verified; Retail: trusted) every subresource is - // currently at the same state. For a reserved/Virtual resource, - // automatically narrows that requirement to tile-mapped - // subresources only (via this resource's own TiledResourceManager) - // — declared-but-never-mapped subresources (e.g. VSM_Atlas's - // unused array headroom) are skipped rather than blocking the - // fold. - bool assert_shared_state(Transitions* list) const; - - - void transition(Transitions* list, ResourceState state, unsigned int subres) const; - - void prepare_state(Transitions* from, const SubResourcesGPU& subres) const; - void prepare_state(Transitions* from, ResourceState state) const; - - void prepare_after_state(Transitions* from, const SubResourcesGPU& subres) const; - - void alias_begin(Transitions* list) const; - void alias_end(Transitions* list) const; - - void connect(Transitions* from, Transitions* to); - - // `from` and `to` are two lists that - // will be submitted inside ONE ExecuteCommandLists scope, `from` - // first. Gives `to`'s first real usage `from`'s last usage as its - // predecessor — so the emitted barrier carries a real SyncBefore - // instead of the SYNC_NONE seed — and marks `from` to skip its - // end-of-list release. Result: one SYNC_NONE entry and one - // SYNC_NONE exit per resource per group instead of one per list. - void chain_lists(Transitions* from, Transitions* to) const; - + BarrierSync type = BarrierSync::NONE; + + // Position of this operation in its list's `operations` sequence + // (0-based). Stamped at creation and never changed, so it stays + // valid as a stable ordering key -- "which operation came first" + // is a plain integer compare, no pointer chasing. + uint index = 0; + + // Runs immediately before this operation's commands: the state + // transitions it needs, plus first-use initialization (discards). + HAL::Barriers barriers_before; + + // Runs immediately after this operation's commands, before the next + // operation begins -- for things that must trail the work rather + // than precede it, e.g. end-of-aliasing barriers. + HAL::Barriers barriers_after; + + // Whether barriers_after has already been given its point in the + // command stream. Reserving it twice emits the SAME barrier group + // twice: the second run declares a LayoutBefore the first one + // already moved past, which D3D12 rejects (#1334). That happens + // whenever something appends to a list after CommandList::end() + // closed it -- process_transitions does exactly that. + bool closed = false; + + CmdListOperation(CommandListType list_type, BarrierSync type, uint index) + : type(type), index(index), barriers_before(list_type), barriers_after(list_type) {} }; - } } diff --git a/sources/HAL/HAL.Sampler.ixx b/sources/HAL/HAL.Sampler.ixx index bd87b80f..542ea1be 100644 --- a/sources/HAL/HAL.Sampler.ixx +++ b/sources/HAL/HAL.Sampler.ixx @@ -91,8 +91,14 @@ namespace HAL SamplerDesc SamplerShadowDesc = SamplerDesc{ Filter::LINEAR, Filter::LINEAR, Filter::POINT, TextureAddressMode::CLAMP, TextureAddressMode::CLAMP , TextureAddressMode::CLAMP,0.0f,16, ComparisonFunc::NONE, float4(1,1,1,1), 0, std::numeric_limits::max() }; + // Reversed-Z (clear=0, closer fragments have larger stored z): lit if + // the compare value (current fragment depth) is >= the sampled + // texel. Was LESS_EQUAL -- correct for direct-Z, never updated when + // this project switched to reversed-Z, and unused anywhere until + // now (confirmed: no .sig file referenced it), so safe to flip in + // place rather than add a second desc. SamplerDesc SamplerShadowComparisonDesc = SamplerDesc{ Filter::LINEAR, Filter::LINEAR, Filter::LINEAR, TextureAddressMode::CLAMP, TextureAddressMode::CLAMP , - TextureAddressMode::CLAMP,0.0f,16, ComparisonFunc::LESS_EQUAL, float4(1,1,1,1), 0, std::numeric_limits::max() }; + TextureAddressMode::CLAMP,0.0f,16, ComparisonFunc::GREATER_EQUAL, float4(1,1,1,1), 0, std::numeric_limits::max() }; SamplerDesc SamplerVolumeWrapDesc = SamplerDesc{ Filter::POINT, Filter::POINT, Filter::POINT, TextureAddressMode::WRAP, TextureAddressMode::WRAP , TextureAddressMode::WRAP,0.0f,16, ComparisonFunc::LESS_EQUAL, float4(1,1,1,1), 0, std::numeric_limits::max() }; diff --git a/sources/HAL/HAL.SubresRangeMap.ixx b/sources/HAL/HAL.SubresRangeMap.ixx new file mode 100644 index 00000000..611a3239 --- /dev/null +++ b/sources/HAL/HAL.SubresRangeMap.ixx @@ -0,0 +1,235 @@ +export module HAL:SubresRangeMap; + +import Core; +import :Types; +import :ResourceStates; + +using namespace HAL; + +export namespace HAL +{ + // Per-subresource state, stored as RANGES rather than one entry per + // subresource. + // + // The barrier system's tracking is a map "which state is each subresource + // in". Keyed per flat index that costs one entry per subresource, which for + // a 2048-slice atlas or a 7x256 Hi-Z pyramid is thousands of entries and + // thousands of individual barriers. But the things that produce those states + // are VIEWS, and a view is always a rectangle in (mip x slice x plane) + // space -- so the natural key is that rectangle. + // + // Invariants: + // - entries are pairwise DISJOINT (assign() subtracts before inserting) + // - `uniform`, when set, is the state of everything no entry covers + // - entries are always concrete; the whole-resource form lives in + // `uniform`, which is why the map needs the resource's bounds + class SubresRangeMap + { + public: + struct Entry + { + SubresRange range; + ResourceState state; + }; + + private: + std::vector entries; + + bool has_uniform = false; + ResourceState uniform; + + // Bounds of the resource this map describes. Needed to materialise the + // whole-resource form into a concrete rectangle. + uint mip_count = 1; + uint slice_count = 1; + uint plane_count = 1; + + // [lo, hi) per axis, so the arithmetic below reads as interval maths + // rather than "first + num" everywhere. + struct Box + { + uint mip_lo = 0, mip_hi = 0; + uint sl_lo = 0, sl_hi = 0; + uint pl_lo = 0, pl_hi = 0; + + bool valid() const { return mip_lo < mip_hi && sl_lo < sl_hi && pl_lo < pl_hi; } + }; + + Box to_box(const SubresRange& r) const + { + if (r.is_all()) + return Box{ 0, mip_count, 0, slice_count, 0, plane_count }; + + return Box{ r.first_mip, r.first_mip + r.num_mips, + r.first_slice, r.first_slice + r.num_slices, + r.first_plane, r.first_plane + r.num_planes }; + } + + SubresRange to_range(const Box& b) const + { + // A box covering everything reports as the whole-resource form, not + // as a range that happens to span the bounds. D3D12 has a dedicated + // sentinel for it, and losing that would turn every whole-resource + // barrier into an explicit range -- correct, but off the fast path + // and noisier in every debug view. + if (b.mip_lo == 0 && b.mip_hi == mip_count + && b.sl_lo == 0 && b.sl_hi == slice_count + && b.pl_lo == 0 && b.pl_hi == plane_count) + return SubresRange::all(); + + return SubresRange{ b.mip_lo, b.mip_hi - b.mip_lo, + b.sl_lo, b.sl_hi - b.sl_lo, + b.pl_lo, b.pl_hi - b.pl_lo }; + } + + static bool overlaps(const Box& a, const Box& b) + { + return a.mip_lo < b.mip_hi && b.mip_lo < a.mip_hi + && a.sl_lo < b.sl_hi && b.sl_lo < a.sl_hi + && a.pl_lo < b.pl_hi && b.pl_lo < a.pl_hi; + } + + static Box intersect(const Box& a, const Box& b) + { + return Box{ std::max(a.mip_lo, b.mip_lo), std::min(a.mip_hi, b.mip_hi), + std::max(a.sl_lo, b.sl_lo), std::min(a.sl_hi, b.sl_hi), + std::max(a.pl_lo, b.pl_lo), std::min(a.pl_hi, b.pl_hi) }; + } + + // a MINUS b, as up to six disjoint boxes. + // + // Split one axis at a time: the slabs of `a` outside `b` along the mip + // axis come out whole, then the same for the slice axis within the + // overlapping mip band, then the plane axis within both. Peeling in a + // fixed order is what keeps the pieces disjoint. + static void subtract(const Box& a, const Box& b, std::vector& out) + { + if (!overlaps(a, b)) { out.push_back(a); return; } + + if (a.mip_lo < b.mip_lo) out.push_back(Box{ a.mip_lo, b.mip_lo, a.sl_lo, a.sl_hi, a.pl_lo, a.pl_hi }); + if (b.mip_hi < a.mip_hi) out.push_back(Box{ b.mip_hi, a.mip_hi, a.sl_lo, a.sl_hi, a.pl_lo, a.pl_hi }); + + const uint m_lo = std::max(a.mip_lo, b.mip_lo); + const uint m_hi = std::min(a.mip_hi, b.mip_hi); + + if (a.sl_lo < b.sl_lo) out.push_back(Box{ m_lo, m_hi, a.sl_lo, b.sl_lo, a.pl_lo, a.pl_hi }); + if (b.sl_hi < a.sl_hi) out.push_back(Box{ m_lo, m_hi, b.sl_hi, a.sl_hi, a.pl_lo, a.pl_hi }); + + const uint s_lo = std::max(a.sl_lo, b.sl_lo); + const uint s_hi = std::min(a.sl_hi, b.sl_hi); + + if (a.pl_lo < b.pl_lo) out.push_back(Box{ m_lo, m_hi, s_lo, s_hi, a.pl_lo, b.pl_lo }); + if (b.pl_hi < a.pl_hi) out.push_back(Box{ m_lo, m_hi, s_lo, s_hi, b.pl_hi, a.pl_hi }); + } + + public: + void reset(uint mips, uint slices, uint planes) + { + entries.clear(); + has_uniform = false; + mip_count = std::max(1u, mips); + slice_count = std::max(1u, slices); + plane_count = std::max(1u, planes); + } + + void clear() { entries.clear(); has_uniform = false; } + + bool empty() const { return entries.empty() && !has_uniform; } + size_t size() const { return entries.size() + (has_uniform ? 1 : 0); } + + bool uniform_set() const { return has_uniform; } + const ResourceState& uniform_state() const { return uniform; } + + const std::vector& get_entries() const { return entries; } + + // True when the whole resource is in one state -- the common case, and + // the one worth keeping cheap: a resource used as a whole never leaves + // this shape. + bool is_uniform() const { return has_uniform && entries.empty(); } + + void assign(const SubresRange& range, const ResourceState& state) + { + if (range.is_all()) + { + entries.clear(); + has_uniform = true; + uniform = state; + return; + } + + const Box box = to_box(range); + if (!box.valid()) return; + + // Carve the assigned box out of everything already stored, so the + // entries stay disjoint and the newest write wins. + std::vector kept; + kept.reserve(entries.size() + 4); + + std::vector pieces; + for (auto& e : entries) + { + pieces.clear(); + subtract(to_box(e.range), box, pieces); + for (auto& p : pieces) + if (p.valid()) kept.push_back(Entry{ to_range(p), e.state }); + } + + kept.push_back(Entry{ to_range(box), state }); + entries.swap(kept); + } + + // Report the state of every part of `query`. + // + // f(SubresRange piece, const ResourceState* state) -- state is null for + // parts nothing has touched and no uniform covers, which is what the + // barrier system treats as "assume where it rests". + template + void visit(const SubresRange& query, F&& f) const + { + const Box q = to_box(query); + if (!q.valid()) return; + + // Leftover starts as the whole query and shrinks as entries claim + // parts of it. + std::vector leftover{ q }; + std::vector next; + + for (auto& e : entries) + { + const Box eb = to_box(e.range); + if (!overlaps(q, eb)) continue; + + bool any = false; + next.clear(); + for (auto& l : leftover) + { + if (!overlaps(l, eb)) { next.push_back(l); continue; } + + const Box hit = intersect(l, eb); + if (hit.valid()) { f(to_range(hit), &e.state); any = true; } + + subtract(l, eb, next); + } + if (any) leftover.swap(next); + else leftover.swap(next); + + if (leftover.empty()) return; + } + + for (auto& l : leftover) + if (l.valid()) f(to_range(l), has_uniform ? &uniform : nullptr); + } + + // Dev-only invariant: entries must never overlap. Every bug in a + // structure like this shows up as double-counting a subresource, which + // is silent until D3D12 rejects a barrier much later. + bool check_disjoint() const + { + for (size_t i = 0; i < entries.size(); i++) + for (size_t j = i + 1; j < entries.size(); j++) + if (overlaps(to_box(entries[i].range), to_box(entries[j].range))) + return false; + return true; + } + }; +} diff --git a/sources/HAL/HAL.TiledMemoryManager.cpp b/sources/HAL/HAL.TiledMemoryManager.cpp index 37d66424..629ce87f 100644 --- a/sources/HAL/HAL.TiledMemoryManager.cpp +++ b/sources/HAL/HAL.TiledMemoryManager.cpp @@ -159,6 +159,44 @@ namespace HAL { } + void TiledResourceManager::load_tiles_batch(CommandList* list, uint3 from, uint3 to, const std::vector& array_slices, uint mip) + { + update_tiling_info info; + info.resource = resource; + for (int slice : array_slices) + load_tiles_internal(info, from, to, flat_index((uint)slice, mip), true); + + // TODO: make list + if (list) + { + (list)->update_tilings(std::move(info)); + } + else + resource->get_device().get_queue(CommandListType::DIRECT)->update_tile_mappings(info); + } + + void TiledResourceManager::zero_tiles_batch(CommandList* list, uint3 from, uint3 to, const std::vector& array_slices, uint mip) + { + update_tiling_info info; + info.resource = resource; + for (int slice : array_slices) + { + uint storage_index = flat_index((uint)slice, mip); + for (uint x = from.x; x <= to.x; x++) + for (uint y = from.y; y <= to.y; y++) + for (uint z = from.z; z <= to.z; z++) + zero_tile(info, { x,y,z }, storage_index); + } + + // TODO: make list + if (list) + { + (list)->update_tilings(std::move(info)); + } + else + resource->get_device().get_queue(CommandListType::DIRECT)->update_tile_mappings(info); + } + void TiledResourceManager::load_tiles(CommandList* list, std::list& tiles, uint array_slice, uint mip, bool recursive) { update_tiling_info info; diff --git a/sources/HAL/HAL.TiledMemoryManager.ixx b/sources/HAL/HAL.TiledMemoryManager.ixx index 0a7fbaeb..a3506e42 100644 --- a/sources/HAL/HAL.TiledMemoryManager.ixx +++ b/sources/HAL/HAL.TiledMemoryManager.ixx @@ -140,6 +140,18 @@ export // index. void zero_tiles(CommandList* list, uint3 from, uint3 to, uint array_slice = 0, uint mip = 0); + // Phase 5.10: same range as load_tiles()/zero_tiles() above, but + // applied to MULTIPLE array slices in one update_tiling_info and + // one commit()/update_tilings() call, instead of one call per + // slice. update_tile_mappings() (HAL.Queue.cpp) issues one real + // ID3D12CommandQueue::UpdateTileMappings per update_tiling_info + // it drains -- calling load_tiles()/zero_tiles() once per slot in + // a loop means one native call per slot; these two exist so a + // caller batching many slots' worth of (un)mapping in the same + // frame (VSM's per-frame tile-mapping drain) pays for exactly one. + void load_tiles_batch(CommandList* list, uint3 from, uint3 to, const std::vector& array_slices, uint mip = 0); + void zero_tiles_batch(CommandList* list, uint3 from, uint3 to, const std::vector& array_slices, uint mip = 0); + template void load_tiles2(CommandList* list, R tiles, uint array_slice = 0, uint mip = 0, bool recursive = false) diff --git a/sources/HAL/HAL.Types.cpp b/sources/HAL/HAL.Types.cpp index 17ac7f8e..9deb24ca 100644 --- a/sources/HAL/HAL.Types.cpp +++ b/sources/HAL/HAL.Types.cpp @@ -105,6 +105,13 @@ namespace HAL if (check(operation & SYNC_DIRECT)) return CommandListType::DIRECT; + // Nothing above found a real DIRECT-only requirement, so this state + // carries no list-type constraint. COPY rather than the COMPUTE default + // below, because COPY is compatible with every queue -- this has to be + // usable as a barrier's "before" wherever it lands. + if (check(operation & BarrierSync::ALL)) + return CommandListType::COPY; + diff --git a/sources/HAL/HAL.Types.ixx b/sources/HAL/HAL.Types.ixx index fad6eb50..987ed227 100644 --- a/sources/HAL/HAL.Types.ixx +++ b/sources/HAL/HAL.Types.ixx @@ -148,8 +148,16 @@ export namespace HAL enum class BarrierSync : uint { NONE = 0x0, - // ALL = 0x1, - + + // Not a real pipeline stage -- "synchronize against everything". Used + // where a barrier's predecessor is genuinely unknown: entering a + // resource from its undefined creation layout after something has + // already touched it. D3D12 rejects SyncBefore=NONE there (#1417), and + // NONE is the only alternative that would otherwise fit. + // Bit 19 rather than 0x1, which would collide with INDEX_INPUT; the + // value never reaches the API directly, to_native() translates it. + ALL = 1<<19, + INDEX_INPUT = 1<<0, VERTEX_SHADING = 1<<1, PIXEL_SHADING = 1<<2, @@ -524,7 +532,9 @@ private: Virtual = 1 << 7, Swapchain = 1<<8, WriteInitialized = 1<<9, - DisableStateTracking = 1<<10, + // 1<<10 was DisableStateTracking. Removed: "the FrameGraph owns this + // resource's transitions" is now Resource::frame_graph_managed, a + // property of the resource rather than of its creation desc. Immutable = 1<<11 // upload once (CopyDest → read), no further transitions }; diff --git a/sources/HAL/HAL.ixx b/sources/HAL/HAL.ixx index 5d4d1156..22c1250b 100644 --- a/sources/HAL/HAL.ixx +++ b/sources/HAL/HAL.ixx @@ -22,6 +22,7 @@ export import :QueryHeap; export import :HeapAllocators; export import :TiledMemoryManager; export import :ResourceStates; +export import :SubresRangeMap; export import :ResourceViews; export import :Resource; export import :Resource.Buffer; diff --git a/sources/HAL/SIG/Slots.ixx b/sources/HAL/SIG/Slots.ixx index 083a76c0..fa8d1f33 100644 --- a/sources/HAL/SIG/Slots.ixx +++ b/sources/HAL/SIG/Slots.ixx @@ -39,9 +39,27 @@ class Slot_Compiler } public: Context* context; - std::vector resources; + std::vector resources; std::set> descriptors; + // Set for the duration of one member's compile() when the .sig declared it + // [Barrier = ALL]. A flag rather than a parameter because a member can be a + // scalar handle, a fixed array, or a vector, each with its own compile() + // overload and its own push_back -- all of which inherit the scope this way + // without every overload having to forward it. + bool bind_whole_resource = false; + + // compile() for a member the .sig marked [Barrier = ALL]. Generated table + // code calls this instead of compile(); everything else is identical, so + // the layout it writes is unchanged. + template + void compile_whole(const T& t) + { + bind_whole_resource = true; + compile(t); + bind_whole_resource = false; + } + std::stringstream s; Slot_Compiler() :s(std::stringstream::out | std::stringstream::binary) { @@ -56,7 +74,7 @@ public: { if (handle.get_storage()->can_free()) descriptors.insert(handle.get_storage()); - resources.push_back(&handle.get_resource_info()); + resources.push_back({ &handle.get_resource_info(), bind_whole_resource }); offset = handle.get_offset(); } @@ -105,7 +123,7 @@ public: if (handle.get_storage()->can_free()) descriptors.insert(handle.get_storage()); offsets.emplace_back(handle.get_offset()); - resources.push_back(&handle.get_resource_info()); + resources.push_back({ &handle.get_resource_info(), bind_whole_resource }); } else { @@ -179,7 +197,7 @@ export { using Slot = _Slot; HLSL::ConstantBuffer const_buffer; - std::vector resources; + std::vector resources; std::set> descriptors; const HLSL::ConstantBuffer
& compiled() const diff --git a/sources/HAL/autogen/autogen.cpp b/sources/HAL/autogen/autogen.cpp index 1dadc8eb..bc8477b9 100644 --- a/sources/HAL/autogen/autogen.cpp +++ b/sources/HAL/autogen/autogen.cpp @@ -50,6 +50,7 @@ std::optional get_slot(std::string_view slot_name) if(slot_name == "RaytracingRays") return SlotID::RaytracingRays; if(slot_name == "ColorRTXOutput") return SlotID::ColorRTXOutput; if(slot_name == "Raytracing") return SlotID::Raytracing; + if(slot_name == "RTXShadowReference") return SlotID::RTXShadowReference; if(slot_name == "SceneData") return SlotID::SceneData; if(slot_name == "GBuffer") return SlotID::GBuffer; if(slot_name == "SkyData") return SlotID::SkyData; @@ -196,6 +197,8 @@ uint get_table_index(SlotID id) if(id == SlotID::Raytracing) return Slots::Raytracing::Slot::ID; + if(id == SlotID::RTXShadowReference) return Slots::RTXShadowReference::Slot::ID; + if(id == SlotID::SceneData) return Slots::SceneData::Slot::ID; if(id == SlotID::GBuffer) return Slots::GBuffer::Slot::ID; @@ -357,6 +360,7 @@ std::string get_slot_name(SlotID id) if(id == SlotID::RaytracingRays) return "RaytracingRays"; if(id == SlotID::ColorRTXOutput) return "ColorRTXOutput"; if(id == SlotID::Raytracing) return "Raytracing"; + if(id == SlotID::RTXShadowReference) return "RTXShadowReference"; if(id == SlotID::SceneData) return "SceneData"; if(id == SlotID::GBuffer) return "GBuffer"; if(id == SlotID::SkyData) return "SkyData"; diff --git a/sources/HAL/autogen/autogen.ixx b/sources/HAL/autogen/autogen.ixx index 8f9a3510..69edf4de 100644 --- a/sources/HAL/autogen/autogen.ixx +++ b/sources/HAL/autogen/autogen.ixx @@ -131,6 +131,8 @@ export import :Autogen.Tables.ColorShadowPayload; export import :Autogen.Tables.Triangle; export import :Autogen.Slots.Raytracing; export import :Autogen.Tables.Raytracing; +export import :Autogen.Slots.RTXShadowReference; +export import :Autogen.Tables.RTXShadowReference; export import :Autogen.Slots.SceneData; export import :Autogen.Tables.SceneData; export import :Autogen.Slots.GBuffer; @@ -274,7 +276,11 @@ export import :Autogen.PSO.DownsampleDepth; export import :Autogen.PSO.DownsampleDepthMip; export import :Autogen.PSO.MipMapping; export import :Autogen.PSO.PSSMApplyCompute; +export import :Autogen.PSO.RTXShadowReferenceCompute; export import :Autogen.PSO.SkyCompute; +export import :Autogen.PSO.SkyCube; +export import :Autogen.PSO.CubemapENV; +export import :Autogen.PSO.CubemapENVDiffuse; export import :Autogen.PSO.EdgeDetectCompute; export import :Autogen.PSO.BlendWeightCompute; export import :Autogen.PSO.BlendingCompute; @@ -300,6 +306,7 @@ export import :Autogen.PSO.VSMCopyPageDepth; export import :Autogen.PSO.VSMCopyPageDepthBatch; export import :Autogen.PSO.VSMDownsampleHiZBatch; export import :Autogen.PSO.VSMApplyCompute; +export import :Autogen.PSO.VSMBlockerSearchCompute; export import :Autogen.PSO.VSMGatherDispatch; export import :Autogen.PSO.VSMDepthAnalysis; export import :Autogen.PSO.FontRender; @@ -317,9 +324,6 @@ export import :Autogen.PSO.GBufferDraw; export import :Autogen.PSO.DepthDraw; export import :Autogen.PSO.Voxelization; export import :Autogen.PSO.Sky; -export import :Autogen.PSO.SkyCube; -export import :Autogen.PSO.CubemapENV; -export import :Autogen.PSO.CubemapENVDiffuse; export import :Autogen.PSO.EdgeDetect; export import :Autogen.PSO.BlendWeight; export import :Autogen.PSO.Blending; diff --git a/sources/HAL/autogen/enums.ixx b/sources/HAL/autogen/enums.ixx index c1f3fdd2..e0bc7260 100644 --- a/sources/HAL/autogen/enums.ixx +++ b/sources/HAL/autogen/enums.ixx @@ -47,7 +47,11 @@ export DownsampleDepthMip, MipMapping, PSSMApplyCompute, + RTXShadowReferenceCompute, SkyCompute, + SkyCube, + CubemapENV, + CubemapENVDiffuse, EdgeDetectCompute, BlendWeightCompute, BlendingCompute, @@ -73,6 +77,7 @@ export VSMCopyPageDepthBatch, VSMDownsampleHiZBatch, VSMApplyCompute, + VSMBlockerSearchCompute, VSMGatherDispatch, VSMDepthAnalysis, FontRender, @@ -90,9 +95,6 @@ export DepthDraw, Voxelization, Sky, - SkyCube, - CubemapENV, - CubemapENVDiffuse, EdgeDetect, BlendWeight, Blending, @@ -193,6 +195,7 @@ export ColorShadowPayload = "ColorShadowPayload"_crc32, Triangle = "Triangle"_crc32, Raytracing = "Raytracing"_crc32, + RTXShadowReference = "RTXShadowReference"_crc32, SceneData = "SceneData"_crc32, GBuffer = "GBuffer"_crc32, SkyData = "SkyData"_crc32, diff --git a/sources/HAL/autogen/layout/DefaultLayout.layout.ixx b/sources/HAL/autogen/layout/DefaultLayout.layout.ixx index 8afb7f9b..5ea805d0 100644 --- a/sources/HAL/autogen/layout/DefaultLayout.layout.ixx +++ b/sources/HAL/autogen/layout/DefaultLayout.layout.ixx @@ -15,11 +15,11 @@ export struct DefaultLayout: public FrameLayout struct Instance0 { static const uint ID = 4; - static const uint CB = 31; + static const uint CB = 34; static const uint CB_ID = 12; static const uint SRV = 7; static const uint SRV_ID = 14; - static const uint UAV = 4; + static const uint UAV = 8; static const uint UAV_ID = 15; static inline const std::vector tables = { 12, 14, 15 }; }; @@ -41,7 +41,7 @@ export struct DefaultLayout: public FrameLayout static const uint ID = 6; static const uint CB = 17; static const uint CB_ID = 20; - static const uint SRV = 8; + static const uint SRV = 10; static const uint SRV_ID = 22; static const uint UAV = 3; static const uint UAV_ID = 23; @@ -122,6 +122,6 @@ export struct DefaultLayout: public FrameLayout template static void for_each(Processor& processor) { - processor.template process({ HAL::Samplers::SamplerLinearWrapDesc, HAL::Samplers::SamplerPointClampDesc, HAL::Samplers::SamplerLinearClampDesc, HAL::Samplers::SamplerAnisoBorderDesc, HAL::Samplers::SamplerPointBorderDesc }); + processor.template process({ HAL::Samplers::SamplerLinearWrapDesc, HAL::Samplers::SamplerPointClampDesc, HAL::Samplers::SamplerLinearClampDesc, HAL::Samplers::SamplerAnisoBorderDesc, HAL::Samplers::SamplerPointBorderDesc, HAL::Samplers::SamplerShadowComparisonDesc }); } }; \ No newline at end of file diff --git a/sources/HAL/autogen/layout/FrameLayout.layout.ixx b/sources/HAL/autogen/layout/FrameLayout.layout.ixx index 6e127741..e55f0ac8 100644 --- a/sources/HAL/autogen/layout/FrameLayout.layout.ixx +++ b/sources/HAL/autogen/layout/FrameLayout.layout.ixx @@ -53,6 +53,6 @@ export struct FrameLayout template static void for_each(Processor& processor) { - processor.template process({ HAL::Samplers::SamplerLinearWrapDesc, HAL::Samplers::SamplerPointClampDesc, HAL::Samplers::SamplerLinearClampDesc, HAL::Samplers::SamplerAnisoBorderDesc, HAL::Samplers::SamplerPointBorderDesc }); + processor.template process({ HAL::Samplers::SamplerLinearWrapDesc, HAL::Samplers::SamplerPointClampDesc, HAL::Samplers::SamplerLinearClampDesc, HAL::Samplers::SamplerAnisoBorderDesc, HAL::Samplers::SamplerPointBorderDesc, HAL::Samplers::SamplerShadowComparisonDesc }); } }; \ No newline at end of file diff --git a/sources/HAL/autogen/pso.cpp b/sources/HAL/autogen/pso.cpp index 610fdfbb..a83cffd8 100644 --- a/sources/HAL/autogen/pso.cpp +++ b/sources/HAL/autogen/pso.cpp @@ -54,7 +54,11 @@ void init_pso(HAL::Device& device, enum_array& pso) tasks.emplace_back(PSOBase::create(device, pso[PSO::DownsampleDepthMip])); tasks.emplace_back(PSOBase::create(device, pso[PSO::MipMapping])); tasks.emplace_back(PSOBase::create(device, pso[PSO::PSSMApplyCompute])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::RTXShadowReferenceCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::SkyCompute])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::SkyCube])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::CubemapENV])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::CubemapENVDiffuse])); tasks.emplace_back(PSOBase::create(device, pso[PSO::EdgeDetectCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::BlendWeightCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::BlendingCompute])); @@ -80,6 +84,7 @@ void init_pso(HAL::Device& device, enum_array& pso) tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMCopyPageDepthBatch])); tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMDownsampleHiZBatch])); tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMApplyCompute])); + tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMBlockerSearchCompute])); tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMGatherDispatch])); tasks.emplace_back(PSOBase::create(device, pso[PSO::VSMDepthAnalysis])); @@ -98,9 +103,6 @@ void init_pso(HAL::Device& device, enum_array& pso) tasks.emplace_back(PSOBase::create(device, pso[PSO::DepthDraw])); tasks.emplace_back(PSOBase::create(device, pso[PSO::Voxelization])); tasks.emplace_back(PSOBase::create(device, pso[PSO::Sky])); - tasks.emplace_back(PSOBase::create(device, pso[PSO::SkyCube])); - tasks.emplace_back(PSOBase::create(device, pso[PSO::CubemapENV])); - tasks.emplace_back(PSOBase::create(device, pso[PSO::CubemapENVDiffuse])); tasks.emplace_back(PSOBase::create(device, pso[PSO::EdgeDetect])); tasks.emplace_back(PSOBase::create(device, pso[PSO::BlendWeight])); tasks.emplace_back(PSOBase::create(device, pso[PSO::Blending])); @@ -157,13 +159,15 @@ void init_pso(HAL::Device& device, enum_array& pso) //decltype(PSOS::GatherMeshes::Invisible) PSOS::GatherMeshes::Invisible; //decltype(PSOS::MipMapping::NonPowerOfTwo) PSOS::MipMapping::NonPowerOfTwo; //decltype(PSOS::MipMapping::Gamma) PSOS::MipMapping::Gamma; +//decltype(PSOS::MipMapping::Slices) PSOS::MipMapping::Slices; //decltype(PSOS::Lighting::SecondBounce) PSOS::Lighting::SecondBounce; //decltype(PSOS::VoxelDownsample::Count) PSOS::VoxelDownsample::Count; //decltype(PSOS::VoxelIndirectFilter::Blur) PSOS::VoxelIndirectFilter::Blur; //decltype(PSOS::VoxelIndirectFilter::Reflection) PSOS::VoxelIndirectFilter::Reflection; +//decltype(PSOS::VSMApplyCompute::VsmPenumbra) PSOS::VSMApplyCompute::VsmPenumbra; +//decltype(PSOS::VSMApplyCompute::VsmRtxVerify) PSOS::VSMApplyCompute::VsmRtxVerify; //decltype(PSOS::FontRender::Format) PSOS::FontRender::Format; //decltype(PSOS::CopyTexture::Format) PSOS::CopyTexture::Format; //decltype(PSOS::GBufferDraw::HiZOcclusion) PSOS::GBufferDraw::HiZOcclusion; //decltype(PSOS::DepthDraw::HiZOcclusion) PSOS::DepthDraw::HiZOcclusion; -//decltype(PSOS::Voxelization::Dynamic) PSOS::Voxelization::Dynamic; -//decltype(PSOS::CubemapENV::Level) PSOS::CubemapENV::Level; \ No newline at end of file +//decltype(PSOS::Voxelization::Dynamic) PSOS::Voxelization::Dynamic; \ No newline at end of file diff --git a/sources/HAL/autogen/pso/CubemapENV.pso.ixx b/sources/HAL/autogen/pso/CubemapENV.pso.ixx index 276c1bf1..876c639c 100644 --- a/sources/HAL/autogen/pso/CubemapENV.pso.ixx +++ b/sources/HAL/autogen/pso/CubemapENV.pso.ixx @@ -17,22 +17,18 @@ export namespace PSOS struct CubemapENV: public PSOBase { struct Keys { - KeyValue Level; GEN_DEF_COMP(Keys); private: SERIALIZE() { - ar&NVP(Level); } }; - GEN_GRAPHICS_PSO(CubemapENV, Level) - GEN_KEY(Level, false); + GEN_COMPUTE_PSO(CubemapENV) SimplePSO init_pso(Keys & key, std::function f) { - static const ShaderDefine<&Keys::Level,&SimpleGraphicsPSO::pixel> Level = "NumSamples"; SimplePSO mpso("CubemapENV"); @@ -40,19 +36,10 @@ export namespace PSOS mpso.root_signature = Layouts::DefaultLayout; - mpso.vertex.file_name = "shaders/cubemap_down.hlsl"; - mpso.vertex.entry_point = "VS"; - mpso.vertex.flags = HAL::ShaderOptions::None; + mpso.compute.file_name = "shaders/cubemap_down.hlsl"; + mpso.compute.entry_point = "CS"; + mpso.compute.flags = HAL::ShaderOptions::None; - mpso.pixel.file_name = "shaders/cubemap_down.hlsl"; - mpso.pixel.entry_point = "PS"; - mpso.pixel.flags = HAL::ShaderOptions::None; - - Level.Apply(mpso, key); - - mpso.rtv_formats = { HAL::Format::R11G11B10_FLOAT }; - mpso.blend = { }; - return mpso; } diff --git a/sources/HAL/autogen/pso/CubemapENVDiffuse.pso.ixx b/sources/HAL/autogen/pso/CubemapENVDiffuse.pso.ixx index 9c2ac961..cff363d0 100644 --- a/sources/HAL/autogen/pso/CubemapENVDiffuse.pso.ixx +++ b/sources/HAL/autogen/pso/CubemapENVDiffuse.pso.ixx @@ -24,7 +24,7 @@ export namespace PSOS } }; - GEN_GRAPHICS_PSO(CubemapENVDiffuse) + GEN_COMPUTE_PSO(CubemapENVDiffuse) SimplePSO init_pso(Keys & key, std::function f) @@ -36,18 +36,10 @@ export namespace PSOS mpso.root_signature = Layouts::DefaultLayout; - mpso.vertex.file_name = "shaders/cubemap_down.hlsl"; - mpso.vertex.entry_point = "VS"; - mpso.vertex.flags = HAL::ShaderOptions::None; + mpso.compute.file_name = "shaders/cubemap_down.hlsl"; + mpso.compute.entry_point = "CS_Diffuse"; + mpso.compute.flags = HAL::ShaderOptions::None; - mpso.pixel.file_name = "shaders/cubemap_down.hlsl"; - mpso.pixel.entry_point = "PS_Diffuse"; - mpso.pixel.flags = HAL::ShaderOptions::None; - - - mpso.rtv_formats = { HAL::Format::R11G11B10_FLOAT }; - mpso.blend = { }; - return mpso; } diff --git a/sources/HAL/autogen/pso/MipMapping.pso.ixx b/sources/HAL/autogen/pso/MipMapping.pso.ixx index 123ac8e8..fbcf4e5d 100644 --- a/sources/HAL/autogen/pso/MipMapping.pso.ixx +++ b/sources/HAL/autogen/pso/MipMapping.pso.ixx @@ -19,24 +19,28 @@ export namespace PSOS struct Keys { KeyValue NonPowerOfTwo; KeyValue Gamma; + KeyValue Slices; GEN_DEF_COMP(Keys); private: SERIALIZE() { ar&NVP(NonPowerOfTwo); ar&NVP(Gamma); + ar&NVP(Slices); } }; - GEN_COMPUTE_PSO(MipMapping, NonPowerOfTwo, Gamma) + GEN_COMPUTE_PSO(MipMapping, NonPowerOfTwo, Gamma, Slices) GEN_KEY(NonPowerOfTwo, true); GEN_KEY(Gamma, true); + GEN_KEY(Slices, true); SimplePSO init_pso(Keys & key, std::function f) { static const ShaderDefine<&Keys::NonPowerOfTwo,&SimpleComputePSO::compute> NonPowerOfTwo = "NON_POWER_OF_TWO"; static const ShaderDefine<&Keys::Gamma,&SimpleComputePSO::compute> Gamma = "CONVERT_TO_SRGB"; + static const ShaderDefine<&Keys::Slices,&SimpleComputePSO::compute> Slices = "ARRAY_SLICES"; SimplePSO mpso("MipMapping"); @@ -50,6 +54,7 @@ export namespace PSOS NonPowerOfTwo.Apply(mpso, key); Gamma.Apply(mpso, key); + Slices.Apply(mpso, key); return mpso; } diff --git a/sources/HAL/autogen/pso/RTXShadowReferenceCompute.pso.ixx b/sources/HAL/autogen/pso/RTXShadowReferenceCompute.pso.ixx new file mode 100644 index 00000000..a2b7c968 --- /dev/null +++ b/sources/HAL/autogen/pso/RTXShadowReferenceCompute.pso.ixx @@ -0,0 +1,52 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +export module HAL:Autogen.PSO.RTXShadowReferenceCompute; + +import Core; +import :PSO; +import :Enums; +import :Types; + + +export namespace PSOS +{ + struct RTXShadowReferenceCompute: public PSOBase + { + struct Keys { + GEN_DEF_COMP(Keys); + private: + SERIALIZE() + { + } + }; + + GEN_COMPUTE_PSO(RTXShadowReferenceCompute) + + + SimplePSO init_pso(Keys & key, std::function f) + { + + + SimplePSO mpso("RTXShadowReferenceCompute"); + if(f) f(mpso,key); + + mpso.root_signature = Layouts::DefaultLayout; + + mpso.compute.file_name = "shaders/RTXShadowReference.hlsl"; + mpso.compute.entry_point = "CS_REFERENCE"; + mpso.compute.flags = HAL::ShaderOptions::None; + + return mpso; + } + + private: + SERIALIZE() + { + ar&NVP(wrap(psos)); + } + }; +} \ No newline at end of file diff --git a/sources/HAL/autogen/pso/SkyCube.pso.ixx b/sources/HAL/autogen/pso/SkyCube.pso.ixx index 31c902e9..eee93cbc 100644 --- a/sources/HAL/autogen/pso/SkyCube.pso.ixx +++ b/sources/HAL/autogen/pso/SkyCube.pso.ixx @@ -24,7 +24,7 @@ export namespace PSOS } }; - GEN_GRAPHICS_PSO(SkyCube) + GEN_COMPUTE_PSO(SkyCube) SimplePSO init_pso(Keys & key, std::function f) @@ -36,18 +36,10 @@ export namespace PSOS mpso.root_signature = Layouts::DefaultLayout; - mpso.vertex.file_name = "shaders/sky.hlsl"; - mpso.vertex.entry_point = "VS_Cube"; - mpso.vertex.flags = HAL::ShaderOptions::None; + mpso.compute.file_name = "shaders/sky.hlsl"; + mpso.compute.entry_point = "CS_Cube"; + mpso.compute.flags = HAL::ShaderOptions::None; - mpso.pixel.file_name = "shaders/sky.hlsl"; - mpso.pixel.entry_point = "PS_Cube"; - mpso.pixel.flags = HAL::ShaderOptions::None; - - - mpso.rtv_formats = { HAL::Format::R11G11B10_FLOAT }; - mpso.blend = { }; - return mpso; } diff --git a/sources/HAL/autogen/pso/VSMApplyCompute.pso.ixx b/sources/HAL/autogen/pso/VSMApplyCompute.pso.ixx index 47112b5e..943de1fa 100644 --- a/sources/HAL/autogen/pso/VSMApplyCompute.pso.ixx +++ b/sources/HAL/autogen/pso/VSMApplyCompute.pso.ixx @@ -17,18 +17,26 @@ export namespace PSOS struct VSMApplyCompute: public PSOBase { struct Keys { + KeyValue VsmPenumbra; + KeyValue VsmRtxVerify; GEN_DEF_COMP(Keys); private: SERIALIZE() { + ar&NVP(VsmPenumbra); + ar&NVP(VsmRtxVerify); } }; - GEN_COMPUTE_PSO(VSMApplyCompute) + GEN_COMPUTE_PSO(VSMApplyCompute, VsmPenumbra, VsmRtxVerify) + GEN_KEY(VsmPenumbra, true); + GEN_KEY(VsmRtxVerify, true); SimplePSO init_pso(Keys & key, std::function f) { + static const ShaderDefine<&Keys::VsmPenumbra,&SimpleComputePSO::compute> VsmPenumbra = "VSM_PENUMBRA"; + static const ShaderDefine<&Keys::VsmRtxVerify,&SimpleComputePSO::compute> VsmRtxVerify = "VSM_RTX_VERIFY"; SimplePSO mpso("VSMApplyCompute"); @@ -40,6 +48,8 @@ export namespace PSOS mpso.compute.entry_point = "CS_RESULT"; mpso.compute.flags = HAL::ShaderOptions::None; + VsmPenumbra.Apply(mpso, key); + VsmRtxVerify.Apply(mpso, key); return mpso; } diff --git a/sources/HAL/autogen/pso/VSMBlockerSearchCompute.pso.ixx b/sources/HAL/autogen/pso/VSMBlockerSearchCompute.pso.ixx new file mode 100644 index 00000000..56424f5d --- /dev/null +++ b/sources/HAL/autogen/pso/VSMBlockerSearchCompute.pso.ixx @@ -0,0 +1,52 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +export module HAL:Autogen.PSO.VSMBlockerSearchCompute; + +import Core; +import :PSO; +import :Enums; +import :Types; + + +export namespace PSOS +{ + struct VSMBlockerSearchCompute: public PSOBase + { + struct Keys { + GEN_DEF_COMP(Keys); + private: + SERIALIZE() + { + } + }; + + GEN_COMPUTE_PSO(VSMBlockerSearchCompute) + + + SimplePSO init_pso(Keys & key, std::function f) + { + + + SimplePSO mpso("VSMBlockerSearchCompute"); + if(f) f(mpso,key); + + mpso.root_signature = Layouts::DefaultLayout; + + mpso.compute.file_name = "shaders/VSM_BlockerSearch.hlsl"; + mpso.compute.entry_point = "CS_BLOCKER_SEARCH"; + mpso.compute.flags = HAL::ShaderOptions::None; + + return mpso; + } + + private: + SERIALIZE() + { + ar&NVP(wrap(psos)); + } + }; +} \ No newline at end of file diff --git a/sources/HAL/autogen/slots/RTXShadowReference.ixx b/sources/HAL/autogen/slots/RTXShadowReference.ixx new file mode 100644 index 00000000..3322b480 --- /dev/null +++ b/sources/HAL/autogen/slots/RTXShadowReference.ixx @@ -0,0 +1,23 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +export module HAL:Autogen.Slots.RTXShadowReference; +import Core; +import :Autogen.Tables.RTXShadowReference; +import :Autogen.Layouts.DefaultLayout; +import :SIG; +import :Types; +import :Enums; +import :Slots; + +export namespace Slots +{ + struct RTXShadowReference :public DataHolder + { + static constexpr SIG_TYPE TYPE = SIG_TYPE::Slot; + RTXShadowReference() = default; + }; +} \ No newline at end of file diff --git a/sources/HAL/autogen/tables/EnvFilter.table.ixx b/sources/HAL/autogen/tables/EnvFilter.table.ixx index e5a54b4c..2a63c986 100644 --- a/sources/HAL/autogen/tables/EnvFilter.table.ixx +++ b/sources/HAL/autogen/tables/EnvFilter.table.ixx @@ -17,21 +17,31 @@ export namespace Table struct EnvFilter { static constexpr SlotID ID = SlotID::EnvFilter; - uint4 face; - float4 scaler; uint4 size; - uint4& GetFace() { return face; } - float4& GetScaler() { return scaler; } + HLSL::RWTexture2DArray targets[8]; uint4& GetSize() { return size; } + HLSL::RWTexture2DArray* GetTargets() { return targets; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; template void compile(Compiler& compiler) const { - compiler.compile(face); - compiler.compile(scaler); compiler.compile(size); + compiler.compile(targets); } - using Compiled = EnvFilter; + struct Compiled + { + uint4 size; // uint4 + uint targets[8]; // RWTexture2DArray + + + private: + SERIALIZE() + { + ar& NVP(size); + } + + + }; static std::string get_typename() { @@ -40,8 +50,6 @@ export namespace Table private: SERIALIZE() { - ar& NVP(face); - ar& NVP(scaler); ar& NVP(size); } diff --git a/sources/HAL/autogen/tables/MipMapping.table.ixx b/sources/HAL/autogen/tables/MipMapping.table.ixx index 911ba044..bc6b95af 100644 --- a/sources/HAL/autogen/tables/MipMapping.table.ixx +++ b/sources/HAL/autogen/tables/MipMapping.table.ixx @@ -21,12 +21,16 @@ export namespace Table uint NumMipLevels; float2 TexelSize; HLSL::Texture2D SrcMip; + HLSL::Texture2DArray SrcMipArray; HLSL::RWTexture2D OutMip[4]; + HLSL::RWTexture2DArray OutMipArray[4]; uint& GetSrcMipLevel() { return SrcMipLevel; } uint& GetNumMipLevels() { return NumMipLevels; } float2& GetTexelSize() { return TexelSize; } HLSL::RWTexture2D* GetOutMip() { return OutMip; } HLSL::Texture2D& GetSrcMip() { return SrcMip; } + HLSL::RWTexture2DArray* GetOutMipArray() { return OutMipArray; } + HLSL::Texture2DArray& GetSrcMipArray() { return SrcMipArray; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; template void compile(Compiler& compiler) const @@ -35,7 +39,9 @@ export namespace Table compiler.compile(NumMipLevels); compiler.compile(TexelSize); compiler.compile(SrcMip); + compiler.compile(SrcMipArray); compiler.compile(OutMip); + compiler.compile(OutMipArray); } struct Compiled { @@ -43,7 +49,9 @@ export namespace Table uint NumMipLevels; // uint float2 TexelSize; // float2 uint SrcMip; // Texture2D + uint SrcMipArray; // Texture2DArray uint OutMip[4]; // RWTexture2D + uint OutMipArray[4]; // RWTexture2DArray private: diff --git a/sources/HAL/autogen/tables/RTXShadowReference.table.ixx b/sources/HAL/autogen/tables/RTXShadowReference.table.ixx new file mode 100644 index 00000000..1676e0fe --- /dev/null +++ b/sources/HAL/autogen/tables/RTXShadowReference.table.ixx @@ -0,0 +1,60 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +export module HAL:Autogen.Tables.RTXShadowReference; + +import Core; +import :SIG; +import :Types; +import :HLSL; +import :Enums; +import :Autogen.Tables.GBuffer; +export namespace Table +{ + #pragma pack(push, 1) + struct RTXShadowReference + { + static constexpr SlotID ID = SlotID::RTXShadowReference; + HLSL::RWTexture2D output; + GBuffer gbuffer; + HLSL::RWTexture2D& GetOutput() { return output; } + GBuffer& GetGbuffer() { return gbuffer; } + static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; + template + void compile(Compiler& compiler) const + { + compiler.compile(output); + compiler.compile(gbuffer); + } + struct Compiled + { + uint output; // RWTexture2D + GBuffer::Compiled gbuffer; // GBuffer + + + private: + SERIALIZE() + { + ar& NVP(gbuffer); + } + + + }; + + static std::string get_typename() + { + return "Tables::RTXShadowReference"; + } + private: + SERIALIZE() + { + ar& NVP(gbuffer); + } + + }; + #pragma pack(pop) +} + diff --git a/sources/HAL/autogen/tables/SkyFace.table.ixx b/sources/HAL/autogen/tables/SkyFace.table.ixx index 35970d93..026fece6 100644 --- a/sources/HAL/autogen/tables/SkyFace.table.ixx +++ b/sources/HAL/autogen/tables/SkyFace.table.ixx @@ -17,15 +17,26 @@ export namespace Table struct SkyFace { static constexpr SlotID ID = SlotID::SkyFace; - uint face; - uint& GetFace() { return face; } + HLSL::RWTexture2DArray faces; + HLSL::RWTexture2DArray& GetFaces() { return faces; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; template void compile(Compiler& compiler) const { - compiler.compile(face); + compiler.compile(faces); } - using Compiled = SkyFace; + struct Compiled + { + uint faces; // RWTexture2DArray + + + private: + SERIALIZE() + { + } + + + }; static std::string get_typename() { @@ -34,7 +45,6 @@ export namespace Table private: SERIALIZE() { - ar& NVP(face); } }; diff --git a/sources/HAL/autogen/tables/VSMConstants.table.ixx b/sources/HAL/autogen/tables/VSMConstants.table.ixx index da330334..1a18bc96 100644 --- a/sources/HAL/autogen/tables/VSMConstants.table.ixx +++ b/sources/HAL/autogen/tables/VSMConstants.table.ixx @@ -21,12 +21,18 @@ export namespace Table int active_max; int page_size; int pages_per_level; + int rtx_dual_blur; + int quad_blocker_search; + int debug_rtx_reference; float4x4 light_view; float4 level_info[26]; int& GetActive_min() { return active_min; } int& GetActive_max() { return active_max; } int& GetPage_size() { return page_size; } int& GetPages_per_level() { return pages_per_level; } + int& GetRtx_dual_blur() { return rtx_dual_blur; } + int& GetQuad_blocker_search() { return quad_blocker_search; } + int& GetDebug_rtx_reference() { return debug_rtx_reference; } float4x4& GetLight_view() { return light_view; } float4* GetLevel_info() { return level_info; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; @@ -37,6 +43,9 @@ export namespace Table compiler.compile(active_max); compiler.compile(page_size); compiler.compile(pages_per_level); + compiler.compile(rtx_dual_blur); + compiler.compile(quad_blocker_search); + compiler.compile(debug_rtx_reference); compiler.compile(light_view); compiler.compile(level_info); } @@ -53,6 +62,9 @@ export namespace Table ar& NVP(active_max); ar& NVP(page_size); ar& NVP(pages_per_level); + ar& NVP(rtx_dual_blur); + ar& NVP(quad_blocker_search); + ar& NVP(debug_rtx_reference); ar& NVP(light_view); ar& NVP(level_info); } diff --git a/sources/HAL/autogen/tables/VSMCopyPageDepthBatch.table.ixx b/sources/HAL/autogen/tables/VSMCopyPageDepthBatch.table.ixx index 5c26fc1c..c8c06860 100644 --- a/sources/HAL/autogen/tables/VSMCopyPageDepthBatch.table.ixx +++ b/sources/HAL/autogen/tables/VSMCopyPageDepthBatch.table.ixx @@ -27,9 +27,9 @@ export namespace Table template void compile(Compiler& compiler) const { - compiler.compile(atlas); + compiler.compile_whole(atlas); compiler.compile(dirty_slots); - compiler.compile(dst_mip0); + compiler.compile_whole(dst_mip0); } struct Compiled { diff --git a/sources/HAL/autogen/tables/VSMDownsampleHiZBatch.table.ixx b/sources/HAL/autogen/tables/VSMDownsampleHiZBatch.table.ixx index 7cef3d97..3799a70d 100644 --- a/sources/HAL/autogen/tables/VSMDownsampleHiZBatch.table.ixx +++ b/sources/HAL/autogen/tables/VSMDownsampleHiZBatch.table.ixx @@ -30,9 +30,9 @@ export namespace Table void compile(Compiler& compiler) const { compiler.compile(src_mip); - compiler.compile(src); + compiler.compile_whole(src); compiler.compile(dirty_slots); - compiler.compile(dst_mip); + compiler.compile_whole(dst_mip); } struct Compiled { diff --git a/sources/HAL/autogen/tables/VSMLighting.table.ixx b/sources/HAL/autogen/tables/VSMLighting.table.ixx index 34fe2879..23081479 100644 --- a/sources/HAL/autogen/tables/VSMLighting.table.ixx +++ b/sources/HAL/autogen/tables/VSMLighting.table.ixx @@ -22,12 +22,18 @@ export namespace Table HLSL::Texture2DArray vsm_atlas; HLSL::Texture2DArray page_table; HLSL::StructuredBuffer page_cameras; + HLSL::Texture2D blue_noise; + HLSL::Texture2D rtx_shadow_mask; HLSL::RWTexture2D result; + HLSL::RWTexture2D blocker_result; GBuffer gbuffer; HLSL::Texture2DArray& GetVsm_atlas() { return vsm_atlas; } HLSL::Texture2DArray& GetPage_table() { return page_table; } HLSL::StructuredBuffer& GetPage_cameras() { return page_cameras; } HLSL::RWTexture2D& GetResult() { return result; } + HLSL::Texture2D& GetBlue_noise() { return blue_noise; } + HLSL::Texture2D& GetRtx_shadow_mask() { return rtx_shadow_mask; } + HLSL::RWTexture2D& GetBlocker_result() { return blocker_result; } GBuffer& GetGbuffer() { return gbuffer; } static constexpr SIG_TYPE TYPE = SIG_TYPE::Table; template @@ -36,7 +42,10 @@ export namespace Table compiler.compile(vsm_atlas); compiler.compile(page_table); compiler.compile(page_cameras); + compiler.compile(blue_noise); + compiler.compile(rtx_shadow_mask); compiler.compile(result); + compiler.compile(blocker_result); compiler.compile(gbuffer); } struct Compiled @@ -44,7 +53,10 @@ export namespace Table uint vsm_atlas; // Texture2DArray uint page_table; // Texture2DArray uint page_cameras; // StructuredBuffer + uint blue_noise; // Texture2D + uint rtx_shadow_mask; // Texture2D uint result; // RWTexture2D + uint blocker_result; // RWTexture2D GBuffer::Compiled gbuffer; // GBuffer diff --git a/sources/RenderSystem/Assets/AssetExplorer.cpp b/sources/RenderSystem/Assets/AssetExplorer.cpp index 11649bfb..c9677167 100644 --- a/sources/RenderSystem/Assets/AssetExplorer.cpp +++ b/sources/RenderSystem/Assets/AssetExplorer.cpp @@ -226,12 +226,12 @@ namespace GUI menu->pos = vec2(pos); - menu->self_open(user_ui); + menu->add_item("Reload")->on_click = [this](menu_list_element::ptr e) { asset->get_asset()->reload_resource(); }; - + menu->self_open(user_ui); } return true; diff --git a/sources/RenderSystem/Assets/AssetRenderer.cpp b/sources/RenderSystem/Assets/AssetRenderer.cpp index 2f91b852..c1c4ebcc 100644 --- a/sources/RenderSystem/Assets/AssetRenderer.cpp +++ b/sources/RenderSystem/Assets/AssetRenderer.cpp @@ -243,8 +243,19 @@ AssetRenderer::AssetRenderer() { std::lock_guard g(lock); + // graph is a plain member, already fully constructed (default Graph()) by + // the time this body runs -- same "too late for Scope" situation as + // main.cpp's vsm, so it needs the same explicit re-parent rather than a + // Scope wrapping its construction. + preview_context->add_child(&graph); + scene_renderer = std::make_shared(); - scene_renderer->register_renderer(meshes_renderer = std::make_shared()); + { + // Distinguishes this mesh_renderer's Properties-tree entry from the + // main view's and SceneTextureRenderer's otherwise-identically-named ones. + VariableContext::Scope scope(*preview_context); + scene_renderer->register_renderer(meshes_renderer = std::make_shared()); + } cam.position = vec3(0, 5, -30); mesh_plane.reset(new MeshAssetInstance(EngineAssets::plane.get_asset())); @@ -268,8 +279,13 @@ SceneTextureRenderer::SceneTextureRenderer() { std::lock_guard g(lock); + preview_context->add_child(&graph); + scene_renderer = std::make_shared(); - scene_renderer->register_renderer(meshes_renderer = std::make_shared()); + { + VariableContext::Scope scope(*preview_context); + scene_renderer->register_renderer(meshes_renderer = std::make_shared()); + } cam.position = vec3(0, 5, -30); mesh_plane.reset(new MeshAssetInstance(EngineAssets::plane.get_asset())); diff --git a/sources/RenderSystem/Assets/AssetRenderer.ixx b/sources/RenderSystem/Assets/AssetRenderer.ixx index a5a89dcb..52b1a160 100644 --- a/sources/RenderSystem/Assets/AssetRenderer.ixx +++ b/sources/RenderSystem/Assets/AssetRenderer.ixx @@ -22,6 +22,12 @@ export class AssetRenderer : public Singleton mesh_renderer::ptr meshes_renderer; std::shared_ptr rendering; + // Groups meshes_renderer's Properties-tree entry under a name distinct + // from the other mesh_renderer instances elsewhere in the app (main + // view x2, SceneTextureRenderer) -- AssetRenderer doesn't derive + // VariableContext itself, hence create() rather than a direct member. + std::unique_ptr preview_context = VariableContext::create(L"Asset Preview"); + MeshAssetInstance::ptr material_tester; MeshAssetInstance::ptr mesh_plane; @@ -57,6 +63,9 @@ export class SceneTextureRenderer mesh_renderer::ptr meshes_renderer; std::shared_ptr rendering; + // See AssetRenderer::preview_context above -- same reasoning. + std::unique_ptr preview_context = VariableContext::create(L"Scene Texture Preview"); + MeshAssetInstance::ptr mesh_plane; MeshAssetInstance::ptr material_tester; // test mesh for material previews diff --git a/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.cpp b/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.cpp index 2b45ee10..7713cbaf 100644 --- a/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.cpp +++ b/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.cpp @@ -16,19 +16,6 @@ using namespace FrameGraph; namespace { - // Streamline's plugins issue their own legacy ResourceBarrier calls on - // tagged resources internally, requiring legacy/enhanced barrier interop - // hand-off at COMMON (D3D12 #1350). NOT transition_present() — that - // requests NO_ACCESS ("handed to the OS"), which is wrong here and trips - // a DEV-only assert. Sync is COMPUTE_SHADING, not NONE, since these - // resources are used again downstream. - const HAL::ResourceState kCommonState{ HAL::BarrierSync::COMPUTE_SHADING, HAL::BarrierAccess::COMMON, HAL::TextureLayout::PRESENT }; - - // ResultTextureNew is WRITTEN by SL's internal compute work (unlike the - // other 3 read-only tags) — forcing it to COMMON tripped D3D12 #1334 on - // SL's own UAV clears, so it stays in UNORDERED_ACCESS instead. - const HAL::ResourceState kUnorderedAccessState{ HAL::BarrierSync::COMPUTE_SHADING, HAL::BarrierAccess::UNORDERED_ACCESS, HAL::TextureLayout::UNORDERED_ACCESS }; - // ResultTextureNew is R16G16B16A16_FLOAT (see setup() below) — full HDR, // not the roughly-sRGB range SL's non-HDR path assumes. constexpr bool kHDR = true; @@ -38,8 +25,10 @@ bool PassDefault::setup( Passes::UpscalingDLSS::Context& data, TaskBuilder& builder) { // Mirrors FSR.cpp's inverted condition — exactly one of the two producers - // of ResultTextureNew is active per frame. - if (!nvidia::DLSS::get().available()) + // of ResultTextureNew is active per frame. g_upscaling_enabled is false + // when main.cpp's "downsampled" toggle is off (frame_size == upscale_size + // already, nothing to upscale) — SMAA runs instead. + if (!g_upscaling_enabled || !nvidia::DLSS::get().available()) return false; auto& frame = builder.graph->get_context(); @@ -47,7 +36,7 @@ bool PassDefault::setup( builder.need(data.ResultTexture, ResourceFlags::ComputeRead); // ExclusiveRead: this pass transitions these to PRESENT/COMMON for - // Streamline (see kCommonState), which must not be folded into the + // Streamline (see DLSS::upscale), which must not be folded into the // shared SRV read-window every other pass uses for them. builder.need(data.GBuffer_Depth, ResourceFlags::ComputeRead | ResourceFlags::ExclusiveRead); builder.need(data.GBuffer_Speed, ResourceFlags::ComputeRead | ResourceFlags::ExclusiveRead); @@ -101,10 +90,8 @@ void PassDefault::render( HAL::Resource* gbuffer_speed = data.GBuffer_Speed->get_resource(); HAL::Resource* result_texture_new = data.ResultTextureNew->get_resource(); - context.get_list()->transition(result_texture, kCommonState); - context.get_list()->transition(gbuffer_depth, kCommonState); - context.get_list()->transition(gbuffer_speed, kCommonState); - context.get_list()->transition(result_texture_new, kUnorderedAccessState); + // No transitions here: DLSS::upscale declares the states its tagged + // resources need, in an operation that wraps the call itself. const nvidia::DLSSMode mode = g_upscaling_dlss_mode; diff --git a/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.ixx b/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.ixx index 67da1f2c..de1840e3 100644 --- a/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.ixx +++ b/sources/RenderSystem/Effects/DLSS/UpscalingDLSS.ixx @@ -14,4 +14,11 @@ export // "use DLSS's own recommended render_size" (the default); set by // main.cpp's scale slider/"Recommended" button. float g_upscaling_dlss_scale_override = -1.0f; + + // False when main.cpp's "downsampled" toggle is off — frame_size == + // upscale_size that frame, so there is nothing to upscale. UpscalingDLSS + // and FSR both gate on this (in addition to their existing DLSS- + // availability check) so neither runs a pointless native-to-native + // upscale; SMAA gates on the opposite so native rendering still gets AA. + bool g_upscaling_enabled = true; } diff --git a/sources/RenderSystem/Effects/FSR/FSR.cpp b/sources/RenderSystem/Effects/FSR/FSR.cpp index cac1d503..a06ad784 100644 --- a/sources/RenderSystem/Effects/FSR/FSR.cpp +++ b/sources/RenderSystem/Effects/FSR/FSR.cpp @@ -2,6 +2,7 @@ module Graphics:FSR; import :FrameGraphContext; +import :UpscalingDLSS; import HAL; @@ -16,8 +17,9 @@ using namespace FrameGraph; bool PassDefault::setup(Passes::FSR::Context& data, TaskBuilder& builder) { - // Prefer DLSS-SR when available — mirrored in UpscalingDLSS.cpp's setup(). - if (nvidia::DLSS::get().available()) + // g_upscaling_enabled: see UpscalingDLSS.cpp's mirrored check. Prefer + // DLSS-SR when available — mirrored in UpscalingDLSS.cpp's setup(). + if (!g_upscaling_enabled || nvidia::DLSS::get().available()) return false; auto& frame = builder.graph->get_context(); diff --git a/sources/RenderSystem/Effects/PostProcess/SMAA.cpp b/sources/RenderSystem/Effects/PostProcess/SMAA.cpp index 7060d4da..2eb34ece 100644 --- a/sources/RenderSystem/Effects/PostProcess/SMAA.cpp +++ b/sources/RenderSystem/Effects/PostProcess/SMAA.cpp @@ -2,6 +2,7 @@ module Graphics:SMAA; import :FrameGraphContext; +import :UpscalingDLSS; import HAL; @@ -28,8 +29,10 @@ SMAA::SMAA() m_smaa_setup = [this](Passes::SMAA::Context& data, FrameGraph::TaskBuilder& builder) -> bool { - // DLSS does its own temporal AA; running SMAA on top would double up. - if (nvidia::DLSS::get().available()) + // Runs only when upscaling is off entirely (native rendering needs its + // own AA) — when it's on, DLSS/FSR each produce ResultTextureNew and + // DLSS does its own temporal AA, so SMAA on top would double up. + if (g_upscaling_enabled) return false; auto& frame = builder.graph->get_context(); diff --git a/sources/RenderSystem/Effects/RTX/RTX.ixx b/sources/RenderSystem/Effects/RTX/RTX.ixx index 50ba77cc..d1204085 100644 --- a/sources/RenderSystem/Effects/RTX/RTX.ixx +++ b/sources/RenderSystem/Effects/RTX/RTX.ixx @@ -14,6 +14,21 @@ public: using ptr = std::shared_ptr; + // Cross-pass debug toggle: when on, RTXShadow::render (PassDefaults.cpp) + // skips its normal Bend/FFX hybrid-shadow-denoiser dispatch and instead + // fires a genuine 16-ray soft-shadow reference directly into ShadowMask + // -- a ground truth to compare VSM's own PCSS approximation against. + // Set from VSM's own debug-view checkbox (VSM.ixx's + // use_vsm_debug_rtx_reference, propagated here once per frame in + // VSM.cpp's m_combine_render) rather than a toggle of its own, since the + // two are the same user-facing switch: "show me the RTX reference". + // Lives on this singleton (not a VSM member) because RTXShadow runs + // earlier in the frame than VSM_Combine and has no direct reference to + // the VSM instance -- this is the shared, always-linked spot both sides + // already import. One-frame lag between toggling the checkbox and + // RTXShadow picking it up (it renders before VSM_Combine writes this + // each frame) is harmless for a debug comparison feature. + bool debug_full_reference_shadow = false; MainRTX rtx{RenderSystem::get().device()}; diff --git a/sources/RenderSystem/Effects/Sky.cpp b/sources/RenderSystem/Effects/Sky.cpp index 071e3d43..3cb863ff 100644 --- a/sources/RenderSystem/Effects/Sky.cpp +++ b/sources/RenderSystem/Effects/Sky.cpp @@ -43,7 +43,6 @@ SkyRender::SkyRender() builder.create(data.sky_cubemap, { ivec3(256, 256, 0), HAL::Format::R11G11B10_FLOAT, 1, 0 }, FrameGraph::ResourceFlags::UnorderedAccess | - FrameGraph::ResourceFlags::RenderTarget | FrameGraph::ResourceFlags::Static); bool changed = ((sky.sunDir - dir).length() > 0.001f); @@ -57,46 +56,44 @@ SkyRender::SkyRender() m_cubesky_render = [this](Passes::CubeSky::Context& data, FrameGraph::FrameContext& context) { - auto& sky = context.graph->get_context(); - auto& graphics = context.get_list()->get_graphics(); + auto& sky = context.graph->get_context(); + auto& compute = context.get_list()->get_compute(); - graphics.set_pipeline(); - graphics.set_topology(HAL::PrimitiveTopologyType::TRIANGLE, - HAL::PrimitiveTopologyFeed::STRIP); + compute.set_pipeline(); { + PROFILE(L"cube_sky_setup"); + Slots::SkyData skydata; skydata.GetInscatter() = inscatter->texture_3d().texture3D; skydata.GetIrradiance() = irradiance->texture_2d().texture2D; skydata.GetTransmittance() = transmittance->texture_2d().texture2D; skydata.GetSunDir() = sky.sunDir; - graphics.set(skydata); - } + compute.set(skydata); - context.graph->set_slot(SlotID::FrameInfo, graphics); + context.graph->set_slot(SlotID::FrameInfo, compute); + } - for (unsigned int i = 0; i < 6; i++) { + PROFILE(L"cube_sky_faces"); + + auto& cube = *data.sky_cubemap; + auto size = cube.get_size(); + + // Mip 0 of all six faces as one array UAV, so the whole cube is a + // single dispatch with the face in Z. HAL::TextureViewDesc subres; - subres.ArraySize = 1; - subres.FirstArraySlice = i; - subres.MipLevels = 1; subres.MipSlice = 0; - - auto face = data.sky_cubemap->resource->create_view( - graphics.get_base(), subres); + subres.MipLevels = 1; + subres.FirstArraySlice = 0; + subres.ArraySize = 6; Slots::SkyFace skyFace; - skyFace.GetFace() = i; - graphics.set(skyFace); - - { - RT::SingleColor rt; - rt.GetColor() = face.renderTarget; - graphics.set_rtv(rt); - } + skyFace.GetFaces() = cube.resource->create_view( + compute.get_base(), subres).rwTexture2DArray; + compute.set(skyFace); - graphics.draw(4); + compute.dispatch(ivec3(size.x, size.y, 6), ivec3(8, 8, 1)); } }; @@ -155,94 +152,92 @@ void PassDefault::render( bool PassDefault::setup( Passes::CubeMapEnviromentProcessor::Context& data, TaskBuilder& builder) { - builder.need(data.sky_cubemap, ResourceFlags::PixelRead); + builder.need(data.sky_cubemap, ResourceFlags::ComputeRead); builder.create(data.sky_cubemap_filtered, { ivec3(64, 64, 0), HAL::Format::R11G11B10_FLOAT, 1 }, - ResourceFlags::RenderTarget | ResourceFlags::Static); + ResourceFlags::UnorderedAccess | ResourceFlags::Static); builder.create(data.sky_cubemap_filtered_diffuse, { ivec3(64, 64, 0), HAL::Format::R11G11B10_FLOAT, 1 }, - ResourceFlags::RenderTarget | ResourceFlags::Static); + ResourceFlags::UnorderedAccess | ResourceFlags::Static); return data.sky_cubemap.is_changed(); } void PassDefault::render( Passes::CubeMapEnviromentProcessor::Context& data, FrameContext& context) { - auto& graphics = context.get_list()->get_graphics(); + auto& compute = context.get_list()->get_compute(); - graphics.set_topology(HAL::PrimitiveTopologyType::TRIANGLE, HAL::PrimitiveTopologyFeed::STRIP); - graphics.set_signature(Layouts::DefaultLayout); + compute.set_signature(Layouts::DefaultLayout); { Slots::EnvSource downsample; downsample.GetSourceTex() = data.sky_cubemap->textureCube; - graphics.set(downsample); + compute.set(downsample); } - // Specular filtered cubemap — one draw per mip level per face. - UINT count = data.sky_cubemap_filtered->resource->get_desc().as_texture().MipLevels; - for (unsigned int m = 0; m < count; m++) + // One mip of a cubemap as a single UAV spanning all six faces. + auto slice_span = [&](HAL::CubeView& cube, UINT mip) { - graphics.set_pipeline(PSOS::CubemapENV::Level(std::min(m, 4u))); + HAL::TextureViewDesc subres; + subres.MipSlice = mip; + subres.MipLevels = 1; + subres.FirstArraySlice = 0; + subres.ArraySize = 6; - for (unsigned int i = 0; i < 6; i++) - { - HAL::TextureViewDesc subres; - subres.ArraySize = 1; - subres.FirstArraySlice = i; - subres.MipLevels = 1; - subres.MipSlice = m; + return cube.resource->create_view(compute.get_base(), subres); + }; - auto face = data.sky_cubemap_filtered->resource->create_view( - graphics.get_base(), subres); + // Specular filtered cubemap -- every mip of every face in one dispatch. The + // shader walks a flat index over the concatenated per-mip spans, so the + // mismatched mip sizes still map to one texel per thread. + { + PROFILE(L"env_specular_filter"); - { - RT::SingleColor rt; - rt.GetColor() = face.renderTarget; - graphics.set_rtv(rt, i == 0 ? RTOptions::Default : RTOptions::SetHandles); - } + auto& filtered = *data.sky_cubemap_filtered; - Slots::EnvFilter filter; - filter.GetFace().x = i; - filter.GetScaler().x = (float(m) + 0.5f) / count; - filter.GetSize().x = (UINT)data.sky_cubemap->resource->get_desc().as_texture().Dimensions.x; - graphics.set(filter); + const UINT count = filtered.get_desc().as_texture().MipLevels; + const UINT base = (UINT)filtered.get_size().x; - graphics.draw(4); - } - } + // EnvFilter carries one array UAV per mip; the 64^2 cubemap is 7 mips. + ASSERT(count <= 8); - // Diffuse filtered cubemap — one draw per face. - graphics.set_pipeline(); - for (unsigned int i = 0; i < 6; i++) - { - HAL::TextureViewDesc subres; - subres.ArraySize = 1; - subres.FirstArraySlice = i; - subres.MipLevels = 1; - subres.MipSlice = 0; + compute.set_pipeline(); - auto face = data.sky_cubemap_filtered_diffuse->resource->create_view( - graphics.get_base(), subres); + // Value-initialised: only the first `count` targets are filled. + Slots::EnvFilter filter{}; + filter.GetSize().x = (UINT)data.sky_cubemap->resource->get_desc().as_texture().Dimensions.x; + filter.GetSize().y = count; + filter.GetSize().z = base; - if (i == 0) + UINT total = 0; + for (UINT m = 0; m < count; m++) { - graphics.set_viewport(face.get_viewport()); - graphics.set_scissor(face.get_scissor()); - } + UINT size = std::max(base >> m, 1u); - { - RT::SingleColor rt; - rt.GetColor() = face.renderTarget; - graphics.set_rtv(rt); + filter.GetTargets()[m] = slice_span(filtered, m).rwTexture2DArray; + total += size * size * 6; } + compute.set(filter); + + compute.dispatch(ivec2(total, 1), ivec2(64, 1)); + } + + // Diffuse filtered cubemap -- mip 0 only, so one dispatch with the face in Z. + { + PROFILE(L"env_diffuse_filter"); + + auto& diffuse = *data.sky_cubemap_filtered_diffuse; + auto size = diffuse.get_size(); + + compute.set_pipeline(); - Slots::EnvFilter filter; - filter.GetFace().x = i; - filter.GetScaler().x = (float(0) + 0.5f) / count; - filter.GetSize().x = (UINT)data.sky_cubemap_filtered_diffuse->resource->get_desc().as_texture().Dimensions.x; - graphics.set(filter); + Slots::EnvFilter filter{}; + filter.GetSize().x = (UINT)size.x; + filter.GetSize().y = 1; + filter.GetSize().z = (UINT)size.x; + filter.GetTargets()[0] = slice_span(diffuse, 0).rwTexture2DArray; + compute.set(filter); - graphics.draw(4); + compute.dispatch(ivec3(size.x, size.y, 6), ivec3(8, 8, 1)); } } diff --git a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp index e761fce9..def7d170 100644 --- a/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp +++ b/sources/RenderSystem/Effects/VoxelGI/VoxelGIGraph.cpp @@ -22,12 +22,19 @@ void Texture3DMultiTiles::flush(HAL::CommandList& list) void Texture3DMultiTiles::set(HAL::ResourceDesc desc) { - desc.Flags |= ResFlags::Virtual | ResFlags::DisableStateTracking; + desc.Flags |= ResFlags::Virtual; tex_dynamic.reset(new HAL::Texture(RenderSystem::get().device(), desc, TextureLayout::SHADER_RESOURCE)); tex_static.reset(new HAL::Texture(RenderSystem::get().device(), desc, TextureLayout::SHADER_RESOURCE)); tex_result.reset(new HAL::Texture(RenderSystem::get().device(), desc, TextureLayout::SHADER_RESOURCE)); + // Allocated outside the FrameGraph's allocator, but only ever transitioned + // by its passes -- so the graph owns their state, which is what the + // DisableStateTracking flag used to say. + tex_dynamic->resource->frame_graph_managed = true; + tex_static->resource->frame_graph_managed = true; + tex_result->resource->frame_graph_managed = true; + tex_dynamic->resource->set_name("tex_dynamic"); tex_dynamic->resource->set_name("tex_static"); @@ -93,9 +100,12 @@ void Texture3DRefTiles::flush(HAL::CommandList& list) void Texture3DRefTiles::set(HAL::ResourceDesc desc) { - desc.Flags |= ResFlags::Virtual | ResFlags::DisableStateTracking; + desc.Flags |= ResFlags::Virtual; tex_result.reset(new HAL::Texture(RenderSystem::get().device(), desc, TextureLayout::SHADER_RESOURCE)); + // See Texture3DMultiTiles::set -- FrameGraph passes own its transitions. + tex_result->resource->frame_graph_managed = true; + static_tiles.resize(tex_result->resource->get_tiled_manager().get_tiles_count(), 0); dynamic_tiles.resize(tex_result->resource->get_tiled_manager().get_tiles_count(), 0); } diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx index 33b19390..3a414fe1 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Base.ixx @@ -246,14 +246,12 @@ public: // happen single-threaded after the setup/append phase joins. concurrent_vector passes; - HAL::SubResourcesGPU merged_read_state; - SyncState from; SyncState to; }; // A resource version's RW-state timeline. Cursor-based like ResourceChain: - // the state slots — and each slot's passes / merged_read_state buffers — + // the state slots — and each slot's passes buffers — // persist across frames. reset_frame() only rewinds the cursor and push() // reuses a slot (clearing passes keeps its storage, so reserve() is a no-op // once it's large enough), so we don't reallocate the per-state vectors @@ -292,7 +290,6 @@ public: s.passes.reserve(reserve_n); // no-op once large enough s.from.reset(); s.to.reset(); - s.merged_read_state.subres.clear(); return s; } @@ -324,7 +321,11 @@ public: HAL::ResourceHandle alloc_ptr; HAL::ResourceDesc d3ddesc; - HAL::HeapType heap_type; + // Initialised, because not every path assigns it: create_resources() skips + // passed resources, and these slots are reused across frames, so an + // uninitialised value is indeterminate rather than merely wrong. Anything + // that filters on heap_type would then drop resources at random. + HAL::HeapType heap_type = HAL::HeapType::DEFAULT; // setup SyncState used_begin; SyncState used_end; @@ -356,10 +357,6 @@ public: uint64 offset_in_bytes; std::shared_ptr view; - HAL::SubResourcesGPU creation_state; - - HAL::SubResourcesGPU last_state; - std::shared_ptr handler; bool passed = false; @@ -466,9 +463,10 @@ public: virtual void init(ResourceAllocInfo& info) override { info.d3ddesc = desc.create_resource_desc(info.flags); - - info.d3ddesc.Flags|=HAL::ResFlags::DisableStateTracking; + // "The FrameGraph owns this resource's transitions" is now + // Resource::frame_graph_managed, set on the resource itself in + // create_resources -- no longer a creation-desc flag. } virtual void init_view(ResourceAllocInfo& info,GPUEntityStorageInterface& frame) override @@ -685,7 +683,6 @@ public: HAL::Resource::ptr resource; HAL::ResourceHandle alloc_ptr; HAL::ResourceDesc desc; - HAL::SubResourcesGPU last_state; bool valid() const { return !!resource; } }; @@ -893,12 +890,6 @@ public: void process_transitions(); void process_fences(); - // Mirror commit_command_lists' batching to find the sets of lists that - // will be submitted in ONE ExecuteCommandLists, and chain - // resource state across the boundaries inside each set. Must run after - // process_transitions (all usages recorded) and process_fences (which - // decides where batches are flushed), and before compile_lists. - void link_list_groups(); void compile_lists(); void reset(); @@ -964,7 +955,6 @@ public: std::future render_task; - std::future compile_task; // Populated after compile_lists(); available during on_compile. // Transition records have barrier_point == nullptr (resolved into description). diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp index 94ead8ed..b6ce489c 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.Timeline.cpp @@ -800,6 +800,13 @@ class FrameGraphTimelineCanvas : public dock_base const PassInfo* pass = nullptr; const HAL::CommandRecord::BarrierDetail* detail = nullptr; bool mismatch = false; + + // Which CmdListOperation this barrier brackets, and on which side. + // Carried from the record so the view can group Pass > Operation > + // Pre/Post instead of presenting one flat run of transitions. + uint op_index = 0; + bool after_op = false; + HAL::BarrierSync op_type = HAL::BarrierSync::NONE; }; std::vector entries; @@ -812,7 +819,7 @@ class FrameGraphTimelineCanvas : public dock_base if (cmd.type != HAL::CommandType::Transition) continue; for (auto& bd : cmd.barrier_details) if (bd.resource_name == track.name) - entries.push_back({ &pass, &bd }); + entries.push_back({ &pass, &bd, false, cmd.op_index, cmd.after_op, cmd.op_type }); } } @@ -890,20 +897,25 @@ class FrameGraphTimelineCanvas : public dock_base if (is_first) { - if (is_persistent) - { - // Persistent: must DISCARD or declare a real before-layout. - if (!has_discard && - bd.before.layout == HAL::TextureLayout::UNDEFINED) - e.mismatch = true; - } - else - { - // Non-persistent: first barrier must DISCARD from UNDEFINED. - if (!has_discard || - bd.before.layout != HAL::TextureLayout::UNDEFINED) - e.mismatch = true; - } + // The only wrong thing a first barrier can do is claim + // UNDEFINED without discarding: that reads contents it just + // declared meaningless. Entering from a real layout is fine + // for ANY resource. + // + // This used to demand additionally that a non-persistent + // resource's first barrier BE a discard from UNDEFINED, + // which no longer matches the barrier system: the discard + // fires once per resource LIFETIME (latched by + // Resource::initialized), not once per frame. A transient + // reused from an earlier frame is already initialized, so it + // correctly enters from its resting layout with no discard -- + // and every one of them was being flagged for it. + // + // Aliased transients still get their discard: alias_begin + // re-arms Resource::virgin, so the next write emits one and + // passes this check on the has_discard branch. + if (!has_discard && bd.before.layout == HAL::TextureLayout::UNDEFINED) + e.mismatch = true; } else { @@ -912,12 +924,19 @@ class FrameGraphTimelineCanvas : public dock_base // // EXCEPTION — a release to UNDEFINED (alias_end) validly ends // the lifetime from whatever layout it holds; with cross-list - // chaining (link_list_groups) its before is handed off from + // chaining (CommandListGroup) its before is handed off from // another list, not the previous barrier in this view. D3D12 // validates the real before-layout, so skip it here. + // EXCEPTION — a DISCARD enters from UNDEFINED by definition. + // It is keyed on the resource's first WRITE, not its first + // barrier, so a read can legitimately precede it: the + // resource is then already tracked at a real layout here + // while the discard still declares UNDEFINED. That is + // correct (the write establishes the contents, so nothing + // needs preserving) and is not a continuity break. const bool is_release = (bd.after.layout == HAL::TextureLayout::UNDEFINED); auto cur = current(); - if (!is_release && cur && bd.before.layout != cur->layout) + if (!is_release && !has_discard && cur && bd.before.layout != cur->layout) e.mismatch = true; } @@ -950,6 +969,15 @@ class FrameGraphTimelineCanvas : public dock_base { const PassInfo* last_pass = nullptr; uint64 last_id = entries.front().detail->resource_id; + + // Pass > Operation > Pre/Post. Barriers only mean something + // relative to the work they bracket, and a flat list hides both + // which operation they belong to and whether they run before or + // after it -- the difference between "prepare for this dispatch" + // and "hand the resource on". + int last_op = -1; + int last_side = -1; // 0 = pre, 1 = post, -1 = none printed yet + for (auto& e : entries) { // A recreate() swaps in a new resource instance under the same @@ -959,6 +987,8 @@ class FrameGraphTimelineCanvas : public dock_base { last_id = e.detail->resource_id; last_pass = nullptr; // force the pass header to reprint + last_op = -1; + last_side = -1; y += 6.0f; add_row("------------ recreated (new instance) ------------", 0.0f, col_dim); } @@ -967,6 +997,8 @@ class FrameGraphTimelineCanvas : public dock_base if (e.pass != last_pass) { last_pass = e.pass; + last_op = -1; // operation numbering is per list + last_side = -1; y += 4.0f; float4 hcol = e.mismatch ? col_err : (e.pass->call_id == clicked_call_id) ? col_sel @@ -974,12 +1006,44 @@ class FrameGraphTimelineCanvas : public dock_base add_row(to_str(e.pass->name), 0.0f, hcol); } + // Operation header, then the side within it. Both reprint + // whenever either changes, so a run of barriers in one group + // stays under a single heading. + if ((int)e.op_index != last_op) + { + last_op = (int)e.op_index; + last_side = -1; + // Named by its class, not just numbered: the index only + // says where it sits in the list, the class says what the + // barriers around it are actually bracketing. + add_row("Operation " + std::to_string(e.op_index) + + " [" + barrier_sync_str(e.op_type) + "]", 12.0f, col_dim); + } + + const int side = e.after_op ? 1 : 0; + if (side != last_side) + { + last_side = side; + add_row(e.after_op ? "Post barriers" : "Pre barriers", 24.0f, col_dim); + } + const auto& bd = *e.detail; // Subresource / split-barrier annotations on the Sync line. + // Exact extent, not just the representative index: a coalesced + // barrier covers a mip/slice rectangle and "sub=N" would be a + // lie about how much it moves. std::string sub_str; - if (bd.subres != HAL::ALL_SUBRESOURCES) - sub_str = " sub=" + std::to_string(bd.subres); + if (!bd.range.is_all()) + { + const auto& r = bd.range; + const bool one = (r.num_mips == 1 && r.num_slices == 1); + + sub_str = one + ? " sub=" + std::to_string(bd.subres) + : " mips " + std::to_string(r.first_mip) + "+" + std::to_string(r.num_mips) + + " slices " + std::to_string(r.first_slice) + "+" + std::to_string(r.num_slices); + } std::string flag_str; using BF = HAL::BarrierFlags; @@ -1004,7 +1068,7 @@ class FrameGraphTimelineCanvas : public dock_base sub_str + flag_str; const std::string bp_prefix = bp_on ? "[*] " : " "; - auto sync_lbl = add_row(bp_prefix + sync_base, 12.0f, sc); + auto sync_lbl = add_row(bp_prefix + sync_base, 36.0f, sc); sync_lbl->clickable = true; const bool is_err = e.mismatch; @@ -1021,10 +1085,10 @@ class FrameGraphTimelineCanvas : public dock_base add_row("Access: " + barrier_access_str(bd.before.access) + " -> " + barrier_access_str(bd.after.access), - 12.0f, dc); + 36.0f, dc); add_row("Layout: " + barrier_layout_str(bd.before.layout) + " -> " + barrier_layout_str(bd.after.layout), - 12.0f, dc); + 36.0f, dc); } } @@ -1187,6 +1251,11 @@ class FrameGraphTimelineCanvas : public dock_base auto u = static_cast(s); auto has = [&](BS f) { return (u & static_cast(f)) != 0; }; // Check composite aliases first. + // + // ALL is not a pipeline stage but "synchronize against everything", and + // it is what state_at_rest() uses -- so without it every return-to-rest + // and every assumed entry rendered as "?". + if (u == static_cast(BS::ALL)) return "ALL"; if (u == static_cast(BS::ALL_DIRECT)) return "ALL_DIRECT"; if (u == static_cast(BS::ALL_COMPUTE)) return "ALL_COMPUTE"; if (u == static_cast(BS::ALL_SHADING)) return "ALL_SHADING"; @@ -1200,8 +1269,11 @@ class FrameGraphTimelineCanvas : public dock_base add(BS::RENDER_TARGET, "RT"); add(BS::COMPUTE_SHADING, "CS"); add(BS::RAYTRACING, "RAY"); + add(BS::ALL, "ALL"); add(BS::COPY, "COPY"); + add(BS::RESOLVE, "RESOLVE"); add(BS::EXECUTE_INDIRECT, "IND"); + add(BS::PREDICATION, "PRED"); add(BS::CLEAR_UNORDERED_ACCESS_VIEW, "CLEAR_UAV"); add(BS::BUILD_RAYTRACING_ACCELERATION_STRUCTURE, "BVH"); add(BS::COPY_RAYTRACING_ACCELERATION_STRUCTURE, "BVH_COPY"); @@ -1584,25 +1656,16 @@ class FrameGraphTimelineCanvas : public dock_base if (is_first) { - if (is_persistent) - { - // Persistent: must DISCARD or declare a real before-layout. - if (!has_discard && - bd.before.layout == HAL::TextureLayout::UNDEFINED) - { - tr.has_mismatch = true; - break; - } - } - else + // Same rule as the per-pass validator above: the only + // wrong first barrier is one claiming UNDEFINED without + // discarding. The discard fires once per resource + // LIFETIME, not per frame, so a reused transient + // legitimately starts from its resting layout. + if (!has_discard && + bd.before.layout == HAL::TextureLayout::UNDEFINED) { - // Non-persistent: first barrier must DISCARD from UNDEFINED. - if (!has_discard || - bd.before.layout != HAL::TextureLayout::UNDEFINED) - { - tr.has_mismatch = true; - break; - } + tr.has_mismatch = true; + break; } } else @@ -1611,9 +1674,13 @@ class FrameGraphTimelineCanvas : public dock_base // UNDEFINED (alias_end) validly ends the lifetime from any // layout — its before may be a cross-list hand-off, and // D3D12 validates the real layout, so skip it here. + // + // A DISCARD is skipped for the same reason: it enters from + // UNDEFINED by definition, and it keys on the first WRITE, + // so a read may already have moved the tracked layout. const bool is_release = (bd.after.layout == HAL::TextureLayout::UNDEFINED); auto cur = current(); - if (!is_release && cur && bd.before.layout != cur->layout) + if (!is_release && !has_discard && cur && bd.before.layout != cur->layout) { tr.has_mismatch = true; break; @@ -1738,6 +1805,21 @@ class FrameGraphTimelineCanvas : public dock_base HAL::Format::R8G8B8A8_UNORM, { thumb_dim }, 1, 1, HAL::ResFlags::ShaderResource | HAL::ResFlags::RenderTarget | HAL::ResFlags::UnorderedAccess); last_write_thumb_tex = std::make_shared(RenderSystem::get().device(), desc); + + // resource@pass, matching the per-pass preview textures in + // FrameGraph.Debug.cpp. The pass is the one that produced + // this cell, found by the cell's call_id; unnamed these all + // report as "Unnamed ID3D12Resource Object" and cannot be + // told apart in validation output. + { + std::string pass_name = "?"; + for (const auto& p : m_passes) + if (p.call_id == cell.call_id) { pass_name = convert(p.name.ptr); break; } + + last_write_thumb_tex->resource->set_name( + "FGDebug::thumb::" + m_resources[ri].name + "@" + pass_name); + } + cell.thumb_tex = last_write_thumb_tex; } else diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.cpp index dc33734c..e65d0d19 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.Debug.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.Debug.cpp @@ -143,8 +143,15 @@ class ResourceDebugger :public GUI::base if (!rendered_image->texture.texture || uint2(rendered_image->texture.texture->get_desc().as_texture().Dimensions.xy) != debug_size) { HAL::ResourceDesc desc = HAL::ResourceDesc::Tex2D(HAL::Format::R8G8B8A8_UNORM, { debug_size }, 1, 1, HAL::ResFlags::ShaderResource | HAL::ResFlags::RenderTarget | HAL::ResFlags::UnorderedAccess); - auto texture = std::make_shared(RenderSystem::get().device(), desc, TextureLayout::SHADER_RESOURCE); rendered_image->texture.texture = std::make_shared(RenderSystem::get().device(), desc); + + // resource@pass, so a validation message names what it is + // and where it came from. These are debugger-owned + // textures, and unnamed they show up as "Unnamed + // ID3D12Resource Object" -- indistinguishable from each + // other and from anything else the debug layer reports. + rendered_image->texture.texture->resource->set_name( + std::string("FGDebug::preview::") + info->name() + "@" + convert(pass->name.ptr)); } diff --git a/sources/RenderSystem/FrameGraph/FrameGraph.cpp b/sources/RenderSystem/FrameGraph/FrameGraph.cpp index bee5d439..c6894f32 100644 --- a/sources/RenderSystem/FrameGraph/FrameGraph.cpp +++ b/sources/RenderSystem/FrameGraph/FrameGraph.cpp @@ -100,7 +100,6 @@ namespace FrameGraph l.carried.resource = cur.resource; l.carried.alloc_ptr = cur.alloc_ptr; l.carried.desc = cur.d3ddesc; - l.carried.last_state = cur.last_state; // best-effort; barrier priming handled later // Detach so the normal free pass / next frame's realloc don't touch it. cur.alloc_ptr = HAL::ResourceHandle{}; @@ -174,7 +173,11 @@ namespace FrameGraph void TaskBuilder::pass_texture(ResourceID id, HAL::TextureResource::ptr tex, HAL::FenceWaiter fence, ResourceFlags flags) { - tex->disable_state_tracking(); + // Handing a resource to the FrameGraph makes the FrameGraph responsible + // for its transitions -- same contract as a resource the graph created + // itself, so it carries the same marker and opts out of the per-group + // return-to-rest in CommandListGroup::compile_transitions. + tex->frame_graph_managed = true; auto tex_desc = tex->get_desc().as_texture(); if (tex_desc.is2D()) @@ -187,6 +190,15 @@ namespace FrameGraph info.resource = tex; info.fence = fence; info.d3ddesc = tex->get_desc(); + + // From the resource, not assumed: create_resources() -- the only other + // place that sets heap_type -- skips passed resources entirely, so + // without this the field keeps its default-constructed value. Anything + // filtering on it then silently drops every passed resource; + // process_transitions did exactly that, which is why a passed texture + // was linked by nobody and rested by nobody (pass_texture also marks it + // frame_graph_managed, opting it out of the per-group rest). + info.heap_type = tex->get_heap_type(); passed_resources.insert(&info); @@ -200,7 +212,6 @@ namespace FrameGraph h.init_view(info, *current_frame); - info.creation_state = info.last_state = info.resource->get_state_manager().copy_gpu(); } else if (tex_desc.is3D()) { @@ -216,6 +227,7 @@ namespace FrameGraph info.resource = tex; info.d3ddesc = tex->get_desc(); info.fence = fence; + info.heap_type = tex->get_heap_type(); // see the 2D branch above passed_resources.insert(&info); h.desc.format = tex->get_desc().as_texture().Format; @@ -223,7 +235,6 @@ namespace FrameGraph h.desc.size = tex->get_desc().as_texture().Dimensions; h.init_view(info, *current_frame); - info.creation_state = info.last_state = info.resource->get_state_manager().copy_gpu(); } else ASSERT(false); @@ -327,11 +338,24 @@ namespace FrameGraph // its thumbnail can be captured from the adopted resource. if (!check(flags & WRITEABLE_FLAGS) && !info->is_history_prev) continue; info->process_debug_resource(pass, this); - } - // Close the pass's open op-batch before deactivating freed resources, - // so alias_end lands after the last op instead of inside its batch. - list->close_op(); + // The ExternalPass exists only so the debugger can capture; it does + // no rendering of its own and is deliberately NOT part of any + // resource's state timeline, so process_transitions never links it + // to the passes around it. Linking it would mean a fence for a pass + // with no GPU work, which is not worth paying for a debug feature. + // + // So it has to be transparent instead: the capture moved the + // resource (to SHADER_RESOURCE, to sample it), and nothing else + // will move it back -- a frame_graph_managed resource is exempt + // from the per-group return to rest. Hand it back at rest here, + // which is the state it was found in, and the state the next group + // assumes for its first touch. Without this the back buffer stayed + // in SHADER_RESOURCE while UI_Render_0 opened its group declaring + // PRESENT (#1334, once per frame). + if (pass->id == std::numeric_limits::max() && info->resource) + list->transition_to_rest(info->resource.get()); + } for (auto info : pass->used.resource_deletions_after) { @@ -660,23 +684,12 @@ namespace FrameGraph } - // Close every pass list's open op-batch before the FrameGraph appends - // its cross-pass transitions (decay-to-resting, prepare_after_state). - // Those are recorded at the list's back point; without this they would - // fall inside the last op's still-open batch and be mis-positioned. - for (auto& pass : builder.enabled_passes) - if (pass->context.list) - pass->context.list->close_op(); - + // Runs after every pass has recorded: process_transitions appends + // producer-side hand-off and resting transitions to lists that are + // already complete, so it needs their final operation sequence. builder.process_transitions(); builder.process_fences(); - // Chain resource state across command-list boundaries so multiple lists - // share one ExecuteCommandLists scope. Runs after process_transitions - // (all usages recorded) and process_fences (batch flush points known), - // before compile_lists. - builder.link_list_groups(); - builder.compile_lists(); @@ -699,7 +712,79 @@ namespace FrameGraph // enum_array, not std::map: the key is a 3-value enum, so a red-black // tree rebuilt every frame buys nothing over direct indexing. - enum_array> queued_lists; + // Batches waiting to be submitted, per queue. A batch IS the barrier + // group: it is exactly the set of lists that will go in one + // ExecuteCommandLists, and the flush points below (a gpu_wait, a fence, + // end of frame) are precisely the group boundaries. + enum_array queued_lists; + + // Resolve a group's recorded Transition points into plain data for the + // debugger. Must run after the group's barriers are computed (so the + // groups are filled) and BEFORE it is submitted: barrier_point points + // into the list's `operations`, which Transitions::on_execute clears as + // soon as the list finishes executing on the submit thread. + std::map pass_of_list; + if constexpr (BuildOptions::Dev) + for (auto& pass : builder.enabled_passes) + if (pass->context.list) + pass_of_list[pass->context.list.get()] = pass; + + auto snapshot_group = [&](HAL::CommandListGroup& group) + { + if constexpr (BuildOptions::Dev) + { + PROFILE(L"debug_snapshot"); + for (auto& list : group.get_lists()) + { + auto it = pass_of_list.find(list.get()); + if (it == pass_of_list.end()) continue; + + auto records = list->take_debug_records(); + + // One name lookup per distinct resource instead of one per + // barrier. A resource is typically barriered many times in a + // list, and most names are past the small-string limit, so + // this is the difference between a heap allocation per + // barrier and one per resource. + // + // The name has to be captured eagerly rather than read from + // b.resource later: these records outlive the frame that + // produced them, and a transient resource can be freed or + // aliased away in the meantime. That is why resource_id is + // documented as never dereferenced. + std::unordered_map names; + + for (auto& rec : records) + { + if (rec.type != HAL::CommandType::Transition || !rec.barrier_point) continue; + + const auto& barriers = rec.barrier_point->get_barriers(); + rec.description = "Barriers: " + std::to_string(barriers.size()); + rec.barrier_details.reserve(barriers.size()); + for (const auto& b : barriers) + { + HAL::CommandRecord::BarrierDetail detail; + + auto name_it = names.find(b.resource); + if (name_it == names.end()) + name_it = names.emplace(b.resource, b.resource ? b.resource->name : "?").first; + detail.resource_name = name_it->second; + + detail.before = b.before; + detail.after = b.after; + detail.subres = HAL::representative_subres(b.resource, b.range); + detail.range = b.range; + detail.flags = b.flags; + detail.resource_id = reinterpret_cast(b.resource); // opaque instance id + rec.barrier_details.push_back(std::move(detail)); + } + rec.barrier_point = nullptr; + } + + it->second->debug_commands = std::move(records); + } + } + }; // Frame-boundary cross-queue sync: each queue waits for the OTHER // queues' end of the previous frame before this frame's first @@ -719,6 +804,35 @@ namespace FrameGraph enum_array frame_end_fence; + // One batch that will become a single ExecuteCommandLists, with the + // ordering work that has to happen around it. Barrier computation is + // the expensive part and is pure once plan_resources() has run, so it + // is hoisted out of this loop and done for every batch at once, in + // parallel; submission below stays strictly ordered because fences + // flow between batches. + struct PendingSubmit + { + HAL::CommandListGroup group; + HAL::CommandListType type; + std::vector waits; // gpu_wait these before submitting + Pass* fence_pass = nullptr; // receives fence_end, if any + }; + std::vector submits; + + // Close the batch open on `type`: decide its first-use questions (this + // is the only part of the barrier work that must happen in submission + // order) and queue it for compilation. + auto close_batch = [&](HAL::CommandListType type, Pass* fence_pass, std::vector waits = {}) + { + auto& group = queued_lists[type]; + if (group.empty() && waits.empty()) return; + + + + submits.push_back(PendingSubmit{ std::move(group), type, std::move(waits), fence_pass }); + group.clear(); + }; + for (auto& pass : builder.enabled_passes) { HAL::CommandListType list_type = pass->get_type(); @@ -727,24 +841,14 @@ namespace FrameGraph if (commandList) { - - + std::vector waits; for (auto sync_pass : pass->sync_state.values) - { - if (!sync_pass) continue; + if (sync_pass) waits.push_back(sync_pass); - if(!queued_lists[list_type].empty()) - { - RenderSystem::get().device().get_queue(list_type)->execute(std::move(queued_lists[list_type])); - queued_lists[list_type].clear(); - } + if (!waits.empty()) + close_batch(list_type, nullptr, std::move(waits)); - RenderSystem::get().device().get_queue(list_type)->gpu_wait(sync_pass->fence_end); - } - - - - queued_lists[list_type].emplace_back(commandList); + queued_lists[list_type].add(commandList); // Diagnostic: full queue serialization — every pass gets a // fence and every OTHER queue waits on it, so no two passes @@ -755,44 +859,83 @@ namespace FrameGraph pass->put_fence = true; if (pass->put_fence) //////////////////////// ARGH!!!! - { - pass->fence_end = RenderSystem::get().device().get_queue(list_type)->execute(std::move(queued_lists[list_type])); + close_batch(list_type, pass); + } - queued_lists[list_type].clear(); - result = pass->fence_end; - frame_end_fence[list_type] = pass->fence_end; + } - if (serialize_queues) - for (auto other : magic_enum::enum_values()) - { - if (other == list_type) continue; - RenderSystem::get().device().get_queue(other)->gpu_wait(pass->fence_end); - } - } - // pass->fence_end = commandList->execute(); + // Whatever is still open at end of frame. + for (auto type : magic_enum::enum_values()) + close_batch(type, nullptr); + + // Resolve every batch's first-use questions FIRST, on this thread, in + // submission order. + // + // This cannot move into the parallel fan-out below: plan_resources() + // reads and mutates Resource::virgin / Resource::initialized, which are + // per-RESOURCE and shared by every group that touches them. Running it + // concurrently races on "which group owns this resource's first-ever + // write", so the initializing discard lands on a nondeterministic group + // -- and in submission order it may land on a group that executes after + // the one that needed it. + { + PROFILE(L"plan_groups"); + for (auto& submit : submits) + submit.group.plan_resources(); + } - // - } + // Every batch's barriers, computed in parallel. plan_resources() has + // already resolved everything order-dependent, so these touch nothing + // shared: `current`/`wanted` are group-local and the Barriers they fill + // belong to their own lists. + { + PROFILE(L"compile_groups"); + std::vector> tasks; + tasks.reserve(submits.size()); - } + for (auto& submit : submits) + tasks.emplace_back(thread_pool::get().enqueue([&submit]() + { + submit.group.compile_transitions(); + submit.group.compile(); + })); + for (auto& t : tasks) + t.wait(); + } - for (auto type : magic_enum::enum_values()) + // Ordered submission. Cheap now -- the batches are already compiled -- + // but it must stay in order: a batch's fence_end is consumed by a later + // batch's gpu_wait, and gpu_wait/execute derive their relative order + // from the order they are posted to the queue's executor thread. { - auto& lists = queued_lists[type]; - if (lists.empty()) continue; - result = RenderSystem::get().device().get_queue(type)->execute(std::move(lists)); - lists.clear(); - frame_end_fence[type] = result; - - if (serialize_queues) - for (auto other : magic_enum::enum_values()) - { - if (other == type) continue; - RenderSystem::get().device().get_queue(other)->gpu_wait(result); - } + PROFILE(L"submit_groups"); + + for (auto& submit : submits) + { + auto queue = RenderSystem::get().device().get_queue(submit.type); + + for (const auto* sync_pass : submit.waits) + queue->gpu_wait(sync_pass->fence_end); + + if (submit.group.empty()) continue; + + snapshot_group(submit.group); + result = queue->execute(submit.group); + + frame_end_fence[submit.type] = result; + if (submit.fence_pass) + submit.fence_pass->fence_end = result; + + if (serialize_queues) + for (auto other : magic_enum::enum_values()) + { + if (other == submit.type) continue; + RenderSystem::get().device().get_queue(other)->gpu_wait(result); + } + } } // Remember each queue's final fence for next frame's boundary wait. @@ -993,145 +1136,21 @@ namespace FrameGraph } } - void TaskBuilder::link_list_groups() - { - PROFILE(L"link_list_groups"); - - // Lists that will be submitted together in one ExecuteCommandLists, per - // queue. Mirrors commit_command_lists: a pass that must gpu_wait flushes - // the pending batch before it, and a pass with put_fence closes one after. - enum_array> pending; - - auto close_group = [&](HAL::CommandListType type) - { - auto& lists = pending[type]; - - // Within a group, chain each resource from the list that used it last - // to the one using it next. That replaces the SYNC_NONE release + - // SYNC_NONE re-seed pair at the boundary with a direct state hand-off, - // leaving exactly one SYNC_NONE entry (first user) and one SYNC_NONE - // exit (last user) per resource per group. - if (lists.size() > 1) - { - std::map last_user; - - for (auto* cmd : lists) - for (auto* res : cmd->get_used_resources()) - { - auto it = last_user.find(res); - if (it != last_user.end() && it->second != cmd) - res->get_state_manager().chain_lists(it->second, cmd); - - last_user[res] = cmd; - } - } - - lists.clear(); - }; - - for (auto& pass : enabled_passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; - - HAL::CommandListType list_type = pass->get_type(); - - bool waits = false; - for (auto sync_pass : pass->sync_state.values) - if (sync_pass) waits = true; - - if (waits) close_group(list_type); // gpu_wait flushes the batch - - pending[list_type].push_back(commandList.get()); - - if (pass->put_fence) close_group(list_type); // fence closes the batch - } - - for (auto type : magic_enum::enum_values()) - close_group(type); - } - void TaskBuilder::compile_lists() { PROFILE(L"compile"); - { - - PROFILE(L"compile_transitions"); - for (auto& pass : enabled_passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; - - - } - - - } - descriptor_commit_task = thread_pool::get().enqueue([this]() { current_frame->commit_descriptors_to_gpu(); }); - { - - PROFILE(L"compile_passes"); - for (auto& pass : enabled_passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; - - pass->compile_task = thread_pool::get().enqueue([commandList, pass]() { - PROFILE(pass->name); - commandList->compile_transitions(); - commandList->compile(); - }); - - } - } - - for (auto& pass : enabled_passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; - - { - PROFILE(L"pass_wait"); - pass->compile_task.wait(); - } + // Lists are compiled by their CommandListGroup at submission time -- + // compile() consumes the barrier groups, so it has to follow the + // group's compile_transitions(). Nothing per-pass to do here. - // Snapshot debug records now: compile_transitions has filled barrier data, - // compile() has run. Resolve Transition descriptions so no raw pointers survive. - if constexpr (BuildOptions::Dev) - { - auto records = commandList->get_debug_records(); - for (auto& rec : records) - { - if (rec.type == HAL::CommandType::Transition && rec.barrier_point) - { - const auto& barriers = rec.barrier_point->transitions.get_barriers(); - rec.description = "Barriers: " + std::to_string(barriers.size()); - rec.barrier_details.reserve(barriers.size()); - for (const auto& b : barriers) - { - HAL::CommandRecord::BarrierDetail detail; - detail.resource_name = b.resource ? b.resource->name : "?"; - detail.before = b.before; - detail.after = b.after; - detail.subres = b.subres; - detail.flags = b.flags; - detail.resource_id = reinterpret_cast(b.resource); // opaque instance id - rec.barrier_details.push_back(std::move(detail)); - } - rec.barrier_point = nullptr; - } - } - pass->debug_commands = std::move(records); - } - } // Descriptors must be visible to the GPU before any list below actually executes. // This is the last possible point to wait - everything since render() kicked the @@ -1148,290 +1167,260 @@ namespace FrameGraph + // Cross-pass linking for FrameGraph-owned resources. + // + // A command-list group can only reason about what it can see: it enters a + // resource from its resting layout and leaves it there. That works for + // resources nobody else schedules, but a FrameGraph resource is written by + // one pass and read by the next, often with a fence between them -- routing + // it through the resting layout at every boundary would be both wrong (the + // group cannot know where it actually is) and expensive. + // + // So the graph, which knows the whole pass timeline, does three things here: + // 1. merges concurrent readers onto one shared state, so several passes + // reading the same resource cost one transition rather than one each; + // 2. tells each consuming list what state the producing pass left the + // resource in (set_entry_state), which is what lets a group see over a + // boundary it would otherwise stop at; + // 3. returns the resource to its resting layout once, at the last pass + // that touches it -- not at every group boundary. + // + // Replaces the pre-2026-08 version of this function, which did the same jobs + // through ResourceStateManager's prepare_state / prepare_after_state seed + // nodes and per-subresource SubResourcesGPU snapshots. void TaskBuilder::process_transitions() { - PROFILE(L"optimizing transitions"); + PROFILE(L"process_transitions"); + for (auto& chain : alloc_resources) for (auto& info : chain.active_span()) { - if (!info.enabled) continue; - /// if (info.passed) continue;///wtf - auto& resource = info.resource; - if (!resource) continue; - if (resource->get_desc().is_buffer()) continue; - if (info.heap_type != HAL::HeapType::DEFAULT) continue; + if (!resource) continue; + // Skip only the CPU-visible heaps, which carry no layout to link. + // NOT "anything that isn't DEFAULT": RESERVED covers both tiled + // resources and natively imported ones (the swapchain arrives that + // way -- Resource::init(NativeImportHandle) never assigns a heap + // type, so it keeps the RESERVED sentinel), and those need linking + // exactly as much as a DEFAULT resource does. This mirrors the same + // choice HAL makes when deciding what to barrier-track at all + // (Transitions::add_resource_usage). + if (info.heap_type == HAL::HeapType::UPLOAD || + info.heap_type == HAL::HeapType::READBACK) continue; + if (!resource->frame_graph_managed) continue; // the group rests these itself + + // Drop passes that never actually touched the resource -- a pass + // can declare a resource and then not use it, and linking through + // it would hand state to a list that has nothing to receive it. + auto untouched = [&](Pass* pass) + { + auto& list = pass->context.list; + if (!list) return true; + return !list->get_exit_state(resource.get()).has_value(); + }; - bool nb = info.id == ResourceID::PSSM_Depths; - auto pass_checker = [&](Pass* pass) { - auto commandList = pass->context.list; - if (!commandList) - return true; + for (auto& state : info.states) + { + std::vector kept; + kept.reserve(state.passes.size()); + for (auto* pass : state.passes) + if (!untouched(pass)) kept.push_back(pass); - auto& cpu_state = info.resource->get_state_manager().get_cpu_state(commandList.get()); - if (!cpu_state.used) - return true; + if (kept.size() == state.passes.size()) continue; + state.passes.clear(); + for (auto* pass : kept) state.passes.push_back(pass); + } - return false; - }; + info.states.remove_if([](const ResourceRWState& s) { return s.passes.empty(); }); + if (info.states.empty()) continue; - auto state_checker = [](const ResourceRWState& state) { - return state.passes.empty(); - }; - // remove unused passes - for (auto& state : info.states) + // A barrier's BEFORE state has to be expressible on the queue that + // records it: a compute list cannot barrier a resource out of + // RENDER_TARGET, and asserting on that is exactly what + // Barriers::transition does. Linking is the only thing that can + // hand a list a state some OTHER queue produced, so every state + // this function hands over is checked first. + auto supported = [&](Pass* pass, const HAL::ResourceState& state) { - state.passes.erase(std::remove_if(state.passes.begin(), state.passes.end(), pass_checker), state.passes.end()); - } - info.states.remove_if(state_checker); + return HAL::IsFullySupport(pass->context.list->get_type(), state); + }; - // merge resourcestate access in a same read or write state - for (auto& state : info.states) + // The pass in a phase that actually runs LAST. Not passes.back(): + // ResourceRWState::passes is a concurrent_vector appended from + // several threads during setup, so its order is arbitrary. Using + // the back element put a phase's hand-off (and the return to rest) + // on whichever pass happened to be appended last, which for a + // multi-pass phase lands mid-frame with other passes still to + // come -- the back buffer went to PRESENT while UI_Render_1..4 + // were still writing it. call_id is the execution order. + auto last_of = [](const auto& passes) -> Pass* { - if (state.write) continue; - state.merged_read_state.subres.resize(resource->get_state_manager().get_subres_count()); - - // calculate merged state - for (auto& pass : state.passes) - { - auto commandList = pass->context.list; - auto& cpu_state = resource->get_state_manager().get_cpu_state(commandList.get()); - state.merged_read_state.merge(cpu_state); - } - - // Exclusive-read states are always singleton — nothing to merge. - if (state.exclusive) continue; + Pass* best = nullptr; + for (auto* p : passes) + if (!best || p->call_id > best->call_id) best = p; + return best; + }; - // propagate merged state through passes - for (auto& pass : state.passes) - { - auto commandList = pass->context.list; - auto& cpu_state = resource->get_state_manager().get_cpu_state(commandList.get()); - cpu_state.merge_read_state(commandList->get_type(), state.merged_read_state); - } - } + // --- 1. one shared state per read phase -------------------------- + // + // Readers of the same version can disagree on sync (a pixel-shader + // read vs a compute read). Left alone each would transition the + // resource again; declaring the union on every reader makes them + // agree, so only the first emits a barrier and the rest are no-ops. + std::vector> merged_read(info.states.size()); - bool need_first_transition = true; - // link statee between passes for (uint i = 0; i < info.states.size(); i++) { auto& state = info.states[i]; - if (!state.write) - { - - for (auto& pass : state.passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; - auto& cpu_state = resource->get_state_manager().get_cpu_state(commandList.get()); - if (!cpu_state.used) continue; + if (state.write || state.exclusive) continue; - need_first_transition = false; - break; - } - - continue; - } - ASSERT(state.passes.size() == 1); - auto pass = state.passes.front(); - auto commandList = pass->context.list; - if (!commandList) continue; - - HAL::CommandListType list_type = pass->get_type(); - - // first write synchronize with start=end - if (i == 0 && (info.is_static() || info.passed)) + std::optional merged; + for (auto* pass : state.passes) { - auto target = ResourceStates::NO_ACCESS; - auto layout = info.last_state.get_subres_state(0).layout; - target.layout = layout; - info.resource->get_state_manager().prepare_state(commandList.get(), target); - } - - - // check previous pass is read - if (i > 0 && !info.states[i - 1].write) - { - auto prev_state = info.states[i - 1]; - auto best_type = prev_state.merged_read_state.get_best_list_type(); - // its in 99% read to write compatible on all queues - ASSERT(IsCompatible(list_type, best_type)); - info.resource->get_state_manager().prepare_state(commandList.get(), prev_state.merged_read_state); - } - - - // check next pass is read - if ((i < info.states.size() - 1) && !info.states[i + 1].write) - { - auto next_state = info.states[i + 1]; - auto best_type = next_state.merged_read_state.get_best_list_type(); - // its in 99% write to read compatible on all queues - ASSERT(IsCompatible(list_type, best_type)); - info.resource->get_state_manager().prepare_after_state(commandList.get(), next_state.merged_read_state); - } - - // cur pass is write, next pass is write ,what can be wrong? - - if ((i < info.states.size() - 1) && info.states[i + 1].write) - { - auto prev_writer = info.states[i]; - auto next_writer = info.states[i + 1]; - - auto prev_pass = prev_writer.passes.front(); - auto next_pass = next_writer.passes.front(); - - auto prev_cmd = prev_pass->context.list; - auto next_cmd = next_pass->context.list; - if (!prev_cmd || !next_cmd) - { - ASSERT(false); // no write requested with no commandlist - continue; - } - - auto& prev_cpu_state = resource->get_state_manager().get_cpu_state(prev_cmd.get()); - auto& next_cpu_state = resource->get_state_manager().get_cpu_state(next_cmd.get()); - - - // TODO: try removing this write state if not used - ASSERT(prev_cpu_state.used); - ASSERT(next_cpu_state.used); - - HAL::CommandListType next_list_type = next_pass->get_type(); - HAL::CommandListType prev_list_type = prev_pass->get_type(); - - HAL::SubResourcesGPU prev_gpu_state; - prev_gpu_state.subres.resize(resource->get_state_manager().get_subres_count()); - prev_gpu_state.set_cpu_state(prev_cpu_state); - - - HAL::SubResourcesGPU next_gpu_state; - next_gpu_state.subres.resize(resource->get_state_manager().get_subres_count()); - next_gpu_state.set_cpu_state_first(next_cpu_state); - - - auto transition_best_type_layout = Merge(prev_gpu_state.get_best_list_type(), next_gpu_state.get_best_list_type()); - - - bool compatible_next_layout = IsCompatible(next_list_type, transition_best_type_layout); - bool compatible_prev_layout = IsCompatible(prev_list_type, transition_best_type_layout); - - auto transition_best_type = Merge(prev_cpu_state.get_best_list_type_last(), next_cpu_state.get_best_list_type_first()); - - bool compatible_next = IsCompatible(next_list_type, transition_best_type); - bool compatible_prev = IsCompatible(prev_list_type, transition_best_type); - - // - //info.resource->get_state_manager().connect(prev_cmd.get(), next_cmd.get()); - /* if (compatible_next && compatible_prev) - { - // do a split transition - info.resource->get_state_manager().connect(prev_cmd.get(), next_cmd.get()); - } - else*/ if (compatible_next_layout) - { - // next pass should rewrite its first state - info.resource->get_state_manager().prepare_state(next_cmd.get(), prev_gpu_state); // add sync&access info - } - else if (compatible_prev_layout) - { - - info.resource->get_state_manager().prepare_after_state(prev_cmd.get(), next_gpu_state); // add sync&access infoZ - } - else - ASSERT(false); - - + auto exit = pass->context.list->get_exit_state(resource.get()); + if (!exit) continue; + if (!merged) { merged = *exit; continue; } + if (auto both = HAL::merge_state(*merged, *exit)) merged = *both; } + if (!merged) continue; - // (Last-touch decay to the resting state now happens once, - // after the states loop — see below — so it also covers a - // read-last touch, not just write-last.) + // Readers on different queues can merge into a state one of + // them cannot record (a pixel-shader read unioned onto a + // compute list). Then there is no single state to agree on -- + // leave every reader with its own and pay the extra barrier. + bool all_can = true; + for (auto* pass : state.passes) + if (!supported(pass, *merged)) { all_can = false; break; } + if (!all_can) continue; + merged_read[i] = merged; + // Propagate it back so every reader asks for the same thing. + for (auto* pass : state.passes) + pass->context.list->add_resource_usage(resource.get(), *merged, HAL::ALL_SUBRESOURCES); } - - - - - - // Canonical resting-state contract for passed/static resources: - // at the LAST pass that touches the resource (read OR write), decay - // it back to creation_state.layout — its per-resource CanonicalRead - // (SHADER_RESOURCE for read resources, PRESENT for the swapchain). - // Within the graph they're FG-scheduled like transients; this is the - // single point where they return to a state any queue/list can pick - // up. A no-op when the last touch already left it there (merge_state - // collapses it). Must run before the end-state capture below so - // last_state carries the resting layout into next frame. - if (!info.states.empty() && (info.is_static() || info.passed)) + // --- 2. link each state to the one before it --------------------- + // + // The consumer is told where the producer left the resource. Only + // used when the consumer's group has not already tracked it, so a + // producer and consumer that land in the SAME group cost nothing + // here -- the group threads the state itself. + for (uint i = 1; i < info.states.size(); i++) { - Pass* last_pass = info.states.back().passes.back(); - auto commandList = last_pass->context.list; - if (commandList) + auto& prev = info.states[i - 1]; + auto& cur = info.states[i]; + if (prev.passes.empty() || cur.passes.empty()) continue; + + Pass* producer = last_of(prev.passes); + auto& producer_list = producer->context.list; + + // The hand-off state has to be the SAME for every pass in the + // consuming phase: they are told what they will find, and a + // phase with several readers has no ordering among them. So + // the resource is converged at the producer -- one barrier on + // the producing list, none on any reader -- rather than + // leaving the first reader to transition it and the rest to + // discover it already transitioned. + // + // The phase's merged read state is the natural target: step 1 + // already made every reader ask for exactly that. + std::optional handoff = merged_read[i]; + + // Must also be recordable on the producer's own queue -- a + // compute list cannot transition anything into a pixel-shader + // read. Resting works from and to any queue, so it is the + // fallback for every case the direct hand-off cannot express. + if (handoff && !HAL::IsFullySupport(producer_list->get_type(), *handoff)) + handoff.reset(); + + // Not a read phase, so there is no merged read state to aim + // for. Aim instead for what the consuming passes need at + // their FIRST use -- the state they would otherwise each + // transition into themselves. + // + // This has to be a state they ALL agree on. Handing them the + // producer's exit instead is only true for whichever consumer + // happens to run first; the others find it already + // transitioned. (Tried 2026-08-19: 6269 x #1334 on + // VoxelLighted, GBuffer_DepthMips, BlueNoise.) Converging the + // producer into their common first-use state costs the + // consumers no barrier at all, so no ordering among them is + // needed -- the same reason merged_read works for readers. + if (!handoff) { - auto target = ResourceStates::NO_ACCESS; - target.layout = info.creation_state.get_subres_state(0).layout; - info.resource->get_state_manager().transition(commandList.get(), target, ALL_SUBRESOURCES); - } - } - - // link end to start transition - if (!info.states.empty() && (info.is_static() || info.passed)) - { - Pass* pass = info.states.back().passes.back(); - auto commandList = pass->context.list; - info.last_state.set_cpu_state(info.resource->get_state_manager().get_cpu_state(commandList.get())); - - } + std::optional common; + bool agree = true; + for (auto* pass : cur.passes) + { + auto first = pass->context.list->get_first_use_state(resource.get()); + if (!first) continue; - } + if (!common) common = first; + else if (!(*common == *first)) { agree = false; break; } + } + if (agree && common && HAL::IsFullySupport(producer_list->get_type(), *common)) + handoff = common; + } - for (auto& pass : enabled_passes) - { - auto commandList = pass->context.list; - if (!commandList) continue; + const bool rested = !handoff; + if (rested) + handoff = HAL::state_at_rest(HAL::resting_layout(resource.get())); - for (auto info : pass->used.resource_deletions_before) - { - if (info->states.empty())continue; - auto& last_state = info->states.back(); - if (last_state.write) - { - auto prev_pass = last_state.passes.front(); - auto prev_cmd = prev_pass->context.list; - auto& prev_cpu_state = info->resource->get_state_manager().get_cpu_state(prev_cmd.get()); + producer_list->transition_to(resource.get(), *handoff); + for (auto* pass : cur.passes) + pass->context.list->set_entry_state(resource.get(), *handoff); + } - HAL::SubResourcesGPU prev_gpu_state; - prev_gpu_state.subres.resize(info->resource->get_state_manager().get_subres_count()); - prev_gpu_state.set_cpu_state(prev_cpu_state); - info->resource->get_state_manager().prepare_state(commandList.get(), prev_gpu_state); - } - else + // --- 3. rest once, at the last pass that touches it -------------- + // + // Read or write -- whichever comes last. This is the single point + // where an FG resource returns to a state any queue can pick up, + // replacing the per-group convergence that CommandListGroup skips + // for frame_graph_managed resources. + // + // Deliberately unconditional, and deliberately the resting state + // rather than whatever the last pass happened to leave behind. + // The resting layout is HAL's global contract -- every group + // enters a resource from it, and code outside the graph does too + // -- so it is the ONLY end state that stays valid no matter who + // picks the resource up next. Carrying the real end state into + // next frame instead would be strictly worse: a resource left in + // DEPTH_STENCIL_WRITE cannot be barriered out of by a compute + // queue at all, so next frame's first pass could be handed a + // before-state it has no way to express. + // + // That also means there is nothing to carry across frames: the + // next frame's first pass needs no entry state, because the + // barrier system's own resting-layout default is already right. + Pass* last_pass = last_of(info.states.back().passes); + if (auto& list = last_pass->context.list) { - info->resource->get_state_manager().prepare_state(commandList.get(), last_state.merged_read_state); - + // Resting here is correct only because last_pass is the phase's + // LAST-EXECUTING pass. Getting that wrong is not a lost + // optimisation but a correctness bug: a back buffer rested to + // PRESENT while later passes still write it makes every + // subsequent use find PRESENT where it expected a readable + // layout (#1334 "layout(COMMON) does not match expected + // (SHADER_RESOURCE)"). + list->transition_to_rest(resource.get()); } - // - - - } - } } + void TaskBuilder::create_resources() { bool delete_resources = !GetAsyncKeyState('5'); @@ -1856,7 +1845,6 @@ namespace FrameGraph { info->resource = link->carried.resource; info->alloc_ptr = link->carried.alloc_ptr; - info->last_state = link->carried.last_state; info->is_new = false; @@ -1865,8 +1853,6 @@ namespace FrameGraph { info->resource = HAL::create_resource(RenderSystem::get().device(), info->d3ddesc, info->heap_type); info->resource->frame_graph_managed = true; - info->creation_state = info->last_state = info->resource->get_state_manager().copy_gpu(); - info->last_state = TextureLayout::UNDEFINED; info->resource->set_name(info->name()); info->alloc_ptr = HAL::ResourceHandle{}; info->is_new = true; @@ -1953,8 +1939,6 @@ namespace FrameGraph if (info->is_new) { info->resource->frame_graph_managed = true; - info->creation_state = info->last_state = info->resource->get_state_manager().copy_gpu(); - info->last_state = TextureLayout::UNDEFINED; info->view = nullptr; info->resource->set_name(info->name()); @@ -2249,9 +2233,18 @@ namespace FrameGraph sync_state_with_self.reset(); render_task = std::future(); - compile_task = std::future(); - debug_commands.clear(); + // debug_commands is deliberately NOT cleared here. + // + // It is filled by the debug snapshot in commit_command_lists, at the END + // of a frame, but the debugger copies it from graph.on_compile, near the + // START of one. Clearing here emptied it between those two points, so the + // debugger only ever copied a vector this frame had not filled yet and + // the Transitions panel showed nothing. + // + // Leaving it means the debugger shows the PREVIOUS frame's transitions, + // which is what it can actually have. The snapshot assigns over it every + // frame, so nothing accumulates. fence_end = HAL::FenceWaiter(); diff --git a/sources/RenderSystem/FrameGraph/PassDefaults.cpp b/sources/RenderSystem/FrameGraph/PassDefaults.cpp index 1d2f1c20..47bd95c7 100644 --- a/sources/RenderSystem/FrameGraph/PassDefaults.cpp +++ b/sources/RenderSystem/FrameGraph/PassDefaults.cpp @@ -197,6 +197,25 @@ void PassDefault::render( compute.set(voxelScreen); } + // Debug reference mode (see RTX::debug_full_reference_shadow's own + // comment in RTX.ixx): skip the normal Bend/FFX hybrid-shadow-denoiser + // dispatch entirely and instead fire a genuine 16-ray soft-shadow + // reference directly into ShadowMask -- a ground truth to compare VSM's + // PCSS approximation against, entirely independent of VSM's own code. + // FrameInfo/SceneData/Raytracing are already bound above, same as the + // normal path needs them. + if (RTX::get().debug_full_reference_shadow) + { + Slots::RTXShadowReference reference; + gbuffer.SetTable(reference.GetGbuffer()); + reference.GetOutput() = data.ShadowMask->rwTexture2D; + compute.set(reference); + + compute.set_pipeline(); + compute.dispatch(ivec2(data.ShadowMask->get_size().x, data.ShadowMask->get_size().y), ivec2{ 16, 16 }); + return; + } + auto light = float4(sky_ctx.sunDir, 0) * camera_ctx.cam->get_view_proj(); Bend::DispatchList res = Bend::BuildDispatchList( diff --git a/sources/RenderSystem/FrameGraph/autogen/enums.h b/sources/RenderSystem/FrameGraph/autogen/enums.h index de81597e..da00df85 100644 --- a/sources/RenderSystem/FrameGraph/autogen/enums.h +++ b/sources/RenderSystem/FrameGraph/autogen/enums.h @@ -60,6 +60,8 @@ export Library::Mipmapping Mipmapping; Library::VSM_GatherDispatch VSM_GatherDispatch; Library::VSM_RenderPages VSM_RenderPages; + Library::VSM_HiZRebuild VSM_HiZRebuild; + Library::VSM_BlockerSearch VSM_BlockerSearch; Library::VSM_Combine VSM_Combine; Library::VSM_DepthAnalysis VSM_DepthAnalysis; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/CubeMapEnviromentProcessor.h b/sources/RenderSystem/FrameGraph/autogen/pass/CubeMapEnviromentProcessor.h index e688afbf..1d9ec075 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/CubeMapEnviromentProcessor.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/CubeMapEnviromentProcessor.h @@ -56,7 +56,7 @@ class CubeMapEnviromentProcessor : public PassNodeBase setup_func_type setup_func; render_func_type render_func; - const FrameGraph::PassFlags flags = FrameGraph::PassFlags::General; + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; }; } diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/CubeSky.h b/sources/RenderSystem/FrameGraph/autogen/pass/CubeSky.h index 8feee479..819abbed 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/CubeSky.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/CubeSky.h @@ -48,7 +48,7 @@ class CubeSky : public PassNodeBase setup_func_type setup_func; render_func_type render_func; - const FrameGraph::PassFlags flags = FrameGraph::PassFlags::General; + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; }; } diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/MainPipeline.pipeline.h b/sources/RenderSystem/FrameGraph/autogen/pass/MainPipeline.pipeline.h index a8b32801..e2779b99 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/MainPipeline.pipeline.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/MainPipeline.pipeline.h @@ -26,9 +26,11 @@ #include "RTXShadow.h" #include "PSSM_Combine.h" #include "VSM_DepthAnalysis.h" +#include "VSM_BlockerSearch.h" #include "VSM_Combine.h" #include "VoxelScreen.h" #include "VoxelCombine.h" +#include "VSM_HiZRebuild.h" #include "ReflCombine.h" #include "Sky.h" #include "SMAA.h" @@ -62,9 +64,11 @@ class MainPipeline : public PipelineBase Passes::ReflectionDenoiser_Reproject reflectionDenoiser_Reproject; Passes::PSSM_Combine pSSM_Combine; Passes::VSM_DepthAnalysis vSM_DepthAnalysis; + Passes::VSM_BlockerSearch vSM_BlockerSearch; Passes::VSM_Combine vSM_Combine; Passes::VoxelScreen voxelScreen; Passes::VoxelCombine voxelCombine; + Passes::VSM_HiZRebuild vSM_HiZRebuild; Passes::ReflCombine reflCombine; Passes::Sky sky; Passes::SMAA sMAA; @@ -98,9 +102,11 @@ class MainPipeline : public PipelineBase Passes::RTXShadow::Name.ptr, Passes::PSSM_Combine::Name.ptr, Passes::VSM_DepthAnalysis::Name.ptr, + Passes::VSM_BlockerSearch::Name.ptr, Passes::VSM_Combine::Name.ptr, Passes::VoxelScreen::Name.ptr, Passes::VoxelCombine::Name.ptr, + Passes::VSM_HiZRebuild::Name.ptr, Passes::ReflCombine::Name.ptr, Passes::Sky::Name.ptr, Passes::SMAA::Name.ptr, @@ -136,7 +142,6 @@ class MainPipeline : public PipelineBase L"VSM_PageTable", L"VSM_PageCameras", L"VSM_PageHiZ", - L"VSM_DirtySlots", L"GBuffer_Albedo", L"GBuffer_Normals", L"GBuffer_Depth", @@ -176,10 +181,12 @@ class MainPipeline : public PipelineBase L"ShadowMask", L"WorkGraphBuffer", L"VSM_DepthAnalysisResult", + L"VSM_BlockerResult", L"VoxelFramesCount", L"VoxelIndirectNoise", L"VoxelIndirectFiltered", L"VoxelIndirectFilteredPrev", + L"VSM_DirtySlots", L"ResultTextureNew", L"SMAA_edges", L"SMAA_blend", @@ -210,11 +217,13 @@ class MainPipeline : public PipelineBase { PassID::BlueNoise, 0 }, { PassID::ScreenReflection, 0 }, { PassID::ReflectionDenoiser_Reproject, 0 }, + { PassID::VSM_BlockerSearch, 0 }, + { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, }; static inline const FrameGraph::PrecompiledState BlueNoise_c0_states[] = { { true, { BlueNoise_c0_pass_refs + 0, 1 } }, - { false, { BlueNoise_c0_pass_refs + 1, 3 } }, + { false, { BlueNoise_c0_pass_refs + 1, 5 } }, }; static inline const FrameGraph::PassRef VoxelAlbedo_c0_pass_refs[] = { { PassID::Voxelize, 0 }, @@ -333,39 +342,39 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PassRef VSM_Atlas_c0_pass_refs[] = { { PassID::VSM_RenderPages, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, + { PassID::VSM_HiZRebuild, 0 }, }; static inline const FrameGraph::PrecompiledState VSM_Atlas_c0_states[] = { { true, { VSM_Atlas_c0_pass_refs + 0, 1 } }, - { false, { VSM_Atlas_c0_pass_refs + 1, 1 } }, + { false, { VSM_Atlas_c0_pass_refs + 1, 3 } }, }; static inline const FrameGraph::PassRef VSM_PageTable_c0_pass_refs[] = { { PassID::VSM_RenderPages, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, }; static inline const FrameGraph::PrecompiledState VSM_PageTable_c0_states[] = { { true, { VSM_PageTable_c0_pass_refs + 0, 1 } }, - { false, { VSM_PageTable_c0_pass_refs + 1, 1 } }, + { false, { VSM_PageTable_c0_pass_refs + 1, 2 } }, }; static inline const FrameGraph::PassRef VSM_PageCameras_c0_pass_refs[] = { { PassID::VSM_RenderPages, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, }; static inline const FrameGraph::PrecompiledState VSM_PageCameras_c0_states[] = { { true, { VSM_PageCameras_c0_pass_refs + 0, 1 } }, - { false, { VSM_PageCameras_c0_pass_refs + 1, 1 } }, + { false, { VSM_PageCameras_c0_pass_refs + 1, 2 } }, }; static inline const FrameGraph::PassRef VSM_PageHiZ_c0_pass_refs[] = { { PassID::VSM_RenderPages, 0 }, + { PassID::VSM_HiZRebuild, 0 }, }; static inline const FrameGraph::PrecompiledState VSM_PageHiZ_c0_states[] = { { true, { VSM_PageHiZ_c0_pass_refs + 0, 1 } }, - }; - static inline const FrameGraph::PassRef VSM_DirtySlots_c0_pass_refs[] = { - { PassID::VSM_RenderPages, 0 }, - }; - static inline const FrameGraph::PrecompiledState VSM_DirtySlots_c0_states[] = { - { true, { VSM_DirtySlots_c0_pass_refs + 0, 1 } }, + { true, { VSM_PageHiZ_c0_pass_refs + 1, 1 } }, }; static inline const FrameGraph::PassRef GBuffer_Albedo_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -374,6 +383,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -382,7 +392,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Albedo_c0_states[] = { { true, { GBuffer_Albedo_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Albedo_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_Albedo_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_Normals_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -392,6 +402,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -400,7 +411,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Normals_c0_states[] = { { true, { GBuffer_Normals_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Normals_c0_pass_refs + 1, 11 } }, + { false, { GBuffer_Normals_c0_pass_refs + 1, 12 } }, }; static inline const FrameGraph::PassRef GBuffer_Depth_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -410,6 +421,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -420,7 +432,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Depth_c0_states[] = { { true, { GBuffer_Depth_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Depth_c0_pass_refs + 1, 13 } }, + { false, { GBuffer_Depth_c0_pass_refs + 1, 14 } }, }; static inline const FrameGraph::PassRef GBuffer_Specular_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -429,6 +441,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -437,7 +450,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Specular_c0_states[] = { { true, { GBuffer_Specular_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Specular_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_Specular_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_Speed_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -447,6 +460,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -456,7 +470,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Speed_c0_states[] = { { true, { GBuffer_Speed_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Speed_c0_pass_refs + 1, 12 } }, + { false, { GBuffer_Speed_c0_pass_refs + 1, 13 } }, }; static inline const FrameGraph::PassRef GBuffer_DepthMips_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -465,6 +479,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -473,7 +488,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_DepthMips_c0_states[] = { { true, { GBuffer_DepthMips_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_DepthMips_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_DepthMips_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_Quality_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -482,6 +497,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -490,7 +506,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_Quality_c0_states[] = { { true, { GBuffer_Quality_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_Quality_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_Quality_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_TempColor_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -499,6 +515,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -506,7 +523,7 @@ class MainPipeline : public PipelineBase { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_TempColor_c0_states[] = { - { false, { GBuffer_TempColor_c0_pass_refs + 0, 11 } }, + { false, { GBuffer_TempColor_c0_pass_refs + 0, 12 } }, }; static inline const FrameGraph::PassRef GBuffer_NormalsPrev_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -516,6 +533,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -524,7 +542,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_NormalsPrev_c0_states[] = { { true, { GBuffer_NormalsPrev_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_NormalsPrev_c0_pass_refs + 1, 11 } }, + { false, { GBuffer_NormalsPrev_c0_pass_refs + 1, 12 } }, }; static inline const FrameGraph::PassRef GBuffer_SpecularPrev_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -533,6 +551,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -541,7 +560,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_SpecularPrev_c0_states[] = { { true, { GBuffer_SpecularPrev_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_SpecularPrev_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_SpecularPrev_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_DepthPrev_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -551,6 +570,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -558,7 +578,7 @@ class MainPipeline : public PipelineBase { PassID::VoxelDebug, 0 }, }; static inline const FrameGraph::PrecompiledState GBuffer_DepthPrev_c0_states[] = { - { false, { GBuffer_DepthPrev_c0_pass_refs + 0, 12 } }, + { false, { GBuffer_DepthPrev_c0_pass_refs + 0, 13 } }, }; static inline const FrameGraph::PassRef GBuffer_HiZ_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -567,6 +587,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -575,7 +596,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_HiZ_c0_states[] = { { true, { GBuffer_HiZ_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_HiZ_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_HiZ_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef GBuffer_HiZ_UAV_c0_pass_refs[] = { { PassID::Scene, 0 }, @@ -584,6 +605,7 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, { PassID::VSM_DepthAnalysis, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -592,7 +614,7 @@ class MainPipeline : public PipelineBase }; static inline const FrameGraph::PrecompiledState GBuffer_HiZ_UAV_c0_states[] = { { true, { GBuffer_HiZ_UAV_c0_pass_refs + 0, 1 } }, - { false, { GBuffer_HiZ_UAV_c0_pass_refs + 1, 10 } }, + { false, { GBuffer_HiZ_UAV_c0_pass_refs + 1, 11 } }, }; static inline const FrameGraph::PassRef sky_cubemap_c0_pass_refs[] = { { PassID::CubeSky, 0 }, @@ -791,10 +813,11 @@ class MainPipeline : public PipelineBase static inline const FrameGraph::PassRef ShadowMask_c0_pass_refs[] = { { PassID::RTXShadow, 0 }, { PassID::PSSM_Combine, 0 }, + { PassID::VSM_Combine, 0 }, }; static inline const FrameGraph::PrecompiledState ShadowMask_c0_states[] = { { true, { ShadowMask_c0_pass_refs + 0, 1 } }, - { false, { ShadowMask_c0_pass_refs + 1, 1 } }, + { false, { ShadowMask_c0_pass_refs + 1, 2 } }, }; static inline const FrameGraph::PassRef WorkGraphBuffer_c0_pass_refs[] = { { PassID::RTXShadow, 0 }, @@ -808,6 +831,14 @@ class MainPipeline : public PipelineBase static inline const FrameGraph::PrecompiledState VSM_DepthAnalysisResult_c0_states[] = { { true, { VSM_DepthAnalysisResult_c0_pass_refs + 0, 1 } }, }; + static inline const FrameGraph::PassRef VSM_BlockerResult_c0_pass_refs[] = { + { PassID::VSM_BlockerSearch, 0 }, + { PassID::VSM_Combine, 0 }, + }; + static inline const FrameGraph::PrecompiledState VSM_BlockerResult_c0_states[] = { + { true, { VSM_BlockerResult_c0_pass_refs + 0, 1 } }, + { false, { VSM_BlockerResult_c0_pass_refs + 1, 1 } }, + }; static inline const FrameGraph::PassRef VoxelFramesCount_c0_pass_refs[] = { { PassID::VoxelScreen, 0 }, { PassID::VoxelCombine, 0 }, @@ -839,6 +870,12 @@ class MainPipeline : public PipelineBase static inline const FrameGraph::PrecompiledState VoxelIndirectFilteredPrev_c0_states[] = { { false, { VoxelIndirectFilteredPrev_c0_pass_refs + 0, 2 } }, }; + static inline const FrameGraph::PassRef VSM_DirtySlots_c0_pass_refs[] = { + { PassID::VSM_HiZRebuild, 0 }, + }; + static inline const FrameGraph::PrecompiledState VSM_DirtySlots_c0_states[] = { + { true, { VSM_DirtySlots_c0_pass_refs + 0, 1 } }, + }; static inline const FrameGraph::PassRef ResultTexture_c1_pass_refs[] = { { PassID::SMAA, 0 }, { PassID::FSR, 0 }, @@ -918,7 +955,6 @@ class MainPipeline : public PipelineBase { ResourceID::VSM_PageTable, 0, VSM_PageTable_c0_states }, { ResourceID::VSM_PageCameras, 0, VSM_PageCameras_c0_states }, { ResourceID::VSM_PageHiZ, 0, VSM_PageHiZ_c0_states }, - { ResourceID::VSM_DirtySlots, 0, VSM_DirtySlots_c0_states }, { ResourceID::GBuffer_Albedo, 0, GBuffer_Albedo_c0_states }, { ResourceID::GBuffer_Normals, 0, GBuffer_Normals_c0_states }, { ResourceID::GBuffer_Depth, 0, GBuffer_Depth_c0_states }, @@ -958,10 +994,12 @@ class MainPipeline : public PipelineBase { ResourceID::ShadowMask, 0, ShadowMask_c0_states }, { ResourceID::WorkGraphBuffer, 0, WorkGraphBuffer_c0_states }, { ResourceID::VSM_DepthAnalysisResult, 0, VSM_DepthAnalysisResult_c0_states }, + { ResourceID::VSM_BlockerResult, 0, VSM_BlockerResult_c0_states }, { ResourceID::VoxelFramesCount, 0, VoxelFramesCount_c0_states }, { ResourceID::VoxelIndirectNoise, 0, VoxelIndirectNoise_c0_states }, { ResourceID::VoxelIndirectFiltered, 0, VoxelIndirectFiltered_c0_states }, { ResourceID::VoxelIndirectFilteredPrev, 0, VoxelIndirectFilteredPrev_c0_states }, + { ResourceID::VSM_DirtySlots, 0, VSM_DirtySlots_c0_states }, { ResourceID::ResultTexture, 1, ResultTexture_c1_states }, { ResourceID::SMAA_edges, 0, SMAA_edges_c0_states }, { ResourceID::SMAA_blend, 0, SMAA_blend_c0_states }, @@ -1062,10 +1100,18 @@ class MainPipeline : public PipelineBase static inline const FrameGraph::PassRef VSM_DepthAnalysis_0_prev[] = { { PassID::Scene, 0 }, }; + static inline const FrameGraph::PassRef VSM_BlockerSearch_0_prev[] = { + { PassID::BlueNoise, 0 }, + { PassID::Scene, 0 }, + { PassID::VSM_RenderPages, 0 }, + }; static inline const FrameGraph::PassRef VSM_Combine_0_prev[] = { + { PassID::BlueNoise, 0 }, { PassID::PSSM_Combine, 0 }, + { PassID::RTXShadow, 0 }, { PassID::ResultCreation, 0 }, { PassID::Scene, 0 }, + { PassID::VSM_BlockerSearch, 0 }, { PassID::VSM_RenderPages, 0 }, }; static inline const FrameGraph::PassRef VoxelScreen_0_prev[] = { @@ -1090,6 +1136,9 @@ class MainPipeline : public PipelineBase { PassID::VSM_Combine, 0 }, { PassID::VoxelScreen, 0 }, }; + static inline const FrameGraph::PassRef VSM_HiZRebuild_0_prev[] = { + { PassID::VSM_RenderPages, 0 }, + }; static inline const FrameGraph::PassRef ReflCombine_0_prev[] = { { PassID::PSSM_Combine, 0 }, { PassID::ReflectionDenoiser_Reproject, 0 }, @@ -1176,9 +1225,9 @@ class MainPipeline : public PipelineBase { PassID::VSM_GatherDispatch, 0, false, {} }, { PassID::VSM_RenderPages, 0, false, VSM_RenderPages_0_prev }, { PassID::Scene, 0, false, Scene_0_prev }, - { PassID::CubeSky, 0, false, {} }, - { PassID::CubeMapDownsample, 0, false, CubeMapDownsample_0_prev }, - { PassID::CubeMapEnviromentProcessor, 0, false, CubeMapEnviromentProcessor_0_prev }, + { PassID::CubeSky, 0, true, {} }, + { PassID::CubeMapDownsample, 0, true, CubeMapDownsample_0_prev }, + { PassID::CubeMapEnviromentProcessor, 0, true, CubeMapEnviromentProcessor_0_prev }, { PassID::Lighting, 0, true, Lighting_0_prev }, { PassID::Mipmapping, 0, true, Mipmapping_0_prev }, { PassID::stencil_renderer_before, 0, false, {} }, @@ -1189,9 +1238,11 @@ class MainPipeline : public PipelineBase { PassID::RTXShadow, 0, true, RTXShadow_0_prev }, { PassID::PSSM_Combine, 0, true, PSSM_Combine_0_prev }, { PassID::VSM_DepthAnalysis, 0, false, VSM_DepthAnalysis_0_prev }, + { PassID::VSM_BlockerSearch, 0, false, VSM_BlockerSearch_0_prev }, { PassID::VSM_Combine, 0, false, VSM_Combine_0_prev }, { PassID::VoxelScreen, 0, true, VoxelScreen_0_prev }, { PassID::VoxelCombine, 0, true, VoxelCombine_0_prev }, + { PassID::VSM_HiZRebuild, 0, true, VSM_HiZRebuild_0_prev }, { PassID::ReflCombine, 0, false, ReflCombine_0_prev }, { PassID::Sky, 0, false, Sky_0_prev }, { PassID::SMAA, 0, false, SMAA_0_prev }, @@ -1237,9 +1288,9 @@ class MainPipeline : public PipelineBase graph.add_library_pass(vSM_RenderPages.setup_func, vSM_RenderPages.render_func, (vSM_RenderPages.flags & ~FrameGraph::PassFlags::Compute)); graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags & ~FrameGraph::PassFlags::Compute)); if (cubeSky.setup_func) - graph.add_library_pass(cubeSky.setup_func, cubeSky.render_func, (cubeSky.flags & ~FrameGraph::PassFlags::Compute)); - graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags & ~FrameGraph::PassFlags::Compute)); - graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags & ~FrameGraph::PassFlags::Compute)); + graph.add_library_pass(cubeSky.setup_func, cubeSky.render_func, (cubeSky.flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); + graph.add_library_pass(PassDefault::setup, PassDefault::render, (PassDefault::flags)); if (lighting.setup_func) graph.add_library_pass(lighting.setup_func, lighting.render_func, (lighting.flags)); if (mipmapping.setup_func) @@ -1258,12 +1309,16 @@ class MainPipeline : public PipelineBase graph.add_library_pass(pSSM_Combine.setup_func, pSSM_Combine.render_func, (pSSM_Combine.flags)); if (vSM_DepthAnalysis.setup_func) graph.add_library_pass(vSM_DepthAnalysis.setup_func, vSM_DepthAnalysis.render_func, (vSM_DepthAnalysis.flags & ~FrameGraph::PassFlags::Compute)); + if (vSM_BlockerSearch.setup_func) + graph.add_library_pass(vSM_BlockerSearch.setup_func, vSM_BlockerSearch.render_func, (vSM_BlockerSearch.flags & ~FrameGraph::PassFlags::Compute)); if (vSM_Combine.setup_func) graph.add_library_pass(vSM_Combine.setup_func, vSM_Combine.render_func, (vSM_Combine.flags & ~FrameGraph::PassFlags::Compute)); if (voxelScreen.setup_func) graph.add_library_pass(voxelScreen.setup_func, voxelScreen.render_func, (voxelScreen.flags)); if (voxelCombine.setup_func) graph.add_library_pass(voxelCombine.setup_func, voxelCombine.render_func, (voxelCombine.flags)); + if (vSM_HiZRebuild.setup_func) + graph.add_library_pass(vSM_HiZRebuild.setup_func, vSM_HiZRebuild.render_func, (vSM_HiZRebuild.flags)); if (reflCombine.setup_func) graph.add_library_pass(reflCombine.setup_func, reflCombine.render_func, (reflCombine.flags & ~FrameGraph::PassFlags::Compute)); if (sky.setup_func) diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_BlockerSearch.h b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_BlockerSearch.h new file mode 100644 index 00000000..30ad721e --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_BlockerSearch.h @@ -0,0 +1,83 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +#pragma once +#include "../PassNodeBase.h" +#include "GBuffer.h" +using namespace FrameGraph; + +namespace Passes +{ + +class VSM_BlockerSearch : public PassNodeBase +{ +public: + struct Context + { + + GBuffer gbuffer; + + Handlers::Texture VSM_Atlas = ResourceID::VSM_Atlas; + + + Handlers::Texture VSM_PageTable = ResourceID::VSM_PageTable; + + Handlers::StructuredBuffer VSM_PageCameras = ResourceID::VSM_PageCameras; + + + Handlers::Texture BlueNoise = ResourceID::BlueNoise; + + + Handlers::Texture VSM_BlockerResult = ResourceID::VSM_BlockerResult; + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::GBuffer_Albedo, false }, + { ResourceID::GBuffer_Normals, false }, + { ResourceID::GBuffer_Depth, false }, + { ResourceID::GBuffer_Specular, false }, + { ResourceID::GBuffer_Speed, false }, + { ResourceID::GBuffer_DepthMips, false }, + { ResourceID::GBuffer_Quality, false }, + { ResourceID::GBuffer_TempColor, false }, + { ResourceID::GBuffer_NormalsPrev, false }, + { ResourceID::GBuffer_SpecularPrev, false }, + { ResourceID::GBuffer_DepthPrev, false }, + { ResourceID::GBuffer_HiZ, false }, + { ResourceID::GBuffer_HiZ_UAV, false }, + { ResourceID::VSM_Atlas, false }, + { ResourceID::VSM_PageTable, false }, + { ResourceID::VSM_PageCameras, false }, + { ResourceID::BlueNoise, false }, + { ResourceID::VSM_BlockerResult, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"VSM_BlockerSearch"}; + + static constexpr PassID ID = PassID::VSM_BlockerSearch; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + + setup_func_type setup_func; + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; +}; + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_Combine.h b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_Combine.h index b810f9e4..2169191b 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_Combine.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_Combine.h @@ -28,6 +28,15 @@ class VSM_Combine : public PassNodeBase Handlers::StructuredBuffer VSM_PageCameras = ResourceID::VSM_PageCameras; + Handlers::Texture BlueNoise = ResourceID::BlueNoise; + + + Handlers::Texture ShadowMask = ResourceID::ShadowMask; + + + Handlers::Texture VSM_BlockerResult = ResourceID::VSM_BlockerResult; + + Handlers::Texture ResultTexture = ResourceID::ResultTexture; // Resources this pass touches, in declaration order, each paired with @@ -50,6 +59,9 @@ class VSM_Combine : public PassNodeBase { ResourceID::VSM_Atlas, false }, { ResourceID::VSM_PageTable, false }, { ResourceID::VSM_PageCameras, false }, + { ResourceID::BlueNoise, false }, + { ResourceID::ShadowMask, false }, + { ResourceID::VSM_BlockerResult, false }, { ResourceID::ResultTexture, true }, }; static constexpr uint resource_count = std::size(resource_accesses); diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_HiZRebuild.h b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_HiZRebuild.h new file mode 100644 index 00000000..6a64fde8 --- /dev/null +++ b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_HiZRebuild.h @@ -0,0 +1,62 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +#pragma once +#include "../PassNodeBase.h" + +using namespace FrameGraph; + +namespace Passes +{ + +class VSM_HiZRebuild : public PassNodeBase +{ +public: + struct Context + { + + + Handlers::Texture VSM_Atlas = ResourceID::VSM_Atlas; + + + Handlers::Texture VSM_PageHiZ = ResourceID::VSM_PageHiZ; + + + Handlers::StructuredBuffer VSM_DirtySlots = ResourceID::VSM_DirtySlots; + + // Resources this pass touches, in declaration order, each paired with + // whether the pass writes it (own [Write], or the view usage's + // [Write] / [Write = {leaves...}] for resources inside a view group). + static inline const FrameGraph::ResourceAccess resource_accesses[] = { + { ResourceID::VSM_Atlas, false }, + { ResourceID::VSM_PageHiZ, true }, + { ResourceID::VSM_DirtySlots, true }, + }; + static constexpr uint resource_count = std::size(resource_accesses); + }; + + + std::span GetUsedResourcesList() const override + { + return std::span(Context::resource_accesses, Context::resource_count); + } + + static constexpr LiteralWStr Name{L"VSM_HiZRebuild"}; + + static constexpr PassID ID = PassID::VSM_HiZRebuild; + + + using setup_func_type = std::function; + using render_func_type = std::function; + + + setup_func_type setup_func; + render_func_type render_func; + + const FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; +}; + +} diff --git a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_RenderPages.h b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_RenderPages.h index 87b51644..49e46074 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass/VSM_RenderPages.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass/VSM_RenderPages.h @@ -31,9 +31,6 @@ class VSM_RenderPages : public PassNodeBase Handlers::StructuredBuffer VSM_DispatchCommands = ResourceID::VSM_DispatchCommands; - - Handlers::StructuredBuffer VSM_DirtySlots = ResourceID::VSM_DirtySlots; - // Resources this pass touches, in declaration order, each paired with // whether the pass writes it (own [Write], or the view usage's // [Write] / [Write = {leaves...}] for resources inside a view group). @@ -43,7 +40,6 @@ class VSM_RenderPages : public PassNodeBase { ResourceID::VSM_PageCameras, true }, { ResourceID::VSM_PageHiZ, true }, { ResourceID::VSM_DispatchCommands, false }, - { ResourceID::VSM_DirtySlots, true }, }; static constexpr uint resource_count = std::size(resource_accesses); }; diff --git a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h index ae04b468..9e00f2cd 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass_defaults.h @@ -123,7 +123,7 @@ template<> struct PassDefault { static constexpr bool enabled = true; - static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::General; + static constexpr FrameGraph::PassFlags flags = FrameGraph::PassFlags::Compute; static bool setup(Passes::CubeMapEnviromentProcessor::Context& data, FrameGraph::TaskBuilder& builder); static void render(Passes::CubeMapEnviromentProcessor::Context& data, FrameGraph::FrameContext& context); diff --git a/sources/RenderSystem/FrameGraph/autogen/pass_ids.h b/sources/RenderSystem/FrameGraph/autogen/pass_ids.h index 41440508..7d4962cd 100644 --- a/sources/RenderSystem/FrameGraph/autogen/pass_ids.h +++ b/sources/RenderSystem/FrameGraph/autogen/pass_ids.h @@ -50,6 +50,8 @@ namespace FrameGraph Mipmapping, VSM_GatherDispatch, VSM_RenderPages, + VSM_HiZRebuild, + VSM_BlockerSearch, VSM_Combine, VSM_DepthAnalysis, Count diff --git a/sources/RenderSystem/FrameGraph/autogen/passes.ixx b/sources/RenderSystem/FrameGraph/autogen/passes.ixx index dfd6c489..6b5ddccf 100644 --- a/sources/RenderSystem/FrameGraph/autogen/passes.ixx +++ b/sources/RenderSystem/FrameGraph/autogen/passes.ixx @@ -53,6 +53,8 @@ export import "../defines.h"; #include "pass/Mipmapping.h" #include "pass/VSM_GatherDispatch.h" #include "pass/VSM_RenderPages.h" +#include "pass/VSM_HiZRebuild.h" +#include "pass/VSM_BlockerSearch.h" #include "pass/VSM_Combine.h" #include "pass/VSM_DepthAnalysis.h" #include "pass/GBuffer.h" @@ -102,6 +104,8 @@ export namespace Passes using ::Passes::Mipmapping; using ::Passes::VSM_GatherDispatch; using ::Passes::VSM_RenderPages; + using ::Passes::VSM_HiZRebuild; + using ::Passes::VSM_BlockerSearch; using ::Passes::VSM_Combine; using ::Passes::VSM_DepthAnalysis; } diff --git a/sources/RenderSystem/FrameGraph/autogen/resource_ids.h b/sources/RenderSystem/FrameGraph/autogen/resource_ids.h index 879bf6ce..00e9da23 100644 --- a/sources/RenderSystem/FrameGraph/autogen/resource_ids.h +++ b/sources/RenderSystem/FrameGraph/autogen/resource_ids.h @@ -85,6 +85,7 @@ namespace FrameGraph VSM_PageCameras, VSM_PageHiZ, VSM_DirtySlots, + VSM_BlockerResult, VSM_DepthAnalysisResult, Count }; @@ -167,6 +168,7 @@ namespace FrameGraph "VSM_PageCameras", "VSM_PageHiZ", "VSM_DirtySlots", + "VSM_BlockerResult", "VSM_DepthAnalysisResult", }; auto i = (unsigned int)id; diff --git a/sources/RenderSystem/GUI/Elements/EditText.cpp b/sources/RenderSystem/GUI/Elements/EditText.cpp index 58b7368d..914d26a6 100644 --- a/sources/RenderSystem/GUI/Elements/EditText.cpp +++ b/sources/RenderSystem/GUI/Elements/EditText.cpp @@ -15,6 +15,15 @@ void GUI::Elements::edit_text::on_key_action(key_action action, long key) keys.push_back(key); } +void GUI::Elements::edit_text::set_text(const std::string& t) +{ + std::lock_guard guard(m); + text = t; + cursor_pos = (unsigned int)text.size(); + label_text->text = text; + placeholder_label->visible = text.empty(); +} + bool GUI::Elements::edit_text::on_mouse_action(mouse_action action, mouse_button button, vec2 pos) { std::lock_guard guard(m); diff --git a/sources/RenderSystem/GUI/Elements/EditText.ixx b/sources/RenderSystem/GUI/Elements/EditText.ixx index ebb1e891..bddb2c3f 100644 --- a/sources/RenderSystem/GUI/Elements/EditText.ixx +++ b/sources/RenderSystem/GUI/Elements/EditText.ixx @@ -55,6 +55,11 @@ export namespace GUI edit_text(); + // Programmatic set (e.g. seeding a bound value at construction). + // Does not fire on_change -- callers seed the initial text before + // wiring on_change, same ordering check_box_text/float_slider use. + void set_text(const std::string& t); + virtual bool on_mouse_action(mouse_action action, mouse_button button, vec2 pos) override; virtual void on_key_action(key_action action, long key) override; diff --git a/sources/RenderSystem/GUI/Elements/FlowGraph/ParameterWindow.ixx b/sources/RenderSystem/GUI/Elements/FlowGraph/ParameterWindow.ixx index 10fc1146..b8eb860c 100644 --- a/sources/RenderSystem/GUI/Elements/FlowGraph/ParameterWindow.ixx +++ b/sources/RenderSystem/GUI/Elements/FlowGraph/ParameterWindow.ixx @@ -3,6 +3,11 @@ export module GUI:FlowGraph.ParameterWindow; import :ScrollContainer; import :TabControl; import :CheckBoxText; +import :FloatSlider; +import :EditText; +import :ComboBox; +import :Label; +import :HorizontalLayout; import :FlowGraph.Canvas; @@ -19,6 +24,48 @@ export namespace GUI return label; } + // Constrained (constructed with the {min, max} overload) gets a slider; + // unconstrained has no range to hand that widget, so it gets a free-form + // numeric text box instead -- either way a Variable is editable, + // unlike the plain read-only label other not-specifically-handled types get. + inline base::ptr create_property_internal(Variable& elem) + { + auto row = std::make_shared(); + row->docking = GUI::dock::TOP; + + auto label = std::make_shared(); + label->text = elem.get_name(); + row->add_child(label); + + if (elem.has_range()) + { + auto slider = std::make_shared(); + slider->min = elem.get_min(); + slider->max = elem.get_max(); + slider->value = (float)elem; + slider->on_change = [&elem](float value) { elem = value; }; + row->add_child(slider); + } + else + { + auto edit = std::make_shared(); + edit->size = { 60, 0 }; + edit->set_text(std::to_string((float)elem)); + // Only characters a float literal can contain -- on_change still + // fires per keystroke (edit_text has no commit-on-enter), so an + // in-progress partial value like "-" or "1." is expected and just + // silently skipped below rather than applied. + edit->filter = [](char ch) { return (ch >= '0' && ch <= '9') || ch == '.' || ch == '-'; }; + edit->on_change = [&elem](const std::string& text) + { + try { elem = std::stof(text); } catch (...) {} + }; + row->add_child(edit); + } + + return row; + } + template inline base::ptr create_property_internal(T& elem) @@ -35,6 +82,36 @@ export namespace GUI { CHECK_PROPERTY(bool); + CHECK_PROPERTY(float); + + // Enums: dispatched through VariableBase's virtuals, not CHECK_PROPERTY + // -- there's no concrete enum type to dynamic_cast to here, since any + // number of distinct enum types can back a Variable. A non-enum + // Variable reports an empty name list, so this is a no-op for those. + auto enum_names = elem.get_enum_names(); + if (!enum_names.empty()) + { + auto row = std::make_shared(); + row->docking = GUI::dock::TOP; + + auto label = std::make_shared(); + label->text = elem.get_name(); + row->add_child(label); + + auto combo = std::make_shared(); + int current = elem.get_enum_index(); + for (size_t i = 0; i < enum_names.size(); i++) + { + auto item = combo->add_item(enum_names[i]); + int index = (int)i; + item->on_select = [&elem, index]() { elem.set_enum_index(index); }; + } + if (current >= 0 && current < (int)enum_names.size()) + combo->get_label()->text = enum_names[current]; + row->add_child(combo); + + return row; + } auto label = std::make_shared(); diff --git a/sources/RenderSystem/GUI/Elements/Label.cpp b/sources/RenderSystem/GUI/Elements/Label.cpp index f9ca3ed2..8f6c3a00 100644 --- a/sources/RenderSystem/GUI/Elements/Label.cpp +++ b/sources/RenderSystem/GUI/Elements/Label.cpp @@ -135,8 +135,7 @@ namespace GUI } void label::pre_draw(HAL::CommandList::ptr command_list) { -// cache.texture->resource->get_state_manager().prepare_state(command_list.get(), { HAL::BarrierSync::NONE, HAL::BarrierAccess::NO_ACCESS, HAL::TextureLayout::SHADER_RESOURCE }); - // command_list->transition(cache.texture->resource, ); + // command_list->add_resource_usage(cache.texture->resource, ); geomerty->clear(); /* @@ -256,7 +255,7 @@ for (const auto& token : parsed) { geomerty->draw(command_list, lay2, 0, { 0,0 }); MipMapGenerator::get().generate(command_list->get_compute(), cache.texture); - // command_list->transition(cache.texture->resource, { HAL::BarrierSync::NONE, HAL::BarrierAccess::NO_ACCESS, HAL::TextureLayout::SHADER_RESOURCE }); + // command_list->add_resource_usage(cache.texture->resource, { HAL::BarrierSync::NONE, HAL::BarrierAccess::NO_ACCESS, HAL::TextureLayout::SHADER_RESOURCE }); }; Fonts::FontGeometry::ptr label::get_geometry() diff --git a/sources/RenderSystem/GUI/Elements/MenuList.cpp b/sources/RenderSystem/GUI/Elements/MenuList.cpp index 1dfce901..61fb56f3 100644 --- a/sources/RenderSystem/GUI/Elements/MenuList.cpp +++ b/sources/RenderSystem/GUI/Elements/MenuList.cpp @@ -23,15 +23,21 @@ GUI::Elements::menu_list::menu_list(bool vertical) // max_size = { 0, 300 }; width_size = size_type::MATCH_CHILDREN; - height_size = size_type::MATCH_CHILDREN; + // Height is NOT MATCH_CHILDREN here: it's driven by auto_size (below), + // which reads contents->scaled_size directly. filled->clamp_to_parent + // clamps filled's height to *this* element's (possibly window-clamped, + // see clamp_to_parent below) bounds, so filled's height can't also be + // the source menu_list derives its own height from -- that'd be + // circular (filled clamped to menu_list, menu_list sized from filled) + // and get stuck at whatever tiny size happened to seed the loop. contents->width_size = size_type::MATCH_CHILDREN; contents->height_size = size_type::MATCH_CHILDREN; filled->width_size = size_type::MATCH_CHILDREN; filled->height_size = size_type::MATCH_CHILDREN; filled->docking= dock::NONE; - auto_size = true; filled->clamp_to_parent = ParentClamp::HEIGHT; + auto_size = true; filled->x_type = pos_x_type::LEFT; if (hor) base::remove_child(hor); @@ -56,15 +62,15 @@ bool GUI::Elements::menu_list::need_open_on_hover() void GUI::Elements::menu_list::make_fixed_width() { width_size = size_type::MATCH_CHILDREN; - height_size = size_type::MATCH_CHILDREN; + // See the vertical-menu constructor for why height isn't MATCH_CHILDREN here. contents->width_size = size_type::MATCH_PARENT_CHILDREN; contents->height_size = size_type::MATCH_CHILDREN; filled->width_size = size_type::MATCH_PARENT_CHILDREN; filled->height_size = size_type::MATCH_CHILDREN; - // auto_size = true; filled->clamp_to_parent = ParentClamp::HEIGHT; + auto_size = true; filled->docking = dock::NONE; filled->x_type = pos_x_type::LEFT; if (hor) base::remove_child(hor); diff --git a/sources/RenderSystem/GUI/Elements/ScrollContainer.cpp b/sources/RenderSystem/GUI/Elements/ScrollContainer.cpp index 15535a66..0aef6119 100644 --- a/sources/RenderSystem/GUI/Elements/ScrollContainer.cpp +++ b/sources/RenderSystem/GUI/Elements/ScrollContainer.cpp @@ -115,9 +115,13 @@ void GUI::Elements::scroll_container::resized() if (!allow_overflow) contents->pos = vec2::min(vec2(0, 0), vec2::max(contents->pos.get(), -contents->scaled_size.get() + vec2(filled->get_render_bounds().size))); + // contents is the only unclamped measure of the real content size, so it -- not + // filled (which clamp_to_parent shrinks to fit us) -- is what we size ourselves + // from. The read is stale on the frame the container is first laid out, but + // c_contents::on_size_changed re-enters resized() as soon as contents computes + // its size, which repairs it within the same frame. if (auto_size) - size = float2{ size->x, contents->size->y + contents->margin->top + contents->margin->bottom + padding->top + padding->bottom }; - + size = float2{ size->x, contents->scaled_size->y + contents->margin->top + contents->margin->bottom + padding->top + padding->bottom }; } void GUI::Elements::scroll_container::remove_child(base::ptr obj) @@ -155,11 +159,11 @@ GUI::Elements::scroll_container::scroll_container() filled->add_child(over_filled); vert->on_move = [this](float y) { - contents->pos = { contents->pos->x, -y* (contents->size->y - filled->get_render_bounds().h) }; + contents->pos = { contents->pos->x, -y* (contents->scaled_size->y - filled->get_render_bounds().h) }; }; hor->on_move = [this](float x) { - contents->pos = { -x* (contents->size->x - filled->get_render_bounds().w), contents->pos->y }; + contents->pos = { -x* (contents->scaled_size->x - filled->get_render_bounds().w), contents->pos->y }; }; } diff --git a/sources/RenderSystem/GUI/Elements/ScrollContainer.ixx b/sources/RenderSystem/GUI/Elements/ScrollContainer.ixx index 3dcfec99..e0812ab4 100644 --- a/sources/RenderSystem/GUI/Elements/ScrollContainer.ixx +++ b/sources/RenderSystem/GUI/Elements/ScrollContainer.ixx @@ -1,4 +1,4 @@ -export module GUI:ScrollContainer; + export module GUI:ScrollContainer; import :Base; import :ScrollBar; diff --git a/sources/RenderSystem/Helpers/MipMapGeneration.cpp b/sources/RenderSystem/Helpers/MipMapGeneration.cpp index 0facff06..655a9896 100644 --- a/sources/RenderSystem/Helpers/MipMapGeneration.cpp +++ b/sources/RenderSystem/Helpers/MipMapGeneration.cpp @@ -11,10 +11,82 @@ using namespace HAL; // generate(compute_context, tex->texture_2d()); //} +// Cube mip chain in one dispatch per 4-mip batch (the shader's structural +// limit), instead of one batch per face: every mip view spans all six slices +// and the dispatch carries the face in Z. void MipMapGenerator::generate_cube(HAL::ComputeContext& compute_context, HAL::CubeView view) { - for (int i = 0; i < 6; i++) - generate(compute_context, view.get_face(i)); + PROFILE(L"MipMapGenerator_cube"); + + compute_context.set_signature(Layouts::DefaultLayout); + + auto& desc = view.get_desc().as_texture(); + + const uint32_t slices = desc.ArraySize; + const uint32_t maps = desc.MipLevels - 1; + const auto size = view.get_size(); + + auto slice_span = [&](uint32_t mip) + { + HAL::TextureViewDesc vdesc; + vdesc.MipSlice = mip; + vdesc.MipLevels = 1; + vdesc.FirstArraySlice = 0; + vdesc.ArraySize = slices; + + return view.resource->create_view(compute_context.get_base(), vdesc); + }; + + for (uint32_t TopMip = 0; TopMip < maps;) + { + uint32_t SrcWidth = uint32_t(size.x >> TopMip); + uint32_t SrcHeight = uint32_t(size.y >> TopMip); + uint32_t DstWidth = SrcWidth >> 1; + uint32_t DstHeight = SrcHeight >> 1; + uint32_t NonPowerOfTwo = (SrcWidth & 1) | (SrcHeight & 1) << 1; + + { + PROFILE(L"set_pipeline"); + + compute_context.set_pipeline( + PSOS::MipMapping::NonPowerOfTwo(NonPowerOfTwo) + | PSOS::MipMapping::Gamma.Use(desc.Format.is_srgb()) + | PSOS::MipMapping::Slices.Use(true) + ); + } + + uint32_t AdditionalMips; + _BitScanForward((unsigned long*)&AdditionalMips, DstWidth | DstHeight); + uint32_t NumMips = 1 + (AdditionalMips > 3 ? 3 : AdditionalMips); + + if (TopMip + NumMips > maps) + NumMips = maps - TopMip; + + if (DstWidth == 0) + DstWidth = 1; + + if (DstHeight == 0) + DstHeight = 1; + + // Value-initialised: the 2D fields and the unused array mips stay zero. + Slots::MipMapping data{}; + data.GetSrcMipLevel() = TopMip; + data.GetNumMipLevels() = NumMips; + data.GetTexelSize() = { 1.0f / DstWidth, 1.0f / DstHeight }; + { + PROFILE(L"create_mip"); + for (uint32_t i = 0; i < NumMips; i++) + { + data.GetOutMipArray()[i] = slice_span(TopMip + 1 + i).rwTexture2DArray; + } + data.GetSrcMipArray() = slice_span(TopMip).texture2DArray; + } + compute_context.set(data); + + compute_context.dispatch(ivec3(DstWidth, DstHeight, slices), ivec3(8, 8, 1)); + + TopMip += NumMips; + } } void MipMapGenerator::generate(HAL::ComputeContext& compute_context, HAL::Texture2DView view) diff --git a/sources/RenderSystem/Helpers/Texture.cpp b/sources/RenderSystem/Helpers/Texture.cpp index 09f26c16..17a1f1f0 100644 --- a/sources/RenderSystem/Helpers/Texture.cpp +++ b/sources/RenderSystem/Helpers/Texture.cpp @@ -142,15 +142,9 @@ namespace HAL } } - // Strict promote: transition out of COPY_DEST into the resource's canonical - // read state on this (DIRECT) upload list, and pin the persistent resting - // state there. Every later command list then seeds it in the read state as - // a no-op and never decays it to COMMON — removing the implicit-promotion / - // #1334 path for uploaded assets. set_resting_state runs before execute so - // this list's own end-of-list decay is a no-op too. - auto desired = resource->get_state_manager().get_desired_state(); - list->transition(resource.get(), desired); - resource->get_state_manager().set_resting_state(desired.layout); + // No explicit transition out of COPY_DEST: update_texture above already + // recorded a usage, so the resource is in this list's used_resources and + // the group's return-to-rest leaves it at its resting layout. list->execute_and_wait(); init(); diff --git a/sources/RenderSystem/Lighting/PSSM.cpp b/sources/RenderSystem/Lighting/PSSM.cpp index f7728005..3853ecab 100644 --- a/sources/RenderSystem/Lighting/PSSM.cpp +++ b/sources/RenderSystem/Lighting/PSSM.cpp @@ -156,7 +156,7 @@ PSSM::PSSM() if (i == 0) // small hack to enable aliasing of entire resource { - command_list->transition((*data.PSSM_Depths).resource, ResourceStates::DEPTH_STENCIL); + command_list->add_resource_usage((*data.PSSM_Depths).resource, ResourceStates::DEPTH_STENCIL); } MeshRenderContext::ptr mesh_ctx(new MeshRenderContext()); diff --git a/sources/RenderSystem/Materials/PreviewSession.cpp b/sources/RenderSystem/Materials/PreviewSession.cpp index 0da6bf69..435d1399 100644 --- a/sources/RenderSystem/Materials/PreviewSession.cpp +++ b/sources/RenderSystem/Materials/PreviewSession.cpp @@ -207,17 +207,9 @@ void materials::MaterialPreviewSession::dispatch() material->render_preview(list->get_compute(), pso, results->texture_2d().rwTexture2DArray, ivec2(preview_resolution)); } - // This resource is only touched by ad hoc immediate lists (never the - // FrameGraph), so its tracked GPU state doesn't get kept in sync - // automatically. Explicitly transition to the desired read state and - // pin it as the resting state (same fix as Texture.cpp's upload path) - // so later lists -- the GUI's SRV read, or our own next dispatch -- - // start from an accurate assumption instead of a stale one. - { - auto desired = results->resource->get_state_manager().get_desired_state(); - list->transition(results->resource.get(), desired); - results->resource->get_state_manager().set_resting_state(desired.layout); - } + // The result is left readable without an explicit transition: the dispatch + // above already recorded a usage, so the group's return-to-rest hands it + // back at its resting layout for the GUI's SRV read. if (need_views) { diff --git a/sources/RenderSystem/Renderer/MeshRenderer.cpp b/sources/RenderSystem/Renderer/MeshRenderer.cpp index f895014f..d9470735 100644 --- a/sources/RenderSystem/Renderer/MeshRenderer.cpp +++ b/sources/RenderSystem/Renderer/MeshRenderer.cpp @@ -58,11 +58,21 @@ void mesh_renderer::render(MeshRenderContext::ptr mesh_render_context, Scene::pt { PROFILE_GPU(L"first stage"); - // The AS samples the Hi-Z pyramid (IsOccludedHiZ) through the global - // FrameInfo slot, so its read state isn't tracked per-pass -- make it - // explicit, or it is still UNORDERED_ACCESS from the last build. - if (use_meshlet_hiz_occlusion) - list.transition(gbuffer->HalfBuffer.hiZ_depth_uav.resource, HAL::ResourceStates::SHADER_RESOURCE); + // No Hi-Z read declared here, unlike stage 2 below. + // + // GBuffer_HiZ_UAV is created WITHOUT ResourceFlags::Static, so it is a + // transient: allocated (and aliased) fresh every frame. At stage 1 it + // has not been built yet this frame -- build_hiz_pyramid runs a few + // lines below -- so declaring an SRV read of it here was reading + // whatever the transient heap last held, every frame, not just the + // first. Stage 2's identical-looking hint IS valid, because stage 1 + // genuinely builds the pyramid it reads. + // + // That also means stage 1's per-meshlet occlusion test has no + // previous-frame pyramid to test against: the persistent copy is + // GBuffer_HiZ (Static), which write_to_depth fills, while MainHiZ is + // wired to this transient (main.cpp). Worth resolving separately. + // Stage 1 walks the full scene list: CPU-known count, direct dispatch. generate_boxes(mesh_render_context, scene, scene->compiledGather[(int)mesh_render_context->render_mesh], nullptr, meshes_count); @@ -82,8 +92,8 @@ void mesh_renderer::render(MeshRenderContext::ptr mesh_render_context, Scene::pt { PROFILE_GPU(L"second stage"); // Same as stage 1: stage 1 left the pyramid in UNORDERED_ACCESS. - if (use_meshlet_hiz_occlusion) - list.transition(gbuffer->HalfBuffer.hiZ_depth_uav.resource, HAL::ResourceStates::SHADER_RESOURCE); + // if (use_meshlet_hiz_occlusion) + // list.add_resource_usage(gbuffer->HalfBuffer.hiZ_depth_uav.resource, HAL::ResourceStates::SHADER_RESOURCE); // Stage 2 retests the invisible list: sized by retest_args, written by // stage 1's GatherMeshes. generate_boxes(mesh_render_context, scene, gather_invisible, &retest_args, 0); diff --git a/sources/RenderSystem/Shadows/VSM/VSM.cpp b/sources/RenderSystem/Shadows/VSM/VSM.cpp index 0119b63d..80312bc7 100644 --- a/sources/RenderSystem/Shadows/VSM/VSM.cpp +++ b/sources/RenderSystem/Shadows/VSM/VSM.cpp @@ -135,27 +135,33 @@ void VSM::attach_scene(std::shared_ptr scene) }); } -void VSM::build_slot_views(Passes::VSM_RenderPages::Context& data, int pyramid_mip_count) +void VSM::build_atlas_views() { - if (slot_views_built) - return; - - auto& storage = RenderSystem::get().device().get_static_gpu_data(); - int physical_slots = page_table.physical_page_count; + std::call_once(atlas_views_once, [this] + { + auto& storage = RenderSystem::get().device().get_static_gpu_data(); + int physical_slots = page_table.physical_page_count; - atlas_slot_views.resize(physical_slots); - for (int slot = 0; slot < physical_slots; slot++) - atlas_slot_views[slot] = HAL::Texture2DView(vsm_atlas_tex->resource, storage, - HAL::TextureViewDesc{ 0, 1, (uint)slot, 1 }); + atlas_slot_views.resize(physical_slots); + for (int slot = 0; slot < physical_slots; slot++) + atlas_slot_views[slot] = HAL::Texture2DView(vsm_atlas_tex->resource, storage, + HAL::TextureViewDesc{ 0, 1, (uint)slot, 1 }); - atlas_array_view = HAL::Texture2DView(vsm_atlas_tex->resource, storage, - HAL::TextureViewDesc{ 0, 1, 0, (uint)physical_slots }); + atlas_array_view = HAL::Texture2DView(vsm_atlas_tex->resource, storage, + HAL::TextureViewDesc{ 0, 1, 0, (uint)physical_slots }); + }); +} - page_hiz_mip_array_views.resize(pyramid_mip_count); - for (int mip = 0; mip < pyramid_mip_count; mip++) - page_hiz_mip_array_views[mip] = data.VSM_PageHiZ->create_mip(mip, storage); +void VSM::build_page_hiz_views(Passes::VSM_HiZRebuild::Context& data, int pyramid_mip_count) +{ + std::call_once(page_hiz_views_once, [this, &data, pyramid_mip_count] + { + auto& storage = RenderSystem::get().device().get_static_gpu_data(); - slot_views_built = true; + page_hiz_mip_array_views.resize(pyramid_mip_count); + for (int mip = 0; mip < pyramid_mip_count; mip++) + page_hiz_mip_array_views[mip] = data.VSM_PageHiZ->create_mip(mip, storage); + }); } void VSM::pass_data(FrameGraph::TaskBuilder& builder) @@ -177,6 +183,24 @@ void VSM::plan_frame(FrameGraph::Graph& graph) if (!scene) return; + // While a non-Final debug view is selected, UI_Render points its own + // need() at a different GBuffer/debug resource instead of ResultTexture + // (GUI/Base.cpp's debug_source) -- nothing consumes ResultTexture that + // frame, so FrameGraph's dependency-based culling drops VSM_Combine and, + // transitively, VSM_RenderPages/VSM_HiZRebuild (none of them + // [Required]). This function isn't part of that cullable pass graph -- + // it's a plain add_slot_generator callback -- so without this check it + // would keep planning (evicting, priority-stealing, dirty-tracking) + // every frame regardless, as if pages were still being rendered, while + // nothing ever actually redraws them. Switching back to Final then + // resumes rendering against a page table that drifted out from under + // the GPU's real content (confirmed live: pages visibly evicted/wrong + // on returning to Final after time spent in another debug view). + // Freezing here instead -- the page table simply holds whatever it held + // the last time this actually ran, and picks back up cleanly. + if (graph.get_context().mode != FrameGraph::DebugMode::Final) + return; + // Re-enabling after a period with the pyramid un-rebuilt: any page // redrawn while disabled has stale Hi-Z content that no longer matches // its current atlas depth. Force the same full-level invalidation a @@ -414,7 +438,7 @@ void VSM::plan_level(int level, float2 cam_pos_ls, const box& bounds_all, uint64 m_plan[level] = plan; } -VSM::VSM() +VSM::VSM() : VariableContext(L"VSM") { position = float3(200, 400, 200); @@ -509,6 +533,19 @@ VSM::VSM() // Now GPU-appended by VSM_GatherDispatch -- this pass only reads it // via exec_indirect. builder.need(data.VSM_DispatchCommands, FrameGraph::ResourceFlags::ComputeRead); + return true; + }; + + // Phase 5.17: the async-compute Hi-Z rebuild pass. need()s VSM_Atlas + // (read, establishes "runs after VSM_RenderPages' draw") and + // VSM_PageHiZ (write, for the per-frame rebuild -- VSM_RenderPages + // still owns create() for the cold-start clear, see its own setup()). + // VSM_DirtySlots moves here entirely since only this pass's dispatches + // consume it now. + m_hizrebuild_setup = [this, physical_slots](Passes::VSM_HiZRebuild::Context& data, FrameGraph::TaskBuilder& builder) -> bool + { + builder.need(data.VSM_Atlas, FrameGraph::ResourceFlags::ComputeRead); + builder.need(data.VSM_PageHiZ, FrameGraph::ResourceFlags::UnorderedAccess); // Phase 5.14: CPU-built and re-uploaded fresh every frame (like // VSM_PageCameras), sized to the whole physical slot budget -- every // dirty page occupies a distinct slot, so that's a hard upper bound @@ -618,7 +655,7 @@ VSM::VSM() graphics.set_signature(Layouts::DefaultLayout); compute.set_signature(Layouts::DefaultLayout); - build_slot_views(data, pyramid_mip_count); + build_atlas_views(); { PROFILE(L"vsm_tile_mapping"); @@ -630,10 +667,13 @@ VSM::VSM() uint3 tile_dims = atlas_tiled.get_tiles_count(); uint3 to = uint3(tile_dims.x - 1, tile_dims.y - 1, 0); - for (int slot : to_map) - atlas_tiled.load_tiles(command_list.get(), uint3(0, 0, 0), to, (uint)slot); - for (int slot : to_unmap) - atlas_tiled.zero_tiles(command_list.get(), uint3(0, 0, 0), to, (uint)slot); + // Phase 5.10: one batched UpdateTileMappings call for however + // many slots need it this frame, not one call per slot -- see + // load_tiles_batch()/zero_tiles_batch()'s own comments. + if (!to_map.empty()) + atlas_tiled.load_tiles_batch(command_list.get(), uint3(0, 0, 0), to, to_map); + if (!to_unmap.empty()) + atlas_tiled.zero_tiles_batch(command_list.get(), uint3(0, 0, 0), to, to_unmap); } } @@ -744,8 +784,14 @@ VSM::VSM() if (data.VSM_PageHiZ.is_new()) { PROFILE(L"clear_uav"); + // whole_resource: the loop clears every mip of every slice, so the + // declaration is exactly the whole resource. Declaring each + // create_mip() view separately shredded it into one entry per slice + // per mip -- all 1792 -- and the next whole-resource use then had to + // reconcile every one of them individually. for (int mip = 0; mip < pyramid_mip_count; mip++) - command_list->clear_uav(data.VSM_PageHiZ->create_mip(mip, *command_list).rwTexture2DArray, vec4(0, 0, 0, 0)); + command_list->clear_uav(data.VSM_PageHiZ->create_mip(mip, *command_list).rwTexture2DArray, + vec4(0, 0, 0, 0), /*whole_resource*/ true); } { @@ -756,8 +802,14 @@ VSM::VSM() // not about to draw into this slice yet, so binding it as the // active render target would just be wasted OM-bind/transition/ // size-bookkeeping work on top of the actual clear. + // whole_resource: the whole atlas is bound as a DSV a few lines below + // regardless, so declaring each clear over just its own slice buys + // nothing and costs a lot -- 257 per-slice declarations diverged the + // group's tracking, and the following whole-resource use then had to + // reconcile all 2048 subresources one at a time. for (int slot : dsv_clear_slots) - command_list->clear_dsv(atlas_slot_views[slot].depthStencil); + command_list->clear_dsv(atlas_slot_views[slot].depthStencil, + true, false, 0, 0, /*whole_resource*/ true); } if (!any_dirty) @@ -817,170 +869,194 @@ VSM::VSM() // old per-level CPU loop issued. graphics.exec_indirect(*data.VSM_DispatchCommands, (UINT)MaxDispatchEntries); - // Tried assert_shared_state() here (2026-08), after giving it a - // tile-mapping mask (HAL.TiledMemoryManager) specifically to fix - // VSM_Atlas's precondition problem: 2048 declared array slices, - // only physical_page_count (~256) ever tile-mapped, so the old - // "every declared subresource is used" requirement could never be - // satisfied. The mask fixed exactly that -- the DEV assert passed - // -- but the fold still produced a real, NEW D3D12 #1334 (72 - // instances, barrier layout SHADER_RESOURCE vs expected - // DEPTH_STENCIL_WRITE), confirmed live. Reverted. - // - // Root cause (this time actually traced, not just observed): once - // folded, SubResourcesCPU::slot(i) returns the single shared - // uniform_state for ANY index, including ones the fold's own - // verification never individually covered (structurally excluded - // by the mask, or simply never touched this list). But - // SubResourcesGPU::merge() -- which persists per-subresource - // layout across FRAMES, not just this list -- loops the FULL - // declared subresource count and calls slot(i) unconditionally - // (HAL.ResourceStates.cpp, SubResourcesGPU::merge). Post-fold that - // silently stamps the persistent cross-frame layout for every - // declared subresource to match uniform_state's first_usage, even - // ones that were never actually verified -- corrupting the record - // for whichever subresource the debug layer then disagrees with. - // This is NOT a mask-specific bug: it explains VSM_PageHiZ's - // earlier #1334 too, even though PageHiZ had no declared/mapped - // mismatch at all -- see [[project-assert-shared-state-vsm-pagehiz]]. - // A real fix needs merge() (or an equivalent) to stop trusting - // slot(i) for indices the fold didn't individually prove, which - // cuts against the whole point of folding (not tracking indices - // individually anymore) -- needs a real design pass, not another - // live-fire attempt. + // (Was a post-mortem on assert_shared_state / SubResourcesCPU folding. + // That whole tracking layer is gone as of the 2026-08 barrier + // rewrite, so the hazard it described no longer exists.) } + }; + + // ---- Hi-Z pyramid rebuild (Phase 5.17: async compute, see vsm.sig's + // VSM_HiZRebuild comment) ----------------------------------------------- - // Rebuild each just-redrawn page's pyramid for next time it's dirty + m_hizrebuild_render = [this, pages_per_level, pyramid_mip_count](Passes::VSM_HiZRebuild::Context& data, FrameGraph::FrameContext& context) + { + // Rebuilds each just-redrawn page's pyramid for next time it's dirty // -- skipped entirely when hiz_culling_enabled is false, not just // left unconsulted: no copy/downsample dispatches at all, so this // toggle actually measures the pyramid-maintenance cost rather than // just disabling its result. - if (hiz_culling_enabled) + if (!hiz_culling_enabled) + return; + + auto& command_list = context.get_list(); + auto& compute = command_list->get_compute(); + compute.set_signature(Layouts::DefaultLayout); + + // This pass records on its own (async-compute) thread, possibly + // concurrently with VSM_RenderPages' -- build both view caches + // from here too (each is a no-op past the first caller, thread- + // safe via std::call_once) rather than assuming VSM_RenderPages' + // render() already ran on the same thread. Confirmed the hard way: + // a plain bool guard produced a live, reproducible null-deref + // crash the first frame this pass had real work to do. + build_atlas_views(); + build_page_hiz_views(data, pyramid_mip_count); + + // Phase 5.14: collect every dirty page's physical slot into one + // flat list -- lets the whole rebuild (copy + full mip chain) + // run as pyramid_mip_count dispatches TOTAL this frame (Z + // dimension = dirty page count), instead of pyramid_mip_count + // dispatches PER dirty page. Cheap CPU-side walk (at most + // MaxLevels x pages_per_level iterations). + std::vector dirty_slots; { - // Phase 5.14: collect every dirty page's physical slot into one - // flat list -- lets the whole rebuild (copy + full mip chain) - // run as pyramid_mip_count dispatches TOTAL this frame (Z - // dimension = dirty page count), instead of pyramid_mip_count - // dispatches PER dirty page. Cheap CPU-side walk (at most - // MaxLevels x pages_per_level iterations). - std::vector dirty_slots; + PROFILE(L"vsm_hiz_collect"); + for (int level = 0; level < VSM::MaxLevels; level++) { - PROFILE(L"vsm_hiz_collect"); - for (int level = 0; level < VSM::MaxLevels; level++) - { - const LevelPlan& plan = m_plan[level]; - if (!plan.valid || plan.dirty_mask == 0) - continue; + const LevelPlan& plan = m_plan[level]; + if (!plan.valid || plan.dirty_mask == 0) + continue; - for (int local = 0; local < pages_per_level; local++) - if ((plan.dirty_mask >> local) & 1) - dirty_slots.push_back((uint32_t)plan.slots[local]); - } + for (int local = 0; local < pages_per_level; local++) + if ((plan.dirty_mask >> local) & 1) + dirty_slots.push_back((uint32_t)plan.slots[local]); } + } + + if (dirty_slots.empty()) + return; - if (!dirty_slots.empty()) + // No manual transitions here any more. VSM_Atlas (DSV -> SRV for + // the copy shader) and VSM_PageHiZ's per-mip UAV/SRV binds are + // declared [Barrier = ALL] in vsm.sig, so each bind records one + // whole-resource use instead of expanding into its + // physical_page_count-slice range -- which is exactly what the + // bare add_resource_usage() calls that used to sit here were + // faking by hand. + command_list->get_copy().update(*data.VSM_DirtySlots, 0, std::span{ dirty_slots }); + + ivec3 dispatch_size((int)page_table.page_size, (int)page_table.page_size, (int)dirty_slots.size()); + + { + PROFILE(L"vsm_hiz_copy"); + compute.set_pipeline(); { - // NOT a candidate for assert_shared_state(), still. Its - // declared array size is MaxPhysicalSlots (2048), not - // physical_page_count (256) -- originally this alone blocked - // the fold (DEV assert fired immediately on the "every - // declared subresource is used" precondition). Fixed that part - // (2026-08) with an optional tile-mapping mask on - // assert_shared_state() (HAL.TiledMemoryManager) so unmapped - // subresources are skipped rather than required -- see the - // comment after the vsm_draw block below for what happened - // next: the precondition passed, but the fold still produced a - // real, new #1334 via a separate mechanism - // (SubResourcesGPU::merge()) that isn't specific to this mask - // at all -- see [[project-assert-shared-state-vsm-pagehiz]]. - - // VSM_Atlas was just a DSV -- nudge readable for the copy shader - // below, same one-off idiom PSSM.cpp uses; the next draw into it - // transitions it back automatically via set_rtv. - command_list->transition(data.VSM_Atlas->resource, HAL::ResourceStates::SHADER_RESOURCE); - - command_list->get_copy().update(*data.VSM_DirtySlots, 0, std::span{ dirty_slots }); - - ivec3 dispatch_size((int)page_table.page_size, (int)page_table.page_size, (int)dirty_slots.size()); + // Narrowed views (atlas_array_view/page_hiz_mip_array_views, + // VSM's own members, built just above), not the base + // handlers' full-array/full-mip-chain ones. dirty_slots. + // Load(z) still picks which physical slice each Z-group + // touches. + Slots::VSMCopyPageDepthBatch copy; + copy.GetAtlas() = atlas_array_view.texture2DArray; + copy.GetDst_mip0() = page_hiz_mip_array_views[0].rwTexture2DArray; + copy.GetDirty_slots() = data.VSM_DirtySlots->structuredBuffer; + compute.set(copy); + } + compute.dispatch(dispatch_size, ivec3(8, 8, 1)); + } - { - PROFILE(L"vsm_hiz_copy"); - compute.set_pipeline(); - { - // Narrowed views (atlas_array_view/page_hiz_mip_array_views), - // not the base handlers' full-array/full-mip-chain ones -- - // originally to dodge HAL::Transitions::stop_using()'s - // teardown cost on the wide base views (thousands of times - // more than the per-slice binds it replaced). stop_using() - // has since been removed (it only fed an unread - // ResourceUsage::last_point, dead split-barrier bookkeeping), - // so that cost no longer applies, but these stay narrow as - // the correct shape for the dispatch. dirty_slots.Load(z) - // still picks which physical slice each Z-group touches. - Slots::VSMCopyPageDepthBatch copy; - copy.GetAtlas() = atlas_array_view.texture2DArray; - copy.GetDst_mip0() = page_hiz_mip_array_views[0].rwTexture2DArray; - copy.GetDirty_slots() = data.VSM_DirtySlots->structuredBuffer; - compute.set(copy); - } - compute.dispatch(dispatch_size, ivec3(8, 8, 1)); - } + { + PROFILE(L"vsm_hiz_downsample"); + compute.set_pipeline(); + for (int mip = 0; mip < pyramid_mip_count - 1; mip++) + { + int dst_size = std::max(1, page_table.page_size >> (mip + 1)); + + Slots::VSMDownsampleHiZBatch down; + // Both sides narrowed to exactly one mip (array-spanning, + // physical_page_count slices) instead of the base + // handler's whole 7-mip-chain SRV -- same narrowing + // reasoning as the copy step above, and this one runs + // pyramid_mip_count-1 times per frame, not once. + // src_mip is no longer needed for addressing (the view + // itself is already narrowed to that mip -- Load's mip + // component is always 0 relative to it) but is kept for + // parity with the entry's own bookkeeping. + down.GetSrc() = page_hiz_mip_array_views[mip].texture2DArray; + down.GetDst_mip() = page_hiz_mip_array_views[mip + 1].rwTexture2DArray; + down.GetDirty_slots() = data.VSM_DirtySlots->structuredBuffer; + down.GetSrc_mip() = (uint)mip; + compute.set(down); + + compute.dispatch(ivec3(dst_size, dst_size, (int)dirty_slots.size()), ivec3(8, 8, 1)); + } + } + }; - { - PROFILE(L"vsm_hiz_downsample"); - compute.set_pipeline(); - for (int mip = 0; mip < pyramid_mip_count - 1; mip++) - { - int dst_size = std::max(1, page_table.page_size >> (mip + 1)); - - Slots::VSMDownsampleHiZBatch down; - // Both sides narrowed to exactly one mip (array-spanning, - // physical_page_count slices) instead of the base - // handler's whole 7-mip-chain SRV -- same narrowing - // reasoning as the copy step above (stop_using() is gone, - // see that comment), and this one runs - // pyramid_mip_count-1 times per frame, not once. - // src_mip is no longer needed for addressing (the view - // itself is already narrowed to that mip -- Load's mip - // component is always 0 relative to it) but is kept for - // parity with the entry's own bookkeeping. - down.GetSrc() = page_hiz_mip_array_views[mip].texture2DArray; - down.GetDst_mip() = page_hiz_mip_array_views[mip + 1].rwTexture2DArray; - down.GetDirty_slots() = data.VSM_DirtySlots->structuredBuffer; - down.GetSrc_mip() = (uint)mip; - compute.set(down); - - compute.dispatch(ivec3(dst_size, dst_size, (int)dirty_slots.size()), ivec3(8, 8, 1)); - } - } + // ---- Blocker search (extracted from combine, see vsm.sig's ------------ + // ---- VSM_BlockerSearch PassNode comment) ------------------------------- - PROFILE(L"vsm_hiz_transition"); - // Tried collapsing this to one wide transition (covering the - // whole physical_page_count-slice last-mip range) + an - // assert_shared_state() fold -- the DEV-mode ASSERT inside - // assert_shared_state() passed (every subresource genuinely - // converged on the same tracked state), but it broke the - // once-ever cold-start clear_uav path with a real, new D3D12 - // validation error (#1334, incompatible barrier layout, - // ClearUnorderedAccessViewFloat expecting UNORDERED_ACCESS but - // finding SHADER_RESOURCE) -- 3584 instances, confirmed via a - // live run. Reverted to the known-good per-dirty-slot loop; - // the interaction between assert_shared_state()'s uniform-mode - // fold and this resource's separate once-ever cold-clear path - // isn't understood well enough yet to trust past what the - // DEV assert alone checks. - // - // Every mip but the last is later read as another mip's - // SrcMip (via the shared whole-chain SRV), so it naturally - // rests as SHADER_RESOURCE; only the coarsest mip's write is - // never followed by a read. - for (uint32_t slot : dirty_slots) - { - UINT last_mip_subres = (UINT)(pyramid_mip_count - 1) + slot * (UINT)pyramid_mip_count; - command_list->transition(data.VSM_PageHiZ->resource, HAL::ResourceStates::SHADER_RESOURCE, last_mip_subres); - } + m_blockersearch_setup = [this](Passes::VSM_BlockerSearch::Context& data, FrameGraph::TaskBuilder& builder) -> bool + { + // Only meaningful (and only ever dispatched) under penumbra mode -- + // there's no blocker search to extract otherwise. m_combine_render's + // own PSO-permutation gate already skips VSM_RTX_VERIFY/etc. the + // same way; this pass just skips existing at all. + if (!use_vsm_penumbra) + return false; + GBufferViewDesc::need(builder, data.gbuffer); + builder.need(data.VSM_Atlas, FrameGraph::ResourceFlags::ComputeRead); + builder.need(data.VSM_PageTable, FrameGraph::ResourceFlags::ComputeRead); + builder.need(data.VSM_PageCameras, FrameGraph::ResourceFlags::ComputeRead); + builder.need(data.BlueNoise, FrameGraph::ResourceFlags::ComputeRead); + auto& frame = builder.graph->get_context(); + builder.create(data.VSM_BlockerResult, + { ivec3(frame.frame_size, 0), HAL::Format::R32G32B32A32_UINT, 1, 1 }, + FrameGraph::ResourceFlags::UnorderedAccess); + return true; + }; + + m_blockersearch_render = [this](Passes::VSM_BlockerSearch::Context& data, FrameGraph::FrameContext& context) + { + GBuffer gbuffer = GBufferViewDesc::actualize(data.gbuffer); + + auto& list = *context.get_list(); + auto& compute = list.get_compute(); + + auto& caminfo = context.graph->get_context(); + auto cam = caminfo.cam; + + context.graph->set_slot(SlotID::FrameInfo, compute); + + camera light_cam = make_light_view_camera(frame_light_pos); + float2 cam_pos_ls = (float4(cam->position, 1) * light_cam.get_view()).xy; + + { + Slots::VSMLighting lighting; + gbuffer.SetTable(lighting.GetGbuffer()); + lighting.GetVsm_atlas() = data.VSM_Atlas->texture2DArray; + lighting.GetPage_table() = data.VSM_PageTable->texture2DArray; + lighting.GetPage_cameras() = data.VSM_PageCameras->structuredBuffer; + lighting.GetBlue_noise() = data.BlueNoise->texture2D; + lighting.GetBlocker_result() = data.VSM_BlockerResult->rwTexture2D; + // Result/rtx_shadow_mask stay unbound (default) -- this pass's + // own shader (CS_BLOCKER_SEARCH) never reads either field. + compute.set(lighting); + } + + { + Slots::VSMConstants constants; + constants.GetActive_min() = active_min; + constants.GetActive_max() = active_max; + constants.GetPage_size() = page_table.page_size; + constants.GetPages_per_level() = page_table.clipmap.pages_per_level; + constants.GetQuad_blocker_search() = use_vsm_stochastic_blocker_search ? 2 : (use_vsm_quad_blocker_search ? 1 : 0); + constants.GetLight_view() = light_cam.get_view(); + // rtx_dual_blur/debug_rtx_reference are resolve-only concerns + // (see m_combine_render) -- left at their zero-initialized + // default here, this pass's shader never reads them. + + for (int level = 0; level < page_table.clipmap.level_count; level++) + { + float2 origin = page_table.clipmap.grid_origin(level, cam_pos_ls); + constants.GetLevel_info()[level] = float4(origin.x, origin.y, page_table.clipmap.page_world_size(level), 0.0f); } + + compute.set(constants); } + + compute.set_pipeline(); + compute.dispatch(context.graph->get_context().frame_size, ivec2{ 16, 16 }); }; // ---- Combine lighting ------------------------------------------------ @@ -992,6 +1068,19 @@ VSM::VSM() builder.need(data.VSM_Atlas, FrameGraph::ResourceFlags::ComputeRead); builder.need(data.VSM_PageTable, FrameGraph::ResourceFlags::ComputeRead); builder.need(data.VSM_PageCameras, FrameGraph::ResourceFlags::ComputeRead); + builder.need(data.BlueNoise, FrameGraph::ResourceFlags::ComputeRead); + // RTXShadow runs unconditionally every frame on RTX-capable + // hardware, independent of PSSM/VSM -- but its own setup() can + // still return false (no RTX hardware), in which case ShadowMask + // never gets created this frame. Same defensive builder.exists() + // guard PSSM_Combine already uses for the same resource. + if (use_vsm_debug_rtx_reference && builder.exists(data.ShadowMask)) + builder.need(data.ShadowMask, FrameGraph::ResourceFlags::ComputeRead); + // Only exists when use_vsm_penumbra is on (see + // m_blockersearch_setup's own early-out) -- same defensive + // builder.exists() guard as ShadowMask above. + if (builder.exists(data.VSM_BlockerResult)) + builder.need(data.VSM_BlockerResult, FrameGraph::ResourceFlags::ComputeRead); return true; }; @@ -1017,6 +1106,14 @@ VSM::VSM() lighting.GetPage_table() = data.VSM_PageTable->texture2DArray; lighting.GetPage_cameras() = data.VSM_PageCameras->structuredBuffer; lighting.GetResult() = data.ResultTexture->rwTexture2D; + lighting.GetBlue_noise() = data.BlueNoise->texture2D; + if (data.ShadowMask) + lighting.GetRtx_shadow_mask() = data.ShadowMask->texture2D; + // Written by m_blockersearch_render above -- only exists when + // use_vsm_penumbra is on (see m_blockersearch_setup's own + // early-out and this pass's own builder.exists() guard). + if (data.VSM_BlockerResult) + lighting.GetBlocker_result() = data.VSM_BlockerResult->rwTexture2D; compute.set(lighting); } @@ -1029,8 +1126,25 @@ VSM::VSM() constants.GetActive_max() = active_max; constants.GetPage_size() = page_table.page_size; constants.GetPages_per_level() = page_table.clipmap.pages_per_level; + constants.GetRtx_dual_blur() = use_vsm_rtx_dual_blur ? 1 : 0; + // 3-way mode packed into one int (see VSM_impl.hlsl's own + // comment on search_mode): 0 = full, 1 = quad-shared, 2 = + // stochastic single tap. Stochastic takes priority if somehow + // both toggles are on -- it's the more aggressive of the two. + constants.GetQuad_blocker_search() = use_vsm_stochastic_blocker_search ? 2 : (use_vsm_quad_blocker_search ? 1 : 0); + // Only meaningful when ShadowMask actually exists this frame + // (see m_combine_setup's builder.exists() guard) -- otherwise + // rtx_shadow_mask was never bound to anything real above. + constants.GetDebug_rtx_reference() = (use_vsm_debug_rtx_reference && data.ShadowMask) ? 1 : 0; constants.GetLight_view() = light_cam.get_view(); + // Propagates into RTXShadow::render (PassDefaults.cpp) via the + // RTX singleton -- see debug_full_reference_shadow's own + // comment in RTX.ixx for why it lives there instead of a + // VSM-local flag. One-frame lag (RTXShadow already ran earlier + // this frame) is fine for a debug toggle. + RTX::get().debug_full_reference_shadow = use_vsm_debug_rtx_reference; + for (int level = 0; level < page_table.clipmap.level_count; level++) { float2 origin = page_table.clipmap.grid_origin(level, cam_pos_ls); @@ -1040,7 +1154,23 @@ VSM::VSM() compute.set(constants); } - compute.set_pipeline(); + bool rtx_capable = use_vsm_penumbra && RenderSystem::get().device().get_properties().rtx; + bool rtx_verify = rtx_capable && use_vsm_rtx_verify; + if (rtx_verify) + { + // Same binding VSM.cpp mirrors from PassDefaults.cpp's + // RTXShadow::render -- Raytracing has its own dedicated + // DefaultLayout root-signature slot, so this doesn't need a + // FrameGraph-tracked field on VSM_Combine's PassNode. + auto& scene_ctx = context.graph->get_context(); + Slots::Raytracing rtx; + rtx.GetScene() = scene_ctx.scene->raytrace_scene->get_handle(); + compute.set(rtx); + } + + compute.set_pipeline( + PSOS::VSMApplyCompute::VsmPenumbra.Use(use_vsm_penumbra) + | PSOS::VSMApplyCompute::VsmRtxVerify.Use(rtx_verify)); compute.dispatch(context.graph->get_context().frame_size, ivec2{ 16, 16 }); }; @@ -1087,7 +1217,7 @@ VSM::VSM() // No manual transitions anywhere in this pass -- update(), set(), // and read()/read_buffer() all self-transition internally // (confirmed by reading their implementations: update_buffer/ - // read_buffer both call base.transition(...) themselves; set()'s + // read_buffer both call base.add_resource_usage(...) themselves; set()'s // self-tracking is already noted elsewhere in this file, "graphics. // set() tracks the read state for this SRV bind itself"). Adding // redundant manual transitions to the same states right before each diff --git a/sources/RenderSystem/Shadows/VSM/VSM.ixx b/sources/RenderSystem/Shadows/VSM/VSM.ixx index 0e1a7d83..3ed0710a 100644 --- a/sources/RenderSystem/Shadows/VSM/VSM.ixx +++ b/sources/RenderSystem/Shadows/VSM/VSM.ixx @@ -31,7 +31,7 @@ import HAL; // (level_info[]/page-table array sizing); the actually-active contiguous // sub-range [active_min, active_max] is computed every frame (Phase 5.7) // and determines which levels actually contribute entries this frame. -export class VSM +export class VSM : public VariableContext { public: // Runtime A/B toggle for measuring Hi-Z occlusion culling's real cost: @@ -39,10 +39,63 @@ public: // bit (the AS then never consults the pyramid, same as a page with no // valid history) and m_renderpages_render skips rebuilding it entirely // -- no copy/downsample dispatches at all, not just an untested pyramid. - // Plain bool: only ever read/written from the render thread and the - // UI's on_check callback (main thread), same low-ceremony treatment as - // auto_rotate_sun in main.cpp. - bool hiz_culling_enabled = true; + Variable hiz_culling_enabled = { true, "Hi-Z culling", this }; + + // Runtime A/B toggle between the fixed single-tap 3x3 hardware-PCF grid + // (get_shadow_vsm's default path) and the PCSS-style blocker-search + + // penumbra-scaled variant (VSM_impl.hlsl, gated by VSM_PENUMBRA) -- picks + // which VSMApplyCompute PSO permutation gets bound in m_combine_render. + // Off by default: new, unvalidated shader math, kept separate from the + // known-working fixed-tap baseline until confirmed visually correct. + Variable use_vsm_penumbra = { true, "PCSS penumbra", this }; // TEMP: flip back to false after validation + + // Runtime toggle for the RTX blocker-distance verification ray (Phase + // 5.18 Part B) -- only has any effect when use_vsm_penumbra is also on + // and the device supports RTX (both checked in m_combine_render before + // selecting the VsmRtxVerify PSO permutation). Off by default: new, + // unvalidated, same cautious rollout as use_vsm_penumbra above. + Variable use_vsm_rtx_verify = { false, "RTX blocker verify", this }; + + // Only meaningful when use_vsm_rtx_verify is also on. false = single + // blur pass (uses the RTX-verified distance when the ray hit something, + // VSM's own estimate otherwise -- cheaper). true = blur both distances + // and take min() of the two resulting shadow values -- pricier (an + // extra 16-tap blur whenever the ray hits) but confirmed visually to + // remove the bright spots a single blended estimate left between + // overlapping penumbras. Defaults to the confirmed-better option since + // this is a quality/perf choice, not an unvalidated-math gate like the + // two toggles above. + Variable use_vsm_rtx_dual_blur = { true, "RTX verify: dual blur + min()", this }; + + // Runtime A/B switch for the quad-shared blocker search (splits the 16 + // Poisson-disc taps 4-per-thread across each 2x2 pixel quad instead of + // every pixel doing all 16, merged via QuadReadAcrossX/Y/Diagonal). + // Off by default -- new, not yet visually/perf verified against the + // original full-per-pixel search, same cautious rollout as + // use_vsm_rtx_verify above. + Variable use_vsm_quad_blocker_search = { false, "Quad-shared blocker search", this }; + + // Third search mode (mutually exclusive with quad-sharing above, takes + // priority if somehow both are on): each pixel samples exactly ONE of + // the 16 Poisson-disc positions, picked by a fresh per-pixel random + // index every frame instead of a fixed subset -- 1/16th the atlas + // samples of the original search. Relies on neighboring pixels' + // different random picks (spatial) plus the per-frame reroll + // (temporal) for vsm_pcf_shadow's own blur to reconstruct a coherent + // result -- no dedicated denoiser backing this, so expect more visible + // noise than quad-sharing, worst on a static/paused frame. Off by + // default: new, unvalidated, same cautious rollout as the toggles + // above. + Variable use_vsm_stochastic_blocker_search = { false, "Stochastic 1-tap blocker search", this }; + + // Debug-view toggle: when on, VSM_Combine displays RTXShadow's own + // (denoised) full-RT shadow mask directly as grayscale, in place of + // VSM's normal lit output, for real geometry pixels -- a reference to + // compare VSM's quality/performance against. RTXShadow already runs + // unconditionally every frame on RTX-capable hardware (see + // PassDefaults.cpp), independent of PSSM/VSM, so no extra pass wiring + // is needed beyond VSM_Combine reading its output. + Variable use_vsm_debug_rtx_reference = { false, "Debug: RTX reference shadow mask", this }; private: // Tracks the previous frame's toggle state so plan_frame() can detect // an off->on transition and force a full pyramid rebuild -- see its @@ -243,7 +296,17 @@ private: // VSM_Atlas. Built on VSM_RenderPages' first real render() // (data.VSM_PageHiZ doesn't exist before its own setup() runs, so this // can't happen at VSM construction time); see build_slot_views(). - bool slot_views_built = false; + // Phase 5.17: VSM_RenderPages (direct queue) and VSM_HiZRebuild (async + // compute queue) now record on genuinely different threads -- the GPU- + // level dependency via VSM_Atlas guarantees the *GPU work* orders + // correctly, but says nothing about which pass's render() callback the + // CPU records FIRST. A plain bool "built" flag raced (confirmed live: + // a null-deref crash the very first frame VSM_HiZRebuild had real work, + // landing right where it first touched atlas_array_view -- built by + // VSM_RenderPages' render(), which this assumed always ran first). + // std::call_once makes "build lazily, exactly once, safe from either + // thread" actually true instead of assumed. + std::once_flag atlas_views_once; std::vector atlas_slot_views; // [slot], DSV clear only // Phase 5.14: batched Hi-Z copy source, narrowed to physical_page_count @@ -252,24 +315,41 @@ private: // to dodge HAL::Transitions::stop_using()'s O(subresources in view) x // O(total resource subresource count) teardown cost (~2ms/call on the // wide view); stop_using() has since been removed entirely (it only fed - // an unread ResourceUsage::last_point, a dead split-barrier mechanism), + // a dead split-barrier mechanism), // so that specific cost no longer applies, but the narrower binding is // still the right shape for this dispatch. See build_slot_views(). HAL::Texture2DView atlas_array_view; // Phase 5.14: the Hi-Z pyramid rebuild is batched across every dirty // page at once (one dispatch per mip level, not one per dirty page per - // mip -- see m_renderpages_render). Array-spanning (every physical - // slot at once) but narrowed to exactly ONE mip per entry -- both SRV - // and UAV -- instead of VSM_PageHiZ's base handler view, which spans - // the WHOLE mip chain (pyramid_mip_count x physical_page_count - // subresources). Same stop_using()-avoidance reasoning as - // atlas_array_view above (see that comment); stop_using() is gone now, - // but this stayed narrow since it's rebound on every one of the - // pyramid_mip_count-1 downsample dispatches, not just once. + // mip). Array-spanning (every physical slot at once) but narrowed to + // exactly ONE mip per entry -- both SRV and UAV -- instead of + // VSM_PageHiZ's base handler view, which spans the WHOLE mip chain + // (pyramid_mip_count x physical_page_count subresources). Same + // stop_using()-avoidance reasoning as atlas_array_view above (see that + // comment); stop_using() is gone now, but this stayed narrow since it's + // rebound on every one of the pyramid_mip_count-1 downsample dispatches, + // not just once. Phase 5.17: only VSM_HiZRebuild's render() ever touches + // this now (the whole rebuild moved there), so it doesn't need the + // cross-pass synchronization atlas_views_once exists for -- but it's + // still built lazily via its own once_flag for the same "exactly once" + // guarantee, cheap insurance against a future second caller. + std::once_flag page_hiz_views_once; std::vector page_hiz_mip_array_views; // [mip] - void build_slot_views(Passes::VSM_RenderPages::Context& data, int pyramid_mip_count); + // Builds atlas_slot_views/atlas_array_view -- needs only vsm_atlas_tex, + // no pass Context, so it's callable identically from either + // VSM_RenderPages' or VSM_HiZRebuild's render() (both need + // atlas_array_view; only VSM_RenderPages needs atlas_slot_views for its + // per-dirty-page DSV clears). Thread-safe via atlas_views_once -- see + // its declaration for why that's required, not just tidy. + void build_atlas_views(); + + // Builds page_hiz_mip_array_views from VSM_HiZRebuild's OWN Context + // (that PassNode need()s VSM_PageHiZ too, alongside VSM_RenderPages, + // which still create()s it for the once-ever cold-start clear -- see + // vsm.sig's VSM_HiZRebuild comment). + void build_page_hiz_views(Passes::VSM_HiZRebuild::Context& data, int pyramid_mip_count); Passes::VSM_GatherDispatch::setup_func_type m_gatherdispatch_setup; Passes::VSM_GatherDispatch::render_func_type m_gatherdispatch_render; @@ -277,6 +357,22 @@ private: Passes::VSM_RenderPages::setup_func_type m_renderpages_setup; Passes::VSM_RenderPages::render_func_type m_renderpages_render; + // Phase 5.17: split off VSM_RenderPages so the per-frame Hi-Z pyramid + // rebuild (copy + downsample dispatches) runs on the async compute + // queue instead of serializing into VSM_RenderPages' own direct-queue + // pass -- nothing else this frame reads VSM_PageHiZ, only next frame's + // draw does. See vsm.sig's VSM_HiZRebuild PassNode comment. + Passes::VSM_HiZRebuild::setup_func_type m_hizrebuild_setup; + Passes::VSM_HiZRebuild::render_func_type m_hizrebuild_render; + + // Blocker-search extraction: one full-screen dispatch running ONLY the + // wide, many-tap PCSS blocker search, writing its result for + // m_combine_render to read back -- see vsm.sig's VSM_BlockerSearch + // PassNode comment. Registered ahead of VSM_Combine in test.sig's + // pipeline listing. + Passes::VSM_BlockerSearch::setup_func_type m_blockersearch_setup; + Passes::VSM_BlockerSearch::render_func_type m_blockersearch_render; + Passes::VSM_Combine::setup_func_type m_combine_setup; Passes::VSM_Combine::render_func_type m_combine_render; @@ -322,6 +418,12 @@ public: pipeline.vSM_RenderPages.setup_func = m_renderpages_setup; pipeline.vSM_RenderPages.render_func = m_renderpages_render; + pipeline.vSM_HiZRebuild.setup_func = m_hizrebuild_setup; + pipeline.vSM_HiZRebuild.render_func = m_hizrebuild_render; + + pipeline.vSM_BlockerSearch.setup_func = m_blockersearch_setup; + pipeline.vSM_BlockerSearch.render_func = m_blockersearch_render; + pipeline.vSM_Combine.setup_func = m_combine_setup; pipeline.vSM_Combine.render_func = m_combine_render; diff --git a/sources/RenderSystem/Shadows/VSM/VSMPageTable.ixx b/sources/RenderSystem/Shadows/VSM/VSMPageTable.ixx index 3bd6995b..d80203c6 100644 --- a/sources/RenderSystem/Shadows/VSM/VSMPageTable.ixx +++ b/sources/RenderSystem/Shadows/VSM/VSMPageTable.ixx @@ -119,7 +119,8 @@ export resident.erase(it); slot_owner[slot] = VSMPageKey{ -1, 0, 0 }; free_list.push_back(slot); - pending_unmap_slots.push_back(slot); + // Phase 5.10: stays physically tile-mapped -- see ever_mapped's + // own comment. Ordinary eviction is not a reason to unmap. } // The other reclaim path: a page that has scrolled entirely off a @@ -154,7 +155,9 @@ export int slot = it->second; slot_owner[slot] = VSMPageKey{ -1, 0, 0 }; free_list.push_back(slot); - pending_unmap_slots.push_back(slot); + // Phase 5.10: stays physically tile-mapped -- see ever_mapped's + // own comment. Recentering a page out of range is not a reason + // to unmap either. it = resident.erase(it); } } @@ -198,6 +201,19 @@ export std::vector last_used_frame; std::vector pending_map_slots; std::vector pending_unmap_slots; + // Phase 5.10: true once a slot has ever been tile-mapped. Ordinary + // eviction/reallocation churn (a page scrolls out, another needs a + // slot the same or next frame) no longer unmaps -- D3D12 tile + // mapping is oblivious to logical page identity, only the render + // content differs between the old and new owner, and drawing into + // an already-mapped slice is already free. So a slot only needs a + // REAL map call the first time it's ever used; every reuse after + // that is just a new owner + a redraw (which dirty_mask/ + // newly_allocated_mask already force). Real reclaim of long-idle + // mapped slots (if VRAM pressure ever needs it) stays a separate, + // deliberately unbuilt future policy -- see vsm-future-phases.md's + // Phase 5.10 Part C -- not something this flag tries to do. + std::vector ever_mapped; void ensure_capacity() { @@ -207,6 +223,7 @@ export resident.clear(); slot_owner.assign(physical_page_count, VSMPageKey{ -1, 0, 0 }); last_used_frame.assign(physical_page_count, 0); + ever_mapped.assign(physical_page_count, false); free_list.clear(); free_list.reserve(physical_page_count); for (int i = physical_page_count - 1; i >= 0; i--) @@ -222,7 +239,16 @@ export { int slot = free_list.back(); free_list.pop_back(); - pending_map_slots.push_back(slot); + // Phase 5.10: only a genuinely first-ever use of this slot + // needs a real tile-mapping call -- a slot reused after an + // ordinary eviction was never unmapped (see evict_page()/ + // evict_outside_range()), so mapping it again would be a + // redundant GPU call for memory that's already backed. + if (!ever_mapped[slot]) + { + pending_map_slots.push_back(slot); + ever_mapped[slot] = true; + } return slot; } diff --git a/sources/SIGParser/sigs/MipMapping.sig b/sources/SIGParser/sigs/MipMapping.sig index 78eec443..0828563b 100644 --- a/sources/SIGParser/sigs/MipMapping.sig +++ b/sources/SIGParser/sigs/MipMapping.sig @@ -14,6 +14,12 @@ struct MipMapping Texture2D SrcMip; + # ARRAY_SLICES variant: the same four output mips and source viewed as + # arrays, so one dispatch covers every slice (cube face = dispatch Z) + # instead of one dispatch per slice. + RWTexture2DArray OutMipArray[4]; + + Texture2DArray SrcMipArray; } @@ -75,6 +81,10 @@ ComputePSO MipMapping [rename = CONVERT_TO_SRGB] [CS] define Gamma; + + [rename = ARRAY_SLICES] + [CS] + define Slices; } diff --git a/sources/SIGParser/sigs/defaultlayout.sig b/sources/SIGParser/sigs/defaultlayout.sig index 87b3031e..9ceeb0fc 100644 --- a/sources/SIGParser/sigs/defaultlayout.sig +++ b/sources/SIGParser/sigs/defaultlayout.sig @@ -10,6 +10,7 @@ layout FrameLayout { Sampler linearClampSampler = SamplerLinearClampDesc; Sampler anisoBordeSampler = SamplerAnisoBorderDesc; Sampler pointBorderSampler = SamplerPointBorderDesc; + Sampler vsmShadowSampler = SamplerShadowComparisonDesc; } layout DefaultLayout: FrameLayout diff --git a/sources/SIGParser/sigs/raytracing.sig b/sources/SIGParser/sigs/raytracing.sig index ad085ffa..7fe6f24a 100644 --- a/sources/SIGParser/sigs/raytracing.sig +++ b/sources/SIGParser/sigs/raytracing.sig @@ -236,6 +236,28 @@ PassNode RTXShadow [Write] ByteAdressBuffer WorkGraphBuffer; } +# Debug reference mode for RTXShadow (see RTX::debug_full_reference_shadow +# in RTX.ixx): a genuine 16-ray soft-shadow computation, entirely separate +# from the Bend/FFX hybrid-shadow-denoiser dispatch RTXShadow normally runs +# -- ground truth to compare VSM's own PCSS approximation against. Bound at +# the same Instance2 slot the Bend path's own DispatchParameters (SS_Shadow. +# sig) uses, since RTXShadow::render() only ever binds one or the other per +# frame, never both at once. +[Bind = DefaultLayout::Instance2] +struct RTXShadowReference +{ + GBuffer gbuffer; + RWTexture2D output; +} + +ComputePSO RTXShadowReferenceCompute +{ + root = DefaultLayout; + + [EntryPoint = CS_REFERENCE] + compute = RTXShadowReference; +} + [Static] [Compute] PassNode RTXColorPass diff --git a/sources/SIGParser/sigs/sky.sig b/sources/SIGParser/sigs/sky.sig index 845cacf6..b3f2ca21 100644 --- a/sources/SIGParser/sigs/sky.sig +++ b/sources/SIGParser/sigs/sky.sig @@ -15,16 +15,23 @@ struct SkyData [Bind = DefaultLayout::Instance1] struct SkyFace { - uint face; + # All six cube faces as one array UAV, so the bake is a single + # (w, h, 6) dispatch with the face taken from the dispatch Z. + RWTexture2DArray faces; } [Bind = DefaultLayout::Instance1] struct EnvFilter { - uint4 face; - float4 scaler; + # .x = edge length of the source cubemap (drives the solid-angle -> source + # mip estimate), .y = number of output mips packed into the dispatch, + # .z = edge length of output mip 0. uint4 size; + + # One array UAV per output mip. The specular pass walks all of them from a + # single flattened dispatch; the diffuse pass writes [0] only. + RWTexture2DArray targets[8]; } @@ -58,48 +65,28 @@ ComputePSO SkyCompute } -GraphicsPSO SkyCube +ComputePSO SkyCube { root = DefaultLayout; - [EntryPoint = VS_Cube] - vertex = sky; - - [EntryPoint = PS_Cube] - pixel = sky; - - rtv = { R11G11B10_FLOAT }; + [EntryPoint = CS_Cube] + compute = sky; } -GraphicsPSO CubemapENV +ComputePSO CubemapENV { root = DefaultLayout; - [EntryPoint = VS] - vertex = cubemap_down; - - [EntryPoint = PS] - pixel = cubemap_down; - - [rename = NumSamples, indirect] - [PS] - define Level = {1,8,32,64,128}; - - - rtv = { R11G11B10_FLOAT }; + [EntryPoint = CS] + compute = cubemap_down; } -GraphicsPSO CubemapENVDiffuse +ComputePSO CubemapENVDiffuse { root = DefaultLayout; - [EntryPoint = VS] - vertex = cubemap_down; - - [EntryPoint = PS_Diffuse] - pixel = cubemap_down; - - rtv = { R11G11B10_FLOAT }; + [EntryPoint = CS_Diffuse] + compute = cubemap_down; } @@ -112,6 +99,7 @@ PassNode Sky } +[Compute] PassNode CubeSky { [Write] TextureCube sky_cubemap; @@ -128,6 +116,7 @@ PassNode CubeMapDownsample } [Static] +[Compute] PassNode CubeMapEnviromentProcessor { TextureCube sky_cubemap; diff --git a/sources/SIGParser/sigs/test.sig b/sources/SIGParser/sigs/test.sig index 39637d3e..bcdc2a57 100644 --- a/sources/SIGParser/sigs/test.sig +++ b/sources/SIGParser/sigs/test.sig @@ -23,14 +23,15 @@ Pipeline MainPipeline VSM_GatherDispatch; VSM_RenderPages; + # shadow (generate_global) Scene; # sky setup (sky.generate) - CubeSky; - CubeMapDownsample; - CubeMapEnviromentProcessor; + [Async]CubeSky; + [Async]CubeMapDownsample; + [Async]CubeMapEnviromentProcessor; # voxel lighting (generate_light) [Async] @@ -53,13 +54,14 @@ Pipeline MainPipeline RTXShadow; [Async]PSSM_Combine; VSM_DepthAnalysis; + VSM_BlockerSearch; VSM_Combine; # voxel screen (voxel_gi.generate) [Async] VoxelScreen; [Async] VoxelCombine; - + [Async]VSM_HiZRebuild; ReflCombine; # sky + post diff --git a/sources/SIGParser/sigs/vsm.sig b/sources/SIGParser/sigs/vsm.sig index b755373d..d3c898ae 100644 --- a/sources/SIGParser/sigs/vsm.sig +++ b/sources/SIGParser/sigs/vsm.sig @@ -22,6 +22,26 @@ struct VSMConstants int active_max; int page_size; int pages_per_level; + # 0 = single blur pass (uses the RTX-verified distance when the + # verification ray hit something, VSM's own estimate otherwise -- cheaper, + # one 16-tap blur per pixel). 1 = blur BOTH distances and take min() of + # the two resulting shadow values (pricier -- an extra 16-tap blur + # whenever the ray hits -- but avoids the bright spots a single blended + # estimate produced between overlapping penumbras). Only read when + # VsmRtxVerify is enabled. + int rtx_dual_blur; + # 0 = every pixel runs its own full 16-tap blocker search (original). + # 1 = split the 16 taps 4-per-thread across each 2x2 pixel quad, merged + # via QuadReadAcrossX/Y/Diagonal -- ~4x fewer atlas samples per pixel for + # the search, same total 16-tap coverage. New/unverified, hence a + # runtime A/B switch rather than replacing the original outright. + int quad_blocker_search; + # Debug view: when nonzero, VSM_Combine ignores get_shadow_vsm entirely + # for real geometry pixels and instead displays RTXShadow's own + # (denoised) full-RT shadow mask directly as grayscale -- a reference + # to compare VSM's quality/performance against. See VSMLighting's + # rtx_shadow_mask field. + int debug_rtx_reference; float4x4 light_view; # MaxLevels (VSM.ixx) storage slots -- one geometric ladder, no # regular/adaptive split (see VSMClipmap::page_world_size). Keep this in @@ -73,8 +93,12 @@ ComputePSO VSMCopyPageDepth [Bind = DefaultLayout::Instance0] struct VSMCopyPageDepthBatch { - Texture2DArray atlas; - RWTexture2DArray dst_mip0; + # atlas / dst_mip0 are NARROWED views (one mip, physical_page_count array + # slices). Without [Barrier = ALL] each bind expands into one barrier per + # slice; the whole resource is being rewritten by this step anyway, so one + # whole-resource transition is both correct and far cheaper. + [Barrier = ALL] Texture2DArray atlas; + [Barrier = ALL] RWTexture2DArray dst_mip0; StructuredBuffer dirty_slots; } @@ -96,8 +120,11 @@ ComputePSO VSMCopyPageDepthBatch [Bind = DefaultLayout::Instance0] struct VSMDownsampleHiZBatch { - Texture2DArray src; - RWTexture2DArray dst_mip; + # Both sides are narrowed to exactly one mip across physical_page_count + # slices, and this runs once per mip per frame -- the single worst + # subresource-expansion site in the frame. See VSMCopyPageDepthBatch. + [Barrier = ALL] Texture2DArray src; + [Barrier = ALL] RWTexture2DArray dst_mip; StructuredBuffer dirty_slots; uint src_mip; } @@ -137,6 +164,36 @@ struct VSMLighting Texture2DArray page_table; StructuredBuffer page_cameras; RWTexture2D result; + # Pre-baked screen-space noise (see BlueNoise.sig) -- rotates the PCSS + # Poisson disc per pixel (VSM_impl.hlsl's get_shadow_vsm) so the fixed + # 16-tap pattern doesn't read as a rigid, repeating grid at wide radii. + # A plain field on the already-Instance2-bound VSMLighting rather than + # re-binding the whole BlueNoise struct (which wants Instance0, already + # taken here by VSMConstants) -- same pattern VoxelGI/ReflectionDenoiser + # already use for consuming this same baked texture. + Texture2D blue_noise; + # Reference-comparison debug view: RTXShadow's own (denoised) full-RT + # shadow mask, same resource PSSM_Combine reads as an alternative to its + # own cascade shadow maps. RTXShadow runs unconditionally every frame on + # RTX-capable hardware regardless of which of PSSM/VSM is the active + # shadow system, so this is available for VSM to sample too -- see + # VSM.cpp's m_combine_setup for the builder.exists() guard (RTXShadow's + # own setup() can return false on non-RTX hardware, in which case this + # resource never gets created that frame). + Texture2D rtx_shadow_mask; + # Blocker-search extraction: written by the new VSM_BlockerSearch pass + # (one full-screen dispatch, same size as VSM_Combine's), read back here + # by VSM_Combine's resolve step -- the wide, many-tap search no longer + # runs inline inside the same dispatch as the final PCF blur/shading. + # uint4, not float4: x = asuint(world_delta), or asuint(-1.0) as the + # sentinel for "no blocker found" (world_delta is otherwise always >=0 + # by construction); y/z = asuint(best_tc.x)/asuint(best_tc.y); w = + # best_slot directly (already a uint). Bit-reinterpreted rather than + # stored as native floats so best_slot doesn't lose precision the way + # it would packed into a half-float channel. RWTexture2D (not a plain + # Texture2D SRV) because VSM_BlockerSearch's own shader writes it -- + # VSM_Combine only ever reads it, via the same field/binding. + RWTexture2D blocker_result; } ComputePSO VSMApplyCompute @@ -145,6 +202,37 @@ ComputePSO VSMApplyCompute [EntryPoint = CS_RESULT] compute = VSM; + + # Fixed single-tap 3x3 hardware-PCF (off) vs blocker-search + penumbra- + # scaled PCF (on) -- see get_shadow_vsm in VSM_impl.hlsl. + [rename = VSM_PENUMBRA] + [CS, nullable] + define VsmPenumbra; + + # Only meaningful together with VsmPenumbra (VSM.cpp only ever enables + # this when penumbra is also on): once VSM's blocker search finds a + # blocker, fires one RayQuery toward the sun to verify/correct its + # distance against the real BVH -- shadow maps only record the front- + # most surface per texel, so a closer blocker can exist without ever + # being rasterized where the search looked. Needs RTX hardware; gated + # at runtime in VSM.cpp, not just by this define, since VSM must keep + # working correctly without it. + [rename = VSM_RTX_VERIFY] + [CS, nullable] + define VsmRtxVerify; +} + +# Blocker-search extraction (see VSM_BlockerSearch's own PassNode comment): +# no VsmPenumbra/VsmRtxVerify defines needed here -- VSM.cpp only ever runs +# this pass at all when penumbra mode is on (there's no blocker search +# without it), and RTX ray-firing happens entirely in the resolve step +# (VSMApplyCompute), not here. +ComputePSO VSMBlockerSearchCompute +{ + root = DefaultLayout; + + [EntryPoint = CS_BLOCKER_SEARCH] + compute = VSM_BlockerSearch; } # Amplification-shader-driven compaction (Phase 1b): CPU dispatches AS @@ -168,6 +256,16 @@ GraphicsPSO VSMDepthDraw amplification = mesh_shader_vsm; ds = D32_FLOAT; + # Back to cull=Front (render only back faces -- avoids self-shadow acne + # "for free" by using the far side of closed geometry as the recorded + # blocker depth). Was briefly cull=None to fix single-sided/thin + # geometry (leaves, cards, thin walls) casting no shadow at all with no + # back face to rasterize -- but that traded a real, if narrow, bug for + # a much messier one: cull=None reintroduces self-shadow acne on ALL + # front-facing geometry, and compensating for it (normal-offset bias, + # self-shadow dead zones, etc.) chased artifacts (gray penumbras, page- + # edge flicker) for longer than the original single-sided-geometry gap + # was worth. Accepting the narrower, well-understood limitation again. cull = Front; } @@ -260,14 +358,56 @@ PassNode VSM_RenderPages [Write] Texture VSM_Atlas; [Write] Texture VSM_PageTable; [Write] StructuredBuffer VSM_PageCameras; + # Still [Write] and still created here (not in VSM_HiZRebuild below): + # the once-ever cold-start clear runs in this pass's render(), before + # the draw reads it for occlusion, so data.VSM_PageHiZ.is_new() has to + # be queryable on THIS pass's own handler -- matching the precedent + # that is_new() is not valid on a handler that never create()'d/need()'d + # the resource in that pass. VSM_HiZRebuild need()s the same resource + # for the actual per-frame rebuild writes. [Write] Texture VSM_PageHiZ; StructuredBuffer VSM_DispatchCommands; +} + +# Phase 5.17: Hi-Z pyramid rebuild, split into its own async-compute pass. +# Nothing this frame reads VSM_PageHiZ after VSM_RenderPages' own draw -- +# the mesh shader's occlusion test only ever consults whatever pyramid +# content was left by the LAST time a page was rebuilt (a page's own +# last-rendered depth is always at least one frame stale by design; see +# the invalidation tracker's job of covering for that). So this pass has +# nothing forcing it to finish before the rest of the frame's direct-queue +# work: it only needs to run after VSM_RenderPages' draw (reads VSM_Atlas, +# which that pass just wrote) and before NEXT frame's VSM_RenderPages draw +# (which reads VSM_PageHiZ). [Compute] puts it on the async compute queue +# instead of serializing it into the direct queue's own critical path. +[Compute] +PassNode VSM_HiZRebuild +{ + Texture VSM_Atlas; + [Write] Texture VSM_PageHiZ; # Phase 5.14: this frame's flat list of dirty physical slots, CPU-built # and uploaded once, consumed by the batched Hi-Z copy/downsample - # dispatches (VSMCopyPageDepthBatch/VSMDownsampleHiZBatch). + # dispatches (VSMCopyPageDepthBatch/VSMDownsampleHiZBatch). Moved here + # from VSM_RenderPages along with the rest of the per-frame rebuild. [Write] StructuredBuffer VSM_DirtySlots; } +# Blocker-search extraction: one full-screen dispatch (same size as +# VSM_Combine's) that runs ONLY the wide, many-tap PCSS blocker search +# (VSM_impl.hlsl's vsm_search_blocker), writing its result to +# VSM_BlockerResult for VSM_Combine's resolve step to read back -- no +# longer inline inside the same dispatch as the final PCF blur/shading. +[Compute] +PassNode VSM_BlockerSearch +{ + GBuffer gbuffer; + Texture VSM_Atlas; + Texture VSM_PageTable; + StructuredBuffer VSM_PageCameras; + Texture BlueNoise; + [Write] Texture VSM_BlockerResult; +} + [Compute] PassNode VSM_Combine { @@ -275,6 +415,16 @@ PassNode VSM_Combine Texture VSM_Atlas; Texture VSM_PageTable; StructuredBuffer VSM_PageCameras; + Texture BlueNoise; + # Same resource RTXShadow writes / PSSM_Combine reads -- see + # VSMLighting's rtx_shadow_mask field for the full rationale. Not + # [Write]: this pass only ever reads it, for the debug-view comparison + # toggle (VSM.ixx's use_vsm_debug_rtx_reference). + Texture ShadowMask; + # Written by VSM_BlockerSearch above -- see VSMLighting's own + # blocker_result field for the packed uint4 layout. Not [Write]: this + # pass only ever reads it. + Texture VSM_BlockerResult; [Write] Texture ResultTexture; } diff --git a/sources/SIGParser/templates/cpp/table.jinja b/sources/SIGParser/templates/cpp/table.jinja index 2f81ebf5..2e6122bb 100644 --- a/sources/SIGParser/templates/cpp/table.jinja +++ b/sources/SIGParser/templates/cpp/table.jinja @@ -69,9 +69,16 @@ export namespace Table {%- endfor -%} +{#- [Barrier = ALL] -> compile_whole(): the bind records a whole-resource use + instead of the view's own mip/array range. Same layout, same offsets -- + only the barrier scope differs. -#} {%- macro compile_func(type) -%} {%- for v in (table.values|unique|selectattr('value_type','==', type)) %} +{%- if v.options.Barrier is defined and v.options.Barrier.value_atom.expr == 'ALL' %} + compiler.compile_whole({{v.name}}); +{%- else %} compiler.compile({{v.name}}); +{%- endif -%} {%- endfor -%} {%- endmacro -%} diff --git a/sources/SIGParser/templates/hlsl/layout.jinja b/sources/SIGParser/templates/hlsl/layout.jinja index db8ea762..953c647f 100644 --- a/sources/SIGParser/templates/hlsl/layout.jinja +++ b/sources/SIGParser/templates/hlsl/layout.jinja @@ -11,8 +11,13 @@ #include "{{layout.parent.name}}.h" {% endif -%} -{%- for s in layout.samplers %} +{%- for key in layout.samplers %} +{%- set s = layout.samplers[key] %} +{%- if s.expr == "SamplerShadowComparisonDesc" %} +SamplerComparisonState {{s.name}}:register(s{{loop.index0}}); +{%- else %} SamplerState {{s.name}}:register(s{{loop.index0}}); +{%- endif %} {%- endfor-%} {%- if layout.parent is undefined %} diff --git a/sources/Spectrum/main.cpp b/sources/Spectrum/main.cpp index dc79f25b..deef52e7 100644 --- a/sources/Spectrum/main.cpp +++ b/sources/Spectrum/main.cpp @@ -111,7 +111,17 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari // VoxelGI::ptr voxel_renderer; GUI::Elements::circle_selector::ptr sun_direction_circle; - bool auto_rotate_sun = false; + Variable auto_rotate_sun = { false, "Auto rotate sun", this }; + + // Grouping contexts for the mesh_renderer instances constructed below -- + // mesh_renderer always names itself "mesh_renderer" (see MeshRenderer.cpp), + // so without these every instance across the whole app would land as an + // identically-named sibling directly under global, with no way to tell + // them apart in the Properties tree. Declared as members (not locals in + // the constructor body) so they stay alive for the whole app, same as the + // mesh_renderer instances parented under them. + VariableContext main_view_forward_context{ L"Main View / Forward" }; + VariableContext main_view_gpu_context{ L"Main View / GPU" }; float sun_rotation_angle = 0; static constexpr float sun_rotation_speed = 0.2f; // radians/sec @@ -143,6 +153,17 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari , vsm(pipeline) #endif { + // Pushed for the rest of this constructor: any VariableContext-derived + // member constructed from here on (stenciler, voxel_gi, mesh_renderer + // via register_renderer, ...) nests under "triangle_drawer" instead of + // landing as a flat sibling of it under global, with no per-member + // wiring needed at each construction site. Doesn't cover vsm/ + // main_view_*_context below -- those are member-initializer-list + // entries (or plain default members) that already finished + // constructing before this body even starts, so they need the + // explicit add_child re-parent instead. + VariableContext::Scope self_scope(*this); + // PSSM stays fully wired either way -- PSSM_Global's global_depth/global_camera // are consumed unconditionally elsewhere in the pipeline (e.g. reflections), // independent of whether PSSM's own cascade+combine shadow result is used. @@ -157,6 +178,19 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari pipeline.pSSM_Combine.render_func = nullptr; #endif + // vsm/main_view_*_context construct before this body runs (regular data + // members), so by the ctor-body-start rule they land under whatever was + // on the Scope stack at that point -- nothing, here, so they'd otherwise + // sit as flat top-level siblings of "triangle_drawer" instead of nested + // under it. VariableContext::add_child re-parents them explicitly (it + // already detaches from the current parent first -- see tree::add_child + // -- so this is safe even though vsm etc. are already attached to + // global); qualified because triangle_drawer also inherits GUI::base's + // unrelated add_child(GUI::base::ptr), which unqualified lookup prefers. + VariableContext::add_child(&main_view_forward_context); + VariableContext::add_child(&main_view_gpu_context); + VariableContext::add_child(&vsm); + texture.mul_color = { 1, 1, 1, 0 }; texture.add_color = { 0, 0, 0, 1 }; // SMAA used to bake this in (pow(x, 1/2.2)) when it ran; now applied @@ -173,12 +207,19 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari scene->name = L"Scene"; scene_renderer = std::make_shared(); - scene_renderer->register_renderer(meshes_renderer = std::make_shared()); - + { + // Distinguishes this mesh_renderer's Properties-tree entry from the + // otherwise-identically-named ones below and in AssetRenderer.cpp. + VariableContext::Scope scope(main_view_forward_context); + scene_renderer->register_renderer(meshes_renderer = std::make_shared()); + } gpu_scene_renderer = std::make_shared(); - gpu_scene_renderer->register_renderer(std::make_shared()); + { + VariableContext::Scope scope(main_view_gpu_context); + gpu_scene_renderer->register_renderer(std::make_shared()); + } //gpu_scene_renderer->register_renderer(gpu_meshes_renderer_static = std::make_shared(scene, MESH_TYPE::STATIC)); @@ -234,22 +275,9 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari sun_direction_circle->set_value({ 1, 0 }); - auto auto_rotate_row = std::make_shared(); - auto_rotate_row->docking = GUI::dock::TOP; - auto_rotate_row->x_type = GUI::pos_x_type::RIGHT; - auto_rotate_row->get_label()->text = "Auto rotate sun"; - auto_rotate_row->get_check()->set_checked(auto_rotate_sun); - auto_rotate_row->on_check = [this](bool v) { auto_rotate_sun = v; }; - base::add_child(auto_rotate_row); - - auto hiz_row = std::make_shared(); - hiz_row->docking = GUI::dock::TOP; - hiz_row->x_type = GUI::pos_x_type::RIGHT; - hiz_row->get_label()->text = "VSM Hi-Z culling"; - hiz_row->get_check()->set_checked(vsm.hiz_culling_enabled); - hiz_row->on_check = [this](bool v) { vsm.hiz_culling_enabled = v; }; - base::add_child(hiz_row); - + // auto_rotate_sun and VSM's toggles are Variables now -- they show + // up automatically in the Properties debug panel (Properties -> triangle_drawer + // / VSM) instead of needing a hand-wired check_box_text row here per toggle. MeshAsset::ptr asset_ptr = EngineAssets::material_tester.get_asset(); @@ -406,6 +434,8 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari auto& vp = graph.get_context(); + g_upscaling_enabled = downsampled; + if (downsampled) { // Use DLSS's own recommended render resolution for the current @@ -434,6 +464,25 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari vp.upscale_size = size; + // Edge-triggered: logs only when the actual computed size driving + // GBuffer_HiZ/GBuffer_HiZ_UAV's size/8 dimensions changes, whatever + // the cause (window resize, DLSS mode switch, scale override drag) -- + // for correlating against a suspected resize/DLSS-triggered Hi-Z + // content bug. Remove once that's tracked down. + static ivec2 last_logged_frame_size = { -1, -1 }; + static ivec2 last_logged_upscale_size = { -1, -1 }; + if (vp.frame_size != last_logged_frame_size || vp.upscale_size != last_logged_upscale_size) + { + Log::get() << "[Viewport] frame_size " << last_logged_frame_size.x << "x" << last_logged_frame_size.y + << " -> " << vp.frame_size.x << "x" << vp.frame_size.y + << ", upscale_size " << last_logged_upscale_size.x << "x" << last_logged_upscale_size.y + << " -> " << vp.upscale_size.x << "x" << vp.upscale_size.y + << ", dlss_mode=" << (int)g_upscaling_dlss_mode + << ", scale_override=" << g_upscaling_dlss_scale_override << Log::endl; + last_logged_frame_size = vp.frame_size; + last_logged_upscale_size = vp.upscale_size; + } + sceneinfo.scene = scene; sceneinfo.renderer = gpu_scene_renderer; caminfo.cam = &cam; @@ -446,10 +495,13 @@ class triangle_drawer : public GUI::Elements::image, public GraphGenerator, Vari if (nvidia::Streamline::get().available()) nvidia::Streamline::get().begin_frame(); - // Jitter only matters when something accumulates it temporally (DLSS); - // FSR1/native have no such accumulation. + // Jitter only matters when something accumulates it temporally (DLSS) + // AND DLSS is actually the pass running this frame — g_upscaling_enabled + // off (downsampled toggled off) or DLSS unsupported both mean nothing + // consumes the jitter, so applying it would just add visible instability + // for no benefit. FSR1/native have no such accumulation either way. vec2 jitter_px(0, 0); - if (nvidia::DLSS::get().available()) + if (g_upscaling_enabled && nvidia::DLSS::get().available()) { // Halton(2,3); phase count per NVIDIA's guidance: 8*(display/render)^2. const float scale_x = float(vp.upscale_size.x) / float(vp.frame_size.x); @@ -1307,8 +1359,23 @@ class GraphRender : public Window, public GUI::user_interface GUI::Elements::dock_base::ptr docker; + // Doesn't make GraphRender itself derive VariableContext (it already + // inherits GUI::base's add_child(GUI::base::ptr) via GUI::user_interface; + // adding a second, unrelated add_child from VariableContext would make + // every existing unqualified add_child(...) call elsewhere in this class + // ambiguous) -- a plain owned context, same as AssetRenderer::preview_context. + std::unique_ptr graph_context = VariableContext::create(L"GraphRender"); + GraphRender() { + // graph is a plain member, already fully constructed (default Graph()) + // by the time this body runs, so it needs this explicit re-parent + // rather than a Scope wrapping its construction. Without it, it lands + // as a flat "Graph" sibling under global instead of nested here -- and + // since Graph's own VariableContext ctor always uses the fixed literal + // name "Graph" (see FrameGraph.cpp), it's otherwise indistinguishable + // from AssetRenderer/SceneTextureRenderer's own Graph members. + graph_context->add_child(&graph); //scale = 1.25f; Window::input_handler = this; @@ -1424,8 +1491,14 @@ class GraphRender : public Window, public GUI::user_interface continue; auto mode = o.mode; + auto mode_name = std::string(o.name); dlss_combo->add_item(o.name)->on_select = - [mode]() { g_upscaling_dlss_mode = mode; }; + [mode, mode_name]() + { + Log::get() << "[DLSS] mode changed " << (int)g_upscaling_dlss_mode + << " -> " << (int)mode << " (" << mode_name << ")" << Log::endl; + g_upscaling_dlss_mode = mode; + }; if (mode == g_upscaling_dlss_mode) dlss_combo->get_label()->text = o.name; } @@ -1467,6 +1540,8 @@ class GraphRender : public Window, public GUI::user_interface scale_slider->on_change = [update_scale_label](float v) { + Log::get() << "[DLSS] scale override changed " << g_upscaling_dlss_scale_override + << " -> " << v << Log::endl; g_upscaling_dlss_scale_override = v; update_scale_label(); }; @@ -1478,6 +1553,8 @@ class GraphRender : public Window, public GUI::user_interface recommended_but->size = { 140, 22 }; recommended_but->on_click = [scale_slider, update_scale_label](GUI::Elements::button::ptr) { + Log::get() << "[DLSS] scale override reset to Recommended (was " + << g_upscaling_dlss_scale_override << ")" << Log::endl; g_upscaling_dlss_scale_override = -1.0f; // Move the slider handle to reflect where "recommended" @@ -1809,6 +1886,8 @@ class GraphRender : public Window, public GUI::user_interface } void on_resize(vec2 size) override { + Log::get() << "[Resize] window resize requested " << size.x << "x" << size.y + << " (clamped from previous new_size " << new_size.x << "x" << new_size.y << ")" << Log::endl; new_size = vec2::max(size, vec2{ 64, 64 }); /*bool was_alive = alive; diff --git a/sources/Test/Tests/Test.HAL.SubresRangeMap.ixx b/sources/Test/Tests/Test.HAL.SubresRangeMap.ixx new file mode 100644 index 00000000..0d2095d5 --- /dev/null +++ b/sources/Test/Tests/Test.HAL.SubresRangeMap.ixx @@ -0,0 +1,229 @@ +export module Test.HAL.SubresRangeMap; + +#define TEST_MODULE_ID SubresRangeMap + +export import Test.Framework; + +import stl.core; +import HAL; + +// SubresRangeMap stores per-subresource state as RECTANGLES in +// (mip x slice x plane) space, which means every read and write goes through +// 3D rectangle subtraction. That is silent when wrong -- a dropped or +// double-counted piece only surfaces much later as a D3D12 barrier with an +// impossible before-state. +// +// So every test here checks the range map against a BRUTE-FORCE reference that +// stores one state per flat subresource. The two must agree for every +// subresource, always. The reference is obviously correct and obviously slow; +// the range map is neither. +export namespace Test +{ + using HAL::SubresRange; + using HAL::SubresRangeMap; + using HAL::ResourceState; + + namespace detail + { + // Distinct, easily-compared states. Only identity matters here. + ResourceState st(unsigned id) + { + return ResourceState{ HAL::BarrierSync::ALL, HAL::BarrierAccess::SHADER_RESOURCE, + static_cast(1u << (id % 8)) }; + } + + struct Reference + { + unsigned mips, slices, planes; + std::vector state_id; // -1 == untouched + + Reference(unsigned m, unsigned s, unsigned p) : mips(m), slices(s), planes(p), + state_id(size_t(m) * s * p, -1) {} + + size_t index(unsigned mip, unsigned sl, unsigned pl) const + { + return (size_t(pl) * slices + sl) * mips + mip; + } + + void assign(const SubresRange& r, int id) + { + const unsigned m0 = r.is_all() ? 0 : r.first_mip; + const unsigned m1 = r.is_all() ? mips : r.first_mip + r.num_mips; + const unsigned s0 = r.is_all() ? 0 : r.first_slice; + const unsigned s1 = r.is_all() ? slices : r.first_slice + r.num_slices; + const unsigned p0 = r.is_all() ? 0 : r.first_plane; + const unsigned p1 = r.is_all() ? planes : r.first_plane + r.num_planes; + + for (unsigned p = p0; p < p1; p++) + for (unsigned s = s0; s < s1; s++) + for (unsigned m = m0; m < m1; m++) + state_id[index(m, s, p)] = id; + } + }; + + // Read every subresource back out of the map and compare to the + // reference. Also verifies each subresource is reported EXACTLY once -- + // the failure mode disjointness bugs produce. + bool agrees(const SubresRangeMap& map, const Reference& ref, + const std::vector& states) + { + std::vector seen(ref.state_id.size(), -2); + std::vector hits(ref.state_id.size(), 0); + + map.visit(SubresRange::all(), [&](SubresRange piece, const ResourceState* s) + { + // A piece covering the whole resource is reported as the + // whole-resource form, whose first/num fields are the sentinel + // rather than bounds -- expand it against the resource instead. + const unsigned p0 = piece.is_all() ? 0 : piece.first_plane; + const unsigned p1 = piece.is_all() ? ref.planes : piece.first_plane + piece.num_planes; + const unsigned s0 = piece.is_all() ? 0 : piece.first_slice; + const unsigned s1 = piece.is_all() ? ref.slices : piece.first_slice + piece.num_slices; + const unsigned m0 = piece.is_all() ? 0 : piece.first_mip; + const unsigned m1 = piece.is_all() ? ref.mips : piece.first_mip + piece.num_mips; + + for (unsigned p = p0; p < p1; p++) + for (unsigned sl = s0; sl < s1; sl++) + for (unsigned m = m0; m < m1; m++) + { + const size_t i = ref.index(m, sl, p); + if (i >= seen.size()) return; + hits[i]++; + + int id = -1; + if (s) + for (unsigned k = 0; k < states.size(); k++) + if (states[k] == *s) { id = int(k); break; } + seen[i] = id; + } + }); + + for (size_t i = 0; i < seen.size(); i++) + { + if (hits[i] != 1) return false; // missing or double-reported + if (seen[i] != ref.state_id[i]) return false; // wrong state + } + return true; + } + } + + using namespace detail; + + TEST(SubresRangeMap, UniformStaysOneEntry) + { + SubresRangeMap map; + map.reset(7, 256, 1); + + map.assign(SubresRange::all(), st(0)); + + ASSERT_TRUE(map.is_uniform()); + ASSERT_EQ(map.size(), size_t(1)); + } + + TEST(SubresRangeMap, WholeMipAcrossSlicesIsOneEntry) + { + SubresRangeMap map; + map.reset(7, 256, 1); + + // The shape this whole structure exists for: one mip, every slice. + map.assign(SubresRange{ 3, 1, 0, 256, 0, 1 }, st(1)); + + ASSERT_EQ(map.get_entries().size(), size_t(1)); + ASSERT_TRUE(map.check_disjoint()); + } + + TEST(SubresRangeMap, OverlappingAssignsStayDisjoint) + { + SubresRangeMap map; + map.reset(4, 8, 1); + + map.assign(SubresRange::all(), st(0)); + map.assign(SubresRange{ 1, 2, 2, 4, 0, 1 }, st(1)); + map.assign(SubresRange{ 0, 3, 3, 2, 0, 1 }, st(2)); // straddles the previous + map.assign(SubresRange{ 2, 1, 0, 8, 0, 1 }, st(3)); + + ASSERT_TRUE(map.check_disjoint()); + } + + TEST(SubresRangeMap, MatchesReferenceOnOverlappingWrites) + { + const unsigned MIPS = 4, SLICES = 8, PLANES = 1; + + SubresRangeMap map; + map.reset(MIPS, SLICES, PLANES); + Reference ref(MIPS, SLICES, PLANES); + + std::vector states; + for (unsigned i = 0; i < 6; i++) states.push_back(st(i)); + + const SubresRange writes[] = { + SubresRange::all(), + SubresRange{ 1, 2, 2, 4, 0, 1 }, + SubresRange{ 0, 1, 0, 8, 0, 1 }, // whole mip 0 + SubresRange{ 3, 1, 7, 1, 0, 1 }, // single subresource + SubresRange{ 0, 4, 3, 1, 0, 1 }, // whole slice 3 + }; + + for (unsigned i = 0; i < std::size(writes); i++) + { + map.assign(writes[i], states[i]); + ref.assign(writes[i], int(i)); + + ASSERT_TRUE(map.check_disjoint()); + ASSERT_TRUE(agrees(map, ref, states)); + } + } + + TEST(SubresRangeMap, MatchesReferenceUnderRandomWrites) + { + const unsigned MIPS = 5, SLICES = 6, PLANES = 2; + + SubresRangeMap map; + map.reset(MIPS, SLICES, PLANES); + Reference ref(MIPS, SLICES, PLANES); + + std::vector states; + for (unsigned i = 0; i < 8; i++) states.push_back(st(i)); + + // Deterministic pseudo-random: a fixed sequence so a failure is + // reproducible rather than a once-seen flake. + unsigned seed = 12345; + auto rnd = [&](unsigned n) { seed = seed * 1664525u + 1013904223u; return (seed >> 16) % n; }; + + for (unsigned iter = 0; iter < 200; iter++) + { + const unsigned m0 = rnd(MIPS), m1 = m0 + 1 + rnd(MIPS - m0); + const unsigned s0 = rnd(SLICES), s1 = s0 + 1 + rnd(SLICES - s0); + const unsigned p0 = rnd(PLANES), p1 = p0 + 1 + rnd(PLANES - p0); + + const SubresRange r{ m0, m1 - m0, s0, s1 - s0, p0, p1 - p0 }; + const unsigned id = rnd(unsigned(states.size())); + + map.assign(r, states[id]); + ref.assign(r, int(id)); + + ASSERT_TRUE(map.check_disjoint()); + ASSERT_TRUE(agrees(map, ref, states)); + } + } + + TEST(SubresRangeMap, UntouchedReportsNoState) + { + SubresRangeMap map; + map.reset(2, 2, 1); + + map.assign(SubresRange{ 0, 1, 0, 1, 0, 1 }, st(1)); + + unsigned with_state = 0, without_state = 0; + map.visit(SubresRange::all(), [&](SubresRange piece, const HAL::ResourceState* s) + { + const unsigned n = piece.is_all() ? 2u * 2u * 1u + : piece.num_mips * piece.num_slices * piece.num_planes; + (s ? with_state : without_state) += n; + }); + + // One subresource written, three never touched and no uniform set. + ASSERT_EQ(with_state, unsigned(1)); + ASSERT_EQ(without_state, unsigned(3)); + } +} diff --git a/sources/Test/main.cpp b/sources/Test/main.cpp index 45803ac5..329976a0 100644 --- a/sources/Test/main.cpp +++ b/sources/Test/main.cpp @@ -8,6 +8,7 @@ import Test.Events; import Test.FileSystem; import Test.Profiling; import Test.HAL; +import Test.HAL.SubresRangeMap; import Core; void SetupLogging() diff --git a/workdir/assert.txt.before b/workdir/assert.txt.before deleted file mode 100644 index 544ee1a4..00000000 --- a/workdir/assert.txt.before +++ /dev/null @@ -1,33 +0,0 @@ -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (!h.texture2D.is_valid() || h.texture2D.is_written()) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.HLSL.ixx:278 -ASSERT FAILED: (exists(result)) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.Base.ixx:879 -ASSERT FAILED: (declared_write == actual_write) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\RenderSystem\FrameGraph\FrameGraph.cpp:876 -ASSERT FAILED: (subres[i].used) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:218 -ASSERT FAILED: (subres[i].used) at C:\Users\Bohdan\Documents\GitHub\Spectrum\sources\HAL\HAL.ResourceStates.cpp:218 diff --git a/workdir/cdb_output.txt b/workdir/cdb_output.txt deleted file mode 100644 index 21ba21e5..00000000 --- a/workdir/cdb_output.txt +++ /dev/null @@ -1,473 +0,0 @@ - -Microsoft (R) Windows Debugger Version 10.0.22621.755 AMD64 -Copyright (c) Microsoft Corporation. All rights reserved. - -CommandLine: ./../bin/debug/spectrum.exe - -************* Path validation summary ************** -Response Time (ms) Location -Deferred srv* -Symbol search path is: srv* -Executable search path is: -ModLoad: 00007ff6`0e450000 00007ff6`0eda7000 spectrum.exe -ModLoad: 00007ffd`16460000 00007ffd`166c6000 ntdll.dll -ModLoad: 00007ffd`15360000 00007ffd`15429000 C:\WINDOWS\System32\KERNEL32.DLL -ModLoad: 00007ffd`13730000 00007ffd`13b2e000 C:\WINDOWS\System32\KERNELBASE.dll -ModLoad: 00007ffd`14190000 00007ffd`14357000 C:\WINDOWS\System32\USER32.dll -ModLoad: 00007ffd`13d80000 00007ffd`13da7000 C:\WINDOWS\System32\win32u.dll -ModLoad: 00007ffd`102e0000 00007ffd`1041e000 C:\WINDOWS\SYSTEM32\dxgi.dll -ModLoad: 00007ffc`d3680000 00007ffc`d36a3000 C:\WINDOWS\SYSTEM32\d3d12.dll -ModLoad: 00007ffd`15320000 00007ffd`1534b000 C:\WINDOWS\System32\GDI32.dll -ModLoad: 000001e7`c8690000 000001e7`c8733000 C:\WINDOWS\System32\msvcp_win.dll -ModLoad: 00007ffd`13680000 00007ffd`13723000 C:\WINDOWS\System32\msvcp_win.dll -ModLoad: 00007ffd`13fd0000 00007ffd`140fa000 C:\WINDOWS\System32\gdi32full.dll -ModLoad: 00007ffd`13db0000 00007ffd`13efc000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 000001e7`c8690000 000001e7`c87dc000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 00007ffd`16320000 00007ffd`16417000 C:\WINDOWS\System32\shcore.dll -ModLoad: 00007ffd`14910000 00007ffd`14aa9000 C:\WINDOWS\System32\ole32.dll -ModLoad: 00007ffd`13500000 00007ffd`1355e000 C:\WINDOWS\SYSTEM32\powrprof.dll -ModLoad: 00007ffd`14580000 00007ffd`14903000 C:\WINDOWS\System32\combase.dll -ModLoad: 00007ffd`16200000 00007ffd`16318000 C:\WINDOWS\System32\RPCRT4.dll -ModLoad: 00007ffd`15e20000 00007ffd`15f12000 C:\WINDOWS\System32\COMDLG32.dll -ModLoad: 00007ffd`156d0000 00007ffd`15737000 C:\WINDOWS\System32\SHLWAPI.dll -ModLoad: 00007ffd`14ac0000 00007ffd`15252000 C:\WINDOWS\System32\SHELL32.dll -ModLoad: 00007ffc`ff260000 00007ffc`ff315000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.26100.8328_none_87eede957a2ccddd\COMCTL32.dll -ModLoad: 00007ffd`15260000 00007ffd`15317000 C:\WINDOWS\System32\ADVAPI32.dll -ModLoad: 00007ffd`16120000 00007ffd`161c9000 C:\WINDOWS\System32\msvcrt.dll -ModLoad: 00007ffc`84aa0000 00007ffc`84feb000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\assimp-vc143-mt.dll -ModLoad: 00007ffc`cbf10000 00007ffc`cbfc0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\freetype.dll -ModLoad: 00007ffc`e9a10000 00007ffc`e9a45000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DSTORAGE.dll -ModLoad: 00007ffd`158a0000 00007ffd`1594a000 C:\WINDOWS\System32\sechost.dll -ModLoad: 00007ffd`07730000 00007ffd`0774b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\z.dll -ModLoad: 00007ffc`faf10000 00007ffc`fafad000 C:\WINDOWS\SYSTEM32\MSVCP140.dll -ModLoad: 00007ffc`b0570000 00007ffc`b05ca000 C:\WINDOWS\SYSTEM32\CONCRT140.dll -ModLoad: 00007ffc`e5d20000 00007ffc`e5d36000 C:\WINDOWS\SYSTEM32\MSVCP140_ATOMIC_WAIT.dll -ModLoad: 00007ffc`fa520000 00007ffc`fa54c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140.dll -ModLoad: 00007ffc`faf00000 00007ffc`faf0c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140_1.dll -ModLoad: 00007ffc`87650000 00007ffc`8772a000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DirectXTex.dll -ModLoad: 00007ffd`10fa0000 00007ffd`10fb0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\poly2tri.dll -ModLoad: 00007ffd`06d60000 00007ffd`06d71000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\minizip.dll -ModLoad: 00007ffc`eb300000 00007ffc`eb317000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\kubazip.dll -ModLoad: 00007ffc`e13b0000 00007ffc`e13e5000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\pugixml.dll -ModLoad: 00007ffc`ea250000 00007ffc`ea267000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\bz2.dll -ModLoad: 00007ffc`d8720000 00007ffc`d8755000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\libpng16.dll -ModLoad: 00007ffc`e99f0000 00007ffc`e9a01000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlidec.dll -ModLoad: 00007ffc`d86e0000 00007ffc`d8715000 C:\WINDOWS\SYSTEM32\VCOMP140.DLL -ModLoad: 00007ffc`e9580000 00007ffc`e95a7000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlicommon.dll -ModLoad: 00007ffd`134e0000 00007ffd`134f4000 C:\WINDOWS\SYSTEM32\UMPDC.dll -ModLoad: 00007ffd`15950000 00007ffd`15982000 C:\WINDOWS\System32\IMM32.DLL -ModLoad: 00007ffd`12360000 00007ffd`1237b000 C:\WINDOWS\SYSTEM32\kernel.appcore.dll -ModLoad: 00007ffd`13b40000 00007ffd`13bec000 C:\WINDOWS\System32\bcryptPrimitives.dll -ModLoad: 00007ffd`101b0000 00007ffd`10259000 C:\WINDOWS\system32\uxtheme.dll -[INFO] : 4 info text -[WARNING] : 4 warning text -[DEBUG] : 5 debug text -[ERROR] : 5 error text -[INFO] : 5 EVENT: start time: 5 -ModLoad: 00007ffc`c2380000 00007ffc`c242d000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.interposer.dll -ModLoad: 00007ffd`10c10000 00007ffd`10e52000 C:\WINDOWS\SYSTEM32\dbghelp.dll -ModLoad: 00007ffd`157c0000 00007ffd`15899000 C:\WINDOWS\System32\OLEAUT32.dll -ModLoad: 00007ffd`10e60000 00007ffd`10e9b000 C:\WINDOWS\SYSTEM32\dbgcore.DLL -ModLoad: 00007ffc`810f0000 00007ffc`811d0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.common.dll -[INFO] : 10 [Streamline] [16-56-37][streamline][warn][tid:44612][0s:002ms:451us]sl.cpp:228[operator ()] Seems like some DX/VK APIs were invoked before slInit()!!! This may result in incorrect behaviour. - -[INFO] : 10 [Streamline] [16-56-37][streamline][info][tid:44612][0s:002ms:711us]pluginManager.cpp:423[setHostSDKVersion] Streamline v2.12.0.17026aaf - built on Mon Jun 22 13:01:47 2026 - host SDK v2.12.0 - -[INFO] : 11 [Streamline] [16-56-37][streamline][info][tid:44612][0s:002ms:817us]pluginManager.cpp:538[findPlugins] Looking for plugins in C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline ... - -ModLoad: 00007ffd`0cce0000 00007ffd`0cceb000 C:\WINDOWS\SYSTEM32\VERSION.dll -[INFO] : 11 [Streamline] [16-56-37][streamline][info][tid:44612][0s:003ms:045us]pluginManager.cpp:1027[loadPlugins] SL_PRODUCTION not defined at build-time, OTA'd plugins will not be loaded! - -[INFO] : 12 [Streamline] [16-56-37][streamline][info][tid:44612][0s:004ms:608us]commonInterface.cpp:168[getSystemCaps] Enumerating up to 8 adapters but only one of them can be used to create a device - no mGPU support in this SDK - -ModLoad: 00007ffd`10290000 00007ffd`102d6000 C:\WINDOWS\SYSTEM32\dxcore.dll -ModLoad: 00007ffd`107d0000 00007ffd`107e4000 C:\WINDOWS\SYSTEM32\resourcepolicyclient.dll -ModLoad: 00007ffd`0dc20000 00007ffd`0dc7f000 C:\WINDOWS\SYSTEM32\directxdatabasehelper.dll -ModLoad: 00007ffd`131e0000 00007ffd`13231000 C:\WINDOWS\SYSTEM32\cfgmgr32.dll -ModLoad: 00007ffc`c9ca0000 00007ffc`c9cda000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffc`c9ca0000 00007ffc`c9cda000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffd`12d60000 00007ffd`12d73000 C:\WINDOWS\SYSTEM32\msasn1.dll -ModLoad: 00007ffd`0cca0000 00007ffd`0ccdb000 C:\WINDOWS\SYSTEM32\cryptnet.dll -ModLoad: 00007ffd`13c00000 00007ffd`13d78000 C:\WINDOWS\System32\CRYPT32.dll -ModLoad: 00007ffd`0ca90000 00007ffd`0cc09000 C:\WINDOWS\SYSTEM32\drvstore.dll -ModLoad: 00007ffd`131a0000 00007ffd`131cf000 C:\WINDOWS\SYSTEM32\devobj.dll -ModLoad: 00007ffd`12cf0000 00007ffd`12d5a000 C:\WINDOWS\SYSTEM32\wldp.dll -ModLoad: 00007ffd`12c30000 00007ffd`12c3c000 C:\WINDOWS\SYSTEM32\cryptbase.dll -ModLoad: 00007ffd`07e20000 00007ffd`07ea9000 C:\WINDOWS\SYSTEM32\nvapi64.dll -ModLoad: 00007ffd`11130000 00007ffd`119c2000 C:\WINDOWS\SYSTEM32\windows.storage.dll -ModLoad: 00007ffd`078a0000 00007ffd`07e17000 C:\WINDOWS\system32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvapi64_impl.dll -ModLoad: 00007ffd`15990000 00007ffd`15e17000 C:\WINDOWS\System32\SETUPAPI.dll -[INFO] : 54 [Streamline] [16-56-37][streamline][info][tid:44612][0s:046ms:164us]commonInterface.cpp:290[getSystemCaps] >----------------------------------------- - -[INFO] : 54 [Streamline] [16-56-37][streamline][info][tid:44612][0s:046ms:440us]commonInterface.cpp:293[getSystemCaps] NVIDIA driver 610.74 - -[INFO] : 1386 [Streamline] [16-56-38][streamline][info][tid:44612][1s:378ms:305us]commonInterface.cpp:318[getSystemCaps] Adapter 0 architecture 0x170 implementation 0x6 revision 0xa1 - bit 0x2 - LUID 0.64955 - -[INFO] : 1386 [Streamline] [16-56-38][streamline][info][tid:44612][1s:378ms:575us]commonInterface.cpp:324[getSystemCaps] -----------------------------------------< - -[INFO] : 1387 [Streamline] [16-56-38][streamline][info][tid:44612][1s:379ms:387us]commonEntry.cpp:1944[getOSVersionAndUpdateTimerResolution] Changed high resolution timer resolution to 5001 [100 ns units]. - -[INFO] : 1388 [Streamline] [16-56-38][streamline][info][tid:44612][1s:379ms:666us]commonEntry.cpp:2023[updateEmbeddedJSON] Detected Windows OS version 10.0.26200 - -[INFO] : 1388 [Streamline] [16-56-38][streamline][info][tid:44612][1s:380ms:651us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.common' - version 2.12.0.17026aaf DEVELOPMENT - id 4294967295 - priority 0 - adapter mask 0x3 - interposer 'no' - -ModLoad: 00007ffc`bdc50000 00007ffc`bdcbb000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.dlss.dll -ModLoad: 00007ffd`14100000 00007ffd`14183000 C:\WINDOWS\System32\wintrust.dll -ModLoad: 00007ffd`161d0000 00007ffd`161f0000 C:\WINDOWS\System32\imagehlp.dll -ModLoad: 00007ffd`12c10000 00007ffd`12c2b000 C:\WINDOWS\SYSTEM32\CRYPTSP.dll -ModLoad: 00007ffd`122c0000 00007ffd`122f9000 C:\WINDOWS\system32\rsaenh.dll -ModLoad: 00007ffd`13570000 00007ffd`1359a000 C:\WINDOWS\SYSTEM32\bcrypt.dll -ModLoad: 00007ffd`12990000 00007ffd`129bf000 C:\WINDOWS\SYSTEM32\gpapi.dll -ModLoad: 00007ffd`135a0000 00007ffd`135c9000 C:\WINDOWS\SYSTEM32\profapi.dll -ModLoad: 00007ffc`80f70000 00007ffc`810e3000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\_nvngx.dll -ModLoad: 00007ffd`12490000 00007ffd`124c6000 C:\WINDOWS\SYSTEM32\ntmarta.dll -ModLoad: 00007ffb`905c0000 00007ffb`9485b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlss.dll -ModLoad: 00007ffb`8de20000 00007ffb`905b8000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlssd.dll -[INFO] : 30559 [Streamline] [16-57-07][streamline][info][tid:44612][30s:550ms:797us]commonEntry.cpp:999[getNGXFeatureRequirements] NGX feature 1 requirements - minOS 10.0.0 minHW 0x160 - -[INFO] : 30561 [Streamline] [16-57-07][streamline][info][tid:44612][30s:552ms:969us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.dlss' - version 2.12.0.17026aaf DEVELOPMENT - id 0 - priority 100 - adapter mask 0x2 - interposer 'no' - -ModLoad: 00007ffc`88d80000 00007ffc`88def000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.dlss_d.dll -[INFO] : 30590 [Streamline] [16-57-07][streamline][info][tid:44612][30s:582ms:339us]commonEntry.cpp:999[getNGXFeatureRequirements] NGX feature 13 requirements - minOS 10.0.0 minHW 0x160 - -[INFO] : 30594 [Streamline] [16-57-07][streamline][info][tid:44612][30s:586ms:688us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.dlss_d' - version 2.12.0.17026aaf DEVELOPMENT - id 1001 - priority 100 - adapter mask 0x2 - interposer 'no' - -ModLoad: 00007ffc`7d790000 00007ffc`7d8e2000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.imgui.dll -ModLoad: 00007ffc`80ea0000 00007ffc`80f66000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\vulkan-1.dll -ModLoad: 00007ffd`0e5a0000 00007ffd`0ea1f000 C:\WINDOWS\SYSTEM32\D3DCOMPILER_47.dll -ModLoad: 00007ffc`e89a0000 00007ffc`e89bb000 C:\WINDOWS\SYSTEM32\XINPUT1_4.dll -ModLoad: 00007ffc`f16b0000 00007ffc`f1894000 C:\WINDOWS\SYSTEM32\inputhost.dll -ModLoad: 00007ffd`0fca0000 00007ffd`0fdc5000 C:\WINDOWS\SYSTEM32\CoreMessaging.dll -[INFO] : 30625 [Streamline] [16-57-07][streamline][warn][tid:44612][30s:616ms:876us]pluginManager.cpp:780[mapPlugins] Ignoring plugin 'sl.imgui' since it is was not requested by the host - -[INFO] : 30634 [Streamline] initialized -[INFO] : 30634 [Streamline] [16-57-07][streamline][info][tid:44612][30s:626ms:255us]pluginManager.cpp:1159[loadPlugins] Plugin execution order based on priority: - -[INFO] : 30634 [Streamline] [16-57-07][streamline][info][tid:44612][30s:626ms:589us]pluginManager.cpp:1162[loadPlugins] P0 - sl.common - -[INFO] : 30634 [Streamline] [16-57-07][streamline][info][tid:44612][30s:626ms:766us]pluginManager.cpp:1162[loadPlugins] P100 - sl.dlss - -[INFO] : 30635 [Streamline] [16-57-07][streamline][info][tid:44612][30s:626ms:923us]pluginManager.cpp:1162[loadPlugins] P100 - sl.dlss_d - -ModLoad: 00007ffc`c1460000 00007ffc`c17b7000 C:\WINDOWS\SYSTEM32\D3D12Core.dll -ModLoad: 00007ffc`cc730000 00007ffc`cc755000 C:\WINDOWS\SYSTEM32\DXGIDebug.dll -ModLoad: 00007ffb`a5cb0000 00007ffb`a6150000 C:\WINDOWS\SYSTEM32\D3D12SDKLayers.dll -[INFO] : 30641 // Adapter: NVIDIA GeForce RTX 3060 Laptop GPU -[INFO] : 30641 adapter: NVIDIA GeForce RTX 3060 Laptop GPU -ModLoad: 00007ffc`cc730000 00007ffc`cc755000 C:\WINDOWS\SYSTEM32\DXGIDebug.dll -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`7d7b0000 00007ffc`7d8eb000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffb`b9c50000 00007ffb`b9db4000 C:\WINDOWS\system32\nvspcap64.dll -ModLoad: 00007ffb`a5cb0000 00007ffb`a6150000 C:\WINDOWS\SYSTEM32\D3D12SDKLayers.dll -ModLoad: 00007ffb`a8dc0000 00007ffb`a94e7000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvppex.dll -ModLoad: 00007ffd`021a0000 00007ffd`0227f000 C:\WINDOWS\SYSTEM32\D3DSCache.dll -ModLoad: 00007ffd`129d0000 00007ffd`12a02000 C:\WINDOWS\SYSTEM32\USERENV.dll -ModLoad: 00007ffc`7bc80000 00007ffc`7bde4000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 32956 // Adapter: AMD Radeon(TM) Graphics -[INFO] : 32956 // Output: \\.\DISPLAY1 -[INFO] : 32956 adapter: AMD Radeon(TM) Graphics -ModLoad: 00007ffc`1e9e0000 00007ffc`22fbb000 C:\WINDOWS\System32\DriverStore\FileRepository\u0405470.inf_amd64_2e71ce0e27c179e1\B404884\amdxc64.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffd`027a0000 00007ffd`027d5000 C:\WINDOWS\SYSTEM32\amdihk64.dll -[INFO] : 32991 // Adapter: Microsoft Basic Render Driver -[INFO] : 32991 adapter: Microsoft Basic Render Driver -ModLoad: 00007ffb`a6750000 00007ffb`a6cfe000 C:\WINDOWS\SYSTEM32\d3d10warp.dll -[INFO] : 32995 Selected adapter: NVIDIA GeForce RTX 3060 Laptop GPU -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`7d7b0000 00007ffc`7d8eb000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffc`7bc80000 00007ffc`7bde4000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 33868 DLSS: super-resolution=yes ray-reconstruction=yes -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`7d7b0000 00007ffc`7d8eb000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffc`7bc80000 00007ffc`7bde4000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 35502 [Streamline] [16-57-12][streamline][info][tid:44612][35s:493ms:220us]pluginManager.cpp:1362[initializePlugins] Initializing plugins - api 0.0.1 - application ID 100721531 - -[INFO] : 35510 [Streamline] [16-57-12][streamline][info][tid:44612][35s:500ms:779us]commonEntry.cpp:1467[slOnPluginStartup] At least one plugin requires NGX, trying to initialize ... - -[INFO] : 35511 [Streamline] [16-57-12][streamline][info][tid:44612][35s:502ms:958us]commonEntry.cpp:1267[ngxLog] [Unknown(32764)] [2026-08-14 16:57:12] [NGXSafeInitializeLog:148] App logging hooks successfully initialized - - -[INFO] : 35512 [Streamline] [16-57-12][streamline][info][tid:44612][35s:504ms:066us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXSafeInitializeLog:139] App logging hooks successfully initialized - - -[INFO] : 35519 [Streamline] [16-57-12][streamline][info][tid:44612][35s:510ms:838us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXGetPathUsingQAI:512] Path to driverStore found using QAI: C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d - - -[INFO] : 35519 [Streamline] [16-57-12][streamline][info][tid:44612][35s:511ms:234us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXGetPath:1045] using path for models: C:\ProgramData\NVIDIA\NGX\models - - -[INFO] : 35604 [Streamline] [16-57-12][streamline][info][tid:44612][35s:596ms:609us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [ModifyExistingFolderDACL:211] updated access control list for NGX cache (NGX cache should now be usable by all authenticated users) - - -[INFO] : 35605 [Streamline] [16-57-12][streamline][info][tid:44612][35s:597ms:307us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXCreateModelsFolder:1072] model folder created: C:\ProgramData\NVIDIA\NGX\models - - -[INFO] : 35605 [Streamline] [16-57-12][streamline][info][tid:44612][35s:597ms:552us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [dlss] - - -[INFO] : 35606 [Streamline] [16-57-12][streamline][info][tid:44612][35s:598ms:046us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9FEB50=1.0.108 - - -[INFO] : 35607 [Streamline] [16-57-12][streamline][info][tid:44612][35s:598ms:660us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_8661E24=2.3.0 - - -[INFO] : 35607 [Streamline] [16-57-12][streamline][info][tid:44612][35s:599ms:306us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9FF4BC=1.0.108 - - -[INFO] : 35608 [Streamline] [16-57-12][streamline][info][tid:44612][35s:599ms:834us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9FD524=1.0.108 - - -[INFO] : 35609 [Streamline] [16-57-12][streamline][info][tid:44612][35s:600ms:788us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DF510=1.3.106 - - -[INFO] : 35610 [Streamline] [16-57-12][streamline][info][tid:44612][35s:601ms:240us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9CB0B8=1.0.108 - - -[INFO] : 35611 [Streamline] [16-57-12][streamline][info][tid:44612][35s:603ms:095us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9E26B0=2.1.28 - - -[INFO] : 35613 [Streamline] [16-57-12][streamline][info][tid:44612][35s:604ms:358us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D48D0=1.0.108 - - -[INFO] : 35614 [Streamline] [16-57-12][streamline][info][tid:44612][35s:605ms:648us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E99B5EC=1.2.109 - - -[INFO] : 35615 [Streamline] [16-57-12][streamline][info][tid:44612][35s:607ms:237us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DFA64=1.0.108 - - -[INFO] : 35616 [Streamline] [16-57-12][streamline][info][tid:44612][35s:608ms:359us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9FD6C0=1.0.108 - - -[INFO] : 35617 [Streamline] [16-57-12][streamline][info][tid:44612][35s:608ms:923us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9CF688=2.2.15 - - -[INFO] : 35620 [Streamline] [16-57-12][streamline][info][tid:44612][35s:610ms:622us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D6F08=1.0.108 - - -[INFO] : 35623 [Streamline] [16-57-12][streamline][info][tid:44612][35s:614ms:706us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9BF564=2.1.201 - - -[INFO] : 35623 [Streamline] [16-57-12][streamline][info][tid:44612][35s:615ms:522us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DB4F4=1.0.108 - - -[INFO] : 35623 [Streamline] [16-57-12][streamline][info][tid:44612][35s:615ms:701us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D2F5C=1.2.110 - - -[INFO] : 35625 [Streamline] [16-57-12][streamline][info][tid:44612][35s:616ms:267us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D7388=1.0.108 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:047us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9F5618=2.1.29 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:202us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DAE68=1.2.109 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:339us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D8C54=1.2.109 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:477us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_3AC09EF=2.2.11 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:620us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D3EF0=2.1.28 - - -[INFO] : 35626 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:761us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9FACDC=2.1.201 - - -[INFO] : 35627 [Streamline] [16-57-12][streamline][info][tid:44612][35s:618ms:900us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_F7361DC=2.1.28 - - -[INFO] : 35627 [Streamline] [16-57-12][streamline][info][tid:44612][35s:619ms:232us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9C3560=2.1.201 - - -[INFO] : 35627 [Streamline] [16-57-12][streamline][info][tid:44612][35s:619ms:415us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9B0430=2.1.28 - - -[INFO] : 35627 [Streamline] [16-57-12][streamline][info][tid:44612][35s:619ms:573us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658703=0.0.0 - - -[INFO] : 35634 [Streamline] [16-57-12][streamline][info][tid:44612][35s:621ms:979us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_87211B8=2.4.12 - - -[INFO] : 35855 [Streamline] [16-57-12][streamline][info][tid:44612][35s:759ms:589us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658702=0.0.0 - - -[INFO] : 35981 [Streamline] [16-57-13][streamline][info][tid:44612][35s:947ms:208us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658701=0.0.0 - - -[INFO] : 36039 [Streamline] [16-57-13][streamline][info][tid:44612][36s:020ms:043us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_87D97FC=2.4.12 - - -[INFO] : 36093 [Streamline] [16-57-13][streamline][info][tid:44612][36s:036ms:800us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_870691C=2.5.1 - - -[INFO] : 36500 [Streamline] [16-57-13][streamline][info][tid:44612][36s:306ms:360us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_EC0E344=2.4.0 - - -[INFO] : 36835 [Streamline] [16-57-13][streamline][info][tid:44612][36s:711ms:954us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_86AA7B4=2.3.1 - - -[INFO] : 36925 [Streamline] [16-57-14][streamline][info][tid:44612][36s:896ms:539us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B98BB3C=2.4.0 - - -[INFO] : 37073 [Streamline] [16-57-14][streamline][info][tid:44612][36s:982ms:944us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_8624CC0=2.3.11 - - -[INFO] : 37178 [Streamline] [16-57-14][streamline][info][tid:44612][37s:122ms:428us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_863B354=3.1.1 - - -[INFO] : 37258 [Streamline] [16-57-14][streamline][info][tid:44612][37s:212ms:537us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_8656478=3.1.11 - - -[INFO] : 37266 [Streamline] [16-57-14][streamline][info][tid:44612][37s:257ms:534us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_876232C=2.3.1 - - -[INFO] : 37266 [Streamline] [16-57-14][streamline][info][tid:44612][37s:258ms:325us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_86BBD3C=3.5.0 - - -[INFO] : 37267 [Streamline] [16-57-14][streamline][info][tid:44612][37s:258ms:841us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658700=310.6.0 - - -[INFO] : 37267 [Streamline] [16-57-14][streamline][info][tid:44612][37s:259ms:297us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [dlisp] - - -[INFO] : 37268 [Streamline] [16-57-14][streamline][info][tid:44612][37s:259ms:807us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9CF688=2.1.15 - - -[INFO] : 37268 [Streamline] [16-57-14][streamline][info][tid:44612][37s:260ms:350us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E99B5EC=1.2.102 - - -[INFO] : 37269 [Streamline] [16-57-14][streamline][info][tid:44612][37s:260ms:674us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658703=310.0.0 - - -[INFO] : 37269 [Streamline] [16-57-14][streamline][info][tid:44612][37s:261ms:227us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D8C54=1.2.106 - - -[INFO] : 37270 [Streamline] [16-57-14][streamline][info][tid:44612][37s:261ms:717us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9D2F5C=1.2.106 - - -[INFO] : 37270 [Streamline] [16-57-14][streamline][info][tid:44612][37s:262ms:070us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DF510=1.3.101 - - -[INFO] : 37270 [Streamline] [16-57-14][streamline][info][tid:44612][37s:262ms:544us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_B9DAE68=1.2.106 - - -[INFO] : 37271 [Streamline] [16-57-14][streamline][info][tid:44612][37s:263ms:072us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [dlssg] - - -[INFO] : 37272 [Streamline] [16-57-14][streamline][info][tid:44612][37s:263ms:924us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_86AA7B4=0.0.0 - - -[INFO] : 37272 [Streamline] [16-57-14][streamline][info][tid:44612][37s:264ms:515us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_863B354=3.1.13 - - -[INFO] : 37273 [Streamline] [16-57-14][streamline][info][tid:44612][37s:265ms:181us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_87D97FC=1.0.2 - - -[INFO] : 37273 [Streamline] [16-57-14][streamline][info][tid:44612][37s:265ms:610us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_8656478=3.1.10 - - -[INFO] : 37274 [Streamline] [16-57-14][streamline][info][tid:44612][37s:266ms:110us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_nrd_0] - - -[INFO] : 37274 [Streamline] [16-57-14][streamline][info][tid:44612][37s:266ms:528us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_dlss_d_0] - - -[INFO] : 37275 [Streamline] [16-57-14][streamline][info][tid:44612][37s:266ms:970us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1150] app_E658703=2.11.0 - - -[INFO] : 37275 [Streamline] [16-57-14][streamline][info][tid:44612][37s:267ms:409us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_common_0] - - -[INFO] : 37276 [Streamline] [16-57-14][streamline][info][tid:44612][37s:267ms:794us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_dlss_0] - - -[INFO] : 37276 [Streamline] [16-57-14][streamline][info][tid:44612][37s:268ms:343us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_pcl_0] - - -[INFO] : 37277 [Streamline] [16-57-14][streamline][info][tid:44612][37s:268ms:808us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_dlss_g_0] - - -[INFO] : 37277 [Streamline] [16-57-14][streamline][info][tid:44612][37s:269ms:474us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_reflex_0] - - -[INFO] : 37278 [Streamline] [16-57-14][streamline][info][tid:44612][37s:270ms:075us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [deepdvc] - - -[INFO] : 37279 [Streamline] [16-57-14][streamline][info][tid:44612][37s:271ms:239us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_sdk_0] - - -[INFO] : 37280 [Streamline] [16-57-14][streamline][info][tid:44612][37s:272ms:040us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_deepdvc_0] - - -[INFO] : 37281 [Streamline] [16-57-14][streamline][info][tid:44612][37s:272ms:836us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_nis_0] - - -[INFO] : 37281 [Streamline] [16-57-14][streamline][info][tid:44612][37s:273ms:505us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [sl_nvperf_0] - - -[INFO] : 37282 [Streamline] [16-57-14][streamline][info][tid:44612][37s:274ms:199us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [dlss_override] - - -[INFO] : 37283 [Streamline] [16-57-14][streamline][info][tid:44612][37s:274ms:768us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [NGXLoadConfig:1144] [dlssd] - - -[INFO] : 37283 [Streamline] [16-57-14][streamline][info][tid:44612][37s:275ms:290us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 86aa7b4 - - -[INFO] : 37283 [Streamline] [16-57-14][streamline][info][tid:44612][37s:275ms:631us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [`anonymous-namespace'::LoadMappingFileData:282] project id 3F9D696D4363312194B0ECB2671E899F cms id B9FBD50 - - -[INFO] : 37284 [Streamline] [16-57-14][streamline][info][tid:44612][37s:275ms:955us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 8618954 - - -[INFO] : 37285 [Streamline] [16-57-14][streamline][info][tid:44612][37s:276ms:639us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId b9b05cc - - -[INFO] : 37285 [Streamline] [16-57-14][streamline][info][tid:44612][37s:277ms:154us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 876232c - - -[INFO] : 37286 [Streamline] [16-57-14][streamline][info][tid:44612][37s:277ms:659us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:12] [MapProjectId:1878] Found cms id 876232c for engine: custom engineVersion 1.0 projectID b7e4f1a2-9c3d-4e58-8f10-2a6b5d9c7e34 - - -[INFO] : 38037 [Streamline] [16-57-15][streamline][info][tid:44612][38s:029ms:044us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:15] [NGXDRSInit:182] NvAPI_DRS_FindApplicationByName -166 - - -[INFO] : 39031 [Streamline] [16-57-16][streamline][info][tid:44612][39s:023ms:643us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [NGXInitContext:245] called from module sl.common.dll at C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline - - -[INFO] : 39032 [Streamline] [16-57-16][streamline][info][tid:44612][39s:024ms:110us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [`anonymous-namespace'::SnippetLocationInfo::load:713] NGXLoadFromPath failed: -1160773628 - - -[INFO] : 39032 [Streamline] [16-57-16][streamline][info][tid:44612][39s:024ms:401us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [NGXReportStatusFeedback:1076] Override shared memory was opened successfully - - -[INFO] : 39032 [Streamline] [16-57-16][streamline][info][tid:44612][39s:024ms:655us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [NGXReportStatusFeedback:1088] Override shared memory was mapped successfully - - -[INFO] : 39146 [Streamline] [16-57-16][streamline][info][tid:44612][39s:138ms:324us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [NGXSecureLoadFeature:1345] Feature dlss failed to load (cmsid 141959980) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlss\versions\131841\files, name: 160_876232C.bin) - - -[INFO] : 39146 [Streamline] [16-57-16][streamline][info][tid:44612][39s:138ms:663us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 16:57:16] [NGXSecureLoadFeature:1345] Feature dlss failed to load (cmsid 241534723) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlss\versions\0\files, name: 160_E658703.bin) - - -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\atlmfc.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\ObjectiveC.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\concurrency.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\cpp_rest.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\stl.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Data.Json.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Geolocation.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Sensors.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Media.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\windows.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\winrt.natvis' diff --git a/workdir/cdb_output2.txt b/workdir/cdb_output2.txt deleted file mode 100644 index c690bec3..00000000 --- a/workdir/cdb_output2.txt +++ /dev/null @@ -1,124 +0,0 @@ - -Microsoft (R) Windows Debugger Version 10.0.22621.755 AMD64 -Copyright (c) Microsoft Corporation. All rights reserved. - -CommandLine: ./../bin/debug/spectrum.exe - -************* Path validation summary ************** -Response Time (ms) Location -Deferred srv* -Symbol search path is: srv* -Executable search path is: -ModLoad: 00007ff6`0e450000 00007ff6`0eda7000 spectrum.exe -ModLoad: 00007ffd`16460000 00007ffd`166c6000 ntdll.dll -ModLoad: 00007ffd`15360000 00007ffd`15429000 C:\WINDOWS\System32\KERNEL32.DLL -ModLoad: 00007ffd`13730000 00007ffd`13b2e000 C:\WINDOWS\System32\KERNELBASE.dll -ModLoad: 00007ffd`14190000 00007ffd`14357000 C:\WINDOWS\System32\USER32.dll -ModLoad: 00007ffd`13d80000 00007ffd`13da7000 C:\WINDOWS\System32\win32u.dll -ModLoad: 00007ffc`d3680000 00007ffc`d36a3000 C:\WINDOWS\SYSTEM32\d3d12.dll -ModLoad: 00007ffd`102e0000 00007ffd`1041e000 C:\WINDOWS\SYSTEM32\dxgi.dll -ModLoad: 00007ffd`13680000 00007ffd`13723000 C:\WINDOWS\System32\msvcp_win.dll -ModLoad: 00007ffd`15320000 00007ffd`1534b000 C:\WINDOWS\System32\GDI32.dll -ModLoad: 00007ffd`13db0000 00007ffd`13efc000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 000001ec`66170000 000001ec`662bc000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 00007ffd`13fd0000 00007ffd`140fa000 C:\WINDOWS\System32\gdi32full.dll -ModLoad: 00007ffd`16320000 00007ffd`16417000 C:\WINDOWS\System32\shcore.dll -ModLoad: 00007ffd`13500000 00007ffd`1355e000 C:\WINDOWS\SYSTEM32\powrprof.dll -ModLoad: 00007ffd`14910000 00007ffd`14aa9000 C:\WINDOWS\System32\ole32.dll -ModLoad: 00007ffd`16200000 00007ffd`16318000 C:\WINDOWS\System32\RPCRT4.dll -ModLoad: 00007ffd`14580000 00007ffd`14903000 C:\WINDOWS\System32\combase.dll -ModLoad: 00007ffd`15e20000 00007ffd`15f12000 C:\WINDOWS\System32\COMDLG32.dll -ModLoad: 00007ffd`156d0000 00007ffd`15737000 C:\WINDOWS\System32\SHLWAPI.dll -ModLoad: 00007ffd`14ac0000 00007ffd`15252000 C:\WINDOWS\System32\SHELL32.dll -ModLoad: 00007ffc`ff260000 00007ffc`ff315000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.26100.8328_none_87eede957a2ccddd\COMCTL32.dll -ModLoad: 00007ffd`15260000 00007ffd`15317000 C:\WINDOWS\System32\ADVAPI32.dll -ModLoad: 00007ffc`e13b0000 00007ffc`e13e5000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DSTORAGE.dll -ModLoad: 00007ffd`16120000 00007ffd`161c9000 C:\WINDOWS\System32\msvcrt.dll -ModLoad: 00007ffb`a5c10000 00007ffb`a615b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\assimp-vc143-mt.dll -ModLoad: 00007ffc`cbf10000 00007ffc`cbfc0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\freetype.dll -ModLoad: 00007ffd`158a0000 00007ffd`1594a000 C:\WINDOWS\System32\sechost.dll -ModLoad: 00007ffc`ea250000 00007ffc`ea26b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\z.dll -ModLoad: 00007ffc`faf10000 00007ffc`fafad000 C:\WINDOWS\SYSTEM32\MSVCP140.dll -ModLoad: 00007ffc`b0570000 00007ffc`b05ca000 C:\WINDOWS\SYSTEM32\CONCRT140.dll -ModLoad: 00007ffc`e5d20000 00007ffc`e5d36000 C:\WINDOWS\SYSTEM32\MSVCP140_ATOMIC_WAIT.dll -ModLoad: 00007ffc`fa520000 00007ffc`fa54c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140.dll -ModLoad: 00007ffc`faf00000 00007ffc`faf0c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140_1.dll -ModLoad: 00007ffc`84cc0000 00007ffc`84d9a000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DirectXTex.dll -ModLoad: 00007ffd`0d780000 00007ffd`0d790000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\poly2tri.dll -ModLoad: 00007ffc`e9590000 00007ffc`e95a1000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\minizip.dll -ModLoad: 00007ffc`e89a0000 00007ffc`e89b7000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\kubazip.dll -ModLoad: 00007ffc`d8720000 00007ffc`d8755000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\pugixml.dll -ModLoad: 00007ffc`d8700000 00007ffc`d8717000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\bz2.dll -ModLoad: 00007ffc`c9ca0000 00007ffc`c9cd5000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\libpng16.dll -ModLoad: 00007ffc`d86e0000 00007ffc`d86f1000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlidec.dll -ModLoad: 00007ffc`cced0000 00007ffc`ccef7000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlicommon.dll -ModLoad: 00007ffc`c9670000 00007ffc`c96a5000 C:\WINDOWS\SYSTEM32\VCOMP140.DLL -ModLoad: 00007ffd`134e0000 00007ffd`134f4000 C:\WINDOWS\SYSTEM32\UMPDC.dll -ModLoad: 00007ffd`15950000 00007ffd`15982000 C:\WINDOWS\System32\IMM32.DLL -ModLoad: 00007ffd`12360000 00007ffd`1237b000 C:\WINDOWS\SYSTEM32\kernel.appcore.dll -ModLoad: 00007ffd`13b40000 00007ffd`13bec000 C:\WINDOWS\System32\bcryptPrimitives.dll -ModLoad: 00007ffd`101b0000 00007ffd`10259000 C:\WINDOWS\system32\uxtheme.dll -[INFO] : 4 info text -[WARNING] : 4 warning text -[DEBUG] : 5 debug text -[ERROR] : 5 error text -[INFO] : 5 EVENT: start time: 5 -ModLoad: 00007ffc`c2380000 00007ffc`c242d000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.interposer.dll -ModLoad: 00007ffd`10c10000 00007ffd`10e52000 C:\WINDOWS\SYSTEM32\dbghelp.dll -ModLoad: 00007ffd`157c0000 00007ffd`15899000 C:\WINDOWS\System32\OLEAUT32.dll -ModLoad: 00007ffd`10e60000 00007ffd`10e9b000 C:\WINDOWS\SYSTEM32\dbgcore.DLL -ModLoad: 00007ffc`84be0000 00007ffc`84cc0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.common.dll -[INFO] : 11 [Streamline] [16-58-57][streamline][warn][tid:23128][0s:003ms:137us]sl.cpp:228[operator ()] Seems like some DX/VK APIs were invoked before slInit()!!! This may result in incorrect behaviour. - -ModLoad: 00007ffd`0cce0000 00007ffd`0cceb000 C:\WINDOWS\SYSTEM32\VERSION.dll -[INFO] : 11 [Streamline] [16-58-57][streamline][info][tid:23128][0s:003ms:556us]pluginManager.cpp:423[setHostSDKVersion] Streamline v2.12.0.17026aaf - built on Mon Jun 22 13:01:47 2026 - host SDK v2.12.0 - -[INFO] : 11 [Streamline] [16-58-57][streamline][info][tid:23128][0s:003ms:719us]pluginManager.cpp:538[findPlugins] Looking for plugins in C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline ... - -[INFO] : 12 [Streamline] [16-58-57][streamline][info][tid:23128][0s:003ms:859us]pluginManager.cpp:1027[loadPlugins] SL_PRODUCTION not defined at build-time, OTA'd plugins will not be loaded! - -[INFO] : 13 [Streamline] [16-58-57][streamline][info][tid:23128][0s:005ms:221us]commonInterface.cpp:168[getSystemCaps] Enumerating up to 8 adapters but only one of them can be used to create a device - no mGPU support in this SDK - -ModLoad: 00007ffd`10290000 00007ffd`102d6000 C:\WINDOWS\SYSTEM32\dxcore.dll -ModLoad: 00007ffd`107d0000 00007ffd`107e4000 C:\WINDOWS\SYSTEM32\resourcepolicyclient.dll -ModLoad: 00007ffd`0dc20000 00007ffd`0dc7f000 C:\WINDOWS\SYSTEM32\directxdatabasehelper.dll -ModLoad: 00007ffd`131e0000 00007ffd`13231000 C:\WINDOWS\SYSTEM32\cfgmgr32.dll -ModLoad: 00007ffc`c92d0000 00007ffc`c930a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffc`c92d0000 00007ffc`c930a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffd`12d60000 00007ffd`12d73000 C:\WINDOWS\SYSTEM32\msasn1.dll -ModLoad: 00007ffd`0cca0000 00007ffd`0ccdb000 C:\WINDOWS\SYSTEM32\cryptnet.dll -ModLoad: 00007ffd`13c00000 00007ffd`13d78000 C:\WINDOWS\System32\CRYPT32.dll -ModLoad: 00007ffd`0ca90000 00007ffd`0cc09000 C:\WINDOWS\SYSTEM32\drvstore.dll -ModLoad: 00007ffd`131a0000 00007ffd`131cf000 C:\WINDOWS\SYSTEM32\devobj.dll -ModLoad: 00007ffd`12cf0000 00007ffd`12d5a000 C:\WINDOWS\SYSTEM32\wldp.dll -ModLoad: 00007ffd`12c30000 00007ffd`12c3c000 C:\WINDOWS\SYSTEM32\cryptbase.dll -ModLoad: 00007ffd`07e20000 00007ffd`07ea9000 C:\WINDOWS\SYSTEM32\nvapi64.dll -ModLoad: 00007ffd`11130000 00007ffd`119c2000 C:\WINDOWS\SYSTEM32\windows.storage.dll -ModLoad: 00007ffd`078a0000 00007ffd`07e17000 C:\WINDOWS\system32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvapi64_impl.dll -ModLoad: 00007ffd`15990000 00007ffd`15e17000 C:\WINDOWS\System32\SETUPAPI.dll -[INFO] : 56 [Streamline] [16-58-57][streamline][info][tid:23128][0s:047ms:915us]commonInterface.cpp:290[getSystemCaps] >----------------------------------------- - -[INFO] : 56 [Streamline] [16-58-57][streamline][info][tid:23128][0s:048ms:184us]commonInterface.cpp:293[getSystemCaps] NVIDIA driver 610.74 - -[INFO] : 1387 [Streamline] [16-58-58][streamline][info][tid:23128][1s:379ms:152us]commonInterface.cpp:318[getSystemCaps] Adapter 0 architecture 0x170 implementation 0x6 revision 0xa1 - bit 0x2 - LUID 0.64955 - -[INFO] : 1387 [Streamline] [16-58-58][streamline][info][tid:23128][1s:379ms:484us]commonInterface.cpp:324[getSystemCaps] -----------------------------------------< - -[INFO] : 1388 [Streamline] [16-58-58][streamline][info][tid:23128][1s:380ms:367us]commonEntry.cpp:1944[getOSVersionAndUpdateTimerResolution] Changed high resolution timer resolution to 4988 [100 ns units]. - -[INFO] : 1388 [Streamline] [16-58-58][streamline][info][tid:23128][1s:380ms:587us]commonEntry.cpp:2023[updateEmbeddedJSON] Detected Windows OS version 10.0.26200 - -[INFO] : 1389 [Streamline] [16-58-58][streamline][info][tid:23128][1s:381ms:336us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.common' - version 2.12.0.17026aaf DEVELOPMENT - id 4294967295 - priority 0 - adapter mask 0x3 - interposer 'no' - -ModLoad: 00007ffc`bdc50000 00007ffc`bdcbb000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.dlss.dll -ModLoad: 00007ffd`14100000 00007ffd`14183000 C:\WINDOWS\System32\wintrust.dll -ModLoad: 00007ffd`161d0000 00007ffd`161f0000 C:\WINDOWS\System32\imagehlp.dll -ModLoad: 00007ffd`12c10000 00007ffd`12c2b000 C:\WINDOWS\SYSTEM32\CRYPTSP.dll -ModLoad: 00007ffd`122c0000 00007ffd`122f9000 C:\WINDOWS\system32\rsaenh.dll -ModLoad: 00007ffd`13570000 00007ffd`1359a000 C:\WINDOWS\SYSTEM32\bcrypt.dll -ModLoad: 00007ffd`12990000 00007ffd`129bf000 C:\WINDOWS\SYSTEM32\gpapi.dll -ModLoad: 00007ffd`135a0000 00007ffd`135c9000 C:\WINDOWS\SYSTEM32\profapi.dll -ModLoad: 00007ffc`84a60000 00007ffc`84bd3000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\_nvngx.dll -ModLoad: 00007ffd`12490000 00007ffd`124c6000 C:\WINDOWS\SYSTEM32\ntmarta.dll -ModLoad: 00007ffb`905c0000 00007ffb`9485b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlss.dll -ModLoad: 00007ffb`8de20000 00007ffb`905b8000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlssd.dll diff --git a/workdir/cdb_output3.txt b/workdir/cdb_output3.txt deleted file mode 100644 index 21b7c485..00000000 --- a/workdir/cdb_output3.txt +++ /dev/null @@ -1,701 +0,0 @@ - -Microsoft (R) Windows Debugger Version 10.0.22621.755 AMD64 -Copyright (c) Microsoft Corporation. All rights reserved. - -CommandLine: ./../bin/debug/spectrum.exe - -************* Path validation summary ************** -Response Time (ms) Location -Deferred srv* -Symbol search path is: srv* -Executable search path is: -ModLoad: 00007ff6`0e450000 00007ff6`0eda7000 spectrum.exe -ModLoad: 00007ffd`16460000 00007ffd`166c6000 ntdll.dll -ModLoad: 00007ffd`15360000 00007ffd`15429000 C:\WINDOWS\System32\KERNEL32.DLL -ModLoad: 00007ffd`13730000 00007ffd`13b2e000 C:\WINDOWS\System32\KERNELBASE.dll -ModLoad: 00007ffd`14190000 00007ffd`14357000 C:\WINDOWS\System32\USER32.dll -ModLoad: 00007ffd`13d80000 00007ffd`13da7000 C:\WINDOWS\System32\win32u.dll -ModLoad: 00007ffd`102e0000 00007ffd`1041e000 C:\WINDOWS\SYSTEM32\dxgi.dll -ModLoad: 00007ffc`d3680000 00007ffc`d36a3000 C:\WINDOWS\SYSTEM32\d3d12.dll -ModLoad: 00007ffd`15320000 00007ffd`1534b000 C:\WINDOWS\System32\GDI32.dll -ModLoad: 00007ffd`13680000 00007ffd`13723000 C:\WINDOWS\System32\msvcp_win.dll -ModLoad: 000001a1`afe20000 000001a1`afec3000 C:\WINDOWS\System32\msvcp_win.dll -ModLoad: 00007ffd`13fd0000 00007ffd`140fa000 C:\WINDOWS\System32\gdi32full.dll -ModLoad: 00007ffd`13db0000 00007ffd`13efc000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 000001a1`afe20000 000001a1`aff6c000 C:\WINDOWS\System32\ucrtbase.dll -ModLoad: 00007ffd`16320000 00007ffd`16417000 C:\WINDOWS\System32\shcore.dll -ModLoad: 00007ffd`14910000 00007ffd`14aa9000 C:\WINDOWS\System32\ole32.dll -ModLoad: 00007ffd`13500000 00007ffd`1355e000 C:\WINDOWS\SYSTEM32\powrprof.dll -ModLoad: 00007ffd`14580000 00007ffd`14903000 C:\WINDOWS\System32\combase.dll -ModLoad: 00007ffd`16200000 00007ffd`16318000 C:\WINDOWS\System32\RPCRT4.dll -ModLoad: 00007ffd`15e20000 00007ffd`15f12000 C:\WINDOWS\System32\COMDLG32.dll -ModLoad: 00007ffd`156d0000 00007ffd`15737000 C:\WINDOWS\System32\SHLWAPI.dll -ModLoad: 00007ffd`14ac0000 00007ffd`15252000 C:\WINDOWS\System32\SHELL32.dll -ModLoad: 00007ffc`ff260000 00007ffc`ff315000 C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_5.82.26100.8328_none_87eede957a2ccddd\COMCTL32.dll -ModLoad: 00007ffd`15260000 00007ffd`15317000 C:\WINDOWS\System32\ADVAPI32.dll -ModLoad: 00007ffd`16120000 00007ffd`161c9000 C:\WINDOWS\System32\msvcrt.dll -ModLoad: 00007ffc`84aa0000 00007ffc`84feb000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\assimp-vc143-mt.dll -ModLoad: 00007ffc`e13b0000 00007ffc`e13e5000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DSTORAGE.dll -ModLoad: 00007ffc`cbf10000 00007ffc`cbfc0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\freetype.dll -ModLoad: 00007ffd`158a0000 00007ffd`1594a000 C:\WINDOWS\System32\sechost.dll -ModLoad: 00007ffd`06d60000 00007ffd`06d7b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\z.dll -ModLoad: 00007ffc`faf10000 00007ffc`fafad000 C:\WINDOWS\SYSTEM32\MSVCP140.dll -ModLoad: 00007ffc`e5d20000 00007ffc`e5d36000 C:\WINDOWS\SYSTEM32\MSVCP140_ATOMIC_WAIT.dll -ModLoad: 00007ffc`b0570000 00007ffc`b05ca000 C:\WINDOWS\SYSTEM32\CONCRT140.dll -ModLoad: 00007ffc`fa520000 00007ffc`fa54c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140.dll -ModLoad: 00007ffc`faf00000 00007ffc`faf0c000 C:\WINDOWS\SYSTEM32\VCRUNTIME140_1.dll -ModLoad: 00007ffc`87650000 00007ffc`8772a000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DirectXTex.dll -ModLoad: 00007ffd`10f70000 00007ffd`10f80000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\poly2tri.dll -ModLoad: 00007ffc`ea250000 00007ffc`ea267000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\kubazip.dll -ModLoad: 00007ffc`eb300000 00007ffc`eb311000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\minizip.dll -ModLoad: 00007ffc`d8720000 00007ffc`d8755000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\pugixml.dll -ModLoad: 00007ffc`e9590000 00007ffc`e95a7000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\bz2.dll -ModLoad: 00007ffc`d86e0000 00007ffc`d8715000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\libpng16.dll -ModLoad: 00007ffc`e89a0000 00007ffc`e89b1000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlidec.dll -ModLoad: 00007ffc`c9ca0000 00007ffc`c9cd5000 C:\WINDOWS\SYSTEM32\VCOMP140.DLL -ModLoad: 00007ffc`cc730000 00007ffc`cc757000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\brotlicommon.dll -ModLoad: 00007ffd`134e0000 00007ffd`134f4000 C:\WINDOWS\SYSTEM32\UMPDC.dll -ModLoad: 00007ffd`15950000 00007ffd`15982000 C:\WINDOWS\System32\IMM32.DLL -ModLoad: 00007ffd`12360000 00007ffd`1237b000 C:\WINDOWS\SYSTEM32\kernel.appcore.dll -ModLoad: 00007ffd`13b40000 00007ffd`13bec000 C:\WINDOWS\System32\bcryptPrimitives.dll -ModLoad: 00007ffd`101b0000 00007ffd`10259000 C:\WINDOWS\system32\uxtheme.dll -[INFO] : 4 info text -[WARNING] : 4 warning text -[DEBUG] : 4 debug text -[ERROR] : 4 error text -[INFO] : 4 EVENT: start time: 4 -ModLoad: 00007ffc`c2380000 00007ffc`c242d000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.interposer.dll -ModLoad: 00007ffd`10c10000 00007ffd`10e52000 C:\WINDOWS\SYSTEM32\dbghelp.dll -ModLoad: 00007ffd`157c0000 00007ffd`15899000 C:\WINDOWS\System32\OLEAUT32.dll -ModLoad: 00007ffd`10e60000 00007ffd`10e9b000 C:\WINDOWS\SYSTEM32\dbgcore.DLL -ModLoad: 00007ffc`810f0000 00007ffc`811d0000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.common.dll -[INFO] : 10 [Streamline] [16-59-53][streamline][warn][tid:23508][0s:002ms:469us]sl.cpp:228[operator ()] Seems like some DX/VK APIs were invoked before slInit()!!! This may result in incorrect behaviour. - -[INFO] : 10 [Streamline] [16-59-53][streamline][info][tid:23508][0s:002ms:705us]pluginManager.cpp:423[setHostSDKVersion] Streamline v2.12.0.17026aaf - built on Mon Jun 22 13:01:47 2026 - host SDK v2.12.0 - -[INFO] : 10 [Streamline] [16-59-53][streamline][info][tid:23508][0s:002ms:837us]pluginManager.cpp:538[findPlugins] Looking for plugins in C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline ... - -[INFO] : 10 [Streamline] [16-59-53][streamline][info][tid:23508][0s:002ms:956us]pluginManager.cpp:1027[loadPlugins] SL_PRODUCTION not defined at build-time, OTA'd plugins will not be loaded! - -ModLoad: 00007ffd`0cce0000 00007ffd`0cceb000 C:\WINDOWS\SYSTEM32\VERSION.dll -[INFO] : 12 [Streamline] [16-59-53][streamline][info][tid:23508][0s:004ms:767us]commonInterface.cpp:168[getSystemCaps] Enumerating up to 8 adapters but only one of them can be used to create a device - no mGPU support in this SDK - -ModLoad: 00007ffd`10290000 00007ffd`102d6000 C:\WINDOWS\SYSTEM32\dxcore.dll -ModLoad: 00007ffd`107d0000 00007ffd`107e4000 C:\WINDOWS\SYSTEM32\resourcepolicyclient.dll -ModLoad: 00007ffd`0dc20000 00007ffd`0dc7f000 C:\WINDOWS\SYSTEM32\directxdatabasehelper.dll -ModLoad: 00007ffd`131e0000 00007ffd`13231000 C:\WINDOWS\SYSTEM32\cfgmgr32.dll -ModLoad: 00007ffc`c9670000 00007ffc`c96aa000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffc`c9670000 00007ffc`c96aa000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdlistx.dll -ModLoad: 00007ffd`12d60000 00007ffd`12d73000 C:\WINDOWS\SYSTEM32\msasn1.dll -ModLoad: 00007ffd`0cca0000 00007ffd`0ccdb000 C:\WINDOWS\SYSTEM32\cryptnet.dll -ModLoad: 00007ffd`13c00000 00007ffd`13d78000 C:\WINDOWS\System32\CRYPT32.dll -ModLoad: 00007ffd`0ca90000 00007ffd`0cc09000 C:\WINDOWS\SYSTEM32\drvstore.dll -ModLoad: 00007ffd`131a0000 00007ffd`131cf000 C:\WINDOWS\SYSTEM32\devobj.dll -ModLoad: 00007ffd`12cf0000 00007ffd`12d5a000 C:\WINDOWS\SYSTEM32\wldp.dll -ModLoad: 00007ffd`12c30000 00007ffd`12c3c000 C:\WINDOWS\SYSTEM32\cryptbase.dll -ModLoad: 00007ffd`07e20000 00007ffd`07ea9000 C:\WINDOWS\SYSTEM32\nvapi64.dll -ModLoad: 00007ffd`11130000 00007ffd`119c2000 C:\WINDOWS\SYSTEM32\windows.storage.dll -ModLoad: 00007ffd`078a0000 00007ffd`07e17000 C:\WINDOWS\system32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvapi64_impl.dll -ModLoad: 00007ffd`15990000 00007ffd`15e17000 C:\WINDOWS\System32\SETUPAPI.dll -[INFO] : 54 [Streamline] [16-59-54][streamline][info][tid:23508][0s:046ms:909us]commonInterface.cpp:290[getSystemCaps] >----------------------------------------- - -[INFO] : 54 [Streamline] [16-59-54][streamline][info][tid:23508][0s:047ms:195us]commonInterface.cpp:293[getSystemCaps] NVIDIA driver 610.74 - -[INFO] : 1387 [Streamline] [16-59-55][streamline][info][tid:23508][1s:380ms:039us]commonInterface.cpp:318[getSystemCaps] Adapter 0 architecture 0x170 implementation 0x6 revision 0xa1 - bit 0x2 - LUID 0.64955 - -[INFO] : 1387 [Streamline] [16-59-55][streamline][info][tid:23508][1s:380ms:284us]commonInterface.cpp:324[getSystemCaps] -----------------------------------------< - -[INFO] : 1388 [Streamline] [16-59-55][streamline][info][tid:23508][1s:381ms:373us]commonEntry.cpp:1944[getOSVersionAndUpdateTimerResolution] Changed high resolution timer resolution to 4988 [100 ns units]. - -[INFO] : 1389 [Streamline] [16-59-55][streamline][info][tid:23508][1s:381ms:607us]commonEntry.cpp:2023[updateEmbeddedJSON] Detected Windows OS version 10.0.26200 - -[INFO] : 1389 [Streamline] [16-59-55][streamline][info][tid:23508][1s:382ms:243us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.common' - version 2.12.0.17026aaf DEVELOPMENT - id 4294967295 - priority 0 - adapter mask 0x3 - interposer 'no' - -ModLoad: 00007ffc`bdc50000 00007ffc`bdcbb000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.dlss.dll -ModLoad: 00007ffd`14100000 00007ffd`14183000 C:\WINDOWS\System32\wintrust.dll -ModLoad: 00007ffd`161d0000 00007ffd`161f0000 C:\WINDOWS\System32\imagehlp.dll -ModLoad: 00007ffd`12c10000 00007ffd`12c2b000 C:\WINDOWS\SYSTEM32\CRYPTSP.dll -ModLoad: 00007ffd`122c0000 00007ffd`122f9000 C:\WINDOWS\system32\rsaenh.dll -ModLoad: 00007ffd`13570000 00007ffd`1359a000 C:\WINDOWS\SYSTEM32\bcrypt.dll -ModLoad: 00007ffd`12990000 00007ffd`129bf000 C:\WINDOWS\SYSTEM32\gpapi.dll -ModLoad: 00007ffd`135a0000 00007ffd`135c9000 C:\WINDOWS\SYSTEM32\profapi.dll -ModLoad: 00007ffc`80f70000 00007ffc`810e3000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\_nvngx.dll -ModLoad: 00007ffd`12490000 00007ffd`124c6000 C:\WINDOWS\SYSTEM32\ntmarta.dll -ModLoad: 00007ffb`905c0000 00007ffb`9485b000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlss.dll -ModLoad: 00007ffb`8de20000 00007ffb`905b8000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\nvngx_dlssd.dll -[INFO] : 30887 [Streamline] [17-00-24][streamline][info][tid:23508][30s:879ms:581us]commonEntry.cpp:999[getNGXFeatureRequirements] NGX feature 1 requirements - minOS 10.0.0 minHW 0x160 - -[INFO] : 30889 [Streamline] [17-00-24][streamline][info][tid:23508][30s:882ms:335us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.dlss' - version 2.12.0.17026aaf DEVELOPMENT - id 0 - priority 100 - adapter mask 0x2 - interposer 'no' - -ModLoad: 00007ffc`8cfb0000 00007ffc`8d01f000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.dlss_d.dll -[INFO] : 30920 [Streamline] [17-00-24][streamline][info][tid:23508][30s:911ms:961us]commonEntry.cpp:999[getNGXFeatureRequirements] NGX feature 13 requirements - minOS 10.0.0 minHW 0x160 - -[INFO] : 30925 [Streamline] [17-00-24][streamline][info][tid:23508][30s:917ms:464us]pluginManager.cpp:883[mapPlugins] Loaded plugin 'sl.dlss_d' - version 2.12.0.17026aaf DEVELOPMENT - id 1001 - priority 100 - adapter mask 0x2 - interposer 'no' - -ModLoad: 00007ffc`7d880000 00007ffc`7d9d2000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline\sl.imgui.dll -ModLoad: 00007ffc`80ea0000 00007ffc`80f66000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\vulkan-1.dll -ModLoad: 00007ffd`0e5a0000 00007ffd`0ea1f000 C:\WINDOWS\SYSTEM32\D3DCOMPILER_47.dll -ModLoad: 00007ffc`d2dc0000 00007ffc`d2ddb000 C:\WINDOWS\SYSTEM32\XINPUT1_4.dll -ModLoad: 00007ffc`f16b0000 00007ffc`f1894000 C:\WINDOWS\SYSTEM32\inputhost.dll -ModLoad: 00007ffd`0fca0000 00007ffd`0fdc5000 C:\WINDOWS\SYSTEM32\CoreMessaging.dll -[INFO] : 30956 [Streamline] [17-00-24][streamline][warn][tid:23508][30s:948ms:099us]pluginManager.cpp:780[mapPlugins] Ignoring plugin 'sl.imgui' since it is was not requested by the host - -[INFO] : 30963 [Streamline] initialized -[INFO] : 30963 [Streamline] [17-00-24][streamline][info][tid:23508][30s:956ms:270us]pluginManager.cpp:1159[loadPlugins] Plugin execution order based on priority: - -[INFO] : 30964 [Streamline] [17-00-24][streamline][info][tid:23508][30s:956ms:614us]pluginManager.cpp:1162[loadPlugins] P0 - sl.common - -[INFO] : 30964 [Streamline] [17-00-24][streamline][info][tid:23508][30s:956ms:802us]pluginManager.cpp:1162[loadPlugins] P100 - sl.dlss - -ModLoad: 00007ffc`c1460000 00007ffc`c17b7000 C:\WINDOWS\SYSTEM32\D3D12Core.dll -[INFO] : 30964 [Streamline] [17-00-24][streamline][info][tid:23508][30s:957ms:046us]pluginManager.cpp:1162[loadPlugins] P100 - sl.dlss_d - -ModLoad: 00007ffc`cb6a0000 00007ffc`cb6c5000 C:\WINDOWS\SYSTEM32\DXGIDebug.dll -ModLoad: 00007ffb`a5cb0000 00007ffb`a6150000 C:\WINDOWS\SYSTEM32\D3D12SDKLayers.dll -[INFO] : 30970 // Adapter: NVIDIA GeForce RTX 3060 Laptop GPU -[INFO] : 30971 adapter: NVIDIA GeForce RTX 3060 Laptop GPU -ModLoad: 00007ffc`cb6a0000 00007ffc`cb6c5000 C:\WINDOWS\SYSTEM32\DXGIDebug.dll -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`7d8a0000 00007ffc`7d9db000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffb`b9c50000 00007ffb`b9db4000 C:\WINDOWS\system32\nvspcap64.dll -ModLoad: 00007ffb`a5cb0000 00007ffb`a6150000 C:\WINDOWS\SYSTEM32\D3D12SDKLayers.dll -ModLoad: 00007ffb`a8dc0000 00007ffb`a94e7000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvppex.dll -ModLoad: 00007ffd`021a0000 00007ffd`0227f000 C:\WINDOWS\SYSTEM32\D3DSCache.dll -ModLoad: 00007ffd`129d0000 00007ffd`12a02000 C:\WINDOWS\SYSTEM32\USERENV.dll -ModLoad: 00007ffc`5df00000 00007ffc`5e064000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 33714 // Adapter: AMD Radeon(TM) Graphics -[INFO] : 33714 // Output: \\.\DISPLAY1 -[INFO] : 33714 adapter: AMD Radeon(TM) Graphics -ModLoad: 00007ffc`1e9e0000 00007ffc`22fbb000 C:\WINDOWS\System32\DriverStore\FileRepository\u0405470.inf_amd64_2e71ce0e27c179e1\B404884\amdxc64.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffd`027a0000 00007ffd`027d5000 C:\WINDOWS\SYSTEM32\amdihk64.dll -[INFO] : 33755 // Adapter: Microsoft Basic Render Driver -[INFO] : 33755 adapter: Microsoft Basic Render Driver -ModLoad: 00007ffb`a6750000 00007ffb`a6cfe000 C:\WINDOWS\SYSTEM32\d3d10warp.dll -[INFO] : 33760 Selected adapter: NVIDIA GeForce RTX 3060 Laptop GPU -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`5df30000 00007ffc`5e06b000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffc`7d870000 00007ffc`7d9d4000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 35564 DLSS: super-resolution=yes ray-reconstruction=yes -ModLoad: 00007ffc`80ea0000 00007ffc`80f6a000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvldumdx.dll -ModLoad: 00007ffb`53ca0000 00007ffb`59aae000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvgpucomp64.dll -ModLoad: 00007ffc`7d6c0000 00007ffc`7d7fb000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\NvMemMapStoragex.dll -ModLoad: 00007ffd`10ea0000 00007ffd`10ed6000 C:\WINDOWS\SYSTEM32\WINMM.dll -ModLoad: 00007ffb`4e6f0000 00007ffb`53c96000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvwgf2umx.dll -ModLoad: 00007ffc`7d870000 00007ffc`7d9d4000 C:\WINDOWS\SYSTEM32\dxilconv.dll -[INFO] : 37085 [Streamline] [17-00-31][streamline][info][tid:23508][37s:077ms:549us]pluginManager.cpp:1362[initializePlugins] Initializing plugins - api 0.0.1 - application ID 100721531 - -[INFO] : 37087 [Streamline] [17-00-31][streamline][info][tid:23508][37s:079ms:627us]commonEntry.cpp:1467[slOnPluginStartup] At least one plugin requires NGX, trying to initialize ... - -[INFO] : 37087 [Streamline] [17-00-31][streamline][info][tid:23508][37s:080ms:361us]commonEntry.cpp:1267[ngxLog] [Unknown(32764)] [2026-08-14 17:00:31] [NGXSafeInitializeLog:148] App logging hooks successfully initialized - - -[INFO] : 37090 [Streamline] [17-00-31][streamline][info][tid:23508][37s:081ms:457us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXSafeInitializeLog:139] App logging hooks successfully initialized - - -[INFO] : 37100 [Streamline] [17-00-31][streamline][info][tid:23508][37s:091ms:972us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXGetPathUsingQAI:512] Path to driverStore found using QAI: C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d - - -[INFO] : 37100 [Streamline] [17-00-31][streamline][info][tid:23508][37s:093ms:369us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXGetPath:1045] using path for models: C:\ProgramData\NVIDIA\NGX\models - - -[INFO] : 37179 [Streamline] [17-00-31][streamline][info][tid:23508][37s:172ms:092us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [ModifyExistingFolderDACL:211] updated access control list for NGX cache (NGX cache should now be usable by all authenticated users) - - -[INFO] : 37180 [Streamline] [17-00-31][streamline][info][tid:23508][37s:172ms:719us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXCreateModelsFolder:1072] model folder created: C:\ProgramData\NVIDIA\NGX\models - - -[INFO] : 37181 [Streamline] [17-00-31][streamline][info][tid:23508][37s:173ms:199us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [dlss] - - -[INFO] : 37181 [Streamline] [17-00-31][streamline][info][tid:23508][37s:173ms:926us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9FEB50=1.0.108 - - -[INFO] : 37182 [Streamline] [17-00-31][streamline][info][tid:23508][37s:174ms:737us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_8661E24=2.3.0 - - -[INFO] : 37183 [Streamline] [17-00-31][streamline][info][tid:23508][37s:175ms:582us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9FF4BC=1.0.108 - - -[INFO] : 37185 [Streamline] [17-00-31][streamline][info][tid:23508][37s:176ms:966us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9FD524=1.0.108 - - -[INFO] : 37192 [Streamline] [17-00-31][streamline][info][tid:23508][37s:180ms:258us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DF510=1.3.106 - - -[INFO] : 37195 [Streamline] [17-00-31][streamline][info][tid:23508][37s:187ms:309us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9CB0B8=1.0.108 - - -[INFO] : 37196 [Streamline] [17-00-31][streamline][info][tid:23508][37s:188ms:385us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9E26B0=2.1.28 - - -[INFO] : 37200 [Streamline] [17-00-31][streamline][info][tid:23508][37s:191ms:413us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D48D0=1.0.108 - - -[INFO] : 37203 [Streamline] [17-00-31][streamline][info][tid:23508][37s:195ms:349us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E99B5EC=1.2.109 - - -[INFO] : 37205 [Streamline] [17-00-31][streamline][info][tid:23508][37s:197ms:325us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DFA64=1.0.108 - - -[INFO] : 37208 [Streamline] [17-00-31][streamline][info][tid:23508][37s:199ms:700us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9FD6C0=1.0.108 - - -[INFO] : 37211 [Streamline] [17-00-31][streamline][info][tid:23508][37s:202ms:594us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9CF688=2.2.15 - - -[INFO] : 37212 [Streamline] [17-00-31][streamline][info][tid:23508][37s:204ms:750us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D6F08=1.0.108 - - -[INFO] : 37212 [Streamline] [17-00-31][streamline][info][tid:23508][37s:204ms:941us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9BF564=2.1.201 - - -[INFO] : 37213 [Streamline] [17-00-31][streamline][info][tid:23508][37s:205ms:223us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DB4F4=1.0.108 - - -[INFO] : 37213 [Streamline] [17-00-31][streamline][info][tid:23508][37s:205ms:773us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D2F5C=1.2.110 - - -[INFO] : 37213 [Streamline] [17-00-31][streamline][info][tid:23508][37s:205ms:937us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D7388=1.0.108 - - -[INFO] : 37213 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:094us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9F5618=2.1.29 - - -[INFO] : 37213 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:279us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DAE68=1.2.109 - - -[INFO] : 37214 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:437us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D8C54=1.2.109 - - -[INFO] : 37214 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:665us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_3AC09EF=2.2.11 - - -[INFO] : 37214 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:823us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D3EF0=2.1.28 - - -[INFO] : 37214 [Streamline] [17-00-31][streamline][info][tid:23508][37s:206ms:979us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9FACDC=2.1.201 - - -[INFO] : 37215 [Streamline] [17-00-31][streamline][info][tid:23508][37s:207ms:194us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_F7361DC=2.1.28 - - -[INFO] : 37221 [Streamline] [17-00-31][streamline][info][tid:23508][37s:212ms:173us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9C3560=2.1.201 - - -[INFO] : 37223 [Streamline] [17-00-31][streamline][info][tid:23508][37s:214ms:083us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9B0430=2.1.28 - - -[INFO] : 37229 [Streamline] [17-00-31][streamline][info][tid:23508][37s:221ms:505us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658703=0.0.0 - - -[INFO] : 37235 [Streamline] [17-00-31][streamline][info][tid:23508][37s:225ms:103us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_87211B8=2.4.12 - - -[INFO] : 37241 [Streamline] [17-00-31][streamline][info][tid:23508][37s:233ms:701us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658702=0.0.0 - - -[INFO] : 37420 [Streamline] [17-00-31][streamline][info][tid:23508][37s:339ms:289us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658701=0.0.0 - - -[INFO] : 37647 [Streamline] [17-00-31][streamline][info][tid:23508][37s:543ms:413us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_87D97FC=2.4.12 - - -[INFO] : 37769 [Streamline] [17-00-31][streamline][info][tid:23508][37s:702ms:700us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_870691C=2.5.1 - - -[INFO] : 37839 [Streamline] [17-00-31][streamline][info][tid:23508][37s:821ms:170us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_EC0E344=2.4.0 - - -[INFO] : 37846 [Streamline] [17-00-31][streamline][info][tid:23508][37s:837ms:764us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_86AA7B4=2.3.1 - - -[INFO] : 37853 [Streamline] [17-00-31][streamline][info][tid:23508][37s:839ms:387us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B98BB3C=2.4.0 - - -[INFO] : 38162 [Streamline] [17-00-32][streamline][info][tid:23508][38s:081ms:154us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_8624CC0=2.3.11 - - -[INFO] : 38186 [Streamline] [17-00-32][streamline][info][tid:23508][38s:165ms:793us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_863B354=3.1.1 - - -[INFO] : 38289 [Streamline] [17-00-32][streamline][info][tid:23508][38s:226ms:294us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_8656478=3.1.11 - - -[INFO] : 38509 [Streamline] [17-00-32][streamline][info][tid:23508][38s:441ms:744us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_876232C=2.3.1 - - -[INFO] : 38646 [Streamline] [17-00-32][streamline][info][tid:23508][38s:569ms:255us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_86BBD3C=3.5.0 - - -[INFO] : 38681 [Streamline] [17-00-32][streamline][info][tid:23508][38s:667ms:973us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658700=310.6.0 - - -[INFO] : 38728 [Streamline] [17-00-32][streamline][info][tid:23508][38s:682ms:408us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [dlisp] - - -[INFO] : 38798 [Streamline] [17-00-32][streamline][info][tid:23508][38s:767ms:496us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9CF688=2.1.15 - - -[INFO] : 38811 [Streamline] [17-00-32][streamline][info][tid:23508][38s:803ms:513us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E99B5EC=1.2.102 - - -[INFO] : 38894 [Streamline] [17-00-32][streamline][info][tid:23508][38s:805ms:078us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658703=310.0.0 - - -[INFO] : 39026 [Streamline] [17-00-32][streamline][info][tid:23508][38s:947ms:761us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D8C54=1.2.106 - - -[INFO] : 39243 [Streamline] [17-00-33][streamline][info][tid:23508][39s:137ms:238us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9D2F5C=1.2.106 - - -[INFO] : 39412 [Streamline] [17-00-33][streamline][info][tid:23508][39s:310ms:550us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DF510=1.3.101 - - -[INFO] : 39531 [Streamline] [17-00-33][streamline][info][tid:23508][39s:486ms:034us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_B9DAE68=1.2.106 - - -[INFO] : 39583 [Streamline] [17-00-33][streamline][info][tid:23508][39s:554ms:883us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [dlssg] - - -[INFO] : 39648 [Streamline] [17-00-33][streamline][info][tid:23508][39s:633ms:722us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_86AA7B4=0.0.0 - - -[INFO] : 39685 [Streamline] [17-00-33][streamline][info][tid:23508][39s:676ms:099us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_863B354=3.1.13 - - -[INFO] : 39708 [Streamline] [17-00-33][streamline][info][tid:23508][39s:689ms:532us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_87D97FC=1.0.2 - - -[INFO] : 39712 [Streamline] [17-00-33][streamline][info][tid:23508][39s:704ms:668us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_8656478=3.1.10 - - -[INFO] : 39713 [Streamline] [17-00-33][streamline][info][tid:23508][39s:705ms:515us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_nrd_0] - - -[INFO] : 39713 [Streamline] [17-00-33][streamline][info][tid:23508][39s:706ms:077us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_dlss_d_0] - - -[INFO] : 39714 [Streamline] [17-00-33][streamline][info][tid:23508][39s:706ms:725us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1150] app_E658703=2.11.0 - - -[INFO] : 39715 [Streamline] [17-00-33][streamline][info][tid:23508][39s:707ms:325us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_common_0] - - -[INFO] : 39716 [Streamline] [17-00-33][streamline][info][tid:23508][39s:708ms:213us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_dlss_0] - - -[INFO] : 39716 [Streamline] [17-00-33][streamline][info][tid:23508][39s:708ms:976us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_pcl_0] - - -[INFO] : 39717 [Streamline] [17-00-33][streamline][info][tid:23508][39s:709ms:971us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_dlss_g_0] - - -[INFO] : 39718 [Streamline] [17-00-33][streamline][info][tid:23508][39s:710ms:514us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_reflex_0] - - -[INFO] : 39718 [Streamline] [17-00-33][streamline][info][tid:23508][39s:711ms:199us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [deepdvc] - - -[INFO] : 39720 [Streamline] [17-00-33][streamline][info][tid:23508][39s:712ms:322us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_sdk_0] - - -[INFO] : 39720 [Streamline] [17-00-33][streamline][info][tid:23508][39s:713ms:085us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_deepdvc_0] - - -[INFO] : 39721 [Streamline] [17-00-33][streamline][info][tid:23508][39s:713ms:956us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_nis_0] - - -[INFO] : 39722 [Streamline] [17-00-33][streamline][info][tid:23508][39s:714ms:694us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [sl_nvperf_0] - - -[INFO] : 39723 [Streamline] [17-00-33][streamline][info][tid:23508][39s:715ms:496us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [dlss_override] - - -[INFO] : 39723 [Streamline] [17-00-33][streamline][info][tid:23508][39s:716ms:096us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [NGXLoadConfig:1144] [dlssd] - - -[INFO] : 39724 [Streamline] [17-00-33][streamline][info][tid:23508][39s:716ms:960us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 86aa7b4 - - -[INFO] : 39725 [Streamline] [17-00-33][streamline][info][tid:23508][39s:717ms:571us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [`anonymous-namespace'::LoadMappingFileData:282] project id 3F9D696D4363312194B0ECB2671E899F cms id B9FBD50 - - -[INFO] : 39726 [Streamline] [17-00-33][streamline][info][tid:23508][39s:718ms:160us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 8618954 - - -[INFO] : 39726 [Streamline] [17-00-33][streamline][info][tid:23508][39s:718ms:889us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId b9b05cc - - -[INFO] : 39727 [Streamline] [17-00-33][streamline][info][tid:23508][39s:719ms:554us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [`anonymous-namespace'::LoadMappingFileData:268] listItem.engineVersion .* listItem.genericCMSId 876232c - - -[INFO] : 39727 [Streamline] [17-00-33][streamline][info][tid:23508][39s:719ms:875us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:31] [MapProjectId:1878] Found cms id 876232c for engine: custom engineVersion 1.0 projectID b7e4f1a2-9c3d-4e58-8f10-2a6b5d9c7e34 - - -[INFO] : 40279 [Streamline] [17-00-34][streamline][info][tid:23508][40s:272ms:121us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:34] [NGXDRSInit:182] NvAPI_DRS_FindApplicationByName -166 - - -[INFO] : 41362 [Streamline] [17-00-35][streamline][info][tid:23508][41s:354ms:930us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [NGXInitContext:245] called from module sl.common.dll at C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline - - -[INFO] : 41363 [Streamline] [17-00-35][streamline][info][tid:23508][41s:355ms:406us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [`anonymous-namespace'::SnippetLocationInfo::load:713] NGXLoadFromPath failed: -1160773628 - - -[INFO] : 41363 [Streamline] [17-00-35][streamline][info][tid:23508][41s:355ms:837us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [NGXReportStatusFeedback:1076] Override shared memory was opened successfully - - -[INFO] : 41363 [Streamline] [17-00-35][streamline][info][tid:23508][41s:356ms:038us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [NGXReportStatusFeedback:1088] Override shared memory was mapped successfully - - -[INFO] : 41487 [Streamline] [17-00-35][streamline][info][tid:23508][41s:479ms:958us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [NGXSecureLoadFeature:1345] Feature dlss failed to load (cmsid 141959980) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlss\versions\131841\files, name: 160_876232C.bin) - - -[INFO] : 41487 [Streamline] [17-00-35][streamline][info][tid:23508][41s:480ms:297us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:35] [NGXSecureLoadFeature:1345] Feature dlss failed to load (cmsid 241534723) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlss\versions\0\files, name: 160_E658703.bin) - - -[INFO] : 46880 [Streamline] [17-00-40][streamline][info][tid:23508][46s:872ms:886us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:40] [`anonymous-namespace'::SnippetLocationInfo::load:713] NGXLoadFromPath failed: -1160773628 - - -[INFO] : 46880 [Streamline] [17-00-40][streamline][info][tid:23508][46s:873ms:235us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:40] [NGXSecureLoadFeature:1459] app 876232C feature dlss snippet: C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline/nvngx_dlss.dll version: 310.7.0 - - -[INFO] : 46976 [Streamline] [17-00-40][streamline][info][tid:23508][46s:968ms:767us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:40] [NGXSecureLoadFeature:1345] Feature dlssd failed to load (cmsid 141959980) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlssd\versions\0\files, name: 160_876232C.bin) - - -[INFO] : 46976 [Streamline] [17-00-40][streamline][info][tid:23508][46s:969ms:238us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:40] [NGXSecureLoadFeature:1345] Feature dlssd failed to load (cmsid 241534723) from cache (location: C:\ProgramData\NVIDIA\NGX\models\dlssd\versions\0\files, name: 160_E658703.bin) - - -[INFO] : 54146 [Streamline] [17-00-48][streamline][info][tid:23508][54s:139ms:048us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:48] [`anonymous-namespace'::SnippetLocationInfo::load:713] NGXLoadFromPath failed: -1160773628 - - -[INFO] : 54146 [Streamline] [17-00-48][streamline][info][tid:23508][54s:139ms:340us]commonEntry.cpp:1267[ngxLog] [Unknown(32765)] [2026-08-14 17:00:48] [NGXSecureLoadFeature:1459] app 876232C feature dlssd snippet: C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\Streamline/nvngx_dlssd.dll version: 310.7.0 - - -ModLoad: 00007ffc`fcce0000 00007ffc`fd18b000 C:\Program Files\NVIDIA Corporation\NvTelemetry\NvTelemetryAPI64.dll -ModLoad: 00007ffd`15630000 00007ffd`156b0000 C:\WINDOWS\System32\WS2_32.dll -ModLoad: 00007ffd`11d20000 00007ffd`11d53000 C:\WINDOWS\SYSTEM32\IPHLPAPI.DLL -[INFO] : 59724 [Streamline] [17-00-53][streamline][info][tid:23508][59s:716ms:780us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:53] [tid:23508][NGXInitLog:232] App logging hooks successfully initialized - - -[INFO] : 59724 [Streamline] [17-00-53][streamline][info][tid:23508][59s:717ms:305us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:53] [tid:23508][NGXInitLog:238] Build v310.7.0 CL 37997616 - - -[INFO] : 59725 [Streamline] [17-00-53][streamline][info][tid:23508][59s:717ms:785us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:53] [tid:23508][NGXInitLog:240] Built with APP_NAME = default - - -[INFO] : 59725 [Streamline] [17-00-53][streamline][info][tid:23508][59s:718ms:150us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:53] [tid:23508][NGXInitLog:246] Loaded from path "C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug/spectrum.exe" - - -[INFO] : 59731 [Streamline] [17-00-53][streamline][info][tid:23508][59s:723ms:688us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:53] [tid:23508][NGXCubinD3D12::Init:135] Enabling texmode_raw - - -[INFO] : 61226 [Streamline] [17-00-55][streamline][info][tid:23508][61s:219ms:058us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][NGXCubinKernelMap::InitCubins:42] Loading NGXCubin kernels - - -[INFO] : 61227 [Streamline] [17-00-55][streamline][info][tid:23508][61s:220ms:237us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][NGXCubinGeneric::SetGPUArch:400] SetGPUArch:: Gpu count = 1, luid: 0xfdbb - - -[INFO] : 61232 [Streamline] [17-00-55][streamline][info][tid:23508][61s:225ms:001us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][NGXCubinGeneric::SetGPUArch:460] m_gpuArch = 0x170 - - -[INFO] : 61235 [Streamline] [17-00-55][streamline][info][tid:23508][61s:228ms:072us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][NGXCubinGeneric::genericPostInit:164] Fast UAV clear: supported - - -[INFO] : 61235 [Streamline] [17-00-55][streamline][info][tid:23508][61s:228ms:404us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DLSSCubinKernelMap::InitCubins:303] Setting DLAA Cubins - - -[INFO] : 61236 [Streamline] [17-00-55][streamline][info][tid:23508][61s:228ms:567us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DLSSCubinKernelMap::InitCubins:303] Setting DLSS Debug Cubins - - -[INFO] : 61236 [Streamline] [17-00-55][streamline][info][tid:23508][61s:228ms:768us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DLSSCubinKernelMap::InitCubins:303] Setting DLTSS Engine Cubins - - -[INFO] : 61236 [Streamline] [17-00-55][streamline][info][tid:23508][61s:228ms:998us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DLSSCubinKernelMap::InitCubins:303] Setting DLTSS NW Cubins - - -[INFO] : 61236 [Streamline] [17-00-55][streamline][info][tid:23508][61s:229ms:227us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DLSSCubinKernelMap::InitCubins:303] Setting DLTSS NW E5M3_SKIP Cubins - - -[INFO] : 61237 [Streamline] [17-00-55][streamline][info][tid:23508][61s:229ms:519us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DldnCubinKernelMap::InitCubins:138] Setting DLDN Debug Cubins - - -[INFO] : 61237 [Streamline] [17-00-55][streamline][info][tid:23508][61s:229ms:683us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:55] [tid:23508][DldnCubinKernelMap::InitCubins:138] Setting DLDN Engine Pre / Post Cubins - - -ModLoad: 00007ffb`9a1a0000 00007ffb`9c07f000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvcuda64.dll -ModLoad: 00007ffc`88d40000 00007ffc`88def000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdxgdmal64.dll -ModLoad: 00007ffb`954b0000 00007ffb`962b2000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvobjectloader64.dll -ModLoad: 00007ffc`e6040000 00007ffc`e60ab000 C:\Program Files\NVIDIA Corporation\NvTelemetry\NvTelemetryBridge64.dll -[INFO] : 63117 [Streamline] [17-00-57][streamline][info][tid:23508][63s:110ms:168us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXInitLog:232] App logging hooks successfully initialized - - -[INFO] : 63118 [Streamline] [17-00-57][streamline][info][tid:23508][63s:110ms:789us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXInitLog:238] Build v310.7.0 CL 37996128 - - -[INFO] : 63118 [Streamline] [17-00-57][streamline][info][tid:23508][63s:111ms:177us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXInitLog:240] Built with APP_NAME = app_transformer_dlssd - - -[INFO] : 63119 [Streamline] [17-00-57][streamline][info][tid:23508][63s:111ms:555us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXInitLog:246] Loaded from path "C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug/spectrum.exe" - - -[INFO] : 63122 [Streamline] [17-00-57][streamline][info][tid:23508][63s:114ms:548us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXCubinD3D12::Init:135] Enabling texmode_raw - - -[INFO] : 63122 [Streamline] [17-00-57][streamline][info][tid:23508][63s:114ms:917us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXCubinKernelMap::InitCubins:42] Loading NGXCubin kernels - - -[INFO] : 63124 [Streamline] [17-00-57][streamline][info][tid:23508][63s:116ms:776us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXCubinGeneric::SetGPUArch:400] SetGPUArch:: Gpu count = 1, luid: 0xfdbb - - -[INFO] : 63130 [Streamline] [17-00-57][streamline][info][tid:23508][63s:123ms:168us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXCubinGeneric::SetGPUArch:460] m_gpuArch = 0x170 - - -[INFO] : 63132 [Streamline] [17-00-57][streamline][info][tid:23508][63s:125ms:226us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][NGXCubinGeneric::genericPostInit:164] Fast UAV clear: supported - - -[INFO] : 63133 [Streamline] [17-00-57][streamline][info][tid:23508][63s:125ms:664us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][DLSSCubinKernelMap::InitCubins:208] Setting DLSS Base Cubins for standalone RR - - -[INFO] : 63133 [Streamline] [17-00-57][streamline][info][tid:23508][63s:125ms:849us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][DldnCubinKernelMap::InitCubins:156] Setting DLDN Debug Cubins - - -[INFO] : 63133 [Streamline] [17-00-57][streamline][info][tid:23508][63s:126ms:196us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][DldnCubinKernelMap::InitCubins:156] Setting DLDN Engine Pre / Post Cubins - - -[INFO] : 63133 [Streamline] [17-00-57][streamline][info][tid:23508][63s:126ms:417us]commonEntry.cpp:1267[ngxLog] [SuperSampling] [2026-08-14 17:00:57] [tid:23508][DldnCubinKernelMap::InitCubins:188] Setting DLDN Engine Pre / Post Cubins - - -[INFO] : 63284 [Streamline] [17-00-57][streamline][info][tid:23508][63s:262ms:517us]pluginManager.cpp:1468[mapPluginCallbacks] Callback sl.common:slSetData:0x0 - -[INFO] : 63286 [Streamline] [17-00-57][streamline][info][tid:23508][63s:278ms:581us]pluginManager.cpp:1469[mapPluginCallbacks] Callback sl.common:slGetData:0x0 - -[INFO] : 63288 [Streamline] [17-00-57][streamline][info][tid:23508][63s:280ms:819us]pluginManager.cpp:1470[mapPluginCallbacks] Callback sl.common:slAllocateResources:0x0 - -[INFO] : 63289 [Streamline] [17-00-57][streamline][info][tid:23508][63s:281ms:471us]pluginManager.cpp:1471[mapPluginCallbacks] Callback sl.common:slFreeResources:0x0 - -[INFO] : 63289 [Streamline] [17-00-57][streamline][info][tid:23508][63s:281ms:753us]pluginManager.cpp:1472[mapPluginCallbacks] Callback sl.common:slEvaluateFeature:0x7ffc8112a0c0 - -[INFO] : 63289 [Streamline] [17-00-57][streamline][info][tid:23508][63s:281ms:943us]pluginManager.cpp:1473[mapPluginCallbacks] Callback sl.common:slSetTag:0x7ffc81128ba0 - -[INFO] : 63289 [Streamline] [17-00-57][streamline][info][tid:23508][63s:282ms:102us]pluginManager.cpp:1474[mapPluginCallbacks] Callback sl.common:slSetTagForFrame:0x7ffc81128d10 - -[INFO] : 63290 [Streamline] [17-00-57][streamline][info][tid:23508][63s:282ms:416us]pluginManager.cpp:1475[mapPluginCallbacks] Callback sl.common:slSetConsts:0x7ffc81129ba0 - -[INFO] : 63290 [Streamline] [17-00-57][streamline][info][tid:23508][63s:282ms:891us]pluginManager.cpp:1235[processPluginHooks] Hook sl.common:slHookVkPresent:before - skipped - -[INFO] : 63290 [Streamline] [17-00-57][streamline][info][tid:23508][63s:283ms:213us]pluginManager.cpp:1235[processPluginHooks] Hook sl.common:slHookVkAfterPresent:after - skipped - -[INFO] : 63290 [Streamline] [17-00-57][streamline][info][tid:23508][63s:283ms:394us]pluginManager.cpp:1235[processPluginHooks] Hook sl.common:slHookVkCmdBindPipeline:after - skipped - -[INFO] : 63291 [Streamline] [17-00-57][streamline][info][tid:23508][63s:283ms:569us]pluginManager.cpp:1235[processPluginHooks] Hook sl.common:slHookVkCmdBindDescriptorSets:after - skipped - -[INFO] : 63291 [Streamline] [17-00-57][streamline][info][tid:23508][63s:283ms:916us]pluginManager.cpp:1235[processPluginHooks] Hook sl.common:slHookVkBeginCommandBuffer:after - skipped - -[INFO] : 63292 [Streamline] [17-00-57][streamline][info][tid:23508][63s:284ms:425us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookCreateSwapChain:before - OK - -[INFO] : 63293 [Streamline] [17-00-57][streamline][info][tid:23508][63s:285ms:957us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookCreateSwapChainForHwnd:before - OK - -[INFO] : 63293 [Streamline] [17-00-57][streamline][info][tid:23508][63s:286ms:241us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookCreateSwapChainForCoreWindow:before - OK - -[INFO] : 63294 [Streamline] [17-00-57][streamline][info][tid:23508][63s:286ms:570us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookResizeSwapChainPre:before - OK - -[INFO] : 63294 [Streamline] [17-00-57][streamline][info][tid:23508][63s:286ms:817us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookPresent:before - OK - -[INFO] : 63294 [Streamline] DLSS available=yes, Balanced@1920x1080 -> render 1114x626 -[INFO] : 63294 [Streamline] [17-00-57][streamline][info][tid:23508][63s:287ms:034us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookAfterPresent:after - OK - -[INFO] : 63294 D3D12 SETUP: message callback registered, cookie 1 -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:287ms:370us]pluginManager.cpp:1259[processPluginHooks] Hook sl.common:slHookPresent1:before - OK - -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:287ms:619us]pluginManager.cpp:1468[mapPluginCallbacks] Callback sl.dlss:slSetData:0x7ffcbdc8d700 - -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:287ms:782us]pluginManager.cpp:1469[mapPluginCallbacks] Callback sl.dlss:slGetData:0x7ffcbdc91f90 - -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:287ms:964us]pluginManager.cpp:1470[mapPluginCallbacks] Callback sl.dlss:slAllocateResources:0x7ffcbdc92720 - -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:288ms:110us]pluginManager.cpp:1471[mapPluginCallbacks] Callback sl.dlss:slFreeResources:0x7ffcbdc927c0 - -[INFO] : 63295 [Streamline] [17-00-57][streamline][info][tid:23508][63s:288ms:294us]pluginManager.cpp:1472[mapPluginCallbacks] Callback sl.dlss:slEvaluateFeature:0x0 - -ModLoad: 00007ffc`7d850000 00007ffc`7d9cf000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\DStorageCore.dll -[INFO] : 63296 [Streamline] [17-00-57][streamline][info][tid:23508][63s:288ms:489us]pluginManager.cpp:1473[mapPluginCallbacks] Callback sl.dlss:slSetTag:0x0 - -[INFO] : 63296 [Streamline] [17-00-57][streamline][info][tid:23508][63s:288ms:915us]pluginManager.cpp:1474[mapPluginCallbacks] Callback sl.dlss:slSetTagForFrame:0x0 - -[INFO] : 63296 [Streamline] [17-00-57][streamline][info][tid:23508][63s:289ms:129us]pluginManager.cpp:1475[mapPluginCallbacks] Callback sl.dlss:slSetConsts:0x0 - -[INFO] : 63296 [Streamline] [17-00-57][streamline][info][tid:23508][63s:289ms:277us]pluginManager.cpp:1220[processPluginHooks] Plugin 'sl.dlss' has no registered hooks - -[INFO] : 63296 [Streamline] [17-00-57][streamline][warn][tid:23508][63s:289ms:418us]d3d12.cpp:1339[createKernel] Kernel mvec.cs:main with hash 0xea3d97bc6893850f already created! - -[INFO] : 63297 [Streamline] [17-00-57][streamline][info][tid:23508][63s:289ms:588us]pluginManager.cpp:1468[mapPluginCallbacks] Callback sl.dlss_d:slSetData:0x7ffc8cfed720 - -[INFO] : 63297 [Streamline] [17-00-57][streamline][info][tid:23508][63s:290ms:399us]pluginManager.cpp:1469[mapPluginCallbacks] Callback sl.dlss_d:slGetData:0x7ffc8cff4d40 - -[INFO] : 63298 [Streamline] [17-00-57][streamline][info][tid:23508][63s:290ms:601us]pluginManager.cpp:1470[mapPluginCallbacks] Callback sl.dlss_d:slAllocateResources:0x7ffc8cff54d0 - -[INFO] : 63299 [Streamline] [17-00-57][streamline][info][tid:23508][63s:291ms:004us]pluginManager.cpp:1471[mapPluginCallbacks] Callback sl.dlss_d:slFreeResources:0x7ffc8cff5570 - -[INFO] : 63299 [Streamline] [17-00-57][streamline][info][tid:23508][63s:291ms:921us]pluginManager.cpp:1472[mapPluginCallbacks] Callback sl.dlss_d:slEvaluateFeature:0x0 - -[INFO] : 63299 [Streamline] [17-00-57][streamline][info][tid:23508][63s:292ms:230us]pluginManager.cpp:1473[mapPluginCallbacks] Callback sl.dlss_d:slSetTag:0x0 - -[INFO] : 63299 [Streamline] [17-00-57][streamline][info][tid:23508][63s:292ms:377us]pluginManager.cpp:1474[mapPluginCallbacks] Callback sl.dlss_d:slSetTagForFrame:0x0 - -[INFO] : 63300 [Streamline] [17-00-57][streamline][info][tid:23508][63s:292ms:514us]pluginManager.cpp:1475[mapPluginCallbacks] Callback sl.dlss_d:slSetConsts:0x0 - -[INFO] : 63300 [Streamline] [17-00-57][streamline][info][tid:23508][63s:292ms:643us]pluginManager.cpp:1220[processPluginHooks] Plugin 'sl.dlss_d' has no registered hooks - -ModLoad: 00007ffb`43fc0000 00007ffb`4e6eb000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvdxdlkernels.dll -[INFO] : 63765 DSTORAGE_COMPRESSION_SUPPORT_GPU_OPTIMIZED|DSTORAGE_COMPRESSION_SUPPORT_USES_COMPUTE_QUEUE|DSTORAGE_COMPRESSION_SUPPORT_USES_COPY_QUEUE -(ac14.92a4): C++ EH exception - code e06d7363 (first chance) -(ac14.92a4): C++ EH exception - code e06d7363 (first chance) -(ac14.ec8): C++ EH exception - code e06d7363 (first chance) -(ac14.ec8): C++ EH exception - code e06d7363 (first chance) -(ac14.92a4): C++ EH exception - code e06d7363 (first chance) -(ac14.92a4): C++ EH exception - code e06d7363 (first chance) -(ac14.ec8): C++ EH exception - code e06d7363 (first chance) -(ac14.ec8): C++ EH exception - code e06d7363 (first chance) -[INFO] : 64407 Current 1783980986 file: 1783980987 -ModLoad: 00007ffb`8ccc0000 00007ffb`8de12000 C:\Users\Bohdan\Documents\GitHub\Spectrum\bin\debug\dxcompiler.dll -[INFO] : 64983 Current 1783980986 file: 1783980987 -ModLoad: 00007ffb`40b50000 00007ffb`43fb4000 C:\WINDOWS\System32\DriverStore\FileRepository\nvami.inf_amd64_1a0e2bbd9919af1d\nvrtum64.dll -[INFO] : 65465 pso init:1519 -[DEBUG] : 65858 The system cannot find the path specified. -[DEBUG] : 65859 The system cannot find the path specified. -[INFO] : 66392 watching "shaders\\UniversalMaterial.hlsl" -[INFO] : 66403 watching "shaders\\UniversalMaterialRaytracing.hlsl" -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.9f1c): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -(ac14.abd4): C++ EH exception - code e06d7363 (first chance) -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\atlmfc.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\ObjectiveC.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\concurrency.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\cpp_rest.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\stl.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Data.Json.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Geolocation.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Devices.Sensors.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\Windows.Media.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\windows.natvis' -NatVis script unloaded from 'C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\Visualizers\winrt.natvis' diff --git a/workdir/pid.txt b/workdir/pid.txt deleted file mode 100644 index 10f71387..00000000 --- a/workdir/pid.txt +++ /dev/null @@ -1 +0,0 @@ -112372 diff --git a/workdir/shaders/GenerateMips.hlsl b/workdir/shaders/GenerateMips.hlsl index 6aaa7ef3..3af3e64b 100644 --- a/workdir/shaders/GenerateMips.hlsl +++ b/workdir/shaders/GenerateMips.hlsl @@ -17,25 +17,40 @@ #define NON_POWER_OF_TWO 0 #endif -static const RWTexture2D OutMip1 = GetMipMapping().GetOutMip(0); -static const RWTexture2D OutMip2 = GetMipMapping().GetOutMip(1); -static const RWTexture2D OutMip3 = GetMipMapping().GetOutMip(2); -static const RWTexture2D OutMip4 = GetMipMapping().GetOutMip(3); -static const Texture2D SrcMip = GetMipMapping().GetSrcMip(); +static const uint SrcMipLevel = 0;// GetMipMapping().GetSrcMipLevel(); // Texture level of source mip +static const uint NumMipLevels = GetMipMapping().GetNumMipLevels(); // Number of OutMips to write: [1, 4] +static const float2 TexelSize = GetMipMapping().GetTexelSize(); // 1.0 / OutMip1.Dimensions static const SamplerState BilinearClamp = linearClampSampler; -/* -RWTexture2D OutMip1 : register(u0); -RWTexture2D OutMip2 : register(u1); -RWTexture2D OutMip3 : register(u2); -RWTexture2D OutMip4 : register(u3); -Texture2D SrcMip : register(t0); -SamplerState BilinearClamp : register(s0);*/ +// ARRAY_SLICES builds the whole array (e.g. all six cube faces) in one +// dispatch, with the slice in Z; the plain build keeps the single-2D-texture +// path. Only the addressing differs, so the reduction below is shared. +#ifdef ARRAY_SLICES -static const uint SrcMipLevel = 0;// GetMipMapping().GetSrcMipLevel(); // Texture level of source mip -static const uint NumMipLevels = GetMipMapping().GetNumMipLevels(); // Number of OutMips to write: [1, 4] -static const float2 TexelSize = GetMipMapping().GetTexelSize(); // 1.0 / OutMip1.Dimensions +float4 SampleSrc(float2 uv, uint slice) +{ + return GetMipMapping().GetSrcMipArray().SampleLevel(BilinearClamp, float3(uv, slice), SrcMipLevel); +} + +void StoreMip(uint mip, uint2 pos, uint slice, float4 color) +{ + GetMipMapping().GetOutMipArray(mip)[uint3(pos, slice)] = color; +} + +#else + +float4 SampleSrc(float2 uv, uint slice) +{ + return GetMipMapping().GetSrcMip().SampleLevel(BilinearClamp, uv, SrcMipLevel); +} + +void StoreMip(uint mip, uint2 pos, uint slice, float4 color) +{ + GetMipMapping().GetOutMip(mip)[pos] = color; +} + +#endif // The reason for separating channels is to reduce bank conflicts in the @@ -87,36 +102,36 @@ void CS(uint GI : SV_GroupIndex, uint3 DTid : SV_DispatchThreadID) // have to take more source texture samples. #if NON_POWER_OF_TWO == 0 float2 UV = TexelSize * (DTid.xy + 0.5); - float4 Src1 = SrcMip.SampleLevel(BilinearClamp, UV, SrcMipLevel); + float4 Src1 = SampleSrc(UV, DTid.z); #elif NON_POWER_OF_TWO == 1 // > 2:1 in X dimension // Use 2 bilinear samples to guarantee we don't undersample when downsizing by more than 2x // horizontally. float2 UV1 = TexelSize * (DTid.xy + float2(0.25, 0.5)); float2 Off = TexelSize * float2(0.5, 0.0); - float4 Src1 = 0.5 * (SrcMip.SampleLevel(BilinearClamp, UV1, SrcMipLevel) + - SrcMip.SampleLevel(BilinearClamp, UV1 + Off, SrcMipLevel)); + float4 Src1 = 0.5 * (SampleSrc(UV1, DTid.z) + + SampleSrc(UV1 + Off, DTid.z)); #elif NON_POWER_OF_TWO == 2 // > 2:1 in Y dimension // Use 2 bilinear samples to guarantee we don't undersample when downsizing by more than 2x // vertically. float2 UV1 = TexelSize * (DTid.xy + float2(0.5, 0.25)); float2 Off = TexelSize * float2(0.0, 0.5); - float4 Src1 = 0.5 * (SrcMip.SampleLevel(BilinearClamp, UV1, SrcMipLevel) + - SrcMip.SampleLevel(BilinearClamp, UV1 + Off, SrcMipLevel)); + float4 Src1 = 0.5 * (SampleSrc(UV1, DTid.z) + + SampleSrc(UV1 + Off, DTid.z)); #elif NON_POWER_OF_TWO == 3 // > 2:1 in in both dimensions // Use 4 bilinear samples to guarantee we don't undersample when downsizing by more than 2x // in both directions. float2 UV1 = TexelSize * (DTid.xy + float2(0.25, 0.25)); float2 O = TexelSize * 0.5; - float4 Src1 = SrcMip.SampleLevel(BilinearClamp, UV1, SrcMipLevel); - Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(O.x, 0.0), SrcMipLevel); - Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(0.0, O.y), SrcMipLevel); - Src1 += SrcMip.SampleLevel(BilinearClamp, UV1 + float2(O.x, O.y), SrcMipLevel); + float4 Src1 = SampleSrc(UV1, DTid.z); + Src1 += SampleSrc(UV1 + float2(O.x, 0.0), DTid.z); + Src1 += SampleSrc(UV1 + float2(0.0, O.y), DTid.z); + Src1 += SampleSrc(UV1 + float2(O.x, O.y), DTid.z); Src1 *= 0.25; #endif - OutMip1[DTid.xy] = PackColor(Src1); + StoreMip(0, DTid.xy, DTid.z, PackColor(Src1)); // A scalar (constant) branch can exit all threads coherently. if (NumMipLevels == 1) @@ -138,7 +153,7 @@ void CS(uint GI : SV_GroupIndex, uint3 DTid : SV_DispatchThreadID) float4 Src3 = LoadColor(GI + 0x08); float4 Src4 = LoadColor(GI + 0x09); Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); - OutMip2[DTid.xy / 2] = PackColor(Src1); + StoreMip(1, DTid.xy / 2, DTid.z, PackColor(Src1)); StoreColor(GI, Src1); } @@ -154,7 +169,7 @@ void CS(uint GI : SV_GroupIndex, uint3 DTid : SV_DispatchThreadID) float4 Src3 = LoadColor(GI + 0x10); float4 Src4 = LoadColor(GI + 0x12); Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); - OutMip3[DTid.xy / 4] = PackColor(Src1); + StoreMip(2, DTid.xy / 4, DTid.z, PackColor(Src1)); StoreColor(GI, Src1); } @@ -171,6 +186,6 @@ void CS(uint GI : SV_GroupIndex, uint3 DTid : SV_DispatchThreadID) float4 Src3 = LoadColor(GI + 0x20); float4 Src4 = LoadColor(GI + 0x24); Src1 = 0.25 * (Src1 + Src2 + Src3 + Src4); - OutMip4[DTid.xy / 8] = PackColor(Src1); + StoreMip(3, DTid.xy / 8, DTid.z, PackColor(Src1)); } } diff --git a/workdir/shaders/RTXShadowReference.hlsl b/workdir/shaders/RTXShadowReference.hlsl new file mode 100644 index 00000000..93522f1a --- /dev/null +++ b/workdir/shaders/RTXShadowReference.hlsl @@ -0,0 +1,108 @@ +#include "Common.hlsl" + +#include "autogen/RTXShadowReference.h" +#include "autogen/FrameInfo.h" +#include "autogen/Raytracing.h" + +// Deterministic per-pixel angle -- no blue noise texture dependency here +// (this file is deliberately self-contained, independent of VSM's own +// code/bindings, so the comparison it's meant to enable stays honest). +// Same hash shape used elsewhere in this codebase for the same purpose. +float rtx_reference_hash_angle(uint2 p) +{ + uint h = p.x * 374761393u + p.y * 668265263u; + h = (h ^ (h >> 13)) * 1274126177u; + h = h ^ (h >> 16); + return (h & 0xFFFFu) / 65536.0 * 6.28318530718; +} + +float2 rtx_reference_rotate(float2 v, float angle) +{ + float s, c; + sincos(angle, s, c); + return float2(v.x * c - v.y * s, v.x * s + v.y * c); +} + +static const float2 RTX_REFERENCE_POISSON_DISK[16] = { + float2(-0.94201624, -0.39906216), float2( 0.94558609, -0.76890725), + float2(-0.09418410, -0.92938870), float2( 0.34495938, 0.29387760), + float2(-0.91588581, 0.45771432), float2(-0.81544232, -0.87912464), + float2(-0.38277543, 0.27676845), float2( 0.97484398, 0.75648379), + float2( 0.44323325, -0.97511554), float2( 0.53742981, -0.47373420), + float2(-0.26496911, -0.41893023), float2( 0.79197514, 0.19090188), + float2(-0.24188840, 0.99706507), float2(-0.81409955, 0.91437590), + float2( 0.19984126, 0.78641367), float2( 0.14383161, -0.14100790), +}; + +// RTXShadow's debug reference mode (see RTX::debug_full_reference_shadow in +// RTX.ixx, and PassDefaults.cpp's RTXShadow::render for how this gets +// selected instead of the pass's normal Bend/FFX hybrid-shadow-denoiser +// dispatch). 16 real rays sampling the sun's full angular disc per pixel -- +// genuine stochastic soft shadow, no denoiser -- a ground truth to compare +// VSM's own PCSS approximation against. Deliberately has NO dependency on +// any VSM code/types (separate binding struct, separate Poisson disc, +// separate hash function) -- it needs to be an independent reference, not +// something that could accidentally inherit a bug VSM's own path has. +[numthreads(16, 16, 1)] +void CS_REFERENCE(uint3 DTid : SV_DispatchThreadID) +{ + uint2 dims; + GetRTXShadowReference().GetOutput().GetDimensions(dims.x, dims.y); + if (any(DTid.xy >= dims)) + return; + + float2 tc = (float2(DTid.xy) + 0.5) / float2(dims); + GBuffer gbuffer = GetRTXShadowReference().GetGbuffer(); + float raw_z = gbuffer.GetDepth().SampleLevel(pointClampSampler, tc, 0); + if (raw_z == 0) + { + // Sky/background -- nothing to shadow, treat as fully lit. + GetRTXShadowReference().GetOutput()[DTid.xy] = float4(1, 1, 1, 1); + return; + } + + Camera camera = GetFrameInfo().GetCamera(); + float3 wpos = depth_to_wpos(raw_z, tc, camera.GetInvViewProj()); + float3 normal = normalize(gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).xyz * 2 - 1); + + // Matches VSM_impl.hlsl's own VSM_SUN_ANGULAR_RADIUS by value, not by + // sharing the constant -- this file is deliberately independent (see + // its own top comment), so keep this in sync by hand if that value + // ever changes and the comparison should track it. + static const float RTX_REFERENCE_SUN_ANGULAR_RADIUS = 0.02; // ~17 deg. + + float3 sun_dir = normalize(GetFrameInfo().GetSunDir().xyz); + float3 up = (abs(sun_dir.y) > 0.99) ? float3(1, 0, 0) : float3(0, 1, 0); + float3 right = normalize(cross(up, sun_dir)); + up = normalize(cross(sun_dir, right)); + float3 ray_origin = wpos + normal * 0.005; + float rotate_angle = rtx_reference_hash_angle(DTid.xy); + + int lit_count = 0; + [unroll] + for (int i = 0; i < 16; i++) + { + float2 offset = rtx_reference_rotate(RTX_REFERENCE_POISSON_DISK[i], rotate_angle); + float3 dir = normalize(sun_dir + + (right * offset.x + up * offset.y) * tan(RTX_REFERENCE_SUN_ANGULAR_RADIUS)); + + RayDesc ray; + ray.Origin = ray_origin; + ray.Direction = dir; + ray.TMin = 0.01; + ray.TMax = 500.0; + + // ACCEPT_FIRST_HIT_AND_END_SEARCH is safe here -- pure hit/miss + // test, not a distance measurement, so the first hit found is + // exactly as good as the closest one for deciding occluded-vs-not. + RayQuery rayQuery; + rayQuery.TraceRayInline(GetRaytracing().GetScene(), RAY_FLAG_NONE, 0xFF, ray); + rayQuery.Proceed(); + + if (rayQuery.CommittedStatus() == COMMITTED_NOTHING) + lit_count++; + } + + float shadow = lit_count / 16.0; + GetRTXShadowReference().GetOutput()[DTid.xy] = float4(shadow, shadow, shadow, 1); +} diff --git a/workdir/shaders/UniversalMaterial.hlsl b/workdir/shaders/UniversalMaterial.hlsl index f6b51650..b783c3fb 100644 --- a/workdir/shaders/UniversalMaterial.hlsl +++ b/workdir/shaders/UniversalMaterial.hlsl @@ -78,7 +78,7 @@ GBuffer universal(vertex_output i, float4 albedo, float metallic,float roughness float3 r = reflect(v, normal); GBuffer result; - result.albedo = float4(albedo.xyz, metallic); + result.albedo = float4(albedo.rgb, metallic); result.normals = float4(GetFrameInfo().compress_normals(normal), (roughness)); result.specular = 0;// float4(metallic, roughness); @@ -95,7 +95,7 @@ void COMPILED_FUNC(in float3 a, in float2 b, out float4 c, out float d, out floa GBuffer PS(vertex_output i) { float4 color = 1; - float metallic = 1; + float metallic = 0.001; float roughness = 1; float4 normal = 0; float4 glow = 0; diff --git a/workdir/shaders/VSM.hlsl b/workdir/shaders/VSM.hlsl index b9aa5fb8..618b2cd3 100644 --- a/workdir/shaders/VSM.hlsl +++ b/workdir/shaders/VSM.hlsl @@ -3,6 +3,10 @@ #include "autogen/VSMLighting.h" #include "autogen/FrameInfo.h" #include "autogen/VSMConstants.h" +// Only actually referenced inside get_shadow_vsm's VSM_RTX_VERIFY branch; +// unconditionally included since it's dead-code-eliminated when the define +// is off, same as every other permutation-gated accessor in this codebase. +#include "autogen/Raytracing.h" static const GBuffer gbuffer = GetVSMLighting().GetGbuffer(); @@ -14,7 +18,7 @@ float2 GetBRDF(float Roughness, float Metallic, float NoV) return GetFrameInfo().GetBrdf().SampleLevel(linearClampSampler, float3(Roughness, Metallic, NoV), 0); } -float4 combine_result(float2 tc) +float4 combine_result(float2 tc, uint2 pixel) { pixel_info info; Camera camera = GetFrameInfo().GetCamera(); @@ -25,6 +29,16 @@ float4 combine_result(float2 tc) info.normal = normalize(gbuffer.GetNormals().SampleLevel(pointClampSampler, tc, 0).xyz * 2 - 1); float raw_z = gbuffer.GetDepth().SampleLevel(pointClampSampler, tc, 0); + // Sky/background pixels (no geometry) skip shading entirely. This used + // to need a has_geometry/no-early-return workaround instead of a plain + // early return: get_shadow_vsm's blocker search ran quad-shared + // (QuadReadAcrossX/Y/Diagonal) inline in THIS dispatch, and a + // compute-shader `return` genuinely deactivates a thread (no + // pixel-shader helper-invocation guarantee), so an early return here + // would've undefined a still-active quad-mate's reads. The blocker + // search extraction moved all quad ops into VSM_BlockerSearch's own + // dispatch (VSM_BlockerSearch.hlsl) -- get_shadow_vsm no longer does + // any quad ops itself, so a plain early return is safe again here. if (raw_z == 0) return 0; info.pos = depth_to_wpos(raw_z, tc, camera.GetInvViewProj()); @@ -32,7 +46,16 @@ float4 combine_result(float2 tc) info.view = normalize(camera.GetPosition() - info.pos); VSMConstants constants = GetVSMConstants(); - float shadow = get_shadow_vsm(constants, GetVSMLighting(), info.pos); + float shadow = get_shadow_vsm(constants, GetVSMLighting(), info.pos, info.normal, pixel); + + // Debug view (runtime toggle, VSM.ixx's use_vsm_debug_rtx_reference): + // bypass VSM's own shadow entirely and show RTXShadow's own denoised + // full-RT shadow mask as grayscale. + if (constants.GetDebug_rtx_reference() != 0) + { + float rtx_shadow = GetVSMLighting().GetRtx_shadow_mask().SampleLevel(pointClampSampler, tc, 0); + return float4(rtx_shadow, rtx_shadow, rtx_shadow, 1); + } // #define VSM_DEBUG_HEATMAP // #define VSM_DEBUG_RAWDEPTH #ifdef VSM_DEBUG_HEATMAP @@ -60,9 +83,13 @@ void CS_RESULT(uint3 DTid : SV_DispatchThreadID) { uint2 dims; GetVSMLighting().GetResult().GetDimensions(dims.x, dims.y); + // Blocker-search extraction moved quad ops entirely into + // VSM_BlockerSearch's own dispatch -- an early return for out-of-bounds + // padding threads is safe again here (see combine_result's own comment + // on the matching sky-pixel case). if (any(DTid.xy >= dims)) return; float2 tc = (float2(DTid.xy) + 0.5) / float2(dims); - GetVSMLighting().GetResult()[DTid.xy] = combine_result(tc); + GetVSMLighting().GetResult()[DTid.xy] = combine_result(tc, DTid.xy); } diff --git a/workdir/shaders/VSM_BlockerSearch.hlsl b/workdir/shaders/VSM_BlockerSearch.hlsl new file mode 100644 index 00000000..6f7080d3 --- /dev/null +++ b/workdir/shaders/VSM_BlockerSearch.hlsl @@ -0,0 +1,52 @@ +#include "Common.hlsl" + +#include "autogen/VSMLighting.h" +#include "autogen/VSMConstants.h" +#include "autogen/FrameInfo.h" + +static const GBuffer gbuffer = GetVSMLighting().GetGbuffer(); + +#include "VSM_impl.hlsl" + +// Compute-queue: one thread per output pixel, writes the blocker-search +// result UAV (VSMLighting's blocker_result field) for VSM_Combine's resolve +// step to read back. Same size as VSM_Combine's own dispatch -- see vsm.sig's +// VSM_BlockerSearch PassNode comment for why this is a separate dispatch now +// instead of running inline inside VSM_Combine's own shader. +// +// Mirrors VSM.hlsl's combine_result/CS_RESULT split for the SAME reason: +// vsm_search_blocker's quad-shared search mode (VSM_impl.hlsl, +// quad_blocker_search == 1) needs every thread in a 2x2 dispatch quad to +// reach its QuadReadAcrossX/Y/Diagonal calls uniformly -- a compute-shader +// `return` genuinely deactivates a thread (no pixel-shader helper-invocation +// guarantee), so an early return for sky pixels or screen-edge padding +// threads would leave still-active quad-mates' reads undefined. This pass +// had exactly that bug class before the fix (and later the search itself) +// moved here -- see VSM.hlsl's own combine_result/CS_RESULT comments, which +// now describe the SIMPLER form this pass used to need before the +// extraction, since they no longer do any quad ops themselves. +[numthreads(16, 16, 1)] +void CS_BLOCKER_SEARCH(uint3 DTid : SV_DispatchThreadID) +{ + uint2 dims; + GetVSMLighting().GetBlocker_result().GetDimensions(dims.x, dims.y); + + float2 tc = (float2(DTid.xy) + 0.5) / float2(dims); + float raw_z = gbuffer.GetDepth().SampleLevel(pointClampSampler, tc, 0); + bool has_geometry = (raw_z != 0); + + Camera camera = GetFrameInfo().GetCamera(); + // Sky pixels get safe, finite dummy inputs (camera position / a fixed + // up vector) rather than whatever depth_to_wpos(0, ...) or an unwritten + // gbuffer normal texel would produce -- vsm_search_blocker never sees + // NaN/Inf-risking input even though its result is discarded below. + float3 wpos = has_geometry ? depth_to_wpos(raw_z, tc, camera.GetInvViewProj()) : camera.GetPosition(); + + VSMConstants constants = GetVSMConstants(); + uint4 result = vsm_search_blocker(constants, GetVSMLighting(), wpos, DTid.xy); + if (!has_geometry) + result = uint4(asuint(-1.0), 0, 0, 0); // sentinel: no blocker (matches vsm_search_blocker's own !valid convention) + + if (all(DTid.xy < dims)) + GetVSMLighting().GetBlocker_result()[DTid.xy] = result; +} diff --git a/workdir/shaders/VSM_impl.hlsl b/workdir/shaders/VSM_impl.hlsl index 03e39637..f5e60281 100644 --- a/workdir/shaders/VSM_impl.hlsl +++ b/workdir/shaders/VSM_impl.hlsl @@ -66,10 +66,308 @@ uint get_vsm_slot(VSMConstants c, VSMLighting lighting, float2 pos_ls, int start return VSM_INVALID_SLOT; } -float get_shadow_vsm(VSMConstants c, VSMLighting lighting, float3 wpos) +// 2D rotation, used to spin the fixed Poisson disc per pixel below. +float2 vsm_rotate(float2 v, float angle) +{ + float s, c; + sincos(angle, s, c); + return float2(v.x * c - v.y * s, v.x * s + v.y * c); +} + +// Resolves which page a light-space XY position falls into, walking from +// the receiver's own level up through coarser levels on a miss -- mirrors +// get_vsm_slot's own fallback (a coarser level's grid is a superset, so it's +// more likely resident). VSM's allocator is dynamic and residency is +// partial by design, so a tap's neighbor page across an edge frequently +// just isn't loaded yet; giving up on that tap outright (as this used to) +// systematically undercounts blocker_count/penumbra taps right at whatever +// happens to be the edge of the currently-resident set -- a visible line +// that isn't a real scene feature, worse than "blurrier shadow beats a +// hole" reasoning already accepted for the receiver itself. +// +// Returns the page's atlas slice slot plus the position's local UV within +// it -- same X/Y-flip convention as get_shadow_vsm's light_tc (see the +// derivation there: UV.x tracks world X directly, UV.y = 1 - fractional +// world Y within the page). +bool vsm_resolve_tap(VSMConstants c, VSMLighting lighting, int level, float2 tap_pos_ls, out uint tap_slot, out float2 tap_uv) +{ + int pages = c.GetPages_per_level(); + + [loop] + for (int l = level; l <= c.GetActive_max(); l++) + { + float4 info = c.GetLevel_info(l); + float2 page_f = (tap_pos_ls - info.xy) / max(info.z, 0.0001); + int2 page = int2(floor(page_f)); + + if (any(page < 0) || any(page >= pages)) + continue; // off this level's grid -- try a coarser (superset) one + + uint candidate = lighting.GetPage_table()[uint3(page.x, page.y, l)]; + if (candidate == VSM_INVALID_SLOT) + continue; + + float2 local = page_f - float2(page); + tap_slot = candidate; + tap_uv = float2(local.x, 1.0 - local.y); + return true; + } + + tap_slot = VSM_INVALID_SLOT; + tap_uv = 0; + return false; +} + +// One PCSS search/PCF tap. Works entirely in GLOBAL light-space (pos_ls), +// not page-local tc -- the offset (already in texels) is converted to a +// light-space delta via texel_world_size (radius in TEXELS * world-units- +// per-texel = world units; UV Y is flipped relative to light-space Y, see +// get_shadow_vsm's own derivation) and resolved through vsm_resolve_tap +// unconditionally, every tap, not just ones that cross the receiver's own +// page edge. Simpler and more uniform than special-casing "stayed in the +// receiver's page" as a separate fast path (that hybrid had its own local-tc +// math running alongside vsm_resolve_tap's independent global-tc math for +// different taps of the same pixel -- two formulations that were meant to +// agree but never got directly compared against each other, which is +// exactly the kind of split that hides a subtle inconsistency). One tap, +// one code path, one coordinate space. +bool vsm_tap(VSMConstants c, VSMLighting lighting, int level, float2 pos_ls, + float texel_world_size, float2 rotated_offset, float radius_texels, + out uint tap_slot, out float2 tap_uv) +{ + float2 tap_pos_ls = pos_ls + rotated_offset * radius_texels * texel_world_size * float2(1, -1); + return vsm_resolve_tap(c, lighting, level, tap_pos_ls, tap_slot, tap_uv); +} + +// Fixed Poisson disc, shared by the blocker search (vsm_search_blocker, +// unconditional below -- runs in its own dispatch/PSO permutation with no +// VSM_PENUMBRA define at all) and the PCF blur (vsm_pcf_shadow, still +// VSM_PENUMBRA-guarded further down) -- file scope so both can see it +// regardless of which #ifdef, if any, applies to the current compile. +static const float2 VSM_POISSON_DISK[16] = { + float2(-0.94201624, -0.39906216), float2( 0.94558609, -0.76890725), + float2(-0.09418410, -0.92938870), float2( 0.34495938, 0.29387760), + float2(-0.91588581, 0.45771432), float2(-0.81544232, -0.87912464), + float2(-0.38277543, 0.27676845), float2( 0.97484398, 0.75648379), + float2( 0.44323325, -0.97511554), float2( 0.53742981, -0.47373420), + float2(-0.26496911, -0.41893023), float2( 0.79197514, 0.19090188), + float2(-0.24188840, 0.99706507), float2(-0.81409955, 0.91437590), + float2( 0.19984126, 0.78641367), float2( 0.14383161, -0.14100790), +}; + +// Blocker-search extraction: everything get_shadow_vsm used to do UP +// THROUGH finding the search result now lives here, called once per pixel +// from VSM_BlockerSearch's own dispatch (VSM_BlockerSearch.hlsl) instead of +// inline inside the same dispatch as the final PCF blur/shading. Packs its +// result into a uint4 for VSMLighting's blocker_result field (see that +// field's own comment for the exact layout) -- asuint(-1.0) in .x is the +// sentinel for "no blocker found" (world_delta is otherwise always >=0 by +// construction). +// +// Unconditional (not #ifdef VSM_PENUMBRA-guarded) -- VSMBlockerSearchCompute +// has no VsmPenumbra define at all (VSM.cpp only ever runs this pass when +// penumbra mode is on in the first place, via its own setup()'s early-out), +// so this function must compile standalone. +// +// Callers must NOT early-return before calling this for any reason (sky +// pixels, screen-edge padding threads, etc.) -- see `valid` below: this +// function's own quad-shared search mode needs every thread in a 2x2 +// dispatch quad to reach its QuadReadAcrossX/Y/Diagonal calls uniformly, a +// requirement that propagates all the way up through this function's +// caller (VSM_BlockerSearch.hlsl's CS_BLOCKER_SEARCH). +uint4 vsm_search_blocker(VSMConstants c, VSMLighting lighting, float3 wpos, uint2 pixel) +{ + float2 pos_ls = mul(c.GetLight_view(), float4(wpos, 1)).xy; + int level_raw = get_vsm_level(c, pos_ls); + // See get_shadow_vsm's own history for the fuller version of this + // early-return-avoidance reasoning -- moved here wholesale along with + // the quad ops it exists to protect; get_shadow_vsm itself no longer + // does any quad ops, so it went back to plain early returns. + bool valid = level_raw >= 0; + int level = valid ? level_raw : c.GetActive_min(); + + uint slot_raw = valid ? get_vsm_slot(c, lighting, pos_ls, level) : VSM_INVALID_SLOT; + valid = valid && (slot_raw != VSM_INVALID_SLOT); + uint slot = valid ? slot_raw : 0; + + Camera page_cam = lighting.GetPage_cameras()[slot]; + float4 pos_l = mul(page_cam.GetViewProj(), float4(wpos, 1)); + + static const float VSM_BLOCKER_SEARCH_RADIUS_WORLD = 4.0; + float texel_world_size = c.GetLevel_info(level).z / c.GetPage_size(); + + float4 vsm_depth_range_p0 = mul(page_cam.GetInvProj(), float4(0, 0, 0, 1)); + float4 vsm_depth_range_p1 = mul(page_cam.GetInvProj(), float4(0, 0, 1, 1)); + float depth_range = abs(vsm_depth_range_p1.z / vsm_depth_range_p1.w - vsm_depth_range_p0.z / vsm_depth_range_p0.w); + + float blocker_search_radius_texels = clamp( + VSM_BLOCKER_SEARCH_RADIUS_WORLD / texel_world_size, 2.0, c.GetPage_size() * 4.0); + + int search_mode = c.GetQuad_blocker_search(); + bool quad_search = search_mode == 1; + bool stochastic_search = search_mode == 2; + + uint2 search_noise_pixel = quad_search ? (pixel & ~1u) : pixel; + float search_noise_angle = lighting.GetBlue_noise().Load(int3(search_noise_pixel % 128, 0)).x * 6.28318530718; + + float max_blocker_z = 0; + float best_sampled_z = 0; + float2 best_tc = 0; + uint best_slot = 0; + float best_pick_noise = lighting.GetBlue_noise().Load(int3(pixel % 128, 0)).y; + static const float VSM_RTX_TAP_TIE_EPS_WORLD = 0.1; + int blocker_count = 0; + + uint quad_lane = (pixel.x & 1) + (pixel.y & 1) * 2; + int random_tap = (int)(frac(lighting.GetBlue_noise().Load(int3(pixel % 128, 0)).x * 4327.31) * 16.0); + random_tap = clamp(random_tap, 0, 15); + int bi_start = stochastic_search ? random_tap : (quad_search ? (int)quad_lane : 0); + int bi_stride = quad_search ? 4 : 1; + int bi_count = stochastic_search ? 1 : (quad_search ? 4 : 16); + + [loop] + for (int bii = 0; bii < bi_count; bii++) + { + if (!valid) + continue; + int bi = bi_start + bii * bi_stride; + uint tap_slot; + float2 tc; + if (!vsm_tap(c, lighting, level, pos_ls, texel_world_size, + vsm_rotate(VSM_POISSON_DISK[bi], search_noise_angle), blocker_search_radius_texels, + tap_slot, tc)) + continue; + float sampled = lighting.GetVsm_atlas().SampleLevel(pointClampSampler, float3(tc, (float)tap_slot), 0); + if (sampled > pos_l.z) + { + bool is_new_max = sampled > max_blocker_z || blocker_count == 0; + bool is_near_tie = !is_new_max + && (max_blocker_z - sampled) * depth_range < VSM_RTX_TAP_TIE_EPS_WORLD + && best_pick_noise > 0.5; + if (is_new_max || is_near_tie) + { + best_sampled_z = sampled; + best_tc = tc; + best_slot = tap_slot; + } + max_blocker_z = max(max_blocker_z, sampled); + blocker_count++; + } + } + + if (quad_search) + { + float max_x = QuadReadAcrossX(max_blocker_z); + float max_y = QuadReadAcrossY(max_blocker_z); + float max_d = QuadReadAcrossDiagonal(max_blocker_z); + uint cnt_x = QuadReadAcrossX(blocker_count); + uint cnt_y = QuadReadAcrossY(blocker_count); + uint cnt_d = QuadReadAcrossDiagonal(blocker_count); + float2 tc_x = QuadReadAcrossX(best_tc); + float2 tc_y = QuadReadAcrossY(best_tc); + float2 tc_d = QuadReadAcrossDiagonal(best_tc); + uint slot_x = QuadReadAcrossX(best_slot); + uint slot_y = QuadReadAcrossY(best_slot); + uint slot_d = QuadReadAcrossDiagonal(best_slot); + float z_x = QuadReadAcrossX(best_sampled_z); + float z_y = QuadReadAcrossY(best_sampled_z); + float z_d = QuadReadAcrossDiagonal(best_sampled_z); + + blocker_count += cnt_x + cnt_y + cnt_d; + if (max_x > max_blocker_z) { max_blocker_z = max_x; best_tc = tc_x; best_slot = slot_x; best_sampled_z = z_x; } + if (max_y > max_blocker_z) { max_blocker_z = max_y; best_tc = tc_y; best_slot = slot_y; best_sampled_z = z_y; } + if (max_d > max_blocker_z) { max_blocker_z = max_d; best_tc = tc_d; best_slot = slot_d; best_sampled_z = z_d; } + } + + if (blocker_count == 0) + return uint4(asuint(-1.0), 0, 0, 0); + + float world_delta = (max_blocker_z - pos_l.z) * depth_range; + return uint4(asuint(world_delta), asuint(best_tc.x), asuint(best_tc.y), best_slot); +} + +#ifdef VSM_PENUMBRA +static const float VSM_SUN_ANGULAR_RADIUS = 0.02; // ~17 deg -- hardcoded, tune to taste. + +// Confidence-weighted PCF blur (see the old inline version's comments in git +// history for the full weighting rationale), factored out of get_shadow_vsm +// so it can run TWICE against two independently-estimated blocker +// distances -- once for VSM's own shadow-map search, once for the +// RTX-verified distance -- instead of collapsing them into one blended +// world_delta before blurring a single time. See get_shadow_vsm's +// VSM_RTX_VERIFY branch for how the two resulting shadow values get +// combined (min(), not a blend) and why. +float vsm_pcf_shadow(VSMConstants c, VSMLighting lighting, int level, float2 pos_ls, + float pos_l_z, float texel_world_size, float depth_range, + float noise_angle, float world_delta) +{ + float penumbra_world = tan(VSM_SUN_ANGULAR_RADIUS) * max(world_delta, 0); + // A distant blocker can make penumbra_world huge, which shows up below as + // the DENOMINATOR of every tap's w = depth_gap_world / penumbra_world -- + // growing it shrinks every tap's weight simultaneously, not just the ones + // that should genuinely read as ambiguous. Push it far enough and sum_w + // collapses under the "default to lit" threshold at the end of the tap + // loop, so the shadow snaps from soft-but-present straight to fully lit + // once world_delta crosses that point -- a visible sharp edge instead of + // the gradual softening a growing penumbra should produce. Capping the + // world-space size keeps w's denominator bounded, so confidence degrades + // smoothly instead of falling off a cliff. + static const float VSM_MAX_PENUMBRA_WORLD = 3.0; // world units, tune to taste. + penumbra_world = min(penumbra_world, VSM_MAX_PENUMBRA_WORLD); + float penumbra_texels = clamp(penumbra_world / texel_world_size, 1.0, c.GetPage_size() * 4.0); + //pos_l_z*=1.001f; + float shadow = 0; + float sum_w = 0; + [unroll] + for (int si = 0; si < 16; si++) + { + uint tap_slot; + float2 tc; + // A failed resolve used to count as a confident LIT vote (full + // weight), matching get_vsm_level/get_vsm_slot's own out-of-range + // convention for "past the edge of the whole clipmap". But + // vsm_resolve_tap also returns false for the much more common case + // of a page that's simply not resident YET (ordinary caching/ + // residency churn at ordinary page boundaries, not the edge of the + // world) -- with a wide search radius, MANY of the 16 taps for a + // receiver near a page boundary can land in a not-yet-resident + // neighbor at once, and confidently voting "lit" for every one of + // them dragged the weighted average toward white right along page + // boundaries (visible as a line). Neutral instead: contribute + // nothing either way, so the result is driven only by taps that + // actually found real data. The true "off the edge of the whole + // world" case is still handled below, once, after the loop. + if (!vsm_tap(c, lighting, level, pos_ls, texel_world_size, + vsm_rotate(VSM_POISSON_DISK[si], noise_angle), penumbra_texels, + tap_slot, tc)) + continue; + float sampled = lighting.GetVsm_atlas().SampleLevel(pointClampSampler, float3(tc, (float)tap_slot), 0); + + float scaler = 1-0.001; + float depth_gap_world = abs(pos_l_z*scaler - sampled) * depth_range; + float w = saturate(depth_gap_world / max(penumbra_world, 0.00001)); + sum_w += w; + shadow += w * (sampled < pos_l_z*scaler); + } + // sum_w stays exactly 0 only when literally every one of the 16 taps + // failed to resolve -- the genuine "off the edge of the whole clipmap" + // case (or every candidate landed in the self-shadow dead zone, which + // only happens right where there's no real penumbra to speak of + // anyway). Default to lit here, once, instead of per-tap. + if (sum_w < 0.0001) + return 1.0; + return shadow / sum_w; +} +#endif + +float get_shadow_vsm(VSMConstants c, VSMLighting lighting, float3 wpos, float3 normal, uint2 pixel) { float2 pos_ls = mul(c.GetLight_view(), float4(wpos, 1)).xy; int level = get_vsm_level(c, pos_ls); + // Blocker-search extraction moved the quad-op safety concern (and the + // `valid`-threading pattern that used to live here to protect it) into + // vsm_search_blocker -- this function no longer does any quad ops + // itself, so plain early returns are safe again. if (level < 0) return 1.0; @@ -77,33 +375,269 @@ float get_shadow_vsm(VSMConstants c, VSMLighting lighting, float3 wpos) if (slot == VSM_INVALID_SLOT) return 1.0; + // VSMDepthDraw switched from cull=Front (render only back faces, so a + // closed mesh's own front face never gets recorded as its own blocker) + // to cull=None -- needed because cull=Front silently casts NO shadow at + // all for single-sided/thin geometry (leaves, cards, thin walls) that + // has no back face to rasterize. cull=None fixes that, but now a + // front-facing receiver's own just-rasterized depth sits almost exactly + // at its own reprojected pos_l.z, so ordinary rasterization/quantization + // noise reads as self-shadowing (acne). + // + // A flat NDC-Z epsilon (tried first, both signs) is the wrong tool for + // this: the PCSS blocker search reads texels up to + // VSM_BLOCKER_SEARCH_RADIUS_WORLD away, and on any sloped/curved surface + // the receiver's OWN depth varies across that radius by far more than a + // small constant can safely absorb without either still acne-ing on + // steep slopes or peter-panning on shallow ones. Standard fix instead: + // push the shading point along its own surface NORMAL, in world space, + // before reprojecting into light space -- geometry-based rather than + // depth-based, so it naturally clears the receiver's own surface + // regardless of slope. A plain fixed world-space distance, not scaled by + // texel_world_size (was, briefly) -- that made the offset itself + // level-dependent, one more moving part than this needs. + static const float VSM_NORMAL_OFFSET_WORLD = 0.05; // world units, tune to taste. + float3 biased_wpos = wpos + normal * VSM_NORMAL_OFFSET_WORLD; + Camera page_cam = lighting.GetPage_cameras()[slot]; - float4 pos_l = mul(page_cam.GetViewProj(), float4(wpos, 1)); - pos_l /= pos_l.w; + float4 pos_l = mul(page_cam.GetViewProj(), float4(wpos, 1)); + float2 light_tc = pos_l.xy * float2(0.5, -0.5) + float2(0.5, 0.5); // The atlas is one array slice per page, so the slot IS the slice and the // page-local UV is used directly -- no packed-atlas offset math. - // No comparison sampler is declared in DefaultLayout's sampler set, so - // this does the depth test manually rather than via SampleCmp -- a plain - // 3x3 box of manually-compared texel-offset taps (SampleLevel's built-in - // int2 offset param) instead of PSSM_impl.hlsl's dilated-Poisson layout, - // whose spread constants are dead/commented-out there and collapse to - // duplicate offsets if copied literally. Reversed-Z convention (clear=0, - // closer fragments have larger stored z): lit if this point is at least - // as close to the light as whatever is stored at that atlas texel. - float shadow = 0; + // vsmShadowSampler (DefaultLayout's FrameLayout, HAL::Samplers:: + // SamplerShadowComparisonDesc) is a real SamplerComparisonState -- + // layout.jinja emits SamplerComparisonState instead of SamplerState for + // any sampler whose desc identifier is SamplerShadowComparisonDesc + // specifically (a name-based check, not a general SIG attribute -- see + // hlsl/layout.jinja). GREATER_EQUAL matches this project's reversed-Z + // convention (clear=0, closer fragments have larger stored z), so + // SampleCmpLevelZero returns 1 (lit) when this point is at least as + // close to the light as whatever is stored at that atlas texel -- same + // polarity as the old manual `pos_l.z >= sampled` compare, now done by + // the sampler's fixed-function hardware bilinear-PCF instead of a single + // point sample. + float2 texel_size = 1.0 / float2(c.GetPage_size(), c.GetPage_size()); + float shadow; + +#ifdef VSM_PENUMBRA + // PCSS: blocker search now runs in its OWN dispatch (VSM_BlockerSearch, + // vsm_search_blocker) -- this reads its result back instead of + // re-searching. Directional light, orthographic page cameras -- no + // perspective divide, so this is NOT the textbook point/spot-light + // formula penumbra=(recv-blocker)*lightSize/blocker (that assumes + // projected footprint shrinks with light-space depth). For a + // directional light penumbra grows linearly with world-space + // blocker-to-receiver distance, scaled by the sun's angular radius. + // + // A dense small-radius grid (the original cut of this) has two problems + // at once: real occluders are usually farther than a couple of texels + // from a penumbra-region receiver, so blocker search finds nothing and + // silently falls back to shadow=1 without ever using the radius; and + // even when it does fire, only 3 sample positions per axis spread across + // a big radius reads as a handful of hard-edged blobs, not a blur. Fixed + // by decoupling tap COUNT (kept small, fixed) from search RADIUS (can be + // large) via a sparse Poisson disc -- the standard PCSS shape. + // VSM_SUN_ANGULAR_RADIUS is now file-scope (see above vsm_tap) -- shared + // with vsm_pcf_shadow, which needs it too. + // World-space, not texel-space: a fixed texel count means a fine (small + // page_world_size) page searches a tiny sliver of world space while a + // coarse page searches a huge one, so real blockers fall outside the + // window on fine pages and blocker_count silently comes back 0 right at + // whatever level boundary the camera happens to be crossing -- a visible + // line where the shadow flips from "fully lit" to a real penumbra. + // Converted to a per-level texel count below via texel_world_size, same + // as penumbra_texels already does. + float texel_world_size = c.GetLevel_info(level).z / c.GetPage_size(); + + // World-units-per-NDC-Z, from the page camera's own InvProj rather than + // assuming a specific reversed-Z/orthographic matrix-element layout -- + // mirrors Common.hlsl's depth_to_wpos unprojection idiom. Needed by + // vsm_pcf_shadow below regardless of which distance estimate ends up + // driving a given blur pass. + float4 vsm_depth_range_p0 = mul(page_cam.GetInvProj(), float4(0, 0, 0, 1)); + float4 vsm_depth_range_p1 = mul(page_cam.GetInvProj(), float4(0, 0, 1, 1)); + float depth_range = abs(vsm_depth_range_p1.z / vsm_depth_range_p1.w - vsm_depth_range_p0.z / vsm_depth_range_p0.w); + + // Rotate the whole disc by a per-pixel blue-noise angle -- 16 fixed taps + // otherwise read as a rigid, repeating rosette once the radius gets big + // (visible as concentric rings rather than a blur). blue_noise_texture is + // baked once per frame at 128x128 (see BlueNoise.sig/blue_noise.hlsl) and + // tiled here the same way every other consumer reads it (VoxelGI's + // reflection reproject, etc.). Drives vsm_pcf_shadow's own (always + // full-resolution, per-pixel) tap rotation below, and the full-penumbra + // ray cone's jitter -- kept genuinely per-pixel for maximum + // decorrelation, unlike vsm_search_blocker's own search_noise_angle. + float noise_angle = lighting.GetBlue_noise().Load(int3(pixel % 128, 0)).x * 6.28318530718; + + // vsm_search_blocker's packed result (VSM_BlockerSearch's own dispatch, + // see VSMLighting's blocker_result field for the exact uint4 layout). + // asfloat(.x)<0 (matching the asuint(-1.0) sentinel it writes) means + // "no blocker found" -- skip straight to fully lit, same as the old + // inline blocker_count==0 fast path. + uint4 blocker_packed = lighting.GetBlocker_result()[pixel]; + float world_delta_or_sentinel = asfloat(blocker_packed.x); + + if (world_delta_or_sentinel < 0) + { + // Nothing in the search disc occludes the light -- fully lit, skip + // the penumbra PCF pass entirely. + shadow = 1.0; + } + else + { + float world_delta = world_delta_or_sentinel; + float2 best_tc = float2(asfloat(blocker_packed.y), asfloat(blocker_packed.z)); + uint best_slot = blocker_packed.w; + +#ifdef VSM_RTX_VERIFY + // Shadow maps only record the front-most surface per texel -- a + // closer blocker can be geometrically present but never rasterized + // into the atlas at the exact XY the search looked, so world_delta + // above can understate the true occlusion. + // + // Rather than probing blindly along the sun direction (which + // assumes the blocker sits exactly on that line -- wrong whenever + // the winning tap had a lateral offset, which is most of them, by + // construction of the Poisson disc), read the ACTUAL point the + // shadow map flagged: unproject (best_tc, best_slot) -- that tap's + // own page camera, since the winning tap may have resolved to a + // different page/level than the receiver via vsm_resolve_tap's + // coarser-level walk -- back into world space, and aim the + // verification ray directly at that specific point. best_sampled_z + // is re-sampled from the atlas here rather than carried through + // vsm_search_blocker's packed uint4 -- it's just the depth already + // stored at (best_tc, best_slot), one extra texture read is cheaper + // than widening the packed result to a 5th channel. + // TMax is the distance to it plus a margin, not a tight bound: the + // point is to give the ray room to find something EVEN CLOSER along + // that same line of sight, not just re-confirm the target. + // + // rtx_world_delta is recomputed from wherever the ray actually + // lands (dot(hit_pos - wpos, sun_dir), the hit projected onto the + // true light axis) rather than substituted directly from + // CommittedRayT() -- that raw T value is a distance along THIS + // ray's own (generally non-axial) direction, not along the light, + // so it isn't a valid drop-in replacement for the axial quantity + // penumbra sizing needs. + // + // Deliberately NOT RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH (unlike + // workgraph_test.hlsl's Shadows_Node) -- that flag returns whatever + // the BVH happens to traverse first, not the closest hit, which + // would make the result an unreliable distance. This needs the + // genuinely closest hit within [TMin, TMax], so RayQuery is left to + // run its normal closest-hit tracking to completion. + static const float VSM_RTX_VERIFY_MARGIN = 1.5; + bool rtx_hit = false; + float rtx_world_delta = world_delta; + { + Camera blocker_page_cam = lighting.GetPage_cameras()[best_slot]; + float best_sampled_z = lighting.GetVsm_atlas().SampleLevel(pointClampSampler, float3(best_tc, (float)best_slot), 0); + // Undo light_tc's own UV<->NDC mapping (see its derivation + // above get_shadow_vsm's atlas sample): tc.x*2-1 un-does + // *0.5+0.5, 1-tc.y*2 un-does *(-0.5)+0.5. + float4 blocker_ndc = float4(best_tc.x * 2 - 1, 1 - best_tc.y * 2, best_sampled_z, 1); + float4 blocker_clip = mul(blocker_page_cam.GetInvViewProj(), blocker_ndc); + float3 blocker_wpos = blocker_clip.xyz / blocker_clip.w; + + float3 sun_dir = normalize(GetFrameInfo().GetSunDir().xyz); + float3 to_target = blocker_wpos - wpos; + float target_dist = length(to_target); + float3 aim_dir = target_dist > 0.0001 ? (to_target / target_dist) : sun_dir; + + // Blue noise .y (unused elsewhere -- noise_angle above only + // consumes .x, so this stays decorrelated from the disc + // rotation) dithers this otherwise perfectly deterministic + // single-sample aim: a SMALL angular jitter around the exact + // target direction, much narrower than the 16-ray mode's full + // sun-disc cone -- this ray's job is still "verify one + // specific point", not "sample the light disc". Smooths + // texel-alignment stair-stepping in the reconstructed distance + // across neighboring pixels (the winning tap snaps to a + // discrete texel, so a perfectly deterministic ray inherits + // that discreteness), and gives a bit of temporal variation + // for free since the blue noise texture itself varies per + // frame -- helps even without an explicit accumulation buffer. + static const float VSM_RTX_VERIFY_JITTER = 0.02; // radians, small -- tune to taste. + float3 jitter_up = (abs(aim_dir.y) > 0.99) ? float3(1, 0, 0) : float3(0, 1, 0); + float3 jitter_right = normalize(cross(jitter_up, aim_dir)); + jitter_up = normalize(cross(aim_dir, jitter_right)); + float jitter_angle = lighting.GetBlue_noise().Load(int3(pixel % 128, 0)).y * 6.28318530718; + float2 jitter_offset = vsm_rotate(float2(1, 0), jitter_angle) * VSM_RTX_VERIFY_JITTER; + float3 jittered_dir = normalize(aim_dir + jitter_right * jitter_offset.x + jitter_up * jitter_offset.y); + + RayDesc ray; + ray.Origin = wpos + normal * 0.005; + ray.Direction = jittered_dir; + ray.TMin = 0.01; + ray.TMax = max(target_dist * VSM_RTX_VERIFY_MARGIN, 0.02); + + RayQuery rayQuery; + rayQuery.TraceRayInline(GetRaytracing().GetScene(), RAY_FLAG_NONE, 0xFF, ray); + rayQuery.Proceed(); + + if (rayQuery.CommittedStatus() != COMMITTED_NOTHING) + { + float3 hit_pos = ray.Origin + ray.Direction * rayQuery.CommittedRayT(); + rtx_world_delta = dot(hit_pos - wpos, sun_dir); + rtx_hit = true; + } + } + + if (rtx_hit && c.GetRtx_dual_blur() != 0) + { + // Two independent blurs, not a blended world_delta: a single + // blend means neither estimate is ever fully trusted, and a + // case where only ONE method actually caught the true occluder + // (VSM's search missed it per the front-most-surface limitation + // above, or the verification ray's narrow TMax/jitter missed + // something VSM's wider search disc still found) gets diluted + // toward the wrong answer instead of winning outright. Running + // vsm_pcf_shadow twice and taking min() -- shadow=0 is fully + // occluded, shadow=1 is fully lit, under this function's + // convention -- means whichever estimate found MORE shadow + // always wins. Confirmed visually: removes bright spots that + // used to appear between overlapping penumbras. Costs an extra + // full 16-tap blur per pixel whenever the ray hits, so it's a + // runtime toggle (VSM::use_vsm_rtx_dual_blur) rather than + // unconditional. + float shadow_vsm = vsm_pcf_shadow(c, lighting, level, pos_ls, pos_l.z, texel_world_size, depth_range, noise_angle, world_delta); + float shadow_rtx = vsm_pcf_shadow(c, lighting, level, pos_ls, pos_l.z, texel_world_size, depth_range, noise_angle, rtx_world_delta); + shadow = min(shadow_vsm, shadow_rtx); + } + else + { + // Single blur pass: use the RTX-verified distance when the ray + // hit something (it's the more accurate estimate), otherwise + // fall back to VSM's own shadow-map-derived distance. + float chosen_delta = rtx_hit ? rtx_world_delta : world_delta; + shadow = vsm_pcf_shadow(c, lighting, level, pos_ls, pos_l.z, texel_world_size, depth_range, noise_angle, chosen_delta); + } +#else + shadow = vsm_pcf_shadow(c, lighting, level, pos_ls, pos_l.z, texel_world_size, depth_range, noise_angle, world_delta); +#endif + } +#else + // Still averaged over a 3x3 grid of manually offset taps (Texture2DArray + // has no SampleCmp overload taking a raw texel offset the way SampleLevel + // does, so int2 offset here comes from re-deriving the UV per tap rather + // than an offset param) -- four-tap hardware PCF per sample point, times + // nine sample points, for a wider soft edge than a single hardware-PCF + // tap alone would give. + shadow = 0; [unroll] for (int oy = -1; oy <= 1; oy++) { [unroll] for (int ox = -1; ox <= 1; ox++) { - float sampled = lighting.GetVsm_atlas().SampleLevel(pointClampSampler, float3(light_tc, (float)slot), 0, int2(ox, oy)); - shadow += (pos_l.z >= sampled) ? 1.0 : 0.0; + float2 tc = light_tc + float2(ox, oy) * texel_size; + shadow += lighting.GetVsm_atlas().SampleCmpLevelZero(vsmShadowSampler, float3(tc, (float)slot), pos_l.z*0.999); } } shadow /= 9.0; +#endif if (pos_l.z < 0 || pos_l.z > 1 || any(light_tc < 0) || any(light_tc > 1)) shadow = 1; diff --git a/workdir/shaders/autogen/RTXShadowReference.h b/workdir/shaders/autogen/RTXShadowReference.h new file mode 100644 index 00000000..4eb4c4ae --- /dev/null +++ b/workdir/shaders/autogen/RTXShadowReference.h @@ -0,0 +1,35 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +#ifndef SLOT_6 + #define SLOT_6 +#else + #error Slot 6 is already used +#endif + +#include "layout/DefaultLayout.h" +#include "tables/RTXShadowReference.h" + +#ifndef CB_DEFINED +#define CB_DEFINED +struct CB { uint offset; }; +#endif + +#ifdef __spirv__ +struct _CB_RTXShadowReference { uint offset; }; +static _CB_RTXShadowReference pass_RTXShadowReference = { _hal_push.s6 }; +#else +ConstantBuffer pass_RTXShadowReference: register(b6, space6); +#endif +ConstantBuffer CreateRTXShadowReference() +{ + return ResourceDescriptorHeap[pass_RTXShadowReference.offset]; +} + +#ifndef NO_GLOBAL +static const ConstantBuffer rTXShadowReference_global = CreateRTXShadowReference(); +ConstantBuffer GetRTXShadowReference() { return rTXShadowReference_global; } +#endif \ No newline at end of file diff --git a/workdir/shaders/autogen/layout/FrameLayout.h b/workdir/shaders/autogen/layout/FrameLayout.h index 40fde061..16124186 100644 --- a/workdir/shaders/autogen/layout/FrameLayout.h +++ b/workdir/shaders/autogen/layout/FrameLayout.h @@ -11,6 +11,7 @@ SamplerState pointClampSampler:register(s1); SamplerState linearClampSampler:register(s2); SamplerState anisoBordeSampler:register(s3); SamplerState pointBorderSampler:register(s4); +SamplerComparisonState vsmShadowSampler:register(s5); #ifdef __spirv__ struct _HALPush { uint s0; uint s1; uint s2; uint s3; diff --git a/workdir/shaders/autogen/tables/EnvFilter.h b/workdir/shaders/autogen/tables/EnvFilter.h index e459b106..13273be0 100644 --- a/workdir/shaders/autogen/tables/EnvFilter.h +++ b/workdir/shaders/autogen/tables/EnvFilter.h @@ -8,10 +8,9 @@ #include "sig_hlsl.hlsl" struct EnvFilter { - uint4 face; // uint4 - float4 scaler; // float4 uint4 size; // uint4 - uint4 GetFace() { return face; } - float4 GetScaler() { return scaler; } + uint targets[8]; // RWTexture2DArray uint4 GetSize() { return size; } + RWTexture2DArray GetTargets(int i) { return ResourceDescriptorHeap[targets[i]]; } + }; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/MipMapping.h b/workdir/shaders/autogen/tables/MipMapping.h index aa7558db..a99fbc08 100644 --- a/workdir/shaders/autogen/tables/MipMapping.h +++ b/workdir/shaders/autogen/tables/MipMapping.h @@ -12,11 +12,16 @@ struct MipMapping uint NumMipLevels; // uint float2 TexelSize; // float2 uint SrcMip; // Texture2D + uint SrcMipArray; // Texture2DArray uint OutMip[4]; // RWTexture2D + uint OutMipArray[4]; // RWTexture2DArray uint GetSrcMipLevel() { return SrcMipLevel; } uint GetNumMipLevels() { return NumMipLevels; } float2 GetTexelSize() { return TexelSize; } RWTexture2D GetOutMip(int i) { return ResourceDescriptorHeap[OutMip[i]]; } Texture2D GetSrcMip() { return ResourceDescriptorHeap[SrcMip]; } + RWTexture2DArray GetOutMipArray(int i) { return ResourceDescriptorHeap[OutMipArray[i]]; } + + Texture2DArray GetSrcMipArray() { return ResourceDescriptorHeap[SrcMipArray]; } }; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/RTXShadowReference.h b/workdir/shaders/autogen/tables/RTXShadowReference.h new file mode 100644 index 00000000..24e7253d --- /dev/null +++ b/workdir/shaders/autogen/tables/RTXShadowReference.h @@ -0,0 +1,16 @@ +// ============================================================================ +// THIS FILE IS AUTOMATICALLY GENERATED - DO NOT MANUALLY EDIT +// ============================================================================ +// Generated by SigParser from .sig files in sources/SIGParser/sigs/ +// Changes will be lost on next generation. Edit the .sig source files instead. +// ============================================================================ +#pragma once +#include "sig_hlsl.hlsl" +#include "GBuffer.h" +struct RTXShadowReference +{ + uint output; // RWTexture2D + GBuffer gbuffer; // GBuffer + GBuffer GetGbuffer() { return gbuffer; } + RWTexture2D GetOutput() { return ResourceDescriptorHeap[output]; } +}; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/SkyFace.h b/workdir/shaders/autogen/tables/SkyFace.h index eda1d3c4..18466c7a 100644 --- a/workdir/shaders/autogen/tables/SkyFace.h +++ b/workdir/shaders/autogen/tables/SkyFace.h @@ -8,6 +8,6 @@ #include "sig_hlsl.hlsl" struct SkyFace { - uint face; // uint - uint GetFace() { return face; } + uint faces; // RWTexture2DArray + RWTexture2DArray GetFaces() { return ResourceDescriptorHeap[faces]; } }; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/VSMConstants.h b/workdir/shaders/autogen/tables/VSMConstants.h index aaa1be51..f29f8ff1 100644 --- a/workdir/shaders/autogen/tables/VSMConstants.h +++ b/workdir/shaders/autogen/tables/VSMConstants.h @@ -12,12 +12,18 @@ struct VSMConstants int active_max; // int int page_size; // int int pages_per_level; // int + int rtx_dual_blur; // int + int quad_blocker_search; // int + int debug_rtx_reference; // int float4x4 light_view; // float4x4 float4 level_info[26]; // float4 int GetActive_min() { return active_min; } int GetActive_max() { return active_max; } int GetPage_size() { return page_size; } int GetPages_per_level() { return pages_per_level; } + int GetRtx_dual_blur() { return rtx_dual_blur; } + int GetQuad_blocker_search() { return quad_blocker_search; } + int GetDebug_rtx_reference() { return debug_rtx_reference; } float4x4 GetLight_view() { return light_view; } float4 GetLevel_info(int i) { return level_info[i]; } }; \ No newline at end of file diff --git a/workdir/shaders/autogen/tables/VSMLighting.h b/workdir/shaders/autogen/tables/VSMLighting.h index f988cf72..d38b0491 100644 --- a/workdir/shaders/autogen/tables/VSMLighting.h +++ b/workdir/shaders/autogen/tables/VSMLighting.h @@ -13,11 +13,17 @@ struct VSMLighting uint vsm_atlas; // Texture2DArray uint page_table; // Texture2DArray uint page_cameras; // StructuredBuffer + uint blue_noise; // Texture2D + uint rtx_shadow_mask; // Texture2D uint result; // RWTexture2D + uint blocker_result; // RWTexture2D GBuffer gbuffer; // GBuffer GBuffer GetGbuffer() { return gbuffer; } Texture2DArray GetVsm_atlas() { return ResourceDescriptorHeap[vsm_atlas]; } Texture2DArray GetPage_table() { return ResourceDescriptorHeap[page_table]; } StructuredBuffer GetPage_cameras() { return ResourceDescriptorHeap[page_cameras]; } RWTexture2D GetResult() { return ResourceDescriptorHeap[result]; } + Texture2D GetBlue_noise() { return ResourceDescriptorHeap[blue_noise]; } + Texture2D GetRtx_shadow_mask() { return ResourceDescriptorHeap[rtx_shadow_mask]; } + RWTexture2D GetBlocker_result() { return ResourceDescriptorHeap[blocker_result]; } }; \ No newline at end of file diff --git a/workdir/shaders/cubemap_down.hlsl b/workdir/shaders/cubemap_down.hlsl index 30046967..f7b23a4f 100644 --- a/workdir/shaders/cubemap_down.hlsl +++ b/workdir/shaders/cubemap_down.hlsl @@ -2,27 +2,9 @@ #include "autogen/EnvSource.h" #include "autogen/EnvFilter.h" +// Edge length of the SOURCE cubemap -- feeds the solid-angle estimate that +// picks which source mip each sample reads. Not the size of what we write. static const uint EnvMapSize = GetEnvFilter().GetSize().x; -static const float roughness = GetEnvFilter().GetScaler().x; - -struct quad_output -{ -float4 pos : SV_POSITION; -float3 tc: TEXCOORD0; -}; - -#ifdef BUILD_FUNC_VS - - -static float2 Pos[] = -{ - float2(-1, -1), - float2(-1, 1), - float2(1, -1), - float2(1, 1) -}; - - static float3x3 mats[] = { @@ -36,19 +18,27 @@ static float3x3 mats[] = float3x3(-1, 0, 0, 0, 1, 0, 0, 0, -1) //Z- }; -quad_output VS(uint index : SV_VERTEXID) +// Texel -> cube direction. The old VS normalized the corner rays before +// interpolation, which warped the face non-linearly; taking mul() of the +// interpolated clip position and normalizing once is the direction a hardware +// TextureCube lookup actually maps to that texel. +float3 face_direction(uint2 texel, uint face, uint size) { - quad_output Output; - Output.pos = float4(Pos[index], 0.99999, 1); - Output.tc = normalize(mul(mats[GetEnvFilter().GetFace().x], float3(Pos[index], 1))); - return Output; + float2 uv = (float2(texel) + 0.5) / float(size); + float2 clip = uv * float2(2, -2) + float2(-1, 1); + return normalize(mul(mats[face], float3(clip, 1))); } -#endif #include "Common.hlsl" -#ifdef BUILD_FUNC_PS -float3 PrefilterEnvMap(float Roughness, float3 R) +#ifdef BUILD_FUNC_CS + +// Sample count per output mip, indexed by min(mip, 4). Used to be the +// NumSamples PSO define; it is a per-thread lookup now that one dispatch spans +// every mip. +static const uint MipSamples[] = { 1, 8, 32, 64, 128 }; + +float3 PrefilterEnvMap(float Roughness, float3 R, uint NumSamples) { float3 N = R; float3 V = R; @@ -60,7 +50,7 @@ float3 PrefilterEnvMap(float Roughness, float3 R) float3x3 space = CalculateTangent(N); float a = Roughness * Roughness; - // [unroll(128)] + for (uint i = 0; i < NumSamples; i++) { float2 Xi = hammersley2d(i, NumSamples); @@ -85,7 +75,6 @@ float3 PrefilterEnvMap(float Roughness, float3 R) //float fMipBias = 1.0f; int fMipLevel = clamp(0.5 * log2(fOmegaS / fOmegaP)+1, 0, 9); - // PrefilteredColor += EnvMap.SampleLevel(EnvMapSampler, L, 0).rgb * NoL; PrefilteredColor += GetEnvSource().GetSourceTex().SampleLevel(linearSampler, L, fMipLevel).rgb * NoL; TotalWeight += NoL; } @@ -95,26 +84,53 @@ float3 PrefilterEnvMap(float Roughness, float3 R) } +// Every mip of every face of the specular cubemap in one dispatch. +// +// The output mips have different sizes, so the domain is the concatenation of +// all of them -- mip 0's 6*w*w texels, then mip 1's, and so on -- walked as a +// flat 1D index. Threads stay one-texel-per-thread that way, which keeps the +// expensive high-roughness mips spread across the grid instead of piled into +// one corner of a mip-0-sized dispatch. +[numthreads(64, 1, 1)] +void CS(uint3 DTid : SV_DispatchThreadID) +{ + const uint mip_count = GetEnvFilter().GetSize().y; + const uint base_size = GetEnvFilter().GetSize().z; + uint index = DTid.x; + // Walk the per-mip spans until the one holding this thread's texel. + uint mip = 0; + uint size = base_size; + uint start = 0; + [loop] + while (mip + 1 < mip_count && index >= start + size * size * 6) + { + start += size * size * 6; + size = max(size >> 1, 1u); + mip++; + } + // Tail of the last thread group. + uint local = index - start; + if (local >= size * size * 6) + return; + uint face = local / (size * size); + uint texel = local - face * size * size; + uint2 xy = uint2(texel % size, texel / size); -float4 PS(quad_output i) : SV_TARGET0 -{ - - float3 itc = normalize(i.tc); - //return float4(itc,1); - - return float4(PrefilterEnvMap(roughness, itc), 1); + float roughness = (float(mip) + 0.5) / mip_count; + float3 itc = face_direction(xy, face, size); + GetEnvFilter().GetTargets(mip)[uint3(xy, face)] = + float4(PrefilterEnvMap(roughness, itc, MipSamples[min(mip, 4u)]), 1); } - #endif -#ifdef BUILD_FUNC_PS_Diffuse +#ifdef BUILD_FUNC_CS_Diffuse float3 PrefilterDiffuse(float Roughness, float3 R) @@ -129,7 +145,7 @@ float3 PrefilterDiffuse(float Roughness, float3 R) const uint NumSamples = 32; // Solid angle covered by 1 pixel with 6 faces that are EnvMapSize X EnvMapSize float fOmegaP = 4.0 * PI / (6.0 * EnvMapSize * EnvMapSize); - // [unroll(128)] + for (uint i = 0; i < NumSamples; i++) { float2 Xi = hammersley2d(i, NumSamples); @@ -154,7 +170,6 @@ float3 PrefilterDiffuse(float Roughness, float3 R) //float fMipBias = 1.0f; int fMipLevel = clamp(0.5 * log2(fOmegaS / fOmegaP) + 1, 0, 9); - // PrefilteredColor += EnvMap.SampleLevel(EnvMapSampler, L, 0).rgb * NoL; PrefilteredColor += GetEnvSource().GetSourceTex().SampleLevel(linearSampler, L, fMipLevel).rgb * NoL; TotalWeight += NoL; } @@ -164,15 +179,19 @@ float3 PrefilterDiffuse(float Roughness, float3 R) } - -float4 PS_Diffuse(quad_output i) : SV_TARGET0 +// The diffuse cubemap is mip 0 only, so one (w, h, 6) dispatch covers it. +[numthreads(8, 8, 1)] +void CS_Diffuse(uint3 DTid : SV_DispatchThreadID) { + uint3 dims; + GetEnvFilter().GetTargets(0).GetDimensions(dims.x, dims.y, dims.z); - float3 itc = normalize(i.tc); - // return float4(float(roughness).xxx ,1); + if (any(DTid >= dims)) + return; - return float4(PrefilterDiffuse(1, itc), 1); + float3 itc = face_direction(DTid.xy, DTid.z, dims.x); + GetEnvFilter().GetTargets(0)[DTid] = float4(PrefilterDiffuse(1, itc), 1); } -#endif \ No newline at end of file +#endif diff --git a/workdir/shaders/sky.hlsl b/workdir/shaders/sky.hlsl index 13c170da..57faa087 100644 --- a/workdir/shaders/sky.hlsl +++ b/workdir/shaders/sky.hlsl @@ -212,18 +212,9 @@ struct quad_output float3 ray : TEXCOORD1; }; -#ifdef BUILD_FUNC_VS_Cube +#ifdef BUILD_FUNC_CS_Cube #include "autogen/SkyFace.h" -static float2 Pos[] = -{ - float2(-1, -1), - float2(-1, 1), - float2(1, -1), - float2(1, 1) -}; - - static float3x3 mats[] = { @@ -237,13 +228,24 @@ float3x3(1,0,0, 0,1,0, 0,0,1), //Z+ float3x3(-1,0,0, 0,1,0, 0,0,-1)//Z- }; -quad_output VS_Cube(uint index : SV_VERTEXID) +// Compute-queue cube bake: one (w, h, 6) dispatch over the whole cube, with the +// face taken from Z and written through an array UAV. The ray the old VS_Cube +// interpolated across the quad was linear in the clip-space position (mats[] is +// a rotation, so mul() commutes with the interpolation), so rebuilding it per +// texel from the pixel centre reproduces it exactly. +[numthreads(8, 8, 1)] +void CS_Cube(uint3 DTid : SV_DispatchThreadID) { - quad_output Output; - Output.pos = float4(Pos[index], 0.99999, 1); - Output.ray = normalize(mul(mats[GetSkyFace().GetFace()], float3(Pos[index], 1))); + uint3 dims; + GetSkyFace().GetFaces().GetDimensions(dims.x, dims.y, dims.z); + if (any(DTid >= dims)) + return; - return Output; + float2 uv = (float2(DTid.xy) + 0.5) / float2(dims.xy); + float2 clip = uv * float2(2, -2) + float2(-1, 1); + float3 v = normalize(mul(mats[DTid.z], float3(clip, 1))); + + GetSkyFace().GetFaces()[DTid] = float4(get_sky_only(camera.GetPosition(), v, 0), 1); } #endif @@ -310,11 +312,3 @@ void CS(uint3 DTid : SV_DispatchThreadID) GetSkyData().GetResult()[DTid.xy] += sky_result(tc, ray); } - - -float4 PS_Cube(quad_output i) : SV_Target0 -{ - float3 v = normalize(i.ray); - - return float4(get_sky_only(camera.GetPosition(), v, 0), 1); -} \ No newline at end of file diff --git a/workdir/stderr.txt b/workdir/stderr.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/workdir/stdout.txt b/workdir/stdout.txt deleted file mode 100644 index e69de29b..00000000