Commit 12d86bc
authored
[Flutter GPU] Allow customizing the vertex layout on a RenderPipeline (#186310)
Adds an explicit `VertexLayout` value type that the caller can pass to
`GpuContext.createRenderPipeline` to override the default interleaved
layout declared by the bound vertex shader's shader bundle, plus a
`slot:` parameter on `RenderPass.bindVertexBuffer` so multiple vertex
buffers can be bound to a single draw. This unblocks structure-of-arrays
mesh loading (positions in one buffer, normals + UVs in another, etc.)
and lets a renderer reorder attributes from the impellerc-generated
default.
The shipped scope intentionally pins only what's expressible against
today's HAL without backing the API into a corner. The deferred
capabilities (instancing, sparse bindings, normalized / packed /
half-float / BGRA / 64-bit formats) are tracked at #186307, #186308, and
#186309, with TODO comments at the relevant call sites pointing at each
tracking issue.
### Dart surface
```dart
enum VertexFormat {
float32, float32x2, float32x3, float32x4,
uint32, uint32x2, uint32x3, uint32x4,
sint32, sint32x2, sint32x3, sint32x4,
}
class VertexAttribute {
String name;
VertexFormat format;
int offsetInBytes; // defaults to 0
}
class VertexBuffer {
int strideInBytes;
List<VertexAttribute> attributes;
}
class VertexLayout {
List<VertexBuffer> buffers;
}
GpuContext.createRenderPipeline(vertex, fragment, {VertexLayout? vertexLayout});
RenderPass.bindVertexBuffer(BufferView, int vertexCount, {int slot = 0});
```
If `vertexLayout` is `null`, the default for the bound vertex shader is
used (today's behavior). The new `slot:` parameter defaults to `0`, so
all existing single-buffer call sites compile unchanged.
Attributes nest under the `VertexBuffer` they read from; each buffer's
position in `VertexLayout.buffers` determines the binding slot it must
be bound to via `RenderPass.bindVertexBuffer` (the first buffer is slot
0, the second is slot 1, and so on). `offsetInBytes` defaults to 0 so
the common structure-of-arrays case (one attribute per buffer at the
start of each element) doesn't need to spell it out.
Attributes are keyed by the shader-side input `name` rather than a raw
integer location, mirroring how uniform bindings are resolved via
`Shader.getUniformSlot('VertInfo')`. This keeps the Dart layout robust
to shader edits that reorder `in` declarations (the underlying location,
which is what every backend ultimately consumes, is read from the
shader's reflection at pipeline build time).
### Example: structure-of-arrays glTF mesh
Most glTF mesh primitives store each vertex attribute (POSITION, NORMAL,
TEXCOORD_0, ...) in its own accessor, often inside its own buffer view.
Without a configurable vertex layout, callers were forced to interleave
those attributes on the CPU before upload. With this change, each
attribute can keep its own buffer and bind at its own slot.
Given a vertex shader that declares three named inputs:
```glsl
in vec3 position;
in vec3 normal;
in vec2 texcoord;
```
A renderer can describe the SoA layout once at pipeline creation and
then bind one buffer per slot per draw:
```dart
import 'package:flutter_gpu/gpu.dart' as gpu;
final pipeline = gpu.gpuContext.createRenderPipeline(
vertexShader,
fragmentShader,
vertexLayout: const gpu.VertexLayout(
buffers: <gpu.VertexBuffer>[
gpu.VertexBuffer(
strideInBytes: 12,
attributes: <gpu.VertexAttribute>[
gpu.VertexAttribute(name: 'position', format: gpu.VertexFormat.float32x3),
],
),
gpu.VertexBuffer(
strideInBytes: 12,
attributes: <gpu.VertexAttribute>[
gpu.VertexAttribute(name: 'normal', format: gpu.VertexFormat.float32x3),
],
),
gpu.VertexBuffer(
strideInBytes: 8,
attributes: <gpu.VertexAttribute>[
gpu.VertexAttribute(name: 'texcoord', format: gpu.VertexFormat.float32x2),
],
),
],
),
);
// Per draw call: bind one buffer per slot.
renderPass.bindPipeline(pipeline);
renderPass.bindVertexBuffer(positionsView, vertexCount, slot: 0);
renderPass.bindVertexBuffer(normalsView, vertexCount, slot: 1);
renderPass.bindVertexBuffer(texcoordsView, vertexCount, slot: 2);
renderPass.draw();
```
Interleaved layouts work too: declare one `VertexBuffer` whose
`strideInBytes` covers the whole vertex, list every attribute under it,
and give each attribute past the first an explicit `offsetInBytes` into
the element:
```dart
vertexLayout: const gpu.VertexLayout(
buffers: <gpu.VertexBuffer>[
gpu.VertexBuffer(
strideInBytes: 32,
attributes: <gpu.VertexAttribute>[
gpu.VertexAttribute(name: 'position', format: gpu.VertexFormat.float32x3),
gpu.VertexAttribute(
name: 'normal',
format: gpu.VertexFormat.float32x3,
offsetInBytes: 12,
),
gpu.VertexAttribute(
name: 'texcoord',
format: gpu.VertexFormat.float32x2,
offsetInBytes: 24,
),
],
),
],
),
```
This is also how a caller would override the impellerc-generated default
to skip an unused attribute or reorder the components.
### Validation
`createRenderPipeline` throws a Dart exception when:
- A `VertexAttribute.format` doesn't match the bound vertex shader's
declared scalar type class (float vs signed int vs unsigned int).
Component-count mismatches are NOT errors, mirroring the
default-substitution rules every modern HAL uses ((0, 0, 0, 1) fill).
- An attribute's `offsetInBytes + format.bytesPerElement` overruns the
owning `VertexBuffer`'s stride.
- Two attributes within the same `VertexBuffer` occupy overlapping byte
ranges (i.e. `[offsetInBytes, offsetInBytes + format.bytesPerElement)`
ranges that intersect).
- An attribute's `name` doesn't match any vertex shader input
declaration.
`RenderPass.bindVertexBuffer` throws `RangeError` if `slot` is outside
`[0, 16)`.
### C++ plumbing
- `Shader::GetStageInputs()` exposes the impellerc-reflected attribute
metadata so the pipeline initializer can resolve user attribute names to
`(location, set, columns, relaxed_precision)` and validate user formats
against the shader's declared scalar type.
- `RenderPipeline` stores its own `impeller::VertexDescriptor`, built
from the user layout when supplied or fetched from the shader's
reflection otherwise.
- `RenderPass` upgrades `vertex_buffer` to a `std::array<BufferView,
16>` indexed by binding slot, tracks the highest bound slot, and
forwards the whole array to
`impeller::RenderPass::SetVertexBuffer(BufferView*, count)`.
The packed `(buffer layouts, attributes, attribute names)` data is
passed via FFI as three `ByteData` handles and copied out of the
typed-data handles before any callback into the Dart VM (else
`Dart_TypedDataAcquireData` would forbid the callback). With nested
attributes, `bufferLayouts` rows shrink to `[strideInBytes,
attributeCount]` and `attributes` rows shrink to `[offsetInBytes,
formatIndex, nameByteLength]`; binding slots are implicit in each
buffer's position, and the C++ side walks attribute rows by consuming
each buffer's `attributeCount` in order. Attribute names are encoded as
concatenated UTF-8 bytes walked in parallel with the attributes integer
table using each entry's `nameByteLength`.
### Tests
Adds six `gpu_test.dart` tests covering an
explicit-layout-matching-default render, a slot-range check, and four
`createRenderPipeline` validation paths (wrong format, overrun stride,
overlapping attributes within a buffer, unknown attribute name). All
pass on `flutter_tester_opengles` (SwANGLE) and `flutter_tester` (Metal)
locally.
Fixes #145013.
## Pre-launch Checklist
- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
If you need help, consider asking for advice on the #hackers-new channel
on [Discord].
If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.
**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.
<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md1 parent 095d8cf commit 12d86bc
14 files changed
Lines changed: 968 additions & 39 deletions
File tree
- engine/src/flutter
- lib/gpu
- lib
- src
- testing/dart
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
64 | 67 | | |
65 | 68 | | |
66 | 69 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| 62 | + | |
62 | 63 | | |
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
| 69 | + | |
68 | 70 | | |
69 | 71 | | |
70 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
157 | 157 | | |
158 | 158 | | |
159 | 159 | | |
160 | | - | |
161 | | - | |
162 | | - | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
163 | 169 | | |
164 | 170 | | |
165 | 171 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
227 | 243 | | |
228 | 244 | | |
229 | 245 | | |
| |||
279 | 295 | | |
280 | 296 | | |
281 | 297 | | |
282 | | - | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
283 | 338 | | |
284 | 339 | | |
285 | 340 | | |
286 | 341 | | |
287 | 342 | | |
| 343 | + | |
288 | 344 | | |
289 | 345 | | |
290 | 346 | | |
| |||
349 | 405 | | |
350 | 406 | | |
351 | 407 | | |
| 408 | + | |
| 409 | + | |
352 | 410 | | |
353 | 411 | | |
354 | 412 | | |
| |||
448 | 506 | | |
449 | 507 | | |
450 | 508 | | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
451 | 523 | | |
452 | 524 | | |
453 | 525 | | |
| |||
519 | 591 | | |
520 | 592 | | |
521 | 593 | | |
522 | | - | |
| 594 | + | |
523 | 595 | | |
524 | 596 | | |
525 | 597 | | |
526 | 598 | | |
527 | 599 | | |
528 | 600 | | |
529 | 601 | | |
| 602 | + | |
530 | 603 | | |
531 | 604 | | |
532 | 605 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
11 | 16 | | |
12 | 17 | | |
13 | 18 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
18 | 34 | | |
19 | 35 | | |
20 | 36 | | |
| |||
23 | 39 | | |
24 | 40 | | |
25 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
26 | 109 | | |
27 | | - | |
28 | | - | |
29 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
30 | 121 | | |
31 | 122 | | |
32 | 123 | | |
33 | 124 | | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
34 | 128 | | |
35 | 129 | | |
0 commit comments