#define PVR_REGS_FOR_RENDERER // see hw/pvr/pvr_regs.h
/*
Created on: Nov 7, 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 .
*/
#include "quad.h"
#include "vulkan_context.h"
#include "commandpool.h"
#include "ssaa.h"
#include "rend/ssaa_pattern.h"
static VulkanContext *GetContext()
{
return VulkanContext::Instance();
}
vk::PipelineVertexInputStateCreateInfo GetQuadInputStateCreateInfo(bool uv)
{
// Vertex input state
static const vk::VertexInputBindingDescription vertexBindingDescriptions[] =
{
{ 0, sizeof(QuadVertex) },
};
static const vk::VertexInputAttributeDescription vertexInputAttributeDescriptions[] =
{
vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32B32Sfloat, offsetof(QuadVertex, pos)), // pos
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32Sfloat, offsetof(QuadVertex, uv)), // tex coord
};
return vk::PipelineVertexInputStateCreateInfo(
vk::PipelineVertexInputStateCreateFlags(),
ARRAY_SIZE(vertexBindingDescriptions),
vertexBindingDescriptions,
ARRAY_SIZE(vertexInputAttributeDescriptions) - (uv ? 0 : 1),
vertexInputAttributeDescriptions);
}
QuadPipeline::QuadPipeline()
{
PsoOwnInit(this, slots, 2);
}
VkPipeline QuadPipeline::BuildPipeline(u64 key, vk::PipelineCache cache)
{
return Create(key != 0, cache, false);
}
vk::Pipeline QuadPipeline::MakeHere(bool alpha)
{
return vk::Pipeline(PsoOwnMade(slots[alpha ? 1 : 0], Create(alpha, GetContext()->GetPipelineCache(), true)));
}
void QuadPipeline::Drop()
{
PsoOwnDrop(this, slots, 2, GetContext() != nullptr ? GetContext()->GetDevice() : vk::Device());
}
/* @orThrow: as vulkan.hpp does when the device cannot make it, else
* VK_NULL_HANDLE */
VkPipeline QuadPipeline::Create(bool alpha, vk::PipelineCache cache, bool orThrow)
{
vk::PipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = GetQuadInputStateCreateInfo(true);
// Input assembly state
vk::PipelineInputAssemblyStateCreateInfo pipelineInputAssemblyStateCreateInfo(vk::PipelineInputAssemblyStateCreateFlags(), vk::PrimitiveTopology::eTriangleStrip);
// Viewport and scissor states
vk::PipelineViewportStateCreateInfo pipelineViewportStateCreateInfo(vk::PipelineViewportStateCreateFlags(), 1, nullptr, 1, nullptr);
// Rasterization and multisample states
vk::PipelineRasterizationStateCreateInfo pipelineRasterizationStateCreateInfo;
pipelineRasterizationStateCreateInfo.lineWidth = 1.0;
vk::PipelineMultisampleStateCreateInfo pipelineMultisampleStateCreateInfo;
pipelineMultisampleStateCreateInfo.rasterizationSamples = (vk::SampleCountFlagBits)samples;
// Depth and stencil
vk::PipelineDepthStencilStateCreateInfo pipelineDepthStencilStateCreateInfo;
// Color flags and blending
vk::PipelineColorBlendAttachmentState pipelineColorBlendAttachmentState(
true, // blendEnable
alpha ? vk::BlendFactor::eSrcAlpha : vk::BlendFactor::eConstantAlpha, // srcColorBlendFactor
alpha ? vk::BlendFactor::eOneMinusSrcAlpha : vk::BlendFactor::eOneMinusConstantAlpha, // dstColorBlendFactor
vk::BlendOp::eAdd, // colorBlendOp
alpha ? vk::BlendFactor::eSrcAlpha : vk::BlendFactor::eConstantAlpha, // srcAlphaBlendFactor
alpha ? vk::BlendFactor::eOneMinusSrcAlpha : vk::BlendFactor::eOneMinusConstantAlpha, // dstAlphaBlendFactor
vk::BlendOp::eAdd, // alphaBlendOp
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG
| vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA
);
vk::PipelineColorBlendStateCreateInfo pipelineColorBlendStateCreateInfo
(
vk::PipelineColorBlendStateCreateFlags(), // flags
false, // logicOpEnable
vk::LogicOp::eNoOp, // logicOp
1, // attachmentCount
&pipelineColorBlendAttachmentState, // pAttachments
{ { 1.0f, 1.0f, 1.0f, 1.0f } } // blendConstants
);
vk::DynamicState dynamicStates[] = { vk::DynamicState::eViewport, vk::DynamicState::eScissor, vk::DynamicState::eBlendConstants };
vk::PipelineDynamicStateCreateInfo pipelineDynamicStateCreateInfo(vk::PipelineDynamicStateCreateFlags(), ARRAY_SIZE(dynamicStates),
dynamicStates);
vk::PipelineShaderStageCreateInfo stages[] = {
{ vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, shaderManager->GetQuadVertexShader(), "main" },
{ vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment, shaderManager->GetQuadFragmentShader(), "main" },
};
vk::GraphicsPipelineCreateInfo graphicsPipelineCreateInfo
(
vk::PipelineCreateFlags(), // flags
2, // stageCount
stages, // pStages
&pipelineVertexInputStateCreateInfo, // pVertexInputState
&pipelineInputAssemblyStateCreateInfo, // pInputAssemblyState
nullptr, // pTessellationState
&pipelineViewportStateCreateInfo, // pViewportState
&pipelineRasterizationStateCreateInfo, // pRasterizationState
&pipelineMultisampleStateCreateInfo, // pMultisampleState
&pipelineDepthStencilStateCreateInfo, // pDepthStencilState
&pipelineColorBlendStateCreateInfo, // pColorBlendState
&pipelineDynamicStateCreateInfo, // pDynamicState
*pipelineLayout, // layout
renderPass, // renderPass
subpass // subpass
);
if (!orThrow)
return PsoCreate(GetContext()->GetDevice(), cache, graphicsPipelineCreateInfo);
return (VkPipeline)GetContext()->GetDevice().createGraphicsPipelineUnique(cache, graphicsPipelineCreateInfo).release();
}
void QuadPipeline::Init(ShaderManager *shaderManager, vk::RenderPass renderPass, int subpass, u32 samples)
{
this->shaderManager = shaderManager;
shaderManager->Init();
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 (!linearSampler)
{
/* What is drawn with this one is a whole picture at another size:
* the screen as it was before the renderer changed size, or before
* the frontend took the context away, under a render that covers
* part of it. Past its edge there is nothing: the edge is repeated.
* With a black border there instead, a picture made larger had its
* outermost pixels mixed with black - a dark line around the screen
* that stayed for as long as the game did not draw there. */
linearSampler = 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 (!nearestSampler)
{
nearestSampler = GetContext()->GetDevice().createSamplerUnique(
vk::SamplerCreateInfo(vk::SamplerCreateFlags(),
vk::Filter::eNearest, vk::Filter::eNearest,
vk::SamplerMipmapMode::eNearest,
vk::SamplerAddressMode::eClampToBorder,
vk::SamplerAddressMode::eClampToBorder,
vk::SamplerAddressMode::eClampToBorder, 0.0f, false,
16.0f, false, vk::CompareOp::eNever, 0.0f, 0.0f,
vk::BorderColor::eFloatOpaqueBlack));
}
if (this->renderPass != renderPass || this->subpass != subpass || this->samples != samples)
{
Drop();
this->renderPass = renderPass;
this->subpass = subpass;
this->samples = samples;
// (both asked for now, not when first drawn: what the VMUs and the OSD are drawn with, the last picture)
PsoSlot *both[] = { &slots[0], &slots[1] };
VkPsoWorker::Instance().BuildSoon(both, 2, GetContext()->GetPipelineCache());
}
}
void QuadDrawer::Init(QuadPipeline *pipeline)
{
this->pipeline = pipeline;
buffer = std::unique_ptr(new QuadBuffer());
slots.clear();
slots.resize(VulkanContext::Instance()->GetSwapChainSize());
}
void QuadDrawer::Draw(vk::CommandBuffer commandBuffer, vk::ImageView imageView, QuadVertex vertices[], bool nearestFilter, CommandPool *commandPool)
{
VulkanContext *context = GetContext();
/* The frontend can make its swap chain longer while the game runs (when
* fast-forward changes how it presents): its frame index then goes past
* the slots made at the start. */
const size_t imageIndex = context->GetCurrentImageIndex();
if (imageIndex >= slots.size())
slots.resize(imageIndex + 1);
Slot& slot = slots[imageIndex];
const vk::Sampler sampler = nearestFilter ? pipeline->GetNearestSampler() : pipeline->GetLinearSampler();
/* The set says which image to draw and how to sample it, and that is the
* same from one frame to the next, so it is written when it changes and
* not every time. Writing it every time was also not allowed: the frame
* before can still be on its way through the GPU using this very set -
* the frontend's frame index, which picks the set, does not move when the
* game renders twice in one run - and a set in use must not be written.
* When it does change, the old set is left as it is for the frames that
* use it and a new one takes its place.
*
* The old one goes to the command pool, which destroys what it is given
* when it comes round to this frame's place again - having waited, on
* the way, for the fence of every frame submitted before then. (It was
* kept here for a number of draws instead, and a number of draws says
* nothing about what the GPU has finished: a game that renders several
* times to one of the frontend's frames gets through them at once.) */
if (!slot.set || slot.view != imageView || slot.sampler != sampler)
{
if (slot.set)
commandPool->DeferFree(std::move(slot.set), context->GetDescriptorPool());
vk::DescriptorSetLayout layout = pipeline->GetDescSetLayout();
slot.set = vk_alloc_desc_set(context->GetDevice(), context->GetDescriptorPool(), layout);
slot.view = imageView;
slot.sampler = sampler;
vk::DescriptorImageInfo imageInfo(sampler, imageView, vk::ImageLayout::eShaderReadOnlyOptimal);
vk::WriteDescriptorSet write(*slot.set, 0, 0, 1, vk::DescriptorType::eCombinedImageSampler, &imageInfo, nullptr, nullptr);
context->GetDevice().updateDescriptorSets(1, &write, 0, nullptr);
}
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipeline->GetPipelineLayout(), 0, 1, &slot.set.get(), 0, nullptr);
buffer->Update(vertices);
buffer->Bind(commandBuffer);
buffer->Draw(commandBuffer);
}
SsaaResolve::SsaaResolve()
{
PsoOwnInit(this, &slot, 1);
}
void SsaaResolve::Drop()
{
PsoOwnDrop(this, &slot, 1, VulkanContext::Instance() != nullptr ? VulkanContext::Instance()->GetDevice() : vk::Device());
}
void SsaaResolve::Init(vk::RenderPass renderPass, u32 subpass, u32 samples, vk::ImageView samplesView)
{
VulkanContext *context = VulkanContext::Instance();
vk::Device device = context->GetDevice();
Drop();
if (!setLayout)
{
vk::DescriptorSetLayoutBinding binding(0, vk::DescriptorType::eInputAttachment, 1, vk::ShaderStageFlagBits::eFragment);
setLayout = device.createDescriptorSetLayoutUnique(vk::DescriptorSetLayoutCreateInfo(vk::DescriptorSetLayoutCreateFlags(), 1, &binding));
pipelineLayout = device.createPipelineLayoutUnique(vk::PipelineLayoutCreateInfo(vk::PipelineLayoutCreateFlags(), 1, &setLayout.get()));
// (shaders/ssaa_resolve.*: the count of samples is a specialization constant)
vertexShader = MakeRasterModule(RSPV_SSAA_RESOLVE_VERT);
fragmentShader = MakeRasterModule(RSPV_SSAA_RESOLVE_FRAG);
}
this->renderPass = renderPass;
this->subpass = subpass;
this->samples = samples;
if (!descSet)
descSet = vk_alloc_desc_set(device, context->GetDescriptorPool(), setLayout.get());
vk::DescriptorImageInfo imageInfo(vk::Sampler(), samplesView, vk::ImageLayout::eShaderReadOnlyOptimal);
vk::WriteDescriptorSet write(*descSet, 0, 0, 1, vk::DescriptorType::eInputAttachment, &imageInfo, nullptr, nullptr);
device.updateDescriptorSets(write, nullptr);
PsoSlot *one = &slot;
VkPsoWorker::Instance().BuildSoon(&one, 1, context->GetPipelineCache());
}
VkPipeline SsaaResolve::BuildPipeline(u64 key, vk::PipelineCache cache)
{
return Create(cache, false);
}
/* @orThrow: as vulkan.hpp does when the device cannot make it, else
* VK_NULL_HANDLE */
VkPipeline SsaaResolve::Create(vk::PipelineCache cache, bool orThrow) const
{
vk::Device device = VulkanContext::Instance()->GetDevice();
const PsoSpecialization specialization(0, samples);
vk::PipelineVertexInputStateCreateInfo vertexInput;
vk::PipelineInputAssemblyStateCreateInfo inputAssembly(vk::PipelineInputAssemblyStateCreateFlags(), vk::PrimitiveTopology::eTriangleList);
vk::PipelineViewportStateCreateInfo viewportState(vk::PipelineViewportStateCreateFlags(), 1, nullptr, 1, nullptr);
vk::PipelineRasterizationStateCreateInfo rasterization;
rasterization.lineWidth = 1.0;
vk::PipelineMultisampleStateCreateInfo multisample;
vk::PipelineDepthStencilStateCreateInfo depthStencil;
vk::PipelineColorBlendAttachmentState blendAttachment(false, vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
vk::PipelineColorBlendStateCreateInfo blend(vk::PipelineColorBlendStateCreateFlags(), false, vk::LogicOp::eNoOp, 1, &blendAttachment);
vk::DynamicState dynamicStates[] = { vk::DynamicState::eViewport, vk::DynamicState::eScissor };
vk::PipelineDynamicStateCreateInfo dynamic(vk::PipelineDynamicStateCreateFlags(), ARRAY_SIZE(dynamicStates), dynamicStates);
vk::PipelineShaderStageCreateInfo stages[] = {
{ vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex, *vertexShader, "main" },
{ vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment, *fragmentShader, "main", &specialization.info },
};
vk::GraphicsPipelineCreateInfo info(vk::PipelineCreateFlags(), 2, stages, &vertexInput, &inputAssembly, nullptr, &viewportState,
&rasterization, &multisample, &depthStencil, &blend, &dynamic, *pipelineLayout, renderPass, subpass);
if (!orThrow)
return PsoCreate(device, cache, info);
return (VkPipeline)device.createGraphicsPipelineUnique(cache, info).release();
}
void SsaaResolve::Draw(vk::CommandBuffer cmd, vk::Extent2D extent) const
{
VkPipeline pipeline = PsoGet(slot);
// (the pipeline thread could not make it: made here, or thrown for as Init() did)
if (pipeline == VK_NULL_HANDLE)
pipeline = PsoOwnMade(slot, Create(VulkanContext::Instance()->GetPipelineCache(), true));
cmd.setViewport(0, vk::Viewport(0.f, 0.f, (float)extent.width, (float)extent.height, 0.f, 1.f));
cmd.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), extent));
cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, vk::Pipeline(pipeline));
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipelineLayout, 0, 1, &descSet.get(), 0, nullptr);
cmd.draw(3, 1, 0, 0);
}
void SsaaResolve::SetSamplesView(vk::ImageView samplesView, CommandPool *commandPool)
{
VulkanContext *context = VulkanContext::Instance();
if (descSet)
commandPool->DeferFree(std::move(descSet), context->GetDescriptorPool());
descSet = vk_alloc_desc_set(context->GetDevice(), context->GetDescriptorPool(), setLayout.get());
vk::DescriptorImageInfo imageInfo(vk::Sampler(), samplesView, vk::ImageLayout::eShaderReadOnlyOptimal);
vk::WriteDescriptorSet write(*descSet, 0, 0, 1, vk::DescriptorType::eInputAttachment, &imageInfo, nullptr, nullptr);
context->GetDevice().updateDescriptorSets(write, nullptr);
}