From ca9aa8174446baa29c613391884b3c485d7bf444 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Fri, 21 Jun 2024 22:44:30 -0300 Subject: [PATCH] vulkan: improve performance and memory usage when there are many draws without many texture/buffer changes. Descriptor set updates only happen if a resource binding changed since the last draw. Uniform data uses a dynamic uniform buffer descriptor so it doesn't need to update the descriptor set if only the offset changes between draws. --- src/modules/graphics/Shader.h | 1 + src/modules/graphics/vulkan/Shader.cpp | 231 ++++++++++++++----------- src/modules/graphics/vulkan/Shader.h | 5 + 3 files changed, 140 insertions(+), 97 deletions(-) diff --git a/src/modules/graphics/Shader.h b/src/modules/graphics/Shader.h index 760d6c9e1..7eaff628c 100644 --- a/src/modules/graphics/Shader.h +++ b/src/modules/graphics/Shader.h @@ -160,6 +160,7 @@ public: std::string name; int resourceIndex; + int bindingStartIndex; union { diff --git a/src/modules/graphics/vulkan/Shader.cpp b/src/modules/graphics/vulkan/Shader.cpp index f8c547392..54be10bd5 100644 --- a/src/modules/graphics/vulkan/Shader.cpp +++ b/src/modules/graphics/vulkan/Shader.cpp @@ -238,6 +238,8 @@ void Shader::newFrame() currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT; currentDescriptorPool = 0; + currentDescriptorSet = VK_NULL_HANDLE; + resourceDescriptorsDirty = true; for (VkDescriptorPool pool : descriptorPools[currentFrame]) vkResetDescriptorPool(device, pool, 0); @@ -245,25 +247,35 @@ void Shader::newFrame() void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint) { - int imageIndex = 0; - int bufferIndex = 0; - int bufferViewIndex = 0; + bool useLocalUniformOffset = false; + uint32 localUniformOffset = 0; if (!localUniformData.empty()) { if (builtinUniformDataOffset.hasValue) { auto builtinData = vgfx->getCurrentBuiltinUniformData(); - auto dst = localUniformData.data() + builtinUniformDataOffset.value; + auto dst = (BuiltinUniformData *) (localUniformData.data() + builtinUniformDataOffset.value); memcpy(dst, &builtinData, sizeof(builtinData)); } - vgfx->mapLocalUniformData(localUniformData.data(), localUniformData.size(), descriptorBuffers[bufferIndex++]); + VkDescriptorBufferInfo info = {}; + vgfx->mapLocalUniformData(localUniformData.data(), localUniformData.size(), info); + + // This is a dynamic uniform buffer, so the offset is specified in BindDescriptorSets + // and it only needs to update the descriptor sets if the buffer changes. + if (info.buffer != descriptorBuffers[0].buffer) + resourceDescriptorsDirty = true; + + descriptorBuffers[0].buffer = info.buffer; + descriptorBuffers[0].range = info.range; + descriptorBuffers[0].offset = 0; + + useLocalUniformOffset = true; + localUniformOffset = info.offset; } - // TODO: iteration order must match the order at the end of compileShaders right now. - // TODO: We can store data via setTextures and setBuffers instead of iterating over - // everything here. + // Sampler updates need to happen here because the handles may change after sendTextures. for (const auto &u : reflection.sampledTextures) { const auto &info = u.second; @@ -277,77 +289,29 @@ void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBind if (vkTexture == nullptr) throw love::Exception("uniform variable %s is not set.", info.name.c_str()); - VkDescriptorImageInfo &imageInfo = descriptorImages[imageIndex++]; + auto sampler = (VkSampler)vkTexture->getSamplerHandle(); - imageInfo.imageLayout = vkTexture->getImageLayout(); - imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle(); - imageInfo.sampler = (VkSampler)vkTexture->getSamplerHandle(); + VkDescriptorImageInfo &imageInfo = descriptorImages[info.bindingStartIndex + i]; + if (sampler != imageInfo.sampler) + { + imageInfo.sampler = sampler; + resourceDescriptorsDirty = true; + } } } - for (const auto &u : reflection.storageTextures) + if (resourceDescriptorsDirty || currentDescriptorSet == VK_NULL_HANDLE) { - const auto &info = u.second; - if (!info.active) - continue; + currentDescriptorSet = allocateDescriptorSet(); - for (int i = 0; i < info.count; i++) - { - auto vkTexture = dynamic_cast(activeTextures[info.resourceIndex + i]); + for (auto &write : descriptorWrites) + write.dstSet = currentDescriptorSet; - if (vkTexture == nullptr) - throw love::Exception("uniform variable %s is not set.", info.name.c_str()); - - VkDescriptorImageInfo &imageInfo = descriptorImages[imageIndex++]; - - imageInfo.imageLayout = vkTexture->getImageLayout(); - imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle(); - } + vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); + resourceDescriptorsDirty = false; } - for (const auto &u : reflection.texelBuffers) - { - const auto &info = u.second; - if (!info.active) - continue; - - for (int i = 0; i < info.count; i++) - { - auto b = activeBuffers[info.resourceIndex + i]; - if (b == nullptr) - throw love::Exception("uniform variable %s is not set.", info.name.c_str()); - - descriptorBufferViews[bufferViewIndex++] = (VkBufferView)b->getTexelBufferHandle(); - } - } - - for (const auto &u : reflection.storageBuffers) - { - const auto &info = u.second; - if (!info.active) - continue; - - for (int i = 0; i < info.count; i++) - { - auto b = activeBuffers[info.resourceIndex + i]; - if (b == nullptr) - throw love::Exception("uniform variable %s is not set.", info.name.c_str()); - - VkDescriptorBufferInfo &bufferInfo = descriptorBuffers[bufferIndex++]; - bufferInfo.buffer = (VkBuffer)b->getHandle(); - bufferInfo.offset = 0; - bufferInfo.range = b->getSize(); - } - } - - VkDescriptorSet currentDescriptorSet = allocateDescriptorSet(); - - for (auto &write : descriptorWrites) - write.dstSet = currentDescriptorSet; - - vkUpdateDescriptorSets(device, descriptorWrites.size(), descriptorWrites.data(), 0, nullptr); - - vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, ¤tDescriptorSet, 0, nullptr); + vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, ¤tDescriptorSet, useLocalUniformOffset ? 1 : 0, &localUniformOffset); } Shader::~Shader() @@ -404,11 +368,14 @@ void Shader::sendTextures(const UniformInfo *info, graphics::Texture **textures, for (int i = 0; i < count; i++) { int resourceindex = info->resourceIndex + i; - auto oldTexture = activeTextures[resourceindex]; + auto prevtexture = activeTextures[resourceindex]; activeTextures[resourceindex] = textures[i]; activeTextures[resourceindex]->retain(); - if (oldTexture) - oldTexture->release(); + if (prevtexture) + prevtexture->release(); + + if (textures[i] != prevtexture) + setTextureDescriptor(info, textures[i], i); } } @@ -420,11 +387,14 @@ void Shader::sendBuffers(const UniformInfo *info, love::graphics::Buffer **buffe for (int i = 0; i < count; i++) { int resourceindex = info->resourceIndex + i; - auto oldBuffer = activeBuffers[resourceindex]; + auto prevbuffer = activeBuffers[resourceindex]; activeBuffers[resourceindex] = buffers[i]; activeBuffers[resourceindex]->retain(); - if (oldBuffer) - oldBuffer->release(); + if (prevbuffer) + prevbuffer->release(); + + if (buffers[i] != prevbuffer) + setBufferDescriptor(info, buffers[i], i); } } @@ -842,22 +812,28 @@ void Shader::compileShaders() write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.dstBinding = localUniformLocation; write.dstArrayElement = 0; - write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; write.descriptorCount = 1; write.pBufferInfo = &descriptorBuffers.back(); descriptorWrites.push_back(write); } - for (const auto &u : reflection.sampledTextures) + for (auto &u : reflection.sampledTextures) { - const UniformInfo &info = u.second; + UniformInfo &info = u.second; if (!info.active) continue; + info.bindingStartIndex = (int)descriptorImages.size(); + for (int i = 0; i < info.count; i++) { VkDescriptorImageInfo imageInfo{}; descriptorImages.push_back(imageInfo); + + auto texture = activeTextures[info.resourceIndex + i]; + if (texture != nullptr) + setTextureDescriptor(&info, texture, i); } VkWriteDescriptorSet write{}; @@ -866,21 +842,27 @@ void Shader::compileShaders() write.dstArrayElement = 0; write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; write.descriptorCount = static_cast(info.count); - write.pImageInfo = &descriptorImages[descriptorImages.size() - info.count]; + write.pImageInfo = &descriptorImages[info.bindingStartIndex]; descriptorWrites.push_back(write); } - for (const auto &u : reflection.storageTextures) + for (auto &u : reflection.storageTextures) { - const UniformInfo &info = u.second; + UniformInfo &info = u.second; if (!info.active) continue; + info.bindingStartIndex = (int)descriptorImages.size(); + for (int i = 0; i < info.count; i++) { VkDescriptorImageInfo imageInfo{}; descriptorImages.push_back(imageInfo); + + auto texture = activeTextures[info.resourceIndex + i]; + if (texture != nullptr) + setTextureDescriptor(&info, texture, i); } VkWriteDescriptorSet write{}; @@ -889,41 +871,55 @@ void Shader::compileShaders() write.dstArrayElement = 0; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; write.descriptorCount = static_cast(info.count); - write.pImageInfo = &descriptorImages[descriptorImages.size() - info.count]; + write.pImageInfo = &descriptorImages[info.bindingStartIndex]; descriptorWrites.push_back(write); } - for (const auto &u : reflection.texelBuffers) + for (auto &u : reflection.texelBuffers) { - const UniformInfo &info = u.second; + UniformInfo &info = u.second; if (!info.active) continue; + info.bindingStartIndex = (int)descriptorBufferViews.size(); + for (int i = 0; i < info.count; i++) + { descriptorBufferViews.push_back(VK_NULL_HANDLE); + auto buffer = activeBuffers[info.resourceIndex + i]; + if (buffer != nullptr) + setBufferDescriptor(&info, buffer, i); + } + VkWriteDescriptorSet write{}; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.dstBinding = info.location; write.dstArrayElement = 0; write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; write.descriptorCount = info.count; - write.pTexelBufferView = &descriptorBufferViews[descriptorBufferViews.size() - info.count]; + write.pTexelBufferView = &descriptorBufferViews[info.bindingStartIndex]; descriptorWrites.push_back(write); } - for (const auto &u : reflection.storageBuffers) + for (auto &u : reflection.storageBuffers) { - const UniformInfo &info = u.second; + UniformInfo &info = u.second; if (!info.active) continue; + info.bindingStartIndex = (int)descriptorBuffers.size(); + for (int i = 0; i < info.count; i++) { VkDescriptorBufferInfo bufferInfo{}; descriptorBuffers.push_back(bufferInfo); + + auto buffer = activeBuffers[info.resourceIndex + i]; + if (buffer != nullptr) + setBufferDescriptor(&info, buffer, i); } VkWriteDescriptorSet write{}; @@ -932,10 +928,12 @@ void Shader::compileShaders() write.dstArrayElement = 0; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; write.descriptorCount = info.count; - write.pBufferInfo = &descriptorBuffers[descriptorBuffers.size() - info.count]; + write.pBufferInfo = &descriptorBuffers[info.bindingStartIndex]; descriptorWrites.push_back(write); } + + resourceDescriptorsDirty = true; } void Shader::createDescriptorSetLayout() @@ -948,7 +946,7 @@ void Shader::createDescriptorSetLayout() continue; auto type = Vulkan::getDescriptorType(entry.second->baseType); - if (type != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + if (type != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) { VkDescriptorSetLayoutBinding layoutBinding{}; @@ -965,7 +963,7 @@ void Shader::createDescriptorSetLayout() { VkDescriptorSetLayoutBinding uniformBinding{}; uniformBinding.binding = localUniformLocation; - uniformBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uniformBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; uniformBinding.descriptorCount = 1; if (isCompute) uniformBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; @@ -1013,7 +1011,7 @@ void Shader::createDescriptorPoolSizes() if (!localUniformData.empty()) { VkDescriptorPoolSize size{}; - size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC; size.descriptorCount = 1; descriptorPoolSizes.push_back(size); @@ -1026,7 +1024,7 @@ void Shader::createDescriptorPoolSizes() VkDescriptorPoolSize size{}; auto type = Vulkan::getDescriptorType(entry.second->baseType); - if (type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + if (type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) continue; size.type = type; @@ -1054,10 +1052,14 @@ void Shader::setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cb const UniformInfo *u = builtinUniformInfo[builtIns[i]]; if (u != nullptr) { + auto prevtexture = activeTextures[u->resourceIndex]; textures[i]->retain(); - if (activeTextures[u->resourceIndex]) - activeTextures[u->resourceIndex]->release(); + if (prevtexture) + prevtexture->release(); activeTextures[u->resourceIndex] = textures[i]; + + if (textures[i] != prevtexture) + setTextureDescriptor(u, textures[i], 0); } } } @@ -1067,13 +1069,48 @@ void Shader::setMainTex(graphics::Texture *texture) const UniformInfo *u = builtinUniformInfo[BUILTIN_TEXTURE_MAIN]; if (u != nullptr) { + auto prevtexture = activeTextures[u->resourceIndex]; texture->retain(); - if (activeTextures[u->resourceIndex]) - activeTextures[u->resourceIndex]->release(); + if (prevtexture) + prevtexture->release(); activeTextures[u->resourceIndex] = texture; + + if (texture != prevtexture) + setTextureDescriptor(u, texture, 0); } } +void Shader::setTextureDescriptor(const UniformInfo *info, love::graphics::Texture *texture, int index) +{ + auto vkTexture = dynamic_cast(texture); + + VkDescriptorImageInfo &imageInfo = descriptorImages[info->bindingStartIndex + index]; + + // Samplers may change after this call, so they're set just before the + // descriptor set is used instead of here. + imageInfo.imageLayout = vkTexture->getImageLayout(); + imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle(); + + resourceDescriptorsDirty = true; +} + +void Shader::setBufferDescriptor(const UniformInfo *info, love::graphics::Buffer *buffer, int index) +{ + if (info->baseType == UNIFORM_STORAGEBUFFER) + { + VkDescriptorBufferInfo &bufferInfo = descriptorBuffers[info->bindingStartIndex + index]; + bufferInfo.buffer = (VkBuffer)buffer->getHandle(); + bufferInfo.offset = 0; + bufferInfo.range = buffer->getSize(); + } + else if (info->baseType == UNIFORM_TEXELBUFFER) + { + descriptorBufferViews[info->bindingStartIndex + index] = (VkBufferView)buffer->getTexelBufferHandle(); + } + + resourceDescriptorsDirty = true; +} + void Shader::createDescriptorPool() { VkDescriptorPoolCreateInfo createInfo{}; diff --git a/src/modules/graphics/vulkan/Shader.h b/src/modules/graphics/vulkan/Shader.h index e1f676f6e..3e4fe7edb 100644 --- a/src/modules/graphics/vulkan/Shader.h +++ b/src/modules/graphics/vulkan/Shader.h @@ -104,6 +104,9 @@ private: void createDescriptorPool(); VkDescriptorSet allocateDescriptorSet(); + void setTextureDescriptor(const UniformInfo *info, love::graphics::Texture *texture, int index); + void setBufferDescriptor(const UniformInfo *info, love::graphics::Buffer *buffer, int index); + VkPipeline computePipeline; VkDescriptorSetLayout descriptorSetLayout; @@ -124,6 +127,8 @@ private: VkDevice device; bool isCompute = false; + bool resourceDescriptorsDirty = false; + VkDescriptorSet currentDescriptorSet = VK_NULL_HANDLE; UniformInfo *builtinUniformInfo[BUILTIN_MAX_ENUM];