From 3bc53094dace40a2b171e957810abca35c570c3a Mon Sep 17 00:00:00 2001 From: Benson Joeris Date: Fri, 8 Feb 2019 16:15:12 -0500 Subject: [PATCH] Add `update` and `merge` methods to `Intervals` Also updated build files and added tests for `Intervals`. --- renderdoc/CMakeLists.txt | 2 + renderdoc/core/intervals.h | 99 ++++++- renderdoc/core/intervals_tests.cpp | 393 ++++++++++++++++++++++++++++ renderdoc/renderdoc.vcxproj | 1 + renderdoc/renderdoc.vcxproj.filters | 3 + 5 files changed, 491 insertions(+), 7 deletions(-) create mode 100644 renderdoc/core/intervals_tests.cpp diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index 059095207..c970a97da 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -105,6 +105,8 @@ set(sources core/remote_server.cpp core/replay_proxy.cpp core/replay_proxy.h + core/intervals.h + core/intervals_tests.cpp android/android.cpp android/android_patch.cpp android/android_tools.cpp diff --git a/renderdoc/core/intervals.h b/renderdoc/core/intervals.h index e35fe818e..77f7ebcd4 100644 --- a/renderdoc/core/intervals.h +++ b/renderdoc/core/intervals.h @@ -189,21 +189,106 @@ public: inline iterator begin() { return Wrap(StartPoints.begin()); } inline const_iterator begin() const { return Wrap(StartPoints.begin()); } inline const_iterator end() const { return Wrap(StartPoints.end()); } - // finds the interval containing `x`. + // Find the interval containing `x`. iterator find(uint64_t x) { - auto it = StartPoints.upper_bound(x); // first starting after `x` + // Find the first interval starting after `x`; return the preceding interval. + auto it = StartPoints.upper_bound(x); RDCASSERT(it != StartPoints.begin()); - it--; // last *not* starting after `x` + it--; return Wrap(it); } - // finds the interval containing `x`. + // Find the interval containing `x`. const_iterator find(uint64_t x) const { - auto it = StartPoints.upper_bound(x); // first starting after `x` + // Find the first interval starting after `x`; return the preceding interval. + auto it = StartPoints.upper_bound(x); RDCASSERT(it != StartPoints.begin()); - it--; // last *not* starting after `x` + it--; return Wrap(it); } -}; \ No newline at end of file + + // Update the values of overlapping intervals to `comp(oldValue, val)` + // (where `oldValue` is the value of the interval prior to calling `update`). + // If start/finish do not lie on the boundaries between intervals, the intervals + // will be split as necessary. + template + void update(uint64_t start, uint64_t finish, T val, Compose comp) + { + auto i = find(start); + + // Split the interval so that `i.start == start` + i->split(start); + + // Loop over all the intervals in `a` that intersect the interval [start, finish) + for(; i->start() < finish; i++) + { + if(i->finish() > finish) + { + // In this case, interval `i` extends beyond `finish`; + // split `i` so that we only update the portion of `i` in [start, finish). + i->split(finish); + + // `split` leaves `i` pointing at the interval starting at `finish`; + // move back to the interval finishing at `finish`. + i--; + } + i->setValue(comp(i->value(), val)); + i->mergeLeft(); + } + + // `i` now points to the interval following the last interval whose value was + // modified; merge `i` with that last modified interval, if the values match. + i->mergeLeft(); + } + + // Update `this` by composing the value of each interval with the value of the + // corresponding interval in `other`. + // If the intervals in `this` and `other` do not line up, then the intervals in + // `this` will be split as necessary. + template + void merge(const Intervals &other, Compose comp) + { + auto j = other.begin(); + auto i = begin(); + + // Loop over the intervals in `this` (iterator `i`), while maintaining the + // interval `j` in `other` that contains `i`. + // The intervals in `this` are split as necessary, so that each `i` is + // contained in a single interval of `other`. + // Loop invariants: + // * i.start() >= j.start() + // * i.start() < j.end() + while(true) + { + RDCASSERT(i->start() >= j->start()); + RDCASSERT(i->start() < j->finish()); + if(i->finish() > j->finish()) + { + i->split(j->finish()); + i--; + } + + // Now i is contained in j, so we can update the value of all of i + i->setValue(comp(i->value(), j->value())); + + // The value of i and the interval left of i are now final; + // if these two intervals now have the same value, they can safely be + // merged into a single interval. + i->mergeLeft(); + + // Move to the next interval in `this`; also advance to the next interval + // in `other`, if necessary to maintain the invariant `i.start < j.end`. + i++; + if(i == end()) + { + j++; + RDCASSERT(j == other.end()); + return; + } + if(i->start() >= j->finish()) + j++; + } + } +}; diff --git a/renderdoc/core/intervals_tests.cpp b/renderdoc/core/intervals_tests.cpp new file mode 100644 index 000000000..7ab42e617 --- /dev/null +++ b/renderdoc/core/intervals_tests.cpp @@ -0,0 +1,393 @@ +/****************************************************************************** +* The MIT License (MIT) +* +* Copyright (c) 2019 Baldur Karlsson +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include "intervals.h" +#include "common/globalconfig.h" + +#if ENABLED(ENABLE_UNIT_TESTS) + +#include "3rdparty/catch/catch.hpp" + +#include +#include + +struct Interval +{ + uint64_t start; + uint64_t value; + uint64_t end; +}; + +void check_intervals(Intervals &value, const std::vector &expected) +{ + auto i = value.begin(); + auto j = expected.begin(); + for(; i != value.end() && j != expected.end(); i++, j++) + { + CHECK(i->start() == j->start); + CHECK(i->value() == j->value); + CHECK(i->finish() == j->end); + } + CHECK((i == value.end())); + CHECK((j == expected.end())); +} + +Intervals make_intervals(const std::vector &intervals) +{ + Intervals res; + for(auto i = intervals.begin(); i != intervals.end(); i++) + { + auto j = res.end(); + j--; + if(i->start > j->start()) + j->split(i->start); + if(i->end < j->finish()) + { + j->split(i->end); + j--; + } + j->setValue(i->value); + } + check_intervals(res, intervals); + return res; +} + +TEST_CASE("Test Intervals type", "[intervals]") +{ + SECTION("update tests") + { + SECTION("empty Intervals") + { + Intervals test; + check_intervals(test, {{0, 0, UINT64_MAX}}); + }; + + SECTION("update a sub-interval") + { + Intervals test; + test.update(5, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update a sub-interval matching on the left") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(5, 7, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 2, 7}, {7, 1, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update a sub-interval matching on the right") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(7, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update an interval that exactly matches an existing interval") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(5, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 2, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update a properly overlapping interval") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(7, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}}); + }; + + SECTION("update a super-interval") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(2, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 2}, {2, 1, 5}, {5, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}}); + }; + + SECTION("update a super-interval matching on the left") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(5, 15, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 2, 10}, {10, 1, 15}, {15, 0, UINT64_MAX}}); + }; + + SECTION("update a super-interval matching on the right") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(2, 10, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 2}, {2, 1, 5}, {5, 2, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update overlapping 2 intervals") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + test.update(7, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, + {5, 1, 7}, + {7, 2, 10}, + {10, 1, 20}, + {20, 11, 25}, + {25, 10, 30}, + {30, 0, UINT64_MAX}}); + }; + + SECTION("update overlapping 2 intervals matching on start of leftmost interval") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + test.update(5, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals( + test, + {{0, 0, 5}, {5, 2, 10}, {10, 1, 20}, {20, 11, 25}, {25, 10, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION("update overlapping 2 intervals matching on end of leftmost interval") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 5, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + test.update(10, 25, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals( + test, + {{0, 0, 5}, {5, 5, 10}, {10, 1, 20}, {20, 11, 25}, {25, 10, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION("update overlapping 2 intervals matching on start of rightmost interval") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + test.update(7, 20, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals( + test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION("update overlapping 2 intervals matching on end of rightmost interval") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, 20}, {20, 10, 30}, {30, 0, UINT64_MAX}}); + test.update(7, 30, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals( + test, {{0, 0, 5}, {5, 1, 7}, {7, 2, 10}, {10, 1, 20}, {20, 11, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION("update triggering merge on left") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(10, 20, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 20}, {20, 0, UINT64_MAX}}); + }; + + SECTION("update triggering merge on right") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(2, 5, 1, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 2}, {2, 1, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("overlapping update triggering merge on left") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(7, 20, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 20}, {20, 0, UINT64_MAX}}); + }; + + SECTION("overlapping update triggering merge on right") + { + Intervals test = make_intervals({{0, 0, 5}, {5, 1, 10}, {10, 0, UINT64_MAX}}); + test.update(2, 7, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; }); + check_intervals(test, {{0, 0, 2}, {2, 1, 10}, {10, 0, UINT64_MAX}}); + }; + + SECTION("update triggering multiple merges") + { + Intervals test = make_intervals( + {{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}}); + test.update(7, 25, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION( + "update triggering multiple merges, including merge with non-overlapping interval on left") + { + Intervals test = make_intervals( + {{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}}); + test.update(10, 25, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}}); + }; + + SECTION( + "update triggering multiple merges, including merge with non-overlapping interval on right") + { + Intervals test = make_intervals( + {{0, 0, 5}, {5, 1, 10}, {10, 0, 12}, {12, 5, 18}, {18, 0, 20}, {20, 1, 30}, {30, 0, UINT64_MAX}}); + test.update(7, 20, 1, [](uint64_t, uint64_t) -> uint64_t { return 1; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 30}, {30, 0, UINT64_MAX}}); + }; + }; + + SECTION("mergeIntervals tests") + { + SECTION("merge matching intervals") + { + Intervals test = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 10}, {10, 2, 20}, {20, 0, 30}, {30, 2, 40}, {40, 0, UINT64_MAX}}); + }; + + SECTION("merge shifted intervals") + { + Intervals test = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, + {5, 1, 10}, + {10, 2, 15}, + {15, 1, 20}, + {20, 0, 25}, + {25, 1, 30}, + {30, 2, 35}, + {35, 1, 40}, + {40, 0, UINT64_MAX}}); + }; + + SECTION("merge into empty intervals") + { + Intervals test = make_intervals({{0, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + }; + + SECTION("merge with empty intervals") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + Intervals other = make_intervals({{0, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + }; + + SECTION("merge into single interval") + { + Intervals test = make_intervals({{0, 0, 10}, {10, 1, 30}, {30, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, + {5, 1, 10}, + {10, 2, 15}, + {15, 1, 25}, + {25, 2, 30}, + {30, 1, 35}, + {35, 0, UINT64_MAX}}); + }; + + SECTION("merge with single interval") + { + Intervals test = + make_intervals({{0, 0, 5}, {5, 1, 15}, {15, 0, 25}, {25, 1, 35}, {35, 0, UINT64_MAX}}); + Intervals other = make_intervals({{0, 0, 10}, {10, 1, 30}, {30, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 5}, + {5, 1, 10}, + {10, 2, 15}, + {15, 1, 25}, + {25, 2, 30}, + {30, 1, 35}, + {35, 0, UINT64_MAX}}); + }; + + SECTION("merge disjoint before") + { + Intervals test = + make_intervals({{0, 0, 50}, {50, 1, 60}, {60, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 10}, + {10, 1, 20}, + {20, 0, 30}, + {30, 1, 40}, + {40, 0, 50}, + {50, 1, 60}, + {60, 0, 70}, + {70, 1, 80}, + {80, 0, UINT64_MAX}}); + }; + + SECTION("merge disjoint after") + { + Intervals test = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 50}, {50, 1, 60}, {60, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 10}, + {10, 1, 20}, + {20, 0, 30}, + {30, 1, 40}, + {40, 0, 50}, + {50, 1, 60}, + {60, 0, 70}, + {70, 1, 80}, + {80, 0, UINT64_MAX}}); + }; + + SECTION("merge disjoint interleaved") + { + Intervals test = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 50}, {50, 1, 60}, {60, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 30}, {30, 1, 40}, {40, 0, 70}, {70, 1, 80}, {80, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 10}, + {10, 1, 20}, + {20, 0, 30}, + {30, 1, 40}, + {40, 0, 50}, + {50, 1, 60}, + {60, 0, 70}, + {70, 1, 80}, + {80, 0, UINT64_MAX}}); + }; + + SECTION("merge disjoint interleaved touching") + { + Intervals test = + make_intervals({{0, 0, 10}, {10, 1, 20}, {20, 0, 30}, {30, 1, 40}, {40, 0, UINT64_MAX}}); + Intervals other = + make_intervals({{0, 0, 20}, {20, 1, 30}, {30, 0, 40}, {40, 1, 50}, {50, 0, UINT64_MAX}}); + test.merge(other, [](uint64_t x, uint64_t y) -> uint64_t { return x + y; }); + check_intervals(test, {{0, 0, 10}, {10, 1, 50}, {50, 0, UINT64_MAX}}); + }; + }; +}; + +#endif // ENABLED(ENABLE_UNIT_TESTS) \ No newline at end of file diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index b0aa228b4..0978527fe 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -410,6 +410,7 @@ + Create diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index 364cfce9d..e39260451 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -824,6 +824,9 @@ Common + + Core +