From e47d3ddb8f262a37c53715f52abaa32d11d237c8 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 2 Mar 2026 18:26:20 +0100 Subject: [PATCH 01/45] vkconfig: Fix command line whitespace decoding --- vkconfig_core/executable_manager.cpp | 6 +- vkconfig_core/test/test_command_line.cpp | 25 +++++++ vkconfig_core/test/test_util.cpp | 90 +++++++++++++++++++++++- vkconfig_core/util.cpp | 29 ++++++++ vkconfig_core/util.h | 4 +- vkconfig_gui/CHANGELOG.md | 5 +- vkconfig_gui/tab_applications.cpp | 2 +- 7 files changed, 156 insertions(+), 5 deletions(-) diff --git a/vkconfig_core/executable_manager.cpp b/vkconfig_core/executable_manager.cpp index c56584bb72..25612e2e4f 100644 --- a/vkconfig_core/executable_manager.cpp +++ b/vkconfig_core/executable_manager.cpp @@ -195,10 +195,14 @@ bool ExecutableManager::Load(const QJsonObject& json_root_object, ConfiguratorMo executable_options.label = json_options_object.value("label").toString().toStdString(); executable_options.working_folder = json_options_object.value("working_folder").toString().toStdString(); + std::vector args; const QJsonArray& json_command_lines_array = json_options_object.value("arguments").toArray(); for (int k = 0, p = json_command_lines_array.size(); k < p; ++k) { - executable_options.args.push_back(json_command_lines_array[k].toString().toStdString()); + args.push_back(json_command_lines_array[k].toString().toStdString()); } + // Workaround to resolved badly stored arguments when we were using SplitSpace instead of SplitArgs to fill the + // executable arguments option in the UI + executable_options.args = SplitArgs(Merge(args, " ")); const QJsonArray& json_environment_variables_array = json_options_object.value("environment_variables").toArray(); for (int k = 0, p = json_environment_variables_array.size(); k < p; ++k) { diff --git a/vkconfig_core/test/test_command_line.cpp b/vkconfig_core/test/test_command_line.cpp index 9757d3035a..10511b5544 100644 --- a/vkconfig_core/test/test_command_line.cpp +++ b/vkconfig_core/test/test_command_line.cpp @@ -876,6 +876,31 @@ TEST(test_command_line, usage_mode_settings_html_default_implicit_and_output) { EXPECT_EQ(HELP_DEFAULT, command_line.help); } +TEST(test_command_line, usage_mode_settings_html_default_implicit_and_output_whitespace) { + static char* argv[] = {(char*)"vkconfig", (char*)"settings", // settings + (char*)"--generate", (char*)"html", // generate + (char*)"--output", (char*)"\"./my directory/my settings.html\""}; + int argc = static_cast(std::size(argv)); + + CommandLine command_line(argc, argv); + + EXPECT_EQ(COMMAND_SETTINGS, command_line.command); + EXPECT_EQ(COMMAND_RESET_SOFT, command_line.command_reset_arg); + EXPECT_EQ(COMMAND_LAYERS_NONE, command_line.command_layers_arg); + EXPECT_EQ(COMMAND_LOADER_NONE, command_line.command_loader_arg); + EXPECT_EQ(COMMAND_DOC_NONE, command_line.command_doc_arg); + EXPECT_EQ(GENERATE_SETTINGS_HTML, command_line.generate_settings_mode); + EXPECT_TRUE(command_line.selected_layers_name.empty()); + EXPECT_STREQ("default", command_line.selected_configuration_name.c_str()); + EXPECT_TRUE(command_line.GetInputPath().Empty()); + EXPECT_STREQ("\"./my directory/my settings.html\"", + ConvertStandardSeparators(command_line.GetOutputPath().RelativePath()).c_str()); + EXPECT_EQ(false, command_line.dry_run); + EXPECT_EQ(ERROR_NONE, command_line.error); + EXPECT_TRUE(command_line.error_args.empty()); + EXPECT_EQ(HELP_DEFAULT, command_line.help); +} + TEST(test_command_line, usage_mode_settings_html_default_explicit_and_output) { static char* argv[] = {(char*)"vkconfig", (char*)"settings", // settings (char*)"--generate", (char*)"html", // generate diff --git a/vkconfig_core/test/test_util.cpp b/vkconfig_core/test/test_util.cpp index 55c179f741..60d18f1934 100644 --- a/vkconfig_core/test/test_util.cpp +++ b/vkconfig_core/test/test_util.cpp @@ -135,7 +135,7 @@ TEST(test_util, trim_surrounding_whitepace) { std::vector strings; strings.push_back("-o profile.json"); strings.push_back(" -o profile.json "); - strings.push_back("\"-o profile.json\""); + strings.push_back("\t-o profile.json\n"); for (std::size_t i = 0, n = strings.size(); i < n; ++i) { std::string trimed_string = TrimSurroundingWhitespace(strings[i]); @@ -144,6 +144,94 @@ TEST(test_util, trim_surrounding_whitepace) { } } +TEST(test_util, split_args_single) { + const std::string source_arg = "--argA"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(1, split_arg.size()); + EXPECT_STREQ("--argA", split_arg[0].c_str()); +} + +TEST(test_util, split_args_single_with_parem) { + const std::string source_arg = "--argA value-A"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(1, split_arg.size()); + EXPECT_STREQ("--argA value-A", split_arg[0].c_str()); +} + +TEST(test_util, split_args_single_no_quote) { + const std::string source_arg = "--argA value A"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(1, split_arg.size()); + EXPECT_STREQ("--argA value A", split_arg[0].c_str()); +} + +TEST(test_util, split_args_multiple_no_quote) { + const std::string source_arg = "--argA value A --argB"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(2, split_arg.size()); + EXPECT_STREQ("--argA value A", split_arg[0].c_str()); + EXPECT_STREQ("--argB", split_arg[1].c_str()); +} + +TEST(test_util, split_args_single_with_quote_with_space) { + const std::string source_arg = "--argA \"value A\""; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(1, split_arg.size()); + EXPECT_STREQ("--argA \"value A\"", split_arg[0].c_str()); +} + +TEST(test_util, split_args_single_with_quote_without_space) { + const std::string source_arg = "--argA \"value-A\""; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(1, split_arg.size()); + EXPECT_STREQ("--argA \"value-A\"", split_arg[0].c_str()); +} + +TEST(test_util, split_args_single_with_quote_and_extra) { + const std::string source_arg = "--argA \"value-A\" --argB"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(2, split_arg.size()); + EXPECT_STREQ("--argA \"value-A\"", split_arg[0].c_str()); + EXPECT_STREQ("--argB", split_arg[1].c_str()); +} + +TEST(test_util, split_args_single_with_command_quote_without_space) { + const std::string source_arg = "command --argA \"value-A\""; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(2, split_arg.size()); + EXPECT_STREQ("command", split_arg[0].c_str()); + EXPECT_STREQ("--argA \"value-A\"", split_arg[1].c_str()); +} + +TEST(test_util, split_args_single_with_command_double_quote_without_space) { + const std::string source_arg = "command area --argA \"value-A\""; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(3, split_arg.size()); + EXPECT_STREQ("command", split_arg[0].c_str()); + EXPECT_STREQ("area", split_arg[1].c_str()); + EXPECT_STREQ("--argA \"value-A\"", split_arg[2].c_str()); +} + +TEST(test_util, split_args_multiple) { + const std::string source_arg = "--argA --argB valueB --argC \"value C\" --argD value D"; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(4, split_arg.size()); + EXPECT_STREQ("--argA", split_arg[0].c_str()); + EXPECT_STREQ("--argB valueB", split_arg[1].c_str()); + EXPECT_STREQ("--argC \"value C\"", split_arg[2].c_str()); + EXPECT_STREQ("--argD value D", split_arg[3].c_str()); +} + +TEST(test_util, split_args_multiple_mix) { + const std::string source_arg = "cargo run --example solari --features=\"bevy_solari https free_camera\""; + const std::vector split_arg = SplitArgs(source_arg); + EXPECT_EQ(4, split_arg.size()); + EXPECT_STREQ("cargo", split_arg[0].c_str()); + EXPECT_STREQ("run", split_arg[1].c_str()); + EXPECT_STREQ("--example solari", split_arg[2].c_str()); + EXPECT_STREQ("--features=\"bevy_solari https free_camera\"", split_arg[3].c_str()); +} + TEST(test_util, split_arg_default) { const std::string source_arg = "--argA --argB=valueB \"--argC=value C\" --argD=\"value D\""; const std::vector split_arg = SplitSpace(source_arg); diff --git a/vkconfig_core/util.cpp b/vkconfig_core/util.cpp index 1f464d59fd..ae9116bfb5 100644 --- a/vkconfig_core/util.cpp +++ b/vkconfig_core/util.cpp @@ -214,6 +214,35 @@ std::vector SplitSpace(const std::string& parse) { return split_result; } +std::vector SplitArgs(const std::string& value) { + std::vector space_splitted = SplitSpace(value); + + std::vector split_result; + + bool previous_value_had_dash = false; + for (std::size_t i = 0, n = space_splitted.size(); i < n; ++i) { + std::string value = space_splitted[i]; + if (value.empty()) { + continue; + } + + if (split_result.empty()) { + split_result.push_back(value); + previous_value_had_dash = value[0] == '-'; + continue; + } + + if (previous_value_had_dash && value[0] != '-') { + split_result[split_result.size() - 1] += " " + value; + } else { + split_result.push_back(value); + previous_value_had_dash = value[0] == '-'; + } + } + + return split_result; +} + std::string Merge(const std::vector& value, const std::string& delimiter) { std::string result; diff --git a/vkconfig_core/util.h b/vkconfig_core/util.h index 69f69ef068..d7b986a78e 100644 --- a/vkconfig_core/util.h +++ b/vkconfig_core/util.h @@ -65,12 +65,14 @@ bool IsStringFound(const std::vector& list, const std::string& valu std::string TrimString(const std::string& str, const std::string& whitespaces = " \t"); -std::string TrimSurroundingWhitespace(const std::string& str, const std::string& whitespaces = " \"\t\n\r"); +std::string TrimSurroundingWhitespace(const std::string& str, const std::string& whitespaces = " \t\n\r"); std::vector Split(const std::string& value, const std::string& delimiter); std::vector SplitSpace(const std::string& value); +std::vector SplitArgs(const std::string& value); + std::string Merge(const std::vector& value, const std::string& delimiter); std::vector ConvertString(const QStringList& string_list); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 954becc862..c6017a1b43 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -5,6 +5,9 @@ - Improve generated layer settings C++ library to output to code for `vulkan.hpp` - Clean up generated layer settings C++ library for less friction on use +### Fixes: +- Fix command line whitespace decoding #2625 + ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) @@ -25,7 +28,7 @@ - Add `settings --layers` command lines option to list multiple layer names when generating files - Add Validation and API Dump default configuration for interleaved log in stdout - Add diagnostic log refresh button -- Add layer settings reset to default values button +- Add layer settings reset to default values button #2573 - Improved UI to set an external `vk_layer_settings.txt` file - Improved "View Enabled Layers" and "Configure All Layers" UI - Add buttons to open Vulkan SDK directories in diagnostics tab #2585 diff --git a/vkconfig_gui/tab_applications.cpp b/vkconfig_gui/tab_applications.cpp index dcab608014..72efe225dc 100644 --- a/vkconfig_gui/tab_applications.cpp +++ b/vkconfig_gui/tab_applications.cpp @@ -418,7 +418,7 @@ void TabApplications::on_launch_options_args_textEdited(const QString &text) { Executable *executable = configurator.executables.GetActiveExecutable(); ExecutableOptions *options = executable->GetActiveOptions(); - options->args = SplitSpace(text.toStdString()); + options->args = SplitArgs(text.toStdString()); } void TabApplications::on_launch_options_envs_textEdited(const QString &text) { From 5ca2588944252d95b9ef8230de4143bf20e37235 Mon Sep 17 00:00:00 2001 From: Christophe Date: Mon, 9 Mar 2026 18:20:17 +0100 Subject: [PATCH 02/45] vkconfig: Clarify UI for use without layers overriding - Clarify UI to clarify what is overridden by Vulkan Configurator: Layers, Devices, Loader log - Redesign system tray: - Add selecting Vulkan Layers configuration from system tray - Add selecting Vullan physical device from system tray - Add enabling Vulkan Loader log from system tray - Add preferences settings to show "Vulkan Executables Scope" --- vkconfig_core/configurator.cpp | 41 +- vkconfig_core/configurator.h | 2 + .../generate_layers_settings_file.cpp | 2 +- .../VK_LAYER_LUNARG_reference_1_2_1.bat | 2 +- .../VK_LAYER_LUNARG_reference_1_2_1.sh | 2 +- .../VK_LAYER_LUNARG_reference_1_2_1.txt | 2 +- ...LAYER_LUNARG_reference_1_2_1.vulkan_h_code | 2 +- ...YER_LUNARG_reference_1_2_1.vulkan_hpp_code | 2 +- .../test/generated/vk_layer_settings.bat | 2 +- .../test/generated/vk_layer_settings.sh | 2 +- .../test/generated/vk_layer_settings.txt | 2 +- .../generated/vulkan_hpp_layer_settings.code | 2 +- .../test/generated/vulkan_layer_settings.code | 2 +- vkconfig_core/type_executable_mode.cpp | 17 +- vkconfig_core/type_executable_mode.h | 5 +- vkconfig_core/type_tab.cpp | 4 +- vkconfig_core/type_tab.h | 2 +- vkconfig_core/version.cpp | 2 +- vkconfig_gui/CHANGELOG.md | 9 +- vkconfig_gui/mainwindow.cpp | 227 ++-- vkconfig_gui/mainwindow.h | 25 +- vkconfig_gui/mainwindow.ui | 1072 +++++++++-------- vkconfig_gui/tab_configurations.cpp | 28 +- vkconfig_gui/tab_configurations.h | 1 + vkconfig_gui/tab_diagnostics.cpp | 6 +- vkconfig_gui/tab_drivers.cpp | 4 + vkconfig_gui/tab_layers.cpp | 5 +- vkconfig_gui/tab_preferences.cpp | 10 + vkconfig_gui/tab_preferences.h | 1 + vkconfig_gui/widget_setting_flags.cpp | 4 +- 30 files changed, 844 insertions(+), 643 deletions(-) diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index ac3713e0f6..e26418a10c 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -442,7 +442,7 @@ bool Configurator::WriteLoaderSettings(OverrideArea override_area, const Path& l break; } default: - case EXECUTABLE_NONE: + assert(0); break; } @@ -1089,14 +1089,17 @@ bool Configurator::Load() { // TAB_CONFIGURATIONS if (json_interface_object.value(GetToken(TAB_CONFIGURATIONS)) != QJsonValue::Undefined) { const QJsonObject& json_object = json_interface_object.value(GetToken(TAB_CONFIGURATIONS)).toObject(); + if (json_object.value("override_layers") != QJsonValue::Undefined) { + this->layers_override_enabled = json_object.value("override_layers").toBool(); + } this->advanced = json_object.value("advanced").toBool(); this->executable_scope = ::GetExecutableScope(json_object.value("executable_scope").toString().toStdString().c_str()); this->selected_global_configuration = json_object.value("selected_global_configuration").toString().toStdString(); } - // TAB_LAYERS - if (json_interface_object.value(GetToken(TAB_LAYERS)) != QJsonValue::Undefined) { - const QJsonObject& json_object = json_interface_object.value(GetToken(TAB_LAYERS)).toObject(); + // TAB_LAYERS_PATHS + if (json_interface_object.value(GetToken(TAB_LAYERS_PATHS)) != QJsonValue::Undefined) { + const QJsonObject& json_object = json_interface_object.value(GetToken(TAB_LAYERS_PATHS)).toObject(); (void)json_object; } @@ -1224,10 +1227,17 @@ bool Configurator::Load() { ::SetHomePath(json_object.value("VULKAN_HOME").toString().toStdString()); } + if (json_object.value("layers_show_scope") != QJsonValue::Undefined) { + this->configuration_show_scope = json_object.value("layers_show_scope").toBool(); + } else { + this->configuration_show_scope = this->executable_scope != EXECUTABLE_ANY; + } + if (json_object.value("all_enabled_executables_behavior") != QJsonValue::Undefined) { this->executable_behavior = ::GetExecutableAllEnabledBehavior( json_object.value("all_enabled_executables_behavior").toString().toStdString().c_str()); } + if (json_object.value("VULKAN_DOWNLOAD") != QJsonValue::Undefined) { ::SetDownloadPath(json_object.value("VULKAN_DOWNLOAD").toString().toStdString()); } @@ -1277,17 +1287,15 @@ bool Configurator::Save() const { // TAB_CONFIGURATIONS { QJsonObject json_object; + json_object.insert("override_layers", this->layers_override_enabled); json_object.insert("advanced", this->advanced); json_object.insert("executable_scope", ::GetToken(this->executable_scope)); json_object.insert("selected_global_configuration", this->selected_global_configuration.c_str()); json_interface_object.insert(::GetToken(TAB_CONFIGURATIONS), json_object); } - // TAB_LAYERS - { - QJsonObject json_object; - json_interface_object.insert(GetToken(TAB_LAYERS), json_object); - } + // TAB_LAYERS_PATHS + {} // TAB_DRIVER { @@ -1368,6 +1376,7 @@ bool Configurator::Save() const { json_object.insert("show_external_layers_settings", this->show_external_layers_settings); json_object.insert("VULKAN_HOME", ::Path(Path::HOME).RelativePath().c_str()); json_object.insert("VULKAN_DOWNLOAD", ::Path(Path::DOWNLOAD).RelativePath().c_str()); + json_object.insert("layers_show_scope", this->configuration_show_scope); json_object.insert("all_enabled_executables_behavior", ::GetToken(this->executable_behavior)); json_interface_object.insert(GetToken(TAB_PREFERENCES), json_object); @@ -1449,9 +1458,13 @@ bool Configurator::ShouldNotify() const { } bool Configurator::HasActiveSettings() const { + if (!this->layers_override_enabled) { + return false; + } + switch (this->executable_scope) { default: - case EXECUTABLE_NONE: + assert(0); return false; case EXECUTABLE_ALL: case EXECUTABLE_PER: @@ -1483,18 +1496,20 @@ bool Configurator::HasActiveSettings() const { } bool Configurator::HasEnabledUI(EnabledUI enabled_ui) const { + if (!this->layers_override_enabled) { + return false; + } + switch (enabled_ui) { default: assert(false); return false; case ENABLE_UI_CONFIG: { switch (this->GetExecutableScope()) { - default: - case EXECUTABLE_NONE: - return false; case EXECUTABLE_ALL: case EXECUTABLE_PER: return this->executables.GetActiveExecutable() != nullptr; + default: case EXECUTABLE_ANY: return true; } diff --git a/vkconfig_core/configurator.h b/vkconfig_core/configurator.h index 6745e5c71a..4a5413f827 100644 --- a/vkconfig_core/configurator.h +++ b/vkconfig_core/configurator.h @@ -177,6 +177,8 @@ class Configurator { Version latest_sdk_version = Version::NONE; Version current_sdk_version = Version::VKHEADER; + bool configuration_show_scope = false; + bool layers_override_enabled = true; bool driver_override_enabled = false; DriverMode driver_override_mode = DRIVER_MODE_SINGLE; DeviceInfo driver_override_info; diff --git a/vkconfig_core/generate_layers_settings_file.cpp b/vkconfig_core/generate_layers_settings_file.cpp index 8f0ad434e4..c43c1524fd 100644 --- a/vkconfig_core/generate_layers_settings_file.cpp +++ b/vkconfig_core/generate_layers_settings_file.cpp @@ -57,7 +57,7 @@ bool GenerateSettingsTXT(Configurator& configurator, OverrideArea override_area, break; } default: - case EXECUTABLE_NONE: + assert(0); break; } diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat index 70c489ce94..af20db3d03 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat @@ -13,7 +13,7 @@ :: See the License for the specific language governing permissions and :: limitations under the License. :: -:: This code was generated by Vulkan Configurator 3.4.3 +:: This code was generated by Vulkan Configurator 3.5.0 :: reference layer diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh index 31f1e2eb9e..a724062d59 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh @@ -13,7 +13,7 @@ #! See the License for the specific language governing permissions and #! limitations under the License. #! -#! This code was generated by Vulkan Configurator 3.4.3 +#! This code was generated by Vulkan Configurator 3.5.0 #! reference layer diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.txt b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.txt index 1a0bc53b63..776c4b26d1 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.txt +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.txt @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# This code was generated by Vulkan Configurator 3.4.3 +# This code was generated by Vulkan Configurator 3.5.0 # reference layer # ========================================== diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code index 9a484c8a57..9bb12fc903 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * This code was generated by Vulkan Configurator 3.4.3 + * This code was generated by Vulkan Configurator 3.5.0 */ #pragma once diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code index fe9ef76574..f7ef21cc06 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * This code was generated by Vulkan Configurator 3.4.3 + * This code was generated by Vulkan Configurator 3.5.0 */ #pragma once diff --git a/vkconfig_core/test/generated/vk_layer_settings.bat b/vkconfig_core/test/generated/vk_layer_settings.bat index 70c489ce94..af20db3d03 100644 --- a/vkconfig_core/test/generated/vk_layer_settings.bat +++ b/vkconfig_core/test/generated/vk_layer_settings.bat @@ -13,7 +13,7 @@ :: See the License for the specific language governing permissions and :: limitations under the License. :: -:: This code was generated by Vulkan Configurator 3.4.3 +:: This code was generated by Vulkan Configurator 3.5.0 :: reference layer diff --git a/vkconfig_core/test/generated/vk_layer_settings.sh b/vkconfig_core/test/generated/vk_layer_settings.sh index 31f1e2eb9e..a724062d59 100644 --- a/vkconfig_core/test/generated/vk_layer_settings.sh +++ b/vkconfig_core/test/generated/vk_layer_settings.sh @@ -13,7 +13,7 @@ #! See the License for the specific language governing permissions and #! limitations under the License. #! -#! This code was generated by Vulkan Configurator 3.4.3 +#! This code was generated by Vulkan Configurator 3.5.0 #! reference layer diff --git a/vkconfig_core/test/generated/vk_layer_settings.txt b/vkconfig_core/test/generated/vk_layer_settings.txt index 1a0bc53b63..776c4b26d1 100644 --- a/vkconfig_core/test/generated/vk_layer_settings.txt +++ b/vkconfig_core/test/generated/vk_layer_settings.txt @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# This code was generated by Vulkan Configurator 3.4.3 +# This code was generated by Vulkan Configurator 3.5.0 # reference layer # ========================================== diff --git a/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code b/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code index fe9ef76574..f7ef21cc06 100644 --- a/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code +++ b/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * This code was generated by Vulkan Configurator 3.4.3 + * This code was generated by Vulkan Configurator 3.5.0 */ #pragma once diff --git a/vkconfig_core/test/generated/vulkan_layer_settings.code b/vkconfig_core/test/generated/vulkan_layer_settings.code index 9a484c8a57..9bb12fc903 100644 --- a/vkconfig_core/test/generated/vulkan_layer_settings.code +++ b/vkconfig_core/test/generated/vulkan_layer_settings.code @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * This code was generated by Vulkan Configurator 3.4.3 + * This code was generated by Vulkan Configurator 3.5.0 */ #pragma once diff --git a/vkconfig_core/type_executable_mode.cpp b/vkconfig_core/type_executable_mode.cpp index 63cfcf2cd5..ed876c9206 100644 --- a/vkconfig_core/type_executable_mode.cpp +++ b/vkconfig_core/type_executable_mode.cpp @@ -25,7 +25,6 @@ const char* GetLabel(ExecutableScope scope) { static const char* TABLE[]{ - "No Vulkan Executable", // EXECUTABLE_NONE "Any Running Vulkan Executable", // EXECUTABLE_ANY "All Enabled Vulkan Executables", // EXECUTABLE_ALL "Per Vulkan Working Directory", // EXECUTABLE_PER @@ -39,10 +38,9 @@ const char* GetLabel(ExecutableScope scope) { const char* GetTooltip(ExecutableScope scope) { static const char* TABLE[]{ - "Vulkan Configurator won't affect the Vulkan layers for any executable running on the system.", // EXECUTABLE_NONE - "Apply the active layers configuration to any executable running.", // EXECUTABLE_ANY - "Apply the active layers configuration to all enabled executables.", // EXECUTABLE_ALL - "Apply a dedicated layers configuration for each Vulkan executable.", // EXECUTABLE_PER + "Apply the active layers configuration to any executable running.", // EXECUTABLE_ANY + "Apply the active layers configuration to all enabled executables.", // EXECUTABLE_ALL + "Apply a dedicated layers configuration for each Vulkan executable.", // EXECUTABLE_PER }; static_assert(std::size(TABLE) == EXECUTABLE_SCOPE_COUNT, @@ -53,10 +51,9 @@ const char* GetTooltip(ExecutableScope scope) { const char* GetToken(ExecutableScope scope) { static const char* TABLE[]{ - "NONE", // EXECUTABLE_NONE - "ANY", // EXECUTABLE_ANY - "ALL", // EXECUTABLE_ALL - "PER", // EXECUTABLE_PER + "ANY", // EXECUTABLE_ANY + "ALL", // EXECUTABLE_ALL + "PER", // EXECUTABLE_PER }; static_assert(std::size(TABLE) == EXECUTABLE_SCOPE_COUNT, @@ -73,7 +70,7 @@ ExecutableScope GetExecutableScope(const char* token) { } } - return EXECUTABLE_NONE; + return EXECUTABLE_ANY; } bool EnabledExecutables(ExecutableScope scope) { return scope == EXECUTABLE_ALL || scope == EXECUTABLE_PER; } diff --git a/vkconfig_core/type_executable_mode.h b/vkconfig_core/type_executable_mode.h index eebe4f89aa..027dd1a03f 100644 --- a/vkconfig_core/type_executable_mode.h +++ b/vkconfig_core/type_executable_mode.h @@ -21,12 +21,11 @@ #pragma once enum ExecutableScope { // Enum value can't be changed - EXECUTABLE_NONE = 0, - EXECUTABLE_ANY, + EXECUTABLE_ANY = 0, EXECUTABLE_ALL, EXECUTABLE_PER, - EXECUTABLE_SCOPE_FIRST = EXECUTABLE_NONE, + EXECUTABLE_SCOPE_FIRST = EXECUTABLE_ANY, EXECUTABLE_SCOPE_LAST = EXECUTABLE_PER }; diff --git a/vkconfig_core/type_tab.cpp b/vkconfig_core/type_tab.cpp index 84913f046b..9667a7cd8f 100644 --- a/vkconfig_core/type_tab.cpp +++ b/vkconfig_core/type_tab.cpp @@ -26,7 +26,7 @@ const char* GetLabel(TabType type) { static const char* TOKENS[]{ "Vulkan Layers", // TAB_CONFIGURATIONS - "Vulkan Layers Paths", // TAB_LAYERS + "Vulkan Layers Paths", // TAB_LAYERS_PATHS "Vulkan Drivers", // TAB_DRIVERS "Application Launcher", // TAB_APPLICATIONS "Diagnostics", // TAB_DIAGNOSTIC @@ -43,7 +43,7 @@ const char* GetLabel(TabType type) { const char* GetToken(TabType type) { static const char* TOKENS[]{ "CONFIGURATIONS", // TAB_CONFIGURATIONS - "LAYERS", // TAB_LAYERS + "LAYERS", // TAB_LAYERS_PATHS "DRIVERS", // TAB_DRIVERS "APPLICATIONS", // TAB_APPLICATIONS "DIAGNOSTIC", // TAB_DIAGNOSTIC diff --git a/vkconfig_core/type_tab.h b/vkconfig_core/type_tab.h index 5384844df5..abfcfc177e 100644 --- a/vkconfig_core/type_tab.h +++ b/vkconfig_core/type_tab.h @@ -22,7 +22,7 @@ enum TabType { TAB_CONFIGURATIONS = 0, - TAB_LAYERS, + TAB_LAYERS_PATHS, TAB_DRIVERS, TAB_APPLICATIONS, TAB_DIAGNOSTIC, diff --git a/vkconfig_core/version.cpp b/vkconfig_core/version.cpp index f72ed2a375..4b825b1d6e 100644 --- a/vkconfig_core/version.cpp +++ b/vkconfig_core/version.cpp @@ -28,7 +28,7 @@ #include #include -const Version Version::VKCONFIG(3, 4, 3); +const Version Version::VKCONFIG(3, 5, 0); const Version Version::VKHEADER(VK_HEADER_VERSION_COMPLETE); const Version Version::NONE(0, 0, 0); const Version Version::LATEST(~0, ~0, ~0); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index c6017a1b43..8b2bf94cbb 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -1,9 +1,16 @@ -## Vulkan Configurator 3.4.3 - May 2026 +## Vulkan Configurator 3.5.0 - May 2026 [Vulkan SDK 1.4.3XX.0](https://github.com/LunarG/VulkanTools/tree/main) +### Features: +- Add selecting Vulkan Layers configuration from system tray +- Add selecting Vullan physical device from system tray +- Add enabling Vulkan Loader log from system tray + ### Improvements: +- Clarify UI to clarify what is overridden by Vulkan Configurator: Layers, Devices, Loader log - Improve generated layer settings C++ library to output to code for `vulkan.hpp` - Clean up generated layer settings C++ library for less friction on use +- Add preferences settings to show "Vulkan Executables Scope" ### Fixes: - Fix command line whitespace decoding #2625 diff --git a/vkconfig_gui/mainwindow.cpp b/vkconfig_gui/mainwindow.cpp index 78b691870b..106e164ab0 100644 --- a/vkconfig_gui/mainwindow.cpp +++ b/vkconfig_gui/mainwindow.cpp @@ -20,6 +20,7 @@ */ #include "mainwindow.h" +#include "style.h" #include "../vkconfig_core/configurator.h" #include "../vkconfig_core/util.h" @@ -40,23 +41,17 @@ #include #include #include +#include #include MainWindow::MainWindow(QApplication &app, QWidget *parent) - : QMainWindow(parent), - _tray_icon(nullptr), - _tray_icon_menu(nullptr), - _tray_restore_action(nullptr), - _tray_layers{nullptr, nullptr, nullptr, nullptr}, - _tray_quit_action(nullptr), - app(app), - ui(new Ui::MainWindow) { + : QMainWindow(parent), _tray_icon(nullptr), app(app), ui(new Ui::MainWindow) { ui->setupUi(this); this->tabs[TAB_DIAGNOSTIC].reset(new TabDiagnostics(*this, ui)); this->tabs[TAB_APPLICATIONS].reset(new TabApplications(*this, ui)); - this->tabs[TAB_LAYERS].reset(new TabLayers(*this, ui)); + this->tabs[TAB_LAYERS_PATHS].reset(new TabLayers(*this, ui)); this->tabs[TAB_CONFIGURATIONS].reset(new TabConfigurations(*this, ui)); this->tabs[TAB_DOCUMENTATION].reset(new TabDocumentation(*this, ui)); this->tabs[TAB_DRIVERS].reset(new TabDrivers(*this, ui)); @@ -78,7 +73,6 @@ MainWindow::MainWindow(QApplication &app, QWidget *parent) this->ui->tab_widget->setCurrentIndex(configurator.active_tab); - this->InitTray(); this->UpdateUI(UPDATE_REBUILD_UI); configurator.Override(OVERRIDE_AREA_ALL); @@ -103,66 +97,135 @@ void MainWindow::UpdateUI(UpdateUIMode mode) { this->UpdateUI_Status(); } -void MainWindow::InitTray() { +void MainWindow::UpdateUI_Status() { + const Configurator &configurator = Configurator::Get(); + + this->setWindowTitle(GetMainWindowTitle().c_str()); + if (QSystemTrayIcon::isSystemTrayAvailable()) { - this->_tray_quit_action = new QAction("&Quit", this); - this->connect(this->_tray_quit_action, &QAction::triggered, qApp, &QCoreApplication::quit); - this->_tray_restore_action = new QAction("Open &Vulkan Configurator", this); - this->connect(this->_tray_restore_action, &QAction::triggered, this, &MainWindow::trayActionRestore); - - this->_tray_icon_menu = new QMenu(this); - this->_tray_icon_menu->addAction(this->_tray_restore_action); - this->_tray_icon_menu->addSeparator(); - for (int i = 0, n = EXECUTABLE_SCOPE_COUNT; i < n; ++i) { - this->_tray_layers[i] = new QAction(::GetLabel(static_cast(i)), this); - this->_tray_layers[i]->setCheckable(true); - this->_tray_icon_menu->addAction(this->_tray_layers[i]); + const Configurator &configurator = Configurator::Get(); + + QMenu *menu = new QMenu(this); + + { + QAction *tray_restore_action = new QAction("&Show Vulkan Configurator UI", this); + tray_restore_action->setIcon(QIcon(":/resourcefiles/vkconfig-on.png")); + this->connect(tray_restore_action, &QAction::triggered, this, &MainWindow::OnTrayActionShow); + menu->addAction(tray_restore_action); + + QAction *tray_quit_action = new QAction("&Quit Vulkan Configurator", this); + tray_quit_action->setIcon(::Get(configurator.current_theme_mode, ::ICON_EXIT)); + this->connect(tray_quit_action, &QAction::triggered, qApp, &QCoreApplication::quit); + menu->addAction(tray_quit_action); } - this->connect(this->_tray_layers[EXECUTABLE_NONE], &QAction::toggled, this, &MainWindow::on_tray_none); - this->connect(this->_tray_layers[EXECUTABLE_ANY], &QAction::toggled, this, &MainWindow::on_tray_any); - this->connect(this->_tray_layers[EXECUTABLE_ALL], &QAction::toggled, this, &MainWindow::on_tray_all); - this->connect(this->_tray_layers[EXECUTABLE_PER], &QAction::toggled, this, &MainWindow::on_tray_per); + { + menu->addSeparator(); - this->_tray_icon_menu->addSeparator(); - this->_tray_icon_menu->addAction(this->_tray_quit_action); - this->_tray_icon = new QSystemTrayIcon(this); - this->_tray_icon->setContextMenu(this->_tray_icon_menu); - this->_tray_icon->show(); - this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::iconActivated); - } -} + const bool enabled_layers = configurator.layers_override_enabled && configurator.GetExecutableScope() != EXECUTABLE_PER; -void MainWindow::UpdateUI_Status() { - const Configurator &configurator = Configurator::Get(); + QAction *tray_override_layers = new QAction("Override System Vulkan &Layers Configurations:", this); + tray_override_layers->setCheckable(true); + tray_override_layers->setChecked(enabled_layers); - this->setWindowTitle(GetMainWindowTitle().c_str()); - if (QSystemTrayIcon::isSystemTrayAvailable()) { - for (int i = 0, n = EXECUTABLE_SCOPE_COUNT; i < n; ++i) { - this->_tray_layers[i]->blockSignals(true); - this->_tray_layers[i]->setChecked(configurator.GetExecutableScope() == i); - this->_tray_layers[i]->blockSignals(false); + this->connect(tray_override_layers, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLayers); + menu->addAction(tray_override_layers); + + QComboBox *widget = new QComboBox(menu); + int current_index = 0; + + for (std::size_t i = 0, n = configurator.configurations.available_configurations.size(); i < n; ++i) { + const Configuration &configuration = configurator.configurations.available_configurations[i]; + + widget->addItem(configuration.key.c_str()); + + if (configuration.key == configurator.GetSelectedGlobalConfiguration()) { + current_index = static_cast(i); + } + } + + widget->setCurrentIndex(current_index); + this->connect(widget, SIGNAL(currentIndexChanged(int)), this, SLOT(OnLayersChanged(int))); + + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(widget); + action->setEnabled(enabled_layers); + menu->addAction(action); } + + { + menu->addSeparator(); + + const bool enabled_physical_devices = + configurator.driver_override_enabled && configurator.driver_override_mode == DRIVER_MODE_SINGLE; + + QAction *tray_override_device = new QAction("Override System Vulkan &Physcial Devices:", this); + tray_override_device->setCheckable(true); + tray_override_device->setChecked(enabled_physical_devices); + + this->connect(tray_override_device, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideDevice); + menu->addAction(tray_override_device); + + QComboBox *widget = new QComboBox(menu); + int current_index = 0; + + for (std::size_t i = 0, n = configurator.vulkan_system_info.physicalDevices.size(); i < n; ++i) { + const VulkanPhysicalDeviceInfo &info = configurator.vulkan_system_info.physicalDevices[i]; + const DeviceInfo &device_info = ::GetDeviceInfo(info); + + widget->addItem(format("%s (%s)", info.GetLabel().c_str(), info.GetVersion().c_str()).c_str()); + + if (device_info == configurator.driver_override_info) { + current_index = static_cast(i); + } + } + + widget->setCurrentIndex(current_index); + this->connect(widget, SIGNAL(currentIndexChanged(int)), this, SLOT(OnDeviceChanged(int))); + + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(widget); + action->setEnabled(enabled_physical_devices); + menu->addAction(action); + } + + { + menu->addSeparator(); + + QAction *tray_override_loader_log = new QAction("Override System Vulkan Loader Log", this); + tray_override_loader_log->setCheckable(true); + tray_override_loader_log->setChecked(configurator.loader_log_enabled); + + this->connect(tray_override_loader_log, &QAction::toggled, this, &MainWindow::OnTrayActionOverrideLog); + menu->addAction(tray_override_loader_log); + } + + if (this->_tray_icon != nullptr) { + delete this->_tray_icon; + } + + this->_tray_icon = new QSystemTrayIcon(this); + this->_tray_icon->setContextMenu(menu); + this->_tray_icon->show(); + this->connect(this->_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::OnIconActivated); } - if (configurator.GetExecutableScope() != EXECUTABLE_NONE) { + if (configurator.layers_override_enabled || configurator.driver_override_enabled) { const QIcon icon(":/resourcefiles/vkconfig-on.png"); this->setWindowIcon(icon); - if (QSystemTrayIcon::isSystemTrayAvailable()) { + if (this->_tray_icon && QSystemTrayIcon::isSystemTrayAvailable()) { this->_tray_icon->setIcon(icon); - this->_tray_icon->setToolTip("Layers controlled by the Vulkan Configurator"); } } else { const QIcon icon(":/resourcefiles/vkconfig-off.png"); this->setWindowIcon(icon); - if (QSystemTrayIcon::isSystemTrayAvailable()) { + if (this->_tray_icon && QSystemTrayIcon::isSystemTrayAvailable()) { this->_tray_icon->setIcon(icon); - this->_tray_icon->setToolTip("Layers controlled by the Vulkan Applications"); } } } -void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { +void MainWindow::OnIconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { default: break; @@ -182,50 +245,56 @@ void MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason) { } } -void MainWindow::trayActionRestore() { +void MainWindow::OnTrayActionShow() { this->hide(); this->showNormal(); this->UpdateUI(UPDATE_REBUILD_UI); } -void MainWindow::on_tray_none(bool checked) { - if (checked) { - Configurator &configurator = Configurator::Get(); - configurator.SetExecutableScope(EXECUTABLE_NONE); - configurator.Override(OVERRIDE_AREA_ALL); +void MainWindow::OnTrayActionOverrideLayers(bool toggled) { + Configurator &configurator = Configurator::Get(); + configurator.layers_override_enabled = toggled; + configurator.Override(OVERRIDE_AREA_ALL); - this->UpdateUI(UPDATE_REBUILD_UI); - } + this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI); + this->UpdateUI(UPDATE_REBUILD_UI); } -void MainWindow::on_tray_any(bool checked) { - if (checked) { - Configurator &configurator = Configurator::Get(); - configurator.SetExecutableScope(EXECUTABLE_ANY); - configurator.Override(OVERRIDE_AREA_ALL); +void MainWindow::OnLayersChanged(int index) { + Configurator &configurator = Configurator::Get(); + configurator.SetActiveConfigurationName(configurator.configurations.available_configurations[index].key); + configurator.Override(OVERRIDE_AREA_ALL); - this->UpdateUI(UPDATE_REBUILD_UI); - } + this->tabs[TAB_CONFIGURATIONS]->UpdateUI(UPDATE_REBUILD_UI); + this->UpdateUI(UPDATE_REBUILD_UI); } -void MainWindow::on_tray_all(bool checked) { - if (checked) { - Configurator &configurator = Configurator::Get(); - configurator.SetExecutableScope(EXECUTABLE_ALL); - configurator.Override(OVERRIDE_AREA_ALL); +void MainWindow::OnTrayActionOverrideDevice(bool toggled) { + Configurator &configurator = Configurator::Get(); + configurator.driver_override_mode = DRIVER_MODE_SINGLE; + configurator.driver_override_enabled = toggled; + configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); - this->UpdateUI(UPDATE_REBUILD_UI); - } + this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI); + this->UpdateUI(UPDATE_REBUILD_UI); } -void MainWindow::on_tray_per(bool checked) { - if (checked) { - Configurator &configurator = Configurator::Get(); - configurator.SetExecutableScope(EXECUTABLE_PER); - configurator.Override(OVERRIDE_AREA_ALL); +void MainWindow::OnDeviceChanged(int index) { + Configurator &configurator = Configurator::Get(); + configurator.driver_override_info = ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[index]); + configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); - this->UpdateUI(UPDATE_REBUILD_UI); - } + this->tabs[TAB_DRIVERS]->UpdateUI(UPDATE_REBUILD_UI); + this->UpdateUI(UPDATE_REBUILD_UI); +} + +void MainWindow::OnTrayActionOverrideLog(bool toggled) { + Configurator &configurator = Configurator::Get(); + configurator.loader_log_enabled = toggled; + configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); + + this->tabs[TAB_DIAGNOSTIC]->UpdateUI(UPDATE_REBUILD_UI); + this->UpdateUI(UPDATE_REBUILD_UI); } /// The only thing we need to do here is clear the configuration if @@ -241,7 +310,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { if (!(configurator.Get(HIDE_MESSAGE_USE_SYSTEM_TRAY))) { std::string shut_down_state; - if (configurator.GetExecutableScope() != EXECUTABLE_NONE) { + if (configurator.layers_override_enabled) { shut_down_state = "Vulkan Layers override will remain in effect while Vulkan Configurator remain active in the system tray."; } else { diff --git a/vkconfig_gui/mainwindow.h b/vkconfig_gui/mainwindow.h index 5bca6d87c9..2c08ede66c 100644 --- a/vkconfig_gui/mainwindow.h +++ b/vkconfig_gui/mainwindow.h @@ -39,7 +39,7 @@ #include #include -#include +#include class MainWindow : public QMainWindow { Q_OBJECT @@ -57,33 +57,26 @@ class MainWindow : public QMainWindow { void changeEvent(QEvent *event) override; QSystemTrayIcon *_tray_icon = nullptr; - QMenu *_tray_icon_menu = nullptr; - QAction *_tray_restore_action = nullptr; - std::array _tray_layers; - QAction *_tray_quit_action = nullptr; - - private slots: - void trayActionRestore(); - void on_tray_none(bool checked); - void on_tray_any(bool checked); - void on_tray_all(bool checked); - void on_tray_per(bool checked); - - void iconActivated(QSystemTrayIcon::ActivationReason reason); public Q_SLOTS: void commitDataRequest(QSessionManager &manager); void on_tab_widget_currentChanged(int index); + void OnIconActivated(QSystemTrayIcon::ActivationReason reason); + void OnTrayActionShow(); + void OnTrayActionOverrideLayers(bool toggled); + void OnTrayActionOverrideDevice(bool toggled); + void OnTrayActionOverrideLog(bool toggled); + void OnLayersChanged(int index); + void OnDeviceChanged(int index); + void UpdateUI_Status(); private: MainWindow(const MainWindow &) = delete; MainWindow &operator=(const MainWindow &) = delete; - void InitTray(); - public: QApplication &app; diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 6d343c0eec..c87d06814e 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -91,7 +91,7 @@ QTabWidget::Rounded - 7 + 4 @@ -120,9 +120,22 @@ 0 - + + + + Arial + 10 + true + + + + Control the Vulkan Layers used by running Vulkan applications + - + Override System Vulkan Layers + + + true @@ -208,7 +221,10 @@ - Vulkan Layers Configuration Scope: + Vulkan Executables Scope: + + + false @@ -914,9 +930,22 @@ 5 - + + + + Arial + 10 + true + + - + System Vulkan Layers Paths + + + false + + + false @@ -953,6 +982,13 @@ 24 + + + Arial + 10 + false + + Check to validate the layer manifests wheen adding a user path. This may take some time... @@ -981,6 +1017,13 @@ 24 + + + Arial + 10 + false + + 24 @@ -1009,6 +1052,13 @@ 24 + + + Arial + 10 + false + + Enter a Vulkan layers directory to control them in Vulkan Configurator @@ -1034,6 +1084,13 @@ 24 + + + Arial + 10 + false + + Append a user-defined layers path @@ -1068,6 +1125,13 @@ 24 + + + Arial + 10 + false + + Reload all modified layer manifest @@ -1092,6 +1156,13 @@ 0 + + + Arial + 10 + false + + QFrame::NoFrame @@ -1160,8 +1231,11 @@ true + + Control Vulkan Physical Device used by all running Vulkan applications + - Override Vulkan Devices to all Vulkan applications: + Override System Vulkan Devices true @@ -2538,61 +2612,78 @@ 5 - - - - Arial - 10 - true - - - - false - - - Diagnostic Logs + + + 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 + + + + true + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + + Arial + 10 + true + + + + Produce Vulkan Loader Log to all running Vulkan applications + + + Override System Vulkan Loader Log + + + true + + + false + + + + 11 0 + + 0 + 0 - - - - 24 - 24 - + + + + Arial + 10 + false + - - - 24 - 24 - + + Log Errors preventing the correct execution of the Vulkan Loader + + + Errors + + + + Arial @@ -2600,38 +2691,33 @@ false - - Open Vulkan Caps Viewer + + Log Warnings which may result in unexpected behavior of the Vulkan Loader - - - - - :/resourcefiles/vulkancapsviewer.png:/resourcefiles/vulkancapsviewer.png + Warnings - - - - 0 - 0 - + + + + Arial + 10 + false + - - - 24 - 24 - + + Log Information relative to the expected Vulkan Loader execution - - - 24 - 24 - + + Information + + + + Arial @@ -2639,28 +2725,30 @@ false - - Export all diagnostic logs + + Log Verbose Information relative to the expected Vulkan Loader execution - + Debug - - - - 0 - 24 - + + + + Arial + 10 + false + - - - 16777215 - 24 - + + Additional Areas: + + + + Arial @@ -2668,65 +2756,16 @@ false - - QComboBox::AdjustToContents + + Enable a log area relative to the Vulkan Layers handling by the Vulkan Loader + + + Layers - - - Vulkan Development Status - - - - - VulkanInfo Summary - - - - - VulkanInfo Full Text - - - - - Vulkan Profile - - - - - Vulkan Loader Log - - - - - Vulkan Loader Configuration - - - - - Vulkan Layers Settings - - - - - Vulkan Loader Settings - - - - - - 0 - 24 - - - - - 16777215 - 24 - - + Arial @@ -2734,25 +2773,72 @@ false - - QComboBox::AdjustToContents + + Enable a log area relative to the Vulkan Drivers handling by the Vulkan Loader + + + Drivers - - - - 24 - 24 - + + + Qt::Horizontal - + - 24 - 24 + 40 + 20 + + + + + + + + + + 0 + 0 + + + + + Arial + 10 + true + + + + Vulkan SDK directories + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + Arial @@ -2761,12 +2847,12 @@ - + Open: - + 24 @@ -2787,7 +2873,7 @@ - Export the selected diagnostic log + Open Vulkan SDK installation directory @@ -2795,16 +2881,16 @@ - + - 0 + 24 24 - 16777215 + 24 24 @@ -2815,16 +2901,16 @@ false + + Open Vulkan SDK home directory + + + + - - - - 0 - 0 - - + 24 @@ -2837,22 +2923,70 @@ 24 + + + Arial + 10 + false + + - Clear the search text + Open Vulkan SDK system directory + + + + + + + + + + Arial + 10 + true + + + + false + + + Diagnostic Logs + + + + 10 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 0 + + + 0 + - - - - 0 - 0 - - + 24 @@ -2865,16 +2999,27 @@ 24 + + + Arial + 10 + false + + - Find Next (F3) + Open Vulkan Caps Viewer + + + :/resourcefiles/vulkancapsviewer.png:/resourcefiles/vulkancapsviewer.png + - + 0 @@ -2893,8 +3038,15 @@ 24 + + + Arial + 10 + false + + - Find Previous (Shift+F3) + Export all diagnostic logs @@ -2902,75 +3054,99 @@ - - - - 0 - 0 - - + - 24 + 0 24 - 24 + 16777215 24 - - Match case (Alt+C) + + + Arial + 10 + false + - - - - - true + + QComboBox::AdjustToContents + + + Vulkan Development Status + + + + + VulkanInfo Summary + + + + + VulkanInfo Full Text + + + + + Vulkan Profile + + + + + Vulkan Loader Log + + + + + Vulkan Loader Configuration + + + + + Vulkan Layers Settings + + + + + Vulkan Loader Settings + + - - - - 0 - 0 - - + - 24 + 0 24 - 24 + 16777215 24 - - Match whole word (Alt+W) - - - + + + Arial + 10 + false + - - true + + QComboBox::AdjustToContents - - - - 0 - 0 - - + 24 @@ -2983,104 +3159,6 @@ 24 - - Use Regular Expressions (Alt+R) - - - - - - true - - - - - - - - - - Consolas - 10 - false - - - - QFrame::NoFrame - - - 0 - - - Qt::ScrollBarAlwaysOn - - - QAbstractScrollArea::AdjustToContents - - - true - - - 10.000000000000000 - - - - - - - - - - 0 - - - - - true - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - Arial - 10 - true - - - - Enable Vulkan Loader Log to all Vulkan applications - - - true - - - false - - - - 11 - - - 0 - - - 0 - - - 0 - - - Arial @@ -3088,33 +3166,25 @@ false - - Log Errors preventing the correct execution of the Vulkan Loader - - Errors + - - - - Arial - 10 - false - - - - Log Warnings which may result in unexpected behavior of the Vulkan Loader + + + + 24 + 24 + - - Warnings + + + 24 + 24 + - - - - Arial @@ -3122,33 +3192,28 @@ false - - Log Information relative to the expected Vulkan Loader execution + + Export the selected diagnostic log - Information + - - - - Arial - 10 - false - - - - Log Verbose Information relative to the expected Vulkan Loader execution + + + + 0 + 24 + - - Debug + + + 16777215 + 24 + - - - - Arial @@ -3156,117 +3221,100 @@ false - - Additional Areas: - - - - - Arial - 10 - false - - - - Enable a log area relative to the Vulkan Layers handling by the Vulkan Loader - - - Layers + + + + 0 + 0 + - - - - - - - Arial - 10 - false - + + + 24 + 24 + - - Enable a log area relative to the Vulkan Drivers handling by the Vulkan Loader + + + 24 + 24 + + + + Clear the search text - Drivers + - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 20 + 24 + 24 - + + + 24 + 24 + + + + Find Next (F3) + + + + + - - - - - - - - 0 - 0 - - - - - Arial - 10 - true - - - - Vulkan SDK directories - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - + - + 0 0 - - - Arial - 10 - false - + + + 24 + 24 + + + + + 24 + 24 + + + + Find Previous (Shift+F3) - Open: + - + + + + 0 + 0 + + 24 @@ -3279,23 +3327,25 @@ 24 - - - Arial - 10 - false - - - Open Vulkan SDK installation directory + Match case (Alt+C) + + true + - + + + + 0 + 0 + + 24 @@ -3308,23 +3358,25 @@ 24 - - - Arial - 10 - false - - - Open Vulkan SDK home directory + Match whole word (Alt+W) + + true + - + + + + 0 + 0 + + 24 @@ -3337,25 +3389,50 @@ 24 - - - Arial - 10 - false - - - Open Vulkan SDK system directory + Use Regular Expressions (Alt+R) + + true + - - - + + + + + + Consolas + 10 + false + + + + QFrame::NoFrame + + + 0 + + + Qt::ScrollBarAlwaysOn + + + QAbstractScrollArea::AdjustToContents + + + true + + + 10.000000000000000 + + + + + @@ -3662,11 +3739,41 @@ + + + + + Arial + 10 + false + + + + Allows overriding Vulkan Layers only for selected Vulkan executable + + + Show "Vulkan Executables Scope" in Vulkan Layers tab + + + + + 0 + + + 0 + 0 + + + + + + + @@ -3720,37 +3827,6 @@ - - - - - - - - Arial - 10 - true - - - - Vulkan Configurator UI: - - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - @@ -4009,7 +4085,7 @@ - + diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index 63b177e4f8..d4eb1fe027 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -81,6 +81,9 @@ TabConfigurations::TabConfigurations(MainWindow &window, std::shared_ptrui->configurations_layers_list->installEventFilter(&window); this->ui->configurations_settings->installEventFilter(&window); + this->connect(this->ui->configurations_group_box_override, SIGNAL(toggled(bool)), this, + SLOT(on_configurations_override_toggled(bool))); + this->connect(this->ui->configurations_executable_scope, SIGNAL(currentIndexChanged(int)), this, SLOT(on_configurations_executable_scope_currentIndexChanged(int))); this->connect(this->ui->configurations_executable_list, SIGNAL(currentIndexChanged(int)), this, @@ -117,6 +120,11 @@ TabConfigurations::TabConfigurations(MainWindow &window, std::shared_ptrui->configurations_group_box_scope->blockSignals(true); + this->ui->configurations_group_box_scope->setVisible(configurator.configuration_show_scope || + configurator.GetExecutableScope() != EXECUTABLE_ANY); + this->ui->configurations_group_box_scope->blockSignals(true); + this->ui->configurations_executable_scope->blockSignals(true); this->ui->configurations_executable_scope->clear(); for (int i = 0, n = EXECUTABLE_SCOPE_COUNT; i < n; ++i) { @@ -347,6 +355,11 @@ void TabConfigurations::UpdateUI(UpdateUIMode ui_update_mode) { this->advanced_mode->setToolTip("Configure all Available Vulkan Layers"); } + this->ui->configurations_group_box_scope->blockSignals(true); + this->ui->configurations_group_box_scope->setVisible(configurator.configuration_show_scope); + this->ui->configurations_group_box_scope->setChecked(configurator.layers_override_enabled); + this->ui->configurations_group_box_scope->blockSignals(true); + this->ui->configurations_executable_scope->setToolTip(::GetTooltip(scope)); this->ui->configurations_executable_list->setEnabled(enabled_executable); this->ui->configurations_executable_append->setEnabled(enabled_executable); @@ -360,6 +373,8 @@ void TabConfigurations::UpdateUI(UpdateUIMode ui_update_mode) { this->ui->configurations_group_box_list->setChecked(executable->enabled); } this->ui->configurations_group_box_list->blockSignals(false); + + this->window.UpdateUI_Status(); } void TabConfigurations::CleanUI() { this->_settings_tree_manager.CleanupGUI(); } @@ -402,7 +417,7 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { return true; } - if (configurator.GetExecutableScope() == EXECUTABLE_NONE || !ui->configurations_list->isEnabled()) { + if (!configurator.layers_override_enabled || !ui->configurations_list->isEnabled()) { return true; } else if (target == this->ui->configurations_list) { QContextMenuEvent *right_click = dynamic_cast(event); @@ -1096,6 +1111,14 @@ void TabConfigurations::on_configurations_advanced_toggle_pressed() { this->UpdateUI(UPDATE_REBUILD_UI); } +void TabConfigurations::on_configurations_override_toggled(bool checked) { + Configurator &configurator = Configurator::Get(); + configurator.layers_override_enabled = checked; + configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); + + this->UpdateUI(UPDATE_REBUILD_UI); +} + void TabConfigurations::on_configurations_executable_scope_currentIndexChanged(int index) { Configurator &configurator = Configurator::Get(); @@ -1136,7 +1159,6 @@ void TabConfigurations::on_configurations_executable_scope_currentIndexChanged(i } this->UpdateUI(UPDATE_REFRESH_UI); - this->window.UpdateUI_Status(); } void TabConfigurations::on_configurations_executable_list_currentIndexChanged(int index) { @@ -1305,7 +1327,7 @@ void TabConfigurations::on_configurations_list_currentRowChanged(int currentRow) alert.setText(text.c_str()); alert.setIcon(QMessageBox::Warning); alert.setCheckBox(new QCheckBox("Do not show again.")); - alert.setInformativeText(format("Use the '%s' tab to add the missing layers.", GetLabel(TAB_LAYERS)).c_str()); + alert.setInformativeText(format("Use the '%s' tab to add the missing layers.", GetLabel(TAB_LAYERS_PATHS)).c_str()); alert.exec(); if (alert.checkBox()->isChecked()) { diff --git a/vkconfig_gui/tab_configurations.h b/vkconfig_gui/tab_configurations.h index 296aa6c615..9af22ad6ca 100644 --- a/vkconfig_gui/tab_configurations.h +++ b/vkconfig_gui/tab_configurations.h @@ -50,6 +50,7 @@ class TabConfigurations : public Tab { public Q_SLOTS: void on_configurations_advanced_toggle_pressed(); + void on_configurations_override_toggled(bool checked); void on_configurations_executable_scope_currentIndexChanged(int index); void on_configurations_executable_list_currentIndexChanged(int index); void on_configurations_executable_append_pressed(); diff --git a/vkconfig_gui/tab_diagnostics.cpp b/vkconfig_gui/tab_diagnostics.cpp index 9be6d65cd6..332824299d 100644 --- a/vkconfig_gui/tab_diagnostics.cpp +++ b/vkconfig_gui/tab_diagnostics.cpp @@ -168,7 +168,8 @@ std::string TabDiagnostics::BuildStatus(DiagnosticMode selected_mode, std::size_ this->log_path = working_directory.AbsolutePath() + "/" + filename; } - configurator.SetExecutableScope(selected_mode == DIAGNOSTIC_VULKAN_PROFILE ? EXECUTABLE_NONE : EXECUTABLE_ANY); + // configurator.SetExecutableScope(selected_mode == DIAGNOSTIC_VULKAN_PROFILE ? EXECUTABLE_NONE : EXECUTABLE_ANY); + configurator.layers_override_enabled = selected_mode != DIAGNOSTIC_VULKAN_PROFILE; configurator.Override(OVERRIDE_AREA_ALL); this->process->setArguments(args); @@ -291,7 +292,8 @@ void TabDiagnostics::UpdateUI(UpdateUIMode mode) { this->ui->diagnostic_group_box_loader_log->blockSignals(true); this->ui->diagnostic_group_box_loader_log->setChecked(configurator.loader_log_enabled); this->ui->diagnostic_group_box_loader_log->blockSignals(false); - // this->UpdateStatus(); + + this->window.UpdateUI_Status(); } void TabDiagnostics::CleanUI() {} diff --git a/vkconfig_gui/tab_drivers.cpp b/vkconfig_gui/tab_drivers.cpp index 0a39cc5789..e4b01ce1ed 100644 --- a/vkconfig_gui/tab_drivers.cpp +++ b/vkconfig_gui/tab_drivers.cpp @@ -143,6 +143,8 @@ void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { this->ui->driver_paths_list->blockSignals(false); } break; } + + this->window.UpdateUI_Status(); } void TabDrivers::CleanUI() {} @@ -185,6 +187,8 @@ void TabDrivers::on_driver_override_toggled(bool checked) { configurator.driver_override_info = ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[this->ui->driver_forced_name->currentIndex()]); configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); + + this->UpdateUI(UPDATE_REBUILD_UI); } void TabDrivers::on_driver_mode_changed(int index) { diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index 50e0f0f749..f30cc9d253 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -32,7 +32,7 @@ #include #include -TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : Tab(TAB_LAYERS, window, ui) { +TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : Tab(TAB_LAYERS_PATHS, window, ui) { Configurator &configurator = Configurator::Get(); this->ui->layers_progress->setValue(0); @@ -125,8 +125,11 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { } void TabLayers::UpdateUI(UpdateUIMode ui_update_mode) { + const Configurator &configurator = Configurator::Get(); + this->ui->layers_progress->resetFormat(); this->ui->layers_progress->setValue(0); + this->UpdateUI_LayersPaths(ui_update_mode); } diff --git a/vkconfig_gui/tab_preferences.cpp b/vkconfig_gui/tab_preferences.cpp index ce0d534140..a4fb5d44eb 100644 --- a/vkconfig_gui/tab_preferences.cpp +++ b/vkconfig_gui/tab_preferences.cpp @@ -40,6 +40,8 @@ TabPreferences::TabPreferences(MainWindow &window, std::shared_ptrconnect(this->ui->preferences_keep_running, SIGNAL(toggled(bool)), this, SLOT(on_keep_running_toggled(bool))); this->connect(this->ui->preferences_vk_home_text, SIGNAL(returnPressed()), this, SLOT(on_vk_home_text_pressed())); this->connect(this->ui->preferences_vk_home_browse, SIGNAL(clicked()), this, SLOT(on_vk_home_browse_pressed())); + this->connect(this->ui->preferences_show_executable_scope, SIGNAL(toggled(bool)), this, + SLOT(on_show_executables_scope_toggled(bool))); this->connect(this->ui->preferences_all_enabled_executables, SIGNAL(currentIndexChanged(int)), this, SLOT(on_all_enabled_executables_changed(int))); this->connect(this->ui->preferences_vk_download_browse, SIGNAL(clicked()), this, SLOT(on_vk_download_browse_pressed())); @@ -119,6 +121,7 @@ void TabPreferences::UpdateUI(UpdateUIMode mode) { this->ui->preferences_vk_home_text->blockSignals(false); this->ui->preferences_all_enabled_executables->blockSignals(true); + this->ui->preferences_all_enabled_executables->setEnabled(configurator.configuration_show_scope); this->ui->preferences_all_enabled_executables->setCurrentIndex( static_cast(configurator.GetAllEnabledExecutableBehavior())); this->ui->preferences_all_enabled_executables->blockSignals(false); @@ -142,6 +145,13 @@ bool TabPreferences::EventFilter(QObject *target, QEvent *event) { return false; } +void TabPreferences::on_show_executables_scope_toggled(bool checked) { + Configurator &configurator = Configurator::Get(); + configurator.configuration_show_scope = checked; + + this->UpdateUI(UPDATE_REFRESH_UI); +} + void TabPreferences::on_all_enabled_executables_changed(int index) { Configurator &configurator = Configurator::Get(); diff --git a/vkconfig_gui/tab_preferences.h b/vkconfig_gui/tab_preferences.h index fa83e0c8dd..62431c434a 100644 --- a/vkconfig_gui/tab_preferences.h +++ b/vkconfig_gui/tab_preferences.h @@ -40,6 +40,7 @@ class TabPreferences : public Tab { void on_keep_running_toggled(bool checked); void on_vk_home_text_pressed(); void on_vk_home_browse_pressed(); + void on_show_executables_scope_toggled(bool checked); void on_all_enabled_executables_changed(int index); void on_vk_download_browse_pressed(); void on_vk_download_open_pressed(); diff --git a/vkconfig_gui/widget_setting_flags.cpp b/vkconfig_gui/widget_setting_flags.cpp index 497e9f0074..573fbe798e 100644 --- a/vkconfig_gui/widget_setting_flags.cpp +++ b/vkconfig_gui/widget_setting_flags.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 73a24effcad29ddeeea2b5b2e1bf5d2073c78770 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 17 Mar 2026 21:51:21 +0100 Subject: [PATCH 03/45] vkconfig: Add Vulkan Drivers override mode for no physical device --- vkconfig_core/application_singleton.cpp | 4 ++-- vkconfig_core/configurator.cpp | 15 +++++++++++++++ vkconfig_core/json.cpp | 4 ++-- vkconfig_core/json_validator.cpp | 4 ++-- vkconfig_core/registry.h | 4 ++-- vkconfig_core/setting_float.h | 4 ++-- vkconfig_core/setting_frames.cpp | 4 ++-- vkconfig_core/setting_frames.h | 4 ++-- vkconfig_core/setting_group.cpp | 4 ++-- vkconfig_core/setting_group.h | 4 ++-- vkconfig_core/setting_int.cpp | 4 ++-- vkconfig_core/setting_int.h | 4 ++-- vkconfig_core/setting_list.h | 4 ++-- vkconfig_core/test/test_date.cpp | 4 ++-- vkconfig_core/test/test_json.cpp | 4 ++-- vkconfig_core/test/test_setting.cpp | 7 +++---- vkconfig_core/test/test_setting_type_bool.cpp | 4 ++-- .../test/test_setting_type_bool_numeric.cpp | 4 ++-- vkconfig_core/test/test_setting_type_enum.cpp | 4 ++-- .../test/test_setting_type_file_load.cpp | 4 ++-- .../test/test_setting_type_file_save.cpp | 4 ++-- vkconfig_core/test/test_setting_type_flags.cpp | 4 ++-- vkconfig_core/test/test_setting_type_float.cpp | 4 ++-- .../test/test_setting_type_folder_save.cpp | 4 ++-- vkconfig_core/test/test_setting_type_frames.cpp | 4 ++-- vkconfig_core/test/test_setting_type_group.cpp | 4 ++-- vkconfig_core/test/test_setting_type_int.cpp | 4 ++-- vkconfig_core/test/test_setting_type_list.cpp | 4 ++-- vkconfig_core/test/test_setting_type_string.cpp | 4 ++-- vkconfig_core/type_driver_mode.cpp | 1 + vkconfig_core/type_driver_mode.h | 3 ++- vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/combo_box.h | 4 ++-- vkconfig_gui/mainwindow.ui | 13 +++++++++---- vkconfig_gui/tab_drivers.cpp | 12 +++++++++++- vkconfig_gui/widget_setting.cpp | 4 ++-- vkconfig_gui/widget_setting_bool.cpp | 4 ++-- vkconfig_gui/widget_setting_bool.h | 4 ++-- vkconfig_gui/widget_setting_flags.h | 4 ++-- vkconfig_gui/widget_setting_float.h | 4 ++-- vkconfig_gui/widget_setting_frames.h | 4 ++-- vkconfig_gui/widget_setting_int.h | 4 ++-- vkconfig_gui/widget_setting_list.cpp | 4 ++-- vkconfig_gui/widget_setting_list.h | 4 ++-- vkconfig_gui/widget_setting_list_element.cpp | 4 ++-- vkconfig_gui/widget_setting_list_element.h | 4 ++-- vkconfig_gui/widget_setting_string.cpp | 4 ++-- vkconfig_gui/widget_setting_string.h | 4 ++-- 48 files changed, 124 insertions(+), 92 deletions(-) diff --git a/vkconfig_core/application_singleton.cpp b/vkconfig_core/application_singleton.cpp index fe6c51d84d..befe4694f1 100644 --- a/vkconfig_core/application_singleton.cpp +++ b/vkconfig_core/application_singleton.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index e26418a10c..1492a167e5 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -244,6 +244,14 @@ QJsonObject Configurator::CreateJsonSettingObject(const Configurator::LoaderSett } json_settings.insert("device_configurations", json_devices); } break; + case DRIVER_MODE_NONE: { + QJsonArray json_devices; + json_settings.insert("device_configurations", json_devices); + } break; + default: { + assert(0); + break; + } } } @@ -316,6 +324,13 @@ QJsonObject Configurator::CreateJsonGlobalObject() const { } json_settings.insert("device_configurations", json_devices); } break; + case DRIVER_MODE_NONE: { + QJsonArray json_devices; + json_settings.insert("device_configurations", json_devices); + } break; + default: { + assert(0); + } } } diff --git a/vkconfig_core/json.cpp b/vkconfig_core/json.cpp index 93b0cc6c2f..044079cf93 100644 --- a/vkconfig_core/json.cpp +++ b/vkconfig_core/json.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/json_validator.cpp b/vkconfig_core/json_validator.cpp index 43b34f1c71..791debdbd1 100644 --- a/vkconfig_core/json_validator.cpp +++ b/vkconfig_core/json_validator.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/registry.h b/vkconfig_core/registry.h index 2fd52d9740..735dd836f4 100644 --- a/vkconfig_core/registry.h +++ b/vkconfig_core/registry.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_float.h b/vkconfig_core/setting_float.h index 28121c9709..8a98d2bd4f 100644 --- a/vkconfig_core/setting_float.h +++ b/vkconfig_core/setting_float.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_frames.cpp b/vkconfig_core/setting_frames.cpp index 2d8cbd53ae..fa85c87854 100644 --- a/vkconfig_core/setting_frames.cpp +++ b/vkconfig_core/setting_frames.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_frames.h b/vkconfig_core/setting_frames.h index 78dda22fdf..4c6bc2ed02 100644 --- a/vkconfig_core/setting_frames.h +++ b/vkconfig_core/setting_frames.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_group.cpp b/vkconfig_core/setting_group.cpp index cd2f96aa41..af312d29e3 100644 --- a/vkconfig_core/setting_group.cpp +++ b/vkconfig_core/setting_group.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_group.h b/vkconfig_core/setting_group.h index 2282189418..bf23d8b461 100644 --- a/vkconfig_core/setting_group.h +++ b/vkconfig_core/setting_group.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_int.cpp b/vkconfig_core/setting_int.cpp index d0ffae06fd..8ba1dd75fb 100644 --- a/vkconfig_core/setting_int.cpp +++ b/vkconfig_core/setting_int.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_int.h b/vkconfig_core/setting_int.h index f504baab28..e1f1b7f798 100644 --- a/vkconfig_core/setting_int.h +++ b/vkconfig_core/setting_int.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/setting_list.h b/vkconfig_core/setting_list.h index 7d563229ca..564c558d3d 100644 --- a/vkconfig_core/setting_list.h +++ b/vkconfig_core/setting_list.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_date.cpp b/vkconfig_core/test/test_date.cpp index 36d8e4b901..6d65e46291 100644 --- a/vkconfig_core/test/test_date.cpp +++ b/vkconfig_core/test/test_date.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_json.cpp b/vkconfig_core/test/test_json.cpp index f95ebb5ee3..56d6750285 100644 --- a/vkconfig_core/test/test_json.cpp +++ b/vkconfig_core/test/test_json.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting.cpp b/vkconfig_core/test/test_setting.cpp index 4af1c111d2..0cb451d0d6 100644 --- a/vkconfig_core/test/test_setting.cpp +++ b/vkconfig_core/test/test_setting.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,10 @@ TEST(test_setting, is_enum_true) { EXPECT_EQ(true, IsEnum(SETTING_ENUM)); } -TEST(test_setting, is_flags_true) { EXPECT_EQ(true, IsFlags(SETTING_FLAGS)); } +TEST(test_setting, is_flags_true) { EXPECT_EQ(true, IsFlags(SETTING_FLAGS)); } TEST(test_setting, is_enum_false) { EXPECT_EQ(false, IsEnum(SETTING_STRING)); } TEST(test_setting, get_setting_token) { EXPECT_STREQ("STRING", GetToken(SETTING_STRING)); } TEST(test_setting, get_setting_type) { EXPECT_EQ(SETTING_STRING, GetSettingType("STRING")); } - diff --git a/vkconfig_core/test/test_setting_type_bool.cpp b/vkconfig_core/test/test_setting_type_bool.cpp index c063b74504..7f1b57e0b1 100644 --- a/vkconfig_core/test/test_setting_type_bool.cpp +++ b/vkconfig_core/test/test_setting_type_bool.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_bool_numeric.cpp b/vkconfig_core/test/test_setting_type_bool_numeric.cpp index 477da3c816..7cf93408f0 100644 --- a/vkconfig_core/test/test_setting_type_bool_numeric.cpp +++ b/vkconfig_core/test/test_setting_type_bool_numeric.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_enum.cpp b/vkconfig_core/test/test_setting_type_enum.cpp index 280a8dbd3e..196a0cdb83 100644 --- a/vkconfig_core/test/test_setting_type_enum.cpp +++ b/vkconfig_core/test/test_setting_type_enum.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_file_load.cpp b/vkconfig_core/test/test_setting_type_file_load.cpp index 1a3c0f2df4..b393fd860c 100644 --- a/vkconfig_core/test/test_setting_type_file_load.cpp +++ b/vkconfig_core/test/test_setting_type_file_load.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_file_save.cpp b/vkconfig_core/test/test_setting_type_file_save.cpp index a472bd0bb4..61085ddd74 100644 --- a/vkconfig_core/test/test_setting_type_file_save.cpp +++ b/vkconfig_core/test/test_setting_type_file_save.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_flags.cpp b/vkconfig_core/test/test_setting_type_flags.cpp index da5c8d803d..275853a8c1 100644 --- a/vkconfig_core/test/test_setting_type_flags.cpp +++ b/vkconfig_core/test/test_setting_type_flags.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_float.cpp b/vkconfig_core/test/test_setting_type_float.cpp index 39ab01ab49..c778629edb 100644 --- a/vkconfig_core/test/test_setting_type_float.cpp +++ b/vkconfig_core/test/test_setting_type_float.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_folder_save.cpp b/vkconfig_core/test/test_setting_type_folder_save.cpp index d8a3030ec1..a5af8d6d50 100644 --- a/vkconfig_core/test/test_setting_type_folder_save.cpp +++ b/vkconfig_core/test/test_setting_type_folder_save.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_frames.cpp b/vkconfig_core/test/test_setting_type_frames.cpp index 11e2a90265..37a8a35944 100644 --- a/vkconfig_core/test/test_setting_type_frames.cpp +++ b/vkconfig_core/test/test_setting_type_frames.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_group.cpp b/vkconfig_core/test/test_setting_type_group.cpp index ca5ec56e20..d7f19c1d4b 100644 --- a/vkconfig_core/test/test_setting_type_group.cpp +++ b/vkconfig_core/test/test_setting_type_group.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_int.cpp b/vkconfig_core/test/test_setting_type_int.cpp index 806c1f4f7a..82ef462f2b 100644 --- a/vkconfig_core/test/test_setting_type_int.cpp +++ b/vkconfig_core/test/test_setting_type_int.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_list.cpp b/vkconfig_core/test/test_setting_type_list.cpp index 2087db2149..8b19304f86 100644 --- a/vkconfig_core/test/test_setting_type_list.cpp +++ b/vkconfig_core/test/test_setting_type_list.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/test/test_setting_type_string.cpp b/vkconfig_core/test/test_setting_type_string.cpp index 225c0b58ec..b30eba3162 100644 --- a/vkconfig_core/test/test_setting_type_string.cpp +++ b/vkconfig_core/test/test_setting_type_string.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_core/type_driver_mode.cpp b/vkconfig_core/type_driver_mode.cpp index d6f858c249..03c7827d52 100644 --- a/vkconfig_core/type_driver_mode.cpp +++ b/vkconfig_core/type_driver_mode.cpp @@ -27,6 +27,7 @@ const char* GetToken(DriverMode mode) { static const char* TOKENS[] = { "Single", // DRIVER_MODE_SINGLE "Sorted", // DRIVER_MODE_SORTED + "None", // DRIVER_MODE_NONE }; static_assert(std::size(TOKENS) == DRIVER_MODE_COUNT); diff --git a/vkconfig_core/type_driver_mode.h b/vkconfig_core/type_driver_mode.h index 8149930dd1..af5294875c 100644 --- a/vkconfig_core/type_driver_mode.h +++ b/vkconfig_core/type_driver_mode.h @@ -23,9 +23,10 @@ enum DriverMode { DRIVER_MODE_SINGLE = 0, DRIVER_MODE_SORTED, + DRIVER_MODE_NONE, DRIVER_MODE_FIRST = DRIVER_MODE_SINGLE, - DRIVER_MODE_LAST = DRIVER_MODE_SORTED + DRIVER_MODE_LAST = DRIVER_MODE_NONE }; enum { DRIVER_MODE_COUNT = DRIVER_MODE_LAST - DRIVER_MODE_FIRST + 1 }; diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 8b2bf94cbb..75cb1c0abf 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -5,6 +5,7 @@ - Add selecting Vulkan Layers configuration from system tray - Add selecting Vullan physical device from system tray - Add enabling Vulkan Loader log from system tray +- Add Vulkan Drivers override mode for no physical device ### Improvements: - Clarify UI to clarify what is overridden by Vulkan Configurator: Layers, Devices, Loader log diff --git a/vkconfig_gui/combo_box.h b/vkconfig_gui/combo_box.h index 828ae3d671..0b2550f9df 100644 --- a/vkconfig_gui/combo_box.h +++ b/vkconfig_gui/combo_box.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index c87d06814e..734d35eff6 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -91,7 +91,7 @@ QTabWidget::Rounded - 4 + 0 @@ -132,7 +132,7 @@ Control the Vulkan Layers used by running Vulkan applications - Override System Vulkan Layers + Configure System Vulkan Layers true @@ -1235,7 +1235,7 @@ Control Vulkan Physical Device used by all running Vulkan applications - Override System Vulkan Devices + Configure System Vulkan Devices true @@ -1326,6 +1326,11 @@ Order Vulkan physical devices + + + No Vulkan physical device available + + @@ -2644,7 +2649,7 @@ Produce Vulkan Loader Log to all running Vulkan applications - Override System Vulkan Loader Log + Configure System Vulkan Loader Log true diff --git a/vkconfig_gui/tab_drivers.cpp b/vkconfig_gui/tab_drivers.cpp index e4b01ce1ed..c00c5a3cb9 100644 --- a/vkconfig_gui/tab_drivers.cpp +++ b/vkconfig_gui/tab_drivers.cpp @@ -65,7 +65,6 @@ void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { this->ui->driver_mode->blockSignals(false); switch (configurator.driver_override_mode) { - default: case DRIVER_MODE_SINGLE: { this->ui->driver_forced_name->blockSignals(true); this->ui->driver_forced_name->clear(); @@ -118,6 +117,17 @@ void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { this->ui->drivers_device_list->setVisible(true); this->ui->drivers_label_last->setVisible(true); } break; + case DRIVER_MODE_NONE: { + this->ui->driver_name_label->setVisible(false); + this->ui->driver_forced_name->setVisible(false); + this->ui->drivers_label_first->setVisible(false); + this->ui->drivers_device_list->setVisible(false); + this->ui->drivers_label_last->setVisible(false); + } break; + default: { + assert(0); + break; + } } this->ui->driver_group_box_paths->blockSignals(true); diff --git a/vkconfig_gui/widget_setting.cpp b/vkconfig_gui/widget_setting.cpp index cd9e7ec7d9..9ec0ad5d14 100644 --- a/vkconfig_gui/widget_setting.cpp +++ b/vkconfig_gui/widget_setting.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_bool.cpp b/vkconfig_gui/widget_setting_bool.cpp index 91451a0c3b..83d2b63771 100644 --- a/vkconfig_gui/widget_setting_bool.cpp +++ b/vkconfig_gui/widget_setting_bool.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_bool.h b/vkconfig_gui/widget_setting_bool.h index e36f7bb012..b764499d73 100644 --- a/vkconfig_gui/widget_setting_bool.h +++ b/vkconfig_gui/widget_setting_bool.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_flags.h b/vkconfig_gui/widget_setting_flags.h index 0697f8e728..fff6402833 100644 --- a/vkconfig_gui/widget_setting_flags.h +++ b/vkconfig_gui/widget_setting_flags.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_float.h b/vkconfig_gui/widget_setting_float.h index 0587c393b4..66ab383de7 100644 --- a/vkconfig_gui/widget_setting_float.h +++ b/vkconfig_gui/widget_setting_float.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_frames.h b/vkconfig_gui/widget_setting_frames.h index d4e5f3564f..7c060b79a7 100644 --- a/vkconfig_gui/widget_setting_frames.h +++ b/vkconfig_gui/widget_setting_frames.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_int.h b/vkconfig_gui/widget_setting_int.h index aac5214b43..98c08c13e1 100644 --- a/vkconfig_gui/widget_setting_int.h +++ b/vkconfig_gui/widget_setting_int.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_list.cpp b/vkconfig_gui/widget_setting_list.cpp index 68c30216e3..98afb89715 100644 --- a/vkconfig_gui/widget_setting_list.cpp +++ b/vkconfig_gui/widget_setting_list.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_list.h b/vkconfig_gui/widget_setting_list.h index 5144ae2b18..51a443cc9b 100644 --- a/vkconfig_gui/widget_setting_list.h +++ b/vkconfig_gui/widget_setting_list.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_list_element.cpp b/vkconfig_gui/widget_setting_list_element.cpp index 4c24c9f0d0..3ecd379d8b 100644 --- a/vkconfig_gui/widget_setting_list_element.cpp +++ b/vkconfig_gui/widget_setting_list_element.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_list_element.h b/vkconfig_gui/widget_setting_list_element.h index c9ad9c8dea..37c7a58273 100644 --- a/vkconfig_gui/widget_setting_list_element.h +++ b/vkconfig_gui/widget_setting_list_element.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_string.cpp b/vkconfig_gui/widget_setting_string.cpp index 83bef4b588..9021beb707 100644 --- a/vkconfig_gui/widget_setting_string.cpp +++ b/vkconfig_gui/widget_setting_string.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/vkconfig_gui/widget_setting_string.h b/vkconfig_gui/widget_setting_string.h index 1cb39bd492..dd9031173f 100644 --- a/vkconfig_gui/widget_setting_string.h +++ b/vkconfig_gui/widget_setting_string.h @@ -1,6 +1,6 @@ /* - * Copyright (c) 2020-2021 Valve Corporation - * Copyright (c) 2020-2021 LunarG, Inc. + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From be0d5ab641f964f9bf03e40a2aaf8ecdc0ffbcfa Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Mar 2026 13:13:17 +0100 Subject: [PATCH 04/45] vkconfig: Fix application launcher 'options' edit #2598 --- vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/tab_applications.cpp | 11 ++++++----- vkconfig_gui/tab_applications.h | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 75cb1c0abf..58de6a4001 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes: - Fix command line whitespace decoding #2625 +- Fix application launcher 'options' edit #2598 ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/tab_applications.cpp b/vkconfig_gui/tab_applications.cpp index 72efe225dc..e40fb1bd71 100644 --- a/vkconfig_gui/tab_applications.cpp +++ b/vkconfig_gui/tab_applications.cpp @@ -53,8 +53,8 @@ TabApplications::TabApplications(MainWindow &window, std::shared_ptrconnect(this->ui->launch_options_list, SIGNAL(currentIndexChanged(int)), this, SLOT(on_launch_options_list_activated(int))); - this->connect(this->ui->launch_options_list->lineEdit(), SIGNAL(textEdited(QString)), this, - SLOT(on_launch_options_list_textEdited(QString))); + this->connect(this->ui->launch_options_list->lineEdit(), &QLineEdit::editingFinished, this, + &TabApplications::on_launch_options_list_finished); this->connect(this->ui->launch_options_append, SIGNAL(clicked()), this, SLOT(on_launch_options_append_pressed())); this->connect(this->ui->launch_options_remove, SIGNAL(clicked()), this, SLOT(on_launch_options_remove_pressed())); @@ -328,14 +328,15 @@ void TabApplications::on_launch_options_list_activated(int index) { ui->launch_options_log_edit->setToolTip(options->log_file.AbsolutePath().c_str()); } -void TabApplications::on_launch_options_list_textEdited(const QString &text) { +void TabApplications::on_launch_options_list_finished() { + std::string new_text = this->ui->launch_options_list->lineEdit()->text().toStdString(); + Configurator &configurator = Configurator::Get(); Executable *executable = configurator.executables.GetActiveExecutable(); - executable->RenameActiveOptions(text.toStdString()); + executable->RenameActiveOptions(new_text); this->RebuildOptions(); this->ui->launch_options_list->setCurrentIndex(executable->GetActiveOptionsIndex()); - // this->ui->launch_options_list->setItemText(executable->GetActiveOptionsIndex(), text); } void TabApplications::on_launch_options_append_pressed() { diff --git a/vkconfig_gui/tab_applications.h b/vkconfig_gui/tab_applications.h index d2e53fb784..5c4b9cd25f 100644 --- a/vkconfig_gui/tab_applications.h +++ b/vkconfig_gui/tab_applications.h @@ -48,7 +48,7 @@ class TabApplications : public Tab { void on_launch_executable_remove_pressed(); void on_launch_options_list_activated(int index); - void on_launch_options_list_textEdited(const QString& text); + void on_launch_options_list_finished(); void on_launch_options_append_pressed(); void on_launch_options_remove_pressed(); From 2e6f2e8f154c4fafa6c7a253dc28ae1a12517a6f Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Mar 2026 16:59:44 +0100 Subject: [PATCH 05/45] vkconfig: Improve layer control tooltips #2595 --- vkconfig_core/layer.cpp | 24 ------- vkconfig_core/layer.h | 1 - vkconfig_core/type_layer_builtin.cpp | 10 +-- vkconfig_core/type_layer_builtin.h | 2 +- vkconfig_core/type_layer_control.cpp | 12 ---- vkconfig_core/type_layer_control.h | 1 - vkconfig_gui/CHANGELOG.md | 2 + .../widget_tab_configurations_layer.cpp | 68 ++++++++++++++++--- 8 files changed, 66 insertions(+), 54 deletions(-) diff --git a/vkconfig_core/layer.cpp b/vkconfig_core/layer.cpp index 0fa35136d7..d6042d39e1 100644 --- a/vkconfig_core/layer.cpp +++ b/vkconfig_core/layer.cpp @@ -103,30 +103,6 @@ LayerControl Layer::GetActualControl() const { } } -std::string Layer::GetActualControlTooltip() const { - if (this->type == LAYER_TYPE_IMPLICIT) { - if (!this->disable_env.empty()) { - if (qEnvironmentVariableIsSet(this->disable_env.c_str())) { - return format("'%s' is set", this->disable_env.c_str()); - } - } - - if (!this->enable_env.empty()) { - const std::string& value = qgetenv(this->enable_env.c_str()).toStdString(); - if (value == this->enable_value) { - return format("'%s' is set to '%s'.", this->enable_env.c_str(), value.c_str()); - } else { - return format("Set '%s' to '%s' to enable '%s' by default.", this->enable_env.c_str(), this->enable_value.c_str(), - this->key.c_str()); - } - } - - return format("Set '%s' to disable '%s' by default.", this->disable_env.c_str(), this->key.c_str()); - } else { - return ::GetDescription(LAYER_CONTROL_AUTO); - } -} - int Layer::FindPresetIndex(const SettingDataSet& settings) const { for (std::size_t i = 0, n = this->presets.size(); i < n; ++i) { if (::HasPreset(settings, this->presets[i].settings)) { diff --git a/vkconfig_core/layer.h b/vkconfig_core/layer.h index 5fbf7d3e8e..8380149405 100644 --- a/vkconfig_core/layer.h +++ b/vkconfig_core/layer.h @@ -82,7 +82,6 @@ class Layer { bool IsValid() const; LayerControl GetActualControl() const; - std::string GetActualControlTooltip() const; int FindPresetIndex(const SettingDataSet& settings) const; diff --git a/vkconfig_core/type_layer_builtin.cpp b/vkconfig_core/type_layer_builtin.cpp index aa0daffbe7..b4d1cb71e1 100644 --- a/vkconfig_core/type_layer_builtin.cpp +++ b/vkconfig_core/type_layer_builtin.cpp @@ -44,20 +44,22 @@ const char* GetToken(LayerBuiltin builtin) { const char* GetLabel(LayerBuiltin builtin) { static const char* TABLE[] = { - "N/A", // LAYER_BUILTIN_NONE - "Vulkan Layers Located by the Vulkan Application", // LAYER_BUILTIN_UNORDERED + "N/A", // LAYER_BUILTIN_NONE + "Vulkan Layers Located by Vulkan Applications", // LAYER_BUILTIN_UNORDERED }; static_assert(std::size(TABLE) == LAYER_BUILTIN_COUNT); return TABLE[builtin]; } - +/* const char* GetDescription(LayerBuiltin builtin) { static const char* TABLE[] = { "N/A", // LAYER_BUILTIN_NONE - "Vulkan Layers are located by the Vulkan Application by setting 'VK_ADD_LAYER_PATH' Application at launch", // LAYER_BUILTIN_UNORDERED + "Vulkan Layers are located by the Vulkan Application by setting 'VK_ADD_LAYER_PATH' Application at launch", // +LAYER_BUILTIN_UNORDERED }; static_assert(std::size(TABLE) == LAYER_BUILTIN_COUNT); return TABLE[builtin]; } +*/ diff --git a/vkconfig_core/type_layer_builtin.h b/vkconfig_core/type_layer_builtin.h index 08ec82d5ef..700f95a8f0 100644 --- a/vkconfig_core/type_layer_builtin.h +++ b/vkconfig_core/type_layer_builtin.h @@ -33,4 +33,4 @@ enum { LAYER_BUILTIN_COUNT = LAYER_BUILTIN_LAST - LAYER_BUILTIN_FIRST + 1 }; LayerBuiltin GetLayerBuiltin(const char* token); const char* GetToken(LayerBuiltin builtin); const char* GetLabel(LayerBuiltin builtin); -const char* GetDescription(LayerBuiltin builtin); +// const char* GetDescription(LayerBuiltin builtin); diff --git a/vkconfig_core/type_layer_control.cpp b/vkconfig_core/type_layer_control.cpp index 215540626c..1d3cda04b2 100644 --- a/vkconfig_core/type_layer_control.cpp +++ b/vkconfig_core/type_layer_control.cpp @@ -70,15 +70,3 @@ const char* GetLabel(LayerControl control) { return TABLE[control]; } - -const char* GetDescription(LayerControl control) { - static const char* TOKENS[] = { - "Explicit layers are disabled by default and implicit layers are enabled by default.", // LAYER_CONTROL_AUTO - "Discard the layer, don't notify the Vulkan Loader that this layer exists.", // LAYER_CONTROL_DISCARD - "Enable the layer, ensuring its execution.", // LAYER_CONTROL_ON - "Disable the layer, preventing its execution.", // LAYER_CONTROL_OFF - }; - static_assert(std::size(TOKENS) == LAYER_CONTROL_COUNT); - - return TOKENS[control]; -} diff --git a/vkconfig_core/type_layer_control.h b/vkconfig_core/type_layer_control.h index d090f38080..e6c19e9df2 100644 --- a/vkconfig_core/type_layer_control.h +++ b/vkconfig_core/type_layer_control.h @@ -42,4 +42,3 @@ LayerControl MapToUnordered(LayerControl control); LayerControl GetLayerControl(const char* token); const char* GetToken(LayerControl control); const char* GetLabel(LayerControl control); -const char* GetDescription(LayerControl control); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 58de6a4001..eb16313a9c 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -16,6 +16,8 @@ ### Fixes: - Fix command line whitespace decoding #2625 - Fix application launcher 'options' edit #2598 +- Fix "Vulkan Layers Located by Vulkan Applications" tooltips #2595 +- Fix "discard" layer control tooltip #2595 ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/widget_tab_configurations_layer.cpp b/vkconfig_gui/widget_tab_configurations_layer.cpp index d38876cd95..5ddf0e4b3e 100644 --- a/vkconfig_gui/widget_tab_configurations_layer.cpp +++ b/vkconfig_gui/widget_tab_configurations_layer.cpp @@ -29,6 +29,56 @@ #include +static const char *GetDescription(LayerControl control, LayerBuiltin builtin) { + if (builtin == LAYER_BUILTIN_UNORDERED) { + static const char *TABLE[] = { + "Vulkan Layers located by Vulkan Applications using 'VK_ADD_LAYER_PATH' at launch", // LAYER_CONTROL_AUTO + "Prevent Vulkan applications from loading additional Vulkan Layers not visible by Vulkan Configurator", // LAYER_CONTROL_DISCARD + }; + static_assert(std::size(TABLE) == LAYER_CONTROL_UNORDERED_COUNT); + + return TABLE[control]; + } else { + static const char + * + TABLE[] = + { + "Explicit layers are disabled by default and implicit layers are enabled by default.", // LAYER_CONTROL_AUTO + "Discard the layer, the Vulkan Loader isn't made awate of this layer but the Vulkan application can locate " + "it.", // LAYER_CONTROL_DISCARD + "Enable the layer, ensuring its execution.", // LAYER_CONTROL_ON + "Disable the layer, preventing its execution.", // LAYER_CONTROL_OFF + }; + static_assert(std::size(TABLE) == LAYER_CONTROL_COUNT); + + return TABLE[control]; + } +} + +static std::string GetActualControlTooltip(const Layer *layer) { + if (layer->type == LAYER_TYPE_IMPLICIT) { + if (!layer->disable_env.empty()) { + if (qEnvironmentVariableIsSet(layer->disable_env.c_str())) { + return format("'%s' is set", layer->disable_env.c_str()); + } + } + + if (!layer->enable_env.empty()) { + const std::string &value = qgetenv(layer->enable_env.c_str()).toStdString(); + if (value == layer->enable_value) { + return format("'%s' is set to '%s'.", layer->enable_env.c_str(), value.c_str()); + } else { + return format("Set '%s' to '%s' to enable '%s' by default.", layer->enable_env.c_str(), layer->enable_value.c_str(), + layer->key.c_str()); + } + } + + return format("Set '%s' to disable '%s' by default.", layer->disable_env.c_str(), layer->key.c_str()); + } else { + return ::GetDescription(LAYER_CONTROL_AUTO, LAYER_BUILTIN_NONE); + } +} + ConfigurationLayerWidget::ConfigurationLayerWidget(TabConfigurations *tab, const Parameter ¶meter) : layer_name(parameter.key), tab(tab) { const Configurator &configurator = Configurator::Get(); @@ -48,22 +98,18 @@ ConfigurationLayerWidget::ConfigurationLayerWidget(TabConfigurations *tab, const for (int i = first; i <= last; ++i) { const LayerControl layer_control = static_cast(i); std::string label = ::GetLabel(layer_control); - /* - if (i == LAYER_CONTROL_AUTO && layer != nullptr) { - label += layer->GetActualControl() == LAYER_CONTROL_ON ? " (E)" : " (D)"; - } - */ + this->layer_state->addItem(label.c_str()); - this->layer_state->setItemData(i, ::GetDescription(layer_control), Qt::ToolTipRole); + this->layer_state->setItemData(i, ::GetDescription(layer_control, parameter.builtin), Qt::ToolTipRole); } this->layer_state->setCurrentIndex(parameter.control); - if (parameter.control == LAYER_CONTROL_AUTO && layer != nullptr) { + if (parameter.builtin == LAYER_BUILTIN_NONE && parameter.control == LAYER_CONTROL_AUTO && layer != nullptr) { std::string message = ::GetToken(layer->type) + std::string(" layer: ") + ::GetLabel(layer->GetActualControl()); - message += format(". %s", layer->GetActualControlTooltip().c_str()); + message += format(". %s", ::GetActualControlTooltip(layer).c_str()); this->layer_state->setToolTip(message.c_str()); } else { - this->layer_state->setToolTip(::GetDescription(parameter.control)); + this->layer_state->setToolTip(::GetDescription(parameter.control, parameter.builtin)); } this->connect(this->layer_state, SIGNAL(currentIndexChanged(int)), this, SLOT(on_layer_state_currentIndexChanged(int))); @@ -97,7 +143,7 @@ ConfigurationLayerWidget::ConfigurationLayerWidget(TabConfigurations *tab, const this->setText(decorated_name.c_str()); if (parameter.builtin != LAYER_BUILTIN_NONE) { - this->setToolTip(::GetDescription(parameter.builtin)); + this->setToolTip(::GetDescription(parameter.control, parameter.builtin)); } else if (!parameter.manifest.Empty()) { this->setToolTip(parameter.manifest.AbsolutePath().c_str()); } @@ -192,7 +238,7 @@ void ConfigurationLayerWidget::on_layer_state_currentIndexChanged(int index) { } parameter->control = control; - this->layer_state->setToolTip(GetDescription(control)); + this->layer_state->setToolTip(GetDescription(control, parameter->builtin)); configurator.Override(OVERRIDE_AREA_ALL); } From ecade915820be949e7100416f70763ad4483a2c0 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Mar 2026 19:25:29 +0100 Subject: [PATCH 06/45] vkconfig: Add vkconfig.json backup on Reset to Default Change-Id: I5a003d3e539bac5f2dce72cc2fc5a9711cbfe1b9 --- vkconfig_core/configurator.cpp | 1 + vkconfig_core/layer.cpp | 1 - vkconfig_core/path.cpp | 22 ++++++++++++++++++++++ vkconfig_core/path.h | 1 + vkconfig_core/type_tab.cpp | 16 ++++++++-------- vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/mainwindow.ui | 2 +- 7 files changed, 34 insertions(+), 10 deletions(-) diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index 1492a167e5..88a5b4779a 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -572,6 +572,7 @@ void Configurator::Reset(bool hard) { this->Surrender(OVERRIDE_AREA_LOADER_SETTINGS_BIT); const Path& vkconfig_init_path = ::Path(Path::INIT); + vkconfig_init_path.Backup(); vkconfig_init_path.Remove(); QSettings settings("LunarG", VKCONFIG_SHORT_NAME); diff --git a/vkconfig_core/layer.cpp b/vkconfig_core/layer.cpp index d6042d39e1..919eb811bf 100644 --- a/vkconfig_core/layer.cpp +++ b/vkconfig_core/layer.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include diff --git a/vkconfig_core/path.cpp b/vkconfig_core/path.cpp index 6ec2ab6580..3f620e004c 100644 --- a/vkconfig_core/path.cpp +++ b/vkconfig_core/path.cpp @@ -189,6 +189,28 @@ bool Path::Create(bool as_file) const { return true; } +bool Path::Backup() const { + if (this->IsFile()) { + QFile file(this->AbsolutePath().c_str()); + + QDateTime date_time = QDateTime::currentDateTime(); + std::string date = date_time.toString(Qt::ISODate).toStdString(); + std::replace(date.begin(), date.end(), ':', '-'); + + std::string path = this->AbsolutePath(); + std::string extension = path.substr(path.find_last_of(".")); + std::string prefix = path.substr(0, path.find_last_of(".")); + //"vkconfig-" + date_time.toString(Qt::ISODate).toStdString() + ".json"; + + std::string dest = prefix + "-" + date + extension; + + bool result = file.copy(dest.c_str()); + return result; + } else { + return false; + } +} + bool Path::Remove() const { if (!this->Exists()) { return false; diff --git a/vkconfig_core/path.h b/vkconfig_core/path.h index 3342628105..a10f34e06b 100644 --- a/vkconfig_core/path.h +++ b/vkconfig_core/path.h @@ -61,6 +61,7 @@ class Path { void Clear(); bool Exists() const; bool Create(bool as_file = false) const; + bool Backup() const; bool Remove() const; std::string Filename() const; std::string Basename() const; diff --git a/vkconfig_core/type_tab.cpp b/vkconfig_core/type_tab.cpp index 9667a7cd8f..b7f0371080 100644 --- a/vkconfig_core/type_tab.cpp +++ b/vkconfig_core/type_tab.cpp @@ -25,14 +25,14 @@ const char* GetLabel(TabType type) { static const char* TOKENS[]{ - "Vulkan Layers", // TAB_CONFIGURATIONS - "Vulkan Layers Paths", // TAB_LAYERS_PATHS - "Vulkan Drivers", // TAB_DRIVERS - "Application Launcher", // TAB_APPLICATIONS - "Diagnostics", // TAB_DIAGNOSTIC - "Documentation", // TAB_DOCUMENTATION - "Preferences", // TAB_PREFERENCES - "About" // TAB_ABOUT + "Vulkan Layers Configuration", // TAB_CONFIGURATIONS + "Vulkan Layers Paths", // TAB_LAYERS_PATHS + "Vulkan Drivers", // TAB_DRIVERS + "Application Launcher", // TAB_APPLICATIONS + "Diagnostics", // TAB_DIAGNOSTIC + "Documentation", // TAB_DOCUMENTATION + "Preferences", // TAB_PREFERENCES + "About" // TAB_ABOUT }; static_assert(std::size(TOKENS) == TAB_COUNT, "The tranlation table size doesn't match the enum number of elements"); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index eb16313a9c..d576d85f91 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -12,6 +12,7 @@ - Improve generated layer settings C++ library to output to code for `vulkan.hpp` - Clean up generated layer settings C++ library for less friction on use - Add preferences settings to show "Vulkan Executables Scope" +- Add `vkconfig.json` backup when doing a "Reset To Default" #2570 ### Fixes: - Fix command line whitespace decoding #2625 diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 734d35eff6..50bd9eb7d8 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -91,7 +91,7 @@ QTabWidget::Rounded - 0 + 1 From 9d627a64d992b1bdd52ffae6e534cbf50abe4c58 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Mar 2026 20:18:52 +0100 Subject: [PATCH 07/45] vkconfig: Add 'Discard Ordering and Enabling Layers' default config #2596 Change-Id: Ia70b0040cfb274ed40e849d291a9f050cf0b9324 --- .../Discard Ordering and Enabling Layers.json | 44 +++++++++++++++++++ .../Validation with API Dump Log.json | 2 +- vkconfig_core/test/resources.qrc | 1 + vkconfig_gui/CHANGELOG.md | 2 + vkconfig_gui/mainwindow.ui | 2 +- vkconfig_gui/resources.qrc | 1 + 6 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 vkconfig_core/configurations/Discard Ordering and Enabling Layers.json diff --git a/vkconfig_core/configurations/Discard Ordering and Enabling Layers.json b/vkconfig_core/configurations/Discard Ordering and Enabling Layers.json new file mode 100644 index 0000000000..e0fdbf378f --- /dev/null +++ b/vkconfig_core/configurations/Discard Ordering and Enabling Layers.json @@ -0,0 +1,44 @@ +{ + "file_format_version": "3.0.0", + "configuration": { + "name": "Discard Ordering and Enabling Layers", + "version": 1, + "platforms": [ + "WINDOWS", + "LINUX", + "MACOS" + ], + "selected_layer_name": "", + "default_layer_control": "discard", + "override_layers": true, + "override_loader": false, + "loader_message_types": [ + "error", + "warn", + "info", + "debug", + "perf", + "layer", + "driver" + ], + "layers": [ + { + "builtin": "unordered_layer_location", + "control": "auto", + "name": "Vulkan Layers Located by Vulkan Applications", + "override_settings": true, + "platforms": [ + "WINDOWS_X86", + "WINDOWS_ARM", + "LINUX", + "MACOS" + ], + "rank": 9, + "settings": [ + ], + "type": "Explicit", + "version": "latest" + } + ] + } +} diff --git a/vkconfig_core/configurations/Validation with API Dump Log.json b/vkconfig_core/configurations/Validation with API Dump Log.json index 5e1e3ae657..3eb702a824 100644 --- a/vkconfig_core/configurations/Validation with API Dump Log.json +++ b/vkconfig_core/configurations/Validation with API Dump Log.json @@ -1,7 +1,7 @@ { "file_format_version": "3.0.0", "configuration": { - "name": "API dump", + "name": "Validation with API Dump Log", "version": 1, "platforms": [ "WINDOWS", diff --git a/vkconfig_core/test/resources.qrc b/vkconfig_core/test/resources.qrc index 2d9d021070..d542348c98 100644 --- a/vkconfig_core/test/resources.qrc +++ b/vkconfig_core/test/resources.qrc @@ -65,6 +65,7 @@ ../configurations/API dump.json ../configurations/Crash Diagnostic.json ../configurations/Disable All Vulkan Layers.json + ../configurations/Discard Ordering and Enabling Layers.json ../configurations/Frame Capture.json ../configurations/Portability.json ../configurations/Validation.json diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index d576d85f91..ea2998c4da 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -13,12 +13,14 @@ - Clean up generated layer settings C++ library for less friction on use - Add preferences settings to show "Vulkan Executables Scope" - Add `vkconfig.json` backup when doing a "Reset To Default" #2570 +- Add "Discard Ordering and Enabling Layers" default configuration #2596 ### Fixes: - Fix command line whitespace decoding #2625 - Fix application launcher 'options' edit #2598 - Fix "Vulkan Layers Located by Vulkan Applications" tooltips #2595 - Fix "discard" layer control tooltip #2595 +- Fix "Validation with API Dump log" configuration not loaded ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 50bd9eb7d8..734d35eff6 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -91,7 +91,7 @@ QTabWidget::Rounded - 1 + 0 diff --git a/vkconfig_gui/resources.qrc b/vkconfig_gui/resources.qrc index 3ecfa0c893..d24e5602a2 100644 --- a/vkconfig_gui/resources.qrc +++ b/vkconfig_gui/resources.qrc @@ -96,6 +96,7 @@ ../vkconfig_core/configurations/API dump.json ../vkconfig_core/configurations/Crash Diagnostic.json ../vkconfig_core/configurations/Disable All Vulkan Layers.json + ../vkconfig_core/configurations/Discard Ordering and Enabling Layers.json ../vkconfig_core/configurations/Frame Capture.json ../vkconfig_core/configurations/Portability.json ../vkconfig_core/configurations/Validation.json From a21b9ee742c08b7193437766ddda9ef27cbc5ee4 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 18 Mar 2026 20:36:42 +0100 Subject: [PATCH 08/45] vkconfig: Updated message when loader is not found #2533 Change-Id: Ibe020afd10370ba142e8dc7a9f25f2ded7e48932 --- vkconfig_gui/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vkconfig_gui/main.cpp b/vkconfig_gui/main.cpp index adaeb92c69..9466e0f8d0 100644 --- a/vkconfig_gui/main.cpp +++ b/vkconfig_gui/main.cpp @@ -110,7 +110,9 @@ int main(int argc, char* argv[]) { QMessageBox alert; alert.QDialog::setWindowTitle("Vulkan Configurator failed to start..."); alert.setText("Could not find a Vulkan Loader. Please install the Vulkan SDK."); - alert.setInformativeText("https://vulkan.lunarg.com/sdk/home"); + alert.setInformativeText( + "For more information: Read the Vulkan SDK 'Getting Started' " + "documentation"); alert.setIcon(QMessageBox::Critical); alert.exec(); return -1; From cd88815e7c403edcc8430c64882148c1b5e1a1d7 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 19 Mar 2026 19:16:08 +0100 Subject: [PATCH 09/45] vkconfig: Fix high CPU usage on idle when no layers configuration is applied Change-Id: I3adc1adf4311a2cc865b6b610345b11d66929627 --- vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/mainwindow.cpp | 2 +- vkconfig_gui/tab_configurations.cpp | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index ea2998c4da..a950384a5b 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -21,6 +21,7 @@ - Fix "Vulkan Layers Located by Vulkan Applications" tooltips #2595 - Fix "discard" layer control tooltip #2595 - Fix "Validation with API Dump log" configuration not loaded +- Fix high CPU usage on idle when no layers configuration is active #2603 ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/mainwindow.cpp b/vkconfig_gui/mainwindow.cpp index 106e164ab0..224ae30588 100644 --- a/vkconfig_gui/mainwindow.cpp +++ b/vkconfig_gui/mainwindow.cpp @@ -364,7 +364,7 @@ void MainWindow::showEvent(QShowEvent *event) { bool MainWindow::eventFilter(QObject *target, QEvent *event) { if (this->tabs[this->ui->tab_widget->currentIndex()] == nullptr) { - return false; + return true; } return this->tabs[this->ui->tab_widget->currentIndex()]->EventFilter(target, event); diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index d4eb1fe027..8740e6122e 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -414,11 +414,11 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { this->UpdateUI_Settings(UPDATE_REBUILD_UI); } - return true; + return false; } if (!configurator.layers_override_enabled || !ui->configurations_list->isEnabled()) { - return true; + return false; } else if (target == this->ui->configurations_list) { QContextMenuEvent *right_click = dynamic_cast(event); if (right_click) { From 61b47365cc1e0f9b6cc244728c5bbf5816eae257 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 19 Mar 2026 20:05:46 +0100 Subject: [PATCH 10/45] vkconfig: Clean up dead code Change-Id: I6646a52f9e044d6080f35c30ff9023e6be8eb529 --- vkconfig_core/configurator.cpp | 2 +- vkconfig_core/configurator.h | 3 --- vkconfig_gui/tab_diagnostics.cpp | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index 88a5b4779a..3e1e6392a6 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -196,7 +196,7 @@ QJsonObject Configurator::CreateJsonSettingObject(const Configurator::LoaderSett json_settings.insert("app_keys", json_app_keys); } - if (loader_settings.override_layers) { + if (this->layers_override_enabled) { json_settings.insert("layers", json_layers); } diff --git a/vkconfig_core/configurator.h b/vkconfig_core/configurator.h index 4a5413f827..cbabe37081 100644 --- a/vkconfig_core/configurator.h +++ b/vkconfig_core/configurator.h @@ -68,7 +68,6 @@ class Configurator { struct LoaderSettings { Path executable_path; std::vector layers; - bool override_layers = true; }; static Configurator& Get(); @@ -165,7 +164,6 @@ class Configurator { const ConfiguratorMode& mode; - bool force_full_loader_log = false; bool reset_hard = false; bool has_crashed = false; TabType active_tab = TAB_CONFIGURATIONS; @@ -185,7 +183,6 @@ class Configurator { std::vector driver_override_list; std::map driver_paths; bool driver_paths_enabled = false; - bool loader_log_enabled = false; int loader_log_messages_flags = GetBit(LOG_ERROR); diff --git a/vkconfig_gui/tab_diagnostics.cpp b/vkconfig_gui/tab_diagnostics.cpp index 332824299d..237945d1f2 100644 --- a/vkconfig_gui/tab_diagnostics.cpp +++ b/vkconfig_gui/tab_diagnostics.cpp @@ -131,8 +131,6 @@ std::string TabDiagnostics::BuildStatus(DiagnosticMode selected_mode, std::size_ std::string filename; if (selected_mode == DIAGNOSTIC_VULKAN_LOADER_LOG) { - configurator.force_full_loader_log = true; - this->status.clear(); args += "--summary"; @@ -180,10 +178,6 @@ std::string TabDiagnostics::BuildStatus(DiagnosticMode selected_mode, std::size_ while (this->process->waitForFinished(1000)) { } - if (selected_mode == DIAGNOSTIC_VULKAN_LOADER_LOG) { - configurator.force_full_loader_log = false; - } - configurator.SetExecutableScope(saved_scope); configurator.Override(OVERRIDE_AREA_ALL); From 38d15480b457e3eebb9a25095fb1607e4ee9db58 Mon Sep 17 00:00:00 2001 From: Christophe Date: Fri, 20 Mar 2026 17:24:59 +0100 Subject: [PATCH 11/45] vkconfig: Fix UUID display in log, following the RFC9562 standard #2423 Change-Id: I7657a8edf8d56a79cfc3515e9ba9b4943c9caa8c --- vkconfig_core/vulkan_util.cpp | 3 +++ vkconfig_gui/CHANGELOG.md | 1 + 2 files changed, 4 insertions(+) diff --git a/vkconfig_core/vulkan_util.cpp b/vkconfig_core/vulkan_util.cpp index 3d66bc3e0b..3215294118 100644 --- a/vkconfig_core/vulkan_util.cpp +++ b/vkconfig_core/vulkan_util.cpp @@ -35,6 +35,9 @@ std::string GetUUIDString(const uint8_t deviceUUID[VK_UUID_SIZE]) { for (std::size_t i = 0, n = VK_UUID_SIZE; i < n; ++i) { result += format("%02X", deviceUUID[i]); + if (i == 3 || i == 5 || i == 7) { + result += "-"; + } } return result; diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index a950384a5b..37c6911b72 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -22,6 +22,7 @@ - Fix "discard" layer control tooltip #2595 - Fix "Validation with API Dump log" configuration not loaded - Fix high CPU usage on idle when no layers configuration is active #2603 +- Fix UUID display in log, following the RFC9562 standard ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) From d65f27efe78a63694f6e004f3d9b2bd9eb47e3a3 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 24 Mar 2026 11:40:13 +0100 Subject: [PATCH 12/45] vkconfig: Load all Vulkan drivers from a path --- vkconfig_core/configurator.cpp | 6 +++--- vkconfig_core/configurator.h | 2 +- vkconfig_core/registry.cpp | 4 ++++ vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/tab_drivers.cpp | 21 +++++++++++++-------- vkconfig_gui/tab_layers.cpp | 14 +++++++++++--- vkconfig_gui/tab_preferences.cpp | 2 +- 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index 3e1e6392a6..07541c7d2d 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -1160,8 +1160,8 @@ bool Configurator::Load() { } } - if (json_object.value("last_driver_path") != QJsonValue::Undefined) { - this->last_driver_path = json_object.value("last_driver_path").toString().toStdString(); + if (json_object.value("last_driver_dir") != QJsonValue::Undefined) { + this->last_driver_dir = json_object.value("last_driver_dir").toString().toStdString(); } if (json_object.value("driver_paths") != QJsonValue::Undefined) { const QJsonObject& json_object_paths = json_object.value("driver_paths").toObject(); @@ -1349,7 +1349,7 @@ bool Configurator::Save() const { } json_object.insert("driver_override_list", json_driver_override_list_array); - json_object.insert("last_driver_path", this->last_driver_path.RelativePath().c_str()); + json_object.insert("last_driver_dir", this->last_driver_dir.RelativePath().c_str()); QJsonObject json_object_paths; for (auto it = this->driver_paths.begin(); it != this->driver_paths.end(); ++it) { QJsonObject json_object_path; diff --git a/vkconfig_core/configurator.h b/vkconfig_core/configurator.h index cbabe37081..e351320b7c 100644 --- a/vkconfig_core/configurator.h +++ b/vkconfig_core/configurator.h @@ -169,7 +169,7 @@ class Configurator { TabType active_tab = TAB_CONFIGURATIONS; bool advanced = true; Path last_path_status = Path(Path::HOME).AbsolutePath() + "/diagnostics"; - Path last_driver_path = Path(Path::HOME).AbsolutePath() + "/*.json"; + Path last_driver_dir = Path(Path::HOME).AbsolutePath(); Path last_path_launch_log = Path(Path::HOME).AbsolutePath() + "/application_log.txt"; Version online_sdk_version = Version::NONE; Version latest_sdk_version = Version::NONE; diff --git a/vkconfig_core/registry.cpp b/vkconfig_core/registry.cpp index b4dc2ec65d..4ffb93fe31 100644 --- a/vkconfig_core/registry.cpp +++ b/vkconfig_core/registry.cpp @@ -214,6 +214,10 @@ std::vector LoadRegistrySoftwareLayers(const char *path, LayerTy info.type = type; info.path = path.IsFile() ? path.AbsoluteDir() : path.AbsolutePath(); + if (!path.Exists()) { + continue; + } + if (::Found(result, info.path)) { continue; } diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 37c6911b72..2883004517 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -14,6 +14,7 @@ - Add preferences settings to show "Vulkan Executables Scope" - Add `vkconfig.json` backup when doing a "Reset To Default" #2570 - Add "Discard Ordering and Enabling Layers" default configuration #2596 +- Improve Vulkan drivers loading, add all drivers in a part at once ### Fixes: - Fix command line whitespace decoding #2625 diff --git a/vkconfig_gui/tab_drivers.cpp b/vkconfig_gui/tab_drivers.cpp index c00c5a3cb9..bd5ea20060 100644 --- a/vkconfig_gui/tab_drivers.cpp +++ b/vkconfig_gui/tab_drivers.cpp @@ -135,7 +135,7 @@ void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { this->ui->driver_group_box_paths->blockSignals(false); this->ui->driver_paths_list->clear(); - this->ui->driver_path_lineedit->setText(configurator.last_driver_path.RelativePath().c_str()); + this->ui->driver_path_lineedit->setText(configurator.last_driver_dir.RelativePath().c_str()); this->ui->driver_paths_list->blockSignals(true); for (auto it = configurator.driver_paths.begin(); it != configurator.driver_paths.end(); ++it) { @@ -252,8 +252,8 @@ void TabDrivers::on_driver_append_pressed() { if (!selected_path.Exists()) { QMessageBox alert; - alert.setWindowTitle("Vulkan Driver Manifest file not found"); - alert.setText("The path"); + alert.setWindowTitle("Vulkan Driver Manifest directory not found"); + alert.setText("The directory:"); alert.setInformativeText(selected_path.AbsolutePath().c_str()); alert.setStandardButtons(QMessageBox::Ok); alert.setDefaultButton(QMessageBox::Ok); @@ -262,8 +262,12 @@ void TabDrivers::on_driver_append_pressed() { return; } - configurator.driver_paths.insert(std::pair(selected_path, true)); - configurator.last_driver_path = selected_path; + configurator.last_driver_dir = selected_path; + + const std::vector drivers_paths = ::CollectFilePaths(selected_path); + for (std::size_t i = 0, n = drivers_paths.size(); i < n; ++i) { + configurator.driver_paths.insert(std::pair(drivers_paths[i], true)); + } configurator.UpdateVulkanSystemInfo(); @@ -273,9 +277,10 @@ void TabDrivers::on_driver_append_pressed() { void TabDrivers::on_driver_browse_pressed() { Configurator &configurator = Configurator::Get(); - const Path &selected_path = QFileDialog::getOpenFileName(this->ui->driver_browse_button, "Adding a Driver Manifests File...", - configurator.last_driver_path.AbsolutePath().c_str(), "*.json") - .toStdString(); + const Path &selected_path = + QFileDialog::getExistingDirectory(this->ui->driver_browse_button, "Adding Driver Manifests Directory...", + configurator.last_driver_dir.AbsolutePath().c_str()) + .toStdString(); if (selected_path.Empty()) { return; diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index f30cc9d253..fefd77001e 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -64,6 +64,15 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { std::vector &paths_group = configurator.layers.paths[group_path]; for (std::size_t i = 0, n = paths_group.size(); i < n; ++i) { + const std::string &layer_path = paths_group[i].path.AbsolutePath(); + const std::vector &manifest_paths = CollectFilePaths(layer_path); + /* + if (group_path != LAYERS_PATHS_GUI) { + if (manifest_paths.empty()) { + continue; + } + } + */ QTreeWidgetItem *item_state = new QTreeWidgetItem; item_state->setFlags(item_state->flags() | Qt::ItemIsSelectable); item_state->setSizeHint(0, QSize(0, ITEM_HEIGHT)); @@ -75,9 +84,6 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { ui->layers_paths_tree->addTopLevelItem(item_state); ui->layers_paths_tree->setItemWidget(item_state, 0, layer_path_widget); - const std::string &layer_path = paths_group[i].path.AbsolutePath(); - const std::vector &manifest_paths = CollectFilePaths(layer_path); - for (std::size_t manifest_index = 0, manifest_count = manifest_paths.size(); manifest_index < manifest_count; ++manifest_index) { const Layer *layer = configurator.layers.FindFromManifest(manifest_paths[manifest_index], true); @@ -97,6 +103,8 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { label += format(" (%s)", GetToken(layer->status)); } + // label += " - " + layer->manifest_path.AbsolutePath(); + QTreeWidgetItem *item = new QTreeWidgetItem; item->setText(0, label.c_str()); item->setToolTip(0, layer->manifest_path.AbsolutePath().c_str()); diff --git a/vkconfig_gui/tab_preferences.cpp b/vkconfig_gui/tab_preferences.cpp index a4fb5d44eb..4424fbb397 100644 --- a/vkconfig_gui/tab_preferences.cpp +++ b/vkconfig_gui/tab_preferences.cpp @@ -170,7 +170,7 @@ void TabPreferences::on_theme_mode_changed(int index) { this->ui->configurations_settings_reset->setIcon(::Get(new_theme_mode, ::ICON_RELOAD)); // Drivers - this->ui->driver_browse_button->setIcon(::Get(new_theme_mode, ::ICON_FILE_SEARCH)); + this->ui->driver_browse_button->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_SEARCH)); // Layers this->ui->layers_browse_button->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_SEARCH)); From f40d4311b0ed698ff82ea0b75e108a9591be6b6b Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 24 Mar 2026 16:19:34 +0100 Subject: [PATCH 13/45] vkconfig: Refactor layers tab and improve UI consistency --- vkconfig_cmd/main_layers.cpp | 25 +- vkconfig_core/configuration.cpp | 2 +- vkconfig_core/json.cpp | 10 +- vkconfig_core/layer.cpp | 32 +- vkconfig_core/layer.h | 23 +- vkconfig_core/layer_manager.cpp | 473 ++- vkconfig_core/layer_manager.h | 33 +- vkconfig_core/registry.cpp | 26 +- vkconfig_core/registry.h | 4 +- vkconfig_core/test/test_configuration.cpp | 46 +- .../test/test_configuration_built_in.cpp | 10 +- .../test/test_configuration_manager.cpp | 26 +- vkconfig_core/test/test_generate_settings.cpp | 3 +- vkconfig_core/test/test_layer.cpp | 32 +- vkconfig_core/test/test_layer_manager.cpp | 136 +- vkconfig_core/test/test_layer_preset.cpp | 10 +- vkconfig_core/test/test_parameter.cpp | 113 +- vkconfig_core/type_layers_paths.cpp | 18 +- vkconfig_core/type_layers_paths.h | 2 + vkconfig_core/type_tab.cpp | 2 +- vkconfig_gui/CHANGELOG.md | 7 + vkconfig_gui/mainwindow.cpp | 32 + vkconfig_gui/mainwindow.ui | 2748 +++++++++-------- vkconfig_gui/tab_applications.cpp | 12 +- vkconfig_gui/tab_configurations.cpp | 16 +- vkconfig_gui/tab_configurations.h | 1 + vkconfig_gui/tab_diagnostics.cpp | 27 +- vkconfig_gui/tab_diagnostics.h | 1 + vkconfig_gui/tab_drivers.cpp | 107 +- vkconfig_gui/tab_drivers.h | 7 + vkconfig_gui/tab_layers.cpp | 170 +- vkconfig_gui/tab_layers.h | 7 +- vkconfig_gui/tab_preferences.cpp | 15 +- vkconfig_gui/tab_preferences.h | 1 + vkconfig_gui/widget_tab_driver_path.cpp | 4 +- vkconfig_gui/widget_tab_layers_path.cpp | 51 +- vkconfig_gui/widget_tab_layers_path.h | 9 +- 37 files changed, 2207 insertions(+), 2034 deletions(-) diff --git a/vkconfig_cmd/main_layers.cpp b/vkconfig_cmd/main_layers.cpp index ad3ba9995b..674844bed7 100644 --- a/vkconfig_cmd/main_layers.cpp +++ b/vkconfig_cmd/main_layers.cpp @@ -87,22 +87,21 @@ static int RunLayersSurrender(Configurator& configurator, const CommandLine& com } static int RunLayersPath(Configurator& configurator, const CommandLine& command_line) { - printf("vkconfig: [INFO] Paths to find Vulkan Layers\n"); + printf("vkconfig: [INFO] Vulkan Layers paths:\n"); - for (int layers_paths_index = 0, layers_paths_count = LAYERS_PATHS_COUNT; layers_paths_index < layers_paths_count; - ++layers_paths_index) { - const std::vector& paths = configurator.layers.paths[layers_paths_index]; - printf("\n%s:\n", GetLabel(static_cast(layers_paths_index))); + const std::set& layer_display_list = configurator.layers.BuildLayerDisplayList(); - if (paths.empty()) { - printf(" - None\n"); - } else { - for (std::size_t i = 0, n = paths.size(); i < n; ++i) { - if (paths[i].enabled) { - printf(" - %s\n", paths[i].path.AbsolutePath().c_str()); - } - } + for (auto it = layer_display_list.begin(), end = layer_display_list.end(); it != end; ++it) { + const Layer* layer = configurator.layers.FindFromManifest(it->manifest_path, true); + if (layer == nullptr) { + continue; } + + const std::string status = layer->status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer->status)); + const std::string text = format("%s - %s%s, %s layer", layer->key.c_str(), layer->api_version.str().c_str(), status.c_str(), + ::GetToken(layer->type)); + + printf(" - %s\n", text.c_str()); } return 0; diff --git a/vkconfig_core/configuration.cpp b/vkconfig_core/configuration.cpp index 7656778023..2bbb65f16b 100644 --- a/vkconfig_core/configuration.cpp +++ b/vkconfig_core/configuration.cpp @@ -173,7 +173,7 @@ bool Configuration::Load(const Path& full_path, const LayerManager& layers) { if (layer != nullptr) { parameter.manifest = layer->manifest_path; - parameter.type = layer->type; + // parameter.type = layer->type; } if (parameter.api_version != Version::LATEST) { diff --git a/vkconfig_core/json.cpp b/vkconfig_core/json.cpp index 044079cf93..20af9301f7 100644 --- a/vkconfig_core/json.cpp +++ b/vkconfig_core/json.cpp @@ -25,12 +25,12 @@ #include -QJsonDocument ParseJsonFile(const char* file) { - QFile file_schema(file); - const bool result = file_schema.open(QIODevice::ReadOnly | QIODevice::Text); +QJsonDocument ParseJsonFile(const char* path) { + QFile file(path); + const bool result = file.open(QIODevice::ReadOnly | QIODevice::Text); if (result) { - const QString& data = file_schema.readAll(); - file_schema.close(); + const QString& data = file.readAll(); + file.close(); QJsonParseError json_parse_error; const QJsonDocument& json_document = QJsonDocument::fromJson(data.toUtf8(), &json_parse_error); diff --git a/vkconfig_core/layer.cpp b/vkconfig_core/layer.cpp index 919eb811bf..a1cb6238e9 100644 --- a/vkconfig_core/layer.cpp +++ b/vkconfig_core/layer.cpp @@ -49,11 +49,9 @@ #include #include -bool operator<(const LayersPathInfo& a, const LayersPathInfo& b) { return a.path.RelativePath() < b.path.RelativePath(); } - -bool Found(const std::vector& data, const Path& path) { +bool Found(const std::vector& data, const Path& path) { for (std::size_t i = 0, n = data.size(); i < n; ++i) { - if (data[i].path == path) { + if (data[i] == path) { return true; } } @@ -179,7 +177,7 @@ void Layer::FillPresetSettings(SettingDataSet& settings_data, const std::vector< } LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool request_validate_manifest, - const std::map& layers_found, ConfiguratorMode configurator_mode) { + ConfiguratorMode configurator_mode) { this->type = type; // Set layer type, no way to know this from the json file if (full_path_to_file.Empty()) { @@ -197,13 +195,6 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool this->manifest_path = full_path_to_file; this->last_modified = full_path_to_file.LastModified(); - auto it = layers_found.find(full_path_to_file.AbsolutePath().c_str()); - if (it != layers_found.end()) { - if (it->second.disabled && it->second.last_modified == this->last_modified) { - return LAYER_LOAD_FAILED; - } - } - // Convert the text to a JSON document & validate it. // It does need to be a valid json formatted file. QJsonParseError json_parse_error; @@ -252,7 +243,7 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool this->key = ReadStringValue(json_layer_object, "name"); - if (this->key == "VK_LAYER_LUNARG_override") { + if (this->key == "VK_LAYER_LUNARG_override" || !(this->key.rfind("VK_", 0) == 0)) { return LAYER_LOAD_IGNORED; } @@ -260,12 +251,7 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool JsonValidator validator; - std::string cached_last_modified; - if (it != layers_found.end()) { - cached_last_modified = it->second.last_modified; - } - const bool should_validate = request_validate_manifest && ((last_modified != cached_last_modified) || !it->second.validated); - const bool is_valid = should_validate ? validator.Check(json_text) : true; + const bool is_valid = request_validate_manifest ? validator.Check(json_text) : true; if (!is_valid) { switch (configurator_mode) { @@ -407,6 +393,14 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool return this->IsValid() ? LAYER_LOAD_ADDED : LAYER_LOAD_INVALID; // Not all JSON file are layer JSON valid } +bool operator<(const Layer& layer_a, const Layer& layer_b) { + if (layer_a.key == layer_b.key) { + return layer_a.api_version < layer_b.api_version; + } else { + return layer_a.key < layer_b.key; + } +} + void CollectDefaultSettingData(const SettingMetaSet& meta_set, SettingDataSet& data_set) { for (std::size_t i = 0, n = meta_set.size(); i < n; ++i) { SettingMeta* setting_meta = meta_set[i]; diff --git a/vkconfig_core/layer.h b/vkconfig_core/layer.h index 8380149405..c4b64734b2 100644 --- a/vkconfig_core/layer.h +++ b/vkconfig_core/layer.h @@ -36,21 +36,15 @@ #include #include -struct LayerStatus { +struct LayerDescriptor { + LayerType type = LAYER_TYPE_EXPLICIT; std::string last_modified; bool validated = false; - bool disabled = false; -}; - -struct LayersPathInfo { - Path path; - LayerType type = LAYER_TYPE_EXPLICIT; bool enabled = true; + bool added = false; }; -bool operator<(const LayersPathInfo& a, const LayersPathInfo& b); - -bool Found(const std::vector& data, const Path& path); +bool Found(const std::vector& data, const Path& path); enum LayerLoadStatus { LAYER_LOAD_ADDED = 0, @@ -64,8 +58,8 @@ enum LayerLoadStatus { LAYER_LOAD_LAST = LAYER_LOAD_IGNORED, }; -inline bool IsDisabled(LayerLoadStatus status) { - return status == LAYER_LOAD_FAILED || status == LAYER_LOAD_INVALID || status == LAYER_LOAD_IGNORED; +inline bool IsEnabled(LayerLoadStatus status) { + return !(status == LAYER_LOAD_FAILED || status == LAYER_LOAD_INVALID || status == LAYER_LOAD_IGNORED); } enum { LAYER_LOAD_COUNT = LAYER_LOAD_LAST - LAYER_LOAD_FIRST + 1 }; @@ -115,13 +109,12 @@ class Layer { std::string enable_env; std::string enable_value; bool is_32bits = false; - bool enabled = true; std::vector settings; std::vector presets; LayerLoadStatus Load(const Path& full_path_to_file, LayerType type, bool request_validate_manifest, - const std::map& layers_found, ConfiguratorMode configurator_mode); + ConfiguratorMode configurator_mode); private: Layer& operator=(const Layer&) = delete; @@ -129,4 +122,6 @@ class Layer { std::vector > memory; // Settings are deleted when all layers instances are deleted. }; +bool operator<(const Layer& layer_a, const Layer& layer_b); + void CollectDefaultSettingData(const SettingMetaSet& meta_set, SettingDataSet& data_set); diff --git a/vkconfig_core/layer_manager.cpp b/vkconfig_core/layer_manager.cpp index 84ac360960..8c5e3cdb0b 100644 --- a/vkconfig_core/layer_manager.cpp +++ b/vkconfig_core/layer_manager.cpp @@ -25,36 +25,34 @@ #include -std::vector GetEnvVariablePaths(const char *variable_name, LayerType type) { - std::vector result; +static std::vector GetEnvVariablePaths(const char *variable_name, LayerType type) { + std::vector result; const char *SEPARATOR = GetToken(PARSE_ENV_VAR); const std::vector &paths = UniqueStrings(Split(qgetenv(variable_name).toStdString(), SEPARATOR)); result.resize(paths.size()); for (std::size_t i = 0, n = paths.size(); i < n; ++i) { - result[i].path = paths[i]; - result[i].enabled = true; - result[i].type = type; + result[i] = paths[i]; } return result; } -std::vector GetImplicitLayerPaths() { - std::vector result; +static std::vector GetImplicitLayerPaths() { + std::vector result; #if VKC_ENV == VKC_ENV_WIN32 - const std::vector &admin_registry_paths = + const std::vector &admin_registry_paths = LoadRegistrySoftwareLayers("HKEY_LOCAL_MACHINE\\Software\\Khronos\\Vulkan\\ImplicitLayers", LAYER_TYPE_IMPLICIT); result.insert(result.begin(), admin_registry_paths.begin(), admin_registry_paths.end()); - const std::vector &user_registry_paths = + const std::vector &user_registry_paths = LoadRegistrySoftwareLayers("HKEY_CURRENT_USER\\Software\\Khronos\\Vulkan\\ImplicitLayers", LAYER_TYPE_IMPLICIT); result.insert(result.begin(), user_registry_paths.begin(), user_registry_paths.end()); // Search for drivers specific layers - const std::vector &drivers_registry_paths = + const std::vector &drivers_registry_paths = LoadRegistrySystemLayers("HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Class\\...\\VulkanImplicitLayers"); result.insert(result.begin(), drivers_registry_paths.begin(), drivers_registry_paths.end()); #else @@ -89,32 +87,25 @@ std::vector GetImplicitLayerPaths() { paths.push_back(LAYERS_PATHS[i]); } } - - for (std::size_t i = 0, n = paths.size(); i < n; ++i) { - LayersPathInfo info; - info.type = LAYER_TYPE_IMPLICIT; - info.path = paths[i]; - result.push_back(info); - } #endif return result; } -std::vector GetExplicitLayerPaths() { - std::vector result; +std::vector GetExplicitLayerPaths() { + std::vector result; #if VKC_ENV == VKC_ENV_WIN32 - const std::vector &admin_registry_paths = + const std::vector &admin_registry_paths = LoadRegistrySoftwareLayers("HKEY_LOCAL_MACHINE\\Software\\Khronos\\Vulkan\\ExplicitLayers", LAYER_TYPE_EXPLICIT); result.insert(result.begin(), admin_registry_paths.begin(), admin_registry_paths.end()); - const std::vector &user_registry_paths = + const std::vector &user_registry_paths = LoadRegistrySoftwareLayers("HKEY_CURRENT_USER\\Software\\Khronos\\Vulkan\\ExplicitLayers", LAYER_TYPE_EXPLICIT); result.insert(result.begin(), user_registry_paths.begin(), user_registry_paths.end()); // Search for drivers specific layers - const std::vector &drivers_registry_paths = + const std::vector &drivers_registry_paths = LoadRegistrySystemLayers("HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Class\\...\\VulkanExplicitLayers"); result.insert(result.begin(), drivers_registry_paths.begin(), drivers_registry_paths.end()); #else @@ -149,39 +140,23 @@ std::vector GetExplicitLayerPaths() { paths.push_back(LAYERS_PATHS[i]); } } - - for (std::size_t i = 0, n = paths.size(); i < n; ++i) { - LayersPathInfo info; - info.type = LAYER_TYPE_EXPLICIT; - info.path = paths[i]; - result.push_back(info); - } #endif return result; } -static LayersPathInfo *FindPathInfo(std::array, LAYERS_PATHS_COUNT> &paths, const std::string &path) { - for (int paths_type_index = LAYERS_PATHS_FIRST; paths_type_index <= LAYERS_PATHS_LAST; ++paths_type_index) { - for (std::size_t i = 0, n = paths[paths_type_index].size(); i < n; ++i) { - if (paths[paths_type_index][i].path == path) { - return &paths[paths_type_index][i]; - } - } - } - - return nullptr; -} - -LayerManager::LayerManager() { this->InitSystemPaths(); } +LayerManager::LayerManager() {} bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode configurator_mode) { + this->available_layers.clear(); + this->layers_found.clear(); + // LAYERS_PATHS_GUI if (json_root_object.value("layers") != QJsonValue::Undefined) { const QJsonObject &json_layers_object = json_root_object.value("layers").toObject(); - if (json_layers_object.value("last_layers_path") != QJsonValue::Undefined) { - this->last_layers_path = json_layers_object.value("last_layers_path").toString().toStdString(); + if (json_layers_object.value("last_layers_dir") != QJsonValue::Undefined) { + this->last_layers_dir = json_layers_object.value("last_layers_dir").toString().toStdString(); } if (json_layers_object.value("validate_manifests") != QJsonValue::Undefined) { @@ -195,26 +170,26 @@ bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode co for (int i = 0, n = json_layers_found_keys.length(); i < n; ++i) { const QJsonObject &json_status_object = json_layers_found_object.value(json_layers_found_keys[i]).toObject(); - LayerStatus layer_status; - layer_status.last_modified = json_status_object.value("last_modified").toString().toStdString(); - layer_status.validated = json_status_object.value("validated").toBool(); - layer_status.disabled = json_status_object.value("disabled").toBool(); + LayerDescriptor descriptor; + if (json_status_object.value("type") != QJsonValue::Undefined) { + descriptor.type = ::GetLayerType(json_status_object.value("type").toString().toStdString().c_str()); + } + descriptor.last_modified = json_status_object.value("last_modified").toString().toStdString(); + descriptor.validated = json_status_object.value("validated").toBool(); + if (json_status_object.value("enabled") != QJsonValue::Undefined) { + descriptor.enabled = json_status_object.value("enabled").toBool(); + } const Path &manifest_path = json_layers_found_keys[i].toStdString(); - this->layers_found.insert(std::make_pair(manifest_path, layer_status)); + this->layers_found.insert(std::make_pair(manifest_path, descriptor)); } } - if (json_layers_object.value("paths") != QJsonValue::Undefined) { - const QJsonObject &json_paths_object = json_layers_object.value("paths").toObject(); - const QStringList &json_paths_keys = json_paths_object.keys(); - - for (int i = 0, n = json_paths_keys.length(); i < n; ++i) { - LayersPathInfo info; - info.path = json_paths_keys[i].toStdString(); - info.enabled = json_paths_object.value(json_paths_keys[i].toStdString().c_str()).toBool(); - this->AppendPath(info); + if (json_layers_object.value("removed") != QJsonValue::Undefined) { + const QJsonArray &array = json_layers_object.value("removed").toArray(); + for (int i = 0, n = array.size(); i < n; ++i) { + this->layers_removed.insert(array[i].toString().toStdString()); } } } @@ -225,30 +200,26 @@ bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode co } bool LayerManager::Save(QJsonObject &json_root_object) const { - QJsonObject json_layers_status_object; + QJsonObject json_layers_found_object; for (auto it = this->layers_found.begin(); it != this->layers_found.end(); ++it) { - QJsonObject json_layer_status_object; - json_layer_status_object.insert("last_modified", it->second.last_modified.c_str()); - json_layer_status_object.insert("validated", it->second.validated); - json_layer_status_object.insert("disabled", it->second.disabled); - json_layers_status_object.insert(it->first.AbsolutePath().c_str(), json_layer_status_object); + QJsonObject object; + object.insert("last_modified", it->second.last_modified.c_str()); + object.insert("type", ::GetToken(it->second.type)); + object.insert("validated", it->second.validated); + object.insert("enabled", it->second.enabled); + json_layers_found_object.insert(it->first.AbsolutePath().c_str(), object); } - QJsonObject json_paths_object; - for (int paths_type_index = LAYERS_PATHS_FIRST; paths_type_index <= LAYERS_PATHS_LAST; ++paths_type_index) { - const std::vector &path_infos = this->paths[paths_type_index]; - - for (std::size_t i = 0, n = path_infos.size(); i < n; ++i) { - json_paths_object.insert(path_infos[i].path.RelativePath().c_str(), path_infos[i].enabled); - } + QJsonArray json_layers_removed_array; + for (auto it = this->layers_removed.begin(); it != this->layers_removed.end(); ++it) { + json_layers_removed_array.append(it->AbsolutePath().c_str()); } QJsonObject json_layers_object; json_layers_object.insert("validate_manifests", this->validate_manifests); - json_layers_object.insert("last_layers_path", this->last_layers_path.RelativePath().c_str()); - json_layers_object.insert("found", json_layers_status_object); - json_layers_object.insert("paths", json_paths_object); - + json_layers_object.insert("last_layers_dir", this->last_layers_dir.RelativePath().c_str()); + json_layers_object.insert("found", json_layers_found_object); + json_layers_object.insert("removed", json_layers_removed_array); json_root_object.insert("layers", json_layers_object); return true; @@ -257,48 +228,43 @@ bool LayerManager::Save(QJsonObject &json_root_object) const { std::string LayerManager::Log() const { std::string log; - for (std::size_t group_index = 0, group_count = this->paths.size(); group_index < group_count; ++group_index) { - const std::vector &paths_group = this->paths[group_index]; - if (paths_group.empty()) { - log += format(" %d. %s paths:\n", group_index + 1, ::GetLabel(static_cast(group_index))); - log += format(" - None\n"); + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + const Layer *layer = &this->available_layers[i]; + + auto it = this->layers_found.find(layer->manifest_path); + if (it == this->layers_found.end()) { + assert(0); + continue; + } + + const LayerDescriptor &descriptor = it->second; + + if (descriptor.type == LAYER_TYPE_IMPLICIT) { + log += format(" * %s - %s (Auto: %s)", layer->key.c_str(), layer->api_version.str().c_str(), + GetLabel(layer->GetActualControl())); } else { - log += format(" %d. %s paths:\n", group_index + 1, ::GetLabel(static_cast(group_index))); + log += format(" * %s - %s", layer->key.c_str(), layer->api_version.str().c_str()); } - for (std::size_t path_index = 0, path_count = paths_group.size(); path_index < path_count; ++path_index) { - log += format(" - %s (%s)\n", paths_group[path_index].path.AbsolutePath().c_str(), - paths_group[path_index].enabled ? "Enabled" : "Disabled"); + if (layer->status != STATUS_STABLE) { + log += format(" (%s)", GetToken(layer->status)); + } + log += "\n"; - const std::vector layers = this->GatherLayers(paths_group[path_index]); + log += format(" %s\n", layer->manifest_path.AbsolutePath().c_str()); + log += "\n"; - for (std::size_t i = 0, n = layers.size(); i < n; ++i) { - if (layers[i]->type == LAYER_TYPE_IMPLICIT) { - log += format(" * %s - %s (Auto: %s)", layers[i]->key.c_str(), layers[i]->api_version.str().c_str(), - GetLabel(layers[i]->GetActualControl())); + if (descriptor.type == LAYER_TYPE_IMPLICIT) { + if (!layer->disable_env.empty()) { + const std::string &value = qEnvironmentVariableIsSet(layer->disable_env.c_str()) ? "set" : "not set"; + log += format(" '%s' is %s\n", layer->disable_env.c_str(), value.c_str()); + } + if (!layer->enable_env.empty()) { + if (qEnvironmentVariableIsSet(layer->enable_env.c_str())) { + const std::string &value = qgetenv(layer->enable_env.c_str()).toStdString(); + log += format(" '%s' is set to '%s'\n", layer->enable_env.c_str(), value.c_str()); } else { - log += format(" * %s - %s", layers[i]->key.c_str(), layers[i]->api_version.str().c_str()); - } - - if (layers[i]->status != STATUS_STABLE) { - log += format(" (%s)", GetToken(layers[i]->status)); - } - log += "\n"; - - if (layers[i]->type == LAYER_TYPE_IMPLICIT) { - if (!layers[i]->disable_env.empty()) { - const std::string &value = qEnvironmentVariableIsSet(layers[i]->disable_env.c_str()) ? "set" : "not set"; - log += format(" '%s' is %s\n", layers[i]->disable_env.c_str(), value.c_str()); - } - if (!layers[i]->enable_env.empty()) { - if (qEnvironmentVariableIsSet(layers[i]->enable_env.c_str())) { - const std::string &value = qgetenv(layers[i]->enable_env.c_str()).toStdString(); - log += format(" '%s' is set to '%s'\n", layers[i]->enable_env.c_str(), value.c_str()); - } else { - log += format(" '%s' is not set to '%s'\n", layers[i]->enable_env.c_str(), - layers[i]->enable_value.c_str()); - } - } + log += format(" '%s' is not set to '%s'\n", layer->enable_env.c_str(), layer->enable_value.c_str()); } } } @@ -309,57 +275,25 @@ std::string LayerManager::Log() const { return log; } -void LayerManager::InitSystemPaths() { - this->available_layers.clear(); - this->layers_found.clear(); - - this->paths[LAYERS_PATHS_IMPLICIT_SYSTEM] = GetImplicitLayerPaths(); - - // LAYERS_PATHS_IMPLICIT_ENV_SET: VK_IMPLICIT_LAYER_PATH env variables - this->paths[LAYERS_PATHS_IMPLICIT_ENV_SET] = GetEnvVariablePaths("VK_IMPLICIT_LAYER_PATH", LAYER_TYPE_IMPLICIT); - - // LAYERS_PATHS_IMPLICIT_ENV_ADD: VK_ADD_IMPLICIT_LAYER_PATH env variables - this->paths[LAYERS_PATHS_IMPLICIT_ENV_ADD] = GetEnvVariablePaths("VK_ADD_IMPLICIT_LAYER_PATH", LAYER_TYPE_IMPLICIT); - - // LAYERS_PATHS_EXPLICIT_SYSTEM - this->paths[LAYERS_PATHS_EXPLICIT_SYSTEM] = GetExplicitLayerPaths(); - - // LAYERS_PATHS_EXPLICIT_ENV_SET: VK_LAYER_PATH env variables - this->paths[LAYERS_PATHS_EXPLICIT_ENV_SET] = GetEnvVariablePaths("VK_LAYER_PATH", LAYER_TYPE_EXPLICIT); - - // LAYERS_PATHS_EXPLICIT_ENV_ADD: VK_ADD_LAYER_PATH env variables - this->paths[LAYERS_PATHS_EXPLICIT_ENV_ADD] = GetEnvVariablePaths("VK_ADD_LAYER_PATH", LAYER_TYPE_EXPLICIT); - - // LAYERS_PATHS_GUI - this->paths[LAYERS_PATHS_GUI].clear(); - - // LAYERS_PATHS_SDK - this->paths[LAYERS_PATHS_SDK].clear(); - { - LayersPathInfo info; - info.path = Path(Path::SDK_EXPLICIT_LAYERS); - info.enabled = true; - this->paths[LAYERS_PATHS_SDK].push_back(info); - } -} - void LayerManager::Clear() { this->available_layers.clear(); } bool LayerManager::Empty() const { return this->available_layers.empty(); } std::size_t LayerManager::Size() const { return this->available_layers.size(); } -std::vector LayerManager::GatherManifests(const std::string &layer_name) const { +std::vector LayerManager::GatherManifests(const std::string &layer_key) const { std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (!this->available_layers[i].enabled) { + if (this->available_layers[i].key != layer_key) { continue; } - if (this->available_layers[i].key == layer_name) { - result.push_back(this->available_layers[i].manifest_path); + if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + continue; } + + result.push_back(this->available_layers[i].manifest_path); } std::sort(result.rbegin(), result.rend()); @@ -367,17 +301,19 @@ std::vector LayerManager::GatherManifests(const std::string &layer_name) c return result; } -std::vector LayerManager::GatherVersions(const std::string &layer_name) const { +std::vector LayerManager::GatherVersions(const std::string &layer_key) const { std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (!this->available_layers[i].enabled) { + if (this->available_layers[i].key != layer_key) { continue; } - if (this->available_layers[i].key == layer_name) { - result.push_back(this->available_layers[i].api_version); + if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + continue; } + + result.push_back(this->available_layers[i].api_version); } std::sort(result.rbegin(), result.rend()); @@ -422,15 +358,17 @@ const Layer *LayerManager::FindLastModified(const std::string &layer_name, const const Layer *result = nullptr; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (this->available_layers[i].enabled == false) { - continue; - } if (this->available_layers[i].key != layer_name) { continue; } if (this->available_layers[i].api_version != version) { continue; } + + if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + continue; + } + if (result != nullptr) { if (result->last_modified > this->available_layers[i].last_modified) { continue; @@ -445,8 +383,10 @@ const Layer *LayerManager::FindLastModified(const std::string &layer_name, const const Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disabled_layers) const { for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (!find_disabled_layers && this->available_layers[i].enabled == false) { - continue; + if (!find_disabled_layers) { + if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + continue; + } } if (this->available_layers[i].manifest_path == manifest_path) { @@ -458,8 +398,10 @@ const Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disabled_layers) { for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (!find_disabled_layers && this->available_layers[i].enabled == false) { - continue; + if (!find_disabled_layers) { + if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + continue; + } } if (this->available_layers[i].manifest_path == manifest_path) { @@ -471,24 +413,44 @@ Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disab // Find all installed layers on the system. void LayerManager::LoadAllInstalledLayers(ConfiguratorMode configurator_mode) { - this->available_layers.clear(); + std::array, LAYERS_PATHS_COUNT> paths; + + // Search new layers + paths[LAYERS_PATHS_IMPLICIT_SYSTEM] = GetImplicitLayerPaths(); + + // LAYERS_PATHS_IMPLICIT_ENV_SET: VK_IMPLICIT_LAYER_PATH env variables + paths[LAYERS_PATHS_IMPLICIT_ENV_SET] = GetEnvVariablePaths("VK_IMPLICIT_LAYER_PATH", LAYER_TYPE_IMPLICIT); + + // LAYERS_PATHS_IMPLICIT_ENV_ADD: VK_ADD_IMPLICIT_LAYER_PATH env variables + paths[LAYERS_PATHS_IMPLICIT_ENV_ADD] = GetEnvVariablePaths("VK_ADD_IMPLICIT_LAYER_PATH", LAYER_TYPE_IMPLICIT); + + // LAYERS_PATHS_EXPLICIT_SYSTEM + paths[LAYERS_PATHS_EXPLICIT_SYSTEM] = GetExplicitLayerPaths(); + + // LAYERS_PATHS_EXPLICIT_ENV_SET: VK_LAYER_PATH env variables + paths[LAYERS_PATHS_EXPLICIT_ENV_SET] = GetEnvVariablePaths("VK_LAYER_PATH", LAYER_TYPE_EXPLICIT); + + // LAYERS_PATHS_EXPLICIT_ENV_ADD: VK_ADD_LAYER_PATH env variables + paths[LAYERS_PATHS_EXPLICIT_ENV_ADD] = GetEnvVariablePaths("VK_ADD_LAYER_PATH", LAYER_TYPE_EXPLICIT); + + // LAYERS_PATHS_SDK + paths[LAYERS_PATHS_SDK].push_back(Path(Path::SDK_EXPLICIT_LAYERS)); - for (std::size_t group_index = 0, group_count = this->paths.size(); group_index < group_count; ++group_index) { + for (std::size_t group_index = 0, group_count = paths.size(); group_index < group_count; ++group_index) { const LayersPaths layers_path = static_cast(group_index); - const std::vector &paths_group = this->paths[group_index]; + const std::vector &paths_group = paths[group_index]; for (std::size_t i = 0, n = paths_group.size(); i < n; ++i) { - this->LoadLayersFromPath(paths_group[i].path, paths_group[i].type, configurator_mode); - this->UpdatePathEnabled(paths_group[i], layers_path); + const std::vector &layers_paths = ::CollectFilePaths(paths_group[i]); + + for (std::size_t p = 0, o = layers_paths.size(); p < o; ++p) { + this->AppendPath(layers_paths[p], ::GetLayerType(layers_path)); + } } } -} - -void LayerManager::LoadLayersFromPath(const Path &layers_path, LayerType type, ConfiguratorMode configurator_mode) { - const std::vector &layers_paths = CollectFilePaths(layers_path); - for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - this->LoadLayer(layers_paths[i], type, configurator_mode); + for (auto it = this->layers_found.begin(); it != this->layers_found.end(); ++it) { + this->LoadLayer(it->first, it->second.type, configurator_mode); } } @@ -506,134 +468,81 @@ LayerLoadStatus LayerManager::LoadLayer(const Path &layer_path, LayerType type, } // Modified to reload - LayerLoadStatus status = - already_loaded_layer->Load(layer_path, type, this->validate_manifests, this->layers_found, configurator_mode); + LayerLoadStatus status = already_loaded_layer->Load(layer_path, type, this->validate_manifests, configurator_mode); if (status == LAYER_LOAD_ADDED) { it->second.last_modified = already_loaded_layer->last_modified; return LAYER_LOAD_RELOADED; } else { - it->second.disabled = IsDisabled(status); + // it->second.enabled = ::IsEnabled(status); return status; } } else { Layer layer; - LayerLoadStatus status = layer.Load(layer_path, type, this->validate_manifests, this->layers_found, configurator_mode); + LayerLoadStatus status = layer.Load(layer_path, type, this->validate_manifests, configurator_mode); if (status == LAYER_LOAD_ADDED) { this->available_layers.push_back(layer); } auto it = this->layers_found.find(layer_path); + // assert(it != this->layers_found.end()); if (it != layers_found.end()) { - it->second.disabled = IsDisabled(status); - it->second.validated = this->validate_manifests && !it->second.disabled; + // it->second.enabled = ::IsEnabled(status); + // it->second.validated = this->validate_manifests && it->second.enabled; it->second.last_modified = layer.last_modified; } else { - LayerStatus found; - found.disabled = IsDisabled(status); - found.validated = this->validate_manifests && !found.disabled; - found.last_modified = layer.last_modified; - this->layers_found.insert(std::make_pair(layer.manifest_path, found)); + LayerDescriptor descriptor; + descriptor.type = layer.type; + descriptor.last_modified = layer.last_modified; + descriptor.validated = this->validate_manifests; + descriptor.enabled = true; + descriptor.added = true; + layers_found.insert(std::make_pair(layer.manifest_path, descriptor)); } return status; } } -bool LayerManager::AreLayersEnabled(const LayersPathInfo &path_info) const { - for (int paths_type_index = LAYERS_PATHS_FIRST; paths_type_index <= LAYERS_PATHS_LAST; ++paths_type_index) { - for (std::size_t i = 0, n = this->paths[paths_type_index].size(); i < n; ++i) { - if (this->paths[paths_type_index][i].path == path_info.path) { - if (this->paths[paths_type_index][i].enabled) { - return true; // If one path is enabled, then the layer remains enable - } - } +void LayerManager::AppendPath(const Path &path, LayerType type, bool added) { + if (added) { + auto it_removed = this->layers_removed.find(path); + if (it_removed != this->layers_removed.end()) { + this->layers_removed.erase(it_removed); } } - return false; -} - -void LayerManager::AppendPath(const LayersPathInfo &info) { - LayersPathInfo *existing_info = FindPathInfo(this->paths, info.path.RelativePath()); - if (existing_info != nullptr) { - existing_info->enabled = info.enabled; - } else { - this->paths[LAYERS_PATHS_GUI].push_back(info); + if (this->layers_removed.find(path) != this->layers_removed.end()) { + return; } - std::sort(this->paths[LAYERS_PATHS_GUI].begin(), this->paths[LAYERS_PATHS_GUI].end()); -} - -void LayerManager::RemovePath(const LayersPathInfo &path_info) { - const std::vector &layers_paths = CollectFilePaths(path_info.path); - - for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - Layer *layer = this->FindFromManifest(layers_paths[i]); - if (layer == nullptr) { - continue; - } - - layer->enabled = false; - } - - for (int paths_type_index = LAYERS_PATHS_FIRST; paths_type_index <= LAYERS_PATHS_LAST; ++paths_type_index) { - std::vector new_path_list; - for (std::size_t i = 0, n = this->paths[paths_type_index].size(); i < n; ++i) { - if (path_info.path == this->paths[paths_type_index][i].path) { - continue; - } - - new_path_list.push_back(this->paths[paths_type_index][i]); - } - this->paths[paths_type_index] = new_path_list; - } -} - -void LayerManager::UpdatePathEnabled(const LayersPathInfo &path_info, LayersPaths paths_type_index) { - for (std::size_t i = 0, n = this->paths[paths_type_index].size(); i < n; ++i) { - if (path_info.path == this->paths[paths_type_index][i].path) { - this->paths[paths_type_index][i].enabled = path_info.enabled; - break; - } - } - - this->UpdateLayersEnabled(path_info); -} - -void LayerManager::UpdateLayersEnabled(const LayersPathInfo &path_info) { - const bool are_enabled = this->AreLayersEnabled(path_info); - - const std::vector &layers_paths = ::CollectFilePaths(path_info.path); - - for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - Layer *layer = this->FindFromManifest(layers_paths[i], true); - if (layer == nullptr) { - continue; - } - - layer->enabled = are_enabled; + auto it = this->layers_found.find(path); + if (it == this->layers_found.end()) { + LayerDescriptor descriptor; + descriptor.type = type; + descriptor.added = added; + descriptor.last_modified = path.LastModified(); + this->layers_found.insert(std::make_pair(path, descriptor)); } } -std::vector LayerManager::CollectManifestPaths() const { - std::vector results; - - for (int paths_type_index = LAYERS_PATHS_FIRST; paths_type_index <= LAYERS_PATHS_LAST; ++paths_type_index) { - for (std::size_t i = 0, n = this->paths[paths_type_index].size(); i < n; ++i) { - const std::vector &layers_paths = ::CollectFilePaths(this->paths[paths_type_index][i].path); - results.insert(results.end(), layers_paths.begin(), layers_paths.end()); - } +void LayerManager::RemovePath(const Path &path) { + auto it_found = this->layers_found.find(path); + if (it_found != this->layers_found.end()) { + this->layers_found.erase(it_found); } - return results; + this->layers_removed.insert(path); } std::vector LayerManager::GatherLayerNames() const { std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (this->available_layers[i].enabled == false) { - continue; + auto it = this->layers_found.find(this->available_layers[i].manifest_path); + if (it != this->layers_found.end()) { + if (!it->second.enabled) { + continue; + } } if (std::find(result.begin(), result.end(), this->available_layers[i].key) != result.end()) { @@ -646,17 +555,45 @@ std::vector LayerManager::GatherLayerNames() const { return result; } -std::vector LayerManager::GatherLayers(const LayersPathInfo &path_info) const { - std::vector result; +bool LayerManager::IsEnabled(const Path &manifest_path) const { + auto it = this->layers_found.find(manifest_path); + if (it != this->layers_found.end()) { + return it->second.enabled; + } + return false; +} - for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - const std::string &layer_path = path_info.path.AbsolutePath(); - const std::string ¤t_layer_path = this->available_layers[i].manifest_path.AbsolutePath(); - if (current_layer_path.find(layer_path) == std::string::npos) { +void LayerManager::Enable(const Path &manifest_path, bool enabled) { + auto it = this->layers_found.find(manifest_path); + if (it != this->layers_found.end()) { + it->second.enabled = enabled; + } +} + +bool operator<(const LayerDisplay &a, const LayerDisplay &b) { + if (a.key == b.key) { + return a.api_version < b.api_version; + } else { + return a.key < b.key; + } +} + +std::set LayerManager::BuildLayerDisplayList() const { + std::set result; + + for (auto it = this->layers_found.begin(), end = this->layers_found.end(); it != end; ++it) { + const Layer *layer = this->FindFromManifest(it->first, true); + if (layer == nullptr) { continue; } - result.push_back(&this->available_layers[i]); + LayerDisplay layer_display; + layer_display.key = layer->key; + layer_display.manifest_path = it->first; + layer_display.api_version = layer->api_version; + layer_display.descriptor = it->second; + + result.insert(layer_display); } return result; diff --git a/vkconfig_core/layer_manager.h b/vkconfig_core/layer_manager.h index 888c608c15..b0fd537861 100644 --- a/vkconfig_core/layer_manager.h +++ b/vkconfig_core/layer_manager.h @@ -28,6 +28,16 @@ #include #include #include +#include + +struct LayerDisplay { + std::string key; + Path manifest_path; + Version api_version; + LayerDescriptor descriptor; +}; + +bool operator<(const LayerDisplay& a, const LayerDisplay& b); class LayerManager : public Serialize { public: @@ -49,26 +59,23 @@ class LayerManager : public Serialize { Layer* FindFromManifest(const Path& manifest_path, bool find_disabled_layers = false); void LoadAllInstalledLayers(ConfiguratorMode configurator_mode); - void LoadLayersFromPath(const Path& layers_path, LayerType type, ConfiguratorMode configurator_mode); LayerLoadStatus LoadLayer(const Path& layer_path, LayerType type, ConfiguratorMode configurator_mode); - bool AreLayersEnabled(const LayersPathInfo& path_info) const; - void AppendPath(const LayersPathInfo& path_info); - void RemovePath(const LayersPathInfo& path_info); - void UpdatePathEnabled(const LayersPathInfo& path_info, LayersPaths layers_paths); - std::vector CollectManifestPaths() const; + void AppendPath(const Path& path, LayerType type, bool added = false); + void RemovePath(const Path& path); + + bool IsEnabled(const Path& manifest_path) const; + void Enable(const Path& manifest_path, bool enabled); + + std::set BuildLayerDisplayList() const; std::vector GatherLayerNames() const; - std::vector GatherLayers(const LayersPathInfo& path_info) const; std::vector available_layers; - std::array, LAYERS_PATHS_COUNT> paths; - Path last_layers_path = Path(Path::HOME); + Path last_layers_dir = Path(Path::HOME); bool validate_manifests = false; + std::map layers_found; private: - void InitSystemPaths(); - void UpdateLayersEnabled(const LayersPathInfo& path_info); - - std::map layers_found; + std::set layers_removed; }; diff --git a/vkconfig_core/registry.cpp b/vkconfig_core/registry.cpp index 4ffb93fe31..444672860c 100644 --- a/vkconfig_core/registry.cpp +++ b/vkconfig_core/registry.cpp @@ -82,7 +82,7 @@ void RemoveRegistryEntriesForLayers() { } /// Look for device specific layers -static void LoadDeviceRegistry(DEVINST id, const QString &entry, std::vector &layers_paths) { +static void LoadDeviceRegistry(DEVINST id, const QString &entry, std::vector &layers_paths) { HKEY key; if (CM_Open_DevNode_Key(id, KEY_QUERY_VALUE, 0, RegDisposition_OpenExisting, &key, CM_REGISTRY_SOFTWARE) != CR_SUCCESS) return; @@ -102,11 +102,7 @@ static void LoadDeviceRegistry(DEVINST id, const QString &entry, std::vector LoadRegistrySystemLayers(const char *input_path) { - std::vector layers_paths; +std::vector LoadRegistrySystemLayers(const char *input_path) { + std::vector layers_paths; QString path(input_path); @@ -202,27 +198,25 @@ std::vector LoadRegistrySystemLayers(const char *input_path) { return layers_paths; } -std::vector LoadRegistrySoftwareLayers(const char *path, LayerType type) { - std::vector result; +std::vector LoadRegistrySoftwareLayers(const char *path, LayerType type) { + std::vector result; QSettings settings(path, QSettings::NativeFormat); const QStringList &files = settings.allKeys(); for (int i = 0, n = files.size(); i < n; ++i) { Path path(files[i].toStdString()); - LayersPathInfo info; - info.type = type; - info.path = path.IsFile() ? path.AbsoluteDir() : path.AbsolutePath(); + Path manifest_path = path.IsFile() ? path.AbsoluteDir() : path.AbsolutePath(); - if (!path.Exists()) { + if (!manifest_path.Exists()) { continue; } - if (::Found(result, info.path)) { + if (::Found(result, manifest_path)) { continue; } - result.push_back(info); + result.push_back(manifest_path); } return result; diff --git a/vkconfig_core/registry.h b/vkconfig_core/registry.h index 735dd836f4..8a1bdc5810 100644 --- a/vkconfig_core/registry.h +++ b/vkconfig_core/registry.h @@ -33,8 +33,8 @@ void AppendRegistryEntriesForLayers(QString override_file, QString settings_file void RemoveRegistryEntriesForLayers(); -std::vector LoadRegistrySystemLayers(const char* path); +std::vector LoadRegistrySystemLayers(const char* path); -std::vector LoadRegistrySoftwareLayers(const char* path, LayerType type); +std::vector LoadRegistrySoftwareLayers(const char* path, LayerType type); #endif // VKC_ENV == VKC_ENV_WIN32 diff --git a/vkconfig_core/test/test_configuration.cpp b/vkconfig_core/test/test_configuration.cpp index 8008de56f0..5d1c485bb1 100644 --- a/vkconfig_core/test/test_configuration.cpp +++ b/vkconfig_core/test/test_configuration.cpp @@ -27,6 +27,14 @@ #include +static void InitLayer(LayerManager& layer_manager) { + const std::vector& layers_paths = ::CollectFilePaths(":/layers"); + + for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { + layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + } +} + static bool operator==(const Configuration& a, const Configuration& b) { if (a.key != b.key) return false; @@ -194,7 +202,7 @@ TEST(test_configuration, make_duplicate_tagged_name_mix) { TEST(test_configuration, create) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); @@ -214,7 +222,7 @@ TEST(test_configuration, create) { TEST(test_configuration, create_disabled) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::CreateDisabled(layer_manager); @@ -234,7 +242,7 @@ TEST(test_configuration, create_disabled) { TEST(test_configuration, SwitchLayerVersion) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); for (std::size_t i = 0, n = configuration.parameters.size(); i < n; ++i) { @@ -261,7 +269,7 @@ TEST(test_configuration, SwitchLayerVersion) { TEST(test_configuration, gather_parameters_exist) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; @@ -294,7 +302,7 @@ TEST(test_configuration, gather_parameters_exist) { TEST(test_configuration, gather_parameters_repeat) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; @@ -324,11 +332,11 @@ TEST(test_configuration, gather_parameters_repeat) { TEST(test_configuration, gather_parameters_missing) { LayerManager layer_manager; + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configuration.GatherParameters(layer_manager); layer_manager.Clear(); @@ -361,11 +369,11 @@ TEST(test_configuration, gather_parameters_missing) { TEST(test_configuration, HasMissingLayer_UnsupportPlatform) { LayerManager layer_manager; + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configuration.GatherParameters(layer_manager); layer_manager.Clear(); @@ -381,11 +389,11 @@ TEST(test_configuration, HasMissingLayer_UnsupportPlatform) { TEST(test_configuration, HasMissingLayer_Off) { LayerManager layer_manager; + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configuration.GatherParameters(layer_manager); layer_manager.Clear(); @@ -401,11 +409,11 @@ TEST(test_configuration, HasMissingLayer_Off) { TEST(test_configuration, gather_parameters_missing_but_unsupported_platform) { LayerManager layer_manager; + ::InitLayer(layer_manager); Configuration configuration; configuration.key = "New Configuration"; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configuration.GatherParameters(layer_manager); layer_manager.Clear(); @@ -421,7 +429,7 @@ TEST(test_configuration, gather_parameters_missing_but_unsupported_platform) { TEST(test_configuration, Reorder_full) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); @@ -457,7 +465,7 @@ TEST(test_configuration, Reorder_full) { TEST(test_configuration, Reorder_partial) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); @@ -485,7 +493,7 @@ TEST(test_configuration, Reorder_partial) { TEST(test_configuration, Reorder_missing) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); @@ -515,7 +523,7 @@ TEST(test_configuration, Reorder_missing) { TEST(test_configuration, Reorder_empty) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); std::size_t size = configuration.parameters.size(); @@ -527,19 +535,19 @@ TEST(test_configuration, Reorder_empty) { } TEST(test_configuration, IsDefault_True) { - LayerManager layers; - layers.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + LayerManager layer_manager; + ::InitLayer(layer_manager); Configuration configuration; - const bool loaded = configuration.Load(":/configurations/Validation.json", layers); + const bool loaded = configuration.Load(":/configurations/Validation.json", layer_manager); EXPECT_TRUE(loaded); EXPECT_TRUE(configuration.IsDefault()); } TEST(test_configuration, IsDefault_False) { - LayerManager layers; - layers.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + LayerManager layer_manager; + ::InitLayer(layer_manager); - Configuration configuration = Configuration::Create(layers, "New Configuration"); + Configuration configuration = Configuration::Create(layer_manager, "New Configuration"); EXPECT_FALSE(configuration.IsDefault()); } diff --git a/vkconfig_core/test/test_configuration_built_in.cpp b/vkconfig_core/test/test_configuration_built_in.cpp index 94fcc8ab05..a0099f0edf 100644 --- a/vkconfig_core/test/test_configuration_built_in.cpp +++ b/vkconfig_core/test/test_configuration_built_in.cpp @@ -55,11 +55,17 @@ static bool operator==(const Parameter& a, const Parameter& b) { static bool operator!=(const std::vector& a, const std::vector& b) { return !(a == b); } -std::map Dummy() { return std::map(); } +static void InitLayer(LayerManager& layer_manager) { + const std::vector& layers_paths = ::CollectFilePaths(":/sdk"); + + for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { + layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + } +} struct TestBuilin { TestBuilin() : layer_manager() { - this->layer_manager.LoadLayersFromPath(":/sdk", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(this->layer_manager); EXPECT_TRUE(!this->layer_manager.available_layers.empty()); } diff --git a/vkconfig_core/test/test_configuration_manager.cpp b/vkconfig_core/test/test_configuration_manager.cpp index d7e9e754ca..94c35bd7a0 100644 --- a/vkconfig_core/test/test_configuration_manager.cpp +++ b/vkconfig_core/test/test_configuration_manager.cpp @@ -22,6 +22,14 @@ #include +static void InitLayer(LayerManager& layer_manager, const char* dir) { + const std::vector& layers_paths = ::CollectFilePaths(dir); + + for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { + layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + } +} + TEST(test_configuration_manager, init_default) { ConfigurationManager configuration_manager; @@ -35,7 +43,7 @@ TEST(test_configuration_manager, init_default) { TEST(test_configuration_manager, create_remove) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; @@ -67,7 +75,7 @@ TEST(test_configuration_manager, create_remove) { TEST(test_configuration_manager, duplicate_names) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; @@ -93,7 +101,7 @@ TEST(test_configuration_manager, duplicate_names) { TEST(test_configuration_manager, duplicate_object) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; @@ -117,7 +125,7 @@ TEST(test_configuration_manager, duplicate_object) { TEST(test_configuration_manager, sort) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; @@ -138,7 +146,7 @@ TEST(test_configuration_manager, sort) { TEST(test_configuration_manager, default_configuration) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; configuration_manager.LoadDefaultConfigurations(layer_manager); @@ -149,7 +157,7 @@ TEST(test_configuration_manager, default_configuration) { TEST(test_configuration_manager, RenameConfiguration) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; configuration_manager.LoadDefaultConfigurations(layer_manager); @@ -172,7 +180,7 @@ TEST(test_configuration_manager, RenameConfiguration) { TEST(test_configuration_manager, SaveConfiguration) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; EXPECT_TRUE(configuration_manager.Empty()); @@ -202,7 +210,7 @@ TEST(test_configuration_manager, SaveConfiguration) { TEST(test_configuration_manager, ExportImport) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/layers"); ConfigurationManager configuration_manager; configuration_manager.LoadDefaultConfigurations(layer_manager); @@ -237,7 +245,7 @@ TEST(test_configuration_manager, UpdateConfigurations) { EXPECT_STREQ(missing_layers_init[0].c_str(), "VK_LAYER_KHRONOS_validation"); EXPECT_EQ(2, configuration_init->parameters.size()); - layer_manager.LoadLayersFromPath(":/sdk", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager, ":/sdk"); configuration_manager.UpdateConfigurations(layer_manager); std::size_t count_update = configuration_manager.available_configurations.size(); diff --git a/vkconfig_core/test/test_generate_settings.cpp b/vkconfig_core/test/test_generate_settings.cpp index d8adb545cb..309a709f4d 100644 --- a/vkconfig_core/test/test_generate_settings.cpp +++ b/vkconfig_core/test/test_generate_settings.cpp @@ -42,8 +42,7 @@ Configurator& GetTestConfigurator() { path.Create(); Configurator& configurator = Configurator::Get(); - configurator.layers.LoadLayersFromPath(":/layers/VK_LAYER_LUNARG_reference_1_2_1.json", LAYER_TYPE_EXPLICIT, - CONFIGURATOR_MODE_CMD); + configurator.layers.LoadLayer(":/layers/VK_LAYER_LUNARG_reference_1_2_1.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configurator.configurations.CreateConfiguration(configurator.layers, "configuration"); configurator.SetActiveConfigurationName("configuration"); return configurator; diff --git a/vkconfig_core/test/test_layer.cpp b/vkconfig_core/test/test_layer.cpp index ccac948d2c..fad75dc56f 100644 --- a/vkconfig_core/test/test_layer.cpp +++ b/vkconfig_core/test/test_layer.cpp @@ -36,8 +36,6 @@ static SettingMetaString* InstantiateString(Layer& layer, const std::string& key return static_cast(layer.Instantiate(layer.settings, key, SETTING_STRING)); } -static std::map Dummy() { return std::map(); } - TEST(test_layer, collect_settings) { Layer layer; @@ -61,7 +59,7 @@ TEST(test_layer, collect_settings) { TEST(test_layer, load_header_overridden) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_00.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_00.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -76,7 +74,6 @@ TEST(test_layer, load_header_overridden) { EXPECT_EQ(true, layer.enable_env.empty()); EXPECT_EQ(true, layer.enable_value.empty()); EXPECT_EQ(false, layer.is_32bits); - EXPECT_EQ(true, layer.enabled); EXPECT_STREQ("${LUNARG_SDK}/layer_dummy.html", layer.url.RelativePath(false).c_str()); EXPECT_TRUE(layer.settings.empty()); EXPECT_TRUE(layer.presets.empty()); @@ -85,7 +82,7 @@ TEST(test_layer, load_header_overridden) { TEST(test_layer, load_header_default) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_01.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_01.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -104,7 +101,7 @@ TEST(test_layer, load_header_default) { TEST(test_layer, load_header_override) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_02.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_02.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -117,7 +114,7 @@ TEST(test_layer, load_header_override) { TEST(test_layer, load_setting_interit) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -140,7 +137,7 @@ TEST(test_layer, load_setting_interit) { TEST(test_layer, load_preset_interit) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -158,7 +155,7 @@ TEST(test_layer, load_preset_interit) { EXPECT_EQ(STATUS_ALPHA, layer.presets[2].status); const LayerLoadStatus reloaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(reloaded, LAYER_LOAD_ADDED); EXPECT_EQ(1, layer.settings.size()); EXPECT_EQ(3, layer.presets.size()); @@ -167,7 +164,7 @@ TEST(test_layer, load_preset_interit) { TEST(test_layer, load_setting_children_interit) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_05.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_05.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -191,7 +188,7 @@ TEST(test_layer, load_setting_children_interit) { TEST(test_layer, load_setting_enum_interit) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_06.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_06.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -234,7 +231,7 @@ TEST(test_layer, load_setting_enum_interit) { TEST(test_layer, load_setting_missing) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -247,7 +244,7 @@ TEST(test_layer, load_setting_missing) { EXPECT_EQ(layer_controlA, LAYER_CONTROL_ON); const LayerLoadStatus reloaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(1, layer.settings.size()); EXPECT_EQ(2, layer.presets.size()); } @@ -255,7 +252,7 @@ TEST(test_layer, load_setting_missing) { TEST(test_layer, load_env_variable) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_08.json", LAYER_TYPE_IMPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_08.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -270,7 +267,6 @@ TEST(test_layer, load_env_variable) { EXPECT_STREQ("VK_LAYER_TEST08_ENABLE", layer.enable_env.c_str()); EXPECT_STREQ("1", layer.enable_value.c_str()); EXPECT_EQ(false, layer.is_32bits); - EXPECT_EQ(true, layer.enabled); EXPECT_STREQ("${LUNARG_SDK}/layer_dummy.html", layer.url.RelativePath(false).c_str()); EXPECT_TRUE(layer.settings.empty()); EXPECT_TRUE(layer.presets.empty()); @@ -304,7 +300,7 @@ TEST(test_layer, load_env_variable) { TEST(test_layer, load_setting_message) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_09.json", LAYER_TYPE_IMPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_09.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); @@ -329,7 +325,7 @@ TEST(test_layer, load_setting_message) { TEST(test_layer, load_1_1_0_header) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); EXPECT_EQ(Version(1, 1, 0), layer.file_format_version); @@ -348,7 +344,7 @@ TEST(test_layer, load_1_1_0_header) { TEST(test_layer, load_1_2_0_preset_and_setting_type) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_2_0.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_2_0.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); // Preset Enum diff --git a/vkconfig_core/test/test_layer_manager.cpp b/vkconfig_core/test/test_layer_manager.cpp index 59054267a1..7e76be08e8 100644 --- a/vkconfig_core/test/test_layer_manager.cpp +++ b/vkconfig_core/test/test_layer_manager.cpp @@ -22,6 +22,14 @@ #include +static void InitLayer(LayerManager& layer_manager) { + const std::vector& layers_paths = ::CollectFilePaths(":/layers"); + + for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { + layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + } +} + TEST(test_layer_manager, clear) { LayerManager layer_manager; @@ -53,13 +61,12 @@ TEST(test_layer_manager, save_json) { const QJsonObject& json_layers_object = json_root_object.value("layers").toObject(); EXPECT_TRUE(json_layers_object.value("found") != QJsonValue::Undefined); - EXPECT_TRUE(json_layers_object.value("paths") != QJsonValue::Undefined); } } TEST(test_layer_manager, load_all) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); EXPECT_EQ(17, layer_manager.Size()); EXPECT_TRUE(!layer_manager.Empty()); @@ -72,7 +79,7 @@ TEST(test_layer_manager, load_dir) { LayerManager layer_manager; EXPECT_TRUE(layer_manager.Empty()); - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); EXPECT_TRUE(layer_manager.Find("VK_LAYER_LUNARG_reference_1_1_0", Version::LATEST) != nullptr); layer_manager.Clear(); @@ -85,7 +92,7 @@ TEST(test_layer_manager, load_file) { LayerManager layer_manager; EXPECT_TRUE(layer_manager.Empty()); - layer_manager.LoadLayersFromPath(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayer(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_TRUE(!layer_manager.Empty()); EXPECT_EQ(1, layer_manager.Size()); @@ -98,8 +105,8 @@ TEST(test_layer_manager, load_file) { TEST(test_layer_manager, reset) { LayerManager layer_manager; + ::InitLayer(layer_manager); - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_FALSE(layer_manager.Empty()); layer_manager.Clear(); @@ -108,7 +115,7 @@ TEST(test_layer_manager, reset) { TEST(test_layer_manager, find_single) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); EXPECT_TRUE(layer_manager.Find("VK_LAYER_LUNARG_test_03", Version::LATEST) != nullptr); @@ -119,7 +126,7 @@ TEST(test_layer_manager, find_single) { TEST(test_layer_manager, reload) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); EXPECT_TRUE(layer_manager.Size() > 1); @@ -130,7 +137,6 @@ TEST(test_layer_manager, reload) { EXPECT_EQ(status1, LAYER_LOAD_UNMODIFIED); Layer* layer1 = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_test_04.json", false); - layer1->enabled = false; const std::size_t reloaded_size1 = layer_manager.Size(); EXPECT_EQ(initial_size, reloaded_size1); @@ -145,7 +151,7 @@ TEST(test_layer_manager, reload) { TEST(test_layer_manager, find_multiple) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); const Layer* layer135 = layer_manager.Find("VK_LAYER_LUNARG_version", Version(1, 1, 135)); EXPECT_TRUE(layer135 != nullptr); @@ -182,7 +188,8 @@ TEST(test_layer_manager, find_multiple) { TEST(test_layer_manager, FindLastModified) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); + const std::size_t initial_count = layer_manager.Size(); Layer* layer204_modified = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", false); @@ -190,7 +197,12 @@ TEST(test_layer_manager, FindLastModified) { Path modified_path = layer204_modified->manifest_path; layer204_modified->manifest_path = ":/layers/VK_LAYER_LUNARG_version_204_copy.json"; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + auto it = layer_manager.layers_found.find(":/layers/VK_LAYER_LUNARG_version_204.json"); + assert(it != layer_manager.layers_found.end()); + + layer_manager.layers_found.insert(std::make_pair(":/layers/VK_LAYER_LUNARG_version_204_copy.json", it->second)); + + ::InitLayer(layer_manager); const std::size_t reloaded_count = layer_manager.Size(); EXPECT_EQ(initial_count + 1, reloaded_count); @@ -210,44 +222,41 @@ TEST(test_layer_manager, FindLastModified) { TEST(test_layer_manager, FindFromManifest) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); + const std::size_t initial_count = layer_manager.Size(); Layer* layer204 = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", false); EXPECT_TRUE(layer204 != nullptr); EXPECT_STREQ(layer204->key.c_str(), "VK_LAYER_LUNARG_version"); EXPECT_EQ(layer204->api_version, Version(1, 3, 204)); - EXPECT_TRUE(layer204->enabled); Layer* layer208 = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_208.json", false); EXPECT_TRUE(layer208 == nullptr); - layer204->enabled = false; + layer_manager.layers_found.find(":/layers/VK_LAYER_LUNARG_version_204.json")->second.enabled = false; + Layer* layer204_disabledA = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", false); EXPECT_TRUE(layer204_disabledA == nullptr); Layer* layer204_disabledB = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", true); // find disabled layers anyway EXPECT_TRUE(layer204_disabledB != nullptr); - EXPECT_FALSE(layer204_disabledB->enabled); - layer204_disabledB->enabled = true; - Layer* layer204_disabledC = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", false); + Layer* layer204_disabledC = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", true); EXPECT_TRUE(layer204_disabledC != nullptr); - EXPECT_TRUE(layer204_disabledC->enabled); - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); const std::size_t enabled_count = layer_manager.Size(); EXPECT_EQ(initial_count, enabled_count); - layer204->enabled = false; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); const std::size_t disabled_count = layer_manager.Size(); EXPECT_EQ(initial_count, disabled_count); } TEST(test_layer_manager, GatherManifests) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); const std::vector& versions_notfound = layer_manager.GatherManifests("VK_LAYER_LUNARG_version_not_found"); EXPECT_TRUE(versions_notfound.empty()); @@ -265,11 +274,12 @@ TEST(test_layer_manager, GatherManifests) { const Layer* layer3 = layer_manager.FindFromManifest(versions_found[3]); EXPECT_EQ(layer3->api_version, Version(1, 1, 135)); + layer_manager.layers_found.find(versions_found[2])->second.enabled = false; + const Layer* layer193 = layer_manager.Find("VK_LAYER_LUNARG_version", Version(1, 2, 193)); EXPECT_TRUE(layer193 != nullptr); Layer* layer_edit = layer_manager.FindFromManifest(layer193->manifest_path); - layer_edit->enabled = false; const std::vector& versions_found_b = layer_manager.GatherManifests("VK_LAYER_LUNARG_version"); EXPECT_FALSE(versions_found_b.empty()); @@ -284,7 +294,7 @@ TEST(test_layer_manager, GatherManifests) { TEST(test_layer_manager, GatherVersions) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); const std::vector& versions_notfound = layer_manager.GatherVersions("VK_LAYER_LUNARG_version_not_found"); EXPECT_TRUE(versions_notfound.empty()); @@ -300,8 +310,7 @@ TEST(test_layer_manager, GatherVersions) { const Layer* layer193 = layer_manager.Find("VK_LAYER_LUNARG_version", Version(1, 2, 193)); EXPECT_TRUE(layer193 != nullptr); - Layer* layer_edit = layer_manager.FindFromManifest(layer193->manifest_path); - layer_edit->enabled = false; + layer_manager.layers_found.find(layer193->manifest_path)->second.enabled = false; const std::vector& versions_found_b = layer_manager.GatherVersions("VK_LAYER_LUNARG_version"); EXPECT_FALSE(versions_found_b.empty()); @@ -313,7 +322,7 @@ TEST(test_layer_manager, GatherVersions) { TEST(test_layer_manager, BuildLayerNameList) { LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); EXPECT_EQ(layer_manager.GatherLayerNames().size(), 14); } @@ -324,80 +333,11 @@ TEST(test_layer_manager, avoid_duplicate) { LayerManager layer_manager; EXPECT_TRUE(layer_manager.Empty()); - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); std::size_t first_load_count = layer_manager.Size(); - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + ::InitLayer(layer_manager); std::size_t second_load_count = layer_manager.Size(); EXPECT_EQ(first_load_count, second_load_count); } - -TEST(test_layer_manager, custom_path_append_remove) { - LayerManager layer_manager; - - LayersPathInfo infoA; - infoA.path = ":/layers"; - LayersPathInfo infoB; - infoB.path = ":/layersB"; - LayersPathInfo infoC; - infoC.path = ":/layersC"; - - layer_manager.AppendPath(infoA); - layer_manager.AppendPath(infoB); - layer_manager.AppendPath(infoC); - - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 3); - - layer_manager.AppendPath(infoA); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 3); - - layer_manager.RemovePath(infoA); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 2); - - layer_manager.AppendPath(infoA); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 3); - - layer_manager.RemovePath(infoA); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 2); - - layer_manager.RemovePath(infoA); // Check that removing an already removed path doesn't cause any issue - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 2); - - layer_manager.RemovePath(infoB); - layer_manager.RemovePath(infoC); - - EXPECT_TRUE(layer_manager.paths[LAYERS_PATHS_GUI].empty()); -} - -TEST(test_layer_manager, custom_path_update_layers) { - LayerManager layer_manager; - layer_manager.LoadLayersFromPath(":/layers", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); - - LayersPathInfo info; - info.path = ":/layers"; - // info.enabled = true; default value - - layer_manager.AppendPath(info); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 1); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI][0].enabled, true); - for (std::size_t i = 0, n = layer_manager.available_layers.size(); i < n; ++i) { - EXPECT_TRUE(layer_manager.available_layers[i].enabled); - } - - info.enabled = false; - layer_manager.UpdatePathEnabled(info, LAYERS_PATHS_GUI); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI][0].enabled, false); - for (std::size_t i = 0, n = layer_manager.available_layers.size(); i < n; ++i) { - EXPECT_FALSE(layer_manager.available_layers[i].enabled); - } - - info.enabled = true; - layer_manager.UpdatePathEnabled(info, LAYERS_PATHS_GUI); - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI][0].enabled, true); - for (std::size_t i = 0, n = layer_manager.available_layers.size(); i < n; ++i) { - EXPECT_TRUE(layer_manager.available_layers[i].enabled); - } - - EXPECT_EQ(layer_manager.paths[LAYERS_PATHS_GUI].size(), 1); -} diff --git a/vkconfig_core/test/test_layer_preset.cpp b/vkconfig_core/test/test_layer_preset.cpp index 113e4bc202..8b5f919b49 100644 --- a/vkconfig_core/test/test_layer_preset.cpp +++ b/vkconfig_core/test/test_layer_preset.cpp @@ -26,8 +26,6 @@ #include -static std::map Dummy() { return std::map(); } - static SettingMetaString* InstantiateString(Layer& layer, const std::string& key) { return static_cast(layer.Instantiate(layer.settings, key, SETTING_STRING)); } @@ -68,7 +66,7 @@ TEST(test_layer_preset, has_preset) { TEST(test_layer_preset, find_preset_index_no_preset) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); SettingDataSet layer_settings; @@ -81,7 +79,7 @@ TEST(test_layer_preset, find_preset_index_no_preset) { TEST(test_layer_preset, find_preset_index_empty) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); SettingDataSet layer_settings; @@ -92,7 +90,7 @@ TEST(test_layer_preset, find_preset_index_empty) { TEST(test_layer_preset, find_preset_index_found) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); SettingDataSet layer_settings; @@ -118,7 +116,7 @@ TEST(test_layer_preset, find_preset_index_found) { TEST(test_layer_preset, find_preset_index_missing_value) { Layer layer; const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_EXPLICIT, false, Dummy(), CONFIGURATOR_MODE_CMD); + layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); SettingDataSet layer_settings; diff --git a/vkconfig_core/test/test_parameter.cpp b/vkconfig_core/test/test_parameter.cpp index 25a9dfe883..2e8acb1691 100644 --- a/vkconfig_core/test/test_parameter.cpp +++ b/vkconfig_core/test/test_parameter.cpp @@ -25,10 +25,33 @@ #include -inline SettingMetaString* InstantiateString(Layer& layer, const std::string& key) { +static SettingMetaString* InstantiateString(Layer& layer, const std::string& key) { return static_cast(layer.Instantiate(layer.settings, key, SETTING_STRING)); } +static void AddLayer(LayerManager& layers, const char* key, LayerType type) { + std::string path = format("./%s.json", key); + + layers.available_layers.push_back(Layer(key, Version(1, 0, 0), Version(1, 2, 148), "1", path.c_str())); + layers.available_layers[layers.available_layers.size() - 1].manifest_path = path; + layers.available_layers[layers.available_layers.size() - 1].type = type; + + LayerDescriptor descriptor; + descriptor.type = type; + layers.layers_found.insert(std::make_pair(path, descriptor)); +} + +static void InitLayers(LayerManager& layers) { + AddLayer(layers, "VK_LAYER_KHRONOS_implicit", LAYER_TYPE_IMPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_explicit", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_validation", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_profiles", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_timeline_semaphore", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_synchronization2", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_shader_object", LAYER_TYPE_EXPLICIT); + AddLayer(layers, "VK_LAYER_KHRONOS_memory_decompression", LAYER_TYPE_EXPLICIT); +} + TEST(test_parameter, apply_settings) { Layer layer; @@ -65,47 +88,40 @@ TEST(test_parameter, apply_settings) { TEST(test_parameter, ordering_found_parameter_rank) { LayerManager layers; - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_implicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_explicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_validation", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_profiles", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_timeline_semaphore", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_synchronization2", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_shader_object", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_memory_decompression", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); + ::InitLayers(layers); + + Parameter parameter_validation("VK_LAYER_KHRONOS_validation", LAYER_CONTROL_AUTO); + EXPECT_EQ(PARAMETER_RANK_VALIDATION_LAYER, GetParameterOrdering(layers, parameter_validation)); + + Parameter parameter_profile("VK_LAYER_KHRONOS_profiles", LAYER_CONTROL_ON); + EXPECT_EQ(PARAMETER_RANK_PROFILES_LAYER, GetParameterOrdering(layers, parameter_profile)); + + Parameter parameter_timeline_semaphore("VK_LAYER_KHRONOS_timeline_semaphore", LAYER_CONTROL_OFF); + EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, GetParameterOrdering(layers, parameter_timeline_semaphore)); - layers.available_layers[0].type = LAYER_TYPE_IMPLICIT; + Parameter parameter_synchronization2("VK_LAYER_KHRONOS_synchronization2", LAYER_CONTROL_AUTO); + EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, GetParameterOrdering(layers, parameter_synchronization2)); + + Parameter parameter_shader_object("VK_LAYER_KHRONOS_shader_object", LAYER_CONTROL_ON); + EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, GetParameterOrdering(layers, parameter_shader_object)); + + Parameter parameter_memory_decompression("VK_LAYER_KHRONOS_memory_decompression", LAYER_CONTROL_OFF); + EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, GetParameterOrdering(layers, parameter_memory_decompression)); + + Parameter parameter_explicit("VK_LAYER_KHRONOS_explicit", LAYER_CONTROL_ON); + EXPECT_EQ(PARAMETER_RANK_EXPLICIT_LAYER, GetParameterOrdering(layers, parameter_explicit)); Parameter parameter_implicit("VK_LAYER_KHRONOS_implicit", LAYER_CONTROL_OFF); parameter_implicit.type = LAYER_TYPE_IMPLICIT; - - EXPECT_EQ(PARAMETER_RANK_VALIDATION_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_validation", LAYER_CONTROL_AUTO))); - EXPECT_EQ(PARAMETER_RANK_PROFILES_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_profiles", LAYER_CONTROL_ON))); - EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_timeline_semaphore", LAYER_CONTROL_OFF))); - EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_synchronization2", LAYER_CONTROL_AUTO))); - EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_shader_object", LAYER_CONTROL_ON))); - EXPECT_EQ(PARAMETER_RANK_EXTENSION_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_memory_decompression", LAYER_CONTROL_OFF))); - EXPECT_EQ(PARAMETER_RANK_EXPLICIT_LAYER, - GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_explicit", LAYER_CONTROL_ON))); EXPECT_EQ(PARAMETER_RANK_IMPLICIT_LAYER, GetParameterOrdering(layers, parameter_implicit)); - EXPECT_EQ(PARAMETER_RANK_MISSING_LAYER, GetParameterOrdering(layers, Parameter("VK_LAYER_KHRONOS_missing", LAYER_CONTROL_OFF))); + + Parameter parameter_missing("VK_LAYER_KHRONOS_missing", LAYER_CONTROL_OFF); + EXPECT_EQ(PARAMETER_RANK_MISSING_LAYER, GetParameterOrdering(layers, parameter_missing)); } TEST(test_parameter, has_missing_layer) { LayerManager layers; - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_implicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_explicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); + ::InitLayers(layers); std::vector parameters; parameters.push_back(Parameter("VK_LAYER_KHRONOS_implicit", LAYER_CONTROL_AUTO)); @@ -118,8 +134,7 @@ TEST(test_parameter, has_missing_layer) { TEST(test_parameter, no_missing_layer) { LayerManager layers; - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_implicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_explicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); + ::InitLayers(layers); std::vector parameters; parameters.push_back(Parameter("VK_LAYER_KHRONOS_implicit", LAYER_CONTROL_AUTO)); @@ -132,20 +147,7 @@ TEST(test_parameter, no_missing_layer) { TEST(test_parameter, order_parameter_automatic) { LayerManager layers; - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_implicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_explicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_validation", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_profiles", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_timeline_semaphore", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_synchronization2", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_shader_object", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_memory_decompression", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers[0].type = LAYER_TYPE_IMPLICIT; + ::InitLayers(layers); Parameter unordered_layers(::GetToken(LAYER_BUILTIN_UNORDERED), LAYER_CONTROL_AUTO); unordered_layers.builtin = LAYER_BUILTIN_UNORDERED; @@ -193,18 +195,7 @@ TEST(test_parameter, order_parameter_automatic) { TEST(test_parameter, order_parameter_manual_partial) { LayerManager layers; - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_implicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_explicit", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_validation", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back(Layer("VK_LAYER_KHRONOS_profiles", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_timeline_semaphore", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_synchronization2", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_memory_decompression", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); - layers.available_layers[0].type = LAYER_TYPE_IMPLICIT; + ::InitLayers(layers); std::vector parameters; parameters.push_back(Parameter("VK_LAYER_KHRONOS_implicit", LAYER_CONTROL_AUTO)); diff --git a/vkconfig_core/type_layers_paths.cpp b/vkconfig_core/type_layers_paths.cpp index a0f607536c..11ee9a9195 100644 --- a/vkconfig_core/type_layers_paths.cpp +++ b/vkconfig_core/type_layers_paths.cpp @@ -33,7 +33,23 @@ const char* GetLabel(LayersPaths Layers_paths_type) { "Vulkan Configurator", // LAYERS_PATHS_GUI "${VULKAN_SDK}", // LAYERS_PATHS_SDK }; - static_assert(std::size(TABLE) == LAYERS_PATHS_COUNT, "The tranlation table size doesn't match the enum number of elements"); + static_assert(std::size(TABLE) == LAYERS_PATHS_COUNT); + + return TABLE[Layers_paths_type]; +} + +LayerType GetLayerType(LayersPaths Layers_paths_type) { + static LayerType TABLE[] = { + LAYER_TYPE_IMPLICIT, // LAYERS_PATHS_IMPLICIT_SYSTEM + LAYER_TYPE_IMPLICIT, // LAYERS_PATHS_IMPLICIT_ENV_SET + LAYER_TYPE_IMPLICIT, // LAYERS_PATHS_IMPLICIT_ENV_ADD + LAYER_TYPE_EXPLICIT, // LAYERS_PATHS_EXPLICIT_SYSTEM + LAYER_TYPE_EXPLICIT, // LAYERS_PATHS_EXPLICIT_ENV_SET + LAYER_TYPE_EXPLICIT, // LAYERS_PATHS_EXPLICIT_ENV_ADD + LAYER_TYPE_EXPLICIT, // LAYERS_PATHS_GUI + LAYER_TYPE_EXPLICIT, // LAYERS_PATHS_SDK + }; + static_assert(std::size(TABLE) == LAYERS_PATHS_COUNT); return TABLE[Layers_paths_type]; } diff --git a/vkconfig_core/type_layers_paths.h b/vkconfig_core/type_layers_paths.h index 2b43cfaf90..6aa990639a 100644 --- a/vkconfig_core/type_layers_paths.h +++ b/vkconfig_core/type_layers_paths.h @@ -39,3 +39,5 @@ enum LayersPaths { enum { LAYERS_PATHS_COUNT = LAYERS_PATHS_LAST - LAYERS_PATHS_FIRST + 1 }; const char* GetLabel(LayersPaths Layers_paths_type); + +LayerType GetLayerType(LayersPaths Layers_paths_type); diff --git a/vkconfig_core/type_tab.cpp b/vkconfig_core/type_tab.cpp index b7f0371080..fbde0f0fc2 100644 --- a/vkconfig_core/type_tab.cpp +++ b/vkconfig_core/type_tab.cpp @@ -26,7 +26,7 @@ const char* GetLabel(TabType type) { static const char* TOKENS[]{ "Vulkan Layers Configuration", // TAB_CONFIGURATIONS - "Vulkan Layers Paths", // TAB_LAYERS_PATHS + "Vulkan Layers Available", // TAB_LAYERS_PATHS "Vulkan Drivers", // TAB_DRIVERS "Application Launcher", // TAB_APPLICATIONS "Diagnostics", // TAB_DIAGNOSTIC diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 2883004517..4bf29248e5 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -15,6 +15,11 @@ - Add `vkconfig.json` backup when doing a "Reset To Default" #2570 - Add "Discard Ordering and Enabling Layers" default configuration #2596 - Improve Vulkan drivers loading, add all drivers in a part at once +- Refactor layers tab to display layer sorted by name and enabling them individually +- Add layer search bar in layer tab +- Show paths instead of Vulkan Configurator variables +- Improve UI consistency +- Move 'Validate Layer Manifest' settings to the preference tab ### Fixes: - Fix command line whitespace decoding #2625 @@ -24,6 +29,8 @@ - Fix "Validation with API Dump log" configuration not loaded - Fix high CPU usage on idle when no layers configuration is active #2603 - Fix UUID display in log, following the RFC9562 standard +- Fix icon to remove a Vulkan driver +- Fix key binding shortcuts (Eg: CTRL+F to search things on various tabs) ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/mainwindow.cpp b/vkconfig_gui/mainwindow.cpp index 224ae30588..99dd15f7a5 100644 --- a/vkconfig_gui/mainwindow.cpp +++ b/vkconfig_gui/mainwindow.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include @@ -72,6 +73,9 @@ MainWindow::MainWindow(QApplication &app, QWidget *parent) } this->ui->tab_widget->setCurrentIndex(configurator.active_tab); + this->ui->tab_widget->installEventFilter(this); + + this->connect(ui->tab_widget, SIGNAL(currentChanged(int)), this, SLOT(on_tab_widget_currentChanged(int))); this->UpdateUI(UPDATE_REBUILD_UI); @@ -384,5 +388,33 @@ void MainWindow::on_tab_widget_currentChanged(int index) { Configurator &configurator = Configurator::Get(); configurator.active_tab = static_cast(index); + switch (configurator.active_tab) { + case TAB_CONFIGURATIONS: { + } break; + case TAB_LAYERS_PATHS: { + // QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->tab_widget); + // this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); + + // QShortcut *shortcut_open = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_O), this->ui->layers_list); + // this->connect(shortcut_open, SIGNAL(activated()), this, SLOT(on_layers_browse_pressed())); + } break; + case TAB_DRIVERS: { + // QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->tab_widget); + // this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); + } break; + case TAB_APPLICATIONS: { + // QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->tab_widget); + // this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); + } break; + case TAB_DIAGNOSTIC: { + } break; + case TAB_DOCUMENTATION: { + } break; + case TAB_PREFERENCES: { + } break; + case TAB_ABOUT: { + } break; + } + this->tabs[index]->UpdateUI(UPDATE_REBUILD_UI); } diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 734d35eff6..4d5af1af73 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -128,8 +128,11 @@ true + + Check to control the Vulkan Layers used by running Vulkan Executables (Ctrl+Space) + - Control the Vulkan Layers used by running Vulkan applications + Configure System Vulkan Layers @@ -904,6 +907,13 @@ + + + Arial + 10 + true + + false @@ -913,9 +923,9 @@ Locate Vulkan Layers versions - + - 0 + 7 5 @@ -938,8 +948,11 @@ true + + List of Vulkan Layers found on the system + - System Vulkan Layers Paths + Vulkan Layers available on the system false @@ -975,7 +988,7 @@ 0 - + 16777215 @@ -989,11 +1002,31 @@ false + + Enter search text to filter layers in the list... (Ctrl+F) + + + + + + + + 24 + 24 + + + + + Arial + 10 + false + + - Check to validate the layer manifests wheen adding a user path. This may take some time... + Clear layer filter - Validate Layer Manifests + @@ -1060,7 +1093,7 @@ - Enter a Vulkan layers directory to control them in Vulkan Configurator + Add a Vulkan Layer manifests path... (Ctrl+O) @@ -1092,7 +1125,7 @@ - Append a user-defined layers path + Add Additional Layers (CTRL+O) @@ -1133,7 +1166,7 @@ - Reload all modified layer manifest + Reload all modified layer manifest (CTRL+R) @@ -1149,13 +1182,7 @@ - - - - 0 - 0 - - + Arial @@ -1166,29 +1193,9 @@ QFrame::NoFrame - - Qt::ScrollBarAlwaysOn - - - QAbstractScrollArea::AdjustToContents - true - - false - - - false - - - false - - - - 1 - - @@ -1231,8 +1238,11 @@ true + + Check to control Vulkan Physical Device used by all running Vulkan executables (Ctrl+Space) + - Control Vulkan Physical Device used by all running Vulkan applications + Configure System Vulkan Devices @@ -1245,7 +1255,7 @@ - 0 + 7 5 @@ -1349,25 +1359,6 @@ - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - @@ -1481,17 +1472,196 @@ + + + + + 0 + 0 + + + + + Arial + 10 + true + + + + Load Additional Vulkan Drivers + + + true + + + + 7 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 0 + + + 0 + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Enter search text to filter drivers in the list... (Ctrl+F) + + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Add Vulkan Driver manifests path... (Ctrl+O) + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + + 24 + 24 + + + + Append a user-defined driver path + + + + + + + + + + + + + Arial + 10 + false + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOn + + + true + + + + + + + + + + + + 0 + 0 + + + + false + + + Tab 4 + + + Vulkan executable launcher + + + + 11 + + + 5 + + + 5 + + + 5 + + + 5 + - - - - 0 - 0 - - + Arial @@ -1499,15 +1669,15 @@ true - - Add Vulkan Driver Paths + + Optional use to launch executables and store launch option - - true + + Optional Vulkan executables launcher - + - 7 + 0 5 @@ -1522,165 +1692,25 @@ 5 - + 0 + + 0 + 0 + + 0 + + + 0 + - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Enter a Vulkan Driver manifest path to make it visible to Vulkan application - - - - - - - - 0 - 0 - - - - - 24 - 24 - - - - - 24 - 24 - - - - Append a user-defined driver path - - - - - - - - - - - - - Arial - 10 - false - - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOn - - - - - - - - - - - - 0 - 0 - - - - false - - - Tab 4 - - - Vulkan executable launcher - - - - 11 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - Arial - 10 - true - - - - - - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - + + 0 24 @@ -1817,7 +1847,7 @@ - Options: + List of Executable Launch Options: @@ -1951,12 +1981,9 @@ 0 - + 7 - - 0 - @@ -1992,6 +2019,9 @@ false + + Enter executable arguments passed at launch... + @@ -2020,6 +2050,9 @@ false + + Enter the Vulkan executable working directory... + @@ -2121,6 +2154,9 @@ false + + Enter environment variables set at executable launch... + @@ -2149,6 +2185,9 @@ false + + Enter log file path where standard outputs is captured... + @@ -2218,7 +2257,7 @@ - 0 + 7 0 @@ -2369,6 +2408,9 @@ 24 + + Export the application output log + @@ -2388,6 +2430,9 @@ 24 + + Enter search text in the current log... (Ctrl+F) + @@ -2410,6 +2455,9 @@ 24 + + Clear the Search Text + @@ -2435,6 +2483,12 @@ 24 + + Find next (F3) + + + + @@ -2460,6 +2514,12 @@ 24 + + Find previous (Shift+F3) + + + + @@ -2485,6 +2545,12 @@ 24 + + Match case (Alt+C) + + + + @@ -2513,6 +2579,12 @@ 24 + + Match while world (Alt+W) + + + + @@ -2541,6 +2613,12 @@ 24 + + Use regular expressions (Alt+R) + + + + @@ -2645,8 +2723,11 @@ true + + Check to produce Vulkan Loader Log to all running Vulkan executables (Ctrl+Space) + - Produce Vulkan Loader Log to all running Vulkan applications + Configure System Vulkan Loader Log @@ -2659,16 +2740,16 @@ - 11 + 5 - 0 + 5 - 0 + 5 - 0 + 5 @@ -2825,16 +2906,16 @@ 0 - 0 + 5 - 0 + 5 - 0 + 5 - 0 + 5 @@ -2961,23 +3042,23 @@ false - Diagnostic Logs + Vulkan System Diagnostic Logs - 10 + 7 - 0 + 5 - 0 + 5 - 0 + 5 - 0 + 5 @@ -3226,6 +3307,9 @@ false + + Enter search text in the current log... (Ctrl+F) + @@ -3480,16 +3564,16 @@ 11 - 0 + 5 - 0 + 5 - 0 + 5 - 0 + 5 @@ -3553,7 +3637,7 @@ Tab 7 - + 11 @@ -3570,16 +3654,32 @@ 5 - + + + + 0 + 0 + + + + + Arial + 10 + true + + false - + Vulkan Configurator Settings + + + false - + - 11 + 7 5 @@ -3594,832 +3694,862 @@ 5 - - - - 0 - 0 - + + + + 16777215 + 24 + Arial 10 - true + false - - false + + Keep Vulkan Configurator running in system tray when closing the main window - - Vulkan Configurator Settings: + + + + + + 0 - - false + + 0 - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Keep Vulkan Configurator running in system tray when closing the main window - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - ${VULKAN_HOME} is the default environment variable for Vulkan SDK tools outputs implementing it. - + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + ${VULKAN_HOME} is the default environment variable for Vulkan SDK tools outputs implementing it. + + + ${VULKAN_HOME}: + + + + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + Arial + 10 + false + + + + + + + + + + + + + 0 + + + 0 + + + + + + Arial + 10 + false + + + + Allows overriding Vulkan Layers only for selected Vulkan executable + + + Show "Vulkan Executables Scope" in Vulkan Layers tab + + + + + + + 0 + + + 0 + + + 0 + + + + + + 24 + 0 + + + + + + + + + + + + Arial + 10 + false + + + + All Enabled Vulkan Executables behavior: + + + + + + + + Arial + 10 + false + + + + QComboBox::AdjustToContents + + - ${VULKAN_HOME}: - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - - Arial - 10 - false - + Write vk_layer_settings.txt file in the global path (visible to all Vulkan executables) + + - + Write vk_layer_settings.txt file in the application launcher working directory - - - - - - - - - Arial - 10 - false - - - - Allows overriding Vulkan Layers only for selected Vulkan executable - + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Arial + 10 + true + + + + Vulkan Configurator Appearence + + + + 0 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Theme Mode: + + + + + + + + 0 + 0 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + QComboBox::AdjustToContents + + - Show "Vulkan Executables Scope" in Vulkan Layers tab - - - - - - - 0 - - - 0 - - - 0 + Use Desktop Theme - - - - - - - - - - - - Arial - 10 - false - - - - All Enabled Vulkan Executables behavior: - - - - - - - - Arial - 10 - false - - - - QComboBox::AdjustToContents - - - - Write vk_layer_settings.txt file in the global path (visible to all Vulkan executables) - - - - - Write vk_layer_settings.txt file in the application launcher working directory - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Show debug layer settings, for layer development + + + + Force Light Theme + + - Show Debug Layer Settings + Force Dark Theme - - - - + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + + Arial + 10 + false + + + + + + + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Use custom Dark mode alternate rows color + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + + Arial + 10 + false + + + + + + + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Use custom Light mode alternate rows color + + + + + + + + 24 + 0 + + + + + + + + + + + + + + + + Arial + 10 + true + + + + This could be useful to check a layer manifest was edited correctly + + + Vulkan Layers Developer Settings + + + + 7 + + + 5 + + + 5 + + + 5 + + + 5 + - + Arial 10 - true + false - - Vulkan Configurator Appearence + + Check to validate the layer manifests wheen adding a user path. This may take some time... + + + Validate added layer manifests against Vulkan layer manifest schema (slow) - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Theme Mode: - - - - - - - - 0 - 0 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - QComboBox::AdjustToContents - - - - Use Desktop Theme - - - - - Force Light Theme - - - - - Force Dark Theme - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 24 - 24 - - - - - Arial - 10 - false - - - - - - - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Use custom Dark mode alternate rows color - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 24 - 24 - - - - - Arial - 10 - false - - - - - - - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Use custom Light mode alternate rows color - - - - - - - - - - - - - - - - - 0 - 0 - + + + + 16777215 + 24 + Arial 10 - true + false - - false + + Show debug layer settings, for layer development - - Vulkan SDK Releases: + + Show debug layer settings - - false + + + + + + + + + + 0 + 0 + + + + + Arial + 10 + true + + + + false + + + Vulkan SDK Releases + + + false + + + false + + + + 7 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 0 - - false + + 0 - - - 0 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 0 - - - 0 - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - ${VULKAN_DOWNLOAD}: - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - 0 - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - - Arial - 10 - false - - - - - - - - - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Open Vulkan SDK download web page... - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Notify new Vulkan SDK release at Vulkan Configurator launch - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - - - 0 - - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - - Arial - 10 - false - - - - Download last Vulkan SDK - - - - - - - - - - - 24 - 24 - - - - - 24 - 24 - - - - - Arial - 10 - false - - - - Open Vulkan SDK download folder - - - - - - - - - - - - + + 7 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + ${VULKAN_DOWNLOAD}: + + + + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + 0 + + + + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + Arial + 10 + false + + + + + + + + + + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Open Vulkan SDK download web page... + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Notify new Vulkan SDK release at Vulkan Configurator launch + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 0 + + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + + Arial + 10 + false + + + + Download last Vulkan SDK + + + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + Arial + 10 + false + + + + Open Vulkan SDK download folder + + + + + + + + + @@ -4515,297 +4645,279 @@ 5 - + + + + 800 + 270 + + - false + true - - - 0 + + false + + + false + + + + + 650 + 0 + 141 + 61 + - - 5 + + - - 5 + + :/resourcefiles/lunarg_logo.png - - 5 + + Qt::NoTextInteraction - - 5 + + + + + 640 + 190 + 91 + 81 + - - - - - 800 - 270 - - - - true - - - - - - false - - - false - - - - - 640 - 20 - 141 - 61 - - - - - - - :/resourcefiles/lunarg_logo.png - - - Qt::NoTextInteraction - - - - - - 640 - 190 - 91 - 81 - - - - - - - :/resourcefiles/qt_logo.png - - - - - - 760 - 70 - 201 - 41 - - - - - Arial - 10 - false - - - - Vulkan Configurator licensed under Apache 2.0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - 680 - 100 - 281 - 41 - - - - - Arial - 10 - false - - - - Qt::LeftToRight - - - Copyright (c) 2020-2026 LunarG, Inc. - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - 880 - 200 - 81 - 24 - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - About Qt - - - - - - 730 - 230 - 231 - 41 - - - - - Arial - 10 - false - - - - Qt licensed under LGPL 3.0 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - true - - - - - - 10 - 10 - 600 - 245 - - - - - 0 - 0 - - - - - 600 - 245 - - - - - - - :/resourcefiles/vulkan_configurator.png - - - true - - - Qt::NoTextInteraction - - - - - - 840 - 30 - 121 - 24 - - - - - 0 - 24 - - - - - 16777215 - 24 - - - - About LunarG - - - - - - 420 - 20 - 141 - 16 - - - - - Arial - 14 - true - - - - Qt::LeftToRight - - - - - - - - - - - false - - - QFrame::NoFrame - - - Qt::ScrollBarAlwaysOn - - - QAbstractScrollArea::AdjustToContents - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - true - - - - + + + + + :/resourcefiles/qt_logo.png + + + + + + 730 + 70 + 231 + 41 + + + + + Arial + 10 + false + + + + Licensed under Apache 2.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + false + + + Qt::NoTextInteraction + + + + + + 660 + 100 + 301 + 41 + + + + + Arial + 10 + false + + + + Qt::LeftToRight + + + Copyright (c) 2020-2026 LunarG, Inc. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + Qt::NoTextInteraction + + + + + + 880 + 200 + 81 + 24 + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + About Qt + + + + + + 730 + 230 + 231 + 41 + + + + + Arial + 10 + false + + + + Qt licensed under LGPL 3.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + true + + + Qt::NoTextInteraction + + + + + + 10 + 10 + 600 + 245 + + + + + 0 + 0 + + + + + 600 + 245 + + + + + + + :/resourcefiles/vulkan_configurator.png + + + true + + + Qt::NoTextInteraction + + + + + + 840 + 30 + 121 + 24 + + + + + 0 + 24 + + + + + 16777215 + 24 + + + + About LunarG + + + + + + 420 + 20 + 141 + 16 + + + + + Arial + 14 + true + + + + Qt::LeftToRight + + + + + + + + + + + false + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOn + + + QAbstractScrollArea::AdjustToContents + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + true + diff --git a/vkconfig_gui/tab_applications.cpp b/vkconfig_gui/tab_applications.cpp index e40fb1bd71..636daaa938 100644 --- a/vkconfig_gui/tab_applications.cpp +++ b/vkconfig_gui/tab_applications.cpp @@ -93,11 +93,11 @@ TabApplications::TabApplications(MainWindow &window, std::shared_ptrui->launch_log_text); this->connect(shortcut_prev, SIGNAL(activated()), this, SLOT(on_search_prev_pressed())); - QShortcut *shortcut_case = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_C), this->ui->launch_log_text); + QShortcut *shortcut_case = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_C), this->ui->launch_search_case); this->connect(shortcut_case, SIGNAL(activated()), this, SLOT(on_search_case_activated())); - QShortcut *shortcut_whole = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_W), this->ui->launch_log_text); + QShortcut *shortcut_whole = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_W), this->ui->launch_search_whole); this->connect(shortcut_whole, SIGNAL(activated()), this, SLOT(on_search_whole_activated())); - QShortcut *shortcut_regex = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_R), this->ui->launch_log_text); + QShortcut *shortcut_regex = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_R), this->ui->launch_search_regex); this->connect(shortcut_regex, SIGNAL(activated()), this, SLOT(on_search_regex_activated())); this->on_search_clear_pressed(); @@ -152,7 +152,7 @@ void TabApplications::UpdateUI(UpdateUIMode mode) { ui->launch_executable_list->blockSignals(true); ui->launch_executable_list->clear(); for (std::size_t i = 0, n = executables.size(); i < n; ++i) { - ui->launch_executable_list->addItem(executables[i].path.RelativePath().c_str()); + ui->launch_executable_list->addItem(executables[i].path.AbsolutePath().c_str()); } ui->launch_executable_list->setCurrentIndex(configurator.executables.GetActiveExecutableIndex()); ui->launch_executable_list->blockSignals(false); @@ -320,11 +320,11 @@ void TabApplications::on_launch_options_list_activated(int index) { const ExecutableOptions *options = executable->GetActiveOptions(); - ui->launch_options_dir_edit->setText(options->working_folder.RelativePath().c_str()); + ui->launch_options_dir_edit->setText(options->working_folder.AbsolutePath().c_str()); ui->launch_options_dir_edit->setToolTip(options->working_folder.AbsolutePath().c_str()); ui->launch_options_args_edit->setText(Merge(options->args, " ").c_str()); ui->launch_options_envs_edit->setText(Merge(options->envs, " ").c_str()); - ui->launch_options_log_edit->setText(options->log_file.RelativePath().c_str()); + ui->launch_options_log_edit->setText(options->log_file.AbsolutePath().c_str()); ui->launch_options_log_edit->setToolTip(options->log_file.AbsolutePath().c_str()); } diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index 8740e6122e..40e75bac23 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -35,6 +35,7 @@ #include #include #include +#include static std::string BuildPlatformsLog(int platforms) { std::string log; @@ -145,6 +146,10 @@ TabConfigurations::TabConfigurations(MainWindow &window, std::shared_ptrconnect(this->advanced_mode, SIGNAL(pressed()), this, SLOT(on_configurations_advanced_toggle_pressed())); + QShortcut *shortcut_override = + new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->configurations_group_box_override); + this->connect(shortcut_override, SIGNAL(activated()), this, SLOT(on_configurations_override_toggled())); + this->UpdateUI(UPDATE_REBUILD_UI); } @@ -313,12 +318,17 @@ void TabConfigurations::UpdateUI_Settings(UpdateUIMode mode) { } void TabConfigurations::UpdateUI(UpdateUIMode ui_update_mode) { + const Configurator &configurator = Configurator::Get(); + + this->ui->configurations_group_box_override->blockSignals(true); + this->ui->configurations_group_box_override->setChecked(configurator.layers_override_enabled); + this->ui->configurations_group_box_override->blockSignals(false); + this->UpdateUI_Configurations(ui_update_mode); this->UpdateUI_Applications(ui_update_mode); this->UpdateUI_Layers(ui_update_mode); this->UpdateUI_Settings(ui_update_mode); - const Configurator &configurator = Configurator::Get(); const ExecutableScope scope = configurator.GetExecutableScope(); const bool enabled_executable = ::EnabledExecutables(scope); @@ -1111,6 +1121,10 @@ void TabConfigurations::on_configurations_advanced_toggle_pressed() { this->UpdateUI(UPDATE_REBUILD_UI); } +void TabConfigurations::on_configurations_override_toggled() { + this->ui->configurations_group_box_override->setChecked(!this->ui->configurations_group_box_override->isChecked()); +} + void TabConfigurations::on_configurations_override_toggled(bool checked) { Configurator &configurator = Configurator::Get(); configurator.layers_override_enabled = checked; diff --git a/vkconfig_gui/tab_configurations.h b/vkconfig_gui/tab_configurations.h index 9af22ad6ca..d6f3e984b3 100644 --- a/vkconfig_gui/tab_configurations.h +++ b/vkconfig_gui/tab_configurations.h @@ -50,6 +50,7 @@ class TabConfigurations : public Tab { public Q_SLOTS: void on_configurations_advanced_toggle_pressed(); + void on_configurations_override_toggled(); void on_configurations_override_toggled(bool checked); void on_configurations_executable_scope_currentIndexChanged(int index); void on_configurations_executable_list_currentIndexChanged(int index); diff --git a/vkconfig_gui/tab_diagnostics.cpp b/vkconfig_gui/tab_diagnostics.cpp index 237945d1f2..c26da9971c 100644 --- a/vkconfig_gui/tab_diagnostics.cpp +++ b/vkconfig_gui/tab_diagnostics.cpp @@ -60,6 +60,9 @@ TabDiagnostics::TabDiagnostics(MainWindow &window, std::shared_ptrconnect(this->ui->diagnostic_dir_system, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_system_pressed())); this->connect(this->ui->diagnostic_dir_info, SIGNAL(clicked()), this, SLOT(on_diagnostic_dir_info_pressed())); + QShortcut *shortcut_override = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->diagnostic_group_box_loader_log); + this->connect(shortcut_override, SIGNAL(activated()), this, SLOT(on_diagnostic_loader_messages_toggled())); + QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->diagnostic_status_text); this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); QShortcut *shortcut_next = new QShortcut(QKeySequence(Qt::Key_F3), this->ui->diagnostic_status_text); @@ -67,11 +70,11 @@ TabDiagnostics::TabDiagnostics(MainWindow &window, std::shared_ptrui->diagnostic_status_text); this->connect(shortcut_prev, SIGNAL(activated()), this, SLOT(on_search_prev_pressed())); - QShortcut *shortcut_case = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_C), this->ui->diagnostic_status_text); + QShortcut *shortcut_case = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_C), this->ui->diagnostic_search_case); this->connect(shortcut_case, SIGNAL(activated()), this, SLOT(on_search_case_activated())); - QShortcut *shortcut_whole = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_W), this->ui->diagnostic_status_text); + QShortcut *shortcut_whole = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_W), this->ui->diagnostic_search_whole); this->connect(shortcut_whole, SIGNAL(activated()), this, SLOT(on_search_whole_activated())); - QShortcut *shortcut_regex = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_R), this->ui->diagnostic_status_text); + QShortcut *shortcut_regex = new QShortcut(QKeySequence(Qt::ALT | Qt::Key_R), this->ui->diagnostic_search_regex); this->connect(shortcut_regex, SIGNAL(activated()), this, SLOT(on_search_regex_activated())); this->ui->diagnostic_search_next->setEnabled(false); @@ -309,6 +312,10 @@ bool TabDiagnostics::EventFilter(QObject *target, QEvent *event) { return false; } +void TabDiagnostics::on_diagnostic_loader_messages_toggled() { + this->ui->diagnostic_group_box_loader_log->setChecked(!this->ui->diagnostic_group_box_loader_log->isChecked()); +} + void TabDiagnostics::on_diagnostic_loader_messages_toggled(bool checked) { Configurator &configurator = Configurator::Get(); configurator.loader_log_enabled = checked; @@ -596,9 +603,16 @@ void TabDiagnostics::on_export_file() { } } -void TabDiagnostics::on_focus_search() { this->ui->diagnostic_search_edit->setFocus(); } +void TabDiagnostics::on_focus_search() { + this->ui->diagnostic_search_edit->setFocus(); + this->ui->diagnostic_status_text->moveCursor(QTextCursor::Start); +} void TabDiagnostics::on_search_textEdited(const QString &text) { + if (!this->ui->diagnostic_search_clear->isEnabled()) { + this->ui->diagnostic_status_text->moveCursor(QTextCursor::Start); + } + this->ui->diagnostic_search_next->setEnabled(!this->ui->diagnostic_search_edit->text().isEmpty()); this->ui->diagnostic_search_prev->setEnabled(!this->ui->diagnostic_search_edit->text().isEmpty()); @@ -608,7 +622,10 @@ void TabDiagnostics::on_search_textEdited(const QString &text) { void TabDiagnostics::on_search_clear_pressed() { this->diagnostic_search_text.clear(); + this->ui->diagnostic_export_file->setEnabled(false); this->ui->diagnostic_search_edit->clear(); + this->ui->diagnostic_search_next->setEnabled(false); + this->ui->diagnostic_search_prev->setEnabled(false); this->ui->diagnostic_search_clear->setEnabled(false); } @@ -667,7 +684,7 @@ void TabDiagnostics::on_context_menu(const QPoint &pos) { menu->addSeparator(); QAction *action_search = new QAction("Search...", nullptr); - action_search->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_F)); + // action_search->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_F)); action_search->setEnabled(true); menu->addAction(action_search); diff --git a/vkconfig_gui/tab_diagnostics.h b/vkconfig_gui/tab_diagnostics.h index 811a03a2b4..244eaab6cd 100644 --- a/vkconfig_gui/tab_diagnostics.h +++ b/vkconfig_gui/tab_diagnostics.h @@ -59,6 +59,7 @@ class TabDiagnostics : public Tab { void on_search_whole_activated(); void on_search_regex_activated(); + void on_diagnostic_loader_messages_toggled(); void on_diagnostic_loader_messages_toggled(bool checked); void on_diagnostic_loader_errors_toggled(bool checked); void on_diagnostic_loader_warns_toggled(bool checked); diff --git a/vkconfig_gui/tab_drivers.cpp b/vkconfig_gui/tab_drivers.cpp index bd5ea20060..a8895afed4 100644 --- a/vkconfig_gui/tab_drivers.cpp +++ b/vkconfig_gui/tab_drivers.cpp @@ -25,12 +25,12 @@ #include "style.h" #include "../vkconfig_core/configurator.h" +#include "../vkconfig_core/json.h" #include +#include TabDrivers::TabDrivers(MainWindow &window, std::shared_ptr ui) : Tab(TAB_DRIVERS, window, ui) { - this->ui->drivers_device_list->installEventFilter(&window); - Configurator &configurator = Configurator::Get(); if (configurator.vulkan_system_info.loaderVersion < Version(1, 4, 322) && false) { @@ -43,8 +43,24 @@ TabDrivers::TabDrivers(MainWindow &window, std::shared_ptr ui) : this->connect(this->ui->driver_forced_name, SIGNAL(currentIndexChanged(int)), this, SLOT(on_driver_name_changed(int))); this->connect(this->ui->driver_group_box_paths, SIGNAL(toggled(bool)), this, SLOT(on_driver_paths_toggled(bool))); - this->connect(this->ui->driver_browse_button, SIGNAL(clicked()), this, SLOT(on_driver_browse_pressed())); - this->connect(this->ui->driver_path_lineedit, SIGNAL(returnPressed()), this, SLOT(on_driver_append_pressed())); + this->connect(this->ui->driver_browse, SIGNAL(clicked()), this, SLOT(on_driver_browse_pressed())); + this->connect(this->ui->driver_path, SIGNAL(returnPressed()), this, SLOT(on_driver_append_pressed())); + + this->connect(this->ui->driver_search, SIGNAL(textEdited(QString)), this, SLOT(on_search_textEdited(QString))); + this->connect(this->ui->driver_search, SIGNAL(returnPressed()), this, SLOT(on_search_next_pressed())); + this->connect(this->ui->driver_search_clear, SIGNAL(clicked()), this, SLOT(on_search_clear_pressed())); + + QShortcut *shortcut_override = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->driver_group_box_override); + this->connect(shortcut_override, SIGNAL(activated()), this, SLOT(on_driver_override_toggled())); + + QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->driver_paths_list); + this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); + QShortcut *shortcut_browse = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_O), this->ui->driver_paths_list); + this->connect(shortcut_browse, SIGNAL(activated()), this, SLOT(on_driver_browse_pressed())); + + // this->ui->driver_group_box_override->installEventFilter(&window); + this->ui->drivers_device_list->installEventFilter(&window); + this->ui->driver_paths_list->installEventFilter(&window); this->UpdateUI(UPDATE_REBUILD_UI); } @@ -54,6 +70,9 @@ TabDrivers::~TabDrivers() {} void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { Configurator &configurator = Configurator::Get(); + this->ui->driver_search_clear->setEnabled(!this->ui->driver_search->text().isEmpty()); + this->ui->driver_search->setFocus(); + switch (ui_update_mode) { case UPDATE_REFRESH_UI: case UPDATE_REBUILD_UI: { @@ -131,14 +150,23 @@ void TabDrivers::UpdateUI(UpdateUIMode ui_update_mode) { } this->ui->driver_group_box_paths->blockSignals(true); - this->ui->driver_group_box_paths->setChecked(configurator.driver_paths_enabled); + this->ui->driver_group_box_paths->setChecked(configurator.driver_paths_enabled && configurator.driver_override_enabled); this->ui->driver_group_box_paths->blockSignals(false); this->ui->driver_paths_list->clear(); - this->ui->driver_path_lineedit->setText(configurator.last_driver_dir.RelativePath().c_str()); this->ui->driver_paths_list->blockSignals(true); for (auto it = configurator.driver_paths.begin(); it != configurator.driver_paths.end(); ++it) { + if (!this->driver_filter.empty()) { + const std::string text = it->first.AbsolutePath(); + + std::string lower_text = ::ToLowerCase(text); + std::string driver_filter_search = ::ToLowerCase(this->driver_filter); + if (lower_text.find(driver_filter_search.c_str()) == std::string::npos) { + continue; + } + } + QListWidgetItem *item_state = new QListWidgetItem; item_state->setFlags(item_state->flags() | Qt::ItemIsSelectable); item_state->setSizeHint(QSize(0, ITEM_HEIGHT)); @@ -190,12 +218,38 @@ bool TabDrivers::EventFilter(QObject *target, QEvent *event) { return false; } +void TabDrivers::on_focus_search() { this->ui->driver_search->setFocus(); } + +void TabDrivers::on_search_textEdited(const QString &text) { + this->driver_filter = text.toStdString(); + + this->ui->driver_search_clear->setEnabled(!text.isEmpty()); + + this->UpdateUI(UPDATE_REBUILD_UI); +} + +void TabDrivers::on_search_clear_pressed() { + this->driver_filter.clear(); + + this->ui->driver_search->clear(); + this->ui->driver_search_clear->setEnabled(false); + + this->UpdateUI(UPDATE_REBUILD_UI); +} + +void TabDrivers::on_search_next_pressed() {} + +void TabDrivers::on_driver_override_toggled() { + this->ui->driver_group_box_override->setChecked(!this->ui->driver_group_box_override->isChecked()); +} + void TabDrivers::on_driver_override_toggled(bool checked) { Configurator &configurator = Configurator::Get(); + int index = std::max(this->ui->driver_forced_name->currentIndex(), 0); + configurator.driver_override_enabled = checked; - configurator.driver_override_info = - ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[this->ui->driver_forced_name->currentIndex()]); + configurator.driver_override_info = ::GetDeviceInfo(configurator.vulkan_system_info.physicalDevices[index]); configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); this->UpdateUI(UPDATE_REBUILD_UI); @@ -244,7 +298,7 @@ void TabDrivers::on_paths_toggled() { void TabDrivers::on_driver_append_pressed() { Configurator &configurator = Configurator::Get(); - const Path &selected_path = this->ui->driver_path_lineedit->text().toStdString(); + const Path &selected_path = this->ui->driver_path->text().toStdString(); if (selected_path.Empty()) { return; @@ -277,16 +331,41 @@ void TabDrivers::on_driver_append_pressed() { void TabDrivers::on_driver_browse_pressed() { Configurator &configurator = Configurator::Get(); - const Path &selected_path = - QFileDialog::getExistingDirectory(this->ui->driver_browse_button, "Adding Driver Manifests Directory...", - configurator.last_driver_dir.AbsolutePath().c_str()) - .toStdString(); + const Path &selected_path = QFileDialog::getExistingDirectory(this->ui->driver_browse, "Adding Driver Manifests Directory...", + configurator.last_driver_dir.AbsolutePath().c_str()) + .toStdString(); if (selected_path.Empty()) { return; } + /* + QJsonDocument document = ParseJsonFile(selected_path.AbsolutePath().c_str()); + if (document.isNull() || document.isEmpty()) { + QMessageBox alert; + alert.setWindowTitle("Could not open the JSON file"); + alert.setText("The path"); + alert.setInformativeText(selected_path.AbsolutePath().c_str()); + alert.setStandardButtons(QMessageBox::Ok); + alert.setDefaultButton(QMessageBox::Ok); + alert.setIcon(QMessageBox::Warning); + alert.exec(); + return; + } - this->ui->driver_path_lineedit->setText(selected_path.AbsolutePath().c_str()); + const QJsonObject& json_root_object = document.object(); + if (json_root_object.value("ICD") == QJsonValue::Undefined) { + QMessageBox alert; + alert.setWindowTitle("Could not open the JSON file"); + alert.setText("The path"); + alert.setInformativeText(selected_path.AbsolutePath().c_str()); + alert.setStandardButtons(QMessageBox::Ok); + alert.setDefaultButton(QMessageBox::Ok); + alert.setIcon(QMessageBox::Warning); + alert.exec(); + return; + } + */ + this->ui->driver_path->setText(selected_path.AbsolutePath().c_str()); this->on_driver_append_pressed(); } diff --git a/vkconfig_gui/tab_drivers.h b/vkconfig_gui/tab_drivers.h index 5b3d0078c3..f2372e6a11 100644 --- a/vkconfig_gui/tab_drivers.h +++ b/vkconfig_gui/tab_drivers.h @@ -38,12 +38,19 @@ class TabDrivers : public Tab { void on_driver_mode_changed(int index); void on_driver_name_changed(int index); + void on_driver_override_toggled(); void on_driver_paths_toggled(bool checked); void on_paths_changed(); void on_paths_toggled(); void on_driver_append_pressed(); void on_driver_browse_pressed(); + void on_focus_search(); + void on_search_textEdited(const QString &text); + void on_search_clear_pressed(); + void on_search_next_pressed(); + private: std::string new_path; + std::string driver_filter; }; diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index fefd77001e..cc1fdc751c 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -28,6 +28,8 @@ #include #include +#include +#include #include #include @@ -38,13 +40,25 @@ TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : T this->ui->layers_progress->setValue(0); this->ui->layers_progress->setVisible(false); this->ui->layers_path_lineedit->setVisible(true); - this->ui->layers_path_lineedit->setText(configurator.layers.last_layers_path.RelativePath().c_str()); - this->ui->layers_validate_checkBox->setChecked(configurator.layers.validate_manifests); + this->ui->layers_search_clear->setEnabled(false); this->connect(this->ui->layers_browse_button, SIGNAL(clicked()), this, SLOT(on_layers_browse_pressed())); this->connect(this->ui->layers_reload_button, SIGNAL(clicked()), this, SLOT(on_layers_reload_pressed())); this->connect(this->ui->layers_path_lineedit, SIGNAL(returnPressed()), this, SLOT(on_layers_append_pressed())); - this->connect(this->ui->layers_validate_checkBox, SIGNAL(toggled(bool)), this, SLOT(on_layers_validate_checkBox_toggled(bool))); + + this->connect(this->ui->layers_search, SIGNAL(textEdited(QString)), this, SLOT(on_search_textEdited(QString))); + this->connect(this->ui->layers_search_clear, SIGNAL(clicked()), this, SLOT(on_search_clear_pressed())); + + QShortcut *shortcut_search = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_F), this->ui->layers_list); + this->connect(shortcut_search, SIGNAL(activated()), this, SLOT(on_focus_search())); + QShortcut *shortcut_browse = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_O), this->ui->layers_list); + this->connect(shortcut_browse, SIGNAL(activated()), this, SLOT(on_layers_browse_pressed())); + QShortcut *shortcut_reload = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_R), this->ui->layers_list); + this->connect(shortcut_reload, SIGNAL(activated()), this, SLOT(on_layers_reload_pressed())); + + // this->ui->layers_browse_button->installEventFilter(&window); + // this->ui->layers_reload_button->installEventFilter(&window); + this->ui->layers_list->installEventFilter(&window); } TabLayers::~TabLayers() {} @@ -54,87 +68,63 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { Configurator &configurator = Configurator::Get(); - this->ui->layers_paths_tree->blockSignals(true); + this->ui->layers_list->blockSignals(true); switch (ui_update_mode) { case UPDATE_REBUILD_UI: { - this->ui->layers_paths_tree->clear(); - for (std::size_t group_index = configurator.layers.paths.size(); group_index > 0; --group_index) { - const LayersPaths group_path = static_cast(group_index - 1); - - std::vector &paths_group = configurator.layers.paths[group_path]; - for (std::size_t i = 0, n = paths_group.size(); i < n; ++i) { - const std::string &layer_path = paths_group[i].path.AbsolutePath(); - const std::vector &manifest_paths = CollectFilePaths(layer_path); - /* - if (group_path != LAYERS_PATHS_GUI) { - if (manifest_paths.empty()) { - continue; - } - } - */ - QTreeWidgetItem *item_state = new QTreeWidgetItem; - item_state->setFlags(item_state->flags() | Qt::ItemIsSelectable); - item_state->setSizeHint(0, QSize(0, ITEM_HEIGHT)); - LayersPathWidget *layer_path_widget = - new LayersPathWidget(paths_group[i], group_path, group_path == LAYERS_PATHS_GUI); - this->connect(layer_path_widget, SIGNAL(itemChanged()), this, SLOT(on_paths_changed())); - this->connect(layer_path_widget, SIGNAL(itemToggled()), this, SLOT(on_paths_toggled())); - - ui->layers_paths_tree->addTopLevelItem(item_state); - ui->layers_paths_tree->setItemWidget(item_state, 0, layer_path_widget); - - for (std::size_t manifest_index = 0, manifest_count = manifest_paths.size(); manifest_index < manifest_count; - ++manifest_index) { - const Layer *layer = configurator.layers.FindFromManifest(manifest_paths[manifest_index], true); - if (layer == nullptr) { - continue; // When the directory has JSON files that are not layer manifest - } - - std::string label = layer->key; - - if (layer->is_32bits) { - label += " (32 bits)"; - } - - label += " - " + layer->api_version.str(); - - if (layer->status != STATUS_STABLE) { - label += format(" (%s)", GetToken(layer->status)); - } - - // label += " - " + layer->manifest_path.AbsolutePath(); - - QTreeWidgetItem *item = new QTreeWidgetItem; - item->setText(0, label.c_str()); - item->setToolTip(0, layer->manifest_path.AbsolutePath().c_str()); - item->setDisabled(!paths_group[i].enabled); - item_state->addChild(item); + this->ui->layers_list->clear(); + + const std::set &layer_display_list = configurator.layers.BuildLayerDisplayList(); + + for (auto it = layer_display_list.begin(), end = layer_display_list.end(); it != end; ++it) { + const Layer *layer = configurator.layers.FindFromManifest(it->manifest_path, true); + if (layer == nullptr) { + continue; + } + + if (!this->layer_filter.empty()) { + const std::string status = layer->status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer->status)); + const std::string text = format("%s - %s%s, %s layer", layer->key.c_str(), layer->api_version.str().c_str(), + status.c_str(), ::GetToken(layer->type)); + + std::string lower_text = ::ToLowerCase(text); + std::string layer_filter_search = ::ToLowerCase(this->layer_filter); + if (lower_text.find(layer_filter_search.c_str()) == std::string::npos) { + continue; } } - } - this->ui->layers_paths_tree->expandAll(); - } break; - case UPDATE_REFRESH_UI: { - for (int i = 0; i < this->ui->layers_paths_tree->topLevelItemCount(); ++i) { - QTreeWidgetItem *item = this->ui->layers_paths_tree->topLevelItem(i); - LayersPathWidget *widget = static_cast(ui->layers_paths_tree->itemWidget(item, 0)); - - for (int i = 0; i < item->childCount(); ++i) { - QTreeWidgetItem *child_item = item->child(i); - child_item->setDisabled(!widget->isChecked()); + QListWidgetItem *item = new QListWidgetItem; + item->setFlags(item->flags() | Qt::ItemIsSelectable); + item->setSizeHint(QSize(0, ITEM_HEIGHT)); + + LayerWidget *layer_widget = new LayerWidget(layer); + layer_widget->setChecked(it->descriptor.enabled); + if (it->descriptor.added) { + QPalette palette = layer_widget->palette(); + palette.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0)); + layer_widget->setPalette(palette); } + + this->connect(layer_widget, SIGNAL(itemChanged()), this, SLOT(on_paths_changed())); + this->connect(layer_widget, SIGNAL(itemToggled()), this, SLOT(on_paths_toggled())); + + ui->layers_list->addItem(item); + ui->layers_list->setItemWidget(item, layer_widget); } } break; + default: { + } break; } - this->ui->layers_paths_tree->blockSignals(false); + this->ui->layers_list->blockSignals(false); } void TabLayers::UpdateUI(UpdateUIMode ui_update_mode) { const Configurator &configurator = Configurator::Get(); + this->ui->layers_search_clear->setEnabled(!this->ui->layers_search->text().isEmpty()); + this->ui->layers_search->setFocus(); this->ui->layers_progress->resetFormat(); this->ui->layers_progress->setValue(0); @@ -168,11 +158,6 @@ void TabLayers::on_paths_toggled() { this->UpdateUI_LayersPaths(UPDATE_REFRESH_UI); } -void TabLayers::on_layers_validate_checkBox_toggled(bool checked) { - Configurator &configurator = Configurator::Get(); - configurator.layers.validate_manifests = checked; -} - void TabLayers::on_layers_append_pressed() { this->LoadLayersManifest(this->ui->layers_path_lineedit->text()); } void TabLayers::on_layers_browse_pressed() { @@ -180,11 +165,30 @@ void TabLayers::on_layers_browse_pressed() { const QString selected_path = QFileDialog::getExistingDirectory(this->ui->layers_browse_button, "Select Layer Manifests Folder...", - configurator.layers.last_layers_path.AbsolutePath().c_str()); + configurator.layers.last_layers_dir.AbsolutePath().c_str()); this->LoadLayersManifest(selected_path); } +void TabLayers::on_focus_search() { this->ui->layers_search->setFocus(); } + +void TabLayers::on_search_textEdited(const QString &text) { + this->layer_filter = text.toStdString(); + + this->ui->layers_search_clear->setEnabled(!text.isEmpty()); + + this->UpdateUI_LayersPaths(UPDATE_REBUILD_UI); +} + +void TabLayers::on_search_clear_pressed() { + this->layer_filter.clear(); + + this->ui->layers_search->clear(); + this->ui->layers_search_clear->setEnabled(false); + + this->UpdateUI_LayersPaths(UPDATE_REBUILD_UI); +} + void TabLayers::on_layers_reload_pressed() { this->ui->layers_path_lineedit->setVisible(false); this->ui->layers_browse_button->setVisible(false); @@ -196,13 +200,11 @@ void TabLayers::on_layers_reload_pressed() { std::vector layers_count(LAYER_LOAD_COUNT, 0); - LayersPathInfo info; - info.path = new_path; - configurator.layers.AppendPath(info); - configurator.layers.UpdatePathEnabled(info, LAYERS_PATHS_GUI); + const std::vector &layers_paths = ::CollectFilePaths(new_path); - const std::vector layers_paths = - new_path.Empty() ? configurator.layers.CollectManifestPaths() : ::CollectFilePaths(new_path); + for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { + configurator.layers.AppendPath(layers_paths[i], LAYER_TYPE_EXPLICIT, true); + } this->ui->layers_progress->setMaximum(static_cast(layers_paths.size())); this->ui->layers_progress->setValue(0); @@ -224,7 +226,7 @@ void TabLayers::on_layers_reload_pressed() { configurator.UpdateConfigurations(); configurator.Override(OVERRIDE_AREA_ALL); - std::string last_layers_path = configurator.layers.last_layers_path.AbsolutePath(); + std::string last_layers_path = configurator.layers.last_layers_dir.AbsolutePath(); if (!configurator.Get(HIDE_MESSAGE_NOTIFICATION_LAYERS_LOADED)) { std::string text = "Loading and reloading all located layers manifests:\n"; @@ -255,8 +257,10 @@ void TabLayers::LoadLayersManifest(const QString &selected_path) { Configurator &configurator = Configurator::Get(); if (!selected_path.isEmpty()) { - configurator.layers.last_layers_path = selected_path.toStdString(); - this->ui->layers_path_lineedit->setText(configurator.layers.last_layers_path.AbsolutePath().c_str()); + configurator.layers.last_layers_dir = selected_path.toStdString(); + if (!configurator.layers.last_layers_dir.Empty()) { + this->ui->layers_path_lineedit->setText(configurator.layers.last_layers_dir.AbsolutePath().c_str()); + } this->on_layers_reload_pressed(); diff --git a/vkconfig_gui/tab_layers.h b/vkconfig_gui/tab_layers.h index 7513a62b6f..aa55c1b3b8 100644 --- a/vkconfig_gui/tab_layers.h +++ b/vkconfig_gui/tab_layers.h @@ -38,11 +38,16 @@ class TabLayers : public Tab { public Q_SLOTS: void on_paths_changed(); void on_paths_toggled(); - void on_layers_validate_checkBox_toggled(bool checked); void on_layers_append_pressed(); void on_layers_browse_pressed(); void on_layers_reload_pressed(); + void on_focus_search(); + void on_search_textEdited(const QString &text); + void on_search_clear_pressed(); + private: void LoadLayersManifest(const QString &selected_path); + + std::string layer_filter; }; diff --git a/vkconfig_gui/tab_preferences.cpp b/vkconfig_gui/tab_preferences.cpp index 4424fbb397..e9cb33fa8b 100644 --- a/vkconfig_gui/tab_preferences.cpp +++ b/vkconfig_gui/tab_preferences.cpp @@ -48,6 +48,7 @@ TabPreferences::TabPreferences(MainWindow &window, std::shared_ptrconnect(this->ui->preferences_vk_download_open, SIGNAL(clicked()), this, SLOT(on_vk_download_open_pressed())); this->connect(this->ui->preferences_reset, SIGNAL(clicked()), this, SLOT(on_reset_hard_pressed())); this->connect(this->ui->preferences_show_debug_settings, SIGNAL(toggled(bool)), this, SLOT(on_layer_debug_mode_toggled(bool))); + this->connect(this->ui->preferences_validate_layer, SIGNAL(toggled(bool)), this, SLOT(on_layer_validate_toggled(bool))); this->connect(this->ui->preferences_open_page, SIGNAL(clicked()), this, SLOT(on_open_page_pressed())); this->connect(this->ui->preferences_notify_releases, SIGNAL(toggled(bool)), this, SLOT(on_notify_releases_toggled(bool))); this->connect(this->ui->preferences_download, SIGNAL(clicked()), this, SLOT(on_download_pressed())); @@ -85,8 +86,8 @@ TabPreferences::TabPreferences(MainWindow &window, std::shared_ptrui->preferences_progress->setVisible(false); this->ui->preferences_notify_releases->setChecked(configurator.GetUseNotifyReleases()); - this->ui->preferences_download->setText("Searching Latest Vulkan SDK..."); + this->ui->preferences_validate_layer->setChecked(configurator.layers.validate_manifests); #if WORKAROUND_WINARM_RELEASE_NOTIFICATION_BUG // Windows ARM crash, it looks like a Qt bug in 6.8.2... @@ -170,11 +171,13 @@ void TabPreferences::on_theme_mode_changed(int index) { this->ui->configurations_settings_reset->setIcon(::Get(new_theme_mode, ::ICON_RELOAD)); // Drivers - this->ui->driver_browse_button->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_SEARCH)); + this->ui->driver_browse->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_SEARCH)); + this->ui->driver_search_clear->setIcon(::Get(new_theme_mode, ::ICON_EXIT)); // Layers this->ui->layers_browse_button->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_SEARCH)); this->ui->layers_reload_button->setIcon(::Get(new_theme_mode, ::ICON_FOLDER_RELOAD)); + this->ui->layers_search_clear->setIcon(::Get(new_theme_mode, ::ICON_EXIT)); // Applications this->ui->launch_executable_search->setIcon(::Get(new_theme_mode, ::ICON_FILE_SEARCH)); @@ -259,7 +262,8 @@ void TabPreferences::on_theme_mode_changed(int index) { this->ui->configurations_list->setPalette(palette); this->ui->configurations_settings->setPalette(palette); this->ui->configurations_layers_list->setPalette(palette); - this->ui->layers_paths_tree->setPalette(palette); + this->ui->layers_list->setPalette(palette); + this->ui->driver_paths_list->setPalette(palette); delete dummy_widget; } @@ -431,6 +435,11 @@ void TabPreferences::on_notify_releases_toggled(bool checked) { configurator.SetUseNotifyReleases(checked); } +void TabPreferences::on_layer_validate_toggled(bool checked) { + Configurator &configurator = Configurator::Get(); + configurator.layers.validate_manifests = checked; +} + void TabPreferences::on_layer_debug_mode_toggled(bool checked) { Configurator &configurator = Configurator::Get(); configurator.SetUseLayerDebugMode(checked); diff --git a/vkconfig_gui/tab_preferences.h b/vkconfig_gui/tab_preferences.h index 62431c434a..7bfa93543a 100644 --- a/vkconfig_gui/tab_preferences.h +++ b/vkconfig_gui/tab_preferences.h @@ -45,6 +45,7 @@ class TabPreferences : public Tab { void on_vk_download_browse_pressed(); void on_vk_download_open_pressed(); void on_reset_hard_pressed(); + void on_layer_validate_toggled(bool checked); void on_layer_debug_mode_toggled(bool checked); void on_open_page_pressed(); void on_download_pressed(); diff --git a/vkconfig_gui/widget_tab_driver_path.cpp b/vkconfig_gui/widget_tab_driver_path.cpp index 650bba6866..7d5e4b97b9 100644 --- a/vkconfig_gui/widget_tab_driver_path.cpp +++ b/vkconfig_gui/widget_tab_driver_path.cpp @@ -32,10 +32,10 @@ DriverPathWidget::DriverPathWidget(const Path& path, bool enabled) : path(path), const Configurator& configurator = Configurator::Get(); this->buttom_remove = new QPushButton(this); - this->buttom_remove->setIcon(::Get(configurator.current_theme_mode, ::ICON_FOLDER_REMOVE)); + this->buttom_remove->setIcon(::Get(configurator.current_theme_mode, ::ICON_FILE_REMOVE)); this->buttom_remove->setFixedSize(24, 24); - this->setText(format("%s", this->path.RelativePath().c_str()).c_str()); + this->setText(format("%s", this->path.AbsolutePath().c_str()).c_str()); this->setToolTip(format("%s", this->path.AbsolutePath().c_str()).c_str()); this->connect(this, SIGNAL(toggled(bool)), this, SLOT(on_toggled(bool))); diff --git a/vkconfig_gui/widget_tab_layers_path.cpp b/vkconfig_gui/widget_tab_layers_path.cpp index 8b3503e4da..d4b84eea4d 100644 --- a/vkconfig_gui/widget_tab_layers_path.cpp +++ b/vkconfig_gui/widget_tab_layers_path.cpp @@ -26,29 +26,27 @@ #include #include -LayersPathWidget::LayersPathWidget(const LayersPathInfo& path_info, LayersPaths layers_path, bool removabled) - : layers_path(layers_path), path_info(path_info) { - this->setChecked(this->path_info.enabled); - +LayerWidget::LayerWidget(const Layer* layer) + : layer_key(layer->key), api_version(layer->api_version), manifest_path(layer->manifest_path) { const Configurator& configurator = Configurator::Get(); + const std::string status = layer->status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer->status)); + const std::string text = format("%s - %s%s, %s layer", layer->key.c_str(), layer->api_version.str().c_str(), status.c_str(), + ::GetToken(layer->type)); + + this->setText(text.c_str()); + this->setToolTip(format("%s", manifest_path.AbsolutePath().c_str()).c_str()); + this->buttom_remove = new QPushButton(this); this->buttom_remove->setIcon(::Get(configurator.current_theme_mode, ::ICON_FOLDER_REMOVE)); - this->buttom_remove->setToolTip("Only layer paths manually added with Vulkan Configurator can be removed."); + this->buttom_remove->setToolTip("Remove the Vulkan layer from Vulkan Configurator"); this->buttom_remove->setFixedSize(24, 24); - this->buttom_remove->setEnabled(layers_path == LAYERS_PATHS_GUI); - - if (!removabled) { - this->buttom_remove->hide(); - } - - this->setText(format("[%s] %s", GetLabel(layers_path), path_info.path.AbsolutePath().c_str()).c_str()); this->connect(this, SIGNAL(toggled(bool)), this, SLOT(on_toggled(bool))); this->connect(this->buttom_remove, SIGNAL(clicked(bool)), this, SLOT(on_buttom_remove_clicked(bool))); } -void LayersPathWidget::resizeEvent(QResizeEvent* event) { +void LayerWidget::resizeEvent(QResizeEvent* event) { QSize size = event->size(); const QFontMetrics fm = this->buttom_remove->fontMetrics(); @@ -58,20 +56,22 @@ void LayersPathWidget::resizeEvent(QResizeEvent* event) { this->buttom_remove->setGeometry(remove_button_rect); } -void LayersPathWidget::on_buttom_remove_clicked(bool checked) { +void LayerWidget::on_buttom_remove_clicked(bool checked) { (void)checked; Configurator& configurator = Configurator::Get(); if (!(configurator.Get(HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH))) { QMessageBox alert; - alert.setWindowTitle("Removing a layers path..."); + alert.setWindowTitle(format("Removing %s %s layer...", this->layer_key.c_str(), this->api_version.str().c_str()).c_str()); alert.setIcon(QMessageBox::Question); alert.setDefaultButton(QMessageBox::No); alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); alert.setCheckBox(new QCheckBox("Do not show again.")); - alert.setText(format("Are you sure you want to remove the following path from %s?", VKCONFIG_NAME).c_str()); - alert.setInformativeText(this->path_info.path.AbsolutePath().c_str()); + alert.setText(format("Are you sure you want to remove %s %s from %s?", this->layer_key.c_str(), + this->api_version.str().c_str(), VKCONFIG_NAME) + .c_str()); + alert.setInformativeText(format("The layer is located: %s", this->manifest_path.AbsolutePath().c_str()).c_str()); int ret_val = alert.exec(); if (alert.checkBox()->isChecked()) { configurator.Set(HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH); @@ -81,21 +81,14 @@ void LayersPathWidget::on_buttom_remove_clicked(bool checked) { } } - configurator.layers.RemovePath(this->path_info); - - this->path_info.path.Clear(); + configurator.layers.RemovePath(this->manifest_path); emit itemChanged(); } -void LayersPathWidget::on_toggled(bool checked) { - // Check the path is not removed - if (!this->path_info.path.Empty()) { - this->path_info.enabled = checked; - - Configurator& configurator = Configurator::Get(); - configurator.layers.UpdatePathEnabled(this->path_info, this->layers_path); +void LayerWidget::on_toggled(bool checked) { + Configurator& configurator = Configurator::Get(); + configurator.layers.Enable(this->manifest_path, checked); - emit itemToggled(); - } + emit itemToggled(); } diff --git a/vkconfig_gui/widget_tab_layers_path.h b/vkconfig_gui/widget_tab_layers_path.h index 83f20332b2..d91da0f0e3 100644 --- a/vkconfig_gui/widget_tab_layers_path.h +++ b/vkconfig_gui/widget_tab_layers_path.h @@ -27,11 +27,11 @@ #include #include -class LayersPathWidget : public QCheckBox { +class LayerWidget : public QCheckBox { Q_OBJECT public: - LayersPathWidget(const LayersPathInfo &path_info, LayersPaths layers_path, bool removabled); + LayerWidget(const Layer *layer); protected: void resizeEvent(QResizeEvent *event) override; @@ -45,7 +45,8 @@ class LayersPathWidget : public QCheckBox { void itemToggled(); public: - LayersPaths layers_path; - LayersPathInfo path_info; + std::string layer_key; + Version api_version; + Path manifest_path; QPushButton *buttom_remove = nullptr; }; From c6b4b1e14c0c11ecd91cabedb042e666ce7bab78 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 31 Mar 2026 13:46:26 +0200 Subject: [PATCH 14/45] vkconfig: Fix mix layer and icd manifest --- vkconfig_cmd/main_layers.cpp | 2 +- vkconfig_core/configuration.h | 5 - vkconfig_core/configurator.cpp | 90 +++- vkconfig_core/layer.cpp | 261 +++++----- vkconfig_core/layer.h | 37 +- vkconfig_core/layer_manager.cpp | 446 ++++++++++++------ vkconfig_core/layer_manager.h | 41 +- vkconfig_core/path.cpp | 60 +++ vkconfig_core/path.h | 6 + vkconfig_core/test/test_configuration.cpp | 2 +- .../test/test_configuration_built_in.cpp | 2 +- .../test/test_configuration_manager.cpp | 2 +- vkconfig_core/test/test_generate_settings.cpp | 2 +- vkconfig_core/test/test_layer.cpp | 91 ++-- vkconfig_core/test/test_layer_manager.cpp | 33 +- vkconfig_core/test/test_layer_preset.cpp | 29 +- vkconfig_core/test/test_parameter.cpp | 24 +- vkconfig_gui/CHANGELOG.md | 2 + vkconfig_gui/mainwindow.ui | 56 ++- vkconfig_gui/tab_configurations.cpp | 15 +- vkconfig_gui/tab_drivers.cpp | 14 +- vkconfig_gui/tab_layers.cpp | 70 ++- vkconfig_gui/tab_layers.h | 1 + vkconfig_gui/widget_tab_layers_path.cpp | 31 +- vkconfig_gui/widget_tab_layers_path.h | 6 +- 25 files changed, 886 insertions(+), 442 deletions(-) diff --git a/vkconfig_cmd/main_layers.cpp b/vkconfig_cmd/main_layers.cpp index 674844bed7..d1daeccfcf 100644 --- a/vkconfig_cmd/main_layers.cpp +++ b/vkconfig_cmd/main_layers.cpp @@ -92,7 +92,7 @@ static int RunLayersPath(Configurator& configurator, const CommandLine& command_ const std::set& layer_display_list = configurator.layers.BuildLayerDisplayList(); for (auto it = layer_display_list.begin(), end = layer_display_list.end(); it != end; ++it) { - const Layer* layer = configurator.layers.FindFromManifest(it->manifest_path, true); + const Layer* layer = configurator.layers.FindFromManifest(it->id.manifest_path, true); if (layer == nullptr) { continue; } diff --git a/vkconfig_core/configuration.h b/vkconfig_core/configuration.h index 9a17aeae39..eb29465b7a 100644 --- a/vkconfig_core/configuration.h +++ b/vkconfig_core/configuration.h @@ -62,12 +62,7 @@ class Configuration { LayerControl default_control = LAYER_CONTROL_AUTO; bool override_settings = false; Path override_settings_path; - // bool override_layers = true; // Keep or not ? std::string selected_layer_name; - // bool override_driver = false; - // std::string override_driver_name = DEFAULT_PHYSICAL_DEVICE; - // bool override_loader = true; - // int loader_log_messages_flags = GetBit(LOG_ERROR); std::vector parameters; diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index 07541c7d2d..23ac737beb 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -1116,7 +1116,41 @@ bool Configurator::Load() { // TAB_LAYERS_PATHS if (json_interface_object.value(GetToken(TAB_LAYERS_PATHS)) != QJsonValue::Undefined) { const QJsonObject& json_object = json_interface_object.value(GetToken(TAB_LAYERS_PATHS)).toObject(); - (void)json_object; + if (json_object.value("last_driver_dir") != QJsonValue::Undefined) { + this->layers.last_layers_dir = json_object.value("last_driver_dir").toString().toStdString(); + } + if (json_object.value("validate_manifests") != QJsonValue::Undefined) { + this->layers.validate_manifests = json_object.value("validate_manifests").toBool(); + } + if (json_object.value("layers") != QJsonValue::Undefined) { + const QJsonObject& json_object_paths = json_object.value("layers").toObject(); + QStringList keys = json_object_paths.keys(); + for (std::size_t i = 0, n = keys.size(); i < n; ++i) { + const QJsonArray& json_descriptors = json_object_paths.value(keys[i]).toArray(); + + std::vector descriptors; + for (std::size_t j = 0, o = json_descriptors.size(); j < o; ++j) { + const QJsonObject& json_descriptor_object = json_descriptors[j].toObject(); + + LayerDisplay display; + display.id.key = json_descriptor_object.value("key").toString().toStdString(); + display.id.manifest_path = keys[i].toStdString(); + display.id.api_version = Version::NONE; + display.descriptor.enabled = json_descriptor_object.value("enabled").toBool(); + display.descriptor.removed = json_descriptor_object.value("removed").toBool(); + display.descriptor.validated = json_descriptor_object.value("validated").toBool(); + descriptors.push_back(display); + } + + this->layers.AppendInit(Path(keys[i].toStdString()), descriptors); + } + } + if (json_object.value("paths") != QJsonValue::Undefined) { + const QJsonArray& json_paths = json_object.value("paths").toArray(); + for (std::size_t i = 0, n = json_paths.size(); i < n; ++i) { + this->layers.gui_added_layers_paths.insert(json_paths[i].toString().toStdString()); + } + } } // TAB_DRIVERS @@ -1199,6 +1233,10 @@ bool Configurator::Load() { if (json_interface_object.value(GetToken(TAB_PREFERENCES)) != QJsonValue::Undefined) { const QJsonObject& json_object = json_interface_object.value(GetToken(TAB_PREFERENCES)).toObject(); + if (json_object.value("validate_manifests") != QJsonValue::Undefined) { + this->layers.validate_manifests = json_object.value("validate_manifests").toBool(); + } + if (json_object.value("use_notify_releases") != QJsonValue::Undefined) { this->use_notify_releases = json_object.value("use_notify_releases").toBool(); } @@ -1311,7 +1349,55 @@ bool Configurator::Save() const { } // TAB_LAYERS_PATHS - {} + { + std::map> data = this->layers.BuildLayerStoreList(); + + QJsonObject json_layers; + for (auto it = data.begin(); it != data.end(); ++it) { + std::map descriptors = it->second; + + QJsonArray json_layer_descriptors; + + bool is_user_added_path = + this->layers.gui_added_layers_paths.find(it->first.AbsoluteDir()) != this->layers.gui_added_layers_paths.end(); + + bool keep = false; // Only remember paths that don't have all layer removed + + for (auto jt = it->second.begin(); jt != it->second.end(); ++jt) { + if (!jt->second.descriptor.removed) { + keep = true; + } + + QJsonObject json_descriptor; + json_descriptor.insert("key", jt->first.c_str()); + json_descriptor.insert("validated", jt->second.descriptor.validated); + json_descriptor.insert("enabled", jt->second.descriptor.enabled); + json_descriptor.insert("removed", jt->second.descriptor.removed); + + json_layer_descriptors.append(json_descriptor); + } + + if (is_user_added_path) { + if (keep) { + json_layers.insert(it->first.AbsolutePath().c_str(), json_layer_descriptors); + } + } else { + json_layers.insert(it->first.AbsolutePath().c_str(), json_layer_descriptors); + } + } + + QJsonArray json_paths; + for (auto it = this->layers.gui_added_layers_paths.begin(); it != this->layers.gui_added_layers_paths.end(); ++it) { + json_paths.append(it->AbsolutePath().c_str()); + } + + QJsonObject json_object; + json_object.insert("paths", json_paths); + json_object.insert("layers", json_layers); + json_object.insert("validate_manifests", this->layers.validate_manifests); + json_object.insert("last_driver_dir", this->layers.last_layers_dir.AbsolutePath().c_str()); + json_interface_object.insert(::GetToken(TAB_LAYERS_PATHS), json_object); + } // TAB_DRIVER { diff --git a/vkconfig_core/layer.cpp b/vkconfig_core/layer.cpp index a1cb6238e9..8d7c89c2cc 100644 --- a/vkconfig_core/layer.cpp +++ b/vkconfig_core/layer.cpp @@ -33,7 +33,6 @@ #include "util.h" #include "path.h" #include "json.h" -#include "json_validator.h" #include "is_dll_32.h" #include "configurator.h" @@ -42,36 +41,22 @@ #include #include #include -#include -#include #include #include #include -bool Found(const std::vector& data, const Path& path) { - for (std::size_t i = 0, n = data.size(); i < n; ++i) { - if (data[i] == path) { - return true; - } - } - - return false; -} - Layer::Layer() : status(STATUS_STABLE), platforms(PLATFORM_DESKTOP_BIT) {} Layer::Layer(const std::string& key) : key(key), status(STATUS_STABLE), platforms(PLATFORM_DESKTOP_BIT) {} -Layer::Layer(const std::string& key, const Version& file_format_version, const Version& api_version, - const std::string& implementation_version, const std::string& library_path) - : key(key), - file_format_version(file_format_version), - binary_path(library_path), - api_version(api_version), - implementation_version(implementation_version), - status(STATUS_STABLE), - platforms(PLATFORM_DESKTOP_BIT) {} +LayerId Layer::GetId() const { + LayerId id; + id.manifest_path = this->manifest_path; + id.key = this->key; + id.api_version = this->api_version; + return id; +} bool Layer::IsValid() const { return file_format_version != Version::NONE && !key.empty() && !binary_path.Empty() && api_version != Version::NONE && @@ -176,71 +161,7 @@ void Layer::FillPresetSettings(SettingDataSet& settings_data, const std::vector< } } -LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool request_validate_manifest, - ConfiguratorMode configurator_mode) { - this->type = type; // Set layer type, no way to know this from the json file - - if (full_path_to_file.Empty()) { - return LAYER_LOAD_IGNORED; - } - - QFile file(full_path_to_file.AbsolutePath().c_str()); - if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { - return LAYER_LOAD_FAILED; - } - - QString json_text = file.readAll(); - file.close(); - - this->manifest_path = full_path_to_file; - this->last_modified = full_path_to_file.LastModified(); - - // Convert the text to a JSON document & validate it. - // It does need to be a valid json formatted file. - QJsonParseError json_parse_error; - const QJsonDocument& json_document = QJsonDocument::fromJson(json_text.toUtf8(), &json_parse_error); - if (json_parse_error.error != QJsonParseError::NoError) { - return LAYER_LOAD_FAILED; - } - - // Make sure it's not empty - if (json_document.isNull() || json_document.isEmpty()) { - return LAYER_LOAD_FAILED; - } - - // First check it's a layer manifest, ignore otherwise. - const QJsonObject& json_root_object = json_document.object(); - if (json_root_object.value("file_format_version") == QJsonValue::Undefined) { - return LAYER_LOAD_IGNORED; // Not a layer JSON file - } - if (json_root_object.value("layer") == QJsonValue::Undefined) { - return LAYER_LOAD_IGNORED; // Not a layer JSON file - } - - this->file_format_version = ReadVersionValue(json_root_object, "file_format_version"); - if (this->file_format_version.GetMajor() > 1) { - switch (configurator_mode) { - default: { - } break; - case CONFIGURATOR_MODE_GUI: { - QMessageBox alert; - alert.setWindowTitle("Failed to load a layer manifest..."); - alert.setText(format("Unsupported layer file format: %s.", this->file_format_version.str().c_str()).c_str()); - alert.setInformativeText( - format("The %s layer is being ignored.", full_path_to_file.AbsolutePath().c_str()).c_str()); - alert.setIcon(QMessageBox::Critical); - alert.exec(); - } break; - case CONFIGURATOR_MODE_CMD: { - fprintf(stderr, "vkconfig: [ERROR] Unsupported layer file format: %s\n", this->file_format_version.str().c_str()); - fprintf(stderr, "\n (%s layer is ignored\n)", full_path_to_file.AbsolutePath().c_str()); - } break; - } - return LAYER_LOAD_INVALID; - } - - const QJsonObject& json_layer_object = ReadObject(json_root_object, "layer"); - +LayerLoadStatus Layer::Load(const QJsonObject& json_layer_object) { this->key = ReadStringValue(json_layer_object, "name"); if (this->key == "VK_LAYER_LUNARG_override" || !(this->key.rfind("VK_", 0) == 0)) { @@ -249,50 +170,6 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool this->api_version = ReadVersionValue(json_layer_object, "api_version"); - JsonValidator validator; - - const bool is_valid = request_validate_manifest ? validator.Check(json_text) : true; - - if (!is_valid) { - switch (configurator_mode) { - default: { - } break; - case CONFIGURATOR_MODE_GUI: { - QMessageBox alert; - alert.setWindowTitle("Failed to load a layer manifest..."); - alert.setText(format("%s is not a valid layer file", full_path_to_file.AbsolutePath().c_str()).c_str()); - alert.setInformativeText("Do you want to save the validation log?"); - alert.setIcon(QMessageBox::Critical); - alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); - alert.setDefaultButton(QMessageBox::Yes); - int result = alert.exec(); - if (result == QMessageBox::Yes) { - const QString& selected_path = QFileDialog::getSaveFileName( - nullptr, format("Export %s validation log", full_path_to_file.AbsolutePath().c_str()).c_str(), - (AbsolutePath(Path::HOME) + "/" + full_path_to_file.Basename() + "_log.txt").c_str(), "Log(*.txt)"); - QFile log_file(selected_path); - const bool result = log_file.open(QIODevice::WriteOnly | QIODevice::Text); - if (result) { - QDesktopServices::openUrl(QUrl::fromLocalFile(selected_path)); - log_file.write(validator.message.toStdString().c_str()); - log_file.close(); - } else { - QMessageBox alert; - alert.setWindowTitle("Failed to save layer manifest log..."); - alert.setText(format("Couldn't not open %s file...", selected_path.toStdString().c_str()).c_str()); - alert.setIcon(QMessageBox::Critical); - alert.exec(); - } - } - } break; - case CONFIGURATOR_MODE_CMD: { - fprintf(stderr, "vkconfig: [ERROR] Couldn't validate layer file: %s\n", full_path_to_file.AbsolutePath().c_str()); - fprintf(stderr, "\n%s\n)", validator.message.toStdString().c_str()); - } break; - } - return LAYER_LOAD_INVALID; - } - const QJsonValue& json_library_path_value = json_layer_object.value("library_path"); if (json_library_path_value != QJsonValue::Undefined) { this->binary_path = json_library_path_value.toString().toStdString(); @@ -301,7 +178,7 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool const Path& binary = this->manifest_path.AbsoluteDir() + "/" + this->binary_path.AbsolutePath(); if (::IsDLL32Bit(binary.AbsolutePath())) { this->is_32bits = true; - return LAYER_LOAD_INVALID; + return LAYER_LOAD_IGNORED; } if (json_layer_object.value("prefix") != QJsonValue::Undefined) { @@ -390,9 +267,127 @@ LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, bool } } - return this->IsValid() ? LAYER_LOAD_ADDED : LAYER_LOAD_INVALID; // Not all JSON file are layer JSON valid + return this->IsValid() ? LAYER_LOAD_ADDED : LAYER_LOAD_INVALID; } +/* +LayerLoadStatus Layer::Load(const Path& full_path_to_file, LayerType type, ConfiguratorMode configurator_mode) { + this->type = type; // Set layer type, no way to know this from the json file + + if (full_path_to_file.Empty()) { + return LAYER_LOAD_IGNORED; + } + + QFile file(full_path_to_file.AbsolutePath().c_str()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return LAYER_LOAD_FAILED; + } + + QString json_text = file.readAll(); + file.close(); + + this->manifest_path = full_path_to_file; + this->last_modified = full_path_to_file.LastModified(); + + // Convert the text to a JSON document & validate it. + // It does need to be a valid json formatted file. + QJsonParseError json_parse_error; + const QJsonDocument& json_document = QJsonDocument::fromJson(json_text.toUtf8(), &json_parse_error); + if (json_parse_error.error != QJsonParseError::NoError) { + return LAYER_LOAD_FAILED; + } + + // Make sure it's not empty + if (json_document.isNull() || json_document.isEmpty()) { + return LAYER_LOAD_FAILED; + } + + // First check it's a layer manifest, ignore otherwise. + const QJsonObject& json_root_object = json_document.object(); + if (json_root_object.value("file_format_version") == QJsonValue::Undefined) { + return LAYER_LOAD_IGNORED; // Not a layer JSON file + } + if (json_root_object.value("layer") == QJsonValue::Undefined && json_root_object.value("layers") == QJsonValue::Undefined) { + return LAYER_LOAD_IGNORED; // Not a layer JSON file + } + + this->file_format_version = ReadVersionValue(json_root_object, "file_format_version"); + if (this->file_format_version.GetMajor() > 1) { + switch (configurator_mode) { + default: { + } break; + case CONFIGURATOR_MODE_GUI: { + QMessageBox alert; + alert.setWindowTitle("Failed to load a layer manifest..."); + alert.setText(format("Unsupported layer file format: %s.", this->file_format_version.str().c_str()).c_str()); + alert.setInformativeText( + format("The %s layer is being ignored.", full_path_to_file.AbsolutePath().c_str()).c_str()); + alert.setIcon(QMessageBox::Critical); + alert.exec(); + } break; + case CONFIGURATOR_MODE_CMD: { + fprintf(stderr, "vkconfig: [ERROR] Unsupported layer file format: %s\n", this->file_format_version.str().c_str()); + fprintf(stderr, "\n (%s layer is ignored\n)", full_path_to_file.AbsolutePath().c_str()); + } break; + } + return LAYER_LOAD_INVALID; + } + + JsonValidator validator; + + const bool is_valid = request_validate_manifest ? validator.Check(json_text) : true; + if (!is_valid) { + switch (configurator_mode) { + default: { + } break; + case CONFIGURATOR_MODE_GUI: { + QMessageBox alert; + alert.setWindowTitle("Failed to load a layer manifest..."); + alert.setText(format("%s is not a valid layer file", full_path_to_file.AbsolutePath().c_str()).c_str()); + alert.setInformativeText("Do you want to save the validation log?"); + alert.setIcon(QMessageBox::Critical); + alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + alert.setDefaultButton(QMessageBox::Yes); + int result = alert.exec(); + if (result == QMessageBox::Yes) { + const QString& selected_path = QFileDialog::getSaveFileName( + nullptr, format("Export %s validation log", full_path_to_file.AbsolutePath().c_str()).c_str(), + (AbsolutePath(Path::HOME) + "/" + full_path_to_file.Basename() + "_log.txt").c_str(), "Log(*.txt)"); + QFile log_file(selected_path); + const bool result = log_file.open(QIODevice::WriteOnly | QIODevice::Text); + if (result) { + QDesktopServices::openUrl(QUrl::fromLocalFile(selected_path)); + log_file.write(validator.message.toStdString().c_str()); + log_file.close(); + } else { + QMessageBox alert; + alert.setWindowTitle("Failed to save layer manifest log..."); + alert.setText(format("Couldn't not open %s file...", selected_path.toStdString().c_str()).c_str()); + alert.setIcon(QMessageBox::Critical); + alert.exec(); + } + } + } break; + case CONFIGURATOR_MODE_CMD: { + fprintf(stderr, "vkconfig: [ERROR] Couldn't validate layer file: %s\n", full_path_to_file.AbsolutePath().c_str()); + fprintf(stderr, "\n%s\n)", validator.message.toStdString().c_str()); + } break; + } + return LAYER_LOAD_INVALID; + } + + if (json_root_object.value("layers") == QJsonValue::Undefined) { + const QJsonArray& json_layers_array = json_root_object.value("layers").toArray(); + for (int i = 0, n = json_layers_array.size(); i < n; ++i) { + const QJsonObject& json_layer_object = json_layers_array[i].toObject(); + return this->LoadLayer(json_layer_object); + } + } else { + const QJsonObject& json_layer_object = ReadObject(json_root_object, "layer"); + return this->LoadLayer(json_layer_object); + } +} +*/ bool operator<(const Layer& layer_a, const Layer& layer_b) { if (layer_a.key == layer_b.key) { return layer_a.api_version < layer_b.api_version; diff --git a/vkconfig_core/layer.h b/vkconfig_core/layer.h index c4b64734b2..cf50a1f12c 100644 --- a/vkconfig_core/layer.h +++ b/vkconfig_core/layer.h @@ -36,21 +36,10 @@ #include #include -struct LayerDescriptor { - LayerType type = LAYER_TYPE_EXPLICIT; - std::string last_modified; - bool validated = false; - bool enabled = true; - bool added = false; -}; - -bool Found(const std::vector& data, const Path& path); - enum LayerLoadStatus { LAYER_LOAD_ADDED = 0, LAYER_LOAD_RELOADED, LAYER_LOAD_UNMODIFIED, - LAYER_LOAD_FAILED, LAYER_LOAD_INVALID, LAYER_LOAD_IGNORED, @@ -58,20 +47,29 @@ enum LayerLoadStatus { LAYER_LOAD_LAST = LAYER_LOAD_IGNORED, }; -inline bool IsEnabled(LayerLoadStatus status) { - return !(status == LAYER_LOAD_FAILED || status == LAYER_LOAD_INVALID || status == LAYER_LOAD_IGNORED); -} - enum { LAYER_LOAD_COUNT = LAYER_LOAD_LAST - LAYER_LOAD_FIRST + 1 }; +struct LayerId { + Path manifest_path; + std::string key; + Version api_version; +}; + +struct LayerDescriptor { + bool validated = false; + bool enabled = true; + bool removed = false; + bool recent = false; +}; + class Layer { public: enum { NO_PRESET = -1, DEFAULT_PRESET = 0 }; Layer(); Layer(const std::string& key); - Layer(const std::string& key, const Version& file_format_version, const Version& api_version, - const std::string& implementation_version, const std::string& library_path); + + LayerId GetId() const; bool IsValid() const; @@ -113,8 +111,9 @@ class Layer { std::vector settings; std::vector presets; - LayerLoadStatus Load(const Path& full_path_to_file, LayerType type, bool request_validate_manifest, - ConfiguratorMode configurator_mode); + LayerLoadStatus Load(const QJsonObject& json_layer_object); + + LayerDescriptor descriptor; private: Layer& operator=(const Layer&) = delete; diff --git a/vkconfig_core/layer_manager.cpp b/vkconfig_core/layer_manager.cpp index 8c5e3cdb0b..1750a56522 100644 --- a/vkconfig_core/layer_manager.cpp +++ b/vkconfig_core/layer_manager.cpp @@ -22,8 +22,11 @@ #include "util.h" #include "type_platform.h" #include "registry.h" +#include "json_validator.h" #include +#include +#include static std::vector GetEnvVariablePaths(const char *variable_name, LayerType type) { std::vector result; @@ -149,7 +152,6 @@ LayerManager::LayerManager() {} bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode configurator_mode) { this->available_layers.clear(); - this->layers_found.clear(); // LAYERS_PATHS_GUI if (json_root_object.value("layers") != QJsonValue::Undefined) { @@ -170,26 +172,14 @@ bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode co for (int i = 0, n = json_layers_found_keys.length(); i < n; ++i) { const QJsonObject &json_status_object = json_layers_found_object.value(json_layers_found_keys[i]).toObject(); - LayerDescriptor descriptor; - if (json_status_object.value("type") != QJsonValue::Undefined) { - descriptor.type = ::GetLayerType(json_status_object.value("type").toString().toStdString().c_str()); - } - descriptor.last_modified = json_status_object.value("last_modified").toString().toStdString(); - descriptor.validated = json_status_object.value("validated").toBool(); - if (json_status_object.value("enabled") != QJsonValue::Undefined) { - descriptor.enabled = json_status_object.value("enabled").toBool(); - } - - const Path &manifest_path = json_layers_found_keys[i].toStdString(); + LayerDisplay layer; + layer.id.manifest_path = json_layers_found_keys[i].toStdString(); + layer.descriptor.enabled = !json_status_object.value("disabled").toBool(); + layer.descriptor.validated = json_status_object.value("validated").toBool(); - this->layers_found.insert(std::make_pair(manifest_path, descriptor)); - } - } - - if (json_layers_object.value("removed") != QJsonValue::Undefined) { - const QJsonArray &array = json_layers_object.value("removed").toArray(); - for (int i = 0, n = array.size(); i < n; ++i) { - this->layers_removed.insert(array[i].toString().toStdString()); + std::vector layers; + layers.push_back(layer); + this->AppendInit(layer.id.manifest_path, layers); } } } @@ -200,27 +190,12 @@ bool LayerManager::Load(const QJsonObject &json_root_object, ConfiguratorMode co } bool LayerManager::Save(QJsonObject &json_root_object) const { - QJsonObject json_layers_found_object; - for (auto it = this->layers_found.begin(); it != this->layers_found.end(); ++it) { - QJsonObject object; - object.insert("last_modified", it->second.last_modified.c_str()); - object.insert("type", ::GetToken(it->second.type)); - object.insert("validated", it->second.validated); - object.insert("enabled", it->second.enabled); - json_layers_found_object.insert(it->first.AbsolutePath().c_str(), object); - } - - QJsonArray json_layers_removed_array; - for (auto it = this->layers_removed.begin(); it != this->layers_removed.end(); ++it) { - json_layers_removed_array.append(it->AbsolutePath().c_str()); - } - QJsonObject json_layers_object; json_layers_object.insert("validate_manifests", this->validate_manifests); json_layers_object.insert("last_layers_dir", this->last_layers_dir.RelativePath().c_str()); - json_layers_object.insert("found", json_layers_found_object); - json_layers_object.insert("removed", json_layers_removed_array); - json_root_object.insert("layers", json_layers_object); + // json_layers_object.insert("found", json_layers_found_object); + // json_layers_object.insert("removed", json_layers_removed_array); + // json_root_object.insert("layers", json_layers_object); return true; } @@ -231,15 +206,7 @@ std::string LayerManager::Log() const { for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { const Layer *layer = &this->available_layers[i]; - auto it = this->layers_found.find(layer->manifest_path); - if (it == this->layers_found.end()) { - assert(0); - continue; - } - - const LayerDescriptor &descriptor = it->second; - - if (descriptor.type == LAYER_TYPE_IMPLICIT) { + if (layer->type == LAYER_TYPE_IMPLICIT) { log += format(" * %s - %s (Auto: %s)", layer->key.c_str(), layer->api_version.str().c_str(), GetLabel(layer->GetActualControl())); } else { @@ -254,7 +221,7 @@ std::string LayerManager::Log() const { log += format(" %s\n", layer->manifest_path.AbsolutePath().c_str()); log += "\n"; - if (descriptor.type == LAYER_TYPE_IMPLICIT) { + if (layer->type == LAYER_TYPE_IMPLICIT) { if (!layer->disable_env.empty()) { const std::string &value = qEnvironmentVariableIsSet(layer->disable_env.c_str()) ? "set" : "not set"; log += format(" '%s' is %s\n", layer->disable_env.c_str(), value.c_str()); @@ -285,11 +252,11 @@ std::vector LayerManager::GatherManifests(const std::string &layer_key) co std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (this->available_layers[i].key != layer_key) { + if (!this->available_layers[i].descriptor.enabled) { continue; } - if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + if (this->available_layers[i].key != layer_key) { continue; } @@ -305,11 +272,11 @@ std::vector LayerManager::GatherVersions(const std::string &layer_key) std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (this->available_layers[i].key != layer_key) { + if (!this->available_layers[i].descriptor.enabled) { continue; } - if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + if (this->available_layers[i].key != layer_key) { continue; } @@ -321,8 +288,63 @@ std::vector LayerManager::GatherVersions(const std::string &layer_key) return result; } +const Layer *LayerManager::Find(LayerId id, bool enable_only) const { + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + const Layer &layer = this->available_layers[i]; + + if (!layer.descriptor.enabled && enable_only) { + continue; + } + + if (layer.manifest_path != id.manifest_path) { + continue; + } + + if (layer.key != id.key) { + continue; + } + + if (layer.api_version != Version::LATEST) { + if (layer.api_version != id.api_version) { + continue; + } + } + + return &this->available_layers[i]; + } + + return nullptr; +} + +Layer *LayerManager::Find(LayerId id, bool enable_only) { + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + Layer &layer = this->available_layers[i]; + + if (!layer.descriptor.enabled && enable_only) { + continue; + } + + if (layer.manifest_path != id.manifest_path) { + continue; + } + + if (layer.key != id.key) { + continue; + } + + if (layer.api_version != Version::LATEST) { + if (layer.api_version != id.api_version) { + continue; + } + } + + return &this->available_layers[i]; + } + + return nullptr; +} + const Layer *LayerManager::Find(const std::string &layer_name, const Version &layer_version) const { - // Version::VERSION_NULL refer to latest version if (layer_version == Version::LATEST) { const std::vector &version = this->GatherVersions(layer_name); if (version.empty()) { @@ -358,14 +380,13 @@ const Layer *LayerManager::FindLastModified(const std::string &layer_name, const const Layer *result = nullptr; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - if (this->available_layers[i].key != layer_name) { + if (!this->available_layers[i].descriptor.enabled) { continue; } - if (this->available_layers[i].api_version != version) { + if (this->available_layers[i].key != layer_name) { continue; } - - if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + if (this->available_layers[i].api_version != version) { continue; } @@ -384,7 +405,7 @@ const Layer *LayerManager::FindLastModified(const std::string &layer_name, const const Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disabled_layers) const { for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { if (!find_disabled_layers) { - if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + if (!this->available_layers[i].descriptor.enabled) { continue; } } @@ -399,7 +420,7 @@ const Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disabled_layers) { for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { if (!find_disabled_layers) { - if (!this->IsEnabled(this->available_layers[i].manifest_path)) { + if (!this->available_layers[i].descriptor.enabled) { continue; } } @@ -411,6 +432,29 @@ Layer *LayerManager::FindFromManifest(const Path &manifest_path, bool find_disab return nullptr; } +void LayerManager::ApplyLayerDescriptor() { + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + Layer &layer = this->available_layers[i]; + + auto it = this->layer_init.find(layer.manifest_path); + if (it == this->layer_init.end()) { + continue; + } + + for (std::size_t j = 0, o = it->second.size(); j < o; ++j) { + const LayerDisplay &init = it->second[j]; + + if (!init.id.key.empty()) { + if (init.id.key != layer.key) { + continue; + } + } + + layer.descriptor = init.descriptor; + } + } +} + // Find all installed layers on the system. void LayerManager::LoadAllInstalledLayers(ConfiguratorMode configurator_mode) { std::array, LAYERS_PATHS_COUNT> paths; @@ -436,113 +480,176 @@ void LayerManager::LoadAllInstalledLayers(ConfiguratorMode configurator_mode) { // LAYERS_PATHS_SDK paths[LAYERS_PATHS_SDK].push_back(Path(Path::SDK_EXPLICIT_LAYERS)); + // LAYERS_PATHS_GUI + std::vector added_paths = this->BuildLayerPaths(); + paths[LAYERS_PATHS_GUI].insert(paths[LAYERS_PATHS_GUI].begin(), added_paths.begin(), added_paths.end()); + for (std::size_t group_index = 0, group_count = paths.size(); group_index < group_count; ++group_index) { const LayersPaths layers_path = static_cast(group_index); const std::vector &paths_group = paths[group_index]; for (std::size_t i = 0, n = paths_group.size(); i < n; ++i) { - const std::vector &layers_paths = ::CollectFilePaths(paths_group[i]); + const std::vector &layers_paths = ::CollectLayersPaths(paths_group[i]); for (std::size_t p = 0, o = layers_paths.size(); p < o; ++p) { - this->AppendPath(layers_paths[p], ::GetLayerType(layers_path)); + this->LoadLayers(layers_paths[p], ::GetLayerType(layers_path), configurator_mode); } } } - for (auto it = this->layers_found.begin(); it != this->layers_found.end(); ++it) { - this->LoadLayer(it->first, it->second.type, configurator_mode); - } + this->ApplyLayerDescriptor(); } -LayerLoadStatus LayerManager::LoadLayer(const Path &layer_path, LayerType type, ConfiguratorMode configurator_mode) { +LayerLoadStatus LayerManager::LoadLayers(const Path &layer_path, LayerType type, ConfiguratorMode configurator_mode) { const std::string &last_modified = layer_path.LastModified(); + LayerDescriptor descriptor; + + QFile file(layer_path.AbsolutePath().c_str()); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + assert(0); + return LAYER_LOAD_INVALID; + } - Layer *already_loaded_layer = this->FindFromManifest(layer_path, true); - if (already_loaded_layer != nullptr) { - // Already loaded - auto it = this->layers_found.find(layer_path); - if (it != layers_found.end()) { - if (last_modified == it->second.last_modified) { - return LAYER_LOAD_UNMODIFIED; + QString json_text = file.readAll(); + file.close(); + + if (this->validate_manifests) { + JsonValidator validator; + descriptor.validated = validator.Check(json_text); + + if (descriptor.validated) { + switch (configurator_mode) { + default: { + } break; + case CONFIGURATOR_MODE_GUI: { + QMessageBox alert; + alert.setWindowTitle("Failed to load a layer manifest..."); + alert.setText(format("%s is not a valid layer file", layer_path.AbsolutePath().c_str()).c_str()); + alert.setInformativeText("Do you want to save the validation log?"); + alert.setIcon(QMessageBox::Critical); + alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + alert.setDefaultButton(QMessageBox::Yes); + int result = alert.exec(); + if (result == QMessageBox::Yes) { + const QString &selected_path = QFileDialog::getSaveFileName( + nullptr, format("Export %s validation log", layer_path.AbsolutePath().c_str()).c_str(), + (AbsolutePath(Path::HOME) + "/" + layer_path.Basename() + "_log.txt").c_str(), "Log(*.txt)"); + QFile log_file(selected_path); + const bool result = log_file.open(QIODevice::WriteOnly | QIODevice::Text); + if (result) { + QDesktopServices::openUrl(QUrl::fromLocalFile(selected_path)); + log_file.write(validator.message.toStdString().c_str()); + log_file.close(); + } else { + QMessageBox alert; + alert.setWindowTitle("Failed to save layer manifest log..."); + alert.setText(format("Couldn't not open %s file...", selected_path.toStdString().c_str()).c_str()); + alert.setIcon(QMessageBox::Critical); + alert.exec(); + } + } + } break; + case CONFIGURATOR_MODE_CMD: { + fprintf(stderr, "vkconfig: [ERROR] Couldn't validate layer file: %s\n", layer_path.AbsolutePath().c_str()); + fprintf(stderr, "\n%s\n)", validator.message.toStdString().c_str()); + } break; } } - // Modified to reload - LayerLoadStatus status = already_loaded_layer->Load(layer_path, type, this->validate_manifests, configurator_mode); - if (status == LAYER_LOAD_ADDED) { - it->second.last_modified = already_loaded_layer->last_modified; - return LAYER_LOAD_RELOADED; - } else { - // it->second.enabled = ::IsEnabled(status); - return status; - } - } else { - Layer layer; - LayerLoadStatus status = layer.Load(layer_path, type, this->validate_manifests, configurator_mode); - if (status == LAYER_LOAD_ADDED) { - this->available_layers.push_back(layer); - } - - auto it = this->layers_found.find(layer_path); - // assert(it != this->layers_found.end()); - if (it != layers_found.end()) { - // it->second.enabled = ::IsEnabled(status); - // it->second.validated = this->validate_manifests && it->second.enabled; - it->second.last_modified = layer.last_modified; - } else { - LayerDescriptor descriptor; - descriptor.type = layer.type; - descriptor.last_modified = layer.last_modified; - descriptor.validated = this->validate_manifests; - descriptor.enabled = true; - descriptor.added = true; - layers_found.insert(std::make_pair(layer.manifest_path, descriptor)); - } + return LAYER_LOAD_INVALID; + } - return status; + // Convert the text to a JSON document & validate it. + // It does need to be a valid json formatted file. + QJsonParseError json_parse_error; + const QJsonDocument &json_document = QJsonDocument::fromJson(json_text.toUtf8(), &json_parse_error); + if (json_parse_error.error != QJsonParseError::NoError) { + return LAYER_LOAD_INVALID; } -} -void LayerManager::AppendPath(const Path &path, LayerType type, bool added) { - if (added) { - auto it_removed = this->layers_removed.find(path); - if (it_removed != this->layers_removed.end()) { - this->layers_removed.erase(it_removed); - } + // Make sure it's not empty + if (json_document.isNull() || json_document.isEmpty()) { + return LAYER_LOAD_INVALID; } - if (this->layers_removed.find(path) != this->layers_removed.end()) { - return; + Version file_format_version; + const QJsonObject &json_root_object = json_document.object(); + if (json_root_object.value("file_format_version") != QJsonValue::Undefined) { + file_format_version = Version(json_root_object.value("file_format_version").toString().toStdString().c_str()); } - auto it = this->layers_found.find(path); - if (it == this->layers_found.end()) { - LayerDescriptor descriptor; - descriptor.type = type; - descriptor.added = added; - descriptor.last_modified = path.LastModified(); - this->layers_found.insert(std::make_pair(path, descriptor)); + LayerLoadStatus status = LAYER_LOAD_ADDED; + + if (json_root_object.value("layers") != QJsonValue::Undefined) { + const QJsonArray &json_layers_array = json_root_object.value("layers").toArray(); + for (int i = 0, n = json_layers_array.size(); i < n; ++i) { + const QJsonObject &json_layer_object = json_layers_array[i].toObject(); + status = this->LoadLayer(json_layer_object, layer_path, type, last_modified, file_format_version, descriptor); + } + } else if (json_root_object.value("layer") != QJsonValue::Undefined) { + const QJsonObject &json_layer_object = json_root_object.value("layer").toObject(); + status = this->LoadLayer(json_layer_object, layer_path, type, last_modified, file_format_version, descriptor); + } else { + assert(0); } + + return status; } -void LayerManager::RemovePath(const Path &path) { - auto it_found = this->layers_found.find(path); - if (it_found != this->layers_found.end()) { - this->layers_found.erase(it_found); +LayerLoadStatus LayerManager::LoadLayer(const QJsonObject &json_layer_object, const Path &layer_path, LayerType type, + const std::string &last_modified, Version file_format_version, LayerDescriptor descriptor) { + Layer layer; + layer.type = type; + layer.manifest_path = layer_path; + layer.last_modified = last_modified; + layer.file_format_version = file_format_version; + layer.descriptor = descriptor; + + LayerLoadStatus status = layer.Load(json_layer_object); + if (status == LAYER_LOAD_INVALID || status == LAYER_LOAD_IGNORED) { + return status; } - this->layers_removed.insert(path); + Layer *duplicated_layer = this->Find(layer.GetId(), false); + if (duplicated_layer != nullptr) { + if (duplicated_layer->descriptor.removed) { + duplicated_layer->descriptor.removed = false; + duplicated_layer->descriptor.enabled = true; + } else if (duplicated_layer->last_modified != layer.last_modified) { + // Reload when the manifest was updated + LayerLoadStatus reloaded_status = duplicated_layer->Load(json_layer_object); + status = reloaded_status == LAYER_LOAD_ADDED ? LAYER_LOAD_RELOADED : reloaded_status; + } else { + status = LAYER_LOAD_UNMODIFIED; + } + } else { + this->available_layers.push_back(layer); + } + + return status; +} + +void LayerManager::RemoveLayer(LayerId id) { + Layer *layer = this->Find(id, false); + assert(layer); + + layer->descriptor.enabled = false; + layer->descriptor.removed = true; +} + +void LayerManager::EnableLayer(LayerId id, bool enable) { + Layer *layer = this->Find(id, false); + assert(layer); + + layer->descriptor.enabled = enable; } std::vector LayerManager::GatherLayerNames() const { std::vector result; for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { - auto it = this->layers_found.find(this->available_layers[i].manifest_path); - if (it != this->layers_found.end()) { - if (!it->second.enabled) { - continue; - } + if (!this->available_layers[i].descriptor.enabled) { + continue; } if (std::find(result.begin(), result.end(), this->available_layers[i].key) != result.end()) { @@ -555,46 +662,79 @@ std::vector LayerManager::GatherLayerNames() const { return result; } -bool LayerManager::IsEnabled(const Path &manifest_path) const { - auto it = this->layers_found.find(manifest_path); - if (it != this->layers_found.end()) { - return it->second.enabled; +bool operator<(const LayerDisplay &a, const LayerDisplay &b) { + if (a.id.key == b.id.key) { + return a.id.api_version < b.id.api_version; + } else { + return a.id.key < b.id.key; } - return false; } -void LayerManager::Enable(const Path &manifest_path, bool enabled) { - auto it = this->layers_found.find(manifest_path); - if (it != this->layers_found.end()) { - it->second.enabled = enabled; - } -} +std::map> LayerManager::BuildLayerStoreList() const { + std::map> result; -bool operator<(const LayerDisplay &a, const LayerDisplay &b) { - if (a.key == b.key) { - return a.api_version < b.api_version; - } else { - return a.key < b.key; + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + const Layer *layer = &this->available_layers[i]; + if (layer == nullptr) { + continue; + } + if (layer->descriptor.removed) { + continue; + } + + LayerDisplay layer_display; + layer_display.id.manifest_path = layer->manifest_path; + layer_display.id.key = layer->key; + layer_display.id.api_version = layer->api_version; + layer_display.descriptor = layer->descriptor; + + auto it = result.find(layer->manifest_path); + if (it == result.end()) { + std::map entry; + entry.insert(std::make_pair(layer->key, layer_display)); + result.insert(std::make_pair(layer->manifest_path, entry)); + } else { + auto jt = it->second.find(layer->key); + if (jt == it->second.end()) { + it->second.insert(std::make_pair(layer->key, layer_display)); + } else { + assert(0); + } + } } + + return result; } std::set LayerManager::BuildLayerDisplayList() const { std::set result; - for (auto it = this->layers_found.begin(), end = this->layers_found.end(); it != end; ++it) { - const Layer *layer = this->FindFromManifest(it->first, true); + for (std::size_t i = 0, n = this->available_layers.size(); i < n; ++i) { + const Layer *layer = &this->available_layers[i]; if (layer == nullptr) { continue; } + if (layer->descriptor.removed) { + continue; + } LayerDisplay layer_display; - layer_display.key = layer->key; - layer_display.manifest_path = it->first; - layer_display.api_version = layer->api_version; - layer_display.descriptor = it->second; - + layer_display.id.manifest_path = layer->manifest_path; + layer_display.id.key = layer->key; + layer_display.id.api_version = layer->api_version; + layer_display.descriptor = layer->descriptor; result.insert(layer_display); } return result; } + +std::vector LayerManager::BuildLayerPaths() const { + std::vector result; + + for (auto it = this->layer_init.begin(); it != this->layer_init.end(); ++it) { + result.push_back(it->first); + } + + return result; +} diff --git a/vkconfig_core/layer_manager.h b/vkconfig_core/layer_manager.h index b0fd537861..4d5d3d9b38 100644 --- a/vkconfig_core/layer_manager.h +++ b/vkconfig_core/layer_manager.h @@ -31,9 +31,7 @@ #include struct LayerDisplay { - std::string key; - Path manifest_path; - Version api_version; + LayerId id; LayerDescriptor descriptor; }; @@ -51,31 +49,44 @@ class LayerManager : public Serialize { bool Empty() const; std::size_t Size() const; - std::vector GatherManifests(const std::string& layer_name) const; - std::vector GatherVersions(const std::string& layer_name) const; - const Layer* Find(const std::string& layer_name, const Version& version = Version::LATEST) const; - const Layer* FindLastModified(const std::string& layer_name, const Version& version) const; + std::vector GatherManifests(const std::string& layer_key) const; + std::vector GatherVersions(const std::string& layer_key) const; + + const Layer* Find(LayerId id, bool enable_only = true) const; + Layer* Find(LayerId id, bool enable_only = true); + + const Layer* Find(const std::string& layer_key, const Version& version = Version::LATEST) const; + const Layer* FindLastModified(const std::string& layer_key, const Version& version) const; const Layer* FindFromManifest(const Path& manifest_path, bool find_disabled_layers = false) const; Layer* FindFromManifest(const Path& manifest_path, bool find_disabled_layers = false); void LoadAllInstalledLayers(ConfiguratorMode configurator_mode); - LayerLoadStatus LoadLayer(const Path& layer_path, LayerType type, ConfiguratorMode configurator_mode); + LayerLoadStatus LoadLayers(const Path& layer_path, LayerType type, ConfiguratorMode configurator_mode); - void AppendPath(const Path& path, LayerType type, bool added = false); - void RemovePath(const Path& path); - - bool IsEnabled(const Path& manifest_path) const; - void Enable(const Path& manifest_path, bool enabled); + void RemoveLayer(LayerId id); + void EnableLayer(LayerId id, bool enable); std::set BuildLayerDisplayList() const; + std::map> BuildLayerStoreList() const; + std::vector BuildLayerPaths() const; std::vector GatherLayerNames() const; + std::set gui_added_layers_paths; + std::vector available_layers; Path last_layers_dir = Path(Path::HOME); bool validate_manifests = false; - std::map layers_found; + + void AppendInit(const Path& path, const std::vector& layers) { + this->layer_init.insert(std::make_pair(path, layers)); + } private: - std::set layers_removed; + LayerLoadStatus LoadLayer(const QJsonObject& json_layer_object, const Path& layer_path, LayerType type, + const std::string& last_modified, Version file_format_version, LayerDescriptor descriptor); + + void ApplyLayerDescriptor(); + + std::map> layer_init; }; diff --git a/vkconfig_core/path.cpp b/vkconfig_core/path.cpp index 3f620e004c..dc1e0f75de 100644 --- a/vkconfig_core/path.cpp +++ b/vkconfig_core/path.cpp @@ -568,6 +568,16 @@ std::string AbsolutePath(Path::Builtin path, bool native_separator) { return Pat std::string RelativePath(Path::Builtin path) { return Path(path).RelativePath(); } +bool Found(const std::vector& data, const Path& path) { + for (std::size_t i = 0, n = data.size(); i < n; ++i) { + if (data[i] == path) { + return true; + } + } + + return false; +} + std::vector CollectFilePaths(const Path& path, const char* filter) { std::vector result; @@ -587,6 +597,56 @@ std::vector CollectFilePaths(const Path& path, const char* filter) { return result; } +std::vector CollectLayersPaths(const Path& directory) { + std::vector paths = CollectFilePaths(directory, "*json"); + + std::vector results; + for (std::size_t i = 0, n = paths.size(); i < n; ++i) { + const QJsonDocument& document = ::ParseJsonFile(paths[i].AbsolutePath().c_str()); + if (document.isNull() || document.isEmpty()) { + continue; + } + + const QJsonObject& json_root_object = document.object(); + if (json_root_object.value("file_format_version") == QJsonValue::Undefined) { + continue; // Not a layer JSON file + } + + if (json_root_object.value("layer") == QJsonValue::Undefined && json_root_object.value("layers") == QJsonValue::Undefined) { + continue; + } + + results.push_back(paths[i]); + } + + return results; +} + +std::vector CollectDriversPaths(const Path& directory) { + std::vector paths = CollectFilePaths(directory, "*json"); + + std::vector results; + for (std::size_t i = 0, n = paths.size(); i < n; ++i) { + const QJsonDocument& document = ::ParseJsonFile(paths[i].AbsolutePath().c_str()); + if (document.isNull() || document.isEmpty()) { + continue; + } + + const QJsonObject& json_root_object = document.object(); + if (json_root_object.value("file_format_version") == QJsonValue::Undefined) { + continue; // Not a driver JSON file + } + + if (json_root_object.value("ICD") == QJsonValue::Undefined) { + continue; + } + + results.push_back(paths[i]); + } + + return results; +} + static std::vector LoadProfiles(const QJsonDocument& doc) { assert(!doc.isNull() && !doc.isEmpty()); diff --git a/vkconfig_core/path.h b/vkconfig_core/path.h index a10f34e06b..51b5ffd78d 100644 --- a/vkconfig_core/path.h +++ b/vkconfig_core/path.h @@ -81,6 +81,8 @@ class Path { friend bool operator!=(const Path& a, const Path& b); }; +bool Found(const std::vector& data, const Path& path); + std::string AbsolutePath(Path::Builtin path, bool native_separator = true); std::string RelativePath(Path::Builtin path); @@ -98,6 +100,10 @@ void SetDownloadPath(const std::string& path); std::vector CollectFilePaths(const Path& directory, const char* filter = "*json"); +std::vector CollectLayersPaths(const Path& directory); + +std::vector CollectDriversPaths(const Path& directory); + std::vector CollectProfileNamesFromFile(const Path& profile_path); std::vector CollectProfileNamesFromDir(const Path& profile_path); diff --git a/vkconfig_core/test/test_configuration.cpp b/vkconfig_core/test/test_configuration.cpp index 5d1c485bb1..af9bc81187 100644 --- a/vkconfig_core/test/test_configuration.cpp +++ b/vkconfig_core/test/test_configuration.cpp @@ -31,7 +31,7 @@ static void InitLayer(LayerManager& layer_manager) { const std::vector& layers_paths = ::CollectFilePaths(":/layers"); for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); } } diff --git a/vkconfig_core/test/test_configuration_built_in.cpp b/vkconfig_core/test/test_configuration_built_in.cpp index a0099f0edf..182eb4b8a4 100644 --- a/vkconfig_core/test/test_configuration_built_in.cpp +++ b/vkconfig_core/test/test_configuration_built_in.cpp @@ -59,7 +59,7 @@ static void InitLayer(LayerManager& layer_manager) { const std::vector& layers_paths = ::CollectFilePaths(":/sdk"); for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); } } diff --git a/vkconfig_core/test/test_configuration_manager.cpp b/vkconfig_core/test/test_configuration_manager.cpp index 94c35bd7a0..724e25c753 100644 --- a/vkconfig_core/test/test_configuration_manager.cpp +++ b/vkconfig_core/test/test_configuration_manager.cpp @@ -26,7 +26,7 @@ static void InitLayer(LayerManager& layer_manager, const char* dir) { const std::vector& layers_paths = ::CollectFilePaths(dir); for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); } } diff --git a/vkconfig_core/test/test_generate_settings.cpp b/vkconfig_core/test/test_generate_settings.cpp index 309a709f4d..559ac5a72c 100644 --- a/vkconfig_core/test/test_generate_settings.cpp +++ b/vkconfig_core/test/test_generate_settings.cpp @@ -42,7 +42,7 @@ Configurator& GetTestConfigurator() { path.Create(); Configurator& configurator = Configurator::Get(); - configurator.layers.LoadLayer(":/layers/VK_LAYER_LUNARG_reference_1_2_1.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + configurator.layers.LoadLayers(":/layers/VK_LAYER_LUNARG_reference_1_2_1.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); configurator.configurations.CreateConfiguration(configurator.layers, "configuration"); configurator.SetActiveConfigurationName("configuration"); return configurator; diff --git a/vkconfig_core/test/test_layer.cpp b/vkconfig_core/test/test_layer.cpp index fad75dc56f..13a82a9a1d 100644 --- a/vkconfig_core/test/test_layer.cpp +++ b/vkconfig_core/test/test_layer.cpp @@ -19,6 +19,7 @@ */ #include "../layer.h" +#include "../layer_manager.h" #include "../util.h" #include "../setting_string.h" #include "../setting_filesystem.h" @@ -57,11 +58,14 @@ TEST(test_layer, collect_settings) { } TEST(test_layer, load_header_overridden) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_00.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_00.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_STREQ("VK_LAYER_LUNARG_test_00", layer.key.c_str()); EXPECT_STREQ(Path(".\\VkLayer_test.dll").RelativePath().c_str(), layer.binary_path.RelativePath().c_str()); @@ -80,11 +84,14 @@ TEST(test_layer, load_header_overridden) { } TEST(test_layer, load_header_default) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_01.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_01.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_STREQ("VK_LAYER_LUNARG_test_01", layer.key.c_str()); EXPECT_STREQ(Path(".\\VkLayer_test.dll").RelativePath().c_str(), layer.binary_path.RelativePath().c_str()); @@ -99,11 +106,14 @@ TEST(test_layer, load_header_default) { } TEST(test_layer, load_header_override) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_02.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_02.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -112,11 +122,14 @@ TEST(test_layer, load_header_override) { } TEST(test_layer, load_setting_interit) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -135,11 +148,14 @@ TEST(test_layer, load_setting_interit) { } TEST(test_layer, load_preset_interit) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -155,18 +171,21 @@ TEST(test_layer, load_preset_interit) { EXPECT_EQ(STATUS_ALPHA, layer.presets[2].status); const LayerLoadStatus reloaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); - EXPECT_EQ(reloaded, LAYER_LOAD_ADDED); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + EXPECT_EQ(reloaded, LAYER_LOAD_UNMODIFIED); EXPECT_EQ(1, layer.settings.size()); EXPECT_EQ(3, layer.presets.size()); } TEST(test_layer, load_setting_children_interit) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_05.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_05.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(1, layer.presets.size()); EXPECT_EQ(1, layer.settings.size()); @@ -186,11 +205,14 @@ TEST(test_layer, load_setting_children_interit) { } TEST(test_layer, load_setting_enum_interit) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_06.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_06.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT | PLATFORM_MACOS_BIT | PLATFORM_ANDROID_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -229,11 +251,14 @@ TEST(test_layer, load_setting_enum_interit) { } TEST(test_layer, load_setting_missing) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -244,17 +269,20 @@ TEST(test_layer, load_setting_missing) { EXPECT_EQ(layer_controlA, LAYER_CONTROL_ON); const LayerLoadStatus reloaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_IMPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(1, layer.settings.size()); EXPECT_EQ(2, layer.presets.size()); } TEST(test_layer, load_env_variable) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_08.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_08.json", LAYER_TYPE_IMPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_STREQ("VK_LAYER_LUNARG_test_08", layer.key.c_str()); EXPECT_STREQ(Path(".\\VkLayer_test.dll").RelativePath().c_str(), layer.binary_path.RelativePath().c_str()); @@ -298,11 +326,14 @@ TEST(test_layer, load_env_variable) { } TEST(test_layer, load_setting_message) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_09.json", LAYER_TYPE_IMPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_09.json", LAYER_TYPE_IMPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 2, 0), layer.file_format_version); EXPECT_EQ(PLATFORM_WINDOWS_BIT | PLATFORM_LINUX_BIT, layer.platforms); EXPECT_EQ(STATUS_BETA, layer.status); @@ -323,11 +354,14 @@ TEST(test_layer, load_setting_message) { } TEST(test_layer, load_1_1_0_header) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + EXPECT_EQ(Version(1, 1, 0), layer.file_format_version); EXPECT_STREQ("VK_LAYER_LUNARG_reference_1_1_0", layer.key.c_str()); EXPECT_STREQ(Path(".\\VkLayer_reference.dll").RelativePath().c_str(), layer.binary_path.RelativePath().c_str()); @@ -342,11 +376,14 @@ TEST(test_layer, load_1_1_0_header) { } TEST(test_layer, load_1_2_0_preset_and_setting_type) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_reference_1_2_0.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_reference_1_2_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + // Preset Enum { const std::size_t index = 1; diff --git a/vkconfig_core/test/test_layer_manager.cpp b/vkconfig_core/test/test_layer_manager.cpp index 7e76be08e8..a0e2bb530c 100644 --- a/vkconfig_core/test/test_layer_manager.cpp +++ b/vkconfig_core/test/test_layer_manager.cpp @@ -26,7 +26,7 @@ static void InitLayer(LayerManager& layer_manager) { const std::vector& layers_paths = ::CollectFilePaths(":/layers"); for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - layer_manager.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(layers_paths[i], LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); } } @@ -54,14 +54,6 @@ TEST(test_layer_manager, save_json) { LayerManager layer_manager; bool result = layer_manager.Save(json_root_object); EXPECT_TRUE(result); - - EXPECT_TRUE(json_root_object.value("layers") != QJsonValue::Undefined); - - if (json_root_object.value("layers") != QJsonValue::Undefined) { - const QJsonObject& json_layers_object = json_root_object.value("layers").toObject(); - - EXPECT_TRUE(json_layers_object.value("found") != QJsonValue::Undefined); - } } TEST(test_layer_manager, load_all) { @@ -92,7 +84,7 @@ TEST(test_layer_manager, load_file) { LayerManager layer_manager; EXPECT_TRUE(layer_manager.Empty()); - layer_manager.LoadLayer(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_reference_1_1_0.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_TRUE(!layer_manager.Empty()); EXPECT_EQ(1, layer_manager.Size()); @@ -133,7 +125,7 @@ TEST(test_layer_manager, reload) { const std::size_t initial_size = layer_manager.Size(); LayerLoadStatus status1 = - layer_manager.LoadLayer(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(status1, LAYER_LOAD_UNMODIFIED); Layer* layer1 = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_test_04.json", false); @@ -142,7 +134,7 @@ TEST(test_layer_manager, reload) { EXPECT_EQ(initial_size, reloaded_size1); LayerLoadStatus status2 = - layer_manager.LoadLayer(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(status2, LAYER_LOAD_UNMODIFIED); const std::size_t reloaded_size2 = layer_manager.Size(); @@ -197,11 +189,6 @@ TEST(test_layer_manager, FindLastModified) { Path modified_path = layer204_modified->manifest_path; layer204_modified->manifest_path = ":/layers/VK_LAYER_LUNARG_version_204_copy.json"; - auto it = layer_manager.layers_found.find(":/layers/VK_LAYER_LUNARG_version_204.json"); - assert(it != layer_manager.layers_found.end()); - - layer_manager.layers_found.insert(std::make_pair(":/layers/VK_LAYER_LUNARG_version_204_copy.json", it->second)); - ::InitLayer(layer_manager); const std::size_t reloaded_count = layer_manager.Size(); EXPECT_EQ(initial_count + 1, reloaded_count); @@ -234,7 +221,7 @@ TEST(test_layer_manager, FindFromManifest) { Layer* layer208 = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_208.json", false); EXPECT_TRUE(layer208 == nullptr); - layer_manager.layers_found.find(":/layers/VK_LAYER_LUNARG_version_204.json")->second.enabled = false; + layer204->descriptor.enabled = false; Layer* layer204_disabledA = layer_manager.FindFromManifest(":/layers/VK_LAYER_LUNARG_version_204.json", false); EXPECT_TRUE(layer204_disabledA == nullptr); @@ -269,12 +256,12 @@ TEST(test_layer_manager, GatherManifests) { EXPECT_EQ(layer0->api_version, Version(1, 3, 290)); const Layer* layer1 = layer_manager.FindFromManifest(versions_found[1]); EXPECT_EQ(layer1->api_version, Version(1, 3, 204)); - const Layer* layer2 = layer_manager.FindFromManifest(versions_found[2]); + Layer* layer2 = layer_manager.FindFromManifest(versions_found[2]); EXPECT_EQ(layer2->api_version, Version(1, 2, 193)); const Layer* layer3 = layer_manager.FindFromManifest(versions_found[3]); EXPECT_EQ(layer3->api_version, Version(1, 1, 135)); - layer_manager.layers_found.find(versions_found[2])->second.enabled = false; + layer2->descriptor.enabled = false; const Layer* layer193 = layer_manager.Find("VK_LAYER_LUNARG_version", Version(1, 2, 193)); EXPECT_TRUE(layer193 != nullptr); @@ -307,10 +294,12 @@ TEST(test_layer_manager, GatherVersions) { EXPECT_EQ(versions_found[2], Version(1, 2, 193)); EXPECT_EQ(versions_found[3], Version(1, 1, 135)); - const Layer* layer193 = layer_manager.Find("VK_LAYER_LUNARG_version", Version(1, 2, 193)); + LayerId layer_id{Path(":/layers/VK_LAYER_LUNARG_version_193.json"), "VK_LAYER_LUNARG_version", Version(1, 2, 193)}; + + Layer* layer193 = layer_manager.Find(layer_id); EXPECT_TRUE(layer193 != nullptr); - layer_manager.layers_found.find(layer193->manifest_path)->second.enabled = false; + layer193->descriptor.enabled = false; const std::vector& versions_found_b = layer_manager.GatherVersions("VK_LAYER_LUNARG_version"); EXPECT_FALSE(versions_found_b.empty()); diff --git a/vkconfig_core/test/test_layer_preset.cpp b/vkconfig_core/test/test_layer_preset.cpp index 8b5f919b49..5ece200a04 100644 --- a/vkconfig_core/test/test_layer_preset.cpp +++ b/vkconfig_core/test/test_layer_preset.cpp @@ -20,6 +20,7 @@ #include "../layer.h" #include "../layer_preset.h" +#include "../layer_manager.h" #include "../setting_string.h" #include "../setting_int.h" #include "../util.h" @@ -64,11 +65,14 @@ TEST(test_layer_preset, has_preset) { } TEST(test_layer_preset, find_preset_index_no_preset) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_03.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + SettingDataSet layer_settings; ::CollectDefaultSettingData(layer.settings, layer_settings); @@ -77,22 +81,28 @@ TEST(test_layer_preset, find_preset_index_no_preset) { } TEST(test_layer_preset, find_preset_index_empty) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + SettingDataSet layer_settings; int index = layer.FindPresetIndex(layer_settings); EXPECT_EQ(index, Layer::NO_PRESET); } TEST(test_layer_preset, find_preset_index_found) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_04.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + SettingDataSet layer_settings; ::CollectDefaultSettingData(layer.settings, layer_settings); SettingDataInt& setting = static_cast(*layer_settings[0]); @@ -114,11 +124,14 @@ TEST(test_layer_preset, find_preset_index_found) { } TEST(test_layer_preset, find_preset_index_missing_value) { - Layer layer; + LayerManager layer_manager; + const LayerLoadStatus load_loaded = - layer.Load(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_EXPLICIT, false, CONFIGURATOR_MODE_CMD); + layer_manager.LoadLayers(":/layers/VK_LAYER_LUNARG_test_07.json", LAYER_TYPE_EXPLICIT, CONFIGURATOR_MODE_CMD); EXPECT_EQ(load_loaded, LAYER_LOAD_ADDED); + Layer& layer = layer_manager.available_layers[0]; + SettingDataSet layer_settings; ::CollectDefaultSettingData(layer.settings, layer_settings); diff --git a/vkconfig_core/test/test_parameter.cpp b/vkconfig_core/test/test_parameter.cpp index 2e8acb1691..3a9421e560 100644 --- a/vkconfig_core/test/test_parameter.cpp +++ b/vkconfig_core/test/test_parameter.cpp @@ -32,13 +32,14 @@ static SettingMetaString* InstantiateString(Layer& layer, const std::string& key static void AddLayer(LayerManager& layers, const char* key, LayerType type) { std::string path = format("./%s.json", key); - layers.available_layers.push_back(Layer(key, Version(1, 0, 0), Version(1, 2, 148), "1", path.c_str())); - layers.available_layers[layers.available_layers.size() - 1].manifest_path = path; - layers.available_layers[layers.available_layers.size() - 1].type = type; - - LayerDescriptor descriptor; - descriptor.type = type; - layers.layers_found.insert(std::make_pair(path, descriptor)); + Layer layer(key); + layer.file_format_version = Version(1, 0, 0); + layer.api_version = Version(1, 2, 148); + layer.implementation_version = "1"; + layer.type = type; + layer.manifest_path = path; + + layers.available_layers.push_back(layer); } static void InitLayers(LayerManager& layers) { @@ -237,8 +238,13 @@ TEST(test_parameter, order_parameter_manual_partial) { EXPECT_STREQ(parameters[8].key.c_str(), "VK_LAYER_KHRONOS_timeline_semaphore"); // Insert a new layer in the parameter list - layers.available_layers.push_back( - Layer("VK_LAYER_KHRONOS_shader_object", Version(1, 0, 0), Version(1, 2, 148), "1", "layer.json")); + Layer layer("VK_LAYER_KHRONOS_shader_object"); + layer.manifest_path = "./layer.json"; + layer.api_version = Version(1, 2, 148); + layer.file_format_version = Version(1, 0, 0); + layer.implementation_version = "1"; + + layers.available_layers.push_back(layer); Parameter parameter_implicit2("VK_LAYER_KHRONOS_shader_object", LAYER_CONTROL_AUTO); parameters.push_back(parameter_implicit2); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 4bf29248e5..963f240612 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -20,6 +20,7 @@ - Show paths instead of Vulkan Configurator variables - Improve UI consistency - Move 'Validate Layer Manifest' settings to the preference tab +- Improve keyboard shortcuts ### Fixes: - Fix command line whitespace decoding #2625 @@ -31,6 +32,7 @@ - Fix UUID display in log, following the RFC9562 standard - Fix icon to remove a Vulkan driver - Fix key binding shortcuts (Eg: CTRL+F to search things on various tabs) +- Fix `VK_LAYER_NV_present` or `VK_LAYER_NV_optimus not` found #2597 ## Vulkan Configurator 3.4.2 - Febuary 2026 [Vulkan SDK 1.4.341.0](https://github.com/LunarG/VulkanTools/tree/vulkan-sdk-1.4.341) diff --git a/vkconfig_gui/mainwindow.ui b/vkconfig_gui/mainwindow.ui index 4d5af1af73..4d5102c8fe 100644 --- a/vkconfig_gui/mainwindow.ui +++ b/vkconfig_gui/mainwindow.ui @@ -91,7 +91,7 @@ QTabWidget::Rounded - 0 + 1 @@ -989,6 +989,12 @@ + + + 0 + 0 + + 16777215 @@ -1002,6 +1008,9 @@ false + + Enter search text to filter layers in the list... (Ctrl+F) + Enter search text to filter layers in the list... (Ctrl+F) @@ -1050,6 +1059,18 @@ 24 + + + 0 + 24 + + + + + 0 + 24 + + Arial @@ -1066,6 +1087,9 @@ false + + QProgressBar::BottomToTop + Loading layers... %p% @@ -1073,6 +1097,12 @@ + + + 0 + 0 + + 0 @@ -1092,6 +1122,9 @@ false + + Add a Vulkan Layer manifests path... (Ctrl+O) + Add a Vulkan Layer manifests path... (Ctrl+O) @@ -1519,6 +1552,12 @@ + + + 0 + 0 + + 16777215 @@ -1532,6 +1571,9 @@ false + + Enter search text to filter drivers in the list... (Ctrl+F) + Enter search text to filter drivers in the list... (Ctrl+F) @@ -1552,6 +1594,12 @@ + + + 0 + 0 + + 16777215 @@ -1565,6 +1613,9 @@ false + + Add Vulkan Driver manifests path... (Ctrl+O) + Add Vulkan Driver manifests path... (Ctrl+O) @@ -2430,6 +2481,9 @@ 24 + + Enter search text in the current log... (Ctrl+F) + Enter search text in the current log... (Ctrl+F) diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index 40e75bac23..956f99df6f 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -195,19 +195,22 @@ void TabConfigurations::UpdateUI_Configurations(UpdateUIMode mode) { ListItem *item = new ListItem(configuration.key.c_str()); item->setFlags(item->flags() | Qt::ItemIsEditable); item->setText(configuration.key.c_str()); + if (configurator.GetActiveConfiguration() == &configuration) { - item->setIcon(::Get(configurator.current_theme_mode, ::ICON_SYSTEM_ON)); + item->setIcon(::Get(configurator.current_theme_mode, has_missing_layer ? ::ICON_SYSTEM_INVALID : ::ICON_SYSTEM_ON)); item->setToolTip(configuration_tooltip.c_str()); current_row = static_cast(i); - } else if (has_missing_layer) { - item->setIcon(::Get(configurator.current_theme_mode, ::ICON_SYSTEM_INVALID)); - item->setToolTip( - format("The '%s' configuration has missing layers. These layers are ignored.", configuration.key.c_str()).c_str()); } else { - item->setIcon(::Get(configurator.current_theme_mode, ::ICON_SYSTEM_OFF)); + item->setIcon(::Get(configurator.current_theme_mode, has_missing_layer ? ::ICON_SYSTEM_INVALID : ::ICON_SYSTEM_OFF)); item->setToolTip( format("Select the '%s' configuration to use it with Vulkan executables", configuration.key.c_str()).c_str()); } + + if (has_missing_layer) { + item->setToolTip( + format("The '%s' configuration has missing layers. These layers are ignored.", configuration.key.c_str()).c_str()); + } + ui->configurations_list->addItem(item); } diff --git a/vkconfig_gui/tab_drivers.cpp b/vkconfig_gui/tab_drivers.cpp index a8895afed4..4b3093b3b2 100644 --- a/vkconfig_gui/tab_drivers.cpp +++ b/vkconfig_gui/tab_drivers.cpp @@ -318,7 +318,19 @@ void TabDrivers::on_driver_append_pressed() { configurator.last_driver_dir = selected_path; - const std::vector drivers_paths = ::CollectFilePaths(selected_path); + const std::vector drivers_paths = ::CollectDriversPaths(selected_path); + if (drivers_paths.empty()) { + QMessageBox alert; + alert.setWindowTitle("No Vulkan Driver Manifest found in the directory"); + alert.setText("The directory:"); + alert.setInformativeText(selected_path.AbsolutePath().c_str()); + alert.setStandardButtons(QMessageBox::Ok); + alert.setDefaultButton(QMessageBox::Ok); + alert.setIcon(QMessageBox::Warning); + alert.exec(); + return; + } + for (std::size_t i = 0, n = drivers_paths.size(); i < n; ++i) { configurator.driver_paths.insert(std::pair(drivers_paths[i], true)); } diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index cc1fdc751c..fe5bf00b4c 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -44,7 +44,9 @@ TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : T this->connect(this->ui->layers_browse_button, SIGNAL(clicked()), this, SLOT(on_layers_browse_pressed())); this->connect(this->ui->layers_reload_button, SIGNAL(clicked()), this, SLOT(on_layers_reload_pressed())); + this->connect(this->ui->layers_path_lineedit, SIGNAL(editingFinished()), this, SLOT(on_layers_append_pressed())); this->connect(this->ui->layers_path_lineedit, SIGNAL(returnPressed()), this, SLOT(on_layers_append_pressed())); + // this->connect(this->ui->layers_path_lineedit, SIGNAL(clicked()), this, SLOT(on_layers_edit_pressed())); this->connect(this->ui->layers_search, SIGNAL(textEdited(QString)), this, SLOT(on_search_textEdited(QString))); this->connect(this->ui->layers_search_clear, SIGNAL(clicked()), this, SLOT(on_search_clear_pressed())); @@ -77,14 +79,14 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { const std::set &layer_display_list = configurator.layers.BuildLayerDisplayList(); for (auto it = layer_display_list.begin(), end = layer_display_list.end(); it != end; ++it) { - const Layer *layer = configurator.layers.FindFromManifest(it->manifest_path, true); + const Layer *layer = configurator.layers.Find(it->id, false); if (layer == nullptr) { continue; } if (!this->layer_filter.empty()) { const std::string status = layer->status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer->status)); - const std::string text = format("%s - %s%s, %s layer", layer->key.c_str(), layer->api_version.str().c_str(), + const std::string text = format("%s - %s%s - %s layer", layer->key.c_str(), layer->api_version.str().c_str(), status.c_str(), ::GetToken(layer->type)); std::string lower_text = ::ToLowerCase(text); @@ -98,19 +100,12 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { item->setFlags(item->flags() | Qt::ItemIsSelectable); item->setSizeHint(QSize(0, ITEM_HEIGHT)); - LayerWidget *layer_widget = new LayerWidget(layer); - layer_widget->setChecked(it->descriptor.enabled); - if (it->descriptor.added) { - QPalette palette = layer_widget->palette(); - palette.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0)); - layer_widget->setPalette(palette); - } - + LayerWidget *layer_widget = new LayerWidget(*layer); this->connect(layer_widget, SIGNAL(itemChanged()), this, SLOT(on_paths_changed())); this->connect(layer_widget, SIGNAL(itemToggled()), this, SLOT(on_paths_toggled())); - ui->layers_list->addItem(item); - ui->layers_list->setItemWidget(item, layer_widget); + this->ui->layers_list->addItem(item); + this->ui->layers_list->setItemWidget(item, layer_widget); } } break; default: { @@ -122,7 +117,15 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { void TabLayers::UpdateUI(UpdateUIMode ui_update_mode) { const Configurator &configurator = Configurator::Get(); - + /* + QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); + policy_search.setHorizontalPolicy(QSizePolicy::MinimumExpanding); + this->ui->layers_search->setSizePolicy(policy_search); + + QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); + policy_append.setHorizontalPolicy(QSizePolicy::MinimumExpanding); + this->ui->layers_path_lineedit->setSizePolicy(policy_append); + */ this->ui->layers_search_clear->setEnabled(!this->ui->layers_search->text().isEmpty()); this->ui->layers_search->setFocus(); this->ui->layers_progress->resetFormat(); @@ -158,9 +161,23 @@ void TabLayers::on_paths_toggled() { this->UpdateUI_LayersPaths(UPDATE_REFRESH_UI); } +void TabLayers::on_layers_edit_pressed() { + /* + QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); + policy_search.setHorizontalPolicy(QSizePolicy::Preferred); + this->ui->layers_search->setSizePolicy(policy_search); + + QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); + policy_append.setHorizontalPolicy(QSizePolicy::MinimumExpanding); + this->ui->layers_path_lineedit->setSizePolicy(policy_append); + */ +} + void TabLayers::on_layers_append_pressed() { this->LoadLayersManifest(this->ui->layers_path_lineedit->text()); } void TabLayers::on_layers_browse_pressed() { + this->on_layers_edit_pressed(); + Configurator &configurator = Configurator::Get(); const QString selected_path = @@ -170,7 +187,18 @@ void TabLayers::on_layers_browse_pressed() { this->LoadLayersManifest(selected_path); } -void TabLayers::on_focus_search() { this->ui->layers_search->setFocus(); } +void TabLayers::on_focus_search() { + this->ui->layers_search->setFocus(); + /* + QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); + policy_search.setHorizontalPolicy(QSizePolicy::MinimumExpanding); + this->ui->layers_search->setSizePolicy(policy_search); + + QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); + policy_append.setHorizontalPolicy(QSizePolicy::Preferred); + this->ui->layers_path_lineedit->setSizePolicy(policy_append); + */ +} void TabLayers::on_search_textEdited(const QString &text) { this->layer_filter = text.toStdString(); @@ -190,6 +218,8 @@ void TabLayers::on_search_clear_pressed() { } void TabLayers::on_layers_reload_pressed() { + this->ui->layers_search->setVisible(false); + this->ui->layers_search_clear->setVisible(false); this->ui->layers_path_lineedit->setVisible(false); this->ui->layers_browse_button->setVisible(false); this->ui->layers_progress->setVisible(true); @@ -200,11 +230,7 @@ void TabLayers::on_layers_reload_pressed() { std::vector layers_count(LAYER_LOAD_COUNT, 0); - const std::vector &layers_paths = ::CollectFilePaths(new_path); - - for (std::size_t i = 0, n = layers_paths.size(); i < n; ++i) { - configurator.layers.AppendPath(layers_paths[i], LAYER_TYPE_EXPLICIT, true); - } + const std::vector &layers_paths = ::CollectLayersPaths(new_path); this->ui->layers_progress->setMaximum(static_cast(layers_paths.size())); this->ui->layers_progress->setValue(0); @@ -217,7 +243,7 @@ void TabLayers::on_layers_reload_pressed() { this->ui->layers_progress->setValue(static_cast(i + 1)); this->ui->layers_progress->update(); - LayerLoadStatus status = configurator.layers.LoadLayer(layers_paths[i], LAYER_TYPE_EXPLICIT, configurator.mode); + LayerLoadStatus status = configurator.layers.LoadLayers(layers_paths[i], LAYER_TYPE_EXPLICIT, configurator.mode); ++layers_count[status]; std::this_thread::sleep_until(std::chrono::system_clock::now() + std::chrono::milliseconds(10)); @@ -248,6 +274,8 @@ void TabLayers::on_layers_reload_pressed() { } } + this->ui->layers_search->setVisible(true); + this->ui->layers_search_clear->setVisible(true); this->ui->layers_path_lineedit->setVisible(true); this->ui->layers_browse_button->setVisible(true); this->ui->layers_progress->setVisible(false); @@ -262,6 +290,8 @@ void TabLayers::LoadLayersManifest(const QString &selected_path) { this->ui->layers_path_lineedit->setText(configurator.layers.last_layers_dir.AbsolutePath().c_str()); } + configurator.layers.gui_added_layers_paths.insert(configurator.layers.last_layers_dir); + this->on_layers_reload_pressed(); this->UpdateUI_LayersPaths(UPDATE_REBUILD_UI); diff --git a/vkconfig_gui/tab_layers.h b/vkconfig_gui/tab_layers.h index aa55c1b3b8..eee0498641 100644 --- a/vkconfig_gui/tab_layers.h +++ b/vkconfig_gui/tab_layers.h @@ -38,6 +38,7 @@ class TabLayers : public Tab { public Q_SLOTS: void on_paths_changed(); void on_paths_toggled(); + void on_layers_edit_pressed(); void on_layers_append_pressed(); void on_layers_browse_pressed(); void on_layers_reload_pressed(); diff --git a/vkconfig_gui/widget_tab_layers_path.cpp b/vkconfig_gui/widget_tab_layers_path.cpp index d4b84eea4d..4da507750b 100644 --- a/vkconfig_gui/widget_tab_layers_path.cpp +++ b/vkconfig_gui/widget_tab_layers_path.cpp @@ -26,22 +26,28 @@ #include #include -LayerWidget::LayerWidget(const Layer* layer) - : layer_key(layer->key), api_version(layer->api_version), manifest_path(layer->manifest_path) { +LayerWidget::LayerWidget(const Layer& layer) : layer_id(layer.GetId()) { const Configurator& configurator = Configurator::Get(); - const std::string status = layer->status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer->status)); - const std::string text = format("%s - %s%s, %s layer", layer->key.c_str(), layer->api_version.str().c_str(), status.c_str(), - ::GetToken(layer->type)); + const std::string status = layer.status == STATUS_STABLE ? "" : format(" (%s)", ::GetToken(layer.status)); + const std::string text = format("%s - %s%s - %s layer", layer.key.c_str(), layer.api_version.str().c_str(), status.c_str(), + ::ToLowerCase(::GetToken(layer.type)).c_str()); this->setText(text.c_str()); - this->setToolTip(format("%s", manifest_path.AbsolutePath().c_str()).c_str()); + this->setToolTip(format("%s", layer.manifest_path.AbsolutePath().c_str()).c_str()); + this->setChecked(layer.descriptor.enabled); this->buttom_remove = new QPushButton(this); this->buttom_remove->setIcon(::Get(configurator.current_theme_mode, ::ICON_FOLDER_REMOVE)); this->buttom_remove->setToolTip("Remove the Vulkan layer from Vulkan Configurator"); this->buttom_remove->setFixedSize(24, 24); + if (layer.descriptor.recent) { + QPalette palette = this->palette(); + palette.setColor(QPalette::Active, QPalette::WindowText, QColor(255, 0, 0)); + this->setPalette(palette); + } + this->connect(this, SIGNAL(toggled(bool)), this, SLOT(on_toggled(bool))); this->connect(this->buttom_remove, SIGNAL(clicked(bool)), this, SLOT(on_buttom_remove_clicked(bool))); } @@ -63,15 +69,16 @@ void LayerWidget::on_buttom_remove_clicked(bool checked) { if (!(configurator.Get(HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH))) { QMessageBox alert; - alert.setWindowTitle(format("Removing %s %s layer...", this->layer_key.c_str(), this->api_version.str().c_str()).c_str()); + alert.setWindowTitle( + format("Removing %s %s layer...", this->layer_id.key.c_str(), this->layer_id.api_version.str().c_str()).c_str()); alert.setIcon(QMessageBox::Question); alert.setDefaultButton(QMessageBox::No); alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); alert.setCheckBox(new QCheckBox("Do not show again.")); - alert.setText(format("Are you sure you want to remove %s %s from %s?", this->layer_key.c_str(), - this->api_version.str().c_str(), VKCONFIG_NAME) + alert.setText(format("Are you sure you want to remove %s %s from %s?", this->layer_id.key.c_str(), + this->layer_id.api_version.str().c_str(), VKCONFIG_NAME) .c_str()); - alert.setInformativeText(format("The layer is located: %s", this->manifest_path.AbsolutePath().c_str()).c_str()); + alert.setInformativeText(format("The layer is located: %s", this->layer_id.manifest_path.AbsolutePath().c_str()).c_str()); int ret_val = alert.exec(); if (alert.checkBox()->isChecked()) { configurator.Set(HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH); @@ -81,14 +88,14 @@ void LayerWidget::on_buttom_remove_clicked(bool checked) { } } - configurator.layers.RemovePath(this->manifest_path); + configurator.layers.RemoveLayer(this->layer_id); emit itemChanged(); } void LayerWidget::on_toggled(bool checked) { Configurator& configurator = Configurator::Get(); - configurator.layers.Enable(this->manifest_path, checked); + configurator.layers.EnableLayer(this->layer_id, checked); emit itemToggled(); } diff --git a/vkconfig_gui/widget_tab_layers_path.h b/vkconfig_gui/widget_tab_layers_path.h index d91da0f0e3..9d9eb99b07 100644 --- a/vkconfig_gui/widget_tab_layers_path.h +++ b/vkconfig_gui/widget_tab_layers_path.h @@ -31,7 +31,7 @@ class LayerWidget : public QCheckBox { Q_OBJECT public: - LayerWidget(const Layer *layer); + LayerWidget(const Layer &layer); protected: void resizeEvent(QResizeEvent *event) override; @@ -45,8 +45,6 @@ class LayerWidget : public QCheckBox { void itemToggled(); public: - std::string layer_key; - Version api_version; - Path manifest_path; + LayerId layer_id; QPushButton *buttom_remove = nullptr; }; From 4027d7186fe90b73eef4ffefeceb376c0f2e701d Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 7 Apr 2026 18:02:37 +0200 Subject: [PATCH 15/45] vkconfig: Removed dead code Change-Id: I9d5fa47014b1afa41f6265e560e6d21a0d9e4b26 --- vkconfig_gui/tab_layers.cpp | 38 ++----------------------------------- vkconfig_gui/tab_layers.h | 1 - 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index fe5bf00b4c..0efd32994e 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -46,7 +46,6 @@ TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : T this->connect(this->ui->layers_reload_button, SIGNAL(clicked()), this, SLOT(on_layers_reload_pressed())); this->connect(this->ui->layers_path_lineedit, SIGNAL(editingFinished()), this, SLOT(on_layers_append_pressed())); this->connect(this->ui->layers_path_lineedit, SIGNAL(returnPressed()), this, SLOT(on_layers_append_pressed())); - // this->connect(this->ui->layers_path_lineedit, SIGNAL(clicked()), this, SLOT(on_layers_edit_pressed())); this->connect(this->ui->layers_search, SIGNAL(textEdited(QString)), this, SLOT(on_search_textEdited(QString))); this->connect(this->ui->layers_search_clear, SIGNAL(clicked()), this, SLOT(on_search_clear_pressed())); @@ -58,8 +57,6 @@ TabLayers::TabLayers(MainWindow &window, std::shared_ptr ui) : T QShortcut *shortcut_reload = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_R), this->ui->layers_list); this->connect(shortcut_reload, SIGNAL(activated()), this, SLOT(on_layers_reload_pressed())); - // this->ui->layers_browse_button->installEventFilter(&window); - // this->ui->layers_reload_button->installEventFilter(&window); this->ui->layers_list->installEventFilter(&window); } @@ -117,15 +114,7 @@ void TabLayers::UpdateUI_LayersPaths(UpdateUIMode ui_update_mode) { void TabLayers::UpdateUI(UpdateUIMode ui_update_mode) { const Configurator &configurator = Configurator::Get(); - /* - QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); - policy_search.setHorizontalPolicy(QSizePolicy::MinimumExpanding); - this->ui->layers_search->setSizePolicy(policy_search); - - QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); - policy_append.setHorizontalPolicy(QSizePolicy::MinimumExpanding); - this->ui->layers_path_lineedit->setSizePolicy(policy_append); - */ + this->ui->layers_search_clear->setEnabled(!this->ui->layers_search->text().isEmpty()); this->ui->layers_search->setFocus(); this->ui->layers_progress->resetFormat(); @@ -161,18 +150,6 @@ void TabLayers::on_paths_toggled() { this->UpdateUI_LayersPaths(UPDATE_REFRESH_UI); } -void TabLayers::on_layers_edit_pressed() { - /* - QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); - policy_search.setHorizontalPolicy(QSizePolicy::Preferred); - this->ui->layers_search->setSizePolicy(policy_search); - - QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); - policy_append.setHorizontalPolicy(QSizePolicy::MinimumExpanding); - this->ui->layers_path_lineedit->setSizePolicy(policy_append); - */ -} - void TabLayers::on_layers_append_pressed() { this->LoadLayersManifest(this->ui->layers_path_lineedit->text()); } void TabLayers::on_layers_browse_pressed() { @@ -187,18 +164,7 @@ void TabLayers::on_layers_browse_pressed() { this->LoadLayersManifest(selected_path); } -void TabLayers::on_focus_search() { - this->ui->layers_search->setFocus(); - /* - QSizePolicy policy_search = this->ui->layers_search->sizePolicy(); - policy_search.setHorizontalPolicy(QSizePolicy::MinimumExpanding); - this->ui->layers_search->setSizePolicy(policy_search); - - QSizePolicy policy_append = this->ui->layers_path_lineedit->sizePolicy(); - policy_append.setHorizontalPolicy(QSizePolicy::Preferred); - this->ui->layers_path_lineedit->setSizePolicy(policy_append); - */ -} +void TabLayers::on_focus_search() { this->ui->layers_search->setFocus(); } void TabLayers::on_search_textEdited(const QString &text) { this->layer_filter = text.toStdString(); diff --git a/vkconfig_gui/tab_layers.h b/vkconfig_gui/tab_layers.h index eee0498641..aa55c1b3b8 100644 --- a/vkconfig_gui/tab_layers.h +++ b/vkconfig_gui/tab_layers.h @@ -38,7 +38,6 @@ class TabLayers : public Tab { public Q_SLOTS: void on_paths_changed(); void on_paths_toggled(); - void on_layers_edit_pressed(); void on_layers_append_pressed(); void on_layers_browse_pressed(); void on_layers_reload_pressed(); From 1126f87115d2d669435e8468fde5555851e30f45 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 8 Apr 2026 13:28:26 +0200 Subject: [PATCH 16/45] vkconfig: Add layers display option to show only explicit layers #2636 --- vkconfig_core/configuration.cpp | 52 ++++++- vkconfig_core/configuration.h | 3 + vkconfig_core/configurator.cpp | 7 +- vkconfig_core/configurator.h | 3 +- vkconfig_core/parameter.cpp | 4 + vkconfig_core/parameter.h | 5 + vkconfig_core/type_hide_message.cpp | 3 +- vkconfig_core/type_hide_message.h | 3 +- vkconfig_core/type_layer_builtin.cpp | 12 -- vkconfig_core/type_layer_builtin.h | 1 - vkconfig_core/type_layers_display_mode.cpp | 58 +++++++ vkconfig_core/type_layers_display_mode.h | 39 +++++ vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/tab_configurations.cpp | 170 ++++++++++++++++----- vkconfig_gui/tab_configurations.h | 8 +- vkconfig_gui/tab_layers.cpp | 2 - vkconfig_gui/vkconfig.pro | 4 + vkconfig_gui/widget_resize_combobox.cpp | 36 +++++ vkconfig_gui/widget_resize_combobox.h | 37 +++++ 19 files changed, 374 insertions(+), 74 deletions(-) create mode 100644 vkconfig_core/type_layers_display_mode.cpp create mode 100644 vkconfig_core/type_layers_display_mode.h create mode 100644 vkconfig_gui/widget_resize_combobox.cpp create mode 100644 vkconfig_gui/widget_resize_combobox.h diff --git a/vkconfig_core/configuration.cpp b/vkconfig_core/configuration.cpp index 2bbb65f16b..fe1e709610 100644 --- a/vkconfig_core/configuration.cpp +++ b/vkconfig_core/configuration.cpp @@ -158,6 +158,9 @@ bool Configuration::Load(const Path& full_path, const LayerManager& layers) { if (json_layer_object.value("rank") != QJsonValue::Undefined) { parameter.overridden_rank = json_layer_object.value("rank").toInt(); } + if (json_layer_object.value("was_explicitly_rank") != QJsonValue::Undefined) { + parameter.was_explicitly_rank = json_layer_object.value("was_explicitly_rank").toBool(); + } if (json_layer_object.value("version") != QJsonValue::Undefined) { const std::string& version = ReadStringValue(json_layer_object, "version"); parameter.api_version = version == "latest" ? Version::LATEST : Version(version.c_str()); @@ -249,7 +252,7 @@ bool Configuration::Save(const Path& full_path, bool export_mode) const { for (std::size_t i = 0, n = this->parameters.size(); i < n; ++i) { const Parameter& parameter = this->parameters[i]; - if (parameter.type == LAYER_TYPE_IMPLICIT && parameter.control == LAYER_CONTROL_AUTO) { + if (export_mode && parameter.IsAutoImplicitLayer()) { continue; } @@ -261,6 +264,7 @@ bool Configuration::Save(const Path& full_path, bool export_mode) const { } json_layer.insert("control", GetToken(parameter.control)); json_layer.insert("rank", parameter.overridden_rank); + json_layer.insert("was_explicitly_rank", parameter.was_explicitly_rank); json_layer.insert("version", parameter.api_version == Version::LATEST ? "latest" : parameter.api_version.str().c_str()); if (parameter.builtin == LAYER_BUILTIN_NONE && !export_mode) { json_layer.insert("manifest", parameter.manifest.RelativePath().c_str()); @@ -526,20 +530,39 @@ void Configuration::GatherParameters(const LayerManager& layers) { std::swap(this->parameters, gathered_parameters); } +void Configuration::ResetLayersOrder(const LayerManager& layers) { + for (std::size_t i = 0, n = this->parameters.size(); i < n; ++i) { + this->parameters[i].was_explicitly_rank = false; + this->parameters[i].overridden_rank = Parameter::NO_RANK; + } + + ::OrderParameter(this->parameters, layers); +} + void Configuration::Reorder(const std::vector& layer_names) { std::vector ordered_parameters; int rank = 0; for (std::size_t i = 0, n = layer_names.size(); i < n; ++i) { - Parameter* parameter = this->Find(layer_names[i]); - if (parameter == nullptr) { - continue; - } + if (layer_names[i] == implicit_layers) { + std::vector parameters = this->GatherImplicitAutoLayers(); + for (std::size_t j = 0, p = parameters.size(); j < p; ++j) { + Parameter* parameter = parameters[j]; + parameter->overridden_rank = rank; + ++rank; + ordered_parameters.push_back(*parameter); + } + } else { + Parameter* parameter = this->Find(layer_names[i]); + if (parameter == nullptr) { + continue; + } - parameter->overridden_rank = rank; - ++rank; - ordered_parameters.push_back(*parameter); + parameter->overridden_rank = rank; + ++rank; + ordered_parameters.push_back(*parameter); + } } // Add the remaining parameters not listed in `layer_names` @@ -563,6 +586,19 @@ void Configuration::Reorder(const std::vector& layer_names) { this->parameters = ordered_parameters; } +std::vector Configuration::GatherImplicitAutoLayers() { + std::vector result; + + for (std::size_t i = 0, n = this->parameters.size(); i < n; ++i) { + Parameter* parameter = &this->parameters[i]; + if (parameter->IsAutoImplicitLayer()) { + result.push_back(parameter); + } + } + + return result; +} + bool Configuration::IsDefault() const { const std::vector& builtin_configuration_files = CollectFilePaths(":/configurations/"); for (std::size_t i = 0, n = builtin_configuration_files.size(); i < n; ++i) { diff --git a/vkconfig_core/configuration.h b/vkconfig_core/configuration.h index eb29465b7a..e7ff6764a6 100644 --- a/vkconfig_core/configuration.h +++ b/vkconfig_core/configuration.h @@ -54,8 +54,11 @@ class Configuration { void SwitchLayerVersion(const LayerManager& layers, const std::string& layer_key, const Path& manifest_path); void SwitchLayerLatest(const LayerManager& layers, const std::string& layer_key); void GatherParameters(const LayerManager& layers); + void ResetLayersOrder(const LayerManager& layers); void Reorder(const std::vector& layer_names); + std::vector GatherImplicitAutoLayers(); + std::string key = "New Configuration"; // User readable display of the configuration name (may contain spaces) int version = 1; int platform_flags = PLATFORM_DESKTOP_BIT; diff --git a/vkconfig_core/configurator.cpp b/vkconfig_core/configurator.cpp index 23ac737beb..42dde00c56 100644 --- a/vkconfig_core/configurator.cpp +++ b/vkconfig_core/configurator.cpp @@ -1108,7 +1108,10 @@ bool Configurator::Load() { if (json_object.value("override_layers") != QJsonValue::Undefined) { this->layers_override_enabled = json_object.value("override_layers").toBool(); } - this->advanced = json_object.value("advanced").toBool(); + if (json_object.value("layers_display_mode") != QJsonValue::Undefined) { + this->layers_display_mode = + ::GetLayersDisplayMode(json_object.value("layers_display_mode").toString().toStdString().c_str()); + } this->executable_scope = ::GetExecutableScope(json_object.value("executable_scope").toString().toStdString().c_str()); this->selected_global_configuration = json_object.value("selected_global_configuration").toString().toStdString(); } @@ -1342,7 +1345,7 @@ bool Configurator::Save() const { { QJsonObject json_object; json_object.insert("override_layers", this->layers_override_enabled); - json_object.insert("advanced", this->advanced); + json_object.insert("layers_display_mode", ::GetToken(this->layers_display_mode)); json_object.insert("executable_scope", ::GetToken(this->executable_scope)); json_object.insert("selected_global_configuration", this->selected_global_configuration.c_str()); json_interface_object.insert(::GetToken(TAB_CONFIGURATIONS), json_object); diff --git a/vkconfig_core/configurator.h b/vkconfig_core/configurator.h index e351320b7c..c3f1bc11b3 100644 --- a/vkconfig_core/configurator.h +++ b/vkconfig_core/configurator.h @@ -34,6 +34,7 @@ #include "type_executable_all_enabled_behavior.h" #include "type_configurator_mode.h" #include "type_diagnostic_mode.h" +#include "type_layers_display_mode.h" #include "type_generate_settings.h" #include "type_driver_mode.h" #include "type_theme_mode.h" @@ -167,7 +168,7 @@ class Configurator { bool reset_hard = false; bool has_crashed = false; TabType active_tab = TAB_CONFIGURATIONS; - bool advanced = true; + LayersDisplayMode layers_display_mode = LAYERS_DISPLAY_EXPLICIT_ONLY; Path last_path_status = Path(Path::HOME).AbsolutePath() + "/diagnostics"; Path last_driver_dir = Path(Path::HOME).AbsolutePath(); Path last_path_launch_log = Path(Path::HOME).AbsolutePath() + "/application_log.txt"; diff --git a/vkconfig_core/parameter.cpp b/vkconfig_core/parameter.cpp index 07ad2db757..c4c04746d8 100644 --- a/vkconfig_core/parameter.cpp +++ b/vkconfig_core/parameter.cpp @@ -40,6 +40,10 @@ static bool IsValidationLayer(const std::string& key) { return key == "VK_LAYER_ static bool IsProfilesLayer(const std::string& key) { return key == "VK_LAYER_KHRONOS_profiles"; } +bool Parameter::IsAutoImplicitLayer() const { + return this->type == LAYER_TYPE_IMPLICIT && this->control == LAYER_CONTROL_AUTO && !this->was_explicitly_rank; +} + bool Parameter::ApplyPresetSettings(const LayerPreset& preset) { for (std::size_t preset_index = 0, preset_count = preset.settings.size(); preset_index < preset_count; ++preset_index) { const SettingData* preset_setting = preset.settings[preset_index]; diff --git a/vkconfig_core/parameter.h b/vkconfig_core/parameter.h index 9cc0717952..c4ad0850cc 100644 --- a/vkconfig_core/parameter.h +++ b/vkconfig_core/parameter.h @@ -27,6 +27,8 @@ #include +static const char* implicit_layers = "implicit_layers"; + enum ParameterRank { PARAMETER_RANK_MISSING_LAYER = 0, PARAMETER_RANK_IMPLICIT_LAYER, @@ -45,6 +47,8 @@ struct Parameter { Parameter(const std::string& key, const LayerControl control) : key(key), control(control) {} + bool IsAutoImplicitLayer() const; + bool ApplyPresetSettings(const LayerPreset& preset); bool GetExpanded(const std::string& setting_key, const std::string& flag = "") const; void SetExpanded(const std::string& setting_key, const std::string& flag, bool expanded); @@ -59,6 +63,7 @@ struct Parameter { Version api_version = Version::LATEST; Path manifest; bool override_settings = true; + bool was_explicitly_rank = false; }; ParameterRank GetParameterOrdering(const LayerManager& layers, const Parameter& parameter); diff --git a/vkconfig_core/type_hide_message.cpp b/vkconfig_core/type_hide_message.cpp index 7317156bc3..489bde32da 100644 --- a/vkconfig_core/type_hide_message.cpp +++ b/vkconfig_core/type_hide_message.cpp @@ -48,7 +48,8 @@ const char* GetToken(HideMessageType value) { "HIDE_MESSAGE_WARN_DARK_THEME_ICON", "HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH", "HIDE_MESSAGE_QUESTION_REMOVING_DRIVER_PATH", - "HIDE_MESSAGE_ERROR_32BIT"}; + "HIDE_MESSAGE_ERROR_32BIT", + "HIDE_MESSAGE_LAYERS_ORDER_RESET"}; static_assert(std::size(TOKENS) == HIDE_MESSAGE_COUNT); if (value >= HIDE_MESSAGE_FIRST && value <= HIDE_MESSAGE_LAST) { diff --git a/vkconfig_core/type_hide_message.h b/vkconfig_core/type_hide_message.h index e430aaeb0a..a9e61f8e1b 100644 --- a/vkconfig_core/type_hide_message.h +++ b/vkconfig_core/type_hide_message.h @@ -47,9 +47,10 @@ enum HideMessageType { HIDE_MESSAGE_QUESTION_REMOVING_LAYERS_PATH, HIDE_MESSAGE_QUESTION_REMOVING_DRIVER_PATH, HIDE_MESSAGE_ERROR_32BIT, + HIDE_MESSAGE_LAYERS_ORDER_RESET, HIDE_MESSAGE_FIRST = HIDE_MESSAGE_NEED_APPLICATION_RESTART, - HIDE_MESSAGE_LAST = HIDE_MESSAGE_ERROR_32BIT, + HIDE_MESSAGE_LAST = HIDE_MESSAGE_LAYERS_ORDER_RESET, HIDE_MESSAGE_INVALID = ~0, }; diff --git a/vkconfig_core/type_layer_builtin.cpp b/vkconfig_core/type_layer_builtin.cpp index b4d1cb71e1..af3fe6b909 100644 --- a/vkconfig_core/type_layer_builtin.cpp +++ b/vkconfig_core/type_layer_builtin.cpp @@ -51,15 +51,3 @@ const char* GetLabel(LayerBuiltin builtin) { return TABLE[builtin]; } -/* -const char* GetDescription(LayerBuiltin builtin) { - static const char* TABLE[] = { - "N/A", // LAYER_BUILTIN_NONE - "Vulkan Layers are located by the Vulkan Application by setting 'VK_ADD_LAYER_PATH' Application at launch", // -LAYER_BUILTIN_UNORDERED - }; - static_assert(std::size(TABLE) == LAYER_BUILTIN_COUNT); - - return TABLE[builtin]; -} -*/ diff --git a/vkconfig_core/type_layer_builtin.h b/vkconfig_core/type_layer_builtin.h index 700f95a8f0..f50143b51d 100644 --- a/vkconfig_core/type_layer_builtin.h +++ b/vkconfig_core/type_layer_builtin.h @@ -33,4 +33,3 @@ enum { LAYER_BUILTIN_COUNT = LAYER_BUILTIN_LAST - LAYER_BUILTIN_FIRST + 1 }; LayerBuiltin GetLayerBuiltin(const char* token); const char* GetToken(LayerBuiltin builtin); const char* GetLabel(LayerBuiltin builtin); -// const char* GetDescription(LayerBuiltin builtin); diff --git a/vkconfig_core/type_layers_display_mode.cpp b/vkconfig_core/type_layers_display_mode.cpp new file mode 100644 index 0000000000..30e98660bb --- /dev/null +++ b/vkconfig_core/type_layers_display_mode.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Richard S. Wright Jr. + * - Christophe Riccio + */ + +#include "type_layers_display_mode.h" + +#include +#include + +const char* GetToken(LayersDisplayMode mode) { + static const char* TOKENS[] = { + "all", // LAYERS_DISPLAY_ALL + "explicit", // LAYERS_DISPLAY_EXPLICIT_ONLY + "enabled", // LAYERS_DISPLAY_ENABLED_ONLY + }; + static_assert(std::size(TOKENS) == LAYERS_DISPLAY_COUNT); + + return TOKENS[mode]; +} + +const char* GetLabel(LayersDisplayMode mode) { + static const char* TOKENS[] = { + "Show all available layers", // LAYERS_DISPLAY_ALL + "Bundle default implicit layers", // LAYERS_DISPLAY_EXPLICIT_ONLY + "Show only enabled layers", // LAYERS_DISPLAY_ENABLED_ONLY + }; + static_assert(std::size(TOKENS) == LAYERS_DISPLAY_COUNT); + + return TOKENS[mode]; +} + +LayersDisplayMode GetLayersDisplayMode(const char* token) { + for (int i = LAYERS_DISPLAY_FIRST, l = LAYERS_DISPLAY_LAST; i <= l; ++i) { + const LayersDisplayMode mode = static_cast(i); + if (std::strcmp(::GetToken(mode), token) == 0) { + return mode; + } + } + + return LAYERS_DISPLAY_ALL; +} diff --git a/vkconfig_core/type_layers_display_mode.h b/vkconfig_core/type_layers_display_mode.h new file mode 100644 index 0000000000..0dcc12216c --- /dev/null +++ b/vkconfig_core/type_layers_display_mode.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Richard S. Wright Jr. + * - Christophe Riccio + */ + +#pragma once + +enum LayersDisplayMode { // Enum value can't be changed + LAYERS_DISPLAY_ALL = 0, + LAYERS_DISPLAY_EXPLICIT_ONLY, + LAYERS_DISPLAY_ENABLED_ONLY, + + LAYERS_DISPLAY_FIRST = LAYERS_DISPLAY_ALL, + LAYERS_DISPLAY_LAST = LAYERS_DISPLAY_ENABLED_ONLY +}; + +enum { LAYERS_DISPLAY_COUNT = LAYERS_DISPLAY_LAST - LAYERS_DISPLAY_FIRST + 1 }; + +const char* GetToken(LayersDisplayMode mode); + +const char* GetLabel(LayersDisplayMode mode); + +LayersDisplayMode GetLayersDisplayMode(const char* token); \ No newline at end of file diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 963f240612..06acf843e4 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -21,6 +21,7 @@ - Improve UI consistency - Move 'Validate Layer Manifest' settings to the preference tab - Improve keyboard shortcuts +- Add layer display option to show only explicit layers #2636 ### Fixes: - Fix command line whitespace decoding #2625 diff --git a/vkconfig_gui/tab_configurations.cpp b/vkconfig_gui/tab_configurations.cpp index 956f99df6f..38a261c404 100644 --- a/vkconfig_gui/tab_configurations.cpp +++ b/vkconfig_gui/tab_configurations.cpp @@ -19,9 +19,9 @@ */ #include "widget_tab_configurations_layer.h" +#include "widget_resize_combobox.h" #include "tab_configurations.h" #include "mainwindow.h" -#include "widget_resize_button.h" #include "style.h" #include "item_tree.h" @@ -138,13 +138,21 @@ TabConfigurations::TabConfigurations(MainWindow &window, std::shared_ptrui->configurations_executable_scope->setCurrentIndex(current_scope); this->ui->configurations_executable_scope->blockSignals(false); - this->advanced_mode = new ResizeButton(this->ui->configurations_group_box_layers, 0); - this->advanced_mode->setMinimumSize(24, 24); - this->advanced_mode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - this->advanced_mode->adjustSize(); - this->ui->configurations_group_box_layers->installEventFilter(this->advanced_mode); - - this->connect(this->advanced_mode, SIGNAL(pressed()), this, SLOT(on_configurations_advanced_toggle_pressed())); + this->layer_display_mode = new ResizeComboBox(this->ui->configurations_group_box_layers, 0); + for (int i = 0, n = LAYERS_DISPLAY_COUNT; i < n; ++i) { + LayersDisplayMode mode = static_cast(i); + this->layer_display_mode->addItem(::GetLabel(mode)); + } + this->layer_display_mode->setCurrentIndex(configurator.layers_display_mode); + QFont font = this->layer_display_mode->font(); + font.setBold(false); + this->layer_display_mode->setFont(font); + this->layer_display_mode->setMinimumSize(196, 24); + this->layer_display_mode->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + this->layer_display_mode->adjustSize(); + this->ui->configurations_group_box_layers->installEventFilter(this->layer_display_mode); + this->connect(this->layer_display_mode, SIGNAL(currentIndexChanged(int)), this, + SLOT(on_configurations_layers_display_currentIndexChanged(int))); QShortcut *shortcut_override = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Space), this->ui->configurations_group_box_override); @@ -252,26 +260,55 @@ void TabConfigurations::UpdateUI_Layers(UpdateUIMode mode) { Configurator &configurator = Configurator::Get(); this->ui->configurations_group_box_layers->setEnabled(configurator.HasEnabledUI(ENABLE_UI_LAYERS)); - this->ui->execute_closer_application_label->setVisible(configurator.advanced); - this->ui->execute_closer_driver_label->setVisible(configurator.advanced); - this->ui->configurations_layers_list->setDragEnabled(configurator.advanced); + this->ui->execute_closer_application_label->setVisible(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY); + this->ui->execute_closer_driver_label->setVisible(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY); + this->ui->configurations_layers_list->setDragEnabled(configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY); Configuration *configuration = configurator.GetActiveConfiguration(); if (configuration != nullptr) { bool selected_layer = configuration->selected_layer_name.empty(); + bool first_implicit_layer = true; + for (std::size_t i = 0, n = configuration->parameters.size(); i < n; ++i) { Parameter ¶meter = configuration->parameters[i]; - if (!configurator.advanced) { - if (parameter.control != LAYER_CONTROL_ON && parameter.control != LAYER_CONTROL_OFF) { - continue; + switch (configurator.layers_display_mode) { + default: + break; + case LAYERS_DISPLAY_ENABLED_ONLY: { + if (configurator.layers_display_mode == LAYERS_DISPLAY_ENABLED_ONLY) { + if (parameter.control != LAYER_CONTROL_ON && parameter.control != LAYER_CONTROL_OFF) { + continue; + } + } + break; + } + case LAYERS_DISPLAY_EXPLICIT_ONLY: { + if (parameter.IsAutoImplicitLayer()) { + if (first_implicit_layer) { + first_implicit_layer = false; + + QListWidgetItem *item = new ListItem(implicit_layers); + item->setFlags(item->flags() | Qt::ItemIsSelectable); + item->setSizeHint(QSize(0, ITEM_HEIGHT)); + if (configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY) { + item->setIcon(::Get(configurator.current_theme_mode, ICON_DRAG)); + } + this->ui->configurations_layers_list->addItem(item); + + QLabel *layer_widget = new QLabel("Vulkan Implicit Layers Located by Vulkan Configurator"); + this->ui->configurations_layers_list->setItemWidget(item, layer_widget); + } + continue; + } + break; } } QListWidgetItem *item = new ListItem(parameter.key.c_str()); item->setFlags(item->flags() | Qt::ItemIsSelectable); item->setSizeHint(QSize(0, ITEM_HEIGHT)); - if (configurator.advanced) { + if (configurator.layers_display_mode != LAYERS_DISPLAY_ENABLED_ONLY) { item->setIcon(::Get(configurator.current_theme_mode, ICON_DRAG)); } this->ui->configurations_layers_list->addItem(item); @@ -359,15 +396,6 @@ void TabConfigurations::UpdateUI(UpdateUIMode ui_update_mode) { "Change the 'Vulkan Loader Configuration scope' to apply a configuration."); } - assert(this->advanced_mode != nullptr); - if (configurator.advanced) { - this->advanced_mode->setIcon(::Get(configurator.current_theme_mode, ::ICON_SHOW)); - this->advanced_mode->setToolTip("View only Enabled Vulkan layers"); - } else { - this->advanced_mode->setIcon(::Get(configurator.current_theme_mode, ::ICON_ADVANCED)); - this->advanced_mode->setToolTip("Configure all Available Vulkan Layers"); - } - this->ui->configurations_group_box_scope->blockSignals(true); this->ui->configurations_group_box_scope->setVisible(configurator.configuration_show_scope); this->ui->configurations_group_box_scope->setChecked(configurator.layers_override_enabled); @@ -409,7 +437,11 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { // Layers were reordered, we need to update the configuration std::vector layer_names; - for (int i = 0, n = ui->configurations_layers_list->count(); i < n; ++i) { + for (int i = 0, n = this->ui->configurations_layers_list->count(); i < n; ++i) { + QListWidgetItem *item = this->ui->configurations_layers_list->item(i); + layer_names.push_back(static_cast(item)->key.c_str()); + + /* QWidget *widget = ui->configurations_layers_list->itemWidget(ui->configurations_layers_list->item(i)); if (widget != nullptr) { ConfigurationLayerWidget *layer_widget = dynamic_cast(widget); @@ -417,10 +449,19 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { layer_names.push_back(layer_widget->layer_name); } } +*/ } Configuration *configuration = configurator.GetActiveConfiguration(); if (configuration != nullptr) { + QListWidgetItem *item = this->ui->configurations_layers_list->currentItem(); + if (item != nullptr) { + Parameter *parameter = configuration->Find(static_cast(item)->key); + if (parameter != nullptr) { + parameter->was_explicitly_rank = true; + } + } + configuration->Reorder(layer_names); configurator.Override(OVERRIDE_AREA_LOADER_SETTINGS_BIT); @@ -504,7 +545,17 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { menu.addSeparator(); - QAction *action_reset_one = new QAction("Reset the Default Configuration", nullptr); + std::string label = name; + if (!label.empty()) { + label = format(" '%s'", label.c_str()); + } + + QAction *action_reset_order = new QAction(format("Reset the%s Default Layers order", label.c_str()).c_str(), nullptr); + action_reset_order->setEnabled(item != nullptr); + action_reset_order->setToolTip("Reset the configuration, discarding all changes of this configuration."); + menu.addAction(action_reset_order); + + QAction *action_reset_one = new QAction(format("Reset the%s Default Configuration", label.c_str()).c_str(), nullptr); action_reset_one->setEnabled(configurator.configurations.IsDefaultConfiguration(name)); action_reset_one->setToolTip("Reset the configuration, discarding all changes of this configuration."); menu.addAction(action_reset_one); @@ -569,6 +620,8 @@ bool TabConfigurations::EventFilter(QObject *target, QEvent *event) { this->OnContextMenuDuplicateClicked(item); } else if (action == action_delete) { this->OnContextMenuDeleteClicked(item); + } else if (action == action_reset_order) { + this->OnContextMenuResetLayersOrderClicked(item); } else if (action == action_reset_one) { this->OnContextMenuResetOneClicked(item); } else if (action == action_reset_all) { @@ -993,6 +1046,39 @@ void TabConfigurations::OnContextMenuDeleteClicked(ListItem *item) { this->UpdateUI(UPDATE_REBUILD_UI); } +void TabConfigurations::OnContextMenuResetLayersOrderClicked(ListItem *item) { + assert(item); + assert(!item->key.empty()); + + Configurator &configurator = Configurator::Get(); + Configuration *configuration = configurator.configurations.FindConfiguration(item->key); + assert(configuration != nullptr); + + if (!(configurator.Get(HIDE_MESSAGE_LAYERS_ORDER_RESET))) { + QMessageBox alert; + alert.setWindowTitle(format("Resetting *%s* Layers order...", configuration->key.c_str()).c_str()); + alert.setText(format("Are you sure you want to reset the *%s* layers order?", configuration->key.c_str()).c_str()); + alert.setInformativeText("The Vulkan Layers order will be reset to the default order."); + alert.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + alert.setDefaultButton(QMessageBox::Yes); + alert.setCheckBox(new QCheckBox("Do not show again.")); + alert.setIcon(QMessageBox::Warning); + int retval = alert.exec(); + + if (alert.checkBox()->isChecked()) { + configurator.Set(HIDE_MESSAGE_LAYERS_ORDER_RESET); + } + + if (retval == QMessageBox::No) { + return; + } + } + + configuration->ResetLayersOrder(configurator.layers); + + this->UpdateUI(UPDATE_REBUILD_UI); +} + void TabConfigurations::OnContextMenuResetOneClicked(ListItem *item) { assert(item); assert(!item->key.empty()); @@ -1002,15 +1088,15 @@ void TabConfigurations::OnContextMenuResetOneClicked(ListItem *item) { assert(configuration != nullptr); QMessageBox alert; - alert.setWindowTitle(format("Resetting *%s* loader configuration...", configuration->key.c_str()).c_str()); - alert.setText(format("Are you sure you want to reset the *%s* loader configuration?", configuration->key.c_str()).c_str()); + alert.setWindowTitle(format("Resetting *%s* layers configuration...", configuration->key.c_str()).c_str()); + alert.setText(format("Are you sure you want to reset the *%s* layers configuration?", configuration->key.c_str()).c_str()); if (configuration->IsDefault()) alert.setInformativeText( - format("The loader configuration, including layers settings, will be restored to default built-in *%s* configuration.", + format("The layers configuration, including layers settings, will be restored to default built-in *%s* configuration.", configuration->key.c_str()) .c_str()); else if (configurator.configurations.HasFile(*configuration)) - alert.setInformativeText(format("The loader configuration, including layers settings, will be reloaded using the *%s* " + alert.setInformativeText(format("The layers configuration, including layers settings, will be reloaded using the *%s* " "saved file from previous %s run.", configuration->key.c_str(), VKCONFIG_NAME) .c_str()); @@ -1117,13 +1203,6 @@ void TabConfigurations::GenerateClicked(GenerateSettingsMode mode) { } } -void TabConfigurations::on_configurations_advanced_toggle_pressed() { - Configurator &configurator = Configurator::Get(); - configurator.advanced = !configurator.advanced; - - this->UpdateUI(UPDATE_REBUILD_UI); -} - void TabConfigurations::on_configurations_override_toggled() { this->ui->configurations_group_box_override->setChecked(!this->ui->configurations_group_box_override->isChecked()); } @@ -1427,23 +1506,30 @@ void TabConfigurations::on_configurations_layers_list_currentRowChanged(int curr return; // No row selected } - QWidget *widget = this->ui->configurations_layers_list->itemWidget(this->ui->configurations_layers_list->item(currentRow)); - if (widget == nullptr) { + ListItem *item = static_cast(this->ui->configurations_layers_list->item(currentRow)); + if (item == nullptr) { return; } - const std::string &layer_string = static_cast(widget)->layer_name; + const std::string &layer_key = item->key; Configurator &configurator = Configurator::Get(); Configuration *configuration = configurator.GetActiveConfiguration(); assert(configuration != nullptr); - if (configuration->selected_layer_name != layer_string) { - configuration->selected_layer_name = layer_string; + if (configuration->selected_layer_name != layer_key) { + configuration->selected_layer_name = layer_key; this->UpdateUI_Settings(UPDATE_REBUILD_UI); } } +void TabConfigurations::on_configurations_layers_display_currentIndexChanged(int index) { + Configurator &configurator = Configurator::Get(); + configurator.layers_display_mode = static_cast(index); + + this->UpdateUI(UPDATE_REBUILD_UI); +} + void TabConfigurations::on_configurations_layerVersionChanged() { this->UpdateUI_Layers(UPDATE_REBUILD_UI); } diff --git a/vkconfig_gui/tab_configurations.h b/vkconfig_gui/tab_configurations.h index d6f3e984b3..7bb04b4cd0 100644 --- a/vkconfig_gui/tab_configurations.h +++ b/vkconfig_gui/tab_configurations.h @@ -26,7 +26,7 @@ #include "../vkconfig_core/type_generate_settings.h" -#include +#include #include class TabConfigurations : public Tab { @@ -48,8 +48,6 @@ class TabConfigurations : public Tab { void UpdateUI_Settings(UpdateUIMode ui_update_mode); public Q_SLOTS: - void on_configurations_advanced_toggle_pressed(); - void on_configurations_override_toggled(); void on_configurations_override_toggled(bool checked); void on_configurations_executable_scope_currentIndexChanged(int index); @@ -68,18 +66,20 @@ class TabConfigurations : public Tab { void on_configuration_settings_file_disable_pressed(); void on_configurations_layers_list_currentRowChanged(int currentRow); + void on_configurations_layers_display_currentIndexChanged(int index); void on_configurations_layerVersionChanged(); private: SettingsTreeManager _settings_tree_manager; - QPushButton *advanced_mode = nullptr; + QComboBox *layer_display_mode = nullptr; void OnContextMenuNewClicked(ListItem *item); void OnContextMenuImportClicked(ListItem *item); void OnContextMenuRenameClicked(ListItem *item); void OnContextMenuDuplicateClicked(ListItem *item); void OnContextMenuDeleteClicked(ListItem *item); + void OnContextMenuResetLayersOrderClicked(ListItem *item); void OnContextMenuResetOneClicked(ListItem *item); void OnContextMenuResetAllClicked(ListItem *item); void OnContextMenuExportConfigsClicked(ListItem *item); diff --git a/vkconfig_gui/tab_layers.cpp b/vkconfig_gui/tab_layers.cpp index 0efd32994e..7be3a581b9 100644 --- a/vkconfig_gui/tab_layers.cpp +++ b/vkconfig_gui/tab_layers.cpp @@ -153,8 +153,6 @@ void TabLayers::on_paths_toggled() { void TabLayers::on_layers_append_pressed() { this->LoadLayersManifest(this->ui->layers_path_lineedit->text()); } void TabLayers::on_layers_browse_pressed() { - this->on_layers_edit_pressed(); - Configurator &configurator = Configurator::Get(); const QString selected_path = diff --git a/vkconfig_gui/vkconfig.pro b/vkconfig_gui/vkconfig.pro index a7cd821db5..0d10ee1ca8 100644 --- a/vkconfig_gui/vkconfig.pro +++ b/vkconfig_gui/vkconfig.pro @@ -74,6 +74,7 @@ SOURCES += \ ../vkconfig_core/type_layer_builtin.cpp \ ../vkconfig_core/type_layer_control.cpp \ ../vkconfig_core/type_layer_type.cpp \ + ../vkconfig_core/type_layers_display_mode.cpp \ ../vkconfig_core/type_layers_paths.cpp \ ../vkconfig_core/type_log.cpp \ ../vkconfig_core/type_platform.cpp \ @@ -91,6 +92,7 @@ SOURCES += \ ../vkconfig_core/version.cpp \ ../vkconfig_core/vulkan_util.cpp \ widget_resize_button.cpp \ + widget_resize_combobox.cpp \ widget_layer_version.cpp \ widget_tab_configurations_layer.cpp \ widget_tab_layers_path.cpp \ @@ -173,6 +175,7 @@ HEADERS += \ ../vkconfig_core/type_layer_builtin.h \ ../vkconfig_core/type_layer_control.h \ ../vkconfig_core/type_layer_type.h \ + ../vkconfig_core/type_layers_display_mode.h \ ../vkconfig_core/type_layers_paths.h \ ../vkconfig_core/type_log.h \ ../vkconfig_core/type_platform.h \ @@ -190,6 +193,7 @@ HEADERS += \ ../vkconfig_core/version.h \ ../vkconfig_core/vulkan_util.h \ widget_resize_button.h \ + widget_resize_combobox.h \ widget_layer_version.h \ widget_tab_configurations_layer.h \ widget_tab_layers_path.h \ diff --git a/vkconfig_gui/widget_resize_combobox.cpp b/vkconfig_gui/widget_resize_combobox.cpp new file mode 100644 index 0000000000..1bb3788d2d --- /dev/null +++ b/vkconfig_gui/widget_resize_combobox.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Christophe Riccio + */ + +#include "widget_resize_combobox.h" + +ResizeComboBox::ResizeComboBox(QWidget *parent, int shift) : QComboBox(parent), parent(parent), shift(shift) {} + +bool ResizeComboBox::eventFilter(QObject *o, QEvent *e) { + (void)o; + + if (e->type() == QEvent::Resize) { + QSize size = this->minimumSize(); + + const QRect enabled_button_rect = + QRect(this->parent->width() - size.width() * (shift + 1) - 5, 0, size.width(), size.height()); + this->setGeometry(enabled_button_rect); + } + return false; +} diff --git a/vkconfig_gui/widget_resize_combobox.h b/vkconfig_gui/widget_resize_combobox.h new file mode 100644 index 0000000000..945264b21d --- /dev/null +++ b/vkconfig_gui/widget_resize_combobox.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Christophe Riccio + */ + +#pragma once + +#include +#include + +class ResizeComboBox : public QComboBox { + Q_OBJECT + + public: + ResizeComboBox(QWidget *parent, int shift); + + bool eventFilter(QObject *o, QEvent *e) override; + + private: + QWidget *parent = nullptr; + int shift = 0; +}; From fe0876c9308bcfd085ba45ea1835e6a3f96d077e Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Wed, 8 Apr 2026 16:48:52 -0400 Subject: [PATCH 17/45] dump: Fix VkIndirectExecutionSetCreateInfoEXT union --- layersvt/generated/api_dump_implementation.h | 1 + scripts/generators/api_dump_generator.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/layersvt/generated/api_dump_implementation.h b/layersvt/generated/api_dump_implementation.h index 8ea2576d11..411ee90ec6 100644 --- a/layersvt/generated/api_dump_implementation.h +++ b/layersvt/generated/api_dump_implementation.h @@ -41669,6 +41669,7 @@ void dump_VkIndirectExecutionSetInfoEXT(const VkIndirectExecutionSetInfoEXT& obj template void dump_VkIndirectExecutionSetCreateInfoEXT(const VkIndirectExecutionSetCreateInfoEXT& object, const ApiDumpSettings& settings, const char* type_name, const char* var_name, int indents, const void* address = nullptr) { dump_start(settings, OutputConstruct::api_struct, type_name, var_name, indents, address); + ApiDumpInstance::current().setIndirectExecutionSetInfoType(object.type); dump_VkStructureType(object.sType, settings, "VkStructureType", "sType", indents + (Format == ApiDumpFormat::Json ? 2 : 1)); dump_separate_members(settings); dump_pNext(object.pNext, settings, "const void*", "pNext", indents + (Format == ApiDumpFormat::Json ? 2 : 1)); diff --git a/scripts/generators/api_dump_generator.py b/scripts/generators/api_dump_generator.py index 10e81dcf13..13fbfd0711 100644 --- a/scripts/generators/api_dump_generator.py +++ b/scripts/generators/api_dump_generator.py @@ -69,7 +69,7 @@ 'ApiDumpInstance::current().setDescriptorType(object.type);', 'VkResourceDescriptorInfoEXT': 'ApiDumpInstance::current().setDescriptorType(object.type);', - 'VkIndirectExecutionSetInfoEXT': + 'VkIndirectExecutionSetCreateInfoEXT': 'ApiDumpInstance::current().setIndirectExecutionSetInfoType(object.type);', 'VkIndirectCommandsLayoutTokenEXT': 'ApiDumpInstance::current().setIndirectCommandsLayoutToken(object.type);', From 885e788c2663b50f91ef130176343be789078d30 Mon Sep 17 00:00:00 2001 From: Christophe Date: Wed, 8 Apr 2026 18:07:15 +0200 Subject: [PATCH 18/45] vkconfig: Add enabled and ordered layers to generated configuration files #2634 #2565 --- .../generate_layers_settings_env.cpp | 44 ++++++++++--------- .../generate_layers_settings_hpp.cpp | 22 +++++++++- .../VK_LAYER_LUNARG_reference_1_2_1.bat | 4 ++ .../VK_LAYER_LUNARG_reference_1_2_1.sh | 4 ++ ...LAYER_LUNARG_reference_1_2_1.vulkan_h_code | 13 +++++- ...YER_LUNARG_reference_1_2_1.vulkan_hpp_code | 13 +++++- .../test/generated/vk_layer_settings.bat | 4 ++ .../test/generated/vk_layer_settings.sh | 4 ++ .../generated/vulkan_hpp_layer_settings.code | 13 +++++- .../test/generated/vulkan_layer_settings.code | 13 +++++- vkconfig_core/test/test_generate_settings.cpp | 9 ++++ vkconfig_gui/CHANGELOG.md | 1 + 12 files changed, 117 insertions(+), 27 deletions(-) diff --git a/vkconfig_core/generate_layers_settings_env.cpp b/vkconfig_core/generate_layers_settings_env.cpp index 1f8f26ca9e..2400fcb829 100644 --- a/vkconfig_core/generate_layers_settings_env.cpp +++ b/vkconfig_core/generate_layers_settings_env.cpp @@ -52,35 +52,37 @@ bool GenerateSettingsEnv(Configurator& configurator, ExportEnvMode mode, const P stream << COMMENT << "\n"; stream << COMMENT << format("This code was generated by Vulkan Configurator %s\n\n", Version::VKCONFIG.str().c_str()).c_str(); - /* - stream << COMMENT << "Loader Settings:\n"; + stream << COMMENT << "Loader Settings:\n"; - const std::vector& stderr_log = ::GetLogTokens(configurator.loader_log_messages_flags); - const std::string stderr_logs = Merge(stderr_log, ","); + const std::vector& stderr_log = ::GetLogTokens(configurator.loader_log_messages_flags); + const std::string stderr_logs = Merge(stderr_log, ","); + /* if (configurator.loader_log_enabled) { + stream << COMMENT << "Vulkan Loader enabled and ordered:\n"; stream << EXPORT << "VK_LOADER_DEBUG=" << stderr_logs.c_str() << "\n"; } - - { - stream << EXPORT << "VK_INSTANCE_LAYERS="; - std::vector layer_list; - for (std::size_t i = 0, n = configuration->parameters.size(); i < n; ++i) { - const Parameter& parameter = configuration->parameters[i]; - if (parameter.builtin == LAYER_BUILTIN_UNORDERED) { - continue; - } - if (parameter.control != LAYER_CONTROL_ON) { - continue; - } - layer_list.push_back(parameter.key); + */ + // if (configurator.layers_override_enabled) + { + stream << COMMENT << "Vulkan Layers enabled and ordered:\n"; + stream << EXPORT << "VK_INSTANCE_LAYERS="; + std::vector layer_list; + for (std::size_t i = 0, n = configuration->parameters.size(); i < n; ++i) { + const Parameter& parameter = configuration->parameters[i]; + if (parameter.builtin == LAYER_BUILTIN_UNORDERED) { + continue; } - stream << Merge(layer_list, ",").c_str(); - stream << "\n"; + if (parameter.control != LAYER_CONTROL_ON) { + continue; + } + layer_list.push_back(parameter.key); } - + stream << Merge(layer_list, ",").c_str(); stream << "\n"; - */ + } + + stream << "\n"; // Loop through all the layers for (std::size_t j = 0, n = configuration->parameters.size(); j < n; ++j) { diff --git a/vkconfig_core/generate_layers_settings_hpp.cpp b/vkconfig_core/generate_layers_settings_hpp.cpp index 97538d8f3e..6e8dc8af52 100644 --- a/vkconfig_core/generate_layers_settings_hpp.cpp +++ b/vkconfig_core/generate_layers_settings_hpp.cpp @@ -278,6 +278,16 @@ bool GenerateSettingsCode(Configurator& configurator, ExportHppMode mode, const stream << "// `LayerSettings` allows initializing layer settings from Vulkan application code.\n"; stream << "struct LayerSettings {\n"; + stream << "\tstd::vector layer_list {\n"; + for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count; + ++parameter_index) { + const Parameter& parameter = configuration->parameters[parameter_index]; + if (parameter.control == LAYER_CONTROL_ON && parameter.builtin == LAYER_BUILTIN_NONE) { + stream << format("\t\t\"%s\",\n", parameter.key.c_str()).c_str(); + } + } + stream << "\t};\n"; + for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count; ++parameter_index) { const Parameter& parameter = configuration->parameters[parameter_index]; @@ -315,10 +325,18 @@ bool GenerateSettingsCode(Configurator& configurator, ExportHppMode mode, const stream << "\t// \tconst VkLayerSettingEXT* pSettings;\n"; stream << "\t// } VkLayerSettingsCreateInfoEXT;\n\n"; + stream << "\tstd::vector layers() {\n"; + stream << "\t\tstd::vector results;\n"; + stream << "\t\tfor (std::size_t i = 0, n = layer_list.size(); i < n; ++i) {\n"; + stream << "\t\t\tresults.push_back(layer_list[i].c_str());\n"; + stream << "\t\t};\n"; + stream << "\t\treturn results;\n"; + stream << "\t};\n\n"; + if (mode == EXPORT_HPP_VULKAN_HPP) { - stream << "\tstd::vector info() {\n"; + stream << "\tstd::vector settings() {\n"; } else { - stream << "\tstd::vector info() {\n"; + stream << "\tstd::vector settings() {\n"; } for (std::size_t parameter_index = 0, parameter_count = configuration->parameters.size(); parameter_index < parameter_count; diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat index af20db3d03..4d712525eb 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.bat @@ -15,6 +15,10 @@ :: :: This code was generated by Vulkan Configurator 3.5.0 +:: Loader Settings: +:: Vulkan Layers enabled and ordered: +set VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_reference_1_2_1 + :: reference layer :: ========================================== diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh index a724062d59..1e6faa28c2 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.sh @@ -15,6 +15,10 @@ #! #! This code was generated by Vulkan Configurator 3.5.0 +#! Loader Settings: +#! Vulkan Layers enabled and ordered: +export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_reference_1_2_1 + #! reference layer #! ========================================== diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code index 9bb12fc903..8bd3de26b0 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_h_code @@ -300,6 +300,9 @@ private: // `LayerSettings` allows initializing layer settings from Vulkan application code. struct LayerSettings { + std::vector layer_list { + "VK_LAYER_LUNARG_reference_1_2_1", + }; Reference121SettingData reference_1_2_1; // Use for VkLayerSettingsCreateInfoEXT `settingCount` and `pSettings` argument @@ -311,7 +314,15 @@ struct LayerSettings { // const VkLayerSettingEXT* pSettings; // } VkLayerSettingsCreateInfoEXT; - std::vector info() { + std::vector layers() { + std::vector results; + for (std::size_t i = 0, n = layer_list.size(); i < n; ++i) { + results.push_back(layer_list[i].c_str()); + }; + return results; + }; + + std::vector settings() { this->reference_1_2_1.init(); std::vector init{ diff --git a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code index f7ef21cc06..f75475ff92 100644 --- a/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code +++ b/vkconfig_core/test/generated/VK_LAYER_LUNARG_reference_1_2_1.vulkan_hpp_code @@ -300,6 +300,9 @@ private: // `LayerSettings` allows initializing layer settings from Vulkan application code. struct LayerSettings { + std::vector layer_list { + "VK_LAYER_LUNARG_reference_1_2_1", + }; Reference121SettingData reference_1_2_1; // Use for VkLayerSettingsCreateInfoEXT `settingCount` and `pSettings` argument @@ -311,7 +314,15 @@ struct LayerSettings { // const VkLayerSettingEXT* pSettings; // } VkLayerSettingsCreateInfoEXT; - std::vector info() { + std::vector layers() { + std::vector results; + for (std::size_t i = 0, n = layer_list.size(); i < n; ++i) { + results.push_back(layer_list[i].c_str()); + }; + return results; + }; + + std::vector settings() { this->reference_1_2_1.init(); std::vector init{ diff --git a/vkconfig_core/test/generated/vk_layer_settings.bat b/vkconfig_core/test/generated/vk_layer_settings.bat index af20db3d03..4d712525eb 100644 --- a/vkconfig_core/test/generated/vk_layer_settings.bat +++ b/vkconfig_core/test/generated/vk_layer_settings.bat @@ -15,6 +15,10 @@ :: :: This code was generated by Vulkan Configurator 3.5.0 +:: Loader Settings: +:: Vulkan Layers enabled and ordered: +set VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_reference_1_2_1 + :: reference layer :: ========================================== diff --git a/vkconfig_core/test/generated/vk_layer_settings.sh b/vkconfig_core/test/generated/vk_layer_settings.sh index a724062d59..1e6faa28c2 100644 --- a/vkconfig_core/test/generated/vk_layer_settings.sh +++ b/vkconfig_core/test/generated/vk_layer_settings.sh @@ -15,6 +15,10 @@ #! #! This code was generated by Vulkan Configurator 3.5.0 +#! Loader Settings: +#! Vulkan Layers enabled and ordered: +export VK_INSTANCE_LAYERS=VK_LAYER_LUNARG_reference_1_2_1 + #! reference layer #! ========================================== diff --git a/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code b/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code index f7ef21cc06..f75475ff92 100644 --- a/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code +++ b/vkconfig_core/test/generated/vulkan_hpp_layer_settings.code @@ -300,6 +300,9 @@ private: // `LayerSettings` allows initializing layer settings from Vulkan application code. struct LayerSettings { + std::vector layer_list { + "VK_LAYER_LUNARG_reference_1_2_1", + }; Reference121SettingData reference_1_2_1; // Use for VkLayerSettingsCreateInfoEXT `settingCount` and `pSettings` argument @@ -311,7 +314,15 @@ struct LayerSettings { // const VkLayerSettingEXT* pSettings; // } VkLayerSettingsCreateInfoEXT; - std::vector info() { + std::vector layers() { + std::vector results; + for (std::size_t i = 0, n = layer_list.size(); i < n; ++i) { + results.push_back(layer_list[i].c_str()); + }; + return results; + }; + + std::vector settings() { this->reference_1_2_1.init(); std::vector init{ diff --git a/vkconfig_core/test/generated/vulkan_layer_settings.code b/vkconfig_core/test/generated/vulkan_layer_settings.code index 9bb12fc903..8bd3de26b0 100644 --- a/vkconfig_core/test/generated/vulkan_layer_settings.code +++ b/vkconfig_core/test/generated/vulkan_layer_settings.code @@ -300,6 +300,9 @@ private: // `LayerSettings` allows initializing layer settings from Vulkan application code. struct LayerSettings { + std::vector layer_list { + "VK_LAYER_LUNARG_reference_1_2_1", + }; Reference121SettingData reference_1_2_1; // Use for VkLayerSettingsCreateInfoEXT `settingCount` and `pSettings` argument @@ -311,7 +314,15 @@ struct LayerSettings { // const VkLayerSettingEXT* pSettings; // } VkLayerSettingsCreateInfoEXT; - std::vector info() { + std::vector layers() { + std::vector results; + for (std::size_t i = 0, n = layer_list.size(); i < n; ++i) { + results.push_back(layer_list[i].c_str()); + }; + return results; + }; + + std::vector settings() { this->reference_1_2_1.init(); std::vector init{ diff --git a/vkconfig_core/test/test_generate_settings.cpp b/vkconfig_core/test/test_generate_settings.cpp index 559ac5a72c..ecfbc9f4e2 100644 --- a/vkconfig_core/test/test_generate_settings.cpp +++ b/vkconfig_core/test/test_generate_settings.cpp @@ -57,6 +57,15 @@ std::string Read(const Path& path) { return data.toStdString(); } +TEST(test_settings, init) { + const std::vector& settings = layer_settings.settings(); + std::vector layers = layer_settings.layers(); + + VkInstanceCreateInfo create_info; + create_info.enabledLayerCount = static_cast(layers.size()); + create_info.ppEnabledLayerNames = &layers[0]; +} + TEST(test_settings, config_generate_html) { const std::string path(std::string(CMAKE_CURRENT_SOURCE_DIR) + "/tmp"); diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index 06acf843e4..b5d3306919 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -22,6 +22,7 @@ - Move 'Validate Layer Manifest' settings to the preference tab - Improve keyboard shortcuts - Add layer display option to show only explicit layers #2636 +- Add enabled and ordered layers to generated configuration files #2634 #2565 ### Fixes: - Fix command line whitespace decoding #2625 From 88a6fa6d1463eb29619bfa30ef8afe9df23b86a1 Mon Sep 17 00:00:00 2001 From: Christophe Date: Thu, 9 Apr 2026 18:51:52 +0200 Subject: [PATCH 19/45] vkconfig: Updated doc, fixes and clean up --- vkconfig_gui/CHANGELOG.md | 1 + vkconfig_gui/README.md | 12 +- vkconfig_gui/highlighter.cpp | 64 ++++++++ vkconfig_gui/highlighter.h | 56 +++++++ .../images/1-vulkan-layers-configuration.gif | Bin 0 -> 1707853 bytes .../images/2-vulkan-layers-available.gif | Bin 0 -> 783812 bytes vkconfig_gui/images/3-vulkan-drivers.gif | Bin 0 -> 543633 bytes vkconfig_gui/mainwindow.ui | 10 +- vkconfig_gui/tab.cpp | 16 ++ vkconfig_gui/tab.h | 3 + vkconfig_gui/tab_applications.cpp | 130 +++++++++++++--- vkconfig_gui/tab_applications.h | 5 + vkconfig_gui/tab_diagnostics.cpp | 142 +++++++++++++++--- vkconfig_gui/tab_diagnostics.h | 5 + vkconfig_gui/tab_layers.cpp | 21 ++- vkconfig_gui/tab_preferences.cpp | 20 +-- vkconfig_gui/vkconfig.pro | 2 + 17 files changed, 412 insertions(+), 75 deletions(-) create mode 100644 vkconfig_gui/highlighter.cpp create mode 100644 vkconfig_gui/highlighter.h create mode 100644 vkconfig_gui/images/1-vulkan-layers-configuration.gif create mode 100644 vkconfig_gui/images/2-vulkan-layers-available.gif create mode 100644 vkconfig_gui/images/3-vulkan-drivers.gif diff --git a/vkconfig_gui/CHANGELOG.md b/vkconfig_gui/CHANGELOG.md index b5d3306919..bbc08dd8d1 100644 --- a/vkconfig_gui/CHANGELOG.md +++ b/vkconfig_gui/CHANGELOG.md @@ -23,6 +23,7 @@ - Improve keyboard shortcuts - Add layer display option to show only explicit layers #2636 - Add enabled and ordered layers to generated configuration files #2634 #2565 +- Improve "search" usability with highligher ### Fixes: - Fix command line whitespace decoding #2625 diff --git a/vkconfig_gui/README.md b/vkconfig_gui/README.md index e4af9ee31b..31e7390f13 100644 --- a/vkconfig_gui/README.md +++ b/vkconfig_gui/README.md @@ -35,25 +35,25 @@ Vulkan Configurator is designed around multiple tabs, each dedicated to specific ### Vulkan Layers Configuration tab -This tab allows configuring Vulkan layers. This includes enabling and specifying the Vulkan layers order on the Vulkan developers matching. Each layer can be configure using the Vulkan Layers settings. +This tab allows configuring Vulkan layers. This includes enabling and ordering Vulkan layers executed by the Vulkan applications. Each layer can be configure using the Vulkan Layers settings. -

+

The context menu of the layers configuration allows generating layer settings files of all types: documentation, `vk_layer_settings.txt`, environment variables scripts and `VK_EXT_layer_settings` C++ helper library. -### Vulkan Layers Paths tab +### Vulkan Layers Available tab This tab allows adding additional Vulkan layers, potentially Vulkan versions of the same Vulkan layer. -

+

### Vulkan Drivers tab This tab allows adding Vulkan drivers but also either sorting or forcing Vulkan physical devices to all Vulkan applications. -

+

-### Vulkan Applications tab +### Application Launcher tab This tab is an application launcher enabling to quickly test Vulkan system changes. diff --git a/vkconfig_gui/highlighter.cpp b/vkconfig_gui/highlighter.cpp new file mode 100644 index 0000000000..d2e5b59d7f --- /dev/null +++ b/vkconfig_gui/highlighter.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Christophe Riccio + */ + +#include "highlighter.h" + +Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) {} + +void Highlighter::setColor(const QColor &color) { + this->format.setBackground(color); + this->Update(); +} + +void Highlighter::setSearch(const QString &text) { + this->search = text; + this->Update(); +} + +void Highlighter::setCase(bool enabled) { + this->search_case = enabled; + this->Update(); +} + +void Highlighter::setWhole(bool enabled) { + this->search_whole = enabled; + this->Update(); +} + +void Highlighter::setRegex(bool enabled) { + this->search_regex = enabled; + this->Update(); +} + +void Highlighter::Update() { + this->pattern = QRegularExpression( + this->search, this->search_case ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); + this->rehighlight(); +} + +void Highlighter::highlightBlock(const QString &text) { + this->format.setFontWeight(QFont::Normal); + + QRegularExpressionMatchIterator matchIterator = this->pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + this->setFormat(match.capturedStart(), match.capturedLength(), this->format); + } +} diff --git a/vkconfig_gui/highlighter.h b/vkconfig_gui/highlighter.h new file mode 100644 index 0000000000..f96a22fdc6 --- /dev/null +++ b/vkconfig_gui/highlighter.h @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2026 Valve Corporation + * Copyright (c) 2020-2026 LunarG, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Authors: + * - Christophe Riccio + */ + +#pragma once + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +class Highlighter : public QSyntaxHighlighter { + Q_OBJECT + + public: + Highlighter(QTextDocument *parent = nullptr); + + void setColor(const QColor &color); + void setSearch(const QString &text); + void setCase(bool enabled); + void setWhole(bool enabled); + void setRegex(bool enabled); + + protected: + void highlightBlock(const QString &text) override; + + private: + void Update(); + + QString search; + bool search_case = false; + bool search_whole = false; + bool search_regex = false; + + QRegularExpression pattern; + QTextCharFormat format; +}; diff --git a/vkconfig_gui/images/1-vulkan-layers-configuration.gif b/vkconfig_gui/images/1-vulkan-layers-configuration.gif new file mode 100644 index 0000000000000000000000000000000000000000..654d635a22ab4a61bd8bb974cedf3e05f68c29fb GIT binary patch literal 1707853 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

-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%0Dd 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