blob: d505599bcc97b0e1245a97bc8094031cef790f22 [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_replay.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"
#include "utils/vk_api_utils.h"
using vvl::BufferDescriptor;
using vvl::DescriptorClass;
using vvl::ImageDescriptor;
using vvl::TexelDescriptor;
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});
}
SyncEnvironment::SyncEnvironment(const SyncValidator& validator, VkQueueFlags queue_flags, QueueId queue_id,
VulkanTypedHandle handle, SyncEventsContext& events_context,
const ResourceUsageInfoProvider& usage_info_provider)
: validator(validator),
queue_flags(queue_flags),
queue_id(queue_id),
handle(handle),
events_context(events_context),
usage_info_provider(usage_info_provider) {}
CommandBufferContext::CommandBufferContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags, VulkanTypedHandle handle)
: sync_state_(sync_validator),
error_messages_(sync_validator.error_messages_),
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_(),
environment_(sync_validator, queue_flags, kQueueIdInvalid, handle, events_context_, *this),
render_pass_contexts_(),
current_renderpass_context_() {}
CommandBufferContext::CommandBufferContext(SyncValidator& sync_validator, vvl::CommandBuffer* cb_state)
: CommandBufferContext(sync_validator, cb_state->GetQueueFlags(), cb_state->Handle()) {
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.
CommandBufferContext::CommandBufferContext(const CommandBufferContext& from, AsProxyContext dummy)
: CommandBufferContext(from.sync_state_, from.cb_state_->GetQueueFlags(), from.cb_state_->Handle()) {
// 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 AccessContext& from_context = from.GetCurrentAccessContext();
// Construct a fully resolved single access context out of from
cb_access_context_.ResolveFromContextRecursePrev(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();
}
CommandBufferContext::~CommandBufferContext() {
sync_state_.stats.RemoveCommandBufferContext();
sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size());
}
void CommandBufferContext::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());
}
replay_entries_.clear();
commands_.clear();
command_data_.Reset();
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;
current_render_pass_instance_id_ = 0;
events_context_.Clear();
rendering_attachments_.clear();
rendering_instance_.reset();
rendering_view_gens_.clear();
}
const RenderingInstance& CommandBufferContext::BeginRenderingInstance(const VkRenderingInfo& rendering_info) {
rendering_attachments_ = CollectAttachments(environment_.validator, rendering_info);
rendering_instance_ = RenderingInstance{rendering_info.flags, rendering_info.renderArea, rendering_info.viewMask,
rendering_info.colorAttachmentCount, rendering_attachments_ /*init span*/};
// TODO: Unconverted draws still apply attachment accesses when record validation is disabled.
// Skip this initialization in that mode once those draws are converted.
rendering_instance_->InitViewGens(rendering_view_gens_);
return *rendering_instance_;
}
void CommandBufferContext::EndRenderingInstance() {
assert(rendering_instance_.has_value());
const bool rendering_has_ended = (rendering_instance_->flags & VK_RENDERING_SUSPENDING_BIT) == 0;
if (rendering_has_ended) {
current_render_pass_instance_id_++;
}
rendering_attachments_.clear();
rendering_instance_.reset();
rendering_view_gens_.clear();
}
bool CommandBufferContext::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(
GetSyncEnvironment(), hazard, *this, kInvalidTag, loc, 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(
GetSyncEnvironment(), hazard, *this, kInvalidTag, loc, 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(
GetSyncEnvironment(), hazard, *this, kInvalidTag, loc, 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(
GetSyncEnvironment(), hazard, *this, kInvalidTag, loc, 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 CommandBufferContext::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;
}
}
}
}
}
DescriptorAccesses CommandBufferContext::CollectDescriptorAccesses(VkPipelineBindPoint pipelineBindPoint) const {
DescriptorAccesses result;
if (!sync_state_.syncval_settings.shader_accesses_heuristic) return result;
const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
const vvl::Pipeline* pipeline = last_bound_state.pipeline_state;
const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots;
if (!pipeline) {
return result;
}
result.pipeline = pipeline;
result.render_pass_instance_id = current_render_pass_instance_id_;
result.subpass = current_renderpass_context_ ? current_renderpass_context_->GetCurrentSubpass() : vvl::kNoIndex32;
for (const auto& stage_state : pipeline->stage_states) {
if ((stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipeline->RasterizationDisabled()) ||
!stage_state.HasSpirv()) {
continue;
}
for (const auto& variable : stage_state.entrypoint->resource_interface_variables) {
if (variable.decorations.set >= ds_slots.size()) {
continue; // [core validation error]
}
const auto& ds_slot = ds_slots[variable.decorations.set];
const auto* descriptor_set = ds_slot.ds_state.get();
if (!descriptor_set) {
continue;
}
const auto binding = descriptor_set->GetBinding(variable.decorations.binding);
if (!binding) {
continue;
}
// Validation based on static analysis of descriptors can produce false-positives.
// This workaround disables validation for the descriptor array case.
if (binding->count > 1) {
continue;
}
const VkDescriptorType descriptor_type = binding->type;
const VkShaderStageFlagBits stage_flag = stage_state.GetStage();
const SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_flag);
if (sync_index == SYNC_ACCESS_INDEX_NONE) {
continue;
}
auto make_descriptor_info = [&](const VulkanTypedHandle& resource_handle, uint32_t index) {
ShaderAccessCommand::DescriptorInfo info;
info.descriptor_set = descriptor_set;
info.resource_handle = resource_handle;
info.set = variable.decorations.set;
info.descriptor_type = descriptor_type;
info.binding = variable.decorations.binding;
info.array_element = index;
info.stage = stage_flag;
return info;
};
for (uint32_t index = 0; index < binding->count; index++) {
const auto* descriptor = binding->GetDescriptor(index);
switch (descriptor->GetClass()) {
case DescriptorClass::ImageSampler:
case DescriptorClass::Image: {
// 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* image_view = image_descriptor->GetImageViewState();
if (image_view->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;
}
ShaderAccessCommand::ImageViewAccess access;
access.info = make_descriptor_info(image_view->Handle(), index);
access.image_view = image_view;
access.image_layout = image_descriptor->GetImageLayout();
access.access_index = sync_index;
if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) {
access.offset = CastTo3D(cb_state_->render_area.offset);
access.extent = CastTo3D(cb_state_->render_area.extent);
}
result.image_accesses.emplace_back(std::move(access));
break;
}
case DescriptorClass::TexelBuffer: {
const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor);
if (texel_descriptor->Invalid()) {
continue;
}
const auto* buffer_view = texel_descriptor->GetBufferViewState();
const auto* buffer = buffer_view->buffer_state.get();
ShaderAccessCommand::BufferAccess access;
access.info = make_descriptor_info(buffer_view->Handle(), index);
access.buffer = buffer;
access.range = MakeRange(*buffer_view);
access.access_index = sync_index;
result.buffer_accesses.emplace_back(std::move(access));
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* buffer = buffer_descriptor->GetBufferState();
ShaderAccessCommand::BufferAccess access;
access.info = make_descriptor_info(buffer->Handle(), index);
access.buffer = buffer;
access.range = MakeRange(*buffer, offset, buffer_descriptor->GetRange());
access.access_index = sync_index;
result.buffer_accesses.emplace_back(std::move(access));
break;
}
case DescriptorClass::AccelerationStructure: {
const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor);
if (accel_descriptor->Invalid()) {
continue;
}
const auto* acceleration_structure = accel_descriptor->GetAccelerationStructureStateKHR();
if (!acceleration_structure) {
continue;
}
const vvl::BufferAndOffset as_buffer = acceleration_structure->GetFirstValidBuffer(cb_state_->dev_data);
if (!as_buffer) {
continue;
}
ShaderAccessCommand::BufferAccess access;
access.info = make_descriptor_info(acceleration_structure->Handle(), index);
access.buffer = as_buffer.state;
access.range = MakeRange(*as_buffer.state, as_buffer.offset, acceleration_structure->GetSize());
access.access_index = sync_index;
result.buffer_accesses.emplace_back(std::move(access));
break;
}
default:
break;
}
}
}
}
return result;
}
void CommandBufferContext::RecordShaderAccesses(ResourceUsageTag tag, DescriptorAccesses& descriptor_accesses) {
for (auto& access : descriptor_accesses.buffer_accesses) {
access.handle_index = AddCommandHandle(tag, access.info.resource_handle).handle_index;
}
for (auto& access : descriptor_accesses.image_accesses) {
access.handle_index = AddCommandHandle(tag, access.image_view->image_state->Handle()).handle_index;
}
const ShaderAccessCommand command = descriptor_accesses.MakeCommand();
const auto& settings = sync_state_.syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
command.Apply(GetSyncEnvironment(), tag, GetCurrentAccessContext());
}
if (settings.full_validation) {
StoreCommand(tag, command);
}
}
bool CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::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);
}
static bool IsStencilWriteable(const LastBound& last_bound_state) {
if (!last_bound_state.IsStencilTestEnable()) {
return false;
}
auto is_writable = [&last_bound_state](const VkStencilOpState& ops) -> bool {
if (ops.writeMask == 0) {
return false;
}
// If compareOp is ALWAYS then failOp never runs (no writes possible)
const bool ignore_fail_op = (ops.compareOp == VK_COMPARE_OP_ALWAYS);
// If compareOp is NEVER then passOp never runs (no writes possible)
const bool ignore_pass_op = (ops.compareOp == VK_COMPARE_OP_NEVER);
// If depth test is not enabled then depthFailOp never runs (no writes possible)
const bool ignore_depth_fail_op = !last_bound_state.IsDepthTestEnable();
const bool is_read = (ops.failOp == VK_STENCIL_OP_KEEP || ignore_fail_op) &&
(ops.passOp == VK_STENCIL_OP_KEEP || ignore_pass_op) &&
(ops.depthFailOp == VK_STENCIL_OP_KEEP || ignore_depth_fail_op);
return !is_read;
};
const VkStencilOpState front_ops = last_bound_state.GetStencilOpStateFront();
const VkStencilOpState back_ops = last_bound_state.GetStencilOpStateBack();
return is_writable(front_ops) || is_writable(back_ops);
}
DrawAttachmentCommand CommandBufferContext::GetDrawAttachmentCommand() const {
const auto& last_bound_state = cb_state_->GetLastBoundGraphics();
const auto* pipeline = last_bound_state.pipeline_state;
// Draws in a secondary command buffer can write to the primary's render-pass attachments.
// We don't track those writes yet. Store a null pipeline so replay also skips
// tracking them, even when the primary's render-pass context becomes available.
if (!current_renderpass_context_ && !rendering_instance_) {
pipeline = nullptr;
}
return {pipeline,
current_renderpass_context_,
GetRenderingInstance(),
current_render_pass_instance_id_,
pipeline && last_bound_state.IsDepthWriteEnable(),
pipeline && IsStencilWriteable(last_bound_state)};
}
bool CommandBufferContext::ValidateDrawAttachment(const Location& loc) const {
return GetDrawAttachmentCommand().Validate(*this, loc);
}
void CommandBufferContext::RecordDrawAttachment(const ResourceUsageTag tag) {
GetDrawAttachmentCommand().Apply(environment_, tag, *current_context_);
}
VkImageAspectFlags CommandBufferContext::GetAttachmentAspectsToClear(VkImageAspectFlags clear_aspect_mask,
const vvl::ImageView& attachment_view) const {
// 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
}
// Color aspects to clear
if (clear_color) {
// The image view aspect mask is used only for color attachment.
// For depth/stencil attachment, it is ignored according to the spec.
const VkImageAspectFlags view_aspect_mask = attachment_view.normalized_subresource_range.aspectMask;
return view_aspect_mask & kColorAspects;
}
// Depth-stencil aspects to clear
bool has_depth_attachment = false;
bool has_stencil_attachment = false;
if (rendering_instance_) {
for (size_t i = rendering_instance_->color_attachment_count; i < rendering_instance_->attachments.size(); i++) {
const RenderingAttachment& attachment = rendering_instance_->attachments[i];
has_depth_attachment |= attachment.type == AttachmentType::kDepth;
has_stencil_attachment |= attachment.type == AttachmentType::kStencil;
}
} else if (current_renderpass_context_) {
const auto& rp_create_info = current_renderpass_context_->GetRenderPassState()->create_info;
const auto& subpass = rp_create_info.pSubpasses[current_renderpass_context_->GetCurrentSubpass()];
if (subpass.pDepthStencilAttachment) {
const uint32_t attachment = subpass.pDepthStencilAttachment->attachment;
if (attachment < rp_create_info.attachmentCount) {
const VkFormat ds_format = rp_create_info.pAttachments[attachment].format;
has_depth_attachment = vkuFormatHasDepth(ds_format);
has_stencil_attachment = vkuFormatHasStencil(ds_format);
}
}
}
VkImageAspectFlags ds_aspects_to_clear = VK_IMAGE_ASPECT_NONE;
if (clear_depth && has_depth_attachment) {
ds_aspects_to_clear |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (clear_stencil && has_stencil_attachment) {
ds_aspects_to_clear |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
return ds_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<CommandBufferContext::ClearAttachmentInfo> CommandBufferContext::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 (rendering_instance_) {
attachment_view = rendering_instance_->GetClearAttachmentView(clear_attachment);
}
if (!attachment_view) {
return {};
}
const VkImageAspectFlags aspects_to_clear = GetAttachmentAspectsToClear(clear_attachment.aspectMask, *attachment_view);
if (!aspects_to_clear) {
return {};
}
std::optional<VkImageSubresourceRange> subresource_range =
RestrictSubresourceRangeToClearLayers(attachment_view->normalized_subresource_range, clear_first_layer, clear_layer_count);
if (!subresource_range.has_value()) {
return {};
}
subresource_range->aspectMask = aspects_to_clear;
return ClearAttachmentInfo{*attachment_view, *subresource_range};
}
bool CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::GetQueueId() const { return kQueueIdInvalid; }
ResourceUsageTag CommandBufferContext::RecordBeginRenderPass(
vvl::Func command, const vvl::RenderPass& rp_state, const VkRect2D& render_area,
const std::vector<std::shared_ptr<const vvl::ImageView>>& attachment_views) {
const ResourceUsageTag barrier_tag = NextCommandTag(command, SubCommandType::kSubpassTransition, 0);
AddCommandHandle(barrier_tag, rp_state.Handle());
NextSubCommandTag(command, SubCommandType::kLoadOp, 0);
render_pass_contexts_.emplace_back(std::make_unique<RenderPassAccessContext>(
rp_state, render_area, environment_.queue_flags, attachment_views, cb_access_context_, current_render_pass_instance_id_,
environment_.queue_id));
current_renderpass_context_ = render_pass_contexts_.back().get();
current_context_ = &current_renderpass_context_->CurrentContext();
return barrier_tag;
}
ResourceUsageTag CommandBufferContext::RecordNextSubpass(vvl::Func command) {
if (!current_renderpass_context_->AdvanceSubpass()) {
return kInvalidTag;
}
current_context_ = &current_renderpass_context_->CurrentContext();
const uint32_t this_subpass = current_renderpass_context_->GetCurrentSubpass();
const uint32_t previous_subpass = this_subpass - 1;
auto resolve_tag = NextCommandTag(command, SubCommandType::kResolveOp, previous_subpass);
AddCommandHandle(resolve_tag, current_renderpass_context_->GetRenderPassState()->Handle());
NextSubCommandTag(command, SubCommandType::kStoreOp, previous_subpass);
NextSubCommandTag(command, SubCommandType::kSubpassTransition, this_subpass);
NextSubCommandTag(command, SubCommandType::kLoadOp, this_subpass);
return resolve_tag;
}
ResourceUsageTag CommandBufferContext::RecordEndRenderPass(vvl::Func 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());
NextSubCommandTag(command, SubCommandType::kSubpassTransition);
current_context_ = &cb_access_context_;
current_renderpass_context_ = nullptr;
current_render_pass_instance_id_++;
return store_tag;
}
void CommandBufferContext::RecordDestroyEvent(vvl::Event* event_state) { events_context_.Destroy(event_state); }
void CommandBufferContext::RecordExecutedCommandBuffer(const CommandBufferContext& recorded_cb_context) {
const AccessContext& recorded_context = recorded_cb_context.GetCbAccessContext();
const ResourceUsageTag base_tag = GetTagCount();
ImportRecordedAccessLog(recorded_cb_context);
auto import_common = [this](const auto& storage, const CommandData& recorded_command_data, ResourceUsageTag tag,
uint32_t tag_count) {
const auto command = storage.MakeCommand(recorded_command_data);
command.Apply(environment_, tag, *current_context_);
StoreCommand(tag, command, tag_count);
};
auto import_draw = [this](const auto& storage, const CommandData& recorded_command_data, ResourceUsageTag tag,
uint32_t tag_count) {
auto command = storage.MakeCommand(recorded_command_data, current_renderpass_context_, GetRenderingInstance());
const uint32_t subpass = current_renderpass_context_ ? current_renderpass_context_->GetCurrentSubpass() : vvl::kNoIndex32;
command.shader_accesses.render_pass_instance_id = current_render_pass_instance_id_;
command.shader_accesses.subpass = subpass;
command.attachment_accesses.render_pass_instance_id = current_render_pass_instance_id_;
command.Apply(environment_, tag, *current_context_);
StoreCommand(tag, command, tag_count);
};
const auto& settings = GetSyncState().syncval_settings;
if (settings.full_validation && recorded_cb_context.HasAllCommands()) {
const CommandData& command_data = recorded_cb_context.GetCommandData();
for (const CommandEntry& entry : recorded_cb_context.GetCommands()) {
const ResourceUsageTag tag = base_tag + entry.tag;
const uint32_t index = entry.command_ref.index;
switch (entry.command_ref.type) {
case CommandType::kBufferCopy: {
import_common(command_data.buffer_copy_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kBufferAccess: {
import_common(command_data.buffer_access_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kImageCopy: {
import_common(command_data.image_copy_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kPipelineBarrier: {
import_common(command_data.barrier_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kBeginRendering: {
auto command = command_data.begin_rendering_commands[index].MakeCommand(command_data);
command.render_pass_instance_id = current_render_pass_instance_id_;
command.rendering_instance.InitViewGens(rendering_view_gens_);
rendering_instance_ = command.rendering_instance;
command.Apply(environment_, tag, *current_context_);
StoreCommand(tag, command, entry.tag_count);
continue;
}
case CommandType::kEndRendering: {
if (!rendering_instance_) {
continue;
}
const EndRenderingCommand command{*rendering_instance_, current_render_pass_instance_id_};
command.Apply(environment_, tag, *current_context_);
StoreCommand(tag, command, entry.tag_count);
EndRenderingInstance();
continue;
}
case CommandType::kBeginRenderPass:
case CommandType::kNextSubpass:
case CommandType::kEndRenderPass: {
// [core validation check]: these commands are invalid in secondary command buffers
continue;
}
case CommandType::kShaderAccess: {
import_common(command_data.shader_access_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kDispatchIndirect: {
import_common(command_data.dispatch_indirect_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kDrawIndirectCount: {
import_draw(command_data.draw_indirect_count_commands[index], command_data, tag, entry.tag_count);
continue;
}
case CommandType::kDrawMeshTasks: {
import_draw(command_data.draw_mesh_tasks_commands[index], command_data, tag, entry.tag_count);
continue;
}
}
assert(false);
}
} else {
// Replay synchronization actions against the current destination state. The
// secondary's recorded access state already includes their effects and is resolved below.
for (const ReplayEntry& entry : recorded_cb_context.GetReplayEntries()) {
const bool replay_action = GetReplayContextChange(entry.operation) == nullptr;
if (replay_action) {
ApplyReplayAction(environment_, entry.operation, *current_context_, base_tag + entry.tag);
}
}
ResolveExecutedCommandBuffer(recorded_context, base_tag);
}
}
void CommandBufferContext::ResolveExecutedCommandBuffer(const AccessContext& recorded_context, ResourceUsageTag offset) {
auto tag_offset = [offset](AccessState* access) { access->OffsetTag(offset); };
current_context_->ResolveFromContext(tag_offset, recorded_context);
}
void CommandBufferContext::ImportRecordedAccessLog(const CommandBufferContext& recorded_context) {
cbs_referenced_->emplace_back(recorded_context.GetCBStateShared());
vvl::Append(*access_log_, *recorded_context.access_log_);
// 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 CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::AddCommandHandle(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle) {
return AddCommandHandleIndexed(tag, typed_handle, vvl::kNoIndex32);
}
ResourceUsageTagEx CommandBufferContext::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 CommandBufferContext::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 CommandBufferContext::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);
}
AttachmentAccess CommandBufferContext::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 CommandBufferContext::GetViewMask() const {
if (rendering_instance_) {
return rendering_instance_->view_mask;
} 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 CommandBufferContext::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 CommandBufferContext::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), cb_context(dev, &cb) {
cb_context.SetSelfReference();
}
void CommandBufferSubState::End() {
cb_context.GetCbAccessContext().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
cb_context.GetSyncState().stats.UpdateMemoryStats();
}
void CommandBufferSubState::Destroy() {
cb_context.Destroy(); // must be first to clean up self references correctly.
}
void CommandBufferSubState::Reset(const Location& loc) { cb_context.Reset(); }
void CommandBufferSubState::NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) {
for (auto& obj : invalid_nodes) {
switch (obj->Type()) {
case kVulkanObjectTypeEvent:
cb_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* p_regions, const Location& loc) {
const auto tag = cb_context.NextCommandTag(loc.function);
const auto src_tag_ex = cb_context.AddCommandHandle(tag, src_buffer_state.Handle());
const auto dst_tag_ex = cb_context.AddCommandHandle(tag, dst_buffer_state.Handle());
small_vector<BufferCopyRegion, 1> regions;
regions.reserve(region_count);
for (const VkBufferCopy& region : vvl::make_span(p_regions, region_count)) {
regions.emplace_back(BufferCopyRegion{region.srcOffset, region.dstOffset, region.size});
}
const BufferCopyCommand command{src_buffer_state, dst_buffer_state, regions, src_tag_ex.handle_index, dst_tag_ex.handle_index};
const auto& settings = cb_context.GetSyncState().syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
AccessContext& access_context = cb_context.GetCbAccessContext();
command.Apply(cb_context.GetSyncEnvironment(), tag, access_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command);
}
}
void CommandBufferSubState::RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
const VkBufferCopy2* p_regions, const Location& loc) {
const auto tag = cb_context.NextCommandTag(loc.function);
const auto src_tag_ex = cb_context.AddCommandHandle(tag, src_buffer_state.Handle());
const auto dst_tag_ex = cb_context.AddCommandHandle(tag, dst_buffer_state.Handle());
small_vector<BufferCopyRegion, 1> regions;
regions.reserve(region_count);
for (const VkBufferCopy2& region : vvl::make_span(p_regions, region_count)) {
regions.emplace_back(BufferCopyRegion{region.srcOffset, region.dstOffset, region.size});
}
const BufferCopyCommand command{src_buffer_state, dst_buffer_state, regions, src_tag_ex.handle_index, dst_tag_ex.handle_index};
const auto& settings = cb_context.GetSyncState().syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
AccessContext& access_context = cb_context.GetCbAccessContext();
command.Apply(cb_context.GetSyncEnvironment(), tag, access_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command);
}
}
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* p_regions, const Location& loc) {
const auto tag = cb_context.NextCommandTag(loc.function);
const auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
const auto dst_tag_ex = cb_context.AddCommandHandle(tag, dst_image_state.Handle());
const auto regions = vvl::make_span(p_regions, region_count);
const ImageCopyCommand command{src_image_state, dst_image_state, regions, src_tag_ex.handle_index, dst_tag_ex.handle_index};
const auto& settings = cb_context.GetSyncState().syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
AccessContext& access_context = cb_context.GetCbAccessContext();
command.Apply(cb_context.GetSyncEnvironment(), tag, access_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command);
}
}
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* p_regions, const Location& loc) {
const auto tag = cb_context.NextCommandTag(loc.function);
const auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
const auto dst_tag_ex = cb_context.AddCommandHandle(tag, dst_image_state.Handle());
small_vector<VkImageCopy, 1> regions;
regions.reserve(region_count);
for (const VkImageCopy2& region : vvl::make_span(p_regions, region_count)) {
regions.emplace_back(
VkImageCopy{region.srcSubresource, region.srcOffset, region.dstSubresource, region.dstOffset, region.extent});
}
const ImageCopyCommand command{src_image_state, dst_image_state, regions, src_tag_ex.handle_index, dst_tag_ex.handle_index};
const auto& settings = cb_context.GetSyncState().syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
AccessContext& access_context = cb_context.GetCbAccessContext();
command.Apply(cb_context.GetSyncEnvironment(), tag, access_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command);
}
}
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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_buffer_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
auto src_tag_ex = cb_context.AddCommandHandle(tag, src_image_state.Handle());
auto dst_tag_ex = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
cb_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 = cb_context.NextCommandTag(loc.function);
for (const auto& attachment : vvl::make_span(pAttachments, attachment_count)) {
for (const auto& rect : vvl::make_span(pRects, rect_count)) {
cb_context.RecordClearAttachment(tag, attachment, rect);
}
}
}
static void RecordBufferAccess(CommandBufferContext& cb_context, vvl::Buffer& buffer_state, AccessRange range,
const Location& loc) {
const ResourceUsageTag tag = cb_context.NextCommandTag(loc.function);
const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, buffer_state.Handle());
const BufferAccessCommand command{buffer_state, range, SYNC_CLEAR_TRANSFER_WRITE, tag_ex.handle_index};
const auto& settings = cb_context.GetSyncState().syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
AccessContext& access_context = cb_context.GetCbAccessContext();
command.Apply(cb_context.GetSyncEnvironment(), tag, access_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command);
}
}
void CommandBufferSubState::RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
const Location& loc) {
const AccessRange range = MakeRange(buffer_state, offset, size);
RecordBufferAccess(cb_context, buffer_state, range, loc);
}
void CommandBufferSubState::RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
const Location& loc) {
const AccessRange range = MakeRange(offset, size); // VK_WHOLE_SIZE not allowed
RecordBufferAccess(cb_context, buffer_state, range, loc);
}
void CommandBufferSubState::RecordDecodeVideo(vvl::VideoSession& vs_state, const VkVideoDecodeInfoKHR& decode_info,
const Location& loc) {
const auto tag = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
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 = cb_context.AddCommandHandle(tag, src_buffer->Handle());
context.UpdateAccessState(*src_buffer, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, src_range, src_tag_ex);
}
const vvl::DeviceState* device_state = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
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 = cb_context.AddCommandHandle(tag, src_buffer->Handle());
context.UpdateAccessState(*src_buffer, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, src_range, src_tag_ex);
}
const vvl::DeviceState* device_state = cb_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 = cb_context.NextCommandTag(loc.function);
AccessContext& context = cb_context.GetCbAccessContext();
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 = cb_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
}
const SyncValidator& validator = cb_context.GetSyncState();
auto rp_state = validator.Get<vvl::RenderPass>(render_pass_begin.renderPass);
if (!rp_state) {
return;
}
std::vector<std::shared_ptr<const vvl::ImageView>> attachments;
auto fb_state = validator.Get<vvl::Framebuffer>(render_pass_begin.framebuffer);
if (fb_state) {
attachments = validator.device_state->GetAttachmentViews(render_pass_begin, *fb_state);
}
const uint32_t render_pass_instance_id = cb_context.GetCurrentRenderPassInstanceId();
const BeginRenderPassCommand command{*rp_state, attachments, render_pass_begin.renderArea, render_pass_instance_id};
const ResourceUsageTag tag =
cb_context.RecordBeginRenderPass(loc.function, *rp_state, render_pass_begin.renderArea, attachments);
RenderPassAccessContext& rp_context = *cb_context.GetCurrentRenderPassContext();
const auto& settings = validator.syncval_settings;
if (settings.IsRecordTimeValidationEnabled()) {
command.Apply(cb_context.GetSyncEnvironment(), tag, rp_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command, BeginRenderPassCommand::kTagCount);
}
cb_context.AddReplayEntry(tag, true, ReplayContextChange(std::move(rp_state), std::move(attachments), &rp_context));
}
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
}
if (!cb_context.GetCurrentRenderPassContext()) {
return; // [core validation check]: begin render pass was not called
}
RenderPassAccessContext& rp_context = *cb_context.GetCurrentRenderPassContext();
const SyncValidator& validator = cb_context.GetSyncState();
const auto& settings = validator.syncval_settings;
const ResourceUsageTag tag = cb_context.RecordNextSubpass(loc.function);
if (tag == kInvalidTag) {
return;
}
const ResourceUsageTag transition_tag = tag + 2;
const NextSubpassCommand command{};
if (settings.IsRecordTimeValidationEnabled()) {
command.Apply(cb_context.GetSyncEnvironment(), tag, rp_context);
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command, NextSubpassCommand::kTagCount);
}
cb_context.AddReplayEntry(transition_tag, true, ReplayContextChange(ReplayContextChange::Type::kNextSubpass));
}
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
}
if (!cb_context.GetCurrentRenderPassContext()) {
return; // [core validation check]: begin render pass was not called
}
RenderPassAccessContext& rp_context = *cb_context.GetCurrentRenderPassContext();
const SyncValidator& validator = cb_context.GetSyncState();
const auto& settings = validator.syncval_settings;
const ResourceUsageTag tag = cb_context.RecordEndRenderPass(loc.function);
const EndRenderPassCommand command{};
if (settings.IsRecordTimeValidationEnabled()) {
command.Apply(cb_context.GetSyncEnvironment(), tag, rp_context, cb_context.GetCbAccessContext());
}
if (settings.full_validation) {
cb_context.StoreCommand(tag, command, EndRenderPassCommand::kTagCount);
}
const ResourceUsageTag transition_tag = tag + 1;
cb_context.AddReplayEntry(transition_tag, true, ReplayContextChange(ReplayContextChange::Type::kEndRenderPass));
}
void CommandBufferSubState::RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t cmd_index,
const Location& loc) {
if (cmd_index == 0) {
ResourceUsageTag cb_tag = cb_context.NextCommandTag(loc.function, SubCommandType::kIndex);
cb_context.AddCommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
} else {
ResourceUsageTag cb_tag = cb_context.NextSubCommandTag(loc.function, SubCommandType::kIndex);
cb_context.AddSubcommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
}
cb_context.RecordExecutedCommandBuffer(GetCommandBufferContext(secondary_command_buffer));
}
} // namespace syncval