From 80dafe6b73647b08d394a6bdd338cd83aa09d7ac Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Fri, 5 Dec 2025 18:04:40 -0500 Subject: [PATCH] layers: Add VK_QCOM_multiview_per_view_render_areas --- layers/core_checks/cc_image.cpp | 9 --- layers/core_checks/cc_render_pass.cpp | 13 ++-- layers/stateless/sl_render_pass.cpp | 89 ++++++++++++++++++++++++ layers/stateless/stateless_validation.h | 3 + layers/utils/vk_api_utils.h | 9 +++ tests/unit/multiview.cpp | 92 +++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 16 deletions(-) diff --git a/layers/core_checks/cc_image.cpp b/layers/core_checks/cc_image.cpp index 356348930ac..bbf22dbf4a4 100644 --- a/layers/core_checks/cc_image.cpp +++ b/layers/core_checks/cc_image.cpp @@ -1076,15 +1076,6 @@ bool CoreChecks::PreCallValidateCmdClearDepthStencilImage(VkCommandBuffer comman return skip; } -// Returns true if sub_rect is entirely contained within rect -static inline bool ContainsRect(VkRect2D rect, VkRect2D sub_rect) { - if ((sub_rect.offset.x < rect.offset.x) || (sub_rect.offset.x + sub_rect.extent.width > rect.offset.x + rect.extent.width) || - (sub_rect.offset.y < rect.offset.y) || (sub_rect.offset.y + sub_rect.extent.height > rect.offset.y + rect.extent.height)) { - return false; - } - return true; -} - bool CoreChecks::ValidateClearAttachmentExtent(const vvl::CommandBuffer &cb_state, const VkRect2D &render_area, uint32_t render_pass_layer_count, uint32_t rect_count, const VkClearRect *clear_rects, const Location &loc) const { diff --git a/layers/core_checks/cc_render_pass.cpp b/layers/core_checks/cc_render_pass.cpp index f68f6b309b1..3e4fa8c8446 100644 --- a/layers/core_checks/cc_render_pass.cpp +++ b/layers/core_checks/cc_render_pass.cpp @@ -508,9 +508,8 @@ bool CoreChecks::ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const uint32_t clear_op_size = 0; // Make sure pClearValues is at least as large as last LOAD_OP_CLEAR // Handle extension struct from EXT_sample_locations - const auto *sample_locations_begin_info = - vku::FindStructInPNextChain(pRenderPassBegin->pNext); - if (sample_locations_begin_info) { + if (const auto* sample_locations_begin_info = + vku::FindStructInPNextChain(pRenderPassBegin->pNext)) { for (uint32_t i = 0; i < sample_locations_begin_info->attachmentInitialSampleLocationsCount; ++i) { const Location sampler_loc = rp_begin_loc.pNext(Struct::VkRenderPassSampleLocationsBeginInfoEXT, Field::pAttachmentInitialSampleLocations, i); @@ -616,8 +615,8 @@ bool CoreChecks::ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const "maintenance7 were not enabled."); } - const auto counters_begin_info = vku::FindStructInPNextChain(pRenderPassBegin->pNext); - if (counters_begin_info) { + if (const auto counters_begin_info = + vku::FindStructInPNextChain(pRenderPassBegin->pNext)) { const LogObjectList objlist(cb_state.Handle(), rp_state->Handle()); uint32_t layer_or_view_count = 0; if (rp_state->has_multiview_enabled) { @@ -3847,8 +3846,8 @@ bool CoreChecks::PreCallValidateCmdBeginRendering(VkCommandBuffer commandBuffer, pRenderingInfo->viewMask, phys_dev_props_core11.maxMultiviewViewCount); } - const auto counters_begin_info = vku::FindStructInPNextChain(pRenderingInfo->pNext); - if (counters_begin_info) { + if (const auto counters_begin_info = + vku::FindStructInPNextChain(pRenderingInfo->pNext)) { const LogObjectList objlist(cb_state->Handle()); uint32_t layer_or_view_count = 0; if (enabled_features.multiview) { diff --git a/layers/stateless/sl_render_pass.cpp b/layers/stateless/sl_render_pass.cpp index 3cc6fae155b..e5a3400f8b8 100644 --- a/layers/stateless/sl_render_pass.cpp +++ b/layers/stateless/sl_render_pass.cpp @@ -24,6 +24,7 @@ #include "utils/math_utils.h" #include "utils/image_utils.h" #include "utils/sync_utils.h" +#include "utils/vk_api_utils.h" namespace stateless { @@ -579,6 +580,82 @@ bool Device::ValidateRenderPassStripeBeginInfo(VkCommandBuffer commandBuffer, co return skip; } +bool Device::ValidateMultiviewPerViewRenderAreasRenderPassBeginInfo( + VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* rp_begin_info, const VkRenderingInfo* rendering_info, + const VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM& multiview_per_view_info, const Location& loc) const { + bool skip = false; + if (multiview_per_view_info.perViewRenderAreaCount != 0 && !enabled_features.multiviewPerViewRenderAreas) { + const char* vuid = rp_begin_info ? "VUID-VkRenderPassBeginInfo-perViewRenderAreaCount-07859" + : "VUID-VkRenderingInfo-perViewRenderAreaCount-07857"; + skip |= LogError(vuid, commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM).dot(Field::perViewRenderAreaCount), + "is %" PRIu32 " but the multiviewPerViewRenderAreas feature was not enabled.", + multiview_per_view_info.perViewRenderAreaCount); + } + + if (rendering_info) { + const uint32_t msb = (uint32_t)MostSignificantBit(rendering_info->viewMask); + if (multiview_per_view_info.perViewRenderAreaCount != msb + 1) { + skip |= LogError( + "VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-pNext-07866", commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM).dot(Field::perViewRenderAreaCount), + "(%" PRIu32 ") must be VkRenderingInfo::viewMask (0x%" PRIx32 ") most significant bit index (%" PRIu32 ") + 1", + multiview_per_view_info.perViewRenderAreaCount, rendering_info->viewMask, msb); + } + } + + const VkRect2D full_render_area = rendering_info ? rendering_info->renderArea : rp_begin_info->renderArea; + + for (uint32_t i = 0; i < multiview_per_view_info.perViewRenderAreaCount; i++) { + const VkRect2D view_rect = multiview_per_view_info.pPerViewRenderAreas[i]; + if (view_rect.offset.x < 0) { + skip |= LogError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07861", commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM) + .dot(Field::pPerViewRenderAreas, i) + .dot(Field::offset) + .dot(Field::x), + "(%" PRId32 ") is less than zero.", view_rect.offset.x); + } + if (view_rect.offset.y < 0) { + skip |= LogError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07862", commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM) + .dot(Field::pPerViewRenderAreas, i) + .dot(Field::offset) + .dot(Field::y), + "(%" PRId32 ") is less than zero.", view_rect.offset.y); + } + if ((view_rect.offset.x + view_rect.extent.width) > phys_dev_props.limits.maxFramebufferWidth) { + skip |= LogError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07863", commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM) + .dot(Field::pPerViewRenderAreas, i) + .dot(Field::offset) + .dot(Field::x), + "%" PRId32 " + extent.width (%" PRIu32 ") greater than maxFramebufferWidth (%" PRIu32 ").", + view_rect.offset.x, view_rect.extent.width, phys_dev_props.limits.maxFramebufferWidth); + } + if ((view_rect.offset.y + view_rect.extent.height) > phys_dev_props.limits.maxFramebufferHeight) { + skip |= LogError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07864", commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM) + .dot(Field::pPerViewRenderAreas, i) + .dot(Field::offset) + .dot(Field::y), + "%" PRId32 " + extent.height (%" PRIu32 ") greater than maxFramebufferHeight (%" PRIu32 ").", + view_rect.offset.y, view_rect.extent.height, phys_dev_props.limits.maxFramebufferHeight); + } + + if (!ContainsRect(full_render_area, view_rect)) { + const char* vuid = rp_begin_info ? "VUID-VkRenderPassBeginInfo-perViewRenderAreaCount-07860" + : "VUID-VkRenderingInfo-perViewRenderAreaCount-07858"; + skip |= + LogError(vuid, commandBuffer, + loc.pNext(Struct::VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM).dot(Field::pPerViewRenderAreas, i), + "(%s) is not contained in %s::renderArea (%s)", string_VkRect2D(view_rect).c_str(), + rendering_info ? "VkRenderingInfo" : "VkRenderPassBeginInfo", string_VkRect2D(full_render_area).c_str()); + } + } + return skip; +} + bool Device::ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo *const rp_begin, const ErrorObject &error_obj) const { bool skip = false; @@ -592,6 +669,12 @@ bool Device::ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkR const Location loc = error_obj.location.dot(Field::pRenderPassBegin); skip |= ValidateRenderPassStripeBeginInfo(commandBuffer, rp_begin->pNext, rp_begin->renderArea, loc); + if (const auto multiview_per_view_info = + vku::FindStructInPNextChain(rp_begin->pNext)) { + skip |= + ValidateMultiviewPerViewRenderAreasRenderPassBeginInfo(commandBuffer, rp_begin, nullptr, *multiview_per_view_info, loc); + } + return skip; } @@ -735,6 +818,12 @@ bool Device::manual_PreCallValidateCmdBeginRendering(VkCommandBuffer commandBuff } } + if (const auto multiview_per_view_info = + vku::FindStructInPNextChain(pRenderingInfo->pNext)) { + skip |= ValidateMultiviewPerViewRenderAreasRenderPassBeginInfo(commandBuffer, nullptr, pRenderingInfo, + *multiview_per_view_info, rendering_info_loc); + } + skip |= ValidateRenderPassStripeBeginInfo(commandBuffer, pRenderingInfo->pNext, pRenderingInfo->renderArea, rendering_info_loc); skip |= ValidateBeginRenderingColorAttachment(commandBuffer, *pRenderingInfo, rendering_info_loc); skip |= ValidateBeginRenderingDepthAttachment(commandBuffer, *pRenderingInfo, rendering_info_loc); diff --git a/layers/stateless/stateless_validation.h b/layers/stateless/stateless_validation.h index ebb135ee4f4..c47db8bc7f5 100644 --- a/layers/stateless/stateless_validation.h +++ b/layers/stateless/stateless_validation.h @@ -1036,6 +1036,9 @@ class Device : public vvl::base::Device { const VkClearColorValue *pColor, uint32_t rangeCount, const VkImageSubresourceRange *pRanges, const Context &context) const; + bool ValidateMultiviewPerViewRenderAreasRenderPassBeginInfo( + VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* rp_begin_info, const VkRenderingInfo* rendering_info, + const VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM& multiview_per_view_info, const Location& loc) const; bool ValidateRenderPassStripeBeginInfo(VkCommandBuffer commandBuffer, const void *pNext, const VkRect2D render_area, const Location &loc) const; bool ValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo *const rp_begin, diff --git a/layers/utils/vk_api_utils.h b/layers/utils/vk_api_utils.h index e3eb6f77357..54908fb01b1 100644 --- a/layers/utils/vk_api_utils.h +++ b/layers/utils/vk_api_utils.h @@ -141,6 +141,15 @@ static inline VkOffset3D CastTo3D(const VkOffset2D &d2) { return d3; } +// Returns true if sub_rect is entirely contained within rect +static inline bool ContainsRect(VkRect2D rect, VkRect2D sub_rect) { + if ((sub_rect.offset.x < rect.offset.x) || (sub_rect.offset.x + sub_rect.extent.width > rect.offset.x + rect.extent.width) || + (sub_rect.offset.y < rect.offset.y) || (sub_rect.offset.y + sub_rect.extent.height > rect.offset.y + rect.extent.height)) { + return false; + } + return true; +} + static constexpr VkPipelineStageFlags2 kFramebufferStagePipelineStageFlags = (VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); diff --git a/tests/unit/multiview.cpp b/tests/unit/multiview.cpp index 6e5ceaadd0e..d5ada6051df 100644 --- a/tests/unit/multiview.cpp +++ b/tests/unit/multiview.cpp @@ -13,10 +13,12 @@ * http://www.apache.org/licenses/LICENSE-2.0 */ +#include #include "../framework/layer_validation_tests.h" #include "../framework/pipeline_helper.h" #include "../framework/descriptor_helper.h" #include "../framework/render_pass_helper.h" +#include "binding.h" #include "utils/convert_utils.h" class NegativeMultiview : public VkLayerTest {}; @@ -1282,6 +1284,96 @@ TEST_F(NegativeMultiview, ShaderLayerBuiltInDynamicRendering) { m_errorMonitor->VerifyFound(); } +TEST_F(NegativeMultiview, MultiviewPerViewRenderAreasFeature) { + SetTargetApiVersion(VK_API_VERSION_1_2); + AddRequiredExtensions(VK_KHR_MULTIVIEW_EXTENSION_NAME); + AddRequiredExtensions(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + AddRequiredExtensions(VK_QCOM_MULTIVIEW_PER_VIEW_RENDER_AREAS_EXTENSION_NAME); + AddRequiredFeature(vkt::Feature::multiview); + AddRequiredFeature(vkt::Feature::dynamicRendering); + RETURN_IF_SKIP(Init()); + + vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + vkt::ImageView image_view = image.CreateView(VK_IMAGE_VIEW_TYPE_2D_ARRAY); + + VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper(); + color_attachment.imageView = image_view; + color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + VkRect2D view_render_area = {{0, 0}, {32, 32}}; + VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM multiview_pre_view = vku::InitStructHelper(); + multiview_pre_view.perViewRenderAreaCount = 1; + multiview_pre_view.pPerViewRenderAreas = &view_render_area; + + VkRenderingInfo rendering_info = vku::InitStructHelper(&multiview_pre_view); + rendering_info.renderArea = {{0, 0}, {32, 32}}; + rendering_info.layerCount = 1u; + rendering_info.colorAttachmentCount = 1u; + rendering_info.pColorAttachments = &color_attachment; + rendering_info.viewMask = 0x1; + + m_command_buffer.Begin(); + m_errorMonitor->SetDesiredError("VUID-VkRenderingInfo-perViewRenderAreaCount-07857"); + m_command_buffer.BeginRendering(rendering_info); + m_errorMonitor->VerifyFound(); + m_command_buffer.End(); +} + +TEST_F(NegativeMultiview, MultiviewPerViewRenderAreas) { + SetTargetApiVersion(VK_API_VERSION_1_2); + AddRequiredExtensions(VK_KHR_MULTIVIEW_EXTENSION_NAME); + AddRequiredExtensions(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + AddRequiredExtensions(VK_QCOM_MULTIVIEW_PER_VIEW_RENDER_AREAS_EXTENSION_NAME); + AddRequiredFeature(vkt::Feature::multiview); + AddRequiredFeature(vkt::Feature::dynamicRendering); + AddRequiredFeature(vkt::Feature::multiviewPerViewRenderAreas); + RETURN_IF_SKIP(Init()); + + vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + vkt::ImageView image_view = image.CreateView(VK_IMAGE_VIEW_TYPE_2D_ARRAY); + + VkRenderingAttachmentInfo color_attachment = vku::InitStructHelper(); + color_attachment.imageView = image_view; + color_attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; + + VkRect2D view_render_area[2] = {{{-1, 0}, {16, 16}}, {{0, -1}, {16, 16}}}; + VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM multiview_pre_view = vku::InitStructHelper(); + multiview_pre_view.perViewRenderAreaCount = 2; + multiview_pre_view.perViewRenderAreaCount = 2; + multiview_pre_view.pPerViewRenderAreas = view_render_area; + + VkRenderingInfo rendering_info = vku::InitStructHelper(&multiview_pre_view); + rendering_info.renderArea = {{-1, -1}, {31, 31}}; + rendering_info.layerCount = 1u; + rendering_info.colorAttachmentCount = 1u; + rendering_info.pColorAttachments = &color_attachment; + rendering_info.viewMask = 0x3; + + m_command_buffer.Begin(); + m_errorMonitor->SetDesiredError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07861"); + m_errorMonitor->SetDesiredError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-offset-07862"); + m_command_buffer.BeginRendering(rendering_info); + m_errorMonitor->VerifyFound(); + + view_render_area[0] = {{0, 0}, {16, 16}}; + view_render_area[1] = {{0, 0}, {32, 16}}; + + m_errorMonitor->SetDesiredError("VUID-VkRenderingInfo-perViewRenderAreaCount-07858"); + m_command_buffer.BeginRendering(rendering_info); + m_errorMonitor->VerifyFound(); + + multiview_pre_view.perViewRenderAreaCount = 1; + m_errorMonitor->SetDesiredError("VUID-VkMultiviewPerViewRenderAreasRenderPassBeginInfoQCOM-pNext-07866"); + m_command_buffer.BeginRendering(rendering_info); + m_errorMonitor->VerifyFound(); + + m_command_buffer.End(); +} + TEST_F(NegativeMultiview, MeshShader) { TEST_DESCRIPTION("https://gitlab.khronos.org/vulkan/vulkan/-/issues/4194"); SetTargetApiVersion(VK_API_VERSION_1_2);