Flutter Impeller
compute_pass_vk.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 
12 #include "vulkan/vulkan_structs.hpp"
13 
14 namespace impeller {
15 
16 ComputePassVK::ComputePassVK(std::shared_ptr<const Context> context,
17  std::shared_ptr<CommandBufferVK> command_buffer)
18  : ComputePass(std::move(context)),
19  command_buffer_(std::move(command_buffer)) {
20  // TOOD(dnfield): This should be moved to caps. But for now keeping this
21  // in parallel with Metal.
22  max_wg_size_ = ContextVK::Cast(*context_)
24  .getProperties()
25  .limits.maxComputeWorkGroupSize;
26  is_valid_ = true;
27 }
28 
30 
31 bool ComputePassVK::IsValid() const {
32  return is_valid_;
33 }
34 
35 void ComputePassVK::OnSetLabel(const std::string& label) {
36  if (label.empty()) {
37  return;
38  }
39  label_ = label;
40 }
41 
42 // |RenderPass|
43 void ComputePassVK::SetCommandLabel(std::string_view label) {
44 #ifdef IMPELLER_DEBUG
45  command_buffer_->GetEncoder()->PushDebugGroup(label);
46  has_label_ = true;
47 #endif // IMPELLER_DEBUG
48 }
49 
50 // |ComputePass|
51 void ComputePassVK::SetPipeline(
52  const std::shared_ptr<Pipeline<ComputePipelineDescriptor>>& pipeline) {
53  const auto& pipeline_vk = ComputePipelineVK::Cast(*pipeline);
54  const vk::CommandBuffer& command_buffer_vk =
55  command_buffer_->GetEncoder()->GetCommandBuffer();
56  command_buffer_vk.bindPipeline(vk::PipelineBindPoint::eCompute,
57  pipeline_vk.GetPipeline());
58  pipeline_layout_ = pipeline_vk.GetPipelineLayout();
59 
60  auto descriptor_result =
61  command_buffer_->GetEncoder()->AllocateDescriptorSets(
62  pipeline_vk.GetDescriptorSetLayout(), ContextVK::Cast(*context_));
63  if (!descriptor_result.ok()) {
64  return;
65  }
66  descriptor_set_ = descriptor_result.value();
67  pipeline_valid_ = true;
68 }
69 
70 // |ComputePass|
71 fml::Status ComputePassVK::Compute(const ISize& grid_size) {
72  if (grid_size.IsEmpty() || !pipeline_valid_) {
73  bound_image_offset_ = 0u;
74  bound_buffer_offset_ = 0u;
75  descriptor_write_offset_ = 0u;
76  has_label_ = false;
77  pipeline_valid_ = false;
78  return fml::Status(fml::StatusCode::kCancelled,
79  "Invalid pipeline or empty grid.");
80  }
81 
82  const ContextVK& context_vk = ContextVK::Cast(*context_);
83  for (auto i = 0u; i < descriptor_write_offset_; i++) {
84  write_workspace_[i].dstSet = descriptor_set_;
85  }
86 
87  context_vk.GetDevice().updateDescriptorSets(descriptor_write_offset_,
88  write_workspace_.data(), 0u, {});
89  const vk::CommandBuffer& command_buffer_vk =
90  command_buffer_->GetEncoder()->GetCommandBuffer();
91 
92  command_buffer_vk.bindDescriptorSets(
93  vk::PipelineBindPoint::eCompute, // bind point
94  pipeline_layout_, // layout
95  0, // first set
96  1, // set count
97  &descriptor_set_, // sets
98  0, // offset count
99  nullptr // offsets
100  );
101 
102  int64_t width = grid_size.width;
103  int64_t height = grid_size.height;
104 
105  // Special case for linear processing.
106  if (height == 1) {
107  command_buffer_vk.dispatch(width, 1, 1);
108  } else {
109  while (width > max_wg_size_[0]) {
110  width = std::max(static_cast<int64_t>(1), width / 2);
111  }
112  while (height > max_wg_size_[1]) {
113  height = std::max(static_cast<int64_t>(1), height / 2);
114  }
115  command_buffer_vk.dispatch(width, height, 1);
116  }
117 
118 #ifdef IMPELLER_DEBUG
119  if (has_label_) {
120  command_buffer_->GetEncoder()->PopDebugGroup();
121  }
122  has_label_ = false;
123 #endif // IMPELLER_DEBUG
124 
125  bound_image_offset_ = 0u;
126  bound_buffer_offset_ = 0u;
127  descriptor_write_offset_ = 0u;
128  has_label_ = false;
129  pipeline_valid_ = false;
130 
131  return fml::Status();
132 }
133 
134 // |ResourceBinder|
135 bool ComputePassVK::BindResource(ShaderStage stage,
136  DescriptorType type,
137  const ShaderUniformSlot& slot,
138  const ShaderMetadata& metadata,
139  BufferView view) {
140  return BindResource(slot.binding, type, view);
141 }
142 
143 // |ResourceBinder|
144 bool ComputePassVK::BindResource(
145  ShaderStage stage,
146  DescriptorType type,
147  const SampledImageSlot& slot,
148  const ShaderMetadata& metadata,
149  std::shared_ptr<const Texture> texture,
150  const std::unique_ptr<const Sampler>& sampler) {
151  if (bound_image_offset_ >= kMaxBindings) {
152  return false;
153  }
154  if (!texture->IsValid() || !sampler) {
155  return false;
156  }
157  const TextureVK& texture_vk = TextureVK::Cast(*texture);
158  const SamplerVK& sampler_vk = SamplerVK::Cast(*sampler);
159 
160  if (!command_buffer_->GetEncoder()->Track(texture)) {
161  return false;
162  }
163 
164  vk::DescriptorImageInfo image_info;
165  image_info.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
166  image_info.sampler = sampler_vk.GetSampler();
167  image_info.imageView = texture_vk.GetImageView();
168  image_workspace_[bound_image_offset_++] = image_info;
169 
170  vk::WriteDescriptorSet write_set;
171  write_set.dstBinding = slot.binding;
172  write_set.descriptorCount = 1u;
173  write_set.descriptorType = ToVKDescriptorType(type);
174  write_set.pImageInfo = &image_workspace_[bound_image_offset_ - 1];
175 
176  write_workspace_[descriptor_write_offset_++] = write_set;
177  return true;
178 }
179 
180 bool ComputePassVK::BindResource(size_t binding,
181  DescriptorType type,
182  const BufferView& view) {
183  if (bound_buffer_offset_ >= kMaxBindings) {
184  return false;
185  }
186 
187  const std::shared_ptr<const DeviceBuffer>& device_buffer = view.buffer;
188  auto buffer = DeviceBufferVK::Cast(*device_buffer).GetBuffer();
189  if (!buffer) {
190  return false;
191  }
192 
193  if (!command_buffer_->GetEncoder()->Track(device_buffer)) {
194  return false;
195  }
196 
197  uint32_t offset = view.range.offset;
198 
199  vk::DescriptorBufferInfo buffer_info;
200  buffer_info.buffer = buffer;
201  buffer_info.offset = offset;
202  buffer_info.range = view.range.length;
203  buffer_workspace_[bound_buffer_offset_++] = buffer_info;
204 
205  vk::WriteDescriptorSet write_set;
206  write_set.dstBinding = binding;
207  write_set.descriptorCount = 1u;
208  write_set.descriptorType = ToVKDescriptorType(type);
209  write_set.pBufferInfo = &buffer_workspace_[bound_buffer_offset_ - 1];
210 
211  write_workspace_[descriptor_write_offset_++] = write_set;
212  return true;
213 }
214 
215 // Note:
216 // https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples
217 // Seems to suggest that anything more finely grained than a global memory
218 // barrier is likely to be weakened into a global barrier. Confirming this on
219 // mobile devices will require some experimentation.
220 
221 // |ComputePass|
222 void ComputePassVK::AddBufferMemoryBarrier() {
223  vk::MemoryBarrier barrier;
224  barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
225  barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
226 
227  command_buffer_->GetEncoder()->GetCommandBuffer().pipelineBarrier(
228  vk::PipelineStageFlagBits::eComputeShader,
229  vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, {}, 0, {});
230 }
231 
232 // |ComputePass|
233 void ComputePassVK::AddTextureMemoryBarrier() {
234  vk::MemoryBarrier barrier;
235  barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
236  barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
237 
238  command_buffer_->GetEncoder()->GetCommandBuffer().pipelineBarrier(
239  vk::PipelineStageFlagBits::eComputeShader,
240  vk::PipelineStageFlagBits::eComputeShader, {}, 1, &barrier, 0, {}, 0, {});
241 }
242 
243 // |ComputePass|
244 bool ComputePassVK::EncodeCommands() const {
245  // Since we only use global memory barrier, we don't have to worry about
246  // compute to compute dependencies across cmd buffers. Instead, we pessimize
247  // here and assume that we wrote to a storage image or buffer and that a
248  // render pass will read from it. if there are ever scenarios where we end up
249  // with compute to compute dependencies this should be revisited.
250 
251  // This does not currently handle image barriers as we do not use them
252  // for anything.
253  vk::MemoryBarrier barrier;
254  barrier.srcAccessMask = vk::AccessFlagBits::eShaderWrite;
255  barrier.dstAccessMask =
256  vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eVertexAttributeRead;
257 
258  command_buffer_->GetEncoder()->GetCommandBuffer().pipelineBarrier(
259  vk::PipelineStageFlagBits::eComputeShader,
260  vk::PipelineStageFlagBits::eVertexInput, {}, 1, &barrier, 0, {}, 0, {});
261 
262  return true;
263 }
264 
265 } // namespace impeller
impeller::DeviceBufferVK::GetBuffer
vk::Buffer GetBuffer() const
Definition: device_buffer_vk.cc:86
compute_pass_vk.h
impeller::ToVKDescriptorType
constexpr vk::DescriptorType ToVKDescriptorType(DescriptorType type)
Definition: formats_vk.h:267
impeller::ContextVK::GetPhysicalDevice
vk::PhysicalDevice GetPhysicalDevice() const
Definition: context_vk.cc:547
impeller::ComputePassVK::~ComputePassVK
~ComputePassVK() override
impeller::ShaderStage
ShaderStage
Definition: shader_types.h:22
formats_vk.h
command_buffer_vk.h
impeller::kMaxBindings
static constexpr size_t kMaxBindings
Definition: pipeline_vk.h:26
impeller::ComputePass::context_
const std::shared_ptr< const Context > context_
Definition: compute_pass.h:65
compute_pipeline_vk.h
texture_vk.h
impeller::ISize
TSize< int64_t > ISize
Definition: size.h:138
std
Definition: comparable.h:95
impeller::BackendCast< ContextVK, Context >::Cast
static ContextVK & Cast(Context &base)
Definition: backend_cast.h:13
sampler_vk.h
offset
Point offset
Definition: stroke_path_geometry.cc:300
impeller
Definition: aiks_blur_unittests.cc:20
impeller::DescriptorType
DescriptorType
Definition: shader_types.h:153