Split each renderpass/framebuffer into multiple "compatibility classes" (RenderPassType).

This commit is contained in:
Henrik Rydgård 2022-09-06 13:30:18 +02:00
parent ad12aced6c
commit e828df9f25
19 changed files with 352 additions and 375 deletions

View File

@ -127,6 +127,15 @@ public:
}
}
template<class T>
inline void IterateMut(T func) {
for (size_t i = 0; i < map.size(); i++) {
if (state[i] == BucketState::TAKEN) {
func(map[i].key, map[i].value);
}
}
}
void Clear() {
memset(state.data(), (int)BucketState::FREE, state.size());
count_ = 0;

View File

@ -29,11 +29,12 @@ static void MergeRenderAreaRectInto(VkRect2D *dest, VkRect2D &src) {
void VulkanQueueRunner::CreateDeviceObjects() {
INFO_LOG(G3D, "VulkanQueueRunner::CreateDeviceObjects");
InitBackbufferRenderPass();
RPKey key{ VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR,
VKRRenderPassStoreAction::STORE, VKRRenderPassStoreAction::DONT_CARE, VKRRenderPassStoreAction::DONT_CARE };
framebufferRenderPass_ = GetRenderPass(key);
RPKey key{
VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR,
VKRRenderPassStoreAction::STORE, VKRRenderPassStoreAction::DONT_CARE, VKRRenderPassStoreAction::DONT_CARE,
};
compatibleRenderPass_ = GetRenderPass(key);
#if 0
// Just to check whether it makes sense to split some of these. drawidx is way bigger than the others...
@ -117,78 +118,14 @@ void VulkanQueueRunner::DestroyDeviceObjects() {
}
readbackBufferSize_ = 0;
renderPasses_.Iterate([&](const RPKey &rpkey, VkRenderPass rp) {
_assert_(rp != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteRenderPass(rp);
renderPasses_.IterateMut([&](const RPKey &rpkey, VKRRenderPass *rp) {
_assert_(rp);
for (int i = 0; i < RP_TYPE_COUNT; i++) {
vulkan_->Delete().QueueDeleteRenderPass(rp->pass[i]);
}
delete rp;
});
renderPasses_.Clear();
_assert_(backbufferRenderPass_ != VK_NULL_HANDLE);
vulkan_->Delete().QueueDeleteRenderPass(backbufferRenderPass_);
backbufferRenderPass_ = VK_NULL_HANDLE;
}
void VulkanQueueRunner::InitBackbufferRenderPass() {
VkAttachmentDescription attachments[2];
attachments[0].format = vulkan_->GetSwapchainFormat();
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // We don't want to preserve the backbuffer between frames so we really don't care.
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // We only render once to the backbuffer per frame so we can do this here.
attachments[0].flags = 0;
attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; // must use this same format later for the back depth buffer.
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // Don't care about storing backbuffer Z - we clear it anyway.
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
attachments[1].flags = 0;
VkAttachmentReference color_reference{};
color_reference.attachment = 0;
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkAttachmentReference depth_reference{};
depth_reference.attachment = 1;
depth_reference.layout = attachments[1].finalLayout;
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.flags = 0;
subpass.inputAttachmentCount = 0;
subpass.pInputAttachments = nullptr;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_reference;
subpass.pResolveAttachments = nullptr;
subpass.pDepthStencilAttachment = &depth_reference;
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
// For the built-in layout transitions.
VkSubpassDependency dep{};
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
dep.dstSubpass = 0;
dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dep.srcAccessMask = 0;
dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo rp_info{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
rp_info.attachmentCount = 2;
rp_info.pAttachments = attachments;
rp_info.subpassCount = 1;
rp_info.pSubpasses = &subpass;
rp_info.dependencyCount = 1;
rp_info.pDependencies = &dep;
VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp_info, nullptr, &backbufferRenderPass_);
_assert_(res == VK_SUCCESS);
}
static VkAttachmentLoadOp ConvertLoadAction(VKRRenderPassLoadAction action) {
@ -208,21 +145,16 @@ static VkAttachmentStoreOp ConvertStoreAction(VKRRenderPassStoreAction action) {
return VK_ATTACHMENT_STORE_OP_DONT_CARE; // avoid compiler warning
}
VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) {
auto pass = renderPasses_.Get(key);
if (pass) {
return pass;
}
VkRenderPass VulkanQueueRunner::CreateRP(const RPKey &key, RenderPassType rpType) {
VkAttachmentDescription attachments[2] = {};
attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM;
attachments[0].format = rpType == RP_TYPE_BACKBUFFER ? vulkan_->GetSwapchainFormat() : VK_FORMAT_R8G8B8A8_UNORM;
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
attachments[0].loadOp = ConvertLoadAction(key.colorLoadAction);
attachments[0].storeOp = ConvertStoreAction(key.colorStoreAction);
attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].initialLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].finalLayout = rpType == RP_TYPE_BACKBUFFER ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachments[0].flags = 0;
attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat;
@ -255,15 +187,48 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(const RPKey &key) {
subpass.preserveAttachmentCount = 0;
subpass.pPreserveAttachments = nullptr;
// Not sure if this is really necessary.
VkSubpassDependency dep{};
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
dep.dstSubpass = 0;
dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dep.srcAccessMask = 0;
dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo rp{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
rp.attachmentCount = 2;
rp.pAttachments = attachments;
rp.subpassCount = 1;
rp.pSubpasses = &subpass;
if (rpType == RP_TYPE_BACKBUFFER) {
rp.dependencyCount = 1;
rp.pDependencies = &dep;
}
VkRenderPass pass;
VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &pass);
_assert_(res == VK_SUCCESS);
_assert_(pass != VK_NULL_HANDLE);
return pass;
}
// Self-dependency: https://github.com/gpuweb/gpuweb/issues/442#issuecomment-547604827
// Also see https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-pipeline-barriers-subpass-self-dependencies
VKRRenderPass *VulkanQueueRunner::GetRenderPass(const RPKey &key) {
auto foundPass = renderPasses_.Get(key);
if (foundPass) {
return foundPass;
}
VKRRenderPass *pass = new VKRRenderPass();
// When we create a render pass, we create all "types" of it immediately,
// practical later when referring to it. Could change to on-demand if it feels motivated
// but I think the render pass objects are cheap.
for (int i = 0; i < RP_TYPE_COUNT; i++) {
pass->pass[i] = CreateRP(key, (RenderPassType)i);
}
renderPasses_.Insert(key, pass);
return pass;
}
@ -272,7 +237,8 @@ void VulkanQueueRunner::PreprocessSteps(std::vector<VKRStep *> &steps) {
// Optimizes renderpasses, then sequences them.
// Planned optimizations:
// * Create copies of render target that are rendered to multiple times and textured from in sequence, and push those render passes
// as early as possible in the frame (Wipeout billboards).
// as early as possible in the frame (Wipeout billboards). This will require taking over more of descriptor management so we can
// substitute descriptors, alternatively using texture array layers creatively.
for (int j = 0; j < (int)steps.size(); j++) {
if (steps[j]->stepType == VKRStepType::RENDER &&
@ -1159,6 +1125,8 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
int lastStencilCompareMask = -1;
int lastStencilReference = -1;
const RenderPassType rpType = step.render.renderPassType;
for (const auto &c : commands) {
switch (c.cmd) {
case VKRRenderCommand::REMOVED:
@ -1181,7 +1149,8 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
{
VKRGraphicsPipeline *graphicsPipeline = c.graphics_pipeline.pipeline;
if (graphicsPipeline != lastGraphicsPipeline) {
VkPipeline pipeline = graphicsPipeline->pipeline->BlockUntilReady();
_dbg_assert_(graphicsPipeline->pipeline[rpType]);
VkPipeline pipeline = graphicsPipeline->pipeline[rpType]->BlockUntilReady();
if (pipeline != VK_NULL_HANDLE) {
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
pipelineLayout = c.pipeline.pipelineLayout;
@ -1338,7 +1307,7 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c
}
void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step, VkCommandBuffer cmd) {
VkRenderPass renderPass;
VKRRenderPass *renderPass;
int numClearVals = 0;
VkClearValue clearVal[2]{};
VkFramebuffer framebuf;
@ -1349,7 +1318,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step
_dbg_assert_(step.render.finalDepthStencilLayout != VK_IMAGE_LAYOUT_UNDEFINED);
VKRFramebuffer *fb = step.render.framebuffer;
framebuf = fb->framebuf;
framebuf = fb->framebuf[step.render.renderPassType];
w = fb->width;
h = fb->height;
@ -1395,7 +1364,13 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step
// Raw, rotated backbuffer size.
w = vulkan_->GetBackbufferWidth();
h = vulkan_->GetBackbufferHeight();
renderPass = GetBackbufferRenderPass();
RPKey key{
VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR, VKRRenderPassLoadAction::CLEAR,
VKRRenderPassStoreAction::STORE, VKRRenderPassStoreAction::DONT_CARE, VKRRenderPassStoreAction::DONT_CARE,
};
renderPass = GetRenderPass(key);
Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor);
numClearVals = 2; // We don't bother with a depth buffer here.
clearVal[1].depthStencil.depth = 0.0f;
@ -1403,7 +1378,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step
}
VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
rp_begin.renderPass = renderPass;
rp_begin.renderPass = renderPass->pass[(int)step.render.renderPassType];
rp_begin.framebuffer = framebuf;
VkRect2D rc = step.render.renderArea;

View File

@ -47,6 +47,14 @@ enum PipelineFlags {
PIPELINE_FLAG_USES_DEPTH_STENCIL = (1 << 4), // Reads or writes the depth buffer.
};
// Pipelines need to be created for the right type of render pass.
enum RenderPassType {
RP_TYPE_BACKBUFFER,
RP_TYPE_COLOR_DEPTH,
// Later will add pure-color render passes.
RP_TYPE_COUNT,
};
struct VkRenderData {
VKRRenderCommand cmd;
union {
@ -147,6 +155,8 @@ struct QueueProfileContext {
double cpuEndTime;
};
struct VKRRenderPass;
struct VKRStep {
VKRStep(VKRStepType _type) : stepType(_type) {}
~VKRStep() {}
@ -159,6 +169,7 @@ struct VKRStep {
union {
struct {
VKRFramebuffer *framebuffer;
// TODO: Look these up through renderPass?
VKRRenderPassLoadAction colorLoad;
VKRRenderPassLoadAction depthLoad;
VKRRenderPassLoadAction stencilLoad;
@ -175,6 +186,9 @@ struct VKRStep {
VkImageLayout finalDepthStencilLayout;
u32 pipelineFlags;
VkRect2D renderArea;
// Render pass type. Deduced after finishing recording the pass, from the used pipelines.
// NOTE: Storing the render pass here doesn't do much good, we change the compatible parameters (load/store ops) during step optimization.
RenderPassType renderPassType;
} render;
struct {
VKRFramebuffer *src;
@ -204,9 +218,24 @@ struct VKRStep {
};
};
struct VKRRenderPass {
VkRenderPass pass[RP_TYPE_COUNT];
};
struct RPKey {
// Only render-pass-compatibility-volatile things can be here.
VKRRenderPassLoadAction colorLoadAction;
VKRRenderPassLoadAction depthLoadAction;
VKRRenderPassLoadAction stencilLoadAction;
VKRRenderPassStoreAction colorStoreAction;
VKRRenderPassStoreAction depthStoreAction;
VKRRenderPassStoreAction stencilStoreAction;
};
class VulkanQueueRunner {
public:
VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan), renderPasses_(16) {}
void SetBackbuffer(VkFramebuffer fb, VkImage img) {
backbuffer_ = fb;
backbufferImage_ = img;
@ -221,14 +250,10 @@ public:
void CreateDeviceObjects();
void DestroyDeviceObjects();
VkRenderPass GetBackbufferRenderPass() const {
return backbufferRenderPass_;
}
// Get a render pass that's compatible with all our framebuffers.
// Note that it's precached, cannot look up in the map as this might be on another thread.
VkRenderPass GetFramebufferRenderPass() const {
return framebufferRenderPass_;
VKRRenderPass *GetCompatibleRenderPass() const {
return compatibleRenderPass_;
}
inline int RPIndex(VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth) {
@ -237,20 +262,11 @@ public:
void CopyReadbackBuffer(int width, int height, Draw::DataFormat srcFormat, Draw::DataFormat destFormat, int pixelStride, uint8_t *pixels);
struct RPKey {
VKRRenderPassLoadAction colorLoadAction;
VKRRenderPassLoadAction depthLoadAction;
VKRRenderPassLoadAction stencilLoadAction;
VKRRenderPassStoreAction colorStoreAction;
VKRRenderPassStoreAction depthStoreAction;
VKRRenderPassStoreAction stencilStoreAction;
};
VKRRenderPass *GetRenderPass(const RPKey &key);
VkRenderPass GetRenderPass(const RPKey &key);
bool GetRenderPassKey(VkRenderPass passToFind, RPKey *outKey) const {
bool GetRenderPassKey(VKRRenderPass *passToFind, RPKey *outKey) const {
bool found = false;
renderPasses_.Iterate([passToFind, &found, outKey](const RPKey &rpkey, VkRenderPass pass) {
renderPasses_.Iterate([passToFind, &found, outKey](const RPKey &rpkey, const VKRRenderPass *pass) {
if (pass == passToFind) {
found = true;
*outKey = rpkey;
@ -273,7 +289,7 @@ public:
}
private:
void InitBackbufferRenderPass();
VkRenderPass CreateRP(const RPKey &key, RenderPassType rpType);
void PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd);
void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd);
@ -302,14 +318,12 @@ private:
VkFramebuffer backbuffer_ = VK_NULL_HANDLE;
VkImage backbufferImage_ = VK_NULL_HANDLE;
VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE;
// The "Compatible" render pass. Used when creating pipelines that render to "normal" framebuffers.
VkRenderPass framebufferRenderPass_ = VK_NULL_HANDLE;
// The "Compatible" render pass. Should be able to get rid of this soon.
VKRRenderPass *compatibleRenderPass_ = nullptr;
// Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents.
// TODO: Create these on demand.
DenseHashMap<RPKey, VkRenderPass, (VkRenderPass)VK_NULL_HANDLE> renderPasses_;
// Each VKRRenderPass contains all compatibility classes (which attachments they have, etc).
DenseHashMap<RPKey, VKRRenderPass *, nullptr> renderPasses_;
// Readback buffer. Currently we only support synchronous readback, so we only really need one.
// We size it generously.

View File

@ -25,7 +25,8 @@
using namespace PPSSPP_VK;
bool VKRGraphicsPipeline::Create(VulkanContext *vulkan) {
// renderPass is an example of the "compatibility class" or RenderPassType type.
bool VKRGraphicsPipeline::Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType) {
if (!desc) {
// Already failed to create this one.
return false;
@ -55,12 +56,30 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan) {
ss[1].pName = "main";
ss[1].flags = 0;
desc->pipe.pStages = ss;
desc->pipe.stageCount = 2;
VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
pipe.pStages = ss;
pipe.stageCount = 2;
pipe.renderPass = compatibleRenderPass;
pipe.basePipelineIndex = 0;
pipe.pColorBlendState = &desc->cbs;
pipe.pDepthStencilState = &desc->dss;
pipe.pRasterizationState = &desc->rs;
// We will use dynamic viewport state.
pipe.pVertexInputState = &desc->vis;
pipe.pViewportState = &desc->views;
pipe.pTessellationState = nullptr;
pipe.pDynamicState = &desc->ds;
pipe.pInputAssemblyState = &desc->inputAssembly;
pipe.pMultisampleState = &desc->ms;
pipe.layout = desc->pipelineLayout;
pipe.basePipelineHandle = VK_NULL_HANDLE;
pipe.basePipelineIndex = 0;
pipe.subpass = 0;
double start = time_now_d();
VkPipeline vkpipeline;
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &desc->pipe, nullptr, &vkpipeline);
VkResult result = vkCreateGraphicsPipelines(vulkan->GetDevice(), desc->pipelineCache, 1, &pipe, nullptr, &vkpipeline);
INFO_LOG(G3D, "Pipeline creation time: %0.2f ms", (time_now_d() - start) * 1000.0);
@ -70,14 +89,14 @@ bool VKRGraphicsPipeline::Create(VulkanContext *vulkan) {
// Would really like to log more here, we could probably attach more info to desc.
//
// At least create a null placeholder to avoid creating over and over if something is broken.
pipeline->Post(VK_NULL_HANDLE);
pipeline[rpType]->Post(VK_NULL_HANDLE);
success = false;
} else if (result != VK_SUCCESS) {
pipeline->Post(VK_NULL_HANDLE);
pipeline[rpType]->Post(VK_NULL_HANDLE);
ERROR_LOG(G3D, "Failed creating graphics pipeline! result='%s'", VulkanResultToString(result));
success = false;
} else {
pipeline->Post(vkpipeline);
pipeline[rpType]->Post(vkpipeline);
}
// Having the desc stick around can be useful for debugging.
@ -110,33 +129,35 @@ bool VKRComputePipeline::Create(VulkanContext *vulkan) {
return success;
}
VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VkRenderPass renderPass, int _width, int _height, const char *tag) : vulkan_(vk) {
VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag) : vulkan_(vk) {
width = _width;
height = _height;
CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag);
CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag);
VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
VkImageView views[2]{};
for (int i = 0; i < RP_TYPE_COUNT; i++) {
VkFramebufferCreateInfo fbci{ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
VkImageView views[2]{};
fbci.renderPass = renderPass;
fbci.attachmentCount = 2;
fbci.pAttachments = views;
views[0] = color.imageView;
views[1] = depth.imageView;
fbci.width = width;
fbci.height = height;
fbci.layers = 1;
fbci.renderPass = compatibleRenderPass->pass[i];
fbci.attachmentCount = 2;
fbci.pAttachments = views;
views[0] = color.imageView;
views[1] = depth.imageView;
fbci.width = width;
fbci.height = height;
fbci.layers = 1;
VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf);
_assert_(res == VK_SUCCESS);
VkResult res = vkCreateFramebuffer(vulkan_->GetDevice(), &fbci, nullptr, &framebuf[i]);
_assert_(res == VK_SUCCESS);
if (tag && vk->Extensions().EXT_debug_utils) {
vk->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag).c_str());
vk->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag).c_str());
vk->SetDebugName(framebuf, VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag).c_str());
this->tag = tag;
if (tag && vk->Extensions().EXT_debug_utils) {
vk->SetDebugName(color.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_color_%s", tag).c_str());
vk->SetDebugName(depth.image, VK_OBJECT_TYPE_IMAGE, StringFromFormat("fb_depth_%s", tag).c_str());
vk->SetDebugName(framebuf[i], VK_OBJECT_TYPE_FRAMEBUFFER, StringFromFormat("fb_%s", tag).c_str());
this->tag = tag;
}
}
}
@ -155,8 +176,10 @@ VKRFramebuffer::~VKRFramebuffer() {
}
if (depth.depthSampleView)
vulkan_->Delete().QueueDeleteImageView(depth.depthSampleView);
if (framebuf)
vulkan_->Delete().QueueDeleteFramebuffer(framebuf);
for (auto &fb : framebuf) {
if (fb)
vulkan_->Delete().QueueDeleteFramebuffer(fb);
}
}
void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int width, int height, VkFormat format, VkImageLayout initialLayout, bool color, const char *tag) {
@ -493,7 +516,7 @@ void VulkanRenderManager::CompileThreadFunc() {
for (auto &entry : toCompile) {
switch (entry.type) {
case CompileQueueEntry::Type::GRAPHICS:
entry.graphics->Create(vulkan_);
entry.graphics->Create(vulkan_, entry.compatibleRenderPass, entry.renderPassType);
break;
case CompileQueueEntry::Type::COMPUTE:
entry.compute->Create(vulkan_);
@ -664,23 +687,45 @@ VkCommandBuffer VulkanRenderManager::GetInitCmd() {
}
void VulkanRenderManager::EndCurRenderStep() {
if (!curRenderStep_)
return;
RPKey key{
curRenderStep_->render.colorLoad, curRenderStep_->render.depthLoad, curRenderStep_->render.stencilLoad,
curRenderStep_->render.colorStore, curRenderStep_->render.depthStore, curRenderStep_->render.stencilStore,
};
RenderPassType rpType = RP_TYPE_COLOR_DEPTH;
curRenderStep_->render.pipelineFlags = curPipelineFlags_;
if (!curRenderStep_->render.framebuffer) {
rpType = RP_TYPE_BACKBUFFER;
}
VKRRenderPass *renderPass = queueRunner_.GetRenderPass(key);
curRenderStep_->render.renderPassType = rpType;
// Save the accumulated pipeline flags so we can use that to configure the render pass.
// We'll often be able to avoid loading/saving the depth/stencil buffer.
if (curRenderStep_) {
curRenderStep_->render.pipelineFlags = curPipelineFlags_;
// We don't do this optimization for very small targets, probably not worth it.
if (!curRenderArea_.Empty() && (curWidth_ > 32 && curHeight_ > 32)) {
curRenderStep_->render.renderArea = curRenderArea_.ToVkRect2D();
} else {
curRenderStep_->render.renderArea.offset = {};
curRenderStep_->render.renderArea.extent = { (uint32_t)curWidth_, (uint32_t)curHeight_ };
}
curRenderArea_.Reset();
// We no longer have a current render step.
curRenderStep_ = nullptr;
curPipelineFlags_ = 0;
compileMutex_.lock();
for (VKRGraphicsPipeline *pipeline : pipelinesToCreate_) {
pipeline->pipeline[rpType] = Promise<VkPipeline>::CreateEmpty();
compileQueue_.push_back(CompileQueueEntry(pipeline, renderPass->pass[rpType], rpType));
}
compileCond_.notify_one();
compileMutex_.unlock();
pipelinesToCreate_.clear();
// We don't do this optimization for very small targets, probably not worth it.
if (!curRenderArea_.Empty() && (curWidth_ > 32 && curHeight_ > 32)) {
curRenderStep_->render.renderArea = curRenderArea_.ToVkRect2D();
} else {
curRenderStep_->render.renderArea.offset = {};
curRenderStep_->render.renderArea.extent = { (uint32_t)curWidth_, (uint32_t)curHeight_ };
}
curRenderArea_.Reset();
// We no longer have a current render step.
curRenderStep_ = nullptr;
curPipelineFlags_ = 0;
}
void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRRenderPassLoadAction color, VKRRenderPassLoadAction depth, VKRRenderPassLoadAction stencil, uint32_t clearColor, float clearDepth, uint8_t clearStencil, const char *tag) {
@ -909,7 +954,7 @@ bool VulkanRenderManager::InitBackbufferFramebuffers(int width, int height) {
VLOG("InitFramebuffers: %dx%d", width, height);
VkFramebufferCreateInfo fb_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
fb_info.renderPass = queueRunner_.GetBackbufferRenderPass();
fb_info.renderPass = queueRunner_.GetCompatibleRenderPass()->pass[RP_TYPE_BACKBUFFER];
fb_info.attachmentCount = 2;
fb_info.pAttachments = attachments;
fb_info.width = width;

View File

@ -43,10 +43,10 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int
class VKRFramebuffer {
public:
VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VkRenderPass renderPass, int _width, int _height, const char *tag);
VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRenderPass *compatibleRenderPass, int _width, int _height, const char *tag);
~VKRFramebuffer();
VkFramebuffer framebuf = VK_NULL_HANDLE;
VkFramebuffer framebuf[RP_TYPE_COUNT]{};
VKRImage color{};
VKRImage depth{};
int width = 0;
@ -132,7 +132,12 @@ struct VKRGraphicsPipelineDesc {
VkVertexInputBindingDescription ibd{};
VkPipelineVertexInputStateCreateInfo vis{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
VkPipelineViewportStateCreateInfo views{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
VkGraphicsPipelineCreateInfo pipe{ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO };
VkPipelineLayout pipelineLayout;
// Does not include the render pass type, it's passed in separately since the
// desc is persistent.
RPKey rpKey;
};
// All the data needed to create a compute pipeline.
@ -144,16 +149,18 @@ struct VKRComputePipelineDesc {
// Wrapped pipeline.
struct VKRGraphicsPipeline {
VKRGraphicsPipeline() {
pipeline = Promise<VkPipeline>::CreateEmpty();
for (int i = 0; i < RP_TYPE_COUNT; i++) {
pipeline[i] = nullptr;
}
}
~VKRGraphicsPipeline() {
delete desc;
}
VKRGraphicsPipelineDesc *desc = nullptr;
Promise<VkPipeline> *pipeline;
Promise<VkPipeline> *pipeline[RP_TYPE_COUNT];
bool Create(VulkanContext *vulkan);
bool Create(VulkanContext *vulkan, VkRenderPass compatibleRenderPass, RenderPassType rpType);
};
struct VKRComputePipeline {
@ -170,13 +177,16 @@ struct VKRComputePipeline {
};
struct CompileQueueEntry {
CompileQueueEntry(VKRGraphicsPipeline *p) : type(Type::GRAPHICS), graphics(p) {}
CompileQueueEntry(VKRComputePipeline *p) : type(Type::COMPUTE), compute(p) {}
CompileQueueEntry(VKRGraphicsPipeline *p, VkRenderPass _compatibleRenderPass, RenderPassType _renderPassType)
: type(Type::GRAPHICS), graphics(p), compatibleRenderPass(_compatibleRenderPass), renderPassType(_renderPassType) {}
CompileQueueEntry(VKRComputePipeline *p) : type(Type::COMPUTE), compute(p), renderPassType(RP_TYPE_COLOR_DEPTH) {}
enum class Type {
GRAPHICS,
COMPUTE,
};
Type type;
VkRenderPass compatibleRenderPass;
RenderPassType renderPassType;
VKRGraphicsPipeline *graphics = nullptr;
VKRComputePipeline *compute = nullptr;
};
@ -228,13 +238,13 @@ public:
// Deferred creation, like in GL. Unlike GL though, the purpose is to allow background creation and avoiding
// stalling the emulation thread as much as possible.
// We delay creating pipelines until the end of the current render pass, so we can create the right type immediately.
VKRGraphicsPipeline *CreateGraphicsPipeline(VKRGraphicsPipelineDesc *desc) {
VKRGraphicsPipeline *pipeline = new VKRGraphicsPipeline();
_dbg_assert_(desc->vertexShader);
_dbg_assert_(desc->fragmentShader);
pipeline->desc = desc;
compileMutex_.lock();
compileQueue_.push_back(CompileQueueEntry(pipeline));
compileCond_.notify_one();
compileMutex_.unlock();
pipelinesToCreate_.push_back(pipeline);
return pipeline;
}
@ -248,16 +258,6 @@ public:
return pipeline;
}
void BindPipeline(VkPipeline pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != VK_NULL_HANDLE);
VkRenderData data{ VKRRenderCommand::BIND_PIPELINE };
data.pipeline.pipeline = pipeline;
data.pipeline.pipelineLayout = pipelineLayout;
curPipelineFlags_ |= flags;
curRenderStep_->commands.push_back(data);
}
void BindPipeline(VKRGraphicsPipeline *pipeline, PipelineFlags flags, VkPipelineLayout pipelineLayout) {
_dbg_assert_(curRenderStep_ && curRenderStep_->stepType == VKRStepType::RENDER);
_dbg_assert_(pipeline != nullptr);
@ -431,20 +431,6 @@ public:
VkCommandBuffer GetInitCmd();
VkRenderPass GetBackbufferRenderPass() {
return queueRunner_.GetBackbufferRenderPass();
}
VkRenderPass GetFramebufferRenderPass() {
return queueRunner_.GetFramebufferRenderPass();
}
VkRenderPass GetCompatibleRenderPass() {
if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) {
return queueRunner_.GetFramebufferRenderPass();
} else {
return queueRunner_.GetBackbufferRenderPass();
}
}
// Gets a frame-unique ID of the current step being recorded. Can be used to figure out
// when the current step has changed, which means the caller will need to re-record its state.
int GetCurrentStepId() const {
@ -581,6 +567,9 @@ private:
std::mutex compileMutex_;
std::vector<CompileQueueEntry> compileQueue_;
// pipelines to create at the end of the current render pass.
std::vector<VKRGraphicsPipeline *> pipelinesToCreate_;
// Swap chain management
struct SwapchainImageData {
VkImage image;

View File

@ -31,6 +31,7 @@
#include "Common/GPU/Vulkan/VulkanContext.h"
#include "Common/GPU/Vulkan/VulkanImage.h"
#include "Common/GPU/Vulkan/VulkanMemory.h"
#include "Common/Thread/Promise.h"
#include "Core/Config.h"
@ -248,13 +249,12 @@ public:
class VKPipeline : public Pipeline {
public:
VKPipeline(VulkanContext *vulkan, size_t size, PipelineFlags _flags) : flags(_flags), vulkan_(vulkan) {
VKPipeline(VulkanRenderManager *rm, size_t size, PipelineFlags _flags) : rm_(rm), flags(_flags) {
uboSize_ = (int)size;
ubo_ = new uint8_t[uboSize_];
}
~VKPipeline() {
vulkan_->Delete().QueueDeletePipeline(backbufferPipeline);
vulkan_->Delete().QueueDeletePipeline(framebufferPipeline);
// TODO: Right now we just leak the pipeline! This is not good.
delete[] ubo_;
}
@ -272,17 +272,16 @@ public:
return uboSize_;
}
VkPipeline backbufferPipeline = VK_NULL_HANDLE;
VkPipeline framebufferPipeline = VK_NULL_HANDLE;
VKRGraphicsPipeline *pipeline = nullptr;
PipelineFlags flags;
int stride[4]{};
int dynamicUniformSize = 0;
bool usesStencil = false;
private:
VulkanContext *vulkan_;
VulkanRenderManager *rm_;
uint8_t *ubo_;
int uboSize_;
};
@ -429,7 +428,7 @@ public:
void DrawIndexed(int vertexCount, int offset) override;
void DrawUP(const void *vdata, int vertexCount) override;
void BindCompatiblePipeline();
void BindCurrentPipeline();
void ApplyDynamicState();
void Clear(int mask, uint32_t colorval, float depthVal, int stencilVal) override;
@ -466,13 +465,6 @@ public:
switch (obj) {
case NativeObject::CONTEXT:
return (uint64_t)vulkan_;
case NativeObject::FRAMEBUFFER_RENDERPASS:
// Return a representative renderpass.
return (uint64_t)renderManager_.GetFramebufferRenderPass();
case NativeObject::BACKBUFFER_RENDERPASS:
return (uint64_t)renderManager_.GetBackbufferRenderPass();
case NativeObject::COMPATIBLE_RENDERPASS:
return (uint64_t)renderManager_.GetCompatibleRenderPass();
case NativeObject::INIT_COMMANDBUFFER:
return (uint64_t)renderManager_.GetInitCmd();
case NativeObject::BOUND_TEXTURE0_IMAGEVIEW:
@ -1047,7 +1039,24 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) {
pipelineFlags |= PIPELINE_FLAG_USES_DEPTH_STENCIL;
}
VKPipeline *pipeline = new VKPipeline(vulkan_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), (PipelineFlags)pipelineFlags);
VKPipeline *pipeline = new VKPipeline(&renderManager_, desc.uniformDesc ? desc.uniformDesc->uniformBufferSize : 16 * sizeof(float), (PipelineFlags)pipelineFlags);
VKRGraphicsPipelineDesc gDesc{};
std::vector<VkPipelineShaderStageCreateInfo> stages;
stages.resize(desc.shaders.size());
for (auto &iter : desc.shaders) {
VKShaderModule *vkshader = (VKShaderModule *)iter;
if (vkshader->GetStage() == ShaderStage::Vertex) {
gDesc.vertexShader = Promise<VkShaderModule>::AlreadyDone(vkshader->Get());
} else if (vkshader->GetStage() == ShaderStage::Fragment) {
gDesc.fragmentShader = Promise<VkShaderModule>::AlreadyDone(vkshader->Get());
} else {
ERROR_LOG(G3D, "Bad stage");
return nullptr;
}
}
if (input) {
for (int i = 0; i < (int)input->bindings.size(); i++) {
@ -1056,84 +1065,50 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) {
} else {
pipeline->stride[0] = 0;
}
_dbg_assert_(input->bindings.size() == 1);
_dbg_assert_((int)input->attributes.size() == (int)input->visc.vertexAttributeDescriptionCount);
std::vector<VkPipelineShaderStageCreateInfo> stages;
stages.resize(desc.shaders.size());
int i = 0;
for (auto &iter : desc.shaders) {
VKShaderModule *vkshader = (VKShaderModule *)iter;
if (!vkshader) {
ERROR_LOG(G3D, "CreateGraphicsPipeline got passed a null shader");
return nullptr;
}
VkPipelineShaderStageCreateInfo &stage = stages[i++];
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
stage.pNext = nullptr;
stage.pSpecializationInfo = nullptr;
stage.stage = StageToVulkan(vkshader->GetStage());
stage.module = vkshader->Get();
stage.pName = "main";
stage.flags = 0;
gDesc.ibd = input->bindings[0];
for (int i = 0; i < input->attributes.size(); i++) {
gDesc.attrs[i] = input->attributes[i];
}
gDesc.vis.vertexAttributeDescriptionCount = input->visc.vertexAttributeDescriptionCount;
gDesc.vis.vertexBindingDescriptionCount = input->visc.vertexBindingDescriptionCount;
gDesc.vis.pVertexBindingDescriptions = &gDesc.ibd;
gDesc.vis.pVertexAttributeDescriptions = gDesc.attrs;
VkPipelineInputAssemblyStateCreateInfo inputAssembly = { VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO };
inputAssembly.topology = primToVK[(int)desc.prim];
inputAssembly.primitiveRestartEnable = false;
gDesc.blend0 = blend->attachments[0];
gDesc.cbs = blend->info;
gDesc.dss = depth->info;
raster->ToVulkan(&gDesc.rs);
// Copy bindings from input layout.
gDesc.inputAssembly.topology = primToVK[(int)desc.prim];
// We treat the three stencil states as a unit in other places, so let's do that here too.
VkDynamicState dynamics[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_STENCIL_WRITE_MASK };
VkPipelineDynamicStateCreateInfo dynamicInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
dynamicInfo.dynamicStateCount = depth->info.stencilTestEnable ? ARRAY_SIZE(dynamics) : 2;
dynamicInfo.pDynamicStates = dynamics;
const VkDynamicState dynamics[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE, VK_DYNAMIC_STATE_STENCIL_WRITE_MASK };
gDesc.ds.dynamicStateCount = depth->info.stencilTestEnable ? ARRAY_SIZE(dynamics) : 2;
for (int i = 0; i < gDesc.ds.dynamicStateCount; i++) {
gDesc.dynamicStates[i] = dynamics[i];
}
gDesc.ds.pDynamicStates = gDesc.dynamicStates;
VkPipelineMultisampleStateCreateInfo ms{ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
ms.pSampleMask = nullptr;
ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
gDesc.ms.pSampleMask = nullptr;
gDesc.ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineViewportStateCreateInfo vs{ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
vs.viewportCount = 1;
vs.scissorCount = 1;
vs.pViewports = nullptr; // dynamic
vs.pScissors = nullptr; // dynamic
gDesc.views.viewportCount = 1;
gDesc.views.scissorCount = 1;
gDesc.views.pViewports = nullptr; // dynamic
gDesc.views.pScissors = nullptr; // dynamic
gDesc.pipelineLayout = pipelineLayout_;
VkPipelineRasterizationStateCreateInfo rs{ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
raster->ToVulkan(&rs);
raster->ToVulkan(&gDesc.rs);
VkPipelineVertexInputStateCreateInfo emptyVisc{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO };
VkGraphicsPipelineCreateInfo createInfo[2]{};
for (auto &info : createInfo) {
info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
info.flags = 0;
info.stageCount = (uint32_t)stages.size();
info.pStages = stages.data();
info.pColorBlendState = &blend->info;
info.pDepthStencilState = &depth->info;
info.pDynamicState = &dynamicInfo;
info.pInputAssemblyState = &inputAssembly;
info.pTessellationState = nullptr;
info.pMultisampleState = &ms;
info.pVertexInputState = input ? &input->visc : &emptyVisc;
info.pRasterizationState = &rs;
info.pViewportState = &vs; // Must set viewport and scissor counts even if we set the actual state dynamically.
info.layout = pipelineLayout_;
info.subpass = 0;
}
createInfo[0].renderPass = renderManager_.GetBackbufferRenderPass();
createInfo[1].renderPass = renderManager_.GetFramebufferRenderPass();
// OK, need to create new pipelines.
VkPipeline pipelines[2]{};
VkResult result = vkCreateGraphicsPipelines(device_, pipelineCache_, 2, createInfo, nullptr, pipelines);
if (result != VK_SUCCESS) {
ERROR_LOG(G3D, "Failed to create graphics pipeline");
delete pipeline;
return nullptr;
}
pipeline->backbufferPipeline = pipelines[0];
pipeline->framebufferPipeline = pipelines[1];
pipeline->pipeline = renderManager_.CreateGraphicsPipeline(&gDesc);;
if (desc.uniformDesc) {
pipeline->dynamicUniformSize = (int)desc.uniformDesc->uniformBufferSize;
@ -1338,7 +1313,7 @@ void VKContext::Draw(int vertexCount, int offset) {
return;
}
BindCompatiblePipeline();
BindCurrentPipeline();
ApplyDynamicState();
renderManager_.Draw(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vertexCount, offset);
}
@ -1358,7 +1333,7 @@ void VKContext::DrawIndexed(int vertexCount, int offset) {
return;
}
BindCompatiblePipeline();
BindCurrentPipeline();
ApplyDynamicState();
renderManager_.DrawIndexed(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vulkanIbuf, (int)ibBindOffset + offset * sizeof(uint32_t), vertexCount, 1, VK_INDEX_TYPE_UINT16);
}
@ -1374,18 +1349,13 @@ void VKContext::DrawUP(const void *vdata, int vertexCount) {
return;
}
BindCompatiblePipeline();
BindCurrentPipeline();
ApplyDynamicState();
renderManager_.Draw(descSet, 1, &ubo_offset, vulkanVbuf, (int)vbBindOffset + curVBufferOffsets_[0], vertexCount);
}
void VKContext::BindCompatiblePipeline() {
VkRenderPass renderPass = renderManager_.GetCompatibleRenderPass();
if (renderPass == renderManager_.GetBackbufferRenderPass()) {
renderManager_.BindPipeline(curPipeline_->backbufferPipeline, curPipeline_->flags, pipelineLayout_);
} else {
renderManager_.BindPipeline(curPipeline_->framebufferPipeline, curPipeline_->flags, pipelineLayout_);
}
void VKContext::BindCurrentPipeline() {
renderManager_.BindPipeline(curPipeline_->pipeline, curPipeline_->flags, pipelineLayout_);
}
void VKContext::Clear(int clearMask, uint32_t colorval, float depthVal, int stencilVal) {
@ -1497,7 +1467,7 @@ Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) {
VkCommandBuffer cmd = renderManager_.GetInitCmd();
// TODO: We always create with depth here, even when it's not needed (such as color temp FBOs).
// Should optimize those away.
VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetFramebufferRenderPass(), desc.width, desc.height, desc.tag);
VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetQueueRunner()->GetCompatibleRenderPass(), desc.width, desc.height, desc.tag);
return new VKFramebuffer(vkrfb);
}

View File

@ -239,9 +239,6 @@ enum class NativeObject {
BACKBUFFER_COLOR_TEX,
BACKBUFFER_DEPTH_TEX,
FEATURE_LEVEL,
COMPATIBLE_RENDERPASS,
BACKBUFFER_RENDERPASS,
FRAMEBUFFER_RENDERPASS,
INIT_COMMANDBUFFER,
BOUND_TEXTURE0_IMAGEVIEW,
BOUND_TEXTURE1_IMAGEVIEW,

View File

@ -79,6 +79,7 @@ public:
// Returns T if the data is ready, nullptr if it's not.
T Poll() {
_dbg_assert_(this != nullptr);
std::lock_guard<std::mutex> guard(readyMutex_);
if (ready_) {
return data_;
@ -95,6 +96,7 @@ public:
}
T BlockUntilReady() {
_dbg_assert_(this != nullptr);
std::lock_guard<std::mutex> guard(readyMutex_);
if (ready_) {
return data_;

View File

@ -36,8 +36,11 @@
#define WRITE(p, ...) p.F(__VA_ARGS__)
bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, std::string *errorString) {
bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString) {
*uniformMask = 0;
if (fragmentShaderFlags) {
*fragmentShaderFlags = (FragmentShaderFlags)0;
}
errorString->clear();
bool highpFog = false;

View File

@ -39,4 +39,9 @@ struct FShaderID;
// For stencil upload
#define CONST_PS_STENCILVALUE 11
bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, std::string *errorString);
// Can technically be deduced from the fragment shader ID, but this is safer.
enum FragmentShaderFlags {
FS_FLAG_INPUT_ATTACHMENT,
};
bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLanguageDesc &compat, Draw::Bugs bugs, uint64_t *uniformMask, FragmentShaderFlags *fragmentShaderFlags, std::string *errorString);

View File

@ -224,7 +224,7 @@ void ShaderManagerD3D11::GetShaders(int prim, u32 vertType, D3D11VertexShader **
// Fragment shader not in cache. Let's compile it.
std::string genErrorString;
uint64_t uniformMask;
GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &genErrorString);
GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, nullptr, &genErrorString);
fs = new D3D11FragmentShader(device_, featureLevel_, FSID, codeBuffer_, useHWTransform);
fsCache_[FSID] = fs;
} else {

View File

@ -623,7 +623,7 @@ VSShader *ShaderManagerDX9::ApplyShader(bool useHWTransform, bool useHWTessellat
// Fragment shader not in cache. Let's compile it.
std::string errorString;
uint64_t uniformMask;
bool success = GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &errorString);
bool success = GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, nullptr, &errorString);
// We're supposed to handle all possible cases.
_assert_(success);
fs = new PSShader(device_, FSID, codeBuffer_);

View File

@ -709,7 +709,7 @@ void ShaderManagerGLES::DirtyLastShader() {
Shader *ShaderManagerGLES::CompileFragmentShader(FShaderID FSID) {
uint64_t uniformMask;
std::string errorString;
if (!GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, &errorString)) {
if (!GenerateFragmentShader(FSID, codeBuffer_, draw_->GetShaderLanguageDesc(), draw_->GetBugs(), &uniformMask, nullptr, &errorString)) {
ERROR_LOG(G3D, "Shader gen error: %s", errorString.c_str());
return nullptr;
}

View File

@ -768,9 +768,7 @@ void DrawEngineVulkan::DoFlush() {
}
_dbg_assert_msg_(vshader->UseHWTransform(), "Bad vshader");
Draw::NativeObject object = framebufferManager_->UseBufferedRendering() ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS;
VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(object);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, renderPass, pipelineKey_, &dec_->decFmt, vshader, fshader, true);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, true);
if (!pipeline || !pipeline->pipeline) {
// Already logged, let's bail out.
return;
@ -895,9 +893,7 @@ void DrawEngineVulkan::DoFlush() {
}
shaderManager_->GetShaders(prim, lastVType_, &vshader, &fshader, pipelineState_, false, false, decOptions_.expandAllWeightsToFloat); // usehwtransform
_dbg_assert_msg_(!vshader->UseHWTransform(), "Bad vshader");
Draw::NativeObject object = framebufferManager_->UseBufferedRendering() ? Draw::NativeObject::FRAMEBUFFER_RENDERPASS : Draw::NativeObject::BACKBUFFER_RENDERPASS;
VkRenderPass renderPass = (VkRenderPass)draw_->GetNativeObject(object);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, renderPass, pipelineKey_, &dec_->decFmt, vshader, fshader, false);
VulkanPipeline *pipeline = pipelineManager_->GetOrCreatePipeline(renderManager, pipelineLayout_, pipelineKey_, &dec_->decFmt, vshader, fshader, false);
if (!pipeline || !pipeline->pipeline) {
// Already logged, let's bail out.
decodedVerts_ = 0;

View File

@ -35,16 +35,20 @@ void PipelineManagerVulkan::Clear() {
// store the keys.
pipelines_.Iterate([&](const VulkanPipelineKey &key, VulkanPipeline *value) {
if (value->pipeline) {
VkPipeline pipeline = value->pipeline->pipeline->BlockUntilReady();
vulkan_->Delete().QueueDeletePipeline(pipeline);
vulkan_->Delete().QueueCallback([](void *p) {
VKRGraphicsPipeline *pipeline = (VKRGraphicsPipeline *)p;
delete pipeline;
}, value->pipeline);
} else {
// Something went wrong.
ERROR_LOG(G3D, "Null pipeline found in PipelineManagerVulkan::Clear - didn't wait for asyncs?");
for (int i = 0; i < RP_TYPE_COUNT; i++) {
if (value->pipeline) {
if (value->pipeline->pipeline[i]) {
VkPipeline pipeline = value->pipeline->pipeline[i]->BlockUntilReady();
vulkan_->Delete().QueueDeletePipeline(pipeline);
vulkan_->Delete().QueueCallback([](void *p) {
VKRGraphicsPipeline *pipeline = (VKRGraphicsPipeline *)p;
delete pipeline;
}, value->pipeline);
}
} else {
// Something went wrong.
ERROR_LOG(G3D, "Null pipeline found in PipelineManagerVulkan::Clear - didn't wait for asyncs?");
}
}
delete value;
});
@ -175,7 +179,7 @@ static std::string CutFromMain(std::string str) {
}
static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager, VkPipelineCache pipelineCache,
VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &key,
VkPipelineLayout layout, const VulkanPipelineRasterStateKey &key,
const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
VKRGraphicsPipelineDesc *desc = new VKRGraphicsPipelineDesc();
desc->pipelineCache = pipelineCache;
@ -298,27 +302,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
views.pViewports = nullptr; // dynamic
views.pScissors = nullptr; // dynamic
VkGraphicsPipelineCreateInfo &pipe = desc->pipe;
pipe.flags = 0;
pipe.stageCount = 2;
pipe.basePipelineIndex = 0;
pipe.pColorBlendState = &desc->cbs;
pipe.pDepthStencilState = &desc->dss;
pipe.pRasterizationState = &desc->rs;
// We will use dynamic viewport state.
pipe.pVertexInputState = &desc->vis;
pipe.pViewportState = &desc->views;
pipe.pTessellationState = nullptr;
pipe.pDynamicState = &desc->ds;
pipe.pInputAssemblyState = &desc->inputAssembly;
pipe.pMultisampleState = &desc->ms;
pipe.layout = layout;
pipe.basePipelineHandle = VK_NULL_HANDLE;
pipe.basePipelineIndex = 0;
pipe.renderPass = renderPass;
pipe.subpass = 0;
desc->pipelineLayout = layout;
VKRGraphicsPipeline *pipeline = renderManager->CreateGraphicsPipeline(desc);
@ -335,7 +319,7 @@ static VulkanPipeline *CreateVulkanPipeline(VulkanRenderManager *renderManager,
return vulkanPipeline;
}
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform) {
if (!pipelineCache_) {
VkPipelineCacheCreateInfo pc{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO };
VkResult res = vkCreatePipelineCache(vulkan_->GetDevice(), &pc, nullptr, &pipelineCache_);
@ -343,10 +327,8 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *
}
VulkanPipelineKey key{};
_assert_msg_(renderPass, "Can't create a pipeline with a null renderpass");
key.raster = rasterKey;
key.renderPass = renderPass;
key.useHWTransform = useHwTransform;
key.vShader = vs->GetModule();
key.fShader = fs->GetModule();
@ -357,7 +339,7 @@ VulkanPipeline *PipelineManagerVulkan::GetOrCreatePipeline(VulkanRenderManager *
return iter;
VulkanPipeline *pipeline = CreateVulkanPipeline(
renderManager, pipelineCache_, layout, renderPass,
renderManager, pipelineCache_, layout,
rasterKey, decFmt, vs, fs, useHwTransform);
pipelines_.Insert(key, pipeline);
@ -565,8 +547,6 @@ struct StoredVulkanPipelineKey {
FShaderID fShaderID;
uint32_t vtxFmtId;
bool useHWTransform;
bool backbufferPass;
VulkanQueueRunner::RPKey renderPassKey;
// For std::set. Better zero-initialize the struct properly for this to work.
bool operator < (const StoredVulkanPipelineKey &other) const {
@ -627,14 +607,6 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha
// NOTE: This is not a vtype, but a decoded vertex format.
key.vtxFmtId = pkey.vtxFmtId;
}
// Figure out what kind of renderpass this pipeline uses.
if (pkey.renderPass == queueRunner->GetBackbufferRenderPass()) {
key.backbufferPass = true;
key.renderPassKey = {};
} else {
key.backbufferPass = false;
queueRunner->GetRenderPassKey(pkey.renderPass, &key.renderPassKey);
}
keys.insert(key);
});
@ -740,18 +712,9 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha
continue;
}
VkRenderPass rp;
if (key.backbufferPass) {
rp = queueRunner->GetBackbufferRenderPass();
} else {
rp = queueRunner->GetRenderPass(key.renderPassKey);
}
DecVtxFormat fmt;
fmt.InitializeFromID(key.vtxFmtId);
VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, rp, key.raster,
key.useHWTransform ? &fmt : 0,
vs, fs, key.useHWTransform);
VulkanPipeline *pipeline = GetOrCreatePipeline(rm, layout, key.raster, key.useHWTransform ? &fmt : 0, vs, fs, key.useHWTransform);
if (!pipeline) {
pipelineCreateFailCount += 1;
}

View File

@ -34,7 +34,7 @@ class VulkanRenderManager;
struct VulkanPipelineKey {
VulkanPipelineRasterStateKey raster; // prim is included here
VkRenderPass renderPass;
VKRRenderPass *renderPass;
Promise<VkShaderModule> *vShader;
Promise<VkShaderModule> *fShader;
uint32_t vtxFmtId;
@ -71,7 +71,7 @@ public:
PipelineManagerVulkan(VulkanContext *ctx);
~PipelineManagerVulkan();
VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, VkRenderPass renderPass, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform);
VulkanPipeline *GetOrCreatePipeline(VulkanRenderManager *renderManager, VkPipelineLayout layout, const VulkanPipelineRasterStateKey &rasterKey, const DecVtxFormat *decFmt, VulkanVertexShader *vs, VulkanFragmentShader *fs, bool useHwTransform);
int GetNumPipelines() const { return (int)pipelines_.size(); }
void Clear();

View File

@ -89,8 +89,8 @@ Promise<VkShaderModule> *CompileShaderModuleAsync(VulkanContext *vulkan, VkShade
}
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code)
: vulkan_(vulkan), id_(id) {
VulkanFragmentShader::VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code)
: vulkan_(vulkan), id_(id), flags_(flags) {
source_ = code;
module_ = CompileShaderModuleAsync(vulkan, VK_SHADER_STAGE_FRAGMENT_BIT, source_.c_str());
if (!module_) {
@ -276,9 +276,10 @@ void ShaderManagerVulkan::GetShaders(int prim, u32 vertType, VulkanVertexShader
// Fragment shader not in cache. Let's compile it.
std::string genErrorString;
uint64_t uniformMask = 0; // Not used
bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &genErrorString);
FragmentShaderFlags flags;
bool success = GenerateFragmentShader(FSID, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString);
_assert_msg_(success, "FS gen error: %s", genErrorString.c_str());
fs = new VulkanFragmentShader(vulkan, FSID, codeBuffer_);
fs = new VulkanFragmentShader(vulkan, FSID, flags, codeBuffer_);
fsCache_.Insert(FSID, fs);
}
@ -370,7 +371,7 @@ VulkanFragmentShader *ShaderManagerVulkan::GetFragmentShaderFromModule(VkShaderM
// instantaneous.
#define CACHE_HEADER_MAGIC 0xff51f420
#define CACHE_VERSION 22
#define CACHE_VERSION 24
struct VulkanCacheHeader {
uint32_t magic;
uint32_t version;
@ -390,6 +391,10 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
if (header.featureFlags != gstate_c.featureFlags)
return false;
// Temporarily disable shader cache.
if (true)
return false;
VulkanContext *vulkan = (VulkanContext *)draw_->GetNativeObject(Draw::NativeObject::CONTEXT);
for (int i = 0; i < header.numVertexShaders; i++) {
VShaderID id;
@ -417,10 +422,11 @@ bool ShaderManagerVulkan::LoadCache(FILE *f) {
}
std::string genErrorString;
uint64_t uniformMask = 0;
if (!GenerateFragmentShader(id, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &genErrorString)) {
FragmentShaderFlags flags;
if (!GenerateFragmentShader(id, codeBuffer_, compat_, draw_->GetBugs(), &uniformMask, &flags, &genErrorString)) {
return false;
}
VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan, id, codeBuffer_);
VulkanFragmentShader *fs = new VulkanFragmentShader(vulkan, id, flags, codeBuffer_);
fsCache_.Insert(id, fs);
}

View File

@ -36,7 +36,7 @@ class VulkanPushBuffer;
class VulkanFragmentShader {
public:
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, const char *code);
VulkanFragmentShader(VulkanContext *vulkan, FShaderID id, FragmentShaderFlags flags, const char *code);
~VulkanFragmentShader();
const std::string &source() const { return source_; }
@ -45,7 +45,9 @@ public:
std::string GetShaderString(DebugShaderStringType type) const;
Promise<VkShaderModule> *GetModule() { return module_; }
const FShaderID &GetID() { return id_; }
const FShaderID &GetID() const { return id_; }
FragmentShaderFlags Flags() const { return flags_; }
protected:
Promise<VkShaderModule> *module_ = nullptr;
@ -54,6 +56,7 @@ protected:
std::string source_;
bool failed_ = false;
FShaderID id_;
FragmentShaderFlags flags_;
};
class VulkanVertexShader {
@ -68,7 +71,7 @@ public:
std::string GetShaderString(DebugShaderStringType type) const;
Promise<VkShaderModule> *GetModule() { return module_; }
const VShaderID &GetID() { return id_; }
const VShaderID &GetID() const { return id_; }
protected:
Promise<VkShaderModule> *module_ = nullptr;

View File

@ -30,27 +30,27 @@ bool GenerateFShader(FShaderID id, char *buffer, ShaderLanguage lang, Draw::Bugs
case ShaderLanguage::GLSL_VULKAN:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_VULKAN);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, errorString);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString);
}
case ShaderLanguage::GLSL_1xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, errorString);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString);
}
case ShaderLanguage::GLSL_3xx:
{
ShaderLanguageDesc compat(ShaderLanguage::GLSL_1xx);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, errorString);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString);
}
case ShaderLanguage::HLSL_D3D9:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D9);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, errorString);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString);
}
case ShaderLanguage::HLSL_D3D11:
{
ShaderLanguageDesc compat(ShaderLanguage::HLSL_D3D11);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, errorString);
return GenerateFragmentShader(id, buffer, compat, bugs, &uniformMask, nullptr, errorString);
}
default:
return false;