Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
We've been talking about various ways to perform CPU-side validation/testing over the outputs of Vello pipeline stages. It's also generally useful to be able to visualize the contents of some of these intermediate data structures (such as bounding boxes, the line soup, etc) and to be able to visually interpret any errors that are surfaced from the validation tests. I implemented the beginnings of this in a new `debug_layers` feature. I'm putting this up as a Draft PR as there are a few unresolved aspects that I'd like to get feedback on first (more on this below). ## Running Instructions To try this out run the `with_winit` example with `--features debug_layers` and use the number keys (1-4) to toggle the individual layers. ## Summary This PR introduces the concept of "debug layers" that are rendered directly to the surface texture over the fine rasterizer output. There are currently 4 different layers: - `BOUNDING_BOXES`: Visualizes the edges of path bounding boxes as green lines - `LINESOUP_SEGMENTS`: Visualizes LineSoup segments as orange lines - `LINESOUP_POINTS`: Visualizes the LineSoup endpoints as cyan circles. - `VALIDATION`: Runs a collection of validation tests on intermediate Vello GPU data structures. This is currently limited to a watertightness test on line segments. Following the test run, the layer visualizes the positions of detected errors as red circles. These layers can be individually toggled using a new `DebugLayers` field in `vello::RenderParams`. The following is an example output with all 4 layers enabled: <img width="906" alt="Screenshot 2023-12-12 at 3 13 51 PM" src="https://github.com/linebender/vello/assets/6933700/658760c1-ed95-41b8-8444-3a6dfa9ddee7"> Each layer is implemented as an individual render pass. The first 3 layers are simple visualizations of intermediate GPU buffers. The `VALIDATION` layer is special since it runs CPU-side validation steps (currently a watertightness test over the LineSoup buffer), which requires read-back. The general idea is that `VALIDATION` can grow to encompass additional sanity checks. ### Overview of Changes - Engine support for render pipeline creation and draw commands. In particular, the existing `blit` pipeline can now be expressed as a `Recording`. The debug layer render passes get recorded to this `Recording`. All render passes share the same render encoder and target the same surface texture. - A simple mechanism to extend the lifetime of GPU buffers beyond their original `Recording` to allow them to be used in a subsequent render pass. Currently this separation into multiple recordings is necessary since the visualizations require GPU->CPU read-back. This is partially encapsulated by the new `CapturedBuffers` data structure. - The `debug` module and the `DebugLayers` and `DebugRenderer` data structures. `DebugRenderer` is an encapsulation of the various render pipelines used to visualize the layers that are requested via `DebugLayers`. `DebugRenderer` is currently also responsible for execution the validation tests when the `VALIDATION` layer is enabled. - The `with_winit` example now has key bindings (the number keys 1-4) to toggle the individual layers. ## Open Questions 1. It probably makes sense to have a better separation between running validation tests and visualizing their output. Currently both are performed by `DebugRenderer::render`. 2. `CapturedBuffers` doesn't handle buffer clean up well. The current `engine` abstractions require that a buffer be returned to the underlying engine's pool via a `Recording` command. This creates and submits a command buffer to simply free a buffer, which is a bit too heavy-weight. This whole mechanism could use some rethinking. Currently, these buffers get conditionally freed in various places in the code and it would be nice to consolidate that logic. 3. The `VALIDATION` layer currently doesn't work with the `--use-cpu` flag since the buffer download command isn't supported for CPU-only buffers. Currently, it's the job of `src/render.rs` to know which buffers need to get downloaded for validation purposes. It currently simply records a download command. It would be nice for the engine to make the download command seamless across both CPU and GPU buffers rather than having the `src/render.rs` code do something different across the CPU vs GPU modalities. 4. Currently all layers require read-back. The debug layers (`BOUNDING_BOXES`, `LINESOUP_SEGMENTS`, `LINESOUP_POINTS`) read back the `BumpAllocators` buffers to obtain instance counts used in their draw commands. This read-back could be avoided by instead issuing indirect draws for the debug layers. I think this could be implemented with a relatively simple scheme: a new compute pipeline stage is introduced (gated by `#[cfg(feature = "debug_layers")]`, which can inspect any number of the vello intermediate buffers (such as the `bump` buffer) and populate an indirect draw buffer. The indirect draw buffer would be laid out with multiple [`DrawIndirect`](https://docs.rs/wgpu/latest/wgpu/util/struct.DrawIndirect.html) entries, each assigned to a different pre-defined instance type (the `DebugRenderer` only issues instanced draws). `DebugRenderer` would then issue an indirect draw with the appropriate indirect buffer offset for each render pipeline. The read-back would still be necessary for CPU-side validation stages and their visualization can't really take advantage of the indirect draw. Then again, the exact ordering of the draw submission and the read-backs implemented in this PR is likely to change following the proposal in #366. --------- Co-authored-by: Daniel McNab <[email protected]> Co-authored-by: Bruce Mitchener <[email protected]>
- Loading branch information