blob: 8e7a21e19e4acd546f4f3049945d2d379635961b [file] [log] [blame]
/* Copyright (c) 2019-2026 The Khronos Group Inc.
* Copyright (c) 2019-2026 Valve Corporation
* Copyright (c) 2019-2026 LunarG, Inc.
* Copyright (C) 2019-2026 Google 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.
*
* John Zulauf <jzulauf@lunarg.com>
*
*/
#pragma once
#include "sync/sync_common.h"
#include "sync/sync_access_state.h"
#include "containers/range.h"
#include "containers/container_utils.h"
#include <algorithm>
#include <cassert>
#include <map>
#include <utility>
namespace syncval {
// There are two types of comparisons of AccessMap ranges:
// a) Two non-empty, non-overlapping ranges.
// This happens when we compare two ranges from the access map, for example, during insert operation.
// The "less" comparison that works here is: (range_a.end <= range_b.begin)
//
// b) Non-empty range vs empty range. The empty range here is a single point passed to LowerBound.
// We compare this single point against non-empty ranges from the access map.
// The "less" comparison from a) works for b) too, except one configuration, when point A coincides
// with the beginning of non-empty range B. The comparison must return false here (e.g. not less).
// That's because the lower bound for such point A is a range B itself. The comparision from a) has
// to be extended with additional check: (range_a.begin < range_b.begin). This correctly handles
// this special case and does not affect result of (range_a.end <= range_b.begin) in all other cases.
struct AccessMapCompare {
bool operator()(const AccessRange &a, const AccessRange &b) const { return a.end <= b.begin && a.begin < b.begin; }
};
// Implements an ordered map of non-overlapping, non-empty ranges
class AccessMap {
using ImplMap = std::map<AccessRange, AccessState, AccessMapCompare>;
public:
using index_type = ResourceAddress;
using value_type = ImplMap::value_type;
using iterator = ImplMap::iterator;
using const_iterator = ImplMap::const_iterator;
public:
// Use explicit assignment to control all places where this happens
void Assign(const AccessMap &other);
AccessMap &operator=(const AccessMap &other) = delete;
AccessMap &operator=(AccessMap &&other) = delete;
iterator begin() { return impl_map_.begin(); }
const_iterator begin() const { return impl_map_.begin(); }
iterator end() { return impl_map_.end(); }
const_iterator end() const { return impl_map_.end(); }
iterator LowerBound(ResourceAddress range_begin);
const_iterator LowerBound(ResourceAddress range_begin) const;
size_t Size() const { return impl_map_.size(); }
void Clear() { impl_map_.clear(); }
iterator Erase(const iterator &pos);
void Erase(iterator first, iterator last);
iterator Insert(const_iterator hint, const AccessRange &range, const AccessState &access_state);
iterator InfillGap(const_iterator range_lower_bound, const AccessRange &range, const AccessState &access_state);
void InfillGaps(const AccessRange &range, const AccessState &access_state);
iterator Split(const iterator split_it, const index_type &index);
AccessMap() : impl_map_(AccessMapCompare()) {}
private:
// No replacement insert
std::pair<iterator, bool> Insert(const AccessRange &range, const AccessState &access_state);
private:
ImplMap impl_map_;
};
// The locator tracks an index value and its corresponding lower bound in the access map.
// Since the index may fall within a gap (no existing access map entry there),
// an "inside_lower_bound_range" flag is used to detect this.
// The locator must not be used after the underlying map is modified. Create a new locator instead.
template <typename TAccessMap>
class TAccessMapLocator {
public:
using index_type = AccessMap::index_type;
using iterator = decltype(TAccessMap().begin());
TAccessMapLocator(TAccessMap &map, index_type index);
TAccessMapLocator(TAccessMap &map, index_type index, const iterator &index_lower_bound);
// Set current location to provided value and update lower bound if necessary
void Seek(index_type seek_to);
// Distance from current location to the next change in access map.
// The next change is either the end of the current range or the beginning
// of the next range. Return 0 if lower_bound points to the end of access map.
index_type DistanceToEdge() const;
private:
iterator LowerBoundForIndex(index_type index) const { return map_->LowerBound(index); }
bool InsideLowerBoundRange() const { return lower_bound != map_->end() && lower_bound->first.includes(index); }
bool TrySeekLocal(index_type seek_to);
private:
TAccessMap *map_;
public:
// Current location in the access map address space
index_type index;
// Lower bound for the current index.
// That's either existing range in the access map or the end sentinel
iterator lower_bound;
// If the current location (index) is inside the lower bound range
bool inside_lower_bound_range;
};
using AccessMapLocator = TAccessMapLocator<AccessMap>;
using ConstAccessMapLocator = TAccessMapLocator<const AccessMap>;
// Traverse access maps over the same range in parallel.
// NextRange advances to the next point where either map starts or finishes a range segment.
// Returns a range over which the two maps do not transition ranges.
class ParallelIterator {
public:
using index_type = AccessRange::index_type;
using iterator = AccessMap::iterator;
ParallelIterator(AccessMap &map_A, const AccessMap &map_B, index_type index)
: map_A_(map_A), pos_A(map_A, index), pos_B(map_B, index), range(index, index + ComputeDelta()) {}
// Must be called when destination map's current range is modified to update cached lower bound.
// The lower bound corresponds to range.begin position.
// No guarantee range.begin is on the edge boundary, but range.end is.
void OnCurrentRangeModified(const iterator &new_lower_bound);
// Seeks to a specific index in both maps after destination map was potentially modified.
// No guarantee range.begin is on the edge boundary, but range.end is.
void SeekAfterModification(index_type index);
// Advance to the next spot where one of the maps changes
void NextRange();
private:
AccessMap &map_A_;
index_type ComputeDelta();
public:
AccessMapLocator pos_A;
ConstAccessMapLocator pos_B;
AccessRange range;
};
// Split a range into pieces bound by the intersection of the iterator's range and the supplied range
AccessMap::iterator Split(AccessMap::iterator in, AccessMap &map, const AccessRange &range);
// Combines directly adjacent ranges with equal AccessState
void Consolidate(AccessMap &map);
// Apply an operation over a range map, infilling where content is absent, updating where content is present.
// The passed pos must either be a lower bound (can be the end iterator) or be strictly less than the range.
// Map entries that intersect range.begin or range.end are split at the intersection point.
template <typename InfillUpdateOps>
AccessMap::iterator InfillUpdateRange(AccessMap &map, AccessMap::iterator pos, const AccessRange &range,
const InfillUpdateOps &ops) {
assert(range.non_empty());
const auto end = map.end();
assert(pos == map.LowerBound(range.begin) || pos->first.strictly_less(range));
if (pos != end && pos->first.strictly_less(range)) {
// pos is not a lower bound for the range (pos < range), but if the range is
// monotonically increasing, the next map entry may be the lower bound
++pos;
// If the new pos is not a lower bound, run the full search
if (pos != end && pos->first.strictly_less(range)) {
pos = map.LowerBound(range.begin);
}
}
assert(pos == map.LowerBound(range.begin));
if (pos != end && range.begin > pos->first.begin) {
// Lower bound starts before the range.
// Split the entry so that a new entry starts exactly at the range.begin
pos = map.Split(pos, range.begin);
++pos;
}
AccessMap::index_type current_begin = range.begin;
while (pos != end && current_begin < range.end) {
if (current_begin < pos->first.begin) { // infill the gap
const AccessRange gap_range(current_begin, std::min(range.end, pos->first.begin));
ops.infill(map, pos, gap_range);
// Advance current location.
// Do not advance pos, as it's the next map entry to visit
current_begin = pos->first.begin;
} else { // update existing entry
assert(current_begin == pos->first.begin);
// Split the current map entry if it goes beyond range.end.
// This ensures the update is restricted to the given range.
if (pos->first.end > range.end) {
pos = map.Split(pos, range.end);
}
ops.update(pos);
// Advance both current location and map entry
current_begin = pos->first.end;
++pos;
}
}
// Fill to the end if needed
if (current_begin < range.end) {
ops.infill(map, pos, AccessRange(current_begin, range.end));
}
return pos;
}
} // namespace syncval