blob: f3c8b84a3ae0e18578c28c0deef8cdb1dad5c18f [file] [log] [blame]
/*
* Copyright (c) 2015-2025 The Khronos Group Inc.
* Copyright (c) 2015-2025 Valve Corporation
* Copyright (c) 2015-2025 LunarG, Inc.
* Copyright (c) 2015-2025 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 PositiveShaderLimits : public VkLayerTest {};
TEST_F(PositiveShaderLimits, MaxSampleMaskWords) {
TEST_DESCRIPTION("Test limit of maxSampleMaskWords.");
RETURN_IF_SKIP(Init());
InitRenderTarget();
// Valid input of sample mask
const char *fs_source = R"glsl(
#version 450
layout(location = 0) out vec4 uFragColor;
void main(){
int y = gl_SampleMaskIn[0];
uFragColor = vec4(0,1,0,1) * y;
}
)glsl";
VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT);
const auto validPipeline = [&](CreatePipelineHelper &helper) {
helper.shader_stages_ = {helper.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
};
CreatePipelineHelper::OneshotTest(*this, validPipeline, kErrorBit);
}
TEST_F(PositiveShaderLimits, ComputeSharedMemoryWorkgroupMemoryExplicitLayout) {
TEST_DESCRIPTION(
"Validate compute shader shared memory does not exceed maxComputeSharedMemorySize when using "
"VK_KHR_workgroup_memory_explicit_layout");
// More background: When workgroupMemoryExplicitLayout is enabled and there are 2 or more structs, the
// maxComputeSharedMemorySize is the MAX of the structs since they share the same WorkGroup memory. Test makes sure validation
// is not doing an ADD and correctly doing a MAX operation in this case.
// need at least SPIR-V 1.4 for SPV_KHR_workgroup_memory_explicit_layout
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_KHR_WORKGROUP_MEMORY_EXPLICIT_LAYOUT_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::workgroupMemoryExplicitLayout);
RETURN_IF_SKIP(Init());
const uint32_t max_shared_memory_size = m_device->Physical().limits_.maxComputeSharedMemorySize;
const uint32_t max_shared_vec4 = max_shared_memory_size / 16;
std::ostringstream csSource;
csSource << R"glsl(
#version 450
#extension GL_EXT_shared_memory_block : enable
// Both structs by themselves are 16 bytes less than the max
shared X {
vec4 x1[)glsl";
csSource << (max_shared_vec4 - 1);
csSource << R"glsl(];
vec4 x2;
};
void main() {
x2.x = 0.0f; // prevent dead-code elimination
}
)glsl";
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, csSource.str().c_str(), VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2);
pipe.CreateComputePipeline();
}
TEST_F(PositiveShaderLimits, ComputeSharedMemoryAtLimit) {
TEST_DESCRIPTION("Validate compute shader shared memory is valid at the exact maxComputeSharedMemorySize");
RETURN_IF_SKIP(Init());
const uint32_t max_shared_memory_size = m_device->Physical().limits_.maxComputeSharedMemorySize;
const uint32_t max_shared_ints = max_shared_memory_size / 4;
std::ostringstream csSource;
csSource << R"glsl(
#version 450
shared int a[)glsl";
csSource << (max_shared_ints);
csSource << R"glsl(];
void main(){}
)glsl";
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, csSource.str().c_str(), VK_SHADER_STAGE_COMPUTE_BIT);
pipe.CreateComputePipeline();
}
TEST_F(PositiveShaderLimits, ComputeSharedMemoryBooleanAtLimit) {
TEST_DESCRIPTION("Validate compute shader shared memory is valid at the exact maxComputeSharedMemorySize using Booleans");
RETURN_IF_SKIP(Init());
const uint32_t max_shared_memory_size = m_device->Physical().limits_.maxComputeSharedMemorySize;
// "Boolean values considered as 32-bit integer values for the purpose of this calculation."
const uint32_t max_shared_bools = max_shared_memory_size / 4;
std::ostringstream csSource;
csSource << R"glsl(
#version 450
shared bool a[)glsl";
csSource << (max_shared_bools);
csSource << R"glsl(];
void main(){}
)glsl";
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, csSource.str().c_str(), VK_SHADER_STAGE_COMPUTE_BIT);
pipe.CreateComputePipeline();
}
TEST_F(PositiveShaderLimits, MeshSharedMemoryAtLimit) {
TEST_DESCRIPTION("Validate mesh shader shared memory is valid at the exact maxMeshSharedMemorySize");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_EXT_MESH_SHADER_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::meshShader);
RETURN_IF_SKIP(Init());
InitRenderTarget();
VkPhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = vku::InitStructHelper();
GetPhysicalDeviceProperties2(mesh_shader_properties);
const uint32_t max_shared_memory_size = mesh_shader_properties.maxMeshSharedMemorySize;
const uint32_t max_shared_ints = max_shared_memory_size / 4;
std::ostringstream mesh_source;
mesh_source << R"glsl(
#version 460
#extension GL_EXT_mesh_shader : require
layout(max_vertices = 3, max_primitives=1) out;
layout(triangles) out;
shared int a[)glsl";
mesh_source << (max_shared_ints);
mesh_source << R"glsl(];
void main(){}
)glsl";
VkShaderObj mesh(*m_device, mesh_source.str().c_str(), VK_SHADER_STAGE_MESH_BIT_EXT, SPV_ENV_VULKAN_1_2);
const auto set_info = [&](CreatePipelineHelper &helper) {
helper.shader_stages_ = {helper.fs_->GetStageCreateInfo(), mesh.GetStageCreateInfo()};
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
TEST_F(PositiveShaderLimits, TaskSharedMemoryAtLimit) {
TEST_DESCRIPTION("Validate Task shader shared memory is valid at the exact maxTaskSharedMemorySize");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_EXT_MESH_SHADER_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::meshShader);
AddRequiredFeature(vkt::Feature::taskShader);
RETURN_IF_SKIP(Init());
InitRenderTarget();
VkPhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = vku::InitStructHelper();
GetPhysicalDeviceProperties2(mesh_shader_properties);
const uint32_t max_shared_memory_size = mesh_shader_properties.maxMeshSharedMemorySize;
const uint32_t max_shared_ints = max_shared_memory_size / 4;
std::ostringstream task_source;
task_source << R"glsl(
#version 460
#extension GL_EXT_mesh_shader : require
shared int a[)glsl";
task_source << (max_shared_ints);
task_source << R"glsl(];
void main(){
EmitMeshTasksEXT(32u, 1u, 1u);
}
)glsl";
VkShaderObj task(*m_device, task_source.str().c_str(), VK_SHADER_STAGE_TASK_BIT_EXT, SPV_ENV_VULKAN_1_2);
VkShaderObj mesh(*m_device, kMeshMinimalGlsl, VK_SHADER_STAGE_MESH_BIT_EXT, SPV_ENV_VULKAN_1_2);
const auto set_info = [&](CreatePipelineHelper &helper) {
helper.shader_stages_ = {task.GetStageCreateInfo(), mesh.GetStageCreateInfo()};
};
CreatePipelineHelper::OneshotTest(*this, set_info, kErrorBit);
}
TEST_F(PositiveShaderLimits, MaxFragmentDualSrcAttachments) {
TEST_DESCRIPTION("Test maxFragmentDualSrcAttachments when blend is not enabled.");
SetTargetApiVersion(VK_API_VERSION_1_1);
AddRequiredFeature(vkt::Feature::independentBlend);
AddRequiredFeature(vkt::Feature::dualSrcBlend);
RETURN_IF_SKIP(Init());
const uint32_t count = m_device->Physical().limits_.maxFragmentDualSrcAttachments + 1;
if (count != 2) {
GTEST_SKIP() << "Test is designed for a maxFragmentDualSrcAttachments of 1";
}
InitRenderTarget(count);
const char *fs_src = R"glsl(
#version 460
layout(location = 0) out vec4 c0;
layout(location = 1) out vec4 c1;
void main() {
c0 = vec4(0.0f);
c1 = vec4(0.0f);
}
)glsl";
VkShaderObj fs(*m_device, fs_src, VK_SHADER_STAGE_FRAGMENT_BIT);
VkPipelineColorBlendAttachmentState color_blend[2] = {};
color_blend[0] = DefaultColorBlendAttachmentState();
color_blend[1] = DefaultColorBlendAttachmentState();
color_blend[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC1_COLOR; // bad!
color_blend[0].blendEnable = false; // but ignored
CreatePipelineHelper pipe(*this);
pipe.cb_ci_.attachmentCount = 2;
pipe.cb_ci_.pAttachments = color_blend;
pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.CreateGraphicsPipeline();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderLimits, MaxFragmentDualSrcAttachmentsDynamicEnabled) {
TEST_DESCRIPTION("Test maxFragmentDualSrcAttachments when blend is not enabled.");
SetTargetApiVersion(VK_API_VERSION_1_1);
AddRequiredExtensions(VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::dualSrcBlend);
AddRequiredFeature(vkt::Feature::extendedDynamicState3ColorBlendEnable);
AddRequiredFeature(vkt::Feature::independentBlend);
RETURN_IF_SKIP(Init());
const uint32_t count = m_device->Physical().limits_.maxFragmentDualSrcAttachments + 1;
if (count != 2) {
GTEST_SKIP() << "Test is designed for a maxFragmentDualSrcAttachments of 1";
}
InitRenderTarget(count);
const char *fs_src = R"glsl(
#version 460
layout(location = 0) out vec4 c0;
layout(location = 1) out vec4 c1;
void main() {
c0 = vec4(0.0f);
c1 = vec4(0.0f);
}
)glsl";
VkShaderObj fs(*m_device, fs_src, VK_SHADER_STAGE_FRAGMENT_BIT);
VkPipelineColorBlendAttachmentState color_blend[2] = {};
color_blend[0] = DefaultColorBlendAttachmentState();
color_blend[1] = DefaultColorBlendAttachmentState();
color_blend[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC1_COLOR; // bad!
CreatePipelineHelper pipe(*this);
pipe.cb_ci_.attachmentCount = 2;
pipe.cb_ci_.pAttachments = color_blend;
pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.AddDynamicState(VK_DYNAMIC_STATE_COLOR_BLEND_ENABLE_EXT);
pipe.CreateGraphicsPipeline();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
VkBool32 color_blend_enabled[2] = {VK_FALSE, VK_FALSE}; // disable any blending
vk::CmdSetColorBlendEnableEXT(m_command_buffer, 0, 2, color_blend_enabled);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderLimits, MaxFragmentOutputAttachments) {
RETURN_IF_SKIP(Init());
InitRenderTarget();
if (m_device->Physical().limits_.maxFragmentOutputAttachments != 4) {
GTEST_SKIP() << "maxFragmentOutputAttachments is not 4";
}
const char *fsSource = R"glsl(
#version 450
layout(location=0) out vec4 c0;
layout(location=1) out vec4 c1;
layout(location=2) out vec4 c2;
layout(location=3) out vec4 c3; // at limit
void main(){
c0 = vec4(1.0);
c1 = vec4(1.0);
c2 = vec4(1.0);
c3 = vec4(1.0);
}
)glsl";
VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.CreateGraphicsPipeline();
}
TEST_F(PositiveShaderLimits, MaxFragmentOutputAttachmentsArray) {
RETURN_IF_SKIP(Init());
InitRenderTarget();
if (m_device->Physical().limits_.maxFragmentOutputAttachments != 4) {
GTEST_SKIP() << "maxFragmentOutputAttachments is not 4";
}
const char *fsSource = R"glsl(
#version 450
layout(location=0) out vec4 c[4];
void main(){
c[3] = vec4(1.0);
c[0] = vec4(1.0);
}
)glsl";
VkShaderObj fs(*m_device, fsSource, VK_SHADER_STAGE_FRAGMENT_BIT);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {pipe.vs_->GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.CreateGraphicsPipeline();
}