- remove patches that are in upstream - remove vdpau as upstream removed it - update version of dependencies - update rust libwrap filename - Update libclc to 22.1 has the 21.1.8 doesn't build on centos stream 9 - Fix python issues with 3.9 (Mesa requires 3.10) - Revert Freedreno tu_autotune to previous C implementation, as C++ implementation - Remove some kmsro driver on x86_64 Resolves: RHEL-135263 Signed-off-by: Jocelyn Falempe <jfalempe@redhat.com>
2457 lines
94 KiB
Diff
2457 lines
94 KiB
Diff
From 7fd383b6eff6fc29d8acee2fd1f1940c925d3568 Mon Sep 17 00:00:00 2001
|
|
From: Jocelyn Falempe <jfalempe@redhat.com>
|
|
Date: Fri, 26 Jun 2026 11:08:27 +0200
|
|
Subject: [PATCH 18/19] Revert "tu: Rewrite autotune in C++"
|
|
|
|
This reverts commit 40ffc052afff7a40da99b398c09594c3ff2d40ed.
|
|
---
|
|
docs/drivers/freedreno.rst | 35 -
|
|
src/freedreno/vulkan/tu_autotune.cc | 1448 ++++++++++---------------
|
|
src/freedreno/vulkan/tu_autotune.h | 325 ++----
|
|
src/freedreno/vulkan/tu_cmd_buffer.cc | 55 +-
|
|
src/freedreno/vulkan/tu_cmd_buffer.h | 3 +-
|
|
src/freedreno/vulkan/tu_device.cc | 13 +-
|
|
src/freedreno/vulkan/tu_device.h | 16 +-
|
|
src/freedreno/vulkan/tu_pass.cc | 23 +
|
|
src/freedreno/vulkan/tu_queue.cc | 6 +-
|
|
9 files changed, 766 insertions(+), 1158 deletions(-)
|
|
|
|
diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst
|
|
index ee733950fe4..f2a47d99e9c 100644
|
|
--- a/docs/drivers/freedreno.rst
|
|
+++ b/docs/drivers/freedreno.rst
|
|
@@ -670,38 +670,3 @@ are supported at the moment: ``nir``, ``nobin``, ``sysmem``, ``gmem``, ``forcebi
|
|
Some of these options will behave differently when toggled at runtime, for example:
|
|
``nolrz`` will still result in LRZ allocation which would not happen if the option
|
|
was set in the environment variable.
|
|
-
|
|
-Autotune
|
|
-^^^^^^^^
|
|
-
|
|
-Turnip supports dynamically selecting between SYSMEM and GMEM rendering with the
|
|
-autotune system, the behavior of which can be controlled with the following
|
|
-environment variables:
|
|
-
|
|
-.. envvar:: TU_AUTOTUNE_ALGO
|
|
-
|
|
- Selects the algorithm used for autotuning. Supported values are:
|
|
-
|
|
- ``bandwidth``
|
|
- Estimates the bandwidth usage of rendering in SYSMEM and GMEM modes, and chooses
|
|
- the one with lower estimated bandwidth. This is the default algorithm.
|
|
-
|
|
-.. envvar:: TU_AUTOTUNE_FLAGS
|
|
-
|
|
- Modifies the behavior of the selected algorithm. Supported flags are:
|
|
-
|
|
- ``big_gmem``
|
|
- Always chooses GMEM rendering if the amount of draw calls in the render pass
|
|
- is greater than a certain threshold. Larger RPs generally benefit more from
|
|
- GMEM rendering due to less overhead from tiling. This tends to lead to worse
|
|
- performance in most cases, so it's only useful for testing.
|
|
-
|
|
- ``small_sysmem``
|
|
- Always chooses SYSMEM rendering if the amount of draw calls in the render pass
|
|
- is lower than a certain threshold. The benefits of GMEM rendering are less
|
|
- pronounced in these smaller RPs and SYSMEM rendering tends to win more often.
|
|
-
|
|
- Multiple flags can be combined by separating them with commas, e.g.
|
|
- ``TU_AUTOTUNE_FLAGS=big_gmem,small_sysmem``.
|
|
-
|
|
- If no flags are specified, the default behavior is used.
|
|
\ No newline at end of file
|
|
diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc
|
|
index 971cc1a9503..e6b0e77af91 100644
|
|
--- a/src/freedreno/vulkan/tu_autotune.cc
|
|
+++ b/src/freedreno/vulkan/tu_autotune.cc
|
|
@@ -5,308 +5,113 @@
|
|
|
|
#include "tu_autotune.h"
|
|
|
|
-#include <algorithm>
|
|
-#include <array>
|
|
-#include <atomic>
|
|
-#include <cmath>
|
|
-#include <optional>
|
|
-#include <string>
|
|
-#include <string_view>
|
|
-
|
|
-#include "util/rand_xor.h"
|
|
-
|
|
-#define XXH_INLINE_ALL
|
|
-#include "util/xxhash.h"
|
|
-
|
|
#include "tu_cmd_buffer.h"
|
|
#include "tu_cs.h"
|
|
#include "tu_device.h"
|
|
#include "tu_image.h"
|
|
#include "tu_pass.h"
|
|
|
|
-/** Compile-time debug options **/
|
|
-
|
|
-#define TU_AUTOTUNE_DEBUG_LOG_BASE 0
|
|
-#define TU_AUTOTUNE_DEBUG_LOG_BANDWIDTH 0
|
|
-
|
|
-#if TU_AUTOTUNE_DEBUG_LOG_BASE
|
|
-#define at_log_base(fmt, ...) mesa_logi("autotune: " fmt, ##__VA_ARGS__)
|
|
-#define at_log_base_h(fmt, hash, ...) mesa_logi("autotune %016" PRIx64 ": " fmt, hash, ##__VA_ARGS__)
|
|
-#else
|
|
-#define at_log_base(fmt, ...)
|
|
-#define at_log_base_h(fmt, hash, ...)
|
|
-#endif
|
|
-
|
|
-#if TU_AUTOTUNE_DEBUG_LOG_BANDWIDTH
|
|
-#define at_log_bandwidth_h(fmt, hash, ...) mesa_logi("autotune-bw %016" PRIx64 ": " fmt, hash, ##__VA_ARGS__)
|
|
-#else
|
|
-#define at_log_bandwidth_h(fmt, hash, ...)
|
|
-#endif
|
|
-
|
|
-/* Process any pending entries on autotuner finish, could be used to gather data from traces. */
|
|
-#define TU_AUTOTUNE_FLUSH_AT_FINISH 0
|
|
-
|
|
-/** Global constants and helpers **/
|
|
-
|
|
-/* GPU always-on timer constants */
|
|
-constexpr uint64_t ALWAYS_ON_FREQUENCY_HZ = 19'200'000;
|
|
-constexpr double GPU_TICKS_PER_US = ALWAYS_ON_FREQUENCY_HZ / 1'000'000.0;
|
|
-
|
|
-constexpr uint64_t
|
|
-ticks_to_us(uint64_t ticks)
|
|
-{
|
|
- return ticks / GPU_TICKS_PER_US;
|
|
-}
|
|
-
|
|
-constexpr bool
|
|
-fence_before(uint32_t a, uint32_t b)
|
|
-{
|
|
- /* Essentially a < b, but handles wrapped values. */
|
|
- return (int32_t) (a - b) < 0;
|
|
-}
|
|
-
|
|
-constexpr const char *
|
|
-render_mode_str(tu_autotune::render_mode mode)
|
|
-{
|
|
- switch (mode) {
|
|
- case tu_autotune::render_mode::SYSMEM:
|
|
- return "SYSMEM";
|
|
- case tu_autotune::render_mode::GMEM:
|
|
- return "GMEM";
|
|
- default:
|
|
- return "UNKNOWN";
|
|
- }
|
|
-}
|
|
-
|
|
-/** Configuration **/
|
|
-
|
|
-enum class tu_autotune::algorithm : uint8_t {
|
|
- BANDWIDTH = 0, /* Uses estimated BW for determining rendering mode. */
|
|
-
|
|
- DEFAULT = BANDWIDTH, /* Default algorithm, used if no other is specified. */
|
|
-};
|
|
-
|
|
-/* Modifier flags, these modify the behavior of the autotuner in a user-defined way. */
|
|
-enum class tu_autotune::mod_flag : uint8_t {
|
|
- BIG_GMEM = BIT(1), /* All RPs with >= 10 draws use GMEM. */
|
|
- SMALL_SYSMEM = BIT(2), /* All RPs with <= 5 draws use SYSMEM. */
|
|
-};
|
|
-
|
|
-/* Metric flags, for internal tracking of enabled metrics. */
|
|
-enum class tu_autotune::metric_flag : uint8_t {
|
|
- SAMPLES = BIT(1), /* Enable tracking samples passed metric. */
|
|
-};
|
|
-
|
|
-struct PACKED tu_autotune::config_t {
|
|
- private:
|
|
- algorithm algo = algorithm::DEFAULT;
|
|
- uint8_t mod_flags = 0; /* See mod_flag enum. */
|
|
- uint8_t metric_flags = 0; /* See metric_flag enum. */
|
|
-
|
|
- constexpr void update_metric_flags()
|
|
- {
|
|
- /* Note: Always keep in sync with rp_history to prevent UB. */
|
|
- if (algo == algorithm::BANDWIDTH) {
|
|
- metric_flags |= (uint8_t) metric_flag::SAMPLES;
|
|
- }
|
|
- }
|
|
-
|
|
- public:
|
|
- constexpr config_t() = default;
|
|
-
|
|
- constexpr config_t(algorithm algo, uint8_t mod_flags): algo(algo), mod_flags(mod_flags)
|
|
- {
|
|
- update_metric_flags();
|
|
- }
|
|
-
|
|
- constexpr bool is_enabled(algorithm a) const
|
|
- {
|
|
- return algo == a;
|
|
- }
|
|
-
|
|
- constexpr bool test(mod_flag f) const
|
|
- {
|
|
- return mod_flags & (uint32_t) f;
|
|
- }
|
|
-
|
|
- constexpr bool test(metric_flag f) const
|
|
- {
|
|
- return metric_flags & (uint32_t) f;
|
|
- }
|
|
-
|
|
- constexpr bool set_algo(algorithm a)
|
|
- {
|
|
- if (algo == a)
|
|
- return false;
|
|
-
|
|
- algo = a;
|
|
- update_metric_flags();
|
|
- return true;
|
|
- }
|
|
-
|
|
- constexpr bool disable(mod_flag f)
|
|
- {
|
|
- if (!(mod_flags & (uint8_t) f))
|
|
- return false;
|
|
-
|
|
- mod_flags &= ~(uint8_t) f;
|
|
- update_metric_flags();
|
|
- return true;
|
|
- }
|
|
+#define XXH_INLINE_ALL
|
|
+#include "util/xxhash.h"
|
|
|
|
- constexpr bool enable(mod_flag f)
|
|
- {
|
|
- if (mod_flags & (uint8_t) f)
|
|
- return false;
|
|
+/* How does it work?
|
|
+ *
|
|
+ * - For each renderpass we calculate the number of samples passed
|
|
+ * by storing the number before and after in GPU memory.
|
|
+ * - To store the values each command buffer holds GPU memory which
|
|
+ * expands with more renderpasses being written.
|
|
+ * - For each renderpass we create tu_renderpass_result entry which
|
|
+ * points to the results in GPU memory.
|
|
+ * - Later on tu_renderpass_result would be added to the
|
|
+ * tu_renderpass_history entry which aggregate results for a
|
|
+ * given renderpass.
|
|
+ * - On submission:
|
|
+ * - Process results which fence was signalled.
|
|
+ * - Free per-submission data which we now don't need.
|
|
+ *
|
|
+ * - Create a command stream to write a fence value. This way we would
|
|
+ * know when we could safely read the results.
|
|
+ * - We cannot rely on the command buffer's lifetime when referencing
|
|
+ * its resources since the buffer could be destroyed before we process
|
|
+ * the results.
|
|
+ * - For each command buffer:
|
|
+ * - Reference its GPU memory.
|
|
+ * - Move if ONE_TIME_SUBMIT or copy all tu_renderpass_result to the queue.
|
|
+ *
|
|
+ * Since the command buffers could be recorded on different threads
|
|
+ * we have to maintaining some amount of locking history table,
|
|
+ * however we change the table only in a single thread at the submission
|
|
+ * time, so in most cases there will be no locking.
|
|
+ */
|
|
|
|
- mod_flags |= (uint8_t) f;
|
|
- update_metric_flags();
|
|
- return true;
|
|
- }
|
|
+void
|
|
+tu_autotune_free_results_locked(struct tu_device *dev, struct list_head *results);
|
|
|
|
- std::string to_string() const
|
|
- {
|
|
-#define ALGO_STR(algo_name) \
|
|
- if (algo == algorithm::algo_name) \
|
|
- str += #algo_name;
|
|
-#define MODF_STR(flag) \
|
|
- if (mod_flags & (uint8_t) mod_flag::flag) { \
|
|
- str += #flag " "; \
|
|
- }
|
|
-#define METRICF_STR(flag) \
|
|
- if (metric_flags & (uint8_t) metric_flag::flag) { \
|
|
- str += #flag " "; \
|
|
- }
|
|
+#define TU_AUTOTUNE_DEBUG_LOG 0
|
|
+/* Dump history entries on autotuner finish,
|
|
+ * could be used to gather data from traces.
|
|
+ */
|
|
+#define TU_AUTOTUNE_LOG_AT_FINISH 0
|
|
|
|
- std::string str = "Algorithm: ";
|
|
+/* How many last renderpass stats are taken into account. */
|
|
+#define MAX_HISTORY_RESULTS 5
|
|
+/* For how many submissions we store renderpass stats. */
|
|
+#define MAX_HISTORY_LIFETIME 128
|
|
|
|
- ALGO_STR(BANDWIDTH);
|
|
|
|
- str += ", Mod Flags: 0x" + std::to_string(mod_flags) + " (";
|
|
- MODF_STR(BIG_GMEM);
|
|
- MODF_STR(SMALL_SYSMEM);
|
|
- str += ")";
|
|
+/**
|
|
+ * Tracks results for a given renderpass key
|
|
+ */
|
|
+struct tu_renderpass_history {
|
|
+ uint64_t key;
|
|
|
|
- str += ", Metric Flags: 0x" + std::to_string(metric_flags) + " (";
|
|
- METRICF_STR(SAMPLES);
|
|
- str += ")";
|
|
+ /* We would delete old history entries */
|
|
+ uint32_t last_fence;
|
|
|
|
- return str;
|
|
+ /**
|
|
+ * List of recent fd_renderpass_result's
|
|
+ */
|
|
+ struct list_head results;
|
|
+ uint32_t num_results;
|
|
|
|
-#undef ALGO_STR
|
|
-#undef MODF_STR
|
|
-#undef METRICF_STR
|
|
- }
|
|
+ uint32_t avg_samples;
|
|
};
|
|
|
|
-union PACKED tu_autotune::packed_config_t {
|
|
- config_t config;
|
|
- uint32_t bits = 0;
|
|
- static_assert(sizeof(bits) >= sizeof(config));
|
|
- static_assert(std::is_trivially_copyable<config_t>::value,
|
|
- "config_t must be trivially copyable to be automatically packed");
|
|
-
|
|
- constexpr packed_config_t(config_t p_config): bits(0)
|
|
- {
|
|
- config = p_config; /* Set after bits(0) to avoid UB in sizeof(bits) > sizeof(config) case.*/
|
|
- }
|
|
+/* Holds per-submission cs which writes the fence. */
|
|
+struct tu_submission_data {
|
|
+ struct list_head node;
|
|
+ uint32_t fence;
|
|
|
|
- constexpr packed_config_t(uint32_t bits): bits(bits)
|
|
- {
|
|
- }
|
|
+ struct tu_cs fence_cs;
|
|
};
|
|
|
|
-tu_autotune::atomic_config_t::atomic_config_t(config_t initial): config_bits(packed_config_t { initial }.bits)
|
|
-{
|
|
-}
|
|
-
|
|
-tu_autotune::config_t
|
|
-tu_autotune::atomic_config_t::load() const
|
|
-{
|
|
- return config_t(packed_config_t { config_bits.load(std::memory_order_relaxed) }.config);
|
|
-}
|
|
-
|
|
-bool
|
|
-tu_autotune::atomic_config_t::compare_and_store(config_t expected, config_t updated)
|
|
-{
|
|
- uint32_t expected_bits = packed_config_t { expected }.bits;
|
|
- return config_bits.compare_exchange_strong(expected_bits, packed_config_t { updated }.bits,
|
|
- std::memory_order_acquire, std::memory_order_relaxed);
|
|
-}
|
|
-
|
|
-tu_autotune::config_t
|
|
-tu_autotune::get_env_config()
|
|
-{
|
|
- static std::once_flag once;
|
|
- static config_t at_config;
|
|
- std::call_once(once, [&] {
|
|
- const char *algo_env_str = os_get_option("TU_AUTOTUNE_ALGO");
|
|
- algorithm algo = algorithm::DEFAULT;
|
|
-
|
|
- if (algo_env_str) {
|
|
- std::string_view algo_strv(algo_env_str);
|
|
- if (algo_strv == "bandwidth") {
|
|
- algo = algorithm::BANDWIDTH;
|
|
- }
|
|
-
|
|
- if (TU_DEBUG(STARTUP))
|
|
- mesa_logi("TU_AUTOTUNE_ALGO=%u (%s)", (uint8_t) algo, algo_env_str);
|
|
- }
|
|
-
|
|
- /* Parse the flags from the environment variable. */
|
|
- const char *flags_env_str = os_get_option("TU_AUTOTUNE_FLAGS");
|
|
- uint32_t mod_flags = 0;
|
|
- if (flags_env_str) {
|
|
- static const struct debug_control tu_at_flags_control[] = {
|
|
- { "big_gmem", (uint32_t) mod_flag::BIG_GMEM },
|
|
- { "small_sysmem", (uint32_t) mod_flag::SMALL_SYSMEM },
|
|
- { NULL, 0 }
|
|
- };
|
|
-
|
|
- mod_flags = parse_debug_string(flags_env_str, tu_at_flags_control);
|
|
- if (TU_DEBUG(STARTUP))
|
|
- mesa_logi("TU_AUTOTUNE_FLAGS=0x%x (%s)", mod_flags, flags_env_str);
|
|
- }
|
|
-
|
|
- assert((uint8_t) mod_flags == mod_flags);
|
|
- at_config = config_t(algo, (uint8_t) mod_flags);
|
|
- });
|
|
-
|
|
- if (TU_DEBUG(STARTUP))
|
|
- mesa_logi("TU_AUTOTUNE: %s", at_config.to_string().c_str());
|
|
-
|
|
- return at_config;
|
|
-}
|
|
-
|
|
-/** Global Fence and Internal CS Management **/
|
|
-
|
|
-tu_autotune::submission_entry::submission_entry(tu_device *device): fence(0)
|
|
-{
|
|
- tu_cs_init(&fence_cs, device, TU_CS_MODE_GROW, 5, "autotune fence cs");
|
|
-}
|
|
-
|
|
-tu_autotune::submission_entry::~submission_entry()
|
|
+static bool
|
|
+fence_before(uint32_t a, uint32_t b)
|
|
{
|
|
- assert(!is_active());
|
|
- tu_cs_finish(&fence_cs);
|
|
+ /* essentially a < b, but handle wrapped values */
|
|
+ return (int32_t)(a - b) < 0;
|
|
}
|
|
|
|
-bool
|
|
-tu_autotune::submission_entry::is_active() const
|
|
+static uint32_t
|
|
+get_autotune_fence(struct tu_autotune *at)
|
|
{
|
|
- return fence_cs.device->global_bo_map->autotune_fence < fence;
|
|
+ return at->device->global_bo_map->autotune_fence;
|
|
}
|
|
|
|
template <chip CHIP>
|
|
static void
|
|
-write_fence_cs(struct tu_device *dev, struct tu_cs *cs, uint32_t fence)
|
|
+create_submission_fence(struct tu_device *dev,
|
|
+ struct tu_cs *cs,
|
|
+ uint32_t fence)
|
|
{
|
|
uint64_t dst_iova = dev->global_bo->iova + gb_offset(autotune_fence);
|
|
if (CHIP >= A7XX) {
|
|
tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 4);
|
|
- tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = CACHE_FLUSH_TS, .write_src = EV_WRITE_USER_32B, .write_dst = EV_DST_RAM,
|
|
- .write_enabled = true)
|
|
- .value);
|
|
+ tu_cs_emit(cs,
|
|
+ CP_EVENT_WRITE7_0(.event = CACHE_FLUSH_TS,
|
|
+ .write_src = EV_WRITE_USER_32B,
|
|
+ .write_dst = EV_DST_RAM,
|
|
+ .write_enabled = true).value);
|
|
} else {
|
|
tu_cs_emit_pkt7(cs, CP_EVENT_WRITE, 4);
|
|
tu_cs_emit(cs, CP_EVENT_WRITE_0_EVENT(CACHE_FLUSH_TS));
|
|
@@ -316,747 +121,636 @@ write_fence_cs(struct tu_device *dev, struct tu_cs *cs, uint32_t fence)
|
|
tu_cs_emit(cs, fence);
|
|
}
|
|
|
|
-struct tu_cs *
|
|
-tu_autotune::submission_entry::try_get_cs(uint32_t new_fence)
|
|
+static struct tu_submission_data *
|
|
+create_submission_data(struct tu_device *dev, struct tu_autotune *at,
|
|
+ uint32_t fence)
|
|
{
|
|
- if (is_active()) {
|
|
- /* If the CS is already active, we cannot write to it. */
|
|
- return nullptr;
|
|
+ struct tu_submission_data *submission_data = NULL;
|
|
+ if (!list_is_empty(&at->submission_data_pool)) {
|
|
+ submission_data = list_first_entry(&at->submission_data_pool,
|
|
+ struct tu_submission_data, node);
|
|
+ list_del(&submission_data->node);
|
|
+ } else {
|
|
+ submission_data = (struct tu_submission_data *) calloc(
|
|
+ 1, sizeof(struct tu_submission_data));
|
|
+ tu_cs_init(&submission_data->fence_cs, dev, TU_CS_MODE_GROW, 5, "autotune fence cs");
|
|
}
|
|
+ submission_data->fence = fence;
|
|
+
|
|
+ struct tu_cs* fence_cs = &submission_data->fence_cs;
|
|
+ tu_cs_begin(fence_cs);
|
|
+ TU_CALLX(dev, create_submission_fence)(dev, fence_cs, fence);
|
|
+ tu_cs_end(fence_cs);
|
|
|
|
- struct tu_device *device = fence_cs.device;
|
|
- tu_cs_reset(&fence_cs);
|
|
- tu_cs_begin(&fence_cs);
|
|
- TU_CALLX(device, write_fence_cs)(device, &fence_cs, new_fence);
|
|
- tu_cs_end(&fence_cs);
|
|
- assert(fence_cs.entry_count == 1); /* We expect the initial allocation to be large enough. */
|
|
- fence = new_fence;
|
|
+ list_addtail(&submission_data->node, &at->pending_submission_data);
|
|
|
|
- return &fence_cs;
|
|
+ return submission_data;
|
|
}
|
|
|
|
-struct tu_cs *
|
|
-tu_autotune::get_cs_for_fence(uint32_t fence)
|
|
+static void
|
|
+finish_submission_data(struct tu_autotune *at,
|
|
+ struct tu_submission_data *data)
|
|
{
|
|
- for (submission_entry &entry : submission_entries) {
|
|
- struct tu_cs *cs = entry.try_get_cs(fence);
|
|
- if (cs)
|
|
- return cs;
|
|
- }
|
|
-
|
|
- /* If we reach here, we have to allocate a new entry. */
|
|
- submission_entry &entry = submission_entries.emplace_back(device);
|
|
- struct tu_cs *cs = entry.try_get_cs(fence);
|
|
- assert(cs); /* We just allocated it, so it should be available. */
|
|
- return cs;
|
|
+ list_del(&data->node);
|
|
+ list_addtail(&data->node, &at->submission_data_pool);
|
|
+ tu_cs_reset(&data->fence_cs);
|
|
}
|
|
|
|
-/** RP Entry Management **/
|
|
-
|
|
-/* The part of the per-RP entry which is written by the GPU. */
|
|
-struct PACKED tu_autotune::rp_gpu_data {
|
|
- /* HW requires the sample start/stop locations to be 128b aligned. */
|
|
- alignas(16) uint64_t samples_start;
|
|
- alignas(16) uint64_t samples_end;
|
|
- uint64_t ts_start;
|
|
- uint64_t ts_end;
|
|
-};
|
|
-
|
|
-/* A small wrapper around rp_history to provide ref-counting and usage timestamps. */
|
|
-struct tu_autotune::rp_history_handle {
|
|
- rp_history *history;
|
|
+static void
|
|
+free_submission_data(struct tu_submission_data *data)
|
|
+{
|
|
+ list_del(&data->node);
|
|
+ tu_cs_finish(&data->fence_cs);
|
|
|
|
- /* Note: Must be called with rp_mutex held. */
|
|
- rp_history_handle(rp_history &history);
|
|
+ free(data);
|
|
+}
|
|
|
|
- constexpr rp_history_handle(std::nullptr_t): history(nullptr)
|
|
- {
|
|
- }
|
|
+static uint64_t
|
|
+hash_renderpass_instance(const struct tu_render_pass *pass,
|
|
+ const struct tu_framebuffer *framebuffer,
|
|
+ const struct tu_cmd_buffer *cmd) {
|
|
+ uint32_t data[3 + pass->attachment_count * 5];
|
|
+ uint32_t* ptr = data;
|
|
|
|
- rp_history_handle(const rp_history_handle &) = delete;
|
|
- rp_history_handle &operator=(const rp_history_handle &) = delete;
|
|
+ *ptr++ = framebuffer->width;
|
|
+ *ptr++ = framebuffer->height;
|
|
+ *ptr++ = framebuffer->layers;
|
|
|
|
- constexpr rp_history_handle(rp_history_handle &&other): history(other.history)
|
|
- {
|
|
- other.history = nullptr;
|
|
+ for (unsigned i = 0; i < pass->attachment_count; i++) {
|
|
+ *ptr++ = cmd->state.attachments[i]->view.width;
|
|
+ *ptr++ = cmd->state.attachments[i]->view.height;
|
|
+ *ptr++ = cmd->state.attachments[i]->image->vk.format;
|
|
+ *ptr++ = cmd->state.attachments[i]->image->vk.array_layers;
|
|
+ *ptr++ = cmd->state.attachments[i]->image->vk.mip_levels;
|
|
}
|
|
|
|
- constexpr rp_history_handle &operator=(rp_history_handle &&other)
|
|
- {
|
|
- if (this != &other) {
|
|
- history = other.history;
|
|
- other.history = nullptr;
|
|
- }
|
|
- return *this;
|
|
- }
|
|
+ return XXH64(data, sizeof(data), pass->autotune_hash);
|
|
+}
|
|
|
|
- constexpr operator bool() const
|
|
- {
|
|
- return history != nullptr;
|
|
- }
|
|
+static void
|
|
+free_result(struct tu_device *dev, struct tu_renderpass_result *result)
|
|
+{
|
|
+ tu_suballoc_bo_free(&dev->autotune_suballoc, &result->bo);
|
|
+ list_del(&result->node);
|
|
+ free(result);
|
|
+}
|
|
|
|
- constexpr rp_history &operator*() const
|
|
- {
|
|
- assert(history);
|
|
- return *history;
|
|
- }
|
|
+static void
|
|
+free_history(struct tu_device *dev, struct tu_renderpass_history *history)
|
|
+{
|
|
+ tu_autotune_free_results_locked(dev, &history->results);
|
|
+ free(history);
|
|
+}
|
|
|
|
- constexpr operator rp_history *() const
|
|
- {
|
|
- return history;
|
|
- }
|
|
+static bool
|
|
+get_history(struct tu_autotune *at, uint64_t rp_key, uint32_t *avg_samples)
|
|
+{
|
|
+ bool has_history = false;
|
|
|
|
- constexpr rp_history *operator->() const
|
|
- {
|
|
- assert(history);
|
|
- return history;
|
|
+ /* If the lock contantion would be found in the wild -
|
|
+ * we could use try_lock here.
|
|
+ */
|
|
+ u_rwlock_rdlock(&at->ht_lock);
|
|
+ struct hash_entry *entry =
|
|
+ _mesa_hash_table_search(at->ht, &rp_key);
|
|
+ if (entry) {
|
|
+ struct tu_renderpass_history *history =
|
|
+ (struct tu_renderpass_history *) entry->data;
|
|
+ if (history->num_results > 0) {
|
|
+ *avg_samples = p_atomic_read(&history->avg_samples);
|
|
+ has_history = true;
|
|
+ }
|
|
}
|
|
+ u_rwlock_rdunlock(&at->ht_lock);
|
|
|
|
- ~rp_history_handle();
|
|
-};
|
|
-
|
|
-/* An "entry" of renderpass autotune results, which is used to store the results of a renderpass autotune run for a
|
|
- * given command buffer. */
|
|
-struct tu_autotune::rp_entry {
|
|
- private:
|
|
- struct tu_device *device;
|
|
-
|
|
- struct tu_suballoc_bo bo;
|
|
- uint8_t *map; /* A direct pointer to the BO's CPU mapping. */
|
|
-
|
|
- static_assert(alignof(rp_gpu_data) == 16);
|
|
- static_assert(offsetof(rp_gpu_data, samples_start) == 0);
|
|
- static_assert(offsetof(rp_gpu_data, samples_end) == 16);
|
|
-
|
|
- public:
|
|
- rp_history_handle history;
|
|
- config_t config; /* Configuration at the time of entry creation. */
|
|
- bool sysmem;
|
|
- uint32_t draw_count;
|
|
+ return has_history;
|
|
+}
|
|
|
|
- rp_entry(struct tu_device *device, rp_history_handle &&history, config_t config, uint32_t draw_count)
|
|
- : device(device), map(nullptr), history(std::move(history)), config(config), draw_count(draw_count)
|
|
- {
|
|
- }
|
|
+static struct tu_renderpass_result *
|
|
+create_history_result(struct tu_autotune *at, uint64_t rp_key)
|
|
+{
|
|
+ struct tu_renderpass_result *result =
|
|
+ (struct tu_renderpass_result *) calloc(1, sizeof(*result));
|
|
+ result->rp_key = rp_key;
|
|
|
|
- ~rp_entry()
|
|
- {
|
|
- if (map) {
|
|
- std::scoped_lock lock(device->autotune->suballoc_mutex);
|
|
- tu_suballoc_bo_free(&device->autotune->suballoc, &bo);
|
|
- }
|
|
- }
|
|
+ return result;
|
|
+}
|
|
|
|
- /* Disable the copy/move operators as that shouldn't be done. */
|
|
- rp_entry(const rp_entry &) = delete;
|
|
- rp_entry &operator=(const rp_entry &) = delete;
|
|
- rp_entry(rp_entry &&) = delete;
|
|
- rp_entry &operator=(rp_entry &&) = delete;
|
|
-
|
|
- void allocate(bool sysmem)
|
|
- {
|
|
- this->sysmem = sysmem;
|
|
- size_t total_size = sizeof(rp_gpu_data);
|
|
-
|
|
- std::scoped_lock lock(device->autotune->suballoc_mutex);
|
|
- VkResult result = tu_suballoc_bo_alloc(&bo, &device->autotune->suballoc, total_size, alignof(rp_gpu_data));
|
|
- if (result != VK_SUCCESS) {
|
|
- mesa_loge("Failed to allocate BO for autotune rp_entry: %u", result);
|
|
- return;
|
|
- }
|
|
+static void
|
|
+history_add_result(struct tu_device *dev, struct tu_renderpass_history *history,
|
|
+ struct tu_renderpass_result *result)
|
|
+{
|
|
+ list_delinit(&result->node);
|
|
+ list_add(&result->node, &history->results);
|
|
|
|
- map = (uint8_t *) tu_suballoc_bo_map(&bo);
|
|
- memset(map, 0, total_size);
|
|
+ if (history->num_results < MAX_HISTORY_RESULTS) {
|
|
+ history->num_results++;
|
|
+ } else {
|
|
+ /* Once above the limit, start popping old results off the
|
|
+ * tail of the list:
|
|
+ */
|
|
+ struct tu_renderpass_result *old_result =
|
|
+ list_last_entry(&history->results, struct tu_renderpass_result, node);
|
|
+ mtx_lock(&dev->autotune_mutex);
|
|
+ free_result(dev, old_result);
|
|
+ mtx_unlock(&dev->autotune_mutex);
|
|
}
|
|
|
|
- rp_gpu_data &get_gpu_data()
|
|
- {
|
|
- assert(map);
|
|
- return *(rp_gpu_data *) map;
|
|
+ /* Do calculations here to avoid locking history in tu_autotune_use_bypass */
|
|
+ uint32_t total_samples = 0;
|
|
+ list_for_each_entry(struct tu_renderpass_result, result,
|
|
+ &history->results, node) {
|
|
+ total_samples += result->samples_passed;
|
|
}
|
|
|
|
- /** Samples-Passed Metric **/
|
|
+ float avg_samples = (float)total_samples / (float)history->num_results;
|
|
+ p_atomic_set(&history->avg_samples, (uint32_t)avg_samples);
|
|
+}
|
|
|
|
- uint64_t get_samples_passed()
|
|
- {
|
|
- assert(config.test(metric_flag::SAMPLES));
|
|
- rp_gpu_data &gpu = get_gpu_data();
|
|
- return gpu.samples_end - gpu.samples_start;
|
|
- }
|
|
+static void
|
|
+process_results(struct tu_autotune *at, uint32_t current_fence)
|
|
+{
|
|
+ struct tu_device *dev = at->device;
|
|
|
|
- void emit_metric_samples_start(struct tu_cmd_buffer *cmd, struct tu_cs *cs, uint64_t start_iova)
|
|
- {
|
|
- tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_CNTL(.copy = true));
|
|
- if (cmd->device->physical_device->info->props.has_event_write_sample_count) {
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
- tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE, .write_sample_count = true).value);
|
|
- tu_cs_emit_qw(cs, start_iova);
|
|
-
|
|
- /* If the renderpass contains an occlusion query with its own ZPASS_DONE, we have to provide a fake ZPASS_DONE
|
|
- * event here to logically close the previous one, preventing firmware from misbehaving due to nested events.
|
|
- * This writes into the samples_end field, which will be overwritten in tu_autotune_end_renderpass.
|
|
- */
|
|
- if (cmd->state.rp.has_zpass_done_sample_count_write_in_rp) {
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
- tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE, .write_sample_count = true,
|
|
- .sample_count_end_offset = true, .write_accum_sample_count_diff = true)
|
|
- .value);
|
|
- tu_cs_emit_qw(cs, start_iova);
|
|
- }
|
|
- } else {
|
|
- tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_BASE(.qword = start_iova));
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE, 1);
|
|
- tu_cs_emit(cs, ZPASS_DONE);
|
|
- }
|
|
- }
|
|
+ list_for_each_entry_safe(struct tu_renderpass_result, result,
|
|
+ &at->pending_results, node) {
|
|
+ if (fence_before(current_fence, result->fence))
|
|
+ break;
|
|
|
|
- void emit_metric_samples_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs, uint64_t start_iova, uint64_t end_iova)
|
|
- {
|
|
- tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_CNTL(.copy = true));
|
|
- if (cmd->device->physical_device->info->props.has_event_write_sample_count) {
|
|
- /* If the renderpass contains ZPASS_DONE events we emit a fake ZPASS_DONE event here, composing a pair of these
|
|
- * events that firmware handles without issue. This first event writes into the samples_end field and the
|
|
- * second event overwrites it. The second event also enables the accumulation flag even when we don't use that
|
|
- * result because the blob always sets it.
|
|
- */
|
|
- if (cmd->state.rp.has_zpass_done_sample_count_write_in_rp) {
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
- tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE, .write_sample_count = true).value);
|
|
- tu_cs_emit_qw(cs, end_iova);
|
|
- }
|
|
+ struct tu_renderpass_history *history = result->history;
|
|
+ result->samples_passed =
|
|
+ result->samples->samples_end - result->samples->samples_start;
|
|
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
- tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE, .write_sample_count = true,
|
|
- .sample_count_end_offset = true, .write_accum_sample_count_diff = true)
|
|
- .value);
|
|
- tu_cs_emit_qw(cs, start_iova);
|
|
- } else {
|
|
- tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_BASE(.qword = end_iova));
|
|
- tu_cs_emit_pkt7(cs, CP_EVENT_WRITE, 1);
|
|
- tu_cs_emit(cs, ZPASS_DONE);
|
|
- }
|
|
+ history_add_result(dev, history, result);
|
|
}
|
|
|
|
- /** CS Emission **/
|
|
-
|
|
- void emit_rp_start(struct tu_cmd_buffer *cmd, struct tu_cs *cs)
|
|
- {
|
|
- assert(map && bo.iova);
|
|
- uint64_t bo_iova = bo.iova;
|
|
- if (config.test(metric_flag::SAMPLES))
|
|
- emit_metric_samples_start(cmd, cs, bo_iova + offsetof(rp_gpu_data, samples_start));
|
|
- }
|
|
+ list_for_each_entry_safe(struct tu_submission_data, submission_data,
|
|
+ &at->pending_submission_data, node) {
|
|
+ if (fence_before(current_fence, submission_data->fence))
|
|
+ break;
|
|
|
|
- void emit_rp_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs)
|
|
- {
|
|
- assert(map && bo.iova);
|
|
- uint64_t bo_iova = bo.iova;
|
|
- if (config.test(metric_flag::SAMPLES))
|
|
- emit_metric_samples_end(cmd, cs, bo_iova + offsetof(rp_gpu_data, samples_start),
|
|
- bo_iova + offsetof(rp_gpu_data, samples_end));
|
|
+ finish_submission_data(at, submission_data);
|
|
}
|
|
-};
|
|
-
|
|
-tu_autotune::rp_entry_batch::rp_entry_batch(): active(false), fence(0), entries()
|
|
-{
|
|
}
|
|
|
|
-void
|
|
-tu_autotune::rp_entry_batch::assign_fence(uint32_t new_fence)
|
|
+static void
|
|
+queue_pending_results(struct tu_autotune *at, struct tu_cmd_buffer *cmdbuf)
|
|
{
|
|
- assert(!active); /* Cannot assign a fence to an active entry batch. */
|
|
- fence = new_fence;
|
|
- active = true;
|
|
-}
|
|
+ bool one_time_submit = cmdbuf->usage_flags &
|
|
+ VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
|
|
-void
|
|
-tu_autotune::rp_entry_batch::mark_inactive()
|
|
-{
|
|
- assert(active);
|
|
- active = false;
|
|
- fence = 0;
|
|
+ if (one_time_submit) {
|
|
+ /* We can just steal the list since it won't be resubmitted again */
|
|
+ list_splicetail(&cmdbuf->renderpass_autotune_results,
|
|
+ &at->pending_results);
|
|
+ list_inithead(&cmdbuf->renderpass_autotune_results);
|
|
+ } else {
|
|
+ list_for_each_entry_safe(struct tu_renderpass_result, result,
|
|
+ &cmdbuf->renderpass_autotune_results, node) {
|
|
+ /* TODO: copying each result isn't nice */
|
|
+ struct tu_renderpass_result *copy =
|
|
+ (struct tu_renderpass_result *) malloc(sizeof(*result));
|
|
+ *copy = *result;
|
|
+ tu_bo_get_ref(copy->bo.bo);
|
|
+ list_addtail(©->node, &at->pending_results);
|
|
+ }
|
|
+ }
|
|
}
|
|
|
|
-/** Renderpass state tracking. **/
|
|
-
|
|
-tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass,
|
|
- const struct tu_framebuffer *framebuffer,
|
|
- const struct tu_cmd_buffer *cmd)
|
|
+struct tu_cs *
|
|
+tu_autotune_on_submit(struct tu_device *dev,
|
|
+ struct tu_autotune *at,
|
|
+ struct tu_cmd_buffer **cmd_buffers,
|
|
+ uint32_t cmd_buffer_count)
|
|
{
|
|
- /* Q: Why not make the key from framebuffer + renderpass pointers?
|
|
- * A: At least DXVK creates new framebuffers each frame while keeping renderpasses the same. Hashing the contents
|
|
- * of the framebuffer and renderpass is more stable, and it maintains stability across runs, so we can reliably
|
|
- * identify the same renderpass instance.
|
|
- */
|
|
+ /* We are single-threaded here */
|
|
|
|
- auto get_hash = [&](uint32_t *data, size_t size) {
|
|
- uint32_t *ptr = data;
|
|
- *ptr++ = framebuffer->width;
|
|
- *ptr++ = framebuffer->height;
|
|
- *ptr++ = framebuffer->layers;
|
|
-
|
|
- for (unsigned i = 0; i < pass->attachment_count; i++) {
|
|
- *ptr++ = cmd->state.attachments[i]->view.width;
|
|
- *ptr++ = cmd->state.attachments[i]->view.height;
|
|
- *ptr++ = cmd->state.attachments[i]->image->vk.format;
|
|
- *ptr++ = cmd->state.attachments[i]->image->vk.array_layers;
|
|
- *ptr++ = cmd->state.attachments[i]->image->vk.mip_levels;
|
|
- }
|
|
+ const uint32_t gpu_fence = get_autotune_fence(at);
|
|
+ const uint32_t new_fence = at->fence_counter++;
|
|
|
|
- return XXH3_64bits(data, size * sizeof(uint32_t));
|
|
- };
|
|
+ process_results(at, gpu_fence);
|
|
|
|
- /* We do a manual Boost-style "small vector" optimization here where the stack is used for the vast majority of
|
|
- * cases, while only extreme cases need to allocate on the heap.
|
|
+ /* Create history entries here to minimize work and locking being
|
|
+ * done on renderpass end.
|
|
*/
|
|
- size_t data_count = 3 + (pass->attachment_count * 5);
|
|
- constexpr size_t STACK_MAX_DATA_COUNT = 3 + (5 * 5); /* in u32 units. */
|
|
-
|
|
- if (data_count <= STACK_MAX_DATA_COUNT) {
|
|
- /* If the data is small enough, we can use the stack. */
|
|
- std::array<uint32_t, STACK_MAX_DATA_COUNT> arr;
|
|
- hash = get_hash(arr.data(), data_count);
|
|
- } else {
|
|
- /* If the data is too large, we have to allocate it on the heap. */
|
|
- std::vector<uint32_t> vec(data_count);
|
|
- hash = get_hash(vec.data(), vec.size());
|
|
- }
|
|
-}
|
|
-
|
|
-/* Exponential moving average (EMA) calculator for smoothing successive values of any metric. An alpha (smoothing
|
|
- * factor) of 0.1 means 10% weight to new values (slow adaptation), while 0.9 means 90% weight (fast adaptation).
|
|
- */
|
|
-template <typename T = double> class exponential_average {
|
|
- private:
|
|
- std::atomic<double> average = std::numeric_limits<double>::quiet_NaN();
|
|
- double alpha;
|
|
-
|
|
- public:
|
|
- explicit exponential_average(double alpha = 0.1) noexcept: alpha(alpha)
|
|
- {
|
|
- }
|
|
-
|
|
- bool empty() const noexcept
|
|
- {
|
|
- double current = average.load(std::memory_order_relaxed);
|
|
- return std::isnan(current);
|
|
- }
|
|
-
|
|
- void add(T value) noexcept
|
|
- {
|
|
- double v = static_cast<double>(value);
|
|
- double current = average.load(std::memory_order_relaxed);
|
|
- double new_avg;
|
|
- do {
|
|
- new_avg = std::isnan(current) ? v : (1.0 - alpha) * current + alpha * v;
|
|
- } while (!average.compare_exchange_weak(current, new_avg, std::memory_order_relaxed, std::memory_order_relaxed));
|
|
- }
|
|
+ for (uint32_t i = 0; i < cmd_buffer_count; i++) {
|
|
+ struct tu_cmd_buffer *cmdbuf = cmd_buffers[i];
|
|
+ list_for_each_entry_safe(struct tu_renderpass_result, result,
|
|
+ &cmdbuf->renderpass_autotune_results, node) {
|
|
+ struct tu_renderpass_history *history;
|
|
+ struct hash_entry *entry =
|
|
+ _mesa_hash_table_search(at->ht, &result->rp_key);
|
|
+ if (!entry) {
|
|
+ history =
|
|
+ (struct tu_renderpass_history *) calloc(1, sizeof(*history));
|
|
+ history->key = result->rp_key;
|
|
+ list_inithead(&history->results);
|
|
+
|
|
+ u_rwlock_wrlock(&at->ht_lock);
|
|
+ _mesa_hash_table_insert(at->ht, &history->key, history);
|
|
+ u_rwlock_wrunlock(&at->ht_lock);
|
|
+ } else {
|
|
+ history = (struct tu_renderpass_history *) entry->data;
|
|
+ }
|
|
|
|
- void clear() noexcept
|
|
- {
|
|
- average.store(std::numeric_limits<double>::quiet_NaN(), std::memory_order_relaxed);
|
|
- }
|
|
+ history->last_fence = new_fence;
|
|
|
|
- T get() const noexcept
|
|
- {
|
|
- double current = average.load(std::memory_order_relaxed);
|
|
- return std::isnan(current) ? T {} : static_cast<T>(current);
|
|
+ result->fence = new_fence;
|
|
+ result->history = history;
|
|
+ }
|
|
}
|
|
-};
|
|
|
|
-/* All historical state pertaining to a uniquely identified RP. This integrates data from RP entries, accumulating
|
|
- * metrics over the long-term and providing autotune algorithms using the data.
|
|
- */
|
|
-struct tu_autotune::rp_history {
|
|
- public:
|
|
- uint64_t hash; /* The hash of the renderpass, just for debug output. */
|
|
+ struct tu_submission_data *submission_data =
|
|
+ create_submission_data(dev, at, new_fence);
|
|
|
|
- std::atomic<uint32_t> refcount = 0; /* Reference count to prevent deletion when active. */
|
|
- std::atomic<uint64_t> last_use_ts; /* Last time the reference count was updated, in monotonic nanoseconds. */
|
|
+ for (uint32_t i = 0; i < cmd_buffer_count; i++) {
|
|
+ struct tu_cmd_buffer *cmdbuf = cmd_buffers[i];
|
|
+ if (list_is_empty(&cmdbuf->renderpass_autotune_results))
|
|
+ continue;
|
|
|
|
- rp_history(uint64_t hash): hash(hash), last_use_ts(os_time_get_nano())
|
|
- {
|
|
+ queue_pending_results(at, cmdbuf);
|
|
}
|
|
|
|
- /** Bandwidth Estimation Algorithm **/
|
|
- struct bandwidth_algo {
|
|
- private:
|
|
- exponential_average<uint32_t> mean_samples_passed;
|
|
+ if (TU_AUTOTUNE_DEBUG_LOG)
|
|
+ mesa_logi("Total history entries: %u", at->ht->entries);
|
|
|
|
- public:
|
|
- void update(uint32_t samples)
|
|
- {
|
|
- mean_samples_passed.add(samples);
|
|
- }
|
|
-
|
|
- render_mode get_optimal_mode(rp_history &history,
|
|
- const struct tu_cmd_state *cmd_state,
|
|
- const struct tu_render_pass *pass,
|
|
- const struct tu_framebuffer *framebuffer,
|
|
- const struct tu_render_pass_state *rp_state)
|
|
- {
|
|
- uint32_t pass_pixel_count = 0;
|
|
- if (cmd_state->per_layer_render_area) {
|
|
- for (unsigned i = 0; i < cmd_state->pass->num_views; i++) {
|
|
- const VkExtent2D &extent = cmd_state->render_areas[i].extent;
|
|
- pass_pixel_count += extent.width * extent.height;
|
|
- }
|
|
- } else {
|
|
- const VkExtent2D &extent = cmd_state->render_areas[0].extent;
|
|
- pass_pixel_count =
|
|
- extent.width * extent.height * MAX2(cmd_state->pass->num_views, cmd_state->framebuffer->layers);
|
|
- }
|
|
-
|
|
- uint64_t sysmem_bandwidth = (uint64_t) pass->sysmem_bandwidth_per_pixel * pass_pixel_count;
|
|
- uint64_t gmem_bandwidth = (uint64_t) pass->gmem_bandwidth_per_pixel * pass_pixel_count;
|
|
-
|
|
- uint64_t total_draw_call_bandwidth = 0;
|
|
- uint64_t mean_samples = mean_samples_passed.get();
|
|
- if (rp_state->drawcall_count && mean_samples > 0.0) {
|
|
- /* The total draw call bandwidth is estimated as the average samples (collected via tracking samples passed
|
|
- * within the CS) multiplied by the drawcall bandwidth per sample, divided by the amount of draw calls.
|
|
- *
|
|
- * This is a rough estimate of the bandwidth used by the draw calls in the renderpass for FB writes which
|
|
- * is used to determine whether to use SYSMEM or GMEM.
|
|
- */
|
|
- total_draw_call_bandwidth =
|
|
- (mean_samples * rp_state->drawcall_bandwidth_per_sample_sum) / rp_state->drawcall_count;
|
|
- }
|
|
+ /* Cleanup old entries from history table. The assumption
|
|
+ * here is that application doesn't hold many old unsubmitted
|
|
+ * command buffers, otherwise this table may grow big.
|
|
+ */
|
|
+ hash_table_foreach(at->ht, entry) {
|
|
+ struct tu_renderpass_history *history =
|
|
+ (struct tu_renderpass_history *) entry->data;
|
|
+ if (fence_before(gpu_fence, history->last_fence + MAX_HISTORY_LIFETIME))
|
|
+ continue;
|
|
|
|
- /* Drawcalls access the memory in SYSMEM rendering (ignoring CCU). */
|
|
- sysmem_bandwidth += total_draw_call_bandwidth;
|
|
-
|
|
- /* Drawcalls access GMEM in GMEM rendering, but we do not want to ignore them completely. The state changes
|
|
- * between tiles also have an overhead. The magic numbers of 11 and 10 are randomly chosen.
|
|
- */
|
|
- gmem_bandwidth = (gmem_bandwidth * 11 + total_draw_call_bandwidth) / 10;
|
|
-
|
|
- bool select_sysmem = sysmem_bandwidth <= gmem_bandwidth;
|
|
- render_mode mode = select_sysmem ? render_mode::SYSMEM : render_mode::GMEM;
|
|
-
|
|
- UNUSED const VkExtent2D &extent = cmd_state->render_areas[0].extent;
|
|
- at_log_bandwidth_h(
|
|
- "%" PRIu32 " selecting %s\n"
|
|
- " mean_samples=%" PRIu64 ", draw_bandwidth_per_sample=%.2f, total_draw_call_bandwidth=%" PRIu64
|
|
- ", render_areas[0]=%" PRIu32 "x%" PRIu32 ", sysmem_bandwidth_per_pixel=%" PRIu32
|
|
- ", gmem_bandwidth_per_pixel=%" PRIu32 ", sysmem_bandwidth=%" PRIu64 ", gmem_bandwidth=%" PRIu64,
|
|
- history.hash, rp_state->drawcall_count, render_mode_str(mode), mean_samples,
|
|
- (float) rp_state->drawcall_bandwidth_per_sample_sum / rp_state->drawcall_count, total_draw_call_bandwidth,
|
|
- extent.width, extent.height, pass->sysmem_bandwidth_per_pixel, pass->gmem_bandwidth_per_pixel,
|
|
- sysmem_bandwidth, gmem_bandwidth);
|
|
-
|
|
- return mode;
|
|
- }
|
|
- } bandwidth;
|
|
+ if (TU_AUTOTUNE_DEBUG_LOG)
|
|
+ mesa_logi("Removed old history entry %016" PRIx64 "", history->key);
|
|
|
|
- void process(rp_entry &entry, tu_autotune &at)
|
|
- {
|
|
- /* We use entry config to know what metrics it has, autotune config to know what algorithms are enabled. */
|
|
- config_t entry_config = entry.config;
|
|
- config_t at_config = at.active_config.load();
|
|
+ u_rwlock_wrlock(&at->ht_lock);
|
|
+ _mesa_hash_table_remove_key(at->ht, &history->key);
|
|
+ u_rwlock_wrunlock(&at->ht_lock);
|
|
|
|
- if (entry_config.test(metric_flag::SAMPLES) && at_config.is_enabled(algorithm::BANDWIDTH))
|
|
- bandwidth.update(entry.get_samples_passed());
|
|
+ mtx_lock(&dev->autotune_mutex);
|
|
+ free_history(dev, history);
|
|
+ mtx_unlock(&dev->autotune_mutex);
|
|
}
|
|
-};
|
|
|
|
-tu_autotune::rp_history_handle::~rp_history_handle()
|
|
-{
|
|
- if (!history)
|
|
- return;
|
|
-
|
|
- history->last_use_ts.store(os_time_get_nano(), std::memory_order_relaxed);
|
|
- ASSERTED uint32_t old_refcount = history->refcount.fetch_sub(1, std::memory_order_relaxed);
|
|
- assert(old_refcount != 0); /* Underflow check. */
|
|
+ return &submission_data->fence_cs;
|
|
}
|
|
|
|
-tu_autotune::rp_history_handle::rp_history_handle(rp_history &history): history(&history)
|
|
+static bool
|
|
+renderpass_key_equals(const void *_a, const void *_b)
|
|
{
|
|
- history.refcount.fetch_add(1, std::memory_order_relaxed);
|
|
- history.last_use_ts.store(os_time_get_nano(), std::memory_order_relaxed);
|
|
+ return *(uint64_t *)_a == *(uint64_t *)_b;
|
|
}
|
|
|
|
-tu_autotune::rp_history_handle
|
|
-tu_autotune::find_rp_history(const rp_key &key)
|
|
+static uint32_t
|
|
+renderpass_key_hash(const void *_a)
|
|
{
|
|
- std::shared_lock lock(rp_mutex);
|
|
- auto it = rp_histories.find(key);
|
|
- if (it != rp_histories.end())
|
|
- return rp_history_handle(it->second);
|
|
-
|
|
- return rp_history_handle(nullptr);
|
|
+ return *((uint64_t *) _a) & 0xffffffff;
|
|
}
|
|
|
|
-tu_autotune::rp_history_handle
|
|
-tu_autotune::find_or_create_rp_history(const rp_key &key)
|
|
+VkResult
|
|
+tu_autotune_init(struct tu_autotune *at, struct tu_device *dev)
|
|
{
|
|
- rp_history *existing = find_rp_history(key);
|
|
- if (existing)
|
|
- return *existing;
|
|
-
|
|
- /* If we reach here, we have to create a new history. */
|
|
- std::unique_lock lock(rp_mutex);
|
|
- auto it = rp_histories.find(key);
|
|
- if (it != rp_histories.end())
|
|
- return it->second; /* Another thread created the history while we were waiting for the lock. */
|
|
- auto history = rp_histories.emplace(std::make_pair(key, key.hash));
|
|
- return rp_history_handle(history.first->second);
|
|
+ at->enabled = true;
|
|
+ at->device = dev;
|
|
+ at->ht = _mesa_hash_table_create(NULL,
|
|
+ renderpass_key_hash,
|
|
+ renderpass_key_equals);
|
|
+ u_rwlock_init(&at->ht_lock);
|
|
+
|
|
+ list_inithead(&at->pending_results);
|
|
+ list_inithead(&at->pending_submission_data);
|
|
+ list_inithead(&at->submission_data_pool);
|
|
+
|
|
+ /* start from 1 because tu6_global::autotune_fence is initialized to 0 */
|
|
+ at->fence_counter = 1;
|
|
+
|
|
+ return VK_SUCCESS;
|
|
}
|
|
|
|
void
|
|
-tu_autotune::reap_old_rp_histories()
|
|
+tu_autotune_fini(struct tu_autotune *at, struct tu_device *dev)
|
|
{
|
|
- constexpr uint64_t REAP_INTERVAL_NS = 10'000'000'000; /* 10s */
|
|
- uint64_t now = os_time_get_nano();
|
|
- if (last_reap_ts + REAP_INTERVAL_NS > now)
|
|
- return;
|
|
- last_reap_ts = now;
|
|
-
|
|
- constexpr size_t MAX_RP_HISTORIES = 1024; /* Not a hard limit, we might exceed this if there's many active RPs. */
|
|
- {
|
|
- /* Quicker non-unique lock, should hit this path mostly. */
|
|
- std::shared_lock lock(rp_mutex);
|
|
- if (rp_histories.size() <= MAX_RP_HISTORIES)
|
|
- return;
|
|
- }
|
|
+ if (TU_AUTOTUNE_LOG_AT_FINISH) {
|
|
+ while (!list_is_empty(&at->pending_results)) {
|
|
+ const uint32_t gpu_fence = get_autotune_fence(at);
|
|
+ process_results(at, gpu_fence);
|
|
+ }
|
|
|
|
- std::unique_lock lock(rp_mutex);
|
|
- size_t og_size = rp_histories.size();
|
|
- if (og_size <= MAX_RP_HISTORIES)
|
|
- return;
|
|
+ hash_table_foreach(at->ht, entry) {
|
|
+ struct tu_renderpass_history *history =
|
|
+ (struct tu_renderpass_history *) entry->data;
|
|
|
|
- std::vector<rp_histories_t::iterator> candidates;
|
|
- candidates.reserve(og_size);
|
|
- for (auto it = rp_histories.begin(); it != rp_histories.end(); ++it) {
|
|
- if (it->second.refcount.load(std::memory_order_relaxed) == 0)
|
|
- candidates.push_back(it);
|
|
+ mesa_logi("%016" PRIx64 " \tavg_passed=%u results=%u",
|
|
+ history->key, history->avg_samples, history->num_results);
|
|
+ }
|
|
}
|
|
|
|
- size_t to_purge = std::min(candidates.size(), og_size - MAX_RP_HISTORIES);
|
|
- if (to_purge == 0) {
|
|
- at_log_base("no RP histories to reap at size %zu, all are active", og_size);
|
|
- return;
|
|
- }
|
|
+ tu_autotune_free_results(dev, &at->pending_results);
|
|
|
|
- /* Partition candidates by last use timestamp, oldest first. */
|
|
- auto partition_end = candidates.begin() + to_purge;
|
|
- if (to_purge < candidates.size()) {
|
|
- std::nth_element(candidates.begin(), partition_end, candidates.end(),
|
|
- [](rp_histories_t::iterator a, rp_histories_t::iterator b) {
|
|
- return a->second.last_use_ts.load(std::memory_order_relaxed) <
|
|
- b->second.last_use_ts.load(std::memory_order_relaxed);
|
|
- });
|
|
+ mtx_lock(&dev->autotune_mutex);
|
|
+ hash_table_foreach(at->ht, entry) {
|
|
+ struct tu_renderpass_history *history =
|
|
+ (struct tu_renderpass_history *) entry->data;
|
|
+ free_history(dev, history);
|
|
}
|
|
+ mtx_unlock(&dev->autotune_mutex);
|
|
|
|
- for (auto it = candidates.begin(); it != partition_end; ++it) {
|
|
- rp_history &history = (*it)->second;
|
|
- if (history.refcount.load(std::memory_order_relaxed) == 0) {
|
|
- at_log_base("reaping RP history %016" PRIx64, history.hash);
|
|
- rp_histories.erase(*it);
|
|
- }
|
|
+ list_for_each_entry_safe(struct tu_submission_data, submission_data,
|
|
+ &at->pending_submission_data, node) {
|
|
+ free_submission_data(submission_data);
|
|
}
|
|
|
|
- at_log_base("reaped old RP histories %zu -> %zu", og_size, rp_histories.size());
|
|
-}
|
|
-
|
|
-void
|
|
-tu_autotune::process_entries()
|
|
-{
|
|
- uint32_t current_fence = device->global_bo_map->autotune_fence;
|
|
-
|
|
- while (!active_batches.empty()) {
|
|
- auto &batch = active_batches.front();
|
|
- assert(batch->active);
|
|
-
|
|
- if (fence_before(current_fence, batch->fence))
|
|
- break; /* Entries are allocated in sequence, next will be newer and
|
|
- also fail so we can just directly break out of the loop. */
|
|
-
|
|
- for (auto &entry : batch->entries)
|
|
- entry->history->process(*entry, *this);
|
|
-
|
|
- batch->mark_inactive();
|
|
- active_batches.pop_front();
|
|
+ list_for_each_entry_safe(struct tu_submission_data, submission_data,
|
|
+ &at->submission_data_pool, node) {
|
|
+ free_submission_data(submission_data);
|
|
}
|
|
|
|
- if (active_batches.size() > 10) {
|
|
- at_log_base("high amount of active batches: %zu, fence: %" PRIu32 " < %" PRIu32, active_batches.size(),
|
|
- current_fence, active_batches.front()->fence);
|
|
- }
|
|
+ _mesa_hash_table_destroy(at->ht, NULL);
|
|
+ u_rwlock_destroy(&at->ht_lock);
|
|
}
|
|
|
|
-struct tu_cs *
|
|
-tu_autotune::on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_count)
|
|
+bool
|
|
+tu_autotune_submit_requires_fence(struct tu_cmd_buffer **cmd_buffers,
|
|
+ uint32_t cmd_buffer_count)
|
|
{
|
|
-
|
|
- /* This call occurs regularly and we are single-threaded here, so we use this opportunity to process any available
|
|
- * entries. It's also important that any entries are processed here because we always want to ensure that we've
|
|
- * processed all entries from prior CBs before we submit any new CBs with the same RP to the GPU.
|
|
- */
|
|
- process_entries();
|
|
- reap_old_rp_histories();
|
|
-
|
|
- bool has_results = false;
|
|
for (uint32_t i = 0; i < cmd_buffer_count; i++) {
|
|
- auto &batch = cmd_buffers[i]->autotune_ctx.batch;
|
|
- if (!batch->entries.empty()) {
|
|
- has_results = true;
|
|
- break;
|
|
- }
|
|
- }
|
|
- if (!has_results)
|
|
- return nullptr; /* No results to process, return early. */
|
|
-
|
|
- /* Generate a new fence and the CS for it. */
|
|
- const uint32_t new_fence = next_fence++;
|
|
- auto fence_cs = get_cs_for_fence(new_fence);
|
|
- for (uint32_t i = 0; i < cmd_buffer_count; i++) {
|
|
- /* Transfer the entries from the command buffers to the active queue. */
|
|
struct tu_cmd_buffer *cmdbuf = cmd_buffers[i];
|
|
- auto &batch = cmdbuf->autotune_ctx.batch;
|
|
- if (batch->entries.empty())
|
|
- continue;
|
|
-
|
|
- batch->assign_fence(new_fence);
|
|
- if (cmdbuf->usage_flags & VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) {
|
|
- /* If the command buffer is one-time submit, we can move the batch directly into the active batches, as it
|
|
- * won't be used again. This would lead to it being deallocated as early as possible.
|
|
- */
|
|
- active_batches.push_back(std::move(batch));
|
|
- } else {
|
|
- active_batches.push_back(batch);
|
|
- }
|
|
+ if (!list_is_empty(&cmdbuf->renderpass_autotune_results))
|
|
+ return true;
|
|
}
|
|
|
|
- return fence_cs;
|
|
+ return false;
|
|
}
|
|
|
|
-tu_autotune::tu_autotune(struct tu_device *device, VkResult &result): device(device), active_config(get_env_config())
|
|
+void
|
|
+tu_autotune_free_results_locked(struct tu_device *dev, struct list_head *results)
|
|
{
|
|
- tu_bo_suballocator_init(&suballoc, device, 128 * 1024, TU_BO_ALLOC_INTERNAL_RESOURCE, "autotune_suballoc");
|
|
+ list_for_each_entry_safe(struct tu_renderpass_result, result,
|
|
+ results, node) {
|
|
+ free_result(dev, result);
|
|
+ }
|
|
+}
|
|
|
|
- result = VK_SUCCESS;
|
|
- return;
|
|
+void
|
|
+tu_autotune_free_results(struct tu_device *dev, struct list_head *results)
|
|
+{
|
|
+ mtx_lock(&dev->autotune_mutex);
|
|
+ tu_autotune_free_results_locked(dev, results);
|
|
+ mtx_unlock(&dev->autotune_mutex);
|
|
}
|
|
|
|
-tu_autotune::~tu_autotune()
|
|
+static bool
|
|
+fallback_use_bypass(const struct tu_render_pass *pass,
|
|
+ const struct tu_framebuffer *framebuffer,
|
|
+ const struct tu_cmd_buffer *cmd_buffer)
|
|
{
|
|
- if (TU_AUTOTUNE_FLUSH_AT_FINISH) {
|
|
- while (!active_batches.empty())
|
|
- process_entries();
|
|
- at_log_base("finished processing all entries");
|
|
+ if (cmd_buffer->state.rp.drawcall_count > 5)
|
|
+ return false;
|
|
+
|
|
+ for (unsigned i = 0; i < pass->subpass_count; i++) {
|
|
+ if (pass->subpasses[i].samples != VK_SAMPLE_COUNT_1_BIT)
|
|
+ return false;
|
|
}
|
|
|
|
- tu_bo_suballocator_finish(&suballoc);
|
|
+ return true;
|
|
}
|
|
|
|
-tu_autotune::cmd_buf_ctx::cmd_buf_ctx(): batch(std::make_shared<rp_entry_batch>())
|
|
+static uint32_t
|
|
+get_render_pass_pixel_count(const struct tu_cmd_buffer *cmd)
|
|
{
|
|
+ if (cmd->state.per_layer_render_area) {
|
|
+ uint32_t pixels = 0;
|
|
+ for (unsigned i = 0; i < cmd->state.pass->num_views; i++) {
|
|
+ const VkExtent2D *extent = &cmd->state.render_areas[i].extent;
|
|
+ pixels += extent->width * extent->height;
|
|
+ }
|
|
+ return pixels;
|
|
+ } else {
|
|
+ const VkExtent2D *extent = &cmd->state.render_areas[0].extent;
|
|
+ return extent->width * extent->height *
|
|
+ MAX2(cmd->state.pass->num_views, cmd->state.framebuffer->layers);
|
|
+ }
|
|
}
|
|
|
|
-tu_autotune::cmd_buf_ctx::~cmd_buf_ctx()
|
|
+static uint64_t
|
|
+estimate_drawcall_bandwidth(const struct tu_cmd_buffer *cmd,
|
|
+ uint32_t avg_renderpass_sample_count)
|
|
{
|
|
- /* This is empty but it causes the implicit destructor to be compiled within this compilation unit with access to
|
|
- * internal structures. Otherwise, we would need to expose the full definition of autotuner internals in the header
|
|
- * file, which is not desirable.
|
|
- */
|
|
-}
|
|
+ const struct tu_cmd_state *state = &cmd->state;
|
|
|
|
-void
|
|
-tu_autotune::cmd_buf_ctx::reset()
|
|
-{
|
|
- batch = std::make_shared<rp_entry_batch>();
|
|
-}
|
|
+ if (!state->rp.drawcall_count)
|
|
+ return 0;
|
|
|
|
-tu_autotune::rp_entry *
|
|
-tu_autotune::cmd_buf_ctx::attach_rp_entry(struct tu_device *device,
|
|
- rp_history_handle &&history,
|
|
- config_t config,
|
|
- uint32_t drawcall_count)
|
|
-{
|
|
- std::unique_ptr<rp_entry> &new_entry =
|
|
- batch->entries.emplace_back(std::make_unique<rp_entry>(device, std::move(history), config, drawcall_count));
|
|
- return new_entry.get();
|
|
+ /* sample count times drawcall_bandwidth_per_sample */
|
|
+ return (uint64_t)avg_renderpass_sample_count *
|
|
+ state->rp.drawcall_bandwidth_per_sample_sum / state->rp.drawcall_count;
|
|
}
|
|
|
|
-tu_autotune::render_mode
|
|
-tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx)
|
|
+bool
|
|
+tu_autotune_use_bypass(struct tu_autotune *at,
|
|
+ struct tu_cmd_buffer *cmd_buffer,
|
|
+ struct tu_renderpass_result **autotune_result)
|
|
{
|
|
- const struct tu_cmd_state *cmd_state = &cmd_buffer->state;
|
|
- const struct tu_render_pass *pass = cmd_state->pass;
|
|
- const struct tu_framebuffer *framebuffer = cmd_state->framebuffer;
|
|
- const struct tu_render_pass_state *rp_state = &cmd_state->rp;
|
|
- cmd_buf_ctx &cb_ctx = cmd_buffer->autotune_ctx;
|
|
- config_t config = active_config.load();
|
|
-
|
|
- /* Just to ensure a segfault for accesses, in case we don't set it. */
|
|
- *rp_ctx = nullptr;
|
|
+ const struct tu_render_pass *pass = cmd_buffer->state.pass;
|
|
+ const struct tu_framebuffer *framebuffer = cmd_buffer->state.framebuffer;
|
|
|
|
/* If a feedback loop in the subpass caused one of the pipelines used to set
|
|
- * SINGLE_PRIM_MODE(FLUSH_PER_OVERLAP_AND_OVERWRITE) or even SINGLE_PRIM_MODE(FLUSH), then that should cause
|
|
- * significantly increased SYSMEM bandwidth (though we haven't quantified it).
|
|
+ * SINGLE_PRIM_MODE(FLUSH_PER_OVERLAP_AND_OVERWRITE) or even
|
|
+ * SINGLE_PRIM_MODE(FLUSH), then that should cause significantly increased
|
|
+ * sysmem bandwidth (though we haven't quantified it).
|
|
*/
|
|
- if (rp_state->sysmem_single_prim_mode)
|
|
- return render_mode::GMEM;
|
|
-
|
|
- /* If the user is using a fragment density map, then this will cause less FS invocations with GMEM, which has a
|
|
- * hard-to-measure impact on performance because it depends on how heavy the FS is in addition to how many
|
|
- * invocations there were and the density. Let's assume the user knows what they're doing when they added the map,
|
|
- * because if SYSMEM is actually faster then they could've just not used the fragment density map.
|
|
+ if (cmd_buffer->state.rp.sysmem_single_prim_mode)
|
|
+ return false;
|
|
+
|
|
+ /* If the user is using a fragment density map, then this will cause less
|
|
+ * FS invocations with GMEM, which has a hard-to-measure impact on
|
|
+ * performance because it depends on how heavy the FS is in addition to how
|
|
+ * many invocations there were and the density. Let's assume the user knows
|
|
+ * what they're doing when they added the map, because if sysmem is
|
|
+ * actually faster then they could've just not used the fragment density
|
|
+ * map.
|
|
*/
|
|
if (pass->has_fdm)
|
|
- return render_mode::GMEM;
|
|
+ return false;
|
|
|
|
- /* SYSMEM is always a safe default mode when we can't fully engage the autotuner. From testing, we know that for an
|
|
- * incorrect decision towards SYSMEM tends to be far less impactful than an incorrect decision towards GMEM, which
|
|
- * can cause significant performance issues.
|
|
+ /* For VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT buffers
|
|
+ * we would have to allocate GPU memory at the submit time and copy
|
|
+ * results into it.
|
|
+ * Native games ususally don't use it, Zink and DXVK don't use it,
|
|
+ * D3D12 doesn't have such concept.
|
|
*/
|
|
- constexpr render_mode default_mode = render_mode::SYSMEM;
|
|
+ bool simultaneous_use =
|
|
+ cmd_buffer->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
|
|
|
- /* For VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT buffers, we would have to allocate GPU memory at the submit time
|
|
- * and copy results into it. We just disable complex autotuner in this case, which isn't a big issue since native
|
|
- * games usually don't use it, Zink and DXVK don't use it, while D3D12 doesn't even have such concept.
|
|
+ if (!at->enabled || simultaneous_use)
|
|
+ return fallback_use_bypass(pass, framebuffer, cmd_buffer);
|
|
+
|
|
+ /* We use 64bit hash as a key since we don't fear rare hash collision,
|
|
+ * the worst that would happen is sysmem being selected when it should
|
|
+ * have not, and with 64bit it would be extremely rare.
|
|
*
|
|
- * We combine this with processing entries at submit time, to avoid a race where the CPU hasn't processed the results
|
|
- * from an earlier submission of the CB while a second submission of the CB is on the GPU queue.
|
|
+ * Q: Why not make the key from framebuffer + renderpass pointers?
|
|
+ * A: At least DXVK creates new framebuffers each frame while keeping
|
|
+ * renderpasses the same. Also we want to support replaying a single
|
|
+ * frame in a loop for testing.
|
|
*/
|
|
- bool simultaneous_use = cmd_buffer->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
|
|
-
|
|
- if (!enabled || simultaneous_use)
|
|
- return default_mode;
|
|
-
|
|
- if (config.test(mod_flag::BIG_GMEM) && rp_state->drawcall_count >= 10)
|
|
- return render_mode::GMEM;
|
|
- if (config.test(mod_flag::SMALL_SYSMEM) && rp_state->drawcall_count <= 5)
|
|
- return render_mode::SYSMEM;
|
|
-
|
|
- rp_key key(pass, framebuffer, cmd_buffer);
|
|
- *rp_ctx = cb_ctx.attach_rp_entry(device, find_or_create_rp_history(key), config, rp_state->drawcall_count);
|
|
- rp_history &history = *((*rp_ctx)->history);
|
|
+ uint64_t renderpass_key = hash_renderpass_instance(pass, framebuffer, cmd_buffer);
|
|
+
|
|
+ *autotune_result = create_history_result(at, renderpass_key);
|
|
+
|
|
+ uint32_t avg_samples = 0;
|
|
+ if (get_history(at, renderpass_key, &avg_samples)) {
|
|
+ const uint32_t pass_pixel_count =
|
|
+ get_render_pass_pixel_count(cmd_buffer);
|
|
+ uint64_t sysmem_bandwidth =
|
|
+ (uint64_t)pass->sysmem_bandwidth_per_pixel * pass_pixel_count;
|
|
+ uint64_t gmem_bandwidth =
|
|
+ (uint64_t)pass->gmem_bandwidth_per_pixel * pass_pixel_count;
|
|
+
|
|
+ const uint64_t total_draw_call_bandwidth =
|
|
+ estimate_drawcall_bandwidth(cmd_buffer, avg_samples);
|
|
+
|
|
+ /* drawcalls access the memory in sysmem rendering (ignoring CCU) */
|
|
+ sysmem_bandwidth += total_draw_call_bandwidth;
|
|
+
|
|
+ /* drawcalls access gmem in gmem rendering, but we do not want to ignore
|
|
+ * them completely. The state changes between tiles also have an
|
|
+ * overhead. The magic numbers of 11 and 10 are randomly chosen.
|
|
+ */
|
|
+ gmem_bandwidth = (gmem_bandwidth * 11 + total_draw_call_bandwidth) / 10;
|
|
+
|
|
+ const bool select_sysmem = sysmem_bandwidth <= gmem_bandwidth;
|
|
+ if (TU_AUTOTUNE_DEBUG_LOG) {
|
|
+ const VkExtent2D *extent = &cmd_buffer->state.render_areas[0].extent;
|
|
+ const float drawcall_bandwidth_per_sample =
|
|
+ (float)cmd_buffer->state.rp.drawcall_bandwidth_per_sample_sum /
|
|
+ cmd_buffer->state.rp.drawcall_count;
|
|
+
|
|
+ mesa_logi("autotune %016" PRIx64 ":%u selecting %s",
|
|
+ renderpass_key,
|
|
+ cmd_buffer->state.rp.drawcall_count,
|
|
+ select_sysmem ? "sysmem" : "gmem");
|
|
+ mesa_logi(" avg_samples=%u, draw_bandwidth_per_sample=%.2f, total_draw_call_bandwidth=%" PRIu64,
|
|
+ avg_samples,
|
|
+ drawcall_bandwidth_per_sample,
|
|
+ total_draw_call_bandwidth);
|
|
+ mesa_logi(" render_area=%ux%u, sysmem_bandwidth_per_pixel=%u, gmem_bandwidth_per_pixel=%u",
|
|
+ extent->width, extent->height,
|
|
+ pass->sysmem_bandwidth_per_pixel,
|
|
+ pass->gmem_bandwidth_per_pixel);
|
|
+ mesa_logi(" sysmem_bandwidth=%" PRIu64 ", gmem_bandwidth=%" PRIu64,
|
|
+ sysmem_bandwidth, gmem_bandwidth);
|
|
+ }
|
|
|
|
- if (config.is_enabled(algorithm::BANDWIDTH))
|
|
- return history.bandwidth.get_optimal_mode(history, cmd_state, pass, framebuffer, rp_state);
|
|
+ return select_sysmem;
|
|
+ }
|
|
|
|
- return default_mode;
|
|
+ return fallback_use_bypass(pass, framebuffer, cmd_buffer);
|
|
}
|
|
|
|
-/** RP-level CS emissions **/
|
|
-
|
|
+template <chip CHIP>
|
|
void
|
|
-tu_autotune::begin_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem)
|
|
+tu_autotune_begin_renderpass(struct tu_cmd_buffer *cmd,
|
|
+ struct tu_cs *cs,
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
- if (!rp_ctx)
|
|
+ if (!autotune_result)
|
|
return;
|
|
|
|
- rp_ctx->allocate(sysmem);
|
|
- rp_ctx->emit_rp_start(cmd, cs);
|
|
+ struct tu_device *dev = cmd->device;
|
|
+
|
|
+ static const uint32_t size = sizeof(struct tu_renderpass_samples);
|
|
+
|
|
+ mtx_lock(&dev->autotune_mutex);
|
|
+ VkResult ret = tu_suballoc_bo_alloc(&autotune_result->bo, &dev->autotune_suballoc, size, size);
|
|
+ mtx_unlock(&dev->autotune_mutex);
|
|
+ if (ret != VK_SUCCESS) {
|
|
+ autotune_result->bo.iova = 0;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ uint64_t result_iova = autotune_result->bo.iova;
|
|
+
|
|
+ autotune_result->samples =
|
|
+ (struct tu_renderpass_samples *) tu_suballoc_bo_map(
|
|
+ &autotune_result->bo);
|
|
+
|
|
+ tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_CNTL(.copy = true));
|
|
+ if (cmd->device->physical_device->info->props.has_event_write_sample_count) {
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
+ tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE,
|
|
+ .write_sample_count = true).value);
|
|
+ tu_cs_emit_qw(cs, result_iova);
|
|
+
|
|
+ /* If the renderpass contains an occlusion query with its own ZPASS_DONE,
|
|
+ * we have to provide a fake ZPASS_DONE event here to logically close the
|
|
+ * previous one, preventing firmware from misbehaving due to nested events.
|
|
+ * This writes into the samples_end field, which will be overwritten in
|
|
+ * tu_autotune_end_renderpass.
|
|
+ */
|
|
+ if (cmd->state.rp.has_zpass_done_sample_count_write_in_rp) {
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
+ tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE,
|
|
+ .write_sample_count = true,
|
|
+ .sample_count_end_offset = true,
|
|
+ .write_accum_sample_count_diff = true).value);
|
|
+ tu_cs_emit_qw(cs, result_iova);
|
|
+ }
|
|
+ } else {
|
|
+ tu_cs_emit_regs(cs,
|
|
+ A6XX_RB_SAMPLE_COUNTER_BASE(.qword = result_iova));
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE, 1);
|
|
+ tu_cs_emit(cs, ZPASS_DONE);
|
|
+ }
|
|
}
|
|
+TU_GENX(tu_autotune_begin_renderpass);
|
|
|
|
-void
|
|
-tu_autotune::end_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx)
|
|
+template <chip CHIP>
|
|
+void tu_autotune_end_renderpass(struct tu_cmd_buffer *cmd,
|
|
+ struct tu_cs *cs,
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
- if (!rp_ctx)
|
|
+ if (!autotune_result)
|
|
+ return;
|
|
+
|
|
+ if (!autotune_result->bo.iova)
|
|
return;
|
|
|
|
- rp_ctx->emit_rp_end(cmd, cs);
|
|
+ uint64_t result_iova = autotune_result->bo.iova;
|
|
+
|
|
+ tu_cs_emit_regs(cs, A6XX_RB_SAMPLE_COUNTER_CNTL(.copy = true));
|
|
+
|
|
+ if (cmd->device->physical_device->info->props.has_event_write_sample_count) {
|
|
+ /* If the renderpass contains ZPASS_DONE events we emit a fake ZPASS_DONE
|
|
+ * event here, composing a pair of these events that firmware handles without
|
|
+ * issue. This first event writes into the samples_end field and the second
|
|
+ * event overwrites it. The second event also enables the accumulation flag
|
|
+ * even when we don't use that result because the blob always sets it.
|
|
+ */
|
|
+ if (cmd->state.rp.has_zpass_done_sample_count_write_in_rp) {
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
+ tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE,
|
|
+ .write_sample_count = true).value);
|
|
+ tu_cs_emit_qw(cs, result_iova + offsetof(struct tu_renderpass_samples, samples_end));
|
|
+ }
|
|
+
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE7, 3);
|
|
+ tu_cs_emit(cs, CP_EVENT_WRITE7_0(.event = ZPASS_DONE,
|
|
+ .write_sample_count = true,
|
|
+ .sample_count_end_offset = true,
|
|
+ .write_accum_sample_count_diff = true).value);
|
|
+ tu_cs_emit_qw(cs, result_iova);
|
|
+ } else {
|
|
+ result_iova += offsetof(struct tu_renderpass_samples, samples_end);
|
|
+
|
|
+ tu_cs_emit_regs(cs,
|
|
+ A6XX_RB_SAMPLE_COUNTER_BASE(.qword = result_iova));
|
|
+ tu_cs_emit_pkt7(cs, CP_EVENT_WRITE, 1);
|
|
+ tu_cs_emit(cs, ZPASS_DONE);
|
|
+ }
|
|
}
|
|
+TU_GENX(tu_autotune_end_renderpass);
|
|
diff --git a/src/freedreno/vulkan/tu_autotune.h b/src/freedreno/vulkan/tu_autotune.h
|
|
index 1dc966a20e9..47a26faee67 100644
|
|
--- a/src/freedreno/vulkan/tu_autotune.h
|
|
+++ b/src/freedreno/vulkan/tu_autotune.h
|
|
@@ -6,237 +6,150 @@
|
|
#ifndef TU_AUTOTUNE_H
|
|
#define TU_AUTOTUNE_H
|
|
|
|
-#include <atomic>
|
|
-#include <deque>
|
|
-#include <memory>
|
|
-#include <mutex>
|
|
-#include <shared_mutex>
|
|
-#include <unordered_map>
|
|
-#include <vector>
|
|
-
|
|
-#include "tu_cs.h"
|
|
+#include "util/hash_table.h"
|
|
+#include "util/rwlock.h"
|
|
+
|
|
#include "tu_suballoc.h"
|
|
|
|
-/* Autotune allows for us to tune rendering parameters (such as GMEM vs SYSMEM, tile size divisor, etc.) based on
|
|
- * dynamic analysis of the rendering workload via on-GPU profiling. This lets us make much better decisions than static
|
|
- * analysis, since we can adapt to the actual workload rather than relying on heuristics.
|
|
+struct tu_renderpass_history;
|
|
+
|
|
+/**
|
|
+ * "autotune" our decisions about bypass vs GMEM rendering, based on historical
|
|
+ * data about a given render target.
|
|
+ *
|
|
+ * In deciding which path to take there are tradeoffs, including some that
|
|
+ * are not reasonably estimateable without having some additional information:
|
|
+ *
|
|
+ * (1) If you know you are touching every pixel (ie. there is a clear),
|
|
+ * then the GMEM path will at least not cost more memory bandwidth than
|
|
+ * sysmem[1]
|
|
+ *
|
|
+ * (2) If there is no clear, GMEM could potentially cost *more* bandwidth
|
|
+ * if there is sysmem->GMEM restore pass.
|
|
+ *
|
|
+ * (3) If you see a high draw count, that is an indication that there will be
|
|
+ * enough pixels accessed multiple times to benefit from the reduced
|
|
+ * memory bandwidth that GMEM brings
|
|
+ *
|
|
+ * (4) But high draw count where there is not much overdraw can actually be
|
|
+ * faster in bypass mode if it is pushing a lot of state change, due to
|
|
+ * not having to go thru the state changes per-tile[1]
|
|
+ *
|
|
+ * The approach taken is to measure the samples-passed for the batch to estimate
|
|
+ * the amount of overdraw to detect cases where the number of pixels touched is
|
|
+ * low.
|
|
+ *
|
|
+ * [1] ignoring early-tile-exit optimizations, but any draw that touches all/
|
|
+ * most of the tiles late in the tile-pass can defeat that
|
|
*/
|
|
struct tu_autotune {
|
|
- private:
|
|
- bool enabled = true;
|
|
- struct tu_device *device;
|
|
-
|
|
- /** Configuration **/
|
|
-
|
|
- enum class algorithm : uint8_t;
|
|
- enum class mod_flag : uint8_t;
|
|
- enum class metric_flag : uint8_t;
|
|
- /* Container for all autotune configuration options. */
|
|
- struct PACKED config_t;
|
|
- union PACKED packed_config_t;
|
|
-
|
|
- /* Allows for thread-safe access to the configurations. */
|
|
- struct atomic_config_t {
|
|
- private:
|
|
- std::atomic<uint32_t> config_bits = 0;
|
|
-
|
|
- public:
|
|
- atomic_config_t(config_t initial_config);
|
|
-
|
|
- config_t load() const;
|
|
-
|
|
- bool compare_and_store(config_t expected, config_t updated);
|
|
- } active_config;
|
|
-
|
|
- config_t get_env_config();
|
|
-
|
|
- /** Global Fence and Internal CS Management **/
|
|
|
|
- /* BO suballocator for reducing BO management for small GMEM/SYSMEM autotune result buffers.
|
|
- * Synchronized by suballoc_mutex.
|
|
+ /* We may have to disable autotuner if there are too many
|
|
+ * renderpasses in-flight.
|
|
*/
|
|
- struct tu_suballocator suballoc;
|
|
- std::mutex suballoc_mutex;
|
|
+ bool enabled;
|
|
|
|
- /* The next value to assign to tu6_global::autotune_fence, this is incremented during on_submit. */
|
|
- uint32_t next_fence = 1;
|
|
+ struct tu_device *device;
|
|
|
|
- /* A wrapper around a CS which sets the global autotune fence to a certain fence value, this allows for ergonomically
|
|
- * managing the lifetime of the CS including recycling it after the fence value has been reached.
|
|
+ /**
|
|
+ * Cache to map renderpass key to historical information about
|
|
+ * rendering to that particular render target.
|
|
*/
|
|
- struct submission_entry {
|
|
- private:
|
|
- uint32_t fence;
|
|
- struct tu_cs fence_cs;
|
|
-
|
|
- public:
|
|
- explicit submission_entry(tu_device *device);
|
|
-
|
|
- ~submission_entry();
|
|
-
|
|
- /* Disable move/copy, since this holds stable pointers to the fence_cs. */
|
|
- submission_entry(const submission_entry &) = delete;
|
|
- submission_entry &operator=(const submission_entry &) = delete;
|
|
- submission_entry(submission_entry &&) = delete;
|
|
- submission_entry &operator=(submission_entry &&) = delete;
|
|
-
|
|
- /* The current state of the submission entry, this is used to track whether the CS is available for reuse, pending
|
|
- * GPU completion or currently being processed.
|
|
- */
|
|
- bool is_active() const;
|
|
+ struct hash_table *ht;
|
|
+ struct u_rwlock ht_lock;
|
|
|
|
- /* If the CS is free, returns the CS which will write out the specified fence value. Otherwise, returns nullptr. */
|
|
- struct tu_cs *try_get_cs(uint32_t new_fence);
|
|
- };
|
|
-
|
|
- /* Unified pool for submission CSes.
|
|
- * Note: This is a deque rather than a vector due to the lack of move semantics in the submission_entry.
|
|
+ /**
|
|
+ * List of per-renderpass results that we are waiting for the GPU
|
|
+ * to finish with before reading back the results.
|
|
*/
|
|
- std::deque<submission_entry> submission_entries;
|
|
-
|
|
- /* Returns a CS which will write out the specified fence value to the global BO's autotune fence. */
|
|
- struct tu_cs *get_cs_for_fence(uint32_t fence);
|
|
-
|
|
- /** RP Entry Management **/
|
|
-
|
|
- struct rp_gpu_data;
|
|
- struct tile_gpu_data;
|
|
- struct rp_entry;
|
|
+ struct list_head pending_results;
|
|
|
|
- /* A wrapper over all entries associated with a single command buffer. */
|
|
- struct rp_entry_batch {
|
|
- bool active; /* If the entry is ready to be processed, i.e. the entry is submitted to the GPU queue and has a
|
|
- valid fence. */
|
|
- uint32_t fence; /* The fence value which is used to signal the completion of the CB submission. This is used to
|
|
- determine when the entries can be processed. */
|
|
- std::vector<std::unique_ptr<rp_entry>> entries;
|
|
-
|
|
- rp_entry_batch();
|
|
-
|
|
- /* Disable the copy/move to avoid performance hazards. */
|
|
- rp_entry_batch(const rp_entry_batch &) = delete;
|
|
- rp_entry_batch &operator=(const rp_entry_batch &) = delete;
|
|
- rp_entry_batch(rp_entry_batch &&) = delete;
|
|
- rp_entry_batch &operator=(rp_entry_batch &&) = delete;
|
|
-
|
|
- void assign_fence(uint32_t new_fence);
|
|
-
|
|
- void mark_inactive();
|
|
- };
|
|
-
|
|
- /* A deque of entry batches that are strongly ordered by the fence value that was written by the GPU, for efficient
|
|
- * iteration and to ensure that we process the entries in the same order they were submitted.
|
|
+ /**
|
|
+ * List of per-submission data that we may want to free after we
|
|
+ * processed submission results.
|
|
+ * This could happend after command buffers which were in the submission
|
|
+ * are destroyed.
|
|
*/
|
|
- std::deque<std::shared_ptr<rp_entry_batch>> active_batches;
|
|
+ struct list_head pending_submission_data;
|
|
|
|
- /* Handles processing of entry batches that are pending to be processed.
|
|
- *
|
|
- * Note: This must be called regularly to process the entries that have been written by the GPU. We currently do this
|
|
- * in the on_submit() method, which is called on every submit of a command buffer.
|
|
+ /**
|
|
+ * List of per-submission data that has been finished and can be reused.
|
|
*/
|
|
- void process_entries();
|
|
+ struct list_head submission_data_pool;
|
|
|
|
- /** Renderpass State Tracking **/
|
|
+ uint32_t fence_counter;
|
|
+ uint32_t idx_counter;
|
|
+};
|
|
|
|
- struct rp_history;
|
|
- struct rp_history_handle;
|
|
+/**
|
|
+ * From the cmdstream, the captured samples-passed values are recorded
|
|
+ * at the start and end of the batch.
|
|
+ *
|
|
+ * Note that we do the math on the CPU to avoid a WFI. But pre-emption
|
|
+ * may force us to revisit that.
|
|
+ */
|
|
+struct PACKED tu_renderpass_samples {
|
|
+ uint64_t samples_start;
|
|
+ /* hw requires the sample start/stop locations to be 128b aligned. */
|
|
+ uint64_t __pad0;
|
|
+ uint64_t samples_end;
|
|
+ uint64_t __pad1;
|
|
+};
|
|
|
|
- /* A strongly typed key which generates a hash to uniquely identify a renderpass instance. This hash is expected to
|
|
- * be stable across runs, so it can be used to identify the same renderpass instance consistently.
|
|
- *
|
|
- * Note: We can potentially include the vector of data we extract from the parameters to generate the hash into
|
|
- * rp_key, which would lead to true value-based equality rather than just hash-based equality which has a cost
|
|
- * but avoids hash collisions causing issues.
|
|
- */
|
|
- struct rp_key {
|
|
- uint64_t hash;
|
|
-
|
|
- rp_key(const struct tu_render_pass *pass,
|
|
- const struct tu_framebuffer *framebuffer,
|
|
- const struct tu_cmd_buffer *cmd);
|
|
-
|
|
- /* Equality operator, used in unordered_map. */
|
|
- constexpr bool operator==(const rp_key &other) const noexcept
|
|
- {
|
|
- return hash == other.hash;
|
|
- }
|
|
- };
|
|
-
|
|
- /* A thin wrapper to satisfy C++'s Hash named requirement for rp_key.
|
|
- *
|
|
- * Note: This should *NEVER* be used to calculate the hash itself as it would lead to the hash being calculated
|
|
- * multiple times, rather than being calculated once and reused when there's multiple successive lookups like
|
|
- * with find_or_create_rp_history() and providing the hash to the rp_history constructor.
|
|
- */
|
|
- struct rp_hash {
|
|
- constexpr size_t operator()(const rp_key &key) const noexcept
|
|
- {
|
|
- /* Note: This will throw away the upper 32-bits on 32-bit architectures. */
|
|
- return static_cast<size_t>(key.hash);
|
|
- }
|
|
- };
|
|
-
|
|
- /* A map between the hash of an RP and the historical state of the RP. Synchronized by rp_mutex. */
|
|
- using rp_histories_t = std::unordered_map<rp_key, rp_history, rp_hash>;
|
|
- rp_histories_t rp_histories;
|
|
- std::shared_mutex rp_mutex;
|
|
- uint64_t last_reap_ts = 0;
|
|
-
|
|
- /* Note: These will internally lock rp_mutex internally, no need to lock it. */
|
|
- rp_history_handle find_rp_history(const rp_key &key);
|
|
- rp_history_handle find_or_create_rp_history(const rp_key &key);
|
|
- void reap_old_rp_histories();
|
|
-
|
|
- public:
|
|
- tu_autotune(struct tu_device *device, VkResult &result);
|
|
-
|
|
- ~tu_autotune();
|
|
-
|
|
- /* Opaque pointer to internal structure with RP context that needs to be preserved across begin/end calls. */
|
|
- using rp_ctx_t = rp_entry *;
|
|
-
|
|
- /* An internal structure that needs to be held by tu_cmd_buffer to track the state of the autotuner for a given CB.
|
|
- *
|
|
- * Note: tu_cmd_buffer is only responsible for the lifetime of this object, all the access to the context state is
|
|
- * done through tu_autotune.
|
|
- */
|
|
- struct cmd_buf_ctx {
|
|
- private:
|
|
- /* A batch of all entries from RPs within this CB. */
|
|
- std::shared_ptr<rp_entry_batch> batch;
|
|
+/* Necessary when writing sample counts using CP_EVENT_WRITE7::ZPASS_DONE. */
|
|
+static_assert(offsetof(struct tu_renderpass_samples, samples_end) == 16);
|
|
|
|
- /* Creates a new RP entry attached to this CB. */
|
|
- rp_entry *
|
|
- attach_rp_entry(struct tu_device *device, rp_history_handle &&history, config_t config, uint32_t draw_count);
|
|
+/**
|
|
+ * Tracks the results from an individual renderpass. Initially created
|
|
+ * per renderpass, and appended to the tail of at->pending_results. At a later
|
|
+ * time, when the GPU has finished writing the results, we fill samples_passed.
|
|
+ */
|
|
+struct tu_renderpass_result {
|
|
+ /* Points into GPU memory */
|
|
+ struct tu_renderpass_samples* samples;
|
|
|
|
- friend struct tu_autotune;
|
|
+ struct tu_suballoc_bo bo;
|
|
|
|
- public:
|
|
- cmd_buf_ctx();
|
|
- ~cmd_buf_ctx();
|
|
+ /*
|
|
+ * Below here, only used internally within autotune
|
|
+ */
|
|
+ uint64_t rp_key;
|
|
+ struct tu_renderpass_history *history;
|
|
+ struct list_head node;
|
|
+ uint32_t fence;
|
|
+ uint64_t samples_passed;
|
|
+};
|
|
|
|
- /* Resets the internal context, should be called when tu_cmd_buffer state has been reset. */
|
|
- void reset();
|
|
- };
|
|
+VkResult tu_autotune_init(struct tu_autotune *at, struct tu_device *dev);
|
|
+void tu_autotune_fini(struct tu_autotune *at, struct tu_device *dev);
|
|
|
|
- enum class render_mode {
|
|
- SYSMEM,
|
|
- GMEM,
|
|
- };
|
|
+bool tu_autotune_use_bypass(struct tu_autotune *at,
|
|
+ struct tu_cmd_buffer *cmd_buffer,
|
|
+ struct tu_renderpass_result **autotune_result);
|
|
+void tu_autotune_free_results(struct tu_device *dev, struct list_head *results);
|
|
|
|
- render_mode get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx);
|
|
+bool tu_autotune_submit_requires_fence(struct tu_cmd_buffer **cmd_buffers,
|
|
+ uint32_t cmd_buffer_count);
|
|
|
|
- void begin_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem);
|
|
+/**
|
|
+ * A magic 8-ball that tells the gmem code whether we should do bypass mode
|
|
+ * for moar fps.
|
|
+ */
|
|
+struct tu_cs *tu_autotune_on_submit(struct tu_device *dev,
|
|
+ struct tu_autotune *at,
|
|
+ struct tu_cmd_buffer **cmd_buffers,
|
|
+ uint32_t cmd_buffer_count);
|
|
|
|
- void end_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx);
|
|
+struct tu_autotune_results_buffer;
|
|
|
|
- /* The submit-time hook for autotuner, this may return a CS (can be NULL) which must be amended for autotuner
|
|
- * tracking to function correctly.
|
|
- *
|
|
- * Note: This must be called from a single-threaded context. There should never be multiple threads calling this
|
|
- * function at the same time.
|
|
- */
|
|
- struct tu_cs *on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_count);
|
|
-};
|
|
+template <chip CHIP>
|
|
+void tu_autotune_begin_renderpass(struct tu_cmd_buffer *cmd,
|
|
+ struct tu_cs *cs,
|
|
+ struct tu_renderpass_result *autotune_result);
|
|
+
|
|
+template <chip CHIP>
|
|
+void tu_autotune_end_renderpass(struct tu_cmd_buffer *cmd,
|
|
+ struct tu_cs *cs,
|
|
+ struct tu_renderpass_result *autotune_result);
|
|
|
|
-#endif /* TU_AUTOTUNE_H */
|
|
\ No newline at end of file
|
|
+#endif /* TU_AUTOTUNE_H */
|
|
diff --git a/src/freedreno/vulkan/tu_cmd_buffer.cc b/src/freedreno/vulkan/tu_cmd_buffer.cc
|
|
index 3fe6be1976d..a1a09f878cf 100644
|
|
--- a/src/freedreno/vulkan/tu_cmd_buffer.cc
|
|
+++ b/src/freedreno/vulkan/tu_cmd_buffer.cc
|
|
@@ -17,7 +17,6 @@
|
|
#include "common/freedreno_gpu_event.h"
|
|
#include "common/freedreno_lrz.h"
|
|
#include "common/freedreno_vrs.h"
|
|
-#include "tu_autotune.h"
|
|
#include "tu_buffer.h"
|
|
#include "tu_clear_blit.h"
|
|
#include "tu_cs.h"
|
|
@@ -1318,7 +1317,7 @@ use_hw_binning(struct tu_cmd_buffer *cmd)
|
|
|
|
static bool
|
|
use_sysmem_rendering(struct tu_cmd_buffer *cmd,
|
|
- tu_autotune::rp_ctx_t *rp_ctx)
|
|
+ struct tu_renderpass_result **autotune_result)
|
|
{
|
|
if (TU_DEBUG(SYSMEM)) {
|
|
cmd->state.rp.gmem_disable_reason = "TU_DEBUG(SYSMEM)";
|
|
@@ -1379,9 +1378,15 @@ use_sysmem_rendering(struct tu_cmd_buffer *cmd,
|
|
if (TU_DEBUG(GMEM))
|
|
return false;
|
|
|
|
- bool use_sysmem = cmd->device->autotune->get_optimal_mode(cmd, rp_ctx) == tu_autotune::render_mode::SYSMEM;
|
|
- if (use_sysmem)
|
|
+ bool use_sysmem = tu_autotune_use_bypass(&cmd->device->autotune,
|
|
+ cmd, autotune_result);
|
|
+ if (*autotune_result) {
|
|
+ list_addtail(&(*autotune_result)->node, &cmd->renderpass_autotune_results);
|
|
+ }
|
|
+
|
|
+ if (use_sysmem) {
|
|
cmd->state.rp.gmem_disable_reason = "Autotune selected sysmem";
|
|
+ }
|
|
|
|
return use_sysmem;
|
|
}
|
|
@@ -3126,7 +3131,7 @@ tu7_emit_concurrent_binning_sysmem(struct tu_cmd_buffer *cmd,
|
|
template <chip CHIP>
|
|
static void
|
|
tu6_sysmem_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
- tu_autotune::rp_ctx_t rp_ctx)
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
const struct tu_framebuffer *fb = cmd->state.framebuffer;
|
|
|
|
@@ -3179,7 +3184,7 @@ tu6_sysmem_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
tu_cs_emit_regs(cs, RB_BIN_FOVEAT(CHIP));
|
|
}
|
|
|
|
- cmd->device->autotune->begin_renderpass(cmd, cs, rp_ctx, true);
|
|
+ tu_autotune_begin_renderpass<CHIP>(cmd, cs, autotune_result);
|
|
|
|
tu_cs_sanity_check(cs);
|
|
}
|
|
@@ -3187,7 +3192,7 @@ tu6_sysmem_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
template <chip CHIP>
|
|
static void
|
|
tu6_sysmem_render_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
- tu_autotune::rp_ctx_t rp_ctx)
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
/* Do any resolves of the last subpass. These are handled in the
|
|
* tile_store_cs in the gmem path.
|
|
@@ -3227,7 +3232,7 @@ tu6_sysmem_render_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
tu_cs_emit(cs, 0); /* value */
|
|
}
|
|
|
|
- cmd->device->autotune->end_renderpass(cmd, cs, rp_ctx);
|
|
+ tu_autotune_end_renderpass<CHIP>(cmd, cs, autotune_result);
|
|
|
|
tu_cs_sanity_check(cs);
|
|
}
|
|
@@ -3377,7 +3382,7 @@ tu7_emit_concurrent_binning_gmem(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
template <chip CHIP>
|
|
static void
|
|
tu6_tile_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
- tu_autotune::rp_ctx_t rp_ctx,
|
|
+ struct tu_renderpass_result *autotune_result,
|
|
const VkOffset2D *fdm_offsets)
|
|
{
|
|
struct tu_physical_device *phys_dev = cmd->device->physical_device;
|
|
@@ -3556,7 +3561,7 @@ tu6_tile_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
if (use_cb)
|
|
tu_trace_start_render_pass(cmd);
|
|
|
|
- cmd->device->autotune->begin_renderpass(cmd, cs, rp_ctx, false);
|
|
+ tu_autotune_begin_renderpass<CHIP>(cmd, cs, autotune_result);
|
|
|
|
tu_cs_sanity_check(cs);
|
|
}
|
|
@@ -3619,7 +3624,7 @@ tu6_render_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
template <chip CHIP>
|
|
static void
|
|
tu6_tile_render_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
- tu_autotune::rp_ctx_t rp_ctx)
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
tu_cs_emit_call(cs, &cmd->draw_epilogue_cs);
|
|
|
|
@@ -3649,7 +3654,7 @@ tu6_tile_render_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs,
|
|
|
|
tu_emit_event_write<CHIP>(cmd, cs, FD_CCU_CLEAN_BLIT_CACHE);
|
|
|
|
- cmd->device->autotune->end_renderpass(cmd, cs, rp_ctx);
|
|
+ tu_autotune_end_renderpass<CHIP>(cmd, cs, autotune_result);
|
|
|
|
tu_cs_sanity_check(cs);
|
|
}
|
|
@@ -3758,7 +3763,7 @@ tu_emit_subsampled(struct tu_cmd_buffer *cmd,
|
|
template <chip CHIP>
|
|
static void
|
|
tu_cmd_render_tiles(struct tu_cmd_buffer *cmd,
|
|
- tu_autotune::rp_ctx_t rp_ctx,
|
|
+ struct tu_renderpass_result *autotune_result,
|
|
const VkOffset2D *fdm_offsets)
|
|
{
|
|
const struct tu_tiling_config *tiling = cmd->state.tiling;
|
|
@@ -3799,7 +3804,7 @@ tu_cmd_render_tiles(struct tu_cmd_buffer *cmd,
|
|
tu6_emit_tile_store_cs<CHIP>(cmd, &cmd->tile_store_cs);
|
|
tu_cs_end(&cmd->tile_store_cs);
|
|
|
|
- tu6_tile_render_begin<CHIP>(cmd, &cmd->cs, rp_ctx, fdm_offsets);
|
|
+ tu6_tile_render_begin<CHIP>(cmd, &cmd->cs, autotune_result, fdm_offsets);
|
|
|
|
/* Note: we reverse the order of walking the pipes and tiles on every
|
|
* other row, to improve texture cache locality compared to raster order.
|
|
@@ -3852,7 +3857,7 @@ tu_cmd_render_tiles(struct tu_cmd_buffer *cmd,
|
|
}
|
|
}
|
|
|
|
- tu6_tile_render_end<CHIP>(cmd, &cmd->cs, rp_ctx);
|
|
+ tu6_tile_render_end<CHIP>(cmd, &cmd->cs, autotune_result);
|
|
|
|
/* Outside of renderpasses we assume all draw states are disabled. We do
|
|
* this outside the draw CS for the normal case where 3d gmem stores aren't
|
|
@@ -3885,7 +3890,7 @@ tu_cmd_render_tiles(struct tu_cmd_buffer *cmd,
|
|
template <chip CHIP>
|
|
static void
|
|
tu_cmd_render_sysmem(struct tu_cmd_buffer *cmd,
|
|
- tu_autotune::rp_ctx_t rp_ctx)
|
|
+ struct tu_renderpass_result *autotune_result)
|
|
{
|
|
VkResult result = tu_allocate_transient_attachments(cmd, true);
|
|
|
|
@@ -3896,7 +3901,7 @@ tu_cmd_render_sysmem(struct tu_cmd_buffer *cmd,
|
|
|
|
tu_trace_start_render_pass(cmd);
|
|
|
|
- tu6_sysmem_render_begin<CHIP>(cmd, &cmd->cs, rp_ctx);
|
|
+ tu6_sysmem_render_begin<CHIP>(cmd, &cmd->cs, autotune_result);
|
|
|
|
trace_start_draw_ib_sysmem(&cmd->trace, &cmd->cs, cmd);
|
|
|
|
@@ -3904,7 +3909,7 @@ tu_cmd_render_sysmem(struct tu_cmd_buffer *cmd,
|
|
|
|
trace_end_draw_ib_sysmem(&cmd->trace, &cmd->cs);
|
|
|
|
- tu6_sysmem_render_end<CHIP>(cmd, &cmd->cs, rp_ctx);
|
|
+ tu6_sysmem_render_end<CHIP>(cmd, &cmd->cs, autotune_result);
|
|
|
|
/* Outside of renderpasses we assume all draw states are disabled. */
|
|
tu_disable_draw_states(cmd, &cmd->cs);
|
|
@@ -3924,11 +3929,11 @@ tu_cmd_render(struct tu_cmd_buffer *cmd_buffer,
|
|
if (cmd_buffer->state.rp.has_tess)
|
|
tu6_lazy_emit_tessfactor_addr<CHIP>(cmd_buffer);
|
|
|
|
- tu_autotune::rp_ctx_t rp_ctx = NULL;
|
|
- if (use_sysmem_rendering(cmd_buffer, &rp_ctx))
|
|
- tu_cmd_render_sysmem<CHIP>(cmd_buffer, rp_ctx);
|
|
+ struct tu_renderpass_result *autotune_result = NULL;
|
|
+ if (use_sysmem_rendering(cmd_buffer, &autotune_result))
|
|
+ tu_cmd_render_sysmem<CHIP>(cmd_buffer, autotune_result);
|
|
else
|
|
- tu_cmd_render_tiles<CHIP>(cmd_buffer, rp_ctx, fdm_offsets);
|
|
+ tu_cmd_render_tiles<CHIP>(cmd_buffer, autotune_result, fdm_offsets);
|
|
}
|
|
|
|
static void tu_reset_render_pass(struct tu_cmd_buffer *cmd_buffer)
|
|
@@ -3994,7 +3999,7 @@ tu_create_cmd_buffer(struct vk_command_pool *pool,
|
|
u_trace_init(&cmd_buffer->rp_trace, &device->trace_context);
|
|
cmd_buffer->trace_renderpass_start =
|
|
u_trace_begin_iterator(&cmd_buffer->rp_trace);
|
|
- new (&cmd_buffer->autotune_ctx) tu_autotune::cmd_buf_ctx();
|
|
+ list_inithead(&cmd_buffer->renderpass_autotune_results);
|
|
|
|
if (TU_DEBUG_START(CHECK_CMD_BUFFER_STATUS)) {
|
|
cmd_buffer->status_bo = tu_cmd_buffer_setup_status_tracking(device);
|
|
@@ -4043,7 +4048,7 @@ tu_cmd_buffer_destroy(struct vk_command_buffer *vk_cmd_buffer)
|
|
u_trace_fini(&cmd_buffer->trace);
|
|
u_trace_fini(&cmd_buffer->rp_trace);
|
|
|
|
- cmd_buffer->autotune_ctx.~cmd_buf_ctx();
|
|
+ tu_autotune_free_results(cmd_buffer->device, &cmd_buffer->renderpass_autotune_results);
|
|
|
|
for (unsigned i = 0; i < MAX_BIND_POINTS; i++) {
|
|
if (cmd_buffer->descriptors[i].push_set.layout)
|
|
@@ -4120,7 +4125,7 @@ tu_reset_cmd_buffer(struct vk_command_buffer *vk_cmd_buffer,
|
|
tu_cs_reset(&cmd_buffer->pre_chain.draw_cs);
|
|
tu_cs_reset(&cmd_buffer->pre_chain.draw_epilogue_cs);
|
|
|
|
- cmd_buffer->autotune_ctx.reset();
|
|
+ tu_autotune_free_results(cmd_buffer->device, &cmd_buffer->renderpass_autotune_results);
|
|
|
|
for (unsigned i = 0; i < MAX_BIND_POINTS; i++) {
|
|
memset(&cmd_buffer->descriptors[i].sets, 0, sizeof(cmd_buffer->descriptors[i].sets));
|
|
diff --git a/src/freedreno/vulkan/tu_cmd_buffer.h b/src/freedreno/vulkan/tu_cmd_buffer.h
|
|
index 614747bb492..10e3198759b 100644
|
|
--- a/src/freedreno/vulkan/tu_cmd_buffer.h
|
|
+++ b/src/freedreno/vulkan/tu_cmd_buffer.h
|
|
@@ -653,7 +653,8 @@ struct tu_cmd_buffer
|
|
struct u_trace_iterator trace_renderpass_start;
|
|
struct u_trace trace, rp_trace;
|
|
|
|
- tu_autotune::cmd_buf_ctx autotune_ctx;
|
|
+ struct list_head renderpass_autotune_results;
|
|
+ struct tu_autotune_results_buffer* autotune_buffer;
|
|
|
|
void *patchpoints_ctx;
|
|
struct util_dynarray fdm_bin_patchpoints;
|
|
diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc
|
|
index fda649af6af..38da9a54ce2 100644
|
|
--- a/src/freedreno/vulkan/tu_device.cc
|
|
+++ b/src/freedreno/vulkan/tu_device.cc
|
|
@@ -2701,6 +2701,7 @@ tu_device_destroy_mutexes(struct tu_device *device)
|
|
{
|
|
mtx_destroy(&device->bo_mutex);
|
|
mtx_destroy(&device->pipeline_mutex);
|
|
+ mtx_destroy(&device->autotune_mutex);
|
|
mtx_destroy(&device->kgsl_profiling_mutex);
|
|
mtx_destroy(&device->event_mutex);
|
|
mtx_destroy(&device->trace_mutex);
|
|
@@ -2814,6 +2815,7 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice,
|
|
|
|
mtx_init(&device->bo_mutex, mtx_plain);
|
|
mtx_init(&device->pipeline_mutex, mtx_plain);
|
|
+ mtx_init(&device->autotune_mutex, mtx_plain);
|
|
mtx_init(&device->kgsl_profiling_mutex, mtx_plain);
|
|
mtx_init(&device->event_mutex, mtx_plain);
|
|
mtx_init(&device->trace_mutex, mtx_plain);
|
|
@@ -2938,6 +2940,9 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice,
|
|
TU_BO_ALLOC_ALLOW_DUMP |
|
|
TU_BO_ALLOC_INTERNAL_RESOURCE),
|
|
"pipeline_suballoc");
|
|
+ tu_bo_suballocator_init(&device->autotune_suballoc, device,
|
|
+ 128 * 1024, TU_BO_ALLOC_INTERNAL_RESOURCE,
|
|
+ "autotune_suballoc");
|
|
if (is_kgsl(physical_device->instance)) {
|
|
tu_bo_suballocator_init(&device->kgsl_profiling_suballoc, device,
|
|
128 * 1024, TU_BO_ALLOC_INTERNAL_RESOURCE,
|
|
@@ -3085,9 +3090,10 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice,
|
|
}
|
|
pthread_condattr_destroy(&condattr);
|
|
|
|
- device->autotune = new tu_autotune(device, result);
|
|
- if (result != VK_SUCCESS)
|
|
+ result = tu_autotune_init(&device->autotune, device);
|
|
+ if (result != VK_SUCCESS) {
|
|
goto fail_timeline_cond;
|
|
+ }
|
|
|
|
device->use_z24uint_s8uint =
|
|
physical_device->info->props.has_z24uint_s8uint &&
|
|
@@ -3245,9 +3251,10 @@ tu_DestroyDevice(VkDevice _device, const VkAllocationCallbacks *pAllocator)
|
|
free(device->dbg_renderpass_stomp_cs);
|
|
}
|
|
|
|
- delete device->autotune;
|
|
+ tu_autotune_fini(&device->autotune, device);
|
|
|
|
tu_bo_suballocator_finish(&device->pipeline_suballoc);
|
|
+ tu_bo_suballocator_finish(&device->autotune_suballoc);
|
|
tu_bo_suballocator_finish(&device->kgsl_profiling_suballoc);
|
|
tu_bo_suballocator_finish(&device->event_suballoc);
|
|
tu_bo_suballocator_finish(&device->vis_stream_suballocator);
|
|
diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h
|
|
index 9665135e0e6..4518f6be6d6 100644
|
|
--- a/src/freedreno/vulkan/tu_device.h
|
|
+++ b/src/freedreno/vulkan/tu_device.h
|
|
@@ -13,7 +13,6 @@
|
|
#include "tu_common.h"
|
|
|
|
#include "radix_sort/radix_sort_vk.h"
|
|
-#include "util/rwlock.h"
|
|
#include "util/u_vector.h"
|
|
#include "util/vma.h"
|
|
#include "vk_device_memory.h"
|
|
@@ -266,12 +265,7 @@ struct tu6_global
|
|
|
|
volatile uint32_t vtx_stats_query_not_running;
|
|
|
|
- /* A fence with a monotonically increasing value that is
|
|
- * incremented by the GPU on each submission that includes
|
|
- * a tu_autotune::submission_entry CS. This is used to track
|
|
- * which submissions have been processed by the GPU before
|
|
- * processing the autotune packet on the CPU.
|
|
- */
|
|
+ /* To know when renderpass stats for autotune are valid */
|
|
volatile uint32_t autotune_fence;
|
|
|
|
/* For recycling command buffers for dynamic suspend/resume comamnds */
|
|
@@ -360,6 +354,12 @@ struct tu_device
|
|
struct tu_suballocator pipeline_suballoc;
|
|
mtx_t pipeline_mutex;
|
|
|
|
+ /* Device-global BO suballocator for reducing BO management for small
|
|
+ * gmem/sysmem autotune result buffers. Synchronized by autotune_mutex.
|
|
+ */
|
|
+ struct tu_suballocator autotune_suballoc;
|
|
+ mtx_t autotune_mutex;
|
|
+
|
|
/* KGSL requires a small chunk of GPU mem to retrieve raw GPU time on
|
|
* each submission.
|
|
*/
|
|
@@ -457,7 +457,7 @@ struct tu_device
|
|
pthread_cond_t timeline_cond;
|
|
pthread_mutex_t submit_mutex;
|
|
|
|
- struct tu_autotune *autotune;
|
|
+ struct tu_autotune autotune;
|
|
|
|
struct breadcrumbs_context *breadcrumbs_ctx;
|
|
|
|
diff --git a/src/freedreno/vulkan/tu_pass.cc b/src/freedreno/vulkan/tu_pass.cc
|
|
index 735707d8a1b..bad05d33406 100644
|
|
--- a/src/freedreno/vulkan/tu_pass.cc
|
|
+++ b/src/freedreno/vulkan/tu_pass.cc
|
|
@@ -549,6 +549,27 @@ tu_render_pass_disable_fdm(struct tu_device *dev, struct tu_render_pass *pass)
|
|
return false;
|
|
}
|
|
|
|
+static void
|
|
+tu_render_pass_calc_hash(struct tu_render_pass *pass)
|
|
+{
|
|
+ #define HASH(hash, data) XXH64(&(data), sizeof(data), hash)
|
|
+
|
|
+ uint64_t hash = HASH(0, pass->attachment_count);
|
|
+ hash = XXH64(pass->attachments,
|
|
+ pass->attachment_count * sizeof(pass->attachments[0]), hash);
|
|
+ hash = HASH(hash, pass->subpass_count);
|
|
+ for (unsigned i = 0; i < pass->subpass_count; i++) {
|
|
+ hash = HASH(hash, pass->subpasses[i].samples);
|
|
+ hash = HASH(hash, pass->subpasses[i].input_count);
|
|
+ hash = HASH(hash, pass->subpasses[i].color_count);
|
|
+ hash = HASH(hash, pass->subpasses[i].resolve_count);
|
|
+ }
|
|
+
|
|
+ pass->autotune_hash = hash;
|
|
+
|
|
+ #undef HASH
|
|
+}
|
|
+
|
|
static void
|
|
tu_render_pass_cond_config(struct tu_device *device,
|
|
struct tu_render_pass *pass)
|
|
@@ -1333,6 +1354,7 @@ tu_CreateRenderPass2(VkDevice _device,
|
|
tu_render_pass_gmem_config(pass, device->physical_device);
|
|
tu_render_pass_bandwidth_config(pass);
|
|
tu_render_pass_calc_views(pass);
|
|
+ tu_render_pass_calc_hash(pass);
|
|
|
|
for (unsigned i = 0; i < pCreateInfo->dependencyCount; ++i) {
|
|
tu_render_pass_add_subpass_dep(pass, &pCreateInfo->pDependencies[i]);
|
|
@@ -1812,6 +1834,7 @@ tu_setup_dynamic_render_pass(struct tu_cmd_buffer *cmd_buffer,
|
|
tu_render_pass_gmem_config(pass, device->physical_device);
|
|
tu_render_pass_bandwidth_config(pass);
|
|
tu_render_pass_calc_views(pass);
|
|
+ tu_render_pass_calc_hash(pass);
|
|
}
|
|
|
|
void
|
|
diff --git a/src/freedreno/vulkan/tu_queue.cc b/src/freedreno/vulkan/tu_queue.cc
|
|
index 97ea5701865..fd2257fbb49 100644
|
|
--- a/src/freedreno/vulkan/tu_queue.cc
|
|
+++ b/src/freedreno/vulkan/tu_queue.cc
|
|
@@ -418,7 +418,6 @@ queue_submit(struct vk_queue *_queue, struct vk_queue_submit *vk_submit)
|
|
struct tu_device *device = queue->device;
|
|
bool u_trace_enabled = u_trace_should_process(&queue->device->trace_context);
|
|
struct util_dynarray dump_cmds;
|
|
- struct tu_cs *autotune_cs = NULL;
|
|
|
|
if (vk_submit->buffer_bind_count ||
|
|
vk_submit->image_bind_count ||
|
|
@@ -496,8 +495,9 @@ queue_submit(struct vk_queue *_queue, struct vk_queue_submit *vk_submit)
|
|
}
|
|
}
|
|
|
|
- autotune_cs = device->autotune->on_submit(cmd_buffers, cmdbuf_count);
|
|
- if (autotune_cs) {
|
|
+ if (tu_autotune_submit_requires_fence(cmd_buffers, cmdbuf_count)) {
|
|
+ struct tu_cs *autotune_cs = tu_autotune_on_submit(
|
|
+ device, &device->autotune, cmd_buffers, cmdbuf_count);
|
|
submit_add_entries(device, submit, &dump_cmds, autotune_cs->entries,
|
|
autotune_cs->entry_count);
|
|
}
|
|
--
|
|
2.54.0
|
|
|