blob: c62fb1791ca087758ba19adf616668add5f3f76b [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 "sync/sync_replay.h"
#include "state_tracker/cmd_buffer_state.h"
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;
};
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_; }
// 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_; }
const RenderPassAccessContext* GetCurrentRenderPassContext() const { return current_renderpass_context_; }
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; }
bool ValidateDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, const Location& loc) const;
void RecordDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, ResourceUsageTag tag);
DescriptorAccesses CollectDescriptorAccesses(VkPipelineBindPoint pipelineBindPoint) const;
void RecordShaderAccesses(ResourceUsageTag tag, DescriptorAccesses& descriptor_accesses);
DrawAttachmentCommand GetDrawAttachmentCommand() const;
bool ValidateDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const Location& loc) const;
void RecordDrawVertex(uint32_t vertexCount, uint32_t firstVertex, ResourceUsageTag tag);
bool ValidateDrawVertexIndex(uint32_t indexCount, uint32_t firstIndex, const Location& loc) const;
void RecordDrawVertexIndex(uint32_t indexCount, uint32_t firstIndex, ResourceUsageTag tag);
bool ValidateDrawAttachment(const Location& loc) const;
void RecordDrawAttachment(ResourceUsageTag tag);
bool ValidateClearAttachment(const Location& loc, const VkClearAttachment& clear_attachment, uint32_t clear_rect_index,
const VkClearRect& clear_rect) const;
void RecordClearAttachment(ResourceUsageTag tag, const VkClearAttachment& clear_attachment, const VkClearRect& clear_rect);
ResourceUsageTag RecordNextSubpass(vvl::Func command);
ResourceUsageTag RecordEndRenderPass(vvl::Func command);
void RecordDestroyEvent(vvl::Event* event_state);
void RecordExecutedCommandBuffer(const CommandBufferContext& recorded_context);
void ResolveExecutedCommandBuffer(const AccessContext& recorded_context, ResourceUsageTag offset);
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);
// NOTE: templated Operation is a GCC workaround (see the ReplayEntry constructor)
template <typename Operation>
void AddReplayEntry(ResourceUsageTag tag, bool validate_layout_transition_first_use, Operation&& operation) {
replay_entries_.emplace_back(tag, validate_layout_transition_first_use, std::forward<Operation>(operation));
}
template <typename Command>
void StoreCommand(ResourceUsageTag tag, const Command& command, uint32_t tag_count = 1) {
const auto storage = command.MakeStorage(command_data_);
commands_.push_back({command_data_.Store(storage), tag, tag_count});
}
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<ReplayEntry>& GetReplayEntries() const { return replay_entries_; }
const std::vector<CommandEntry>& GetCommands() const { return commands_; }
const CommandData& GetCommandData() const { return command_data_; }
// TODO: remove after convertion to commands is finished
bool HasAllCommands() const {
size_t command_index = 0;
for (ResourceUsageTag tag = 0; tag < access_log_->size(); tag++) {
if (command_index < commands_.size() && commands_[command_index].tag == tag) {
tag += commands_[command_index].tag_count - 1;
command_index++;
continue;
}
// We dont create replay commands for CmdExecuteCommands
const ResourceUsageRecord& record = (*access_log_)[tag];
if (record.command == vvl::Func::vkCmdExecuteCommands && record.sub_command_type == SubCommandType::kIndex) {
continue;
}
return false;
}
return command_index == commands_.size();
}
// 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;
// Should be called only during render pass instance
uint32_t GetViewMask() const;
struct ClearAttachmentInfo {
const vvl::ImageView& attachment_view;
VkImageSubresourceRange subresource_range{};
};
std::optional<ClearAttachmentInfo> GetClearAttachmentInfo(const VkClearAttachment& clear_attachment, uint32_t clear_first_layer,
uint32_t clear_layer_count) 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_;
// NOTE: Don't need the following for proxy contexts (that validate secondary execution).
// They flatten the source's current access state into cb_access_context_
//
// TODO: After removing legacy submit-time replay, replace this context history and the
// non-owning current pointer with a single owning current context.
std::vector<std::unique_ptr<RenderPassAccessContext>> render_pass_contexts_;
RenderPassAccessContext* current_renderpass_context_;
std::vector<ReplayEntry> replay_entries_;
std::vector<CommandEntry> commands_;
CommandData command_data_;
// 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