blob: 51adb452b107ef2b896d99e65c56d5afb44695dd [file] [log] [blame]
// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/Pipeline.h"
#include <algorithm>
#include <utility>
#include "absl/container/flat_hash_set.h"
#include "dawn/common/Enumerator.h"
#include "dawn/native/BindGroupLayout.h"
#include "dawn/native/Device.h"
#include "dawn/native/ObjectBase.h"
#include "dawn/native/ObjectContentHasher.h"
#include "dawn/native/PipelineLayout.h"
#include "dawn/native/ShaderModule.h"
namespace {
bool IsDoubleValueRepresentableAsF16(double value) {
constexpr double kLowestF16 = -65504.0;
constexpr double kMaxF16 = 65504.0;
return kLowestF16 <= value && value <= kMaxF16;
}
} // namespace
namespace dawn::native {
ResultOrError<ShaderModuleEntryPoint> ValidateProgrammableStage(DeviceBase* device,
const ShaderModuleBase* module,
const char* entryPointName,
uint32_t constantCount,
const ConstantEntry* constants,
const PipelineLayoutBase* layout,
SingleShaderStage stage) {
DAWN_TRY(device->ValidateObject(module));
if (entryPointName) {
DAWN_INVALID_IF(!module->HasEntryPoint(entryPointName),
"Entry point \"%s\" doesn't exist in the shader module %s.", entryPointName,
module);
} else {
size_t entryPointCount = module->GetEntryPointCount(stage);
if (entryPointCount == 0) {
return DAWN_VALIDATION_ERROR(
"Compatible entry point for stage (%s) doesn't exist in the shader module %s.",
stage, module);
} else if (entryPointCount > 1) {
return DAWN_VALIDATION_ERROR(
"The entry-point is defaulted but multiple entry points for stage (%s) exist in "
"the shader module %s.",
stage, module);
}
}
ShaderModuleEntryPoint entryPoint = module->ReifyEntryPointName(entryPointName, stage);
const EntryPointMetadata& metadata = module->GetEntryPoint(entryPoint.name);
if (!metadata.infringedLimitErrors.empty()) {
std::ostringstream limitList;
for (const std::string& limit : metadata.infringedLimitErrors) {
limitList << " - " << limit << "\n";
}
return DAWN_VALIDATION_ERROR("%s infringes limits:\n%s", &entryPoint, limitList.str());
}
DAWN_INVALID_IF(metadata.stage != stage,
"The stage (%s) of the entry point \"%s\" isn't the expected one (%s).",
metadata.stage, entryPoint.name, stage);
if (layout != nullptr) {
DAWN_TRY(ValidateCompatibilityWithPipelineLayout(device, metadata, layout));
}
// Validate if overridable constants exist in shader module
// pipelineBase is not yet constructed at this moment so iterate constants from descriptor
size_t numUninitializedConstants = metadata.uninitializedOverrides.size();
// Keep an initialized constants sets to handle duplicate initialization cases
absl::flat_hash_set<std::string> stageInitializedConstantIdentifiers;
for (uint32_t i = 0; i < constantCount; i++) {
DAWN_INVALID_IF(metadata.overrides.count(constants[i].key) == 0,
"Pipeline overridable constant \"%s\" not found in %s.", constants[i].key,
module);
DAWN_INVALID_IF(!std::isfinite(constants[i].value),
"Pipeline overridable constant \"%s\" with value (%f) is not finite in %s",
constants[i].key, constants[i].value, module);
// Validate if constant value can be represented by the given scalar type in shader
auto type = metadata.overrides.at(constants[i].key).type;
switch (type) {
case EntryPointMetadata::Override::Type::Float32:
DAWN_INVALID_IF(!IsDoubleValueRepresentable<float>(constants[i].value),
"Pipeline overridable constant \"%s\" with value (%f) is not "
"representable in type (%s)",
constants[i].key, constants[i].value, "f32");
break;
case EntryPointMetadata::Override::Type::Float16:
DAWN_INVALID_IF(!IsDoubleValueRepresentableAsF16(constants[i].value),
"Pipeline overridable constant \"%s\" with value (%f) is not "
"representable in type (%s)",
constants[i].key, constants[i].value, "f16");
break;
case EntryPointMetadata::Override::Type::Int32:
DAWN_INVALID_IF(!IsDoubleValueRepresentable<int32_t>(constants[i].value),
"Pipeline overridable constant \"%s\" with value (%f) is not "
"representable in type (%s)",
constants[i].key, constants[i].value,
type == EntryPointMetadata::Override::Type::Int32 ? "i32" : "b");
break;
case EntryPointMetadata::Override::Type::Uint32:
DAWN_INVALID_IF(!IsDoubleValueRepresentable<uint32_t>(constants[i].value),
"Pipeline overridable constant \"%s\" with value (%f) is not "
"representable in type (%s)",
constants[i].key, constants[i].value, "u32");
break;
case EntryPointMetadata::Override::Type::Boolean:
// Conversion to boolean can't fail
// https://webidl.spec.whatwg.org/#es-boolean
break;
default:
DAWN_UNREACHABLE();
}
if (!stageInitializedConstantIdentifiers.contains(constants[i].key)) {
if (metadata.uninitializedOverrides.contains(constants[i].key)) {
numUninitializedConstants--;
}
stageInitializedConstantIdentifiers.insert(constants[i].key);
} else {
// There are duplicate initializations
return DAWN_VALIDATION_ERROR(
"Pipeline overridable constants \"%s\" is set more than once", constants[i].key);
}
}
// Validate if any overridable constant is left uninitialized
if (DAWN_UNLIKELY(numUninitializedConstants > 0)) {
std::string uninitializedConstantsArray;
bool isFirst = true;
for (std::string identifier : metadata.uninitializedOverrides) {
if (stageInitializedConstantIdentifiers.contains(identifier)) {
continue;
}
if (isFirst) {
isFirst = false;
} else {
uninitializedConstantsArray.append(", ");
}
uninitializedConstantsArray.append(identifier);
}
return DAWN_VALIDATION_ERROR(
"There are uninitialized pipeline overridable constants, their "
"identifiers:[%s]",
uninitializedConstantsArray);
}
return entryPoint;
}
WGPUCreatePipelineAsyncStatus CreatePipelineAsyncStatusFromErrorType(InternalErrorType error) {
switch (error) {
case InternalErrorType::None:
return WGPUCreatePipelineAsyncStatus_Success;
case InternalErrorType::Validation:
return WGPUCreatePipelineAsyncStatus_ValidationError;
case InternalErrorType::DeviceLost:
return WGPUCreatePipelineAsyncStatus_DeviceLost;
case InternalErrorType::Internal:
case InternalErrorType::OutOfMemory:
return WGPUCreatePipelineAsyncStatus_InternalError;
default:
DAWN_UNREACHABLE();
return WGPUCreatePipelineAsyncStatus_Unknown;
}
}
// PipelineBase
PipelineBase::PipelineBase(DeviceBase* device,
PipelineLayoutBase* layout,
const char* label,
std::vector<StageAndDescriptor> stages)
: ApiObjectBase(device, label), mLayout(layout) {
DAWN_ASSERT(!stages.empty());
for (const StageAndDescriptor& stage : stages) {
// Extract argument for this stage.
SingleShaderStage shaderStage = stage.shaderStage;
ShaderModuleBase* module = stage.module;
const char* entryPointName = stage.entryPoint.c_str();
const EntryPointMetadata& metadata = module->GetEntryPoint(entryPointName);
DAWN_ASSERT(metadata.stage == shaderStage);
// Record them internally.
bool isFirstStage = mStageMask == wgpu::ShaderStage::None;
mStageMask |= StageBit(shaderStage);
mStages[shaderStage] = {module, entryPointName, &metadata, {}};
auto& constants = mStages[shaderStage].constants;
for (uint32_t i = 0; i < stage.constantCount; i++) {
constants.emplace(stage.constants[i].key, stage.constants[i].value);
}
// Compute the max() of all minBufferSizes across all stages.
RequiredBufferSizes stageMinBufferSizes =
ComputeRequiredBufferSizesForLayout(metadata, layout);
if (isFirstStage) {
mMinBufferSizes = std::move(stageMinBufferSizes);
} else {
for (auto [group, minBufferSize] : Enumerate(mMinBufferSizes)) {
DAWN_ASSERT(stageMinBufferSizes[group].size() == minBufferSize.size());
for (size_t i = 0; i < stageMinBufferSizes[group].size(); ++i) {
minBufferSize[i] = std::max(minBufferSize[i], stageMinBufferSizes[group][i]);
}
}
}
}
}
PipelineBase::PipelineBase(DeviceBase* device, ObjectBase::ErrorTag tag, const char* label)
: ApiObjectBase(device, tag, label) {}
PipelineBase::~PipelineBase() = default;
PipelineLayoutBase* PipelineBase::GetLayout() {
DAWN_ASSERT(!IsError());
return mLayout.Get();
}
const PipelineLayoutBase* PipelineBase::GetLayout() const {
DAWN_ASSERT(!IsError());
return mLayout.Get();
}
const RequiredBufferSizes& PipelineBase::GetMinBufferSizes() const {
DAWN_ASSERT(!IsError());
return mMinBufferSizes;
}
const ProgrammableStage& PipelineBase::GetStage(SingleShaderStage stage) const {
DAWN_ASSERT(!IsError());
return mStages[stage];
}
const PerStage<ProgrammableStage>& PipelineBase::GetAllStages() const {
return mStages;
}
bool PipelineBase::HasStage(SingleShaderStage stage) const {
return mStageMask & StageBit(stage);
}
wgpu::ShaderStage PipelineBase::GetStageMask() const {
return mStageMask;
}
MaybeError PipelineBase::ValidateGetBindGroupLayout(BindGroupIndex groupIndex) {
DAWN_TRY(GetDevice()->ValidateIsAlive());
DAWN_TRY(GetDevice()->ValidateObject(this));
DAWN_TRY(GetDevice()->ValidateObject(mLayout.Get()));
DAWN_INVALID_IF(groupIndex >= kMaxBindGroupsTyped,
"Bind group layout index (%u) exceeds the maximum number of bind groups (%u).",
groupIndex, kMaxBindGroups);
DAWN_INVALID_IF(
!mLayout->GetBindGroupLayoutsMask()[groupIndex],
"Bind group layout index (%u) doesn't correspond to a bind group for this pipeline.",
groupIndex);
return {};
}
ResultOrError<Ref<BindGroupLayoutBase>> PipelineBase::GetBindGroupLayout(uint32_t groupIndexIn) {
BindGroupIndex groupIndex(groupIndexIn);
DAWN_TRY(ValidateGetBindGroupLayout(groupIndex));
return Ref<BindGroupLayoutBase>(mLayout->GetFrontendBindGroupLayout(groupIndex));
}
BindGroupLayoutBase* PipelineBase::APIGetBindGroupLayout(uint32_t groupIndexIn) {
Ref<BindGroupLayoutBase> result;
if (GetDevice()->ConsumedError(GetBindGroupLayout(groupIndexIn), &result,
"Validating GetBindGroupLayout (%u) on %s", groupIndexIn,
this)) {
result = BindGroupLayoutBase::MakeError(GetDevice());
}
return ReturnToAPI(std::move(result));
}
size_t PipelineBase::ComputeContentHash() {
ObjectContentHasher recorder;
recorder.Record(mLayout->GetContentHash());
recorder.Record(mStageMask);
for (SingleShaderStage stage : IterateStages(mStageMask)) {
recorder.Record(mStages[stage].module->GetContentHash());
recorder.Record(mStages[stage].entryPoint);
recorder.Record(mStages[stage].constants);
}
return recorder.GetContentHash();
}
// static
bool PipelineBase::EqualForCache(const PipelineBase* a, const PipelineBase* b) {
// The layout is deduplicated so it can be compared by pointer.
if (a->mLayout.Get() != b->mLayout.Get() || a->mStageMask != b->mStageMask) {
return false;
}
for (SingleShaderStage stage : IterateStages(a->mStageMask)) {
// The module is deduplicated so it can be compared by pointer.
if (a->mStages[stage].module.Get() != b->mStages[stage].module.Get() ||
a->mStages[stage].entryPoint != b->mStages[stage].entryPoint ||
a->mStages[stage].constants.size() != b->mStages[stage].constants.size()) {
return false;
}
// If the constants.size are the same, we still need to compare the key and value.
if (!std::equal(a->mStages[stage].constants.begin(), a->mStages[stage].constants.end(),
b->mStages[stage].constants.begin())) {
return false;
}
}
return true;
}
PipelineBase::ScopedUseShaderPrograms PipelineBase::UseShaderPrograms() {
ScopedUseShaderPrograms programs;
for (SingleShaderStage shaderStage :
{SingleShaderStage::Vertex, SingleShaderStage::Fragment, SingleShaderStage::Compute}) {
auto& module = mStages[shaderStage].module;
if (module.Get()) {
// Hold an external API reference of ShaderModuleBase to keep mTintProgram in
// ShaderModuleBase alive.
programs[shaderStage] = module->UseTintProgram();
}
}
return programs;
}
MaybeError PipelineBase::Initialize(std::optional<ScopedUseShaderPrograms> scopedUsePrograms) {
if (!scopedUsePrograms) {
scopedUsePrograms = UseShaderPrograms();
}
return InitializeImpl();
}
} // namespace dawn::native