blob: 774a82e48b24850b98ca23525b2348d5a7c42c89 [file] [log] [blame]
/*
* Copyright (c) 2019-2024 Valve Corporation
* Copyright (c) 2019-2024 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"
class CommandBufferAccessContext;
class CommandExecutionContext;
class RenderPassAccessContext;
class ReplayState;
namespace vvl {
class ImageView;
class RenderPass;
class CommandBuffer;
} // namespace vvl
using SyncMemoryBarrier = SyncBarrier;
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->GetAccessStateMap(); }
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 SyncBufferMemoryBarrier {
using Buffer = std::shared_ptr<const vvl::Buffer>;
Buffer buffer;
SyncBarrier barrier;
ResourceAccessRange range;
bool IsLayoutTransition() const { return false; }
const ResourceAccessRange &Range() const { return range; };
const vvl::Buffer *GetState() const { return buffer.get(); }
SyncBufferMemoryBarrier(const Buffer &buffer_, const SyncBarrier &barrier_, const ResourceAccessRange &range_)
: buffer(buffer_), barrier(barrier_), range(range_) {}
SyncBufferMemoryBarrier() = default;
};
struct SyncImageMemoryBarrier {
using ImageState = syncval_state::ImageState;
using Image = std::shared_ptr<const ImageState>;
Image image;
uint32_t index;
SyncBarrier barrier;
VkImageLayout old_layout;
VkImageLayout new_layout;
VkImageSubresourceRange range;
bool IsLayoutTransition() const { return old_layout != new_layout; }
const VkImageSubresourceRange &Range() const { return range; };
const ImageState *GetState() const { return image.get(); }
SyncImageMemoryBarrier(const Image &image_, uint32_t index_, const SyncBarrier &barrier_, VkImageLayout old_layout_,
VkImageLayout new_layout_, const VkImageSubresourceRange &subresource_range_)
: image(image_),
index(index_),
barrier(barrier_),
old_layout(old_layout_),
new_layout(new_layout_),
range(subresource_range_) {}
SyncImageMemoryBarrier() = default;
};
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:
// Only non-null and valid for SyncOps within a render pass instance WIP -- think about how to manage for non RPI calls within
// RPI and 2ndarys...
uint32_t subpass_ = VK_SUBPASS_EXTERNAL;
vvl::Func command_;
};
class SyncOpBarriers : public SyncOpBase {
protected:
template <typename Barriers, typename FunctorFactory>
static void ApplyBarriers(const Barriers &barriers, const FunctorFactory &factory, QueueId queue_id, ResourceUsageTag tag,
AccessContext *context);
template <typename Barriers, typename FunctorFactory>
static void ApplyGlobalBarriers(const Barriers &barriers, const FunctorFactory &factory, QueueId queue_id, ResourceUsageTag tag,
AccessContext *access_context);
SyncOpBarriers(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount,
const VkMemoryBarrier *pMemoryBarriers, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier *pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier *pImageMemoryBarriers);
SyncOpBarriers(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags, uint32_t event_count,
const VkDependencyInfo *pDependencyInfo);
~SyncOpBarriers() override = default;
protected:
struct BarrierSet {
using ImageState = syncval_state::ImageState;
VkDependencyFlags dependency_flags;
SyncExecScope src_exec_scope;
SyncExecScope dst_exec_scope;
std::vector<SyncMemoryBarrier> memory_barriers;
std::vector<SyncBufferMemoryBarrier> buffer_memory_barriers;
std::vector<SyncImageMemoryBarrier> image_memory_barriers;
bool single_exec_scope;
void MakeMemoryBarriers(const SyncExecScope &src, const SyncExecScope &dst, VkDependencyFlags dependencyFlags,
uint32_t memoryBarrierCount, const VkMemoryBarrier *pMemoryBarriers);
void MakeBufferMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst,
VkDependencyFlags dependencyFlags, uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier *pBufferMemoryBarriers);
void MakeImageMemoryBarriers(const SyncValidator &sync_state, const SyncExecScope &src, const SyncExecScope &dst,
VkDependencyFlags dependencyFlags, uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier *pImageMemoryBarriers);
void MakeMemoryBarriers(VkQueueFlags queue_flags, VkDependencyFlags dependency_flags, uint32_t barrier_count,
const VkMemoryBarrier2 *barriers);
void MakeBufferMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, VkDependencyFlags dependency_flags,
uint32_t barrier_count, const VkBufferMemoryBarrier2 *barriers);
void MakeImageMemoryBarriers(const SyncValidator &sync_state, VkQueueFlags queue_flags, VkDependencyFlags dependency_flags,
uint32_t barrier_count, const VkImageMemoryBarrier2 *barriers);
};
std::vector<BarrierSet> barriers_;
};
class SyncOpPipelineBarrier : public SyncOpBarriers {
public:
SyncOpPipelineBarrier(vvl::Func command, const SyncValidator &sync_state, VkQueueFlags queue_flags,
VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkDependencyFlags dependencyFlags,
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;
};
class SyncOpWaitEvents : public SyncOpBarriers {
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;
protected:
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;
// 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_;
void MakeEventsList(const SyncValidator &sync_state, uint32_t event_count, const VkEvent *events);
};
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 syncval_state::ImageViewState *> 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_;
};
// The barrier operation for pipeline and subpass dependencies`
struct PipelineBarrierOp {
SyncBarrier barrier;
bool layout_transition;
ResourceAccessState::QueueScopeOps scope;
PipelineBarrierOp(QueueId queue_id, const SyncBarrier &barrier_, bool layout_transition_)
: barrier(barrier_), layout_transition(layout_transition_), scope(queue_id) {
if (queue_id != kQueueIdInvalid) {
// This is a submit time application... supress layout transitions to not taint the QueueBatchContext write state
layout_transition = false;
}
}
PipelineBarrierOp(const PipelineBarrierOp &rhs)
: barrier(rhs.barrier), layout_transition(rhs.layout_transition), scope(rhs.scope) {}
void operator()(ResourceAccessState *access_state) const { access_state->ApplyBarrier(scope, barrier, layout_transition); }
};
// 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 : public PipelineBarrierOp {
void operator()(ResourceAccessState *access_state) const {
access_state->ApplyBarrier(scope, barrier, layout_transition);
access_state->ApplyPendingBarriers(kInvalidTag); // There can't be any need for this tag
}
BatchBarrierOp(QueueId queue_id, const SyncBarrier &barrier_) : PipelineBarrierOp(queue_id, barrier_, false) {}
};
// The barrier operation for wait events
struct WaitEventBarrierOp {
ResourceAccessState::EventScopeOps scope_ops;
SyncBarrier barrier;
bool layout_transition;
WaitEventBarrierOp(const QueueId scope_queue_, const ResourceUsageTag scope_tag_, const SyncBarrier &barrier_,
bool layout_transition_)
: scope_ops(scope_queue_, scope_tag_), barrier(barrier_), layout_transition(layout_transition_) {
if (scope_queue_ != kQueueIdInvalid) {
// This is a submit time application... supress layout transitions to not taint the QueueBatchContext write state
layout_transition = false;
}
}
void operator()(ResourceAccessState *access_state) const { access_state->ApplyBarrier(scope_ops, barrier, layout_transition); }
};
// Allow keep track of the exec contexts replay state
class ReplayState {
public:
struct RenderPassReplayState {
// 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
RenderPassReplayState() { Reset(); }
AccessContext *Begin(VkQueueFlags queue_flags, const SyncOpBeginRenderPass &begin_op_,
const AccessContext &external_context);
AccessContext *Next();
void End(AccessContext &external_context);
const SyncOpBeginRenderPass *begin_op = nullptr;
const AccessContext *replay_context = nullptr;
uint32_t subpass = VK_SUBPASS_EXTERNAL;
std::vector<AccessContext> subpass_contexts;
void Reset() {
begin_op = nullptr;
replay_context = nullptr;
subpass = VK_SUBPASS_EXTERNAL;
subpass_contexts.clear();
}
operator bool() const { return begin_op != nullptr; }
};
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_;
};