battle_render_optimizer.h 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #pragma once
  2. #include <atomic>
  3. #include <cstdint>
  4. #include <mutex>
  5. namespace Render {
  6. struct BattleRenderConfig {
  7. int temporal_culling_threshold = 15;
  8. int animation_throttle_threshold = 30;
  9. float animation_throttle_distance = 40.0F;
  10. int animation_skip_frames = 2;
  11. bool enabled = true;
  12. };
  13. class BattleRenderOptimizer {
  14. public:
  15. static auto instance() noexcept -> BattleRenderOptimizer & {
  16. static BattleRenderOptimizer inst;
  17. return inst;
  18. }
  19. void begin_frame() noexcept {
  20. m_frame_counter.fetch_add(1, std::memory_order_relaxed);
  21. m_units_rendered_this_frame.store(0, std::memory_order_relaxed);
  22. m_units_skipped_temporal.store(0, std::memory_order_relaxed);
  23. m_animations_throttled.store(0, std::memory_order_relaxed);
  24. }
  25. void set_visible_unit_count(int count) noexcept {
  26. m_visible_unit_count.store(count, std::memory_order_relaxed);
  27. }
  28. void set_config(const BattleRenderConfig &config) noexcept {
  29. std::lock_guard<std::mutex> lock(m_config_mutex);
  30. m_config = config;
  31. }
  32. [[nodiscard]] auto config() const noexcept -> BattleRenderConfig {
  33. std::lock_guard<std::mutex> lock(m_config_mutex);
  34. return m_config;
  35. }
  36. [[nodiscard]] auto is_battle_mode() const noexcept -> bool {
  37. std::lock_guard<std::mutex> lock(m_config_mutex);
  38. return m_config.enabled &&
  39. m_visible_unit_count.load(std::memory_order_relaxed) >=
  40. m_config.temporal_culling_threshold;
  41. }
  42. [[nodiscard]] auto
  43. should_render_unit(uint32_t entity_id, bool is_moving, bool is_selected,
  44. bool is_hovered) const noexcept -> bool {
  45. if (!is_battle_mode()) {
  46. return true;
  47. }
  48. if (is_selected || is_hovered || is_moving) {
  49. return true;
  50. }
  51. uint32_t frame = m_frame_counter.load(std::memory_order_relaxed);
  52. bool render = ((entity_id + frame) % 2) == 0;
  53. if (!render) {
  54. m_units_skipped_temporal.fetch_add(1, std::memory_order_relaxed);
  55. } else {
  56. m_units_rendered_this_frame.fetch_add(1, std::memory_order_relaxed);
  57. }
  58. return render;
  59. }
  60. [[nodiscard]] auto
  61. should_update_animation(uint32_t entity_id, float distance_sq,
  62. bool is_selected) const noexcept -> bool {
  63. BattleRenderConfig cfg;
  64. {
  65. std::lock_guard<std::mutex> lock(m_config_mutex);
  66. cfg = m_config;
  67. }
  68. if (!cfg.enabled) {
  69. return true;
  70. }
  71. int visible_count = m_visible_unit_count.load(std::memory_order_relaxed);
  72. if (visible_count < cfg.animation_throttle_threshold) {
  73. return true;
  74. }
  75. if (is_selected) {
  76. return true;
  77. }
  78. float const throttle_distance_sq =
  79. cfg.animation_throttle_distance * cfg.animation_throttle_distance;
  80. if (distance_sq < throttle_distance_sq) {
  81. return true;
  82. }
  83. uint32_t frame = m_frame_counter.load(std::memory_order_relaxed);
  84. bool update = ((entity_id + frame) %
  85. static_cast<uint32_t>(cfg.animation_skip_frames + 1)) == 0;
  86. if (!update) {
  87. m_animations_throttled.fetch_add(1, std::memory_order_relaxed);
  88. }
  89. return update;
  90. }
  91. [[nodiscard]] auto get_batching_boost() const noexcept -> float {
  92. BattleRenderConfig cfg;
  93. {
  94. std::lock_guard<std::mutex> lock(m_config_mutex);
  95. cfg = m_config;
  96. }
  97. if (!cfg.enabled) {
  98. return 1.0F;
  99. }
  100. int visible_count = m_visible_unit_count.load(std::memory_order_relaxed);
  101. if (visible_count < cfg.temporal_culling_threshold) {
  102. return 1.0F;
  103. }
  104. float excess_ratio =
  105. static_cast<float>(visible_count - cfg.temporal_culling_threshold) /
  106. static_cast<float>(cfg.temporal_culling_threshold);
  107. return 1.0F + excess_ratio * 0.5F;
  108. }
  109. [[nodiscard]] auto frame_counter() const noexcept -> uint32_t {
  110. return m_frame_counter.load(std::memory_order_relaxed);
  111. }
  112. [[nodiscard]] auto units_rendered_this_frame() const noexcept -> int {
  113. return m_units_rendered_this_frame.load(std::memory_order_relaxed);
  114. }
  115. [[nodiscard]] auto units_skipped_temporal() const noexcept -> int {
  116. return m_units_skipped_temporal.load(std::memory_order_relaxed);
  117. }
  118. [[nodiscard]] auto animations_throttled() const noexcept -> int {
  119. return m_animations_throttled.load(std::memory_order_relaxed);
  120. }
  121. [[nodiscard]] auto visible_unit_count() const noexcept -> int {
  122. return m_visible_unit_count.load(std::memory_order_relaxed);
  123. }
  124. private:
  125. BattleRenderOptimizer() = default;
  126. mutable std::mutex m_config_mutex;
  127. BattleRenderConfig m_config;
  128. std::atomic<uint32_t> m_frame_counter{0};
  129. std::atomic<int> m_visible_unit_count{0};
  130. mutable std::atomic<int> m_units_rendered_this_frame{0};
  131. mutable std::atomic<int> m_units_skipped_temporal{0};
  132. mutable std::atomic<int> m_animations_throttled{0};
  133. };
  134. } // namespace Render