blob: 910c8e2c634144648f3fca9955f6bad10a4696c9 [file]
/* 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