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]`. Each buffer's binding slot is
87// implicit in its position in the array (the first buffer is slot 0, etc.).
88constexpr size_t kBufferLayoutInts = 2;
89
90// Width of each "row" in the packed `attributes` ByteData passed from Dart:
91// `[offsetInBytes, formatIndex, nameByteLength]`. Attribute rows are
92// flattened across buffers in buffer-list order; each buffer's
93// `attributeCount` indicates how many attribute rows belong to it. The
94// attribute name itself lives in a parallel `attribute_names` byte blob
95// walked sequentially using the per-row `nameByteLength`.
96constexpr size_t kAttributeInts = 3;
97
98// Builds an `impeller::VertexDescriptor` from the user-supplied buffer
99// layout and attribute arrays, validating each entry against the vertex
100// shader's reflection metadata. On validation failure, returns a non-OK
101// status whose message describes the first problem found.
102//
103// Binding slots are implicit in buffer-list position. Buffer N (0-indexed)
104// is bound at binding slot N. This makes sparse bindings impossible to
105// express by construction; Impeller's RenderPass::SetVertexBuffer also
106// rejects sparse bindings, and lifting that restriction would need a
107// `firstBinding`-style entry point added to the HAL.
108// TODO(https://github.com/flutter/flutter/issues/186308): Allow sparse
109// vertex buffer binding slots.
110absl::StatusOr<std::shared_ptr<impeller::VertexDescriptor>>
111BuildCustomVertexDescriptor(const flutter::gpu::Shader& vertex_shader,
112 std::span<const int32_t> buffer_layouts,
113 std::span<const int32_t> attributes,
114 std::span<const char> attribute_names) {
115 const size_t buffer_layout_count = buffer_layouts.size() / kBufferLayoutInts;
116 const size_t attribute_count = attributes.size() / kAttributeInts;
117 const auto& shader_inputs = vertex_shader.GetStageInputs();
118
119 std::vector<impeller::ShaderStageBufferLayout> stage_layouts;
120 stage_layouts.reserve(buffer_layout_count);
121 std::vector<impeller::ShaderStageIOSlot> stage_inputs;
122 stage_inputs.reserve(attribute_count);
123
124 size_t attr_cursor = 0;
125 size_t name_cursor = 0;
126 for (size_t buffer_index = 0; buffer_index < buffer_layout_count;
127 ++buffer_index) {
128 const int32_t stride = buffer_layouts[buffer_index * kBufferLayoutInts + 0];
129 const int32_t attr_count_in_buffer =
130 buffer_layouts[buffer_index * kBufferLayoutInts + 1];
131 if (stride <= 0) {
132 return absl::InvalidArgumentError(
133 absl::StrCat("VertexBuffer.strideInBytes must be positive (got ",
134 stride, ") on buffer at index ", buffer_index, "."));
135 }
136 if (attr_count_in_buffer < 0 ||
137 attr_cursor + static_cast<size_t>(attr_count_in_buffer) >
138 attribute_count) {
139 return absl::InvalidArgumentError(
140 "Internal error: attribute count overruns the packed attributes "
141 "blob.");
142 }
143 stage_layouts.push_back({static_cast<size_t>(stride), buffer_index});
144
145 // Track each attribute's byte range within this buffer so we can
146 // detect overlaps after building them all.
147 struct AttrRange {
148 std::string name;
149 size_t begin;
150 size_t end;
151 };
152 std::vector<AttrRange> ranges_in_buffer;
153 ranges_in_buffer.reserve(attr_count_in_buffer);
154
155 for (size_t a = 0; a < static_cast<size_t>(attr_count_in_buffer); ++a) {
156 const int32_t offset = attributes[attr_cursor * kAttributeInts + 0];
157 const int32_t format_index = attributes[attr_cursor * kAttributeInts + 1];
158 const int32_t name_byte_length =
159 attributes[attr_cursor * kAttributeInts + 2];
160 ++attr_cursor;
161
162 if (name_byte_length <= 0 ||
163 name_cursor + static_cast<size_t>(name_byte_length) >
164 attribute_names.size()) {
165 return absl::InvalidArgumentError(
166 "Internal error: attribute name overruns the packed names blob.");
167 }
168 const std::string_view name(attribute_names.data() + name_cursor,
169 static_cast<size_t>(name_byte_length));
170 name_cursor += static_cast<size_t>(name_byte_length);
171
172 if (offset < 0) {
173 return absl::InvalidArgumentError(absl::StrCat(
174 "VertexAttribute '", name,
175 "' offsetInBytes must be non-negative (got ", offset, ")."));
176 }
177 if (format_index < 0 ||
178 static_cast<size_t>(format_index) >= kVertexFormatTable.size()) {
179 return absl::InvalidArgumentError(
180 absl::StrCat("VertexAttribute '", name, "' format index ",
181 format_index, " is out of range."));
182 }
183 const VertexFormatInfo& format = kVertexFormatTable[format_index];
184
185 if (static_cast<size_t>(offset) + format.bytes_per_element >
186 static_cast<size_t>(stride)) {
187 return absl::InvalidArgumentError(absl::StrCat(
188 "VertexAttribute '", name, "' (offset ", offset, " + ",
189 format.bytes_per_element, " bytes) overruns stride of ", stride,
190 " on buffer at index ", buffer_index, "."));
191 }
192
193 // Detect overlap against earlier attributes in this buffer before
194 // doing any shader-side lookups, so the overlap diagnostic isn't
195 // shadowed by a less informative name-mismatch error.
196 const size_t begin = static_cast<size_t>(offset);
197 const size_t end = begin + format.bytes_per_element;
198 const std::string name_owned(name);
199 for (const auto& other : ranges_in_buffer) {
200 if (begin < other.end && other.begin < end) {
201 return absl::InvalidArgumentError(
202 absl::StrCat("VertexAttribute '", name, "' (bytes [", begin, ", ",
203 end, ")) overlaps VertexAttribute '", other.name,
204 "' (bytes [", other.begin, ", ", other.end,
205 ")) on buffer at index ", buffer_index, "."));
206 }
207 }
208 ranges_in_buffer.push_back({name_owned, begin, end});
209
210 // Find the matching shader input by name to validate format and to
211 // copy the (location, set, columns, relaxed_precision) metadata we
212 // don't carry on the Dart side. The Shader's IOSlot names point into
213 // the shader bundle flatbuffer, which the Shader keeps alive via its
214 // code mapping; the impellerc-generated builds use static string
215 // literals. Either way, strcmp against a NUL-terminated needle is
216 // safe.
217 const impeller::ShaderStageIOSlot* shader_slot = nullptr;
218 for (const auto& slot : shader_inputs) {
219 if (slot.name != nullptr &&
220 std::strcmp(slot.name, name_owned.c_str()) == 0) {
221 shader_slot = &slot;
222 break;
223 }
224 }
225 if (shader_slot == nullptr) {
226 return absl::InvalidArgumentError(absl::StrCat(
227 "VertexAttribute name '", name,
228 "' does not match any input declared by the bound vertex "
229 "shader."));
230 }
231 // Match the shader's scalar type class (float vs signed int vs
232 // unsigned int). Mirroring WebGPU, Vulkan, and Metal, component-count
233 // mismatches are NOT errors: the shader receives default substitution
234 // (missing components default to (0, 0, 0, 1)) when the buffer
235 // supplies fewer components than declared, and reads only the leading
236 // components when the buffer supplies more. The shipped enum only
237 // contains 32-bit formats, so checking `type` alone is sufficient
238 // until 8/16-bit formats are added.
239 // TODO(https://github.com/flutter/flutter/issues/186309): Add
240 // normalized, packed, half-float, BGRA-swizzled, and 64-bit vertex
241 // attribute formats; the format check will need to also verify
242 // `bit_width` once those land.
243 if (shader_slot->type != format.type ||
244 shader_slot->bit_width != format.bit_width) {
245 return absl::InvalidArgumentError(absl::StrCat(
246 "VertexAttribute '", name,
247 "' format does not match the vertex shader's declared input "
248 "type."));
249 }
250
251 impeller::ShaderStageIOSlot built = *shader_slot;
252 built.binding = buffer_index;
253 built.offset = static_cast<size_t>(offset);
254 stage_inputs.push_back(built);
255 }
256 }
257
258 if (attr_cursor != attribute_count) {
259 return absl::InvalidArgumentError(
260 "Internal error: attributes blob has trailing rows not consumed "
261 "by any buffer.");
262 }
263 if (name_cursor != attribute_names.size()) {
264 return absl::InvalidArgumentError(
265 "Internal error: attribute names blob has trailing bytes.");
266 }
267
268 auto descriptor = std::make_shared<impeller::VertexDescriptor>();
269 descriptor->SetStageInputs(stage_inputs, stage_layouts);
270 return descriptor;
271}
272
273} // namespace
274
275} // namespace gpu
276} // namespace flutter
277
278//----------------------------------------------------------------------------
279/// Exports
280///
281
283 Dart_Handle wrapper,
284 flutter::gpu::Context* gpu_context,
285 flutter::gpu::Shader* vertex_shader,
286 flutter::gpu::Shader* fragment_shader,
287 Dart_Handle buffer_layouts_handle,
288 Dart_Handle attributes_handle,
289 Dart_Handle attribute_names_handle) {
290 // Lazily register the shaders synchronously if they haven't been already.
291 vertex_shader->RegisterSync(*gpu_context);
292 fragment_shader->RegisterSync(*gpu_context);
293
294 std::shared_ptr<impeller::VertexDescriptor> vertex_descriptor;
295
296 const bool buffer_layouts_provided = !Dart_IsNull(buffer_layouts_handle);
297 const bool attributes_provided = !Dart_IsNull(attributes_handle);
298 const bool attribute_names_provided = !Dart_IsNull(attribute_names_handle);
299 if (buffer_layouts_provided != attributes_provided ||
300 attributes_provided != attribute_names_provided) {
301 return tonic::ToDart(
302 "VertexLayout requires buffer layouts, attributes, and attribute "
303 "names to be provided together.");
304 }
305
306 if (buffer_layouts_provided) {
307 // Copy the packed Dart-side ByteData buffers into local vectors so the
308 // tonic::DartByteData typed-data handles are released before we make any
309 // call back into the Dart VM (e.g. tonic::ToDart for an error string).
310 // Holding a typed-data handle while calling into the VM raises
311 // "Callbacks into the Dart VM are currently prohibited." Errors raised
312 // inside the inner scope must therefore be deferred to a local string
313 // and returned only after the typed-data handles go out of scope.
314 std::vector<int32_t> buffer_layouts_ints;
315 std::vector<int32_t> attribute_ints;
316 std::vector<char> attribute_names_bytes;
317 std::string copy_error;
318 {
319 tonic::DartByteData buffer_layouts_data(buffer_layouts_handle);
320 tonic::DartByteData attributes_data(attributes_handle);
321 tonic::DartByteData attribute_names_data(attribute_names_handle);
322 if (buffer_layouts_data.length_in_bytes() %
323 (flutter::gpu::kBufferLayoutInts * sizeof(int32_t)) !=
324 0) {
325 copy_error =
326 "Internal error: buffer layouts ByteData has invalid length.";
327 } else if (attributes_data.length_in_bytes() %
328 (flutter::gpu::kAttributeInts * sizeof(int32_t)) !=
329 0) {
330 copy_error = "Internal error: attributes ByteData has invalid length.";
331 } else {
332 const auto* buffer_layouts_src =
333 static_cast<const int32_t*>(buffer_layouts_data.data());
334 const auto* attributes_src =
335 static_cast<const int32_t*>(attributes_data.data());
336 const auto* names_src =
337 static_cast<const char*>(attribute_names_data.data());
338 buffer_layouts_ints.assign(
339 buffer_layouts_src,
340 buffer_layouts_src +
341 buffer_layouts_data.length_in_bytes() / sizeof(int32_t));
342 attribute_ints.assign(
343 attributes_src, attributes_src + attributes_data.length_in_bytes() /
344 sizeof(int32_t));
345 attribute_names_bytes.assign(
346 names_src, names_src + attribute_names_data.length_in_bytes());
347 }
348 }
349 if (!copy_error.empty()) {
350 return tonic::ToDart(copy_error);
351 }
352
353 absl::StatusOr<std::shared_ptr<impeller::VertexDescriptor>> built =
354 flutter::gpu::BuildCustomVertexDescriptor(
355 *vertex_shader,
356 std::span<const int32_t>(buffer_layouts_ints.data(),
357 buffer_layouts_ints.size()),
358 std::span<const int32_t>(attribute_ints.data(),
359 attribute_ints.size()),
360 std::span<const char>(attribute_names_bytes.data(),
361 attribute_names_bytes.size()));
362 if (!built.ok()) {
363 return tonic::ToDart(std::string(built.status().message()));
364 }
365 vertex_descriptor = *std::move(built);
366 } else {
367 vertex_descriptor = vertex_shader->CreateVertexDescriptor();
368 }
369
370 auto res = fml::MakeRefCounted<flutter::gpu::RenderPipeline>(
371 fml::RefPtr<flutter::gpu::Shader>(vertex_shader), //
372 fml::RefPtr<flutter::gpu::Shader>(fragment_shader), //
373 std::move(vertex_descriptor));
374 res->AssociateWithDartWrapper(wrapper);
375
376 return Dart_Null();
377}
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:77
const std::vector< impeller::ShaderStageIOSlot > & GetStageInputs() const
Definition shader.cc:104
std::shared_ptr< impeller::VertexDescriptor > CreateVertexDescriptor() const
Definition shader.cc:97
const std::vector< impeller::DescriptorSetLayout > & GetDescriptorSetLayouts() const
Definition shader.cc:118
std::shared_ptr< const impeller::ShaderFunction > GetFunctionFromLibrary(impeller::ShaderLibrary &library)
Definition shader.cc:67
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
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