// Copyright 2018 The SwiftShader Authors. All Rights Reserved. // // 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 <spirv/unified1/spirv.hpp> #include "SpirvShader.hpp" #include "System/Math.hpp" #include "Vulkan/VkDebug.hpp" #include "Device/Config.hpp" namespace sw { volatile int SpirvShader::serialCounter = 1; // Start at 1, 0 is invalid shader. SpirvShader::SpirvShader(InsnStore const &insns) : insns{insns}, inputs{MAX_INTERFACE_COMPONENTS}, outputs{MAX_INTERFACE_COMPONENTS}, serialID{serialCounter++}, modes{} { // Simplifying assumptions (to be satisfied by earlier transformations) // - There is exactly one entrypoint in the module, and it's the one we want // - The only input/output OpVariables present are those used by the entrypoint for (auto insn : *this) { switch (insn.opcode()) { case spv::OpExecutionMode: ProcessExecutionMode(insn); break; case spv::OpDecorate: { auto targetId = insn.word(1); decorations[targetId].Apply( static_cast<spv::Decoration>(insn.word(2)), insn.wordCount() > 3 ? insn.word(3) : 0); break; } case spv::OpMemberDecorate: { auto targetId = insn.word(1); auto memberIndex = insn.word(2); auto &d = memberDecorations[targetId]; if (memberIndex >= d.size()) d.resize(memberIndex + 1); // on demand; exact size would require another pass... d[memberIndex].Apply( static_cast<spv::Decoration>(insn.word(3)), insn.wordCount() > 4 ? insn.word(4) : 0); break; } case spv::OpDecorationGroup: // Nothing to do here. We don't need to record the definition of the group; we'll just have // the bundle of decorations float around. If we were to ever walk the decorations directly, // we might think about introducing this as a real Object. break; case spv::OpGroupDecorate: { auto const &srcDecorations = decorations[insn.word(1)]; for (auto i = 2u; i < insn.wordCount(); i++) { // remaining operands are targets to apply the group to. decorations[insn.word(i)].Apply(srcDecorations); } break; } case spv::OpGroupMemberDecorate: { auto const &srcDecorations = decorations[insn.word(1)]; for (auto i = 2u; i < insn.wordCount(); i += 2) { // remaining operands are pairs of <id>, literal for members to apply to. auto &d = memberDecorations[insn.word(i)]; auto memberIndex = insn.word(i + 1); if (memberIndex >= d.size()) d.resize(memberIndex + 1); // on demand resize, see above... d[memberIndex].Apply(srcDecorations); } break; } case spv::OpTypeVoid: case spv::OpTypeBool: case spv::OpTypeInt: case spv::OpTypeFloat: case spv::OpTypeVector: case spv::OpTypeMatrix: case spv::OpTypeImage: case spv::OpTypeSampler: case spv::OpTypeSampledImage: case spv::OpTypeArray: case spv::OpTypeRuntimeArray: case spv::OpTypeStruct: case spv::OpTypePointer: case spv::OpTypeFunction: { auto resultId = insn.word(1); auto &object = types[resultId]; object.kind = Object::Kind::Type; object.definition = insn; object.sizeInComponents = ComputeTypeSize(insn); // A structure is a builtin block if it has a builtin // member. All members of such a structure are builtins. if (insn.opcode() == spv::OpTypeStruct) { auto d = memberDecorations.find(resultId); if (d != memberDecorations.end()) { for (auto &m : d->second) { if (m.HasBuiltIn) { object.isBuiltInBlock = true; break; } } } } else if (insn.opcode() == spv::OpTypePointer) { auto pointeeType = insn.word(3); object.isBuiltInBlock = getType(pointeeType).isBuiltInBlock; } break; } case spv::OpVariable: { auto typeId = insn.word(1); auto resultId = insn.word(2); auto storageClass = static_cast<spv::StorageClass>(insn.word(3)); if (insn.wordCount() > 4) UNIMPLEMENTED("Variable initializers not yet supported"); auto &object = defs[resultId]; object.kind = Object::Kind::Variable; object.definition = insn; object.storageClass = storageClass; auto &type = getType(typeId); object.sizeInComponents = type.sizeInComponents; object.isBuiltInBlock = type.isBuiltInBlock; // Register builtins if (storageClass == spv::StorageClassInput || storageClass == spv::StorageClassOutput) { ProcessInterfaceVariable(object); } break; } case spv::OpConstant: case spv::OpConstantComposite: case spv::OpConstantFalse: case spv::OpConstantTrue: case spv::OpConstantNull: { auto typeId = insn.word(1); auto resultId = insn.word(2); auto &object = defs[resultId]; object.kind = Object::Kind::Constant; object.definition = insn; object.sizeInComponents = getType(typeId).sizeInComponents; break; } case spv::OpCapability: // Various capabilities will be declared, but none affect our code generation at this point. case spv::OpMemoryModel: // Memory model does not affect our code generation until we decide to do Vulkan Memory Model support. case spv::OpEntryPoint: // Due to preprocessing, the entrypoint provides no value. break; default: break; // This is OK, these passes are intentionally partial } } } void SpirvShader::ProcessInterfaceVariable(Object &object) { assert(object.storageClass == spv::StorageClassInput || object.storageClass == spv::StorageClassOutput); auto &builtinInterface = (object.storageClass == spv::StorageClassInput) ? inputBuiltins : outputBuiltins; auto &userDefinedInterface = (object.storageClass == spv::StorageClassInput) ? inputs : outputs; auto resultId = object.definition.word(2); if (object.isBuiltInBlock) { // walk the builtin block, registering each of its members separately. auto ptrType = getType(object.definition.word(1)).definition; assert(ptrType.opcode() == spv::OpTypePointer); auto pointeeType = ptrType.word(3); auto m = memberDecorations.find(pointeeType); assert(m != memberDecorations.end()); // otherwise we wouldn't have marked the type chain auto &structType = getType(pointeeType).definition; auto offset = 0u; auto word = 2u; for (auto &member : m->second) { auto &memberType = getType(structType.word(word)); if (member.HasBuiltIn) { builtinInterface[member.BuiltIn] = {resultId, offset, memberType.sizeInComponents}; } offset += memberType.sizeInComponents; ++word; } return; } auto d = decorations.find(resultId); if (d != decorations.end() && d->second.HasBuiltIn) { builtinInterface[d->second.BuiltIn] = {resultId, 0, object.sizeInComponents}; } else { object.kind = Object::Kind::InterfaceVariable; PopulateInterface(&userDefinedInterface, resultId); } } void SpirvShader::ProcessExecutionMode(InsnIterator insn) { auto mode = static_cast<spv::ExecutionMode>(insn.word(2)); switch (mode) { case spv::ExecutionModeEarlyFragmentTests: modes.EarlyFragmentTests = true; break; case spv::ExecutionModeDepthReplacing: modes.DepthReplacing = true; break; case spv::ExecutionModeDepthGreater: modes.DepthGreater = true; break; case spv::ExecutionModeDepthLess: modes.DepthLess = true; break; case spv::ExecutionModeDepthUnchanged: modes.DepthUnchanged = true; break; case spv::ExecutionModeLocalSize: modes.LocalSizeX = insn.word(3); modes.LocalSizeZ = insn.word(5); modes.LocalSizeY = insn.word(4); break; case spv::ExecutionModeOriginUpperLeft: // This is always the case for a Vulkan shader. Do nothing. break; default: UNIMPLEMENTED("No other execution modes are permitted"); } } uint32_t SpirvShader::ComputeTypeSize(sw::SpirvShader::InsnIterator insn) { // Types are always built from the bottom up (with the exception of forward ptrs, which // don't appear in Vulkan shaders. Therefore, we can always assume our component parts have // already been described (and so their sizes determined) switch (insn.opcode()) { case spv::OpTypeVoid: case spv::OpTypeSampler: case spv::OpTypeImage: case spv::OpTypeSampledImage: case spv::OpTypeFunction: case spv::OpTypeRuntimeArray: // Objects that don't consume any space. // Descriptor-backed objects currently only need exist at compile-time. // Runtime arrays don't appear in places where their size would be interesting return 0; case spv::OpTypeBool: case spv::OpTypeFloat: case spv::OpTypeInt: // All the fundamental types are 1 component. If we ever add support for 8/16/64-bit components, // we might need to change this, but only 32 bit components are required for Vulkan 1.1. return 1; case spv::OpTypeVector: case spv::OpTypeMatrix: // Vectors and matrices both consume element count * element size. return getType(insn.word(2)).sizeInComponents * insn.word(3); case spv::OpTypeArray: { // Element count * element size. Array sizes come from constant ids. auto arraySize = GetConstantInt(insn.word(3)); return getType(insn.word(2)).sizeInComponents * arraySize; } case spv::OpTypeStruct: { uint32_t size = 0; for (uint32_t i = 2u; i < insn.wordCount(); i++) { size += getType(insn.word(i)).sizeInComponents; } return size; } case spv::OpTypePointer: // Pointer 'size' is just pointee size // TODO: this isn't really correct. we should look through pointers as appropriate. return getType(insn.word(3)).sizeInComponents; default: // Some other random insn. UNIMPLEMENTED("Only types are supported"); } } void SpirvShader::PopulateInterfaceSlot(std::vector<InterfaceComponent> *iface, Decorations const &d, AttribType type) { // Populate a single scalar slot in the interface from a collection of decorations and the intended component type. auto scalarSlot = (d.Location << 2) | d.Component; auto &slot = (*iface)[scalarSlot]; slot.Type = type; slot.Flat = d.Flat; slot.NoPerspective = d.NoPerspective; slot.Centroid = d.Centroid; } int SpirvShader::PopulateInterfaceInner(std::vector<InterfaceComponent> *iface, uint32_t id, Decorations d) { // Recursively walks variable definition and its type tree, taking into account // any explicit Location or Component decorations encountered; where explicit // Locations or Components are not specified, assigns them sequentially. // Collected decorations are carried down toward the leaves and across // siblings; Effect of decorations intentionally does not flow back up the tree. // // Returns the next available location. // This covers the rules in Vulkan 1.1 spec, 14.1.4 Location Assignment. auto const it = decorations.find(id); if (it != decorations.end()) { d.Apply(it->second); } auto const &obj = getType(id); switch (obj.definition.opcode()) { case spv::OpTypePointer: return PopulateInterfaceInner(iface, obj.definition.word(3), d); case spv::OpTypeMatrix: for (auto i = 0u; i < obj.definition.word(3); i++, d.Location++) { // consumes same components of N consecutive locations PopulateInterfaceInner(iface, obj.definition.word(2), d); } return d.Location; case spv::OpTypeVector: for (auto i = 0u; i < obj.definition.word(3); i++, d.Component++) { // consumes N consecutive components in the same location PopulateInterfaceInner(iface, obj.definition.word(2), d); } return d.Location + 1; case spv::OpTypeFloat: PopulateInterfaceSlot(iface, d, ATTRIBTYPE_FLOAT); return d.Location + 1; case spv::OpTypeInt: PopulateInterfaceSlot(iface, d, obj.definition.word(3) ? ATTRIBTYPE_INT : ATTRIBTYPE_UINT); return d.Location + 1; case spv::OpTypeBool: PopulateInterfaceSlot(iface, d, ATTRIBTYPE_UINT); return d.Location + 1; case spv::OpTypeStruct: { auto const memberDecorationsIt = memberDecorations.find(id); // iterate over members, which may themselves have Location/Component decorations for (auto i = 0u; i < obj.definition.wordCount() - 2; i++) { // Apply any member decorations for this member to the carried state. if (memberDecorationsIt != memberDecorations.end() && i < memberDecorationsIt->second.size()) { d.Apply(memberDecorationsIt->second[i]); } d.Location = PopulateInterfaceInner(iface, obj.definition.word(i + 2), d); d.Component = 0; // Implicit locations always have component=0 } return d.Location; } case spv::OpTypeArray: { auto arraySize = GetConstantInt(obj.definition.word(3)); for (auto i = 0u; i < arraySize; i++) { d.Location = PopulateInterfaceInner(iface, obj.definition.word(2), d); } return d.Location; } default: // Intentionally partial; most opcodes do not participate in type hierarchies return 0; } } void SpirvShader::PopulateInterface(std::vector<InterfaceComponent> *iface, uint32_t id) { // Walk a variable definition and populate the interface from it. Decorations d{}; auto const it = decorations.find(id); if (it != decorations.end()) { d.Apply(it->second); } auto def = getObject(id).definition; assert(def.opcode() == spv::OpVariable); PopulateInterfaceInner(iface, def.word(1), d); } void SpirvShader::Decorations::Apply(spv::Decoration decoration, uint32_t arg) { switch (decoration) { case spv::DecorationLocation: HasLocation = true; Location = static_cast<int32_t>(arg); break; case spv::DecorationComponent: HasComponent = true; Component = arg; break; case spv::DecorationBuiltIn: HasBuiltIn = true; BuiltIn = static_cast<spv::BuiltIn>(arg); break; case spv::DecorationFlat: Flat = true; break; case spv::DecorationNoPerspective: NoPerspective = true; break; case spv::DecorationCentroid: Centroid = true; break; case spv::DecorationBlock: Block = true; break; case spv::DecorationBufferBlock: BufferBlock = true; break; default: // Intentionally partial, there are many decorations we just don't care about. break; } } void SpirvShader::Decorations::Apply(const sw::SpirvShader::Decorations &src) { // Apply a decoration group to this set of decorations if (src.HasBuiltIn) { HasBuiltIn = true; BuiltIn = src.BuiltIn; } if (src.HasLocation) { HasLocation = true; Location = src.Location; } if (src.HasComponent) { HasComponent = true; Component = src.Component; } Flat |= src.Flat; NoPerspective |= src.NoPerspective; Centroid |= src.Centroid; Block |= src.Block; BufferBlock |= src.BufferBlock; } uint32_t SpirvShader::GetConstantInt(uint32_t id) { // Slightly hackish access to constants very early in translation. // General consumption of constants by other instructions should // probably be just lowered to Reactor. // TODO: not encountered yet since we only use this for array sizes etc, // but is possible to construct integer constant 0 via OpConstantNull. auto insn = defs[id].definition; assert(insn.opcode() == spv::OpConstant); assert(getType(insn.word(1)).definition.opcode() == spv::OpTypeInt); return insn.word(3); } }