/* Copyright (C) 2024 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ShaderProgram.h" #include "graphics/ShaderDefines.h" #include "ps/CLogger.h" #include "ps/containers/StaticVector.h" #include "ps/CStr.h" #include "ps/CStrInternStatic.h" #include "ps/Filesystem.h" #include "ps/Profile.h" #include "ps/XML/Xeromyces.h" #include "renderer/backend/vulkan/DescriptorManager.h" #include "renderer/backend/vulkan/Device.h" #include "renderer/backend/vulkan/RingCommandContext.h" #include "renderer/backend/vulkan/Texture.h" #include "renderer/backend/vulkan/Utilities.h" #include #include namespace Renderer { namespace Backend { namespace Vulkan { namespace { VkShaderModule CreateShaderModule(CDevice* device, const VfsPath& path) { CVFSFile file; if (file.Load(g_VFS, path) != PSRETURN_OK) { LOGERROR("Failed to load shader file: '%s'", path.string8()); return VK_NULL_HANDLE; } VkShaderModuleCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; // Casting to uint32_t requires to fit alignment and size. ENSURE(file.GetBufferSize() % 4 == 0); ENSURE(reinterpret_cast(file.GetBuffer()) % alignof(uint32_t) == 0u); createInfo.codeSize = file.GetBufferSize(); createInfo.pCode = reinterpret_cast(file.GetBuffer()); VkShaderModule shaderModule; const VkResult result = vkCreateShaderModule(device->GetVkDevice(), &createInfo, nullptr, &shaderModule); if (result != VK_SUCCESS) { LOGERROR("Failed to create shader module from file: '%s' %d (%s)", path.string8(), static_cast(result), Utilities::GetVkResultName(result)); return VK_NULL_HANDLE; } device->SetObjectName(VK_OBJECT_TYPE_SHADER_MODULE, shaderModule, path.string8().c_str()); return shaderModule; } VfsPath FindProgramMatchingDefines(const VfsPath& xmlFilename, const CShaderDefines& defines) { CXeromyces xeroFile; PSRETURN ret = xeroFile.Load(g_VFS, xmlFilename); if (ret != PSRETURN_OK) return {}; // TODO: add XML validation. #define EL(x) const int el_##x = xeroFile.GetElementID(#x) #define AT(x) const int at_##x = xeroFile.GetAttributeID(#x) EL(define); EL(defines); EL(program); AT(file); AT(name); AT(value); #undef AT #undef EL const CStrIntern strUndefined("UNDEFINED"); VfsPath programFilename; XMBElement root = xeroFile.GetRoot(); XERO_ITER_EL(root, rootChild) { if (rootChild.GetNodeName() == el_program) { CShaderDefines programDefines; XERO_ITER_EL(rootChild, programChild) { if (programChild.GetNodeName() == el_defines) { XERO_ITER_EL(programChild, definesChild) { XMBAttributeList attributes = definesChild.GetAttributes(); if (definesChild.GetNodeName() == el_define) { const CStrIntern value(attributes.GetNamedItem(at_value)); if (value == strUndefined) continue; programDefines.Add( CStrIntern(attributes.GetNamedItem(at_name)), value); } } } } if (programDefines == defines) return L"shaders/" + rootChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); } } return {}; } } // anonymous namespace IDevice* CVertexInputLayout::GetDevice() { return m_Device; } // static std::unique_ptr CShaderProgram::Create( CDevice* device, const CStr& name, const CShaderDefines& baseDefines) { const VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml"; std::unique_ptr shaderProgram(new CShaderProgram()); shaderProgram->m_Device = device; shaderProgram->m_FileDependencies = {xmlFilename}; CShaderDefines defines = baseDefines; if (device->GetDescriptorManager().UseDescriptorIndexing()) defines.Add(str_USE_DESCRIPTOR_INDEXING, str_1); const VfsPath programFilename = FindProgramMatchingDefines(xmlFilename, defines); if (programFilename.empty()) { LOGERROR("Program '%s' with required defines not found.", name); for (const auto& pair : defines.GetMap()) LOGERROR(" \"%s\": \"%s\"", pair.first.c_str(), pair.second.c_str()); return nullptr; } shaderProgram->m_FileDependencies.emplace_back(programFilename); CXeromyces programXeroFile; if (programXeroFile.Load(g_VFS, programFilename) != PSRETURN_OK) return nullptr; XMBElement programRoot = programXeroFile.GetRoot(); #define EL(x) const int el_##x = programXeroFile.GetElementID(#x) #define AT(x) const int at_##x = programXeroFile.GetAttributeID(#x) EL(binding); EL(compute); EL(descriptor_set); EL(descriptor_sets); EL(fragment); EL(member); EL(push_constant); EL(stream); EL(vertex); AT(binding); AT(file); AT(location); AT(name); AT(offset); AT(set); AT(size); AT(type); #undef AT #undef EL auto addPushConstant = [&pushConstants=shaderProgram->m_PushConstants, &pushConstantDataFlags=shaderProgram->m_PushConstantDataFlags, &at_name, &at_offset, &at_size]( const XMBElement& element, VkShaderStageFlags stageFlags) -> bool { const XMBAttributeList attributes = element.GetAttributes(); const CStrIntern name = CStrIntern(attributes.GetNamedItem(at_name)); const uint32_t size = attributes.GetNamedItem(at_size).ToUInt(); const uint32_t offset = attributes.GetNamedItem(at_offset).ToUInt(); if (offset % 4 != 0 || size % 4 != 0) { LOGERROR("Push constant should have offset and size be multiple of 4."); return false; } for (PushConstant& pushConstant : pushConstants) { if (pushConstant.name == name) { if (size != pushConstant.size || offset != pushConstant.offset) { LOGERROR("All shared push constants must have the same size and offset."); return false; } // We found the same constant so we don't need to add it again. pushConstant.stageFlags |= stageFlags; for (uint32_t index = 0; index < (size >> 2); ++index) pushConstantDataFlags[(offset >> 2) + index] |= stageFlags; return true; } if (offset + size < pushConstant.offset || offset >= pushConstant.offset + pushConstant.size) continue; LOGERROR("All push constant must not intersect each other in memory."); return false; } pushConstants.push_back({name, offset, size, stageFlags}); for (uint32_t index = 0; index < (size >> 2); ++index) pushConstantDataFlags[(offset >> 2) + index] = stageFlags; return true; }; uint32_t texturesDescriptorSetSize = 0; std::unordered_map textureMapping; VkDescriptorType storageImageDescriptorType = VK_DESCRIPTOR_TYPE_MAX_ENUM; uint32_t storageImageDescriptorSetSize = 0; std::unordered_map storageImageMapping; auto addDescriptorSets = [&](const XMBElement& element) -> bool { const bool useDescriptorIndexing = device->GetDescriptorManager().UseDescriptorIndexing(); // TODO: reduce the indentation. XERO_ITER_EL(element, descriporSetsChild) { if (descriporSetsChild.GetNodeName() == el_descriptor_set) { const uint32_t set = descriporSetsChild.GetAttributes().GetNamedItem(at_set).ToUInt(); if (useDescriptorIndexing && set == 0 && !descriporSetsChild.GetChildNodes().empty()) { LOGERROR("Descritor set for descriptor indexing shouldn't contain bindings."); return false; } XERO_ITER_EL(descriporSetsChild, descriporSetChild) { if (descriporSetChild.GetNodeName() == el_binding) { const XMBAttributeList attributes = descriporSetChild.GetAttributes(); const uint32_t binding = attributes.GetNamedItem(at_binding).ToUInt(); const uint32_t size = attributes.GetNamedItem(at_size).ToUInt(); const CStr type = attributes.GetNamedItem(at_type); if (type == "uniform") { const uint32_t expectedSet = device->GetDescriptorManager().GetUniformSet(); if (set != expectedSet || binding != 0) { LOGERROR("We support only a single uniform block per shader program."); return false; } shaderProgram->m_MaterialConstantsDataSize = size; XERO_ITER_EL(descriporSetChild, bindingChild) { if (bindingChild.GetNodeName() == el_member) { const XMBAttributeList memberAttributes = bindingChild.GetAttributes(); const uint32_t offset = memberAttributes.GetNamedItem(at_offset).ToUInt(); const uint32_t size = memberAttributes.GetNamedItem(at_size).ToUInt(); const CStrIntern name{memberAttributes.GetNamedItem(at_name)}; bool found = false; for (const Uniform& uniform : shaderProgram->m_Uniforms) { if (uniform.name == name) { if (offset != uniform.offset || size != uniform.size) { LOGERROR("All uniforms across all stage should match."); return false; } found = true; } else { if (offset + size <= uniform.offset || uniform.offset + uniform.size <= offset) continue; LOGERROR("Uniforms must not overlap each other."); return false; } } if (!found) shaderProgram->m_Uniforms.push_back({name, offset, size}); } } } else if (type == "sampler1D" || type == "sampler2D" || type == "sampler2DShadow" || type == "sampler3D" || type == "samplerCube") { if (useDescriptorIndexing) { LOGERROR("We support only uniform descriptor sets with enabled descriptor indexing."); return false; } const CStrIntern name{attributes.GetNamedItem(at_name)}; textureMapping[name] = binding; texturesDescriptorSetSize = std::max(texturesDescriptorSetSize, binding + 1); } else if (type == "storageImage" || type == "storageBuffer") { const CStrIntern name{attributes.GetNamedItem(at_name)}; storageImageMapping[name] = binding; storageImageDescriptorSetSize = std::max(storageImageDescriptorSetSize, binding + 1); const VkDescriptorType descriptorType = type == "storageBuffer" ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; if (storageImageDescriptorType == VK_DESCRIPTOR_TYPE_MAX_ENUM) storageImageDescriptorType = descriptorType; else if (storageImageDescriptorType != descriptorType) { LOGERROR("Shader should have storages of the same type."); return false; } } else { LOGERROR("Unsupported binding: '%s'", type.c_str()); return false; } } } } } return true; }; XERO_ITER_EL(programRoot, programChild) { if (programChild.GetNodeName() == el_vertex) { if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM && shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS) { LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str()); return nullptr; } shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; const VfsPath shaderModulePath = L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); shaderProgram->m_FileDependencies.emplace_back(shaderModulePath); shaderProgram->m_ShaderModules.emplace_back( CreateShaderModule(device, shaderModulePath)); if (shaderProgram->m_ShaderModules.back() == VK_NULL_HANDLE) return nullptr; VkPipelineShaderStageCreateInfo vertexShaderStageInfo{}; vertexShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertexShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertexShaderStageInfo.module = shaderProgram->m_ShaderModules.back(); vertexShaderStageInfo.pName = "main"; shaderProgram->m_Stages.emplace_back(std::move(vertexShaderStageInfo)); XERO_ITER_EL(programChild, stageChild) { if (stageChild.GetNodeName() == el_stream) { XMBAttributeList attributes = stageChild.GetAttributes(); const uint32_t location = attributes.GetNamedItem(at_location).ToUInt(); const CStr streamName = attributes.GetNamedItem(at_name); VertexAttributeStream stream = VertexAttributeStream::UV7; if (streamName == "pos") stream = VertexAttributeStream::POSITION; else if (streamName == "normal") stream = VertexAttributeStream::NORMAL; else if (streamName == "color") stream = VertexAttributeStream::COLOR; else if (streamName == "uv0") stream = VertexAttributeStream::UV0; else if (streamName == "uv1") stream = VertexAttributeStream::UV1; else if (streamName == "uv2") stream = VertexAttributeStream::UV2; else if (streamName == "uv3") stream = VertexAttributeStream::UV3; else if (streamName == "uv4") stream = VertexAttributeStream::UV4; else if (streamName == "uv5") stream = VertexAttributeStream::UV5; else if (streamName == "uv6") stream = VertexAttributeStream::UV6; else if (streamName == "uv7") stream = VertexAttributeStream::UV7; else debug_warn("Unknown stream"); shaderProgram->m_StreamLocations[stream] = location; } else if (stageChild.GetNodeName() == el_push_constant) { if (!addPushConstant(stageChild, VK_SHADER_STAGE_VERTEX_BIT)) return nullptr; } else if (stageChild.GetNodeName() == el_descriptor_sets) { if (!addDescriptorSets(stageChild)) return nullptr; } } } else if (programChild.GetNodeName() == el_fragment) { if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM && shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_GRAPHICS) { LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str()); return nullptr; } shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; const VfsPath shaderModulePath = L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); shaderProgram->m_FileDependencies.emplace_back(shaderModulePath); shaderProgram->m_ShaderModules.emplace_back( CreateShaderModule(device, shaderModulePath)); if (shaderProgram->m_ShaderModules.back() == VK_NULL_HANDLE) return nullptr; VkPipelineShaderStageCreateInfo fragmentShaderStageInfo{}; fragmentShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragmentShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragmentShaderStageInfo.module = shaderProgram->m_ShaderModules.back(); fragmentShaderStageInfo.pName = "main"; shaderProgram->m_Stages.emplace_back(std::move(fragmentShaderStageInfo)); XERO_ITER_EL(programChild, stageChild) { if (stageChild.GetNodeName() == el_push_constant) { if (!addPushConstant(stageChild, VK_SHADER_STAGE_FRAGMENT_BIT)) return nullptr; } else if (stageChild.GetNodeName() == el_descriptor_sets) { if (!addDescriptorSets(stageChild)) return nullptr; } } } else if (programChild.GetNodeName() == el_compute) { if (shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM && shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_COMPUTE) { LOGERROR("Shader program can't mix different pipelines: '%s'.", name.c_str()); return nullptr; } shaderProgram->m_PipelineBindPoint = VK_PIPELINE_BIND_POINT_COMPUTE; const VfsPath shaderModulePath = L"shaders/" + programChild.GetAttributes().GetNamedItem(at_file).FromUTF8(); shaderProgram->m_FileDependencies.emplace_back(shaderModulePath); shaderProgram->m_ShaderModules.emplace_back( CreateShaderModule(device, shaderModulePath)); if (shaderProgram->m_ShaderModules.back() == VK_NULL_HANDLE) return nullptr; VkPipelineShaderStageCreateInfo computeShaderStageInfo{}; computeShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; computeShaderStageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; computeShaderStageInfo.module = shaderProgram->m_ShaderModules.back(); computeShaderStageInfo.pName = "main"; shaderProgram->m_Stages.emplace_back(std::move(computeShaderStageInfo)); XERO_ITER_EL(programChild, stageChild) { if (stageChild.GetNodeName() == el_push_constant) { if (!addPushConstant(stageChild, VK_SHADER_STAGE_COMPUTE_BIT)) return nullptr; } else if (stageChild.GetNodeName() == el_descriptor_sets) { if (!addDescriptorSets(stageChild)) return nullptr; } } } } if (shaderProgram->m_Stages.empty()) { LOGERROR("Program should contain at least one stage."); return nullptr; } ENSURE(shaderProgram->m_PipelineBindPoint != VK_PIPELINE_BIND_POINT_MAX_ENUM); for (size_t index = 0; index < shaderProgram->m_PushConstants.size(); ++index) shaderProgram->m_PushConstantMapping[shaderProgram->m_PushConstants[index].name] = index; std::vector pushConstantRanges; pushConstantRanges.reserve(shaderProgram->m_PushConstants.size()); std::transform( shaderProgram->m_PushConstants.begin(), shaderProgram->m_PushConstants.end(), std::back_insert_iterator(pushConstantRanges), [](const PushConstant& pushConstant) { return VkPushConstantRange{pushConstant.stageFlags, pushConstant.offset, pushConstant.size}; }); if (!pushConstantRanges.empty()) { std::sort(pushConstantRanges.begin(), pushConstantRanges.end(), [](const VkPushConstantRange& lhs, const VkPushConstantRange& rhs) { return lhs.offset < rhs.offset; }); // Merge subsequent constants. auto it = pushConstantRanges.begin(); while (std::next(it) != pushConstantRanges.end()) { auto next = std::next(it); if (it->stageFlags == next->stageFlags) { it->size = next->offset - it->offset + next->size; pushConstantRanges.erase(next); } else it = next; } for (const VkPushConstantRange& range : pushConstantRanges) if (std::count_if(pushConstantRanges.begin(), pushConstantRanges.end(), [stageFlags=range.stageFlags](const VkPushConstantRange& range) { return range.stageFlags & stageFlags; }) != 1) { LOGERROR("Any two range must not include the same stage in stageFlags."); return nullptr; } } for (size_t index = 0; index < shaderProgram->m_Uniforms.size(); ++index) shaderProgram->m_UniformMapping[shaderProgram->m_Uniforms[index].name] = index; if (!shaderProgram->m_Uniforms.empty()) { if (shaderProgram->m_MaterialConstantsDataSize > device->GetChoosenPhysicalDevice().properties.limits.maxUniformBufferRange) { LOGERROR("Uniform buffer size is too big for the device."); return nullptr; } shaderProgram->m_MaterialConstantsData = std::make_unique(shaderProgram->m_MaterialConstantsDataSize); } std::vector layouts = device->GetDescriptorManager().GetDescriptorSetLayouts(); if (texturesDescriptorSetSize > 0) { ENSURE(!device->GetDescriptorManager().UseDescriptorIndexing()); shaderProgram->m_TextureBinding.emplace( device, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, texturesDescriptorSetSize, std::move(textureMapping)); layouts.emplace_back(shaderProgram->m_TextureBinding->GetDescriptorSetLayout()); } if (storageImageDescriptorSetSize > 0) { shaderProgram->m_StorageImageBinding.emplace( device, storageImageDescriptorType, storageImageDescriptorSetSize, std::move(storageImageMapping)); layouts.emplace_back(shaderProgram->m_StorageImageBinding->GetDescriptorSetLayout()); } VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{}; pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutCreateInfo.setLayoutCount = layouts.size(); pipelineLayoutCreateInfo.pSetLayouts = layouts.data(); pipelineLayoutCreateInfo.pushConstantRangeCount = pushConstantRanges.size(); pipelineLayoutCreateInfo.pPushConstantRanges = pushConstantRanges.data(); const VkResult result = vkCreatePipelineLayout( device->GetVkDevice(), &pipelineLayoutCreateInfo, nullptr, &shaderProgram->m_PipelineLayout); if (result != VK_SUCCESS) { LOGERROR("Failed to create a pipeline layout: %d (%s)", static_cast(result), Utilities::GetVkResultName(result)); return nullptr; } return shaderProgram; } CShaderProgram::CShaderProgram() = default; CShaderProgram::~CShaderProgram() { if (m_PipelineLayout != VK_NULL_HANDLE) m_Device->ScheduleObjectToDestroy(VK_OBJECT_TYPE_PIPELINE_LAYOUT, m_PipelineLayout, VK_NULL_HANDLE); for (VkShaderModule shaderModule : m_ShaderModules) if (shaderModule != VK_NULL_HANDLE) m_Device->ScheduleObjectToDestroy(VK_OBJECT_TYPE_SHADER_MODULE, shaderModule, VK_NULL_HANDLE); } IDevice* CShaderProgram::GetDevice() { return m_Device; } int32_t CShaderProgram::GetBindingSlot(const CStrIntern name) const { if (auto it = m_PushConstantMapping.find(name); it != m_PushConstantMapping.end()) return it->second; if (auto it = m_UniformMapping.find(name); it != m_UniformMapping.end()) return it->second + m_PushConstants.size(); if (const int32_t bindingSlot = m_TextureBinding.has_value() ? m_TextureBinding->GetBindingSlot(name) : -1; bindingSlot != -1) return bindingSlot + m_PushConstants.size() + m_UniformMapping.size(); if (const int32_t bindingSlot = m_StorageImageBinding.has_value() ? m_StorageImageBinding->GetBindingSlot(name) : -1; bindingSlot != -1) return bindingSlot + m_PushConstants.size() + m_UniformMapping.size() + (m_TextureBinding.has_value() ? m_TextureBinding->GetBoundDeviceObjects().size() : 0); return -1; } std::vector CShaderProgram::GetFileDependencies() const { return m_FileDependencies; } uint32_t CShaderProgram::GetStreamLocation(const VertexAttributeStream stream) const { auto it = m_StreamLocations.find(stream); return it != m_StreamLocations.end() ? it->second : std::numeric_limits::max(); } void CShaderProgram::Bind() { if (m_MaterialConstantsData) m_MaterialConstantsDataOutdated = true; } void CShaderProgram::Unbind() { if (m_TextureBinding.has_value()) m_TextureBinding->Unbind(); if (m_StorageImageBinding.has_value()) m_StorageImageBinding->Unbind(); } void CShaderProgram::PreDraw(CRingCommandContext& commandContext) { BindOutdatedDescriptorSets(commandContext); if (m_PushConstantDataMask) { for (uint32_t index = 0; index < 32;) { if (!(m_PushConstantDataMask & (1 << index))) { ++index; continue; } uint32_t indexEnd = index + 1; while (indexEnd < 32 && (m_PushConstantDataMask & (1 << indexEnd)) && m_PushConstantDataFlags[index] == m_PushConstantDataFlags[indexEnd]) ++indexEnd; vkCmdPushConstants( commandContext.GetCommandBuffer(), GetPipelineLayout(), m_PushConstantDataFlags[index], index * 4, (indexEnd - index) * 4, m_PushConstantData.data() + index * 4); index = indexEnd; } m_PushConstantDataMask = 0; } } void CShaderProgram::PreDispatch( CRingCommandContext& commandContext) { PreDraw(commandContext); if (m_StorageImageBinding.has_value()) for (CTexture* texture : m_StorageImageBinding->GetBoundDeviceObjects()) if (texture) { if (!(texture->GetUsage() & ITexture::Usage::SAMPLED) && texture->IsInitialized()) continue; VkImageLayout oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; if (!texture->IsInitialized()) oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; Utilities::SetTextureLayout( commandContext.GetCommandBuffer(), texture, oldLayout, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); } } void CShaderProgram::PostDispatch(CRingCommandContext& commandContext) { if (m_StorageImageBinding.has_value()) for (CTexture* texture : m_StorageImageBinding->GetBoundDeviceObjects()) if (texture) { if (!(texture->GetUsage() & ITexture::Usage::SAMPLED) && texture->IsInitialized()) continue; Utilities::SetTextureLayout( commandContext.GetCommandBuffer(), texture, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); } } void CShaderProgram::BindOutdatedDescriptorSets( CRingCommandContext& commandContext) { // TODO: combine calls after more sets to bind. PS::StaticVector, 2> descriptortSets; if (m_TextureBinding.has_value() && m_TextureBinding->IsOutdated()) { constexpr uint32_t TEXTURE_BINDING_SET = 1u; descriptortSets.emplace_back(TEXTURE_BINDING_SET, m_TextureBinding->UpdateAndReturnDescriptorSet()); } if (m_StorageImageBinding.has_value() && m_StorageImageBinding->IsOutdated()) { constexpr uint32_t STORAGE_IMAGE_BINDING_SET = 2u; descriptortSets.emplace_back(STORAGE_IMAGE_BINDING_SET, m_StorageImageBinding->UpdateAndReturnDescriptorSet()); } for (const auto& [firstSet, descriptorSet] : descriptortSets) { vkCmdBindDescriptorSets( commandContext.GetCommandBuffer(), GetPipelineBindPoint(), GetPipelineLayout(), firstSet, 1, &descriptorSet, 0, nullptr); } } void CShaderProgram::SetUniform( const int32_t bindingSlot, const float value) { const float values[1] = {value}; SetUniform(bindingSlot, PS::span(values, values + 1)); } void CShaderProgram::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY) { const float values[2] = {valueX, valueY}; SetUniform(bindingSlot, PS::span(values, values + 2)); } void CShaderProgram::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ) { const float values[3] = {valueX, valueY, valueZ}; SetUniform(bindingSlot, PS::span(values, values + 3)); } void CShaderProgram::SetUniform( const int32_t bindingSlot, const float valueX, const float valueY, const float valueZ, const float valueW) { const float values[4] = {valueX, valueY, valueZ, valueW}; SetUniform(bindingSlot, PS::span(values, values + 4)); } void CShaderProgram::SetUniform(const int32_t bindingSlot, PS::span values) { if (bindingSlot < 0) return; const auto data = GetUniformData(bindingSlot, values.size() * sizeof(float)); std::memcpy(data.first, values.data(), data.second); } std::pair CShaderProgram::GetUniformData( const int32_t bindingSlot, const uint32_t dataSize) { if (bindingSlot < static_cast(m_PushConstants.size())) { const uint32_t size = m_PushConstants[bindingSlot].size; const uint32_t offset = m_PushConstants[bindingSlot].offset; ENSURE(size <= dataSize); m_PushConstantDataMask |= ((1 << (size >> 2)) - 1) << (offset >> 2); return {m_PushConstantData.data() + offset, size}; } else { ENSURE(bindingSlot - m_PushConstants.size() < m_Uniforms.size()); const Uniform& uniform = m_Uniforms[bindingSlot - m_PushConstants.size()]; m_MaterialConstantsDataOutdated = true; const uint32_t size = uniform.size; const uint32_t offset = uniform.offset; ENSURE(size <= dataSize); return {m_MaterialConstantsData.get() + offset, size}; } } void CShaderProgram::SetTexture(const int32_t bindingSlot, CTexture* texture) { if (bindingSlot < 0) return; CDescriptorManager& descriptorManager = m_Device->GetDescriptorManager(); if (descriptorManager.UseDescriptorIndexing()) { const uint32_t descriptorIndex = descriptorManager.GetTextureDescriptor(texture->As()); ENSURE(bindingSlot < static_cast(m_PushConstants.size())); const uint32_t size = m_PushConstants[bindingSlot].size; const uint32_t offset = m_PushConstants[bindingSlot].offset; ENSURE(size == sizeof(descriptorIndex)); std::memcpy(m_PushConstantData.data() + offset, &descriptorIndex, size); m_PushConstantDataMask |= ((1 << (size >> 2)) - 1) << (offset >> 2); } else { ENSURE(bindingSlot >= static_cast(m_PushConstants.size() + m_UniformMapping.size())); ENSURE(m_TextureBinding.has_value()); const uint32_t index = bindingSlot - (m_PushConstants.size() + m_UniformMapping.size()); m_TextureBinding->SetObject(index, texture); } } void CShaderProgram::SetStorageTexture(const int32_t bindingSlot, CTexture* texture) { if (bindingSlot < 0) return; const int32_t offset = static_cast(m_PushConstants.size() + m_UniformMapping.size() + (m_TextureBinding.has_value() ? m_TextureBinding->GetBoundDeviceObjects().size() : 0)); ENSURE(bindingSlot >= offset); ENSURE(m_StorageImageBinding.has_value()); const uint32_t index = bindingSlot - offset; m_StorageImageBinding->SetObject(index, texture); } } // namespace Vulkan } // namespace Backend } // namespace Renderer