mirror of
https://github.com/libretro/parallel-gx1.git
synced 2026-11-11 03:11:28 -10:00
Common/ObjectPool.c (C89) keeps objects of one class in chunks, the first made at backend start: a freed object is pushed on a lock-free stack, an allocation takes the stack whole (an exchange, so no ABA) and pushes the rest back, or else takes the chunks' next slot. A pool destroyed with objects still out is freed with the last of them. Classes take it with OBJECT_POOL_MEMBERS / OBJECT_POOL_DEFINE: VKPipeline, VKShader, VKTexture, VKFramebuffer and the vertex loaders, which are now their own bucket list nodes. Vulkan's descriptor pool and cleanup lists get their first room at boot. Play allocations: 58 to 1 on the core's thread, 172 to 1 on the compile workers (both a first use).
865 lines
35 KiB
C++
865 lines
35 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "VideoBackends/Vulkan/VKPipeline.h"
|
|
|
|
#include <array>
|
|
|
|
#include <cstring>
|
|
|
|
#include <retro_atomic.h>
|
|
|
|
#include "Common/ObjectPool.h"
|
|
#include "Common/Assert.h"
|
|
#include "Common/EnumMap.h"
|
|
#include "Common/MsgHandler.h"
|
|
|
|
#include "VideoBackends/Vulkan/ObjectCache.h"
|
|
#include "VideoBackends/Vulkan/VKShader.h"
|
|
#include "VideoBackends/Vulkan/VKTexture.h"
|
|
#include "VideoBackends/Vulkan/VKVertexFormat.h"
|
|
#include "VideoBackends/Vulkan/VulkanContext.h"
|
|
#include "VideoCommon/StutterStats.h"
|
|
|
|
namespace Vulkan
|
|
{
|
|
/* Pipelines that differ only in what is set per draw are one VkPipeline,
|
|
* found in an open-addressed table every thread building pipelines reads
|
|
* and adds to without a lock. A slot goes from free to building to ready,
|
|
* and to dead when the last pipeline using it goes: it is never free
|
|
* again until the backend comes up again, so a search ends at the first
|
|
* free slot. Its key is written once, before it is ready. */
|
|
#define SHARED_SLOTS 16384u
|
|
#define SHARED_PROBES 64u
|
|
|
|
enum
|
|
{
|
|
SHARED_FREE = 0,
|
|
SHARED_BUILDING,
|
|
SHARED_READY,
|
|
SHARED_DEAD
|
|
};
|
|
|
|
struct VKSharedKey
|
|
{
|
|
const NativeVertexFormat* vertex_format;
|
|
u32 vertex_shader;
|
|
u32 geometry_shader;
|
|
u32 pixel_shader;
|
|
u32 rasterization;
|
|
u32 blending;
|
|
u32 framebuffer;
|
|
u32 usage;
|
|
};
|
|
|
|
struct VKSharedPipeline
|
|
{
|
|
VkPipeline pipeline;
|
|
VKSharedKey key;
|
|
u32 hash;
|
|
retro_atomic_int_t state;
|
|
retro_atomic_int_t refs;
|
|
};
|
|
|
|
static VKSharedPipeline s_shared[SHARED_SLOTS];
|
|
|
|
static u32 ShaderUid(const AbstractShader* shader)
|
|
{
|
|
return shader ? static_cast<const VKShader*>(shader)->GetUid() : 0u;
|
|
}
|
|
|
|
static u32 HashMix(u32 h, u32 v)
|
|
{
|
|
h ^= v;
|
|
h *= 0x01000193u;
|
|
return h ^ (h >> 15);
|
|
}
|
|
|
|
static u32 HashSharedKey(const VKSharedKey* key)
|
|
{
|
|
const uintptr_t vf = reinterpret_cast<uintptr_t>(key->vertex_format);
|
|
u32 h = 0x811c9dc5u;
|
|
h = HashMix(h, static_cast<u32>(vf));
|
|
h = HashMix(h, static_cast<u32>(static_cast<u64>(vf) >> 32));
|
|
h = HashMix(h, key->vertex_shader);
|
|
h = HashMix(h, key->geometry_shader);
|
|
h = HashMix(h, key->pixel_shader);
|
|
h = HashMix(h, key->rasterization);
|
|
h = HashMix(h, key->blending);
|
|
h = HashMix(h, key->framebuffer);
|
|
return HashMix(h, key->usage);
|
|
}
|
|
|
|
static bool SharedKeyEqual(const VKSharedKey* a, const VKSharedKey* b)
|
|
{
|
|
return a->vertex_format == b->vertex_format && a->vertex_shader == b->vertex_shader &&
|
|
a->geometry_shader == b->geometry_shader && a->pixel_shader == b->pixel_shader &&
|
|
a->rasterization == b->rasterization && a->blending == b->blending &&
|
|
a->framebuffer == b->framebuffer && a->usage == b->usage;
|
|
}
|
|
|
|
/* A ready slot with the key, its count raised: one whose last user is
|
|
* going is passed over, as its count is never raised from zero. */
|
|
static VKSharedPipeline* SharedFind(const VKSharedKey* key, u32 hash)
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < SHARED_PROBES; i++)
|
|
{
|
|
VKSharedPipeline* s = &s_shared[(hash + i) & (SHARED_SLOTS - 1)];
|
|
const int state = retro_atomic_load_acquire_int(&s->state);
|
|
if (state == SHARED_FREE)
|
|
return nullptr;
|
|
if (state != SHARED_READY || s->hash != hash || !SharedKeyEqual(&s->key, key))
|
|
continue;
|
|
for (;;)
|
|
{
|
|
const int refs = retro_atomic_load_acquire_int(&s->refs);
|
|
if (refs <= 0)
|
|
break;
|
|
if (retro_atomic_cas_int(&s->refs, refs, refs + 1))
|
|
return s;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/* The first free slot on the key's chain, or none: the pipeline is then
|
|
* its own. */
|
|
static VKSharedPipeline* SharedInsert(const VKSharedKey* key, u32 hash, VkPipeline pipeline)
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < SHARED_PROBES; i++)
|
|
{
|
|
VKSharedPipeline* s = &s_shared[(hash + i) & (SHARED_SLOTS - 1)];
|
|
if (retro_atomic_load_acquire_int(&s->state) != SHARED_FREE ||
|
|
!retro_atomic_cas_int(&s->state, SHARED_FREE, SHARED_BUILDING))
|
|
{
|
|
continue;
|
|
}
|
|
s->key = *key;
|
|
s->hash = hash;
|
|
s->pipeline = pipeline;
|
|
retro_atomic_store_relaxed_int(&s->refs, 1);
|
|
retro_atomic_store_release_int(&s->state, SHARED_READY);
|
|
return s;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void VKPipeline::ResetSharedPipelines()
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < SHARED_SLOTS; i++)
|
|
{
|
|
retro_atomic_store_relaxed_int(&s_shared[i].refs, 0);
|
|
retro_atomic_store_relaxed_int(&s_shared[i].state, SHARED_FREE);
|
|
}
|
|
retro_atomic_thread_fence_release();
|
|
}
|
|
|
|
/* Pipeline libraries, of the four parts a pipeline is linked from, are
|
|
* kept in the same table, their key's usage carrying which part they are:
|
|
* they stay, counted once, until DestroySharedLibraries. */
|
|
#define SHARED_LIBRARY_SHIFT 8
|
|
|
|
void VKPipeline::DestroySharedLibraries()
|
|
{
|
|
u32 i;
|
|
for (i = 0; i < SHARED_SLOTS; i++)
|
|
{
|
|
VKSharedPipeline* s = &s_shared[i];
|
|
if (retro_atomic_load_acquire_int(&s->state) != SHARED_READY ||
|
|
!(s->key.usage >> SHARED_LIBRARY_SHIFT))
|
|
{
|
|
continue;
|
|
}
|
|
vkDestroyPipeline(g_vulkan_context->GetDevice(), s->pipeline, nullptr);
|
|
retro_atomic_store_relaxed_int(&s->refs, 0);
|
|
retro_atomic_store_release_int(&s->state, SHARED_DEAD);
|
|
}
|
|
}
|
|
|
|
/* The library for one part, found or built from the parts of info it
|
|
* takes; *owned when the table had no room for it, and the caller
|
|
* destroys it once linked. */
|
|
static VkPipeline GetPipelineLibrary(const VKSharedKey* key,
|
|
const VkGraphicsPipelineCreateInfo* info,
|
|
VkGraphicsPipelineLibraryFlagsEXT part, bool* owned)
|
|
{
|
|
const u32 hash = HashSharedKey(key);
|
|
VKSharedPipeline* s = SharedFind(key, hash);
|
|
VkGraphicsPipelineLibraryCreateInfoEXT library_info;
|
|
VkGraphicsPipelineCreateInfo library_create;
|
|
VkPipeline library;
|
|
|
|
*owned = false;
|
|
if (s)
|
|
{
|
|
library = s->pipeline;
|
|
retro_atomic_fetch_sub_int(&s->refs, 1);
|
|
return library;
|
|
}
|
|
library_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT;
|
|
library_info.pNext = info->pNext;
|
|
library_info.flags = part;
|
|
library_create = *info;
|
|
library_create.pNext = &library_info;
|
|
library_create.flags |= VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
|
|
if (vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), g_object_cache->GetPipelineCache(),
|
|
1, &library_create, nullptr, &library) != VK_SUCCESS)
|
|
{
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
if (!SharedInsert(key, hash, library))
|
|
*owned = true;
|
|
return library;
|
|
}
|
|
|
|
/* A pipeline linked from the libraries of its four parts, each found or
|
|
* built with only what that part takes: the vertex input, the vertex and
|
|
* geometry shaders, the pixel shader, and the colour output. With cull
|
|
* mode, depth state and blending set per draw, the parts are shared by
|
|
* most of the pipelines asked for, and linking without link-time
|
|
* optimisation costs a fraction of building the pipeline. */
|
|
static VkPipeline LinkPipeline(const AbstractPipelineConfig& build, const VKSharedKey* key,
|
|
const VkGraphicsPipelineCreateInfo* info)
|
|
{
|
|
static const VkGraphicsPipelineLibraryFlagsEXT parts[4] = {
|
|
VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT,
|
|
VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT,
|
|
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT,
|
|
VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT,
|
|
};
|
|
VkGraphicsPipelineCreateInfo part_info[4];
|
|
VKSharedKey part_key[4];
|
|
VkPipeline libraries[4];
|
|
bool owned[4];
|
|
VkPipelineLibraryCreateInfoKHR link_info;
|
|
VkGraphicsPipelineCreateInfo link;
|
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
u32 i;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
part_info[i] = *info;
|
|
part_info[i].stageCount = 0;
|
|
part_info[i].pStages = nullptr;
|
|
part_info[i].pVertexInputState = nullptr;
|
|
part_info[i].pInputAssemblyState = nullptr;
|
|
part_info[i].pViewportState = nullptr;
|
|
part_info[i].pRasterizationState = nullptr;
|
|
part_info[i].pMultisampleState = nullptr;
|
|
part_info[i].pDepthStencilState = nullptr;
|
|
part_info[i].pColorBlendState = nullptr;
|
|
memset(&part_key[i], 0, sizeof(part_key[i]));
|
|
part_key[i].usage = key->usage | ((i + 1) << SHARED_LIBRARY_SHIFT);
|
|
}
|
|
/* vertex input: the vertex layout and the topology */
|
|
part_info[0].pVertexInputState = info->pVertexInputState;
|
|
part_info[0].pInputAssemblyState = info->pInputAssemblyState;
|
|
part_info[0].layout = VK_NULL_HANDLE;
|
|
part_info[0].renderPass = VK_NULL_HANDLE;
|
|
part_info[0].pDynamicState = nullptr;
|
|
part_key[0].vertex_format = key->vertex_format;
|
|
part_key[0].rasterization = static_cast<u32>(build.rasterization_state.primitive.Value());
|
|
/* the vertex and geometry shaders, all stages but the last */
|
|
part_info[1].stageCount = info->stageCount - 1;
|
|
part_info[1].pStages = info->pStages;
|
|
part_info[1].pViewportState = info->pViewportState;
|
|
part_info[1].pRasterizationState = info->pRasterizationState;
|
|
part_key[1].vertex_shader = key->vertex_shader;
|
|
part_key[1].geometry_shader = key->geometry_shader;
|
|
part_key[1].rasterization = key->rasterization;
|
|
part_key[1].framebuffer = key->framebuffer;
|
|
/* the pixel shader */
|
|
part_info[2].stageCount = 1;
|
|
part_info[2].pStages = info->pStages + info->stageCount - 1;
|
|
part_info[2].pMultisampleState = info->pMultisampleState;
|
|
part_info[2].pDepthStencilState = info->pDepthStencilState;
|
|
part_key[2].pixel_shader = key->pixel_shader;
|
|
part_key[2].framebuffer = key->framebuffer;
|
|
/* the colour output */
|
|
part_info[3].pMultisampleState = info->pMultisampleState;
|
|
part_info[3].pColorBlendState = info->pColorBlendState;
|
|
part_info[3].layout = VK_NULL_HANDLE;
|
|
part_key[3].blending = key->blending;
|
|
part_key[3].framebuffer = key->framebuffer;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
libraries[i] = GetPipelineLibrary(&part_key[i], &part_info[i], parts[i], &owned[i]);
|
|
if (libraries[i] == VK_NULL_HANDLE)
|
|
break;
|
|
}
|
|
if (i == 4)
|
|
{
|
|
link_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR;
|
|
link_info.pNext = nullptr;
|
|
link_info.libraryCount = 4;
|
|
link_info.pLibraries = libraries;
|
|
memset(&link, 0, sizeof(link));
|
|
link.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
|
link.pNext = &link_info;
|
|
link.layout = info->layout;
|
|
link.basePipelineIndex = -1;
|
|
if (vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(),
|
|
g_object_cache->GetPipelineCache(), 1, &link, nullptr,
|
|
&pipeline) != VK_SUCCESS)
|
|
{
|
|
pipeline = VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
while (i--)
|
|
{
|
|
if (owned[i])
|
|
vkDestroyPipeline(g_vulkan_context->GetDevice(), libraries[i], nullptr);
|
|
}
|
|
return pipeline;
|
|
}
|
|
|
|
VKPipeline::VKPipeline(const AbstractPipelineConfig& config, VkPipeline pipeline,
|
|
VkPipelineLayout pipeline_layout, AbstractPipelineUsage usage,
|
|
u32 dynamic_state, u32 dynamic_blend, VKSharedPipeline* shared)
|
|
: AbstractPipeline(config), m_pipeline(pipeline), m_pipeline_layout(pipeline_layout),
|
|
m_shared(shared), m_usage(usage), m_dynamic_state(dynamic_state),
|
|
m_dynamic_blend(dynamic_blend)
|
|
{
|
|
}
|
|
|
|
/* what a game makes in its first minutes, in one chunk */
|
|
OBJECT_POOL_DEFINE(VKPipeline, 256)
|
|
|
|
VKPipeline::~VKPipeline()
|
|
{
|
|
if (m_shared)
|
|
{
|
|
if (retro_atomic_fetch_sub_int(&m_shared->refs, 1) != 1)
|
|
return;
|
|
retro_atomic_store_release_int(&m_shared->state, SHARED_DEAD);
|
|
}
|
|
vkDestroyPipeline(g_vulkan_context->GetDevice(), m_pipeline, nullptr);
|
|
}
|
|
|
|
static bool IsStripPrimitiveTopology(VkPrimitiveTopology topology)
|
|
{
|
|
return topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP ||
|
|
topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP ||
|
|
topology == VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY ||
|
|
topology == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
|
|
}
|
|
|
|
static const VkCullModeFlags s_cull_modes[4] = {VK_CULL_MODE_NONE, VK_CULL_MODE_BACK_BIT,
|
|
VK_CULL_MODE_FRONT_BIT, VK_CULL_MODE_FRONT_AND_BACK};
|
|
|
|
static VkPipelineRasterizationStateCreateInfo
|
|
GetVulkanRasterizationState(const RasterizationState& state)
|
|
{
|
|
bool depth_clamp = g_backend_info.bSupportsDepthClamp;
|
|
|
|
return {
|
|
VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkPipelineRasterizationStateCreateFlags flags
|
|
depth_clamp, // VkBool32 depthClampEnable
|
|
VK_FALSE, // VkBool32 rasterizerDiscardEnable
|
|
VK_POLYGON_MODE_FILL, // VkPolygonMode polygonMode
|
|
s_cull_modes[u32(state.cull_mode.Value())], // VkCullModeFlags cullMode
|
|
VK_FRONT_FACE_CLOCKWISE, // VkFrontFace frontFace
|
|
VK_FALSE, // VkBool32 depthBiasEnable
|
|
0.0f, // float depthBiasConstantFactor
|
|
0.0f, // float depthBiasClamp
|
|
0.0f, // float depthBiasSlopeFactor
|
|
1.0f // float lineWidth
|
|
};
|
|
}
|
|
|
|
static VkPipelineMultisampleStateCreateInfo GetVulkanMultisampleState(const FramebufferState& state)
|
|
{
|
|
return {
|
|
VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkPipelineMultisampleStateCreateFlags flags
|
|
static_cast<VkSampleCountFlagBits>(
|
|
state.samples.Value()), // VkSampleCountFlagBits rasterizationSamples
|
|
static_cast<bool>(state.per_sample_shading), // VkBool32 sampleShadingEnable
|
|
1.0f, // float minSampleShading
|
|
nullptr, // const VkSampleMask* pSampleMask;
|
|
VK_FALSE, // VkBool32 alphaToCoverageEnable
|
|
VK_FALSE // VkBool32 alphaToOneEnable
|
|
};
|
|
}
|
|
|
|
static VkCompareOp GetVulkanCompareOp(CompareMode func)
|
|
{
|
|
// Less/greater are swapped due to inverted depth.
|
|
const bool inverted_depth = !g_backend_info.bSupportsReversedDepthRange;
|
|
switch (func)
|
|
{
|
|
case CompareMode::Never:
|
|
return VK_COMPARE_OP_NEVER;
|
|
case CompareMode::Less:
|
|
return inverted_depth ? VK_COMPARE_OP_GREATER : VK_COMPARE_OP_LESS;
|
|
case CompareMode::Equal:
|
|
return VK_COMPARE_OP_EQUAL;
|
|
case CompareMode::LEqual:
|
|
return inverted_depth ? VK_COMPARE_OP_GREATER_OR_EQUAL : VK_COMPARE_OP_LESS_OR_EQUAL;
|
|
case CompareMode::Greater:
|
|
return inverted_depth ? VK_COMPARE_OP_LESS : VK_COMPARE_OP_GREATER;
|
|
case CompareMode::NEqual:
|
|
return VK_COMPARE_OP_NOT_EQUAL;
|
|
case CompareMode::GEqual:
|
|
return inverted_depth ? VK_COMPARE_OP_LESS_OR_EQUAL : VK_COMPARE_OP_GREATER_OR_EQUAL;
|
|
case CompareMode::Always:
|
|
return VK_COMPARE_OP_ALWAYS;
|
|
default:
|
|
PanicAlertFmt("Invalid compare mode {}", func);
|
|
return VK_COMPARE_OP_ALWAYS;
|
|
}
|
|
}
|
|
|
|
static VkPipelineDepthStencilStateCreateInfo GetVulkanDepthStencilState(const DepthState& state)
|
|
{
|
|
const VkCompareOp compare_op = GetVulkanCompareOp(state.func);
|
|
|
|
return {
|
|
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkPipelineDepthStencilStateCreateFlags flags
|
|
state.test_enable, // VkBool32 depthTestEnable
|
|
state.update_enable, // VkBool32 depthWriteEnable
|
|
compare_op, // VkCompareOp depthCompareOp
|
|
VK_FALSE, // VkBool32 depthBoundsTestEnable
|
|
VK_FALSE, // VkBool32 stencilTestEnable
|
|
{}, // VkStencilOpState front
|
|
{}, // VkStencilOpState back
|
|
0.0f, // float minDepthBounds
|
|
1.0f // float maxDepthBounds
|
|
};
|
|
}
|
|
|
|
static VkPipelineColorBlendAttachmentState
|
|
GetVulkanAttachmentBlendState(const BlendingState& state, AbstractPipelineUsage usage)
|
|
{
|
|
VkPipelineColorBlendAttachmentState vk_state = {};
|
|
|
|
bool use_dual_source = state.use_dual_src;
|
|
|
|
vk_state.blendEnable = static_cast<VkBool32>(state.blend_enable);
|
|
vk_state.colorBlendOp = state.subtract ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
|
|
vk_state.alphaBlendOp = state.subtract_alpha ? VK_BLEND_OP_REVERSE_SUBTRACT : VK_BLEND_OP_ADD;
|
|
|
|
if (use_dual_source)
|
|
{
|
|
static constexpr Common::EnumMap<VkBlendFactor, SrcBlendFactor::InvDstAlpha> src_factors{
|
|
VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR,
|
|
VK_BLEND_FACTOR_SRC1_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA,
|
|
VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
|
|
};
|
|
static constexpr Common::EnumMap<VkBlendFactor, DstBlendFactor::InvDstAlpha> dst_factors{
|
|
VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_SRC_COLOR, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR,
|
|
VK_BLEND_FACTOR_SRC1_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA,
|
|
VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
|
|
};
|
|
|
|
vk_state.srcColorBlendFactor = src_factors[state.src_factor];
|
|
vk_state.srcAlphaBlendFactor = src_factors[state.src_factor_alpha];
|
|
vk_state.dstColorBlendFactor = dst_factors[state.dst_factor];
|
|
vk_state.dstAlphaBlendFactor = dst_factors[state.dst_factor_alpha];
|
|
}
|
|
else
|
|
{
|
|
static constexpr Common::EnumMap<VkBlendFactor, SrcBlendFactor::InvDstAlpha> src_factors{
|
|
VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_DST_COLOR, VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR,
|
|
VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
|
VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
|
|
};
|
|
|
|
static constexpr Common::EnumMap<VkBlendFactor, DstBlendFactor::InvDstAlpha> dst_factors{
|
|
VK_BLEND_FACTOR_ZERO, VK_BLEND_FACTOR_ONE,
|
|
VK_BLEND_FACTOR_SRC_COLOR, VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR,
|
|
VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
|
|
VK_BLEND_FACTOR_DST_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA,
|
|
};
|
|
|
|
vk_state.srcColorBlendFactor = src_factors[state.src_factor];
|
|
vk_state.srcAlphaBlendFactor = src_factors[state.src_factor_alpha];
|
|
vk_state.dstColorBlendFactor = dst_factors[state.dst_factor];
|
|
vk_state.dstAlphaBlendFactor = dst_factors[state.dst_factor_alpha];
|
|
}
|
|
|
|
if (state.color_update)
|
|
{
|
|
vk_state.colorWriteMask =
|
|
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
|
|
}
|
|
else
|
|
{
|
|
vk_state.colorWriteMask = 0;
|
|
}
|
|
|
|
if (state.alpha_update)
|
|
vk_state.colorWriteMask |= VK_COLOR_COMPONENT_A_BIT;
|
|
|
|
return vk_state;
|
|
}
|
|
|
|
void VKPipeline::GetDynamicBlendState(u32 dynamic_blend, VkBool32* enable,
|
|
VkColorBlendEquationEXT* equation,
|
|
VkColorComponentFlags* write_mask)
|
|
{
|
|
BlendingState state;
|
|
state.hex = dynamic_blend & (VK_DYNAMIC_BLEND_MASK | VK_DYNAMIC_BLEND_DUAL_SRC);
|
|
const VkPipelineColorBlendAttachmentState vk_state =
|
|
GetVulkanAttachmentBlendState(state, AbstractPipelineUsage::GX);
|
|
*enable = vk_state.blendEnable;
|
|
equation->srcColorBlendFactor = vk_state.srcColorBlendFactor;
|
|
equation->dstColorBlendFactor = vk_state.dstColorBlendFactor;
|
|
equation->colorBlendOp = vk_state.colorBlendOp;
|
|
equation->srcAlphaBlendFactor = vk_state.srcAlphaBlendFactor;
|
|
equation->dstAlphaBlendFactor = vk_state.dstAlphaBlendFactor;
|
|
equation->alphaBlendOp = vk_state.alphaBlendOp;
|
|
*write_mask = vk_state.colorWriteMask;
|
|
}
|
|
|
|
VkLogicOp VKPipeline::GetVulkanLogicOp(u32 logic_mode)
|
|
{
|
|
static const VkLogicOp vk_logic_ops[16] = {
|
|
VK_LOGIC_OP_CLEAR, VK_LOGIC_OP_AND, VK_LOGIC_OP_AND_REVERSE,
|
|
VK_LOGIC_OP_COPY, VK_LOGIC_OP_AND_INVERTED, VK_LOGIC_OP_NO_OP,
|
|
VK_LOGIC_OP_XOR, VK_LOGIC_OP_OR, VK_LOGIC_OP_NOR,
|
|
VK_LOGIC_OP_EQUIVALENT, VK_LOGIC_OP_INVERT, VK_LOGIC_OP_OR_REVERSE,
|
|
VK_LOGIC_OP_COPY_INVERTED, VK_LOGIC_OP_OR_INVERTED, VK_LOGIC_OP_NAND,
|
|
VK_LOGIC_OP_SET};
|
|
return vk_logic_ops[logic_mode & 15];
|
|
}
|
|
|
|
void VKPipeline::GetDrawState(const RasterizationState& rasterization, const DepthState& depth,
|
|
const BlendingState& blending, u32* state, u32* blend)
|
|
{
|
|
*state = 0;
|
|
*blend = 0;
|
|
if (!g_vulkan_context->SupportsExtendedDynamicState())
|
|
return;
|
|
*state = VK_DYNAMIC_USED | s_cull_modes[u32(rasterization.cull_mode.Value())] |
|
|
(depth.test_enable ? VK_DYNAMIC_DEPTH_TEST : 0u) |
|
|
(depth.update_enable ? VK_DYNAMIC_DEPTH_WRITE : 0u) |
|
|
(static_cast<u32>(GetVulkanCompareOp(depth.func)) << VK_DYNAMIC_DEPTH_OP_SHIFT);
|
|
if (!g_vulkan_context->SupportsDynamicBlending())
|
|
return;
|
|
*blend = VK_DYNAMIC_BLEND_USED |
|
|
(blending.hex & (VK_DYNAMIC_BLEND_MASK | VK_DYNAMIC_BLEND_DUAL_SRC));
|
|
if (g_vulkan_context->SupportsDynamicLogicOp())
|
|
*blend |= VK_DYNAMIC_BLEND_LOGIC_USED | (blending.hex & VK_DYNAMIC_BLEND_LOGIC_MASK);
|
|
}
|
|
|
|
void VKPipeline::PopulateBackendInfoDrawState(BackendInfo* backend_info)
|
|
{
|
|
RasterizationState cull;
|
|
DepthState depth;
|
|
cull.hex = 0;
|
|
cull.cull_mode = static_cast<CullMode>(3);
|
|
depth.hex = 0;
|
|
depth.test_enable = 1;
|
|
depth.update_enable = 1;
|
|
depth.func = static_cast<CompareMode>(7);
|
|
backend_info->uDrawStateRasterizationBits = 0;
|
|
backend_info->uDrawStateDepthBits = 0;
|
|
backend_info->uDrawStateBlendingBits = 0;
|
|
if (!g_vulkan_context->SupportsExtendedDynamicState())
|
|
return;
|
|
backend_info->uDrawStateRasterizationBits = cull.hex;
|
|
backend_info->uDrawStateDepthBits = depth.hex;
|
|
if (!g_vulkan_context->SupportsDynamicBlending())
|
|
return;
|
|
/* use_dual_src only picks the factors, which are set per draw too: the pipeline is the same */
|
|
backend_info->uDrawStateBlendingBits = VK_DYNAMIC_BLEND_MASK | VK_DYNAMIC_BLEND_DUAL_SRC;
|
|
if (g_vulkan_context->SupportsDynamicLogicOp())
|
|
backend_info->uDrawStateBlendingBits |= VK_DYNAMIC_BLEND_LOGIC_MASK;
|
|
}
|
|
|
|
static VkPipelineColorBlendStateCreateInfo
|
|
GetVulkanColorBlendState(const BlendingState& state,
|
|
const VkPipelineColorBlendAttachmentState* attachments,
|
|
uint32_t num_attachments)
|
|
{
|
|
VkBool32 vk_logic_op_enable = static_cast<VkBool32>(state.logic_op_enable);
|
|
if (vk_logic_op_enable && !g_backend_info.bSupportsLogicOp)
|
|
{
|
|
// At the time of writing, Adreno and Mali drivers didn't support logic ops.
|
|
// The "emulation" through blending path has been removed, so just disable it completely.
|
|
// These drivers don't support dual-source blend either, so issues are to be expected.
|
|
vk_logic_op_enable = VK_FALSE;
|
|
}
|
|
|
|
VkLogicOp vk_logic_op =
|
|
vk_logic_op_enable ? VKPipeline::GetVulkanLogicOp(u32(state.logic_mode.Value())) :
|
|
VK_LOGIC_OP_CLEAR;
|
|
|
|
VkPipelineColorBlendStateCreateInfo vk_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkPipelineColorBlendStateCreateFlags flags
|
|
vk_logic_op_enable, // VkBool32 logicOpEnable
|
|
vk_logic_op, // VkLogicOp logicOp
|
|
num_attachments, // uint32_t attachmentCount
|
|
attachments, // const VkPipelineColorBlendAttachmentState* pAttachments
|
|
{1.0f, 1.0f, 1.0f, 1.0f} // float blendConstants[4]
|
|
};
|
|
|
|
return vk_state;
|
|
}
|
|
|
|
VKPipeline* VKPipeline::Create(const AbstractPipelineConfig& config)
|
|
{
|
|
DEBUG_ASSERT(config.vertex_shader && config.pixel_shader);
|
|
|
|
// Get render pass for config.
|
|
VkRenderPass render_pass = g_object_cache->GetRenderPass(
|
|
VKTexture::GetVkFormatForHostTextureFormat(config.framebuffer_state.color_texture_format),
|
|
VKTexture::GetVkFormatForHostTextureFormat(config.framebuffer_state.depth_texture_format),
|
|
config.framebuffer_state.samples, VK_ATTACHMENT_LOAD_OP_LOAD);
|
|
|
|
if (render_pass == VK_NULL_HANDLE)
|
|
{
|
|
PanicAlertFmt("Failed to get render pass");
|
|
return nullptr;
|
|
}
|
|
|
|
// Get pipeline layout.
|
|
VkPipelineLayout pipeline_layout;
|
|
switch (config.usage)
|
|
{
|
|
case AbstractPipelineUsage::GX:
|
|
pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_STANDARD);
|
|
break;
|
|
case AbstractPipelineUsage::GXUber:
|
|
pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_UBER);
|
|
break;
|
|
case AbstractPipelineUsage::Utility:
|
|
pipeline_layout = g_object_cache->GetPipelineLayout(PIPELINE_LAYOUT_UTILITY);
|
|
break;
|
|
default:
|
|
PanicAlertFmt("Unknown pipeline layout.");
|
|
return nullptr;
|
|
}
|
|
|
|
/* Cull mode and depth state set per draw, and blending with them where
|
|
* the device allows it: the pipeline is built, and shared, with them
|
|
* left out of its key. */
|
|
AbstractPipelineConfig build = config;
|
|
u32 dynamic_bits = 0;
|
|
u32 dynamic_blend = 0;
|
|
VKSharedKey key;
|
|
u32 hash = 0;
|
|
if (config.usage != AbstractPipelineUsage::Utility &&
|
|
g_vulkan_context->SupportsExtendedDynamicState())
|
|
{
|
|
VKSharedPipeline* shared;
|
|
/* the logic op mode alone picks no pipeline either, where it is set
|
|
* per draw: one with integer output draws every mode */
|
|
GetDrawState(config.rasterization_state, config.depth_state, config.blending_state,
|
|
&dynamic_bits, &dynamic_blend);
|
|
build.rasterization_state.hex &= ~g_backend_info.uDrawStateRasterizationBits;
|
|
build.depth_state.hex &= ~g_backend_info.uDrawStateDepthBits;
|
|
build.blending_state.hex &= ~g_backend_info.uDrawStateBlendingBits;
|
|
key.vertex_format = build.vertex_format;
|
|
key.vertex_shader = ShaderUid(build.vertex_shader);
|
|
key.geometry_shader = ShaderUid(build.geometry_shader);
|
|
key.pixel_shader = ShaderUid(build.pixel_shader);
|
|
key.rasterization = build.rasterization_state.hex;
|
|
key.blending = build.blending_state.hex;
|
|
key.framebuffer = build.framebuffer_state.hex;
|
|
key.usage = static_cast<u32>(build.usage);
|
|
hash = HashSharedKey(&key);
|
|
shared = SharedFind(&key, hash);
|
|
if (shared)
|
|
{
|
|
return new VKPipeline(config, shared->pipeline, pipeline_layout, config.usage,
|
|
dynamic_bits, dynamic_blend, shared);
|
|
}
|
|
}
|
|
|
|
// Declare descriptors for empty vertex buffers/attributes
|
|
static const VkPipelineVertexInputStateCreateInfo empty_vertex_input_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, // VkStructureType sType
|
|
nullptr, // const void* pNext
|
|
0, // VkPipelineVertexInputStateCreateFlags flags
|
|
0, // uint32_t vertexBindingDescriptionCount
|
|
nullptr, // const VkVertexInputBindingDescription* pVertexBindingDescriptions
|
|
0, // uint32_t vertexAttributeDescriptionCount
|
|
nullptr // const VkVertexInputAttributeDescription* pVertexAttributeDescriptions
|
|
};
|
|
|
|
// Vertex inputs
|
|
const VkPipelineVertexInputStateCreateInfo& vertex_input_state =
|
|
config.vertex_format ?
|
|
static_cast<const VertexFormat*>(config.vertex_format)->GetVertexInputStateInfo() :
|
|
empty_vertex_input_state;
|
|
|
|
// Input assembly
|
|
static constexpr std::array<VkPrimitiveTopology, 4> vk_primitive_topologies = {
|
|
{VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
|
|
VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP}};
|
|
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, nullptr, 0,
|
|
vk_primitive_topologies[static_cast<u32>(build.rasterization_state.primitive.Value())],
|
|
VK_FALSE};
|
|
|
|
// See Vulkan spec, section 19:
|
|
// If topology is VK_PRIMITIVE_TOPOLOGY_POINT_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST,
|
|
// VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY,
|
|
// VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY or VK_PRIMITIVE_TOPOLOGY_PATCH_LIST,
|
|
// primitiveRestartEnable must be VK_FALSE
|
|
if (g_backend_info.bSupportsPrimitiveRestart &&
|
|
IsStripPrimitiveTopology(input_assembly_state.topology))
|
|
{
|
|
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
|
}
|
|
|
|
// Shaders to stages
|
|
VkPipelineShaderStageCreateInfo shader_stages[3];
|
|
uint32_t num_shader_stages = 0;
|
|
if (config.vertex_shader)
|
|
{
|
|
shader_stages[num_shader_stages++] = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
VK_SHADER_STAGE_VERTEX_BIT,
|
|
static_cast<const VKShader*>(config.vertex_shader)->GetShaderModule(),
|
|
"main"};
|
|
}
|
|
if (config.geometry_shader)
|
|
{
|
|
shader_stages[num_shader_stages++] = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
VK_SHADER_STAGE_GEOMETRY_BIT,
|
|
static_cast<const VKShader*>(config.geometry_shader)->GetShaderModule(),
|
|
"main"};
|
|
}
|
|
if (config.pixel_shader)
|
|
{
|
|
shader_stages[num_shader_stages++] = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
|
|
nullptr,
|
|
0,
|
|
VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
static_cast<const VKShader*>(config.pixel_shader)->GetShaderModule(),
|
|
"main"};
|
|
}
|
|
|
|
// Fill in Vulkan descriptor structs from our state structures.
|
|
VkPipelineRasterizationStateCreateInfo rasterization_state =
|
|
GetVulkanRasterizationState(build.rasterization_state);
|
|
VkPipelineMultisampleStateCreateInfo multisample_state =
|
|
GetVulkanMultisampleState(config.framebuffer_state);
|
|
VkPipelineDepthStencilStateCreateInfo depth_stencil_state =
|
|
GetVulkanDepthStencilState(build.depth_state);
|
|
VkPipelineColorBlendAttachmentState blend_attachment_state =
|
|
GetVulkanAttachmentBlendState(build.blending_state, config.usage);
|
|
|
|
VkPipelineColorBlendStateCreateInfo blend_state =
|
|
GetVulkanColorBlendState(build.blending_state, &blend_attachment_state, 1);
|
|
|
|
static const VkDepthClampRangeEXT clamp_range = {0.0f, MAX_EFB_DEPTH};
|
|
static const VkPipelineViewportDepthClampControlCreateInfoEXT depth_clamp_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_DEPTH_CLAMP_CONTROL_CREATE_INFO_EXT, nullptr,
|
|
VK_DEPTH_CLAMP_MODE_USER_DEFINED_RANGE_EXT, // VkDepthClampModeEXT depthClampMode
|
|
&clamp_range // const VkDepthClampRangeEXT* pDepthClampRange
|
|
};
|
|
|
|
// This viewport isn't used, but needs to be specified anyway.
|
|
static const VkViewport viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
|
|
static const VkRect2D scissor = {{0, 0}, {1, 1}};
|
|
static const VkPipelineViewportStateCreateInfo viewport_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
|
|
g_backend_info.bSupportsUnrestrictedDepthRange ? &depth_clamp_state : nullptr,
|
|
0, // VkPipelineViewportStateCreateFlags flags;
|
|
1, // uint32_t viewportCount
|
|
&viewport, // const VkViewport* pViewports
|
|
1, // uint32_t scissorCount
|
|
&scissor // const VkRect2D* pScissors
|
|
};
|
|
|
|
// Viewport and scissor are set per draw, and cull mode and depth state with them where the
|
|
// device allows it.
|
|
static const VkDynamicState dynamic_states[11] = {
|
|
VK_DYNAMIC_STATE_VIEWPORT,
|
|
VK_DYNAMIC_STATE_SCISSOR,
|
|
VK_DYNAMIC_STATE_CULL_MODE_EXT,
|
|
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
|
|
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
|
|
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
|
|
VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT,
|
|
VK_DYNAMIC_STATE_COLOR_BLEND_EQUATION_EXT,
|
|
VK_DYNAMIC_STATE_COLOR_WRITE_MASK_EXT,
|
|
VK_DYNAMIC_STATE_LOGIC_OP_ENABLE_EXT,
|
|
VK_DYNAMIC_STATE_LOGIC_OP_EXT,
|
|
};
|
|
const VkPipelineDynamicStateCreateInfo dynamic_state = {
|
|
VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, nullptr,
|
|
0, // VkPipelineDynamicStateCreateFlags flags
|
|
(dynamic_blend & VK_DYNAMIC_BLEND_LOGIC_USED) ? 11u :
|
|
dynamic_blend ? 9u :
|
|
dynamic_bits ? 6u :
|
|
2u, // uint32_t dynamicStateCount
|
|
dynamic_states // const VkDynamicState* pDynamicStates
|
|
};
|
|
|
|
// Combine to full pipeline info structure.
|
|
VkGraphicsPipelineCreateInfo pipeline_info = {
|
|
VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
|
|
nullptr, // VkStructureType sType
|
|
0, // VkPipelineCreateFlags flags
|
|
num_shader_stages, // uint32_t stageCount
|
|
shader_stages, // const VkPipelineShaderStageCreateInfo* pStages
|
|
&vertex_input_state, // const VkPipelineVertexInputStateCreateInfo* pVertexInputState
|
|
&input_assembly_state, // const VkPipelineInputAssemblyStateCreateInfo* pInputAssemblyState
|
|
nullptr, // const VkPipelineTessellationStateCreateInfo* pTessellationState
|
|
&viewport_state, // const VkPipelineViewportStateCreateInfo* pViewportState
|
|
&rasterization_state, // const VkPipelineRasterizationStateCreateInfo* pRasterizationState
|
|
&multisample_state, // const VkPipelineMultisampleStateCreateInfo* pMultisampleState
|
|
&depth_stencil_state, // const VkPipelineDepthStencilStateCreateInfo* pDepthStencilState
|
|
&blend_state, // const VkPipelineColorBlendStateCreateInfo* pColorBlendState
|
|
&dynamic_state, // const VkPipelineDynamicStateCreateInfo* pDynamicState
|
|
pipeline_layout, // VkPipelineLayout layout
|
|
render_pass, // VkRenderPass renderPass
|
|
0, // uint32_t subpass
|
|
VK_NULL_HANDLE, // VkPipeline basePipelineHandle
|
|
-1 // int32_t basePipelineIndex
|
|
};
|
|
|
|
VkPipeline pipeline = VK_NULL_HANDLE;
|
|
STUTTER_MARK(t);
|
|
/* Ubershader pipelines, built on the spot when a draw needs one, are
|
|
* linked from libraries where the device can; a link that fails builds
|
|
* the pipeline whole. */
|
|
if (config.usage == AbstractPipelineUsage::GXUber && dynamic_bits &&
|
|
g_vulkan_context->SupportsFastLinking())
|
|
{
|
|
pipeline = LinkPipeline(build, &key, &pipeline_info);
|
|
}
|
|
if (pipeline == VK_NULL_HANDLE)
|
|
{
|
|
VkResult res =
|
|
vkCreateGraphicsPipelines(g_vulkan_context->GetDevice(), g_object_cache->GetPipelineCache(),
|
|
1, &pipeline_info, nullptr, &pipeline);
|
|
if (res != VK_SUCCESS)
|
|
{
|
|
LOG_VULKAN_ERROR(res, "vkCreateGraphicsPipelines failed: ");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
STUTTER_DRIVER_SINCE(t);
|
|
|
|
return new VKPipeline(config, pipeline, pipeline_layout, config.usage,
|
|
dynamic_bits, dynamic_blend,
|
|
dynamic_bits ? SharedInsert(&key, hash, pipeline) : nullptr);
|
|
}
|
|
} // namespace Vulkan
|