Flutter Impeller
runtime_effect_contents.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 <algorithm>
8 #include <future>
9 #include <memory>
10 
11 #include "flutter/fml/logging.h"
12 #include "flutter/fml/make_copyable.h"
14 #include "impeller/core/formats.h"
18 #include "impeller/entity/runtime_effect.vert.h"
25 
26 namespace impeller {
27 
28 // static
30  const uint8_t* source_data,
31  HostBuffer& data_host_buffer,
32  const RuntimeUniformDescription& uniform) {
33  size_t minimum_uniform_alignment =
34  data_host_buffer.GetMinimumUniformAlignment();
35  size_t alignment = std::max(uniform.bit_width / 8, minimum_uniform_alignment);
36 
37  if (uniform.padding_layout.empty()) {
38  return data_host_buffer.Emplace(source_data, uniform.GetGPUSize(),
39  alignment);
40  }
41 
42  // If the uniform has a padding layout, we need to repack the data.
43  // We can do this by using the EmplaceProc to write directly to the
44  // HostBuffer.
45  return data_host_buffer.Emplace(
46  uniform.GetGPUSize(), alignment,
47  [&uniform, source_data](uint8_t* destination) {
48  size_t count = uniform.array_elements.value_or(1);
49  if (count == 0) {
50  // Make sure to run at least once.
51  count = 1;
52  }
53  size_t uniform_byte_index = 0u;
54  size_t struct_float_index = 0u;
55  auto* float_destination = reinterpret_cast<float*>(destination);
56  auto* float_source = reinterpret_cast<const float*>(source_data);
57 
58  for (size_t i = 0; i < count; i++) {
59  for (RuntimePaddingType byte_type : uniform.padding_layout) {
60  if (byte_type == RuntimePaddingType::kPadding) {
61  float_destination[struct_float_index++] = 0.f;
62  } else {
63  FML_DCHECK(byte_type == RuntimePaddingType::kFloat);
64  float_destination[struct_float_index++] =
65  float_source[uniform_byte_index++];
66  }
67  }
68  }
69  });
70 }
71 
72 RuntimeEffectContents::RuntimeEffectContents(const Geometry* geometry)
73  : geometry_(geometry) {}
74 
76 
78  return geometry_;
79 }
80 
82  std::shared_ptr<RuntimeStage> runtime_stage) {
83  runtime_stage_ = std::move(runtime_stage);
84  // Precompute the scoped registry name now so that the hot per-frame
85  // `Render` path does not re-allocate it on every call. The name is keyed
86  // on the stage's library id and entrypoint, both of which are stable for
87  // a given `runtime_stage_` value, so this stays valid until the next
88  // `SetRuntimeStage`.
89  if (runtime_stage_) {
90  scoped_fragment_name_ = ShaderKey::MakeUserScopedName(
91  ShaderKey::kScopeRuntimeEffect, runtime_stage_->GetLibraryId(),
92  runtime_stage_->GetEntrypoint());
93  } else {
94  scoped_fragment_name_.clear();
95  }
96 }
97 
99  std::shared_ptr<std::vector<uint8_t>> uniform_data) {
100  uniform_data_ = std::move(uniform_data);
101 }
102 
104  std::vector<TextureInput> texture_inputs) {
105  texture_inputs_ = std::move(texture_inputs);
106 }
107 
109  switch (type) {
110  case kSampledImage:
112  case kFloat:
113  return ShaderType::kFloat;
114  case kStruct:
115  return ShaderType::kStruct;
116  }
117 }
118 
119 static std::unique_ptr<ShaderMetadata> MakeShaderMetadata(
120  const RuntimeUniformDescription& uniform) {
121  std::unique_ptr<ShaderMetadata> metadata = std::make_unique<ShaderMetadata>();
122  metadata->name = uniform.name;
123 
124  // If the element is not an array, then the runtime stage flatbuffer will
125  // represent the unspecified array_elements as the default value of 0.
126  std::optional<size_t> array_elements;
127  if (uniform.array_elements.value_or(0) > 0) {
128  array_elements = uniform.array_elements;
129  }
130 
131  size_t member_size = uniform.dimensions.rows * uniform.dimensions.cols *
132  (uniform.bit_width / 8u);
133 
134  const ShaderType shader_type = GetShaderType(uniform.type);
135  const std::optional<ShaderFloatType> float_type = DeriveShaderFloatType(
136  shader_type, uniform.dimensions.rows, uniform.dimensions.cols);
137 
138  metadata->members.emplace_back(ShaderStructMemberMetadata{
139  .type = shader_type, //
140  .size = member_size, //
141  .byte_length = member_size * array_elements.value_or(1), //
142  .array_elements = array_elements, //
143  .float_type = float_type, //
144  });
145 
146  return metadata;
147 }
148 
150  const ContentContext& renderer) const {
151  if (!RegisterShader(renderer)) {
152  return false;
153  }
154  ContentContextOptions options;
156  renderer.GetContext()->GetCapabilities()->GetDefaultColorFormat();
157  CreatePipeline(renderer, options, /*async=*/true);
158  return true;
159 }
160 
161 bool RuntimeEffectContents::RegisterShader(
162  const ContentContext& renderer) const {
163  const std::shared_ptr<Context>& context = renderer.GetContext();
164  const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
165 
166  std::shared_ptr<const ShaderFunction> function =
167  library->GetFunction(scoped_fragment_name_, ShaderStage::kFragment);
168 
169  //--------------------------------------------------------------------------
170  /// Resolve runtime stage function.
171  ///
172 
173  if (function && runtime_stage_->IsDirty()) {
174  renderer.ClearCachedRuntimeEffectPipeline(scoped_fragment_name_);
175  context->GetPipelineLibrary()->RemovePipelinesWithEntryPoint(function);
176  library->UnregisterFunction(scoped_fragment_name_, ShaderStage::kFragment);
177 
178  function = nullptr;
179  }
180 
181  if (!function) {
182  std::promise<bool> promise;
183  auto future = promise.get_future();
184 
185  library->RegisterFunction(
186  scoped_fragment_name_, ToShaderStage(runtime_stage_->GetShaderStage()),
187  runtime_stage_->GetCodeMapping(),
188  fml::MakeCopyable([promise = std::move(promise)](bool result) mutable {
189  promise.set_value(result);
190  }));
191 
192  if (!future.get()) {
193  VALIDATION_LOG << "Failed to build runtime effect (entry point: "
194  << runtime_stage_->GetEntrypoint() << ")";
195  return false;
196  }
197 
198  function =
199  library->GetFunction(scoped_fragment_name_, ShaderStage::kFragment);
200  if (!function) {
202  << "Failed to fetch runtime effect function immediately after "
203  "registering it (entry point: "
204  << runtime_stage_->GetEntrypoint() << ")";
205  return false;
206  }
207 
208  runtime_stage_->SetClean();
209  }
210  return true;
211 }
212 
213 std::shared_ptr<Pipeline<PipelineDescriptor>>
214 RuntimeEffectContents::CreatePipeline(const ContentContext& renderer,
215  ContentContextOptions options,
216  bool async) const {
217  const std::shared_ptr<Context>& context = renderer.GetContext();
218  const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
219  const std::shared_ptr<const Capabilities>& caps = context->GetCapabilities();
220  const PixelFormat color_attachment_format = caps->GetDefaultColorFormat();
221  const PixelFormat stencil_attachment_format =
222  caps->GetDefaultDepthStencilFormat();
223 
224  using VS = RuntimeEffectVertexShader;
225 
226  PipelineDescriptor desc;
227  desc.SetLabel("Runtime Stage");
228  desc.AddStageEntrypoint(
229  library->GetFunction(VS::kEntrypointName, ShaderStage::kVertex));
230  desc.AddStageEntrypoint(
231  library->GetFunction(scoped_fragment_name_, ShaderStage::kFragment));
232 
233  std::shared_ptr<VertexDescriptor> vertex_descriptor =
234  std::make_shared<VertexDescriptor>();
235  vertex_descriptor->SetStageInputs(VS::kAllShaderStageInputs,
236  VS::kInterleavedBufferLayout);
237  vertex_descriptor->RegisterDescriptorSetLayouts(VS::kDescriptorSetLayouts);
238  vertex_descriptor->RegisterDescriptorSetLayouts(
239  runtime_stage_->GetDescriptorSetLayouts().data(),
240  runtime_stage_->GetDescriptorSetLayouts().size());
241  desc.SetVertexDescriptor(std::move(vertex_descriptor));
242  desc.SetColorAttachmentDescriptor(
243  0u, {.format = color_attachment_format, .blending_enabled = true});
244 
245  desc.SetStencilAttachmentDescriptors(StencilAttachmentDescriptor{});
246  desc.SetStencilPixelFormat(stencil_attachment_format);
247 
248  desc.SetDepthStencilAttachmentDescriptor(DepthAttachmentDescriptor{});
249  desc.SetDepthPixelFormat(stencil_attachment_format);
250 
251  options.ApplyToPipelineDescriptor(desc);
252  if (async) {
253  context->GetPipelineLibrary()->GetPipeline(desc, async);
254  return nullptr;
255  }
256 
257  auto pipeline = context->GetPipelineLibrary()->GetPipeline(desc, async).Get();
258  if (!pipeline) {
259  VALIDATION_LOG << "Failed to get or create runtime effect pipeline.";
260  return nullptr;
261  }
262 
263  return pipeline;
264 }
265 
267  const Entity& entity,
268  RenderPass& pass) const {
269  const std::shared_ptr<Context>& context = renderer.GetContext();
270  const std::shared_ptr<ShaderLibrary>& library = context->GetShaderLibrary();
271 
272  //--------------------------------------------------------------------------
273  /// Get or register shader. Flutter will do this when the runtime effect
274  /// is first loaded, but this check is added to supporting testing of the
275  /// Aiks API and non-flutter usage of Impeller.
276  ///
277  if (!RegisterShader(renderer)) {
278  return false;
279  }
280 
281  //--------------------------------------------------------------------------
282  /// Fragment stage uniforms.
283  ///
284  BindFragmentCallback bind_callback = [this, &renderer,
285  &context](RenderPass& pass) {
286  size_t buffer_index = 0;
287  size_t buffer_offset = 0;
288  size_t sampler_location = 0;
289  size_t buffer_location = 0;
290 
291  // Uniforms are ordered in the IPLR according to their
292  // declaration and the uniform location reflects the correct offset to
293  // be mapped to - except that it may include all proceeding
294  // uniforms of a different type. For example, a texture sampler that comes
295  // after 4 float uniforms may have a location of 4. Since we know that
296  // the declarations are already ordered, we can track the uniform location
297  // ourselves.
298  auto& data_host_buffer = renderer.GetTransientsDataBuffer();
299  for (const auto& uniform : runtime_stage_->GetUniforms()) {
300  std::unique_ptr<ShaderMetadata> metadata = MakeShaderMetadata(uniform);
301  switch (uniform.type) {
302  case kSampledImage: {
303  FML_DCHECK(sampler_location < texture_inputs_.size());
304  auto& input = texture_inputs_[sampler_location];
305 
306  raw_ptr<const Sampler> sampler =
307  context->GetSamplerLibrary()->GetSampler(
308  input.sampler_descriptor);
309 
310  SampledImageSlot image_slot;
311  image_slot.name = uniform.name.c_str();
312  image_slot.binding = uniform.binding;
313  image_slot.texture_index = sampler_location;
315  DescriptorType::kSampledImage, image_slot,
316  std::move(metadata), input.texture, sampler);
317  sampler_location++;
318  break;
319  }
320  case kFloat: {
321  FML_DCHECK(renderer.GetContext()->GetBackendType() !=
323  << "Uniform " << uniform.name
324  << " had unexpected type kFloat for Vulkan backend.";
325 
326  BufferView buffer_view = EmplaceUniform(
327  uniform_data_->data() + buffer_offset, data_host_buffer, uniform);
328 
329  ShaderUniformSlot uniform_slot;
330  uniform_slot.name = uniform.name.c_str();
331  uniform_slot.ext_res_0 = buffer_location;
333  DescriptorType::kUniformBuffer, uniform_slot,
334  std::move(metadata), std::move(buffer_view));
335  buffer_index++;
336  buffer_offset += uniform.GetDartSize();
337  buffer_location++;
338  break;
339  }
340  case kStruct: {
341  FML_DCHECK(renderer.GetContext()->GetBackendType() ==
343  ShaderUniformSlot uniform_slot;
344  uniform_slot.binding = uniform.location;
345  uniform_slot.name = uniform.name.c_str();
346 
347  pass.BindResource(
349  uniform_slot, nullptr,
350  EmplaceUniform(uniform_data_->data(), data_host_buffer, uniform));
351  }
352  }
353  }
354 
355  return true;
356  };
357 
358  /// Now that the descriptor set layouts are known, get the pipeline.
359  using VS = RuntimeEffectVertexShader;
360 
361  PipelineBuilderCallback pipeline_callback =
362  [&](ContentContextOptions options) {
363  // Pipeline creation callback for the cache handler to call.
364  return renderer.GetCachedRuntimeEffectPipeline(
365  scoped_fragment_name_, options, [&]() {
366  return CreatePipeline(renderer, options, /*async=*/false);
367  });
368  };
369 
370  return ColorSourceContents::DrawGeometry<VS>(renderer, entity, pass,
371  pipeline_callback,
372  VS::FrameInfo{}, bind_callback);
373 }
374 
375 } // namespace impeller
std::function< PipelineRef(ContentContextOptions)> PipelineBuilderCallback
std::function< bool(RenderPass &pass)> BindFragmentCallback
void ClearCachedRuntimeEffectPipeline(const std::string &unique_entrypoint_name) const
PipelineRef GetCachedRuntimeEffectPipeline(const std::string &unique_entrypoint_name, const ContentContextOptions &options, const std::function< std::shared_ptr< Pipeline< PipelineDescriptor >>()> &create_callback) const
HostBuffer & GetTransientsDataBuffer() const
Retrieve the current host buffer for transient storage of other non-index data.
std::shared_ptr< Context > GetContext() const
BufferView Emplace(const BufferType &buffer, size_t alignment=0)
Emplace non-uniform data (like contiguous vertices) onto the host buffer.
Definition: host_buffer.h:92
size_t GetMinimumUniformAlignment() const
Retrieve the minimum uniform buffer alignment in bytes.
Definition: host_buffer.cc:241
Render passes encode render commands directed as one specific render target into an underlying comman...
Definition: render_pass.h:30
virtual bool BindDynamicResource(ShaderStage stage, DescriptorType type, const SampledImageSlot &slot, std::unique_ptr< ShaderMetadata > metadata, std::shared_ptr< const Texture > texture, raw_ptr< const Sampler >)
Bind with dynamically generated shader metadata.
Definition: render_pass.cc:270
virtual bool BindResource(ShaderStage stage, DescriptorType type, const ShaderUniformSlot &slot, const ShaderMetadata *metadata, BufferView view) override
Definition: render_pass.cc:225
bool Render(const ContentContext &renderer, const Entity &entity, RenderPass &pass) const override
bool BootstrapShader(const ContentContext &renderer) const
Load the runtime effect and ensure a default PSO is initialized.
void SetRuntimeStage(std::shared_ptr< RuntimeStage > runtime_stage)
void SetTextureInputs(std::vector< TextureInput > texture_inputs)
static BufferView EmplaceUniform(const uint8_t *source_data, HostBuffer &host_buffer, const RuntimeUniformDescription &uniform)
const Geometry * GetGeometry() const override
Get the geometry that this contents will use to render.
void SetUniformData(std::shared_ptr< std::vector< uint8_t >> uniform_data)
A wrapper around a raw ptr that adds additional unopt mode only checks.
Definition: raw_ptr.h:15
constexpr ShaderStage ToShaderStage(RuntimeShaderStage stage)
Definition: shader_types.h:29
constexpr std::optional< ShaderFloatType > DeriveShaderFloatType(ShaderType type, size_t vec_size, size_t columns)
Derive the ShaderFloatType from the base ShaderType and the (vec_size, columns) dimensions reported b...
Definition: shader_types.h:92
static std::unique_ptr< ShaderMetadata > MakeShaderMetadata(const RuntimeUniformDescription &uniform)
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
static ShaderType GetShaderType(RuntimeUniformType type)
LinePipeline::VertexShader VS
std::shared_ptr< ContextGLES > context
std::shared_ptr< PipelineGLES > pipeline
size_t GetGPUSize() const
Computes the total number of bytes that this uniform requires for representation in the GPU.
RuntimeUniformDimensions dimensions
Definition: runtime_types.h:58
std::vector< RuntimePaddingType > padding_layout
Definition: runtime_types.h:61
std::optional< size_t > array_elements
Definition: runtime_types.h:60
Metadata required to bind a combined texture and sampler.
Definition: shader_types.h:153
size_t texture_index
ext_res_0 is the Metal binding value.
Definition: shader_types.h:158
const char * name
The name of the uniform slot.
Definition: shader_types.h:155
size_t binding
The Vulkan binding value.
Definition: shader_types.h:164
static std::string MakeUserScopedName(std::string_view scope, std::string_view library_id, std::string_view entrypoint)
Definition: shader_key.cc:19
static constexpr std::string_view kScopeRuntimeEffect
Definition: shader_key.h:56
Metadata required to bind a buffer.
Definition: shader_types.h:136
size_t binding
The Vulkan binding value.
Definition: shader_types.h:147
size_t ext_res_0
ext_res_0 is the Metal binding value.
Definition: shader_types.h:141
const char * name
The name of the uniform slot.
Definition: shader_types.h:138
#define VALIDATION_LOG
Definition: validation.h:91