blob: 0ff086c6426f3b6809a1655df62fc05f8f88b8be [file] [log] [blame]
/*
* Copyright (c) 2023-2025 The Khronos Group Inc.
* Copyright (c) 2023-2025 Valve Corporation
* Copyright (c) 2023-2025 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include "pipeline_helper.h"
CreatePipelineHelper::CreatePipelineHelper(VkLayerTest &test, void *pNext) : layer_test_(test) {
// default VkDevice, can be overwritten if multi-device tests
device_ = layer_test_.DeviceObj();
gp_ci_ = vku::InitStructHelper();
gp_ci_.pNext = pNext;
// InitDescriptorSetInfo
dsl_bindings_.emplace_back(VkDescriptorSetLayoutBinding{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr});
// InitInputAndVertexInfo
vi_ci_ = vku::InitStructHelper();
ia_ci_ = vku::InitStructHelper();
ia_ci_.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
// InitMultisampleInfo
ms_ci_ = vku::InitStructHelper();
ms_ci_.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
ms_ci_.sampleShadingEnable = VK_FALSE;
ms_ci_.minSampleShading = 1.0;
ms_ci_.pSampleMask = nullptr;
// InitPipelineLayoutInfo
pipeline_layout_ci_ = vku::InitStructHelper();
pipeline_layout_ci_.setLayoutCount = 1; // Not really changeable because InitState() sets exactly one pSetLayout
pipeline_layout_ci_.pSetLayouts = nullptr; // must bound after it is created
// InitViewportInfo
viewport_ = {0.0f, 0.0f, 64.0f, 64.0f, 0.0f, 1.0f};
scissor_ = {{0, 0}, {64, 64}};
vp_state_ci_ = vku::InitStructHelper();
vp_state_ci_.viewportCount = 1;
vp_state_ci_.pViewports = &viewport_;
vp_state_ci_.scissorCount = 1;
vp_state_ci_.pScissors = &scissor_;
// InitRasterizationInfo
rs_state_ci_ = vku::InitStructHelper();
rs_state_ci_.flags = 0;
rs_state_ci_.depthClampEnable = VK_FALSE;
rs_state_ci_.rasterizerDiscardEnable = VK_FALSE;
rs_state_ci_.polygonMode = VK_POLYGON_MODE_FILL;
rs_state_ci_.cullMode = VK_CULL_MODE_BACK_BIT;
rs_state_ci_.frontFace = VK_FRONT_FACE_CLOCKWISE;
rs_state_ci_.depthBiasEnable = VK_FALSE;
rs_state_ci_.lineWidth = 1.0F;
// InitLineRasterizationInfo
line_state_ci_ = vku::InitStructHelper();
line_state_ci_.lineRasterizationMode = VK_LINE_RASTERIZATION_MODE_DEFAULT;
line_state_ci_.stippledLineEnable = VK_FALSE;
line_state_ci_.lineStippleFactor = 0;
line_state_ci_.lineStipplePattern = 0;
if (test.IsExtensionsEnabled(VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME) ||
test.IsExtensionsEnabled(VK_KHR_LINE_RASTERIZATION_EXTENSION_NAME)) {
rs_state_ci_.pNext = &line_state_ci_;
}
// Init VkPipelineColorBlendAttachmentState
cb_attachments_.colorWriteMask =
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
// InitBlendStateInfo
cb_ci_ = vku::InitStructHelper();
cb_ci_.logicOpEnable = VK_FALSE;
cb_ci_.logicOp = VK_LOGIC_OP_COPY; // ignored if enable is VK_FALSE above
cb_ci_.attachmentCount = 1;
cb_ci_.pAttachments = &cb_attachments_;
for (int i = 0; i < 4; i++) {
cb_ci_.blendConstants[0] = 1.0F;
}
pc_ci_ = vku::InitStructHelper();
pc_ci_.flags = 0;
pc_ci_.initialDataSize = 0;
pc_ci_.pInitialData = nullptr;
InitShaderInfo();
// Color-only rendering in a subpass with no depth/stencil attachment
// Active Pipeline Shader Stages
// Vertex Shader
// Fragment Shader
// Required: Fixed-Function Pipeline Stages
// VkPipelineVertexInputStateCreateInfo
// VkPipelineInputAssemblyStateCreateInfo
// VkPipelineViewportStateCreateInfo
// VkPipelineRasterizationStateCreateInfo
// VkPipelineMultisampleStateCreateInfo
// VkPipelineColorBlendStateCreateInfo
gp_ci_.layout = VK_NULL_HANDLE;
gp_ci_.flags = VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT;
gp_ci_.pVertexInputState = &vi_ci_;
gp_ci_.pInputAssemblyState = &ia_ci_;
gp_ci_.pTessellationState = nullptr;
gp_ci_.pViewportState = &vp_state_ci_;
gp_ci_.pMultisampleState = &ms_ci_;
gp_ci_.pRasterizationState = &rs_state_ci_;
gp_ci_.pDepthStencilState = nullptr;
gp_ci_.pColorBlendState = &cb_ci_;
gp_ci_.pDynamicState = nullptr;
gp_ci_.renderPass = layer_test_.RenderPass();
}
CreatePipelineHelper::~CreatePipelineHelper() { Destroy(); }
void CreatePipelineHelper::InitShaderInfo() { ResetShaderInfo(kVertexMinimalGlsl, kFragmentMinimalGlsl); }
void CreatePipelineHelper::ResetShaderInfo(const char *vertex_shader_text, const char *fragment_shader_text) {
vs_ = std::make_unique<VkShaderObj>(*device_, vertex_shader_text, VK_SHADER_STAGE_VERTEX_BIT);
fs_ = std::make_unique<VkShaderObj>(*device_, fragment_shader_text, VK_SHADER_STAGE_FRAGMENT_BIT);
// We shouldn't need a fragment shader but add it to be able to run on more devices
shader_stages_ = {vs_->GetStageCreateInfo(), fs_->GetStageCreateInfo()};
}
void CreatePipelineHelper::VertexShaderOnly() {
shader_stages_.clear();
shader_stages_.push_back(vs_->GetStageCreateInfo());
}
void CreatePipelineHelper::Destroy() {
if (pipeline_cache_ != VK_NULL_HANDLE) {
vk::DestroyPipelineCache(device_->handle(), pipeline_cache_, nullptr);
pipeline_cache_ = VK_NULL_HANDLE;
}
if (pipeline_ != VK_NULL_HANDLE) {
vk::DestroyPipeline(device_->handle(), pipeline_, nullptr);
pipeline_ = VK_NULL_HANDLE;
}
}
// Designed for majority of cases that just need to simply add dynamic state
void CreatePipelineHelper::AddDynamicState(VkDynamicState dynamic_state) {
dynamic_states_.push_back(dynamic_state);
dyn_state_ci_ = vku::InitStructHelper();
dyn_state_ci_.pDynamicStates = dynamic_states_.data();
dyn_state_ci_.dynamicStateCount = dynamic_states_.size();
// Set here and don't have have to worry about late bind setting it
gp_ci_.pDynamicState = &dyn_state_ci_;
}
void CreatePipelineHelper::InitVertexInputLibInfo(void *p_next) {
gpl_info.emplace(vku::InitStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(p_next));
gpl_info->flags = VK_GRAPHICS_PIPELINE_LIBRARY_VERTEX_INPUT_INTERFACE_BIT_EXT;
gp_ci_ = vku::InitStructHelper(&gpl_info);
gp_ci_.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
gp_ci_.pVertexInputState = &vi_ci_;
gp_ci_.pInputAssemblyState = &ia_ci_;
gp_ci_.stageCount = 0;
shader_stages_.clear();
}
void CreatePipelineHelper::InitPreRasterLibInfo(const VkPipelineShaderStageCreateInfo *info, void *p_next) {
gpl_info.emplace(vku::InitStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(p_next));
gpl_info->flags = VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT;
gp_ci_ = vku::InitStructHelper(&gpl_info);
gp_ci_.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
gp_ci_.pViewportState = &vp_state_ci_;
gp_ci_.pRasterizationState = &rs_state_ci_;
// If using Dynamic Rendering, will need to be set to null
// otherwise needs to be shared across libraries in the same executable pipeline
gp_ci_.renderPass = layer_test_.RenderPass();
gp_ci_.subpass = 0;
gp_ci_.stageCount = 1; // default is just the Vertex shader
gp_ci_.pStages = info;
}
void CreatePipelineHelper::InitFragmentLibInfo(const VkPipelineShaderStageCreateInfo *info, void *p_next) {
gpl_info.emplace(vku::InitStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(p_next));
gpl_info->flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT;
gp_ci_ = vku::InitStructHelper(&gpl_info);
gp_ci_.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
// gp_ci_.pTessellationState = nullptr; // TODO
gp_ci_.pViewportState = &vp_state_ci_;
// If using Dynamic Rendering, will need to be set to null
// otherwise needs to be shared across libraries in the same executable pipeline
gp_ci_.renderPass = layer_test_.RenderPass();
gp_ci_.subpass = 0;
// TODO if renderPass is null, MS info is not needed
gp_ci_.pMultisampleState = &ms_ci_;
gp_ci_.stageCount = 1; // default is just the Fragment shader
gp_ci_.pStages = info;
}
void CreatePipelineHelper::InitFragmentOutputLibInfo(void *p_next) {
gpl_info.emplace(vku::InitStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(p_next));
gpl_info->flags = VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_OUTPUT_INTERFACE_BIT_EXT;
gp_ci_ = vku::InitStructHelper(&gpl_info);
gp_ci_.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR | VK_PIPELINE_CREATE_RETAIN_LINK_TIME_OPTIMIZATION_INFO_BIT_EXT;
gp_ci_.pColorBlendState = &cb_ci_;
gp_ci_.pMultisampleState = &ms_ci_;
gp_ci_.pRasterizationState = &rs_state_ci_;
// If using Dynamic Rendering, will need to be set to null
// otherwise needs to be shared across libraries in the same executable pipeline
gp_ci_.renderPass = layer_test_.RenderPass();
gp_ci_.subpass = 0;
gp_ci_.stageCount = 0;
shader_stages_.clear();
}
void CreatePipelineHelper::InitShaderLibInfo(std::vector<VkPipelineShaderStageCreateInfo> &info, void *p_next) {
gpl_info.emplace(vku::InitStruct<VkGraphicsPipelineLibraryCreateInfoEXT>(p_next));
gpl_info->flags =
VK_GRAPHICS_PIPELINE_LIBRARY_PRE_RASTERIZATION_SHADERS_BIT_EXT | VK_GRAPHICS_PIPELINE_LIBRARY_FRAGMENT_SHADER_BIT_EXT;
gp_ci_ = vku::InitStructHelper(&gpl_info);
gp_ci_.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR;
gp_ci_.pViewportState = &vp_state_ci_;
gp_ci_.pViewportState = &vp_state_ci_;
gp_ci_.pRasterizationState = &rs_state_ci_;
// If using Dynamic Rendering, will need to be set to null
// otherwise needs to be shared across libraries in the same executable pipeline
gp_ci_.renderPass = layer_test_.RenderPass();
gp_ci_.subpass = 0;
gp_ci_.pMultisampleState = &ms_ci_;
gp_ci_.stageCount = info.size();
gp_ci_.pStages = info.data();
}
void CreatePipelineHelper::InitPipelineCache() {
if (pipeline_cache_ != VK_NULL_HANDLE) {
vk::DestroyPipelineCache(device_->handle(), pipeline_cache_, nullptr);
}
VkResult err = vk::CreatePipelineCache(device_->handle(), &pc_ci_, NULL, &pipeline_cache_);
ASSERT_EQ(VK_SUCCESS, err);
}
void CreatePipelineHelper::LateBindPipelineInfo() {
// By value or dynamically located items must be late bound
if (gp_ci_.layout == VK_NULL_HANDLE) {
// Create a default descriptor and pipeline layout
if (pipeline_layout_ == VK_NULL_HANDLE) {
if (!descriptor_set_) {
// User can pass in own bindings
descriptor_set_.reset(new OneOffDescriptorSet(device_, dsl_bindings_));
ASSERT_TRUE(descriptor_set_->Initialized());
}
const std::vector<VkPushConstantRange> push_ranges(
pipeline_layout_ci_.pPushConstantRanges,
pipeline_layout_ci_.pPushConstantRanges + pipeline_layout_ci_.pushConstantRangeCount);
pipeline_layout_ = vkt::PipelineLayout(*device_, {&descriptor_set_->layout_}, push_ranges, pipeline_layout_ci_.flags);
}
gp_ci_.layout = pipeline_layout_;
}
if (gp_ci_.stageCount == 0) {
gp_ci_.stageCount = shader_stages_.size();
gp_ci_.pStages = shader_stages_.data();
}
if ((gp_ci_.pTessellationState == nullptr) && IsValidVkStruct(tess_ci_)) {
gp_ci_.pTessellationState = &tess_ci_;
}
if ((gp_ci_.pDynamicState == nullptr) && IsValidVkStruct(dyn_state_ci_)) {
gp_ci_.pDynamicState = &dyn_state_ci_;
}
if ((gp_ci_.pDepthStencilState == nullptr) && IsValidVkStruct(ds_ci_)) {
gp_ci_.pDepthStencilState = &ds_ci_;
}
}
VkResult CreatePipelineHelper::CreateGraphicsPipeline(bool do_late_bind, bool no_cache) {
InitPipelineCache();
if (do_late_bind) {
LateBindPipelineInfo();
}
return vk::CreateGraphicsPipelines(device_->handle(), no_cache ? VK_NULL_HANDLE : pipeline_cache_, 1, &gp_ci_, NULL,
&pipeline_);
}
CreateComputePipelineHelper::CreateComputePipelineHelper(vkt::Device& device, void *pNext) {
// default VkDevice, can be overwritten if multi-device tests
device_ = &device;
cp_ci_ = vku::InitStructHelper();
cp_ci_.pNext = pNext;
// InitDescriptorSetInfo
dsl_bindings_.emplace_back(VkDescriptorSetLayoutBinding{0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr});
// InitPipelineLayoutInfo
pipeline_layout_ci_ = vku::InitStructHelper();
pipeline_layout_ci_.setLayoutCount = 1; // Not really changeable because InitState() sets exactly one pSetLayout
pipeline_layout_ci_.pSetLayouts = nullptr; // must bound after it is created
// InitPipelineCacheInfo
pc_ci_ = vku::InitStructHelper();
pc_ci_.flags = 0;
pc_ci_.initialDataSize = 0;
pc_ci_.pInitialData = nullptr;
cs_ = VkShaderObj(device, kMinimalShaderGlsl, VK_SHADER_STAGE_COMPUTE_BIT);
cp_ci_.flags = 0;
cp_ci_.layout = VK_NULL_HANDLE;
}
CreateComputePipelineHelper::CreateComputePipelineHelper(VkLayerTest &test, void *pNext)
: CreateComputePipelineHelper(*test.DeviceObj(), pNext) {}
CreateComputePipelineHelper::CreateComputePipelineHelper(CreateComputePipelineHelper &&rhs) noexcept { *this = std::move(rhs); }
CreateComputePipelineHelper &CreateComputePipelineHelper::operator=(CreateComputePipelineHelper &&rhs) noexcept {
dsl_bindings_ = std::move(rhs.dsl_bindings_);
descriptor_set_ = std::move(rhs.descriptor_set_);
pipeline_layout_ci_ = rhs.pipeline_layout_ci_;
rhs.pipeline_layout_ci_ = {};
pipeline_layout_ = std::move(rhs.pipeline_layout_);
cp_ci_ = rhs.cp_ci_;
rhs.cp_ci_ = {};
pc_ci_ = rhs.pc_ci_;
rhs.pc_ci_ = {};
pipeline_cache_ = rhs.pipeline_cache_;
rhs.pipeline_cache_ = VK_NULL_HANDLE;
cs_ = std::move(rhs.cs_);
override_skip_ = rhs.override_skip_;
rhs.override_skip_ = false;
device_ = rhs.device_;
rhs.device_ = nullptr;
pipeline_ = rhs.pipeline_;
rhs.pipeline_ = VK_NULL_HANDLE;
return *this;
}
CreateComputePipelineHelper::~CreateComputePipelineHelper() noexcept { Destroy(); }
void CreateComputePipelineHelper::InitPipelineCache() {
if (pipeline_cache_ != VK_NULL_HANDLE) {
vk::DestroyPipelineCache(device_->handle(), pipeline_cache_, nullptr);
}
VkResult err = vk::CreatePipelineCache(device_->handle(), &pc_ci_, NULL, &pipeline_cache_);
ASSERT_EQ(VK_SUCCESS, err);
}
void CreateComputePipelineHelper::Destroy() {
if (pipeline_cache_ != VK_NULL_HANDLE) {
vk::DestroyPipelineCache(device_->handle(), pipeline_cache_, nullptr);
pipeline_cache_ = VK_NULL_HANDLE;
}
if (pipeline_ != VK_NULL_HANDLE) {
vk::DestroyPipeline(device_->handle(), pipeline_, nullptr);
pipeline_ = VK_NULL_HANDLE;
}
}
void CreateComputePipelineHelper::LateBindPipelineInfo() {
// By value or dynamically located items must be late bound
if (cp_ci_.layout == VK_NULL_HANDLE) {
// Create a default descriptor and pipeline layout
if (pipeline_layout_ == VK_NULL_HANDLE) {
if (!descriptor_set_.Initialized()) {
// User can pass in own bindings
descriptor_set_ = OneOffDescriptorSet(device_, dsl_bindings_);
ASSERT_TRUE(descriptor_set_.Initialized());
}
const std::vector<VkPushConstantRange> push_ranges(
pipeline_layout_ci_.pPushConstantRanges,
pipeline_layout_ci_.pPushConstantRanges + pipeline_layout_ci_.pushConstantRangeCount);
pipeline_layout_ = vkt::PipelineLayout(*device_, {&descriptor_set_.layout_}, push_ranges, pipeline_layout_ci_.flags);
}
cp_ci_.layout = pipeline_layout_;
}
cp_ci_.stage = cs_.GetStageCreateInfo();
}
VkResult CreateComputePipelineHelper::CreateComputePipeline(bool do_late_bind, bool no_cache) {
InitPipelineCache();
if (do_late_bind) {
LateBindPipelineInfo();
}
return vk::CreateComputePipelines(device_->handle(), no_cache ? VK_NULL_HANDLE : pipeline_cache_, 1, &cp_ci_, NULL, &pipeline_);
}
namespace vkt {
GraphicsPipelineLibraryStage::GraphicsPipelineLibraryStage(vvl::span<const uint32_t> spv, VkShaderStageFlagBits stage) : spv(spv) {
shader_ci = vku::InitStructHelper();
shader_ci.codeSize = spv.size() * sizeof(uint32_t);
shader_ci.pCode = spv.data();
stage_ci = vku::InitStructHelper(&shader_ci);
stage_ci.stage = stage;
stage_ci.module = VK_NULL_HANDLE;
stage_ci.pName = "main";
}
SimpleGPL::SimpleGPL(VkLayerTest &test, VkPipelineLayout layout, const char *vertex_shader, const char *fragment_shader)
: vertex_input_lib_(test), pre_raster_lib_(test), frag_shader_lib_(test), frag_out_lib_(test) {
auto device = test.DeviceObj();
vertex_input_lib_.InitVertexInputLibInfo();
vertex_input_lib_.CreateGraphicsPipeline(false);
// For GPU-AV tests this shrinks things so only a single fragment is executed
VkViewport viewport = {0, 0, 1, 1, 0, 1};
VkRect2D scissor = {{0, 0}, {1, 1}};
const auto vs_spv = test.GLSLToSPV(VK_SHADER_STAGE_VERTEX_BIT, vertex_shader ? vertex_shader : kVertexMinimalGlsl);
vkt::GraphicsPipelineLibraryStage vs_stage(vs_spv, VK_SHADER_STAGE_VERTEX_BIT);
pre_raster_lib_.InitPreRasterLibInfo(&vs_stage.stage_ci);
pre_raster_lib_.vp_state_ci_.pViewports = &viewport;
pre_raster_lib_.vp_state_ci_.pScissors = &scissor;
pre_raster_lib_.gp_ci_.layout = layout;
pre_raster_lib_.CreateGraphicsPipeline();
const auto fs_spv = test.GLSLToSPV(VK_SHADER_STAGE_FRAGMENT_BIT, fragment_shader ? fragment_shader : kFragmentMinimalGlsl);
vkt::GraphicsPipelineLibraryStage fs_stage(fs_spv, VK_SHADER_STAGE_FRAGMENT_BIT);
frag_shader_lib_.InitFragmentLibInfo(&fs_stage.stage_ci);
frag_shader_lib_.gp_ci_.layout = layout;
frag_shader_lib_.CreateGraphicsPipeline(false);
frag_out_lib_.InitFragmentOutputLibInfo();
frag_out_lib_.CreateGraphicsPipeline(false);
VkPipeline libraries[4] = {
vertex_input_lib_,
pre_raster_lib_,
frag_shader_lib_,
frag_out_lib_,
};
VkPipelineLibraryCreateInfoKHR link_info = vku::InitStructHelper();
link_info.libraryCount = size32(libraries);
link_info.pLibraries = libraries;
VkGraphicsPipelineCreateInfo exe_pipe_ci = vku::InitStructHelper(&link_info);
exe_pipe_ci.layout = layout;
pipe_.Init(*device, exe_pipe_ci);
}
} // namespace vkt