/* * Created on: Oct 3, 2019 Copyright 2019 flyinghead This file is part of Flycast. Flycast 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. Flycast 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 Flycast. If not, see . */ #pragma once #include "vulkan.h" #include "shaders.h" #include "texture.h" #include "utils.h" #include "vulkan_context.h" #include class DescriptorSets { public: DescriptorSets() = default; DescriptorSets(DescriptorSets &&) = default; DescriptorSets(const DescriptorSets &) = delete; DescriptorSets& operator=(DescriptorSets &&) = default; DescriptorSets& operator=(const DescriptorSets &) = delete; void Init(SamplerManager* samplerManager, vk::PipelineLayout pipelineLayout, vk::DescriptorSetLayout perFrameLayout, vk::DescriptorSetLayout perPolyLayout) { this->samplerManager = samplerManager; this->pipelineLayout = pipelineLayout; this->perFrameLayout = perFrameLayout; this->perPolyLayout = perPolyLayout; } void UpdateUniforms(vk::Buffer buffer, u32 vertexUniformOffset, u32 fragmentUniformOffset, vk::ImageView fogImageView, vk::ImageView paletteImageView) { if (perFrameDescSets.empty()) { perFrameDescSets = GetContext()->GetDevice().allocateDescriptorSetsUnique( vk::DescriptorSetAllocateInfo(GetContext()->GetDescriptorPool(), 1, &perFrameLayout)); } perFrameDescSetsInFlight.emplace_back(std::move(perFrameDescSets.back())); perFrameDescSets.pop_back(); vk::DescriptorSet perFrameDescSet = *perFrameDescSetsInFlight.back(); std::vector bufferInfos; bufferInfos.emplace_back(buffer, vertexUniformOffset, sizeof(VertexShaderUniforms)); bufferInfos.emplace_back(buffer, fragmentUniformOffset, sizeof(FragmentShaderUniforms)); std::vector writeDescriptorSets; writeDescriptorSets.emplace_back(perFrameDescSet, 0, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bufferInfos[0], nullptr); writeDescriptorSets.emplace_back(perFrameDescSet, 1, 0, 1, vk::DescriptorType::eUniformBuffer, nullptr, &bufferInfos[1], nullptr); if (fogImageView) { TSP fogTsp = {}; fogTsp.FilterMode = 1; fogTsp.ClampU = 1; fogTsp.ClampV = 1; vk::Sampler fogSampler = samplerManager->GetSampler(fogTsp); static vk::DescriptorImageInfo imageInfo; imageInfo = { fogSampler, fogImageView, vk::ImageLayout::eShaderReadOnlyOptimal }; writeDescriptorSets.emplace_back(perFrameDescSet, 2, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); } if (paletteImageView) { TSP palTsp = {}; palTsp.FilterMode = 0; palTsp.ClampU = 1; palTsp.ClampV = 1; vk::Sampler palSampler = samplerManager->GetSampler(palTsp); static vk::DescriptorImageInfo imageInfo; imageInfo = { palSampler, paletteImageView, vk::ImageLayout::eShaderReadOnlyOptimal }; writeDescriptorSets.emplace_back(perFrameDescSet, 3, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); } GetContext()->GetDevice().updateDescriptorSets(writeDescriptorSets, nullptr); } void SetTexture(Texture *texture, TSP tsp) { auto& inFlight = perPolyDescSetsInFlight; std::pair index = std::make_pair(texture, tsp.full & SamplerManager::TSP_Mask); if (inFlight.find(index) != inFlight.end()) return; if (perPolyDescSets.empty()) { std::vector layouts(10, perPolyLayout); perPolyDescSets = GetContext()->GetDevice().allocateDescriptorSetsUnique( vk::DescriptorSetAllocateInfo(GetContext()->GetDescriptorPool(), (u32)layouts.size(), &layouts[0])); } vk::DescriptorImageInfo imageInfo(samplerManager->GetSampler(tsp), texture->GetReadOnlyImageView(), vk::ImageLayout::eShaderReadOnlyOptimal); std::vector writeDescriptorSets; writeDescriptorSets.emplace_back(*perPolyDescSets.back(), 0, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); GetContext()->GetDevice().updateDescriptorSets(writeDescriptorSets, nullptr); inFlight[index] = std::move(perPolyDescSets.back()); perPolyDescSets.pop_back(); } void BindPerFrameDescriptorSets(vk::CommandBuffer cmdBuffer) { cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, 1, &perFrameDescSetsInFlight.back().get(), 0, nullptr); } void BindPerPolyDescriptorSets(vk::CommandBuffer cmdBuffer, Texture *texture, TSP tsp) { cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 1, 1, &perPolyDescSetsInFlight[std::make_pair(texture, tsp.full & SamplerManager::TSP_Mask)].get(), 0, nullptr); } void Reset() { for (auto& pair : perPolyDescSetsInFlight) perPolyDescSets.emplace_back(std::move(pair.second)); perPolyDescSetsInFlight.clear(); for (auto& descset : perFrameDescSetsInFlight) perFrameDescSets.emplace_back(std::move(descset)); perFrameDescSetsInFlight.clear(); } private: VulkanContext *GetContext() const { return VulkanContext::Instance(); } vk::DescriptorSetLayout perFrameLayout; vk::DescriptorSetLayout perPolyLayout; vk::PipelineLayout pipelineLayout; std::vector perFrameDescSets; std::vector perFrameDescSetsInFlight; std::vector perPolyDescSets; std::map, vk::UniqueDescriptorSet> perPolyDescSetsInFlight; SamplerManager* samplerManager = nullptr; }; class PipelineManager { public: virtual ~PipelineManager() = default; void Init(ShaderManager *shaderManager, vk::RenderPass renderPass) { this->shaderManager = shaderManager; if (!perFrameLayout) { // Descriptor set and pipeline layout vk::DescriptorSetLayoutBinding perFrameBindings[] = { { 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex }, // vertex uniforms { 1, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eFragment }, // fragment uniforms { 2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment },// fog texture { 3, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment },// palette texture }; vk::DescriptorSetLayoutBinding perPolyBindings[] = { { 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment },// texture }; perFrameLayout = GetContext()->GetDevice().createDescriptorSetLayoutUnique( vk::DescriptorSetLayoutCreateInfo(vk::DescriptorSetLayoutCreateFlags(), ARRAY_SIZE(perFrameBindings), perFrameBindings)); perPolyLayout = GetContext()->GetDevice().createDescriptorSetLayoutUnique( vk::DescriptorSetLayoutCreateInfo(vk::DescriptorSetLayoutCreateFlags(), ARRAY_SIZE(perPolyBindings), perPolyBindings)); vk::DescriptorSetLayout layouts[] = { *perFrameLayout, *perPolyLayout }; vk::PushConstantRange pushConstant(vk::ShaderStageFlagBits::eFragment, 0, 24); pipelineLayout = GetContext()->GetDevice().createPipelineLayoutUnique( vk::PipelineLayoutCreateInfo(vk::PipelineLayoutCreateFlags(), ARRAY_SIZE(layouts), layouts, 1, &pushConstant)); } if (this->renderPass != renderPass) { this->renderPass = renderPass; Reset(); } } vk::Pipeline GetPipeline(u32 listType, bool sortTriangles, const PolyParam& pp, bool gpuPalette) { u32 pipehash = hash(listType, sortTriangles, &pp, gpuPalette); const auto &pipeline = pipelines.find(pipehash); if (pipeline != pipelines.end()) return pipeline->second.get(); CreatePipeline(listType, sortTriangles, pp, gpuPalette); return *pipelines[pipehash]; } vk::Pipeline GetModifierVolumePipeline(ModVolMode mode, int cullMode) { u32 pipehash = hash(mode, cullMode); const auto &pipeline = modVolPipelines.find(pipehash); if (pipeline != modVolPipelines.end()) return pipeline->second.get(); CreateModVolPipeline(mode, cullMode); return *modVolPipelines[pipehash]; } vk::Pipeline GetDepthPassPipeline(int cullMode) { cullMode = std::max(std::min(cullMode, (int)depthPassPipelines.size() - 1), 0); const auto &pipeline = depthPassPipelines[cullMode]; if (!pipeline) CreateDepthPassPipeline(cullMode); return *pipeline; } void Reset() { pipelines.clear(); modVolPipelines.clear(); } vk::PipelineLayout GetPipelineLayout() const { return *pipelineLayout; } vk::DescriptorSetLayout GetPerFrameDSLayout() const { return *perFrameLayout; } vk::DescriptorSetLayout GetPerPolyDSLayout() const { return *perPolyLayout; } vk::RenderPass GetRenderPass() const { return renderPass; } private: void CreateModVolPipeline(ModVolMode mode, int cullMode); void CreateDepthPassPipeline(int cullMode); u32 hash(u32 listType, bool sortTriangles, const PolyParam *pp, bool gpuPalette) const { u32 hash = pp->pcw.Gouraud | (pp->pcw.Offset << 1) | (pp->pcw.Texture << 2) | (pp->pcw.Shadow << 3) | (((pp->tileclip >> 28) == 3) << 4); hash |= ((listType >> 1) << 5); bool ignoreTexAlpha = pp->tsp.IgnoreTexA || pp->tcw.PixelFmt == Pixel565; hash |= (pp->tsp.ShadInstr << 7) | (ignoreTexAlpha << 9) | (pp->tsp.UseAlpha << 10) | (pp->tsp.ColorClamp << 11) | ((config::Fog ? pp->tsp.FogCtrl : 2) << 12) | (pp->tsp.SrcInstr << 14) | (pp->tsp.DstInstr << 17); hash |= (pp->isp.ZWriteDis << 20) | (pp->isp.CullMode << 21) | (pp->isp.DepthMode << 23); hash |= ((u32)sortTriangles << 26) | ((u32)gpuPalette << 27); return hash; } u32 hash(ModVolMode mode, int cullMode) const { return ((int)mode << 2) | cullMode; } vk::PipelineVertexInputStateCreateInfo GetMainVertexInputStateCreateInfo(bool full = true) const { // Vertex input state static const vk::VertexInputBindingDescription vertexBindingDescriptions[] = { { 0, sizeof(Vertex) }, }; static const vk::VertexInputAttributeDescription vertexInputAttributeDescriptions[] = { vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, x)), // pos vk::VertexInputAttributeDescription(1, 0, vk::Format::eR8G8B8A8Uint, offsetof(Vertex, col)), // base color vk::VertexInputAttributeDescription(2, 0, vk::Format::eR8G8B8A8Uint, offsetof(Vertex, spc)), // offset color vk::VertexInputAttributeDescription(3, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, u)), // tex coord }; static const vk::VertexInputAttributeDescription vertexInputLightAttributeDescriptions[] = { vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, x)), // pos }; return vk::PipelineVertexInputStateCreateInfo( vk::PipelineVertexInputStateCreateFlags(), ARRAY_SIZE(vertexBindingDescriptions), vertexBindingDescriptions, full ? ARRAY_SIZE(vertexInputAttributeDescriptions) : ARRAY_SIZE(vertexInputLightAttributeDescriptions), full ? vertexInputAttributeDescriptions : vertexInputLightAttributeDescriptions); } void CreatePipeline(u32 listType, bool sortTriangles, const PolyParam& pp, bool gpuPalette); std::map pipelines; std::map modVolPipelines; std::array depthPassPipelines; vk::UniquePipelineLayout pipelineLayout; vk::UniqueDescriptorSetLayout perFrameLayout; vk::UniqueDescriptorSetLayout perPolyLayout; protected: VulkanContext *GetContext() const { return VulkanContext::Instance(); } vk::RenderPass renderPass; ShaderManager *shaderManager = nullptr; }; class RttPipelineManager : public PipelineManager { public: void Init(ShaderManager *shaderManager) { // RTT render pass renderToTextureBuffer = config::RenderToTextureBuffer; vk::AttachmentDescription attachmentDescriptions[] = { vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), vk::Format::eR8G8B8A8Unorm, vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eColorAttachmentOptimal, renderToTextureBuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal), vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), GetContext()->GetDepthFormat(), vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined, vk::ImageLayout::eDepthStencilAttachmentOptimal), }; vk::AttachmentReference colorReference(0, vk::ImageLayout::eColorAttachmentOptimal); vk::AttachmentReference depthReference(1, vk::ImageLayout::eDepthStencilAttachmentOptimal); vk::SubpassDescription subpass(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, 0, nullptr, 1, &colorReference, nullptr, &depthReference); vk::SubpassDependency dependencies[] { vk::SubpassDependency(VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eFragmentShader, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::AccessFlagBits::eShaderRead, vk::AccessFlagBits::eColorAttachmentWrite), vk::SubpassDependency(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead), }; vk::SubpassDependency vramWriteDeps[] { vk::SubpassDependency(0, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost, vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead), }; rttRenderPass = GetContext()->GetDevice().createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(), 2, attachmentDescriptions, 1, &subpass, renderToTextureBuffer ? ARRAY_SIZE(vramWriteDeps) : ARRAY_SIZE(dependencies), renderToTextureBuffer ? vramWriteDeps : dependencies)); PipelineManager::Init(shaderManager, *rttRenderPass); } void CheckSettingsChange() { if (renderToTextureBuffer != config::RenderToTextureBuffer) Init(shaderManager); } private: vk::UniqueRenderPass rttRenderPass; bool renderToTextureBuffer = false; }; class OSDPipeline { public: void Init(ShaderManager *shaderManager, vk::ImageView imageView, vk::RenderPass renderPass) { this->shaderManager = shaderManager; if (!pipelineLayout) { vk::DescriptorSetLayoutBinding bindings[] = { { 0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment },// texture }; descSetLayout = GetContext()->GetDevice().createDescriptorSetLayoutUnique( vk::DescriptorSetLayoutCreateInfo(vk::DescriptorSetLayoutCreateFlags(), ARRAY_SIZE(bindings), bindings)); pipelineLayout = GetContext()->GetDevice().createPipelineLayoutUnique( vk::PipelineLayoutCreateInfo(vk::PipelineLayoutCreateFlags(), 1, &descSetLayout.get())); } if (!sampler) { sampler = GetContext()->GetDevice().createSamplerUnique( vk::SamplerCreateInfo(vk::SamplerCreateFlags(), vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear, vk::SamplerAddressMode::eClampToEdge, vk::SamplerAddressMode::eClampToEdge, vk::SamplerAddressMode::eClampToEdge, 0.0f, false, 16.0f, false, vk::CompareOp::eNever, 0.0f, 0.0f, vk::BorderColor::eFloatOpaqueBlack)); } if (this->renderPass != renderPass) { this->renderPass = renderPass; pipeline.reset(); } if (!descriptorSet) { descriptorSet = std::move(GetContext()->GetDevice().allocateDescriptorSetsUnique( vk::DescriptorSetAllocateInfo(GetContext()->GetDescriptorPool(), 1, &descSetLayout.get())).front()); } vk::DescriptorImageInfo imageInfo(*sampler, imageView, vk::ImageLayout::eShaderReadOnlyOptimal); std::vector writeDescriptorSets; writeDescriptorSets.emplace_back(*descriptorSet, 0, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr); GetContext()->GetDevice().updateDescriptorSets(writeDescriptorSets, nullptr); } vk::Pipeline GetPipeline() { if (!pipeline) CreatePipeline(); return *pipeline; } void BindDescriptorSets(vk::CommandBuffer cmdBuffer) const { cmdBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, 1, &descriptorSet.get(), 0, nullptr); } private: VulkanContext *GetContext() const { return VulkanContext::Instance(); } void CreatePipeline(); vk::RenderPass renderPass; vk::UniquePipeline pipeline; vk::UniqueSampler sampler; vk::UniqueDescriptorSet descriptorSet; vk::UniquePipelineLayout pipelineLayout; vk::UniqueDescriptorSetLayout descSetLayout; ShaderManager *shaderManager = nullptr; };