blob: f3639ea2ed38c49c39e5e87be592d7fb9ce9273e [file] [edit]
/*
* 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.
*/
#pragma once
#include "sync/sync_command.h"
#include "sync/sync_dynamic_rendering.h"
#include "sync/sync_event.h"
#include "sync/sync_render_pass.h"
#include "state_tracker/cmd_buffer_state.h"
#include <mutex>
struct RecordObject;
namespace syncval {
class SyncValidator;
class ErrorMessages;
struct AccessStats;
class AlternateResourceUsage {
public:
struct RecordBase;
struct RecordBase {
using Record = std::unique_ptr<RecordBase>;
virtual Record MakeRecord() const = 0;
virtual vvl::Func GetCommand() const = 0;
virtual VkSwapchainKHR GetSwapchainHandle() const = 0;
virtual ~RecordBase() {}
};
vvl::Func GetCommand() const { return record_->GetCommand(); }
VkSwapchainKHR GetSwapchainHandle() const { return record_->GetSwapchainHandle(); }
AlternateResourceUsage() = default;
AlternateResourceUsage(const RecordBase& record) : record_(record.MakeRecord()) {}
AlternateResourceUsage(const AlternateResourceUsage& other) : record_() {
if (bool(other.record_)) {
record_ = other.record_->MakeRecord();
}
}
AlternateResourceUsage& operator=(const AlternateResourceUsage& other) {
if (bool(other.record_)) {
record_ = other.record_->MakeRecord();
} else {
record_.reset();
}
return *this;
}
operator bool() const { return bool(record_); }
private:
RecordBase::Record record_;
};
// Vulkan handle and associated information.
// Command buffer context stores array of handles that are referenced by the tagged commands.
// VulkanTypedHandle is stored in unpacked form to avoid structure padding gaps.
struct HandleRecord {
uint64_t handle = 0;
VulkanObjectType type = kVulkanObjectTypeUnknown;
uint32_t index = vvl::kNoIndex32;
HandleRecord() = default;
explicit HandleRecord(const VulkanTypedHandle& typed_handle, uint32_t index = vvl::kNoIndex32)
: handle(typed_handle.handle), type(typed_handle.type), index(index) {}
bool IsIndexed() const { return index != vvl::kNoIndex32; }
VulkanTypedHandle TypedHandle() const {
VulkanTypedHandle typed_handle;
typed_handle.handle = handle;
typed_handle.type = type;
return typed_handle;
}
};
enum class SubCommandType { kNone, kSubpassTransition, kLoadOp, kStoreOp, kResolveOp, kIndex };
// ResourceUsageRecord encodes information about the command that performed the access.
// It's important to limit the size of this structure. Separate record is stored per access command.
struct ResourceUsageRecord {
static constexpr auto kMaxIndex = std::numeric_limits<ResourceUsageTag>::max();
ResourceUsageRecord(vvl::Func command, uint32_t seq_num, SubCommandType sub_type, const vvl::CommandBuffer* cb_state,
uint32_t reset_count, uint32_t subpass = vvl::kNoIndex32)
: command(command),
seq_num(seq_num),
sub_command_type(sub_type),
subpass(subpass),
cb_state(cb_state),
reset_count(reset_count) {}
ResourceUsageRecord(const AlternateResourceUsage& other) : alt_usage(other) {}
vvl::Func command = vvl::Func::Empty;
// TODO: this value should be relplaced by correct index of the Vulkan API command. Then it will be more useful.
// Currently this indexes only the commands that initiate memory accesses (so are of interest to syncval).
uint32_t seq_num = 0;
SubCommandType sub_command_type = SubCommandType::kNone;
uint32_t subpass = vvl::kNoIndex32;
// This is somewhat repetitive, but it prevents the need for Exec/Submit time touchup, after which usage records can be
// from different command buffers and resets.
// plain pointer as a shared pointer is held by the context storing this record
const vvl::CommandBuffer* cb_state = nullptr;
uint32_t reset_count = 0;
uint32_t first_handle_index = vvl::kNoIndex32;
uint32_t handle_count = 0;
uint32_t label_command_index = vvl::kNoIndex32;
AlternateResourceUsage alt_usage;
};
// ResourceUsageInfo is similar to ResourceUsageRecord but prioritizes accessibility over memory efficiency.
// This structure can be as large as needed. Instances are usually stored on the stack.
struct ResourceUsageInfo {
vvl::Func command = vvl::Func::Empty;
uint32_t command_seq = vvl::kNoIndex32;
SubCommandType sub_command_type = SubCommandType::kNone;
uint32_t subpass = vvl::kNoIndex32;
VulkanTypedHandle resource_handle;
std::string debug_region_name;
const vvl::CommandBuffer* cb = nullptr;
uint32_t command_buffer_reset_count = 0;
const vvl::Queue* queue = nullptr;
uint64_t submit_index = 0;
uint32_t batch_index = 0;
ResourceUsageTag batch_base_tag = 0;
};
// Provides debug region name for the specified access log command.
// If empty name is returned it means the command is not inside debug region.
struct DebugNameProvider {
virtual std::string GetDebugRegionName(const ResourceUsageRecord& record) const = 0;
};
struct ResourceUsageInfoProvider {
virtual ResourceUsageInfo GetResourceUsageInfo(ResourceUsageTagEx tag_ex) const = 0;
};
using AccessLog = std::vector<ResourceUsageRecord>;
using CommandBufferSet = std::vector<std::shared_ptr<const vvl::CommandBuffer>>;
struct SyncEnvironment {
SyncEnvironment(const SyncValidator& validator, VkQueueFlags queue_flags, QueueId queue_id, VulkanTypedHandle handle,
SyncEventsContext& events_context, const ResourceUsageInfoProvider& usage_info_provider);
const SyncValidator& validator;
const VkQueueFlags queue_flags;
const QueueId queue_id;
const VulkanTypedHandle handle;
SyncEventsContext& events_context;
const ResourceUsageInfoProvider& usage_info_provider;
};
// Identifies a reported error for deduplication purposes
struct ReportedHazard {
ReportedHazard(ResourceUsageTag tag, ResourceUsageTag prior_tag, SyncAccessIndex access, SyncAccessIndex prior_access,
SyncHazard hazard, VulkanTypedHandle resource);
// Local tags identifying the current and prior commands.
// prior_tag can be kInvalidTag when the prior access belongs to the same command
// (for example, render pass loadOp against initial layout transition)
ResourceUsageTag tag;
ResourceUsageTag prior_tag;
// Hash of the constructor arguments except the tags
uint64_t hash;
bool operator==(const ReportedHazard& other) const;
};
// a) Reports errors
// b) Collects the record-time errors so that replay does not duplicate them
struct ErrorReporter {
const CommandBufferContext& cb_context;
const Location& loc;
// Collected reports during recording. Null during queue submission
std::vector<ReportedHazard>* new_hazards = nullptr;
// Tag offset during replay
const ResourceUsageTag base_tag = 0;
// Local tag during replay, kInvalidTag during record-time validation
ResourceUsageTag replay_tag = kInvalidTag;
bool IsReplay() const { return replay_tag != kInvalidTag; }
// Logs the error unless it was already reported at record time. Return skip value
bool ReportHazard(const HazardResult& hazard, VulkanTypedHandle resource, const LogObjectList& objlist,
const Location& error_loc, const std::string& error) const;
bool ReportEventError(const SyncEventState& event_state, const char* vuid, const LogObjectList& objlist,
const Location& error_loc, const std::string& message) const;
private:
ResourceUsageTag CommandTag() const;
bool IsAlreadyReported(ReportedHazard report) const;
void Collect(ReportedHazard report) const;
};
class CommandBufferContext final : public ResourceUsageInfoProvider, public DebugNameProvider {
public:
struct AsProxyContext {};
CommandBufferContext(SyncValidator& sync_validator, vvl::CommandBuffer* cb_state);
CommandBufferContext(const CommandBufferContext& real_context, AsProxyContext dummy);
~CommandBufferContext();
// NOTE: because this class is encapsulated in syncval::CommandBuffer, it isn't safe
// to use shared_from_this from the constructor.
void SetSelfReference() { cbs_referenced_->push_back(cb_state_->shared_from_this()); }
void Destroy() {
// the cb self reference must be cleared or the command buffer reference count will never go to 0
cbs_referenced_.reset();
cb_state_ = nullptr;
}
void Reset();
const SyncValidator& GetSyncState() const { return sync_state_; }
ResourceUsageInfo GetResourceUsageInfo(ResourceUsageTagEx tag_ex) const override;
AccessContext& GetCurrentAccessContext() { return *current_context_; }
const AccessContext& GetCurrentAccessContext() const { return *current_context_; }
QueueId GetQueueId() const;
SyncEnvironment& GetSyncEnvironment() { return environment_; }
const SyncEnvironment& GetSyncEnvironment() const { return environment_; }
void RecordReportedHazards(const std::vector<ReportedHazard>& new_hazards) const;
void FinalizeReportedHazards();
bool HasReportedHazard(const ReportedHazard& report) const;
// The command buffer's own access context. Subpass contexts exist only inside a vkCmdBeginRenderPass
// instance, so use this instead of GetCurrentAccessContext() anywhere else. Dynamic rendering has no
// subpass contexts, so this applies there too.
AccessContext& GetCbAccessContext() { return cb_access_context_; }
const AccessContext& GetCbAccessContext() const { return cb_access_context_; }
RenderPassAccessContext* GetCurrentRenderPassContext() { return current_renderpass_context_.get(); }
const RenderPassAccessContext* GetCurrentRenderPassContext() const { return current_renderpass_context_.get(); }
void EndRenderPassContext() { current_renderpass_context_.reset(); }
uint32_t GetCurrentRenderPassInstanceId() const { return current_render_pass_instance_id_; }
ResourceUsageTag 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 RenderingInstance& BeginRenderingInstance(const VkRenderingInfo& rendering_info);
void EndRenderingInstance();
const RenderingInstance* GetRenderingInstance() const { return rendering_instance_ ? &*rendering_instance_ : nullptr; }
// Null outside rendering or in secondaries that inherit rendering
const VkRect2D* GetCurrentRenderArea() const;
DescriptorAccesses CollectDescriptorAccesses(VkPipelineBindPoint pipelineBindPoint) const;
void RecordShaderAccesses(ResourceUsageTag tag, DescriptorAccesses& descriptor_accesses);
DrawAttachmentCommand GetDrawAttachmentCommand() const;
VertexInputAccesses CollectVertexAccesses(uint32_t first_vertex, uint32_t vertex_count) const;
VertexInputAccesses CollectIndexAccesses(uint32_t first_index, uint32_t index_count) const;
MultiDrawVertexInputAccesses CollectMultiDrawVertexAccesses(uint32_t draw_count, const VkMultiDrawInfoEXT* draw_info,
uint32_t stride) const;
MultiDrawVertexInputAccesses CollectMultiDrawIndexAccesses(uint32_t draw_count, const VkMultiDrawIndexedInfoEXT* draw_info,
uint32_t stride) const;
std::vector<ClearAttachmentsCommand::Attachment> CollectClearAttachments(
vvl::span<const VkClearAttachment> clear_attachments) const;
uint32_t GetViewMask() const; // can be called only during render pass instance
ResourceUsageTag RecordNextSubpass(vvl::Func command);
ResourceUsageTag RecordEndRenderPass(vvl::Func command);
void RecordDestroyEvent(vvl::Event* event_state);
void RecordExecutedCommandBuffer(const CommandBufferContext& recorded_context);
size_t GetTagCount() const { return access_log_->size(); }
ResourceUsageTag NextCommandTag(vvl::Func command, SubCommandType subcommand = SubCommandType::kNone,
uint32_t subpass = vvl::kNoIndex32);
ResourceUsageTag NextSubCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass = vvl::kNoIndex32);
ResourceUsageTagEx AddCommandHandle(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle);
ResourceUsageTagEx AddCommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle, uint32_t index);
// Default subcommand behavior is that it references the same handles as the main command.
// The following method allows to set subcommand handles independently of the main command.
void AddSubcommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle, uint32_t index);
bool NeedsCommandStorage() const;
template <typename Command>
void StoreCommand(ResourceUsageTag tag, const Command& command) {
if (!NeedsCommandStorage()) {
return;
}
const auto storage = command.MakeStorage(command_data_);
commands_.push_back({command_data_.Store(storage), tag});
}
const std::vector<HandleRecord>& GetHandleRecords() const { return handles_; }
std::shared_ptr<const vvl::CommandBuffer> GetCBStateShared() const { return cb_state_->shared_from_this(); }
const vvl::CommandBuffer& GetCBState() const { return *cb_state_; }
std::shared_ptr<AccessLog> GetAccessLogShared() const { return access_log_; }
std::shared_ptr<CommandBufferSet> GetCBReferencesShared() const { return cbs_referenced_; }
void ImportRecordedAccessLog(const CommandBufferContext& cb_context);
const std::vector<CommandEntry>& GetCommands() const { return commands_; }
const CommandData& GetCommandData() const { return command_data_; }
// DebugNameProvider
std::string GetDebugRegionName(const ResourceUsageRecord& record) const override;
std::vector<vvl::LabelCommand>& GetProxyLabelCommands() { return proxy_label_commands_; }
void UpdateStats(AccessStats& access_stats) const;
private:
CommandBufferContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags, VulkanTypedHandle handle);
uint32_t AddHandle(const VulkanTypedHandle& typed_handle, uint32_t index);
AttachmentAccess GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type = AttachmentAccessType::Access) const;
VkImageAspectFlags GetAttachmentAspectsToClear(VkImageAspectFlags clear_aspect_mask,
const vvl::ImageView& attachment_view) const;
void CheckCommandTagDebugCheckpoint();
private:
const SyncValidator& sync_state_;
const ErrorMessages& error_messages_;
// Note: since every CommandBufferContext is encapsulated in its CommandBuffer object,
// a reference count is not needed here.
vvl::CommandBuffer* cb_state_ = nullptr;
std::shared_ptr<AccessLog> access_log_;
std::shared_ptr<CommandBufferSet> cbs_referenced_;
uint32_t command_number_;
uint32_t reset_count_;
// Handles referenced by the tagged commands
std::vector<HandleRecord> handles_;
// Location of the current command in the access log (it's not always the last element, there might be
// subcommands that follow). The subcommands by default reference the same handles as the main command.
ResourceUsageTag current_command_tag_ = vvl::kNoIndex32;
AccessContext cb_access_context_;
AccessContext* current_context_;
SyncEventsContext events_context_;
SyncEnvironment environment_;
std::unique_ptr<RenderPassAccessContext> current_renderpass_context_;
std::vector<CommandEntry> commands_;
CommandData command_data_;
// Reports used to suppress duplicates during replay. Mutable because reports are collected during
// validation and must be stored in a const CommandBufferContext. Command buffer access requires external
// synchronization. The mutex is still needed to prevent invalid concurrent recording from corrupting the reports.
mutable std::mutex reported_hazards_mutex_;
mutable std::vector<ReportedHazard> reported_hazards_;
// Dynamic rendering state
std::vector<RenderingAttachment> rendering_attachments_;
std::optional<RenderingInstance> rendering_instance_;
std::vector<ImageRangeGen> rendering_view_gens_;
// Secondary buffer validation uses proxy context and does local update (imitates Record).
// Because in this case PreRecord is not called, the label state is not updated. We make
// a copy of label state to update it locally together with proxy context.
std::vector<vvl::LabelCommand> proxy_label_commands_;
// Zero-based render pass instance id, incremented for each render pass instance.
// Used to initialize the corresponding value in the access object during recording.
// At submit time, these ids are offset to ensure unique values among all submitted
// command buffers.
uint32_t current_render_pass_instance_id_ = 0;
};
class CommandBufferSubState : public vvl::CommandBufferSubState {
public:
CommandBufferContext cb_context;
CommandBufferSubState(SyncValidator& dev, vvl::CommandBuffer& cb);
void NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) override;
void End() override;
void Destroy() override;
void Reset(const Location& loc) override;
void RecordCopyBuffer(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
const VkBufferCopy* regions, const Location& loc) override;
void RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
const VkBufferCopy2* regions, const Location& loc) override;
void RecordCopyImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy* regions,
const Location& loc) override;
void RecordCopyImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, VkImageLayout src_image_layout,
VkImageLayout dst_image_layout, uint32_t region_count, const VkImageCopy2* regions,
const Location& loc) override;
void RecordCopyBufferToImage(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout dst_image_layout,
uint32_t region_count, const VkBufferImageCopy* regions, const Location& loc) override;
void RecordCopyBufferToImage2(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout dst_image_layout,
uint32_t region_count, const VkBufferImageCopy2* regions, const Location& loc) override;
void 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) override;
void 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) override;
void 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) override;
void 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) override;
void RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
const VkImageResolve* regions, const Location& loc) override;
void RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
const VkImageResolve2* regions, const Location& loc) override;
void RecordClearColorImage(vvl::Image& image_state, VkImageLayout image_layout, const VkClearColorValue* color_values,
uint32_t range_count, const VkImageSubresourceRange* ranges, const Location& loc) override;
void RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout image_layout,
const VkClearDepthStencilValue* depth_stencil_values, uint32_t range_count,
const VkImageSubresourceRange* ranges, const Location& loc) override;
void RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments, uint32_t rect_count,
const VkClearRect* pRects, const Location& loc) override;
void RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location& loc) override;
void RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size, const Location& loc) override;
void RecordDecodeVideo(vvl::VideoSession& vs_state, const VkVideoDecodeInfoKHR& decode_info, const Location& loc) override;
void RecordEncodeVideo(vvl::VideoSession& vs_state, const VkVideoEncodeInfoKHR& encode_info, const Location& loc) override;
void 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) override;
void RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin, const VkSubpassBeginInfo& subpass_begin_info,
const Location& loc) override;
void RecordNextSubpass(const VkSubpassBeginInfo& subpass_begin_info, const VkSubpassEndInfo* subpass_end_info,
const Location& loc) override;
void RecordEndRenderPass(const VkSubpassEndInfo* subpass_end_info, const Location& loc) override;
void RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t cmd_index, const Location& loc) override;
};
static inline CommandBufferSubState& SubState(vvl::CommandBuffer& cb) {
return *static_cast<CommandBufferSubState*>(cb.SubState(LayerObjectTypeSyncValidation));
}
static inline const CommandBufferSubState& SubState(const vvl::CommandBuffer& cb) {
return *static_cast<const CommandBufferSubState*>(cb.SubState(LayerObjectTypeSyncValidation));
}
static inline CommandBufferContext& GetCommandBufferContext(vvl::CommandBuffer& cb) {
return static_cast<CommandBufferSubState*>(cb.SubState(LayerObjectTypeSyncValidation))->cb_context;
}
static inline const CommandBufferContext& GetCommandBufferContext(const vvl::CommandBuffer& cb) {
return static_cast<const CommandBufferSubState*>(cb.SubState(LayerObjectTypeSyncValidation))->cb_context;
}
} // namespace syncval