| 1 | // Copyright 2018 The SwiftShader Authors. All Rights Reserved. | 
|---|
| 2 | // | 
|---|
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|---|
| 4 | // you may not use this file except in compliance with the License. | 
|---|
| 5 | // You may obtain a copy of the License at | 
|---|
| 6 | // | 
|---|
| 7 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|---|
| 8 | // | 
|---|
| 9 | // Unless required by applicable law or agreed to in writing, software | 
|---|
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, | 
|---|
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|---|
| 12 | // See the License for the specific language governing permissions and | 
|---|
| 13 | // limitations under the License. | 
|---|
| 14 |  | 
|---|
| 15 | #include "VkPipeline.hpp" | 
|---|
| 16 |  | 
|---|
| 17 | #include "VkDevice.hpp" | 
|---|
| 18 | #include "VkPipelineCache.hpp" | 
|---|
| 19 | #include "VkPipelineLayout.hpp" | 
|---|
| 20 | #include "VkShaderModule.hpp" | 
|---|
| 21 | #include "VkRenderPass.hpp" | 
|---|
| 22 | #include "Pipeline/ComputeProgram.hpp" | 
|---|
| 23 | #include "Pipeline/SpirvShader.hpp" | 
|---|
| 24 |  | 
|---|
| 25 | #include "marl/trace.h" | 
|---|
| 26 |  | 
|---|
| 27 | #include "spirv-tools/optimizer.hpp" | 
|---|
| 28 |  | 
|---|
| 29 | #include <iostream> | 
|---|
| 30 |  | 
|---|
| 31 | namespace | 
|---|
| 32 | { | 
|---|
| 33 |  | 
|---|
| 34 | sw::StreamType getStreamType(VkFormat format) | 
|---|
| 35 | { | 
|---|
| 36 | switch(format) | 
|---|
| 37 | { | 
|---|
| 38 | case VK_FORMAT_R8_UNORM: | 
|---|
| 39 | case VK_FORMAT_R8G8_UNORM: | 
|---|
| 40 | case VK_FORMAT_R8G8B8A8_UNORM: | 
|---|
| 41 | case VK_FORMAT_R8_UINT: | 
|---|
| 42 | case VK_FORMAT_R8G8_UINT: | 
|---|
| 43 | case VK_FORMAT_R8G8B8A8_UINT: | 
|---|
| 44 | case VK_FORMAT_A8B8G8R8_UNORM_PACK32: | 
|---|
| 45 | case VK_FORMAT_A8B8G8R8_UINT_PACK32: | 
|---|
| 46 | return sw::STREAMTYPE_BYTE; | 
|---|
| 47 | case VK_FORMAT_B8G8R8A8_UNORM: | 
|---|
| 48 | return sw::STREAMTYPE_COLOR; | 
|---|
| 49 | case VK_FORMAT_R8_SNORM: | 
|---|
| 50 | case VK_FORMAT_R8_SINT: | 
|---|
| 51 | case VK_FORMAT_R8G8_SNORM: | 
|---|
| 52 | case VK_FORMAT_R8G8_SINT: | 
|---|
| 53 | case VK_FORMAT_R8G8B8A8_SNORM: | 
|---|
| 54 | case VK_FORMAT_R8G8B8A8_SINT: | 
|---|
| 55 | case VK_FORMAT_A8B8G8R8_SNORM_PACK32: | 
|---|
| 56 | case VK_FORMAT_A8B8G8R8_SINT_PACK32: | 
|---|
| 57 | return sw::STREAMTYPE_SBYTE; | 
|---|
| 58 | case VK_FORMAT_A2B10G10R10_UNORM_PACK32: | 
|---|
| 59 | return sw::STREAMTYPE_2_10_10_10_UINT; | 
|---|
| 60 | case VK_FORMAT_R16_UNORM: | 
|---|
| 61 | case VK_FORMAT_R16_UINT: | 
|---|
| 62 | case VK_FORMAT_R16G16_UNORM: | 
|---|
| 63 | case VK_FORMAT_R16G16_UINT: | 
|---|
| 64 | case VK_FORMAT_R16G16B16A16_UNORM: | 
|---|
| 65 | case VK_FORMAT_R16G16B16A16_UINT: | 
|---|
| 66 | return sw::STREAMTYPE_USHORT; | 
|---|
| 67 | case VK_FORMAT_R16_SNORM: | 
|---|
| 68 | case VK_FORMAT_R16_SINT: | 
|---|
| 69 | case VK_FORMAT_R16G16_SNORM: | 
|---|
| 70 | case VK_FORMAT_R16G16_SINT: | 
|---|
| 71 | case VK_FORMAT_R16G16B16A16_SNORM: | 
|---|
| 72 | case VK_FORMAT_R16G16B16A16_SINT: | 
|---|
| 73 | return sw::STREAMTYPE_SHORT; | 
|---|
| 74 | case VK_FORMAT_R16_SFLOAT: | 
|---|
| 75 | case VK_FORMAT_R16G16_SFLOAT: | 
|---|
| 76 | case VK_FORMAT_R16G16B16A16_SFLOAT: | 
|---|
| 77 | return sw::STREAMTYPE_HALF; | 
|---|
| 78 | case VK_FORMAT_R32_UINT: | 
|---|
| 79 | case VK_FORMAT_R32G32_UINT: | 
|---|
| 80 | case VK_FORMAT_R32G32B32_UINT: | 
|---|
| 81 | case VK_FORMAT_R32G32B32A32_UINT: | 
|---|
| 82 | return sw::STREAMTYPE_UINT; | 
|---|
| 83 | case VK_FORMAT_R32_SINT: | 
|---|
| 84 | case VK_FORMAT_R32G32_SINT: | 
|---|
| 85 | case VK_FORMAT_R32G32B32_SINT: | 
|---|
| 86 | case VK_FORMAT_R32G32B32A32_SINT: | 
|---|
| 87 | return sw::STREAMTYPE_INT; | 
|---|
| 88 | case VK_FORMAT_R32_SFLOAT: | 
|---|
| 89 | case VK_FORMAT_R32G32_SFLOAT: | 
|---|
| 90 | case VK_FORMAT_R32G32B32_SFLOAT: | 
|---|
| 91 | case VK_FORMAT_R32G32B32A32_SFLOAT: | 
|---|
| 92 | return sw::STREAMTYPE_FLOAT; | 
|---|
| 93 | default: | 
|---|
| 94 | UNIMPLEMENTED( "format"); | 
|---|
| 95 | } | 
|---|
| 96 |  | 
|---|
| 97 | return sw::STREAMTYPE_BYTE; | 
|---|
| 98 | } | 
|---|
| 99 |  | 
|---|
| 100 | unsigned char getNumberOfChannels(VkFormat format) | 
|---|
| 101 | { | 
|---|
| 102 | switch(format) | 
|---|
| 103 | { | 
|---|
| 104 | case VK_FORMAT_R8_UNORM: | 
|---|
| 105 | case VK_FORMAT_R8_SNORM: | 
|---|
| 106 | case VK_FORMAT_R8_UINT: | 
|---|
| 107 | case VK_FORMAT_R8_SINT: | 
|---|
| 108 | case VK_FORMAT_R16_UNORM: | 
|---|
| 109 | case VK_FORMAT_R16_SNORM: | 
|---|
| 110 | case VK_FORMAT_R16_UINT: | 
|---|
| 111 | case VK_FORMAT_R16_SINT: | 
|---|
| 112 | case VK_FORMAT_R16_SFLOAT: | 
|---|
| 113 | case VK_FORMAT_R32_UINT: | 
|---|
| 114 | case VK_FORMAT_R32_SINT: | 
|---|
| 115 | case VK_FORMAT_R32_SFLOAT: | 
|---|
| 116 | return 1; | 
|---|
| 117 | case VK_FORMAT_R8G8_UNORM: | 
|---|
| 118 | case VK_FORMAT_R8G8_SNORM: | 
|---|
| 119 | case VK_FORMAT_R8G8_UINT: | 
|---|
| 120 | case VK_FORMAT_R8G8_SINT: | 
|---|
| 121 | case VK_FORMAT_R16G16_UNORM: | 
|---|
| 122 | case VK_FORMAT_R16G16_SNORM: | 
|---|
| 123 | case VK_FORMAT_R16G16_UINT: | 
|---|
| 124 | case VK_FORMAT_R16G16_SINT: | 
|---|
| 125 | case VK_FORMAT_R16G16_SFLOAT: | 
|---|
| 126 | case VK_FORMAT_R32G32_UINT: | 
|---|
| 127 | case VK_FORMAT_R32G32_SINT: | 
|---|
| 128 | case VK_FORMAT_R32G32_SFLOAT: | 
|---|
| 129 | return 2; | 
|---|
| 130 | case VK_FORMAT_R32G32B32_UINT: | 
|---|
| 131 | case VK_FORMAT_R32G32B32_SINT: | 
|---|
| 132 | case VK_FORMAT_R32G32B32_SFLOAT: | 
|---|
| 133 | return 3; | 
|---|
| 134 | case VK_FORMAT_R8G8B8A8_UNORM: | 
|---|
| 135 | case VK_FORMAT_R8G8B8A8_SNORM: | 
|---|
| 136 | case VK_FORMAT_R8G8B8A8_UINT: | 
|---|
| 137 | case VK_FORMAT_R8G8B8A8_SINT: | 
|---|
| 138 | case VK_FORMAT_B8G8R8A8_UNORM: | 
|---|
| 139 | case VK_FORMAT_A8B8G8R8_UNORM_PACK32: | 
|---|
| 140 | case VK_FORMAT_A8B8G8R8_SNORM_PACK32: | 
|---|
| 141 | case VK_FORMAT_A8B8G8R8_UINT_PACK32: | 
|---|
| 142 | case VK_FORMAT_A8B8G8R8_SINT_PACK32: | 
|---|
| 143 | case VK_FORMAT_A2B10G10R10_UNORM_PACK32: | 
|---|
| 144 | case VK_FORMAT_R16G16B16A16_UNORM: | 
|---|
| 145 | case VK_FORMAT_R16G16B16A16_SNORM: | 
|---|
| 146 | case VK_FORMAT_R16G16B16A16_UINT: | 
|---|
| 147 | case VK_FORMAT_R16G16B16A16_SINT: | 
|---|
| 148 | case VK_FORMAT_R16G16B16A16_SFLOAT: | 
|---|
| 149 | case VK_FORMAT_R32G32B32A32_UINT: | 
|---|
| 150 | case VK_FORMAT_R32G32B32A32_SINT: | 
|---|
| 151 | case VK_FORMAT_R32G32B32A32_SFLOAT: | 
|---|
| 152 | return 4; | 
|---|
| 153 | default: | 
|---|
| 154 | UNIMPLEMENTED( "format"); | 
|---|
| 155 | } | 
|---|
| 156 |  | 
|---|
| 157 | return 0; | 
|---|
| 158 | } | 
|---|
| 159 |  | 
|---|
| 160 | // preprocessSpirv applies and freezes specializations into constants, and inlines all functions. | 
|---|
| 161 | std::vector<uint32_t> preprocessSpirv( | 
|---|
| 162 | std::vector<uint32_t> const &code, | 
|---|
| 163 | VkSpecializationInfo const *specializationInfo) | 
|---|
| 164 | { | 
|---|
| 165 | spvtools::Optimizer opt{SPV_ENV_VULKAN_1_1}; | 
|---|
| 166 |  | 
|---|
| 167 | opt.SetMessageConsumer([](spv_message_level_t level, const char*, const spv_position_t& p, const char* m) { | 
|---|
| 168 | switch (level) | 
|---|
| 169 | { | 
|---|
| 170 | case SPV_MSG_FATAL:          vk::warn( "SPIR-V FATAL: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 171 | case SPV_MSG_INTERNAL_ERROR: vk::warn( "SPIR-V INTERNAL_ERROR: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 172 | case SPV_MSG_ERROR:          vk::warn( "SPIR-V ERROR: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 173 | case SPV_MSG_WARNING:        vk::warn( "SPIR-V WARNING: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 174 | case SPV_MSG_INFO:           vk::trace( "SPIR-V INFO: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 175 | case SPV_MSG_DEBUG:          vk::trace( "SPIR-V DEBUG: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 176 | default:                     vk::trace( "SPIR-V MESSAGE: %d:%d %s\n", int(p.line), int(p.column), m); | 
|---|
| 177 | } | 
|---|
| 178 | }); | 
|---|
| 179 |  | 
|---|
| 180 | // If the pipeline uses specialization, apply the specializations before freezing | 
|---|
| 181 | if (specializationInfo) | 
|---|
| 182 | { | 
|---|
| 183 | std::unordered_map<uint32_t, std::vector<uint32_t>> specializations; | 
|---|
| 184 | for (auto i = 0u; i < specializationInfo->mapEntryCount; ++i) | 
|---|
| 185 | { | 
|---|
| 186 | auto const &e = specializationInfo->pMapEntries[i]; | 
|---|
| 187 | auto value_ptr = | 
|---|
| 188 | static_cast<uint32_t const *>(specializationInfo->pData) + e.offset / sizeof(uint32_t); | 
|---|
| 189 | specializations.emplace(e.constantID, | 
|---|
| 190 | std::vector<uint32_t>{value_ptr, value_ptr + e.size / sizeof(uint32_t)}); | 
|---|
| 191 | } | 
|---|
| 192 | opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass(specializations)); | 
|---|
| 193 | } | 
|---|
| 194 |  | 
|---|
| 195 | // Full optimization list taken from spirv-opt. | 
|---|
| 196 | opt.RegisterPass(spvtools::CreateWrapOpKillPass()) | 
|---|
| 197 | .RegisterPass(spvtools::CreateDeadBranchElimPass()) | 
|---|
| 198 | .RegisterPass(spvtools::CreateMergeReturnPass()) | 
|---|
| 199 | .RegisterPass(spvtools::CreateInlineExhaustivePass()) | 
|---|
| 200 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 201 | .RegisterPass(spvtools::CreatePrivateToLocalPass()) | 
|---|
| 202 | .RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()) | 
|---|
| 203 | .RegisterPass(spvtools::CreateLocalSingleStoreElimPass()) | 
|---|
| 204 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 205 | .RegisterPass(spvtools::CreateScalarReplacementPass()) | 
|---|
| 206 | .RegisterPass(spvtools::CreateLocalAccessChainConvertPass()) | 
|---|
| 207 | .RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()) | 
|---|
| 208 | .RegisterPass(spvtools::CreateLocalSingleStoreElimPass()) | 
|---|
| 209 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 210 | .RegisterPass(spvtools::CreateLocalMultiStoreElimPass()) | 
|---|
| 211 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 212 | .RegisterPass(spvtools::CreateCCPPass()) | 
|---|
| 213 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 214 | .RegisterPass(spvtools::CreateRedundancyEliminationPass()) | 
|---|
| 215 | .RegisterPass(spvtools::CreateCombineAccessChainsPass()) | 
|---|
| 216 | .RegisterPass(spvtools::CreateSimplificationPass()) | 
|---|
| 217 | .RegisterPass(spvtools::CreateVectorDCEPass()) | 
|---|
| 218 | .RegisterPass(spvtools::CreateDeadInsertElimPass()) | 
|---|
| 219 | .RegisterPass(spvtools::CreateDeadBranchElimPass()) | 
|---|
| 220 | .RegisterPass(spvtools::CreateSimplificationPass()) | 
|---|
| 221 | .RegisterPass(spvtools::CreateIfConversionPass()) | 
|---|
| 222 | .RegisterPass(spvtools::CreateCopyPropagateArraysPass()) | 
|---|
| 223 | .RegisterPass(spvtools::CreateReduceLoadSizePass()) | 
|---|
| 224 | .RegisterPass(spvtools::CreateAggressiveDCEPass()) | 
|---|
| 225 | .RegisterPass(spvtools::CreateBlockMergePass()) | 
|---|
| 226 | .RegisterPass(spvtools::CreateRedundancyEliminationPass()) | 
|---|
| 227 | .RegisterPass(spvtools::CreateDeadBranchElimPass()) | 
|---|
| 228 | .RegisterPass(spvtools::CreateBlockMergePass()) | 
|---|
| 229 | .RegisterPass(spvtools::CreateSimplificationPass()); | 
|---|
| 230 |  | 
|---|
| 231 | std::vector<uint32_t> optimized; | 
|---|
| 232 | opt.Run(code.data(), code.size(), &optimized); | 
|---|
| 233 |  | 
|---|
| 234 | if (false) { | 
|---|
| 235 | spvtools::SpirvTools core(SPV_ENV_VULKAN_1_1); | 
|---|
| 236 | std::string preOpt; | 
|---|
| 237 | core.Disassemble(code, &preOpt, SPV_BINARY_TO_TEXT_OPTION_NONE); | 
|---|
| 238 | std::string postOpt; | 
|---|
| 239 | core.Disassemble(optimized, &postOpt, SPV_BINARY_TO_TEXT_OPTION_NONE); | 
|---|
| 240 | std::cout << "PRE-OPT: "<< preOpt << std::endl | 
|---|
| 241 | << "POST-OPT: "<< postOpt << std::endl; | 
|---|
| 242 | } | 
|---|
| 243 |  | 
|---|
| 244 | return optimized; | 
|---|
| 245 | } | 
|---|
| 246 |  | 
|---|
| 247 | std::shared_ptr<sw::SpirvShader> createShader(const vk::PipelineCache::SpirvShaderKey& key, const vk::ShaderModule *module, bool robustBufferAccess) | 
|---|
| 248 | { | 
|---|
| 249 | auto code = preprocessSpirv(key.getInsns(), key.getSpecializationInfo()); | 
|---|
| 250 | ASSERT(code.size() > 0); | 
|---|
| 251 |  | 
|---|
| 252 | // If the pipeline has specialization constants, assume they're unique and | 
|---|
| 253 | // use a new serial ID so the shader gets recompiled. | 
|---|
| 254 | uint32_t codeSerialID = (key.getSpecializationInfo() ? vk::ShaderModule::nextSerialID() : module->getSerialID()); | 
|---|
| 255 |  | 
|---|
| 256 | // TODO(b/119409619): use allocator. | 
|---|
| 257 | return std::make_shared<sw::SpirvShader>(codeSerialID, key.getPipelineStage(), key.getEntryPointName().c_str(), | 
|---|
| 258 | code, key.getRenderPass(), key.getSubpassIndex(), robustBufferAccess); | 
|---|
| 259 | } | 
|---|
| 260 |  | 
|---|
| 261 | std::shared_ptr<sw::ComputeProgram> createProgram(const vk::PipelineCache::ComputeProgramKey& key) | 
|---|
| 262 | { | 
|---|
| 263 | MARL_SCOPED_EVENT( "createProgram"); | 
|---|
| 264 |  | 
|---|
| 265 | vk::DescriptorSet::Bindings descriptorSets;  // FIXME(b/129523279): Delay code generation until invoke time. | 
|---|
| 266 | // TODO(b/119409619): use allocator. | 
|---|
| 267 | auto program = std::make_shared<sw::ComputeProgram>(key.getShader(), key.getLayout(), descriptorSets); | 
|---|
| 268 | program->generate(); | 
|---|
| 269 | program->finalize(); | 
|---|
| 270 | return program; | 
|---|
| 271 | } | 
|---|
| 272 |  | 
|---|
| 273 | } // anonymous namespace | 
|---|
| 274 |  | 
|---|
| 275 | namespace vk | 
|---|
| 276 | { | 
|---|
| 277 |  | 
|---|
| 278 | Pipeline::Pipeline(PipelineLayout const *layout, const Device *device) | 
|---|
| 279 | : layout(layout), | 
|---|
| 280 | robustBufferAccess(device->getEnabledFeatures().robustBufferAccess) | 
|---|
| 281 | { | 
|---|
| 282 | } | 
|---|
| 283 |  | 
|---|
| 284 | GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo* pCreateInfo, void* mem, const Device *device) | 
|---|
| 285 | : Pipeline(vk::Cast(pCreateInfo->layout), device) | 
|---|
| 286 | { | 
|---|
| 287 | context.robustBufferAccess = robustBufferAccess; | 
|---|
| 288 |  | 
|---|
| 289 | if(((pCreateInfo->flags & | 
|---|
| 290 | ~(VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT | | 
|---|
| 291 | VK_PIPELINE_CREATE_DERIVATIVE_BIT | | 
|---|
| 292 | VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT)) != 0) || | 
|---|
| 293 | (pCreateInfo->pTessellationState != nullptr)) | 
|---|
| 294 | { | 
|---|
| 295 | UNIMPLEMENTED( "pCreateInfo settings"); | 
|---|
| 296 | } | 
|---|
| 297 |  | 
|---|
| 298 | if(pCreateInfo->pDynamicState) | 
|---|
| 299 | { | 
|---|
| 300 | for(uint32_t i = 0; i < pCreateInfo->pDynamicState->dynamicStateCount; i++) | 
|---|
| 301 | { | 
|---|
| 302 | VkDynamicState dynamicState = pCreateInfo->pDynamicState->pDynamicStates[i]; | 
|---|
| 303 | switch(dynamicState) | 
|---|
| 304 | { | 
|---|
| 305 | case VK_DYNAMIC_STATE_VIEWPORT: | 
|---|
| 306 | case VK_DYNAMIC_STATE_SCISSOR: | 
|---|
| 307 | case VK_DYNAMIC_STATE_LINE_WIDTH: | 
|---|
| 308 | case VK_DYNAMIC_STATE_DEPTH_BIAS: | 
|---|
| 309 | case VK_DYNAMIC_STATE_BLEND_CONSTANTS: | 
|---|
| 310 | case VK_DYNAMIC_STATE_DEPTH_BOUNDS: | 
|---|
| 311 | case VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK: | 
|---|
| 312 | case VK_DYNAMIC_STATE_STENCIL_WRITE_MASK: | 
|---|
| 313 | case VK_DYNAMIC_STATE_STENCIL_REFERENCE: | 
|---|
| 314 | ASSERT(dynamicState < (sizeof(dynamicStateFlags) * 8)); | 
|---|
| 315 | dynamicStateFlags |= (1 << dynamicState); | 
|---|
| 316 | break; | 
|---|
| 317 | default: | 
|---|
| 318 | UNIMPLEMENTED( "dynamic state"); | 
|---|
| 319 | } | 
|---|
| 320 | } | 
|---|
| 321 | } | 
|---|
| 322 |  | 
|---|
| 323 | const VkPipelineVertexInputStateCreateInfo* vertexInputState = pCreateInfo->pVertexInputState; | 
|---|
| 324 | if(vertexInputState->flags != 0) | 
|---|
| 325 | { | 
|---|
| 326 | UNIMPLEMENTED( "vertexInputState->flags"); | 
|---|
| 327 | } | 
|---|
| 328 |  | 
|---|
| 329 | // Context must always have a PipelineLayout set. | 
|---|
| 330 | context.pipelineLayout = layout; | 
|---|
| 331 |  | 
|---|
| 332 | // Temporary in-binding-order representation of buffer strides, to be consumed below | 
|---|
| 333 | // when considering attributes. TODO: unfuse buffers from attributes in backend, is old GL model. | 
|---|
| 334 | uint32_t vertexStrides[MAX_VERTEX_INPUT_BINDINGS]; | 
|---|
| 335 | uint32_t instanceStrides[MAX_VERTEX_INPUT_BINDINGS]; | 
|---|
| 336 | for(uint32_t i = 0; i < vertexInputState->vertexBindingDescriptionCount; i++) | 
|---|
| 337 | { | 
|---|
| 338 | auto const & desc = vertexInputState->pVertexBindingDescriptions[i]; | 
|---|
| 339 | vertexStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_VERTEX ? desc.stride : 0; | 
|---|
| 340 | instanceStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_INSTANCE ? desc.stride : 0; | 
|---|
| 341 | } | 
|---|
| 342 |  | 
|---|
| 343 | for(uint32_t i = 0; i < vertexInputState->vertexAttributeDescriptionCount; i++) | 
|---|
| 344 | { | 
|---|
| 345 | auto const & desc = vertexInputState->pVertexAttributeDescriptions[i]; | 
|---|
| 346 | sw::Stream& input = context.input[desc.location]; | 
|---|
| 347 | input.count = getNumberOfChannels(desc.format); | 
|---|
| 348 | input.type = getStreamType(desc.format); | 
|---|
| 349 | input.normalized = !vk::Format(desc.format).isNonNormalizedInteger(); | 
|---|
| 350 | input.offset = desc.offset; | 
|---|
| 351 | input.binding = desc.binding; | 
|---|
| 352 | input.vertexStride = vertexStrides[desc.binding]; | 
|---|
| 353 | input.instanceStride = instanceStrides[desc.binding]; | 
|---|
| 354 | } | 
|---|
| 355 |  | 
|---|
| 356 | const VkPipelineInputAssemblyStateCreateInfo* assemblyState = pCreateInfo->pInputAssemblyState; | 
|---|
| 357 | if(assemblyState->flags != 0) | 
|---|
| 358 | { | 
|---|
| 359 | UNIMPLEMENTED( "pCreateInfo->pInputAssemblyState settings"); | 
|---|
| 360 | } | 
|---|
| 361 |  | 
|---|
| 362 | primitiveRestartEnable = (assemblyState->primitiveRestartEnable != VK_FALSE); | 
|---|
| 363 | context.topology = assemblyState->topology; | 
|---|
| 364 |  | 
|---|
| 365 | const VkPipelineViewportStateCreateInfo* viewportState = pCreateInfo->pViewportState; | 
|---|
| 366 | if(viewportState) | 
|---|
| 367 | { | 
|---|
| 368 | if((viewportState->flags != 0) || | 
|---|
| 369 | (viewportState->viewportCount != 1) || | 
|---|
| 370 | (viewportState->scissorCount != 1)) | 
|---|
| 371 | { | 
|---|
| 372 | UNIMPLEMENTED( "pCreateInfo->pViewportState settings"); | 
|---|
| 373 | } | 
|---|
| 374 |  | 
|---|
| 375 | if(!hasDynamicState(VK_DYNAMIC_STATE_SCISSOR)) | 
|---|
| 376 | { | 
|---|
| 377 | scissor = viewportState->pScissors[0]; | 
|---|
| 378 | } | 
|---|
| 379 |  | 
|---|
| 380 | if(!hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT)) | 
|---|
| 381 | { | 
|---|
| 382 | viewport = viewportState->pViewports[0]; | 
|---|
| 383 | } | 
|---|
| 384 | } | 
|---|
| 385 |  | 
|---|
| 386 | const VkPipelineRasterizationStateCreateInfo* rasterizationState = pCreateInfo->pRasterizationState; | 
|---|
| 387 | if((rasterizationState->flags != 0) || | 
|---|
| 388 | (rasterizationState->depthClampEnable != VK_FALSE)) | 
|---|
| 389 | { | 
|---|
| 390 | UNIMPLEMENTED( "pCreateInfo->pRasterizationState settings"); | 
|---|
| 391 | } | 
|---|
| 392 |  | 
|---|
| 393 | context.rasterizerDiscard = (rasterizationState->rasterizerDiscardEnable == VK_TRUE); | 
|---|
| 394 | context.cullMode = rasterizationState->cullMode; | 
|---|
| 395 | context.frontFace = rasterizationState->frontFace; | 
|---|
| 396 | context.polygonMode = rasterizationState->polygonMode; | 
|---|
| 397 | context.depthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasConstantFactor : 0.0f; | 
|---|
| 398 | context.slopeDepthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasSlopeFactor : 0.0f; | 
|---|
| 399 |  | 
|---|
| 400 | const VkBaseInStructure* extensionCreateInfo = reinterpret_cast<const VkBaseInStructure*>(rasterizationState->pNext); | 
|---|
| 401 | while(extensionCreateInfo) | 
|---|
| 402 | { | 
|---|
| 403 | // Casting to a long since some structures, such as | 
|---|
| 404 | // VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT | 
|---|
| 405 | // are not enumerated in the official Vulkan header | 
|---|
| 406 | switch((long)(extensionCreateInfo->sType)) | 
|---|
| 407 | { | 
|---|
| 408 | case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT: | 
|---|
| 409 | { | 
|---|
| 410 | const VkPipelineRasterizationLineStateCreateInfoEXT* lineStateCreateInfo = reinterpret_cast<const VkPipelineRasterizationLineStateCreateInfoEXT*>(extensionCreateInfo); | 
|---|
| 411 | context.lineRasterizationMode = lineStateCreateInfo->lineRasterizationMode; | 
|---|
| 412 | } | 
|---|
| 413 | break; | 
|---|
| 414 | case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT: | 
|---|
| 415 | { | 
|---|
| 416 | const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT* provokingVertexModeCreateInfo = | 
|---|
| 417 | reinterpret_cast<const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT*>(extensionCreateInfo); | 
|---|
| 418 | context.provokingVertexMode = provokingVertexModeCreateInfo->provokingVertexMode; | 
|---|
| 419 | } | 
|---|
| 420 | break; | 
|---|
| 421 | default: | 
|---|
| 422 | UNIMPLEMENTED( "extensionCreateInfo->sType"); | 
|---|
| 423 | break; | 
|---|
| 424 | } | 
|---|
| 425 |  | 
|---|
| 426 | extensionCreateInfo = extensionCreateInfo->pNext; | 
|---|
| 427 | } | 
|---|
| 428 |  | 
|---|
| 429 | const VkPipelineMultisampleStateCreateInfo* multisampleState = pCreateInfo->pMultisampleState; | 
|---|
| 430 | if(multisampleState) | 
|---|
| 431 | { | 
|---|
| 432 | switch (multisampleState->rasterizationSamples) | 
|---|
| 433 | { | 
|---|
| 434 | case VK_SAMPLE_COUNT_1_BIT: | 
|---|
| 435 | context.sampleCount = 1; | 
|---|
| 436 | break; | 
|---|
| 437 | case VK_SAMPLE_COUNT_4_BIT: | 
|---|
| 438 | context.sampleCount = 4; | 
|---|
| 439 | break; | 
|---|
| 440 | default: | 
|---|
| 441 | UNIMPLEMENTED( "Unsupported sample count"); | 
|---|
| 442 | } | 
|---|
| 443 |  | 
|---|
| 444 | if (multisampleState->pSampleMask) | 
|---|
| 445 | { | 
|---|
| 446 | context.sampleMask = multisampleState->pSampleMask[0]; | 
|---|
| 447 | } | 
|---|
| 448 |  | 
|---|
| 449 | context.alphaToCoverage = (multisampleState->alphaToCoverageEnable == VK_TRUE); | 
|---|
| 450 |  | 
|---|
| 451 | if((multisampleState->flags != 0) || | 
|---|
| 452 | (multisampleState->sampleShadingEnable != VK_FALSE) || | 
|---|
| 453 | (multisampleState->alphaToOneEnable != VK_FALSE)) | 
|---|
| 454 | { | 
|---|
| 455 | UNIMPLEMENTED( "multisampleState"); | 
|---|
| 456 | } | 
|---|
| 457 | } | 
|---|
| 458 | else | 
|---|
| 459 | { | 
|---|
| 460 | context.sampleCount = 1; | 
|---|
| 461 | } | 
|---|
| 462 |  | 
|---|
| 463 | const VkPipelineDepthStencilStateCreateInfo* depthStencilState = pCreateInfo->pDepthStencilState; | 
|---|
| 464 | if(depthStencilState) | 
|---|
| 465 | { | 
|---|
| 466 | if((depthStencilState->flags != 0) || | 
|---|
| 467 | (depthStencilState->depthBoundsTestEnable != VK_FALSE)) | 
|---|
| 468 | { | 
|---|
| 469 | UNIMPLEMENTED( "depthStencilState"); | 
|---|
| 470 | } | 
|---|
| 471 |  | 
|---|
| 472 | context.depthBoundsTestEnable = (depthStencilState->depthBoundsTestEnable == VK_TRUE); | 
|---|
| 473 | context.depthBufferEnable = (depthStencilState->depthTestEnable == VK_TRUE); | 
|---|
| 474 | context.depthWriteEnable = (depthStencilState->depthWriteEnable == VK_TRUE); | 
|---|
| 475 | context.depthCompareMode = depthStencilState->depthCompareOp; | 
|---|
| 476 |  | 
|---|
| 477 | context.stencilEnable = (depthStencilState->stencilTestEnable == VK_TRUE); | 
|---|
| 478 | if(context.stencilEnable) | 
|---|
| 479 | { | 
|---|
| 480 | context.frontStencil = depthStencilState->front; | 
|---|
| 481 | context.backStencil = depthStencilState->back; | 
|---|
| 482 | } | 
|---|
| 483 | } | 
|---|
| 484 |  | 
|---|
| 485 | const VkPipelineColorBlendStateCreateInfo* colorBlendState = pCreateInfo->pColorBlendState; | 
|---|
| 486 | if(colorBlendState) | 
|---|
| 487 | { | 
|---|
| 488 | if((colorBlendState->flags != 0) || | 
|---|
| 489 | ((colorBlendState->logicOpEnable != VK_FALSE))) | 
|---|
| 490 | { | 
|---|
| 491 | UNIMPLEMENTED( "colorBlendState"); | 
|---|
| 492 | } | 
|---|
| 493 |  | 
|---|
| 494 | if(!hasDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS)) | 
|---|
| 495 | { | 
|---|
| 496 | blendConstants.r = colorBlendState->blendConstants[0]; | 
|---|
| 497 | blendConstants.g = colorBlendState->blendConstants[1]; | 
|---|
| 498 | blendConstants.b = colorBlendState->blendConstants[2]; | 
|---|
| 499 | blendConstants.a = colorBlendState->blendConstants[3]; | 
|---|
| 500 | } | 
|---|
| 501 |  | 
|---|
| 502 | for (auto i = 0u; i < colorBlendState->attachmentCount; i++) | 
|---|
| 503 | { | 
|---|
| 504 | const VkPipelineColorBlendAttachmentState& attachment = colorBlendState->pAttachments[i]; | 
|---|
| 505 | context.colorWriteMask[i] = attachment.colorWriteMask; | 
|---|
| 506 |  | 
|---|
| 507 | context.setBlendState(i, { (attachment.blendEnable == VK_TRUE), | 
|---|
| 508 | attachment.srcColorBlendFactor, attachment.dstColorBlendFactor, attachment.colorBlendOp, | 
|---|
| 509 | attachment.srcAlphaBlendFactor, attachment.dstAlphaBlendFactor, attachment.alphaBlendOp }); | 
|---|
| 510 | } | 
|---|
| 511 | } | 
|---|
| 512 |  | 
|---|
| 513 | context.multiSampleMask = context.sampleMask & ((unsigned) 0xFFFFFFFF >> (32 - context.sampleCount)); | 
|---|
| 514 | } | 
|---|
| 515 |  | 
|---|
| 516 | void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks* pAllocator) | 
|---|
| 517 | { | 
|---|
| 518 | vertexShader.reset(); | 
|---|
| 519 | fragmentShader.reset(); | 
|---|
| 520 | } | 
|---|
| 521 |  | 
|---|
| 522 | size_t GraphicsPipeline::ComputeRequiredAllocationSize(const VkGraphicsPipelineCreateInfo* pCreateInfo) | 
|---|
| 523 | { | 
|---|
| 524 | return 0; | 
|---|
| 525 | } | 
|---|
| 526 |  | 
|---|
| 527 | void GraphicsPipeline::setShader(const VkShaderStageFlagBits& stage, const std::shared_ptr<sw::SpirvShader> spirvShader) | 
|---|
| 528 | { | 
|---|
| 529 | switch(stage) | 
|---|
| 530 | { | 
|---|
| 531 | case VK_SHADER_STAGE_VERTEX_BIT: | 
|---|
| 532 | ASSERT(vertexShader.get() == nullptr); | 
|---|
| 533 | vertexShader = spirvShader; | 
|---|
| 534 | context.vertexShader = vertexShader.get(); | 
|---|
| 535 | break; | 
|---|
| 536 |  | 
|---|
| 537 | case VK_SHADER_STAGE_FRAGMENT_BIT: | 
|---|
| 538 | ASSERT(fragmentShader.get() == nullptr); | 
|---|
| 539 | fragmentShader = spirvShader; | 
|---|
| 540 | context.pixelShader = fragmentShader.get(); | 
|---|
| 541 | break; | 
|---|
| 542 |  | 
|---|
| 543 | default: | 
|---|
| 544 | UNSUPPORTED( "Unsupported stage"); | 
|---|
| 545 | break; | 
|---|
| 546 | } | 
|---|
| 547 | } | 
|---|
| 548 |  | 
|---|
| 549 | const std::shared_ptr<sw::SpirvShader> GraphicsPipeline::getShader(const VkShaderStageFlagBits& stage) const | 
|---|
| 550 | { | 
|---|
| 551 | switch(stage) | 
|---|
| 552 | { | 
|---|
| 553 | case VK_SHADER_STAGE_VERTEX_BIT: | 
|---|
| 554 | return vertexShader; | 
|---|
| 555 | case VK_SHADER_STAGE_FRAGMENT_BIT: | 
|---|
| 556 | return fragmentShader; | 
|---|
| 557 | default: | 
|---|
| 558 | UNSUPPORTED( "Unsupported stage"); | 
|---|
| 559 | return fragmentShader; | 
|---|
| 560 | } | 
|---|
| 561 | } | 
|---|
| 562 |  | 
|---|
| 563 | void GraphicsPipeline::compileShaders(const VkAllocationCallbacks* pAllocator, const VkGraphicsPipelineCreateInfo* pCreateInfo, PipelineCache* pPipelineCache) | 
|---|
| 564 | { | 
|---|
| 565 | for (auto pStage = pCreateInfo->pStages; pStage != pCreateInfo->pStages + pCreateInfo->stageCount; pStage++) | 
|---|
| 566 | { | 
|---|
| 567 | if (pStage->flags != 0) | 
|---|
| 568 | { | 
|---|
| 569 | UNIMPLEMENTED( "pStage->flags"); | 
|---|
| 570 | } | 
|---|
| 571 |  | 
|---|
| 572 | const ShaderModule *module = vk::Cast(pStage->module); | 
|---|
| 573 | const PipelineCache::SpirvShaderKey key(pStage->stage, pStage->pName, module->getCode(), | 
|---|
| 574 | vk::Cast(pCreateInfo->renderPass), pCreateInfo->subpass, | 
|---|
| 575 | pStage->pSpecializationInfo); | 
|---|
| 576 | auto pipelineStage = key.getPipelineStage(); | 
|---|
| 577 |  | 
|---|
| 578 | if(pPipelineCache) | 
|---|
| 579 | { | 
|---|
| 580 | PipelineCache& pipelineCache = *pPipelineCache; | 
|---|
| 581 | { | 
|---|
| 582 | std::unique_lock<std::mutex> lock(pipelineCache.getShaderMutex()); | 
|---|
| 583 | const std::shared_ptr<sw::SpirvShader>* spirvShader = pipelineCache[key]; | 
|---|
| 584 | if(!spirvShader) | 
|---|
| 585 | { | 
|---|
| 586 | auto shader = createShader(key, module, robustBufferAccess); | 
|---|
| 587 | setShader(pipelineStage, shader); | 
|---|
| 588 | pipelineCache.insert(key, getShader(pipelineStage)); | 
|---|
| 589 | } | 
|---|
| 590 | else | 
|---|
| 591 | { | 
|---|
| 592 | setShader(pipelineStage, *spirvShader); | 
|---|
| 593 | } | 
|---|
| 594 | } | 
|---|
| 595 | } | 
|---|
| 596 | else | 
|---|
| 597 | { | 
|---|
| 598 | auto shader = createShader(key, module, robustBufferAccess); | 
|---|
| 599 | setShader(pipelineStage, shader); | 
|---|
| 600 | } | 
|---|
| 601 | } | 
|---|
| 602 | } | 
|---|
| 603 |  | 
|---|
| 604 | uint32_t GraphicsPipeline::computePrimitiveCount(uint32_t vertexCount) const | 
|---|
| 605 | { | 
|---|
| 606 | switch(context.topology) | 
|---|
| 607 | { | 
|---|
| 608 | case VK_PRIMITIVE_TOPOLOGY_POINT_LIST: | 
|---|
| 609 | return vertexCount; | 
|---|
| 610 | case VK_PRIMITIVE_TOPOLOGY_LINE_LIST: | 
|---|
| 611 | return vertexCount / 2; | 
|---|
| 612 | case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP: | 
|---|
| 613 | return std::max<uint32_t>(vertexCount, 1) - 1; | 
|---|
| 614 | case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST: | 
|---|
| 615 | return vertexCount / 3; | 
|---|
| 616 | case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP: | 
|---|
| 617 | return std::max<uint32_t>(vertexCount, 2) - 2; | 
|---|
| 618 | case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN: | 
|---|
| 619 | return std::max<uint32_t>(vertexCount, 2) - 2; | 
|---|
| 620 | default: | 
|---|
| 621 | UNIMPLEMENTED( "context.topology %d", int(context.topology)); | 
|---|
| 622 | } | 
|---|
| 623 |  | 
|---|
| 624 | return 0; | 
|---|
| 625 | } | 
|---|
| 626 |  | 
|---|
| 627 | const sw::Context& GraphicsPipeline::getContext() const | 
|---|
| 628 | { | 
|---|
| 629 | return context; | 
|---|
| 630 | } | 
|---|
| 631 |  | 
|---|
| 632 | const VkRect2D& GraphicsPipeline::getScissor() const | 
|---|
| 633 | { | 
|---|
| 634 | return scissor; | 
|---|
| 635 | } | 
|---|
| 636 |  | 
|---|
| 637 | const VkViewport& GraphicsPipeline::getViewport() const | 
|---|
| 638 | { | 
|---|
| 639 | return viewport; | 
|---|
| 640 | } | 
|---|
| 641 |  | 
|---|
| 642 | const sw::Color<float>& GraphicsPipeline::getBlendConstants() const | 
|---|
| 643 | { | 
|---|
| 644 | return blendConstants; | 
|---|
| 645 | } | 
|---|
| 646 |  | 
|---|
| 647 | bool GraphicsPipeline::hasDynamicState(VkDynamicState dynamicState) const | 
|---|
| 648 | { | 
|---|
| 649 | return (dynamicStateFlags & (1 << dynamicState)) != 0; | 
|---|
| 650 | } | 
|---|
| 651 |  | 
|---|
| 652 | ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo* pCreateInfo, void* mem, const Device *device) | 
|---|
| 653 | : Pipeline(vk::Cast(pCreateInfo->layout), device) | 
|---|
| 654 | { | 
|---|
| 655 | } | 
|---|
| 656 |  | 
|---|
| 657 | void ComputePipeline::destroyPipeline(const VkAllocationCallbacks* pAllocator) | 
|---|
| 658 | { | 
|---|
| 659 | shader.reset(); | 
|---|
| 660 | program.reset(); | 
|---|
| 661 | } | 
|---|
| 662 |  | 
|---|
| 663 | size_t ComputePipeline::ComputeRequiredAllocationSize(const VkComputePipelineCreateInfo* pCreateInfo) | 
|---|
| 664 | { | 
|---|
| 665 | return 0; | 
|---|
| 666 | } | 
|---|
| 667 |  | 
|---|
| 668 | void ComputePipeline::compileShaders(const VkAllocationCallbacks* pAllocator, const VkComputePipelineCreateInfo* pCreateInfo, PipelineCache* pPipelineCache) | 
|---|
| 669 | { | 
|---|
| 670 | auto &stage = pCreateInfo->stage; | 
|---|
| 671 | const ShaderModule *module = vk::Cast(stage.module); | 
|---|
| 672 |  | 
|---|
| 673 | ASSERT(shader.get() == nullptr); | 
|---|
| 674 | ASSERT(program.get() == nullptr); | 
|---|
| 675 |  | 
|---|
| 676 | const PipelineCache::SpirvShaderKey shaderKey( | 
|---|
| 677 | stage.stage, stage.pName, module->getCode(), nullptr, 0, stage.pSpecializationInfo); | 
|---|
| 678 | if(pPipelineCache) | 
|---|
| 679 | { | 
|---|
| 680 | PipelineCache& pipelineCache = *pPipelineCache; | 
|---|
| 681 | { | 
|---|
| 682 | std::unique_lock<std::mutex> lock(pipelineCache.getShaderMutex()); | 
|---|
| 683 | const std::shared_ptr<sw::SpirvShader>* spirvShader = pipelineCache[shaderKey]; | 
|---|
| 684 | if(!spirvShader) | 
|---|
| 685 | { | 
|---|
| 686 | shader = createShader(shaderKey, module, robustBufferAccess); | 
|---|
| 687 | pipelineCache.insert(shaderKey, shader); | 
|---|
| 688 | } | 
|---|
| 689 | else | 
|---|
| 690 | { | 
|---|
| 691 | shader = *spirvShader; | 
|---|
| 692 | } | 
|---|
| 693 | } | 
|---|
| 694 |  | 
|---|
| 695 | { | 
|---|
| 696 | const PipelineCache::ComputeProgramKey programKey(shader.get(), layout); | 
|---|
| 697 | std::unique_lock<std::mutex> lock(pipelineCache.getProgramMutex()); | 
|---|
| 698 | const std::shared_ptr<sw::ComputeProgram>* computeProgram = pipelineCache[programKey]; | 
|---|
| 699 | if(!computeProgram) | 
|---|
| 700 | { | 
|---|
| 701 | program = createProgram(programKey); | 
|---|
| 702 | pipelineCache.insert(programKey, program); | 
|---|
| 703 | } | 
|---|
| 704 | else | 
|---|
| 705 | { | 
|---|
| 706 | program = *computeProgram; | 
|---|
| 707 | } | 
|---|
| 708 | } | 
|---|
| 709 | } | 
|---|
| 710 | else | 
|---|
| 711 | { | 
|---|
| 712 | shader = createShader(shaderKey, module, robustBufferAccess); | 
|---|
| 713 | const PipelineCache::ComputeProgramKey programKey(shader.get(), layout); | 
|---|
| 714 | program = createProgram(programKey); | 
|---|
| 715 | } | 
|---|
| 716 | } | 
|---|
| 717 |  | 
|---|
| 718 | void ComputePipeline::run(uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ, | 
|---|
| 719 | uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ, | 
|---|
| 720 | vk::DescriptorSet::Bindings const &descriptorSets, | 
|---|
| 721 | vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets, | 
|---|
| 722 | sw::PushConstantStorage const &pushConstants) | 
|---|
| 723 | { | 
|---|
| 724 | ASSERT_OR_RETURN(program != nullptr); | 
|---|
| 725 | program->run( | 
|---|
| 726 | descriptorSets, descriptorDynamicOffsets, pushConstants, | 
|---|
| 727 | baseGroupX, baseGroupY, baseGroupZ, | 
|---|
| 728 | groupCountX, groupCountY, groupCountZ); | 
|---|
| 729 | } | 
|---|
| 730 |  | 
|---|
| 731 | } // namespace vk | 
|---|
| 732 |  | 
|---|