| // 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; |
| } |
| |
| } // namespace dawn::native |