blob: 4b576b160c1dd3c8514ce8d649388c89b887460e [file] [log] [blame]
/*
* 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_renderpass.h"
#include "sync/sync_reporting.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;
}
};
// 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();
enum class SubcommandType { kNone, kSubpassTransition, kLoadOp, kStoreOp, kResolveOp, kIndex };
ResourceUsageRecord(vvl::Func command, uint32_t seq_num, SubcommandType sub_type, const vvl::CommandBuffer *cb_state,
uint32_t reset_count)
: command(command), seq_num(seq_num), sub_command_type(sub_type), 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;
// 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;
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;
};
// Command execution context is the base class for command buffer and queue contexts
class CommandExecutionContext {
public:
using AccessLog = std::vector<ResourceUsageRecord>;
using CommandBufferSet = std::vector<std::shared_ptr<const vvl::CommandBuffer>>;
CommandExecutionContext(const SyncValidator &sync_validator, VkQueueFlags queue_flags);
virtual ~CommandExecutionContext() = default;
virtual AccessContext *GetCurrentAccessContext() = 0;
virtual SyncEventsContext *GetCurrentEventsContext() = 0;
virtual const AccessContext *GetCurrentAccessContext() const = 0;
virtual const SyncEventsContext *GetCurrentEventsContext() const = 0;
virtual QueueId GetQueueId() const = 0;
virtual VulkanTypedHandle Handle() const = 0;
virtual ResourceUsageInfo GetResourceUsageInfo(ResourceUsageTagEx tag_ex) const = 0;
bool ValidForSyncOps() const;
const SyncValidator &GetSyncState() const { return sync_state_; }
VkQueueFlags GetQueueFlags() const { return queue_flags_; }
protected:
const SyncValidator &sync_state_;
const syncval::ErrorMessages &error_messages_;
const VkQueueFlags queue_flags_;
};
class CommandBufferAccessContext : public CommandExecutionContext, DebugNameProvider {
public:
using SyncOpPointer = std::shared_ptr<SyncOpBase>;
constexpr static SyncAccessIndex kResolveRead = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ;
constexpr static SyncAccessIndex kResolveWrite = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
constexpr static SyncOrdering kColorResolveOrder = SyncOrdering::kColorAttachment;
// Although depth resolve runs on the color attachment output stage and uses color accesses, depth accesses
// still participate in the ordering. That's why using raster and not only color attachment ordering
constexpr static SyncOrdering kDepthStencilResolveOrder = SyncOrdering::kRaster;
constexpr static SyncOrdering kStoreOrder = SyncOrdering::kRaster;
struct SyncOpEntry {
ResourceUsageTag tag;
SyncOpPointer sync_op;
SyncOpEntry(ResourceUsageTag tag_, SyncOpPointer &&sync_op_) : tag(tag_), sync_op(std::move(sync_op_)) {}
SyncOpEntry() = default;
SyncOpEntry(const SyncOpEntry &other) = default;
};
CommandBufferAccessContext(SyncValidator &sync_validator, vvl::CommandBuffer *cb_state);
struct AsProxyContext {};
CommandBufferAccessContext(const CommandBufferAccessContext &real_context, AsProxyContext dummy);
~CommandBufferAccessContext() override;
// 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();
ResourceUsageInfo GetResourceUsageInfo(ResourceUsageTagEx tag_ex) const override;
AccessContext *GetCurrentAccessContext() override { return current_context_; }
SyncEventsContext *GetCurrentEventsContext() override { return &events_context_; }
const AccessContext *GetCurrentAccessContext() const override { return current_context_; }
const SyncEventsContext *GetCurrentEventsContext() const override { return &events_context_; }
QueueId GetQueueId() const override;
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<const vvl::ImageView *> &attachment_views);
bool ValidateBeginRendering(const ErrorObject &error_obj, BeginRenderingCmdState &cmd_state) const;
void RecordBeginRendering(BeginRenderingCmdState &cmd_state, const Location &loc);
bool ValidateEndRendering(const ErrorObject &error_obj) const;
void RecordEndRendering(const RecordObject &record_obj);
bool ValidateDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, const Location &loc) const;
void RecordDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint, ResourceUsageTag tag);
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;
bool ValidateDrawDynamicRenderingAttachment(const Location &loc) const;
void RecordDrawAttachment(ResourceUsageTag tag);
void RecordDrawDynamicRenderingAttachment(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 CommandBufferAccessContext &recorded_context);
void ResolveExecutedCommandBuffer(const AccessContext &recorded_context, ResourceUsageTag offset);
size_t GetTagCount() const { return access_log_->size(); }
VulkanTypedHandle Handle() const override {
if (cb_state_) {
return cb_state_->Handle();
}
return VulkanTypedHandle(static_cast<VkCommandBuffer>(VK_NULL_HANDLE), kVulkanObjectTypeCommandBuffer);
}
ResourceUsageTag NextCommandTag(vvl::Func command,
ResourceUsageRecord::SubcommandType subcommand = ResourceUsageRecord::SubcommandType::kNone);
ResourceUsageTag NextSubcommandTag(vvl::Func command, ResourceUsageRecord::SubcommandType subcommand);
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);
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 {
assert(cb_state_);
return *cb_state_;
}
template <class T, class... Args>
void RecordSyncOp(Args &&...args) {
// T must be as derived from SyncOpBase or the compiler will flag the next line as an error.
SyncOpPointer sync_op(std::make_shared<T>(std::forward<Args>(args)...));
RecordSyncOp(std::move(sync_op)); // Call the non-template version
}
std::shared_ptr<AccessLog> GetAccessLogShared() const { return access_log_; }
std::shared_ptr<CommandBufferSet> GetCBReferencesShared() const { return cbs_referenced_; }
void ImportRecordedAccessLog(const CommandBufferAccessContext &cb_context);
const std::vector<SyncOpEntry> &GetSyncOps() const { return sync_ops_; };
// 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:
CommandBufferAccessContext(const SyncValidator &sync_validator, VkQueueFlags queue_flags);
uint32_t AddHandle(const VulkanTypedHandle &typed_handle, uint32_t index);
void RecordSyncOp(SyncOpPointer &&sync_op);
AttachmentAccess GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type = AttachmentAccessType::Access) 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;
void CheckCommandTagDebugCheckpoint();
private:
// Note: since every CommandBufferAccessContext is encapsulated in its CommandBuffer object,
// a reference count is not needed here.
vvl::CommandBuffer *cb_state_;
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_;
// Don't need the following for an active proxy cb context
std::vector<std::unique_ptr<RenderPassAccessContext>> render_pass_contexts_;
RenderPassAccessContext *current_renderpass_context_;
std::vector<SyncOpEntry> sync_ops_;
// State during dynamic rendering (dynamic rendering rendering passes must be
// contained within a single command buffer)
std::unique_ptr<DynamicRenderingInfo> dynamic_rendering_info_;
// 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.
// TODO: add the above mentioned submit time behavior
uint32_t current_render_pass_instance_id_ = 0;
};
class CommandBufferSubState : public vvl::CommandBufferSubState {
public:
CommandBufferAccessContext access_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 CommandBufferAccessContext *GetAccessContext(vvl::CommandBuffer &cb) {
return &static_cast<CommandBufferSubState *>(cb.SubState(LayerObjectTypeSyncValidation))->access_context;
}
static inline const CommandBufferAccessContext *GetAccessContext(const vvl::CommandBuffer &cb) {
return &static_cast<const CommandBufferSubState *>(cb.SubState(LayerObjectTypeSyncValidation))->access_context;
}
} // namespace syncval