Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
render_pipeline.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
7#include <array>
8#include <cstdint>
9#include <cstring>
10#include <span>
11#include <string_view>
12
17#include "third_party/abseil-cpp/absl/status/status.h"
18#include "third_party/abseil-cpp/absl/status/statusor.h"
19#include "third_party/abseil-cpp/absl/strings/str_cat.h"
21
22namespace flutter {
23namespace gpu {
24
26
30 std::shared_ptr<impeller::VertexDescriptor> vertex_descriptor)
31 : vertex_shader_(std::move(vertex_shader)),
32 fragment_shader_(std::move(fragment_shader)),
33 vertex_descriptor_(std::move(vertex_descriptor)) {
34 // Register the descriptor set layouts contributed by each shader exactly
35 // once, here at construction. Doing this in BindToPipelineDescriptor (as
36 // earlier revisions did) would append the same layouts on every bind
37 // since `RegisterDescriptorSetLayouts` accumulates rather than replaces.
38 vertex_descriptor_->RegisterDescriptorSetLayouts(
39 vertex_shader_->GetDescriptorSetLayouts().data(),
40 vertex_shader_->GetDescriptorSetLayouts().size());
41 vertex_descriptor_->RegisterDescriptorSetLayouts(
42 fragment_shader_->GetDescriptorSetLayouts().data(),
43 fragment_shader_->GetDescriptorSetLayouts().size());
44}
45
49 desc.SetVertexDescriptor(vertex_descriptor_);
50
51 desc.AddStageEntrypoint(vertex_shader_->GetFunctionFromLibrary(library));
52 desc.AddStageEntrypoint(fragment_shader_->GetFunctionFromLibrary(library));
53}
54
56
57namespace {
58
59// Translation table from the Dart-side `VertexFormat` enum (encoded as the
60// enum index) to the `(ShaderType, bit_width, vec_size, bytes_per_element)`
61// tuple Impeller's HAL stores in `ShaderStageIOSlot`. The order MUST stay
62// in sync with `lib/src/vertex_layout.dart`.
63struct VertexFormatInfo {
65 size_t bit_width;
66 size_t vec_size;
68};
69
70constexpr std::array<VertexFormatInfo, 12> kVertexFormatTable = {{
71 {impeller::ShaderType::kFloat, 32, 1, 4}, // float32
72 {impeller::ShaderType::kFloat, 32, 2, 8}, // float32x2
73 {impeller::ShaderType::kFloat, 32, 3, 12}, // float32x3
74 {impeller::ShaderType::kFloat, 32, 4, 16}, // float32x4
75 {impeller::ShaderType::kUnsignedInt, 32, 1, 4}, // uint32
76 {impeller::ShaderType::kUnsignedInt, 32, 2, 8}, // uint32x2
77 {impeller::ShaderType::kUnsignedInt, 32, 3, 12}, // uint32x3
78 {impeller::ShaderType::kUnsignedInt, 32, 4, 16}, // uint32x4
79 {impeller::ShaderType::kSignedInt, 32, 1, 4}, // sint32
80 {impeller::ShaderType::kSignedInt, 32, 2, 8}, // sint32x2
81 {impeller::ShaderType::kSignedInt, 32, 3, 12}, // sint32x3
82 {impeller::ShaderType::kSignedInt, 32, 4, 16}, // sint32x4
83}};
84
85// Width of each "row" in the packed `bufferLayouts` ByteData passed from
86// Dart: `[strideInBytes, attributeCount, stepMode]`. Each buffer's binding
87// slot is implicit in its position in the array (the first buffer is slot 0,
88// etc.).
89constexpr size_t kBufferLayoutInts = 3;
90
91// Width of each "row" in the packed `attributes` ByteData passed from Dart:
92// `[offsetInBytes, formatIndex, nameByteLength]`. Attribute rows are
93// flattened across buffers in buffer-list order; each buffer's
94// `attributeCount` indicates how many attribute rows belong to it. The
95// attribute name itself lives in a parallel `attribute_names` byte blob
96// walked sequentially using the per-row `nameByteLength`.
97constexpr size_t kAttributeInts = 3;
98
99// Builds an `impeller::VertexDescriptor` from the user-supplied buffer
100// layout and attribute arrays, validating each entry against the vertex
101// shader's reflection metadata. On validation failure, returns a non-OK
102// status whose message describes the first problem found.
103//
104// Binding slots are implicit in buffer-list position. Buffer N (0-indexed)
105// is bound at binding slot N. This makes sparse bindings impossible to
106// express by construction; Impeller's RenderPass::SetVertexBuffer also
107// rejects sparse bindings, and lifting that restriction would need a
108// `firstBinding`-style entry point added to the HAL.
109// TODO(https://github.com/flutter/flutter/issues/186308): Allow sparse
110// vertex buffer binding slots.
111absl::StatusOr<std::shared_ptr<impeller::VertexDescriptor>>
112BuildCustomVertexDescriptor(const flutter::gpu::Shader& vertex_shader,
113 std::span<const int32_t> buffer_layouts,
114 std::span<const int32_t> attributes,
115 std::span<const char> attribute_names) {
116 const size_t buffer_layout_count = buffer_layouts.size() / kBufferLayoutInts;
117 const size_t attribute_count = attributes.size() / kAttributeInts;
118 const auto& shader_inputs = vertex_shader.GetStageInputs();
119
120 std::vector<impeller::ShaderStageBufferLayout> stage_layouts;
121 stage_layouts.reserve(buffer_layout_count);
122 std::vector<impeller::ShaderStageIOSlot> stage_inputs;
123 stage_inputs.reserve(attribute_count);
124
125 size_t attr_cursor = 0;
126 size_t name_cursor = 0;
127 for (size_t buffer_index = 0; buffer_index < buffer_layout_count;
128 ++buffer_index) {
129 const int32_t stride = buffer_layouts[buffer_index * kBufferLayoutInts + 0];
130 const int32_t attr_count_in_buffer =
131 buffer_layouts[buffer_index * kBufferLayoutInts + 1];
132 const int32_t step_mode =
133 buffer_layouts[buffer_index * kBufferLayoutInts + 2];
134 if (stride <= 0) {
135 return absl::InvalidArgumentError(
136 absl::StrCat("VertexBuffer.strideInBytes must be positive (got ",
137 stride, ") on buffer at index ", buffer_index, "."));
138 }
139 if (step_mode < 0 || step_mode > 1) {
140 return absl::InvalidArgumentError(
141 absl::StrCat("VertexBuffer.stepMode is out of range (got ", step_mode,
142 ") on buffer at index ", buffer_index, "."));
143 }
144 if (attr_count_in_buffer < 0 ||
145 attr_cursor + static_cast<size_t>(attr_count_in_buffer) >
146 attribute_count) {
147 return absl::InvalidArgumentError(
148 "Internal error: attribute count overruns the packed attributes "
149 "blob.");
150 }
151 stage_layouts.push_back({static_cast<size_t>(stride), buffer_index,
152 step_mode == 0
155
156 // Track each attribute's byte range within this buffer so we can
157 // detect overlaps after building them all.
158 struct AttrRange {
159 std::string name;
160 size_t begin;
161 size_t end;
162 };
163 std::vector<AttrRange> ranges_in_buffer;
164 ranges_in_buffer.reserve(attr_count_in_buffer);
165
166 for (size_t a = 0; a < static_cast<size_t>(attr_count_in_buffer); ++a) {
167 const int32_t offset = attributes[attr_cursor * kAttributeInts + 0];
168 const int32_t format_index = attributes[attr_cursor * kAttributeInts + 1];
169 const int32_t name_byte_length =
170 attributes[attr_cursor * kAttributeInts + 2];
171 ++attr_cursor;
172
173 if (name_byte_length <= 0 ||
174 name_cursor + static_cast<size_t>(name_byte_length) >
175 attribute_names.size()) {
176 return absl::InvalidArgumentError(
177 "Internal error: attribute name overruns the packed names blob.");
178 }
179 const std::string_view name(attribute_names.data() + name_cursor,
180 static_cast<size_t>(name_byte_length));
181 name_cursor += static_cast<size_t>(name_byte_length);
182
183 if (offset < 0) {
184 return absl::InvalidArgumentError(absl::StrCat(
185 "VertexAttribute '", name,
186 "' offsetInBytes must be non-negative (got ", offset, ")."));
187 }
188 if (format_index < 0 ||
189 static_cast<size_t>(format_index) >= kVertexFormatTable.size()) {
190 return absl::InvalidArgumentError(
191 absl::StrCat("VertexAttribute '", name, "' format index ",
192 format_index, " is out of range."));
193 }
194 const VertexFormatInfo& format = kVertexFormatTable[format_index];
195
196 if (static_cast<size_t>(offset) + format.bytes_per_element >
197 static_cast<size_t>(stride)) {
198 return absl::InvalidArgumentError(absl::StrCat(
199 "VertexAttribute '", name, "' (offset ", offset, " + ",
200 format.bytes_per_element, " bytes) overruns stride of ", stride,
201 " on buffer at index ", buffer_index, "."));
202 }
203
204 // Detect overlap against earlier attributes in this buffer before
205 // doing any shader-side lookups, so the overlap diagnostic isn't
206 // shadowed by a less informative name-mismatch error.
207 const size_t begin = static_cast<size_t>(offset);
208 const size_t end = begin + format.bytes_per_element;
209 const std::string name_owned(name);
210 for (const auto& other : ranges_in_buffer) {
211 if (begin < other.end && other.begin < end) {
212 return absl::InvalidArgumentError(
213 absl::StrCat("VertexAttribute '", name, "' (bytes [", begin, ", ",
214 end, ")) overlaps VertexAttribute '", other.name,
215 "' (bytes [", other.begin, ", ", other.end,
216 ")) on buffer at index ", buffer_index, "."));
217 }
218 }
219 ranges_in_buffer.push_back({name_owned, begin, end});
220
221 // Find the matching shader input by name to validate format and to
222 // copy the (location, set, columns, relaxed_precision) metadata we
223 // don't carry on the Dart side. The Shader's IOSlot names point into
224 // the shader bundle flatbuffer, which the Shader keeps alive via its
225 // code mapping; the impellerc-generated builds use static string
226 // literals. Either way, strcmp against a NUL-terminated needle is
227 // safe.
228 const impeller::ShaderStageIOSlot* shader_slot = nullptr;
229 for (const auto& slot : shader_inputs) {
230 if (slot.name != nullptr &&
231 std::strcmp(slot.name, name_owned.c_str()) == 0) {
232 shader_slot = &slot;
233 break;
234 }
235 }
236 if (shader_slot == nullptr) {
237 return absl::InvalidArgumentError(absl::StrCat(
238 "VertexAttribute name '", name,
239 "' does not match any input declared by the bound vertex "
240 "shader."));
241 }
242 // Match the shader's scalar type class (float vs signed int vs
243 // unsigned int). Component-count mismatches are NOT errors: the shader
244 // receives default substitution (missing components default to
245 // (0, 0, 0, 1)) when the buffer supplies fewer components than
246 // declared, and reads only the leading components when the buffer
247 // supplies more. The shipped enum only contains 32-bit formats, so
248 // checking `type` alone is sufficient until 8/16-bit formats are added.
249 // TODO(https://github.com/flutter/flutter/issues/186309): Add
250 // normalized, packed, half-float, BGRA-swizzled, and 64-bit vertex
251 // attribute formats; the format check will need to also verify
252 // `bit_width` once those land.
253 if (shader_slot->type != format.type ||
254 shader_slot->bit_width != format.bit_width) {
255 return absl::InvalidArgumentError(absl::StrCat(
256 "VertexAttribute '", name,
257 "' format does not match the vertex shader's declared input "
258 "type."));
259 }
260
261 impeller::ShaderStageIOSlot built = *shader_slot;
262 built.binding = buffer_index;
263 built.offset = static_cast<size_t>(offset);
264 stage_inputs.push_back(built);
265 }
266 }
267
268 if (attr_cursor != attribute_count) {
269 return absl::InvalidArgumentError(
270 "Internal error: attributes blob has trailing rows not consumed "
271 "by any buffer.");
272 }
273 if (name_cursor != attribute_names.size()) {
274 return absl::InvalidArgumentError(
275 "Internal error: attribute names blob has trailing bytes.");
276 }
277
278 auto descriptor = std::make_shared<impeller::VertexDescriptor>();
279 descriptor->SetStageInputs(stage_inputs, stage_layouts);
280 return descriptor;
281}
282
283} // namespace
284
285} // namespace gpu
286} // namespace flutter
287
288//----------------------------------------------------------------------------
289/// Exports
290///
291
293 Dart_Handle wrapper,
294 flutter::gpu::Context* gpu_context,
295 flutter::gpu::Shader* vertex_shader,
296 flutter::gpu::Shader* fragment_shader,
297 Dart_Handle buffer_layouts_handle,
298 Dart_Handle attributes_handle,
299 Dart_Handle attribute_names_handle) {
300 // Lazily register the shaders synchronously if they haven't been already.
301 vertex_shader->RegisterSync(*gpu_context);
302 fragment_shader->RegisterSync(*gpu_context);
303
304 std::shared_ptr<impeller::VertexDescriptor> vertex_descriptor;
305
306 const bool buffer_layouts_provided = !Dart_IsNull(buffer_layouts_handle);
307 const bool attributes_provided = !Dart_IsNull(attributes_handle);
308 const bool attribute_names_provided = !Dart_IsNull(attribute_names_handle);
309 if (buffer_layouts_provided != attributes_provided ||
310 attributes_provided != attribute_names_provided) {
311 return tonic::ToDart(
312 "VertexLayout requires buffer layouts, attributes, and attribute "
313 "names to be provided together.");
314 }
315
316 if (buffer_layouts_provided) {
317 // Copy the packed Dart-side ByteData buffers into local vectors so the
318 // tonic::DartByteData typed-data handles are released before we make any
319 // call back into the Dart VM (e.g. tonic::ToDart for an error string).
320 // Holding a typed-data handle while calling into the VM raises
321 // "Callbacks into the Dart VM are currently prohibited." Errors raised
322 // inside the inner scope must therefore be deferred to a local string
323 // and returned only after the typed-data handles go out of scope.
324 std::vector<int32_t> buffer_layouts_ints;
325 std::vector<int32_t> attribute_ints;
326 std::vector<char> attribute_names_bytes;
327 std::string copy_error;
328 {
329 tonic::DartByteData buffer_layouts_data(buffer_layouts_handle);
330 tonic::DartByteData attributes_data(attributes_handle);
331 tonic::DartByteData attribute_names_data(attribute_names_handle);
332 if (buffer_layouts_data.length_in_bytes() %
333 (flutter::gpu::kBufferLayoutInts * sizeof(int32_t)) !=
334 0) {
335 copy_error =
336 "Internal error: buffer layouts ByteData has invalid length.";
337 } else if (attributes_data.length_in_bytes() %
338 (flutter::gpu::kAttributeInts * sizeof(int32_t)) !=
339 0) {
340 copy_error = "Internal error: attributes ByteData has invalid length.";
341 } else {
342 const auto* buffer_layouts_src =
343 static_cast<const int32_t*>(buffer_layouts_data.data());
344 const auto* attributes_src =
345 static_cast<const int32_t*>(attributes_data.data());
346 const auto* names_src =
347 static_cast<const char*>(attribute_names_data.data());
348 buffer_layouts_ints.assign(
349 buffer_layouts_src,
350 buffer_layouts_src +
351 buffer_layouts_data.length_in_bytes() / sizeof(int32_t));
352 attribute_ints.assign(
353 attributes_src, attributes_src + attributes_data.length_in_bytes() /
354 sizeof(int32_t));
355 attribute_names_bytes.assign(
356 names_src, names_src + attribute_names_data.length_in_bytes());
357 }
358 }
359 if (!copy_error.empty()) {
360 return tonic::ToDart(copy_error);
361 }
362
363 absl::StatusOr<std::shared_ptr<impeller::VertexDescriptor>> built =
364 flutter::gpu::BuildCustomVertexDescriptor(
365 *vertex_shader,
366 std::span<const int32_t>(buffer_layouts_ints.data(),
367 buffer_layouts_ints.size()),
368 std::span<const int32_t>(attribute_ints.data(),
369 attribute_ints.size()),
370 std::span<const char>(attribute_names_bytes.data(),
371 attribute_names_bytes.size()));
372 if (!built.ok()) {
373 return tonic::ToDart(std::string(built.status().message()));
374 }
375 vertex_descriptor = *std::move(built);
376 } else {
377 vertex_descriptor = vertex_shader->CreateVertexDescriptor();
378 }
379
380 auto res = fml::MakeRefCounted<flutter::gpu::RenderPipeline>(
381 fml::RefPtr<flutter::gpu::Shader>(vertex_shader), //
382 fml::RefPtr<flutter::gpu::Shader>(fragment_shader), //
383 std::move(vertex_descriptor));
384 res->AssociateWithDartWrapper(wrapper);
385
386 return Dart_Null();
387}
void BindToPipelineDescriptor(impeller::ShaderLibrary &library, impeller::PipelineDescriptor &desc)
RenderPipeline(fml::RefPtr< flutter::gpu::Shader > vertex_shader, fml::RefPtr< flutter::gpu::Shader > fragment_shader, std::shared_ptr< impeller::VertexDescriptor > vertex_descriptor)
An immutable collection of shaders loaded from a shader bundle asset.
Definition shader.h:23
bool RegisterSync(Context &context)
Definition shader.cc:118
const std::vector< impeller::ShaderStageIOSlot > & GetStageInputs() const
Definition shader.cc:158
std::shared_ptr< impeller::VertexDescriptor > CreateVertexDescriptor() const
Definition shader.cc:151
const std::vector< impeller::DescriptorSetLayout > & GetDescriptorSetLayouts() const
Definition shader.cc:172
std::shared_ptr< const impeller::ShaderFunction > GetFunctionFromLibrary(impeller::ShaderLibrary &library)
Definition shader.cc:69
PipelineDescriptor & SetVertexDescriptor(std::shared_ptr< VertexDescriptor > vertex_descriptor)
PipelineDescriptor & AddStageEntrypoint(std::shared_ptr< const ShaderFunction > function)
const void * data() const
size_t length_in_bytes() const
uint32_t vec_size
#define IMPLEMENT_WRAPPERTYPEINFO(LibraryName, ClassName)
uint32_t uint32_t * format
const char * name
Definition fuchsia.cc:50
DEF_SWITCHES_START aot vmservice shared library name
Definition switch_defs.h:27
VertexInputRate
Whether a vertex buffer binding advances its read position once per vertex or once per instance.
@ kInstance
The binding is read once per instance.
@ kVertex
The binding is read once per vertex. This is the default.
Definition ref_ptr.h:261
Dart_Handle ToDart(const T &object)
Dart_Handle InternalFlutterGpu_RenderPipeline_Initialize(Dart_Handle wrapper, flutter::gpu::Context *gpu_context, flutter::gpu::Shader *vertex_shader, flutter::gpu::Shader *fragment_shader, Dart_Handle buffer_layouts_handle, Dart_Handle attributes_handle, Dart_Handle attribute_names_handle)
size_t bytes_per_element
impeller::ShaderType type
size_t bit_width
const size_t end