99 lines
4.1 KiB
C++
99 lines
4.1 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <cstdint>
|
|
|
|
namespace renderive {
|
|
|
|
enum class Render_Partition_Mode : std::uint8_t {
|
|
Automatic,
|
|
Fixed
|
|
};
|
|
|
|
namespace detail {
|
|
|
|
struct Render_Partition_Range {
|
|
std::size_t first{};
|
|
std::size_t last{};
|
|
};
|
|
|
|
inline Render_Partition_Range render_partition_range(std::size_t size,
|
|
int index,
|
|
int count) noexcept {
|
|
if (size == 0 || count <= 0 || index < 0 || index >= count)
|
|
return {};
|
|
return {size * static_cast<std::size_t>(index) / static_cast<std::size_t>(count),
|
|
size * static_cast<std::size_t>(index + 1) / static_cast<std::size_t>(count)};
|
|
}
|
|
|
|
class Adaptive_Render_Partitioner {
|
|
public:
|
|
[[nodiscard]] int graph_partition_count(Render_Partition_Mode mode,
|
|
int configured_count,
|
|
int worker_count,
|
|
std::size_t work_size,
|
|
std::size_t minimum_partition_size) noexcept {
|
|
if (mode == Render_Partition_Mode::Fixed)
|
|
return std::max(1, configured_count);
|
|
|
|
const std::size_t granularity = std::max<std::size_t>(1, minimum_partition_size);
|
|
const int work_limit = static_cast<int>(std::min<std::size_t>(
|
|
static_cast<std::size_t>(std::numeric_limits<int>::max()),
|
|
std::max<std::size_t>(1, work_size / granularity)));
|
|
const int automatic_limit = std::clamp(work_limit, 1, std::max(1, worker_count));
|
|
automatic_count_ = std::clamp(automatic_count_, 1, automatic_limit);
|
|
return automatic_count_;
|
|
}
|
|
|
|
int begin(int graph_partition_count, std::size_t work_size) noexcept {
|
|
const int useful = static_cast<int>(std::min<std::size_t>(
|
|
static_cast<std::size_t>(std::numeric_limits<int>::max()),
|
|
std::max<std::size_t>(1, work_size)));
|
|
started_at_ = Clock::now();
|
|
return std::clamp(graph_partition_count, 1, useful);
|
|
}
|
|
|
|
[[nodiscard]] bool finish(Render_Partition_Mode mode, int active_count,
|
|
std::uint64_t frame_interval_ns, int worker_count,
|
|
std::size_t work_size,
|
|
std::size_t minimum_partition_size) noexcept {
|
|
if (mode != Render_Partition_Mode::Automatic)
|
|
return false;
|
|
const std::size_t granularity = std::max<std::size_t>(1, minimum_partition_size);
|
|
const int work_limit = static_cast<int>(std::min<std::size_t>(
|
|
static_cast<std::size_t>(std::numeric_limits<int>::max()),
|
|
std::max<std::size_t>(1, work_size / granularity)));
|
|
const int automatic_limit = std::clamp(work_limit, 1, std::max(1, worker_count));
|
|
const auto elapsed = Clock::now() - started_at_;
|
|
const auto elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
|
|
const auto bottleneck_threshold = frame_interval_ns == 0
|
|
? std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
std::chrono::microseconds(1500))
|
|
: std::max(std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
std::chrono::microseconds(750)),
|
|
std::chrono::nanoseconds(frame_interval_ns / 4));
|
|
const int previous = automatic_count_;
|
|
const auto estimated_serial_cost = elapsed_ns * std::max(1, active_count);
|
|
if (elapsed_ns > bottleneck_threshold && active_count < automatic_limit) {
|
|
automatic_count_ = std::min(automatic_limit, active_count * 2);
|
|
} else if (active_count > 1 &&
|
|
estimated_serial_cost < bottleneck_threshold * 3 / 5) {
|
|
automatic_count_ = 1;
|
|
} else {
|
|
automatic_count_ = std::clamp(active_count, 1, automatic_limit);
|
|
}
|
|
return automatic_count_ != previous;
|
|
}
|
|
|
|
private:
|
|
using Clock = std::chrono::steady_clock;
|
|
Clock::time_point started_at_{};
|
|
int automatic_count_{1};
|
|
};
|
|
|
|
} // namespace detail
|
|
} // namespace renderive
|