blob: b01ba5ecf641dd08115df23e9d35d725cdfd2c92 [file]
/*
* Copyright (c) 2019-2026 Valve Corporation
* Copyright (c) 2019-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 <vulkan/utility/vk_format_utils.h>
#include "sync/sync_command_buffer.h"
#include "error_message/error_location.h"
#include "sync/sync_image.h"
#include "sync/sync_op.h"
#include "sync/sync_reporting.h"
#include "sync/sync_validation.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/image_state.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/ray_tracing_state.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/shader_module.h"
#include "state_tracker/pipeline_state.h"
#include "utils/image_utils.h"
#include "utils/math_utils.h"
#include "utils/text_utils.h"
namespace syncval {
constexpr VkImageAspectFlags kColorAspects =
VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT;
struct ShaderStageAccesses {
SyncAccessIndex sampled_read;
SyncAccessIndex storage_read;
SyncAccessIndex storage_write;
SyncAccessIndex uniform_read;
SyncAccessIndex acceleration_structure_read;
};
// TODO: generate me
static ShaderStageAccesses GetShaderStageAccesses(VkShaderStageFlagBits shader_stage) {
static const vvl::unordered_map<VkShaderStageFlagBits, ShaderStageAccesses> map = {
// clang-format off
{VK_SHADER_STAGE_VERTEX_BIT, {
SYNC_VERTEX_SHADER_SHADER_SAMPLED_READ,
SYNC_VERTEX_SHADER_SHADER_STORAGE_READ,
SYNC_VERTEX_SHADER_SHADER_STORAGE_WRITE,
SYNC_VERTEX_SHADER_UNIFORM_READ,
SYNC_VERTEX_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, {
SYNC_TESSELLATION_CONTROL_SHADER_SHADER_SAMPLED_READ,
SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_READ,
SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_WRITE,
SYNC_TESSELLATION_CONTROL_SHADER_UNIFORM_READ,
SYNC_TESSELLATION_CONTROL_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, {
SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_SAMPLED_READ,
SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_READ,
SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_WRITE,
SYNC_TESSELLATION_EVALUATION_SHADER_UNIFORM_READ,
SYNC_TESSELLATION_EVALUATION_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_GEOMETRY_BIT, {
SYNC_GEOMETRY_SHADER_SHADER_SAMPLED_READ,
SYNC_GEOMETRY_SHADER_SHADER_STORAGE_READ,
SYNC_GEOMETRY_SHADER_SHADER_STORAGE_WRITE,
SYNC_GEOMETRY_SHADER_UNIFORM_READ,
SYNC_GEOMETRY_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_FRAGMENT_BIT, {
SYNC_FRAGMENT_SHADER_SHADER_SAMPLED_READ,
SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ,
SYNC_FRAGMENT_SHADER_SHADER_STORAGE_WRITE,
SYNC_FRAGMENT_SHADER_UNIFORM_READ,
SYNC_FRAGMENT_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_COMPUTE_BIT, {
SYNC_COMPUTE_SHADER_SHADER_SAMPLED_READ,
SYNC_COMPUTE_SHADER_SHADER_STORAGE_READ,
SYNC_COMPUTE_SHADER_SHADER_STORAGE_WRITE,
SYNC_COMPUTE_SHADER_UNIFORM_READ,
SYNC_COMPUTE_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_RAYGEN_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_ANY_HIT_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_MISS_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_INTERSECTION_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_CALLABLE_BIT_KHR, {
SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_TASK_BIT_EXT, {
SYNC_TASK_SHADER_EXT_SHADER_SAMPLED_READ,
SYNC_TASK_SHADER_EXT_SHADER_STORAGE_READ,
SYNC_TASK_SHADER_EXT_SHADER_STORAGE_WRITE,
SYNC_TASK_SHADER_EXT_UNIFORM_READ,
SYNC_TASK_SHADER_EXT_ACCELERATION_STRUCTURE_READ,
}},
{VK_SHADER_STAGE_MESH_BIT_EXT, {
SYNC_MESH_SHADER_EXT_SHADER_SAMPLED_READ,
SYNC_MESH_SHADER_EXT_SHADER_STORAGE_READ,
SYNC_MESH_SHADER_EXT_SHADER_STORAGE_WRITE,
SYNC_MESH_SHADER_EXT_UNIFORM_READ,
SYNC_MESH_SHADER_EXT_ACCELERATION_STRUCTURE_READ,
}},
// clang-format on
};
auto it = map.find(shader_stage);
assert(it != map.end());
return it->second;
}
static AccessRange MakeRangeForVertexData(VkDeviceSize offset, uint32_t first_vertex, uint32_t vertex_count,
const VertexBindingState& vertex_binding) {
uint32_t element_size = 0;
for (const auto& [_, vertex_attrib] : vertex_binding.locations) {
element_size = std::max(element_size, vertex_attrib.desc.offset + GetVertexInputFormatSize(vertex_attrib.desc.format));
}
const VkDeviceSize range_start = offset + (first_vertex * vertex_binding.desc.stride);
VkDeviceSize range_size = 0;
if (vertex_count > 0) {
// Take into account stride between elements but not after the last element.
range_size = (vertex_count - 1) * vertex_binding.desc.stride + element_size;
}
return MakeRange(range_start, range_size);
}
static AccessRange MakeRangeForIndexData(VkDeviceSize offset, uint32_t first_index, uint32_t index_count, uint32_t index_size) {
const VkDeviceSize range_start = offset + (first_index * index_size);
const VkDeviceSize range_size = index_count * index_size;
return MakeRange(range_start, range_size);
}
static AccessRange MakeRange(const vvl::BufferView& buf_view_state) {
return MakeRange(*buf_view_state.buffer_state.get(), buf_view_state.create_info.offset, buf_view_state.create_info.range);
}
static SyncAccessIndex GetSyncStageAccessIndexsByDescriptorSet(VkDescriptorType descriptor_type,
const spirv::ResourceInterfaceVariable& variable,
VkShaderStageFlagBits stage_flag) {
if (!variable.IsAccessed()) {
return SYNC_ACCESS_INDEX_NONE;
}
if (descriptor_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) {
assert(stage_flag == VK_SHADER_STAGE_FRAGMENT_BIT);
return SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ;
}
const auto stage_accesses = GetShaderStageAccesses(stage_flag);
if (descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) {
return stage_accesses.uniform_read;
}
if (descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR ||
descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV ||
descriptor_type == VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV) {
return stage_accesses.acceleration_structure_read;
}
// If the desriptorSet is writable, we don't need to care SHADER_READ. SHADER_WRITE is enough.
// Because if write hazard happens, read hazard might or might not happen.
// But if write hazard doesn't happen, read hazard is impossible to happen.
if (variable.IsWrittenTo()) {
return stage_accesses.storage_write;
} else if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER) {
return stage_accesses.sampled_read;
} else {
if (variable.IsImage() && !variable.IsImageReadFrom()) {
// only image descriptor was accessed, not the image data
return SYNC_ACCESS_INDEX_NONE;
}
return stage_accesses.storage_read;
}
}
static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage,
const VkImageSubresourceRange& subresource_range, const ResourceUsageTag& tag) {
const auto& sub_state = SubState(image);
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false);
access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag});
}
static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage,
const VkImageSubresourceRange& subresource_range, const VkOffset3D& offset,
const VkExtent3D& extent, ResourceUsageTagEx tag_ex) {
const auto& sub_state = SubState(image);
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, offset, extent, false);
access_context.UpdateAccessState(range_gen, current_usage, tag_ex);
}
static void UpdateVideoAccessState(AccessContext& access_context, const vvl::VideoSession& vs_state,
const vvl::VideoPictureResource& resource, SyncAccessIndex current_usage, ResourceUsageTag tag) {
const auto image = static_cast<const vvl::Image*>(resource.image_state.get());
const auto offset = resource.GetEffectiveImageOffset(vs_state);
const auto extent = resource.GetEffectiveImageExtent(vs_state);
const auto& sub_state = SubState(*image);
ImageRangeGen range_gen(sub_state.MakeImageRangeGen(resource.range, offset, extent, false));
access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag});
}
CommandExecutionContext::CommandExecutionContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags)
: sync_state_(sync_validator), error_messages_(sync_validator.error_messages_), queue_flags_(queue_flags) {}
bool CommandExecutionContext::ValidForSyncOps() const {
const bool valid = GetCurrentEventsContext() && GetCurrentAccessContext();
assert(valid);
return valid;
}
CommandBufferAccessContext::CommandBufferAccessContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags)
: CommandExecutionContext(sync_validator, queue_flags),
cb_state_(),
access_log_(std::make_shared<AccessLog>()),
cbs_referenced_(std::make_shared<CommandBufferSet>()),
command_number_(0),
reset_count_(0),
cb_access_context_(sync_validator),
current_context_(&cb_access_context_),
events_context_(),
render_pass_contexts_(),
current_renderpass_context_(),
sync_ops_() {}
CommandBufferAccessContext::CommandBufferAccessContext(SyncValidator& sync_validator, vvl::CommandBuffer* cb_state)
: CommandBufferAccessContext(sync_validator, cb_state->GetQueueFlags()) {
cb_state_ = cb_state;
sync_state_.stats.AddCommandBufferContext();
}
// NOTE: Make sure the proxy doesn't outlive from, as the proxy is pointing directly to access contexts owned by from.
CommandBufferAccessContext::CommandBufferAccessContext(const CommandBufferAccessContext& from, AsProxyContext dummy)
: CommandBufferAccessContext(from.sync_state_, from.cb_state_->GetQueueFlags()) {
// Copy only the needed fields out of from for a temporary, proxy command buffer context
cb_state_ = from.cb_state_;
access_log_ = std::make_shared<AccessLog>(*from.access_log_); // potentially large, but no choice given tagging lookup.
command_number_ = from.command_number_;
reset_count_ = from.reset_count_;
handles_ = from.handles_;
sync_state_.stats.AddHandleRecord((uint32_t)from.handles_.size());
const auto* from_context = from.GetCurrentAccessContext();
assert(from_context);
// Construct a fully resolved single access context out of from
cb_access_context_.ResolveFromContext(*from_context);
// The proxy has flatten the current render pass context (if any), but the async contexts are needed for hazard detection
cb_access_context_.ImportAsyncContexts(*from_context);
events_context_ = from.events_context_;
// We don't want to copy the full render_pass_context_ history just for the proxy.
sync_state_.stats.AddCommandBufferContext();
}
CommandBufferAccessContext::~CommandBufferAccessContext() {
sync_state_.stats.RemoveCommandBufferContext();
sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size());
}
void CommandBufferAccessContext::Reset() {
access_log_ = std::make_shared<AccessLog>();
cbs_referenced_ = std::make_shared<CommandBufferSet>();
if (cb_state_) {
cbs_referenced_->push_back(cb_state_->shared_from_this());
}
sync_ops_.clear();
command_number_ = 0;
reset_count_++;
sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size());
handles_.clear();
current_command_tag_ = vvl::kNoIndex32;
cb_access_context_.Reset();
render_pass_contexts_.clear();
current_context_ = &cb_access_context_;
current_renderpass_context_ = nullptr;
events_context_.Clear();
dynamic_rendering_info_.reset();
}
bool CommandBufferAccessContext::ValidateBeginRendering(const ErrorObject& error_obj, BeginRenderingCmdState& cmd_state) const {
bool skip = false;
const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo();
// Load operations do not happen when resuming
if (info.info.flags & VK_RENDERING_RESUMING_BIT) {
return skip;
}
// Need to hazard detect load operations vs. the attachment views
for (size_t i = 0; i < info.attachments.size(); i++) {
const auto& attachment = info.attachments[i];
const SyncAccessIndex load_index = attachment.GetLoadUsage();
if (load_index == SYNC_ACCESS_INDEX_NONE) {
continue;
}
const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp);
ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask);
const HazardResult hazard = GetCurrentAccessContext()->DetectAttachmentHazard(range_gen, load_index, attachment_access);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());
std::ostringstream ss;
ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields();
ss << " (" << sync_state_.FormatHandle(attachment.view->Handle());
ss << ", loadOp " << string_VkAttachmentLoadOp(attachment.info.loadOp) << ")";
std::string resource_description = ss.str();
const std::string error = sync_state_.error_messages_.BeginRenderingError(hazard, *this, error_obj.location.function,
resource_description, attachment.info.loadOp);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
if (skip) {
break;
}
}
}
return skip;
}
void CommandBufferAccessContext::RecordBeginRendering(BeginRenderingCmdState& cmd_state, const Location& loc) {
const auto tag = NextCommandTag(loc.function);
const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo();
if ((info.info.flags & VK_RENDERING_RESUMING_BIT) == 0) {
for (size_t i = 0; i < info.attachments.size(); i++) {
const DynamicRenderingInfo::Attachment& attachment = info.attachments[i];
const SyncAccessIndex load_index = attachment.GetLoadUsage();
if (load_index == SYNC_ACCESS_INDEX_NONE) {
continue;
}
ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask);
const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp);
GetCurrentAccessContext()->UpdateAttachmentAccessState(range_gen, load_index, attachment_access,
ResourceUsageTagEx{tag});
}
}
dynamic_rendering_info_ = std::move(cmd_state.info);
}
bool CommandBufferAccessContext::ValidateEndRendering(const ErrorObject& error_obj) const {
bool skip = false;
// Only validate resolve and store if not suspending (as specified by BeginRendering)
if (!dynamic_rendering_info_ || (dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
return skip;
}
for (uint32_t i = 0; i < (uint32_t)dynamic_rendering_info_->attachments.size(); i++) {
const auto& attachment = dynamic_rendering_info_->attachments[i];
auto attachment_description = [this, &error_obj, &attachment, i](const auto& view, std::ostringstream& ss) {
ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields();
ss << " (" << sync_state_.FormatHandle(view->Handle());
};
// The logic about whether to resolve is embedded in the Attachment constructor
if (attachment.resolve_gen) {
const bool is_color = attachment.type == AttachmentType::kColor;
const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;
const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead);
ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, kResolveRead, resolve_read_access);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());
std::ostringstream ss;
attachment_description(attachment.view, ss);
ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")";
const std::string resource_description = ss.str();
const std::string error = sync_state_.error_messages_.EndRenderingResolveError(
hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, false);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
if (skip) {
break;
}
}
const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite);
ImageRangeGen resolve_gen = *attachment.resolve_gen;
hazard = current_context_->DetectAttachmentHazard(resolve_gen, kResolveWrite, resolve_write_access);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), attachment.resolve_view->Handle());
std::ostringstream ss;
attachment_description(attachment.resolve_view, ss);
ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")";
const std::string resource_description = ss.str();
const std::string error = sync_state_.error_messages_.EndRenderingResolveError(
hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, true);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
if (skip) {
break;
}
}
}
const SyncAccessIndex store_access = attachment.GetStoreUsage();
if (store_access != SYNC_ACCESS_INDEX_NONE) {
const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp);
ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, store_access, attachment_access);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());
std::ostringstream ss;
attachment_description(attachment.view, ss);
ss << ", storeOp " << string_VkAttachmentStoreOp(attachment.info.storeOp) << ")";
const std::string resource_description = ss.str();
const std::string error = sync_state_.error_messages_.EndRenderingStoreError(
hazard, *this, error_obj.location.function, resource_description, attachment.info.storeOp);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
if (skip) {
break;
}
}
}
}
return skip;
}
void CommandBufferAccessContext::RecordEndRendering(const RecordObject& record_obj) {
if (!dynamic_rendering_info_) {
return;
}
if ((dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
dynamic_rendering_info_.reset();
return;
}
auto store_tag = NextCommandTag(record_obj.location.function, SubCommandType::kStoreOp);
AccessContext& access_context = *GetCurrentAccessContext();
for (const auto& attachment : dynamic_rendering_info_->attachments) {
if (attachment.resolve_gen) {
const bool is_color = attachment.type == AttachmentType::kColor;
const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;
const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead);
ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
access_context.UpdateAttachmentAccessState(view_gen, kResolveRead, resolve_read_access, ResourceUsageTagEx{store_tag});
const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite);
ImageRangeGen resolve_gen = *attachment.resolve_gen;
access_context.UpdateAttachmentAccessState(resolve_gen, kResolveWrite, resolve_write_access,
ResourceUsageTagEx{store_tag});
}
const SyncAccessIndex store_index = attachment.GetStoreUsage();
if (store_index != SYNC_ACCESS_INDEX_NONE) {
const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp);
ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
access_context.UpdateAttachmentAccessState(view_gen, store_index, attachment_access, ResourceUsageTagEx{store_tag});
}
}
current_render_pass_instance_id_++;
dynamic_rendering_info_.reset();
}
bool CommandBufferAccessContext::ValidateDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint,
const Location& loc) const {
bool skip = false;
if (!sync_state_.syncval_settings.shader_accesses_heuristic) {
return skip;
}
const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
const vvl::Pipeline* pipe = last_bound_state.pipeline_state;
const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots;
if (!pipe) {
return skip;
}
using DescriptorClass = vvl::DescriptorClass;
using BufferDescriptor = vvl::BufferDescriptor;
using ImageDescriptor = vvl::ImageDescriptor;
using TexelDescriptor = vvl::TexelDescriptor;
for (const auto& stage_state : pipe->stage_states) {
if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) {
continue;
} else if (!stage_state.HasSpirv()) {
continue;
}
for (const auto& variable : stage_state.entrypoint->resource_interface_variables) {
if (variable.decorations.set >= ds_slots.size()) {
// This should be caught by Core validation, but if core checks are disabled SyncVal should not crash.
continue;
}
const auto& ds_slot = ds_slots[variable.decorations.set];
const auto* descriptor_set = ds_slot.ds_state.get();
if (!descriptor_set) continue;
auto binding = descriptor_set->GetBinding(variable.decorations.binding);
if (!binding) continue;
const auto descriptor_type = binding->type;
SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage());
// Currently, validation of memory accesses based on declared descriptors can produce false-positives.
// The shader can decide not to do such accesses, it can perform accesses with more narrow scope
// (e.g. read access, when both reads and writes are allowed) or for an array of descriptors, not all
// elements are accessed in the general case.
//
// This workaround disables validation for the descriptor array case.
if (binding->count > 1) {
continue;
}
for (uint32_t index = 0; index < binding->count; index++) {
const auto* descriptor = binding->GetDescriptor(index);
switch (descriptor->GetClass()) {
case DescriptorClass::ImageSampler:
case DescriptorClass::Image: {
if (descriptor->Invalid()) {
continue;
}
// NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types.
const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor);
const auto* img_view_state = image_descriptor->GetImageViewState();
VkImageLayout image_layout = image_descriptor->GetImageLayout();
if (img_view_state->is_depth_sliced) {
// NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
// Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
// See: VUID 00343
continue;
}
HazardResult hazard;
if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) {
const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent);
const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset);
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster);
hazard = current_context_->DetectAttachmentHazard(*img_view_state, offset, extent, sync_index,
attachment_access);
} else {
hazard = current_context_->DetectHazard(*img_view_state, sync_index);
}
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), img_view_state->Handle(), pipe->Handle());
const auto error = error_messages_.ImageDescriptorError(
hazard, *this, loc.function, sync_state_.FormatHandle(*img_view_state), *pipe,
variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index,
stage_state.GetStage(), image_layout);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
break;
}
case DescriptorClass::TexelBuffer: {
const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor);
if (texel_descriptor->Invalid()) {
continue;
}
const auto* buf_view_state = texel_descriptor->GetBufferViewState();
const auto* buf_state = buf_view_state->buffer_state.get();
const AccessRange range = MakeRange(*buf_view_state);
auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), buf_view_state->Handle(), pipe->Handle());
const auto error = error_messages_.BufferDescriptorError(
hazard, *this, loc.function, sync_state_.FormatHandle(*buf_view_state), *pipe,
variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index,
stage_state.GetStage());
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
break;
}
case DescriptorClass::GeneralBuffer: {
const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor);
if (buffer_descriptor->Invalid()) {
continue;
}
VkDeviceSize offset = buffer_descriptor->GetOffset();
if (vvl::IsDynamicDescriptor(descriptor_type)) {
const uint32_t dynamic_offset_index =
descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding);
if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) {
continue; // core validation error
}
offset += ds_slot.dynamic_offsets[dynamic_offset_index];
}
const auto* buf_state = buffer_descriptor->GetBufferState();
const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange());
auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle());
const auto error = error_messages_.BufferDescriptorError(
hazard, *this, loc.function, sync_state_.FormatHandle(*buf_state), *pipe, variable.decorations.set,
*descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage());
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
break;
}
case DescriptorClass::AccelerationStructure: {
const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor);
if (accel_descriptor->Invalid()) {
continue;
}
const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR();
if (!accel) {
continue;
}
if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) {
const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize());
auto hazard = current_context_->DetectHazard(*as_buffer.state, sync_index, range);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), as_buffer.state->Handle(), pipe->Handle());
const std::string resource_description = sync_state_.FormatHandle(accel->Handle());
const std::string error = error_messages_.AccelerationStructureDescriptorError(
hazard, *this, loc.function, resource_description, *pipe, variable.decorations.set,
*descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage());
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
}
break;
}
// TODO: INLINE_UNIFORM_BLOCK_EXT
default:
break;
}
}
}
}
return skip;
}
// TODO: Record structure repeats Validate. Unify this code, it was the source of bugs few times already.
void CommandBufferAccessContext::RecordDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint,
const ResourceUsageTag tag) {
if (!sync_state_.syncval_settings.shader_accesses_heuristic) {
return;
}
const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
const vvl::Pipeline* pipe = last_bound_state.pipeline_state;
const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots;
if (!pipe) {
return;
}
using DescriptorClass = vvl::DescriptorClass;
using BufferDescriptor = vvl::BufferDescriptor;
using ImageDescriptor = vvl::ImageDescriptor;
using TexelDescriptor = vvl::TexelDescriptor;
for (const auto& stage_state : pipe->stage_states) {
if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) {
continue;
} else if (!stage_state.HasSpirv()) {
continue;
}
for (const auto& variable : stage_state.entrypoint->resource_interface_variables) {
if (variable.decorations.set >= ds_slots.size()) {
// This should be caught by Core validation, but if core checks are disabled SyncVal should not crash.
continue;
}
const auto& ds_slot = ds_slots[variable.decorations.set];
const auto* descriptor_set = ds_slot.ds_state.get();
if (!descriptor_set) continue;
auto binding = descriptor_set->GetBinding(variable.decorations.binding);
if (!binding) continue;
const auto descriptor_type = binding->type;
SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage());
// Do not update state for descriptor array (the same as in Validate function).
if (binding->count > 1) {
continue;
}
for (uint32_t i = 0; i < binding->count; i++) {
const auto* descriptor = binding->GetDescriptor(i);
switch (descriptor->GetClass()) {
case DescriptorClass::ImageSampler:
case DescriptorClass::Image: {
// NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types.
const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor);
if (image_descriptor->Invalid()) {
continue;
}
const auto* img_view_state = image_descriptor->GetImageViewState();
if (img_view_state->is_depth_sliced) {
// NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
// Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
// See: VUID 00343
continue;
}
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, img_view_state->image_state->Handle());
if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) {
const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent);
const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset);
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster);
ImageRangeGen range_gen(MakeImageRangeGen(*img_view_state, offset, extent));
current_context_->UpdateAttachmentAccessState(range_gen, SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ,
attachment_access, tag_ex);
} else {
ImageRangeGen range_gen = MakeImageRangeGen(*img_view_state);
current_context_->UpdateAccessState(range_gen, sync_index, tag_ex);
}
break;
}
case DescriptorClass::TexelBuffer: {
const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor);
if (texel_descriptor->Invalid()) {
continue;
}
const auto* buf_view_state = texel_descriptor->GetBufferViewState();
const auto* buf_state = buf_view_state->buffer_state.get();
const AccessRange range = MakeRange(*buf_view_state);
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_view_state->Handle());
current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex);
break;
}
case DescriptorClass::GeneralBuffer: {
const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor);
if (buffer_descriptor->Invalid()) {
continue;
}
VkDeviceSize offset = buffer_descriptor->GetOffset();
if (vvl::IsDynamicDescriptor(descriptor_type)) {
const uint32_t dynamic_offset_index =
descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding);
if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) {
continue; // core validation error
}
offset += ds_slot.dynamic_offsets[dynamic_offset_index];
}
const auto* buf_state = buffer_descriptor->GetBufferState();
const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange());
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle());
current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex);
break;
}
case DescriptorClass::AccelerationStructure: {
const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor);
if (accel_descriptor->Invalid()) {
continue;
}
const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR();
if (!accel) {
continue;
}
if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) {
const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize());
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, accel->Handle());
current_context_->UpdateAccessState(*as_buffer.state, sync_index, range, tag_ex);
}
break;
}
// TODO: INLINE_UNIFORM_BLOCK_EXT
default:
break;
}
}
}
}
}
bool CommandBufferAccessContext::ValidateDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const Location& loc) const {
bool skip = false;
const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state;
if (!pipe) {
return skip;
}
const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info;
const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
? cb_state_->dynamic_state_value.vertex_bindings
: pipe->vertex_input_state->bindings;
for (const auto& [_, binding_state] : vertex_bindings) {
const auto& binding_desc = binding_state.desc;
if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) {
// TODO: add support to determine range of instance level attributes
continue;
}
if (const vvl::VertexBufferBinding* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) {
// TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer());
if (!buf_state) continue; // also skips if using nullDescriptor
const AccessRange range =
MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state);
auto hazard = current_context_->DetectHazard(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle());
const std::string resource_description = "vertex " + sync_state_.FormatHandle(*buf_state);
const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
}
}
return skip;
}
void CommandBufferAccessContext::RecordDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const ResourceUsageTag tag) {
const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state;
if (!pipe) {
return;
}
const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info;
const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
? cb_state_->dynamic_state_value.vertex_bindings
: pipe->vertex_input_state->bindings;
for (const auto& [_, binding_state] : vertex_bindings) {
const auto& binding_desc = binding_state.desc;
if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) {
// TODO: add support to determine range of instance level attributes
continue;
}
if (const auto* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) {
// TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer());
if (!buf_state) continue; // also skips if using nullDescriptor
const AccessRange range =
MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state);
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle());
current_context_->UpdateAccessState(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range, tag_ex);
}
}
}
bool CommandBufferAccessContext::ValidateDrawVertexIndex(uint32_t index_count, uint32_t firstIndex, const Location& loc) const {
bool skip = false;
const auto& index_binding = cb_state_->index_buffer_binding;
// TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer());
if (!index_buf_state) return skip;
const uint32_t index_size = IndexTypeByteSize(index_binding.index_type);
const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, index_count, index_size);
auto hazard = current_context_->DetectHazard(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), index_buf_state->Handle());
if (const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state) {
objlist.add(pipe->Handle());
}
const std::string resource_description = "index " + sync_state_.FormatHandle(*index_buf_state);
const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
}
// TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed
// versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement
// and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed).
// Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and
// then assume it is immutable (common scenario).
// skip |= ValidateDrawVertex(?, ?, loc);
return skip;
}
void CommandBufferAccessContext::RecordDrawVertexIndex(uint32_t indexCount, uint32_t firstIndex, const ResourceUsageTag tag) {
const auto& index_binding = cb_state_->index_buffer_binding;
// TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer());
if (!index_buf_state) {
return;
}
const uint32_t index_size = IndexTypeByteSize(index_binding.index_type);
const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, indexCount, index_size);
const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, index_buf_state->Handle());
current_context_->UpdateAccessState(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range, tag_ex);
// TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed
// versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement
// and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed).
// Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and
// then assume it is immutable (common scenario).
// RecordDrawVertex(?, ?, tag);
}
bool CommandBufferAccessContext::ValidateDrawAttachment(const Location& loc) const {
bool skip = false;
if (current_renderpass_context_) {
skip |= current_renderpass_context_->ValidateDrawSubpassAttachment(*this, loc.function);
} else if (dynamic_rendering_info_) {
skip |= ValidateDrawDynamicRenderingAttachment(loc);
}
return skip;
}
bool CommandBufferAccessContext::ValidateDrawDynamicRenderingAttachment(const Location& location) const {
bool skip = false;
const auto& last_bound_state = cb_state_->GetLastBoundGraphics();
const auto* pipe = last_bound_state.pipeline_state;
if (!pipe || pipe->RasterizationDisabled()) return skip;
const auto& list = pipe->fs_writable_output_location_list;
const auto& access_context = *GetCurrentAccessContext();
const DynamicRenderingInfo& info = *dynamic_rendering_info_;
for (const auto output_location : list) {
if (output_location >= info.info.colorAttachmentCount) {
continue;
}
const auto& attachment = info.attachments[output_location];
if (!attachment.IsWriteable(last_bound_state)) {
continue;
}
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
HazardResult hazard =
access_context.DetectAttachmentHazard(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
LogObjectList obj_list(cb_state_->Handle(), attachment.view->Handle());
Location loc = attachment.GetLocation(location, output_location);
const std::string error = error_messages_.Error(
hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view), "DynamicRenderingAttachmentError");
skip |= sync_state_.SyncError(hazard.Hazard(), obj_list, loc.dot(vvl::Field::imageView), error);
}
}
// TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
// PHASE1 TODO: Add layout based read/vs. write selection.
// PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
// PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.
for (size_t i = info.info.colorAttachmentCount; i < info.attachments.size(); i++) {
const auto& attachment = info.attachments[i];
bool writeable = attachment.IsWriteable(last_bound_state);
if (writeable) {
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
HazardResult hazard = access_context.DetectAttachmentHazard(
view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());
Location loc = attachment.GetLocation(location);
const std::string error =
error_messages_.Error(hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view),
"DynamicRenderingAttachmentError");
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc.dot(vvl::Field::imageView), error);
}
}
}
return skip;
}
void CommandBufferAccessContext::RecordDrawAttachment(const ResourceUsageTag tag) {
if (current_renderpass_context_) {
current_renderpass_context_->RecordDrawSubpassAttachment(*cb_state_, tag);
} else if (dynamic_rendering_info_) {
RecordDrawDynamicRenderingAttachment(tag);
}
}
void CommandBufferAccessContext::RecordDrawDynamicRenderingAttachment(ResourceUsageTag tag) {
const auto& last_bound_state = cb_state_->GetLastBoundGraphics();
const auto* pipe = last_bound_state.pipeline_state;
if (!pipe || pipe->RasterizationDisabled()) {
return;
}
const auto& list = pipe->fs_writable_output_location_list;
auto& access_context = *GetCurrentAccessContext();
const DynamicRenderingInfo& info = *dynamic_rendering_info_;
for (const auto output_location : list) {
if (output_location >= info.info.colorAttachmentCount) {
continue;
}
const auto& attachment = info.attachments[output_location];
if (!attachment.IsWriteable(last_bound_state)) {
continue;
}
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
access_context.UpdateAttachmentAccessState(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access,
ResourceUsageTagEx{tag});
}
// TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
// PHASE1 TODO: Add layout based read/vs. write selection.
// PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
// PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.
const uint32_t attachment_count = static_cast<uint32_t>(info.attachments.size());
for (uint32_t i = info.info.colorAttachmentCount; i < attachment_count; i++) {
const auto& attachment = info.attachments[i];
bool writeable = attachment.IsWriteable(last_bound_state);
if (writeable) {
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
access_context.UpdateAttachmentAccessState(view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
attachment_access, ResourceUsageTagEx{tag});
}
}
}
static VkImageAspectFlags GetAttachmentAspectsToClear(VkImageAspectFlags clear_aspect_mask, const vvl::ImageView& attachment_view,
bool separate_depth_stencil_attachment_access) {
// Check if clear request is valid.
const bool clear_color = (clear_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
const bool clear_depth = (clear_aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
const bool clear_stencil = (clear_aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
if (!clear_color && !clear_depth && !clear_stencil) {
return 0; // nothing to clear
}
if (clear_color && (clear_depth || clear_stencil)) {
return 0; // according to spec it's not allowed
}
// View's aspect mask is used only for color attachment.
// For depth/stencil attachment view aspect mask is ignored according to spec.
const VkImageAspectFlags view_aspect_mask = attachment_view.normalized_subresource_range.aspectMask;
// Collect aspects that should be cleared.
VkImageAspectFlags aspects_to_clear = VK_IMAGE_ASPECT_NONE;
if (clear_color && (view_aspect_mask & kColorAspects) != 0) {
assert(CountSetBits(view_aspect_mask) == 1);
aspects_to_clear |= view_aspect_mask;
}
if (clear_depth && vkuFormatHasDepth(attachment_view.create_info.format)) {
aspects_to_clear |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (clear_stencil && vkuFormatHasStencil(attachment_view.create_info.format)) {
aspects_to_clear |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
// Even if only depth or stencil aspect is specified we validate access to
// both depth and stencil aspects because they can be interleaved.
if (!separate_depth_stencil_attachment_access && (aspects_to_clear & kDepthStencilAspects) != 0) {
aspects_to_clear = kDepthStencilAspects;
}
return aspects_to_clear;
}
static std::optional<VkImageSubresourceRange> RestrictSubresourceRangeToClearLayers(
const VkImageSubresourceRange& normalized_subresource_range, uint32_t clear_first_layer, uint32_t clear_layer_count) {
// Contract of this function
assert(normalized_subresource_range.layerCount != VK_REMAINING_ARRAY_LAYERS);
// According to spec
assert(clear_layer_count != VK_REMAINING_ARRAY_LAYERS);
const uint32_t first = std::max(normalized_subresource_range.baseArrayLayer, clear_first_layer);
const uint32_t last_range = normalized_subresource_range.baseArrayLayer + normalized_subresource_range.layerCount;
const uint32_t last_clear = clear_first_layer + clear_layer_count;
const uint32_t last = std::min(last_range, last_clear);
if (first >= last) {
return {};
}
std::optional<VkImageSubresourceRange> result = normalized_subresource_range;
result->baseArrayLayer = first;
result->layerCount = last - first;
return result;
}
std::optional<CommandBufferAccessContext::ClearAttachmentInfo> CommandBufferAccessContext::GetClearAttachmentInfo(
const VkClearAttachment& clear_attachment, uint32_t clear_first_layer, uint32_t clear_layer_count) const {
const vvl::ImageView* attachment_view = nullptr;
if (current_renderpass_context_) {
attachment_view = current_renderpass_context_->GetClearAttachmentView(clear_attachment);
} else if (dynamic_rendering_info_) {
attachment_view = dynamic_rendering_info_->GetClearAttachmentView(clear_attachment);
}
if (!attachment_view) {
return {};
}
const bool separate_depth_stencil_attachment_access =
GetSyncState().device_state->enabled_features.maintenance7 &&
GetSyncState().device_state->phys_dev_ext_props.maintenance7_props.separateDepthStencilAttachmentAccess;
const VkImageAspectFlags aspects =
GetAttachmentAspectsToClear(clear_attachment.aspectMask, *attachment_view, separate_depth_stencil_attachment_access);
if (!aspects) {
return {};
}
const auto subresource_range =
RestrictSubresourceRangeToClearLayers(attachment_view->normalized_subresource_range, clear_first_layer, clear_layer_count);
if (!subresource_range.has_value()) {
return {};
}
return ClearAttachmentInfo{*attachment_view, *subresource_range};
}
bool CommandBufferAccessContext::ValidateClearAttachment(const Location& loc, const VkClearAttachment& clear_attachment,
uint32_t clear_rect_index, const VkClearRect& clear_rect) const {
bool skip = false;
const auto optional_info = GetClearAttachmentInfo(clear_attachment, clear_rect.baseArrayLayer, clear_rect.layerCount);
if (!optional_info) {
return skip;
}
const ClearAttachmentInfo& info = *optional_info;
const VkImageSubresourceRange subresource_range = info.subresource_range;
const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask;
const uint32_t view_mask = GetViewMask();
const ImageSubState& sub_state = SubState(*info.attachment_view.image_state);
// NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified
// Validate Color clear
auto report_color_hazard = [this, &skip, &loc, &info](const HazardResult& hazard, const VkClearAttachment& clear_attachment,
uint32_t clear_rect_index, const VkClearRect& clear_rect) {
std::ostringstream ss;
ss << string_VkImageAspectFlags(clear_attachment.aspectMask);
ss << " aspect of color attachment " << clear_attachment.colorAttachment;
ss << " (" << sync_state_.FormatHandle(info.attachment_view) << ")";
if (current_renderpass_context_) {
ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass();
}
const std::string resource_description = ss.str();
const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle());
const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description,
clear_attachment.aspectMask, clear_rect_index, clear_rect);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
};
if (aspects_to_clear & kColorAspects) {
// [core validation check]: if COLOR_ASPECT is included then PLANE aspects are not allowed,
// and if PLANE aspect is included then only one is allowed.
assert(CountSetBits(aspects_to_clear) == 1);
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
if (view_mask == 0) {
HazardResult hazard = current_context_->DetectAttachmentHazard(
*info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced,
SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
}
} else {
const auto view_indices = GetSetBitIndices(view_mask);
const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
for (uint32_t view_index : view_indices) {
if (view_index < attachment_subresource.layerCount) {
VkImageSubresourceRange view_subresource = attachment_subresource;
view_subresource.baseArrayLayer += view_index;
view_subresource.layerCount = 1;
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced);
HazardResult hazard = current_context_->DetectAttachmentHazard(
range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
}
}
}
}
}
// Validate Depth-Stencil clear
auto report_depth_stencil_hazard = [this, &skip, &loc, &info](const HazardResult& hazard,
const VkClearAttachment& clear_attachment,
uint32_t clear_rect_index, const VkClearRect& clear_rect) {
std::ostringstream ss;
ss << string_VkImageAspectFlags(clear_attachment.aspectMask);
ss << " aspect(s) of depth-stencil attachment (";
ss << sync_state_.FormatHandle(info.attachment_view) << ")";
if (current_renderpass_context_) {
ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass();
}
const std::string resource_description = ss.str();
const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle());
const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description,
clear_attachment.aspectMask, clear_rect_index, clear_rect);
skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
};
if (aspects_to_clear & kDepthStencilAspects) {
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
if (view_mask == 0) {
// vkCmdClearAttachments depth/stencil writes are executed by the EARLY_FRAGMENT_TESTS_BIT and LATE_FRAGMENT_TESTS_BIT
// stages. The implementation tracks the most recent access, which happens in the LATE_FRAGMENT_TESTS_BIT stage.
HazardResult hazard = current_context_->DetectAttachmentHazard(
*info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced,
SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
}
} else {
const auto view_indices = GetSetBitIndices(view_mask);
const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
for (uint32_t view_index : view_indices) {
if (view_index < attachment_subresource.layerCount) {
VkImageSubresourceRange view_subresource = attachment_subresource;
view_subresource.baseArrayLayer += view_index;
view_subresource.layerCount = 1;
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced);
HazardResult hazard = current_context_->DetectAttachmentHazard(
range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);
if (hazard.IsHazard()) {
report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
}
}
}
}
}
return skip;
}
void CommandBufferAccessContext::RecordClearAttachment(ResourceUsageTag tag, const VkClearAttachment& clear_attachment,
const VkClearRect& rect) {
const auto optional_info = GetClearAttachmentInfo(clear_attachment, rect.baseArrayLayer, rect.layerCount);
if (!optional_info) {
return;
}
const ClearAttachmentInfo& info = *optional_info;
const VkImageSubresourceRange subresource_range = info.subresource_range;
const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask;
const uint32_t view_mask = GetViewMask();
const ImageSubState& sub_state = SubState(*info.attachment_view.image_state);
auto update_access_state = [this, aspects_to_clear, tag](ImageRangeGen& range_gen) {
if (aspects_to_clear & kColorAspects) {
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
current_context_->UpdateAttachmentAccessState(range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
attachment_access, ResourceUsageTagEx{tag});
} else {
const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
current_context_->UpdateAttachmentAccessState(range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
attachment_access, ResourceUsageTagEx{tag});
}
};
// NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified
if (view_mask == 0) {
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false);
update_access_state(range_gen);
} else {
const auto view_indices = GetSetBitIndices(view_mask);
const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
for (uint32_t view_index : view_indices) {
if (view_index < attachment_subresource.layerCount) {
VkImageSubresourceRange view_subresource = attachment_subresource;
view_subresource.baseArrayLayer += view_index;
view_subresource.layerCount = 1;
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, false);
update_access_state(range_gen);
}
}
}
}
QueueId CommandBufferAccessContext::GetQueueId() const { return kQueueIdInvalid; }
ResourceUsageTag CommandBufferAccessContext::RecordBeginRenderPass(vvl::Func command, const vvl::RenderPass& rp_state,
const VkRect2D& render_area,
const std::vector<const vvl::ImageView*>& attachment_views) {
// Create an access context the current renderpass.
const auto barrier_tag = NextCommandTag(command, SubCommandType::kSubpassTransition, 0);
AddCommandHandle(barrier_tag, rp_state.Handle());
const auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, 0);
render_pass_contexts_.emplace_back(std::make_unique<RenderPassAccessContext>(
rp_state, render_area, GetQueueFlags(), attachment_views, cb_access_context_, current_render_pass_instance_id_));
current_renderpass_context_ = render_pass_contexts_.back().get();
current_renderpass_context_->RecordBeginRenderPass(barrier_tag, load_tag);
current_context_ = &current_renderpass_context_->CurrentContext();
return barrier_tag;
}
ResourceUsageTag CommandBufferAccessContext::RecordNextSubpass(vvl::Func command) {
assert(current_renderpass_context_);
if (!current_renderpass_context_) return NextCommandTag(command);
// At this point current subpass value has not updated yet to the index of "next subpass"
const uint32_t previous_subpass = current_renderpass_context_->GetCurrentSubpass();
const uint32_t this_subpass = previous_subpass + 1;
auto resolve_tag = NextCommandTag(command, SubCommandType::kResolveOp, previous_subpass);
AddCommandHandle(resolve_tag, current_renderpass_context_->GetRenderPassState()->Handle());
auto store_tag = NextSubCommandTag(command, SubCommandType::kStoreOp, previous_subpass);
auto transition_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition, this_subpass);
auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, this_subpass);
current_renderpass_context_->RecordNextSubpass(resolve_tag, store_tag, transition_tag, load_tag);
current_context_ = &current_renderpass_context_->CurrentContext();
return transition_tag;
}
ResourceUsageTag CommandBufferAccessContext::RecordEndRenderPass(vvl::Func command) {
assert(current_renderpass_context_);
if (!current_renderpass_context_) return NextCommandTag(command);
const uint32_t current_subpass = current_renderpass_context_->GetCurrentSubpass();
auto store_tag = NextCommandTag(command, SubCommandType::kStoreOp, current_subpass);
AddCommandHandle(store_tag, current_renderpass_context_->GetRenderPassState()->Handle());
auto barrier_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition);
current_renderpass_context_->RecordEndRenderPass(&cb_access_context_, store_tag, barrier_tag);
current_context_ = &cb_access_context_;
current_renderpass_context_ = nullptr;
current_render_pass_instance_id_++;
return barrier_tag;
}
void CommandBufferAccessContext::RecordDestroyEvent(vvl::Event* event_state) { GetCurrentEventsContext()->Destroy(event_state); }
void CommandBufferAccessContext::RecordExecutedCommandBuffer(const CommandBufferAccessContext& recorded_cb_context) {
const AccessContext* recorded_context = recorded_cb_context.GetCurrentAccessContext();
assert(recorded_context);
// Just run through the barriers ignoring the usage from the recorded context, as Resolve will overwrite outdated state
const ResourceUsageTag base_tag = GetTagCount();
for (const auto& sync_op : recorded_cb_context.GetSyncOps()) {
// we update the range to any include layout transition first use writes,
// as they are stored along with the source scope (as effective barrier) when recorded
sync_op.sync_op->ReplayRecord(*this, base_tag + sync_op.tag);
}
ImportRecordedAccessLog(recorded_cb_context);
ResolveExecutedCommandBuffer(*recorded_context, base_tag);
}
void CommandBufferAccessContext::ResolveExecutedCommandBuffer(const AccessContext& recorded_context, ResourceUsageTag offset) {
auto tag_offset = [offset](AccessState* access) { access->OffsetTag(offset); };
GetCurrentAccessContext()->ResolveFromContext(tag_offset, recorded_context);
}
void CommandBufferAccessContext::ImportRecordedAccessLog(const CommandBufferAccessContext& recorded_context) {
cbs_referenced_->emplace_back(recorded_context.GetCBStateShared());
access_log_->insert(access_log_->end(), recorded_context.access_log_->cbegin(), recorded_context.access_log_->cend());
// Adjust command indices for the log records added from recorded_context.
const auto& recorded_label_commands = recorded_context.cb_state_->GetLabelCommands();
const bool use_proxy = !proxy_label_commands_.empty();
const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands();
if (!label_commands.empty()) {
assert(label_commands.size() >= recorded_label_commands.size());
const uint32_t command_offset = static_cast<uint32_t>(label_commands.size() - recorded_label_commands.size());
for (size_t i = 0; i < recorded_context.access_log_->size(); i++) {
size_t index = (access_log_->size() - 1) - i;
assert((*access_log_)[index].label_command_index != vvl::kNoIndex32);
(*access_log_)[index].label_command_index += command_offset;
}
}
}
ResourceUsageTag CommandBufferAccessContext::NextCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) {
command_number_++;
current_command_tag_ = access_log_->size();
ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass);
if (!cb_state_->GetLabelCommands().empty()) {
record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1);
}
CheckCommandTagDebugCheckpoint();
return current_command_tag_;
}
ResourceUsageTag CommandBufferAccessContext::NextSubCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) {
const ResourceUsageTag tag = access_log_->size();
ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass);
// By default copy handle range from the main command, but can be overwritten with AddSubcommandHandle.
const auto& main_command_record = (*access_log_)[current_command_tag_];
record.first_handle_index = main_command_record.first_handle_index;
record.handle_count = main_command_record.handle_count;
if (!cb_state_->GetLabelCommands().empty()) {
record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1);
}
return tag;
}
uint32_t CommandBufferAccessContext::AddHandle(const VulkanTypedHandle& typed_handle, uint32_t index) {
const uint32_t handle_index = static_cast<uint32_t>(handles_.size());
handles_.emplace_back(HandleRecord(typed_handle, index));
sync_state_.stats.AddHandleRecord();
return handle_index;
}
ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandle(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle) {
return AddCommandHandleIndexed(tag, typed_handle, vvl::kNoIndex32);
}
ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle,
uint32_t index) {
assert(tag < access_log_->size());
const uint32_t handle_index = AddHandle(typed_handle, index);
// TODO: the following range check is not needed. Test and remove.
if (tag < access_log_->size()) {
auto& record = (*access_log_)[tag];
if (record.first_handle_index == vvl::kNoIndex32) {
record.first_handle_index = handle_index;
record.handle_count = 1;
} else {
// assert that command handles occupy continuous range
assert(handle_index - record.first_handle_index == record.handle_count);
record.handle_count++;
}
}
return {tag, handle_index};
}
void CommandBufferAccessContext::AddSubcommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle,
uint32_t index) {
assert(tag < access_log_->size());
const uint32_t handle_index = AddHandle(typed_handle, index);
// TODO: the following range check is not needed. Test and remove.
if (tag < access_log_->size()) {
auto& record = (*access_log_)[tag];
const auto& main_command_record = (*access_log_)[current_command_tag_];
if (record.first_handle_index == main_command_record.first_handle_index) {
// override default behavior that subcommand references the same handles as the main command
record.first_handle_index = handle_index;
record.handle_count = 1;
} else {
// assert that command handles occupy continuous range
assert(handle_index - record.first_handle_index == record.handle_count);
record.handle_count++;
}
}
}
std::string CommandBufferAccessContext::GetDebugRegionName(const ResourceUsageRecord& record) const {
const bool use_proxy = !proxy_label_commands_.empty();
const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands();
return vvl::CommandBuffer::GetDebugRegionName(label_commands, record.label_command_index);
}
void CommandBufferAccessContext::RecordSyncOp(SyncOpPointer&& sync_op) {
auto tag = sync_op->Record(this);
// As renderpass operations can have side effects on the command buffer access context,
// update the sync operation to record these if any.
sync_ops_.emplace_back(tag, std::move(sync_op));
}
AttachmentAccess CommandBufferAccessContext::GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type) const {
AttachmentAccess attachment_access;
attachment_access.type = type;
attachment_access.ordering = ordering;
attachment_access.render_pass_instance_id = current_render_pass_instance_id_;
attachment_access.subpass = current_renderpass_context_ ? current_renderpass_context_->GetCurrentSubpass() : vvl::kNoIndex32;
return attachment_access;
}
uint32_t CommandBufferAccessContext::GetViewMask() const {
if (dynamic_rendering_info_) {
return dynamic_rendering_info_->info.viewMask;
} else if (current_renderpass_context_) {
const auto& render_pass_ci = current_renderpass_context_->GetRenderPassState()->create_info;
const uint32_t subpass = current_renderpass_context_->GetCurrentSubpass();
return render_pass_ci.pSubpasses[subpass].viewMask;
} else {
assert(false && "GetViewMask musk be called only during render pass instance");
return 0;
}
}
// NOTE: debug location reporting feature works only for reproducible application sessions
// (it uses command number/reset count from the error message from the previous session).
// It's considered experimental and can be replaced with a better way to report syncval debug locations.
//
// Logs informational message when vulkan command stream reaches a specific location.
// The message can be intercepted by the reporting routines. For example, the message handler can trigger a breakpoint.
// The location can be specified through environment variables.
// VK_SYNCVAL_DEBUG_COMMAND_NUMBER: the command number
// VK_SYNCVAL_DEBUG_RESET_COUNT: (optional, default value is 1) command buffer reset count
// VK_SYNCVAL_DEBUG_CMDBUF_PATTERN: (optional, empty string by default) pattern to match command buffer debug name
void CommandBufferAccessContext::CheckCommandTagDebugCheckpoint() {
auto get_cmdbuf_name = [](const DebugReport& debug_report, uint64_t cmdbuf_handle) {
std::unique_lock<std::mutex> lock(debug_report.debug_output_mutex);
std::string object_name = debug_report.GetUtilsObjectNameNoLock(cmdbuf_handle);
if (object_name.empty()) {
object_name = debug_report.GetMarkerObjectNameNoLock(cmdbuf_handle);
}
text::ToLower(object_name);
return object_name;
};
if (sync_state_.debug_command_number == command_number_ && sync_state_.debug_reset_count == reset_count_) {
const auto cmdbuf_name = get_cmdbuf_name(*sync_state_.debug_report, cb_state_->Handle().handle);
const auto& pattern = sync_state_.debug_cmdbuf_pattern;
const bool cmdbuf_match = pattern.empty() || (cmdbuf_name.find(pattern) != std::string::npos);
if (cmdbuf_match) {
sync_state_.LogInfo("SYNCVAL_DEBUG_COMMAND", LogObjectList(), Location(access_log_->back().command),
"Command stream has reached command #%" PRIu32 " in command buffer %s with reset count #%" PRIu32,
sync_state_.debug_command_number, sync_state_.FormatHandle(cb_state_->Handle()).c_str(),
sync_state_.debug_reset_count);
}
}
}
void UpdateAccessMapStats(const AccessMap& access_map, AccessContextStats& stats);
void CommandBufferAccessContext::UpdateStats(AccessStats& access_stats) const {
#if VVL_ENABLE_SYNCVAL_STATS != 0
UpdateAccessMapStats(cb_access_context_.GetAccessMap(), access_stats.cb_access_stats);
for (const auto& render_pass_context : render_pass_contexts_) {
for (const AccessContext& subpass_access_context : render_pass_context->GetSubpassContexts()) {
UpdateAccessMapStats(subpass_access_context.GetAccessMap(), access_stats.subpass_access_stats);
}
}
#endif
}
CommandBufferSubState::CommandBufferSubState(SyncValidator& dev, vvl::CommandBuffer& cb)
: vvl::CommandBufferSubState(cb), access_context(dev, &cb) {
access_context.SetSelfReference();
}
void CommandBufferSubState::End() {
access_context.GetCurrentAccessContext()->Finalize();
// For threads that are dedicated to recording command buffers but do not submit themselves,
// the end of recording is a logical point to update memory stats
access_context.GetSyncState().stats.UpdateMemoryStats();
}
void CommandBufferSubState::Destroy() {
access_context.Destroy(); // must be first to clean up self references correctly.
}
void CommandBufferSubState::Reset(const Location& loc) { access_context.Reset(); }
void CommandBufferSubState::NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) {
for (auto& obj : invalid_nodes) {
switch (obj->Type()) {
case kVulkanObjectTypeEvent:
access_context.RecordDestroyEvent(static_cast<vvl::Event*>(obj.get()));
break;
default:
break;
}
}
}
void CommandBufferSubState::RecordCopyBuffer(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
const VkBufferCopy* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size);
context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);
const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size);
context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
const VkBufferCopy2* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size);
context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);
const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size);
context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyImage(vvl::Image& src_image_state, vvl::Image& dst_image_state,
VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
const VkImageCopy* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource),
copy_region.srcOffset, copy_region.extent, src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource),
copy_region.dstOffset, copy_region.extent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state,
VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
const VkImageCopy2* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource),
copy_region.srcOffset, copy_region.extent, src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource),
copy_region.dstOffset, copy_region.extent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyBufferToImage(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout,
uint32_t region_count, const VkBufferImageCopy* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region));
context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource),
copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyBufferToImage2(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout,
uint32_t region_count, const VkBufferImageCopy2* regions,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region));
context->UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource),
copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyImageToBuffer(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
VkImageLayout src_image_layout, uint32_t region_count,
const VkBufferImageCopy* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource),
copy_region.imageOffset, copy_region.imageExtent, src_tag_ex);
AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region));
context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
}
}
void CommandBufferSubState::RecordCopyImageToBuffer2(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
VkImageLayout src_image_layout, uint32_t region_count,
const VkBufferImageCopy2* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
for (const auto& copy_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource),
copy_region.imageOffset, copy_region.imageExtent, src_tag_ex);
AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region));
context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
}
}
void CommandBufferSubState::RecordBlitImage(vvl::Image& src_image_state, vvl::Image& dst_image_state,
VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
const VkImageBlit* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& blit_region : vvl::make_span(regions, region_count)) {
VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x),
std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y),
std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)};
VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)),
static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)),
static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))};
UpdateImageAccessState(*context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource),
offset, extent, src_tag_ex);
offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x),
std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y),
std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)};
extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)),
static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)),
static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))};
UpdateImageAccessState(*context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource),
offset, extent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordBlitImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state,
VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
const VkImageBlit2* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& blit_region : vvl::make_span(regions, region_count)) {
VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x),
std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y),
std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)};
VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)),
static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)),
static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))};
UpdateImageAccessState(*context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource),
offset, extent, src_tag_ex);
offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x),
std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y),
std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)};
extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)),
static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)),
static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))};
UpdateImageAccessState(*context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource),
offset, extent, dst_tag_ex);
}
}
void CommandBufferSubState::RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
const VkImageResolve* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& resolve_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_RESOLVE_TRANSFER_READ,
RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent,
src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE,
RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent,
dst_tag_ex);
}
}
void CommandBufferSubState::RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
const VkImageResolve2* regions, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());
for (const auto& resolve_region : vvl::make_span(regions, region_count)) {
UpdateImageAccessState(*context, src_image_state, SYNC_RESOLVE_TRANSFER_READ,
RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent,
src_tag_ex);
UpdateImageAccessState(*context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE,
RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent,
dst_tag_ex);
}
}
void CommandBufferSubState::RecordClearColorImage(vvl::Image& image_state, VkImageLayout, const VkClearColorValue*,
uint32_t range_count, const VkImageSubresourceRange* ranges,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
assert(context);
access_context.AddCommandHandle(tag, image_state.Handle());
for (uint32_t index = 0; index < range_count; index++) {
const auto& range = ranges[index];
UpdateImageAccessState(*context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag);
}
}
void CommandBufferSubState::RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout, const VkClearDepthStencilValue*,
uint32_t range_count, const VkImageSubresourceRange* ranges,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
assert(context);
access_context.AddCommandHandle(tag, image_state.Handle());
for (uint32_t index = 0; index < range_count; index++) {
const auto& range = ranges[index];
UpdateImageAccessState(*context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag);
}
}
void CommandBufferSubState::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments,
uint32_t rect_count, const VkClearRect* pRects, const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
for (const auto& attachment : vvl::make_span(pAttachments, attachment_count)) {
for (const auto& rect : vvl::make_span(pRects, rect_count)) {
access_context.RecordClearAttachment(tag, attachment, rect);
}
}
}
void CommandBufferSubState::RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
assert(context);
const AccessRange range = MakeRange(buffer_state, offset, size);
const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle());
context->UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex);
}
void CommandBufferSubState::RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
assert(context);
// VK_WHOLE_SIZE not allowed
const AccessRange range = MakeRange(offset, size);
const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle());
context->UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex);
}
void CommandBufferSubState::RecordDecodeVideo(vvl::VideoSession& vs_state, const VkVideoDecodeInfoKHR& decode_info,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(decode_info.srcBuffer)) {
const AccessRange src_range = MakeRange(*src_buffer, decode_info.srcBufferOffset, decode_info.srcBufferRange);
const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle());
context->UpdateAccessState(*src_buffer, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, src_range, src_tag_ex);
}
const auto* device_state = access_context.GetSyncState().device_state;
auto dst_resource = vvl::VideoPictureResource(*device_state, decode_info.dstPictureResource);
if (dst_resource) {
UpdateVideoAccessState(*context, vs_state, dst_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag);
}
if (decode_info.pSetupReferenceSlot != nullptr && decode_info.pSetupReferenceSlot->pPictureResource != nullptr) {
auto setup_resource = vvl::VideoPictureResource(*device_state, *decode_info.pSetupReferenceSlot->pPictureResource);
if (setup_resource && (setup_resource != dst_resource)) {
UpdateVideoAccessState(*context, vs_state, setup_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag);
}
}
for (uint32_t i = 0; i < decode_info.referenceSlotCount; ++i) {
if (decode_info.pReferenceSlots[i].pPictureResource != nullptr) {
auto reference_resource = vvl::VideoPictureResource(*device_state, *decode_info.pReferenceSlots[i].pPictureResource);
if (reference_resource) {
UpdateVideoAccessState(*context, vs_state, reference_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, tag);
}
}
}
}
void CommandBufferSubState::RecordEncodeVideo(vvl::VideoSession& vs_state, const VkVideoEncodeInfoKHR& encode_info,
const Location& loc) {
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(encode_info.dstBuffer)) {
const AccessRange src_range = MakeRange(*src_buffer, encode_info.dstBufferOffset, encode_info.dstBufferRange);
const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle());
context->UpdateAccessState(*src_buffer, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, src_range, src_tag_ex);
}
const auto* device_state = access_context.GetSyncState().device_state;
auto src_resource = vvl::VideoPictureResource(*device_state, encode_info.srcPictureResource);
if (src_resource) {
UpdateVideoAccessState(*context, vs_state, src_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag);
}
if (encode_info.pSetupReferenceSlot != nullptr && encode_info.pSetupReferenceSlot->pPictureResource != nullptr) {
auto setup_resource = vvl::VideoPictureResource(*device_state, *encode_info.pSetupReferenceSlot->pPictureResource);
if (setup_resource) {
UpdateVideoAccessState(*context, vs_state, setup_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, tag);
}
}
for (uint32_t i = 0; i < encode_info.referenceSlotCount; ++i) {
if (encode_info.pReferenceSlots[i].pPictureResource != nullptr) {
auto reference_resource = vvl::VideoPictureResource(*device_state, *encode_info.pReferenceSlots[i].pPictureResource);
if (reference_resource) {
UpdateVideoAccessState(*context, vs_state, reference_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag);
}
}
}
if (encode_info.flags & (VK_VIDEO_ENCODE_WITH_QUANTIZATION_DELTA_MAP_BIT_KHR | VK_VIDEO_ENCODE_WITH_EMPHASIS_MAP_BIT_KHR)) {
auto quantization_map_info = vku::FindStructInPNextChain<VkVideoEncodeQuantizationMapInfoKHR>(encode_info.pNext);
if (quantization_map_info) {
auto image_view_state = base.dev_data.Get<vvl::ImageView>(quantization_map_info->quantizationMap);
if (image_view_state) {
VkOffset3D offset = {0, 0, 0};
VkExtent3D extent = {quantization_map_info->quantizationMapExtent.width,
quantization_map_info->quantizationMapExtent.height, 1};
ImageRangeGen range_gen(MakeImageRangeGen(*image_view_state, offset, extent));
context->UpdateAccessState(range_gen, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, ResourceUsageTagEx{tag});
}
}
}
}
void CommandBufferSubState::RecordCopyQueryPoolResults(vvl::QueryPool& pool_state, vvl::Buffer& dst_buffer_state,
uint32_t first_query, uint32_t query_count, VkDeviceSize dst_offset,
VkDeviceSize stride, VkQueryResultFlags flags, const Location& loc) {
if (query_count == 0) {
return;
}
const auto tag = access_context.NextCommandTag(loc.function);
auto* context = access_context.GetCurrentAccessContext();
const uint32_t query_size = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4;
const VkDeviceSize range_size = (query_count - 1) * stride + query_size;
const AccessRange range = MakeRange(dst_offset, range_size);
const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
context->UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, range, tag_ex);
// TODO:Track VkQueryPool
}
void CommandBufferSubState::RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin,
const VkSubpassBeginInfo& subpass_begin_info, const Location& loc) {
if (!base.IsPrimary()) {
return; // [core validation check]: only primary command buffer can begin render pass
}
access_context.RecordSyncOp<SyncOpBeginRenderPass>(loc.function, access_context.GetSyncState(), &render_pass_begin,
&subpass_begin_info);
}
void CommandBufferSubState::RecordNextSubpass(const VkSubpassBeginInfo& subpass_begin_info,
const VkSubpassEndInfo* subpass_end_info, const Location& loc) {
if (!base.IsPrimary()) {
return; // [core validation check]: only primary command buffer can start next subpass
}
access_context.RecordSyncOp<SyncOpNextSubpass>(loc.function, access_context.GetSyncState(), &subpass_begin_info,
subpass_end_info);
}
void CommandBufferSubState::RecordEndRenderPass(const VkSubpassEndInfo* subpass_end_info, const Location& loc) {
if (!base.IsPrimary()) {
return; // [core validation check]: only primary command buffer can end render pass
}
// Resolve the all subpass contexts to the command buffer contexts
access_context.RecordSyncOp<SyncOpEndRenderPass>(loc.function, access_context.GetSyncState(), subpass_end_info);
}
void CommandBufferSubState::RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t cmd_index,
const Location& loc) {
if (cmd_index == 0) {
ResourceUsageTag cb_tag = access_context.NextCommandTag(loc.function, SubCommandType::kIndex);
access_context.AddCommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
} else {
ResourceUsageTag cb_tag = access_context.NextSubCommandTag(loc.function, SubCommandType::kIndex);
access_context.AddSubcommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
}
access_context.RecordExecutedCommandBuffer(*GetAccessContext(secondary_command_buffer));
}
} // namespace syncval