mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Comments By: phosit, Stan Differential Revision: https://code.wildfiregames.com/D4876 This was SVN commit r27412.
699 lines
23 KiB
C++
699 lines
23 KiB
C++
/* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "ShaderProgram.h"
|
|
|
|
#include "graphics/ShaderDefines.h"
|
|
#include "ps/CLogger.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/Texture.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
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<uintptr_t>(file.GetBuffer()) % alignof(uint32_t) == 0u);
|
|
createInfo.codeSize = file.GetBufferSize();
|
|
createInfo.pCode = reinterpret_cast<const uint32_t*>(file.GetBuffer());
|
|
|
|
VkShaderModule shaderModule;
|
|
if (vkCreateShaderModule(device->GetVkDevice(), &createInfo, nullptr, &shaderModule) != VK_SUCCESS)
|
|
{
|
|
LOGERROR("Failed to create shader module from file: '%s'", path.string8());
|
|
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> CShaderProgram::Create(
|
|
CDevice* device, const CStr& name, const CShaderDefines& baseDefines)
|
|
{
|
|
const VfsPath xmlFilename = L"shaders/" + wstring_from_utf8(name) + L".xml";
|
|
|
|
std::unique_ptr<CShaderProgram> 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(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;
|
|
};
|
|
|
|
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)};
|
|
shaderProgram->m_TextureMapping[name] = binding;
|
|
shaderProgram->m_TexturesDescriptorSetSize =
|
|
std::max(shaderProgram->m_TexturesDescriptorSetSize, binding + 1);
|
|
}
|
|
else
|
|
{
|
|
LOGERROR("Unsupported binding: '%s'", type.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
XERO_ITER_EL(programRoot, programChild)
|
|
{
|
|
if (programChild.GetNodeName() == el_vertex)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shaderProgram->m_Stages.empty())
|
|
{
|
|
LOGERROR("Program should contain at least one stage.");
|
|
return nullptr;
|
|
}
|
|
|
|
for (size_t index = 0; index < shaderProgram->m_PushConstants.size(); ++index)
|
|
shaderProgram->m_PushConstantMapping[shaderProgram->m_PushConstants[index].name] = index;
|
|
std::vector<VkPushConstantRange> 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<std::byte[]>(shaderProgram->m_MaterialConstantsDataSize);
|
|
}
|
|
|
|
std::vector<VkDescriptorSetLayout> layouts =
|
|
device->GetDescriptorManager().GetDescriptorSetLayouts();
|
|
if (shaderProgram->m_TexturesDescriptorSetSize > 0)
|
|
{
|
|
ENSURE(!device->GetDescriptorManager().UseDescriptorIndexing());
|
|
shaderProgram->m_BoundTextures.resize(shaderProgram->m_TexturesDescriptorSetSize);
|
|
shaderProgram->m_BoundTexturesUID.resize(shaderProgram->m_TexturesDescriptorSetSize);
|
|
shaderProgram->m_BoundTexturesOutdated = true;
|
|
shaderProgram->m_TexturesDescriptorSetLayout =
|
|
device->GetDescriptorManager().GetSingleTypeDescritorSetLayout(
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, shaderProgram->m_TexturesDescriptorSetSize);
|
|
layouts.emplace_back(shaderProgram->m_TexturesDescriptorSetLayout);
|
|
}
|
|
|
|
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", static_cast<int>(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 (auto it = m_TextureMapping.find(name); it != m_TextureMapping.end())
|
|
return it->second + m_PushConstants.size() + m_UniformMapping.size();
|
|
return -1;
|
|
}
|
|
|
|
std::vector<VfsPath> 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<uint32_t>::max();
|
|
}
|
|
|
|
void CShaderProgram::Bind()
|
|
{
|
|
if (m_MaterialConstantsData)
|
|
m_MaterialConstantsDataOutdated = true;
|
|
}
|
|
|
|
void CShaderProgram::Unbind()
|
|
{
|
|
if (m_TexturesDescriptorSetSize > 0)
|
|
{
|
|
for (CTexture*& texture : m_BoundTextures)
|
|
texture = nullptr;
|
|
for (CTexture::UID& uid : m_BoundTexturesUID)
|
|
uid = 0;
|
|
m_BoundTexturesOutdated = true;
|
|
}
|
|
}
|
|
|
|
void CShaderProgram::PreDraw(VkCommandBuffer commandBuffer)
|
|
{
|
|
UpdateActiveDescriptorSet(commandBuffer);
|
|
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(
|
|
commandBuffer, GetPipelineLayout(),
|
|
m_PushConstantDataFlags[index],
|
|
index * 4, (indexEnd - index) * 4, m_PushConstantData.data() + index * 4);
|
|
index = indexEnd;
|
|
}
|
|
m_PushConstantDataMask = 0;
|
|
}
|
|
}
|
|
|
|
void CShaderProgram::UpdateActiveDescriptorSet(
|
|
VkCommandBuffer commandBuffer)
|
|
{
|
|
if (m_BoundTexturesOutdated)
|
|
{
|
|
m_BoundTexturesOutdated = false;
|
|
|
|
m_ActiveTexturesDescriptorSet =
|
|
m_Device->GetDescriptorManager().GetSingleTypeDescritorSet(
|
|
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_TexturesDescriptorSetLayout,
|
|
m_BoundTexturesUID, m_BoundTextures);
|
|
ENSURE(m_ActiveTexturesDescriptorSet != VK_NULL_HANDLE);
|
|
|
|
vkCmdBindDescriptorSets(
|
|
commandBuffer, GetPipelineBindPoint(), GetPipelineLayout(),
|
|
1, 1, &m_ActiveTexturesDescriptorSet, 0, nullptr);
|
|
}
|
|
}
|
|
|
|
void CShaderProgram::SetUniform(
|
|
const int32_t bindingSlot,
|
|
const float value)
|
|
{
|
|
const float values[1] = {value};
|
|
SetUniform(bindingSlot, PS::span<const float>(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<const float>(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<const float>(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<const float>(values, values + 4));
|
|
}
|
|
|
|
void CShaderProgram::SetUniform(const int32_t bindingSlot, PS::span<const float> values)
|
|
{
|
|
if (bindingSlot < 0)
|
|
return;
|
|
const auto data = GetUniformData(bindingSlot, values.size() * sizeof(float));
|
|
std::memcpy(data.first, values.data(), data.second);
|
|
}
|
|
|
|
std::pair<std::byte*, uint32_t> CShaderProgram::GetUniformData(
|
|
const int32_t bindingSlot, const uint32_t dataSize)
|
|
{
|
|
if (bindingSlot < static_cast<int32_t>(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<CTexture>());
|
|
ENSURE(bindingSlot < static_cast<int32_t>(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<int32_t>(m_PushConstants.size() + m_UniformMapping.size()));
|
|
const uint32_t index = bindingSlot - (m_PushConstants.size() + m_UniformMapping.size());
|
|
if (m_BoundTexturesUID[index] != texture->GetUID())
|
|
{
|
|
m_BoundTextures[index] = texture;
|
|
m_BoundTexturesUID[index] = texture->GetUID();
|
|
m_BoundTexturesOutdated = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Vulkan
|
|
|
|
} // namespace Backend
|
|
|
|
} // namespace Renderer
|