#pragma once #include #include #include #include #include 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(index) / static_cast(count), size * static_cast(index + 1) / static_cast(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(1, minimum_partition_size); const int work_limit = static_cast(std::min( static_cast(std::numeric_limits::max()), std::max(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(std::min( static_cast(std::numeric_limits::max()), std::max(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(1, minimum_partition_size); const int work_limit = static_cast(std::min( static_cast(std::numeric_limits::max()), std::max(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(elapsed); const auto bottleneck_threshold = frame_interval_ns == 0 ? std::chrono::duration_cast( std::chrono::microseconds(1500)) : std::max(std::chrono::duration_cast( 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