diff --git a/.gitignore b/.gitignore index 7ba333c..6dd9f46 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ mesa-*/ /rustc-hash-*.tar.gz /wayland-protocols-*.tar.xz +/llvm-project-22.1.1.src.tar.xz diff --git a/0001-Fix-build-with-python-3.9.patch b/0001-Fix-build-with-python-3.9.patch new file mode 100644 index 0000000..f2730da --- /dev/null +++ b/0001-Fix-build-with-python-3.9.patch @@ -0,0 +1,255 @@ +From 8aee5837d69f15322a5d9ca9a9df4e2dd77554a5 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 19 Jun 2026 13:44:02 +0200 +Subject: [PATCH 01/19] Fix build with python 3.9 + +Signed-off-by: Jocelyn Falempe +--- + meson.build | 2 +- + src/intel/compiler/jay/jay_opcodes.py | 2 +- + src/vulkan/util/vk_cmd_queue_gen.py | 195 +++++++++++++------------- + 3 files changed, 99 insertions(+), 100 deletions(-) + +diff --git a/meson.build b/meson.build +index 392f147f365..b6aec7f8d93 100644 +--- a/meson.build ++++ b/meson.build +@@ -1037,7 +1037,7 @@ endif + # Find a python executable that meets our version requirement. + # - On Windows, a venv has no versioned aliased to 'python'. + # - On RHEL 9, python3 is 3.9, so we must use python3.12. +-python_version_req = '>= 3.10' ++python_version_req = '>= 3.9' + python_exec_list = ['python3.16', 'python3.15', 'python3.14', 'python3.13', + 'python3.12', 'python3.11', 'python3.10', 'python3', 'python'] + +diff --git a/src/intel/compiler/jay/jay_opcodes.py b/src/intel/compiler/jay/jay_opcodes.py +index 029b087ed7c..4951e875ac7 100644 +--- a/src/intel/compiler/jay/jay_opcodes.py ++++ b/src/intel/compiler/jay/jay_opcodes.py +@@ -1,6 +1,6 @@ + # Copyright 2026 Intel Corporation + # SPDX-License-Identifier: MIT +- ++from __future__ import annotations + from typing import TYPE_CHECKING + from dataclasses import dataclass + import enum +diff --git a/src/vulkan/util/vk_cmd_queue_gen.py b/src/vulkan/util/vk_cmd_queue_gen.py +index 4c74158f11e..2468d9bc098 100644 +--- a/src/vulkan/util/vk_cmd_queue_gen.py ++++ b/src/vulkan/util/vk_cmd_queue_gen.py +@@ -644,113 +644,112 @@ def get_param_copy(builder, command, types, src_parent_access, dst_parent_access + src = src_parent_access + param.name + dst = dst_parent_access + (to_field_name(param.name) if dst_snake_case else param.name) + +- match categorize_param(command, types, None, param): +- case ParamCategory.ASSIGNABLE: +- builder.add("%s = %s;" % (dst, src)) +- case ParamCategory.FLAT_ARRAY: +- builder.add("memcpy(%s, %s, sizeof(*%s) * %s);" % (dst, src, src, get_array_len(param))) +- case ParamCategory.UNSIZED_RAW_POINTER: +- builder.add("%s = (%s)%s;" % (dst, remove_suffix(param.decl.replace("const", ""), param.name), src)) +- case ParamCategory.STRING: +- builder.add("%s = linear_strdup(queue->ctx, %s);" % (dst, src)) +- case ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE_DATA: +- builder.add("%s = enqueue_push_descriptor_template_data(queue, %sdescriptorUpdateTemplate, %s);" % (dst, src_parent_access, src)) +- case ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE: +- builder.add("%s = %s;" % (dst, src)) +- builder.add("enqueue_descriptor_template(queue, %s);" % (src)) +- case ParamCategory.PIPELINE_LAYOUT: +- builder.add("%s = %s;" % (dst, src)) +- builder.add("enqueue_pipeline_layout(queue, %s);" % (src)) +- case ParamCategory.STRUCT: +- +- if nullable: +- builder.add("if (%s) {" % (src)) +- builder.level += 1 +- +- if param.type == "void": +- size = 1 +- else: +- size = "sizeof(%s)" % param.type +- +- is_ndarray = param.len and "," in param.len +- if param.len and param.len != "struct-ptr" and not is_ndarray: +- size = "%s * ceil(%s%s)" % (size, src_parent_access, param.len) +- +- builder.add("%s = linear_alloc_child(queue->ctx, %s);" % (dst, size)) +- builder.add("if (%s == NULL) return NULL;" % (dst)) +- builder.add("memcpy((void *)%s, %s, %s);" % (dst, src, size)) +- if param.type == 'VkDescriptorSetLayout': +- array_index = builder.get_variable_name("i") +- builder.add("for (unsigned %s = 0; %s < %s%s; %s++) {" % (array_index, array_index, src_parent_access, param.len, array_index)) +- builder.level += 1 +- builder.add("enqueue_descriptor_layout(queue, %s[%s]);" % (src, array_index)) +- builder.level -= 1 +- builder.add("}") +- +- if param.type in types: +- has_explicit_copy = param.type in EXPLICIT_PARAM_COPIES +- needs_member_copy = has_explicit_copy +- for member in types[param.type].members: +- match categorize_param(command, types, param.type, member): +- case ParamCategory.PNEXT | ParamCategory.STRUCT | ParamCategory.STRING: +- needs_member_copy = True +- case ParamCategory.PIPELINE_LAYOUT: +- builder.add("enqueue_pipeline_layout(queue, %s->%s);" % (src, member.name)) +- case ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE: +- builder.add("enqueue_descriptor_template(queue, %s->%s);" % (src, member.name)) +- +- if needs_member_copy: ++ cat_param = categorize_param(command, types, None, param) ++ if cat_param == ParamCategory.ASSIGNABLE: ++ builder.add("%s = %s;" % (dst, src)) ++ elif cat_param == ParamCategory.FLAT_ARRAY: ++ builder.add("memcpy(%s, %s, sizeof(*%s) * %s);" % (dst, src, src, get_array_len(param))) ++ elif cat_param == ParamCategory.UNSIZED_RAW_POINTER: ++ builder.add("%s = (%s)%s;" % (dst, remove_suffix(param.decl.replace("const", ""), param.name), src)) ++ elif cat_param == ParamCategory.STRING: ++ builder.add("%s = linear_strdup(queue->ctx, %s);" % (dst, src)) ++ elif cat_param == ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE_DATA: ++ builder.add("%s = enqueue_push_descriptor_template_data(queue, %sdescriptorUpdateTemplate, %s);" % (dst, src_parent_access, src)) ++ elif cat_param == ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE: ++ builder.add("%s = %s;" % (dst, src)) ++ builder.add("enqueue_descriptor_template(queue, %s);" % (src)) ++ elif cat_param == ParamCategory.PIPELINE_LAYOUT: ++ builder.add("%s = %s;" % (dst, src)) ++ builder.add("enqueue_pipeline_layout(queue, %s);" % (src)) ++ elif cat_param == ParamCategory.STRUCT: ++ if nullable: ++ builder.add("if (%s) {" % (src)) ++ builder.level += 1 ++ ++ if param.type == "void": ++ size = 1 ++ else: ++ size = "sizeof(%s)" % param.type ++ ++ is_ndarray = param.len and "," in param.len ++ if param.len and param.len != "struct-ptr" and not is_ndarray: ++ size = "%s * ceil(%s%s)" % (size, src_parent_access, param.len) ++ ++ builder.add("%s = linear_alloc_child(queue->ctx, %s);" % (dst, size)) ++ builder.add("if (%s == NULL) return NULL;" % (dst)) ++ builder.add("memcpy((void *)%s, %s, %s);" % (dst, src, size)) ++ if param.type == 'VkDescriptorSetLayout': ++ array_index = builder.get_variable_name("i") ++ builder.add("for (unsigned %s = 0; %s < %s%s; %s++) {" % (array_index, array_index, src_parent_access, param.len, array_index)) ++ builder.level += 1 ++ builder.add("enqueue_descriptor_layout(queue, %s[%s]);" % (src, array_index)) ++ builder.level -= 1 ++ builder.add("}") ++ ++ if param.type in types: ++ has_explicit_copy = param.type in EXPLICIT_PARAM_COPIES ++ needs_member_copy = has_explicit_copy ++ for member in types[param.type].members: ++ cat_param_type = categorize_param(command, types, param.type, member) ++ if cat_param_type == ParamCategory.PNEXT or cat_param_type == ParamCategory.STRUCT or cat_param_type == ParamCategory.STRING: ++ needs_member_copy = True ++ elif cat_param_type == ParamCategory.PIPELINE_LAYOUT: ++ builder.add("enqueue_pipeline_layout(queue, %s->%s);" % (src, member.name)) ++ elif cat_param_type == ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE: ++ builder.add("enqueue_descriptor_template(queue, %s->%s);" % (src, member.name)) ++ ++ if needs_member_copy: ++ tmp_dst_name = builder.get_variable_name("tmp_dst") ++ tmp_src_name = builder.get_variable_name("tmp_src") ++ ++ builder.add("%s *%s = (void *)%s;" % (param.type, tmp_dst_name, dst)) ++ builder.add("%s *%s = (void *)%s;" % (param.type, tmp_src_name, src)) ++ ++ struct_array_copy = param.len and param.len != "struct-ptr" and param.type != "void" ++ if struct_array_copy: ++ array_index = builder.get_variable_name("i") ++ builder.add("for (uint32_t %s = 0; %s < %s%s; %s++) {" % (array_index, array_index, src_parent_access, param.len, array_index)) ++ builder.level += 1 ++ prev_tmp_dst_name = tmp_dst_name ++ prev_tmp_src_name = tmp_src_name + tmp_dst_name = builder.get_variable_name("tmp_dst") + tmp_src_name = builder.get_variable_name("tmp_src") ++ builder.add("%s *%s = %s + %s;" % (param.type, tmp_dst_name, prev_tmp_dst_name, array_index)) ++ builder.add("%s *%s = %s + %s;" % (param.type, tmp_src_name, prev_tmp_src_name, array_index)) + +- builder.add("%s *%s = (void *)%s;" % (param.type, tmp_dst_name, dst)) +- builder.add("%s *%s = (void *)%s;" % (param.type, tmp_src_name, src)) +- +- struct_array_copy = param.len and param.len != "struct-ptr" and param.type != "void" +- if struct_array_copy: +- array_index = builder.get_variable_name("i") +- builder.add("for (uint32_t %s = 0; %s < %s%s; %s++) {" % (array_index, array_index, src_parent_access, param.len, array_index)) +- builder.level += 1 +- prev_tmp_dst_name = tmp_dst_name +- prev_tmp_src_name = tmp_src_name +- tmp_dst_name = builder.get_variable_name("tmp_dst") +- tmp_src_name = builder.get_variable_name("tmp_src") +- builder.add("%s *%s = %s + %s;" % (param.type, tmp_dst_name, prev_tmp_dst_name, array_index)) +- builder.add("%s *%s = %s + %s;" % (param.type, tmp_src_name, prev_tmp_src_name, array_index)) +- ++ for member in types[param.type].members: ++ category = categorize_param(command, types, param.type, member) ++ if category == ParamCategory.PNEXT: ++ get_pnext_copy(builder, command, types, param.type, "%s->pNext" % (tmp_src_name), "%s->pNext" % (tmp_dst_name)) ++ elif category == ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE_DATA: ++ get_param_copy(builder, command, types, "%s->" % (tmp_src_name), "%s->" % (tmp_dst_name), member, dst_initialized=True) ++ ++ if has_explicit_copy: ++ builder.add("enqueue_%s(queue, %s, %s);" % (param.type, tmp_dst_name, tmp_src_name)) ++ else: + for member in types[param.type].members: + category = categorize_param(command, types, param.type, member) +- if category == ParamCategory.PNEXT: +- get_pnext_copy(builder, command, types, param.type, "%s->pNext" % (tmp_src_name), "%s->pNext" % (tmp_dst_name)) +- elif category == ParamCategory.DESCRIPTOR_UPDATE_TEMPLATE_DATA: ++ if category == ParamCategory.STRUCT or category == ParamCategory.STRING: + get_param_copy(builder, command, types, "%s->" % (tmp_src_name), "%s->" % (tmp_dst_name), member, dst_initialized=True) + +- if has_explicit_copy: +- builder.add("enqueue_%s(queue, %s, %s);" % (param.type, tmp_dst_name, tmp_src_name)) +- else: +- for member in types[param.type].members: +- category = categorize_param(command, types, param.type, member) +- if category == ParamCategory.STRUCT or category == ParamCategory.STRING: +- get_param_copy(builder, command, types, "%s->" % (tmp_src_name), "%s->" % (tmp_dst_name), member, dst_initialized=True) +- +- if struct_array_copy: +- builder.level -= 1 +- builder.add("}") +- +- if nullable: +- builder.level -= 1 +- if dst_initialized: +- builder.add("}") +- else: +- builder.add("} else {") +- builder.level += 1 +- builder.add("%s = NULL;" % (dst)) ++ if struct_array_copy: + builder.level -= 1 + builder.add("}") +- case ParamCategory.NULL: +- assert False +- case ParamCategory.PNEXT: +- assert False ++ ++ if nullable: ++ builder.level -= 1 ++ if dst_initialized: ++ builder.add("}") ++ else: ++ builder.add("} else {") ++ builder.level += 1 ++ builder.add("%s = NULL;" % (dst)) ++ builder.level -= 1 ++ builder.add("}") ++ elif cat_param == ParamCategory.NULL: ++ assert False ++ elif cat_param == ParamCategory.PNEXT: ++ assert False + + def get_params_copy(command, types): + builder = CodeBuilder(1) +-- +2.54.0 + diff --git a/0001-Revert-dri-fix-__DRI_IMAGE_FORMAT-to-PIPE_FORMAT-map.patch b/0001-Revert-dri-fix-__DRI_IMAGE_FORMAT-to-PIPE_FORMAT-map.patch deleted file mode 100644 index cb45b1d..0000000 --- a/0001-Revert-dri-fix-__DRI_IMAGE_FORMAT-to-PIPE_FORMAT-map.patch +++ /dev/null @@ -1,59 +0,0 @@ -From 102d282d8add081f5f1aab35974218f151744ac5 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= -Date: Mon, 23 Feb 2026 08:40:07 +0100 -Subject: [PATCH] Revert "dri: fix __DRI_IMAGE_FORMAT* to PIPE_FORMAT* - mappings" - -This reverts commit 2ae8d0362bec12e84f787f226d2ba7a18130084f. ---- - src/loader/loader_dri_helper.h | 18 ++++++++++-------- - 1 file changed, 10 insertions(+), 8 deletions(-) - -diff --git a/src/loader/loader_dri_helper.h b/src/loader/loader_dri_helper.h -index 169e36b5d80..0d801e99648 100644 ---- a/src/loader/loader_dri_helper.h -+++ b/src/loader/loader_dri_helper.h -@@ -61,8 +61,10 @@ struct loader_screen_resources { - - - /** -- * These formats are endian independent they result in the same layout -- * regradless of a big or little endian cpu. -+ * These formats correspond to the similarly named MESA_FORMAT_* -+ * tokens, except in the native endian of the CPU. For example, on -+ * little endian __DRI_IMAGE_FORMAT_XRGB8888 corresponds to -+ * MESA_FORMAT_XRGB8888, but MESA_FORMAT_XRGB8888_REV on big endian. - * - * __DRI_IMAGE_FORMAT_NONE is for images that aren't directly usable - * by the driver (YUV planar formats) but serve as a base image for -@@ -73,21 +75,21 @@ struct loader_screen_resources { - * createImageFromNames (NONE, see above) and fromPlane (R8 & GR88). - */ - #define __DRI_IMAGE_FORMAT_RGB565 PIPE_FORMAT_B5G6R5_UNORM --#define __DRI_IMAGE_FORMAT_XRGB8888 PIPE_FORMAT_B8G8R8X8_UNORM --#define __DRI_IMAGE_FORMAT_ARGB8888 PIPE_FORMAT_B8G8R8A8_UNORM --#define __DRI_IMAGE_FORMAT_ABGR8888 PIPE_FORMAT_R8G8B8A8_UNORM --#define __DRI_IMAGE_FORMAT_XBGR8888 PIPE_FORMAT_R8G8B8X8_UNORM -+#define __DRI_IMAGE_FORMAT_XRGB8888 PIPE_FORMAT_BGRX8888_UNORM -+#define __DRI_IMAGE_FORMAT_ARGB8888 PIPE_FORMAT_BGRA8888_UNORM -+#define __DRI_IMAGE_FORMAT_ABGR8888 PIPE_FORMAT_RGBA8888_UNORM -+#define __DRI_IMAGE_FORMAT_XBGR8888 PIPE_FORMAT_RGBX8888_UNORM - #define __DRI_IMAGE_FORMAT_RGB888 PIPE_FORMAT_B8G8R8_UNORM - #define __DRI_IMAGE_FORMAT_BGR888 PIPE_FORMAT_R8G8B8_UNORM - #define __DRI_IMAGE_FORMAT_R8 PIPE_FORMAT_R8_UNORM --#define __DRI_IMAGE_FORMAT_GR88 PIPE_FORMAT_R8G8_UNORM -+#define __DRI_IMAGE_FORMAT_GR88 PIPE_FORMAT_RG88_UNORM - #define __DRI_IMAGE_FORMAT_NONE PIPE_FORMAT_NONE - #define __DRI_IMAGE_FORMAT_XRGB2101010 PIPE_FORMAT_B10G10R10X2_UNORM - #define __DRI_IMAGE_FORMAT_ARGB2101010 PIPE_FORMAT_B10G10R10A2_UNORM - #define __DRI_IMAGE_FORMAT_SARGB8 PIPE_FORMAT_BGRA8888_SRGB - #define __DRI_IMAGE_FORMAT_ARGB1555 PIPE_FORMAT_B5G5R5A1_UNORM - #define __DRI_IMAGE_FORMAT_R16 PIPE_FORMAT_R16_UNORM --#define __DRI_IMAGE_FORMAT_GR1616 PIPE_FORMAT_R16G16_UNORM -+#define __DRI_IMAGE_FORMAT_GR1616 PIPE_FORMAT_RG1616_UNORM - #define __DRI_IMAGE_FORMAT_XBGR2101010 PIPE_FORMAT_R10G10B10X2_UNORM - #define __DRI_IMAGE_FORMAT_ABGR2101010 PIPE_FORMAT_R10G10B10A2_UNORM - #define __DRI_IMAGE_FORMAT_SABGR8 PIPE_FORMAT_RGBA8888_SRGB --- -2.53.0 - diff --git a/0001-device-select-add-a-layer-setting-to-disable-device-.patch b/0001-device-select-add-a-layer-setting-to-disable-device-.patch deleted file mode 100644 index c286c70..0000000 --- a/0001-device-select-add-a-layer-setting-to-disable-device-.patch +++ /dev/null @@ -1,149 +0,0 @@ -From b0158d174d297276397b21a6657ea0ef14652183 Mon Sep 17 00:00:00 2001 -From: Dave Airlie -Date: Wed, 5 Nov 2025 11:01:05 +1000 -Subject: [PATCH 1/2] device-select: add a layer setting to disable device - selection logic - -There are cases like zink where we have a file descriptors we are searching -for devices for, so we don't need device selecting reordering, we just want -the fastest path to get the devices so we can match them. - -This also helps avoid some cases of deadlock inside compositors where -zink/vulkan initialises later and tries to connect to the compositor. - -This uses a VK_EXT_layer_setting to add a bypass setting. ---- - .../VkLayer_MESA_device_select.json.in | 6 +++ - .../device-select-layer/device_select_layer.c | 53 +++++++++++++++---- - 2 files changed, 48 insertions(+), 11 deletions(-) - -diff --git a/src/vulkan/device-select-layer/VkLayer_MESA_device_select.json.in b/src/vulkan/device-select-layer/VkLayer_MESA_device_select.json.in -index 40d6ea8cd8b..1623381a81a 100644 ---- a/src/vulkan/device-select-layer/VkLayer_MESA_device_select.json.in -+++ b/src/vulkan/device-select-layer/VkLayer_MESA_device_select.json.in -@@ -7,6 +7,12 @@ - "api_version": "1.4.303", - "implementation_version": "1", - "description": "Linux device selection layer", -+ "instance_extensions": [ -+ { -+ "name": "VK_EXT_layer_settings", -+ "spec_version": "2" -+ } -+ ], - "functions": { - "vkNegotiateLoaderLayerInterfaceVersion": "vkNegotiateLoaderLayerInterfaceVersion" - }, -diff --git a/src/vulkan/device-select-layer/device_select_layer.c b/src/vulkan/device-select-layer/device_select_layer.c -index 19cfc556f54..c03938b82c0 100644 ---- a/src/vulkan/device-select-layer/device_select_layer.c -+++ b/src/vulkan/device-select-layer/device_select_layer.c -@@ -54,7 +54,9 @@ struct instance_info { - PFN_vkGetPhysicalDeviceProperties2 GetPhysicalDeviceProperties2; - bool has_pci_bus, has_vulkan11; - bool has_wayland, has_xcb; -- bool zink, xwayland, xserver; -+ bool xserver; -+ /* don't do device selection */ -+ bool bypass_device_select; - }; - - static struct hash_table *device_select_instance_ht = NULL; -@@ -118,10 +120,34 @@ static VkResult device_select_CreateInstance(const VkInstanceCreateInfo *pCreate - const VkAllocationCallbacks *pAllocator, - VkInstance *pInstance) - { -- VkLayerInstanceCreateInfo *chain_info; -- for(chain_info = (VkLayerInstanceCreateInfo*)pCreateInfo->pNext; chain_info; chain_info = (VkLayerInstanceCreateInfo*)chain_info->pNext) -- if(chain_info->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && chain_info->function == VK_LAYER_LINK_INFO) -+ VkLayerInstanceCreateInfo *chain_info = NULL; -+ bool bypass_device_select = false; -+ vk_foreach_struct_const(s, pCreateInfo->pNext) { -+ switch (s->sType) { -+ case VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO: { -+ const VkLayerInstanceCreateInfo *this_info = (const void *)s; -+ if (this_info->function == VK_LAYER_LINK_INFO) -+ chain_info = (VkLayerInstanceCreateInfo *)this_info; /* loses const */ - break; -+ } -+ case VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT: { -+ const VkLayerSettingsCreateInfoEXT *lsci = (const void *)s; -+ for (unsigned i = 0; i < lsci->settingCount; i++) { -+ const VkLayerSettingEXT *ls = &lsci->pSettings[i]; -+ if (!strcmp(ls->pLayerName, "MESA_device_select")) { -+ if (!strcmp(ls->pSettingName, "no_device_select")) { -+ assert(ls->type == VK_LAYER_SETTING_TYPE_BOOL32_EXT); -+ uint32_t *values = (uint32_t *)ls->pValues; -+ bypass_device_select = values[0]; -+ } -+ } -+ } -+ break; -+ } -+ default: -+ break; -+ } -+ } - - assert(chain_info->u.pLayerInfo); - -@@ -140,10 +166,10 @@ static VkResult device_select_CreateInstance(const VkInstanceCreateInfo *pCreate - return result; - } - -+ bool zink = !strcmp(engineName, "mesa zink"); -+ bool xwayland = !strcmp(applicationName, "Xwayland"); - struct instance_info *info = (struct instance_info *)calloc(1, sizeof(struct instance_info)); - info->GetInstanceProcAddr = GetInstanceProcAddr; -- info->zink = !strcmp(engineName, "mesa zink"); -- info->xwayland = !strcmp(applicationName, "Xwayland"); - info->xserver = !strcmp(applicationName, "Xorg") || !strcmp(applicationName, "Xephyr"); - - bool has_wayland = getenv("WAYLAND_DISPLAY") || getenv("WAYLAND_SOCKET"); -@@ -155,16 +181,20 @@ static VkResult device_select_CreateInstance(const VkInstanceCreateInfo *pCreate - info->has_wayland = true; - #endif - #ifdef VK_USE_PLATFORM_XCB_KHR -- if (!strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_KHR_XCB_SURFACE_EXTENSION_NAME) && has_xcb) -- info->has_xcb = !info->xserver || !info->zink; -+ if (!strcmp(pCreateInfo->ppEnabledExtensionNames[i], VK_KHR_XCB_SURFACE_EXTENSION_NAME) && -+ has_xcb) -+ info->has_xcb = !info->xserver || !zink; - #endif - } - -+ if (zink && xwayland) -+ bypass_device_select = true; - /* - * The loader is currently not able to handle GetPhysicalDeviceProperties2KHR calls in - * EnumeratePhysicalDevices when there are other layers present. To avoid mysterious crashes - * for users just use only the vulkan version for now. - */ -+ info->bypass_device_select = bypass_device_select; - info->has_vulkan11 = pCreateInfo->pApplicationInfo && - pCreateInfo->pApplicationInfo->apiVersion >= VK_MAKE_VERSION(1, 1, 0); - -@@ -558,7 +588,7 @@ static VkResult device_select_EnumeratePhysicalDevices(VkInstance instance, - uint32_t selected_physical_device_count = 0; - const char* selection = getenv("MESA_VK_DEVICE_SELECT"); - bool expose_only_one_dev = false; -- if (info->zink && info->xwayland) -+ if (info->bypass_device_select) - return info->EnumeratePhysicalDevices(instance, pPhysicalDeviceCount, pPhysicalDevices); - VkResult result = info->EnumeratePhysicalDevices(instance, &physical_device_count, NULL); - VK_OUTARRAY_MAKE_TYPED(VkPhysicalDevice, out, pPhysicalDevices, pPhysicalDeviceCount); -@@ -643,8 +673,9 @@ static VkResult device_select_EnumeratePhysicalDeviceGroups(VkInstance instance, - struct instance_info *info = device_select_layer_get_instance(instance); - uint32_t physical_device_group_count = 0; - uint32_t selected_physical_device_group_count = 0; -- if (info->zink && info->xwayland) -- return info->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, pPhysicalDeviceGroups); -+ if (info->bypass_device_select) -+ return info->EnumeratePhysicalDeviceGroups(instance, pPhysicalDeviceGroupCount, -+ pPhysicalDeviceGroups); - VkResult result = info->EnumeratePhysicalDeviceGroups(instance, &physical_device_group_count, NULL); - VK_OUTARRAY_MAKE_TYPED(VkPhysicalDeviceGroupProperties, out, pPhysicalDeviceGroups, pPhysicalDeviceGroupCount); - --- -2.51.1 - diff --git a/0001-drisw-Modify-drisw_swap_buffers_with_damage-to-swap-.patch b/0001-drisw-Modify-drisw_swap_buffers_with_damage-to-swap-.patch deleted file mode 100644 index 02281ab..0000000 --- a/0001-drisw-Modify-drisw_swap_buffers_with_damage-to-swap-.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 17ab0f2ece0a45dd5df507a466ecf6f40d452e1a Mon Sep 17 00:00:00 2001 -From: Lucas Fryzek -Date: Wed, 3 Dec 2025 19:33:57 -0500 -Subject: [PATCH 1/2] drisw: Modify drisw_swap_buffers_with_damage to swap - entire buffer - -When swapping buffer with damage regions, to be strictly correct we -need to swap the entire back buffer to the front buffer. This needs to -be done in case the compositor does not support damage regions. This -means we need to ignore the input damage region and tell drisw to swap -the entire buffer. - -Cc: mesa-stable -Part-of: ---- - src/gallium/frontends/dri/drisw.c | 28 ++++++++-------------------- - 1 file changed, 8 insertions(+), 20 deletions(-) - -diff --git a/src/gallium/frontends/dri/drisw.c b/src/gallium/frontends/dri/drisw.c -index 4359ca569e5..394b6986832 100644 ---- a/src/gallium/frontends/dri/drisw.c -+++ b/src/gallium/frontends/dri/drisw.c -@@ -225,6 +225,13 @@ drisw_copy_to_front(struct pipe_context *pipe, - static void - drisw_swap_buffers_with_damage(struct dri_drawable *drawable, int nrects, const int *rects) - { -+ /* Damage regions still require us to update the whole front buffer -+ * in case the compositor doesn't obey them, so we will just ignore -+ * the passed in damage regions and swap the whole buffer -+ */ -+ (void)nrects; -+ (void)rects; -+ - struct dri_context *ctx = dri_get_current(); - struct dri_screen *screen = drawable->screen; - struct pipe_resource *ptex; -@@ -242,25 +249,6 @@ drisw_swap_buffers_with_damage(struct dri_drawable *drawable, int nrects, const - if (ptex) { - struct pipe_fence_handle *fence = NULL; - -- struct pipe_box stack_boxes[64]; -- if (nrects > ARRAY_SIZE(stack_boxes)) -- nrects = 0; -- if (nrects) { -- for (unsigned int i = 0; i < nrects; i++) { -- const int *rect = &rects[i * 4]; -- -- int w = MIN2(rect[2], ptex->width0); -- int h = MIN2(rect[3], ptex->height0); -- int x = CLAMP(rect[0], 0, ptex->width0); -- int y = CLAMP(ptex->height0 - rect[1] - h, 0, ptex->height0); -- -- if (h > ptex->height0 - y) -- h = ptex->height0 - y; -- -- u_box_2d(x, y, w, h, &stack_boxes[i]); -- } -- } -- - if (ctx->pp) - pp_run(ctx->pp, ptex, ptex, drawable->textures[ST_ATTACHMENT_DEPTH_STENCIL]); - -@@ -279,7 +267,7 @@ drisw_swap_buffers_with_damage(struct dri_drawable *drawable, int nrects, const - screen->base.screen->fence_finish(screen->base.screen, ctx->st->pipe, - fence, OS_TIMEOUT_INFINITE); - screen->base.screen->fence_reference(screen->base.screen, &fence, NULL); -- drisw_copy_to_front(ctx->st->pipe, drawable, ptex, nrects, nrects ? stack_boxes : NULL); -+ drisw_copy_to_front(ctx->st->pipe, drawable, ptex, 0, NULL); - drawable->buffer_age = 1; - - /* TODO: remove this if the framebuffer state doesn't change. */ --- -2.52.0 - diff --git a/0001-gallivm-handle-u16-correct-on-const-loads.patch b/0001-gallivm-handle-u16-correct-on-const-loads.patch deleted file mode 100644 index 9769f7a..0000000 --- a/0001-gallivm-handle-u16-correct-on-const-loads.patch +++ /dev/null @@ -1,36 +0,0 @@ -From c016346b50e9085b531f9bcbd7cfd63d3806a3e1 Mon Sep 17 00:00:00 2001 -From: Dave Airlie -Date: Wed, 11 Feb 2026 05:47:57 +1000 -Subject: [PATCH] gallivm: handle u16 correct on const loads. - -I somehow screwed this up on my previous attempt at fixing this bug, - -This should fix the loop limiter bug on big endian properly. - -Reviewed-by: Georg Lehmann -Cc: mesa-stable -Fixes: e28cfb2bada2 ("gallivm: handle u8/u16 const loads properly on big-endian.") -Part-of: ---- - src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c | 5 +---- - 1 file changed, 1 insertion(+), 4 deletions(-) - -diff --git a/src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c b/src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c -index 770f1cc6592..e755225dce9 100644 ---- a/src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c -+++ b/src/gallium/auxiliary/gallivm/lp_bld_nir_soa.c -@@ -1269,10 +1269,7 @@ emit_load_const(struct lp_build_nir_soa_context *bld, - - for (unsigned i = 0; i < instr->def.num_components; i++) { - outval[i] = lp_build_const_int_vec(bld->base.gallivm, int_bld->type, -- bits == 8 ? instr->value[i].u8 : -- bits == 16 ? instr->value[i].u32 : -- bits == 32 ? instr->value[i].u32 : -- instr->value[i].u64); -+ nir_const_value_as_uint(instr->value[i], bits)); - } - for (unsigned i = instr->def.num_components; i < NIR_MAX_VEC_COMPONENTS; i++) { - outval[i] = NULL; --- -2.53.0 - diff --git a/0002-Fix-enum.patch b/0002-Fix-enum.patch new file mode 100644 index 0000000..e001cb3 --- /dev/null +++ b/0002-Fix-enum.patch @@ -0,0 +1,36 @@ +From af7ed267f67b024829c391f9016d0fb7ca18cecc Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Wed, 24 Jun 2026 11:42:19 +0200 +Subject: [PATCH 02/19] Fix enum + +Signed-off-by: Jocelyn Falempe +--- + src/intel/compiler/jay/jay_ir.h | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/intel/compiler/jay/jay_ir.h b/src/intel/compiler/jay/jay_ir.h +index 8307044ef8a..afd923062ca 100644 +--- a/src/intel/compiler/jay/jay_ir.h ++++ b/src/intel/compiler/jay/jay_ir.h +@@ -579,7 +579,8 @@ jay_type_is_any_float(enum jay_type t) + return jay_base_type(t) == JAY_TYPE_F || jay_base_type(t) == JAY_TYPE_BF; + } + +-enum jay_predication : uint8_t { ++typedef uint8_t jay_predication; ++enum { + /** No predication. */ + JAY_NOT_PREDICATED = 0, + +@@ -634,7 +635,7 @@ typedef struct jay_inst { + bool decrement_dep:1; + unsigned padding :12; + +- enum jay_predication predication; ++ jay_predication predication; + enum jay_conditional_mod conditional_mod; + + jay_def cond_flag; /**< conditional flag */ +-- +2.54.0 + diff --git a/0002-Revert-drisw-Copy-entire-buffer-ignoring-damage-regi.patch b/0002-Revert-drisw-Copy-entire-buffer-ignoring-damage-regi.patch deleted file mode 100644 index a0384e1..0000000 --- a/0002-Revert-drisw-Copy-entire-buffer-ignoring-damage-regi.patch +++ /dev/null @@ -1,72 +0,0 @@ -From 48799005d7f3b099cb2e93d09ce6dc211f619887 Mon Sep 17 00:00:00 2001 -From: Lucas Fryzek -Date: Wed, 3 Dec 2025 19:19:56 -0500 -Subject: [PATCH 2/2] Revert "drisw: Copy entire buffer ignoring damage - regions" - -This reverts commit 755e795e4c0d2660129c14998425f7dd3299bdf9. - -Cc: mesa-stable -Part-of: ---- - src/gallium/winsys/sw/dri/dri_sw_winsys.c | 38 +++++++++++++++++------ - 1 file changed, 29 insertions(+), 9 deletions(-) - -diff --git a/src/gallium/winsys/sw/dri/dri_sw_winsys.c b/src/gallium/winsys/sw/dri/dri_sw_winsys.c -index 7d18b6138ea..0b2c8754ec5 100644 ---- a/src/gallium/winsys/sw/dri/dri_sw_winsys.c -+++ b/src/gallium/winsys/sw/dri/dri_sw_winsys.c -@@ -352,21 +352,41 @@ dri_sw_displaytarget_display(struct sw_winsys *ws, - struct dri_sw_winsys *dri_sw_ws = dri_sw_winsys(ws); - struct dri_sw_displaytarget *dri_sw_dt = dri_sw_displaytarget(dt); - struct dri_drawable *dri_drawable = (struct dri_drawable *)context_private; -- unsigned width, height; -+ unsigned width, height, x = 0, y = 0; - unsigned blsize = util_format_get_blocksize(dri_sw_dt->format); - bool is_shm = dri_sw_dt->shmid != -1; - /* Set the width to 'stride / cpp'. - * - * PutImage correctly clips to the width of the dst drawable. - */ -- width = dri_sw_dt->stride / blsize; -- height = dri_sw_dt->height; -- if (is_shm) -- dri_sw_ws->lf->put_image_shm(dri_drawable, dri_sw_dt->shmid, dri_sw_dt->data, 0, 0, -- 0, 0, width, height, dri_sw_dt->stride); -- else -- dri_sw_ws->lf->put_image(dri_drawable, dri_sw_dt->data, width, height); -- return; -+ if (!nboxes) { -+ width = dri_sw_dt->stride / blsize; -+ height = dri_sw_dt->height; -+ if (is_shm) -+ dri_sw_ws->lf->put_image_shm(dri_drawable, dri_sw_dt->shmid, dri_sw_dt->data, 0, 0, -+ 0, 0, width, height, dri_sw_dt->stride); -+ else -+ dri_sw_ws->lf->put_image(dri_drawable, dri_sw_dt->data, width, height); -+ return; -+ } -+ for (unsigned i = 0; i < nboxes; i++) { -+ unsigned offset = dri_sw_dt->stride * box[i].y; -+ unsigned offset_x = box[i].x * blsize; -+ char *data = dri_sw_dt->data + offset; -+ x = box[i].x; -+ y = box[i].y; -+ width = box[i].width; -+ height = box[i].height; -+ if (is_shm) { -+ /* don't add x offset for shm, the put_image_shm will deal with it */ -+ dri_sw_ws->lf->put_image_shm(dri_drawable, dri_sw_dt->shmid, dri_sw_dt->data, offset, offset_x, -+ x, y, width, height, dri_sw_dt->stride); -+ } else { -+ data += offset_x; -+ dri_sw_ws->lf->put_image2(dri_drawable, data, -+ x, y, width, height, dri_sw_dt->stride); -+ } -+ } - } - - static void --- -2.52.0 - diff --git a/0002-zink-use-device-select-layer-settings-to-disable-dev.patch b/0002-zink-use-device-select-layer-settings-to-disable-dev.patch deleted file mode 100644 index 319e4b3..0000000 --- a/0002-zink-use-device-select-layer-settings-to-disable-dev.patch +++ /dev/null @@ -1,106 +0,0 @@ -From 30c754624ab73b180c66658701814ec5e3d12a31 Mon Sep 17 00:00:00 2001 -From: Dave Airlie -Date: Wed, 5 Nov 2025 11:02:26 +1000 -Subject: [PATCH 2/2] zink: use device select layer settings to disable device - selection - -In the case where we have a device that we want to choose after -probing, there is no point in asking the device select layer to do -any reordering at all. - -This helps avoid a deadlock inside compositors where we don't need -device selection anyways. ---- - src/gallium/drivers/zink/zink_instance.py | 18 ++++++++++++++++++ - src/gallium/drivers/zink/zink_screen.c | 9 ++++++++- - 2 files changed, 26 insertions(+), 1 deletion(-) - -diff --git a/src/gallium/drivers/zink/zink_instance.py b/src/gallium/drivers/zink/zink_instance.py -index 1ab36bee2ca..c0e813be164 100644 ---- a/src/gallium/drivers/zink/zink_instance.py -+++ b/src/gallium/drivers/zink/zink_instance.py -@@ -37,6 +37,7 @@ import platform - # - nonstandard: Disables validation (cross-checking with vk.xml) if True. - EXTENSIONS = [ - Extension("VK_EXT_debug_utils"), -+ Extension("VK_EXT_layer_settings"), - Extension("VK_KHR_get_physical_device_properties2"), - Extension("VK_KHR_external_memory_capabilities"), - Extension("VK_KHR_external_semaphore_capabilities"), -@@ -62,8 +63,10 @@ LAYERS = [ - conditions=["zink_debug & ZINK_DEBUG_VALIDATION"]), - Layer("VK_LAYER_LUNARG_standard_validation", - conditions=["zink_debug & ZINK_DEBUG_VALIDATION", "!have_layer_KHRONOS_validation"]), -+ Layer("VK_LAYER_MESA_device_select") - ] - -+ - REPLACEMENTS = { - "VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES2_EXTENSION_NAME" : "VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME" - } -@@ -87,6 +90,7 @@ struct zink_screen; - - struct zink_instance_info { - uint32_t loader_version; -+ bool no_device_select; - - %for ext in extensions: - bool have_${ext.name_with_vendor()}; -@@ -261,6 +265,20 @@ zink_create_instance(struct zink_screen *screen, struct zink_instance_info *inst - ici.ppEnabledLayerNames = layers; - ici.enabledLayerCount = num_layers; - -+ VkLayerSettingEXT ds_layer = {0}; -+ VkLayerSettingsCreateInfoEXT lsci = {0}; -+ uint32_t no_device_select_value = instance_info->no_device_select; -+ if (have_EXT_layer_settings && have_layer_MESA_device_select) { -+ ds_layer.pLayerName = "MESA_device_select"; -+ ds_layer.pSettingName = "no_device_select"; -+ ds_layer.type = VK_LAYER_SETTING_TYPE_BOOL32_EXT; -+ ds_layer.valueCount = 1; -+ ds_layer.pValues = &no_device_select_value; -+ lsci.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT; -+ lsci.settingCount = 1; -+ lsci.pSettings = &ds_layer; -+ ici.pNext = &lsci; -+ } - GET_PROC_ADDR_INSTANCE_LOCAL(screen, NULL, CreateInstance); - assert(vk_CreateInstance); - -diff --git a/src/gallium/drivers/zink/zink_screen.c b/src/gallium/drivers/zink/zink_screen.c -index 7ec86d0090a..34eee2ba113 100644 ---- a/src/gallium/drivers/zink/zink_screen.c -+++ b/src/gallium/drivers/zink/zink_screen.c -@@ -1576,6 +1576,12 @@ zink_destroy_screen(struct pipe_screen *pscreen) - glsl_type_singleton_decref(); - } - -+static bool -+zink_picks_device(int dev_major, uint64_t adapter_luid) -+{ -+ return (dev_major > 0 && dev_major < 255) || adapter_luid; -+} -+ - static int - zink_get_display_device(const struct zink_screen *screen, uint32_t pdev_count, - const VkPhysicalDevice *pdevs, int64_t dev_major, -@@ -1647,7 +1653,7 @@ choose_pdev(struct zink_screen *screen, int64_t dev_major, int64_t dev_minor, ui - bool cpu = debug_get_bool_option("LIBGL_ALWAYS_SOFTWARE", false) || - debug_get_bool_option("D3D_ALWAYS_SOFTWARE", false); - -- if (cpu || (dev_major > 0 && dev_major < 255) || adapter_luid) { -+ if (cpu || zink_picks_device(dev_major, adapter_luid)) { - uint32_t pdev_count; - int idx; - VkPhysicalDevice *pdevs; -@@ -3309,6 +3315,7 @@ zink_internal_create_screen(const struct pipe_screen_config *config, int64_t dev - simple_mtx_lock(&instance_lock); - if (++instance_refcount == 1) { - instance_info.loader_version = zink_get_loader_version(screen); -+ instance_info.no_device_select = zink_picks_device(dev_major, adapter_luid); - instance = zink_create_instance(screen, &instance_info); - } - if (!instance) { --- -2.51.1 - diff --git a/0003-Revert-tu-autotune-Clear-active_batches-before-histo.patch b/0003-Revert-tu-autotune-Clear-active_batches-before-histo.patch new file mode 100644 index 0000000..bdb83b3 --- /dev/null +++ b/0003-Revert-tu-autotune-Clear-active_batches-before-histo.patch @@ -0,0 +1,40 @@ +From 1d770a0fb463c03f7e4a1b0627b4af98a8789ad0 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:53:05 +0200 +Subject: [PATCH 03/19] Revert "tu/autotune: Clear active_batches before + history objects are freed" + +This reverts commit 42df4a7e416b387e06a67173e67ed6de1bfd6c5b. +--- + .pick_status.json | 2 +- + src/freedreno/vulkan/tu_autotune.cc | 1 - + 2 files changed, 1 insertion(+), 2 deletions(-) + +diff --git a/.pick_status.json b/.pick_status.json +index 69436008ada..aeb82c74693 100644 +--- a/.pick_status.json ++++ b/.pick_status.json +@@ -3314,7 +3314,7 @@ + "description": "tu/autotune: Clear active_batches before history objects are freed", + "nominated": true, + "nomination_type": 2, +- "resolution": 1, ++ "resolution": 0, + "main_sha": null, + "because_sha": "40ffc052afff7a40da99b398c09594c3ff2d40ed", + "notes": null +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 820a655b2fc..028e9282227 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1698,7 +1698,6 @@ tu_autotune::~tu_autotune() + at_log_base("finished processing all entries"); + } + +- active_batches.clear(); + tu_bo_suballocator_finish(&suballoc); + } + +-- +2.54.0 + diff --git a/0004-Revert-tu-autotune-Allocate-performance-counters-fro.patch b/0004-Revert-tu-autotune-Allocate-performance-counters-fro.patch new file mode 100644 index 0000000..d91b1d0 --- /dev/null +++ b/0004-Revert-tu-autotune-Allocate-performance-counters-fro.patch @@ -0,0 +1,52 @@ +From d5561b08593b59df1f2bdc82a676fb565b84ef56 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:53:27 +0200 +Subject: [PATCH 04/19] Revert "tu/autotune: Allocate performance counters from + low-to-high" + +This reverts commit 203ae3509166ebb5076bdc0df28dbb30062c4dfd. +--- + .pick_status.json | 2 +- + src/freedreno/vulkan/tu_autotune.cc | 10 ++++++---- + 2 files changed, 7 insertions(+), 5 deletions(-) + +diff --git a/.pick_status.json b/.pick_status.json +index aeb82c74693..e04710b3344 100644 +--- a/.pick_status.json ++++ b/.pick_status.json +@@ -13964,7 +13964,7 @@ + "description": "tu/autotune: Allocate performance counters from low-to-high", + "nominated": true, + "nomination_type": 1, +- "resolution": 1, ++ "resolution": 0, + "main_sha": null, + "because_sha": null, + "notes": null +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 028e9282227..2dd5017b11b 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1660,13 +1660,15 @@ tu_autotune::tu_autotune(struct tu_device *device, VkResult &result) + auto always_count_countable = get_perfcntr_countable(cp_group, "PERF_CP_ALWAYS_COUNT"); + if (preemption_latency_countable && always_count_countable) { + if (cp_group->num_counters >= 2) { +- preemption_latency_selector_reg = cp_group->counters[0].select_reg; ++ uint32_t preemption_latency_counter_index = cp_group->num_counters - 2; ++ preemption_latency_selector_reg = cp_group->counters[preemption_latency_counter_index].select_reg; + preemption_latency_selector = preemption_latency_countable->selector; +- preemption_latency_counter_reg_lo = cp_group->counters[0].counter_reg_lo; ++ preemption_latency_counter_reg_lo = cp_group->counters[preemption_latency_counter_index].counter_reg_lo; + +- always_count_selector_reg = cp_group->counters[1].select_reg; ++ uint32_t always_count_counter_index = cp_group->num_counters - 1; ++ always_count_selector_reg = cp_group->counters[always_count_counter_index].select_reg; + always_count_selector = always_count_countable->selector; +- always_count_counter_reg_lo = cp_group->counters[1].counter_reg_lo; ++ always_count_counter_reg_lo = cp_group->counters[always_count_counter_index].counter_reg_lo; + } else { + fail_reason = "not enough counters in CP group for preemption latency tracking"; + } +-- +2.54.0 + diff --git a/0005-Revert-tu-autotune-Fail-gracefully-when-CP-counters-.patch b/0005-Revert-tu-autotune-Fail-gracefully-when-CP-counters-.patch new file mode 100644 index 0000000..741fb12 --- /dev/null +++ b/0005-Revert-tu-autotune-Fail-gracefully-when-CP-counters-.patch @@ -0,0 +1,149 @@ +From e9862fcd366d761f131865b390bde72157983a24 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:53:34 +0200 +Subject: [PATCH 05/19] Revert "tu/autotune: Fail gracefully when CP counters + are unavailable" + +This reverts commit 920a84802792cea85d752518a20881f1f6be110d. +--- + .pick_status.json | 2 +- + src/freedreno/vulkan/tu_autotune.cc | 90 ++++++++++++++--------------- + src/freedreno/vulkan/tu_autotune.h | 1 + + 3 files changed, 45 insertions(+), 48 deletions(-) + +diff --git a/.pick_status.json b/.pick_status.json +index e04710b3344..c54f039590a 100644 +--- a/.pick_status.json ++++ b/.pick_status.json +@@ -13984,7 +13984,7 @@ + "description": "tu/autotune: Fail gracefully when CP counters are unavailable", + "nominated": true, + "nomination_type": 1, +- "resolution": 1, ++ "resolution": 0, + "main_sha": null, + "because_sha": null, + "notes": null +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 2dd5017b11b..e1e4666768c 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1632,60 +1632,56 @@ tu_autotune::tu_autotune(struct tu_device *device, VkResult &result) + { + tu_bo_suballocator_init(&suballoc, device, 128 * 1024, TU_BO_ALLOC_INTERNAL_RESOURCE, "autotune_suballoc"); + +- if (supports_preempt_latency_tracking()) { +- uint32_t group_count; +- const struct fd_perfcntr_group *groups = fd_perfcntrs(&device->physical_device->dev_id, &group_count); +- const char *fail_reason = nullptr; +- +- const fd_perfcntr_group *cp_group = nullptr; +- for (uint32_t i = 0; i < group_count; i++) { +- if (strcmp(groups[i].name, "CP") == 0) { +- cp_group = &groups[i]; +- break; +- } +- } ++ uint32_t group_count; ++ const struct fd_perfcntr_group *groups = fd_perfcntrs(&device->physical_device->dev_id, &group_count); + +- if (cp_group) { +- auto get_perfcntr_countable = [](const struct fd_perfcntr_group *group, +- const char *name) -> const struct fd_perfcntr_countable * { +- for (uint32_t i = 0; i < group->num_countables; i++) { +- if (strcmp(group->countables[i].name, name) == 0) +- return &group->countables[i]; +- } ++ for (uint32_t i = 0; i < group_count; i++) { ++ if (strcmp(groups[i].name, "CP") == 0) { ++ cp_group = &groups[i]; ++ break; ++ } ++ } + +- return nullptr; +- }; ++ if (!cp_group) { ++ mesa_loge("autotune: CP group not found"); ++ result = VK_ERROR_INITIALIZATION_FAILED; ++ return; ++ } else if (cp_group->num_countables < 5) { ++ mesa_loge("autotune: CP group has too few countables"); ++ result = VK_ERROR_INITIALIZATION_FAILED; ++ return; ++ } + +- auto preemption_latency_countable = get_perfcntr_countable(cp_group, "PERF_CP_PREEMPTION_REACTION_DELAY"); +- auto always_count_countable = get_perfcntr_countable(cp_group, "PERF_CP_ALWAYS_COUNT"); +- if (preemption_latency_countable && always_count_countable) { +- if (cp_group->num_counters >= 2) { +- uint32_t preemption_latency_counter_index = cp_group->num_counters - 2; +- preemption_latency_selector_reg = cp_group->counters[preemption_latency_counter_index].select_reg; +- preemption_latency_selector = preemption_latency_countable->selector; +- preemption_latency_counter_reg_lo = cp_group->counters[preemption_latency_counter_index].counter_reg_lo; +- +- uint32_t always_count_counter_index = cp_group->num_counters - 1; +- always_count_selector_reg = cp_group->counters[always_count_counter_index].select_reg; +- always_count_selector = always_count_countable->selector; +- always_count_counter_reg_lo = cp_group->counters[always_count_counter_index].counter_reg_lo; +- } else { +- fail_reason = "not enough counters in CP group for preemption latency tracking"; +- } +- } else { +- fail_reason = "required countables not found in CP group"; +- } +- } else { +- fail_reason = "CP counter group not found"; ++ auto get_perfcntr_countable = [](const struct fd_perfcntr_group *group, ++ const char *name) -> const struct fd_perfcntr_countable * { ++ for (uint32_t i = 0; i < group->num_countables; i++) { ++ if (strcmp(group->countables[i].name, name) == 0) ++ return &group->countables[i]; + } + +- if (fail_reason) { +- if (TU_DEBUG(STARTUP) || active_config.load().test(mod_flag::PREEMPT_OPTIMIZE)) +- mesa_logw("autotune: %s, preemption optimization not supported", fail_reason); ++ mesa_loge("autotune: %s not found in group %s", name, group->name); ++ return nullptr; ++ }; + +- supported_mod_flags &= ~((uint32_t) mod_flag::PREEMPT_OPTIMIZE); +- disable_preempt_optimize(); ++ if (supports_preempt_latency_tracking()) { ++ auto preemption_latency_countable = get_perfcntr_countable(cp_group, "PERF_CP_PREEMPTION_REACTION_DELAY"); ++ auto always_count_countable = get_perfcntr_countable(cp_group, "PERF_CP_ALWAYS_COUNT"); ++ ++ if (cp_group->num_counters < 2) { ++ mesa_loge("autotune: CP group has too few counters for preemption latency tracking"); ++ result = VK_ERROR_INITIALIZATION_FAILED; ++ return; + } ++ ++ uint32_t preemption_latency_counter_index = cp_group->num_counters - 2; ++ preemption_latency_selector_reg = cp_group->counters[preemption_latency_counter_index].select_reg; ++ preemption_latency_selector = preemption_latency_countable->selector; ++ preemption_latency_counter_reg_lo = cp_group->counters[preemption_latency_counter_index].counter_reg_lo; ++ ++ uint32_t always_count_counter_index = cp_group->num_counters - 1; ++ always_count_selector_reg = cp_group->counters[always_count_counter_index].select_reg; ++ always_count_selector = always_count_countable->selector; ++ always_count_counter_reg_lo = cp_group->counters[always_count_counter_index].counter_reg_lo; + } + + result = VK_SUCCESS; +diff --git a/src/freedreno/vulkan/tu_autotune.h b/src/freedreno/vulkan/tu_autotune.h +index 55e579ae93a..aba8d3e6f95 100644 +--- a/src/freedreno/vulkan/tu_autotune.h ++++ b/src/freedreno/vulkan/tu_autotune.h +@@ -242,6 +242,7 @@ struct tu_autotune { + std::mutex rp_latency_mutex; /* Protects rp_latency_tracking */ + uint64_t last_latency_cleanup_ts = 0; + ++ const fd_perfcntr_group *cp_group; + uint32_t preemption_latency_selector_reg; + uint32_t preemption_latency_selector; + uint32_t preemption_latency_counter_reg_lo; +-- +2.54.0 + diff --git a/0006-Revert-tu-autotune-Only-lock-RPs-sustain-certain-mod.patch b/0006-Revert-tu-autotune-Only-lock-RPs-sustain-certain-mod.patch new file mode 100644 index 0000000..ae7a38d --- /dev/null +++ b/0006-Revert-tu-autotune-Only-lock-RPs-sustain-certain-mod.patch @@ -0,0 +1,53 @@ +From 59621a3e812fbc8267ebfb03001c82eff48cc86a Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:54:40 +0200 +Subject: [PATCH 06/19] Revert "tu/autotune: Only lock RPs sustain certain mode + for 30s" + +This reverts commit da089bf741b3c06fa7ae21c2a349f4f96d5c6230. +--- + src/freedreno/vulkan/tu_autotune.cc | 14 +------------- + 1 file changed, 1 insertion(+), 13 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index e1e4666768c..338f2b68018 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1060,9 +1060,6 @@ struct tu_autotune::rp_history { + bool locked = false; /* If true, the probability will no longer be updated. */ + uint64_t seed[2] { 0x3bffb83978e24f88, 0x9238d5d56c71cd35 }; + +- bool is_sysmem_winning = false; +- uint64_t winning_since_ts = 0; +- + public: + profiled_algo(uint64_t hash) + { +@@ -1129,15 +1126,6 @@ struct tu_autotune::rp_history { + constexpr uint32_t MIN_LOCK_DURATION_COUNT = 15; + constexpr uint64_t MIN_LOCK_THRESHOLD = GPU_TICKS_PER_US * 1'000; /* 1ms */ + constexpr uint32_t LOCK_PERCENT_DIFF = 30; +- constexpr uint64_t LOCK_TIME_WINDOW_NS = 30'000'000'000; /* 30s */ +- +- uint64_t now = os_time_get_nano(); +- bool current_sysmem_winning = avg_sysmem < avg_gmem; +- +- if (winning_since_ts == 0 || current_sysmem_winning != is_sysmem_winning) { +- winning_since_ts = now; +- is_sysmem_winning = current_sysmem_winning; +- } + + bool has_resolved = sysmem_prob == SLOW_MAX_PROBABILITY || sysmem_prob == SLOW_MIN_PROBABILITY; + bool enough_samples = +@@ -1147,7 +1135,7 @@ struct tu_autotune::rp_history { + uint64_t percent_diff = (100 * (max_avg - min_avg)) / min_avg; + + if (has_resolved && enough_samples && max_avg >= MIN_LOCK_THRESHOLD && +- percent_diff >= LOCK_PERCENT_DIFF && (now - winning_since_ts) >= LOCK_TIME_WINDOW_NS) { ++ percent_diff >= LOCK_PERCENT_DIFF) { + if (avg_gmem < avg_sysmem) + sysmem_prob = 0; + else +-- +2.54.0 + diff --git a/0007-Revert-tu-autotune-Allow-99-max-probability-in-profi.patch b/0007-Revert-tu-autotune-Allow-99-max-probability-in-profi.patch new file mode 100644 index 0000000..910db2c --- /dev/null +++ b/0007-Revert-tu-autotune-Allow-99-max-probability-in-profi.patch @@ -0,0 +1,56 @@ +From 2fa8853519888429f653daf491a0f4b78c3f6081 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:54:57 +0200 +Subject: [PATCH 07/19] Revert "tu/autotune: Allow 99% max probability in + profiled mode" + +This reverts commit c725f2aea31dccb4d6b027d44c5dd464a23f391b. +--- + src/freedreno/vulkan/tu_autotune.cc | 20 +++++++------------- + 1 file changed, 7 insertions(+), 13 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 338f2b68018..cededf09497 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1100,22 +1100,16 @@ struct tu_autotune::rp_history { + } + + /* Adjust probability based on timing results. */ +- constexpr uint32_t FAST_STEP_DELTA = 5, FAST_MIN_PROBABILITY = 5, FAST_MAX_PROBABILITY = 95; +- constexpr uint32_t SLOW_STEP_DELTA = 1, SLOW_MIN_PROBABILITY = 1, SLOW_MAX_PROBABILITY = 99; ++ constexpr uint32_t STEP_DELTA = 5; /* 5% */ ++ constexpr uint32_t MIN_PROB = 5, MAX_PROB = 95; + + uint64_t avg_sysmem = sysmem_ema.get(); + uint64_t avg_gmem = gmem_ema.get(); + +- if (avg_gmem < avg_sysmem) { +- if (sysmem_prob > FAST_MIN_PROBABILITY && sysmem_prob <= FAST_MAX_PROBABILITY) +- sysmem_prob = MAX2(sysmem_prob - FAST_STEP_DELTA, FAST_MIN_PROBABILITY); +- else if (sysmem_prob > SLOW_MIN_PROBABILITY) +- sysmem_prob = MAX2(sysmem_prob - SLOW_STEP_DELTA, SLOW_MIN_PROBABILITY); +- } else if (avg_sysmem < avg_gmem) { +- if (sysmem_prob >= FAST_MIN_PROBABILITY && sysmem_prob < FAST_MAX_PROBABILITY) +- sysmem_prob = MIN2(sysmem_prob + FAST_STEP_DELTA, FAST_MAX_PROBABILITY); +- else if (sysmem_prob < SLOW_MAX_PROBABILITY) +- sysmem_prob = MIN2(sysmem_prob + SLOW_STEP_DELTA, SLOW_MAX_PROBABILITY); ++ if (avg_gmem < avg_sysmem && sysmem_prob > MIN_PROB) { ++ sysmem_prob = MAX2(sysmem_prob - STEP_DELTA, MIN_PROB); ++ } else if (avg_sysmem < avg_gmem && sysmem_prob < MAX_PROB) { ++ sysmem_prob = MIN2(sysmem_prob + STEP_DELTA, MAX_PROB); + } + + /* If the RP duration exceeds a certain minimum duration threshold (i.e. has a large impact on frametime) +@@ -1127,7 +1121,7 @@ struct tu_autotune::rp_history { + constexpr uint64_t MIN_LOCK_THRESHOLD = GPU_TICKS_PER_US * 1'000; /* 1ms */ + constexpr uint32_t LOCK_PERCENT_DIFF = 30; + +- bool has_resolved = sysmem_prob == SLOW_MAX_PROBABILITY || sysmem_prob == SLOW_MIN_PROBABILITY; ++ bool has_resolved = sysmem_prob == MAX_PROB || sysmem_prob == MIN_PROB; + bool enough_samples = + sysmem_ema.count >= MIN_LOCK_DURATION_COUNT && gmem_ema.count >= MIN_LOCK_DURATION_COUNT; + uint64_t min_avg = MIN2(avg_sysmem, avg_gmem); +-- +2.54.0 + diff --git a/0008-Revert-tu-autotune-Add-render-mode-locking-to-PROFIL.patch b/0008-Revert-tu-autotune-Add-render-mode-locking-to-PROFIL.patch new file mode 100644 index 0000000..a8f33e2 --- /dev/null +++ b/0008-Revert-tu-autotune-Add-render-mode-locking-to-PROFIL.patch @@ -0,0 +1,112 @@ +From 978eda7888fde320c31647257cc2c521af7152cb Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:55:06 +0200 +Subject: [PATCH 08/19] Revert "tu/autotune: Add render mode locking to + PROFILED algorithm" + +This reverts commit 3b3ae477f3fe9cff421bb11f272cbc099c7ede56. +--- + src/freedreno/vulkan/tu_autotune.cc | 49 ++++++----------------------- + 1 file changed, 10 insertions(+), 39 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index cededf09497..202e68c3a1a 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -1057,7 +1057,6 @@ struct tu_autotune::rp_history { + + std::atomic sysmem_probability = PROBABILITY_MID; + bool should_reset = false; /* If true, will reset sysmem_probability before next update. */ +- bool locked = false; /* If true, the probability will no longer be updated. */ + uint64_t seed[2] { 0x3bffb83978e24f88, 0x9238d5d56c71cd35 }; + + public: +@@ -1068,9 +1067,6 @@ struct tu_autotune::rp_history { + + void update(rp_history &history, bool immediate) + { +- if (locked) +- return; +- + auto &sysmem_ema = history.sysmem_rp_average; + auto &gmem_ema = history.gmem_rp_average; + uint32_t sysmem_prob = sysmem_probability.load(std::memory_order_relaxed); +@@ -1080,13 +1076,15 @@ struct tu_autotune::rp_history { + * scenario for autotune performance, since we know the optimal decisions. + */ + ++ if (sysmem_prob == 0 || sysmem_prob == 100) ++ return; /* Already resolved, no further updates are necessary. */ ++ + if (sysmem_ema.count < 1) { + sysmem_prob = PROBABILITY_MAX; + } else if (gmem_ema.count < 1) { + sysmem_prob = 0; + } else { + sysmem_prob = gmem_ema.get() < sysmem_ema.get() ? 0 : PROBABILITY_MAX; +- locked = true; + } + } else { + if (sysmem_ema.count < MIN_PROFILE_DURATION_COUNT || gmem_ema.count < MIN_PROFILE_DURATION_COUNT) { +@@ -1100,41 +1098,14 @@ struct tu_autotune::rp_history { + } + + /* Adjust probability based on timing results. */ +- constexpr uint32_t STEP_DELTA = 5; /* 5% */ +- constexpr uint32_t MIN_PROB = 5, MAX_PROB = 95; ++ constexpr uint32_t STEP_DELTA = 5, MIN_PROBABILITY = 5, MAX_PROBABILITY = 95; + + uint64_t avg_sysmem = sysmem_ema.get(); + uint64_t avg_gmem = gmem_ema.get(); +- +- if (avg_gmem < avg_sysmem && sysmem_prob > MIN_PROB) { +- sysmem_prob = MAX2(sysmem_prob - STEP_DELTA, MIN_PROB); +- } else if (avg_sysmem < avg_gmem && sysmem_prob < MAX_PROB) { +- sysmem_prob = MIN2(sysmem_prob + STEP_DELTA, MAX_PROB); +- } +- +- /* If the RP duration exceeds a certain minimum duration threshold (i.e. has a large impact on frametime) +- * and the percentage difference between the modes is large enough, we lock into the optimal mode. This +- * avoids performance hazards from switching to an extremely suboptimal mode even if done very rarely. +- * Note: Due to the potentially huge negative impact of a bad lock, this is a very conservative check. +- */ +- constexpr uint32_t MIN_LOCK_DURATION_COUNT = 15; +- constexpr uint64_t MIN_LOCK_THRESHOLD = GPU_TICKS_PER_US * 1'000; /* 1ms */ +- constexpr uint32_t LOCK_PERCENT_DIFF = 30; +- +- bool has_resolved = sysmem_prob == MAX_PROB || sysmem_prob == MIN_PROB; +- bool enough_samples = +- sysmem_ema.count >= MIN_LOCK_DURATION_COUNT && gmem_ema.count >= MIN_LOCK_DURATION_COUNT; +- uint64_t min_avg = MIN2(avg_sysmem, avg_gmem); +- uint64_t max_avg = MAX2(avg_sysmem, avg_gmem); +- uint64_t percent_diff = (100 * (max_avg - min_avg)) / min_avg; +- +- if (has_resolved && enough_samples && max_avg >= MIN_LOCK_THRESHOLD && +- percent_diff >= LOCK_PERCENT_DIFF) { +- if (avg_gmem < avg_sysmem) +- sysmem_prob = 0; +- else +- sysmem_prob = 100; +- locked = true; ++ if (avg_gmem < avg_sysmem && sysmem_prob > MIN_PROBABILITY) { ++ sysmem_prob = MAX2(sysmem_prob - STEP_DELTA, MIN_PROBABILITY); ++ } else if (avg_sysmem < avg_gmem && sysmem_prob < MAX_PROBABILITY) { ++ sysmem_prob = MIN2(sysmem_prob + STEP_DELTA, MAX_PROBABILITY); + } + } + } +@@ -1142,9 +1113,9 @@ struct tu_autotune::rp_history { + sysmem_probability.store(sysmem_prob, std::memory_order_relaxed); + + at_log_profiled_h("update%s avg_gmem: %" PRIu64 " us (%" PRIu64 " samples) avg_sysmem: %" PRIu64 +- " us (%" PRIu64 " samples) = sysmem_probability: %" PRIu32 " locked: %u", ++ " us (%" PRIu64 " samples) = sysmem_probability: %" PRIu32, + history.hash, immediate ? "-imm" : "", ticks_to_us(gmem_ema.get()), gmem_ema.count, +- ticks_to_us(sysmem_ema.get()), sysmem_ema.count, sysmem_prob, locked); ++ ticks_to_us(sysmem_ema.get()), sysmem_ema.count, sysmem_prob); + } + + public: +-- +2.54.0 + diff --git a/0009-Revert-tu-util-Allow-setting-autotune-mode-from-dric.patch b/0009-Revert-tu-util-Allow-setting-autotune-mode-from-dric.patch new file mode 100644 index 0000000..d4fb69e --- /dev/null +++ b/0009-Revert-tu-util-Allow-setting-autotune-mode-from-dric.patch @@ -0,0 +1,119 @@ +From 4f7224ba2f0991d5773d83dd4a55ac2e6368d383 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:55:18 +0200 +Subject: [PATCH 09/19] Revert "tu+util: Allow setting autotune mode from + driconf" + +This reverts commit ed643d1766dbc16ce9a7457a4f93e1af1b5b3a07. +--- + docs/drivers/freedreno.rst | 2 -- + src/freedreno/vulkan/tu_autotune.cc | 15 +++++---------- + src/freedreno/vulkan/tu_device.cc | 3 --- + src/freedreno/vulkan/tu_device.h | 3 --- + src/util/driconf.h | 4 ---- + 5 files changed, 5 insertions(+), 22 deletions(-) + +diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst +index 60ab3e98c11..90afea283a4 100644 +--- a/docs/drivers/freedreno.rst ++++ b/docs/drivers/freedreno.rst +@@ -707,8 +707,6 @@ environment variables: + in some high-confidence cases as well as letting ``TU_AUTOTUNE_FLAGS`` still + be applied. + +- The algorithm can be set via the driconf option ``tu_autotune_algorithm`` as well. +- + .. envvar:: TU_AUTOTUNE_FLAGS + + Modifies the behavior of the selected algorithm. Supported flags are: +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 202e68c3a1a..b57c393e06e 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -273,16 +273,11 @@ 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; +- const char *algo_str = os_get_option("TU_AUTOTUNE_ALGO"); +- std::string_view algo_strv; + +- if (algo_str) +- algo_strv = algo_str; +- else if (device->instance->autotune_algo) +- algo_strv = device->instance->autotune_algo; +- +- if (!algo_strv.empty()) { ++ if (algo_env_str) { ++ std::string_view algo_strv(algo_env_str); + if (algo_strv == "bandwidth") { + algo = algorithm::BANDWIDTH; + } else if (algo_strv == "profiled") { +@@ -294,11 +289,11 @@ tu_autotune::get_env_config() + } else if (algo_strv == "prefer_gmem") { + algo = algorithm::PREFER_GMEM; + } else { +- mesa_logw("Unknown TU_AUTOTUNE_ALGO '%s', using default", algo_strv.data()); ++ mesa_logw("Unknown TU_AUTOTUNE_ALGO '%s', using default", algo_env_str); + } + + if (TU_DEBUG(STARTUP)) +- mesa_logi("TU_AUTOTUNE_ALGO=%u (%s)", (uint8_t) algo, algo_strv.data()); ++ mesa_logi("TU_AUTOTUNE_ALGO=%u (%s)", (uint8_t) algo, algo_env_str); + } + + /* Parse the flags from the environment variable. */ +diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc +index a41b93c4948..25e11d45f8f 100644 +--- a/src/freedreno/vulkan/tu_device.cc ++++ b/src/freedreno/vulkan/tu_device.cc +@@ -1840,7 +1840,6 @@ static const driOptionDescription tu_dri_options[] = { + DRI_CONF_TU_IGNORE_FRAG_DEPTH_DIRECTION(false) + DRI_CONF_TU_ENABLE_SOFTFLOAT32(false) + DRI_CONF_TU_EMULATE_ALPHA_TO_COVERAGE(false) +- DRI_CONF_TU_AUTOTUNE_ALGORITHM() + DRI_CONF_SECTION_END + }; + +@@ -1873,8 +1872,6 @@ tu_init_dri_options(struct tu_instance *instance) + driQueryOptionb(&instance->dri_options, "tu_enable_softfloat32"); + instance->emulate_alpha_to_coverage = + driQueryOptionb(&instance->dri_options, "tu_emulate_alpha_to_coverage"); +- instance->autotune_algo = +- driQueryOptionstr(&instance->dri_options, "tu_autotune_algorithm"); + } + + static uint32_t instance_count = 0; +diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h +index c9f521fcc15..b0ecf2f62ba 100644 +--- a/src/freedreno/vulkan/tu_device.h ++++ b/src/freedreno/vulkan/tu_device.h +@@ -237,9 +237,6 @@ struct tu_instance + * instead. + */ + bool emulate_alpha_to_coverage; +- +- /* Configuration option to use a specific autotune algorithm by default. */ +- const char *autotune_algo; + }; + VK_DEFINE_HANDLE_CASTS(tu_instance, vk.base, VkInstance, + VK_OBJECT_TYPE_INSTANCE) +diff --git a/src/util/driconf.h b/src/util/driconf.h +index 5a73c15ac2f..a0b5e40af0b 100644 +--- a/src/util/driconf.h ++++ b/src/util/driconf.h +@@ -692,10 +692,6 @@ + DRI_CONF_OPT_B(tu_emulate_alpha_to_coverage, def, \ + "Enable emulation of alpha-to-coverage") + +-#define DRI_CONF_TU_AUTOTUNE_ALGORITHM() \ +- DRI_CONF_OPT_S_NODEF(tu_autotune_algorithm, \ +- "Set the preferred autotune algorithm") +- + /** + * \brief Honeykrisp specific configuration options + */ +-- +2.54.0 + diff --git a/0010-Revert-tu-autotune-Add-prefer-SYSMEM-GMEM-mode.patch b/0010-Revert-tu-autotune-Add-prefer-SYSMEM-GMEM-mode.patch new file mode 100644 index 0000000..4aae6d8 --- /dev/null +++ b/0010-Revert-tu-autotune-Add-prefer-SYSMEM-GMEM-mode.patch @@ -0,0 +1,85 @@ +From dad9e8d8725004cb48a0f56982223b5c5a43f765 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 10:55:26 +0200 +Subject: [PATCH 10/19] Revert "tu/autotune: Add prefer SYSMEM/GMEM mode" + +This reverts commit 180c0de74600079795001993feb31bafb9ba0488. +--- + docs/drivers/freedreno.rst | 8 -------- + src/freedreno/vulkan/tu_autotune.cc | 19 ++++--------------- + 2 files changed, 4 insertions(+), 23 deletions(-) + +diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst +index 90afea283a4..e5adeeb5e68 100644 +--- a/docs/drivers/freedreno.rst ++++ b/docs/drivers/freedreno.rst +@@ -699,14 +699,6 @@ environment variables: + for single-frame traces run multiple times in a CI where this algorithm can + immediately chose the optimal rendering mode for each RP. + +- ``prefer_sysmem``/``prefer_gmem`` +- Always chooses SYSMEM/GMEM rendering. This is useful for games that work +- better in one mode over the other due to their rendering patterns, setting +- this is better than using ``TU_DEBUG=sysmem``/``TU_DEBUG=gmem`` when done +- for performance reasons, since these still allow the other mode to be used +- in some high-confidence cases as well as letting ``TU_AUTOTUNE_FLAGS`` still +- be applied. +- + .. envvar:: TU_AUTOTUNE_FLAGS + + Modifies the behavior of the selected algorithm. Supported flags are: +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index b57c393e06e..99ad1d495d5 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -94,11 +94,9 @@ render_mode_str(tu_autotune::render_mode mode) + /** Configuration **/ + + enum class tu_autotune::algorithm : uint8_t { +- BANDWIDTH = 0, /* Uses estimated BW for determining rendering mode. */ +- PROFILED = 1, /* Uses dynamically profiled results for determining rendering mode. */ +- PROFILED_IMM = 2, /* Same as PROFILED but immediately resolves the SYSMEM/GMEM probability. */ +- PREFER_SYSMEM = 3, /* Always use SYSMEM unless we have strong evidence that GMEM is better. */ +- PREFER_GMEM = 4, /* Always use GMEM unless we have strong evidence that SYSMEM is better. */ ++ BANDWIDTH = 0, /* Uses estimated BW for determining rendering mode. */ ++ PROFILED = 1, /* Uses dynamically profiled results for determining rendering mode. */ ++ PROFILED_IMM = 2, /* Same as PROFILED but immediately resolves the SYSMEM/GMEM probability. */ + + DEFAULT = BANDWIDTH, /* Default algorithm, used if no other is specified. */ + }; +@@ -209,8 +207,6 @@ struct PACKED tu_autotune::config_t { + ALGO_STR(BANDWIDTH); + ALGO_STR(PROFILED); + ALGO_STR(PROFILED_IMM); +- ALGO_STR(PREFER_SYSMEM); +- ALGO_STR(PREFER_GMEM); + + str += ", Mod Flags: 0x" + std::to_string(mod_flags) + " ("; + MODF_STR(BIG_GMEM); +@@ -284,10 +280,6 @@ tu_autotune::get_env_config() + algo = algorithm::PROFILED; + } else if (algo_strv == "profiled_imm") { + algo = algorithm::PROFILED_IMM; +- } else if (algo_strv == "prefer_sysmem") { +- algo = algorithm::PREFER_SYSMEM; +- } else if (algo_strv == "prefer_gmem") { +- algo = algorithm::PREFER_GMEM; + } else { + mesa_logw("Unknown TU_AUTOTUNE_ALGO '%s', using default", algo_env_str); + } +@@ -1780,11 +1772,8 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + */ + bool can_early_return = !config.test(mod_flag::PREEMPT_OPTIMIZE); + auto early_return_mode = [&]() -> std::optional { +- if ((config.test(mod_flag::BIG_GMEM) && rp_state->drawcall_count >= 10) || +- config.is_enabled(algorithm::PREFER_GMEM)) ++ if (config.test(mod_flag::BIG_GMEM) && rp_state->drawcall_count >= 10) + return render_mode::GMEM; +- if (config.is_enabled(algorithm::PREFER_SYSMEM)) +- return render_mode::SYSMEM; + return std::nullopt; + }(); + +-- +2.54.0 + diff --git a/0011-Revert-tu-Only-emit-preempt-optimization-ambles-when.patch b/0011-Revert-tu-Only-emit-preempt-optimization-ambles-when.patch new file mode 100644 index 0000000..eb855e0 --- /dev/null +++ b/0011-Revert-tu-Only-emit-preempt-optimization-ambles-when.patch @@ -0,0 +1,88 @@ +From f514b4c856c652c2c959f8a0cb1f215c191b1d10 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:01:43 +0200 +Subject: [PATCH 11/19] Revert "tu: Only emit preempt optimization ambles when + active" + +This reverts commit 46aac5abaf0fb6382ad5d497d6cf0974fb6e1d84. +--- + src/freedreno/vulkan/tu_autotune.cc | 6 ++---- + src/freedreno/vulkan/tu_autotune.h | 3 +-- + src/freedreno/vulkan/tu_cmd_buffer.cc | 6 +++--- + 3 files changed, 6 insertions(+), 9 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 99ad1d495d5..c456c248cdb 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -2119,11 +2119,11 @@ tu_autotune::emit_reset_rp_hash_draw_state(struct tu_cmd_buffer *cmd, struct tu_ + tu_cs_emit_qw(cs, reset_rp_hash_draw_state.iova); + } + +-bool ++void + tu_autotune::emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + { + if (!cmd->autotune_ctx.tracks_preempt_latency()) +- return false; ++ return; + + tu_cs_emit_pkt7(cs, CP_MEM_WRITE, 4); + tu_cs_emit_qw(cs, global_iova(cmd, max_preemption_latency)); +@@ -2143,8 +2143,6 @@ tu_autotune::emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, stru + + write_preempt_counters_to_iova(cs, true, true, global_iova(cmd, base_preemption_latency), + global_iova(cmd, base_always_count), global_iova(cmd, base_aon)); +- +- return true; + } + + tu_autotune::rp_key_opt +diff --git a/src/freedreno/vulkan/tu_autotune.h b/src/freedreno/vulkan/tu_autotune.h +index aba8d3e6f95..868c06bb7d6 100644 +--- a/src/freedreno/vulkan/tu_autotune.h ++++ b/src/freedreno/vulkan/tu_autotune.h +@@ -355,8 +355,7 @@ struct tu_autotune { + void init_reset_rp_hash_draw_state(); + void emit_reset_rp_hash_draw_state(struct tu_cmd_buffer *cmd, struct tu_cs *cs) const; + +- /* Returns if preemption latency tracking is enabled for this CB. */ +- bool emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, struct tu_cs *cs); ++ void emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, struct tu_cs *cs); + /* Returns the RP hash only when preemption latency tracking is enabled. */ + rp_key_opt emit_preempt_latency_tracking_rp_hash(struct tu_cmd_buffer *cmd); + }; +diff --git a/src/freedreno/vulkan/tu_cmd_buffer.cc b/src/freedreno/vulkan/tu_cmd_buffer.cc +index 082825fa84b..c345564dee7 100644 +--- a/src/freedreno/vulkan/tu_cmd_buffer.cc ++++ b/src/freedreno/vulkan/tu_cmd_buffer.cc +@@ -2439,7 +2439,7 @@ tu_init_hw(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + tu7_set_thread_br_patchpoint(cmd, cs, false); + } + +- bool track_preempt_latency = dev->autotune->emit_preempt_latency_tracking_setup(cmd, cs); ++ dev->autotune->emit_preempt_latency_tracking_setup(cmd, cs); + + tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); + tu_cs_emit_qw(cs, cmd->device->bin_preamble_entry.bo->iova + +@@ -2465,7 +2465,7 @@ tu_init_hw(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + (1u << TU_PREDICATE_VTX_STATS_NOT_RUNNING)); + } + +- if (dev->switch_back_amble_entry.size > 0 && track_preempt_latency) { ++ if (dev->switch_back_amble_entry.size > 0) { + tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); + tu_cs_emit_qw(cs, dev->switch_back_amble_entry.bo->iova + dev->switch_back_amble_entry.offset); + tu_cs_emit(cs, CP_SET_AMBLE_2_DWORDS(dev->switch_back_amble_entry.size / sizeof(uint32_t)) | +@@ -2476,7 +2476,7 @@ tu_init_hw(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + tu_cs_emit(cs, CP_SET_AMBLE_2_TYPE(PREAMBLE_AMBLE_TYPE)); + } + +- if (dev->switch_away_amble_entry.size > 0 && track_preempt_latency) { ++ if (dev->switch_away_amble_entry.size > 0) { + tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); + tu_cs_emit_qw(cs, dev->switch_away_amble_entry.bo->iova + dev->switch_away_amble_entry.offset); + tu_cs_emit(cs, CP_SET_AMBLE_2_DWORDS(dev->switch_away_amble_entry.size / sizeof(uint32_t)) | +-- +2.54.0 + diff --git a/0012-Revert-tu-Disable-features-using-performance-counter.patch b/0012-Revert-tu-Disable-features-using-performance-counter.patch new file mode 100644 index 0000000..66896ca --- /dev/null +++ b/0012-Revert-tu-Disable-features-using-performance-counter.patch @@ -0,0 +1,99 @@ +From ab7adaf47f2606e6b85a46470a586718cd409517 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:01:57 +0200 +Subject: [PATCH 12/19] Revert "tu: Disable features using performance counter + for KGSL" + +This reverts commit 18437c7a65a909044add3cb78c1e291bbf871743. +--- + src/freedreno/vulkan/tu_autotune.cc | 3 +-- + src/freedreno/vulkan/tu_device.cc | 2 +- + src/freedreno/vulkan/tu_device.h | 3 --- + src/freedreno/vulkan/tu_knl_drm_msm.cc | 2 -- + src/freedreno/vulkan/tu_knl_drm_virtio.cc | 1 - + src/freedreno/vulkan/tu_knl_kgsl.cc | 3 --- + 6 files changed, 2 insertions(+), 12 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index c456c248cdb..439ac05a845 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -323,8 +323,7 @@ uint32_t + tu_autotune::get_supported_mod_flags(tu_device *device) const + { + uint32_t supported_mod_flags = (uint32_t) mod_flag::BIG_GMEM | (uint32_t) mod_flag::TUNE_SMALL; +- if (device->physical_device->info->props.max_draw_states > TU_DRAW_STATE_AT_WRITE_RP_HASH && +- device->physical_device->is_perf_cntr_selectable) { ++ if (device->physical_device->info->props.max_draw_states > TU_DRAW_STATE_AT_WRITE_RP_HASH) { + supported_mod_flags |= (uint32_t) mod_flag::PREEMPT_OPTIMIZE; + } + return supported_mod_flags; +diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc +index 25e11d45f8f..24ca3d2391c 100644 +--- a/src/freedreno/vulkan/tu_device.cc ++++ b/src/freedreno/vulkan/tu_device.cc +@@ -212,7 +212,7 @@ get_device_extensions(const struct tu_physical_device *device, + .KHR_maintenance8 = tu_is_vk_1_1(device), + .KHR_map_memory2 = true, + .KHR_multiview = tu_has_multiview(device), +- .KHR_performance_query = (TU_DEBUG(PERFC) || TU_DEBUG(PERFCRAW)) && device->is_perf_cntr_selectable, ++ .KHR_performance_query = TU_DEBUG(PERFC) || TU_DEBUG(PERFCRAW), + .KHR_pipeline_executable_properties = true, + .KHR_pipeline_library = true, + #ifdef TU_USE_WSI_PLATFORM +diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h +index b0ecf2f62ba..638ea404fa7 100644 +--- a/src/freedreno/vulkan/tu_device.h ++++ b/src/freedreno/vulkan/tu_device.h +@@ -140,9 +140,6 @@ struct tu_physical_device + + bool has_preemption; + +- /* Whether performance counter selector registers can be written by userspace CSes. */ +- bool is_perf_cntr_selectable; +- + struct { + uint32_t non_lazy_type_count; + uint32_t type_count; +diff --git a/src/freedreno/vulkan/tu_knl_drm_msm.cc b/src/freedreno/vulkan/tu_knl_drm_msm.cc +index 16e1cb40337..75a6e1a0250 100644 +--- a/src/freedreno/vulkan/tu_knl_drm_msm.cc ++++ b/src/freedreno/vulkan/tu_knl_drm_msm.cc +@@ -1667,8 +1667,6 @@ tu_knl_drm_msm_load(struct tu_instance *instance, + + device->has_preemption = tu_drm_has_preemption(device); + +- device->is_perf_cntr_selectable = true; +- + /* Even if kernel is new enough, the GPU itself may not support it. */ + device->has_cached_coherent_memory = + (device->msm_minor_version >= 8) && +diff --git a/src/freedreno/vulkan/tu_knl_drm_virtio.cc b/src/freedreno/vulkan/tu_knl_drm_virtio.cc +index ea4d31ab665..9dbf9ee166d 100644 +--- a/src/freedreno/vulkan/tu_knl_drm_virtio.cc ++++ b/src/freedreno/vulkan/tu_knl_drm_virtio.cc +@@ -1343,7 +1343,6 @@ tu_knl_drm_virtio_load(struct tu_instance *instance, + device->has_set_iova = true; + device->has_lazy_bos = true; + device->has_preemption = has_preemption; +- device->is_perf_cntr_selectable = true; + device->uche_trap_base = uche_trap_base; + + device->ubwc_config.bank_swizzle_levels = bank_swizzle_levels; +diff --git a/src/freedreno/vulkan/tu_knl_kgsl.cc b/src/freedreno/vulkan/tu_knl_kgsl.cc +index c01160ea1f3..b960ef16673 100644 +--- a/src/freedreno/vulkan/tu_knl_kgsl.cc ++++ b/src/freedreno/vulkan/tu_knl_kgsl.cc +@@ -1868,9 +1868,6 @@ tu_knl_kgsl_load(struct tu_instance *instance, int fd) + /* preemption is always supported on kgsl */ + device->has_preemption = true; + +- /* KGSL doesn't allow writing the perf counter selector as the expectation is to use the uAPI provided for this. */ +- device->is_perf_cntr_selectable = false; +- + device->ubwc_config.highest_bank_bit = highest_bank_bit; + + /* The other config values can be partially inferred from the UBWC version, +-- +2.54.0 + diff --git a/0013-Revert-tu-autotune-Add-Preempt-Optimize-mode.patch b/0013-Revert-tu-autotune-Add-Preempt-Optimize-mode.patch new file mode 100644 index 0000000..2b605bf --- /dev/null +++ b/0013-Revert-tu-autotune-Add-Preempt-Optimize-mode.patch @@ -0,0 +1,2213 @@ +From 9dd8829adf3ddcb94f04a42236556b5138b32319 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:03:06 +0200 +Subject: [PATCH 13/19] Revert "tu/autotune: Add "Preempt Optimize" mode" + +This reverts commit 3fcec4762f3fec551c00da52f349c83443152159. +--- + docs/drivers/freedreno.rst | 16 - + src/freedreno/common/freedreno_dev_info.h | 3 - + src/freedreno/common/freedreno_devices.py | 2 - + src/freedreno/vulkan/tu_autotune.cc | 925 +--------------------- + src/freedreno/vulkan/tu_autotune.h | 128 +-- + src/freedreno/vulkan/tu_clear_blit.cc | 5 +- + src/freedreno/vulkan/tu_cmd_buffer.cc | 108 +-- + src/freedreno/vulkan/tu_cmd_buffer.h | 14 +- + src/freedreno/vulkan/tu_device.cc | 34 +- + src/freedreno/vulkan/tu_device.h | 26 +- + src/freedreno/vulkan/tu_pass.h | 2 - + src/freedreno/vulkan/tu_queue.cc | 9 +- + src/freedreno/vulkan/tu_queue.h | 1 - + src/freedreno/vulkan/tu_tracepoints.py | 4 +- + src/freedreno/vulkan/tu_util.cc | 94 +-- + src/freedreno/vulkan/tu_util.h | 13 +- + 16 files changed, 92 insertions(+), 1292 deletions(-) + +diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst +index e5adeeb5e68..9b1992fd628 100644 +--- a/docs/drivers/freedreno.rst ++++ b/docs/drivers/freedreno.rst +@@ -714,22 +714,6 @@ environment variables: + calls). By default, small RPs always use SYSMEM mode as they generally don't + benefit from GMEM rendering due to the overhead of tiling. + +- ``preempt_optimize`` +- Tries to keep non-preemptible time in the render pass is below a certain +- threshold. This is useful for systems with GPU-based compositors where long +- non-preemptible times can lead to missed frame deadlines, causing noticeable +- stuttering. This flag will reduce the performance of the render pass in order +- to improve overall system responsiveness, it should not be used unless the +- rest of the system is affected by preemption delays. +- +- This is done by measuring the time between a preemption request and preemption +- actually occurring, if it is above a threshold we force those RPs to use GMEM +- rendering, where non-preemptible times are driven by tile size which we +- progressively reduce until the non-preemptible time is below the threshold. +- +- It should be noted that this will occupy the last two CP performance counters +- which may interfere with other profiling tools (such as `fdperf`). +- + Multiple flags can be combined by separating them with commas, e.g. + ``TU_AUTOTUNE_FLAGS=big_gmem,tune_small``. + +diff --git a/src/freedreno/common/freedreno_dev_info.h b/src/freedreno/common/freedreno_dev_info.h +index a1299a0dc09..5c48e68e626 100644 +--- a/src/freedreno/common/freedreno_dev_info.h ++++ b/src/freedreno/common/freedreno_dev_info.h +@@ -471,9 +471,6 @@ struct fd_dev_info { + + /* Whether the device supports the image processing opcode */ + bool has_image_processing; +- +- /* The amount of valid draw state IDs. */ +- uint32_t max_draw_states; + } props; + }; + +diff --git a/src/freedreno/common/freedreno_devices.py b/src/freedreno/common/freedreno_devices.py +index 80bc917221c..5298a9642e4 100644 +--- a/src/freedreno/common/freedreno_devices.py ++++ b/src/freedreno/common/freedreno_devices.py +@@ -147,7 +147,6 @@ a6xx_base = GPUProps( + line_width_min = 1.0, + line_width_max = 1.0, + mov_half_shared_quirk = True, +- max_draw_states = 32, + ) + + +@@ -861,7 +860,6 @@ a7xx_gen3 = GPUProps( + new_control_regs = True, + has_hw_bin_scaling = True, + has_image_processing = True, +- max_draw_states = 64, + has_64b_image_atomics = True, + ) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 439ac05a845..c708c83c044 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -28,7 +29,6 @@ + #define TU_AUTOTUNE_DEBUG_LOG_BASE 0 + #define TU_AUTOTUNE_DEBUG_LOG_BANDWIDTH 0 + #define TU_AUTOTUNE_DEBUG_LOG_PROFILED 0 +-#define TU_AUTOTUNE_DEBUG_LOG_PREEMPT 0 + + #if TU_AUTOTUNE_DEBUG_LOG_BASE + #define at_log_base(fmt, ...) mesa_logi("autotune: " fmt, ##__VA_ARGS__) +@@ -50,12 +50,6 @@ + #define at_log_profiled_h(fmt, hash, ...) + #endif + +-#if TU_AUTOTUNE_DEBUG_LOG_PREEMPT +-#define at_log_preempt_h(fmt, hash, ...) mesa_logi("autotune-preempt %016" PRIx64 ": " fmt, hash, ##__VA_ARGS__) +-#else +-#define at_log_preempt_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 + +@@ -105,14 +99,12 @@ enum class tu_autotune::algorithm : uint8_t { + enum class tu_autotune::mod_flag : uint8_t { + BIG_GMEM = BIT(1), /* All RPs with >= 10 draws use GMEM. */ + TUNE_SMALL = BIT(2), /* Try tuning all RPs with <= 5 draws, ignored by default. */ +- PREEMPT_OPTIMIZE = BIT(3), /* Attempts to minimize the preemption latency. */ + }; + + /* Metric flags, for internal tracking of enabled metrics. */ + enum class tu_autotune::metric_flag : uint8_t { + SAMPLES = BIT(1), /* Enable tracking samples passed metric. */ + TS = BIT(2), /* Enable tracking per-RP timestamp metric. */ +- TS_TILE = BIT(3), /* Enable tracking per-tile timestamp metric. */ + }; + + struct PACKED tu_autotune::config_t { +@@ -129,10 +121,6 @@ struct PACKED tu_autotune::config_t { + } else if (algo == algorithm::PROFILED || algo == algorithm::PROFILED_IMM) { + metric_flags |= (uint8_t) metric_flag::TS; + } +- +- if (mod_flags & (uint8_t) mod_flag::PREEMPT_OPTIMIZE) { +- metric_flags |= (uint8_t) metric_flag::TS | (uint8_t) metric_flag::TS_TILE; +- } + } + + public: +@@ -211,13 +199,11 @@ struct PACKED tu_autotune::config_t { + str += ", Mod Flags: 0x" + std::to_string(mod_flags) + " ("; + MODF_STR(BIG_GMEM); + MODF_STR(TUNE_SMALL); +- MODF_STR(PREEMPT_OPTIMIZE); + str += ")"; + + str += ", Metric Flags: 0x" + std::to_string(metric_flags) + " ("; + METRICF_STR(SAMPLES); + METRICF_STR(TS); +- METRICF_STR(TS_TILE); + str += ")"; + + return str; +@@ -295,18 +281,12 @@ tu_autotune::get_env_config() + static const struct debug_control tu_at_flags_control[] = { + { "big_gmem", (uint32_t) mod_flag::BIG_GMEM }, + { "tune_small", (uint32_t) mod_flag::TUNE_SMALL }, +- { "preempt_optimize", (uint32_t) mod_flag::PREEMPT_OPTIMIZE }, + { 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); +- +- if ((mod_flags & ~supported_mod_flags) != 0) { +- mesa_logw("Unsupported TU_AUTOTUNE_FLAGS=0x%x, supported flags: 0x%x", mod_flags, supported_mod_flags); +- mod_flags &= supported_mod_flags; +- } + } + + assert((uint8_t) mod_flags == mod_flags); +@@ -319,16 +299,6 @@ tu_autotune::get_env_config() + return at_config; + } + +-uint32_t +-tu_autotune::get_supported_mod_flags(tu_device *device) const +-{ +- uint32_t supported_mod_flags = (uint32_t) mod_flag::BIG_GMEM | (uint32_t) mod_flag::TUNE_SMALL; +- if (device->physical_device->info->props.max_draw_states > TU_DRAW_STATE_AT_WRITE_RP_HASH) { +- supported_mod_flags |= (uint32_t) mod_flag::PREEMPT_OPTIMIZE; +- } +- return supported_mod_flags; +-} +- + /** Global Fence and Internal CS Management **/ + + tu_autotune::submission_entry::submission_entry(tu_device *device): fence(0) +@@ -413,26 +383,6 @@ struct PACKED tu_autotune::rp_gpu_data { + uint64_t ts_end; + }; + +-/* Per-tile values for GMEM rendering, this structure is appended to the end of rp_gpu_data for each tile. */ +-struct PACKED tu_autotune::tile_gpu_data { +- uint64_t ts_start; +- uint64_t ts_end; +- +- /* A helper for the offset of this relative to BO start. */ +- static constexpr uint64_t offset(uint32_t tile_index) +- { +- return sizeof(rp_gpu_data) + (tile_index * sizeof(tile_gpu_data)); +- } +-}; +- +-/* ALl of these values correspond to the RP in the batch with the max preemption latency. */ +-struct PACKED tu_autotune::rp_batch_preempt_gpu_data { +- uint64_t preemption_latency; /* in CP clock ticks. */ +- uint64_t preemption_latency_rp_hash; +- uint64_t always_count_delta; +- uint64_t aon_delta; +-}; +- + /* A small wrapper around rp_history to provide ref-counting and usage timestamps. */ + struct tu_autotune::rp_history_handle { + rp_history *history; +@@ -498,15 +448,16 @@ struct tu_autotune::rp_entry { + 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); +- static_assert(sizeof(rp_gpu_data) % alignof(tile_gpu_data) == 0); + + public: + rp_history_handle history; + config_t config; /* Configuration at the time of entry creation. */ + bool sysmem; +- uint32_t tile_count; + uint32_t draw_count; + ++ /* Amount of repeated RPs so far, used for uniquely identifying instances of the same RPs. */ ++ uint32_t duplicates = 0; ++ + 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) + { +@@ -526,11 +477,10 @@ struct tu_autotune::rp_entry { + rp_entry(rp_entry &&) = delete; + rp_entry &operator=(rp_entry &&) = delete; + +- void allocate(bool sysmem, uint32_t tile_count) ++ void allocate(bool sysmem) + { + this->sysmem = sysmem; +- this->tile_count = tile_count; +- size_t total_size = sizeof(rp_gpu_data) + (tile_count * sizeof(tile_gpu_data)); ++ 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)); +@@ -549,14 +499,6 @@ struct tu_autotune::rp_entry { + return *(rp_gpu_data *) map; + } + +- tile_gpu_data &get_tile_gpu_data(uint32_t tile_index) +- { +- assert(map); +- assert(tile_index < tile_count); +- uint64_t offset = tile_gpu_data::offset(tile_index); +- return *(tile_gpu_data *) (map + offset); +- } +- + /** Samples-Passed Metric **/ + + uint64_t get_samples_passed() +@@ -628,25 +570,10 @@ struct tu_autotune::rp_entry { + return gpu.ts_end - gpu.ts_start; + } + +- /* The amount of cycles spent in the longest tile. This is used to calculate the average draw duration for +- * determining the largest non-preemptible duration for GMEM rendering. +- */ +- uint64_t get_max_tile_duration() +- { +- assert(config.test(metric_flag::TS_TILE)); +- uint64_t max_duration = 0; +- for (uint32_t i = 0; i < tile_count; i++) { +- tile_gpu_data &tile = get_tile_gpu_data(i); +- max_duration = MAX2(max_duration, tile.ts_end - tile.ts_start); +- } +- return max_duration; +- } +- + void emit_metric_timestamp(struct tu_cs *cs, uint64_t timestamp_iova) + { + tu_cs_emit_pkt7(cs, CP_REG_TO_MEM, 3); +- tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(TU_CALLX(device, __CP_ALWAYS_ON_COUNTER)({}).reg) | CP_REG_TO_MEM_0_CNT(2) | +- CP_REG_TO_MEM_0_64B); ++ tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(REG_A6XX_CP_ALWAYS_ON_COUNTER) | CP_REG_TO_MEM_0_CNT(2) | CP_REG_TO_MEM_0_64B); + tu_cs_emit_qw(cs, timestamp_iova); + } + +@@ -674,73 +601,10 @@ struct tu_autotune::rp_entry { + if (config.test(metric_flag::TS)) + emit_metric_timestamp(cs, bo_iova + offsetof(rp_gpu_data, ts_end)); + } +- +- void emit_tile_start(struct tu_cmd_buffer *cmd, struct tu_cs *cs, uint32_t tile_index) +- { +- assert(map && bo.iova); +- assert(!sysmem); +- assert(tile_index < tile_count); +- if (config.test(metric_flag::TS_TILE)) +- emit_metric_timestamp(cs, bo.iova + tile_gpu_data::offset(tile_index) + offsetof(tile_gpu_data, ts_start)); +- } +- +- void emit_tile_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs, uint32_t tile_index) +- { +- assert(map && bo.iova); +- assert(!sysmem); +- assert(tile_index < tile_count); +- if (config.test(metric_flag::TS_TILE)) +- emit_metric_timestamp(cs, bo.iova + tile_gpu_data::offset(tile_index) + offsetof(tile_gpu_data, ts_end)); +- } + }; + +-tu_autotune::rp_batch_preempt_latency::rp_batch_preempt_latency(struct tu_device *device, bool allocate) +- : device(device), allocated(allocate) ++tu_autotune::rp_entry_batch::rp_entry_batch(): active(false), fence(0), entries() + { +- if (!allocate) +- return; +- +- { +- std::scoped_lock lock(device->autotune->suballoc_mutex); +- VkResult result = tu_suballoc_bo_alloc(&bo, &device->autotune->suballoc, sizeof(rp_batch_preempt_gpu_data), +- alignof(rp_batch_preempt_gpu_data)); +- +- if (result != VK_SUCCESS) { +- mesa_loge("Failed to allocate BO for autotune rp_batch_preempt_gpu_data: %u", result); +- allocated = false; +- return; +- } +- } +- +- map = (uint8_t *) tu_suballoc_bo_map(&bo); +- memset(map, 0, sizeof(rp_batch_preempt_gpu_data)); +-} +- +-tu_autotune::rp_batch_preempt_latency::~rp_batch_preempt_latency() +-{ +- if (!allocated) +- return; +- +- std::scoped_lock lock(device->autotune->suballoc_mutex); +- tu_suballoc_bo_free(&device->autotune->suballoc, &bo); +-} +- +-tu_autotune::rp_batch_preempt_gpu_data +-tu_autotune::rp_batch_preempt_latency::get_gpu_data() +-{ +- assert(allocated); +- return *(rp_batch_preempt_gpu_data *) map; +-} +- +-tu_autotune::rp_entry_batch::rp_entry_batch(struct tu_device *device, bool track_preempt_latency) +- : active(false), fence(0), entries(), preempt_latency(device, track_preempt_latency) +-{ +-} +- +-bool +-tu_autotune::rp_entry_batch::requires_processing() const +-{ +- return !entries.empty() || (preempt_latency.allocated && !all_renderpasses.empty()); + } + + void +@@ -759,28 +623,6 @@ tu_autotune::rp_entry_batch::mark_inactive() + fence = 0; + } + +-void +-tu_autotune::rp_entry_batch::snapshot_preempt_data(struct tu_cs *cs) +-{ +- if (!preempt_latency.allocated) +- return; +- +- constexpr size_t base_offset = gb_offset(max_preemption_latency); +- static_assert(gb_offset(max_preemption_latency) == +- base_offset + offsetof(rp_batch_preempt_gpu_data, preemption_latency)); +- static_assert(gb_offset(max_preemption_latency_rp_hash) == +- base_offset + offsetof(rp_batch_preempt_gpu_data, preemption_latency_rp_hash)); +- static_assert(gb_offset(max_always_count_delta) == +- base_offset + offsetof(rp_batch_preempt_gpu_data, always_count_delta)); +- static_assert(gb_offset(max_aon_delta) == base_offset + offsetof(rp_batch_preempt_gpu_data, aon_delta)); +- static_assert(sizeof(rp_batch_preempt_gpu_data) == 32); +- +- tu_cs_emit_pkt7(cs, CP_MEMCPY, 5); +- tu_cs_emit(cs, sizeof(rp_batch_preempt_gpu_data) / sizeof(uint32_t)); +- tu_cs_emit_qw(cs, preempt_latency.device->global_bo->iova + base_offset); +- tu_cs_emit_qw(cs, preempt_latency.bo.iova); +-} +- + /** Renderpass state tracking. **/ + + tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass, +@@ -1118,125 +960,6 @@ struct tu_autotune::rp_history { + } + } profiled; + +- /** Preemption Latency Optimization Mode **/ +- struct preempt_optimize_mode { +- private: +- adaptive_average gmem_tile_average; +- +- /* If the renderpass has long draws which are at risk of causing high preemptible latency. */ +- std::atomic latency_risk = false; +- /* The factor by which the tile size should be divided to reduce preemption latency. */ +- std::atomic tile_size_divisor = 1; +- +- /* The next timestamp to update the latency sensitivity parameters at. */ +- uint64_t latency_update_ts = 0; +- /* The next timestamp where it's allowed to decrement the divisor. */ +- uint64_t divisor_decrement_ts = 0; +- /* The next timestamp where it's allowed to mark the RP as no longer latency sensitive. */ +- uint64_t latency_switch_ts = 0; +- +- /* Threshold of longest non-preemptible duration before activating latency optimization: 1.5ms */ +- static constexpr uint64_t TARGET_THRESHOLD = GPU_TICKS_PER_US * 1500; +- +- public: +- void update_gmem(rp_history &history, uint64_t tile_duration) +- { +- constexpr uint64_t default_update_duration_ns = 100'000'000; /* 100ms */ +- constexpr uint64_t change_update_duration_ns = 500'000'000; /* 500ms */ +- constexpr uint64_t downward_update_duration_ns = 10'000'000'000; /* 10s */ +- constexpr uint64_t latency_insensitive_duration_ns = 30'000'000'000; /* 30s */ +- +- gmem_tile_average.add(tile_duration); +- +- uint64_t now = os_time_get_nano(); +- if (latency_update_ts > now) +- return; /* No need to update yet. */ +- +- /* If the RP is latency sensitive and we're using GMEM, we should check if it's worth reducing the tile size to +- * reduce the latency risk further or if it's already low enough that it's not worth the performance hit. +- */ +- +- uint64_t update_duration_ns = default_update_duration_ns; +- if (gmem_tile_average.count > MIN_PROFILE_DURATION_COUNT) { +- uint64_t avg_gmem_tile = gmem_tile_average.get(); +- bool l_latency_risk = latency_risk.load(std::memory_order_relaxed); +- if (!l_latency_risk) { +- if (avg_gmem_tile > TARGET_THRESHOLD) { +- latency_risk.store(true, std::memory_order_relaxed); +- latency_switch_ts = now + latency_insensitive_duration_ns; +- +- at_log_preempt_h("high gmem tile duration %" PRIu64 ", marking as latency sensitive", history.hash, +- avg_gmem_tile); +- } +- } else { +- uint32_t l_tile_size_divisor = tile_size_divisor.load(std::memory_order_relaxed); +- at_log_preempt_h("avg_gmem_tile: %" PRIu64 " us (%u), latency_risk: %u, tile_size_divisor: %" PRIu32, +- history.hash, ticks_to_us(avg_gmem_tile), avg_gmem_tile > TARGET_THRESHOLD, +- l_latency_risk, l_tile_size_divisor); +- +- int delta = 0; +- if (avg_gmem_tile > TARGET_THRESHOLD && l_tile_size_divisor < TU_GMEM_LAYOUT_DIVISOR_MAX) { +- /* If the average tile duration is high, we should reduce the tile size to reduce the latency risk. */ +- delta = 1; +- +- divisor_decrement_ts = now + downward_update_duration_ns; +- } else if (avg_gmem_tile * 4 < TARGET_THRESHOLD && l_tile_size_divisor > 1 && +- divisor_decrement_ts <= now) { +- /* If the average tile duration is low enough that we can get away with a larger tile size, we should +- * increase the tile size to reduce the performance hit of the smaller tiles. +- * +- * Note: The 4x factor is to account for the tile duration being halved when we increase the tile size +- * divisor by 1, with an additional 2x factor to generally be conservative about reducing the divisor +- * since it can lead to oscillation between tile sizes. +- * +- * Similarly, divisor_decrement_ts is used to limit how often we can reduce the divisor to avoid +- * oscillation. +- */ +- delta = -1; +- latency_switch_ts = now + latency_insensitive_duration_ns; +- } else if (avg_gmem_tile * 10 < TARGET_THRESHOLD && l_tile_size_divisor == 1 && +- latency_switch_ts <= now) { +- /* If the average tile duration is low enough that we no longer consider the RP latency sensitive, we +- * can switch it back to non-latency sensitive. +- */ +- latency_risk.store(false, std::memory_order_relaxed); +- } +- +- if (delta != 0) { +- /* Clear all the results to avoid biasing the decision based on the old tile size. */ +- gmem_tile_average.clear(); +- +- uint32_t new_tile_size_divisor = l_tile_size_divisor + delta; +- at_log_preempt_h("updating tile size divisor: %" PRIu32 " -> %" PRIu32, history.hash, +- l_tile_size_divisor, new_tile_size_divisor); +- +- tile_size_divisor.store(new_tile_size_divisor, std::memory_order_relaxed); +- +- update_duration_ns = change_update_duration_ns; +- } +- } +- +- latency_update_ts = now + update_duration_ns; +- } +- } +- +- /* If this RP has a risk of causing high preemption latency. */ +- bool is_latency_sensitive() const +- { +- return latency_risk.load(std::memory_order_relaxed); +- } +- +- void mark_as_latency_sensitive() +- { +- latency_risk.store(true, std::memory_order_relaxed); +- } +- +- uint32_t get_tile_size_divisor() const +- { +- return tile_size_divisor.load(std::memory_order_relaxed); +- } +- } preempt_optimize; +- + 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. */ +@@ -1252,9 +975,6 @@ struct tu_autotune::rp_history { + sysmem_rp_average.add(rp_duration); + } else { + gmem_rp_average.add(entry.get_rp_duration()); +- +- if (entry_config.test(metric_flag::TS_TILE) && at_config.test(mod_flag::PREEMPT_OPTIMIZE)) +- preempt_optimize.update_gmem(*this, entry.get_max_tile_duration()); + } + + if (at_config.is_enabled(algorithm::PROFILED) || at_config.is_enabled(algorithm::PROFILED_IMM)) { +@@ -1363,72 +1083,6 @@ tu_autotune::reap_old_rp_histories() + at_log_base("reaped old RP histories %zu -> %zu", og_size, rp_histories.size()); + } + +-std::shared_ptr +-tu_autotune::create_batch() const +-{ +- return std::make_shared(device, active_config.load().test(mod_flag::PREEMPT_OPTIMIZE)); +-} +- +-bool +-tu_autotune::supports_preempt_latency_tracking() const +-{ +- return supported_mod_flags & (uint32_t) mod_flag::PREEMPT_OPTIMIZE; +-} +- +-void +-tu_autotune::cleanup_latency_tracking() +-{ +- constexpr uint64_t CLEANUP_INTERVAL_NS = 10'000'000'000; /* 10s */ +- constexpr uint64_t FORGET_ABOUT_DELAY_AFTER_NS = 20'000'000'000; /* 20s */ +- +- if (!active_config.load().test(mod_flag::PREEMPT_OPTIMIZE)) +- return; +- +- uint64_t now = os_time_get_nano(); +- if (last_latency_cleanup_ts + CLEANUP_INTERVAL_NS > now) +- return; +- last_latency_cleanup_ts = now; +- +- std::scoped_lock delay_lock(rp_latency_mutex); +- std::shared_lock history_lock(rp_mutex); +- +- for (auto it = rp_latency_tracking.begin(); it != rp_latency_tracking.end();) { +- auto &tracker = it->second; +- uint64_t rp_hash = it->first; +- +- /* Check if corresponding rp_history has cleared its latency_risk flag. */ +- if (tracker.info.seen_latency_spike) { +- auto history_it = rp_histories.find(rp_key { rp_hash }); +- if (history_it == rp_histories.end() || !history_it->second.preempt_optimize.is_latency_sensitive() || +- history_it->second.last_use_ts.load(std::memory_order_relaxed) + FORGET_ABOUT_DELAY_AFTER_NS < now) { +- tracker.info.seen_latency_spike = false; +- at_log_preempt_h("clearing latency-sensitive flag", rp_hash); +- } +- } +- +- /* Remove tracking entry if no recent latency events and flag is cleared. */ +- if (tracker.recent_latency_timestamps_ns.empty() && !tracker.info.seen_latency_spike) { +- it = rp_latency_tracking.erase(it); +- } else { +- ++it; +- } +- } +-} +- +-tu_autotune::rp_latency_info +-tu_autotune::get_rp_latency_info(uint64_t rp_hash, bool unmark_sensitive) +-{ +- std::scoped_lock lock(rp_latency_mutex); +- auto it = rp_latency_tracking.find(rp_hash); +- if (it == rp_latency_tracking.end()) +- return {}; +- +- rp_latency_info info = it->second.info; +- if (unmark_sensitive) +- it->second.info.mark_rp_as_sensitive = false; +- return info; +-} +- + void + tu_autotune::process_entries() + { +@@ -1442,8 +1096,6 @@ tu_autotune::process_entries() + break; /* Entries are allocated in sequence, next will be newer and + also fail so we can just directly break out of the loop. */ + +- process_batch_preempt_data(*batch); +- + for (auto &entry : batch->entries) + entry->history->process(*entry, *this); + +@@ -1457,68 +1109,6 @@ tu_autotune::process_entries() + } + } + +-void +-tu_autotune::process_batch_preempt_data(rp_entry_batch &batch) +-{ +- constexpr uint64_t LATENCY_THRESHOLD_US = 1500; /* 1.5ms */ +- constexpr uint64_t LATENCY_WINDOW_NS = 2'000'000'000; /* 2s */ +- +- if (!batch.preempt_latency.allocated) +- return; +- +- rp_batch_preempt_gpu_data gpu_data = batch.preempt_latency.get_gpu_data(); +- if (gpu_data.preemption_latency == 0 || gpu_data.preemption_latency_rp_hash == 0 || +- gpu_data.always_count_delta == 0 || gpu_data.aon_delta == 0) +- return; +- +- /* Convert preemption latency from CP clock ticks to microseconds. +- * +- * The always_count_delta and aon_delta represent the number of ticks that have passed in the CP and AON clock +- * domains, respectively, during the interval where counters were active. By using the CP-to-AON clock ratio, we can +- * convert the preemption latency from CP ticks to AON ticks (which runs at ALWAYS_ON_FREQUENCY_HZ), and then to wall +- * clock microseconds. +- * +- * Note: This clock ratio averages over the whole execution interval, rather than strictly when the preemption_latency +- * counter was ticking, so it's not perfectly accurate, but it should be good enough for our purposes. +- */ +- uint64_t delay_aon_ticks = (gpu_data.preemption_latency * gpu_data.aon_delta) / gpu_data.always_count_delta; +- uint64_t delay_us = delay_aon_ticks / GPU_TICKS_PER_US; +- +- if (delay_us < LATENCY_THRESHOLD_US) +- return; +- +- at_log_preempt_h("preemption latency spike detected: %" PRIu64 " us (always_count_delta: %" PRIu64 +- ", aon_delta: %" PRIu64 ", delay_aon_ticks: %" PRIu64 ", preemption_latency: %" PRIu64 +- ", estimated_cp_mhz: %" PRIu64 ")", +- gpu_data.preemption_latency_rp_hash, delay_us, gpu_data.always_count_delta, gpu_data.aon_delta, +- delay_aon_ticks, gpu_data.preemption_latency, +- (gpu_data.always_count_delta * ALWAYS_ON_FREQUENCY_HZ) / gpu_data.aon_delta / 1'000'000); +- +- std::scoped_lock lock(rp_latency_mutex); +- uint64_t now = os_time_get_nano(); +- +- auto &tracker = rp_latency_tracking[gpu_data.preemption_latency_rp_hash]; +- tracker.recent_latency_timestamps_ns.push_back(now); +- +- /* Remove old timestamps outside the window. */ +- tracker.recent_latency_timestamps_ns.erase( +- std::remove_if(tracker.recent_latency_timestamps_ns.begin(), tracker.recent_latency_timestamps_ns.end(), +- [&](uint64_t ts) { return (now - ts) > LATENCY_WINDOW_NS; }), +- tracker.recent_latency_timestamps_ns.end()); +- +- /* Mark as latency-sensitive if 2+ occurrences in window. */ +- if (tracker.recent_latency_timestamps_ns.size() >= 2) { +- tracker.info.seen_latency_spike = true; +- tracker.info.mark_rp_as_sensitive = true; +- at_log_preempt_h("marking RP as latency-sensitive after %zu long preemption events", +- gpu_data.preemption_latency_rp_hash, tracker.recent_latency_timestamps_ns.size()); +- } else { +- tracker.info.seen_latency_spike = true; +- at_log_preempt_h("RP preemption latency spike seen, but not marking as sensitive yet at %zu events", +- gpu_data.preemption_latency_rp_hash, tracker.recent_latency_timestamps_ns.size()); +- } +-} +- + struct tu_cs * + tu_autotune::on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_count) + { +@@ -1528,13 +1118,12 @@ tu_autotune::on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_c + * processed all entries from prior CBs before we submit any new CBs with the same RP to the GPU. + */ + process_entries(); +- cleanup_latency_tracking(); + 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->requires_processing()) { ++ if (!batch->entries.empty()) { + has_results = true; + break; + } +@@ -1549,7 +1138,7 @@ tu_autotune::on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_c + /* 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->requires_processing()) ++ if (batch->entries.empty()) + continue; + + batch->assign_fence(new_fence); +@@ -1566,63 +1155,10 @@ tu_autotune::on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_c + return fence_cs; + } + +-tu_autotune::tu_autotune(struct tu_device *device, VkResult &result) +- : device(device), supported_mod_flags(get_supported_mod_flags(device)), active_config(get_env_config()) ++tu_autotune::tu_autotune(struct tu_device *device, VkResult &result): device(device), active_config(get_env_config()) + { + tu_bo_suballocator_init(&suballoc, device, 128 * 1024, TU_BO_ALLOC_INTERNAL_RESOURCE, "autotune_suballoc"); + +- uint32_t group_count; +- const struct fd_perfcntr_group *groups = fd_perfcntrs(&device->physical_device->dev_id, &group_count); +- +- for (uint32_t i = 0; i < group_count; i++) { +- if (strcmp(groups[i].name, "CP") == 0) { +- cp_group = &groups[i]; +- break; +- } +- } +- +- if (!cp_group) { +- mesa_loge("autotune: CP group not found"); +- result = VK_ERROR_INITIALIZATION_FAILED; +- return; +- } else if (cp_group->num_countables < 5) { +- mesa_loge("autotune: CP group has too few countables"); +- result = VK_ERROR_INITIALIZATION_FAILED; +- return; +- } +- +- auto get_perfcntr_countable = [](const struct fd_perfcntr_group *group, +- const char *name) -> const struct fd_perfcntr_countable * { +- for (uint32_t i = 0; i < group->num_countables; i++) { +- if (strcmp(group->countables[i].name, name) == 0) +- return &group->countables[i]; +- } +- +- mesa_loge("autotune: %s not found in group %s", name, group->name); +- return nullptr; +- }; +- +- if (supports_preempt_latency_tracking()) { +- auto preemption_latency_countable = get_perfcntr_countable(cp_group, "PERF_CP_PREEMPTION_REACTION_DELAY"); +- auto always_count_countable = get_perfcntr_countable(cp_group, "PERF_CP_ALWAYS_COUNT"); +- +- if (cp_group->num_counters < 2) { +- mesa_loge("autotune: CP group has too few counters for preemption latency tracking"); +- result = VK_ERROR_INITIALIZATION_FAILED; +- return; +- } +- +- uint32_t preemption_latency_counter_index = cp_group->num_counters - 2; +- preemption_latency_selector_reg = cp_group->counters[preemption_latency_counter_index].select_reg; +- preemption_latency_selector = preemption_latency_countable->selector; +- preemption_latency_counter_reg_lo = cp_group->counters[preemption_latency_counter_index].counter_reg_lo; +- +- uint32_t always_count_counter_index = cp_group->num_counters - 1; +- always_count_selector_reg = cp_group->counters[always_count_counter_index].select_reg; +- always_count_selector = always_count_countable->selector; +- always_count_counter_reg_lo = cp_group->counters[always_count_counter_index].counter_reg_lo; +- } +- + result = VK_SUCCESS; + return; + } +@@ -1638,7 +1174,7 @@ tu_autotune::~tu_autotune() + tu_bo_suballocator_finish(&suballoc); + } + +-tu_autotune::cmd_buf_ctx::cmd_buf_ctx(struct tu_autotune &autotune): batch(autotune.create_batch()) ++tu_autotune::cmd_buf_ctx::cmd_buf_ctx(): batch(std::make_shared()) + { + } + +@@ -1650,22 +1186,10 @@ tu_autotune::cmd_buf_ctx::~cmd_buf_ctx() + */ + } + +-bool +-tu_autotune::cmd_buf_ctx::tracks_preempt_latency() const +-{ +- return batch->preempt_latency.allocated; +-} +- +-void +-tu_autotune::cmd_buf_ctx::snapshot_preempt_data(struct tu_cs *cs) +-{ +- batch->snapshot_preempt_data(cs); +-} +- + void +-tu_autotune::cmd_buf_ctx::reset(struct tu_autotune &autotune) ++tu_autotune::cmd_buf_ctx::reset() + { +- batch = autotune.create_batch(); ++ batch = std::make_shared(); + } + + tu_autotune::rp_entry * +@@ -1679,32 +1203,18 @@ tu_autotune::cmd_buf_ctx::attach_rp_entry(struct tu_device *device, + return new_entry.get(); + } + +-tu_autotune::rp_key +-tu_autotune::cmd_buf_ctx::generate_rp_key(const struct tu_render_pass *pass, +- const struct tu_framebuffer *framebuffer, +- const struct tu_cmd_buffer *cmd, +- bool record_instance) ++tu_autotune::rp_entry * ++tu_autotune::cmd_buf_ctx::find_rp_entry(const rp_key &key) + { +- rp_key key(pass, framebuffer, cmd); +- /* When nearly identical renderpasses appear multiple times within the same command buffer, we need to generate a +- * unique hash for each instance to distinguish them. While this approach doesn't address identical renderpasses +- * across different command buffers, it is good enough in most cases. +- */ +- auto it = this->batch->all_renderpasses.find(key.hash); +- if (it != this->batch->all_renderpasses.end()) { +- key = rp_key(key, it->second); +- if (record_instance) +- it->second++; +- } else { +- if (record_instance) +- this->batch->all_renderpasses[key.hash] = 1; ++ for (auto &entry : batch->entries) { ++ if (entry->history->hash == key.hash) ++ return entry.get(); + } +- +- return key; ++ return nullptr; + } + + tu_autotune::render_mode +-tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx, rp_key_opt key_opt) ++tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx) + { + const struct tu_cmd_state *cmd_state = &cmd_buffer->state; + const struct tu_render_pass *pass = cmd_state->pass; +@@ -1746,84 +1256,33 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + */ + bool simultaneous_use = cmd_buffer->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + +- std::optional latency_info; +- if (key_opt && config.test(mod_flag::PREEMPT_OPTIMIZE)) +- latency_info = get_rp_latency_info(key_opt->hash, true); +- + /* These smaller RPs with few draws are too difficult to create a balanced hash for that can independently identify + * them while not being so unique to not properly identify them across CBs. They're generally insigificant outside of + * a few edge cases such as during deferred rendering G-buffer passes, as we don't have a good way to deal with those + * edge cases yet, we just disable the autotuner for small RPs entirely for now unless TUNE_SMALL is specified. +- * +- * Note: If we detect a small RP to be latency sensitive, we enable the autotuner for it anyway. + */ +- bool ignore_small_rp = !config.test(mod_flag::TUNE_SMALL) && rp_state->drawcall_count < 5 && +- (!latency_info || !latency_info->seen_latency_spike); ++ bool ignore_small_rp = !config.test(mod_flag::TUNE_SMALL) && rp_state->drawcall_count < 5; + + if (!enabled || simultaneous_use || ignore_small_rp) + return default_mode; + +- /* We can return early with the decision based on the draw call count, instead of needing to hash the renderpass +- * instance and look up the history, which is far more expensive. +- * +- * However, certain options such as latency sensitive mode take precedence over any of the other autotuner options +- * and we cannot do so in those cases. +- */ +- bool can_early_return = !config.test(mod_flag::PREEMPT_OPTIMIZE); +- auto early_return_mode = [&]() -> std::optional { +- if (config.test(mod_flag::BIG_GMEM) && rp_state->drawcall_count >= 10) +- return render_mode::GMEM; +- return std::nullopt; +- }(); +- +- if (can_early_return && early_return_mode) { +- at_log_base_h("%" PRIu32 " draw calls, using %s (early)", +- key_opt ? key_opt->hash : rp_key(pass, framebuffer, cmd_buffer).hash, rp_state->drawcall_count, +- render_mode_str(*early_return_mode)); +- return *early_return_mode; +- } +- +- rp_key key(0); +- if (key_opt) +- key = *key_opt; +- else +- key = cb_ctx.generate_rp_key(pass, framebuffer, cmd_buffer); +- +- rp_history &history = *find_or_create_rp_history(key); +- if (config.test(mod_flag::PREEMPT_OPTIMIZE)) { +- if (!latency_info && !key_opt) +- latency_info = get_rp_latency_info(key.hash, true); +- assert(latency_info); /* Should always have it at this point. */ +- +- if (!latency_info->seen_latency_spike) { +- /* If the RP isn't latency sensitive according to the latency tracking, disable the preemption optimization +- * to avoid unnecessary performance hit from the predictive latency sensitive heuristics for RPs that +- * haven't seen any real latency spikes. +- */ +- at_log_base_h("no latency spike seen for RP, disabling preempt optimization", key.hash); +- config.disable(mod_flag::PREEMPT_OPTIMIZE); +- } ++ if (config.test(mod_flag::BIG_GMEM) && rp_state->drawcall_count >= 10) ++ return render_mode::GMEM; + +- if (latency_info->mark_rp_as_sensitive) { +- at_log_base_h("marking RP as latency sensitive based on latency tracking", key.hash); +- history.preempt_optimize.mark_as_latency_sensitive(); +- } +- } +- *rp_ctx = cb_ctx.attach_rp_entry(device, history, config, rp_state->drawcall_count); ++ rp_key key(pass, framebuffer, cmd_buffer); + +- if (config.test(mod_flag::PREEMPT_OPTIMIZE) && history.preempt_optimize.is_latency_sensitive()) { +- /* Try to mitigate the risk of high preemption latency by always using GMEM, which should break up any larger +- * draws into smaller ones with tiling. +- */ +- at_log_base_h("high preemption latency risk, using GMEM", key.hash); +- return render_mode::GMEM; ++ /* When nearly identical renderpasses appear multiple times within the same command buffer, we need to generate a ++ * unique hash for each instance to distinguish them. While this approach doesn't address identical renderpasses ++ * across different command buffers, it is good enough in most cases. ++ */ ++ rp_entry *entry = cb_ctx.find_rp_entry(key); ++ if (entry) { ++ entry->duplicates++; ++ key = rp_key(key, entry->duplicates); + } + +- if (early_return_mode) { +- at_log_base_h("%" PRIu32 " draw calls, using %s (late)", key.hash, rp_state->drawcall_count, +- render_mode_str(*early_return_mode)); +- return *early_return_mode; +- } ++ *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); + + if (config.is_enabled(algorithm::PROFILED) || config.is_enabled(algorithm::PROFILED_IMM)) + return history.profiled.get_optimal_mode(history); +@@ -1834,95 +1293,15 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + return default_mode; + } + +-uint32_t +-tu_autotune::get_tile_size_divisor(struct tu_cmd_buffer *cmd_buffer) +-{ +- 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; +- +- if (!enabled || !active_config.load().test(mod_flag::PREEMPT_OPTIMIZE) || rp_state->sysmem_single_prim_mode || +- pass->has_fdm || cmd_buffer->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT) +- return 1; +- +- rp_key key = cmd_buffer->autotune_ctx.generate_rp_key(pass, framebuffer, cmd_buffer, false); +- +- rp_latency_info latency_info = get_rp_latency_info(key.hash, false); +- if (!latency_info.seen_latency_spike) { +- at_log_base_h("no RP latency spike seen, using tile_size_divisor=1", key.hash); +- return 1; +- } +- +- tu_autotune::rp_history_handle history = find_rp_history(key); +- if (!history) { +- at_log_base_h("no RP history found, using tile_size_divisor=1", key.hash); +- return 1; +- } +- +- uint32_t tile_size_divisor = history->preempt_optimize.get_tile_size_divisor(); +- +- return tile_size_divisor; +-} +- +-void +-tu_autotune::disable_preempt_optimize() +-{ +- config_t original, updated; +- do { +- original = updated = active_config.load(); +- if (!original.test(mod_flag::PREEMPT_OPTIMIZE)) +- return; /* Already disabled, nothing to do. */ +- updated.disable(mod_flag::PREEMPT_OPTIMIZE); +- } while (!active_config.compare_and_store(original, updated)); +-} +- +-void +-tu_autotune::write_preempt_counters_to_iova(struct tu_cs *cs, +- bool emit_selector, +- bool emit_wfi, +- uint64_t latency_iova, +- uint64_t always_count_iova, +- uint64_t aon_iova) const +-{ +- if (emit_selector) { +- tu_cs_emit_pkt4(cs, preemption_latency_selector_reg, 1); +- tu_cs_emit(cs, preemption_latency_selector); +- +- tu_cs_emit_pkt4(cs, always_count_selector_reg, 1); +- tu_cs_emit(cs, always_count_selector); +- } +- +- if (emit_wfi) +- tu_cs_emit_wfi(cs); +- +- tu_cs_emit_pkt7(cs, CP_REG_TO_MEM, 3); +- tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(preemption_latency_counter_reg_lo) | CP_REG_TO_MEM_0_64B); +- tu_cs_emit_qw(cs, latency_iova); +- +- tu_cs_emit_pkt7(cs, CP_REG_TO_MEM, 3); +- tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(always_count_counter_reg_lo) | CP_REG_TO_MEM_0_64B); +- tu_cs_emit_qw(cs, always_count_iova); +- +- tu_cs_emit_pkt7(cs, CP_REG_TO_MEM, 3); +- tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(TU_CALLX(device, __CP_ALWAYS_ON_COUNTER)({}).reg) | CP_REG_TO_MEM_0_CNT(2) | +- CP_REG_TO_MEM_0_64B); +- tu_cs_emit_qw(cs, aon_iova); +-} +- + /** RP-level CS emissions **/ + + void +-tu_autotune::begin_renderpass( +- struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem, uint32_t tile_count) ++tu_autotune::begin_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem) + { + if (!rp_ctx) + return; + +- assert(sysmem || tile_count > 0); +- assert(!sysmem || tile_count == 0); +- +- rp_ctx->allocate(sysmem, tile_count); ++ rp_ctx->allocate(sysmem); + rp_ctx->emit_rp_start(cmd, cs); + } + +@@ -1934,233 +1313,3 @@ tu_autotune::end_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_ + + rp_ctx->emit_rp_end(cmd, cs); + } +- +-/** Tile-level CS emissions **/ +- +-void +-tu_autotune::begin_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, uint32_t tile_idx) +-{ +- if (!rp_ctx) +- return; +- +- rp_ctx->emit_tile_start(cmd, cs, tile_idx); +-} +- +-void +-tu_autotune::end_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, uint32_t tile_idx) +-{ +- if (!rp_ctx) +- return; +- +- rp_ctx->emit_tile_end(cmd, cs, tile_idx); +-} +- +-/** Preemption Latency Tracking API **/ +- +-uint32_t +-tu_autotune::get_switch_away_amble_size() const +-{ +- return supports_preempt_latency_tracking() ? 32 : 0; +-} +- +-uint32_t +-tu_autotune::get_switch_back_amble_size() const +-{ +- return supports_preempt_latency_tracking() ? 128 : 0; +-} +- +-void +-tu_autotune::emit_switch_away_amble(struct tu_cs *cs) const +-{ +- if (!supports_preempt_latency_tracking()) +- return; +- +- const uint64_t mem = device->global_bo->iova; +- +- tu_cond_exec_start(cs, CP_COND_REG_EXEC_0_MODE(THREAD_MODE) | CP_COND_REG_EXEC_0_BR); +- +- write_preempt_counters_to_iova(cs, false, false, mem + gb_offset(new_preemption_latency), +- mem + gb_offset(new_always_count), mem + gb_offset(new_aon)); +- +- /* We need to account for accumulation of PERF_CP_PREEMPTION_REACTION_DELAY, so we always has the last preemption +- * latency stored to subtract. preemption_latency = new_preemption_latency - base_preemption_latency. +- */ +- tu_cs_emit_pkt7(cs, CP_MEM_TO_MEM, 9); +- tu_cs_emit(cs, CP_MEM_TO_MEM_0_DOUBLE | CP_MEM_TO_MEM_0_NEG_B | CP_MEM_TO_MEM_0_WAIT_FOR_MEM_WRITES); +- tu_cs_emit_qw(cs, mem + gb_offset(preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(new_preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(base_preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(zero_64b)); +- +- static size_t counter = 0; +- if (counter++ % 2 == 0) { +- tu_cs_emit_pkt4(cs, preemption_latency_selector_reg, 1); +- tu_cs_emit(cs, always_count_selector); +- +- tu_cs_emit_pkt4(cs, always_count_selector_reg, 1); +- tu_cs_emit(cs, preemption_latency_selector); +- } +- +- tu_cond_exec_end(cs); +-} +- +-void +-tu_autotune::emit_switch_back_amble(struct tu_cs *cs) const +-{ +- if (!supports_preempt_latency_tracking()) +- return; +- +- const uint64_t mem = device->global_bo->iova; +- +- tu_cond_exec_start(cs, CP_COND_REG_EXEC_0_MODE(THREAD_MODE) | CP_COND_REG_EXEC_0_BR); +- +- /* Update max_preemption_latency and max_preemption_latency_rp_hash if this preemption had a longer preemption delay +- * than the previous one in the current cmdbuffer. +- */ +- { +- uint32_t scratch_reg = TU_CALLX(device, tu_scratch_reg)(5, 0).reg; +- +- /* scratch = max_preemption_latency - preemption_reaction_delay. */ +- tu_cs_emit_pkt7(cs, CP_MEM_TO_MEM, 9); +- tu_cs_emit(cs, CP_MEM_TO_MEM_0_DOUBLE | CP_MEM_TO_MEM_0_NEG_B); +- tu_cs_emit_qw(cs, mem + gb_offset(preemption_latency_cmp_scratch)); +- tu_cs_emit_qw(cs, mem + gb_offset(max_preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(zero_64b)); +- +- /* Wait for the mem_op to complete. */ +- tu_cs_emit_pkt7(cs, CP_WAIT_MEM_WRITES, 0); +- +- /* Load high 32 bits of difference into scratch register. */ +- tu_cs_emit_pkt7(cs, CP_MEM_TO_REG, 3); +- tu_cs_emit(cs, CP_MEM_TO_REG_0_REG(scratch_reg) | CP_MEM_TO_REG_0_CNT(1)); +- tu_cs_emit_qw(cs, mem + gb_offset(preemption_latency_cmp_scratch) + sizeof(uint32_t)); +- +- /* Test bit 31 (sign bit). */ +- tu_cs_emit_pkt7(cs, CP_REG_TEST, 1); +- tu_cs_emit(cs, A6XX_CP_REG_TEST_0_REG(scratch_reg) | A6XX_CP_REG_TEST_0_BIT(31)); +- +- /* If negative (preemption_reaction_delay > max_preemption_latency), update. */ +- tu_cond_exec_start(cs, CP_COND_REG_EXEC_0_MODE(PRED_TEST)); +- { +- /* max_preemption_latency = preemption_reaction_delay .*/ +- tu_cs_emit_pkt7(cs, CP_MEMCPY, 5); +- tu_cs_emit(cs, 2); +- tu_cs_emit_qw(cs, mem + gb_offset(preemption_latency)); +- tu_cs_emit_qw(cs, mem + gb_offset(max_preemption_latency)); +- +- /* max_preemption_latency_rp_hash = cur_rp_hash. */ +- tu_cs_emit_pkt7(cs, CP_MEMCPY, 5); +- tu_cs_emit(cs, 2); +- tu_cs_emit_qw(cs, mem + gb_offset(cur_rp_hash)); +- tu_cs_emit_qw(cs, mem + gb_offset(max_preemption_latency_rp_hash)); +- +- /* max_always_count_delta = new_always_count - base_always_count. */ +- tu_cs_emit_pkt7(cs, CP_MEM_TO_MEM, 9); +- tu_cs_emit(cs, CP_MEM_TO_MEM_0_DOUBLE | CP_MEM_TO_MEM_0_NEG_B); +- tu_cs_emit_qw(cs, mem + gb_offset(max_always_count_delta)); +- tu_cs_emit_qw(cs, mem + gb_offset(new_always_count)); +- tu_cs_emit_qw(cs, mem + gb_offset(base_always_count)); +- tu_cs_emit_qw(cs, mem + gb_offset(zero_64b)); +- +- /* max_aon_delta = new_aon - base_aon. */ +- tu_cs_emit_pkt7(cs, CP_MEM_TO_MEM, 9); +- tu_cs_emit(cs, CP_MEM_TO_MEM_0_DOUBLE | CP_MEM_TO_MEM_0_NEG_B); +- tu_cs_emit_qw(cs, mem + gb_offset(max_aon_delta)); +- tu_cs_emit_qw(cs, mem + gb_offset(new_aon)); +- tu_cs_emit_qw(cs, mem + gb_offset(base_aon)); +- tu_cs_emit_qw(cs, mem + gb_offset(zero_64b)); +- +- /* Ensures that base_{always_count, aon} are read before the REG_TO_MEM. */ +- tu_cs_emit_pkt7(cs, CP_WAIT_MEM_WRITES, 0); +- } +- tu_cond_exec_end(cs); +- } +- +- /* We still need to re-emit the selectors since another context may have changed them. +- * Note: Emitting WFI in PREAMBLE_AMBLE_TYPE leads to a GPU hang for some reason, so we skip the WFI which seems to +- * work even when selectors are intentionally scrambled at the end of switch_away_amble to simulate another +- * context changing the selectors. +- */ +- write_preempt_counters_to_iova(cs, true, false, mem + gb_offset(base_preemption_latency), +- mem + gb_offset(base_always_count), mem + gb_offset(base_aon)); +- +- tu_cond_exec_end(cs); +-} +- +-void +-tu_autotune::init_reset_rp_hash_draw_state() +-{ +- if (!supports_preempt_latency_tracking()) { +- memset(&reset_rp_hash_draw_state, 0, sizeof(reset_rp_hash_draw_state)); +- return; +- } +- +- struct tu_cs cs; +- reset_rp_hash_draw_state = tu_cs_draw_state(&device->sub_cs, &cs, 5); +- +- tu_cs_emit_pkt7(&cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(&cs, device->global_bo->iova + gb_offset(cur_rp_hash)); +- tu_cs_emit_qw(&cs, 0); +-} +- +-void +-tu_autotune::emit_reset_rp_hash_draw_state(struct tu_cmd_buffer *cmd, struct tu_cs *cs) const +-{ +- if (!cmd->autotune_ctx.tracks_preempt_latency()) +- return; +- +- assert(reset_rp_hash_draw_state.size != 0); /* init_reset_rp_hash_draw_state() not called. */ +- +- tu_cs_emit_pkt7(cs, CP_SET_DRAW_STATE, 3); +- tu_cs_emit(cs, CP_SET_DRAW_STATE__0_COUNT(reset_rp_hash_draw_state.size) | CP_SET_DRAW_STATE__0_SYSMEM | +- CP_SET_DRAW_STATE__0_GROUP_ID(TU_DRAW_STATE_INPUT_ATTACHMENTS_SYSMEM)); +- tu_cs_emit_qw(cs, reset_rp_hash_draw_state.iova); +-} +- +-void +-tu_autotune::emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, struct tu_cs *cs) +-{ +- if (!cmd->autotune_ctx.tracks_preempt_latency()) +- return; +- +- tu_cs_emit_pkt7(cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(cs, global_iova(cmd, max_preemption_latency)); +- tu_cs_emit_qw(cs, 0); +- +- tu_cs_emit_pkt7(cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(cs, global_iova(cmd, max_preemption_latency_rp_hash)); +- tu_cs_emit_qw(cs, ~0ull); +- +- tu_cs_emit_pkt7(cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(cs, global_iova(cmd, max_always_count_delta)); +- tu_cs_emit_qw(cs, 0); +- +- tu_cs_emit_pkt7(cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(cs, global_iova(cmd, max_aon_delta)); +- tu_cs_emit_qw(cs, 0); +- +- write_preempt_counters_to_iova(cs, true, true, global_iova(cmd, base_preemption_latency), +- global_iova(cmd, base_always_count), global_iova(cmd, base_aon)); +-} +- +-tu_autotune::rp_key_opt +-tu_autotune::emit_preempt_latency_tracking_rp_hash(struct tu_cmd_buffer *cmd) +-{ +- if (!cmd->autotune_ctx.tracks_preempt_latency()) +- return std::nullopt; +- +- tu_autotune::rp_key rp_key = cmd->autotune_ctx.generate_rp_key(cmd->state.pass, cmd->state.framebuffer, cmd); +- +- struct tu_cs cs; +- struct tu_draw_state ds = tu_cs_draw_state(&cmd->sub_cs, &cs, 5); +- +- tu_cs_emit_pkt7(&cs, CP_MEM_WRITE, 4); +- tu_cs_emit_qw(&cs, global_iova(cmd, cur_rp_hash)); +- tu_cs_emit_qw(&cs, rp_key.hash); +- +- tu_cs_emit_pkt7(&cmd->cs, CP_SET_DRAW_STATE, 3); +- tu_cs_emit_draw_state(&cmd->cs, TU_DRAW_STATE_AT_WRITE_RP_HASH, ds); +- +- return rp_key; +-} +\ No newline at end of file +diff --git a/src/freedreno/vulkan/tu_autotune.h b/src/freedreno/vulkan/tu_autotune.h +index 868c06bb7d6..0f4215e8461 100644 +--- a/src/freedreno/vulkan/tu_autotune.h ++++ b/src/freedreno/vulkan/tu_autotune.h +@@ -10,7 +10,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -36,8 +35,6 @@ struct tu_autotune { + struct PACKED config_t; + union PACKED packed_config_t; + +- uint32_t supported_mod_flags; +- + /* Allows for thread-safe access to the configurations. */ + struct atomic_config_t { + private: +@@ -52,7 +49,6 @@ struct tu_autotune { + } active_config; + + config_t get_env_config(); +- uint32_t get_supported_mod_flags(tu_device *device) const; + + /** Global Fence and Internal CS Management **/ + +@@ -105,22 +101,8 @@ struct tu_autotune { + + struct rp_gpu_data; + struct tile_gpu_data; +- struct rp_batch_preempt_gpu_data; + struct rp_entry; + +- struct rp_batch_preempt_latency { +- struct tu_device *device; +- +- bool allocated; +- struct tu_suballoc_bo bo; +- uint8_t *map; +- +- rp_batch_preempt_latency(struct tu_device *device, bool allocate); +- ~rp_batch_preempt_latency(); +- +- rp_batch_preempt_gpu_data get_gpu_data(); +- }; +- + /* 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 +@@ -128,11 +110,8 @@ struct tu_autotune { + 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> entries; +- std::unordered_map all_renderpasses; +- +- rp_batch_preempt_latency preempt_latency; + +- rp_entry_batch(struct tu_device *device, bool track_preempt_latency); ++ rp_entry_batch(); + + /* Disable the copy/move to avoid performance hazards. */ + rp_entry_batch(const rp_entry_batch &) = delete; +@@ -140,17 +119,11 @@ struct tu_autotune { + rp_entry_batch(rp_entry_batch &&) = delete; + rp_entry_batch &operator=(rp_entry_batch &&) = delete; + +- bool requires_processing() const; +- + void assign_fence(uint32_t new_fence); + + void mark_inactive(); +- +- void snapshot_preempt_data(struct tu_cs *cs); + }; + +- std::shared_ptr create_batch() const; +- + /* 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. + */ +@@ -163,8 +136,6 @@ struct tu_autotune { + */ + void process_entries(); + +- void process_batch_preempt_data(rp_entry_batch &batch); +- + /** Renderpass State Tracking **/ + + struct rp_history; +@@ -187,11 +158,6 @@ struct tu_autotune { + /* Further salt the hash to distinguish between multiple instances of the same RP within a single command buffer. */ + rp_key(const rp_key &key, uint32_t duplicates); + +- /* Constructor for hash-only lookup */ +- explicit constexpr rp_key(uint64_t hash): hash(hash) +- { +- } +- + /* Equality operator, used in unordered_map. */ + constexpr bool operator==(const rp_key &other) const noexcept + { +@@ -224,45 +190,6 @@ struct tu_autotune { + rp_history_handle find_or_create_rp_history(const rp_key &key); + void reap_old_rp_histories(); + +- /** Preemption Latency Tracking **/ +- +- struct rp_latency_info { +- bool seen_latency_spike = false; /* If a preemption latency spike was seen recently. */ +- bool mark_rp_as_sensitive = false; /* Marks RP as latency-sensitive in rp_history */ +- }; +- +- /* Tracks recent preemption latency occurrences for a specific RP hash */ +- struct rp_latency_tracker { +- std::vector recent_latency_timestamps_ns; /* Timestamps of recent latency events */ +- rp_latency_info info; +- }; +- +- /* Global map tracking RPs that have caused preemption latency */ +- std::unordered_map rp_latency_tracking; +- std::mutex rp_latency_mutex; /* Protects rp_latency_tracking */ +- uint64_t last_latency_cleanup_ts = 0; +- +- const fd_perfcntr_group *cp_group; +- uint32_t preemption_latency_selector_reg; +- uint32_t preemption_latency_selector; +- uint32_t preemption_latency_counter_reg_lo; +- +- uint32_t always_count_selector_reg; +- uint32_t always_count_selector; +- uint32_t always_count_counter_reg_lo; +- +- struct tu_draw_state reset_rp_hash_draw_state; +- +- bool supports_preempt_latency_tracking() const; +- void cleanup_latency_tracking(); +- tu_autotune::rp_latency_info get_rp_latency_info(uint64_t rp_hash, bool unmark_sensitive); +- void write_preempt_counters_to_iova(struct tu_cs *cs, +- bool emit_selector, +- bool emit_wfi, +- uint64_t latency_iova, +- uint64_t always_count_iova, +- uint64_t aon_iova) const; +- + public: + tu_autotune(struct tu_device *device, VkResult &result); + +@@ -285,23 +212,16 @@ struct tu_autotune { + rp_entry * + attach_rp_entry(struct tu_device *device, rp_history_handle &&history, config_t config, uint32_t draw_count); + +- bool tracks_preempt_latency() const; ++ rp_entry *find_rp_entry(const rp_key &key); + + friend struct tu_autotune; + + public: +- cmd_buf_ctx(struct tu_autotune &autotune); ++ cmd_buf_ctx(); + ~cmd_buf_ctx(); + +- rp_key generate_rp_key(const struct tu_render_pass *pass, +- const struct tu_framebuffer *framebuffer, +- const struct tu_cmd_buffer *cmd, +- bool record_instance = true); +- +- void snapshot_preempt_data(struct tu_cs *cs); +- + /* Resets the internal context, should be called when tu_cmd_buffer state has been reset. */ +- void reset(struct tu_autotune &autotune); ++ void reset(); + }; + + enum class render_mode { +@@ -309,33 +229,12 @@ struct tu_autotune { + GMEM, + }; + +- /* Wrapper to expose rp_key for passing around publicly. */ +- struct rp_key_opt : public std::optional { +- using std::optional::optional; +- }; ++ render_mode get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx); + +- /* Note: For preemption latency tracking to work, key_opt from emit_preempt_latency_tracking_rp_hash() must be used. */ +- render_mode get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx, rp_key_opt key_opt); +- +- /* Returns the optimal tile size divisor for the given CB state. */ +- uint32_t get_tile_size_divisor(struct tu_cmd_buffer *cmd_buffer); +- +- /* Disables preemption latency optimization within the autotuner, this is used when high-priority queues are used to +- * ensure that the autotuner does not interfere with the high-priority queue's performance. +- * +- * Note: This should be called before any renderpass is started, otherwise it may lead to undefined behavior. +- */ +- void disable_preempt_optimize(); +- +- void +- begin_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem, uint32_t tile_count); ++ void begin_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, bool sysmem); + + void end_renderpass(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx); + +- void begin_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, uint32_t tile_idx); +- +- void end_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, rp_ctx_t rp_ctx, uint32_t tile_idx); +- + /* The submit-time hook for autotuner, this may return a CS (can be NULL) which must be amended for autotuner + * tracking to function correctly. + * +@@ -343,21 +242,6 @@ struct tu_autotune { + * function at the same time. + */ + struct tu_cs *on_submit(struct tu_cmd_buffer **cmd_buffers, uint32_t cmd_buffer_count); +- +- /** Preemption Latency Tracking API **/ +- +- uint32_t get_switch_away_amble_size() const; +- uint32_t get_switch_back_amble_size() const; +- void emit_switch_away_amble(struct tu_cs *cs) const; +- void emit_switch_back_amble(struct tu_cs *cs) const; +- +- /* Note: MUST be called from a single-threaded context before emit_reset_rp_hash_draw_state(). */ +- void init_reset_rp_hash_draw_state(); +- void emit_reset_rp_hash_draw_state(struct tu_cmd_buffer *cmd, struct tu_cs *cs) const; +- +- void emit_preempt_latency_tracking_setup(struct tu_cmd_buffer *cmd, struct tu_cs *cs); +- /* Returns the RP hash only when preemption latency tracking is enabled. */ +- rp_key_opt emit_preempt_latency_tracking_rp_hash(struct tu_cmd_buffer *cmd); + }; + + #endif /* TU_AUTOTUNE_H */ +\ No newline at end of file +diff --git a/src/freedreno/vulkan/tu_clear_blit.cc b/src/freedreno/vulkan/tu_clear_blit.cc +index 096903c0497..fe5f5aeb03e 100644 +--- a/src/freedreno/vulkan/tu_clear_blit.cc ++++ b/src/freedreno/vulkan/tu_clear_blit.cc +@@ -5905,10 +5905,7 @@ tu_choose_gmem_layout(struct tu_cmd_buffer *cmd) + } + } + +- cmd->state.gmem_layout_divisor = cmd->device->autotune->get_tile_size_divisor(cmd); +- +- cmd->state.tiling = tu_framebuffer_get_tiling_config(cmd->state.framebuffer, cmd->device, cmd->state.pass, +- cmd->state.gmem_layout, cmd->state.gmem_layout_divisor); ++ cmd->state.tiling = &cmd->state.framebuffer->tiling[cmd->state.gmem_layout]; + } + + struct apply_store_coords_state { +diff --git a/src/freedreno/vulkan/tu_cmd_buffer.cc b/src/freedreno/vulkan/tu_cmd_buffer.cc +index c345564dee7..3e04110b4e6 100644 +--- a/src/freedreno/vulkan/tu_cmd_buffer.cc ++++ b/src/freedreno/vulkan/tu_cmd_buffer.cc +@@ -1109,7 +1109,7 @@ tu6_apply_depth_bounds_workaround(struct tu_device *device, + A6XX_RB_DEPTH_CNTL_ZFUNC(FUNC_ALWAYS); + } + +-void ++static void + tu_cs_emit_draw_state(struct tu_cs *cs, uint32_t id, struct tu_draw_state state) + { + uint32_t enable_mask; +@@ -1287,9 +1287,8 @@ tu_vsc_config(struct tu_cmd_buffer *cmd, const struct tu_tiling_config *tiling) + static bool + use_hw_binning(struct tu_cmd_buffer *cmd) + { +- struct tu_framebuffer *fb = cmd->state.framebuffer; +- const struct tu_tiling_config *tiling = +- tu_framebuffer_get_tiling_config(fb, cmd->device, cmd->state.pass, cmd->state.gmem_layout, cmd->state.gmem_layout_divisor); ++ const struct tu_framebuffer *fb = cmd->state.framebuffer; ++ const struct tu_tiling_config *tiling = &fb->tiling[cmd->state.gmem_layout]; + const struct tu_vsc_config *vsc = tu_vsc_config(cmd, tiling); + + /* XFB commands are emitted for BINNING || SYSMEM, which makes it +@@ -1319,8 +1318,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, +- tu_autotune::rp_key_opt rp_key) ++ tu_autotune::rp_ctx_t *rp_ctx) + { + if (TU_DEBUG(SYSMEM)) { + cmd->state.rp.gmem_disable_reason = "TU_DEBUG(SYSMEM)"; +@@ -1389,9 +1387,7 @@ use_sysmem_rendering(struct tu_cmd_buffer *cmd, + return true; + } + +- tu_autotune::render_mode optimal_mode = +- cmd->device->autotune->get_optimal_mode(cmd, rp_ctx, rp_key); +- bool use_sysmem = optimal_mode == tu_autotune::render_mode::SYSMEM; ++ bool use_sysmem = cmd->device->autotune->get_optimal_mode(cmd, rp_ctx) == tu_autotune::render_mode::SYSMEM; + if (use_sysmem) + cmd->state.rp.gmem_disable_reason = "Autotune selected sysmem"; + +@@ -2320,28 +2316,6 @@ tu_init_bin_preamble(struct tu_device *device) + device->bin_preamble_bv_entry = tu_cs_end_sub_stream(&device->sub_cs, &preamble_cs); + } + +- uint32_t switch_away_size = device->autotune->get_switch_away_amble_size(); +- if (switch_away_size > 0) { +- result = tu_cs_begin_sub_stream(&device->sub_cs, switch_away_size, &preamble_cs); +- if (result != VK_SUCCESS) +- return vk_startup_errorf(device->instance, result, "switch away amble"); +- +- device->autotune->emit_switch_away_amble(&preamble_cs); +- device->switch_away_amble_entry = tu_cs_end_sub_stream(&device->sub_cs, &preamble_cs); +- } +- +- uint32_t switch_back_size = device->autotune->get_switch_back_amble_size(); +- if (switch_back_size > 0) { +- result = tu_cs_begin_sub_stream(&device->sub_cs, switch_back_size, &preamble_cs); +- if (result != VK_SUCCESS) +- return vk_startup_errorf(device->instance, result, "switch back amble"); +- +- device->autotune->emit_switch_back_amble(&preamble_cs); +- device->switch_back_amble_entry = tu_cs_end_sub_stream(&device->sub_cs, &preamble_cs); +- } +- +- device->autotune->init_reset_rp_hash_draw_state(); +- + return VK_SUCCESS; + } + +@@ -2439,8 +2413,6 @@ tu_init_hw(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + tu7_set_thread_br_patchpoint(cmd, cs, false); + } + +- dev->autotune->emit_preempt_latency_tracking_setup(cmd, cs); +- + tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); + tu_cs_emit_qw(cs, cmd->device->bin_preamble_entry.bo->iova + + cmd->device->bin_preamble_entry.offset); +@@ -2465,27 +2437,13 @@ tu_init_hw(struct tu_cmd_buffer *cmd, struct tu_cs *cs) + (1u << TU_PREDICATE_VTX_STATS_NOT_RUNNING)); + } + +- if (dev->switch_back_amble_entry.size > 0) { +- tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); +- tu_cs_emit_qw(cs, dev->switch_back_amble_entry.bo->iova + dev->switch_back_amble_entry.offset); +- tu_cs_emit(cs, CP_SET_AMBLE_2_DWORDS(dev->switch_back_amble_entry.size / sizeof(uint32_t)) | +- CP_SET_AMBLE_2_TYPE(PREAMBLE_AMBLE_TYPE)); +- } else { +- tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); +- tu_cs_emit_qw(cs, 0); +- tu_cs_emit(cs, CP_SET_AMBLE_2_TYPE(PREAMBLE_AMBLE_TYPE)); +- } +- +- if (dev->switch_away_amble_entry.size > 0) { +- tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); +- tu_cs_emit_qw(cs, dev->switch_away_amble_entry.bo->iova + dev->switch_away_amble_entry.offset); +- tu_cs_emit(cs, CP_SET_AMBLE_2_DWORDS(dev->switch_away_amble_entry.size / sizeof(uint32_t)) | +- CP_SET_AMBLE_2_TYPE(POSTAMBLE_AMBLE_TYPE)); +- } else { +- tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); +- tu_cs_emit_qw(cs, 0); +- tu_cs_emit(cs, CP_SET_AMBLE_2_TYPE(POSTAMBLE_AMBLE_TYPE)); +- } ++ tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); ++ tu_cs_emit_qw(cs, 0); ++ tu_cs_emit(cs, CP_SET_AMBLE_2_TYPE(PREAMBLE_AMBLE_TYPE)); ++ ++ tu_cs_emit_pkt7(cs, CP_SET_AMBLE, 3); ++ tu_cs_emit_qw(cs, 0); ++ tu_cs_emit(cs, CP_SET_AMBLE_2_TYPE(POSTAMBLE_AMBLE_TYPE)); + + if (CHIP >= A7XX) { + tu7_set_thread_br_patchpoint(cmd, cs, false); +@@ -3229,7 +3187,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, 0); ++ cmd->device->autotune->begin_renderpass(cmd, cs, rp_ctx, true); + + tu_cs_sanity_check(cs); + } +@@ -3606,8 +3564,7 @@ tu6_tile_render_begin(struct tu_cmd_buffer *cmd, struct tu_cs *cs, + if (use_cb) + tu_trace_start_render_pass(cmd); + +- uint32_t tile_count = vsc->tile_count.width * vsc->tile_count.height; +- cmd->device->autotune->begin_renderpass(cmd, cs, rp_ctx, false, tile_count); ++ cmd->device->autotune->begin_renderpass(cmd, cs, rp_ctx, false); + + tu_cs_sanity_check(cs); + } +@@ -3616,18 +3573,13 @@ template + static void + tu6_render_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, + const struct tu_tile_config *tile, +- const VkOffset2D *fdm_offsets, +- tu_autotune::rp_ctx_t rp_ctx, +- const struct tu_vsc_config *vsc) ++ const VkOffset2D *fdm_offsets) + { +- uint32_t tile_idx = (tile->pos.y * vsc->tile_count.width) + tile->pos.x; + tu6_emit_tile_select(cmd, &cmd->cs, tile, fdm_offsets); + tu_lrz_before_tile(cmd, &cmd->cs); + + trace_start_draw_ib_gmem(&cmd->trace, &cmd->cs, cmd); + +- cmd->device->autotune->begin_tile(cmd, cs, rp_ctx, tile_idx); +- + /* Primitives that passed all tests are still counted in in each + * tile even with HW binning beforehand. Do not permit it. + */ +@@ -3639,8 +3591,6 @@ tu6_render_tile(struct tu_cmd_buffer *cmd, struct tu_cs *cs, + if (cmd->state.prim_generated_query_running_before_rp) + tu_emit_event_write(cmd, cs, FD_START_PRIMITIVE_CTRS); + +- cmd->device->autotune->end_tile(cmd, cs, rp_ctx, tile_idx); +- + if (use_hw_binning(cmd)) { + tu_set_render_mode(cs, { .mode = RM6_BIN_END_OF_DRAWS, .uses_gmem = true }); + } +@@ -3903,7 +3853,7 @@ tu_cmd_render_tiles(struct tu_cmd_buffer *cmd, + tu_identity_frag_area(cmd, tile); + } + +- tu6_render_tile(cmd, &cmd->cs, tile, fdm_offsets, rp_ctx, vsc); ++ tu6_render_tile(cmd, &cmd->cs, tile, fdm_offsets); + } + slot_row += tile_row_stride; + } +@@ -3982,10 +3932,8 @@ tu_cmd_render(struct tu_cmd_buffer *cmd_buffer, + if (cmd_buffer->state.rp.has_tess) + tu6_lazy_emit_tessfactor_addr(cmd_buffer); + +- tu_autotune::rp_key_opt rp_key = cmd_buffer->device->autotune->emit_preempt_latency_tracking_rp_hash(cmd_buffer); +- + tu_autotune::rp_ctx_t rp_ctx = NULL; +- if (use_sysmem_rendering(cmd_buffer, &rp_ctx, rp_key)) ++ if (use_sysmem_rendering(cmd_buffer, &rp_ctx)) + tu_cmd_render_sysmem(cmd_buffer, rp_ctx); + else + tu_cmd_render_tiles(cmd_buffer, rp_ctx, fdm_offsets); +@@ -4006,7 +3954,6 @@ static void tu_reset_render_pass(struct tu_cmd_buffer *cmd_buffer) + cmd_buffer->state.attachments = NULL; + cmd_buffer->state.clear_values = NULL; + cmd_buffer->state.gmem_layout = TU_GMEM_LAYOUT_COUNT; /* invalid value to prevent looking up gmem offsets */ +- cmd_buffer->state.gmem_layout_divisor = 0; + cmd_buffer->state.renderpass_cb_disabled = false; + memset(&cmd_buffer->state.rp, 0, sizeof(cmd_buffer->state.rp)); + +@@ -4055,7 +4002,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(*device->autotune); ++ new (&cmd_buffer->autotune_ctx) tu_autotune::cmd_buf_ctx(); + + if (TU_DEBUG_START(CHECK_CMD_BUFFER_STATUS)) { + cmd_buffer->status_bo = tu_cmd_buffer_setup_status_tracking(device); +@@ -4181,7 +4128,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(*cmd_buffer->device->autotune); ++ cmd_buffer->autotune_ctx.reset(); + + for (unsigned i = 0; i < MAX_BIND_POINTS; i++) { + memset(&cmd_buffer->descriptors[i].sets, 0, sizeof(cmd_buffer->descriptors[i].sets)); +@@ -5248,15 +5195,7 @@ tu_EndCommandBuffer(VkCommandBuffer commandBuffer) + } + + if (cmd_buffer->vk.level == VK_COMMAND_BUFFER_LEVEL_PRIMARY) { +- struct u_trace_address addr_preempt_latency = {}; +- addr_preempt_latency.offset = global_iova(cmd_buffer, max_preemption_latency); +- +- struct u_trace_address addr_preempt_latency_rp_hash = {}; +- addr_preempt_latency_rp_hash.offset = global_iova(cmd_buffer, max_preemption_latency_rp_hash); +- +- trace_end_cmd_buffer(&cmd_buffer->trace, &cmd_buffer->cs, cmd_buffer, addr_preempt_latency, addr_preempt_latency_rp_hash); +- +- cmd_buffer->autotune_ctx.snapshot_preempt_data(&cmd_buffer->cs); ++ trace_end_cmd_buffer(&cmd_buffer->trace, &cmd_buffer->cs, cmd_buffer); + } else { + trace_end_secondary_cmd_buffer( + cmd_buffer->state.pass ? &cmd_buffer->rp_trace : &cmd_buffer->trace, +@@ -6104,9 +6043,7 @@ tu_restore_suspended_pass(struct tu_cmd_buffer *cmd, + cmd->state.per_layer_render_area = suspended->state.suspended_pass.per_layer_render_area; + cmd->state.fdm_subsampled = suspended->state.suspended_pass.fdm_subsampled; + cmd->state.gmem_layout = suspended->state.suspended_pass.gmem_layout; +- cmd->state.gmem_layout_divisor = suspended->state.suspended_pass.gmem_layout_divisor; +- cmd->state.tiling = tu_framebuffer_get_tiling_config(cmd->state.framebuffer, cmd->device, cmd->state.pass, +- cmd->state.gmem_layout, cmd->state.gmem_layout_divisor); ++ cmd->state.tiling = &cmd->state.framebuffer->tiling[cmd->state.gmem_layout]; + cmd->state.lrz = suspended->state.suspended_pass.lrz; + } + +@@ -7118,7 +7055,6 @@ tu_CmdBeginRendering(VkCommandBuffer commandBuffer, + cmd->state.suspended_pass.attachments = cmd->state.attachments; + cmd->state.suspended_pass.clear_values = cmd->state.clear_values; + cmd->state.suspended_pass.gmem_layout = cmd->state.gmem_layout; +- cmd->state.suspended_pass.gmem_layout_divisor = cmd->state.gmem_layout_divisor; + } + + tu_fill_render_pass_state(&cmd->state.vk_rp, +@@ -9185,8 +9121,6 @@ tu_dispatch(struct tu_cmd_buffer *cmd, + tu_emit_event_write(cmd, cs, FD_LABEL); + } + +- cmd->device->autotune->emit_reset_rp_hash_draw_state(cmd, cs); +- + /* TODO: We could probably flush less if we add a compute_flush_bits + * bitfield. + */ +diff --git a/src/freedreno/vulkan/tu_cmd_buffer.h b/src/freedreno/vulkan/tu_cmd_buffer.h +index c8ebc46a5d1..614747bb492 100644 +--- a/src/freedreno/vulkan/tu_cmd_buffer.h ++++ b/src/freedreno/vulkan/tu_cmd_buffer.h +@@ -46,9 +46,6 @@ enum tu_draw_state_group_id + /* dynamic state related draw states */ + TU_DRAW_STATE_DYNAMIC, + TU_DRAW_STATE_COUNT = TU_DRAW_STATE_DYNAMIC + TU_DYNAMIC_STATE_COUNT, +- +- /* autotune preemption delay tracking draw state */ +- TU_DRAW_STATE_AT_WRITE_RP_HASH = TU_DRAW_STATE_COUNT + 1, + }; + + struct tu_descriptor_state +@@ -532,12 +529,11 @@ struct tu_cmd_state + /* Decides which GMEM layout to use from the tu_pass, based on whether the CCU + * might get used by tu_store_gmem_attachment(). + */ +- tu_gmem_layout gmem_layout; +- uint32_t gmem_layout_divisor; ++ enum tu_gmem_layout gmem_layout; + + const struct tu_render_pass *pass; + const struct tu_subpass *subpass; +- struct tu_framebuffer *framebuffer; ++ const struct tu_framebuffer *framebuffer; + const struct tu_tiling_config *tiling; + VkRect2D render_areas[MAX_VIEWS]; + bool per_layer_render_area; +@@ -553,12 +549,11 @@ struct tu_cmd_state + struct { + const struct tu_render_pass *pass; + const struct tu_subpass *subpass; +- struct tu_framebuffer *framebuffer; ++ const struct tu_framebuffer *framebuffer; + VkRect2D render_areas[MAX_VIEWS]; + bool per_layer_render_area; + bool fdm_subsampled; + enum tu_gmem_layout gmem_layout; +- uint32_t gmem_layout_divisor; + + const struct tu_image_view **attachments; + VkClearValue *clear_values; +@@ -865,9 +860,6 @@ void tu_disable_draw_states(struct tu_cmd_buffer *cmd, struct tu_cs *cs); + void tu6_apply_depth_bounds_workaround(struct tu_device *device, + uint32_t *rb_depth_cntl); + +-void +-tu_cs_emit_draw_state(struct tu_cs *cs, uint32_t id, struct tu_draw_state state); +- + bool tu_enable_fdm_offset(struct tu_cmd_buffer *cmd); + + typedef void (*tu_fdm_bin_apply_t)(struct tu_cmd_buffer *cmd, +diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc +index 24ca3d2391c..fda649af6af 100644 +--- a/src/freedreno/vulkan/tu_device.cc ++++ b/src/freedreno/vulkan/tu_device.cc +@@ -2734,7 +2734,6 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + VkResult result; + struct tu_device *device; + bool border_color_without_format = false; +- bool autotune_disable_preempt_optimize = false; + + vk_foreach_struct_const (ext, pCreateInfo->pNext) { + switch (ext->sType) { +@@ -2860,13 +2859,6 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + for (unsigned i = 0; i < pCreateInfo->queueCreateInfoCount; i++) { + const VkDeviceQueueCreateInfo *queue_create = + &pCreateInfo->pQueueCreateInfos[i]; +- const VkDeviceQueueGlobalPriorityCreateInfoKHR *priority_info = +- vk_find_struct_const(queue_create->pNext, +- DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR); +- const VkQueueGlobalPriorityKHR global_priority = priority_info ? +- priority_info->globalPriority : +- (TU_DEBUG(HIPRIO) ? VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR : +- VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR); + uint32_t qfi = queue_create->queueFamilyIndex; + enum tu_queue_type type = physical_device->queue_families[qfi].type; + device->queues[qfi] = (struct tu_queue *) vk_alloc( +@@ -2886,16 +2878,13 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + device->queue_count[qfi] = queue_create->queueCount; + + for (unsigned q = 0; q < queue_create->queueCount; q++) { +- result = tu_queue_init(device, &device->queues[qfi][q], type, +- global_priority, q, queue_create); ++ result = tu_queue_init(device, &device->queues[qfi][q], type, q, ++ queue_create); + if (result != VK_SUCCESS) { + device->queue_count[qfi] = q; + goto fail_queues; + } + } +- +- autotune_disable_preempt_optimize |= +- (global_priority == VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR); + } + + result = vk_meta_device_init(&device->vk, &device->meta); +@@ -3009,8 +2998,6 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + global->dbg_gmem_total_stores = 0; + global->dbg_gmem_taken_stores = 0; + +- global->zero_64b = 0; +- + /* initialize to ones so ffs can be used to find unused slots */ + BITSET_ONES(device->custom_border_color); + +@@ -3062,13 +3049,6 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + } + } + +- device->autotune = new tu_autotune(device, result); +- if (result != VK_SUCCESS) +- goto fail_autotune; +- +- if (autotune_disable_preempt_optimize) +- device->autotune->disable_preempt_optimize(); +- + result = tu_init_bin_preamble(device); + if (result != VK_SUCCESS) + goto fail_bin_preamble; +@@ -3105,6 +3085,10 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + } + pthread_condattr_destroy(&condattr); + ++ device->autotune = new tu_autotune(device, result); ++ if (result != VK_SUCCESS) ++ goto fail_timeline_cond; ++ + device->use_z24uint_s8uint = + physical_device->info->props.has_z24uint_s8uint && + (!border_color_without_format || +@@ -3161,8 +3145,6 @@ tu_CreateDevice(VkPhysicalDevice physicalDevice, + + fail_timeline_cond: + fail_a725_workaround: +-fail_autotune: +- delete device->autotune; + fail_bin_preamble: + fail_prepare_perfcntrs_pass_cs: + free(device->perfcntrs_pass_cs_entries); +@@ -4133,7 +4115,7 @@ tu_CreateFramebuffer(VkDevice _device, + } + } + +- tu_framebuffer_init_tiling_config(framebuffer, device, pass); ++ tu_framebuffer_tiling_config(framebuffer, device, pass); + + /* For MSRTSS, allocate extra images that are tied to the VkFramebuffer */ + if (msrtss_attachment_count > 0) { +@@ -4195,7 +4177,7 @@ tu_setup_dynamic_framebuffer(struct tu_cmd_buffer *cmd_buffer, + view->image->max_tile_h_constraint_fdm; + } + +- tu_framebuffer_init_tiling_config(framebuffer, cmd_buffer->device, pass); ++ tu_framebuffer_tiling_config(framebuffer, cmd_buffer->device, pass); + } + + VkResult +diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h +index 638ea404fa7..1be07dcdc43 100644 +--- a/src/freedreno/vulkan/tu_device.h ++++ b/src/freedreno/vulkan/tu_device.h +@@ -293,27 +293,6 @@ struct tu6_global + volatile uint32_t userspace_fence; + uint32_t _pad5; + +- /* Autotune preemption delay tracking */ +- uint64_t cur_rp_hash; +- +- uint64_t base_preemption_latency; +- uint64_t new_preemption_latency; +- volatile uint64_t preemption_latency; +- +- uint64_t base_always_count; +- uint64_t new_always_count; +- uint64_t base_aon; +- uint64_t new_aon; +- +- /* These four fields must be contiguous so that snapshot_preempt_data can copy them all in a single CP_MEMCPY. */ +- volatile uint64_t max_preemption_latency; +- volatile uint64_t max_preemption_latency_rp_hash; +- volatile uint64_t max_always_count_delta; +- volatile uint64_t max_aon_delta; +- +- uint64_t preemption_latency_cmp_scratch; +- uint64_t zero_64b; +- + struct bcolor_entry bcolor[]; + }; + #define gb_offset(member) offsetof(struct tu6_global, member) +@@ -466,8 +445,6 @@ struct tu_device + + struct tu_cs_entry bin_preamble_entry, bin_preamble_bv_entry; + +- struct tu_cs_entry switch_away_amble_entry, switch_back_amble_entry; +- + struct tu_bo *vis_stream_bo; + mtx_t vis_stream_mtx; + +@@ -617,8 +594,7 @@ struct tu_framebuffer + + uint32_t max_tile_w_constraint; + uint32_t max_tile_h_constraint; +- uint32_t initd_divisor; /* The tile divisors up to this have been initialized, for lazy init. */ +- struct tu_tiling_config tiling[TU_GMEM_LAYOUT_COUNT * TU_GMEM_LAYOUT_DIVISOR_MAX]; ++ struct tu_tiling_config tiling[TU_GMEM_LAYOUT_COUNT]; + + uint32_t attachment_count; + const struct tu_image_view *attachments[0]; +diff --git a/src/freedreno/vulkan/tu_pass.h b/src/freedreno/vulkan/tu_pass.h +index e2f0f112a72..04d24f945ed 100644 +--- a/src/freedreno/vulkan/tu_pass.h ++++ b/src/freedreno/vulkan/tu_pass.h +@@ -22,8 +22,6 @@ enum tu_gmem_layout + TU_GMEM_LAYOUT_COUNT, + }; + +-constexpr uint32_t TU_GMEM_LAYOUT_DIVISOR_MAX = 6; /* 1x (no divisor), 2 (1/2), 3 (1/3) */ +- + struct tu_subpass_barrier { + VkPipelineStageFlags2 src_stage_mask; + VkPipelineStageFlags2 dst_stage_mask; +diff --git a/src/freedreno/vulkan/tu_queue.cc b/src/freedreno/vulkan/tu_queue.cc +index fe81a5d8581..97ea5701865 100644 +--- a/src/freedreno/vulkan/tu_queue.cc ++++ b/src/freedreno/vulkan/tu_queue.cc +@@ -607,10 +607,17 @@ VkResult + tu_queue_init(struct tu_device *device, + struct tu_queue *queue, + enum tu_queue_type type, +- const VkQueueGlobalPriorityKHR global_priority, + int idx, + const VkDeviceQueueCreateInfo *create_info) + { ++ const VkDeviceQueueGlobalPriorityCreateInfoKHR *priority_info = ++ vk_find_struct_const(create_info->pNext, ++ DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_KHR); ++ const VkQueueGlobalPriorityKHR global_priority = priority_info ? ++ priority_info->globalPriority : ++ (TU_DEBUG(HIPRIO) ? VK_QUEUE_GLOBAL_PRIORITY_HIGH_KHR : ++ VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR); ++ + const int priority = tu_get_submitqueue_priority( + device->physical_device, global_priority, type, + device->vk.enabled_features.globalPriorityQuery); +diff --git a/src/freedreno/vulkan/tu_queue.h b/src/freedreno/vulkan/tu_queue.h +index 278756a43af..28925bfcb50 100644 +--- a/src/freedreno/vulkan/tu_queue.h ++++ b/src/freedreno/vulkan/tu_queue.h +@@ -43,7 +43,6 @@ VkResult + tu_queue_init(struct tu_device *device, + struct tu_queue *queue, + enum tu_queue_type type, +- const VkQueueGlobalPriorityKHR global_priority, + int idx, + const VkDeviceQueueCreateInfo *create_info); + +diff --git a/src/freedreno/vulkan/tu_tracepoints.py b/src/freedreno/vulkan/tu_tracepoints.py +index 3155977415c..bc65d58a530 100644 +--- a/src/freedreno/vulkan/tu_tracepoints.py ++++ b/src/freedreno/vulkan/tu_tracepoints.py +@@ -90,9 +90,7 @@ begin_end_tp('cmd_buffer', + Arg(type='const char *', name='engineName', var='cmd->device->instance->vk.app_info.engine_name ? cmd->device->instance->vk.app_info.engine_name : "Unknown"', c_format='%s'), + Arg(type='uint8_t', name='oneTimeSubmit', var='(cmd->usage_flags & VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT)', c_format='%u'), + Arg(type='uint8_t', name='simultaneousUse', var='(cmd->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT)', c_format='%u')], +- end_args=[ArgStruct(type='const struct tu_cmd_buffer *', var='cmd'), +- Arg(type='uint32_t', var='preempt_latency', c_format='%u', is_indirect=True), +- Arg(type='uint64_t', var='preempt_latency_rp_hash', c_format='0x%" PRIx64 "', to_prim_type='(uint64_t){}', is_indirect=True),], ++ end_args=[ArgStruct(type='const struct tu_cmd_buffer *', var='cmd')], + end_tp_struct=[Arg(type='uint32_t', name='renderpasses', var='cmd->state.total_renderpasses', c_format='%u'), + Arg(type='uint32_t', name='dispatches', var='cmd->state.total_dispatches', c_format='%u')]) + +diff --git a/src/freedreno/vulkan/tu_util.cc b/src/freedreno/vulkan/tu_util.cc +index f0c19c3465d..68f2d67dda6 100644 +--- a/src/freedreno/vulkan/tu_util.cc ++++ b/src/freedreno/vulkan/tu_util.cc +@@ -365,51 +365,6 @@ is_hw_binning_possible(const struct tu_vsc_config *vsc) + return tiles_per_pipe <= 32; + } + +-static void +-tu_tiling_config_divide_tile(const struct tu_device *dev, +- const struct tu_render_pass *pass, +- const struct tu_framebuffer *fb, +- const struct tu_tiling_config *tiling, +- struct tu_tiling_config *new_tiling, +- uint32_t divisor) +-{ +- assert(divisor > 0); +- +- *new_tiling = *tiling; +- if (divisor == 1 || !tiling->possible || tiling->tile0.width == ~0) { +- /* If the divisor is 1, or if the tiling is not possible, or if the +- * tiling is invalid, just return the original tiling. */ +- return; +- } +- +- /* Get the hardware-specified alignment values. */ +- const uint32_t tile_align_w = pass->tile_align_w; +- const uint32_t tile_align_h = dev->physical_device->info->tile_align_h; +- +- /* Divide the current tile dimensions by the divisor. */ +- uint32_t new_tile_width = tiling->tile0.width / divisor; +- uint32_t new_tile_height = tiling->tile0.height / divisor; +- +- /* Clamp to the minimum alignment if necessary and align down. */ +- if (new_tile_width < tile_align_w) +- new_tile_width = tile_align_w; +- else +- new_tile_width = ROUND_DOWN_TO_NPOT(new_tile_width, tile_align_w); +- +- if (new_tile_height < tile_align_h) +- new_tile_height = tile_align_h; +- else +- new_tile_height = ROUND_DOWN_TO_NPOT(new_tile_height, tile_align_h); +- +- new_tiling->tile0.width = new_tile_width; +- new_tiling->tile0.height = new_tile_height; +- +- /* Recalculate the tile count from the framebuffer dimensions to ensure +- * full coverage. */ +- new_tiling->vsc.tile_count.width = DIV_ROUND_UP(fb->width, new_tile_width); +- new_tiling->vsc.tile_count.height = DIV_ROUND_UP(fb->height, new_tile_height); +-} +- + static void + tu_tiling_config_update_pipe_layout(struct tu_vsc_config *vsc, + const struct tu_device *dev, +@@ -514,9 +469,9 @@ tu_tiling_config_update_binning(struct tu_vsc_config *vsc, const struct tu_devic + } + + void +-tu_framebuffer_init_tiling_config(struct tu_framebuffer *fb, +- const struct tu_device *device, +- const struct tu_render_pass *pass) ++tu_framebuffer_tiling_config(struct tu_framebuffer *fb, ++ const struct tu_device *device, ++ const struct tu_render_pass *pass) + { + for (int gmem_layout = 0; gmem_layout < TU_GMEM_LAYOUT_COUNT; gmem_layout++) { + struct tu_tiling_config *tiling = &fb->tiling[gmem_layout]; +@@ -540,49 +495,6 @@ tu_framebuffer_init_tiling_config(struct tu_framebuffer *fb, + tu_tiling_config_update_binning(fdm_offset_vsc, device); + } + } +- +- fb->initd_divisor = 1; +-} +- +-const struct tu_tiling_config * +-tu_framebuffer_get_tiling_config(struct tu_framebuffer *fb, +- const struct tu_device *device, +- const struct tu_render_pass *pass, +- int gmem_layout, +- uint32_t divisor) +-{ +- assert(divisor >= 1 && divisor <= TU_GMEM_LAYOUT_DIVISOR_MAX); +- assert(divisor == 1 || !pass->has_fdm); /* For FDM, it's expected that FDM alone will be sufficient to +- appropriately size the tiles for the framebuffer.*/ +- struct tu_tiling_config *tiling = &fb->tiling[(TU_GMEM_LAYOUT_COUNT * (divisor - 1)) + gmem_layout]; +- +- if (divisor > fb->initd_divisor) { +- const struct tu_tiling_config *base_tiling = +- tu_framebuffer_get_tiling_config(fb, device, pass, gmem_layout, divisor - 1); +- tu_tiling_config_divide_tile(device, pass, fb, base_tiling, tiling, divisor); +- +- struct tu_vsc_config *vsc = &tiling->vsc; +- if (tiling->possible) { +- tu_tiling_config_update_pipe_layout(vsc, device, false); +- tu_tiling_config_update_pipes(vsc, device); +- tu_tiling_config_update_binning(vsc, device); +- +- struct tu_vsc_config *fdm_offset_vsc = &tiling->fdm_offset_vsc; +- fdm_offset_vsc->tile_count = (VkExtent2D) { ~1, ~1 }; +- } +- +- if (!tiling->possible || /* If tiling is no longer possible, this is pointless. */ +- (vsc->binning_useful && !vsc->binning_possible) || /* Dividing further without HW binning is a bad idea. */ +- (vsc->tile_count.width * vsc->tile_count.height > 100) /* 100 tiles are too many, even with HW binning. */ +- ) { +- /* Revert to the previous level's tiling configuration. */ +- *tiling = *base_tiling; +- } +- +- fb->initd_divisor = divisor; +- } +- +- return tiling; + } + + void +diff --git a/src/freedreno/vulkan/tu_util.h b/src/freedreno/vulkan/tu_util.h +index cfa18f34978..7eda2796f39 100644 +--- a/src/freedreno/vulkan/tu_util.h ++++ b/src/freedreno/vulkan/tu_util.h +@@ -135,16 +135,9 @@ __tu_finishme(const char *file, int line, const char *format, ...) + } while (0) + + void +-tu_framebuffer_init_tiling_config(struct tu_framebuffer *fb, +- const struct tu_device *device, +- const struct tu_render_pass *pass); +- +-const struct tu_tiling_config * +-tu_framebuffer_get_tiling_config(struct tu_framebuffer *fb, +- const struct tu_device *device, +- const struct tu_render_pass *pass, +- int gmem_layout, +- uint32_t divisor); ++tu_framebuffer_tiling_config(struct tu_framebuffer *fb, ++ const struct tu_device *device, ++ const struct tu_render_pass *pass); + + #define TU_STAGE_MASK ((1 << MESA_SHADER_STAGES) - 1) + +-- +2.54.0 + diff --git a/0014-Revert-tu-autotune-Disable-autotuning-for-small-rend.patch b/0014-Revert-tu-autotune-Disable-autotuning-for-small-rend.patch new file mode 100644 index 0000000..ed56b28 --- /dev/null +++ b/0014-Revert-tu-autotune-Disable-autotuning-for-small-rend.patch @@ -0,0 +1,93 @@ +From db5315272229f2df847d663aec61c4204627adc4 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:03:15 +0200 +Subject: [PATCH 14/19] Revert "tu/autotune: Disable autotuning for small + renderpasses by default" + +This reverts commit bf2777c0130954f969c5a1038a01f94ec2d1ac63. +--- + docs/drivers/freedreno.rst | 10 +++++----- + src/freedreno/vulkan/tu_autotune.cc | 19 +++++++------------ + 2 files changed, 12 insertions(+), 17 deletions(-) + +diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst +index 9b1992fd628..a2318559526 100644 +--- a/docs/drivers/freedreno.rst ++++ b/docs/drivers/freedreno.rst +@@ -709,12 +709,12 @@ environment variables: + 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. + +- ``tune_small`` +- Enables tuning for small render passes (those with a small number of draw +- calls). By default, small RPs always use SYSMEM mode as they generally don't +- benefit from GMEM rendering due to the overhead of tiling. ++ ``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,tune_small``. ++ ``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 c708c83c044..38d09f5db45 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -97,8 +97,8 @@ enum class tu_autotune::algorithm : uint8_t { + + /* 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. */ +- TUNE_SMALL = BIT(2), /* Try tuning all RPs with <= 5 draws, ignored by default. */ ++ 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. */ +@@ -198,7 +198,7 @@ struct PACKED tu_autotune::config_t { + + str += ", Mod Flags: 0x" + std::to_string(mod_flags) + " ("; + MODF_STR(BIG_GMEM); +- MODF_STR(TUNE_SMALL); ++ MODF_STR(SMALL_SYSMEM); + str += ")"; + + str += ", Metric Flags: 0x" + std::to_string(metric_flags) + " ("; +@@ -280,7 +280,7 @@ tu_autotune::get_env_config() + if (flags_env_str) { + static const struct debug_control tu_at_flags_control[] = { + { "big_gmem", (uint32_t) mod_flag::BIG_GMEM }, +- { "tune_small", (uint32_t) mod_flag::TUNE_SMALL }, ++ { "small_sysmem", (uint32_t) mod_flag::SMALL_SYSMEM }, + { NULL, 0 } + }; + +@@ -1256,18 +1256,13 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + */ + bool simultaneous_use = cmd_buffer->usage_flags & VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + +- /* These smaller RPs with few draws are too difficult to create a balanced hash for that can independently identify +- * them while not being so unique to not properly identify them across CBs. They're generally insigificant outside of +- * a few edge cases such as during deferred rendering G-buffer passes, as we don't have a good way to deal with those +- * edge cases yet, we just disable the autotuner for small RPs entirely for now unless TUNE_SMALL is specified. +- */ +- bool ignore_small_rp = !config.test(mod_flag::TUNE_SMALL) && rp_state->drawcall_count < 5; +- +- if (!enabled || simultaneous_use || ignore_small_rp) ++ 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); + +-- +2.54.0 + diff --git a/0015-Revert-tu-autotune-Prefer-SYSMEM-when-only-SW-binnin.patch b/0015-Revert-tu-autotune-Prefer-SYSMEM-when-only-SW-binnin.patch new file mode 100644 index 0000000..734e691 --- /dev/null +++ b/0015-Revert-tu-autotune-Prefer-SYSMEM-when-only-SW-binnin.patch @@ -0,0 +1,100 @@ +From c70d031b220ca3ed83867bb1cd7e2e179109d13f Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:03:25 +0200 +Subject: [PATCH 15/19] Revert "tu/autotune: Prefer SYSMEM when only SW binning + is possible" + +This reverts commit 8e1fe9da20ceb13c125509fe5665108a57d642cf. +--- + src/freedreno/vulkan/tu_cmd_buffer.cc | 14 +++----------- + src/freedreno/vulkan/tu_device.h | 7 ++----- + src/freedreno/vulkan/tu_util.cc | 14 +++++++++----- + 3 files changed, 14 insertions(+), 21 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_cmd_buffer.cc b/src/freedreno/vulkan/tu_cmd_buffer.cc +index 3e04110b4e6..3fe6be1976d 100644 +--- a/src/freedreno/vulkan/tu_cmd_buffer.cc ++++ b/src/freedreno/vulkan/tu_cmd_buffer.cc +@@ -1313,7 +1313,7 @@ use_hw_binning(struct tu_cmd_buffer *cmd) + return true; + } + +- return vsc->binning_possible && vsc->binning_useful; ++ return vsc->binning; + } + + static bool +@@ -1376,16 +1376,8 @@ use_sysmem_rendering(struct tu_cmd_buffer *cmd, + return true; + } + +- if (TU_DEBUG(GMEM)) { +- cmd->state.rp.gmem_disable_reason = "TU_DEBUG(GMEM)"; ++ if (TU_DEBUG(GMEM)) + return false; +- } +- +- /* This is a case where it's better to avoid GMEM, too many tiles but no HW binning possible. */ +- if (!vsc->binning_possible && vsc->binning_useful) { +- cmd->state.rp.gmem_disable_reason = "Too many tiles and HW binning is not possible"; +- return true; +- } + + bool use_sysmem = cmd->device->autotune->get_optimal_mode(cmd, rp_ctx) == tu_autotune::render_mode::SYSMEM; + if (use_sysmem) +@@ -6431,7 +6423,7 @@ tu_emit_subpass_begin_gmem(struct tu_cmd_buffer *cmd, struct tu_resolve_group *r + * (perf queries), then we can't do this optimization since the + * start-of-the-CS geometry condition will have been overwritten. + */ +- bool cond_load_allowed = vsc->binning_possible && ++ bool cond_load_allowed = vsc->binning && + cmd->state.pass->has_cond_load_store && + !cmd->state.rp.draw_cs_writes_to_cond_pred; + +diff --git a/src/freedreno/vulkan/tu_device.h b/src/freedreno/vulkan/tu_device.h +index 1be07dcdc43..9665135e0e6 100644 +--- a/src/freedreno/vulkan/tu_device.h ++++ b/src/freedreno/vulkan/tu_device.h +@@ -561,11 +561,8 @@ struct tu_vsc_config { + /* Whether binning could be used for gmem rendering using this framebuffer. */ + bool binning_possible; + +- /* Whether binning is useful for GMEM rendering performance using this framebuffer. This is independent of whether +- * binning is possible, and is determined by the tile count. Not binning when it's useful would be a performance +- * hazard, and GMEM rendering should be avoided in the case where it's useful to bin but not possible to do so. +- */ +- bool binning_useful; ++ /* Whether binning should be used for gmem rendering using this framebuffer. */ ++ bool binning; + + /* pipe register values */ + uint32_t pipe_config[MAX_VSC_PIPES]; +diff --git a/src/freedreno/vulkan/tu_util.cc b/src/freedreno/vulkan/tu_util.cc +index 68f2d67dda6..77b8ac4ddbb 100644 +--- a/src/freedreno/vulkan/tu_util.cc ++++ b/src/freedreno/vulkan/tu_util.cc +@@ -460,12 +460,16 @@ tu_tiling_config_update_pipes(struct tu_vsc_config *vsc, + static void + tu_tiling_config_update_binning(struct tu_vsc_config *vsc, const struct tu_device *device) + { +- vsc->binning_useful = (vsc->tile_count.width * vsc->tile_count.height) > 2; ++ if (vsc->binning_possible) { ++ vsc->binning = (vsc->tile_count.width * vsc->tile_count.height) > 2; + +- if (TU_DEBUG(FORCEBIN)) +- vsc->binning_useful = true; +- if (TU_DEBUG(NOBIN)) +- vsc->binning_useful = false; ++ if (TU_DEBUG(FORCEBIN)) ++ vsc->binning = true; ++ if (TU_DEBUG(NOBIN)) ++ vsc->binning = false; ++ } else { ++ vsc->binning = false; ++ } + } + + void +-- +2.54.0 + diff --git a/0016-Revert-tu-autotune-Add-Profiled-algorithm.patch b/0016-Revert-tu-autotune-Add-Profiled-algorithm.patch new file mode 100644 index 0000000..d04f514 --- /dev/null +++ b/0016-Revert-tu-autotune-Add-Profiled-algorithm.patch @@ -0,0 +1,359 @@ +From 9a69681069d4d8b9b7fd925bd381db3e8cf3e720 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:03:35 +0200 +Subject: [PATCH 16/19] Revert "tu/autotune: Add "Profiled" algorithm" + +This reverts commit fac705ab8aa15e98325cf216e66512601de0c005. +--- + docs/drivers/freedreno.rst | 13 -- + src/freedreno/vulkan/tu_autotune.cc | 201 +--------------------------- + 2 files changed, 1 insertion(+), 213 deletions(-) + +diff --git a/docs/drivers/freedreno.rst b/docs/drivers/freedreno.rst +index a2318559526..ee733950fe4 100644 +--- a/docs/drivers/freedreno.rst ++++ b/docs/drivers/freedreno.rst +@@ -686,19 +686,6 @@ environment variables: + Estimates the bandwidth usage of rendering in SYSMEM and GMEM modes, and chooses + the one with lower estimated bandwidth. This is the default algorithm. + +- ``profiled`` +- Dynamically profiles the RP timings in SYSMEM and GMEM modes, and uses that to +- move a probability distribution towards the optimal choice over time. This +- algorithm tends to be far more accurate than the bandwidth algorithm at choosing +- the optimal rendering mode but may result in larger FPS variance due to being +- based on a probability distribution with random sampling. +- +- ``profiled_imm`` +- Similar to ``profiled``, but only profiles the first few instances of a RP +- and then sticks to the chosen mode for subsequent instances. This is meant +- for single-frame traces run multiple times in a CI where this algorithm can +- immediately chose the optimal rendering mode for each RP. +- + .. envvar:: TU_AUTOTUNE_FLAGS + + Modifies the behavior of the selected algorithm. Supported flags are: +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index 38d09f5db45..cfc145e3286 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -28,7 +28,6 @@ + + #define TU_AUTOTUNE_DEBUG_LOG_BASE 0 + #define TU_AUTOTUNE_DEBUG_LOG_BANDWIDTH 0 +-#define TU_AUTOTUNE_DEBUG_LOG_PROFILED 0 + + #if TU_AUTOTUNE_DEBUG_LOG_BASE + #define at_log_base(fmt, ...) mesa_logi("autotune: " fmt, ##__VA_ARGS__) +@@ -44,12 +43,6 @@ + #define at_log_bandwidth_h(fmt, hash, ...) + #endif + +-#if TU_AUTOTUNE_DEBUG_LOG_PROFILED +-#define at_log_profiled_h(fmt, hash, ...) mesa_logi("autotune-prof %016" PRIx64 ": " fmt, hash, ##__VA_ARGS__) +-#else +-#define at_log_profiled_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 + +@@ -89,8 +82,6 @@ render_mode_str(tu_autotune::render_mode mode) + + enum class tu_autotune::algorithm : uint8_t { + BANDWIDTH = 0, /* Uses estimated BW for determining rendering mode. */ +- PROFILED = 1, /* Uses dynamically profiled results for determining rendering mode. */ +- PROFILED_IMM = 2, /* Same as PROFILED but immediately resolves the SYSMEM/GMEM probability. */ + + DEFAULT = BANDWIDTH, /* Default algorithm, used if no other is specified. */ + }; +@@ -104,7 +95,6 @@ enum class tu_autotune::mod_flag : uint8_t { + /* Metric flags, for internal tracking of enabled metrics. */ + enum class tu_autotune::metric_flag : uint8_t { + SAMPLES = BIT(1), /* Enable tracking samples passed metric. */ +- TS = BIT(2), /* Enable tracking per-RP timestamp metric. */ + }; + + struct PACKED tu_autotune::config_t { +@@ -118,8 +108,6 @@ struct PACKED tu_autotune::config_t { + /* Note: Always keep in sync with rp_history to prevent UB. */ + if (algo == algorithm::BANDWIDTH) { + metric_flags |= (uint8_t) metric_flag::SAMPLES; +- } else if (algo == algorithm::PROFILED || algo == algorithm::PROFILED_IMM) { +- metric_flags |= (uint8_t) metric_flag::TS; + } + } + +@@ -193,8 +181,6 @@ struct PACKED tu_autotune::config_t { + std::string str = "Algorithm: "; + + ALGO_STR(BANDWIDTH); +- ALGO_STR(PROFILED); +- ALGO_STR(PROFILED_IMM); + + str += ", Mod Flags: 0x" + std::to_string(mod_flags) + " ("; + MODF_STR(BIG_GMEM); +@@ -203,7 +189,6 @@ struct PACKED tu_autotune::config_t { + + str += ", Metric Flags: 0x" + std::to_string(metric_flags) + " ("; + METRICF_STR(SAMPLES); +- METRICF_STR(TS); + str += ")"; + + return str; +@@ -262,12 +247,6 @@ tu_autotune::get_env_config() + std::string_view algo_strv(algo_env_str); + if (algo_strv == "bandwidth") { + algo = algorithm::BANDWIDTH; +- } else if (algo_strv == "profiled") { +- algo = algorithm::PROFILED; +- } else if (algo_strv == "profiled_imm") { +- algo = algorithm::PROFILED_IMM; +- } else { +- mesa_logw("Unknown TU_AUTOTUNE_ALGO '%s', using default", algo_env_str); + } + + if (TU_DEBUG(STARTUP)) +@@ -561,22 +540,6 @@ struct tu_autotune::rp_entry { + } + } + +- /** RP/Tile Timestamp Metric **/ +- +- uint64_t get_rp_duration() +- { +- assert(config.test(metric_flag::TS)); +- rp_gpu_data &gpu = get_gpu_data(); +- return gpu.ts_end - gpu.ts_start; +- } +- +- void emit_metric_timestamp(struct tu_cs *cs, uint64_t timestamp_iova) +- { +- tu_cs_emit_pkt7(cs, CP_REG_TO_MEM, 3); +- tu_cs_emit(cs, CP_REG_TO_MEM_0_REG(REG_A6XX_CP_ALWAYS_ON_COUNTER) | CP_REG_TO_MEM_0_CNT(2) | CP_REG_TO_MEM_0_64B); +- tu_cs_emit_qw(cs, timestamp_iova); +- } +- + /** CS Emission **/ + + void emit_rp_start(struct tu_cmd_buffer *cmd, struct tu_cs *cs) +@@ -585,9 +548,6 @@ struct tu_autotune::rp_entry { + 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)); +- +- if (config.test(metric_flag::TS)) +- emit_metric_timestamp(cs, bo_iova + offsetof(rp_gpu_data, ts_start)); + } + + void emit_rp_end(struct tu_cmd_buffer *cmd, struct tu_cs *cs) +@@ -597,9 +557,6 @@ struct tu_autotune::rp_entry { + 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)); +- +- if (config.test(metric_flag::TS)) +- emit_metric_timestamp(cs, bo_iova + offsetof(rp_gpu_data, ts_end)); + } + }; + +@@ -734,66 +691,10 @@ template class exponential_average { + } + }; + +-/* An improvement over pure EMA to filter out spikes by using two EMAs: +- * - A "slow" EMA with a low alpha to track the long-term average. +- * - A "fast" EMA with a high alpha to track short-term changes. +- * When retrieving the average, if the fast EMA deviates significantly from the slow EMA, it indicates a spike, and we +- * fall back to the slow EMA. +- */ +-template class adaptive_average { +- private: +- static constexpr double DEFAULT_SLOW_ALPHA = 0.1, DEFAULT_FAST_ALPHA = 0.5, DEFAULT_DEVIATION_THRESHOLD = 0.3; +- exponential_average slow; +- exponential_average fast; +- double deviationThreshold; +- +- public: +- size_t count = 0; +- +- explicit adaptive_average(double slow_alpha = DEFAULT_SLOW_ALPHA, +- double fast_alpha = DEFAULT_FAST_ALPHA, +- double deviation_threshold = DEFAULT_DEVIATION_THRESHOLD) noexcept +- : slow(slow_alpha), fast(fast_alpha), deviationThreshold(deviation_threshold) +- { +- } +- +- void add(T value) noexcept +- { +- slow.add(value); +- fast.add(value); +- count++; +- } +- +- T get() const noexcept +- { +- double s = slow.get(); +- double f = fast.get(); +- /* Use fast if it's close to slow (normal variation). +- * Use slow if fast deviates too much (likely a spike). +- */ +- double deviation = std::abs(f - s) / s; +- return (deviation < deviationThreshold) ? f : s + (f - s) * deviationThreshold; +- } +- +- void clear() noexcept +- { +- slow.clear(); +- fast.clear(); +- count = 0; +- } +-}; +- + /* 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 { +- private: +- /* Amount of duration samples for profiling before we start averaging. */ +- static constexpr uint32_t MIN_PROFILE_DURATION_COUNT = 5; +- +- adaptive_average sysmem_rp_average; +- adaptive_average gmem_rp_average; +- + public: + uint64_t hash; /* The hash of the renderpass, just for debug output. */ + uint32_t duplicates; /* The amount of times we've seen this RP, used for identifying repeated RPs. */ +@@ -801,7 +702,7 @@ struct tu_autotune::rp_history { + std::atomic refcount = 0; /* Reference count to prevent deletion when active. */ + std::atomic last_use_ts; /* Last time the reference count was updated, in monotonic nanoseconds. */ + +- rp_history(uint64_t hash): hash(hash), last_use_ts(os_time_get_nano()), profiled(hash) ++ rp_history(uint64_t hash): hash(hash), last_use_ts(os_time_get_nano()) + { + } + +@@ -876,90 +777,6 @@ struct tu_autotune::rp_history { + } + } bandwidth; + +- /** Profiled Algorithms **/ +- struct profiled_algo { +- private: +- /* Range [0 (GMEM), 100 (SYSMEM)], where 50 means no preference. */ +- constexpr static uint32_t PROBABILITY_MAX = 100, PROBABILITY_MID = 50; +- constexpr static uint32_t PROBABILITY_PREFER_SYSMEM = 80, PROBABILITY_PREFER_GMEM = 20; +- +- std::atomic sysmem_probability = PROBABILITY_MID; +- bool should_reset = false; /* If true, will reset sysmem_probability before next update. */ +- uint64_t seed[2] { 0x3bffb83978e24f88, 0x9238d5d56c71cd35 }; +- +- public: +- profiled_algo(uint64_t hash) +- { +- seed[1] = hash; +- } +- +- void update(rp_history &history, bool immediate) +- { +- auto &sysmem_ema = history.sysmem_rp_average; +- auto &gmem_ema = history.gmem_rp_average; +- uint32_t sysmem_prob = sysmem_probability.load(std::memory_order_relaxed); +- if (immediate) { +- /* Try to immediately resolve the probability, this is useful for CI running a single trace of frames where +- * the probabilites aren't expected to change from run to run. This environment also gives us a best case +- * scenario for autotune performance, since we know the optimal decisions. +- */ +- +- if (sysmem_prob == 0 || sysmem_prob == 100) +- return; /* Already resolved, no further updates are necessary. */ +- +- if (sysmem_ema.count < 1) { +- sysmem_prob = PROBABILITY_MAX; +- } else if (gmem_ema.count < 1) { +- sysmem_prob = 0; +- } else { +- sysmem_prob = gmem_ema.get() < sysmem_ema.get() ? 0 : PROBABILITY_MAX; +- } +- } else { +- if (sysmem_ema.count < MIN_PROFILE_DURATION_COUNT || gmem_ema.count < MIN_PROFILE_DURATION_COUNT) { +- /* Not enough data to make a decision, bias towards least used. */ +- sysmem_prob = sysmem_ema.count < gmem_ema.count ? PROBABILITY_PREFER_SYSMEM : PROBABILITY_PREFER_GMEM; +- should_reset = true; +- } else { +- if (should_reset) { +- sysmem_prob = PROBABILITY_MID; +- should_reset = false; +- } +- +- /* Adjust probability based on timing results. */ +- constexpr uint32_t STEP_DELTA = 5, MIN_PROBABILITY = 5, MAX_PROBABILITY = 95; +- +- uint64_t avg_sysmem = sysmem_ema.get(); +- uint64_t avg_gmem = gmem_ema.get(); +- if (avg_gmem < avg_sysmem && sysmem_prob > MIN_PROBABILITY) { +- sysmem_prob = MAX2(sysmem_prob - STEP_DELTA, MIN_PROBABILITY); +- } else if (avg_sysmem < avg_gmem && sysmem_prob < MAX_PROBABILITY) { +- sysmem_prob = MIN2(sysmem_prob + STEP_DELTA, MAX_PROBABILITY); +- } +- } +- } +- +- sysmem_probability.store(sysmem_prob, std::memory_order_relaxed); +- +- at_log_profiled_h("update%s avg_gmem: %" PRIu64 " us (%" PRIu64 " samples) avg_sysmem: %" PRIu64 +- " us (%" PRIu64 " samples) = sysmem_probability: %" PRIu32, +- history.hash, immediate ? "-imm" : "", ticks_to_us(gmem_ema.get()), gmem_ema.count, +- ticks_to_us(sysmem_ema.get()), sysmem_ema.count, sysmem_prob); +- } +- +- public: +- render_mode get_optimal_mode(rp_history &history) +- { +- uint32_t l_sysmem_probability = sysmem_probability.load(std::memory_order_relaxed); +- bool select_sysmem = (rand_xorshift128plus(seed) % PROBABILITY_MAX) < l_sysmem_probability; +- render_mode mode = select_sysmem ? render_mode::SYSMEM : render_mode::GMEM; +- +- at_log_profiled_h("%" PRIu32 "%% sysmem chance, using %s", history.hash, l_sysmem_probability, +- render_mode_str(mode)); +- +- return mode; +- } +- } profiled; +- + 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. */ +@@ -968,19 +785,6 @@ struct tu_autotune::rp_history { + + if (entry_config.test(metric_flag::SAMPLES) && at_config.is_enabled(algorithm::BANDWIDTH)) + bandwidth.update(entry.get_samples_passed()); +- if (entry_config.test(metric_flag::TS)) { +- if (entry.sysmem) { +- uint64_t rp_duration = entry.get_rp_duration(); +- +- sysmem_rp_average.add(rp_duration); +- } else { +- gmem_rp_average.add(entry.get_rp_duration()); +- } +- +- if (at_config.is_enabled(algorithm::PROFILED) || at_config.is_enabled(algorithm::PROFILED_IMM)) { +- profiled.update(*this, at_config.is_enabled(algorithm::PROFILED_IMM)); +- } +- } + } + }; + +@@ -1279,9 +1083,6 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + *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); + +- if (config.is_enabled(algorithm::PROFILED) || config.is_enabled(algorithm::PROFILED_IMM)) +- return history.profiled.get_optimal_mode(history); +- + if (config.is_enabled(algorithm::BANDWIDTH)) + return history.bandwidth.get_optimal_mode(history, cmd_state, pass, framebuffer, rp_state); + +-- +2.54.0 + diff --git a/0017-Revert-tu-autotune-Improve-RP-hash.patch b/0017-Revert-tu-autotune-Improve-RP-hash.patch new file mode 100644 index 0000000..d1b6251 --- /dev/null +++ b/0017-Revert-tu-autotune-Improve-RP-hash.patch @@ -0,0 +1,171 @@ +From 8137f3552ab872c65db82d57bf490fd18414c1c6 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:03:43 +0200 +Subject: [PATCH 17/19] Revert "tu/autotune: Improve RP hash" + +This reverts commit 44564b966d87a7059b142c268f5457ec8396f5bc. +--- + src/freedreno/vulkan/tu_autotune.cc | 71 +++++------------------------ + src/freedreno/vulkan/tu_autotune.h | 5 -- + 2 files changed, 11 insertions(+), 65 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_autotune.cc b/src/freedreno/vulkan/tu_autotune.cc +index cfc145e3286..971cc1a9503 100644 +--- a/src/freedreno/vulkan/tu_autotune.cc ++++ b/src/freedreno/vulkan/tu_autotune.cc +@@ -434,9 +434,6 @@ struct tu_autotune::rp_entry { + bool sysmem; + uint32_t draw_count; + +- /* Amount of repeated RPs so far, used for uniquely identifying instances of the same RPs. */ +- uint32_t duplicates = 0; +- + 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) + { +@@ -586,25 +583,12 @@ tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass, + const struct tu_framebuffer *framebuffer, + const struct tu_cmd_buffer *cmd) + { +- /* It may be hard to match the same renderpass between frames, or rather it's hard to strike a +- * balance between being too lax with identifying different renderpasses as the same one, and +- * not recognizing the same renderpass between frames when only a small thing changed. +- * +- * This is mainly an issue with translation layers (particularly DXVK), because a layer may +- * break a "renderpass" into smaller ones due to some heuristic that isn't consistent between +- * frames. +- * +- * Note: Not using image IOVA leads to too many false matches. ++ /* 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. + */ + +- struct PACKED packed_att_properties { +- uint64_t iova; +- bool load; +- bool store; +- bool load_stencil; +- bool store_stencil; +- }; +- + auto get_hash = [&](uint32_t *data, size_t size) { + uint32_t *ptr = data; + *ptr++ = framebuffer->width; +@@ -612,18 +596,12 @@ tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass, + *ptr++ = framebuffer->layers; + + for (unsigned i = 0; i < pass->attachment_count; i++) { +- packed_att_properties props = { +- .iova = cmd->state.attachments[i]->image->iova + cmd->state.attachments[i]->view.offset, +- .load = pass->attachments[i].load, +- .store = pass->attachments[i].store, +- .load_stencil = pass->attachments[i].load_stencil, +- .store_stencil = pass->attachments[i].store_stencil, +- }; +- +- memcpy(ptr, &props, sizeof(packed_att_properties)); +- ptr += sizeof(packed_att_properties) / sizeof(uint32_t); ++ *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; + } +- assert(ptr == data + size); + + return XXH3_64bits(data, size * sizeof(uint32_t)); + }; +@@ -631,8 +609,8 @@ tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass, + /* 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. + */ +- size_t data_count = 3 + (pass->attachment_count * sizeof(packed_att_properties) / sizeof(uint32_t)); +- constexpr size_t STACK_MAX_DATA_COUNT = 3 + (5 * 3); /* in u32 units. */ ++ 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. */ +@@ -645,11 +623,6 @@ tu_autotune::rp_key::rp_key(const struct tu_render_pass *pass, + } + } + +-tu_autotune::rp_key::rp_key(const rp_key &key, uint32_t duplicates) +-{ +- hash = XXH3_64bits_withSeed(&key.hash, sizeof(key.hash), duplicates); +-} +- + /* 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). + */ +@@ -697,7 +670,6 @@ template class exponential_average { + struct tu_autotune::rp_history { + public: + uint64_t hash; /* The hash of the renderpass, just for debug output. */ +- uint32_t duplicates; /* The amount of times we've seen this RP, used for identifying repeated RPs. */ + + std::atomic refcount = 0; /* Reference count to prevent deletion when active. */ + std::atomic last_use_ts; /* Last time the reference count was updated, in monotonic nanoseconds. */ +@@ -1007,16 +979,6 @@ tu_autotune::cmd_buf_ctx::attach_rp_entry(struct tu_device *device, + return new_entry.get(); + } + +-tu_autotune::rp_entry * +-tu_autotune::cmd_buf_ctx::find_rp_entry(const rp_key &key) +-{ +- for (auto &entry : batch->entries) { +- if (entry->history->hash == key.hash) +- return entry.get(); +- } +- return nullptr; +-} +- + tu_autotune::render_mode + tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx) + { +@@ -1069,17 +1031,6 @@ tu_autotune::get_optimal_mode(struct tu_cmd_buffer *cmd_buffer, rp_ctx_t *rp_ctx + return render_mode::SYSMEM; + + rp_key key(pass, framebuffer, cmd_buffer); +- +- /* When nearly identical renderpasses appear multiple times within the same command buffer, we need to generate a +- * unique hash for each instance to distinguish them. While this approach doesn't address identical renderpasses +- * across different command buffers, it is good enough in most cases. +- */ +- rp_entry *entry = cb_ctx.find_rp_entry(key); +- if (entry) { +- entry->duplicates++; +- key = rp_key(key, entry->duplicates); +- } +- + *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); + +diff --git a/src/freedreno/vulkan/tu_autotune.h b/src/freedreno/vulkan/tu_autotune.h +index 0f4215e8461..1dc966a20e9 100644 +--- a/src/freedreno/vulkan/tu_autotune.h ++++ b/src/freedreno/vulkan/tu_autotune.h +@@ -155,9 +155,6 @@ struct tu_autotune { + const struct tu_framebuffer *framebuffer, + const struct tu_cmd_buffer *cmd); + +- /* Further salt the hash to distinguish between multiple instances of the same RP within a single command buffer. */ +- rp_key(const rp_key &key, uint32_t duplicates); +- + /* Equality operator, used in unordered_map. */ + constexpr bool operator==(const rp_key &other) const noexcept + { +@@ -212,8 +209,6 @@ struct tu_autotune { + rp_entry * + attach_rp_entry(struct tu_device *device, rp_history_handle &&history, config_t config, uint32_t draw_count); + +- rp_entry *find_rp_entry(const rp_key &key); +- + friend struct tu_autotune; + + public: +-- +2.54.0 + diff --git a/0018-Revert-tu-Rewrite-autotune-in-C.patch b/0018-Revert-tu-Rewrite-autotune-in-C.patch new file mode 100644 index 0000000..19674fb --- /dev/null +++ b/0018-Revert-tu-Rewrite-autotune-in-C.patch @@ -0,0 +1,2456 @@ +From 7fd383b6eff6fc29d8acee2fd1f1940c925d3568 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +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 +-#include +-#include +-#include +-#include +-#include +-#include +- +-#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::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 + 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 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 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 class exponential_average { +- private: +- std::atomic average = std::numeric_limits::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(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::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(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 refcount = 0; /* Reference count to prevent deletion when active. */ +- std::atomic 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 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 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()) ++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(); +-} ++ 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 &new_entry = +- batch->entries.emplace_back(std::make_unique(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 + 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 ++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 +-#include +-#include +-#include +-#include +-#include +-#include +- +-#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 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_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> 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> 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(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_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 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 ++void tu_autotune_begin_renderpass(struct tu_cmd_buffer *cmd, ++ struct tu_cs *cs, ++ struct tu_renderpass_result *autotune_result); ++ ++template ++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 + 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(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 + 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(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 + 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(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 + 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(cmd, cs, FD_CCU_CLEAN_BLIT_CACHE); + +- cmd->device->autotune->end_renderpass(cmd, cs, rp_ctx); ++ tu_autotune_end_renderpass(cmd, cs, autotune_result); + + tu_cs_sanity_check(cs); + } +@@ -3758,7 +3763,7 @@ tu_emit_subsampled(struct tu_cmd_buffer *cmd, + template + 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(cmd, &cmd->tile_store_cs); + tu_cs_end(&cmd->tile_store_cs); + +- tu6_tile_render_begin(cmd, &cmd->cs, rp_ctx, fdm_offsets); ++ tu6_tile_render_begin(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(cmd, &cmd->cs, rp_ctx); ++ tu6_tile_render_end(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 + 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(cmd, &cmd->cs, rp_ctx); ++ tu6_sysmem_render_begin(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(cmd, &cmd->cs, rp_ctx); ++ tu6_sysmem_render_end(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(cmd_buffer); + +- tu_autotune::rp_ctx_t rp_ctx = NULL; +- if (use_sysmem_rendering(cmd_buffer, &rp_ctx)) +- tu_cmd_render_sysmem(cmd_buffer, rp_ctx); ++ struct tu_renderpass_result *autotune_result = NULL; ++ if (use_sysmem_rendering(cmd_buffer, &autotune_result)) ++ tu_cmd_render_sysmem(cmd_buffer, autotune_result); + else +- tu_cmd_render_tiles(cmd_buffer, rp_ctx, fdm_offsets); ++ tu_cmd_render_tiles(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 + diff --git a/0019-Fix-is_perf_cntr_selectable-in-tu_device.cc.patch b/0019-Fix-is_perf_cntr_selectable-in-tu_device.cc.patch new file mode 100644 index 0000000..7b70984 --- /dev/null +++ b/0019-Fix-is_perf_cntr_selectable-in-tu_device.cc.patch @@ -0,0 +1,27 @@ +From 362798e015220647aa46361475b4af999ed90d22 Mon Sep 17 00:00:00 2001 +From: Jocelyn Falempe +Date: Fri, 26 Jun 2026 11:41:20 +0200 +Subject: [PATCH 19/19] Fix is_perf_cntr_selectable in tu_device.cc + +Signed-off-by: Jocelyn Falempe +--- + src/freedreno/vulkan/tu_device.cc | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/src/freedreno/vulkan/tu_device.cc b/src/freedreno/vulkan/tu_device.cc +index 38da9a54ce2..bfc7f7c2ca8 100644 +--- a/src/freedreno/vulkan/tu_device.cc ++++ b/src/freedreno/vulkan/tu_device.cc +@@ -1778,9 +1778,6 @@ tu_physical_device_init(struct tu_physical_device *device, + + device->vk.pipeline_cache_import_ops = cache_import_ops; + +- /* gen8 and onwards must use kernel UAPI for perfcntr management */ +- device->is_perf_cntr_selectable &= (device->info->chip <= 7); +- + return VK_SUCCESS; + + fail_free_name: +-- +2.54.0 + diff --git a/mesa.spec b/mesa.spec index 048e799..e8bd99d 100644 --- a/mesa.spec +++ b/mesa.spec @@ -3,7 +3,6 @@ %global with_radeonsi 1 %global with_vmware 1 %global with_vulkan_hw 1 -%global with_vdpau 1 %if !0%{?rhel} %global with_r300 1 %global with_r600 1 @@ -52,7 +51,7 @@ %if 0%{?with_asahi} %global asahi_platform_vulkan %{?with_vulkan_hw:,asahi}%{!?with_vulkan_hw:%{nil}} %endif -%global extra_platform_vulkan %{?with_vulkan_hw:,broadcom,freedreno,panfrost,imagination-experimental}%{!?with_vulkan_hw:%{nil}} +%global extra_platform_vulkan %{?with_vulkan_hw:,broadcom,freedreno,panfrost,imagination}%{!?with_vulkan_hw:%{nil}} %endif %if !0%{?rhel} @@ -80,9 +79,9 @@ Name: mesa Summary: Mesa graphics libraries -%global ver 25.2.7 +%global ver 26.1.1 Version: %{lua:ver = string.gsub(rpm.expand("%{ver}"), "-", "~"); print(ver)} -Release: 4%{?dist} +Release: 1%{?dist} License: MIT AND BSD-3-Clause AND SGI-B-2.0 URL: http://www.mesa3d.org @@ -99,16 +98,16 @@ Source2: https://github.com/mesonbuild/meson/releases/download/%{meson_ver}/meso # libclc is not available in RHEL but it is required for Intel drivers since # mesa >= 24.1.0 -%global libclc_version 21.1.3 -Source3: https://github.com/llvm/llvm-project/releases/download/llvmorg-%{libclc_version}/libclc-%{libclc_version}.src.tar.xz +%global libclc_version 22.1.1 +Source3: https://github.com/llvm/llvm-project/releases/download/llvmorg-%{libclc_version}/llvm-project-%{libclc_version}.src.tar.xz BuildRequires: libedit-devel BuildRequires: clang-devel >= %{libclc_version} # Build our own version # BuildRequires: spirv-llvm-translator-tools # spirv-llvm-translator is a dependency of libclc -%global spirv_llvm_trans_ver 21.1.2 -%global spirv_llvm_trans_commit a37544fc0bb90b4e8b8d755580119c018891dfa4 +%global spirv_llvm_trans_ver 22.1.1 +%global spirv_llvm_trans_commit 114f5ee1cec66fe2ca733c12770c1bc196675d32 %global spirv_llvm_trans_shortcommit %(c=%{spirv_llvm_trans_commit}; echo ${c:0:7}) Source4: https://github.com/KhronosGroup/SPIRV-LLVM-Translator/archive/%{spirv_llvm_trans_commit}/spirv-llvm-translator-%{spirv_llvm_trans_shortcommit}.tar.gz BuildRequires: cmake @@ -128,10 +127,10 @@ BuildRequires: wayland-devel # https://gitlab.freedesktop.org/mesa/mesa/-/tree/main/subprojects # but we generally want the latest compatible versions %global rust_paste_ver 1.0.15 -%global rust_proc_macro2_ver 1.0.101 -%global rust_quote_ver 1.0.40 -%global rust_syn_ver 2.0.106 -%global rust_unicode_ident_ver 1.0.18 +%global rust_proc_macro2_ver 1.0.106 +%global rust_quote_ver 1.0.44 +%global rust_syn_ver 2.0.115 +%global rust_unicode_ident_ver 1.0.23 %global rustc_hash_ver 2.1.1 Source10: https://crates.io/api/v1/crates/paste/%{rust_paste_ver}/download#/paste-%{rust_paste_ver}.tar.gz Source11: https://crates.io/api/v1/crates/proc-macro2/%{rust_proc_macro2_ver}/download#/proc-macro2-%{rust_proc_macro2_ver}.tar.gz @@ -140,18 +139,25 @@ Source13: https://crates.io/api/v1/crates/syn/%{rust_syn_ver}/download#/sy Source14: https://crates.io/api/v1/crates/unicode-ident/%{rust_unicode_ident_ver}/download#/unicode-ident-%{rust_unicode_ident_ver}.tar.gz Source15: https://crates.io/api/v1/crates/rustc-hash/%{rustc_hash_ver}/download#/rustc-hash-%{rustc_hash_ver}.tar.gz -# fix zink/device-select bug -Patch11: 0001-device-select-add-a-layer-setting-to-disable-device-.patch -Patch12: 0002-zink-use-device-select-layer-settings-to-disable-dev.patch - -# Fix s390x regressions: -# https://issues.redhat.com/browse/RHEL-151315 -Patch13: 0001-gallivm-handle-u16-correct-on-const-loads.patch -Patch14: 0001-Revert-dri-fix-__DRI_IMAGE_FORMAT-to-PIPE_FORMAT-map.patch - -# Fix performance issue with Xorg -Patch21: 0001-drisw-Modify-drisw_swap_buffers_with_damage-to-swap-.patch -Patch22: 0002-Revert-drisw-Copy-entire-buffer-ignoring-damage-regi.patch +Patch: 0001-Fix-build-with-python-3.9.patch +Patch: 0002-Fix-enum.patch +Patch: 0003-Revert-tu-autotune-Clear-active_batches-before-histo.patch +Patch: 0004-Revert-tu-autotune-Allocate-performance-counters-fro.patch +Patch: 0005-Revert-tu-autotune-Fail-gracefully-when-CP-counters-.patch +Patch: 0006-Revert-tu-autotune-Only-lock-RPs-sustain-certain-mod.patch +Patch: 0007-Revert-tu-autotune-Allow-99-max-probability-in-profi.patch +Patch: 0008-Revert-tu-autotune-Add-render-mode-locking-to-PROFIL.patch +Patch: 0009-Revert-tu-util-Allow-setting-autotune-mode-from-dric.patch +Patch: 0010-Revert-tu-autotune-Add-prefer-SYSMEM-GMEM-mode.patch +Patch: 0011-Revert-tu-Only-emit-preempt-optimization-ambles-when.patch +Patch: 0012-Revert-tu-Disable-features-using-performance-counter.patch +Patch: 0013-Revert-tu-autotune-Add-Preempt-Optimize-mode.patch +Patch: 0014-Revert-tu-autotune-Disable-autotuning-for-small-rend.patch +Patch: 0015-Revert-tu-autotune-Prefer-SYSMEM-when-only-SW-binnin.patch +Patch: 0016-Revert-tu-autotune-Add-Profiled-algorithm.patch +Patch: 0017-Revert-tu-autotune-Improve-RP-hash.patch +Patch: 0018-Revert-tu-Rewrite-autotune-in-C.patch +Patch: 0019-Fix-is_perf_cntr_selectable-in-tu_device.cc.patch BuildRequires: meson BuildRequires: gcc @@ -200,9 +206,6 @@ BuildRequires: flex %if 0%{?with_lmsensors} BuildRequires: lm_sensors-devel %endif -%if 0%{?with_vdpau} -BuildRequires: pkgconfig(vdpau) >= 1.1 -%endif %if 0%{?with_va} BuildRequires: pkgconfig(libva) >= 0.38.0 %endif @@ -247,6 +250,7 @@ BuildRequires: pkgconfig(vulkan) %if 0%{?with_d3d12} BuildRequires: pkgconfig(DirectX-Headers) >= 1.614.1 %endif +BuildRequires: libatomic %description %{summary}. @@ -257,6 +261,7 @@ Provides: mesa-dri-filesystem = %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: mesa-omx-drivers < %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: mesa-libd3d < %{?epoch:%{epoch}:}%{version}-%{release} Obsoletes: mesa-libd3d-devel < %{?epoch:%{epoch}:}%{version}-%{release} +Obsoletes: mesa-vdpau-drivers < %{?epoch:%{epoch}:}%{version}-%{release} %description filesystem %{summary}. @@ -330,15 +335,6 @@ Obsoletes: %{name}-vaapi-drivers < 22.2.0-5 %{summary}. %endif -%if 0%{?with_vdpau} -%package vdpau-drivers -Summary: Mesa-based VDPAU drivers -Requires: %{name}-filesystem%{?_isa} = %{?epoch:%{epoch}:}%{version}-%{release} - -%description vdpau-drivers -%{summary}. -%endif - %package libgbm Summary: Mesa gbm runtime library Provides: libgbm @@ -448,9 +444,9 @@ path = "src/nouveau/nil/lib.rs" # only direct dependencies need to be listed here [dependencies] -paste = "$(grep ^directory subprojects/paste.wrap | sed 's|.*-||')" -syn = { version = "$(grep ^directory subprojects/syn.wrap | sed 's|.*-||')", features = ["clone-impls"] } -rustc-hash = "$(grep ^directory subprojects/rustc-hash.wrap | sed 's|.*-||')" +paste = "$(grep ^directory subprojects/paste*.wrap | sed 's|.*-||')" +syn = { version = "$(grep ^directory subprojects/syn*.wrap | sed 's|.*-||')", features = ["clone-impls"] } +rustc-hash = "$(grep ^directory subprojects/rustc-hash*.wrap | sed 's|.*-||')" _EOF %if 0%{?vendor_nvk_crates} %cargo_prep -v subprojects/packagecache @@ -500,14 +496,19 @@ export PKG_CONFIG_PATH=%{buildroot}%{_libdir}/pkgconfig:%{buildroot}%{_datadir}/ export PATH=%{buildroot}%{_bindir}:$PATH # Build libclc -cd libclc-%{libclc_version}.src +cd llvm-project-%{libclc_version}.src/libclc export CFLAGS="%{build_cflags} -D__extern_always_inline=inline" +export CC=clang %cmake -GNinja \ -DCMAKE_INSTALL_DATADIR:PATH=%{_lib} \ + -DLLVM_DIR="/usr/lib64/cmake/llvm/" \ -DLIBCLC_TARGETS_TO_BUILD="spirv-mesa3d-;spirv64-mesa3d-" \ + -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" \ -DLLVM_SPIRV=%{buildroot}%{_bindir}/llvm-spirv %cmake_build %cmake_install +unset CC +unset CFLAGS cd - sed -e "s!libexecdir=!libexecdir=\/%{buildroot}!" -i %{buildroot}%{_libdir}/pkgconfig/libclc.pc @@ -528,7 +529,7 @@ export RUSTFLAGS="%build_rustflags" export MESON_PACKAGE_CACHE_DIR="%{cargo_registry}/" %endif rewrite_wrap_file() { - sed -e "/source.*/d" -e "s/${1}-.*/$(basename ${MESON_PACKAGE_CACHE_DIR:-subprojects/packagecache}/${1}-*)/" -i subprojects/${1}.wrap + sed -e "/source.*/d" -e "s/^directory = ${1}-.*/directory = $(basename ${MESON_PACKAGE_CACHE_DIR:-subprojects/packagecache}/${1}-*)/" -i subprojects/${1}*.wrap } rewrite_wrap_file proc-macro2 @@ -546,7 +547,6 @@ rewrite_wrap_file rustc-hash %else -Dgallium-drivers=llvmpipe,virgl \ %endif - -Dgallium-vdpau=%{?with_vdpau:enabled}%{!?with_vdpau:disabled} \ -Dgallium-va=%{?with_va:enabled}%{!?with_va:disabled} \ -Dgallium-mediafoundation=disabled \ -Dteflon=%{?with_teflon:true}%{!?with_teflon:false} \ @@ -568,6 +568,7 @@ rewrite_wrap_file rustc-hash -Dshared-llvm=enabled \ -Dvalgrind=%{?with_valgrind:enabled}%{!?with_valgrind:disabled} \ -Dbuild-tests=false \ + -Ddisplay-info=disabled \ %if !0%{?with_libunwind} -Dlibunwind=disabled \ %endif @@ -622,15 +623,51 @@ rm -f %{buildroot}%{_libdir}/pkgconfig/LLVMSPIRVLib.pc rm -f %{buildroot}%{_datadir}/pkgconfig/wayland-protocols.pc rm -fr %{buildroot}%{_datadir}/wayland-protocols/ -# libvdpau opens the versioned name, don't bother including the unversioned -rm -vf %{buildroot}%{_libdir}/vdpau/*.so - # likewise glvnd rm -vf %{buildroot}%{_libdir}/libGLX_mesa.so rm -vf %{buildroot}%{_libdir}/libEGL_mesa.so # XXX can we just not build this rm -vf %{buildroot}%{_libdir}/libGLES* +# Remove some unused kmsro driver on x86_64 +%ifarch x86_64 i686 ppc64le +rm -f %{buildroot}%{_libdir}/dri/armada-drm_dri.so +rm -f %{buildroot}%{_libdir}/dri/exynos_dri.so +rm -f %{buildroot}%{_libdir}/dri/gm12u320_dri.so +rm -f %{buildroot}%{_libdir}/dri/hdlcd_dri.so +rm -f %{buildroot}%{_libdir}/dri/hx8357d_dri.so +rm -f %{buildroot}%{_libdir}/dri/ili9163_dri.so +rm -f %{buildroot}%{_libdir}/dri/ili9225_dri.so +rm -f %{buildroot}%{_libdir}/dri/ili9341_dri.so +rm -f %{buildroot}%{_libdir}/dri/ili9486_dri.so +rm -f %{buildroot}%{_libdir}/dri/imx-dcss_dri.so +rm -f %{buildroot}%{_libdir}/dri/mediatek_dri.so +rm -f %{buildroot}%{_libdir}/dri/meson_dri.so +rm -f %{buildroot}%{_libdir}/dri/mi0283qt_dri.so +rm -f %{buildroot}%{_libdir}/dri/panel-mipi-dbi_dri.so +rm -f %{buildroot}%{_libdir}/dri/pl111_dri.so +rm -f %{buildroot}%{_libdir}/dri/repaper_dri.so +rm -f %{buildroot}%{_libdir}/dri/rockchip_dri.so +rm -f %{buildroot}%{_libdir}/dri/rzg2l-du_dri.so +rm -f %{buildroot}%{_libdir}/dri/ssd130x_dri.so +rm -f %{buildroot}%{_libdir}/dri/st7586_dri.so +rm -f %{buildroot}%{_libdir}/dri/st7735r_dri.so +rm -f %{buildroot}%{_libdir}/dri/sti_dri.so +rm -f %{buildroot}%{_libdir}/dri/sun4i-drm_dri.so +rm -f %{buildroot}%{_libdir}/dri/vkms_dri.so +rm -f %{buildroot}%{_libdir}/dri/zynqmp-dpsub_dri.so +rm -f %{buildroot}%{_libdir}/dri/ingenic-drm_dri.so +rm -f %{buildroot}%{_libdir}/dri/imx-drm_dri.so +rm -f %{buildroot}%{_libdir}/dri/imx-lcdif_dri.so +rm -f %{buildroot}%{_libdir}/dri/kirin_dri.so +rm -f %{buildroot}%{_libdir}/dri/komeda_dri.so +rm -f %{buildroot}%{_libdir}/dri/mali-dp_dri.so +rm -f %{buildroot}%{_libdir}/dri/mcde_dri.so +rm -f %{buildroot}%{_libdir}/dri/mxsfb-drm_dri.so +rm -f %{buildroot}%{_libdir}/dri/rcar-du_dri.so +rm -f %{buildroot}%{_libdir}/dri/stm_dri.so +%endif + %if ! 0%{?with_asahi} # This symlink is unconditionally created when any kmsro driver is enabled # We don't want this one so delete it @@ -719,14 +756,8 @@ popd %{_libdir}/dri/i915_dri.so %endif %endif -%ifarch %{arm} aarch64 -%if 0%{?with_asahi} -%{_libdir}/dri/apple_dri.so -%{_libdir}/dri/asahi_dri.so -%endif -%if 0%{?with_d3d12} -%{_libdir}/dri/d3d12_dri.so -%endif + +%ifarch aarch64 %{_libdir}/dri/ingenic-drm_dri.so %{_libdir}/dri/imx-drm_dri.so %{_libdir}/dri/imx-lcdif_dri.so @@ -738,6 +769,16 @@ popd %{_libdir}/dri/rcar-du_dri.so %{_libdir}/dri/stm_dri.so %endif +%ifarch %{arm} aarch64 +%if 0%{?with_asahi} +%{_libdir}/dri/apple_dri.so +%{_libdir}/dri/asahi_dri.so +%endif +%if 0%{?with_d3d12} +%{_libdir}/dri/d3d12_dri.so +%endif +%endif + %if 0%{?with_vc4} %{_libdir}/dri/vc4_dri.so %endif @@ -794,6 +835,10 @@ popd %{_libdir}/dri/vkms_dri.so %{_libdir}/dri/zynqmp-dpsub_dri.so %endif +%ifarch x86_64 i686 ppc64le +%{_libdir}/dri/udl_dri.so +%endif + %if 0%{?with_vulkan_hw} %{_libdir}/dri/zink_dri.so %endif @@ -813,22 +858,6 @@ popd %{_libdir}/dri/virtio_gpu_drv_video.so %endif -%if 0%{?with_vdpau} -%files vdpau-drivers -%dir %{_libdir}/vdpau -%{_libdir}/vdpau/libvdpau_nouveau.so.1* -%if 0%{?with_r600} -%{_libdir}/vdpau/libvdpau_r600.so.1* -%endif -%if 0%{?with_radeonsi} -%{_libdir}/vdpau/libvdpau_radeonsi.so.1* -%endif -%if 0%{?with_d3d12} -%{_libdir}/vdpau/libvdpau_d3d12.so.1* -%endif -%{_libdir}/vdpau/libvdpau_virtio_gpu.so.1* -%endif - %if 0%{?with_d3d12} %files dxil-devel %{_bindir}/spirv2dxil @@ -869,24 +898,28 @@ popd %{_libdir}/libvulkan_intel_hasvk.so %{_datadir}/vulkan/icd.d/intel_hasvk_icd.*.json %endif -%ifarch %{arm} aarch64 +%ifarch aarch64 x86_64 %{ix86} %if 0%{?with_asahi} %{_libdir}/libvulkan_asahi.so %{_datadir}/vulkan/icd.d/asahi_icd.*.json %endif +%ifarch aarch64 %{_libdir}/libvulkan_broadcom.so %{_datadir}/vulkan/icd.d/broadcom_icd.*.json %{_libdir}/libvulkan_freedreno.so %{_datadir}/vulkan/icd.d/freedreno_icd.*.json %{_libdir}/libvulkan_panfrost.so %{_datadir}/vulkan/icd.d/panfrost_icd.*.json -%{_libdir}/libpowervr_rogue.so %{_libdir}/libvulkan_powervr_mesa.so %{_datadir}/vulkan/icd.d/powervr_mesa_icd.*.json %endif %endif +%endif %changelog +* Tue Jun 16 2026 Jocelyn Falempe - 26.1.1-1 + Resolves: https://redhat.atlassian.net/browse/RHEL-135263 + * Mon Feb 23 2026 José Expósito - 25.2.7-4 - Fix s390x regressions Resolves: https://issues.redhat.com/browse/RHEL-151315 diff --git a/sources b/sources index f875bf2..6292a58 100644 --- a/sources +++ b/sources @@ -1,11 +1,11 @@ -SHA512 (libclc-21.1.3.src.tar.xz) = daf38b915bcc8065c56a581ccbca90ccc0d41a54489392a2ae6ec3e0a8782ea231bc6edb9624cd879d698074017ee3c1d2b038e388cb75ea08a80ee3e27a57d1 -SHA512 (mesa-25.2.7.tar.xz) = 87dd815e0d11d6ec0eb969ee93d3f376103bb899d90599e0b7902394e41c58139384df79f89633e132ca969348d3320f55308a74651d409b454d51f1bcda27bc +SHA512 (llvm-project-22.1.1.src.tar.xz) = dddf09651c0e77caa83284788765016b023a9e239cfe35820bab7be64b68218e86bcf39bb07ee14dcddf7b0974b551344d2bff0e109cc9458b0394a3c940917c +SHA512 (mesa-26.1.1.tar.xz) = 384a7db33d2f06e287337329f2f17503082ba6b1cd876a1884e940fdb6193ffae6d6b14344625256f3b3b39972ad697130a4d2ea25c40979f7aa76c2176f7988 SHA512 (meson-1.7.0.tar.gz) = a5d1f00b193ca37ae64f85c9dfc29a2661c167d82d9953b9acd1393b222b05fa5fc03ffdf00fd1ae7a2014da3a7366c35f70bf02e3204e929b74f7b00c17c840 SHA512 (paste-1.0.15.tar.gz) = 5026d3ec7141ec4e2517a0b1283912d0801e9356f77b703d954b379439b8d85e3886d42fb28f7835edaeeac465582da14233564fb010c71425a59c9e1cbd46b4 -SHA512 (proc-macro2-1.0.101.tar.gz) = 3171c807d24371da2931f9c706fb3129bb9bf3ac40418e5d14cfc372baf96e5fee9ede72091163858e3ba0b4f88594efa1031b0bb7128ca68e7b847dead6856c -SHA512 (quote-1.0.40.tar.gz) = 45a76e22a2b0bec47e4ba73c3b73cc41d821dfcce9876134c5d8eed514da214aee4ce7612e372c8709f888c0d8b9b7e5442f27adb7a59f3571f0339ed7e2ac99 +SHA512 (proc-macro2-1.0.106.tar.gz) = b726e2c92af434bfa88cd4f53c3fe6db647503567675fb439890dee3d15f5111137e3242b28d164114ce081c10acf3fd11950753ddb349190c87ee04e7d97744 +SHA512 (quote-1.0.44.tar.gz) = 6c1e9b31e0c8bd13cd865e6846dc243d88f2c057adeb8e674117bdcb46947219a6a352a6a50be1132c483f55331e6556275ac514513dbf017825c64e5d96010d SHA512 (rustc-hash-2.1.1.tar.gz) = 87097d98d47f327d000041ab13acddc366f1500d9c3e5c82169c3358112c7a7c03701c9b3c2c81d9f9da65b7ebac1c479b179dfaf7c059cd0b929b4673e51084 -SHA512 (spirv-llvm-translator-a37544f.tar.gz) = 9e790322beead5fd2934140e8f4870978a860fe03aaffd042ff74578b76f7f404dcd0dbaea1d6ee2a408b477e5de9819910a5b86ce4ba8a018e21b7d60ed5e97 -SHA512 (syn-2.0.106.tar.gz) = e07e1058770fa3f1039eaf335340cefb597c0dd11bb90fec9fa777ca5815d0e0bb1711bb4db52cac77e205dd68fbe2bce0e1aa9895c2a52a1ea6d7758d13424c -SHA512 (unicode-ident-1.0.18.tar.gz) = d11f89fb696f9e2953c96a40b5478832651b268c83b9c7a700b07e768e795d6e8dc346597d1226df21219d36866768d1f640bd8edb68db8bd3d5d437b2bfd324 +SHA512 (spirv-llvm-translator-114f5ee.tar.gz) = 5e983a5b4a7353448a5ccbf75ab37600e9f2157a1bcc6920e79cd762ffb4d07985ee54d9809f7f97b4495d44d89ccd66bebb67325569e9eef63014230c44bf56 +SHA512 (syn-2.0.115.tar.gz) = 47afe5cd05da90d116e35259b4e3c03236e719ef8f62fbf1d32e4e7e78b454571f9e88e77e86665e815d5dcfe065c75fff987f57ba66942277f8f16fb680ead3 +SHA512 (unicode-ident-1.0.23.tar.gz) = f2e57950b87ab93456a74788c22b8f865fe8864147104507ed40cc87979c0a06007eec17c2c2241cb586b5e9600e5b518059611fde6325c7a3a79265751f8fe0 SHA512 (wayland-protocols-1.41.tar.xz) = 6122fe4f20a1a0908abd631ff31302b56018050e5e835c1413d5b40a527980c30859ed9cddf595213f7d5eb1d50baaf6adc312cef9279d60b8a15e447e259863