blob: fa5513d3d6df9532dfcc6aceb441584f35c526d6 [file]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/lib/gpu/compute_pipeline.h"
#include <optional>
#include <utility>
#include "impeller/renderer/capabilities.h"
#include "impeller/renderer/pipeline_library.h"
#include "impeller/renderer/shader_library.h"
#include "third_party/abseil-cpp/absl/status/status.h"
#include "third_party/abseil-cpp/absl/strings/str_cat.h"
#include "tonic/converter/dart_converter.h"
namespace flutter {
namespace gpu {
IMPLEMENT_WRAPPERTYPEINFO(flutter_gpu, ComputePipeline);
ComputePipeline::ComputePipeline(fml::RefPtr<Shader> shader)
: shader_(std::move(shader)) {}
ComputePipeline::~ComputePipeline() = default;
const fml::RefPtr<Shader>& ComputePipeline::GetShader() const {
return shader_;
}
absl::StatusOr<std::shared_ptr<BackendComputePipeline>>
ComputePipeline::GetOrCreatePipeline(impeller::Context& context) {
// A dirty shader was reloaded since it was registered. Re-registering it
// evicts every pipeline built from the old code, including `pipeline_`.
if (shader_->IsDirty()) {
if (absl::Status status = shader_->RegisterSync(context); !status.ok()) {
pipeline_ = nullptr;
return status;
}
}
std::shared_ptr<const impeller::ShaderFunction> function =
shader_->GetFunctionFromLibrary(*context.GetShaderLibrary());
if (!function) {
pipeline_ = nullptr;
return absl::InternalError(
absl::StrCat("Unable to resolve the compute shader function '",
shader_->GetEntrypoint(), "' from the shader library."));
}
// The library hands out a new function object whenever the shader is
// registered again, including by another pipeline built from the same
// shader after a reload, so a matching function means `pipeline_` was built
// from the current code.
if (pipeline_ &&
pipeline_->GetDescriptor().GetStageEntrypoint() == function) {
return pipeline_;
}
pipeline_ = nullptr;
const std::optional<std::array<uint32_t, 3>>& workgroup_size =
shader_->GetWorkgroupSize();
if (!workgroup_size.has_value()) {
return absl::InvalidArgumentError(
absl::StrCat("The shader '", shader_->GetEntrypoint(),
"' is not a compute shader."));
}
impeller::ComputePipelineDescriptor desc;
desc.SetLabel(shader_->GetEntrypoint());
desc.SetStageEntrypoint(std::move(function));
desc.SetWorkgroupSize(workgroup_size.value());
desc.RegisterDescriptorSetLayouts(shader_->GetDescriptorSetLayouts().data(),
shader_->GetDescriptorSetLayouts().size());
// Check the device limits here, rather than leaving it to the backend, so
// that the reason reaches Dart instead of only the validation log.
const impeller::ComputeLimits limits =
context.GetCapabilities()->GetComputeLimits();
if (absl::Status status = desc.ValidateWorkgroupSize(
limits.max_workgroup_size, limits.max_workgroup_invocations);
!status.ok()) {
return status;
}
std::shared_ptr<BackendComputePipeline> pipeline =
context.GetPipelineLibrary()->GetPipeline(desc, /*async=*/false).Get();
if (!pipeline) {
// On Metal a pipeline can allow fewer invocations per workgroup than the
// device, for example when the shader uses many registers. That is only
// known once the backend builds the pipeline, which logs the reason.
const std::array<uint32_t, 3>& size = workgroup_size.value();
return absl::InternalError(absl::StrCat(
"Failed to build the compute pipeline for the shader '",
shader_->GetEntrypoint(), "'. Its workgroup size of ", size[0], "x",
size[1], "x", size[2],
" may exceed what this pipeline supports on this device; the Impeller "
"validation log has the reason."));
}
pipeline_ = std::move(pipeline);
return pipeline_;
}
} // namespace gpu
} // namespace flutter
//----------------------------------------------------------------------------
/// Exports
///
Dart_Handle InternalFlutterGpu_ComputePipeline_Initialize(
Dart_Handle wrapper,
flutter::gpu::Context* gpu_context,
flutter::gpu::Shader* compute_shader) {
impeller::Context& context = gpu_context->GetContext();
if (!flutter::gpu::SupportsCompute(context)) {
return tonic::ToDart(
"Compute is not supported on this backend. Check "
"GpuContext.supportsCompute before creating a ComputePipeline.");
}
if (compute_shader->GetShaderStage() != impeller::ShaderStage::kCompute) {
return tonic::ToDart(absl::StrCat("The shader '",
compute_shader->GetEntrypoint(),
"' is not a compute shader."));
}
auto pipeline = fml::MakeRefCounted<flutter::gpu::ComputePipeline>(
fml::RefPtr<flutter::gpu::Shader>(compute_shader));
// Build the backend pipeline now, so that a shader the device cannot run
// fails here rather than at the first dispatch.
if (absl::StatusOr<std::shared_ptr<flutter::gpu::BackendComputePipeline>>
backend_pipeline = pipeline->GetOrCreatePipeline(context);
!backend_pipeline.ok()) {
return tonic::ToDart(std::string(backend_pipeline.status().message()));
}
pipeline->AssociateWithDartWrapper(wrapper);
return Dart_Null();
}