1
0
mirror of https://github.com/doitsujin/dxvk.git synced 2025-03-14 22:29:15 +01:00
dxvk/src/dxvk/dxvk_shader.cpp
2022-07-09 13:41:07 +02:00

671 lines
23 KiB
C++

#include "dxvk_device.h"
#include "dxvk_pipemanager.h"
#include "dxvk_shader.h"
#include <dxvk_dummy_frag.h>
#include <algorithm>
#include <unordered_map>
#include <unordered_set>
namespace dxvk {
DxvkShader::DxvkShader(
const DxvkShaderCreateInfo& info,
SpirvCodeBuffer&& spirv)
: m_info(info), m_code(spirv), m_bindings(info.stage) {
m_info.uniformData = nullptr;
m_info.bindings = nullptr;
// Copy resource binding slot infos
for (uint32_t i = 0; i < info.bindingCount; i++) {
DxvkBindingInfo binding = info.bindings[i];
binding.stages = info.stage;
m_bindings.addBinding(binding);
}
if (info.pushConstSize) {
VkPushConstantRange pushConst;
pushConst.stageFlags = info.stage;
pushConst.offset = info.pushConstOffset;
pushConst.size = info.pushConstSize;
m_bindings.addPushConstantRange(pushConst);
}
// Copy uniform buffer data
if (info.uniformSize) {
m_uniformData.resize(info.uniformSize);
std::memcpy(m_uniformData.data(), info.uniformData, info.uniformSize);
m_info.uniformData = m_uniformData.data();
}
// Run an analysis pass over the SPIR-V code to gather some
// info that we may need during pipeline compilation.
std::vector<BindingOffsets> bindingOffsets;
std::vector<uint32_t> varIds;
SpirvCodeBuffer code = std::move(spirv);
uint32_t o1VarId = 0;
for (auto ins : code) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationBinding) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].bindingId = ins.arg(3);
bindingOffsets[varId].bindingOffset = ins.offset() + 3;
varIds.push_back(varId);
}
if (ins.arg(2) == spv::DecorationDescriptorSet) {
uint32_t varId = ins.arg(1);
bindingOffsets.resize(std::max(bindingOffsets.size(), size_t(varId + 1)));
bindingOffsets[varId].setOffset = ins.offset() + 3;
}
if (ins.arg(2) == spv::DecorationSpecId && ins.arg(3) < MaxNumSpecConstants)
m_flags.set(DxvkShaderFlag::HasSpecConstants);
if (ins.arg(2) == spv::DecorationLocation && ins.arg(3) == 1) {
m_o1LocOffset = ins.offset() + 3;
o1VarId = ins.arg(1);
}
if (ins.arg(2) == spv::DecorationIndex && ins.arg(1) == o1VarId)
m_o1IdxOffset = ins.offset() + 3;
}
if (ins.opCode() == spv::OpExecutionMode) {
if (ins.arg(2) == spv::ExecutionModeStencilRefReplacingEXT)
m_flags.set(DxvkShaderFlag::ExportsStencilRef);
if (ins.arg(2) == spv::ExecutionModeXfb)
m_flags.set(DxvkShaderFlag::HasTransformFeedback);
}
if (ins.opCode() == spv::OpCapability) {
if (ins.arg(1) == spv::CapabilitySampleRateShading)
m_flags.set(DxvkShaderFlag::HasSampleRateShading);
if (ins.arg(1) == spv::CapabilityShaderViewportIndexLayerEXT)
m_flags.set(DxvkShaderFlag::ExportsViewportIndexLayerFromVertexStage);
}
// Ignore the actual shader code, there's nothing interesting for us in there.
if (ins.opCode() == spv::OpFunction)
break;
}
// Combine spec constant IDs with other binding info
for (auto varId : varIds) {
BindingOffsets info = bindingOffsets[varId];
if (info.bindingOffset)
m_bindingOffsets.push_back(info);
}
}
DxvkShader::~DxvkShader() {
}
SpirvCodeBuffer DxvkShader::getCode(
const DxvkBindingLayoutObjects* layout,
const DxvkShaderModuleCreateInfo& state) const {
SpirvCodeBuffer spirvCode = m_code.decompress();
uint32_t* code = spirvCode.data();
// Remap resource binding IDs
for (const auto& info : m_bindingOffsets) {
auto mappedBinding = layout->lookupBinding(info.bindingId);
if (mappedBinding) {
code[info.bindingOffset] = mappedBinding->binding;
if (info.setOffset)
code[info.setOffset] = mappedBinding->set;
}
}
// For dual-source blending we need to re-map
// location 1, index 0 to location 0, index 1
if (state.fsDualSrcBlend && m_o1IdxOffset && m_o1LocOffset)
std::swap(code[m_o1IdxOffset], code[m_o1LocOffset]);
// Replace undefined input variables with zero
for (uint32_t u : bit::BitMask(state.undefinedInputs))
eliminateInput(spirvCode, u);
return spirvCode;
}
bool DxvkShader::canUsePipelineLibrary() const {
// Pipeline libraries are unsupported for geometry and
// tessellation stages since we'd need to compile them
// all into one library
if (m_info.stage != VK_SHADER_STAGE_VERTEX_BIT
&& m_info.stage != VK_SHADER_STAGE_FRAGMENT_BIT
&& m_info.stage != VK_SHADER_STAGE_COMPUTE_BIT)
return false;
// Ignore shaders that have user-defined spec constants
return !m_flags.test(DxvkShaderFlag::HasSpecConstants);
}
void DxvkShader::dump(std::ostream& outputStream) const {
m_code.decompress().store(outputStream);
}
void DxvkShader::eliminateInput(SpirvCodeBuffer& code, uint32_t location) {
struct SpirvTypeInfo {
spv::Op op = spv::OpNop;
uint32_t baseTypeId = 0;
uint32_t compositeSize = 0;
spv::StorageClass storageClass = spv::StorageClassMax;
};
std::unordered_map<uint32_t, SpirvTypeInfo> types;
std::unordered_map<uint32_t, uint32_t> constants;
std::unordered_set<uint32_t> candidates;
// Find the input variable in question
size_t inputVarOffset = 0;
uint32_t inputVarTypeId = 0;
uint32_t inputVarId = 0;
for (auto ins : code) {
if (ins.opCode() == spv::OpDecorate) {
if (ins.arg(2) == spv::DecorationLocation
&& ins.arg(3) == location)
candidates.insert(ins.arg(1));
}
if (ins.opCode() == spv::OpConstant)
constants.insert({ ins.arg(2), ins.arg(3) });
if (ins.opCode() == spv::OpTypeFloat || ins.opCode() == spv::OpTypeInt)
types.insert({ ins.arg(1), { ins.opCode(), 0, ins.arg(2), spv::StorageClassMax }});
if (ins.opCode() == spv::OpTypeVector)
types.insert({ ins.arg(1), { ins.opCode(), ins.arg(2), ins.arg(3), spv::StorageClassMax }});
if (ins.opCode() == spv::OpTypeArray) {
auto constant = constants.find(ins.arg(3));
if (constant == constants.end())
continue;
types.insert({ ins.arg(1), { ins.opCode(), ins.arg(2), constant->second, spv::StorageClassMax }});
}
if (ins.opCode() == spv::OpTypePointer)
types.insert({ ins.arg(1), { ins.opCode(), ins.arg(3), 0, spv::StorageClass(ins.arg(2)) }});
if (ins.opCode() == spv::OpVariable && spv::StorageClass(ins.arg(3)) == spv::StorageClassInput) {
if (candidates.find(ins.arg(2)) != candidates.end()) {
inputVarOffset = ins.offset();
inputVarTypeId = ins.arg(1);
inputVarId = ins.arg(2);
break;
}
}
}
if (!inputVarId)
return;
// Declare private pointer types
auto pointerType = types.find(inputVarTypeId);
if (pointerType == types.end())
return;
code.beginInsertion(inputVarOffset);
std::vector<std::pair<uint32_t, SpirvTypeInfo>> privateTypes;
for (auto p = types.find(pointerType->second.baseTypeId);
p != types.end();
p = types.find(p->second.baseTypeId)) {
std::pair<uint32_t, SpirvTypeInfo> info = *p;
info.first = 0;
info.second.baseTypeId = p->first;
info.second.storageClass = spv::StorageClassPrivate;
for (auto t : types) {
if (t.second.op == info.second.op
&& t.second.baseTypeId == info.second.baseTypeId
&& t.second.storageClass == info.second.storageClass)
info.first = t.first;
}
if (!info.first) {
info.first = code.allocId();
code.putIns(spv::OpTypePointer, 4);
code.putWord(info.first);
code.putWord(info.second.storageClass);
code.putWord(info.second.baseTypeId);
}
privateTypes.push_back(info);
}
// Define zero constants
uint32_t constantId = 0;
for (auto i = privateTypes.rbegin(); i != privateTypes.rend(); i++) {
if (constantId) {
uint32_t compositeSize = i->second.compositeSize;
uint32_t compositeId = code.allocId();
code.putIns(spv::OpConstantComposite, 3 + compositeSize);
code.putWord(i->second.baseTypeId);
code.putWord(compositeId);
for (uint32_t i = 0; i < compositeSize; i++)
code.putWord(constantId);
constantId = compositeId;
} else {
constantId = code.allocId();
code.putIns(spv::OpConstant, 4);
code.putWord(i->second.baseTypeId);
code.putWord(constantId);
code.putWord(0);
}
}
// Erase and re-declare variable
code.erase(4);
code.putIns(spv::OpVariable, 5);
code.putWord(privateTypes[0].first);
code.putWord(inputVarId);
code.putWord(spv::StorageClassPrivate);
code.putWord(constantId);
code.endInsertion();
// Remove variable from interface list
for (auto ins : code) {
if (ins.opCode() == spv::OpEntryPoint) {
uint32_t argIdx = 2 + code.strLen(ins.chr(2));
while (argIdx < ins.length()) {
if (ins.arg(argIdx) == inputVarId) {
ins.setArg(0, spv::OpEntryPoint | ((ins.length() - 1) << spv::WordCountShift));
code.beginInsertion(ins.offset() + argIdx);
code.erase(1);
code.endInsertion();
break;
}
argIdx += 1;
}
}
}
// Remove location and other declarations
for (auto iter = code.begin(); iter != code.end(); ) {
auto ins = *(iter++);
if (ins.opCode() == spv::OpDecorate && ins.arg(1) == inputVarId) {
uint32_t numWords;
switch (ins.arg(2)) {
case spv::DecorationLocation:
case spv::DecorationFlat:
case spv::DecorationNoPerspective:
case spv::DecorationCentroid:
case spv::DecorationPatch:
case spv::DecorationSample:
numWords = ins.length();
break;
default:
numWords = 0;
}
if (numWords) {
code.beginInsertion(ins.offset());
code.erase(numWords);
iter = SpirvInstructionIterator(code.data(), code.endInsertion(), code.dwords());
}
}
if (ins.opCode() == spv::OpFunction)
break;
}
// Fix up pointer types used in access chain instructions
std::unordered_map<uint32_t, uint32_t> accessChainIds;
for (auto ins : code) {
if (ins.opCode() == spv::OpAccessChain
|| ins.opCode() == spv::OpInBoundsAccessChain) {
uint32_t depth = ins.length() - 4;
if (ins.arg(3) == inputVarId) {
// Access chains accessing the variable directly
ins.setArg(1, privateTypes.at(depth).first);
accessChainIds.insert({ ins.arg(2), depth });
} else {
// Access chains derived from the variable
auto entry = accessChainIds.find(ins.arg(2));
if (entry != accessChainIds.end()) {
depth += entry->second;
ins.setArg(1, privateTypes.at(depth).first);
accessChainIds.insert({ ins.arg(2), depth });
}
}
}
}
}
DxvkShaderStageInfo::DxvkShaderStageInfo(const DxvkDevice* device)
: m_device(device) {
}
void DxvkShaderStageInfo::addStage(
VkShaderStageFlagBits stage,
SpirvCodeBuffer&& code,
const VkSpecializationInfo* specInfo) {
// Take ownership of the SPIR-V code buffer
auto& codeBuffer = m_codeBuffers[m_stageCount];
codeBuffer = std::move(code);
// For graphics pipelines, as long as graphics pipeline libraries are
// enabled, we do not need to create a shader module object and can
// instead chain the create info to the shader stage info struct.
// For compute pipelines, this doesn't work and we still need a module.
auto& moduleInfo = m_moduleInfos[m_stageCount];
moduleInfo = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
moduleInfo.codeSize = codeBuffer.size();
moduleInfo.pCode = codeBuffer.data();
VkShaderModule shaderModule = VK_NULL_HANDLE;
if (!m_device->features().extGraphicsPipelineLibrary.graphicsPipelineLibrary || stage == VK_SHADER_STAGE_COMPUTE_BIT) {
auto vk = m_device->vkd();
if (vk->vkCreateShaderModule(vk->device(), &moduleInfo, nullptr, &shaderModule))
throw DxvkError("DxvkShaderStageInfo: Failed to create shader module");
}
// Set up shader stage info with the data provided
auto& stageInfo = m_stageInfos[m_stageCount];
stageInfo = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO };
if (!stageInfo.module)
stageInfo.pNext = &moduleInfo;
stageInfo.stage = stage;
stageInfo.module = shaderModule;
stageInfo.pName = "main";
stageInfo.pSpecializationInfo = specInfo;
m_stageCount++;
}
DxvkShaderStageInfo::~DxvkShaderStageInfo() {
auto vk = m_device->vkd();
for (uint32_t i = 0; i < m_stageCount; i++) {
if (m_stageInfos[i].module)
vk->vkDestroyShaderModule(vk->device(), m_stageInfos[i].module, nullptr);
}
}
DxvkShaderPipelineLibrary::DxvkShaderPipelineLibrary(
const DxvkDevice* device,
DxvkPipelineManager* manager,
const DxvkShader* shader,
const DxvkBindingLayoutObjects* layout)
: m_device (device),
m_cache (&manager->m_cache),
m_stats (&manager->m_stats),
m_shader (shader),
m_layout (layout) {
}
DxvkShaderPipelineLibrary::~DxvkShaderPipelineLibrary() {
auto vk = m_device->vkd();
vk->vkDestroyPipeline(vk->device(), m_pipeline, nullptr);
vk->vkDestroyPipeline(vk->device(), m_pipelineNoDepthClip, nullptr);
}
VkPipeline DxvkShaderPipelineLibrary::getPipelineHandle(
const DxvkShaderPipelineLibraryCompileArgs& args) {
std::lock_guard lock(m_mutex);
VkShaderStageFlagBits stage = VK_SHADER_STAGE_FRAGMENT_BIT;
if (m_shader)
stage = m_shader->info().stage;
VkPipeline& pipeline = (stage == VK_SHADER_STAGE_VERTEX_BIT && !args.depthClipEnable)
? m_pipelineNoDepthClip
: m_pipeline;
if (pipeline)
return pipeline;
switch (stage) {
case VK_SHADER_STAGE_VERTEX_BIT:
pipeline = compileVertexShaderPipeline(args);
break;
case VK_SHADER_STAGE_FRAGMENT_BIT:
pipeline = compileFragmentShaderPipeline();
break;
case VK_SHADER_STAGE_COMPUTE_BIT:
pipeline = compileComputeShaderPipeline();
break;
default:
// Should be unreachable
return VK_NULL_HANDLE;
}
if (args == DxvkShaderPipelineLibraryCompileArgs())
m_stats->numGraphicsLibraries += 1;
return pipeline;
}
void DxvkShaderPipelineLibrary::compilePipeline() {
// Just compile the pipeline with default args. Implicitly skips
// this step if another thread has compiled the pipeline in the
// meantime, in order to avoid duplicate work.
getPipelineHandle(DxvkShaderPipelineLibraryCompileArgs());
}
VkPipeline DxvkShaderPipelineLibrary::compileVertexShaderPipeline(
const DxvkShaderPipelineLibraryCompileArgs& args) {
auto vk = m_device->vkd();
DxvkShaderStageInfo stageInfo(m_device);
stageInfo.addStage(VK_SHADER_STAGE_VERTEX_BIT,
m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()), nullptr);
// Set up dynamic state. We do not know any pipeline state
// at this time, so make as much state dynamic as we can.
std::array<VkDynamicState, 5> dynamicStates = {{
VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT,
VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT_EXT,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_CULL_MODE_EXT,
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
}};
VkPipelineDynamicStateCreateInfo dyInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
dyInfo.dynamicStateCount = dynamicStates.size();
dyInfo.pDynamicStates = dynamicStates.data();
// All viewport state is dynamic, so we do not need to initialize this.
VkPipelineViewportStateCreateInfo vpInfo = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO };
// Set up rasterizer state. Depth bias, cull mode and front face are all
// dynamic, but we do not have dynamic state for depth bias enablement
// with the original version of VK_EXT_extended_dynamic_state, so always
// enable that. Do not support any polygon modes other than FILL.
VkPipelineRasterizationDepthClipStateCreateInfoEXT rsDepthClipInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_DEPTH_CLIP_STATE_CREATE_INFO_EXT };
VkPipelineRasterizationStateCreateInfo rsInfo = { VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO };
rsInfo.depthClampEnable = VK_TRUE;
rsInfo.rasterizerDiscardEnable = VK_FALSE;
rsInfo.polygonMode = VK_POLYGON_MODE_FILL;
rsInfo.depthBiasEnable = VK_TRUE;
rsInfo.lineWidth = 1.0f;
if (m_device->features().extDepthClipEnable.depthClipEnable) {
rsDepthClipInfo.pNext = std::exchange(rsInfo.pNext, &rsDepthClipInfo);
rsDepthClipInfo.depthClipEnable = args.depthClipEnable;
} else {
rsInfo.depthClampEnable = !args.depthClipEnable;
}
// Only the view mask is used as input, and since we do not use MultiView, it is always 0
VkPipelineRenderingCreateInfo rtInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR };
VkGraphicsPipelineLibraryCreateInfoEXT libInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, &rtInfo };
libInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT;
VkGraphicsPipelineCreateInfo info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &libInfo };
info.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
info.stageCount = stageInfo.getStageCount();
info.pStages = stageInfo.getStageInfos();
info.pViewportState = &vpInfo;
info.pRasterizationState = &rsInfo;
info.pDynamicState = &dyInfo;
info.layout = m_layout->getPipelineLayout(true);
info.basePipelineIndex = -1;
VkPipeline pipeline = VK_NULL_HANDLE;
if (vk->vkCreateGraphicsPipelines(vk->device(), m_cache->handle(), 1, &info, nullptr, &pipeline))
throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline");
return pipeline;
}
VkPipeline DxvkShaderPipelineLibrary::compileFragmentShaderPipeline() {
auto vk = m_device->vkd();
// Initialize code buffer. As a special case, it is possible that
// we have to deal with a null shader, but the pipeline library
// extension requires us to always specify a fragment shader.
DxvkShaderStageInfo stageInfo(m_device);
if (m_shader) {
stageInfo.addStage(VK_SHADER_STAGE_FRAGMENT_BIT,
m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()), nullptr);
} else {
stageInfo.addStage(VK_SHADER_STAGE_FRAGMENT_BIT,
SpirvCodeBuffer(dxvk_dummy_frag), nullptr);
}
// Set up dynamic state. We do not know any pipeline state
// at this time, so make as much state dynamic as we can.
uint32_t dynamicStateCount = 0;
std::array<VkDynamicState, 10> dynamicStates;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_WRITE_MASK;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_REFERENCE;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_STENCIL_OP_EXT;
if (m_device->features().core.features.depthBounds) {
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT;
dynamicStates[dynamicStateCount++] = VK_DYNAMIC_STATE_DEPTH_BOUNDS;
}
VkPipelineDynamicStateCreateInfo dyInfo = { VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO };
dyInfo.dynamicStateCount = dynamicStateCount;
dyInfo.pDynamicStates = dynamicStates.data();
// Set up multisample state. If sample shading is enabled, assume that
// we only have one sample enabled, with a non-zero sample mask and no
// alpha-to-coverage.
uint32_t msSampleMask = 0x1;
VkPipelineMultisampleStateCreateInfo msInfo = { VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO };
msInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
msInfo.pSampleMask = &msSampleMask;
msInfo.sampleShadingEnable = VK_TRUE;
msInfo.minSampleShading = 1.0f;
// All depth-stencil state is dynamic, so no need to initialize this.
// Depth bounds testing is disabled on devices which don't support it.
VkPipelineDepthStencilStateCreateInfo dsInfo = { VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO };
// Only the view mask is used as input, and since we do not use MultiView, it is always 0
VkPipelineRenderingCreateInfo rtInfo = { VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR };
VkGraphicsPipelineLibraryCreateInfoEXT libInfo = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_LIBRARY_CREATE_INFO_EXT, &rtInfo };
libInfo.flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT;
VkGraphicsPipelineCreateInfo info = { VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, &libInfo };
info.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
info.stageCount = stageInfo.getStageCount();
info.pStages = stageInfo.getStageInfos();
info.pDepthStencilState = &dsInfo;
info.pDynamicState = &dyInfo;
info.layout = m_layout->getPipelineLayout(true);
info.basePipelineIndex = -1;
if (m_shader && m_shader->flags().test(DxvkShaderFlag::HasSampleRateShading))
info.pMultisampleState = &msInfo;
VkPipeline pipeline = VK_NULL_HANDLE;
if (vk->vkCreateGraphicsPipelines(vk->device(), m_cache->handle(), 1, &info, nullptr, &pipeline))
throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline");
return pipeline;
}
VkPipeline DxvkShaderPipelineLibrary::compileComputeShaderPipeline() {
auto vk = m_device->vkd();
DxvkShaderStageInfo stageInfo(m_device);
stageInfo.addStage(VK_SHADER_STAGE_COMPUTE_BIT,
m_shader->getCode(m_layout, DxvkShaderModuleCreateInfo()), nullptr);
// Compile the compute pipeline as normal
VkComputePipelineCreateInfo info = { VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO };
info.stage = *stageInfo.getStageInfos();
info.layout = m_layout->getPipelineLayout(false);
info.basePipelineIndex = -1;
VkPipeline pipeline = VK_NULL_HANDLE;
if (vk->vkCreateComputePipelines(vk->device(), m_cache->handle(), 1, &info, nullptr, &pipeline))
throw DxvkError("DxvkShaderPipelineLibrary: Failed to create compute pipeline");
return pipeline;
}
}