From 1b99c301b1d4cce96faf3380f783e6cb7da6bea7 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sat, 31 May 2025 12:27:51 +1000 Subject: [PATCH 01/22] Full Icon framework & support. --- .../Higher Res Icons (256x)/clear-cache.png | Bin 0 -> 10627 bytes .../Higher Res Icons (256x)/clear-disk.png | Bin 0 -> 5630 bytes .../Higher Res Icons (256x)/cs-logo.png | Bin 0 -> 19104 bytes .../Higher Res Icons (256x)/load-settings.png | Bin 0 -> 7440 bytes .../Higher Res Icons (256x)/save-settings.png | Bin 0 -> 6087 bytes .../CommunityShaders/Icons/clear-cache.png | Bin 0 -> 4162 bytes .../CommunityShaders/Icons/clear-disk.png | Bin 0 -> 2743 bytes .../CommunityShaders/Icons/cs-logo(white).png | Bin 0 -> 17301 bytes .../CommunityShaders/Icons/cs-logo.png | Bin 0 -> 8715 bytes .../CommunityShaders/Icons/load-settings.png | Bin 0 -> 3495 bytes .../CommunityShaders/Icons/save-settings.png | Bin 0 -> 2904 bytes src/Menu.cpp | 310 +++++++++++++----- src/Menu.h | 22 +- src/UIIconLoader.cpp | 74 +++++ src/UIIconLoader.h | 16 + src/UITextHelper.h | 0 vcpkg.json | 1 + 17 files changed, 348 insertions(+), 75 deletions(-) create mode 100644 package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-cache.png create mode 100644 package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-disk.png create mode 100644 package/Interface/CommunityShaders/Higher Res Icons (256x)/cs-logo.png create mode 100644 package/Interface/CommunityShaders/Higher Res Icons (256x)/load-settings.png create mode 100644 package/Interface/CommunityShaders/Higher Res Icons (256x)/save-settings.png create mode 100644 package/Interface/CommunityShaders/Icons/clear-cache.png create mode 100644 package/Interface/CommunityShaders/Icons/clear-disk.png create mode 100644 package/Interface/CommunityShaders/Icons/cs-logo(white).png create mode 100644 package/Interface/CommunityShaders/Icons/cs-logo.png create mode 100644 package/Interface/CommunityShaders/Icons/load-settings.png create mode 100644 package/Interface/CommunityShaders/Icons/save-settings.png create mode 100644 src/UIIconLoader.cpp create mode 100644 src/UIIconLoader.h create mode 100644 src/UITextHelper.h diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-cache.png b/package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-cache.png new file mode 100644 index 0000000000000000000000000000000000000000..f21dc6816dac55887d7c84ba1fa3c434fff70e0f GIT binary patch literal 10627 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 literal 0 HcmV?d00001 diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/save-settings.png b/package/Interface/CommunityShaders/Higher Res Icons (256x)/save-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..b013f1d74be669a8beef4ad7f01fad855635adb2 GIT binary patch literal 6087 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 literal 0 HcmV?d00001 diff --git a/package/Interface/CommunityShaders/Icons/cs-logo(white).png b/package/Interface/CommunityShaders/Icons/cs-logo(white).png new file mode 100644 index 0000000000000000000000000000000000000000..f56ea21a3df04cbdfa99778ac6f11b9eba6ed5c8 GIT binary patch literal 17301 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 R;CD7)&@l(AXBNKK{vS#ORXhLy literal 0 HcmV?d00001 diff --git a/package/Interface/CommunityShaders/Icons/cs-logo.png b/package/Interface/CommunityShaders/Icons/cs-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4d243390215ddb0b5abe594e314769b6278440 GIT binary patch literal 8715 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 QeBA-Mp?e!mx{eF|ACWwDn*aa+ literal 0 HcmV?d00001 diff --git a/package/Interface/CommunityShaders/Icons/load-settings.png b/package/Interface/CommunityShaders/Icons/load-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb2d32e181e25b172aeee6210c79c4a3864636e GIT binary patch literal 3495 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 literal 0 HcmV?d00001 diff --git a/src/Menu.cpp b/src/Menu.cpp index 9c4d2c250d..c2632b2a9a 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -16,6 +16,8 @@ #include "Streamline.h" #include "TruePBR.h" #include "Upscaling.h" +#include "UIIconLoader.h" +#include "UITextHelper.h" #include "Features/LightLimitFix/ParticleLights.h" @@ -73,6 +75,7 @@ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT( Menu::ThemeSettings, GlobalScale, UseSimplePalette, + ShowActionIcons, Palette, Style, FullPalette) @@ -181,7 +184,13 @@ void Menu::SetupImGuiStyle() const bool IsEnabled = false; Menu::~Menu() -{ +{ // Release icon textures if loaded + uiIcons.saveSettings.Release(); + uiIcons.loadSettings.Release(); + uiIcons.clearCache.Release(); + uiIcons.clearDiskCache.Release(); + uiIcons.logo.Release(); + ImGui_ImplDX11_Shutdown(); ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(); @@ -206,13 +215,18 @@ void Menu::Init() IMGUI_CHECKVERSION(); ImGui::CreateContext(); auto& imgui_io = ImGui::GetIO(); - imgui_io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable; imgui_io.BackendFlags = ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_RendererHasVtxOffset; - + + // Enhanced font configuration for sharper text rendering ImFontConfig font_config; - font_config.GlyphExtraSpacing.x = -0.5; - + font_config.GlyphExtraSpacing.x = 0.0f; // Neutral spacing for cleaner look + font_config.OversampleV = 2; // Increased vertical oversampling + font_config.PixelSnapH = true; // Align to pixel grid for sharper rendering + font_config.RasterizerMultiply = 1.1f; // Slightly darker font rendering + font_config.FontBuilderFlags = 0; // No additional flags needed + + // Add high-quality font with improved settings imgui_io.Fonts->AddFontFromFileTTF("Data\\Interface\\CommunityShaders\\Fonts\\Jost-Regular.ttf", 36, &font_config); DXGI_SWAP_CHAIN_DESC desc; @@ -234,6 +248,10 @@ void Menu::Init() } } } + // Load UI icons + if (!UIIconLoader::InitializeMenuIcons(this)) { + logger::warn("Failed to load UI icons. Will fallback to text buttons"); + } initialized = true; } @@ -246,77 +264,209 @@ void Menu::DrawSettings() ImGui::SetNextWindowSize(Util::GetNativeViewportSizeScaled(0.8f), ImGuiCond_FirstUseEver); auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); - ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); { - if (!ImGui::IsWindowDocked()) { - ImGui::SetWindowFontScale(1.5f); - ImGui::TextUnformatted(title.c_str()); - ImGui::SetWindowFontScale(1.f); - - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); - } - auto shaderCache = globals::shaderCache; - - if (ImGui::BeginTable("##LeButtons", 4, ImGuiTableFlags_SizingStretchSame)) { - ImGui::TableNextColumn(); - if (ImGui::Button("Save Settings", { -1, 0 })) { - globals::state->Save(); - } - - ImGui::TableNextColumn(); - if (ImGui::Button("Load Settings", { -1, 0 })) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - - ImGui::TableNextColumn(); - if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { - shaderCache->Clear(); - // any features should be added to shadercache's clear. - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); - } - - ImGui::TableNextColumn(); - if (ImGui::Button("Clear Disk Cache", { -1, 0 })) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); - } - - if (shaderCache->GetFailedTasks()) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Button("Toggle Error Message", { -1, 0 })) { - shaderCache->ToggleErrorMessages(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Hide or show the shader failure message. " - "Your installation is broken and will likely see errors in game. " - "Please double check you have updated all features and that your load order is correct. " - "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); - } - } - ImGui::EndTable(); - } - - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); + const float iconSize = 48.0f; + const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons + + // Begin a layout with title on the left and buttons on the right + if (ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Buttons", ImGuiTableColumnFlags_WidthFixed); ImGui::TableNextColumn(); // Title on the left with logo + if (!ImGui::IsWindowDocked()) { + // Calculate logo size with proper scaling + const float textScaleFactor = 1.5f; + const float logoHeightScale = 1.25f; + const float titleHeight = ImGui::GetFontSize() * logoHeightScale; + + // Always display logo if texture is available + if (uiIcons.logo.texture) { + float logoAspectRatio = uiIcons.logo.size.x / uiIcons.logo.size.y; + ImVec2 logoSize(titleHeight * logoAspectRatio, titleHeight); + + // Add a bit of padding before the logo and text + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); + + // Use our helper to render aligned logo and text with perfect vertical alignment + UITextHelper::RenderAlignedTextWithLogo( + uiIcons.logo.texture, + logoSize, + title.c_str(), + textScaleFactor + ); + } else { + // No logo, just render the text with proper alignment + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); + UITextHelper::RenderSharpText(title.c_str(), true, textScaleFactor); + ImGui::PopStyleVar(); + } + } // Buttons on the right + ImGui::TableNextColumn(); + + // Only show icon buttons in the header if setting is enabled and all icon textures are available + bool canShowIcons = settings.Theme.ShowActionIcons && + uiIcons.saveSettings.texture && + uiIcons.loadSettings.texture && + uiIcons.clearCache.texture && + uiIcons.clearDiskCache.texture; + + if (canShowIcons) { + // Create a horizontal layout for the buttons and remove button borders + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); // Transparent button background + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f,0.7f,0.7f,0.2f)); // Subtle hover effect + + // Save Settings Button + if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { + globals::state->Save(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Save Settings"); + } + ImGui::SameLine(); + + // Load Settings Button + if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Load Settings"); + } + ImGui::SameLine(); + + // Clear Shader Cache Button + if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { + shaderCache->Clear(); + // any features should be added to shadercache's clear. + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Clear Shader Cache\n\n" + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + ImGui::SameLine(); + + // Clear Disk Cache Button + if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Clear Disk Cache\n\n" + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + + // Restore default style + ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize + ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered + } + + ImGui::EndTable(); + } + // First separator - always shown + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + + // If icons are disabled, show action buttons as text between separators + if (!settings.Theme.ShowActionIcons) { + if (ImGui::BeginTable("##ActionButtons", 4, ImGuiTableFlags_SizingStretchSame)) { + // Save Settings Button + ImGui::TableNextColumn(); + if (ImGui::Button("Save Settings", { -1, 0 })) { + globals::state->Save(); + } + + // Load Settings Button + ImGui::TableNextColumn(); + if (ImGui::Button("Load Settings", { -1, 0 })) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + + // Clear Shader Cache Button + ImGui::TableNextColumn(); + if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { + shaderCache->Clear(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + + // Clear Disk Cache Button + ImGui::TableNextColumn(); + if (ImGui::Button("Clear Disk Cache", { -1, 0 })) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + + // Error message toggle if needed + if (shaderCache->GetFailedTasks()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Toggle Error Message", { -1, 0 })) { + shaderCache->ToggleErrorMessages(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Hide or show the shader failure message. " + "Your installation is broken and will likely see errors in game. " + "Please double check you have updated all features and that your load order is correct. " + "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); + } + } + + ImGui::EndTable(); + } + + // Second separator - only shown if icons are disabled or if there are failed tasks + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + } else if (shaderCache->GetFailedTasks()) { + // If icons are enabled but there are failed tasks, show error toggle button + // and add the second separator + if (ImGui::Button("Toggle Error Message", { -1, 0 })) { + shaderCache->ToggleErrorMessages(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Hide or show the shader failure message. " + "Your installation is broken and will likely see errors in game. " + "Please double check you have updated all features and that your load order is correct. " + "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); + } + + // Add second separator when showing error button + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + } else { // No additional separator needed - already handled in the conditional block above + } + + // Main content starts here - no additional separator needed as it's already handled in the conditions above float footer_height = ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().ItemSpacing.y * 3 + 3.0f; // text + separator @@ -655,13 +805,25 @@ void Menu::DrawGeneralSettings() auto& colors = themeSettings.FullPalette; if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { - if (ImGui::BeginTabItem("Sizes")) { + if (ImGui::BeginTabItem("UI Options")) { if (ImGui::SliderFloat("Global Scale", &themeSettings.GlobalScale, -1.f, 1.f, "%.2f")) { float trueScale = exp2(themeSettings.GlobalScale); auto& io = ImGui::GetIO(); io.FontGlobalScale = trueScale; } + + ImGui::SeparatorText("UI Elements"); + ImGui::Checkbox("Use Icon Buttons in Header", &themeSettings.ShowActionIcons); + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("When enabled: Shows action buttons (Save, Load, Clear Cache, Clear Disk Cache) as icons in the header\n" + "When disabled: Shows as text buttons below the header"); + } + + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Sizes")) { ImGui::SeparatorText("Main"); ImGui::SliderFloat2("Window Padding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); diff --git a/src/Menu.h b/src/Menu.h index 8941f67741..26d15ea4c9 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -43,12 +43,32 @@ class Menu void ProcessInputEvents(RE::InputEvent* const* a_events); bool ShouldSwallowInput(); void OnFocusLost(); - + // UI icon textures + struct UIIcon { + ID3D11ShaderResourceView* texture = nullptr; + ImVec2 size = ImVec2(32.0f, 32.0f); + + void Release() { + if (texture) { + texture->Release(); + texture = nullptr; + } + } + }; + struct UIIcons { + UIIcon saveSettings; + UIIcon loadSettings; + UIIcon clearCache; + UIIcon clearDiskCache; + UIIcon logo; // New logo icon + } uiIcons; + struct ThemeSettings { float GlobalScale = REL::Module::IsVR() ? -0.5f : 0.f; // exponential bool UseSimplePalette = true; // simple palette or full customization + bool ShowActionIcons = true; // whether to show action buttons as icons struct PaletteColors { ImVec4 Background{ 0.f, 0.f, 0.f, 0.5882353186607361f }; diff --git a/src/UIIconLoader.cpp b/src/UIIconLoader.cpp new file mode 100644 index 0000000000..0f1ebf3734 --- /dev/null +++ b/src/UIIconLoader.cpp @@ -0,0 +1,74 @@ +// https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples +// https://github.com/microsoft/fluentui-system-icons + +#include "UIIconLoader.h" +#include "Globals.h" +#include "Menu.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +namespace UIIconLoader +{bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size) + { + // Load from disk into a raw RGBA buffer + int image_width = 0; + int image_height = 0; + int channels_in_file; + unsigned char* image_data = stbi_load(filename, &image_width, &image_height, &channels_in_file, 4); + if (image_data == NULL) + return false; + + // Create texture + D3D11_TEXTURE2D_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = image_width; + desc.Height = image_height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + + ID3D11Texture2D* pTexture = NULL; + D3D11_SUBRESOURCE_DATA subResource; + subResource.pSysMem = image_data; + subResource.SysMemPitch = desc.Width * 4; + subResource.SysMemSlicePitch = 0; + globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); + + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); + pTexture->Release(); + + out_size = ImVec2((float)image_width, (float)image_height); + stbi_image_free(image_data); + + return true; + } bool InitializeMenuIcons(Menu* menu) + { + if (!menu) { + return false; + } + + bool success = true; + // Define path to icons + std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; + + // Load all required icons + success &= LoadTextureFromFile((basePath + "save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size); + success &= LoadTextureFromFile((basePath + "load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size); + success &= LoadTextureFromFile((basePath + "clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size); + success &= LoadTextureFromFile((basePath + "clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size); + success &= LoadTextureFromFile((basePath + "cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size); + + return success; + } +} diff --git a/src/UIIconLoader.h b/src/UIIconLoader.h new file mode 100644 index 0000000000..d98abcfd71 --- /dev/null +++ b/src/UIIconLoader.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +// Forward declarations +class Menu; + +namespace UIIconLoader +{ + // Load a texture from a file path + bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size); + + // Initialize the icons for the Menu class + bool InitializeMenuIcons(Menu* menu); +} diff --git a/src/UITextHelper.h b/src/UITextHelper.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vcpkg.json b/vcpkg.json index aaef2c7c4a..a4d6ddbbcd 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -23,6 +23,7 @@ "magic-enum", "nlohmann-json", "pystring", + "stb", "tracy", "unordered-dense", "xbyak" From 3507e94367aa8b71f461ac300563866cb60ed409 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sat, 31 May 2025 12:41:42 +1000 Subject: [PATCH 02/22] UI Text helper function for better looking large text Function for crispier large text like titles. Helps subtly but does make a difference, especially when high-res icons are visible near text. --- src/UITextHelper.h | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/UITextHelper.h b/src/UITextHelper.h index e69de29bb2..98ee75bcb0 100644 --- a/src/UITextHelper.h +++ b/src/UITextHelper.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +namespace UITextHelper +{ + // Helper function to render sharper text by ensuring pixel-perfect alignment + inline void RenderSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f) + { + + if (alignToPixelGrid) + { + // Get current position + ImVec2 pos = ImGui::GetCursorPos(); + + // Align to pixel grid for sharper rendering + pos.x = std::round(pos.x); + pos.y = std::round(pos.y); + + // Set aligned position + ImGui::SetCursorPos(pos); + } + + // Apply scale if needed + if (scale != 1.0f) + ImGui::SetWindowFontScale(scale); + + // Use Text instead of TextUnformatted for better rendering + ImGui::Text("%s", text); + + // Restore scale if needed + if (scale != 1.0f) + ImGui::SetWindowFontScale(1.0f); + } + + // Helper function to render aligned text and logo + inline void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = 1.5f) + { + // Save current cursor position + ImVec2 startPos = ImGui::GetCursorPos(); + + // Calculate scaled text height + float fontHeight = ImGui::GetFontSize() * textScale; + float logoHeight = logoSize.y; + + // Calculate vertical offset to center align logo with text + float verticalOffset = (fontHeight - logoHeight) * 0.5f; + + // Position cursor for logo with vertical alignment + ImGui::SetCursorPos(ImVec2(startPos.x, startPos.y + verticalOffset)); + + // Render logo + ImGui::Image(logoTexture, logoSize); + ImGui::SameLine(); + + // Reset cursor for text with proper vertical alignment + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), startPos.y)); + + // Use windowed font scale for sharper text + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::SetWindowFontScale(textScale); + + // Render text aligned to pixel grid for sharpness + ImGui::Text("%s", text); + + // Restore style + ImGui::SetWindowFontScale(1.0f); + ImGui::PopStyleVar(); + } +} From 35fcac91ebdd47862323fc861ff5b1dd70dc10ef Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sat, 31 May 2025 12:41:49 +1000 Subject: [PATCH 03/22] Update Menu.cpp --- src/Menu.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Menu.cpp b/src/Menu.cpp index c2632b2a9a..fc38f709ae 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -221,6 +221,7 @@ void Menu::Init() // Enhanced font configuration for sharper text rendering ImFontConfig font_config; font_config.GlyphExtraSpacing.x = 0.0f; // Neutral spacing for cleaner look + font_config.OversampleH = 3; // Increased horizontal oversampling for sharper text font_config.OversampleV = 2; // Increased vertical oversampling font_config.PixelSnapH = true; // Align to pixel grid for sharper rendering font_config.RasterizerMultiply = 1.1f; // Slightly darker font rendering From 5ee980cab8b17ca93fdcbda51fc7e57d10172edf Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sat, 31 May 2025 12:49:59 +1000 Subject: [PATCH 04/22] Update src/UIIconLoader.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/UIIconLoader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/UIIconLoader.cpp b/src/UIIconLoader.cpp index 0f1ebf3734..fac05ee90c 100644 --- a/src/UIIconLoader.cpp +++ b/src/UIIconLoader.cpp @@ -41,7 +41,8 @@ namespace UIIconLoader // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; From ab879dfa6ddb964abbdc1415261be531aab840c5 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sat, 31 May 2025 12:51:27 +1000 Subject: [PATCH 05/22] Update src/UIIconLoader.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/UIIconLoader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/UIIconLoader.cpp b/src/UIIconLoader.cpp index fac05ee90c..65e38c289f 100644 --- a/src/UIIconLoader.cpp +++ b/src/UIIconLoader.cpp @@ -53,7 +53,10 @@ namespace UIIconLoader stbi_image_free(image_data); return true; - } bool InitializeMenuIcons(Menu* menu) + return true; + } + + bool InitializeMenuIcons(Menu* menu) { if (!menu) { return false; From ab7d837af5de55f01e012297469b10012ddcf670 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 1 Jun 2025 12:44:03 +1000 Subject: [PATCH 06/22] Icon folder changes, fix for compile issue. --- .../Higher Res Icons (256x)/clear-cache.png | Bin .../Higher Res Icons (256x)/clear-disk.png | Bin .../Higher Res Icons (256x)/cs-logo.png | Bin .../Higher Res Icons (256x)/load-settings.png | Bin .../Higher Res Icons (256x)/save-settings.png | Bin .../Icons/{ => Unused}/cs-logo(white).png | Bin src/UIIconLoader.cpp | 1 - 7 files changed, 1 deletion(-) rename package/Interface/CommunityShaders/{ => Icons/Unused/Microsoft Colour FluentUI}/Higher Res Icons (256x)/clear-cache.png (100%) rename package/Interface/CommunityShaders/{ => Icons/Unused/Microsoft Colour FluentUI}/Higher Res Icons (256x)/clear-disk.png (100%) rename package/Interface/CommunityShaders/{ => Icons/Unused/Microsoft Colour FluentUI}/Higher Res Icons (256x)/cs-logo.png (100%) rename package/Interface/CommunityShaders/{ => Icons/Unused/Microsoft Colour FluentUI}/Higher Res Icons (256x)/load-settings.png (100%) rename package/Interface/CommunityShaders/{ => Icons/Unused/Microsoft Colour FluentUI}/Higher Res Icons (256x)/save-settings.png (100%) rename package/Interface/CommunityShaders/Icons/{ => Unused}/cs-logo(white).png (100%) diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-cache.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-cache.png similarity index 100% rename from package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-cache.png rename to package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-cache.png diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-disk.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-disk.png similarity index 100% rename from package/Interface/CommunityShaders/Higher Res Icons (256x)/clear-disk.png rename to package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-disk.png diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/cs-logo.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/cs-logo.png similarity index 100% rename from package/Interface/CommunityShaders/Higher Res Icons (256x)/cs-logo.png rename to package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/cs-logo.png diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/load-settings.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/load-settings.png similarity index 100% rename from package/Interface/CommunityShaders/Higher Res Icons (256x)/load-settings.png rename to package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/load-settings.png diff --git a/package/Interface/CommunityShaders/Higher Res Icons (256x)/save-settings.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/save-settings.png similarity index 100% rename from package/Interface/CommunityShaders/Higher Res Icons (256x)/save-settings.png rename to package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/save-settings.png diff --git a/package/Interface/CommunityShaders/Icons/cs-logo(white).png b/package/Interface/CommunityShaders/Icons/Unused/cs-logo(white).png similarity index 100% rename from package/Interface/CommunityShaders/Icons/cs-logo(white).png rename to package/Interface/CommunityShaders/Icons/Unused/cs-logo(white).png diff --git a/src/UIIconLoader.cpp b/src/UIIconLoader.cpp index 65e38c289f..26a288542c 100644 --- a/src/UIIconLoader.cpp +++ b/src/UIIconLoader.cpp @@ -53,7 +53,6 @@ namespace UIIconLoader stbi_image_free(image_data); return true; - return true; } bool InitializeMenuIcons(Menu* menu) From c2747f43b7246ed4b3b89ee7ee671f4fedd5b1fb Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 15 Jun 2025 21:12:33 +1000 Subject: [PATCH 07/22] Reorganise --- README.md | 9 ++ .../{ => Community Shaders Logo}/cs-logo.png | Bin .../CommunityShaders/Icons/LICENSE.txt | 21 +++ .../Icons/Microsoft Icons/LICENSE | 21 +++ .../{ => Microsoft Icons}/clear-cache.png | Bin .../{ => Microsoft Icons}/clear-disk.png | Bin .../{ => Microsoft Icons}/load-settings.png | Bin .../{ => Microsoft Icons}/save-settings.png | Bin .../Higher Res Icons (256x)/clear-cache.png | Bin 10627 -> 0 bytes .../Higher Res Icons (256x)/clear-disk.png | Bin 5630 -> 0 bytes .../Higher Res Icons (256x)/cs-logo.png | Bin 19104 -> 0 bytes .../Higher Res Icons (256x)/load-settings.png | Bin 7440 -> 0 bytes .../Higher Res Icons (256x)/save-settings.png | Bin 6087 -> 0 bytes .../Icons/Unused/cs-logo(white).png | Bin 17301 -> 0 bytes src/Menu.cpp | 9 +- src/UIIconLoader.cpp | 77 ---------- src/UIIconLoader.h | 16 --- src/UITextHelper.h | 71 ---------- src/Utils/UI.cpp | 134 ++++++++++++++++++ src/Utils/UI.h | 15 ++ 20 files changed, 204 insertions(+), 169 deletions(-) rename package/Interface/CommunityShaders/Icons/{ => Community Shaders Logo}/cs-logo.png (100%) create mode 100644 package/Interface/CommunityShaders/Icons/LICENSE.txt create mode 100644 package/Interface/CommunityShaders/Icons/Microsoft Icons/LICENSE rename package/Interface/CommunityShaders/Icons/{ => Microsoft Icons}/clear-cache.png (100%) rename package/Interface/CommunityShaders/Icons/{ => Microsoft Icons}/clear-disk.png (100%) rename package/Interface/CommunityShaders/Icons/{ => Microsoft Icons}/load-settings.png (100%) rename package/Interface/CommunityShaders/Icons/{ => Microsoft Icons}/save-settings.png (100%) delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-cache.png delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-disk.png delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/cs-logo.png delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/load-settings.png delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/save-settings.png delete mode 100644 package/Interface/CommunityShaders/Icons/Unused/cs-logo(white).png delete mode 100644 src/UIIconLoader.cpp delete mode 100644 src/UIIconLoader.h delete mode 100644 src/UITextHelper.h diff --git a/README.md b/README.md index 3fbc0ad5b9..8a403c5d88 100644 --- a/README.md +++ b/README.md @@ -131,3 +131,12 @@ See LICENSE within each directory; if none, it's [Default](#default) - [Features Shaders](features) - [Package Shaders](package/Shaders/) + +### Community + +### Icons + +- [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) +- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. + +### Community \ No newline at end of file diff --git a/package/Interface/CommunityShaders/Icons/cs-logo.png b/package/Interface/CommunityShaders/Icons/Community Shaders Logo/cs-logo.png similarity index 100% rename from package/Interface/CommunityShaders/Icons/cs-logo.png rename to package/Interface/CommunityShaders/Icons/Community Shaders Logo/cs-logo.png diff --git a/package/Interface/CommunityShaders/Icons/LICENSE.txt b/package/Interface/CommunityShaders/Icons/LICENSE.txt new file mode 100644 index 0000000000..bc9c36b28f --- /dev/null +++ b/package/Interface/CommunityShaders/Icons/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/Interface/CommunityShaders/Icons/Microsoft Icons/LICENSE b/package/Interface/CommunityShaders/Icons/Microsoft Icons/LICENSE new file mode 100644 index 0000000000..bc9c36b28f --- /dev/null +++ b/package/Interface/CommunityShaders/Icons/Microsoft Icons/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/package/Interface/CommunityShaders/Icons/clear-cache.png b/package/Interface/CommunityShaders/Icons/Microsoft Icons/clear-cache.png similarity index 100% rename from package/Interface/CommunityShaders/Icons/clear-cache.png rename to package/Interface/CommunityShaders/Icons/Microsoft Icons/clear-cache.png diff --git a/package/Interface/CommunityShaders/Icons/clear-disk.png b/package/Interface/CommunityShaders/Icons/Microsoft Icons/clear-disk.png similarity index 100% rename from package/Interface/CommunityShaders/Icons/clear-disk.png rename to package/Interface/CommunityShaders/Icons/Microsoft Icons/clear-disk.png diff --git a/package/Interface/CommunityShaders/Icons/load-settings.png b/package/Interface/CommunityShaders/Icons/Microsoft Icons/load-settings.png similarity index 100% rename from package/Interface/CommunityShaders/Icons/load-settings.png rename to package/Interface/CommunityShaders/Icons/Microsoft Icons/load-settings.png diff --git a/package/Interface/CommunityShaders/Icons/save-settings.png b/package/Interface/CommunityShaders/Icons/Microsoft Icons/save-settings.png similarity index 100% rename from package/Interface/CommunityShaders/Icons/save-settings.png rename to package/Interface/CommunityShaders/Icons/Microsoft Icons/save-settings.png diff --git a/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-cache.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/clear-cache.png deleted file mode 100644 index f21dc6816dac55887d7c84ba1fa3c434fff70e0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10627 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 diff --git a/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/save-settings.png b/package/Interface/CommunityShaders/Icons/Unused/Microsoft Colour FluentUI/Higher Res Icons (256x)/save-settings.png deleted file mode 100644 index b013f1d74be669a8beef4ad7f01fad855635adb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6087 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 R;CD7)&@l(AXBNKK{vS#ORXhLy diff --git a/src/Menu.cpp b/src/Menu.cpp index fc38f709ae..7c00b814e0 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -16,8 +16,7 @@ #include "Streamline.h" #include "TruePBR.h" #include "Upscaling.h" -#include "UIIconLoader.h" -#include "UITextHelper.h" +#include "Utils/UI.h" #include "Features/LightLimitFix/ParticleLights.h" @@ -250,7 +249,7 @@ void Menu::Init() } } // Load UI icons - if (!UIIconLoader::InitializeMenuIcons(this)) { + if (!Util::InitializeMenuIcons(this)) { logger::warn("Failed to load UI icons. Will fallback to text buttons"); } @@ -290,7 +289,7 @@ void Menu::DrawSettings() ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); // Use our helper to render aligned logo and text with perfect vertical alignment - UITextHelper::RenderAlignedTextWithLogo( + Util::RenderAlignedTextWithLogo( uiIcons.logo.texture, logoSize, title.c_str(), @@ -300,7 +299,7 @@ void Menu::DrawSettings() // No logo, just render the text with proper alignment ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); - UITextHelper::RenderSharpText(title.c_str(), true, textScaleFactor); + Util::RenderSharpText(title.c_str(), true, textScaleFactor); ImGui::PopStyleVar(); } } // Buttons on the right diff --git a/src/UIIconLoader.cpp b/src/UIIconLoader.cpp deleted file mode 100644 index 26a288542c..0000000000 --- a/src/UIIconLoader.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples -// https://github.com/microsoft/fluentui-system-icons - -#include "UIIconLoader.h" -#include "Globals.h" -#include "Menu.h" - -#define STB_IMAGE_IMPLEMENTATION -#include - -namespace UIIconLoader -{bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size) - { - // Load from disk into a raw RGBA buffer - int image_width = 0; - int image_height = 0; - int channels_in_file; - unsigned char* image_data = stbi_load(filename, &image_width, &image_height, &channels_in_file, 4); - if (image_data == NULL) - return false; - - // Create texture - D3D11_TEXTURE2D_DESC desc; - ZeroMemory(&desc, sizeof(desc)); - desc.Width = image_width; - desc.Height = image_height; - desc.MipLevels = 1; - desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - desc.SampleDesc.Count = 1; - desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - desc.CPUAccessFlags = 0; - - ID3D11Texture2D* pTexture = NULL; - D3D11_SUBRESOURCE_DATA subResource; - subResource.pSysMem = image_data; - subResource.SysMemPitch = desc.Width * 4; - subResource.SysMemSlicePitch = 0; - globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); - - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; - srvDesc.Texture2D.MostDetailedMip = 0; - globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); - pTexture->Release(); - - out_size = ImVec2((float)image_width, (float)image_height); - stbi_image_free(image_data); - - return true; - } - - bool InitializeMenuIcons(Menu* menu) - { - if (!menu) { - return false; - } - - bool success = true; - // Define path to icons - std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; - - // Load all required icons - success &= LoadTextureFromFile((basePath + "save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size); - success &= LoadTextureFromFile((basePath + "load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size); - success &= LoadTextureFromFile((basePath + "clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size); - success &= LoadTextureFromFile((basePath + "clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size); - success &= LoadTextureFromFile((basePath + "cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size); - - return success; - } -} diff --git a/src/UIIconLoader.h b/src/UIIconLoader.h deleted file mode 100644 index d98abcfd71..0000000000 --- a/src/UIIconLoader.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -// Forward declarations -class Menu; - -namespace UIIconLoader -{ - // Load a texture from a file path - bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size); - - // Initialize the icons for the Menu class - bool InitializeMenuIcons(Menu* menu); -} diff --git a/src/UITextHelper.h b/src/UITextHelper.h deleted file mode 100644 index 98ee75bcb0..0000000000 --- a/src/UITextHelper.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include - -namespace UITextHelper -{ - // Helper function to render sharper text by ensuring pixel-perfect alignment - inline void RenderSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f) - { - - if (alignToPixelGrid) - { - // Get current position - ImVec2 pos = ImGui::GetCursorPos(); - - // Align to pixel grid for sharper rendering - pos.x = std::round(pos.x); - pos.y = std::round(pos.y); - - // Set aligned position - ImGui::SetCursorPos(pos); - } - - // Apply scale if needed - if (scale != 1.0f) - ImGui::SetWindowFontScale(scale); - - // Use Text instead of TextUnformatted for better rendering - ImGui::Text("%s", text); - - // Restore scale if needed - if (scale != 1.0f) - ImGui::SetWindowFontScale(1.0f); - } - - // Helper function to render aligned text and logo - inline void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = 1.5f) - { - // Save current cursor position - ImVec2 startPos = ImGui::GetCursorPos(); - - // Calculate scaled text height - float fontHeight = ImGui::GetFontSize() * textScale; - float logoHeight = logoSize.y; - - // Calculate vertical offset to center align logo with text - float verticalOffset = (fontHeight - logoHeight) * 0.5f; - - // Position cursor for logo with vertical alignment - ImGui::SetCursorPos(ImVec2(startPos.x, startPos.y + verticalOffset)); - - // Render logo - ImGui::Image(logoTexture, logoSize); - ImGui::SameLine(); - - // Reset cursor for text with proper vertical alignment - ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), startPos.y)); - - // Use windowed font scale for sharper text - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::SetWindowFontScale(textScale); - - // Render text aligned to pixel grid for sharpness - ImGui::Text("%s", text); - - // Restore style - ImGui::SetWindowFontScale(1.0f); - ImGui::PopStyleVar(); - } -} diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 485da2a4eb..d5bf43613a 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -1,4 +1,10 @@ #include "UI.h" +#include "../Globals.h" +#include "../Menu.h" + +#define STB_IMAGE_IMPLEMENTATION +#include +#include namespace Util { @@ -44,4 +50,132 @@ namespace Util const auto Size = ImGui::GetMainViewport()->Size; return { Size.x * scale, Size.y * scale }; } + + // Icon loading functions (moved from UIIconLoader) + bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size) + { + // Load from disk into a raw RGBA buffer + int image_width = 0; + int image_height = 0; + int channels_in_file; + unsigned char* image_data = stbi_load(filename, &image_width, &image_height, &channels_in_file, 4); + if (image_data == NULL) + return false; + + // Create texture + D3D11_TEXTURE2D_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.Width = image_width; + desc.Height = image_height; + desc.MipLevels = 1; + desc.ArraySize = 1; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + desc.SampleDesc.Count = 1; + desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.CPUAccessFlags = 0; + + ID3D11Texture2D* pTexture = NULL; + D3D11_SUBRESOURCE_DATA subResource; + subResource.pSysMem = image_data; + subResource.SysMemPitch = desc.Width * 4; + subResource.SysMemSlicePitch = 0; + globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); + + // Create texture view + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; + ZeroMemory(&srvDesc, sizeof(srvDesc)); + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MostDetailedMip = 0; + globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); + pTexture->Release(); + + out_size = ImVec2((float)image_width, (float)image_height); + stbi_image_free(image_data); + + return true; + } + + bool InitializeMenuIcons(Menu* menu) + { + if (!menu) { + return false; + } + + bool success = true; + // Define path to icons + std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; + + // Load all required icons + success &= LoadTextureFromFile((basePath + "Microsoft Icons\\save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size); + success &= LoadTextureFromFile((basePath + "Microsoft Icons\\load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size); + success &= LoadTextureFromFile((basePath + "Microsoft Icons\\clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size); + success &= LoadTextureFromFile((basePath + "Microsoft Icons\\clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size); + success &= LoadTextureFromFile((basePath + "Community Shaders Logo\\cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size); + + return success; + } + + // Text rendering helpers (moved from UITextHelper) + void RenderSharpText(const char* text, bool alignToPixelGrid, float scale) + { + if (alignToPixelGrid) { + // Get current position + ImVec2 pos = ImGui::GetCursorPos(); + + // Align to pixel grid for sharper rendering + pos.x = std::round(pos.x); + pos.y = std::round(pos.y); + + // Set aligned position + ImGui::SetCursorPos(pos); + } + + // Apply scale if needed + if (scale != 1.0f) + ImGui::SetWindowFontScale(scale); + + // Use Text instead of TextUnformatted for better rendering + ImGui::Text("%s", text); + + // Restore scale if needed + if (scale != 1.0f) + ImGui::SetWindowFontScale(1.0f); + } + + void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale) + { + // Save current cursor position + ImVec2 startPos = ImGui::GetCursorPos(); + + // Calculate scaled text height + float fontHeight = ImGui::GetFontSize() * textScale; + float logoHeight = logoSize.y; + + // Calculate vertical offset to center align logo with text + float verticalOffset = (fontHeight - logoHeight) * 0.5f; + + // Position cursor for logo with vertical alignment + ImGui::SetCursorPos(ImVec2(startPos.x, startPos.y + verticalOffset)); + + // Render logo + ImGui::Image(logoTexture, logoSize); + ImGui::SameLine(); + + // Reset cursor for text with proper vertical alignment + ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), startPos.y)); + + // Use windowed font scale for sharper text + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::SetWindowFontScale(textScale); + + // Render text aligned to pixel grid for sharpness + ImGui::Text("%s", text); + + // Restore style + ImGui::SetWindowFontScale(1.0f); + ImGui::PopStyleVar(); + } } // namespace Util diff --git a/src/Utils/UI.h b/src/Utils/UI.h index d10abc5f60..f8bcaf7949 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -1,5 +1,12 @@ #pragma once +#include +#include +#include + +// Forward declarations +class Menu; + namespace Util { @@ -39,4 +46,12 @@ namespace Util bool PercentageSlider(const char* label, float* data, float lb = 0.f, float ub = 100.f, const char* format = "%.1f %%"); ImVec2 GetNativeViewportSizeScaled(float scale); + + // Icon loading functions (moved from UIIconLoader) + bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size); + bool InitializeMenuIcons(Menu* menu); + + // Text rendering helpers (moved from UITextHelper) + void RenderSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f); + void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = 1.5f); } // namespace Util From 3b67b361113335a2aa6a543ac6727e08a04658c4 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 15 Jun 2025 21:22:34 +1000 Subject: [PATCH 08/22] Update README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 8a403c5d88..9687a30709 100644 --- a/README.md +++ b/README.md @@ -132,11 +132,7 @@ See LICENSE within each directory; if none, it's [Default](#default) - [Features Shaders](features) - [Package Shaders](package/Shaders/) -### Community - ### Icons - [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) -- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. - -### Community \ No newline at end of file +- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. \ No newline at end of file From 0954e5a94b8bab61b3fd20e0c0e0b132b948cfb1 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 15 Jun 2025 21:31:36 +1000 Subject: [PATCH 09/22] fixes --- README.md | 2 -- src/Utils/UI.cpp | 43 ++++++++++++++++++++++++++++++++++++++----- src/Utils/UI.h | 10 ++++------ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8a403c5d88..922fbb611b 100644 --- a/README.md +++ b/README.md @@ -138,5 +138,3 @@ See LICENSE within each directory; if none, it's [Default](#default) - [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) - [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. - -### Community \ No newline at end of file diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index d5bf43613a..954157e557 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -1,4 +1,9 @@ #include "UI.h" + +#include +#include +#include + #include "../Globals.h" #include "../Menu.h" @@ -54,6 +59,14 @@ namespace Util // Icon loading functions (moved from UIIconLoader) bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size) { + // Validate output parameter + if (!out_srv) { + return false; + } + + // Initialize output to nullptr + *out_srv = nullptr; + // Load from disk into a raw RGBA buffer int image_width = 0; int image_height = 0; @@ -62,6 +75,12 @@ namespace Util if (image_data == NULL) return false; + // Validate that we have a valid D3D device + if (!globals::d3d::device) { + stbi_image_free(image_data); + return false; + } + // Create texture D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); @@ -75,12 +94,17 @@ namespace Util desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.CPUAccessFlags = 0; - ID3D11Texture2D* pTexture = NULL; + ID3D11Texture2D* pTexture = nullptr; D3D11_SUBRESOURCE_DATA subResource; subResource.pSysMem = image_data; subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); + + HRESULT hr = globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); + if (FAILED(hr) || !pTexture) { + stbi_image_free(image_data); + return false; + } // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; @@ -89,12 +113,21 @@ namespace Util srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; - globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); - pTexture->Release(); - out_size = ImVec2((float)image_width, (float)image_height); + hr = globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); + if (FAILED(hr) || !*out_srv) { + // Clean up on failure + pTexture->Release(); + stbi_image_free(image_data); + *out_srv = nullptr; + return false; + } + + // Success - clean up intermediate resources + pTexture->Release(); stbi_image_free(image_data); + out_size = ImVec2((float)image_width, (float)image_height); return true; } diff --git a/src/Utils/UI.h b/src/Utils/UI.h index f8bcaf7949..392c999866 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -1,10 +1,8 @@ #pragma once -#include -#include -#include - // Forward declarations +struct ID3D11ShaderResourceView; +struct ImVec2; class Menu; namespace Util @@ -47,11 +45,11 @@ namespace Util bool PercentageSlider(const char* label, float* data, float lb = 0.f, float ub = 100.f, const char* format = "%.1f %%"); ImVec2 GetNativeViewportSizeScaled(float scale); - // Icon loading functions (moved from UIIconLoader) + // Icon loading functions bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size); bool InitializeMenuIcons(Menu* menu); - // Text rendering helpers (moved from UITextHelper) + // Text rendering helpers for clearer title text void RenderSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f); void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = 1.5f); } // namespace Util From 3ce7198b0b7dbaa0e6a74916c40a8c8c8ea2e8f6 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 15 Jun 2025 21:55:06 +1000 Subject: [PATCH 10/22] rabbit suggested fixes --- src/Menu.cpp | 10 ++-- src/Utils/UI.cpp | 130 ++++++++++++++++++++++++++++++++++++----------- src/Utils/UI.h | 14 +++-- 3 files changed, 116 insertions(+), 38 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index 7c00b814e0..37f2b6abea 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -289,7 +289,7 @@ void Menu::DrawSettings() ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); // Use our helper to render aligned logo and text with perfect vertical alignment - Util::RenderAlignedTextWithLogo( + Util::DrawAlignedTextWithLogo( uiIcons.logo.texture, logoSize, title.c_str(), @@ -299,7 +299,7 @@ void Menu::DrawSettings() // No logo, just render the text with proper alignment ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); - Util::RenderSharpText(title.c_str(), true, textScaleFactor); + Util::DrawSharpText(title.c_str(), true, textScaleFactor); ImGui::PopStyleVar(); } } // Buttons on the right @@ -377,8 +377,8 @@ void Menu::DrawSettings() ImGui::Spacing(); } - // If icons are disabled, show action buttons as text between separators - if (!settings.Theme.ShowActionIcons) { + // If icons are disabled or missing textures, show action buttons as text between separators + if (!canShowIcons) { if (ImGui::BeginTable("##ActionButtons", 4, ImGuiTableFlags_SizingStretchSame)) { // Save Settings Button ImGui::TableNextColumn(); @@ -437,7 +437,7 @@ void Menu::DrawSettings() ImGui::EndTable(); } - // Second separator - only shown if icons are disabled or if there are failed tasks + // Second separator - only shown if icons are disabled/missing or if there are failed tasks if (!ImGui::IsWindowDocked()) { ImGui::Spacing(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 954157e557..3e0ffe617b 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -57,10 +57,13 @@ namespace Util } // Icon loading functions (moved from UIIconLoader) - bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size) + bool LoadTextureFromFile(ID3D11Device* device, + const char* filename, + ID3D11ShaderResourceView** out_srv, + ImVec2& out_size) { - // Validate output parameter - if (!out_srv) { + // Validate input parameters + if (!device || !out_srv) { return false; } @@ -75,23 +78,19 @@ namespace Util if (image_data == NULL) return false; - // Validate that we have a valid D3D device - if (!globals::d3d::device) { - stbi_image_free(image_data); - return false; - } - // Create texture D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; desc.Height = image_height; - desc.MipLevels = 1; + desc.MipLevels = 0; // Generate full mip chain desc.ArraySize = 1; - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + // Preserve icon colour fidelity + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; desc.CPUAccessFlags = 0; ID3D11Texture2D* pTexture = nullptr; @@ -100,7 +99,7 @@ namespace Util subResource.SysMemPitch = desc.Width * 4; subResource.SysMemSlicePitch = 0; - HRESULT hr = globals::d3d::device->CreateTexture2D(&desc, &subResource, &pTexture); + HRESULT hr = device->CreateTexture2D(&desc, &subResource, &pTexture); if (FAILED(hr) || !pTexture) { stbi_image_free(image_data); return false; @@ -109,12 +108,12 @@ namespace Util // Create texture view D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = desc.MipLevels; + srvDesc.Texture2D.MipLevels = -1; // Use all available mip levels srvDesc.Texture2D.MostDetailedMip = 0; - hr = globals::d3d::device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); + hr = device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); if (FAILED(hr) || !*out_srv) { // Clean up on failure pTexture->Release(); @@ -123,6 +122,11 @@ namespace Util return false; } + // Generate mipmaps for smooth scaling at different DPI levels + if (globals::d3d::context) { + globals::d3d::context->GenerateMips(*out_srv); + } + // Success - clean up intermediate resources pTexture->Release(); stbi_image_free(image_data); @@ -137,23 +141,77 @@ namespace Util return false; } - bool success = true; + // Get the D3D device from globals + ID3D11Device* device = globals::d3d::device; + if (!device) { + return false; + } + // Define path to icons std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; - // Load all required icons - success &= LoadTextureFromFile((basePath + "Microsoft Icons\\save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size); - success &= LoadTextureFromFile((basePath + "Microsoft Icons\\load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size); - success &= LoadTextureFromFile((basePath + "Microsoft Icons\\clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size); - success &= LoadTextureFromFile((basePath + "Microsoft Icons\\clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size); - success &= LoadTextureFromFile((basePath + "Community Shaders Logo\\cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size); + // Initialize all texture pointers to nullptr for safe cleanup + menu->uiIcons.saveSettings.texture = nullptr; + menu->uiIcons.loadSettings.texture = nullptr; + menu->uiIcons.clearCache.texture = nullptr; + menu->uiIcons.clearDiskCache.texture = nullptr; + menu->uiIcons.logo.texture = nullptr; + + // Load icons one by one, cleaning up on any failure + if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size)) { + goto cleanup_and_fail; + } + + if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size)) { + goto cleanup_and_fail; + } + + if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size)) { + goto cleanup_and_fail; + } + + if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size)) { + goto cleanup_and_fail; + } + + if (!LoadTextureFromFile(device, (basePath + "Community Shaders Logo\\cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size)) { + goto cleanup_and_fail; + } + + // All icons loaded successfully + return true; - return success; + cleanup_and_fail: + // Release any successfully loaded SRVs to prevent GPU memory leaks + if (menu->uiIcons.saveSettings.texture) { + menu->uiIcons.saveSettings.texture->Release(); + menu->uiIcons.saveSettings.texture = nullptr; + } + if (menu->uiIcons.loadSettings.texture) { + menu->uiIcons.loadSettings.texture->Release(); + menu->uiIcons.loadSettings.texture = nullptr; + } + if (menu->uiIcons.clearCache.texture) { + menu->uiIcons.clearCache.texture->Release(); + menu->uiIcons.clearCache.texture = nullptr; + } + if (menu->uiIcons.clearDiskCache.texture) { + menu->uiIcons.clearDiskCache.texture->Release(); + menu->uiIcons.clearDiskCache.texture = nullptr; + } + if (menu->uiIcons.logo.texture) { + menu->uiIcons.logo.texture->Release(); + menu->uiIcons.logo.texture = nullptr; + } + + return false; } // Text rendering helpers (moved from UITextHelper) - void RenderSharpText(const char* text, bool alignToPixelGrid, float scale) + ImVec2 DrawSharpText(const char* text, bool alignToPixelGrid, float scale) { + ImVec2 startPos = ImGui::GetCursorPos(); + if (alignToPixelGrid) { // Get current position ImVec2 pos = ImGui::GetCursorPos(); @@ -167,18 +225,25 @@ namespace Util } // Apply scale if needed - if (scale != 1.0f) + float originalScale = 1.0f; + if (scale != 1.0f) { + originalScale = ImGui::GetWindowFontScale(); ImGui::SetWindowFontScale(scale); + } // Use Text instead of TextUnformatted for better rendering ImGui::Text("%s", text); - // Restore scale if needed + // Restore original scale if needed if (scale != 1.0f) - ImGui::SetWindowFontScale(1.0f); + ImGui::SetWindowFontScale(originalScale); + + // Calculate and return the rendered size + ImVec2 endPos = ImGui::GetCursorPos(); + return ImVec2(endPos.x - startPos.x, endPos.y - startPos.y); } - void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale) + ImVec2 DrawAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale) { // Save current cursor position ImVec2 startPos = ImGui::GetCursorPos(); @@ -202,13 +267,18 @@ namespace Util // Use windowed font scale for sharper text ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + float originalScale = ImGui::GetWindowFontScale(); ImGui::SetWindowFontScale(textScale); // Render text aligned to pixel grid for sharpness ImGui::Text("%s", text); // Restore style - ImGui::SetWindowFontScale(1.0f); + ImGui::SetWindowFontScale(originalScale); ImGui::PopStyleVar(); + + // Calculate and return the total rendered size + ImVec2 endPos = ImGui::GetCursorPos(); + return ImVec2(endPos.x - startPos.x, endPos.y - startPos.y); } } // namespace Util diff --git a/src/Utils/UI.h b/src/Utils/UI.h index 392c999866..290b9848bd 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -1,12 +1,15 @@ #pragma once // Forward declarations +struct ID3D11Device; struct ID3D11ShaderResourceView; struct ImVec2; class Menu; namespace Util { + // Text rendering constants + constexpr float DefaultHeaderTextScale = 1.5f; // Larger scale for header text to improve readability /** * Usage: @@ -46,10 +49,15 @@ namespace Util ImVec2 GetNativeViewportSizeScaled(float scale); // Icon loading functions - bool LoadTextureFromFile(const char* filename, ID3D11ShaderResourceView** out_srv, ImVec2& out_size); + // `device` must remain alive for the SRV lifetime. Caller owns *out_srv and must `Release()` it. + bool LoadTextureFromFile(ID3D11Device* device, + const char* filename, + ID3D11ShaderResourceView** out_srv, + ImVec2& out_size); bool InitializeMenuIcons(Menu* menu); // Text rendering helpers for clearer title text - void RenderSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f); - void RenderAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = 1.5f); + // These functions modify ImGui rendering state and should be called within ImGui context + ImVec2 DrawSharpText(const char* text, bool alignToPixelGrid = true, float scale = 1.0f); + ImVec2 DrawAlignedTextWithLogo(ID3D11ShaderResourceView* logoTexture, const ImVec2& logoSize, const char* text, float textScale = DefaultHeaderTextScale); } // namespace Util From a5e9cab2aac40e5713d15c30295c2a7946e97bdc Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Sun, 15 Jun 2025 22:28:52 +1000 Subject: [PATCH 11/22] fix --- src/Utils/UI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 3e0ffe617b..2acee3c576 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -83,7 +83,7 @@ namespace Util ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; desc.Height = image_height; - desc.MipLevels = 0; // Generate full mip chain + desc.MipLevels = 1; // Start with single mip level for initial data desc.ArraySize = 1; // Preserve icon colour fidelity desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; @@ -110,7 +110,7 @@ namespace Util ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = -1; // Use all available mip levels + srvDesc.Texture2D.MipLevels = 0; // Use all available mip levels srvDesc.Texture2D.MostDetailedMip = 0; hr = device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); From fed8c3639ae6cffdd7d7d31009145e82979ce3b5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 12:34:35 +0000 Subject: [PATCH 12/22] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- README.md | 8 +- src/Menu.cpp | 432 +++++++++++++++++++++++------------------------ src/Menu.h | 17 +- src/Utils/UI.cpp | 22 +-- src/Utils/UI.h | 6 +- 5 files changed, 244 insertions(+), 241 deletions(-) diff --git a/README.md b/README.md index 7136196bcf..f57edd25a0 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ The Modding Libraries include: See LICENSE within each directory; if none, it's [Default](#default) -- [Features Shaders](features) -- [Package Shaders](package/Shaders/) +- [Features Shaders](features) +- [Package Shaders](package/Shaders/) ### Icons -- [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) -- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. +- [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) +- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. diff --git a/src/Menu.cpp b/src/Menu.cpp index 06a8286009..c8eab4e0cd 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -18,8 +18,8 @@ #include "Streamline.h" #include "TruePBR.h" #include "Upscaling.h" -#include "Utils/UI.h" #include "Util.h" +#include "Utils/UI.h" #include "Features/LightLimitFix/ParticleLights.h" @@ -208,7 +208,7 @@ void Menu::SetupImGuiStyle() const bool IsEnabled = false; Menu::~Menu() -{ // Release icon textures if loaded +{ // Release icon textures if loaded uiIcons.saveSettings.Release(); uiIcons.loadSettings.Release(); uiIcons.clearCache.Release(); @@ -241,16 +241,16 @@ void Menu::Init() auto& imgui_io = ImGui::GetIO(); imgui_io.ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_DockingEnable; imgui_io.BackendFlags = ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_RendererHasVtxOffset; - + // Enhanced font configuration for sharper text rendering ImFontConfig font_config; - font_config.GlyphExtraSpacing.x = 0.0f; // Neutral spacing for cleaner look - font_config.OversampleH = 3; // Increased horizontal oversampling for sharper text - font_config.OversampleV = 2; // Increased vertical oversampling - font_config.PixelSnapH = true; // Align to pixel grid for sharper rendering - font_config.RasterizerMultiply = 1.1f; // Slightly darker font rendering - font_config.FontBuilderFlags = 0; // No additional flags needed - + font_config.GlyphExtraSpacing.x = 0.0f; // Neutral spacing for cleaner look + font_config.OversampleH = 3; // Increased horizontal oversampling for sharper text + font_config.OversampleV = 2; // Increased vertical oversampling + font_config.PixelSnapH = true; // Align to pixel grid for sharper rendering + font_config.RasterizerMultiply = 1.1f; // Slightly darker font rendering + font_config.FontBuilderFlags = 0; // No additional flags needed + // Add high-quality font with improved settings imgui_io.Fonts->AddFontFromFileTTF("Data\\Interface\\CommunityShaders\\Fonts\\Jost-Regular.ttf", 36, &font_config); @@ -291,7 +291,6 @@ void Menu::DrawSettings() auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); { - if (!ImGui::IsWindowDocked()) { ImGui::SetWindowFontScale(1.5f); ImGui::TextUnformatted(title.c_str()); @@ -303,204 +302,206 @@ void Menu::DrawSettings() } auto shaderCache = globals::shaderCache; - const float iconSize = 48.0f; - const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons - - // Begin a layout with title on the left and buttons on the right - if (ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Buttons", ImGuiTableColumnFlags_WidthFixed); ImGui::TableNextColumn(); // Title on the left with logo - if (!ImGui::IsWindowDocked()) { - // Calculate logo size with proper scaling - const float textScaleFactor = 1.5f; - const float logoHeightScale = 1.25f; - const float titleHeight = ImGui::GetFontSize() * logoHeightScale; - - // Always display logo if texture is available - if (uiIcons.logo.texture) { - float logoAspectRatio = uiIcons.logo.size.x / uiIcons.logo.size.y; - ImVec2 logoSize(titleHeight * logoAspectRatio, titleHeight); - - // Add a bit of padding before the logo and text - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); - - // Use our helper to render aligned logo and text with perfect vertical alignment - Util::DrawAlignedTextWithLogo( - uiIcons.logo.texture, - logoSize, - title.c_str(), - textScaleFactor - ); - } else { - // No logo, just render the text with proper alignment - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); - Util::DrawSharpText(title.c_str(), true, textScaleFactor); - ImGui::PopStyleVar(); - } - } // Buttons on the right - ImGui::TableNextColumn(); - - // Only show icon buttons in the header if setting is enabled and all icon textures are available - bool canShowIcons = settings.Theme.ShowActionIcons && - uiIcons.saveSettings.texture && - uiIcons.loadSettings.texture && - uiIcons.clearCache.texture && - uiIcons.clearDiskCache.texture; - - if (canShowIcons) { - // Create a horizontal layout for the buttons and remove button borders - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0)); // Transparent button background - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f,0.7f,0.7f,0.2f)); // Subtle hover effect - - // Save Settings Button - if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { - globals::state->Save(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Save Settings"); - } - ImGui::SameLine(); - - // Load Settings Button - if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Load Settings"); - } - ImGui::SameLine(); - - // Clear Shader Cache Button - if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { - shaderCache->Clear(); - // any features should be added to shadercache's clear. - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Clear Shader Cache\n\n" - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); - } - ImGui::SameLine(); - - // Clear Disk Cache Button - if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Clear Disk Cache\n\n" - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); - } - - // Restore default style - ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize - ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered - } - - ImGui::EndTable(); - } - // First separator - always shown - if (!ImGui::IsWindowDocked()) { - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); - } - - // If icons are disabled or missing textures, show action buttons as text between separators - if (!canShowIcons) { - if (ImGui::BeginTable("##ActionButtons", 4, ImGuiTableFlags_SizingStretchSame)) { - // Save Settings Button - ImGui::TableNextColumn(); - if (ImGui::Button("Save Settings", { -1, 0 })) { - globals::state->Save(); - } - - // Load Settings Button - ImGui::TableNextColumn(); - if (ImGui::Button("Load Settings", { -1, 0 })) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - - // Clear Shader Cache Button - ImGui::TableNextColumn(); - if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { - shaderCache->Clear(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); - } - - // Clear Disk Cache Button - ImGui::TableNextColumn(); - if (ImGui::Button("Clear Disk Cache", { -1, 0 })) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); - } - - // Error message toggle if needed - if (shaderCache->GetFailedTasks()) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Button("Toggle Error Message", { -1, 0 })) { - shaderCache->ToggleErrorMessages(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Hide or show the shader failure message. " - "Your installation is broken and will likely see errors in game. " - "Please double check you have updated all features and that your load order is correct. " - "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); - } - } - - ImGui::EndTable(); - } - - // Second separator - only shown if icons are disabled/missing or if there are failed tasks - if (!ImGui::IsWindowDocked()) { - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); - } - } else if (shaderCache->GetFailedTasks()) { - // If icons are enabled but there are failed tasks, show error toggle button - // and add the second separator - if (ImGui::Button("Toggle Error Message", { -1, 0 })) { - shaderCache->ToggleErrorMessages(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Hide or show the shader failure message. " - "Your installation is broken and will likely see errors in game. " - "Please double check you have updated all features and that your load order is correct. " - "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); - } - - // Add second separator when showing error button - if (!ImGui::IsWindowDocked()) { - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); - } - } else { // No additional separator needed - already handled in the conditional block above - } + const float iconSize = 48.0f; + const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons + + // Begin a layout with title on the left and buttons on the right + if (ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Buttons", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableNextColumn(); // Title on the left with logo + if (!ImGui::IsWindowDocked()) { + // Calculate logo size with proper scaling + const float textScaleFactor = 1.5f; + const float logoHeightScale = 1.25f; + const float titleHeight = ImGui::GetFontSize() * logoHeightScale; + + // Always display logo if texture is available + if (uiIcons.logo.texture) { + float logoAspectRatio = uiIcons.logo.size.x / uiIcons.logo.size.y; + ImVec2 logoSize(titleHeight * logoAspectRatio, titleHeight); + + // Add a bit of padding before the logo and text + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); + + // Use our helper to render aligned logo and text with perfect vertical alignment + Util::DrawAlignedTextWithLogo( + uiIcons.logo.texture, + logoSize, + title.c_str(), + textScaleFactor); + } else { + // No logo, just render the text with proper alignment + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); + Util::DrawSharpText(title.c_str(), true, textScaleFactor); + ImGui::PopStyleVar(); + } + } // Buttons on the right + ImGui::TableNextColumn(); + + // Only show icon buttons in the header if setting is enabled and all icon textures are available + bool canShowIcons = settings.Theme.ShowActionIcons && + uiIcons.saveSettings.texture && + uiIcons.loadSettings.texture && + uiIcons.clearCache.texture && + uiIcons.clearDiskCache.texture; + + if (canShowIcons) { + // Create a horizontal layout for the buttons and remove button borders + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect + + // Save Settings Button + if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { + globals::state->Save(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Save Settings"); + } + ImGui::SameLine(); + + // Load Settings Button + if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Load Settings"); + } + ImGui::SameLine(); + + // Clear Shader Cache Button + if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { + shaderCache->Clear(); + // any features should be added to shadercache's clear. + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Shader Cache\n\n" + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + ImGui::SameLine(); + + // Clear Disk Cache Button + if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Disk Cache\n\n" + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + + // Restore default style + ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize + ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered + } + + ImGui::EndTable(); + } + // First separator - always shown + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + + // If icons are disabled or missing textures, show action buttons as text between separators + if (!canShowIcons) { + if (ImGui::BeginTable("##ActionButtons", 4, ImGuiTableFlags_SizingStretchSame)) { + // Save Settings Button + ImGui::TableNextColumn(); + if (ImGui::Button("Save Settings", { -1, 0 })) { + globals::state->Save(); + } + + // Load Settings Button + ImGui::TableNextColumn(); + if (ImGui::Button("Load Settings", { -1, 0 })) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + + // Clear Shader Cache Button + ImGui::TableNextColumn(); + if (ImGui::Button("Clear Shader Cache", { -1, 0 })) { + shaderCache->Clear(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + + // Clear Disk Cache Button + ImGui::TableNextColumn(); + if (ImGui::Button("Clear Disk Cache", { -1, 0 })) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + + // Error message toggle if needed + if (shaderCache->GetFailedTasks()) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Button("Toggle Error Message", { -1, 0 })) { + shaderCache->ToggleErrorMessages(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Hide or show the shader failure message. " + "Your installation is broken and will likely see errors in game. " + "Please double check you have updated all features and that your load order is correct. " + "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); + } + } + + ImGui::EndTable(); + } + + // Second separator - only shown if icons are disabled/missing or if there are failed tasks + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + } else if (shaderCache->GetFailedTasks()) { + // If icons are enabled but there are failed tasks, show error toggle button + // and add the second separator + if (ImGui::Button("Toggle Error Message", { -1, 0 })) { + shaderCache->ToggleErrorMessages(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Hide or show the shader failure message. " + "Your installation is broken and will likely see errors in game. " + "Please double check you have updated all features and that your load order is correct. " + "See CommunityShaders.log for details and check the Nexus Mods page or Discord server. "); + } + + // Add second separator when showing error button + if (!ImGui::IsWindowDocked()) { + ImGui::Spacing(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); + ImGui::Spacing(); + } + } else { // No additional separator needed - already handled in the conditional block above + } // Main content starts here - no additional separator needed as it's already handled in the conditions above @@ -897,25 +898,24 @@ void Menu::DrawGeneralSettings() if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabItem("UI Options")) { if (ImGui::SliderFloat("Global Scale", &themeSettings.GlobalScale, -1.f, 1.f, "%.2f")) { - float trueScale = exp2(themeSettings.GlobalScale); auto& io = ImGui::GetIO(); io.FontGlobalScale = trueScale; } - + ImGui::SeparatorText("UI Elements"); ImGui::Checkbox("Use Icon Buttons in Header", &themeSettings.ShowActionIcons); if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("When enabled: Shows action buttons (Save, Load, Clear Cache, Clear Disk Cache) as icons in the header\n" - "When disabled: Shows as text buttons below the header"); + ImGui::Text( + "When enabled: Shows action buttons (Save, Load, Clear Cache, Clear Disk Cache) as icons in the header\n" + "When disabled: Shows as text buttons below the header"); } - + ImGui::EndTabItem(); } - - if (ImGui::BeginTabItem("Sizes")) { + if (ImGui::BeginTabItem("Sizes")) { ImGui::SeparatorText("Main"); ImGui::SliderFloat2("Window Padding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("Frame Padding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); diff --git a/src/Menu.h b/src/Menu.h index a8f8e49b66..b1421a9b58 100644 --- a/src/Menu.h +++ b/src/Menu.h @@ -47,25 +47,28 @@ class Menu bool ShouldSwallowInput(); void OnFocusLost(); // UI icon textures - struct UIIcon { + struct UIIcon + { ID3D11ShaderResourceView* texture = nullptr; ImVec2 size = ImVec2(32.0f, 32.0f); - - void Release() { + + void Release() + { if (texture) { texture->Release(); texture = nullptr; } } }; - struct UIIcons { + struct UIIcons + { UIIcon saveSettings; UIIcon loadSettings; UIIcon clearCache; UIIcon clearDiskCache; - UIIcon logo; // New logo icon - } uiIcons; - + UIIcon logo; // New logo icon + } uiIcons; + struct ThemeSettings { float GlobalScale = REL::Module::IsVR() ? -0.5f : 0.f; // exponential diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index ed4334174a..b1f7cbfe1d 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -8,8 +8,8 @@ #include "../Menu.h" #define STB_IMAGE_IMPLEMENTATION -#include #include +#include namespace Util { @@ -60,9 +60,9 @@ namespace Util // Icon loading functions (moved from UIIconLoader) bool LoadTextureFromFile(ID3D11Device* device, - const char* filename, - ID3D11ShaderResourceView** out_srv, - ImVec2& out_size) + const char* filename, + ID3D11ShaderResourceView** out_srv, + ImVec2& out_size) { // Validate input parameters if (!device || !out_srv) { @@ -85,7 +85,7 @@ namespace Util ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; desc.Height = image_height; - desc.MipLevels = 1; // Start with single mip level for initial data + desc.MipLevels = 1; // Start with single mip level for initial data desc.ArraySize = 1; // Preserve icon colour fidelity desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; @@ -112,7 +112,7 @@ namespace Util ZeroMemory(&srvDesc, sizeof(srvDesc)); srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = 0; // Use all available mip levels + srvDesc.Texture2D.MipLevels = 0; // Use all available mip levels srvDesc.Texture2D.MostDetailedMip = 0; hr = device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); @@ -183,7 +183,7 @@ namespace Util // All icons loaded successfully return true; - cleanup_and_fail: +cleanup_and_fail: // Release any successfully loaded SRVs to prevent GPU memory leaks if (menu->uiIcons.saveSettings.texture) { menu->uiIcons.saveSettings.texture->Release(); @@ -213,7 +213,7 @@ namespace Util ImVec2 DrawSharpText(const char* text, bool alignToPixelGrid, float scale) { ImVec2 startPos = ImGui::GetCursorPos(); - + if (alignToPixelGrid) { // Get current position ImVec2 pos = ImGui::GetCursorPos(); @@ -239,7 +239,7 @@ namespace Util // Restore original scale if needed if (scale != 1.0f) ImGui::SetWindowFontScale(originalScale); - + // Calculate and return the rendered size ImVec2 endPos = ImGui::GetCursorPos(); return ImVec2(endPos.x - startPos.x, endPos.y - startPos.y); @@ -278,11 +278,11 @@ namespace Util // Restore style ImGui::SetWindowFontScale(originalScale); ImGui::PopStyleVar(); - + // Calculate and return the total rendered size ImVec2 endPos = ImGui::GetCursorPos(); return ImVec2(endPos.x - startPos.x, endPos.y - startPos.y); - } + } // StyledButtonWrapper implementation StyledButtonWrapper::StyledButtonWrapper(const ImVec4& normalColor, const ImVec4& hoveredColor, const ImVec4& activeColor) : m_pushedStyles(0) diff --git a/src/Utils/UI.h b/src/Utils/UI.h index b84df67603..5e356d38f3 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -119,9 +119,9 @@ namespace Util // Icon loading functions // `device` must remain alive for the SRV lifetime. Caller owns *out_srv and must `Release()` it. bool LoadTextureFromFile(ID3D11Device* device, - const char* filename, - ID3D11ShaderResourceView** out_srv, - ImVec2& out_size); + const char* filename, + ID3D11ShaderResourceView** out_srv, + ImVec2& out_size); bool InitializeMenuIcons(Menu* menu); // Text rendering helpers for clearer title text From f01c2a107fc91d2f17adacd95f028daea5ffc7a8 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 17:01:50 +1000 Subject: [PATCH 13/22] build fix --- src/Menu.cpp | 15 +++++++-------- src/Utils/UI.cpp | 11 ++--------- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index c8eab4e0cd..b47caa7f60 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -300,11 +300,17 @@ void Menu::DrawSettings() ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); ImGui::Spacing(); } - auto shaderCache = globals::shaderCache; const float iconSize = 48.0f; const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons + // Only show icon buttons if setting is enabled and all icon textures are available + bool canShowIcons = settings.Theme.ShowActionIcons && + uiIcons.saveSettings.texture && + uiIcons.loadSettings.texture && + uiIcons.clearCache.texture && + uiIcons.clearDiskCache.texture; + // Begin a layout with title on the left and buttons on the right if (ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); @@ -340,13 +346,6 @@ void Menu::DrawSettings() } // Buttons on the right ImGui::TableNextColumn(); - // Only show icon buttons in the header if setting is enabled and all icon textures are available - bool canShowIcons = settings.Theme.ShowActionIcons && - uiIcons.saveSettings.texture && - uiIcons.loadSettings.texture && - uiIcons.clearCache.texture && - uiIcons.clearDiskCache.texture; - if (canShowIcons) { // Create a horizontal layout for the buttons and remove button borders ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index b1f7cbfe1d..60372d4bcc 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -225,20 +225,16 @@ namespace Util // Set aligned position ImGui::SetCursorPos(pos); } - // Apply scale if needed - float originalScale = 1.0f; if (scale != 1.0f) { - originalScale = ImGui::GetWindowFontScale(); ImGui::SetWindowFontScale(scale); } // Use Text instead of TextUnformatted for better rendering ImGui::Text("%s", text); - // Restore original scale if needed if (scale != 1.0f) - ImGui::SetWindowFontScale(originalScale); + ImGui::SetWindowFontScale(1.0f); // Calculate and return the rendered size ImVec2 endPos = ImGui::GetCursorPos(); @@ -266,17 +262,14 @@ namespace Util // Reset cursor for text with proper vertical alignment ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX(), startPos.y)); - // Use windowed font scale for sharper text ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - float originalScale = ImGui::GetWindowFontScale(); ImGui::SetWindowFontScale(textScale); // Render text aligned to pixel grid for sharpness ImGui::Text("%s", text); - // Restore style - ImGui::SetWindowFontScale(originalScale); + ImGui::SetWindowFontScale(1.0f); ImGui::PopStyleVar(); // Calculate and return the total rendered size From 4896741a4fd4d434d4f0e2ce4e65cc6f5f52ef59 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 17:27:24 +1000 Subject: [PATCH 14/22] Fixes --- src/Menu.cpp | 131 ++++++++++++++++++++++++----------------------- src/Utils/UI.cpp | 126 ++++++++++++++++++++++----------------------- 2 files changed, 131 insertions(+), 126 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index b47caa7f60..6fe8efa6ce 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -288,31 +288,26 @@ void Menu::DrawSettings() ImGui::SetNextWindowPos(Util::GetNativeViewportSizeScaled(0.5f), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(Util::GetNativeViewportSizeScaled(0.8f), ImGuiCond_FirstUseEver); - auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); - ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); + auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); { - if (!ImGui::IsWindowDocked()) { - ImGui::SetWindowFontScale(1.5f); - ImGui::TextUnformatted(title.c_str()); - ImGui::SetWindowFontScale(1.0f); - - ImGui::Spacing(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); - ImGui::Spacing(); - } auto shaderCache = globals::shaderCache; const float iconSize = 48.0f; const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons - // Only show icon buttons if setting is enabled and all icon textures are available + // Check if we can show icons - require setting enabled and at least some icons loaded bool canShowIcons = settings.Theme.ShowActionIcons && - uiIcons.saveSettings.texture && - uiIcons.loadSettings.texture && - uiIcons.clearCache.texture && - uiIcons.clearDiskCache.texture; - // Begin a layout with title on the left and buttons on the right - if (ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { + // Debug logging for icon availability + if (settings.Theme.ShowActionIcons) { + logger::debug("Icon status - Save: {}, Load: {}, Cache: {}, Disk: {}, Logo: {}", + uiIcons.saveSettings.texture ? "OK" : "NULL", + uiIcons.loadSettings.texture ? "OK" : "NULL", + uiIcons.clearCache.texture ? "OK" : "NULL", + uiIcons.clearDiskCache.texture ? "OK" : "NULL", + uiIcons.logo.texture ? "OK" : "NULL"); + } + // Begin a layout with title on the left and buttons on the right (only if we have icons) + if (canShowIcons && ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Buttons", ImGuiTableColumnFlags_WidthFixed); ImGui::TableNextColumn(); // Title on the left with logo @@ -342,69 +337,79 @@ void Menu::DrawSettings() ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); Util::DrawSharpText(title.c_str(), true, textScaleFactor); ImGui::PopStyleVar(); - } - } // Buttons on the right + } } // Buttons on the right ImGui::TableNextColumn(); - if (canShowIcons) { - // Create a horizontal layout for the buttons and remove button borders - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons + // Create a horizontal layout for the buttons and remove button borders + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect // Save Settings Button - if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { - globals::state->Save(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Save Settings"); + if (uiIcons.saveSettings.texture) { + if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { + globals::state->Save(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Save Settings"); + } + ImGui::SameLine(); } - ImGui::SameLine(); // Load Settings Button - if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Load Settings"); + if (uiIcons.loadSettings.texture) { + if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Load Settings"); + } + ImGui::SameLine(); } - ImGui::SameLine(); // Clear Shader Cache Button - if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { - shaderCache->Clear(); - // any features should be added to shadercache's clear. - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Shader Cache\n\n" - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); + if (uiIcons.clearCache.texture) { + if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { + shaderCache->Clear(); + // any features should be added to shadercache's clear. + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Shader Cache\n\n" + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + ImGui::SameLine(); } - ImGui::SameLine(); // Clear Disk Cache Button - if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Disk Cache\n\n" - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); - } - - // Restore default style - ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize - ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered - } + if (uiIcons.clearDiskCache.texture) { + if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Disk Cache\n\n" + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + } // Restore default style + ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize + ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered ImGui::EndTable(); + } else if (!canShowIcons) { + // No icons available - show just the title without the table layout + if (!ImGui::IsWindowDocked()) { + ImGui::SetWindowFontScale(1.5f); + ImGui::TextUnformatted(title.c_str()); + ImGui::SetWindowFontScale(1.0f); + } } // First separator - always shown if (!ImGui::IsWindowDocked()) { diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 60372d4bcc..f6a2e1a331 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -57,7 +57,6 @@ namespace Util const auto Size = ImGui::GetMainViewport()->Size; return { Size.x * scale, Size.y * scale }; } - // Icon loading functions (moved from UIIconLoader) bool LoadTextureFromFile(ID3D11Device* device, const char* filename, @@ -66,33 +65,40 @@ namespace Util { // Validate input parameters if (!device || !out_srv) { + logger::warn("LoadTextureFromFile: Invalid parameters - device: {}, out_srv: {}", + device ? "valid" : "null", out_srv ? "valid" : "null"); return false; } // Initialize output to nullptr *out_srv = nullptr; + logger::debug("LoadTextureFromFile: Attempting to load {}", filename); + // Load from disk into a raw RGBA buffer int image_width = 0; int image_height = 0; int channels_in_file; unsigned char* image_data = stbi_load(filename, &image_width, &image_height, &channels_in_file, 4); - if (image_data == NULL) + if (image_data == NULL) { + logger::warn("LoadTextureFromFile: Failed to load image data from {}", filename); return false; + } - // Create texture + logger::debug("LoadTextureFromFile: Loaded image {}x{} with {} channels from {}", + image_width, image_height, channels_in_file, filename); // Create texture with simpler setup to avoid HRESULT 0x80070057 D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; desc.Height = image_height; - desc.MipLevels = 1; // Start with single mip level for initial data + desc.MipLevels = 1; // Start with just one mip level desc.ArraySize = 1; - // Preserve icon colour fidelity - desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; - desc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; + desc.MiscFlags = 0; // Remove mipmap generation for now desc.CPUAccessFlags = 0; ID3D11Texture2D* pTexture = nullptr; @@ -103,54 +109,45 @@ namespace Util HRESULT hr = device->CreateTexture2D(&desc, &subResource, &pTexture); if (FAILED(hr) || !pTexture) { + logger::warn("LoadTextureFromFile: Failed to create D3D11 texture, HRESULT: 0x{:08X}", static_cast(hr)); stbi_image_free(image_data); return false; } - // Create texture view - D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; - ZeroMemory(&srvDesc, sizeof(srvDesc)); - srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; - srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; - srvDesc.Texture2D.MipLevels = 0; // Use all available mip levels - srvDesc.Texture2D.MostDetailedMip = 0; - - hr = device->CreateShaderResourceView(pTexture, &srvDesc, out_srv); + // Create simple shader resource view + hr = device->CreateShaderResourceView(pTexture, nullptr, out_srv); if (FAILED(hr) || !*out_srv) { - // Clean up on failure + logger::warn("LoadTextureFromFile: Failed to create shader resource view, HRESULT: 0x{:08X}", static_cast(hr)); pTexture->Release(); stbi_image_free(image_data); *out_srv = nullptr; return false; } - - // Generate mipmaps for smooth scaling at different DPI levels - if (globals::d3d::context) { - globals::d3d::context->GenerateMips(*out_srv); - } - // Success - clean up intermediate resources pTexture->Release(); stbi_image_free(image_data); out_size = ImVec2((float)image_width, (float)image_height); + logger::debug("LoadTextureFromFile: Successfully loaded {} ({}x{})", filename, image_width, image_height); return true; } - bool InitializeMenuIcons(Menu* menu) { if (!menu) { + logger::warn("InitializeMenuIcons: Menu pointer is null"); return false; } // Get the D3D device from globals ID3D11Device* device = globals::d3d::device; if (!device) { + logger::warn("InitializeMenuIcons: D3D device is null"); return false; } // Define path to icons std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; + logger::info("InitializeMenuIcons: Loading icons from base path: {}", basePath); // Initialize all texture pointers to nullptr for safe cleanup menu->uiIcons.saveSettings.texture = nullptr; @@ -159,54 +156,57 @@ namespace Util menu->uiIcons.clearDiskCache.texture = nullptr; menu->uiIcons.logo.texture = nullptr; - // Load icons one by one, cleaning up on any failure - if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size)) { - goto cleanup_and_fail; + // Instead of failing completely if one icon fails, try to load each one individually + bool anyIconLoaded = false; + int iconsLoaded = 0; + + // Load save settings icon + if (LoadTextureFromFile(device, (basePath + "Microsoft Icons\\save-settings.png").c_str(), &menu->uiIcons.saveSettings.texture, menu->uiIcons.saveSettings.size)) { + logger::info("InitializeMenuIcons: Successfully loaded save-settings icon"); + iconsLoaded++; + anyIconLoaded = true; + } else { + logger::warn("InitializeMenuIcons: Failed to load save-settings icon from: {}", basePath + "Microsoft Icons\\save-settings.png"); } - if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size)) { - goto cleanup_and_fail; + // Load load settings icon + if (LoadTextureFromFile(device, (basePath + "Microsoft Icons\\load-settings.png").c_str(), &menu->uiIcons.loadSettings.texture, menu->uiIcons.loadSettings.size)) { + logger::info("InitializeMenuIcons: Successfully loaded load-settings icon"); + iconsLoaded++; + anyIconLoaded = true; + } else { + logger::warn("InitializeMenuIcons: Failed to load load-settings icon from: {}", basePath + "Microsoft Icons\\load-settings.png"); } - if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size)) { - goto cleanup_and_fail; + // Load clear cache icon + if (LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-cache.png").c_str(), &menu->uiIcons.clearCache.texture, menu->uiIcons.clearCache.size)) { + logger::info("InitializeMenuIcons: Successfully loaded clear-cache icon"); + iconsLoaded++; + anyIconLoaded = true; + } else { + logger::warn("InitializeMenuIcons: Failed to load clear-cache icon from: {}", basePath + "Microsoft Icons\\clear-cache.png"); } - if (!LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size)) { - goto cleanup_and_fail; + // Load clear disk cache icon + if (LoadTextureFromFile(device, (basePath + "Microsoft Icons\\clear-disk.png").c_str(), &menu->uiIcons.clearDiskCache.texture, menu->uiIcons.clearDiskCache.size)) { + logger::info("InitializeMenuIcons: Successfully loaded clear-disk icon"); + iconsLoaded++; + anyIconLoaded = true; + } else { + logger::warn("InitializeMenuIcons: Failed to load clear-disk icon from: {}", basePath + "Microsoft Icons\\clear-disk.png"); } - if (!LoadTextureFromFile(device, (basePath + "Community Shaders Logo\\cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size)) { - goto cleanup_and_fail; - } - - // All icons loaded successfully - return true; - -cleanup_and_fail: - // Release any successfully loaded SRVs to prevent GPU memory leaks - if (menu->uiIcons.saveSettings.texture) { - menu->uiIcons.saveSettings.texture->Release(); - menu->uiIcons.saveSettings.texture = nullptr; - } - if (menu->uiIcons.loadSettings.texture) { - menu->uiIcons.loadSettings.texture->Release(); - menu->uiIcons.loadSettings.texture = nullptr; - } - if (menu->uiIcons.clearCache.texture) { - menu->uiIcons.clearCache.texture->Release(); - menu->uiIcons.clearCache.texture = nullptr; - } - if (menu->uiIcons.clearDiskCache.texture) { - menu->uiIcons.clearDiskCache.texture->Release(); - menu->uiIcons.clearDiskCache.texture = nullptr; - } - if (menu->uiIcons.logo.texture) { - menu->uiIcons.logo.texture->Release(); - menu->uiIcons.logo.texture = nullptr; + // Load logo icon + if (LoadTextureFromFile(device, (basePath + "Community Shaders Logo\\cs-logo.png").c_str(), &menu->uiIcons.logo.texture, menu->uiIcons.logo.size)) { + logger::info("InitializeMenuIcons: Successfully loaded logo icon"); + iconsLoaded++; + anyIconLoaded = true; + } else { + logger::warn("InitializeMenuIcons: Failed to load logo icon from: {}", basePath + "Community Shaders Logo\\cs-logo.png"); } - return false; + logger::info("InitializeMenuIcons: Loaded {}/5 icons successfully", iconsLoaded); + return anyIconLoaded; } // Text rendering helpers (moved from UITextHelper) From 9333086286d3e8b1e40c3375e646d28f65d6429d Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 17:27:31 +1000 Subject: [PATCH 15/22] Update Menu.cpp --- src/Menu.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index 6fe8efa6ce..3d803593d8 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -282,7 +282,7 @@ void Menu::Init() } void Menu::DrawSettings() -{ + { ImGui::DockSpaceOverViewport(NULL, ImGuiDockNodeFlags_PassthruCentralNode); ImGui::SetNextWindowPos(Util::GetNativeViewportSizeScaled(0.5f), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); @@ -296,6 +296,10 @@ void Menu::DrawSettings() // Check if we can show icons - require setting enabled and at least some icons loaded bool canShowIcons = settings.Theme.ShowActionIcons && + (uiIcons.saveSettings.texture || + uiIcons.loadSettings.texture || + uiIcons.clearCache.texture || + uiIcons.clearDiskCache.texture); // Debug logging for icon availability if (settings.Theme.ShowActionIcons) { From 899d69120621efdadb790d0fffa79691323eec72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 07:27:56 +0000 Subject: [PATCH 16/22] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Menu.cpp | 116 ++++++++++++++++++++++++----------------------- src/Utils/UI.cpp | 6 +-- 2 files changed, 62 insertions(+), 60 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index 3d803593d8..07d78a8575 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -282,13 +282,14 @@ void Menu::Init() } void Menu::DrawSettings() - { +{ ImGui::DockSpaceOverViewport(NULL, ImGuiDockNodeFlags_PassthruCentralNode); ImGui::SetNextWindowPos(Util::GetNativeViewportSizeScaled(0.5f), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); ImGui::SetNextWindowSize(Util::GetNativeViewportSizeScaled(0.8f), ImGuiCond_FirstUseEver); - auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); + auto title = std::format("Community Shaders {}", Util::GetFormattedVersion(Plugin::VERSION)); + ImGui::Begin(title.c_str(), &IsEnabled, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar); { auto shaderCache = globals::shaderCache; const float iconSize = 48.0f; @@ -297,15 +298,15 @@ void Menu::DrawSettings() // Check if we can show icons - require setting enabled and at least some icons loaded bool canShowIcons = settings.Theme.ShowActionIcons && (uiIcons.saveSettings.texture || - uiIcons.loadSettings.texture || - uiIcons.clearCache.texture || - uiIcons.clearDiskCache.texture); + uiIcons.loadSettings.texture || + uiIcons.clearCache.texture || + uiIcons.clearDiskCache.texture); // Debug logging for icon availability if (settings.Theme.ShowActionIcons) { logger::debug("Icon status - Save: {}, Load: {}, Cache: {}, Disk: {}, Logo: {}", uiIcons.saveSettings.texture ? "OK" : "NULL", - uiIcons.loadSettings.texture ? "OK" : "NULL", + uiIcons.loadSettings.texture ? "OK" : "NULL", uiIcons.clearCache.texture ? "OK" : "NULL", uiIcons.clearDiskCache.texture ? "OK" : "NULL", uiIcons.logo.texture ? "OK" : "NULL"); @@ -341,68 +342,69 @@ void Menu::DrawSettings() ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 5.0f); Util::DrawSharpText(title.c_str(), true, textScaleFactor); ImGui::PopStyleVar(); - } } // Buttons on the right + } + } // Buttons on the right ImGui::TableNextColumn(); // Create a horizontal layout for the buttons and remove button borders ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect - // Save Settings Button - if (uiIcons.saveSettings.texture) { - if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { - globals::state->Save(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Save Settings"); - } - ImGui::SameLine(); + // Save Settings Button + if (uiIcons.saveSettings.texture) { + if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { + globals::state->Save(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Save Settings"); } + ImGui::SameLine(); + } - // Load Settings Button - if (uiIcons.loadSettings.texture) { - if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Load Settings"); - } - ImGui::SameLine(); + // Load Settings Button + if (uiIcons.loadSettings.texture) { + if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Load Settings"); } + ImGui::SameLine(); + } - // Clear Shader Cache Button - if (uiIcons.clearCache.texture) { - if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { - shaderCache->Clear(); - // any features should be added to shadercache's clear. - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Shader Cache\n\n" - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); - } - ImGui::SameLine(); + // Clear Shader Cache Button + if (uiIcons.clearCache.texture) { + if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { + shaderCache->Clear(); + // any features should be added to shadercache's clear. } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Shader Cache\n\n" + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + ImGui::SameLine(); + } - // Clear Disk Cache Button - if (uiIcons.clearDiskCache.texture) { - if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Disk Cache\n\n" - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); - } - } // Restore default style + // Clear Disk Cache Button + if (uiIcons.clearDiskCache.texture) { + if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Disk Cache\n\n" + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); + } + } // Restore default style ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index f6a2e1a331..c494aca19e 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -65,7 +65,7 @@ namespace Util { // Validate input parameters if (!device || !out_srv) { - logger::warn("LoadTextureFromFile: Invalid parameters - device: {}, out_srv: {}", + logger::warn("LoadTextureFromFile: Invalid parameters - device: {}, out_srv: {}", device ? "valid" : "null", out_srv ? "valid" : "null"); return false; } @@ -85,8 +85,8 @@ namespace Util return false; } - logger::debug("LoadTextureFromFile: Loaded image {}x{} with {} channels from {}", - image_width, image_height, channels_in_file, filename); // Create texture with simpler setup to avoid HRESULT 0x80070057 + logger::debug("LoadTextureFromFile: Loaded image {}x{} with {} channels from {}", + image_width, image_height, channels_in_file, filename); // Create texture with simpler setup to avoid HRESULT 0x80070057 D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; From 248e4a55ae98efebc46e7cc767ae6f2171b723dc Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 17:31:32 +1000 Subject: [PATCH 17/22] Licence tweaks --- README.md | 4 ++-- .../CommunityShaders/Icons/LICENSE.txt | 21 ------------------- 2 files changed, 2 insertions(+), 23 deletions(-) delete mode 100644 package/Interface/CommunityShaders/Icons/LICENSE.txt diff --git a/README.md b/README.md index f57edd25a0..19d76b3f50 100644 --- a/README.md +++ b/README.md @@ -148,5 +148,5 @@ See LICENSE within each directory; if none, it's [Default](#default) ### Icons -- [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) -- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not subject to the GPL-3.0 license and may only be used in unmodified form for local use. No trademark license is granted for the logo's use. +- [Microsoft Icons](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/) are subject to the [MIT License](package/Interface/CommunityShaders/Icons/Microsoft%20Icons/LICENSE) https://github.com/microsoft/fluentui-system-icons +- [Community Shaders Logo](package/Interface/CommunityShaders/Icons/Community%20Shaders%20Logo/) is not covered by the GPL-3.0 license. It is provided solely for personal use (e.g., building from source) and may only be used in unmodified form. There is no license for any other purpose or to distribute the logo. No trademark license is granted for the logo. Any use not expressly permitted is prohibited without the express written consent of the Community Shaders team. diff --git a/package/Interface/CommunityShaders/Icons/LICENSE.txt b/package/Interface/CommunityShaders/Icons/LICENSE.txt deleted file mode 100644 index bc9c36b28f..0000000000 --- a/package/Interface/CommunityShaders/Icons/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Microsoft Corporation - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. From 7a0ca4b7e4cad0a31a41379dc7c75b7350d870ee Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 17:58:39 +1000 Subject: [PATCH 18/22] Comment clean & Mipmap fixes --- src/Utils/UI.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index c494aca19e..a9d080ffde 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -84,9 +84,9 @@ namespace Util logger::warn("LoadTextureFromFile: Failed to load image data from {}", filename); return false; } - + // Creates Textures for Icons with Mipmapping to support high DPI displays. logger::debug("LoadTextureFromFile: Loaded image {}x{} with {} channels from {}", - image_width, image_height, channels_in_file, filename); // Create texture with simpler setup to avoid HRESULT 0x80070057 + image_width, image_height, channels_in_file, filename); D3D11_TEXTURE2D_DESC desc; ZeroMemory(&desc, sizeof(desc)); desc.Width = image_width; @@ -97,8 +97,8 @@ namespace Util desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; desc.Usage = D3D11_USAGE_DEFAULT; - desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; - desc.MiscFlags = 0; // Remove mipmap generation for now + desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + desc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; desc.CPUAccessFlags = 0; ID3D11Texture2D* pTexture = nullptr; @@ -113,7 +113,6 @@ namespace Util stbi_image_free(image_data); return false; } - // Create simple shader resource view hr = device->CreateShaderResourceView(pTexture, nullptr, out_srv); if (FAILED(hr) || !*out_srv) { @@ -123,6 +122,14 @@ namespace Util *out_srv = nullptr; return false; } + + // Generate mipmaps for better icon quality at different scales + ID3D11DeviceContext* context = nullptr; + device->GetImmediateContext(&context); + if (context) { + context->GenerateMips(*out_srv); + context->Release(); + } // Success - clean up intermediate resources pTexture->Release(); stbi_image_free(image_data); From f239dc4a4555c9ac608e5c81f1874b34d64287ef Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 18:04:35 +1000 Subject: [PATCH 19/22] Texture memory leak cleanup --- src/Utils/UI.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index a9d080ffde..22f094f2bd 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -151,17 +151,26 @@ namespace Util logger::warn("InitializeMenuIcons: D3D device is null"); return false; } - // Define path to icons std::string basePath = "Data\\Interface\\CommunityShaders\\Icons\\"; logger::info("InitializeMenuIcons: Loading icons from base path: {}", basePath); // Initialize all texture pointers to nullptr for safe cleanup - menu->uiIcons.saveSettings.texture = nullptr; - menu->uiIcons.loadSettings.texture = nullptr; - menu->uiIcons.clearCache.texture = nullptr; - menu->uiIcons.clearDiskCache.texture = nullptr; - menu->uiIcons.logo.texture = nullptr; + std::array texturePointers = { + &menu->uiIcons.saveSettings.texture, + &menu->uiIcons.loadSettings.texture, + &menu->uiIcons.clearCache.texture, + &menu->uiIcons.clearDiskCache.texture, + &menu->uiIcons.logo.texture + }; + + // Safely release existing textures + for (auto* texturePtr : texturePointers) { + if (*texturePtr) { + (*texturePtr)->Release(); + *texturePtr = nullptr; + } + } // Instead of failing completely if one icon fails, try to load each one individually bool anyIconLoaded = false; From 3400e533d17f9940bc6b408ca7d63f602db97ac0 Mon Sep 17 00:00:00 2001 From: David Kehoe Date: Mon, 16 Jun 2025 19:00:50 +1000 Subject: [PATCH 20/22] fixes --- src/Menu.cpp | 40 ++++++++++++++++++++++------------------ src/Utils/UI.cpp | 3 --- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index 07d78a8575..0b22b1a7d1 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -294,7 +294,6 @@ void Menu::DrawSettings() auto shaderCache = globals::shaderCache; const float iconSize = 48.0f; const ImVec2 buttonSize(iconSize, iconSize); // No padding for header icons - // Check if we can show icons - require setting enabled and at least some icons loaded bool canShowIcons = settings.Theme.ShowActionIcons && (uiIcons.saveSettings.texture || @@ -311,19 +310,22 @@ void Menu::DrawSettings() uiIcons.clearDiskCache.texture ? "OK" : "NULL", uiIcons.logo.texture ? "OK" : "NULL"); } - // Begin a layout with title on the left and buttons on the right (only if we have icons) - if (canShowIcons && ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { + + // Always show logo if available, regardless of action icons setting + bool showLogo = uiIcons.logo.texture != nullptr; + + // Begin a layout - with or without action buttons depending on settings + if ((showLogo || canShowIcons) && ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Buttons", ImGuiTableColumnFlags_WidthFixed); ImGui::TableNextColumn(); // Title on the left with logo if (!ImGui::IsWindowDocked()) { - // Calculate logo size with proper scaling - const float textScaleFactor = 1.5f; - const float logoHeightScale = 1.25f; + const float textScaleFactor = 1.7f; + const float logoHeightScale = 1.3f; const float titleHeight = ImGui::GetFontSize() * logoHeightScale; // Always display logo if texture is available - if (uiIcons.logo.texture) { + if (showLogo) { float logoAspectRatio = uiIcons.logo.size.x / uiIcons.logo.size.y; ImVec2 logoSize(titleHeight * logoAspectRatio, titleHeight); @@ -345,12 +347,13 @@ void Menu::DrawSettings() } } // Buttons on the right ImGui::TableNextColumn(); - - // Create a horizontal layout for the buttons and remove button borders - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect + // Only show action buttons if canShowIcons is true + if (canShowIcons) { + // Create a horizontal layout for the buttons and remove button borders + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4.0f, 0.0f)); // Tighter spacing for the icons + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); // Remove button borders + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect // Save Settings Button if (uiIcons.saveSettings.texture) { @@ -403,13 +406,15 @@ void Menu::DrawSettings() "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " "Only delete the Disk Cache manually if you are encountering issues. "); + } } - } // Restore default style - ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize - ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered + // Restore default style only if we pushed styles + ImGui::PopStyleVar(2); // Pop both style variables: ItemSpacing and FrameBorderSize + ImGui::PopStyleColor(2); // Pop both style colors: Button and ButtonHovered + } // End of canShowIcons action buttons section ImGui::EndTable(); - } else if (!canShowIcons) { + } else if (!(showLogo || canShowIcons)) { // No icons available - show just the title without the table layout if (!ImGui::IsWindowDocked()) { ImGui::SetWindowFontScale(1.5f); @@ -419,7 +424,6 @@ void Menu::DrawSettings() } // First separator - always shown if (!ImGui::IsWindowDocked()) { - ImGui::Spacing(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal, 3.0f); ImGui::Spacing(); } diff --git a/src/Utils/UI.cpp b/src/Utils/UI.cpp index 22f094f2bd..d905c54a1e 100644 --- a/src/Utils/UI.cpp +++ b/src/Utils/UI.cpp @@ -325,9 +325,6 @@ namespace Util ImGui::TextWrapped("%s", description); ImGui::Spacing(); } - - // Note: For this simplified version, we don't use TreeNode - // The sections are always expanded in FeatureIssues UI } SectionWrapper::~SectionWrapper() From 7504d4f542ba9640d625e7eda0f928983e40a67d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:01:17 +0000 Subject: [PATCH 21/22] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Menu.cpp | 92 ++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Menu.cpp b/src/Menu.cpp index 0b22b1a7d1..f7f48653d2 100644 --- a/src/Menu.cpp +++ b/src/Menu.cpp @@ -313,7 +313,7 @@ void Menu::DrawSettings() // Always show logo if available, regardless of action icons setting bool showLogo = uiIcons.logo.texture != nullptr; - + // Begin a layout - with or without action buttons depending on settings if ((showLogo || canShowIcons) && ImGui::BeginTable("##HeaderLayout", 2, ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("Title", ImGuiTableColumnFlags_WidthStretch); @@ -355,57 +355,57 @@ void Menu::DrawSettings() ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); // Transparent button background ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.7f, 0.7f, 0.2f)); // Subtle hover effect - // Save Settings Button - if (uiIcons.saveSettings.texture) { - if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { - globals::state->Save(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Save Settings"); + // Save Settings Button + if (uiIcons.saveSettings.texture) { + if (ImGui::ImageButton("##SaveSettingsBtn", uiIcons.saveSettings.texture, buttonSize)) { + globals::state->Save(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Save Settings"); + } + ImGui::SameLine(); } - ImGui::SameLine(); - } - // Load Settings Button - if (uiIcons.loadSettings.texture) { - if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { - globals::state->Load(); - globals::features::llf::particleLights->GetConfigs(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text("Load Settings"); + // Load Settings Button + if (uiIcons.loadSettings.texture) { + if (ImGui::ImageButton("##LoadSettingsBtn", uiIcons.loadSettings.texture, buttonSize)) { + globals::state->Load(); + globals::features::llf::particleLights->GetConfigs(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text("Load Settings"); + } + ImGui::SameLine(); } - ImGui::SameLine(); - } - // Clear Shader Cache Button - if (uiIcons.clearCache.texture) { - if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { - shaderCache->Clear(); - // any features should be added to shadercache's clear. - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Shader Cache\n\n" - "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " - "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " - "This is only needed for hot-loading shaders for development purposes. "); + // Clear Shader Cache Button + if (uiIcons.clearCache.texture) { + if (ImGui::ImageButton("##ClearShaderCacheBtn", uiIcons.clearCache.texture, buttonSize)) { + shaderCache->Clear(); + // any features should be added to shadercache's clear. + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Shader Cache\n\n" + "The Shader Cache is the collection of compiled shaders which replace the vanilla shaders at runtime. " + "Clearing the shader cache will mean that shaders are recompiled only when the game re-encounters them. " + "This is only needed for hot-loading shaders for development purposes. "); + } + ImGui::SameLine(); } - ImGui::SameLine(); - } - // Clear Disk Cache Button - if (uiIcons.clearDiskCache.texture) { - if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { - shaderCache->DeleteDiskCache(); - } - if (auto _tt = Util::HoverTooltipWrapper()) { - ImGui::Text( - "Clear Disk Cache\n\n" - "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " - "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " - "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " - "Only delete the Disk Cache manually if you are encountering issues. "); + // Clear Disk Cache Button + if (uiIcons.clearDiskCache.texture) { + if (ImGui::ImageButton("##ClearDiskCacheBtn", uiIcons.clearDiskCache.texture, buttonSize)) { + shaderCache->DeleteDiskCache(); + } + if (auto _tt = Util::HoverTooltipWrapper()) { + ImGui::Text( + "Clear Disk Cache\n\n" + "The Disk Cache is a collection of compiled shaders on disk, which are automatically created when shaders are added to the Shader Cache. " + "If you do not have a Disk Cache, or it is outdated or invalid, you will see \"Compiling Shaders\" in the upper-left corner. " + "After this has completed you will no longer see this message apart from when loading from the Disk Cache. " + "Only delete the Disk Cache manually if you are encountering issues. "); } } // Restore default style only if we pushed styles From c452cbea0b4c5a561097a48ac2aa863c2a97065d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:19:35 +0000 Subject: [PATCH 22/22] =?UTF-8?q?style:=20=F0=9F=8E=A8=20apply=20pre-commi?= =?UTF-8?q?t.ci=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automated formatting by clang-format, prettier, and other hooks. See https://pre-commit.ci for details. --- src/Utils/UI.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Utils/UI.h b/src/Utils/UI.h index 01b585c204..e75088e05a 100644 --- a/src/Utils/UI.h +++ b/src/Utils/UI.h @@ -7,7 +7,6 @@ struct ID3D11ShaderResourceView; struct ImVec2; class Menu; - namespace Util { // Text rendering constants @@ -118,7 +117,6 @@ namespace Util bool PercentageSlider(const char* label, float* data, float lb = 0.f, float ub = 100.f, const char* format = "%.1f %%"); ImVec2 GetNativeViewportSizeScaled(float scale); - // Icon loading functions // `device` must remain alive for the SRV lifetime. Caller owns *out_srv and must `Release()` it. bool LoadTextureFromFile(ID3D11Device* device, @@ -147,7 +145,6 @@ namespace Util */ void DrawSectionHeader(const char* sectionName, bool useWhiteText = false); - class PerformanceOverlay { public: