Skip to content

Commit 7a9792c

Browse files
gpuav: Check if vertex input before instrumenting
1 parent 4b80fa7 commit 7a9792c

File tree

5 files changed

+199
-16
lines changed

5 files changed

+199
-16
lines changed

layers/gpuav/spirv/vertex_attribute_fetch_oob_pass.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
/* Copyright (c) 2025 The Khronos Group Inc.
2-
* Copyright (c) 2025 Valve Corporation
3-
* Copyright (c) 2025 LunarG, Inc.
1+
/* Copyright (c) 2025-2026 The Khronos Group Inc.
2+
* Copyright (c) 2025-2026 Valve Corporation
3+
* Copyright (c) 2025-2026 LunarG, Inc.
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -43,6 +43,30 @@ bool VertexAttributeFetchOobPass::Instrument() {
4343
continue;
4444
}
4545

46+
// Handle edge case where there is no vertex input actually
47+
// the entry point must list all input variables
48+
{
49+
bool found_input = false;
50+
51+
uint32_t word = entry_point_inst->GetEntryPointInterfaceStart();
52+
const uint32_t total_words = entry_point_inst->Length();
53+
for (; word < total_words; word++) {
54+
const uint32_t interface_id = entry_point_inst->Word(word);
55+
const Variable* variable = type_manager_.FindVariableById(interface_id);
56+
// guaranteed by spirv-val to be a OpVariable
57+
assert(variable);
58+
if (variable->StorageClass() == spv::StorageClassInput) {
59+
found_input = true;
60+
break;
61+
}
62+
}
63+
64+
if (!found_input) {
65+
// vertex shader has no input, nothing to validate
66+
return false;
67+
}
68+
}
69+
4670
const uint32_t vertex_shader_entry_point_id = entry_point_inst->Word(2);
4771
for (const auto& function : module_.functions_) {
4872
if (function->instrumentation_added_) {

layers/state_tracker/shader_instruction.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* limitations under the License.
1414
*/
1515

16+
#include <spirv/unified1/spirv.hpp>
1617
#include <sstream>
1718
#include "state_tracker/shader_instruction.h"
1819
#include "generated/spirv_grammar_helper.h"
@@ -262,6 +263,19 @@ spv::StorageClass Instruction::StorageClass() const {
262263
return storage_class;
263264
}
264265

