blob: 50fc65d5a17a262ff9ac0291df91779884ab134b [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"
#include "../framework/descriptor_helper.h"
#include "error_message/log_message_type.h"
class PositiveShaderStorageImage : public VkLayerTest {};
TEST_F(PositiveShaderStorageImage, WriteMoreComponent) {
TEST_DESCRIPTION("Test writing to image with less components.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredFeature(vkt::Feature::shaderStorageImageExtendedFormats);
RETURN_IF_SKIP(Init());
// not valid GLSL, but would look like:
// layout(set = 0, binding = 0, Rg32ui) uniform uimage2D storageImage;
// imageStore(storageImage, ivec2(1, 1), uvec3(1, 1, 1));
//
// Rg32ui == 2-component but writing 3 texels to it
const char* source = R"(
OpCapability Shader
OpCapability StorageImageExtendedFormats
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%func = OpTypeFunction %void
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%image = OpTypeImage %uint 2D 0 0 0 2 Rg32ui
%ptr = OpTypePointer UniformConstant %image
%var = OpVariable %ptr UniformConstant
%v2int = OpTypeVector %int 2
%int_1 = OpConstant %int 1
%coord = OpConstantComposite %v2int %int_1 %int_1
%v3uint = OpTypeVector %uint 3
%uint_1 = OpConstant %uint 1
%texelU3 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
%main = OpFunction %void None %func
%label = OpLabel
%load = OpLoad %image %var
OpImageWrite %load %coord %texelU3 ZeroExtend
OpReturn
OpFunctionEnd
)";
OneOffDescriptorSet ds(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
});
const VkFormat format = VK_FORMAT_R32G32_UINT; // Rg32ui
if (!FormatFeaturesAreSupported(Gpu(), format, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
GTEST_SKIP() << "Format doesn't support storage image";
}
vkt::Image image(*m_device, 32, 32, format, VK_IMAGE_USAGE_STORAGE_BIT);
vkt::ImageView view = image.CreateView();
ds.WriteDescriptorImageInfo(0, view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_IMAGE_LAYOUT_GENERAL);
ds.UpdateDescriptorSets();
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&ds.layout_});
pipe.CreateComputePipeline();
m_command_buffer.Begin();
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.pipeline_layout_, 0, 1, &ds.set_, 0, nullptr);
vk::CmdDispatch(m_command_buffer, 1, 1, 1);
m_command_buffer.End();
}
TEST_F(PositiveShaderStorageImage, UnknownWriteMoreComponent) {
TEST_DESCRIPTION("Test writing to image with less components for Unknown for OpTypeImage.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredExtensions(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::shaderStorageImageExtendedFormats);
AddRequiredFeature(vkt::Feature::shaderStorageImageWriteWithoutFormat);
RETURN_IF_SKIP(Init());
// not valid GLSL, but would look like:
// layout(set = 0, binding = 0, Unknown) readonly uniform uimage2D storageImage;
// imageStore(storageImage, ivec2(1, 1), uvec3(1, 1, 1));
//
// Unknown will become a 2-component but writing 3 texels to it
const char* source = R"(
OpCapability Shader
OpCapability StorageImageExtendedFormats
OpCapability StorageImageWriteWithoutFormat
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %var NonReadable
%void = OpTypeVoid
%func = OpTypeFunction %void
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%image = OpTypeImage %uint 2D 0 0 0 2 Unknown
%ptr = OpTypePointer UniformConstant %image
%var = OpVariable %ptr UniformConstant
%v2int = OpTypeVector %int 2
%int_1 = OpConstant %int 1
%coord = OpConstantComposite %v2int %int_1 %int_1
%v3uint = OpTypeVector %uint 3
%uint_1 = OpConstant %uint 1
%texelU3 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
%main = OpFunction %void None %func
%label = OpLabel
%load = OpLoad %image %var
OpImageWrite %load %coord %texelU3 ZeroExtend
OpReturn
OpFunctionEnd
)";
OneOffDescriptorSet ds(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
});
const VkFormat format = VK_FORMAT_R32G32_UINT;
if (!FormatFeaturesAreSupported(Gpu(), format, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
GTEST_SKIP() << "Format doesn't support storage image";
}
VkFormatProperties3 fmt_props_3 = vku::InitStructHelper();
VkFormatProperties2 fmt_props = vku::InitStructHelper(&fmt_props_3);
vk::GetPhysicalDeviceFormatProperties2(Gpu(), format, &fmt_props);
if ((fmt_props_3.optimalTilingFeatures & VK_FORMAT_FEATURE_2_STORAGE_WRITE_WITHOUT_FORMAT_BIT) == 0) {
GTEST_SKIP() << "Format doesn't support storage write without format";
}
vkt::Image image(*m_device, 32, 32, format, VK_IMAGE_USAGE_STORAGE_BIT);
vkt::ImageView view = image.CreateView();
ds.WriteDescriptorImageInfo(0, view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_IMAGE_LAYOUT_GENERAL);
ds.UpdateDescriptorSets();
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&ds.layout_});
pipe.CreateComputePipeline();
m_command_buffer.Begin();
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.pipeline_layout_, 0, 1, &ds.set_, 0, nullptr);
vk::CmdDispatch(m_command_buffer, 1, 1, 1);
m_command_buffer.End();
}
TEST_F(PositiveShaderStorageImage, WriteSpecConstantMoreComponent) {
TEST_DESCRIPTION("Test writing to image with less components with Texel being a spec constant.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredFeature(vkt::Feature::shaderStorageImageExtendedFormats);
RETURN_IF_SKIP(Init());
// not valid GLSL, but would look like:
// layout (constant_id = 0) const uint sc = 1;
// layout(set = 0, binding = 0, Rg32ui) uniform uimage2D storageImage;
// imageStore(storageImage, ivec2(1, 1), uvec3(1, sc, sc + 1));
//
// Rg32ui == 2-component but writing 3 texels to it
const char* source = R"(
OpCapability Shader
OpCapability StorageImageExtendedFormats
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%func = OpTypeFunction %void
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%image = OpTypeImage %uint 2D 0 0 0 2 Rg32ui
%ptr = OpTypePointer UniformConstant %image
%var = OpVariable %ptr UniformConstant
%v2int = OpTypeVector %int 2
%int_1 = OpConstant %int 1
%coord = OpConstantComposite %v2int %int_1 %int_1
%v3uint = OpTypeVector %uint 3
%uint_1 = OpConstant %uint 1
%sc = OpSpecConstant %uint 1
%sc_p1 = OpSpecConstantOp %uint IAdd %sc %uint_1
%texelU3 = OpSpecConstantComposite %v3uint %uint_1 %sc %sc_p1
%main = OpFunction %void None %func
%label = OpLabel
%load = OpLoad %image %var
OpImageWrite %load %coord %texelU3 ZeroExtend
OpReturn
OpFunctionEnd
)";
OneOffDescriptorSet ds(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
});
const VkFormat format = VK_FORMAT_R32G32_UINT; // Rg32ui
if (!FormatFeaturesAreSupported(Gpu(), format, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
GTEST_SKIP() << "Format doesn't support storage image";
}
vkt::Image image(*m_device, 32, 32, format, VK_IMAGE_USAGE_STORAGE_BIT);
vkt::ImageView view = image.CreateView();
ds.WriteDescriptorImageInfo(0, view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_IMAGE_LAYOUT_GENERAL);
ds.UpdateDescriptorSets();
uint32_t data = 2;
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;
CreateComputePipelineHelper pipe(*this);
pipe.cs_ =
VkShaderObj(*m_device, source, VK_SHADER_STAGE_COMPUTE_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM, &specialization_info);
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&ds.layout_});
pipe.CreateComputePipeline();
m_command_buffer.Begin();
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe.pipeline_layout_, 0, 1, &ds.set_, 0, nullptr);
vk::CmdDispatch(m_command_buffer, 1, 1, 1);
m_command_buffer.End();
}
TEST_F(PositiveShaderStorageImage, UnknownWriteLessComponentMultiEntrypoint) {
TEST_DESCRIPTION("Test writing to image unknown format with less components, but in unused Entrypoint.");
SetTargetApiVersion(VK_API_VERSION_1_2);
AddRequiredFeature(vkt::Feature::shaderStorageImageWriteWithoutFormat);
RETURN_IF_SKIP(Init());
InitRenderTarget();
// The vertex and fragment shader are just a passthrough
// The compute shader has the invalid OpImageWrite
const char* source = R"(
OpCapability Shader
OpCapability StorageImageWriteWithoutFormat
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main_f "main" %4
OpEntryPoint Vertex %main_v "main" %2
OpEntryPoint GLCompute %main_c "main" %var_image
OpExecutionMode %main_f OriginUpperLeft
OpExecutionMode %main_c LocalSize 1 1 1
OpMemberDecorate %builtin_vert 0 BuiltIn Position
OpMemberDecorate %builtin_vert 1 BuiltIn PointSize
OpMemberDecorate %builtin_vert 2 BuiltIn ClipDistance
OpMemberDecorate %builtin_vert 3 BuiltIn CullDistance
OpDecorate %builtin_vert Block
OpDecorate %4 Location 0
OpDecorate %var_image DescriptorSet 0
OpDecorate %var_image Binding 0
OpDecorate %var_image NonReadable
%void = OpTypeVoid
%8 = OpTypeFunction %void
%float = OpTypeFloat 32
; Vertex types
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%array = OpTypeArray %float %uint_1
%builtin_vert = OpTypeStruct %v4float %float %array %array
%ptr_builtin_vert = OpTypePointer Output %builtin_vert
%2 = OpVariable %ptr_builtin_vert Output
; Fragment types
%ptr_output_frag = OpTypePointer Output %v4float
%4 = OpVariable %ptr_output_frag Output
%float_0 = OpConstant %float 0
%23 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
; Compute types
%int = OpTypeInt 32 1
%v2int = OpTypeVector %int 2
%int_1 = OpConstant %int 1
%coord = OpConstantComposite %v2int %int_1 %int_1
%v3uint = OpTypeVector %uint 3
%texelU3 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
%image = OpTypeImage %uint 2D 0 0 0 2 Unknown
%ptr_image = OpTypePointer UniformConstant %image
%var_image = OpVariable %ptr_image UniformConstant
%main_v = OpFunction %void None %8
%24 = OpLabel
OpReturn
OpFunctionEnd
%main_f = OpFunction %void None %8
%28 = OpLabel
OpStore %4 %23
OpReturn
OpFunctionEnd
%main_c = OpFunction %void None %8
%29 = OpLabel
%load_image = OpLoad %image %var_image
OpImageWrite %load_image %coord %texelU3 ZeroExtend
OpReturn
OpFunctionEnd
)";
OneOffDescriptorSet ds(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr},
});
const VkFormat format = VK_FORMAT_R8G8B8A8_UINT;
if (!FormatFeaturesAreSupported(Gpu(), format, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
GTEST_SKIP() << "Format doesn't support storage image";
}
vkt::Image image(*m_device, 32, 32, format, VK_IMAGE_USAGE_STORAGE_BIT);
vkt::ImageView view = image.CreateView();
ds.WriteDescriptorImageInfo(0, view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_IMAGE_LAYOUT_GENERAL);
ds.UpdateDescriptorSets();
VkShaderObj vs(*m_device, source, VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
VkShaderObj fs(*m_device, source, VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
CreatePipelineHelper pipe(*this);
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
pipe.pipeline_layout_ = vkt::PipelineLayout(*m_device, {&ds.layout_});
pipe.CreateGraphicsPipeline();
m_command_buffer.Begin();
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe.pipeline_layout_, 0, 1, &ds.set_, 0, nullptr);
// This does not invoke the Compute Entrypoint where the bad write would be
vk::CmdDraw(m_command_buffer, 3, 1, 0, 0);
m_command_buffer.EndRenderPass();
m_command_buffer.End();
}
TEST_F(PositiveShaderStorageImage, FormatTypeMatch) {
TEST_DESCRIPTION("Will not produce warning as formats are the same");
RETURN_IF_SKIP(Init());
m_errorMonitor->ExpectSuccess(kErrorBit | kWarningBit);
std::string cs_source = R"glsl(
#version 450
layout(set = 0, binding = 0, Rgba8) uniform image2D si0;
void main() {
imageStore(si0, ivec2(0), vec4(0));
}
)glsl";
vkt::Image image(*m_device, 4, 4, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_STORAGE_BIT);
vkt::ImageView image_view = image.CreateView();
OneOffDescriptorSet descriptor_set(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_ALL, nullptr},
});
vkt::PipelineLayout pipeline_layout(*m_device, {&descriptor_set.layout_});
descriptor_set.WriteDescriptorImageInfo(0, image_view, VK_NULL_HANDLE, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
VK_IMAGE_LAYOUT_GENERAL);
descriptor_set.UpdateDescriptorSets();
CreateComputePipelineHelper pipe(*this);
pipe.cs_ = VkShaderObj(*m_device, cs_source.c_str(), VK_SHADER_STAGE_COMPUTE_BIT);
pipe.cp_ci_.layout = pipeline_layout;
pipe.CreateComputePipeline();
m_command_buffer.Begin();
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &descriptor_set.set_, 0,
nullptr);
vk::CmdDispatch(m_command_buffer, 1, 1, 1);
m_errorMonitor->VerifyFound();
}