blob: 2b76076f50973d410fc56999e7db2d35ea958c74 [file]
/*
* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (c) 2015-2026 Google, 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 "../framework/layer_validation_tests.h"
#include "../framework/pipeline_helper.h"
class PositiveShaderPushConstants : public VkLayerTest {};
TEST_F(PositiveShaderPushConstants, OverlappingPushConstantRange) {
TEST_DESCRIPTION("Test overlapping push-constant ranges.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
const char* const vsSource = R"glsl(
#version 450
layout(push_constant, std430) uniform foo { float x[8]; } constants;
void main(){
gl_Position = vec4(constants.x[0]);
}
)glsl";
const char* const fsSource = R"glsl(
#version 450
layout(push_constant, std430) uniform foo { float x[4]; } constants;
layout(location=0) out vec4 o;
void main(){
o = vec4(constants.x[0]);
}
)glsl";
VkShaderObj vs(*m_device, vsSource, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT);
VkPushConstantRange push_constant_ranges[2]{{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 8},
{VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float) * 4}};
VkPipelineLayoutCreateInfo const pipeline_layout_info{
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 2, push_constant_ranges};
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ci_ = pipeline_layout_info;
pipe.CreateGraphicsPipeline();
}
TEST_F(PositiveShaderPushConstants, MultipleEntryPointVert) {
TEST_DESCRIPTION("Test push-constant only being used by single entrypoint.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
// #version 450
// layout(push_constant, std430) uniform foo { float x; } consts;
// void main(){
// gl_Position = vec4(consts.x);
// }
//
// #version 450
// layout(location=0) out vec4 o;
// void main(){
// o = vec4(1.0);
// }
const std::string source_body = R"(
OpExecutionMode %main_f OriginUpperLeft
OpSource GLSL 450
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
OpDecorate %gl_PerVertex Block
OpMemberDecorate %foo 0 Offset 0
OpDecorate %foo Block
OpDecorate %out_frag Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr_float_uint_1 = OpTypeArray %float %uint_1
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
%out_vert = OpVariable %_ptr_Output_gl_PerVertex Output
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%foo = OpTypeStruct %float
%_ptr_PushConstant_foo = OpTypePointer PushConstant %foo
%consts = OpVariable %_ptr_PushConstant_foo PushConstant
%_ptr_PushConstant_float = OpTypePointer PushConstant %float
%_ptr_Output_v4float = OpTypePointer Output %v4float
%out_frag = OpVariable %_ptr_Output_v4float Output
%float_1 = OpConstant %float 1
%vec_1_0 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%main_v = OpFunction %void None %3
%label_v = OpLabel
%20 = OpAccessChain %_ptr_PushConstant_float %consts %int_0
%21 = OpLoad %float %20
%22 = OpCompositeConstruct %v4float %21 %21 %21 %21
%24 = OpAccessChain %_ptr_Output_v4float %out_vert %int_0
OpStore %24 %22
OpReturn
OpFunctionEnd
%main_f = OpFunction %void None %3
%label_f = OpLabel
OpStore %out_frag %vec_1_0
OpReturn
OpFunctionEnd
)";
std::string vert_first = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main_v "main_v" %out_vert
OpEntryPoint Fragment %main_f "main_f" %out_frag
)" + source_body;
std::string frag_first = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main_f "main_f" %out_frag
OpEntryPoint Vertex %main_v "main_v" %out_vert
)" + source_body;
VkPushConstantRange push_constant_ranges[1]{{VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float)}};
VkPipelineLayoutCreateInfo const pipeline_layout_info{
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, push_constant_ranges};
// Vertex entry point first
{
VkShaderObj vs(*m_device, vert_first.c_str(), VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_v");
VkShaderObj fs(*m_device, vert_first.c_str(), VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_f");
const auto set_info = [&](CreatePipelineHelper& helper) {
helper.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
helper.pipeline_layout_ci_ = pipeline_layout_info;
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
// Fragment entry point first
{
VkShaderObj vs(*m_device, frag_first.c_str(), VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_v");
VkShaderObj fs(*m_device, frag_first.c_str(), VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_f");
const auto set_info = [&](CreatePipelineHelper& helper) {
helper.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
helper.pipeline_layout_ci_ = pipeline_layout_info;
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
}
TEST_F(PositiveShaderPushConstants, MultipleEntryPointFrag) {
TEST_DESCRIPTION("Test push-constant only being used by single entrypoint.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
// #version 450
// void main(){
// gl_Position = vec4(1.0);
// }
//
// #version 450
// layout(push_constant, std430) uniform foo { float x; } consts;
// layout(location=0) out vec4 o;
// void main(){
// o = vec4(consts.x);
// }
const std::string source_body = R"(
OpExecutionMode %main_f OriginUpperLeft
OpSource GLSL 450
OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
OpDecorate %gl_PerVertex Block
OpDecorate %out_frag Location 0
OpMemberDecorate %foo 0 Offset 0
OpDecorate %foo Block
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_arr_float_uint_1 = OpTypeArray %float %uint_1
%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
%out_vert = OpVariable %_ptr_Output_gl_PerVertex Output
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%float_1 = OpConstant %float 1
%17 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
%_ptr_Output_v4float = OpTypePointer Output %v4float
%out_frag = OpVariable %_ptr_Output_v4float Output
%foo = OpTypeStruct %float
%_ptr_PushConstant_foo = OpTypePointer PushConstant %foo
%consts = OpVariable %_ptr_PushConstant_foo PushConstant
%_ptr_PushConstant_float = OpTypePointer PushConstant %float
%main_v = OpFunction %void None %3
%label_v = OpLabel
%19 = OpAccessChain %_ptr_Output_v4float %out_vert %int_0
OpStore %19 %17
OpReturn
OpFunctionEnd
%main_f = OpFunction %void None %3
%label_f = OpLabel
%26 = OpAccessChain %_ptr_PushConstant_float %consts %int_0
%27 = OpLoad %float %26
%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
OpStore %out_frag %28
OpReturn
OpFunctionEnd
)";
std::string vert_first = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main_v "main_v" %out_vert
OpEntryPoint Fragment %main_f "main_f" %out_frag
)" + source_body;
std::string frag_first = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main_f "main_f" %out_frag
OpEntryPoint Vertex %main_v "main_v" %out_vert
)" + source_body;
VkPushConstantRange push_constant_ranges[1]{{VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float)}};
VkPipelineLayoutCreateInfo const pipeline_layout_info{
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, push_constant_ranges};
// Vertex entry point first
{
VkShaderObj vs(*m_device, vert_first.c_str(), VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_v");
VkShaderObj fs(*m_device, vert_first.c_str(), VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_f");
const auto set_info = [&](CreatePipelineHelper& helper) {
helper.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
helper.pipeline_layout_ci_ = pipeline_layout_info;
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
// Fragment entry point first
{
VkShaderObj vs(*m_device, frag_first.c_str(), VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_v");
VkShaderObj fs(*m_device, frag_first.c_str(), VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM, nullptr,
"main_f");
const auto set_info = [&](CreatePipelineHelper& helper) {
helper.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
helper.pipeline_layout_ci_ = pipeline_layout_info;
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
}
TEST_F(PositiveShaderPushConstants, CompatibilityGraphicsOnly) {
TEST_DESCRIPTION("Based on verified valid examples from internal Vulkan Spec issue #2168");
RETURN_IF_SKIP(Init());
InitRenderTarget();
const char* const vsSource = R"glsl(
#version 450
layout(push_constant, std430) uniform foo { layout(offset = 16) vec4 x; } constants;
void main(){
gl_Position = constants.x;
}
)glsl";
VkShaderObj vs(*m_device, vsSource, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
// range A and B are the same while range C is different
// All 3 ranges fit the range from the shader
const uint32_t pc_size = 32;
VkPushConstantRange range_a = {VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size};
VkPushConstantRange range_b = {VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size};
VkPushConstantRange range_c = {VK_SHADER_STAGE_VERTEX_BIT, 16, pc_size};
VkPipelineLayoutCreateInfo pipeline_layout_info_a = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, &range_a};
VkPipelineLayoutCreateInfo pipeline_layout_info_b = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, &range_b};
VkPipelineLayoutCreateInfo pipeline_layout_info_c = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, &range_c};
CreatePipelineHelper pipeline_helper_a(*this); // layout_a and range_a
CreatePipelineHelper pipeline_helper_b(*this); // layout_b and range_b
CreatePipelineHelper pipeline_helper_c(*this); // layout_c and range_c
pipeline_helper_a.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipeline_helper_a.pipeline_layout_ci_ = pipeline_layout_info_a;
pipeline_helper_a.CreateGraphicsPipeline();
pipeline_helper_b.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipeline_helper_b.pipeline_layout_ci_ = pipeline_layout_info_b;
pipeline_helper_b.CreateGraphicsPipeline();
pipeline_helper_c.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipeline_helper_c.pipeline_layout_ci_ = pipeline_layout_info_c;
pipeline_helper_c.CreateGraphicsPipeline();
// Easier to see in command buffers
const VkPipelineLayout layout_a = pipeline_helper_a.pipeline_layout_;
const VkPipelineLayout layout_b = pipeline_helper_b.pipeline_layout_;
const VkPipelineLayout layout_c = pipeline_helper_c.pipeline_layout_;
const VkPipeline pipeline_a = pipeline_helper_a;
const VkPipeline pipeline_b = pipeline_helper_b;
const VkPipeline pipeline_c = pipeline_helper_c;
const float data[16] = {}; // dummy data to match shader size
vkt::Buffer vbo(*m_device, sizeof(float) * 3, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
// case 1 - bind different layout with the same range
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_b);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 2 - bind layout with same range then push different range
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_b, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_b);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 3 - same range same layout then same range from a different layout and same range from the same layout
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_a);
vk::CmdPushConstants(m_command_buffer, layout_b, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 4 - same range same layout then diff range and same range update
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_a);
vk::CmdPushConstants(m_command_buffer, layout_c, VK_SHADER_STAGE_VERTEX_BIT, 16, pc_size, data);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 5 - update push constant bind different layout with the same range then bind correct layout
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_b);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_a);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 6 - update push constant then bind different layout with overlapping range then bind correct layout
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_c);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_a);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
// case 7 - bind different layout with different range then update push constant and bind correct layout
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_c);
vk::CmdPushConstants(m_command_buffer, layout_a, VK_SHADER_STAGE_VERTEX_BIT, 0, pc_size, data);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_a);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderPushConstants, StaticallyUnused) {
TEST_DESCRIPTION("Test cases where creating pipeline with no use of push constants but still has ranges in layout");
RETURN_IF_SKIP(Init());
InitRenderTarget();
// Create set of Pipeline Layouts that cover variations of ranges
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_VERTEX_BIT, 0, 4};
VkPipelineLayoutCreateInfo pipeline_layout_info = {
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr, 1, &push_constant_range};
const char* vsSourceUnused = R"glsl(
#version 450
layout(push_constant, std430) uniform foo { float x; } consts;
void main(){
gl_Position = vec4(1.0);
}
)glsl";
const char* vsSourceEmpty = R"glsl(
#version 450
void main(){
gl_Position = vec4(1.0);
}
)glsl";
VkShaderObj vsUnused(*m_device, vsSourceUnused, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj vsEmpty(*m_device, vsSourceEmpty, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
// Just in layout
CreatePipelineHelper pipeline_unused(*this);
pipeline_unused.shader_stages_ = {vsUnused.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipeline_unused.pipeline_layout_ci_ = pipeline_layout_info;
pipeline_unused.CreateGraphicsPipeline();
// Shader never had a reference
CreatePipelineHelper pipeline_empty(*this);
pipeline_empty.shader_stages_ = {vsEmpty.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipeline_empty.pipeline_layout_ci_ = pipeline_layout_info;
pipeline_empty.CreateGraphicsPipeline();
vkt::Buffer vbo(*m_device, sizeof(float) * 3, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
// Draw without ever pushing to the unused and empty pipelines
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_unused);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindVertexBuffers(m_command_buffer, 1, 1, &vbo.handle(), &kZeroDeviceSize);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_empty);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderPushConstants, OffsetVector) {
TEST_DESCRIPTION("Vector uses offset in the shader.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
const char* const vsSource = R"glsl(
#version 450
layout(push_constant) uniform Material {
layout(offset = 16) vec4 color;
}constants;
void main() {
gl_Position = constants.color;
}
)glsl";
VkShaderObj vs(*m_device, vsSource, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
// Set up a push constant range
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_VERTEX_BIT, 16, 32};
const vkt::PipelineLayout pipeline_layout(*m_device, {}, {push_constant_range});
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {}, {push_constant_range});
pipe.CreateGraphicsPipeline();
const float data[16] = {}; // dummy data to match shader size
m_command_buffer.Begin();
vk::CmdPushConstants(m_command_buffer, pipe.pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT, 16, 16, data);
m_command_buffer.End();
}
TEST_F(PositiveShaderPushConstants, PhysicalStorageBufferBasic) {
TEST_DESCRIPTION("Basic use of Physical Storage Buffers in a Push Constant block.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
RETURN_IF_SKIP(Init());
InitRenderTarget();
const char* const vsSource = R"glsl(
#version 450
#extension GL_EXT_buffer_reference : enable
#extension GL_EXT_scalar_block_layout : enable
layout(buffer_reference, buffer_reference_align=16, scalar) readonly buffer VectorBuffer {
float v;
};
layout(push_constant, scalar) uniform pc {
layout(offset = 16) VectorBuffer vb; // always 8 bytes
float extra; // offset == 24
} pcs;
void main() {
gl_Position = vec4(pcs.vb.v);
}
)glsl";
VkShaderObj vs(*m_device, vsSource, VK_SHADER_STAGE_VERTEX_BIT);
VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
// Use exact range
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_VERTEX_BIT, 16, 28};
const vkt::PipelineLayout pipeline_layout(*m_device, {}, {push_constant_range});
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {}, {push_constant_range});
pipe.CreateGraphicsPipeline();
const float data[12] = {}; // dummy data to match shader size
m_command_buffer.Begin();
vk::CmdPushConstants(m_command_buffer, pipe.pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT, 16, 12, data);
m_command_buffer.End();
}
TEST_F(PositiveShaderPushConstants, PhysicalStorageBufferVertFrag) {
TEST_DESCRIPTION("Reproduces Github issue #2467 and effectively #2465 as well.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
RETURN_IF_SKIP(Init());
InitRenderTarget();
const char* vertex_source = R"glsl(
#version 450
#extension GL_EXT_buffer_reference : enable
#extension GL_EXT_scalar_block_layout : enable
layout(buffer_reference, buffer_reference_align=16, scalar) readonly buffer VectorBuffer {
vec3 x;
vec3 y;
vec3 z;
};
layout(push_constant, scalar) uniform pc {
VectorBuffer vb; // only 8 bytes, just a pointer
} pcs;
void main() {
gl_Position = vec4(pcs.vb.x, 1.0);
}
)glsl";
VkShaderObj vs(*m_device, vertex_source, VK_SHADER_STAGE_VERTEX_BIT);
const char* fragment_source = R"glsl(
#version 450
#extension GL_EXT_buffer_reference : enable
#extension GL_EXT_scalar_block_layout : enable
layout(buffer_reference, buffer_reference_align=16, scalar) readonly buffer VectorBuffer {
vec3 v;
};
layout(push_constant, scalar) uniform pushConstants {
layout(offset=8) VectorBuffer vb;
} pcs;
layout(location=0) out vec4 o;
void main() {
o = vec4(pcs.vb.v, 1.0);
}
)glsl";
VkShaderObj fs(*m_device, fragment_source, VK_SHADER_STAGE_FRAGMENT_BIT);
std::array<VkPushConstantRange, 2> push_ranges;
push_ranges[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
push_ranges[0].size = sizeof(uint64_t);
push_ranges[0].offset = 0;
push_ranges[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
push_ranges[1].size = sizeof(uint64_t);
push_ranges[1].offset = sizeof(uint64_t);
VkPipelineLayoutCreateInfo const pipeline_layout_info{
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, nullptr, 0, 0, nullptr,
static_cast<uint32_t>(push_ranges.size()), push_ranges.data()};
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ci_ = pipeline_layout_info;
pipe.CreateGraphicsPipeline();
}
TEST_F(PositiveShaderPushConstants, MultipleStructs) {
TEST_DESCRIPTION("Test having multiple structs Push Constant structs, but only one is used.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
// Note - it is invalid SPIR-V for an entrypoint to have 2 Push Constant variables used. This is only valid because it is being
// ignored
//
// What this looks like:
//
// layout(push_constant) uniform pc_a { layout(offset = 32) vec4 x; } a;
// layout(push_constant) uniform pc_b { layout(offset = 16) vec4 x; } b;
const char* source = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %1 "main"
; used in range [32 - 48]
OpDecorate %struct_pc Block
OpMemberDecorate %struct_pc 0 Offset 32
; unused in range [16 - 32]
OpDecorate %struct_unused Block
OpMemberDecorate %struct_unused 0 Offset 16
%void = OpTypeVoid
%7 = OpTypeFunction %void
%uint = OpTypeInt 32 0
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint_0 = OpConstant %uint 0
%uint_2 = OpConstant %uint 2
%ptr_pc_float = OpTypePointer PushConstant %float
%struct_pc = OpTypeStruct %v4float
%ptr_pc_struct = OpTypePointer PushConstant %struct_pc
%var = OpVariable %ptr_pc_struct PushConstant
; Unused
; Vulkan 1.0 you do not need declare this in the OpEntryPoint
%struct_unused = OpTypeStruct %v4float
%ptr_unused = OpTypePointer PushConstant %struct_unused
%var_unused = OpVariable %ptr_unused PushConstant
%1 = OpFunction %void None %7
%16 = OpLabel
%17 = OpAccessChain %ptr_pc_float %var %uint_0 %uint_2
OpReturn
OpFunctionEnd
)";
VkShaderObj vs(*m_device, source, VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_ASM);
VkShaderObj fs(*m_device, kFragmentMinimalGlsl, VK_SHADER_STAGE_FRAGMENT_BIT);
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_VERTEX_BIT, 32, 16};
const vkt::PipelineLayout pipeline_layout(*m_device, {}, {push_constant_range});
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {}, {push_constant_range});
pipe.CreateGraphicsPipeline();
const float data[16] = {};
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdPushConstants(m_command_buffer, pipe.pipeline_layout_, VK_SHADER_STAGE_VERTEX_BIT, 32, 16, data);
vk::CmdDraw(m_command_buffer, 1, 0, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderPushConstants, SpecConstantSizeDefault) {
TEST_DESCRIPTION("Use SpecConstant to adjust size of Push Constant Block, but use default value");
RETURN_IF_SKIP(Init());
const char* cs_source = R"glsl(
#version 460
layout (constant_id = 2) const int my_array_size = 1;
layout (push_constant) uniform my_buf {
float my_array[my_array_size];
} pc;
void main() {
float a = pc.my_array[0];
}
)glsl";
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 32};
const vkt::PipelineLayout pipeline_layout(*m_device, {}, {push_constant_range});
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {}, {push_constant_range});
pipe.CreateComputePipeline();
}
TEST_F(PositiveShaderPushConstants, SpecConstantSizeSet) {
TEST_DESCRIPTION("Use SpecConstant to adjust size of Push Constant Block");
RETURN_IF_SKIP(Init());
const char* cs_source = R"glsl(
#version 460
layout (constant_id = 0) const int my_array_size = 256;
layout (push_constant) uniform my_buf {
float my_array[my_array_size];
} pc;
void main() {
float a = pc.my_array[0];
}
)glsl";
// Setting makes the VkPushConstantRange valid
uint32_t data = 1;
VkSpecializationMapEntry entry;
entry.constantID = 0;
entry.offset = 0;
entry.size = sizeof(uint32_t);
VkSpecializationInfo specialization_info = {};
specialization_info.mapEntryCount = 1;
specialization_info.pMapEntries = &entry;
specialization_info.dataSize = sizeof(uint32_t);
specialization_info.pData = &data;
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 16};
const vkt::PipelineLayout pipeline_layout(*m_device, {}, {push_constant_range});
CreateComputePipelineHelper pipe(*this);
pipe.cs_ =
VkShaderObj(*m_device, cs_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_0, SPV_SOURCE_GLSL, &specialization_info);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {}, {push_constant_range});
pipe.CreateComputePipeline();
}
TEST_F(PositiveShaderPushConstants, Storage8BitPointers) {
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9364");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::bufferDeviceAddress);
AddRequiredFeature(vkt::Feature::shaderInt8);
// storagePushConstant8 is not enabled
RETURN_IF_SKIP(Init());
// RWStructuredBuffer<int> result;
// [shader("compute")]
// [numthreads(1,1,1)]
// void main(uint8_t* input) {
// result[0] = (int)input[0];
// }
const char* spv_source = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int8
OpCapability Shader
OpExtension "SPV_KHR_physical_storage_buffer"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main" %result %entryPointParams
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %_ptr_PhysicalStorageBuffer_uchar ArrayStride 1
OpDecorate %EntryPointParams_std430 Block
OpMemberDecorate %EntryPointParams_std430 0 Offset 0
OpDecorate %_runtimearr_int ArrayStride 4
OpDecorate %RWStructuredBuffer Block
OpMemberDecorate %RWStructuredBuffer 0 Offset 0
OpDecorate %result Binding 0
OpDecorate %result DescriptorSet 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
%_runtimearr_int = OpTypeRuntimeArray %int
%RWStructuredBuffer = OpTypeStruct %_runtimearr_int
%_ptr_StorageBuffer_RWStructuredBuffer = OpTypePointer StorageBuffer %RWStructuredBuffer
%uchar = OpTypeInt 8 0
%_ptr_PhysicalStorageBuffer_uchar = OpTypePointer PhysicalStorageBuffer %uchar
%EntryPointParams_std430 = OpTypeStruct %_ptr_PhysicalStorageBuffer_uchar
%_ptr_PushConstant_EntryPointParams_std430 = OpTypePointer PushConstant %EntryPointParams_std430
%_ptr_PushConstant__ptr_PhysicalStorageBuffer_uchar = OpTypePointer PushConstant %_ptr_PhysicalStorageBuffer_uchar
%result = OpVariable %_ptr_StorageBuffer_RWStructuredBuffer StorageBuffer
%entryPointParams = OpVariable %_ptr_PushConstant_EntryPointParams_std430 PushConstant
%main = OpFunction %void None %3
%4 = OpLabel
%8 = OpAccessChain %_ptr_StorageBuffer_int %result %int_0 %int_0
%19 = OpAccessChain %_ptr_PushConstant__ptr_PhysicalStorageBuffer_uchar %entryPointParams %int_0
%20 = OpLoad %_ptr_PhysicalStorageBuffer_uchar %19
%21 = OpPtrAccessChain %_ptr_PhysicalStorageBuffer_uchar %20 %int_0
%22 = OpLoad %uchar %21 Aligned 1
%23 = OpSConvert %int %22
OpStore %8 %23
OpReturn
OpFunctionEnd
)";
VkPushConstantRange push_constant_range = {VK_SHADER_STAGE_COMPUTE_BIT, 0, 8};
OneOffDescriptorSet descriptor_set(m_device, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_ALL, nullptr}});
const vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_}, {push_constant_range});
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, spv_source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
pipe.cp_ci_.layout = pipeline_layout;
pipe.CreateComputePipeline();
}