266+
// OpEntryPoint are annoying because the offset to the interface variable requires you to first detect how big the "Name" string is
267+
uint32_t Instruction::GetEntryPointInterfaceStart() const {
268+
assert(Opcode() == spv::OpEntryPoint || Opcode() == spv::OpGraphEntryPointARM);
269+
uint32_t word = 3; // operand Name operand starts
270+
// Find the end of the entrypoint's name string. additional zero bytes follow the actual null terminator, to fill out the rest
271+
// of the word - so we only need to look at the last byte in the word to determine which word contains the terminator.
272+
while (Word(word) & 0xff000000u) {
273+
++word;
274+
}
275+
++word;
276+
return word;
277+
}
278+
265279
void Instruction::Fill(const std::vector<uint32_t>& words) {
266280
for (uint32_t word : words) {
267281
words_.emplace_back(word);

layers/state_tracker/shader_instruction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class Instruction {
9292
bool operator==(Instruction const& other) const { return words_ == other.words_; }
9393
bool operator!=(Instruction const& other) const { return words_ != other.words_; }
9494

95+
uint32_t GetEntryPointInterfaceStart() const;
96+
9597
// The following is only used for GPU-AV where we need to possibly update an Instruction
9698
Instruction(spirv_iterator it, uint32_t position_offset);
9799
// Assumes caller will fill remaining words

layers/state_tracker/shader_module.cpp

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -683,14 +683,7 @@ std::vector<StageInterfaceVariable> EntryPoint::GetStageInterfaceVariables(const
683683
std::vector<StageInterfaceVariable> variables;
684684

685685
// spirv-val validates that any Input/Output used in the entrypoint is listed in as interface IDs
686-
uint32_t word = 3; // operand Name operand starts
687-
// Find the end of the entrypoint's name string. additional zero bytes follow the actual null terminator, to fill out
688-
// the rest of the word - so we only need to look at the last byte in the word to determine which word contains the
689-
// terminator.
690-
while (entrypoint.entrypoint_insn.Word(word) & 0xff000000u) {
691-
++word;
692-
}
693-
++word;
686+
uint32_t word = entrypoint.entrypoint_insn.GetEntryPointInterfaceStart();
694687

695688
vvl::unordered_set<uint32_t> unique_interface_id;
696689
for (; word < entrypoint.entrypoint_insn.Length(); word++) {
@@ -700,7 +693,7 @@ std::vector<StageInterfaceVariable> EntryPoint::GetStageInterfaceVariables(const
700693
};
701694
// guaranteed by spirv-val to be a OpVariable
702695
const Instruction& insn = *module_state.FindDef(interface_id);
703-
const spv::StorageClass storage_class = (spv::StorageClass)insn.Word(3);
696+
const spv::StorageClass storage_class = insn.StorageClass();
704697

705698
if (storage_class == spv::StorageClassInput || storage_class == spv::StorageClassOutput) {
706699
variables.emplace_back(module_state, insn, entrypoint.stage, parsed);

tests/unit/gpu_av_vertex_attribute_fetch_positive.cpp

Lines changed: 154 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*
2-
* Copyright (c) 2020-2025 The Khronos Group Inc.
3-
* Copyright (c) 2020-2025 Valve Corporation
4-
* Copyright (c) 2020-2025 LunarG, Inc.
5-
* Copyright (c) 2020-2025 Google, Inc.
2+
* Copyright (c) 2020-2026 The Khronos Group Inc.
3+
* Copyright (c) 2020-2026 Valve Corporation
4+
* Copyright (c) 2020-2026 LunarG, Inc.
5+
* Copyright (c) 2020-2026 Google, Inc.
66
*
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
1515
#include "../framework/layer_validation_tests.h"
1616
#include "../framework/pipeline_helper.h"
1717
#include "../framework/buffer_helper.h"
18+
#include "../framework/shader_helper.h"
1819

1920
class PositiveGpuAVVertexAttributeFetch : public GpuAVTest {};
2021

@@ -70,3 +71,152 @@ TEST_F(PositiveGpuAVVertexAttributeFetch, IndirectDrawZeroStride) {
7071
m_default_queue->SubmitAndWait(m_command_buffer);
7172
m_errorMonitor->VerifyFound();
7273
}
74+
75+
TEST_F(PositiveGpuAVVertexAttributeFetch, NoVertexInput) {
76+
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11475");
77+
RETURN_IF_SKIP(InitGpuAvFramework());
78+
RETURN_IF_SKIP(InitState());
79+
InitRenderTarget();
80+
81+
const char* vs_source = R"glsl(
82+
#version 450
83+
layout (location = 0) out vec3 outEyespacePosition;
84+
layout (location = 1) out vec2 outTexcoords;
85+
86+
void main() {
87+
outEyespacePosition = vec3(0);
88+
outTexcoords = vec2(0);
89+
}
90+
)glsl";
91+
VkShaderObj vs(*m_device, vs_source, VK_SHADER_STAGE_VERTEX_BIT);
92+
93+
const char* fs_source = R"glsl(
94+
#version 450
95+
layout (location = 0) in vec3 inEyeSpacePosition;
96+
layout (location = 0) out vec4 outColour;
97+
98+
void main() {
99+
outColour = vec4(0);
100+
}
101+
)glsl";
102+
VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT);
103+
104+
CreatePipelineHelper pipe(*this);
105+
VkVertexInputBindingDescription input_binding = {0, 0, VK_VERTEX_INPUT_RATE_VERTEX};
106+
VkVertexInputAttributeDescription input_attrib = {1, 0, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 0};
107+
pipe.vi_ci_.pVertexBindingDescriptions = &input_binding;
108+
pipe.vi_ci_.vertexBindingDescriptionCount = 1;
109+
pipe.vi_ci_.pVertexAttributeDescriptions = &input_attrib;
110+
pipe.vi_ci_.vertexAttributeDescriptionCount = 1;
111+
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
112+
pipe.CreateGraphicsPipeline();
113+
114+
VkCommandBufferBeginInfo begin_info = vku::InitStructHelper();
115+
m_command_buffer.Begin(&begin_info);
116+
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
117+
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
118+
119+
vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0, 666, 42});
120+
vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
121+
VkDeviceSize vertex_buffer_offset = 0;
122+
vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
123+
vk::CmdBindVertexBuffers(m_command_buffer, 0, 1, &vertex_buffer.handle(), &vertex_buffer_offset);
124+
125+
vk::CmdDrawIndexed(m_command_buffer, 3, 1, 0, 0, 0);
126+
127+
m_command_buffer.EndRenderPass();
128+
m_command_buffer.End();
129+
130+
m_default_queue->SubmitAndWait(m_command_buffer);
131+
m_errorMonitor->VerifyFound();
132+
}
133+
134+
TEST_F(PositiveGpuAVVertexAttributeFetch, NoVertexInputWithBuiltin) {
135+
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/11475");
136+
SetTargetApiVersion(VK_API_VERSION_1_2);
137+
AddRequiredFeature(vkt::Feature::shaderDrawParameters);
138+
RETURN_IF_SKIP(InitGpuAvFramework());
139+
RETURN_IF_SKIP(InitState());
140+
InitRenderTarget();
141+
142+
// Declares the variable for builtin [VertexIndex, InstanceIndex, BaseVertex] but never accesses them
143+
const char* vs_source = R"(
144+
OpCapability DrawParameters
145+
OpCapability Shader
146+
OpMemoryModel Logical GLSL450
147+
OpEntryPoint Vertex %main "main" %entryPointParam_main_outEyespacePosition %entryPointParam_main_outTexcoords
148+
OpDecorate %entryPointParam_main_outEyespacePosition Location 0
149+
OpDecorate %entryPointParam_main_outTexcoords Location 1
150+
OpDecorate %gl_Position BuiltIn Position
151+
OpDecorate %gl_VertexIndex BuiltIn VertexIndex
152+
OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
153+
OpDecorate %15 BuiltIn BaseVertex
154+
%float = OpTypeFloat 32
155+
%v3float = OpTypeVector %float 3
156+
%_ptr_Output_v3float = OpTypePointer Output %v3float
157+
%v2float = OpTypeVector %float 2
158+
%_ptr_Output_v2float = OpTypePointer Output %v2float
159+
%v4float = OpTypeVector %float 4
160+
%_ptr_Output_v4float = OpTypePointer Output %v4float
161+
%int = OpTypeInt 32 1
162+
%_ptr_Input_int = OpTypePointer Input %int
163+
%void = OpTypeVoid
164+
%18 = OpTypeFunction %void
165+
%float_0 = OpConstant %float 0
166+
%20 = OpConstantComposite %v3float %float_0 %float_0 %float_0
167+
%23 = OpConstantComposite %v2float %float_0 %float_0
168+
%entryPointParam_main_outEyespacePosition = OpVariable %_ptr_Output_v3float Output
169+
%entryPointParam_main_outTexcoords = OpVariable %_ptr_Output_v2float Output
170+
%gl_Position = OpVariable %_ptr_Output_v4float Output
171+
%gl_VertexIndex = OpVariable %_ptr_Input_int Input
172+
%gl_InstanceIndex = OpVariable %_ptr_Input_int Input
173+
%15 = OpVariable %_ptr_Input_int Input
174+
%main = OpFunction %void None %18
175+
%19 = OpLabel
176+
OpStore %entryPointParam_main_outEyespacePosition %20
177+
OpStore %entryPointParam_main_outTexcoords %23
178+
OpReturn
179+
OpFunctionEnd
180+
)";
181+
VkShaderObj vs(*m_device, vs_source, VK_SHADER_STAGE_VERTEX_BIT, SPV_ENV_VULKAN_1_2, SPV_SOURCE_ASM);
182+
183+
const char* fs_source = R"glsl(
184+
#version 450
185+
layout (location = 0) in vec3 inEyeSpacePosition;
186+
layout (location = 0) out vec4 outColour;
187+
188+
void main() {
189+
outColour = vec4(0);
190+
}
191+
)glsl";
192+
VkShaderObj fs(*m_device, fs_source, VK_SHADER_STAGE_FRAGMENT_BIT, SPV_ENV_VULKAN_1_2);
193+
194+
CreatePipelineHelper pipe(*this);
195+
VkVertexInputBindingDescription input_binding = {0, 0, VK_VERTEX_INPUT_RATE_VERTEX};
196+
VkVertexInputAttributeDescription input_attrib = {1, 0, VK_FORMAT_A2B10G10R10_UNORM_PACK32, 0};
197+
pipe.vi_ci_.pVertexBindingDescriptions = &input_binding;
198+
pipe.vi_ci_.vertexBindingDescriptionCount = 1;
199+
pipe.vi_ci_.pVertexAttributeDescriptions = &input_attrib;
200+
pipe.vi_ci_.vertexAttributeDescriptionCount = 1;
201+
pipe.shader_stages_ = {vs.GetStageCreateInfo(), fs.GetStageCreateInfo()};
202+
pipe.CreateGraphicsPipeline();
203+
204+
VkCommandBufferBeginInfo begin_info = vku::InitStructHelper();
205+
m_command_buffer.Begin(&begin_info);
206+
m_command_buffer.BeginRenderPass(m_renderPassBeginInfo);
207+
vk::CmdBindPipeline(m_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
208+
209+
vkt::Buffer index_buffer = vkt::IndexBuffer<uint32_t>(*m_device, {0, 666, 42});
210+
vkt::Buffer vertex_buffer = vkt::VertexBuffer<float>(*m_device, {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
211+
VkDeviceSize vertex_buffer_offset = 0;
212+
vk::CmdBindIndexBuffer(m_command_buffer, index_buffer, 0, VK_INDEX_TYPE_UINT32);
213+
vk::CmdBindVertexBuffers(m_command_buffer, 0, 1, &vertex_buffer.handle(), &vertex_buffer_offset);
214+
215+
vk::CmdDrawIndexed(m_command_buffer, 3, 1, 0, 0, 0);
216+
217+
m_command_buffer.EndRenderPass();
218+
m_command_buffer.End();
219+
220+
m_default_queue->SubmitAndWait(m_command_buffer);
221+
m_errorMonitor->VerifyFound();
222+
}

0 commit comments

Comments
 (0)