mirror of
https://github.com/love2d/love.git
synced 2026-08-12 16:40:57 +02:00
f4c38eb00a
This reverts commit 47e3fc85d2.
1060 lines
32 KiB
C++
1060 lines
32 KiB
C++
#include "Shader.h"
|
|
#include "Graphics.h"
|
|
|
|
#include "libraries/glslang/glslang/Public/ShaderLang.h"
|
|
#include "libraries/glslang/SPIRV/GlslangToSpv.h"
|
|
|
|
|
|
#include <vector>
|
|
|
|
namespace love
|
|
{
|
|
namespace graphics
|
|
{
|
|
namespace vulkan
|
|
{
|
|
|
|
static const TBuiltInResource defaultTBuiltInResource = {
|
|
/* .MaxLights = */ 32,
|
|
/* .MaxClipPlanes = */ 6,
|
|
/* .MaxTextureUnits = */ 32,
|
|
/* .MaxTextureCoords = */ 32,
|
|
/* .MaxVertexAttribs = */ 64,
|
|
/* .MaxVertexUniformComponents = */ 16384,
|
|
/* .MaxVaryingFloats = */ 128,
|
|
/* .MaxVertexTextureImageUnits = */ 32,
|
|
/* .MaxCombinedTextureImageUnits = */ 80,
|
|
/* .MaxTextureImageUnits = */ 32,
|
|
/* .MaxFragmentUniformComponents = */ 16384,
|
|
/* .MaxDrawBuffers = */ 8,
|
|
/* .MaxVertexUniformVectors = */ 4096,
|
|
/* .MaxVaryingVectors = */ 32,
|
|
/* .MaxFragmentUniformVectors = */ 4096,
|
|
/* .MaxVertexOutputVectors = */ 32,
|
|
/* .MaxFragmentInputVectors = */ 31,
|
|
/* .MinProgramTexelOffset = */ -8,
|
|
/* .MaxProgramTexelOffset = */ 7,
|
|
/* .MaxClipDistances = */ 8,
|
|
/* .MaxComputeWorkGroupCountX = */ 65535,
|
|
/* .MaxComputeWorkGroupCountY = */ 65535,
|
|
/* .MaxComputeWorkGroupCountZ = */ 65535,
|
|
/* .MaxComputeWorkGroupSizeX = */ 1024,
|
|
/* .MaxComputeWorkGroupSizeY = */ 1024,
|
|
/* .MaxComputeWorkGroupSizeZ = */ 64,
|
|
/* .MaxComputeUniformComponents = */ 1024,
|
|
/* .MaxComputeTextureImageUnits = */ 32,
|
|
/* .MaxComputeImageUniforms = */ 16,
|
|
/* .MaxComputeAtomicCounters = */ 4096,
|
|
/* .MaxComputeAtomicCounterBuffers = */ 8,
|
|
/* .MaxVaryingComponents = */ 128,
|
|
/* .MaxVertexOutputComponents = */ 128,
|
|
/* .MaxGeometryInputComponents = */ 128,
|
|
/* .MaxGeometryOutputComponents = */ 128,
|
|
/* .MaxFragmentInputComponents = */ 128,
|
|
/* .MaxImageUnits = */ 192,
|
|
/* .MaxCombinedImageUnitsAndFragmentOutputs = */ 144,
|
|
/* .MaxCombinedShaderOutputResources = */ 144,
|
|
/* .MaxImageSamples = */ 32,
|
|
/* .MaxVertexImageUniforms = */ 16,
|
|
/* .MaxTessControlImageUniforms = */ 16,
|
|
/* .MaxTessEvaluationImageUniforms = */ 16,
|
|
/* .MaxGeometryImageUniforms = */ 16,
|
|
/* .MaxFragmentImageUniforms = */ 16,
|
|
/* .MaxCombinedImageUniforms = */ 80,
|
|
/* .MaxGeometryTextureImageUnits = */ 16,
|
|
/* .MaxGeometryOutputVertices = */ 256,
|
|
/* .MaxGeometryTotalOutputComponents = */ 1024,
|
|
/* .MaxGeometryUniformComponents = */ 1024,
|
|
/* .MaxGeometryVaryingComponents = */ 64,
|
|
/* .MaxTessControlInputComponents = */ 128,
|
|
/* .MaxTessControlOutputComponents = */ 128,
|
|
/* .MaxTessControlTextureImageUnits = */ 16,
|
|
/* .MaxTessControlUniformComponents = */ 1024,
|
|
/* .MaxTessControlTotalOutputComponents = */ 4096,
|
|
/* .MaxTessEvaluationInputComponents = */ 128,
|
|
/* .MaxTessEvaluationOutputComponents = */ 128,
|
|
/* .MaxTessEvaluationTextureImageUnits = */ 16,
|
|
/* .MaxTessEvaluationUniformComponents = */ 1024,
|
|
/* .MaxTessPatchComponents = */ 120,
|
|
/* .MaxPatchVertices = */ 32,
|
|
/* .MaxTessGenLevel = */ 64,
|
|
/* .MaxViewports = */ 16,
|
|
/* .MaxVertexAtomicCounters = */ 4096,
|
|
/* .MaxTessControlAtomicCounters = */ 4096,
|
|
/* .MaxTessEvaluationAtomicCounters = */ 4096,
|
|
/* .MaxGeometryAtomicCounters = */ 4096,
|
|
/* .MaxFragmentAtomicCounters = */ 4096,
|
|
/* .MaxCombinedAtomicCounters = */ 4096,
|
|
/* .MaxAtomicCounterBindings = */ 8,
|
|
/* .MaxVertexAtomicCounterBuffers = */ 8,
|
|
/* .MaxTessControlAtomicCounterBuffers = */ 8,
|
|
/* .MaxTessEvaluationAtomicCounterBuffers = */ 8,
|
|
/* .MaxGeometryAtomicCounterBuffers = */ 8,
|
|
/* .MaxFragmentAtomicCounterBuffers = */ 8,
|
|
/* .MaxCombinedAtomicCounterBuffers = */ 8,
|
|
/* .MaxAtomicCounterBufferSize = */ 16384,
|
|
/* .MaxTransformFeedbackBuffers = */ 4,
|
|
/* .MaxTransformFeedbackInterleavedComponents = */ 64,
|
|
/* .MaxCullDistances = */ 8,
|
|
/* .MaxCombinedClipAndCullDistances = */ 8,
|
|
/* .MaxSamples = */ 32,
|
|
/* .maxMeshOutputVerticesNV = */ 256,
|
|
/* .maxMeshOutputPrimitivesNV = */ 512,
|
|
/* .maxMeshWorkGroupSizeX_NV = */ 32,
|
|
/* .maxMeshWorkGroupSizeY_NV = */ 1,
|
|
/* .maxMeshWorkGroupSizeZ_NV = */ 1,
|
|
/* .maxTaskWorkGroupSizeX_NV = */ 32,
|
|
/* .maxTaskWorkGroupSizeY_NV = */ 1,
|
|
/* .maxTaskWorkGroupSizeZ_NV = */ 1,
|
|
/* .maxMeshViewCountNV = */ 4,
|
|
/* .maxDualSourceDrawBuffersEXT = */ 1,
|
|
/* .limits = */ {
|
|
/* .nonInductiveForLoops = */ 1,
|
|
/* .whileLoops = */ 1,
|
|
/* .doWhileLoops = */ 1,
|
|
/* .generalUniformIndexing = */ 1,
|
|
/* .generalAttributeMatrixVectorIndexing = */ 1,
|
|
/* .generalVaryingIndexing = */ 1,
|
|
/* .generalSamplerIndexing = */ 1,
|
|
/* .generalVariableIndexing = */ 1,
|
|
/* .generalConstantMatrixVectorIndexing = */ 1,
|
|
}
|
|
};
|
|
|
|
static const uint32_t STREAMBUFFER_DEFAULT_SIZE = 16;
|
|
static const uint32_t DESCRIPTOR_POOL_SIZE = 1;
|
|
|
|
static VkShaderStageFlagBits getStageBit(ShaderStageType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case SHADERSTAGE_VERTEX:
|
|
return VK_SHADER_STAGE_VERTEX_BIT;
|
|
case SHADERSTAGE_PIXEL:
|
|
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
case SHADERSTAGE_COMPUTE:
|
|
return VK_SHADER_STAGE_COMPUTE_BIT;
|
|
default:
|
|
throw love::Exception("invalid type");
|
|
}
|
|
}
|
|
|
|
static EShLanguage getGlslShaderType(ShaderStageType stage)
|
|
{
|
|
switch (stage)
|
|
{
|
|
case SHADERSTAGE_VERTEX:
|
|
return EShLangVertex;
|
|
case SHADERSTAGE_PIXEL:
|
|
return EShLangFragment;
|
|
case SHADERSTAGE_COMPUTE:
|
|
return EShLangCompute;
|
|
default:
|
|
throw love::Exception("unkonwn shader stage type");
|
|
}
|
|
}
|
|
|
|
Shader::Shader(StrongRef<love::graphics::ShaderStage> stages[])
|
|
: graphics::Shader(stages)
|
|
{
|
|
auto gfx = Module::getInstance<Graphics>(Module::ModuleType::M_GRAPHICS);
|
|
vgfx = dynamic_cast<Graphics*>(gfx);
|
|
|
|
loadVolatile();
|
|
}
|
|
|
|
bool Shader::loadVolatile()
|
|
{
|
|
computePipeline = VK_NULL_HANDLE;
|
|
|
|
for (int i = 0; i < BUILTIN_MAX_ENUM; i++)
|
|
builtinUniformInfo[i] = nullptr;
|
|
|
|
compileShaders();
|
|
calculateUniformBufferSizeAligned();
|
|
createDescriptorSetLayout();
|
|
createPipelineLayout();
|
|
createDescriptorPoolSizes();
|
|
createStreamBuffers();
|
|
descriptorSetsVector.resize(vgfx->getNumImagesInFlight());
|
|
currentFrame = 0;
|
|
currentUsedUniformStreamBuffersCount = 0;
|
|
currentUsedDescriptorSetsCount = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Shader::unloadVolatile()
|
|
{
|
|
if (shaderModules.empty())
|
|
return;
|
|
|
|
auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
|
|
gfx->queueCleanUp([shaderModules = std::move(shaderModules), device = device, descriptorSetLayout = descriptorSetLayout, pipelineLayout = pipelineLayout, descriptorPools = descriptorPools, computePipeline = computePipeline](){
|
|
for (const auto pool : descriptorPools)
|
|
vkDestroyDescriptorPool(device, pool, nullptr);
|
|
for (const auto shaderModule : shaderModules)
|
|
vkDestroyShaderModule(device, shaderModule, nullptr);
|
|
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
|
|
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
|
|
if (computePipeline != VK_NULL_HANDLE)
|
|
vkDestroyPipeline(device, computePipeline, nullptr);
|
|
});
|
|
|
|
while (!freeDescriptorSets.empty())
|
|
freeDescriptorSets.pop();
|
|
|
|
for (const auto &streamBufferVector : streamBuffers)
|
|
for (const auto streamBuffer : streamBufferVector)
|
|
delete streamBuffer;
|
|
|
|
shaderModules.clear();
|
|
shaderStages.clear();
|
|
streamBuffers.clear();
|
|
descriptorPools.clear();
|
|
descriptorSetsVector.clear();
|
|
}
|
|
|
|
const std::vector<VkPipelineShaderStageCreateInfo> &Shader::getShaderStages() const
|
|
{
|
|
return shaderStages;
|
|
}
|
|
|
|
const VkPipelineLayout Shader::getGraphicsPipelineLayout() const
|
|
{
|
|
return pipelineLayout;
|
|
}
|
|
|
|
VkPipeline Shader::getComputePipeline() const
|
|
{
|
|
return computePipeline;
|
|
}
|
|
|
|
static VkDescriptorImageInfo *createDescriptorImageInfo(graphics::Texture *texture, bool sampler)
|
|
{
|
|
auto vkTexture = (Texture*)texture;
|
|
|
|
auto imageInfo = new VkDescriptorImageInfo();
|
|
|
|
imageInfo->imageLayout = vkTexture->getImageLayout();
|
|
imageInfo->imageView = (VkImageView)vkTexture->getRenderTargetHandle();
|
|
if (sampler)
|
|
imageInfo->sampler = (VkSampler)vkTexture->getSamplerHandle();
|
|
|
|
return imageInfo;
|
|
}
|
|
|
|
void Shader::newFrame(uint32_t frameIndex)
|
|
{
|
|
currentFrame = frameIndex;
|
|
|
|
currentUsedUniformStreamBuffersCount = 0;
|
|
currentUsedDescriptorSetsCount = 0;
|
|
|
|
if (streamBuffers.at(currentFrame).size() > 1)
|
|
{
|
|
size_t newSize = 0;
|
|
for (auto streamBuffer : streamBuffers.at(currentFrame))
|
|
{
|
|
newSize += streamBuffer->getSize();
|
|
delete streamBuffer;
|
|
}
|
|
streamBuffers.at(currentFrame).clear();
|
|
streamBuffers.at(currentFrame).push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, newSize));
|
|
}
|
|
else
|
|
streamBuffers.at(currentFrame).at(0)->nextFrame();
|
|
|
|
if (currentUsedDescriptorSetsCount >= static_cast<uint32_t>(descriptorSetsVector.at(currentFrame).size()))
|
|
descriptorSetsVector.at(currentFrame).push_back(allocateDescriptorSet());
|
|
|
|
currentDescriptorSet = descriptorSetsVector.at(currentFrame).at(currentUsedDescriptorSetsCount);
|
|
|
|
// update everything other than uniform buffers
|
|
for (const auto &[key, val] : uniformInfos) {
|
|
// fixme: other types.
|
|
if (val.baseType == UNIFORM_SAMPLER) {
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = currentDescriptorSet;
|
|
write.dstBinding = val.location;
|
|
write.dstArrayElement = 0;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
write.descriptorCount = val.count;
|
|
|
|
std::vector<VkDescriptorImageInfo> imageInfos;
|
|
|
|
for (int i = 0; i < val.count; i++)
|
|
{
|
|
auto vkTexture = dynamic_cast<Texture*>(val.textures[i]);
|
|
|
|
VkDescriptorImageInfo imageInfo{};
|
|
imageInfo.imageLayout = vkTexture->getImageLayout();
|
|
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
|
|
imageInfo.sampler = (VkSampler)vkTexture->getSamplerHandle();
|
|
|
|
imageInfos.push_back(imageInfo);
|
|
}
|
|
|
|
write.pImageInfo = imageInfos.data();
|
|
|
|
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
|
}
|
|
if (val.baseType == UNIFORM_STORAGETEXTURE) {
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = currentDescriptorSet;
|
|
write.dstBinding = val.location;
|
|
write.dstArrayElement = 0;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
|
write.descriptorCount = val.count;
|
|
|
|
std::vector<VkDescriptorImageInfo> imageInfos;
|
|
|
|
for (int i = 0; i < val.count; i++)
|
|
{
|
|
auto vkTexture = dynamic_cast<Texture*>(val.textures[i]);
|
|
|
|
VkDescriptorImageInfo imageInfo{};
|
|
imageInfo.imageLayout = vkTexture->getImageLayout();
|
|
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
|
|
|
|
imageInfos.push_back(imageInfo);
|
|
}
|
|
|
|
write.pImageInfo = imageInfos.data();
|
|
|
|
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shader::cmdPushDescriptorSets(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint)
|
|
{
|
|
if (!localUniformData.empty())
|
|
{
|
|
auto usedStreamBufferMemory = currentUsedUniformStreamBuffersCount * uniformBufferSizeAligned;
|
|
if (usedStreamBufferMemory >= streamBuffers.at(currentFrame).back()->getSize())
|
|
{
|
|
streamBuffers.at(currentFrame).push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, STREAMBUFFER_DEFAULT_SIZE * uniformBufferSizeAligned));
|
|
currentUsedUniformStreamBuffersCount = 0;
|
|
}
|
|
|
|
if (builtinUniformDataOffset.has_value())
|
|
{
|
|
auto builtinData = vgfx->getCurrentBuiltinUniformData();
|
|
auto dst = localUniformData.data() + builtinUniformDataOffset.value();
|
|
memcpy(dst, &builtinData, sizeof(builtinData));
|
|
}
|
|
|
|
auto currentStreamBuffer = streamBuffers.at(currentFrame).back();
|
|
|
|
auto mapInfo = currentStreamBuffer->map(uniformBufferSizeAligned);
|
|
memcpy(mapInfo.data, localUniformData.data(), localUniformData.size());
|
|
currentStreamBuffer->unmap(uniformBufferSizeAligned);
|
|
currentStreamBuffer->markUsed(uniformBufferSizeAligned);
|
|
|
|
VkDescriptorBufferInfo bufferInfo{};
|
|
bufferInfo.buffer = (VkBuffer)currentStreamBuffer->getHandle();
|
|
bufferInfo.offset = currentUsedUniformStreamBuffersCount * uniformBufferSizeAligned;
|
|
bufferInfo.range = localUniformData.size();
|
|
|
|
VkWriteDescriptorSet uniformWrite{};
|
|
uniformWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
uniformWrite.dstSet = currentDescriptorSet;
|
|
uniformWrite.dstBinding = uniformLocation;
|
|
uniformWrite.dstArrayElement = 0;
|
|
uniformWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
uniformWrite.descriptorCount = 1;
|
|
uniformWrite.pBufferInfo = &bufferInfo;
|
|
|
|
vkUpdateDescriptorSets(device, 1, &uniformWrite, 0, nullptr);
|
|
|
|
currentUsedUniformStreamBuffersCount++;
|
|
}
|
|
|
|
static const std::vector<BuiltinUniform> builtinUniformTextures = {
|
|
BUILTIN_TEXTURE_MAIN,
|
|
BUILTIN_TEXTURE_VIDEO_Y,
|
|
BUILTIN_TEXTURE_VIDEO_CB,
|
|
BUILTIN_TEXTURE_VIDEO_CR,
|
|
};
|
|
|
|
for (const auto &builtin : builtinUniformTextures)
|
|
if (builtinUniformInfo[builtin] != nullptr)
|
|
{
|
|
auto texture = dynamic_cast<Texture*>(builtinUniformInfo[builtin]->textures[0]);
|
|
|
|
VkDescriptorImageInfo imageInfo{};
|
|
imageInfo.imageLayout = texture->getImageLayout();
|
|
imageInfo.imageView = (VkImageView)texture->getRenderTargetHandle();
|
|
imageInfo.sampler = (VkSampler)texture->getSamplerHandle();
|
|
|
|
VkWriteDescriptorSet textureWrite{};
|
|
textureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
textureWrite.dstSet = currentDescriptorSet;
|
|
textureWrite.dstBinding = builtinUniformInfo[builtin]->location;
|
|
textureWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
textureWrite.descriptorCount = 1;
|
|
textureWrite.pImageInfo = &imageInfo;
|
|
|
|
vkUpdateDescriptorSets(device, 1, &textureWrite, 0, nullptr);
|
|
}
|
|
|
|
vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, ¤tDescriptorSet, 0, nullptr);
|
|
|
|
currentUsedDescriptorSetsCount++;
|
|
|
|
if (currentUsedDescriptorSetsCount >= static_cast<uint32_t>(descriptorSetsVector.at(currentFrame).size()))
|
|
descriptorSetsVector.at(currentFrame).push_back(allocateDescriptorSet());
|
|
|
|
currentDescriptorSet = descriptorSetsVector.at(currentFrame).at(currentUsedDescriptorSetsCount);
|
|
}
|
|
|
|
Shader::~Shader()
|
|
{
|
|
unloadVolatile();
|
|
}
|
|
|
|
void Shader::attach()
|
|
{
|
|
auto &usedShadersInFrame = vgfx->getUsedShadersInFrame();
|
|
if (usedShadersInFrame.find(this) == usedShadersInFrame.end())
|
|
{
|
|
newFrame(vgfx->getFrameIndex());
|
|
usedShadersInFrame.insert(this);
|
|
}
|
|
|
|
if (!isCompute)
|
|
{
|
|
if (Shader::current != this)
|
|
{
|
|
Graphics::flushBatchedDrawsGlobal();
|
|
Shader::current = this;
|
|
Vulkan::shaderSwitch();
|
|
}
|
|
}
|
|
else
|
|
vgfx->setComputeShader(this);
|
|
}
|
|
|
|
int Shader::getVertexAttributeIndex(const std::string &name)
|
|
{
|
|
auto it = attributes.find(name);
|
|
return it == attributes.end() ? -1 : it->second;
|
|
}
|
|
|
|
const Shader::UniformInfo *Shader::getUniformInfo(const std::string &name) const
|
|
{
|
|
return &uniformInfos.at(name);
|
|
}
|
|
|
|
const Shader::UniformInfo *Shader::getUniformInfo(BuiltinUniform builtin) const
|
|
{
|
|
return builtinUniformInfo[builtin];
|
|
}
|
|
|
|
static bool usesLocalUniformData(const graphics::Shader::UniformInfo *info)
|
|
{
|
|
return info->baseType == graphics::Shader::UNIFORM_BOOL ||
|
|
info->baseType == graphics::Shader::UNIFORM_FLOAT ||
|
|
info->baseType == graphics::Shader::UNIFORM_INT ||
|
|
info->baseType == graphics::Shader::UNIFORM_MATRIX ||
|
|
info->baseType == graphics::Shader::UNIFORM_UINT;
|
|
}
|
|
|
|
void Shader::updateUniform(const UniformInfo *info, int count)
|
|
{
|
|
if (current == this)
|
|
Graphics::flushBatchedDrawsGlobal();
|
|
|
|
if (usesLocalUniformData(info))
|
|
memcpy(localUniformData.data(), localUniformStagingData.data(), localUniformStagingData.size());
|
|
else if (info->baseType == UNIFORM_SAMPLER || info->baseType == UNIFORM_STORAGETEXTURE)
|
|
{
|
|
bool isSampler = info->baseType == UNIFORM_SAMPLER;
|
|
|
|
std::vector<VkDescriptorImageInfo> imageInfos;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
auto vkTexture = dynamic_cast<Texture*>(info->textures[i]);
|
|
|
|
VkDescriptorImageInfo imageInfo{};
|
|
|
|
imageInfo.imageLayout = vkTexture->getImageLayout();
|
|
imageInfo.imageView = (VkImageView)vkTexture->getRenderTargetHandle();
|
|
if (isSampler)
|
|
imageInfo.sampler = (VkSampler)vkTexture->getSamplerHandle();
|
|
|
|
imageInfos.push_back(imageInfo);
|
|
}
|
|
|
|
VkWriteDescriptorSet write{};
|
|
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write.dstSet = currentDescriptorSet;
|
|
write.dstBinding = info->location;
|
|
write.dstArrayElement = 0;
|
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
write.descriptorCount = static_cast<uint32_t>(count);
|
|
write.pImageInfo = imageInfos.data();
|
|
|
|
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
|
}
|
|
}
|
|
|
|
void Shader::sendTextures(const UniformInfo *info, graphics::Texture **textures, int count)
|
|
{
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
auto oldTexture = info->textures[i];
|
|
info->textures[i] = textures[i];
|
|
info->textures[i]->retain();
|
|
if (oldTexture)
|
|
oldTexture->release();
|
|
}
|
|
}
|
|
|
|
void Shader::calculateUniformBufferSizeAligned()
|
|
{
|
|
auto minAlignment = vgfx->getMinUniformBufferOffsetAlignment();
|
|
size_t size = localUniformStagingData.size();
|
|
auto factor = static_cast<VkDeviceSize>(std::ceil(
|
|
static_cast<float>(size) / static_cast<float>(minAlignment)
|
|
));
|
|
uniformBufferSizeAligned = factor * minAlignment;
|
|
}
|
|
|
|
void Shader::buildLocalUniforms(spirv_cross::Compiler &comp, const spirv_cross::SPIRType &type, size_t baseoff, const std::string &basename)
|
|
{
|
|
using namespace spirv_cross;
|
|
|
|
const auto &membertypes = type.member_types;
|
|
|
|
for (size_t uindex = 0; uindex < membertypes.size(); uindex++)
|
|
{
|
|
const auto &memberType = comp.get_type(membertypes[uindex]);
|
|
size_t memberSize = comp.get_declared_struct_member_size(type, uindex);
|
|
size_t offset = baseoff + comp.type_struct_member_offset(type, uindex);
|
|
|
|
std::string name = basename + comp.get_member_name(type.self, uindex);
|
|
|
|
switch (memberType.basetype)
|
|
{
|
|
case SPIRType::Struct:
|
|
name += ".";
|
|
buildLocalUniforms(comp, memberType, offset, name);
|
|
continue;
|
|
case SPIRType::Int:
|
|
case SPIRType::UInt:
|
|
case SPIRType::Float:
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
UniformInfo u{};
|
|
u.name = name;
|
|
u.dataSize = memberSize;
|
|
u.count = memberType.array.empty() ? 1 : memberType.array[0];
|
|
u.components = 1;
|
|
u.data = localUniformStagingData.data() + offset;
|
|
|
|
if (memberType.columns == 1)
|
|
{
|
|
if (memberType.basetype == SPIRType::Int)
|
|
u.baseType = UNIFORM_INT;
|
|
else if (memberType.basetype == SPIRType::UInt)
|
|
u.baseType = UNIFORM_UINT;
|
|
else
|
|
u.baseType = UNIFORM_FLOAT;
|
|
u.components = memberType.vecsize;
|
|
}
|
|
else
|
|
{
|
|
u.baseType = UNIFORM_MATRIX;
|
|
u.matrix.rows = memberType.vecsize;
|
|
u.matrix.columns = memberType.columns;
|
|
}
|
|
|
|
const auto &reflectionIt = validationReflection.localUniforms.find(u.name);
|
|
if (reflectionIt != validationReflection.localUniforms.end())
|
|
{
|
|
const auto &localUniform = reflectionIt->second;
|
|
const auto &values = localUniform.initializerValues;
|
|
if (!values.empty())
|
|
memcpy(
|
|
u.data,
|
|
values.data(),
|
|
std::min(u.dataSize, values.size() * sizeof(LocalUniformValue)));
|
|
}
|
|
|
|
uniformInfos[u.name] = u;
|
|
|
|
BuiltinUniform builtin = BUILTIN_MAX_ENUM;
|
|
if (getConstant(u.name.c_str(), builtin))
|
|
{
|
|
if (builtin == BUILTIN_UNIFORMS_PER_DRAW)
|
|
builtinUniformDataOffset = offset;
|
|
builtinUniformInfo[builtin] = &uniformInfos[u.name];
|
|
}
|
|
}
|
|
}
|
|
|
|
void Shader::compileShaders()
|
|
{
|
|
using namespace glslang;
|
|
using namespace spirv_cross;
|
|
|
|
std::vector<TShader*> glslangShaders;
|
|
|
|
auto program = new TProgram();
|
|
|
|
device = vgfx->getDevice();
|
|
|
|
const auto &enabledExtensions = vgfx->getEnabledOptionalDeviceExtensions();
|
|
|
|
for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
|
|
{
|
|
if (!stages[i])
|
|
continue;
|
|
|
|
auto stage = (ShaderStageType)i;
|
|
|
|
if (stage == SHADERSTAGE_COMPUTE)
|
|
isCompute = true;
|
|
|
|
auto glslangShaderStage = getGlslShaderType(stage);
|
|
auto tshader = new TShader(glslangShaderStage);
|
|
|
|
tshader->setEnvInput(EShSourceGlsl, glslangShaderStage, EShClientVulkan, 450);
|
|
tshader->setEnvClient(EShClientVulkan, EShTargetVulkan_1_2);
|
|
if (enabledExtensions.spirv14)
|
|
tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_4);
|
|
else
|
|
tshader->setEnvTarget(EshTargetSpv, EShTargetSpv_1_0);
|
|
tshader->setAutoMapLocations(true);
|
|
tshader->setAutoMapBindings(true);
|
|
tshader->setEnvInputVulkanRulesRelaxed();
|
|
tshader->setGlobalUniformBinding(0);
|
|
tshader->setGlobalUniformSet(0);
|
|
|
|
auto &glsl = stages[i]->getSource();
|
|
const char *csrc = glsl.c_str();
|
|
const int sourceLength = static_cast<int>(glsl.length());
|
|
tshader->setStringsWithLengths(&csrc, &sourceLength, 1);
|
|
|
|
int defaultVersion = 450;
|
|
EProfile defaultProfile = ECoreProfile;
|
|
bool forceDefault = false;
|
|
bool forwardCompat = true;
|
|
|
|
if (!tshader->parse(&defaultTBuiltInResource, defaultVersion, defaultProfile, forceDefault, forwardCompat, EShMsgSuppressWarnings))
|
|
{
|
|
const char *msg1 = tshader->getInfoLog();
|
|
const char *msg2 = tshader->getInfoDebugLog();
|
|
|
|
throw love::Exception("error while parsing shader");
|
|
}
|
|
|
|
program->addShader(tshader);
|
|
glslangShaders.push_back(tshader);
|
|
}
|
|
|
|
if (!program->link(EShMsgDefault))
|
|
throw love::Exception("link failed! %s\n", program->getInfoLog());
|
|
|
|
if (!program->mapIO())
|
|
throw love::Exception("mapIO failed");
|
|
|
|
uniformInfos.clear();
|
|
|
|
for (int i = 0; i < SHADERSTAGE_MAX_ENUM; i++)
|
|
{
|
|
auto shaderStage = (ShaderStageType)i;
|
|
auto glslangStage = getGlslShaderType(shaderStage);
|
|
auto intermediate = program->getIntermediate(glslangStage);
|
|
if (intermediate == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
spv::SpvBuildLogger logger;
|
|
glslang::SpvOptions opt;
|
|
opt.validate = true;
|
|
|
|
std::vector<uint32_t> spirv;
|
|
GlslangToSpv(*intermediate, spirv, &logger, &opt);
|
|
|
|
std::string msgs = logger.getAllMessages();
|
|
|
|
VkShaderModuleCreateInfo createInfo{};
|
|
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
createInfo.codeSize = spirv.size() * sizeof(uint32_t);
|
|
createInfo.pCode = spirv.data();
|
|
|
|
auto device = vgfx->getDevice();
|
|
|
|
VkShaderModule shaderModule;
|
|
|
|
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) {
|
|
throw love::Exception("failed to create shader module");
|
|
}
|
|
|
|
shaderModules.push_back(shaderModule);
|
|
|
|
VkPipelineShaderStageCreateInfo shaderStageInfo{};
|
|
shaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
shaderStageInfo.stage = getStageBit((ShaderStageType)i);
|
|
shaderStageInfo.module = shaderModule;
|
|
shaderStageInfo.pName = "main";
|
|
|
|
shaderStages.push_back(shaderStageInfo);
|
|
|
|
spirv_cross::CompilerGLSL comp(spirv);
|
|
|
|
// we only care about variables that are actually getting used.
|
|
auto active = comp.get_active_interface_variables();
|
|
auto shaderResources = comp.get_shader_resources(active);
|
|
comp.set_enabled_interface_variables(std::move(active));
|
|
|
|
for (const auto &resource : shaderResources.uniform_buffers)
|
|
{
|
|
if (resource.name == "gl_DefaultUniformBlock")
|
|
{
|
|
const auto& type = comp.get_type(resource.base_type_id);
|
|
size_t uniformBufferObjectSize = comp.get_declared_struct_size(type);
|
|
auto defaultUniformBlockSize = comp.get_declared_struct_size(type);
|
|
localUniformStagingData.resize(defaultUniformBlockSize);
|
|
localUniformData.resize(defaultUniformBlockSize);
|
|
uniformLocation = comp.get_decoration(resource.id, spv::DecorationBinding);
|
|
|
|
memset(localUniformStagingData.data(), 0, defaultUniformBlockSize);
|
|
memset(localUniformData.data(), 0, defaultUniformBlockSize);
|
|
|
|
std::string basename("");
|
|
buildLocalUniforms(comp, type, 0, basename);
|
|
|
|
memcpy(localUniformData.data(), localUniformStagingData.data(), localUniformStagingData.size());
|
|
}
|
|
else
|
|
throw love::Exception("unimplemented: non default uniform blocks.");
|
|
}
|
|
|
|
for (const auto &r : shaderResources.sampled_images)
|
|
{
|
|
const SPIRType &basetype = comp.get_type(r.base_type_id);
|
|
const SPIRType &type = comp.get_type(r.type_id);
|
|
const SPIRType &imagetype = comp.get_type(basetype.image.type);
|
|
|
|
graphics::Shader::UniformInfo info;
|
|
info.location = comp.get_decoration(r.id, spv::DecorationBinding);
|
|
info.baseType = UNIFORM_SAMPLER;
|
|
info.name = r.name;
|
|
info.count = type.array.empty() ? 1 : type.array[0];
|
|
info.isDepthSampler = type.image.depth;
|
|
info.components = 1;
|
|
|
|
switch (imagetype.basetype)
|
|
{
|
|
case SPIRType::Float:
|
|
info.dataBaseType = DATA_BASETYPE_FLOAT;
|
|
break;
|
|
case SPIRType::Int:
|
|
info.dataBaseType = DATA_BASETYPE_INT;
|
|
break;
|
|
case SPIRType::UInt:
|
|
info.dataBaseType = DATA_BASETYPE_UINT;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (basetype.image.dim)
|
|
{
|
|
case spv::Dim2D:
|
|
info.textureType = basetype.image.arrayed ? TEXTURE_2D_ARRAY : TEXTURE_2D;
|
|
info.textures = new love::graphics::Texture * [info.count];
|
|
break;
|
|
case spv::Dim3D:
|
|
info.textureType = TEXTURE_VOLUME;
|
|
info.textures = new love::graphics::Texture * [info.count];
|
|
break;
|
|
case spv::DimCube:
|
|
if (basetype.image.arrayed) {
|
|
throw love::Exception("cubemap arrays are not currently supported");
|
|
}
|
|
info.textureType = TEXTURE_CUBE;
|
|
info.textures = new love::graphics::Texture * [info.count];
|
|
break;
|
|
case spv::DimBuffer:
|
|
throw love::Exception("dim buffers not implemented yet");
|
|
default:
|
|
throw love::Exception("unknown dim");
|
|
}
|
|
|
|
if (info.baseType == UNIFORM_SAMPLER)
|
|
{
|
|
auto tex = vgfx->getDefaultTexture();
|
|
for (int i = 0; i < info.count; i++) {
|
|
info.textures[i] = tex;
|
|
tex->retain();
|
|
}
|
|
}
|
|
// fixme
|
|
else if (info.baseType == UNIFORM_TEXELBUFFER)
|
|
throw love::Exception("texel buffers not supported yet");
|
|
|
|
uniformInfos[r.name] = info;
|
|
BuiltinUniform builtin;
|
|
if (getConstant(r.name.c_str(), builtin))
|
|
builtinUniformInfo[builtin] = &uniformInfos[info.name];
|
|
}
|
|
|
|
for (const auto &r : shaderResources.storage_buffers)
|
|
{
|
|
const auto &type = comp.get_type(r.type_id);
|
|
|
|
UniformInfo u{};
|
|
u.baseType = UNIFORM_STORAGEBUFFER;
|
|
u.components = 1;
|
|
u.name = r.name;
|
|
u.count = type.array.empty() ? 1 : type.array[0];
|
|
u.location = comp.get_decoration(r.id, spv::DecorationBinding);
|
|
|
|
const auto reflectionit = validationReflection.storageBuffers.find(u.name);
|
|
if (reflectionit != validationReflection.storageBuffers.end())
|
|
{
|
|
u.bufferStride = reflectionit->second.stride;
|
|
u.bufferMemberCount = reflectionit->second.memberCount;
|
|
u.access = reflectionit->second.access;
|
|
}
|
|
else
|
|
continue;
|
|
|
|
// todo: some stuff missing
|
|
|
|
// fixme: memory leak
|
|
u.buffers = new love::graphics::Buffer *[u.count];
|
|
|
|
for (int i = 0; i < u.count; i++)
|
|
u.buffers[i] = nullptr;
|
|
|
|
uniformInfos[u.name] = u;
|
|
}
|
|
|
|
for (const auto &r : shaderResources.storage_images)
|
|
{
|
|
const auto &type = comp.get_type(r.type_id);
|
|
|
|
UniformInfo u{};
|
|
u.baseType = UNIFORM_STORAGETEXTURE;
|
|
u.components = 1;
|
|
u.name = r.name;
|
|
u.count = type.array.empty() ? 1 : type.array[0];
|
|
u.textures = new love::graphics::Texture *[u.count];
|
|
u.location = comp.get_decoration(r.id, spv::DecorationBinding);
|
|
|
|
for (int i = 0; i < u.count; i++)
|
|
u.textures[i] = nullptr;
|
|
|
|
// some stuff missing ?
|
|
|
|
uniformInfos[u.name] = u;
|
|
}
|
|
|
|
if (shaderStage == SHADERSTAGE_VERTEX)
|
|
for (const auto &r : shaderResources.stage_inputs)
|
|
{
|
|
const auto &name = r.name;
|
|
const int attributeLocation = static_cast<int>(comp.get_decoration(r.id, spv::DecorationLocation));
|
|
attributes[name] = attributeLocation;
|
|
}
|
|
}
|
|
|
|
delete program;
|
|
for (auto shader : glslangShaders)
|
|
delete shader;
|
|
}
|
|
|
|
void Shader::createDescriptorSetLayout()
|
|
{
|
|
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
|
|
|
for (auto const &[key, val] : uniformInfos)
|
|
{
|
|
auto type = Vulkan::getDescriptorType(val.baseType);
|
|
if (type != VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER)
|
|
{
|
|
VkDescriptorSetLayoutBinding layoutBinding{};
|
|
|
|
layoutBinding.binding = val.location;
|
|
layoutBinding.descriptorType = type;
|
|
layoutBinding.descriptorCount = val.count;
|
|
if (isCompute)
|
|
layoutBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
else
|
|
layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
bindings.push_back(layoutBinding);
|
|
}
|
|
}
|
|
|
|
if (!localUniformStagingData.empty())
|
|
{
|
|
VkDescriptorSetLayoutBinding uniformBinding{};
|
|
uniformBinding.binding = uniformLocation;
|
|
uniformBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
uniformBinding.descriptorCount = 1;
|
|
if (isCompute)
|
|
uniformBinding.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
else
|
|
uniformBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
bindings.push_back(uniformBinding);
|
|
}
|
|
|
|
VkDescriptorSetLayoutCreateInfo layoutInfo{};
|
|
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
|
|
layoutInfo.pBindings = bindings.data();
|
|
|
|
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS)
|
|
throw love::Exception("failed to create descriptor set layout");
|
|
}
|
|
|
|
void Shader::createPipelineLayout()
|
|
{
|
|
VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
|
|
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
pipelineLayoutInfo.setLayoutCount = 1;
|
|
pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout;
|
|
pipelineLayoutInfo.pushConstantRangeCount = 0;
|
|
|
|
if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS)
|
|
throw love::Exception("failed to create pipeline layout");
|
|
|
|
if (isCompute)
|
|
{
|
|
assert(shaderStages.size() == 1);
|
|
|
|
VkComputePipelineCreateInfo computeInfo{};
|
|
computeInfo.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
|
computeInfo.stage = shaderStages.at(0);
|
|
computeInfo.layout = pipelineLayout;
|
|
|
|
if (vkCreateComputePipelines(device, VK_NULL_HANDLE, 1, &computeInfo, nullptr, &computePipeline) != VK_SUCCESS)
|
|
throw love::Exception("failed to create compute pipeline");
|
|
}
|
|
}
|
|
|
|
void Shader::createDescriptorPoolSizes()
|
|
{
|
|
if (!localUniformData.empty())
|
|
{
|
|
VkDescriptorPoolSize size{};
|
|
size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
size.descriptorCount = 1;
|
|
|
|
descriptorPoolSizes.push_back(size);
|
|
}
|
|
|
|
for (const auto &[key, val] : uniformInfos)
|
|
{
|
|
VkDescriptorPoolSize size{};
|
|
auto type = Vulkan::getDescriptorType(val.baseType);
|
|
if (type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) {
|
|
continue;
|
|
}
|
|
size.type = type;
|
|
size.descriptorCount = 1;
|
|
descriptorPoolSizes.push_back(size);
|
|
}
|
|
}
|
|
|
|
void Shader::createStreamBuffers()
|
|
{
|
|
const auto numImagesInFlight = vgfx->getNumImagesInFlight();
|
|
streamBuffers.resize(numImagesInFlight);
|
|
for (uint32_t i = 0; i < numImagesInFlight; i++)
|
|
streamBuffers[i].push_back(new StreamBuffer(vgfx, BUFFERUSAGE_UNIFORM, STREAMBUFFER_DEFAULT_SIZE * uniformBufferSizeAligned));
|
|
}
|
|
|
|
void Shader::setVideoTextures(graphics::Texture *ytexture, graphics::Texture *cbtexture, graphics::Texture *crtexture)
|
|
{
|
|
std::array<graphics::Texture*, 3> textures = {
|
|
ytexture, cbtexture, crtexture
|
|
};
|
|
|
|
std::array<BuiltinUniform, 3> builtIns = {
|
|
BUILTIN_TEXTURE_VIDEO_Y,
|
|
BUILTIN_TEXTURE_VIDEO_CB,
|
|
BUILTIN_TEXTURE_VIDEO_CR,
|
|
};
|
|
|
|
static_assert(textures.size() == builtIns.size());
|
|
|
|
for (size_t i = 0; i < textures.size(); i++)
|
|
if (builtinUniformInfo[builtIns[i]] != nullptr)
|
|
{
|
|
textures[i]->retain();
|
|
builtinUniformInfo[builtIns[i]]->textures[0]->release();
|
|
builtinUniformInfo[builtIns[i]]->textures[0] = textures[i];
|
|
}
|
|
}
|
|
|
|
bool Shader::hasUniform(const std::string &name) const
|
|
{
|
|
return uniformInfos.find(name) != uniformInfos.end();
|
|
}
|
|
|
|
void Shader::setMainTex(graphics::Texture *texture)
|
|
{
|
|
if (builtinUniformInfo[BUILTIN_TEXTURE_MAIN] != nullptr)
|
|
{
|
|
texture->retain();
|
|
builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0]->release();
|
|
builtinUniformInfo[BUILTIN_TEXTURE_MAIN]->textures[0] = texture;
|
|
}
|
|
}
|
|
|
|
VkDescriptorSet Shader::allocateDescriptorSet()
|
|
{
|
|
if (freeDescriptorSets.empty())
|
|
{
|
|
VkDescriptorPoolCreateInfo createInfo{};
|
|
createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
createInfo.maxSets = DESCRIPTOR_POOL_SIZE;
|
|
createInfo.poolSizeCount = static_cast<uint32_t>(descriptorPoolSizes.size());
|
|
createInfo.pPoolSizes = descriptorPoolSizes.data();
|
|
|
|
VkDescriptorPool pool;
|
|
if (vkCreateDescriptorPool(device, &createInfo, nullptr, &pool) != VK_SUCCESS)
|
|
throw love::Exception("failed to create descriptor pool");
|
|
descriptorPools.push_back(pool);
|
|
|
|
std::vector<VkDescriptorSetLayout> layouts(DESCRIPTOR_POOL_SIZE, descriptorSetLayout);
|
|
|
|
VkDescriptorSetAllocateInfo allocInfo{};
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
allocInfo.descriptorPool = pool;
|
|
allocInfo.descriptorSetCount = DESCRIPTOR_POOL_SIZE;
|
|
allocInfo.pSetLayouts = layouts.data();
|
|
|
|
std::vector<VkDescriptorSet> descriptorSet;
|
|
descriptorSet.resize(DESCRIPTOR_POOL_SIZE);
|
|
VkResult result = vkAllocateDescriptorSets(device, &allocInfo, descriptorSet.data());
|
|
if (result != VK_SUCCESS)
|
|
throw love::Exception("failed to allocate descriptor set");
|
|
|
|
for (const auto ds : descriptorSet)
|
|
freeDescriptorSets.push(ds);
|
|
}
|
|
|
|
auto ds = freeDescriptorSets.front();
|
|
freeDescriptorSets.pop();
|
|
return ds;
|
|
}
|
|
|
|
} // vulkan
|
|
} // graphics
|
|
} // love
|