blob: b2800d39956f0bc47355cc3092c9ee68bb792627 [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.
*/
#include "sync/sync_replay.h"
#include "sync/sync_render_pass.h"
#include "sync/sync_command_buffer.h"
#include "sync/sync_event.h"
#include "sync/sync_image.h"
#include "sync/sync_validation.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/render_pass_state.h"
#include "utils/image_utils.h"
#include "utils/sync_utils.h"
using vvl::Func;
namespace syncval {
PipelineBarrierReplay::PipelineBarrierReplay(BarrierSet&& barrier_set) : barrier_set(std::move(barrier_set)) {}
SetEventReplay::SetEventReplay(std::shared_ptr<const vvl::Event>&& event, const SyncExecScope& src_exec_scope,
std::shared_ptr<const AccessContext>&& src_access_context, const Location& loc)
: command(loc.function),
event(std::move(event)),
recorded_context(std::move(src_access_context)),
src_exec_scope(src_exec_scope) {}
ResetEventReplay::ResetEventReplay(std::shared_ptr<const vvl::Event>&& event, const SyncExecScope& exec_scope, const Location& loc)
: command(loc.function), event(std::move(event)), exec_scope(exec_scope) {}
WaitEventsReplay::WaitEventsReplay(std::vector<std::shared_ptr<const vvl::Event>>&& events, std::vector<BarrierSet>&& barrier_sets,
const Location& loc)
: command(loc.function), events(std::move(events)), barrier_sets(std::move(barrier_sets)) {}
// Tracks the recorded and destination contexts used during replay.
// Command buffer contexts are used outside a render pass and subpass contexts inside one.
class ReplayContexts {
public:
ReplayContexts(const CommandBufferContext& recorded_cb_context, AccessContext& destination_context, VkQueueFlags queue_flags)
: recorded_cb_context_(recorded_cb_context), destination_context_(destination_context), queue_flags_(queue_flags) {}
void BeginRenderPass(const RenderPassAccessContext& rp_context) {
const vvl::RenderPass& render_pass = *rp_context.GetRenderPassState();
const uint32_t subpass_count = render_pass.create_info.subpassCount;
rp_context_ = &rp_context;
current_subpass_ = 0;
destination_subpass_contexts_ =
InitSubpassContexts(queue_flags_, render_pass, destination_context_, kQueueIdInvalid);
// Replace the Async contexts with the async context of the "external" context.
// For replay we don't care about async subpasses, only async queue batches
for (uint32_t i = 0; i < subpass_count; i++) {
AccessContext& subpass_context = destination_subpass_contexts_[i];
subpass_context.ClearAsyncContexts();
subpass_context.ImportAsyncContexts(destination_context_);
}
}
void NextSubpass() {
const uint32_t subpass_count = rp_context_->GetRenderPassState()->create_info.subpassCount;
if (current_subpass_ + 1 < subpass_count) {
// Any store/resolve operations happen before the NextSubpass tag.
current_subpass_++;
}
}
void EndRenderPass() {
const uint32_t subpass_count = rp_context_->GetRenderPassState()->create_info.subpassCount;
destination_context_.ResolveChildContexts(vvl::make_span(destination_subpass_contexts_.get(), subpass_count));
rp_context_ = nullptr;
current_subpass_ = 0;
destination_subpass_contexts_.reset();
}
const AccessContext& GetRecordedContext() const {
return rp_context_ ? rp_context_->GetSubpassContexts()[current_subpass_] : recorded_cb_context_.GetCbAccessContext();
}
const AccessContext& GetDestinationContext() const {
return rp_context_ ? destination_subpass_contexts_[current_subpass_] : destination_context_;
}
AccessContext& GetDestinationContext() {
return rp_context_ ? destination_subpass_contexts_[current_subpass_] : destination_context_;
}
private:
const CommandBufferContext& recorded_cb_context_;
AccessContext& destination_context_;
const VkQueueFlags queue_flags_;
const RenderPassAccessContext* rp_context_ = nullptr;
uint32_t current_subpass_ = 0;
// Unlike the recorded subpass contexts, these contain no recorded accesses.
// They only store subpass dependencies applied to the destination state.
std::unique_ptr<AccessContext[]> destination_subpass_contexts_;
};
static bool ValidateEventCommand(const SyncEnvironment& env, const ReplayOperation& operation,
const AccessContext& destination_context, ResourceUsageTag exec_tag) {
if (const auto* set_event = GetSetEventReplay(operation)) {
return ValidateCmdSetEvent(env, set_event->event, set_event->src_exec_scope, exec_tag, Location(set_event->command));
}
if (const auto* reset_event = GetResetEventReplay(operation)) {
return ValidateCmdResetEvent(env, reset_event->event, reset_event->exec_scope, exec_tag, Location(reset_event->command));
}
if (const auto* wait_events = GetWaitEventsReplay(operation)) {
bool skip = false;
Location location(wait_events->command);
skip |= ValidateCmdWaitEvents(env, wait_events->events, exec_tag, location);
skip |= DetectCmdWaitEventsImageBarrierHazard(env, destination_context, wait_events->events, wait_events->barrier_sets,
exec_tag, location);
return skip;
}
return false;
}
void ApplyReplayAction(SyncEnvironment& env, const ReplayOperation& operation, AccessContext& access_context,
ResourceUsageTag exec_tag) {
if (const auto* barrier = GetPipelineBarrierReplay(operation)) {
ApplyBarrier(env, access_context, barrier->barrier_set, exec_tag);
} else if (const auto* set_event = GetSetEventReplay(operation)) {
// Build the event's first-scope context by merging the current replay destination
// with the command-buffer context captured when the set-event command was recorded.
// Keep the recorded context unchanged because the command buffer can be replayed multiple times.
auto merged_context = std::make_shared<AccessContext>(*access_context.validator);
merged_context->InitFrom(access_context);
merged_context->ResolveFromContext(QueueTagOffsetBarrierAction(env.queue_id, exec_tag), *set_event->recorded_context);
merged_context->TrimAndClearFirstAccess();
ApplyCmdSetEvent(env, set_event->event, set_event->src_exec_scope, merged_context, exec_tag, set_event->command);
} else if (const auto* reset_event = GetResetEventReplay(operation)) {
ApplyCmdResetEvent(env, reset_event->event, exec_tag, reset_event->command);
} else if (const auto* wait_events = GetWaitEventsReplay(operation)) {
ApplyCmdWaitEvents(env, access_context, wait_events->events, wait_events->barrier_sets, exec_tag, wait_events->command);
}
}
static void ApplyContextChange(const ReplayContextChange& change, ReplayContexts& contexts) {
if (change.type == ReplayContextChange::Type::kBeginRenderPass) {
contexts.BeginRenderPass(*change.rp_context);
return;
}
if (change.type == ReplayContextChange::Type::kNextSubpass) {
contexts.NextSubpass();
return;
}
if (change.type == ReplayContextChange::Type::kEndRenderPass) {
contexts.EndRenderPass();
return;
}
}
static bool DetectFirstUseHazard(const SyncEnvironment& env, const ReplayContexts& contexts,
const CommandBufferContext& recorded_cb_context, const ResourceUsageRange& first_use_range,
const Location& cb_loc) {
bool skip = false;
if (!first_use_range.non_empty()) {
return skip;
}
const AccessContext& recorded_context = contexts.GetRecordedContext();
const AccessContext& destination_context = contexts.GetDestinationContext();
const HazardResult hazard = recorded_context.DetectFirstUseHazard(env.queue_id, first_use_range, destination_context);
if (hazard.IsHazard()) {
LogObjectList objlist(env.handle, recorded_cb_context.GetCBState().Handle());
const std::string error = env.validator.error_messages_.FirstUseError(env, hazard, recorded_cb_context, cb_loc.index);
skip |= env.validator.SyncError(hazard.Hazard(), objlist, cb_loc, error);
}
return skip;
}
// Validate first-use hazards. The following describes how it works.
//
// The first access to a memory location can occur anywhere in the command buffer
// (not necessarily at the beginning), and first accesses to different resources
// may be interleaved with barriers. To validate each first access against accesses
// from previous submissions, we need to replay all sync operations that occur before that
// specific first access.
//
// This defines the algorithm: replay sync operations until we reach the next first access,
// validate that first access, then continue replaying operations until the next first
// access, validate that one, and so on until we reach the end of the command buffer.
bool ValidateFirstUseHazards(SyncEnvironment& env, const CommandBufferContext& recorded_cb_context,
AccessContext& destination_context, ResourceUsageTag base_tag, const Location& cb_loc) {
bool skip = false;
ReplayContexts contexts(recorded_cb_context, destination_context, env.queue_flags);
ResourceUsageRange first_use_range = {0, 0};
for (const ReplayEntry& entry : recorded_cb_context.GetReplayEntries()) {
first_use_range.end = entry.tag;
skip |= DetectFirstUseHazard(env, contexts, recorded_cb_context, first_use_range, cb_loc);
const auto* context_change = GetReplayContextChange(entry.operation);
if (context_change) {
ApplyContextChange(*context_change, contexts);
}
if (entry.validate_layout_transition_first_use) {
skip |= DetectFirstUseHazard(env, contexts, recorded_cb_context, {entry.tag, entry.tag + 1}, cb_loc);
}
const bool replay_action = context_change == nullptr;
if (replay_action) {
const ResourceUsageTag exec_tag = base_tag + entry.tag;
AccessContext& current_destination = contexts.GetDestinationContext();
skip |= ValidateEventCommand(env, entry.operation, current_destination, exec_tag);
ApplyReplayAction(env, entry.operation, current_destination, exec_tag);
}
first_use_range.begin = entry.tag + 1;
}
// Validate first accesses after the last replay entry.
first_use_range.end = ResourceUsageRecord::kMaxIndex;
skip |= DetectFirstUseHazard(env, contexts, recorded_cb_context, first_use_range, cb_loc);
return skip;
}
} // namespace syncval