| // Copyright 2017 The NXT Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "backend/ShaderModule.h" |
| |
| #include "backend/BindGroupLayout.h" |
| #include "backend/Device.h" |
| #include "backend/Pipeline.h" |
| #include "backend/PipelineLayout.h" |
| |
| #include <spirv-cross/spirv_cross.hpp> |
| |
| namespace backend { |
| |
| ShaderModuleBase::ShaderModuleBase(ShaderModuleBuilder* builder) : mDevice(builder->mDevice) { |
| } |
| |
| DeviceBase* ShaderModuleBase::GetDevice() const { |
| return mDevice; |
| } |
| |
| void ShaderModuleBase::ExtractSpirvInfo(const spirv_cross::Compiler& compiler) { |
| // TODO(cwallez@chromium.org): make errors here builder-level |
| // currently errors here do not prevent the shadermodule from being used |
| const auto& resources = compiler.get_shader_resources(); |
| |
| switch (compiler.get_execution_model()) { |
| case spv::ExecutionModelVertex: |
| mExecutionModel = nxt::ShaderStage::Vertex; |
| break; |
| case spv::ExecutionModelFragment: |
| mExecutionModel = nxt::ShaderStage::Fragment; |
| break; |
| case spv::ExecutionModelGLCompute: |
| mExecutionModel = nxt::ShaderStage::Compute; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| // Extract push constants |
| mPushConstants.mask.reset(); |
| mPushConstants.sizes.fill(0); |
| mPushConstants.types.fill(PushConstantType::Int); |
| |
| if (resources.push_constant_buffers.size() > 0) { |
| auto interfaceBlock = resources.push_constant_buffers[0]; |
| |
| const auto& blockType = compiler.get_type(interfaceBlock.type_id); |
| ASSERT(blockType.basetype == spirv_cross::SPIRType::Struct); |
| |
| for (uint32_t i = 0; i < blockType.member_types.size(); i++) { |
| ASSERT(compiler.get_member_decoration_mask(blockType.self, i) & |
| 1ull << spv::DecorationOffset); |
| uint32_t offset = |
| compiler.get_member_decoration(blockType.self, i, spv::DecorationOffset); |
| ASSERT(offset % 4 == 0); |
| offset /= 4; |
| |
| auto memberType = compiler.get_type(blockType.member_types[i]); |
| PushConstantType constantType; |
| if (memberType.basetype == spirv_cross::SPIRType::Int) { |
| constantType = PushConstantType::Int; |
| } else if (memberType.basetype == spirv_cross::SPIRType::UInt) { |
| constantType = PushConstantType::UInt; |
| } else { |
| ASSERT(memberType.basetype == spirv_cross::SPIRType::Float); |
| constantType = PushConstantType::Float; |
| } |
| |
| // TODO(cwallez@chromium.org): check for overflows and make the logic better take |
| // into account things like the array of types with padding. |
| uint32_t size = memberType.vecsize * memberType.columns; |
| // Handle unidimensional arrays |
| if (!memberType.array.empty()) { |
| size *= memberType.array[0]; |
| } |
| |
| if (offset + size > kMaxPushConstants) { |
| mDevice->HandleError("Push constant block too big in the SPIRV"); |
| return; |
| } |
| |
| mPushConstants.mask.set(offset); |
| mPushConstants.names[offset] = |
| interfaceBlock.name + "." + compiler.get_member_name(blockType.self, i); |
| mPushConstants.sizes[offset] = size; |
| mPushConstants.types[offset] = constantType; |
| } |
| } |
| |
| // Fill in bindingInfo with the SPIRV bindings |
| auto ExtractResourcesBinding = [this](const std::vector<spirv_cross::Resource>& resources, |
| const spirv_cross::Compiler& compiler, |
| nxt::BindingType bindingType) { |
| constexpr uint64_t requiredBindingDecorationMask = |
| (1ull << spv::DecorationBinding) | (1ull << spv::DecorationDescriptorSet); |
| |
| for (const auto& resource : resources) { |
| ASSERT((compiler.get_decoration_mask(resource.id) & |
| requiredBindingDecorationMask) == requiredBindingDecorationMask); |
| uint32_t binding = compiler.get_decoration(resource.id, spv::DecorationBinding); |
| uint32_t set = compiler.get_decoration(resource.id, spv::DecorationDescriptorSet); |
| |
| if (binding >= kMaxBindingsPerGroup || set >= kMaxBindGroups) { |
| mDevice->HandleError("Binding over limits in the SPIRV"); |
| continue; |
| } |
| |
| auto& info = mBindingInfo[set][binding]; |
| info.used = true; |
| info.id = resource.id; |
| info.base_type_id = resource.base_type_id; |
| info.type = bindingType; |
| } |
| }; |
| |
| ExtractResourcesBinding(resources.uniform_buffers, compiler, |
| nxt::BindingType::UniformBuffer); |
| ExtractResourcesBinding(resources.separate_images, compiler, |
| nxt::BindingType::SampledTexture); |
| ExtractResourcesBinding(resources.separate_samplers, compiler, nxt::BindingType::Sampler); |
| ExtractResourcesBinding(resources.storage_buffers, compiler, |
| nxt::BindingType::StorageBuffer); |
| |
| // Extract the vertex attributes |
| if (mExecutionModel == nxt::ShaderStage::Vertex) { |
| for (const auto& attrib : resources.stage_inputs) { |
| ASSERT(compiler.get_decoration_mask(attrib.id) & (1ull << spv::DecorationLocation)); |
| uint32_t location = compiler.get_decoration(attrib.id, spv::DecorationLocation); |
| |
| if (location >= kMaxVertexAttributes) { |
| mDevice->HandleError("Attribute location over limits in the SPIRV"); |
| return; |
| } |
| |
| mUsedVertexAttributes.set(location); |
| } |
| |
| // Without a location qualifier on vertex outputs, spirv_cross::CompilerMSL gives them |
| // all the location 0, causing a compile error. |
| for (const auto& attrib : resources.stage_outputs) { |
| if (!(compiler.get_decoration_mask(attrib.id) & |
| (1ull << spv::DecorationLocation))) { |
| mDevice->HandleError("Need location qualifier on vertex output"); |
| return; |
| } |
| } |
| } |
| |
| if (mExecutionModel == nxt::ShaderStage::Fragment) { |
| // Without a location qualifier on vertex inputs, spirv_cross::CompilerMSL gives them |
| // all the location 0, causing a compile error. |
| for (const auto& attrib : resources.stage_inputs) { |
| if (!(compiler.get_decoration_mask(attrib.id) & |
| (1ull << spv::DecorationLocation))) { |
| mDevice->HandleError("Need location qualifier on fragment input"); |
| return; |
| } |
| } |
| } |
| } |
| |
| const ShaderModuleBase::PushConstantInfo& ShaderModuleBase::GetPushConstants() const { |
| return mPushConstants; |
| } |
| |
| const ShaderModuleBase::ModuleBindingInfo& ShaderModuleBase::GetBindingInfo() const { |
| return mBindingInfo; |
| } |
| |
| const std::bitset<kMaxVertexAttributes>& ShaderModuleBase::GetUsedVertexAttributes() const { |
| return mUsedVertexAttributes; |
| } |
| |
| nxt::ShaderStage ShaderModuleBase::GetExecutionModel() const { |
| return mExecutionModel; |
| } |
| |
| bool ShaderModuleBase::IsCompatibleWithPipelineLayout(const PipelineLayoutBase* layout) { |
| for (size_t group = 0; group < kMaxBindGroups; ++group) { |
| if (!IsCompatibleWithBindGroupLayout(group, layout->GetBindGroupLayout(group))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ShaderModuleBase::IsCompatibleWithBindGroupLayout(size_t group, |
| const BindGroupLayoutBase* layout) { |
| const auto& layoutInfo = layout->GetBindingInfo(); |
| for (size_t i = 0; i < kMaxBindingsPerGroup; ++i) { |
| const auto& moduleInfo = mBindingInfo[group][i]; |
| |
| if (!moduleInfo.used) { |
| continue; |
| } |
| |
| if (moduleInfo.type != layoutInfo.types[i]) { |
| return false; |
| } |
| if ((layoutInfo.visibilities[i] & StageBit(mExecutionModel)) == 0) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| ShaderModuleBuilder::ShaderModuleBuilder(DeviceBase* device) : Builder(device) { |
| } |
| |
| std::vector<uint32_t> ShaderModuleBuilder::AcquireSpirv() { |
| return std::move(mSpirv); |
| } |
| |
| ShaderModuleBase* ShaderModuleBuilder::GetResultImpl() { |
| if (mSpirv.size() == 0) { |
| HandleError("Shader module needs to have the source set"); |
| return nullptr; |
| } |
| |
| return mDevice->CreateShaderModule(this); |
| } |
| |
| void ShaderModuleBuilder::SetSource(uint32_t codeSize, const uint32_t* code) { |
| mSpirv.assign(code, code + codeSize); |
| } |
| |
| } // namespace backend |