| /* Copyright (c) 2025-2026 The Khronos Group Inc. |
| * Copyright (c) 2025-2026 Valve Corporation |
| * Copyright (c) 2025-2026 LunarG, 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 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "best_practices/bp_state.h" |
| #include <vulkan/utility/vk_format_utils.h> |
| #include <vulkan/vulkan_core.h> |
| #include "best_practices/best_practices_validation.h" |
| #include "containers/container_utils.h" |
| #include "generated/error_location_helper.h" |
| #include "state_tracker/queue_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/pipeline_state.h" |
| #include "utils/action_command_utils.h" |
| |
| namespace bp_state { |
| |
| static bool SparseMetaDataRequired(vvl::Image& img) { |
| if (img.create_from_swapchain) { |
| return false; |
| } |
| |
| for (const auto& req : img.sparse_requirements) { |
| if (req.formatProperties.aspectMask & VK_IMAGE_ASPECT_METADATA_BIT) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| ImageSubState::ImageSubState(vvl::Image& img) : vvl::ImageSubState(img), sparse_metadata_required(SparseMetaDataRequired(img)) { |
| SetupUsages(); |
| } |
| |
| ImageSubState::Usage ImageSubState::UpdateUsage(uint32_t array_layer, uint32_t mip_level, IMAGE_SUBRESOURCE_USAGE_BP usage, |
| uint32_t queue_family) { |
| auto last_usage = usages_[array_layer][mip_level]; |
| usages_[array_layer][mip_level].type = usage; |
| usages_[array_layer][mip_level].queue_family_index = queue_family; |
| return last_usage; |
| } |
| |
| ImageSubState::Usage ImageSubState::GetUsage(uint32_t array_layer, uint32_t mip_level) const { |
| return usages_[array_layer][mip_level]; |
| } |
| |
| IMAGE_SUBRESOURCE_USAGE_BP ImageSubState::GetUsageType(uint32_t array_layer, uint32_t mip_level) const { |
| return GetUsage(array_layer, mip_level).type; |
| } |
| |
| uint32_t ImageSubState::GetLastQueueFamily(uint32_t array_layer, uint32_t mip_level) const { |
| return GetUsage(array_layer, mip_level).queue_family_index; |
| } |
| |
| void ImageSubState::SetupUsages() { |
| usages_.resize(base.GetArrayLayers()); |
| for (auto& mip_vec : usages_) { |
| mip_vec.resize(base.GetMipLevels(), {IMAGE_SUBRESOURCE_USAGE_BP::UNDEFINED, VK_QUEUE_FAMILY_IGNORED}); |
| } |
| } |
| |
| CommandBufferSubState::CommandBufferSubState(vvl::CommandBuffer& cb, BestPractices& validator) |
| : vvl::CommandBufferSubState(cb), validator(validator) {} |
| |
| void CommandBufferSubState::RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t, const Location&) { |
| auto& secondary_sub_state = SubState(secondary_command_buffer); |
| if (secondary_command_buffer.IsSecondary()) { |
| render_pass_state.has_draw_cmd |= secondary_sub_state.render_pass_state.has_draw_cmd; |
| } |
| |
| for (auto& function : secondary_sub_state.queue_submit_functions) { |
| queue_submit_functions.push_back(function); |
| } |
| |
| for (auto& early_clear : secondary_sub_state.render_pass_state.earlyClearAttachments) { |
| if (validator.ClearAttachmentsIsFullClear(*this, uint32_t(early_clear.rects.size()), early_clear.rects.data())) { |
| RecordAttachmentClearAttachments(early_clear.framebufferAttachment, early_clear.colorAttachment, early_clear.aspects, |
| uint32_t(early_clear.rects.size()), early_clear.rects.data()); |
| } else { |
| RecordAttachmentAccess(early_clear.framebufferAttachment, early_clear.aspects); |
| } |
| } |
| |
| for (auto& touch : secondary_sub_state.render_pass_state.touchesAttachments) { |
| RecordAttachmentAccess(touch.framebufferAttachment, touch.aspects); |
| } |
| |
| render_pass_state.numDrawCallsDepthEqualCompare += secondary_sub_state.render_pass_state.numDrawCallsDepthEqualCompare; |
| render_pass_state.numDrawCallsDepthOnly += secondary_sub_state.render_pass_state.numDrawCallsDepthOnly; |
| |
| for (const auto& [event, secondary_info] : secondary_sub_state.event_signaling_state) { |
| if (auto* primary_info = vvl::Find(event_signaling_state, event)) { |
| primary_info->signaled = secondary_info.signaled; |
| } else { |
| event_signaling_state.emplace(event, secondary_info); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordPushConstants(VkPipelineLayout layout, VkShaderStageFlags stage_flags, uint32_t offset, |
| uint32_t size, const void* values) { |
| PushConstantData push_constant_data; |
| push_constant_data.layout = layout; |
| push_constant_data.stage_flags = stage_flags; |
| push_constant_data.offset = offset; |
| push_constant_data.values.resize(size); |
| auto byte_values = static_cast<const std::byte*>(values); |
| std::copy(byte_values, byte_values + size, push_constant_data.values.data()); |
| push_constant_data_chunks.emplace_back(push_constant_data); |
| } |
| |
| void CommandBufferSubState::ClearPushConstants() { push_constant_data_chunks.clear(); } |
| |
| void CommandBufferSubState::RecordBeginRenderingCommon(const VkRenderPassBeginInfo* pRenderPassBegin, |
| const VkRenderingInfo* pRenderingInfo) { |
| auto rp_state = base.active_render_pass.get(); |
| ASSERT_AND_RETURN(rp_state); |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| vvl::ImageView* depth_image_view = nullptr; |
| std::optional<VkAttachmentLoadOp> load_op; |
| |
| if (pRenderingInfo) { // dynamic |
| const auto depth_attachment = pRenderingInfo->pDepthAttachment; |
| if (depth_attachment) { |
| load_op.emplace(depth_attachment->loadOp); |
| const auto depth_image_view_shared_ptr = base.dev_data.Get<vvl::ImageView>(depth_attachment->imageView); |
| if (depth_image_view_shared_ptr) { |
| depth_image_view = depth_image_view_shared_ptr.get(); |
| } |
| } |
| |
| for (uint32_t i = 0; i < pRenderingInfo->colorAttachmentCount; ++i) { |
| const auto& color_attachment = pRenderingInfo->pColorAttachments[i]; |
| if (color_attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) { |
| if (auto image_view_state = base.dev_data.Get<vvl::ImageView>(color_attachment.imageView)) { |
| const VkFormat format = image_view_state->create_info.format; |
| validator.RecordClearColor(format, color_attachment.clearValue.color); |
| } |
| } |
| } |
| |
| } else if (pRenderPassBegin) { // non-dynamic |
| if (rp_state->create_info.pAttachments) { |
| if (rp_state->create_info.subpassCount > 0) { |
| const auto depth_attachment = rp_state->create_info.pSubpasses[0].pDepthStencilAttachment; |
| if (depth_attachment) { |
| const uint32_t attachment_index = depth_attachment->attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| load_op.emplace(rp_state->create_info.pAttachments[attachment_index].loadOp); |
| depth_image_view = base.active_attachments[attachment_index].image_view; |
| } |
| } |
| } |
| for (uint32_t i = 0; i < pRenderPassBegin->clearValueCount; ++i) { |
| const auto& attachment = rp_state->create_info.pAttachments[i]; |
| if (attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) { |
| const auto& clear_color = pRenderPassBegin->pClearValues[i].color; |
| validator.RecordClearColor(attachment.format, clear_color); |
| } |
| } |
| } |
| } |
| if (depth_image_view && (depth_image_view->normalized_subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0U) { |
| const VkImage depth_image = depth_image_view->image_state->VkHandle(); |
| RecordBindZcullScopeNV(depth_image, depth_image_view->normalized_subresource_range); |
| } else { |
| RecordUnbindZcullScopeNV(); |
| } |
| if (load_op) { |
| if (*load_op == VK_ATTACHMENT_LOAD_OP_CLEAR || *load_op == VK_ATTACHMENT_LOAD_OP_DONT_CARE) { |
| RecordResetScopeZcullDirectionNV(); |
| } |
| } |
| } |
| |
| // Spec states that after BeginRenderPass all resources should be rebound |
| if (rp_state->has_multiview_enabled) { |
| UnbindResources(); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBeginRendering(const VkRenderingInfo& rendering_info, const Location&) { |
| RecordBeginRenderingCommon(nullptr, &rendering_info); |
| } |
| |
| void CommandBufferSubState::RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin, const VkSubpassBeginInfo&, |
| const Location&) { |
| RecordBeginRenderingCommon(&render_pass_begin, nullptr); |
| |
| auto rp_state = base.active_render_pass.get(); |
| ASSERT_AND_RETURN(rp_state); |
| |
| // Check load ops |
| for (uint32_t att = 0; att < rp_state->create_info.attachmentCount; att++) { |
| const auto& attachment = rp_state->create_info.pAttachments[att]; |
| |
| if (!RenderPassUsesAttachmentAsImageOnly(rp_state->create_info, att) && |
| !RenderPassUsesAttachmentOnTile(rp_state->create_info, att)) { |
| continue; |
| } |
| |
| // If renderpass doesn't load attachment, no need to validate image in queue |
| if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_NONE) || |
| (vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_NONE)) { |
| continue; |
| } |
| |
| IMAGE_SUBRESOURCE_USAGE_BP usage = IMAGE_SUBRESOURCE_USAGE_BP::UNDEFINED; |
| |
| if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_LOAD) || |
| (vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_LOAD)) { |
| usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_READ_TO_TILE; |
| } else if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR) || |
| (vkuFormatHasStencil(attachment.format) && attachment.stencilLoadOp == VK_ATTACHMENT_LOAD_OP_CLEAR)) { |
| usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_CLEARED; |
| } else if (RenderPassUsesAttachmentAsImageOnly(rp_state->create_info, att)) { |
| usage = IMAGE_SUBRESOURCE_USAGE_BP::DESCRIPTOR_ACCESS; |
| } |
| |
| VkImageView image_view = VK_NULL_HANDLE; |
| if (base.active_framebuffer) { |
| if (base.active_framebuffer->create_info.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) { |
| const VkRenderPassAttachmentBeginInfo* rpabi = |
| vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(render_pass_begin.pNext); |
| if (rpabi) { |
| image_view = rpabi->pAttachments[att]; |
| } |
| } else { |
| image_view = base.active_framebuffer->create_info.pAttachments[att]; |
| } |
| } |
| |
| if (auto image_view_state = base.dev_data.Get<vvl::ImageView>(image_view)) { |
| validator.QueueValidateImageView(queue_submit_functions, vvl::Func::vkCmdBeginRenderPass, *image_view_state, usage); |
| } |
| } |
| |
| // Check store ops |
| for (uint32_t att = 0; att < rp_state->create_info.attachmentCount; att++) { |
| const auto& attachment = rp_state->create_info.pAttachments[att]; |
| |
| if (!RenderPassUsesAttachmentOnTile(rp_state->create_info, att)) { |
| continue; |
| } |
| |
| // If renderpass doesn't store attachment, no need to validate image in queue |
| if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.storeOp == VK_ATTACHMENT_STORE_OP_NONE) || |
| (vkuFormatHasStencil(attachment.format) && attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_NONE)) { |
| continue; |
| } |
| |
| IMAGE_SUBRESOURCE_USAGE_BP usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_DISCARDED; |
| |
| if ((!vkuFormatIsStencilOnly(attachment.format) && attachment.storeOp == VK_ATTACHMENT_STORE_OP_STORE) || |
| (vkuFormatHasStencil(attachment.format) && attachment.stencilStoreOp == VK_ATTACHMENT_STORE_OP_STORE)) { |
| usage = IMAGE_SUBRESOURCE_USAGE_BP::RENDER_PASS_STORED; |
| } |
| |
| VkImageView image_view = VK_NULL_HANDLE; |
| if (base.active_framebuffer) { |
| if (base.active_framebuffer->create_info.flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT) { |
| const VkRenderPassAttachmentBeginInfo* rpabi = |
| vku::FindStructInPNextChain<VkRenderPassAttachmentBeginInfo>(render_pass_begin.pNext); |
| if (rpabi) { |
| image_view = rpabi->pAttachments[att]; |
| } |
| } else { |
| image_view = base.active_framebuffer->create_info.pAttachments[att]; |
| } |
| } |
| |
| if (auto image_view_state = base.dev_data.Get<vvl::ImageView>(image_view)) { |
| validator.QueueValidateImageView(queue_submit_functions_after_render_pass, vvl::Func::vkCmdEndRenderPass, |
| *image_view_state, usage); |
| } |
| } |
| |
| // Reset state for the render pass |
| { |
| render_pass_state.touchesAttachments.clear(); |
| render_pass_state.earlyClearAttachments.clear(); |
| render_pass_state.numDrawCallsDepthOnly = 0; |
| render_pass_state.numDrawCallsDepthEqualCompare = 0; |
| render_pass_state.colorAttachment = false; |
| render_pass_state.depthAttachment = false; |
| render_pass_state.drawTouchAttachments = true; |
| render_pass_state.has_draw_cmd = false; |
| // Don't reset state related to pipeline state. |
| |
| // Reset NV state |
| nv = {}; |
| |
| // track depth / color attachment usage within the renderpass |
| for (size_t i = 0; i < rp_state->create_info.subpassCount; i++) { |
| // record if depth/color attachments are in use for this renderpass |
| if (rp_state->create_info.pSubpasses[i].pDepthStencilAttachment != nullptr) render_pass_state.depthAttachment = true; |
| |
| if (rp_state->create_info.pSubpasses[i].colorAttachmentCount > 0) render_pass_state.colorAttachment = true; |
| } |
| // Spec states that after BeginRenderPass all resources should be rebound |
| if (base.active_render_pass && base.active_render_pass->has_multiview_enabled) { |
| UnbindResources(); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordNextSubpass(const VkSubpassBeginInfo&, const VkSubpassEndInfo*, const Location&) { |
| if (!base.active_render_pass) { |
| return; |
| } |
| |
| // Spec states that after NextSubpass all resources should be rebound |
| if (base.active_render_pass->has_multiview_enabled) { |
| UnbindResources(); |
| } |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| vvl::ImageView* depth_image_view = nullptr; |
| |
| const auto depth_attachment = |
| base.active_render_pass->create_info.pSubpasses[base.GetActiveSubpass()].pDepthStencilAttachment; |
| if (depth_attachment) { |
| const uint32_t attachment_index = depth_attachment->attachment; |
| if (attachment_index != VK_ATTACHMENT_UNUSED) { |
| depth_image_view = base.active_attachments[attachment_index].image_view; |
| } |
| } |
| if (depth_image_view && (depth_image_view->normalized_subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0U) { |
| const VkImage depth_image = depth_image_view->image_state->VkHandle(); |
| RecordBindZcullScopeNV(depth_image, depth_image_view->normalized_subresource_range); |
| } else { |
| RecordUnbindZcullScopeNV(); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordEndRenderingCommon(const vvl::RenderPass& rp_state) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| std::optional<VkAttachmentStoreOp> store_op; |
| |
| if (rp_state.UsesDynamicRendering()) { |
| const auto depth_attachment = rp_state.dynamic_rendering_begin_rendering_info.pDepthAttachment; |
| if (depth_attachment) { |
| store_op.emplace(depth_attachment->storeOp); |
| } |
| } else { |
| if (rp_state.create_info.subpassCount > 0) { |
| const uint32_t last_subpass = rp_state.create_info.subpassCount - 1; |
| const auto depth_attachment = rp_state.create_info.pSubpasses[last_subpass].pDepthStencilAttachment; |
| if (depth_attachment) { |
| const uint32_t attachment = depth_attachment->attachment; |
| if (attachment != VK_ATTACHMENT_UNUSED) { |
| store_op.emplace(rp_state.create_info.pAttachments[attachment].storeOp); |
| } |
| } |
| } |
| } |
| |
| if (store_op) { |
| if (*store_op == VK_ATTACHMENT_STORE_OP_DONT_CARE || *store_op == VK_ATTACHMENT_STORE_OP_NONE) { |
| RecordResetScopeZcullDirectionNV(); |
| } |
| } |
| |
| RecordUnbindZcullScopeNV(); |
| } |
| } |
| |
| void CommandBufferSubState::RecordEndRendering(const VkRenderingEndInfoEXT*) { |
| if (!base.active_render_pass) { |
| return; |
| } |
| RecordEndRenderingCommon(*base.active_render_pass); |
| } |
| |
| void CommandBufferSubState::RecordEndRenderPass(const VkSubpassEndInfo*, const Location&) { |
| if (!base.active_render_pass) { |
| return; |
| } |
| RecordEndRenderingCommon(*base.active_render_pass); |
| |
| // Add Deferred Queue |
| queue_submit_functions.insert(queue_submit_functions.end(), queue_submit_functions_after_render_pass.begin(), |
| queue_submit_functions_after_render_pass.end()); |
| queue_submit_functions_after_render_pass.clear(); |
| } |
| |
| void CommandBufferSubState::RecordCopyImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout, VkImageLayout, |
| uint32_t region_count, const VkImageCopy* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout, VkImageLayout, |
| uint32_t region_count, const VkImageCopy2* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBufferToImage(vvl::Buffer&, vvl::Image& dst_image_state, VkImageLayout, uint32_t region_count, |
| const VkBufferImageCopy* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE, |
| regions[i].imageSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyBufferToImage2(vvl::Buffer&, vvl::Image& dst_image_state, VkImageLayout, |
| uint32_t region_count, const VkBufferImageCopy2* regions, |
| const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_WRITE, |
| regions[i].imageSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyImageToBuffer(vvl::Image& src_image_state, vvl::Buffer&, VkImageLayout, uint32_t region_count, |
| const VkBufferImageCopy* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_READ, |
| regions[i].imageSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordCopyImageToBuffer2(vvl::Image& src_image_state, vvl::Buffer&, VkImageLayout, |
| uint32_t region_count, const VkBufferImageCopy2* regions, |
| const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::COPY_READ, |
| regions[i].imageSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBlitImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout, VkImageLayout, |
| uint32_t region_count, const VkImageBlit* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::BLIT_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::BLIT_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBlitImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout, VkImageLayout, |
| uint32_t region_count, const VkImageBlit2* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::BLIT_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::BLIT_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count, |
| const VkImageResolve* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count, |
| const VkImageResolve2* regions, const Location& loc) { |
| for (uint32_t i = 0; i < region_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, src_image_state, IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_READ, |
| regions[i].srcSubresource); |
| validator.QueueValidateImage(queue_submit_functions, loc, dst_image_state, IMAGE_SUBRESOURCE_USAGE_BP::RESOLVE_WRITE, |
| regions[i].dstSubresource); |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearColorImage(vvl::Image& image_state, VkImageLayout image_layout, |
| const VkClearColorValue* color_values, uint32_t range_count, |
| const VkImageSubresourceRange* ranges, const Location& loc) { |
| for (uint32_t i = 0; i < range_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, image_state, IMAGE_SUBRESOURCE_USAGE_BP::CLEARED, ranges[i]); |
| } |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| validator.RecordClearColor(image_state.GetFormat(), *color_values); |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout image_layout, |
| const VkClearDepthStencilValue* depth_stencil_values, uint32_t range_count, |
| const VkImageSubresourceRange* ranges, const Location& loc) { |
| for (uint32_t i = 0; i < range_count; i++) { |
| validator.QueueValidateImage(queue_submit_functions, loc, image_state, IMAGE_SUBRESOURCE_USAGE_BP::CLEARED, ranges[i]); |
| } |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| for (uint32_t i = 0; i < range_count; i++) { |
| RecordResetZcullDirectionNV(image_state, ranges[i]); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments, |
| uint32_t rect_count, const VkClearRect* pRects, const Location&) { |
| auto* rp_state = base.active_render_pass.get(); |
| auto* fb_state = base.active_framebuffer.get(); |
| if (rect_count == 0 || !rp_state) { |
| return; |
| } |
| if (!base.IsSecondary() && !fb_state && !rp_state->UsesDynamicRendering()) { |
| return; |
| } |
| |
| // If we have a rect which covers the entire frame buffer, we have a LOAD_OP_CLEAR-like command. |
| const bool full_clear = validator.ClearAttachmentsIsFullClear(*this, rect_count, pRects); |
| |
| if (rp_state->UsesDynamicRendering()) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| auto pColorAttachments = rp_state->dynamic_rendering_begin_rendering_info.pColorAttachments; |
| |
| for (uint32_t i = 0; i < attachment_count; i++) { |
| auto& clear_attachment = pAttachments[i]; |
| |
| if (clear_attachment.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { |
| RecordResetScopeZcullDirectionNV(); |
| } |
| if ((clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) && |
| clear_attachment.colorAttachment != VK_ATTACHMENT_UNUSED && pColorAttachments) { |
| const auto& attachment = pColorAttachments[clear_attachment.colorAttachment]; |
| if (attachment.imageView) { |
| if (auto image_view_state = base.dev_data.Get<vvl::ImageView>(attachment.imageView)) { |
| const VkFormat format = image_view_state->create_info.format; |
| validator.RecordClearColor(format, clear_attachment.clearValue.color); |
| } |
| } |
| } |
| } |
| } |
| |
| // TODO: Implement other best practices for dynamic rendering |
| |
| } else { |
| auto& subpass = rp_state->create_info.pSubpasses[base.GetActiveSubpass()]; |
| for (uint32_t i = 0; i < attachment_count; i++) { |
| auto& attachment = pAttachments[i]; |
| uint32_t fb_attachment = VK_ATTACHMENT_UNUSED; |
| VkImageAspectFlags aspects = attachment.aspectMask; |
| |
| if (aspects & VK_IMAGE_ASPECT_DEPTH_BIT) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordResetScopeZcullDirectionNV(); |
| } |
| } |
| if (aspects & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { |
| if (subpass.pDepthStencilAttachment) { |
| fb_attachment = subpass.pDepthStencilAttachment->attachment; |
| } |
| } else if (aspects & VK_IMAGE_ASPECT_COLOR_BIT) { |
| fb_attachment = subpass.pColorAttachments[attachment.colorAttachment].attachment; |
| } |
| if (fb_attachment != VK_ATTACHMENT_UNUSED) { |
| if (full_clear) { |
| RecordAttachmentClearAttachments(fb_attachment, attachment.colorAttachment, aspects, rect_count, pRects); |
| } else { |
| RecordAttachmentAccess(fb_attachment, aspects); |
| } |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| const VkFormat format = rp_state->create_info.pAttachments[fb_attachment].format; |
| validator.RecordClearColor(format, attachment.clearValue.color); |
| } |
| } |
| } |
| } |
| } |
| |
| void CommandBufferSubState::Destroy() { ResetCBState(); } |
| |
| void CommandBufferSubState::Reset(const Location&) { ResetCBState(); } |
| |
| void CommandBufferSubState::ResetCBState() { |
| num_submits = 0; |
| small_indexed_draw_call_count = 0; |
| queue_submit_functions.clear(); |
| queue_submit_functions_after_render_pass.clear(); |
| ClearPushConstants(); |
| } |
| |
| void CommandBufferSubState::RecordActionCommand(LastBound& last_bound, const Location& loc) { |
| if (vvl::IsCommandDrawMesh(loc.function) || vvl::IsCommandDrawVertex(loc.function)) { |
| render_pass_state.has_draw_cmd = true; |
| } |
| |
| // Draw |
| if (last_bound.bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordCmdDrawTypeNVIDIA(); |
| } |
| |
| if (render_pass_state.drawTouchAttachments) { |
| for (auto& touch : render_pass_state.nextDrawTouchesAttachments) { |
| RecordAttachmentAccess(touch.framebufferAttachment, touch.aspects); |
| } |
| // No need to touch the same attachments over and over. |
| render_pass_state.drawTouchAttachments = false; |
| } |
| } |
| |
| validator.UpdateBoundDescriptorSets(*this, last_bound, loc); |
| } |
| |
| void CommandBufferSubState::RecordSetEvent(VkEvent event, VkPipelineStageFlags) { |
| if (auto* signaling_info = vvl::Find(event_signaling_state, event)) { |
| signaling_info->signaled = true; |
| } else { |
| event_signaling_state.emplace(event, bp_state::CommandBufferSubState::SignalingInfo(true)); |
| } |
| } |
| |
| void CommandBufferSubState::RecordSetEvent2(VkEvent event, const VkDependencyInfo&, const Location&) { |
| if (auto* signaling_info = vvl::Find(event_signaling_state, event)) { |
| signaling_info->signaled = true; |
| } else { |
| event_signaling_state.emplace(event, bp_state::CommandBufferSubState::SignalingInfo(true)); |
| } |
| } |
| |
| void CommandBufferSubState::RecordResetEvent(VkEvent event, VkPipelineStageFlags2, const Location&) { |
| if (auto* signaling_info = vvl::Find(event_signaling_state, event)) { |
| signaling_info->signaled = false; |
| } else { |
| event_signaling_state.emplace(event, bp_state::CommandBufferSubState::SignalingInfo(false)); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBarriers(uint32_t, const VkBufferMemoryBarrier*, uint32_t image_barrier_count, |
| const VkImageMemoryBarrier* image_barriers, VkPipelineStageFlags, VkPipelineStageFlags, |
| const Location& loc) { |
| if (loc.function != vvl::Func::vkCmdPipelineBarrier) { |
| return; // TODO - This has only been tested with pipeline barriers, need to try Events |
| } |
| |
| for (uint32_t i = 0; i < image_barrier_count; ++i) { |
| const VkImageMemoryBarrier barrier = image_barriers[i]; |
| auto image_state = base.dev_data.Get<vvl::Image>(barrier.image); |
| ASSERT_AND_RETURN(image_state); |
| |
| // Is a queue ownership acquisition barrier |
| if (barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex && |
| barrier.dstQueueFamilyIndex == base.command_pool.queueFamilyIndex) { |
| auto subresource_range = barrier.subresourceRange; |
| queue_submit_functions.emplace_back( |
| [image_state, subresource_range](const vvl::Queue& qs, const vvl::CommandBuffer& cbs) -> bool { |
| ForEachSubresource(*image_state, subresource_range, [&](uint32_t layer, uint32_t level) { |
| // Update queue family index without changing usage, signifying a correct queue family transfer |
| auto& sub_state = bp_state::SubState(*image_state); |
| sub_state.UpdateUsage(layer, level, sub_state.GetUsageType(layer, level), qs.queue_family_index); |
| }); |
| return false; |
| }); |
| } |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordResetZcullDirectionNV(*image_state, barrier.subresourceRange); |
| } |
| } |
| } |
| |
| void CommandBufferSubState::RecordBarriers2(const VkDependencyInfo& dep_info, const Location& loc) { |
| if (!IsValueIn(loc.function, {vvl::Func::vkCmdPipelineBarrier2, vvl::Func::vkCmdPipelineBarrier2KHR})) { |
| return; // TODO - This has only been tested with pipeline barriers, need to try Events |
| } |
| |
| for (uint32_t i = 0; i < dep_info.imageMemoryBarrierCount; ++i) { |
| const VkImageMemoryBarrier2 barrier = dep_info.pImageMemoryBarriers[i]; |
| auto image_state = base.dev_data.Get<vvl::Image>(barrier.image); |
| ASSERT_AND_RETURN(image_state); |
| |
| // Is a queue ownership acquisition barrier |
| if (barrier.srcQueueFamilyIndex != barrier.dstQueueFamilyIndex && |
| barrier.dstQueueFamilyIndex == base.command_pool.queueFamilyIndex) { |
| auto subresource_range = barrier.subresourceRange; |
| queue_submit_functions.emplace_back( |
| [image_state, subresource_range](const vvl::Queue& qs, const vvl::CommandBuffer& cbs) -> bool { |
| ForEachSubresource(*image_state, subresource_range, [&](uint32_t layer, uint32_t level) { |
| // Update queue family index without changing usage, signifying a correct queue family transfer |
| auto& sub_state = bp_state::SubState(*image_state); |
| sub_state.UpdateUsage(layer, level, sub_state.GetUsageType(layer, level), qs.queue_family_index); |
| }); |
| return false; |
| }); |
| } |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordResetZcullDirectionNV(*image_state, barrier.subresourceRange); |
| } |
| } |
| } |
| |
| static std::vector<bp_state::AttachmentInfo> GetAttachmentAccess(vvl::Pipeline& pipeline) { |
| std::vector<bp_state::AttachmentInfo> result; |
| auto rp = pipeline.RenderPassState(); |
| if (!rp || rp->UsesDynamicRendering()) { |
| return result; |
| } |
| |
| const auto& subpass = rp->create_info.pSubpasses[pipeline.Subpass()]; |
| |
| // NOTE: see PIPELINE_LAYOUT and vku::safe_VkGraphicsPipelineCreateInfo constructors. pColorBlendState and pDepthStencilState |
| // are only non-null if they are enabled. |
| const auto* color_blend_state = pipeline.ColorBlendState(); |
| if (color_blend_state && !(pipeline.ignore_color_attachments)) { |
| // According to spec, pColorBlendState must be ignored if subpass does not have color attachments. |
| uint32_t num_color_attachments = std::min(subpass.colorAttachmentCount, color_blend_state->attachmentCount); |
| for (uint32_t j = 0; j < num_color_attachments; j++) { |
| if (color_blend_state->pAttachments[j].colorWriteMask != 0) { |
| uint32_t attachment = subpass.pColorAttachments[j].attachment; |
| if (attachment != VK_ATTACHMENT_UNUSED) { |
| result.emplace_back(attachment, VK_IMAGE_ASPECT_COLOR_BIT); |
| } |
| } |
| } |
| } |
| |
| const auto* ds_state = pipeline.DepthStencilState(); |
| if (ds_state && (ds_state->depthTestEnable || ds_state->depthBoundsTestEnable || ds_state->stencilTestEnable)) { |
| uint32_t attachment = subpass.pDepthStencilAttachment ? subpass.pDepthStencilAttachment->attachment : VK_ATTACHMENT_UNUSED; |
| if (attachment != VK_ATTACHMENT_UNUSED) { |
| VkImageAspectFlags aspects = 0; |
| if (ds_state->depthTestEnable || ds_state->depthBoundsTestEnable) { |
| aspects |= VK_IMAGE_ASPECT_DEPTH_BIT; |
| } |
| if (ds_state->stencilTestEnable) { |
| aspects |= VK_IMAGE_ASPECT_STENCIL_BIT; |
| } |
| result.emplace_back(attachment, aspects); |
| } |
| } |
| return result; |
| } |
| |
| void CommandBufferSubState::RecordSetDepthTestStateNV(VkCompareOp new_depth_compare_op, bool new_depth_test_enable) { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| if (nv.depth_compare_op != new_depth_compare_op) { |
| switch (new_depth_compare_op) { |
| case VK_COMPARE_OP_LESS: |
| case VK_COMPARE_OP_LESS_OR_EQUAL: |
| nv.zcull_direction = ZcullDirection::Less; |
| break; |
| case VK_COMPARE_OP_GREATER: |
| case VK_COMPARE_OP_GREATER_OR_EQUAL: |
| nv.zcull_direction = ZcullDirection::Greater; |
| break; |
| default: |
| // The other ops carry over the previous state. |
| break; |
| } |
| } |
| nv.depth_compare_op = new_depth_compare_op; |
| nv.depth_test_enable = new_depth_test_enable; |
| } |
| |
| void CommandBufferSubState::RecordSetDepthCompareOp(VkCompareOp depth_compare_op) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordSetDepthTestStateNV(depth_compare_op, nv.depth_test_enable); |
| } |
| } |
| |
| void CommandBufferSubState::RecordSetDepthTestEnable(VkBool32 depth_test_enable) { |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| RecordSetDepthTestStateNV(nv.depth_compare_op, depth_test_enable); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBindPipeline(VkPipelineBindPoint bind_point, vvl::Pipeline& pipeline) { |
| if (bind_point == VK_PIPELINE_BIND_POINT_GRAPHICS) { |
| // AMD best practice |
| validator.PipelineUsedInFrame(pipeline.VkHandle()); |
| |
| render_pass_state.nextDrawTouchesAttachments = GetAttachmentAccess(pipeline); |
| render_pass_state.drawTouchAttachments = true; |
| |
| const auto* blend_state = pipeline.ColorBlendState(); |
| const auto* stencil_state = pipeline.DepthStencilState(); |
| |
| if (blend_state && !(pipeline.ignore_color_attachments)) { |
| // assume the pipeline is depth-only unless any of the attachments have color writes enabled |
| render_pass_state.depthOnly = true; |
| for (size_t i = 0; i < blend_state->attachmentCount; i++) { |
| if (blend_state->pAttachments[i].colorWriteMask != 0) { |
| render_pass_state.depthOnly = false; |
| } |
| } |
| } |
| |
| // check for depth value usage |
| render_pass_state.depthEqualComparison = false; |
| |
| if (stencil_state && stencil_state->depthTestEnable) { |
| switch (stencil_state->depthCompareOp) { |
| case VK_COMPARE_OP_EQUAL: |
| case VK_COMPARE_OP_GREATER_OR_EQUAL: |
| case VK_COMPARE_OP_LESS_OR_EQUAL: |
| render_pass_state.depthEqualComparison = true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (validator.VendorCheckEnabled(kBPVendorNVIDIA)) { |
| using TessGeometryMeshState = bp_state::CommandBufferStateNV::TessGeometryMesh::State; |
| auto& tgm = nv.tess_geometry_mesh; |
| |
| // Make sure the message is only signaled once per command buffer |
| tgm.threshold_signaled = tgm.num_switches >= kNumBindPipelineTessGeometryMeshSwitchesThresholdNVIDIA; |
| |
| // Track pipeline switches with tessellation, geometry, and/or mesh shaders enabled, and disabled |
| auto tgm_stages = VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | |
| VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT; |
| auto new_tgm_state = |
| (pipeline.active_shaders & tgm_stages) != 0 ? TessGeometryMeshState::Enabled : TessGeometryMeshState::Disabled; |
| if (tgm.state != new_tgm_state && tgm.state != TessGeometryMeshState::Unknown) { |
| tgm.num_switches++; |
| } |
| tgm.state = new_tgm_state; |
| |
| // Track depthTestEnable and depthCompareOp |
| const auto* depth_stencil_state = pipeline.DepthStencilState(); |
| auto dynamic_state = pipeline.GraphicsCreateInfo().pDynamicState; |
| if (depth_stencil_state && dynamic_state) { |
| auto dynamic_state_begin = dynamic_state->pDynamicStates; |
| auto dynamic_state_end = dynamic_state->pDynamicStates + dynamic_state->dynamicStateCount; |
| |
| const bool dynamic_depth_test_enable = |
| std::find(dynamic_state_begin, dynamic_state_end, VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE) != dynamic_state_end; |
| const bool dynamic_depth_func = |
| std::find(dynamic_state_begin, dynamic_state_end, VK_DYNAMIC_STATE_DEPTH_COMPARE_OP) != dynamic_state_end; |
| |
| if (!dynamic_depth_test_enable) { |
| RecordSetDepthTestStateNV(nv.depth_compare_op, depth_stencil_state->depthTestEnable != VK_FALSE); |
| } |
| if (!dynamic_depth_func) { |
| RecordSetDepthTestStateNV(depth_stencil_state->depthCompareOp, nv.depth_test_enable); |
| } |
| } |
| } |
| } |
| } |
| |
| void CommandBufferSubState::Submit(vvl::Queue& queue_state, uint32_t perf_submit_pass, const Location& loc) { |
| for (auto& func : queue_submit_functions) { |
| func(queue_state, base); |
| } |
| } |
| |
| void CommandBufferSubState::RecordAttachmentAccess(uint32_t attachment, VkImageAspectFlags aspects) { |
| // Called when we have a partial clear attachment, or a normal draw call which accesses an attachment. |
| auto itr = |
| std::find_if(render_pass_state.touchesAttachments.begin(), render_pass_state.touchesAttachments.end(), |
| [attachment](const bp_state::AttachmentInfo& info) { return info.framebufferAttachment == attachment; }); |
| |
| if (itr != render_pass_state.touchesAttachments.end()) { |
| itr->aspects |= aspects; |
| } else { |
| render_pass_state.touchesAttachments.emplace_back(attachment, aspects); |
| } |
| } |
| |
| void CommandBufferSubState::RecordAttachmentClearAttachments(uint32_t fb_attachment, uint32_t color_attachment, |
| VkImageAspectFlags aspects, uint32_t rectCount, |
| const VkClearRect* pRects) { |
| // If we observe a full clear before any other access to a frame buffer attachment, |
| // we have candidate for redundant clear attachments. |
| auto itr = |
| std::find_if(render_pass_state.touchesAttachments.begin(), render_pass_state.touchesAttachments.end(), |
| [fb_attachment](const bp_state::AttachmentInfo& info) { return info.framebufferAttachment == fb_attachment; }); |
| |
| uint32_t new_aspects = aspects; |
| if (itr != render_pass_state.touchesAttachments.end()) { |
| new_aspects = aspects & ~itr->aspects; |
| itr->aspects |= aspects; |
| } else { |
| render_pass_state.touchesAttachments.emplace_back(fb_attachment, aspects); |
| } |
| |
| if (new_aspects == 0) { |
| return; |
| } |
| |
| if (base.IsSecondary()) { |
| // The first command might be a clear, but might not be the first in the render pass, defer any checks until |
| // CmdExecuteCommands. |
| render_pass_state.earlyClearAttachments.push_back( |
| {fb_attachment, color_attachment, new_aspects, std::vector<VkClearRect>{pRects, pRects + rectCount}}); |
| } |
| } |
| |
| void CommandBufferSubState::RecordBindZcullScopeNV(VkImage depth_attachment, const VkImageSubresourceRange& subresource_range) { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| if (depth_attachment == VK_NULL_HANDLE) { |
| nv.zcull_scope = {}; |
| return; |
| } |
| |
| assert((subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0U); |
| |
| auto image_state = base.dev_data.Get<vvl::Image>(depth_attachment); |
| ASSERT_AND_RETURN(image_state); |
| |
| const uint32_t mip_levels = image_state->GetMipLevels(); |
| const uint32_t array_layers = image_state->GetArrayLayers(); |
| |
| auto& tree = nv.zcull_per_image[depth_attachment]; |
| if (tree.states.empty()) { |
| tree.mip_levels = mip_levels; |
| tree.array_layers = array_layers; |
| tree.states.resize(array_layers * mip_levels); |
| } |
| |
| nv.zcull_scope.image = depth_attachment; |
| nv.zcull_scope.range = subresource_range; |
| nv.zcull_scope.tree = &tree; |
| } |
| |
| void CommandBufferSubState::RecordUnbindZcullScopeNV() { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| RecordBindZcullScopeNV(VK_NULL_HANDLE, VkImageSubresourceRange{}); |
| } |
| |
| void CommandBufferSubState::RecordResetScopeZcullDirectionNV() { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| auto& scope = nv.zcull_scope; |
| auto image_state = base.dev_data.Get<vvl::Image>(scope.image); |
| if (!image_state) { |
| return; |
| } |
| RecordResetZcullDirectionNV(*image_state, scope.range); |
| } |
| |
| void CommandBufferSubState::RecordResetZcullDirectionNV(vvl::Image& depth_image, const VkImageSubresourceRange& subresource_range) { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| RecordSetZcullDirectionNV(depth_image, subresource_range, ZcullDirection::Unknown); |
| |
| const auto image_it = nv.zcull_per_image.find(depth_image.VkHandle()); |
| if (image_it == nv.zcull_per_image.end()) { |
| return; |
| } |
| auto& tree = image_it->second; |
| |
| ForEachSubresource(depth_image, subresource_range, [&tree](uint32_t layer, uint32_t level) { |
| auto& subresource = tree.GetState(layer, level); |
| subresource.num_less_draws = 0; |
| subresource.num_greater_draws = 0; |
| }); |
| } |
| |
| void CommandBufferSubState::RecordSetZcullDirectionNV(vvl::Image& depth_image, const VkImageSubresourceRange& subresource_range, |
| ZcullDirection mode) { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| const auto image_it = nv.zcull_per_image.find(depth_image.VkHandle()); |
| if (image_it == nv.zcull_per_image.end()) { |
| return; |
| } |
| auto& tree = image_it->second; |
| |
| ForEachSubresource(depth_image, subresource_range, [&tree, this](uint32_t layer, uint32_t level) { |
| tree.GetState(layer, level).direction = nv.zcull_direction; |
| }); |
| } |
| |
| void CommandBufferSubState::RecordSetScopeZcullDirectionNV(ZcullDirection mode) { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| auto& scope = nv.zcull_scope; |
| auto image_state = base.dev_data.Get<vvl::Image>(scope.image); |
| if (!image_state) { |
| return; |
| } |
| RecordSetZcullDirectionNV(*image_state, scope.range, mode); |
| } |
| |
| void CommandBufferSubState::RecordZcullDrawNV() { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| // Add one draw to each subresource depending on the current Z-cull direction |
| auto& scope = nv.zcull_scope; |
| |
| auto image = base.dev_data.Get<vvl::Image>(scope.image); |
| if (!image) { |
| return; |
| } |
| |
| ForEachSubresource(*image, scope.range, [&scope](uint32_t layer, uint32_t level) { |
| auto& subresource = scope.tree->GetState(layer, level); |
| |
| switch (subresource.direction) { |
| case ZcullDirection::Unknown: |
| // Unreachable |
| assert(false); |
| break; |
| case ZcullDirection::Less: |
| ++subresource.num_less_draws; |
| break; |
| case ZcullDirection::Greater: |
| ++subresource.num_greater_draws; |
| break; |
| } |
| }); |
| } |
| |
| void CommandBufferSubState::RecordCmdDrawTypeNVIDIA() { |
| assert(validator.VendorCheckEnabled(kBPVendorNVIDIA)); |
| |
| if (nv.depth_test_enable && nv.zcull_direction != ZcullDirection::Unknown) { |
| RecordSetScopeZcullDirectionNV(nv.zcull_direction); |
| RecordZcullDrawNV(); |
| } |
| } |
| |
| } // namespace bp_state |