blob: c5ff7b099fb52da7dd5a9883a114e538c9a5b49e [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.
*/
#pragma once
#include "sync/sync_access_context.h"
#include <vulkan/utility/vk_safe_struct.hpp>
#include "error_message/error_location.h"
struct DeviceExtensions;
namespace vvl {
class Event;
class ImageView;
class RenderPass;
} // namespace vvl
namespace syncval {
class CommandBufferAccessContext;
class CommandExecutionContext;
class RenderPassAccessContext;
class ReplayState;
class SyncValidator;
struct SyncEventState {
enum IgnoreReason { NotIgnored = 0, ResetWaitRace, Reset2WaitRace, SetRace, MissingStageBits, SetVsWait2, MissingSetEvent };
using EventPointer = std::shared_ptr<const vvl::Event>;
EventPointer event;
vvl::Func last_command; // Only Event commands are valid here.
ResourceUsageTag last_command_tag; // Needed to filter replay validation
vvl::Func unsynchronized_set;
VkPipelineStageFlags2 barriers;
SyncExecScope scope;
ResourceUsageTag first_scope_tag;
bool destroyed;
std::shared_ptr<const AccessContext> first_scope;
SyncEventState()
: event(),
last_command(vvl::Func::Empty),
last_command_tag(0),
unsynchronized_set(vvl::Func::Empty),
barriers(0U),
scope(),
first_scope_tag(),
destroyed(true) {}
SyncEventState(const SyncEventState &) = default;
SyncEventState(SyncEventState &&) = default;
SyncEventState(const SyncEventState::EventPointer &event_state);
void ResetFirstScope();
const AccessContext::ScopeMap &FirstScope() const { return first_scope->GetAccessMap(); }
IgnoreReason IsIgnoredByWait(vvl::Func command, VkPipelineStageFlags2 srcStageMask) const;
bool HasBarrier(VkPipelineStageFlags2 stageMask, VkPipelineStageFlags2 exec_scope) const;
void AddReferencedTags(ResourceUsageTagSet &referenced) const;
};
class SyncEventsContext {
public:
using Map = vvl::unordered_map<const vvl::Event *, std::shared_ptr<SyncEventState>>;
using iterator = Map::iterator;
using const_iterator = Map::const_iterator;
SyncEventState *GetFromShared(const SyncEventState::EventPointer &event_state) {
const auto find_it = map_.find(event_state.get());
if (find_it == map_.end()) {
if (!event_state.get()) return nullptr;
const auto *event_plain_ptr = event_state.get();
auto sync_state = std::make_shared<SyncEventState>(event_state);
auto insert_pair = map_.emplace(event_plain_ptr, sync_state);
return insert_pair.first->second.get();
}
return find_it->second.get();
}
const SyncEventState *Get(const vvl::Event *event_state) const {
const auto find_it = map_.find(event_state);
if (find_it == map_.end()) {
return nullptr;
}
return find_it->second.get();
}
const SyncEventState *Get(const SyncEventState::EventPointer &event_state) const { return Get(event_state.get()); }
void ApplyBarrier(const SyncExecScope &src, const SyncExecScope &dst, ResourceUsageTag tag);
void ApplyTaggedWait(VkQueueFlags queue_flags, ResourceUsageTag tag);
void Destroy(const vvl::Event *event_state) {
auto sync_it = map_.find(event_state);
if (sync_it != map_.end()) {
sync_it->second->destroyed = true;
map_.erase(sync_it);
}
}
void Clear() { map_.clear(); }
SyncEventsContext &DeepCopy(const SyncEventsContext &from);
void AddReferencedTags(ResourceUsageTagSet &referenced) const;
private:
Map map_;
};
struct SyncBufferBarrier {
std::shared_ptr<const vvl::Buffer> buffer;
SyncBarrier barrier;
AccessRange range;
SyncBufferBarrier(const std::shared_ptr<const vvl::Buffer> &buffer, const SyncBarrier &barrier, const AccessRange &range)
: buffer(buffer), barrier(barrier), range(range) {}
};
struct SyncImageBarrier {
std::shared_ptr<const vvl::Image> image;
SyncBarrier barrier;
VkImageSubresourceRange subresource_range;
bool layout_transition;
uint32_t barrier_index;
uint32_t handle_index = vvl::kNoIndex32;
SyncImageBarrier(const std::shared_ptr<const vvl::Image> &image, const SyncBarrier &barrier,
const VkImageSubresourceRange &subresource_range, bool layout_transition, uint32_t barrier_index)
: image(image),
barrier(barrier),
subresource_range(subresource_range),
layout_transition(layout_transition),
barrier_index(barrier_index) {}
};
struct BarrierSet {
SyncExecScope src_exec_scope;
SyncExecScope dst_exec_scope;
std::vector<SyncBarrier> memory_barriers;
std::vector<SyncBufferBarrier> buffer_barriers;
std::vector<SyncImageBarrier> image_barriers;
bool single_exec_scope = false;
// The numbers of additional global barriers introduced to track execution dependencies
// defined by image and buffer barriers, or a single execution dependencies when a sync1
// barrier command specifies no barriers (only exec scopes). Used for statistics tracking.
uint32_t execution_dependency_barrier_count = 0;
void MakeMemoryBarriers(const SyncExecScope &src, const SyncExecScope &dst, uint32_t barrier_count,
const VkMemoryBarrier *barriers);
void MakeMemoryBarriers(VkQueueFlags queue_flags, const VkDependencyInfo &dep_info);
void MakeBufferMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst,
uint32_t barrier_count, const VkBufferMemoryBarrier *barriers);
void MakeBufferMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t barrier_count,
const VkBufferMemoryBarrier2 *barriers);
void MakeImageMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst,
uint32_t barrier_count, const VkImageMemoryBarrier *barriers, const DeviceExtensions &extensions);
void MakeImageMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t barrier_count,
const VkImageMemoryBarrier2 *barriers, const DeviceExtensions &extensions);
};
class SyncOpBase {
public:
SyncOpBase() : command_(vvl::Func::Empty) {}
SyncOpBase(vvl::Func command) : command_(command) {}
virtual ~SyncOpBase() = default;
const char *CmdName() const { return vvl::String(command_); }
virtual bool Validate(const CommandBufferAccessContext &cb_context) const = 0;
virtual ResourceUsageTag Record(CommandBufferAccessContext *cb_context) = 0;
virtual bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const = 0;
virtual void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const = 0;
protected:
vvl::Func command_;
};
class SyncOpPipelineBarrier : public SyncOpBase {
public:
SyncOpPipelineBarrier(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, uint32_t memoryBarrierCount,
const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier *pImageMemoryBarriers);
SyncOpPipelineBarrier(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags,
const VkDependencyInfo &pDependencyInfo);
~SyncOpPipelineBarrier() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
uint32_t GetExecutionDependencyBarrierCount() const { return barrier_set_.execution_dependency_barrier_count; }
private:
// A single barrier can be applied more efficently (immidiately) compared to multiple barrier.
// The latter are applied in two steps (collect and then apply)
void ApplySingleBufferBarrier(CommandExecutionContext &exec_context, const SyncBufferBarrier &buffer_barrier,
const SyncBarrier &exec_dep_barrier) const;
void ApplySingleImageBarrier(CommandExecutionContext &exec_context, const SyncImageBarrier &image_barrier,
const SyncBarrier &exec_dep_barrier, ResourceUsageTag exec_tag) const;
void ApplySingleMemoryBarrier(CommandExecutionContext &exec_context, const SyncBarrier &memory_barrier) const;
// This handles all configurations where barriers cannot be applied immidiately and need to use
// the PendingBarriers helper to ensure independent barrier application. All such configurations
// use more than one barrier.
void ApplyMultipleBarriers(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const;
private:
BarrierSet barrier_set_;
};
class SyncOpWaitEvents : public SyncOpBase {
public:
SyncOpWaitEvents(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t eventCount,
const VkEvent *pEvents, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier *pImageMemoryBarriers);
SyncOpWaitEvents(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t eventCount,
const VkEvent *pEvents, const VkDependencyInfo *pDependencyInfo);
~SyncOpWaitEvents() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
private:
static const char *const kIgnored;
bool DoValidate(const CommandExecutionContext &ex_context, const ResourceUsageTag base_tag) const;
void DoRecord(CommandExecutionContext &ex_context, const ResourceUsageTag base_tag) const;
void MakeEventsList(const SyncValidator &sync_state, uint32_t event_count, const VkEvent *events);
// TODO PHASE2 This is the wrong thing to use for "replay".. as the event state will have moved on since the record
// TODO PHASE2 May need to capture by value w.r.t. "first use" or build up in calling/enqueue context through replay.
std::vector<std::shared_ptr<const vvl::Event>> events_;
std::vector<BarrierSet> barrier_sets_;
};
class SyncOpResetEvent : public SyncOpBase {
public:
SyncOpResetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event,
VkPipelineStageFlags2 stageMask);
~SyncOpResetEvent() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
private:
bool DoValidate(const CommandExecutionContext &ex_context, const ResourceUsageTag base_tag) const;
std::shared_ptr<const vvl::Event> event_;
SyncExecScope exec_scope_;
};
class SyncOpSetEvent : public SyncOpBase {
public:
SyncOpSetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event,
VkPipelineStageFlags2 stageMask, const AccessContext *access_context);
SyncOpSetEvent(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkEvent event,
const VkDependencyInfo &dep_info, const AccessContext *access_context);
~SyncOpSetEvent() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
private:
bool DoValidate(const CommandExecutionContext &ex_context, const ResourceUsageTag base_tag) const;
void DoRecord(QueueId queue_id, ResourceUsageTag recorded_tag, const std::shared_ptr<const AccessContext> &access_context,
SyncEventsContext *events_context) const;
std::shared_ptr<const vvl::Event> event_;
// The Access context of the command buffer at record set event time.
std::shared_ptr<const AccessContext> recorded_context_;
SyncExecScope src_exec_scope_;
// Note that the dep info is *not* dehandled, but retained for comparison with a future WaitEvents2
std::shared_ptr<vku::safe_VkDependencyInfo> dep_info_;
};
class SyncOpBeginRenderPass : public SyncOpBase {
public:
SyncOpBeginRenderPass(vvl::Func command, const SyncValidator &sync_state, const VkRenderPassBeginInfo *pRenderPassBegin,
const VkSubpassBeginInfo *pSubpassBeginInfo);
~SyncOpBeginRenderPass() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
const RenderPassAccessContext *GetRenderPassAccessContext() const { return rp_context_; }
protected:
vku::safe_VkRenderPassBeginInfo renderpass_begin_info_;
vku::safe_VkSubpassBeginInfo subpass_begin_info_;
std::vector<std::shared_ptr<const vvl::ImageView>> shared_attachments_;
std::vector<const vvl::ImageView *> attachments_;
std::shared_ptr<const vvl::RenderPass> rp_state_;
const RenderPassAccessContext *rp_context_;
};
class SyncOpNextSubpass : public SyncOpBase {
public:
SyncOpNextSubpass(vvl::Func command, const SyncValidator &sync_state, const VkSubpassBeginInfo *pSubpassBeginInfo,
const VkSubpassEndInfo *pSubpassEndInfo);
~SyncOpNextSubpass() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
protected:
vku::safe_VkSubpassBeginInfo subpass_begin_info_;
vku::safe_VkSubpassEndInfo subpass_end_info_;
};
class SyncOpEndRenderPass : public SyncOpBase {
public:
SyncOpEndRenderPass(vvl::Func command, const SyncValidator &sync_state, const VkSubpassEndInfo *pSubpassEndInfo);
~SyncOpEndRenderPass() override = default;
bool Validate(const CommandBufferAccessContext &cb_context) const override;
ResourceUsageTag Record(CommandBufferAccessContext *cb_context) override;
bool ReplayValidate(ReplayState &replay, ResourceUsageTag recorded_tag) const override;
void ReplayRecord(CommandExecutionContext &exec_context, ResourceUsageTag exec_tag) const override;
protected:
vku::safe_VkSubpassEndInfo subpass_end_info_;
};
// Batch barrier ops don't modify in place, and thus don't need to hold pending state, and also are *never* layout transitions.
struct BatchBarrierOp {
SyncBarrier barrier;
BarrierScope barrier_scope;
BatchBarrierOp(QueueId queue_id, const SyncBarrier &barrier) : barrier(barrier), barrier_scope(barrier, queue_id) {}
void operator()(AccessState *access_state) const { access_state->ApplyBarrier(barrier_scope, barrier); }
};
// Allow keep track of the exec contexts replay state
class ReplayState {
public:
// A minimal subset of the functionality present in the RenderPassAccessContext. Since the accesses are recorded in the
// first_use information of the recorded access contexts, s.t. all we need to support is the barrier/resolve operations
struct RenderPassReplayState {
AccessContext *Begin(VkQueueFlags queue_flags, const SyncOpBeginRenderPass &begin_op_,
const AccessContext &external_context);
AccessContext *Next();
void End(AccessContext &external_context);
vvl::span<AccessContext> GetSubpassContexts();
const SyncOpBeginRenderPass *begin_op = nullptr;
const AccessContext *replay_context = nullptr;
uint32_t subpass = VK_SUBPASS_EXTERNAL;
std::unique_ptr<AccessContext[]> subpass_contexts;
};
bool ValidateFirstUse();
bool DetectFirstUseHazard(const ResourceUsageRange &first_use_range) const;
ReplayState(CommandExecutionContext &exec_context, const CommandBufferAccessContext &recorded_context,
const ErrorObject &error_object, uint32_t index, ResourceUsageTag base_tag);
CommandExecutionContext &GetExecutionContext() const { return exec_context_; }
ResourceUsageTag GetBaseTag() const { return base_tag_; }
AccessContext *ReplayStateRenderPassBegin(VkQueueFlags queue_flags, const SyncOpBeginRenderPass &begin_op,
const AccessContext &external_context);
AccessContext *ReplayStateRenderPassNext();
void ReplayStateRenderPassEnd(AccessContext &external_context);
protected:
const AccessContext *GetRecordedAccessContext() const;
CommandExecutionContext &exec_context_;
const CommandBufferAccessContext &recorded_context_;
const ErrorObject &error_obj_;
const uint32_t index_;
const ResourceUsageTag base_tag_;
RenderPassReplayState rp_replay_;
};
} // namespace syncval