diff --git a/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 b/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 new file mode 100644 index 00000000..6c17dd6c --- /dev/null +++ b/functions/private/Initialize-WinUtilTaskbarOverlayAssets.ps1 @@ -0,0 +1,21 @@ +function Initialize-WinUtilTaskbarOverlayAssets { + param( + [bool]$IncludeLogo = $true, + [bool]$IncludeStatusAssets = $true + ) + + if ($IncludeLogo -and -not $sync["logorender"]) { + $sync["logorender"] = (Invoke-WinUtilAssets -Type "Logo" -Size 90 -Render) + Write-WinUtilPerformanceCheckpoint -Name "Taskbar logo asset rendered" + } + + if ($IncludeStatusAssets -and -not $sync["checkmarkrender"]) { + $sync["checkmarkrender"] = (Invoke-WinUtilAssets -Type "checkmark" -Size 512 -Render) + Write-WinUtilPerformanceCheckpoint -Name "Taskbar checkmark asset rendered" + } + + if ($IncludeStatusAssets -and -not $sync["warningrender"]) { + $sync["warningrender"] = (Invoke-WinUtilAssets -Type "warning" -Size 512 -Render) + Write-WinUtilPerformanceCheckpoint -Name "Taskbar warning asset rendered" + } +} diff --git a/functions/private/Invoke-WinUtilAssets.ps1 b/functions/private/Invoke-WinUtilAssets.ps1 index 6907909e..2239e8db 100644 --- a/functions/private/Invoke-WinUtilAssets.ps1 +++ b/functions/private/Invoke-WinUtilAssets.ps1 @@ -5,6 +5,17 @@ function Invoke-WinUtilAssets { [switch]$render ) + if ($render -and $null -ne $sync) { + if ($null -eq $sync.RenderedAssetCache) { + $sync.RenderedAssetCache = @{} + } + + $cacheKey = "$(([string]$type).ToLowerInvariant())|$Size" + if ($sync.RenderedAssetCache.ContainsKey($cacheKey)) { + return $sync.RenderedAssetCache[$cacheKey] + } + } + # Create the Viewbox and set its size $LogoViewbox = New-Object Windows.Controls.Viewbox $LogoViewbox.Width = $Size @@ -191,6 +202,13 @@ C 21.36,47.14 28.67,50.71 30.01,52.63 $bitmapImage.StreamSource = $imageStream $bitmapImage.CacheOption = [Windows.Media.Imaging.BitmapCacheOption]::OnLoad $bitmapImage.EndInit() + if ($bitmapImage.CanFreeze) { + $bitmapImage.Freeze() + } + + if ($null -ne $sync -and $sync.ContainsKey("RenderedAssetCache")) { + $sync.RenderedAssetCache[$cacheKey] = $bitmapImage + } return $bitmapImage } else { diff --git a/functions/private/Set-WinUtilTaskbarItem.ps1 b/functions/private/Set-WinUtilTaskbarItem.ps1 index 6eeb7674..ea2b6d43 100644 --- a/functions/private/Set-WinUtilTaskbarItem.ps1 +++ b/functions/private/Set-WinUtilTaskbarItem.ps1 @@ -61,12 +61,21 @@ function Set-WinUtilTaskbaritem { if ($overlay) { switch ($overlay) { 'logo' { + if (-not $sync["logorender"]) { + Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $true -IncludeStatusAssets $false + } $sync["Form"].taskbarItemInfo.Overlay = $sync["logorender"] } 'checkmark' { + if (-not $sync["checkmarkrender"]) { + Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true + } $sync["Form"].taskbarItemInfo.Overlay = $sync["checkmarkrender"] } 'warning' { + if (-not $sync["warningrender"]) { + Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true + } $sync["Form"].taskbarItemInfo.Overlay = $sync["warningrender"] } 'None' { diff --git a/pester/assets.Tests.ps1 b/pester/assets.Tests.ps1 new file mode 100644 index 00000000..028654f2 --- /dev/null +++ b/pester/assets.Tests.ps1 @@ -0,0 +1,42 @@ +#=========================================================================== +# Tests - Asset rendering +#=========================================================================== + +BeforeAll { + $script:repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +} + +Describe "Rendered asset caching" { + It "caches rendered bitmap assets by type and size" { + $assetScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Invoke-WinUtilAssets.ps1") -Raw + + $assetScript | Should -Match 'RenderedAssetCache' + $assetScript | Should -Match '\$cacheKey = "\$\(\(\[string\]\$type\)\.ToLowerInvariant\(\)\)\|\$Size"' + $assetScript | Should -Match 'return \$sync\.RenderedAssetCache\[\$cacheKey\]' + $assetScript | Should -Match '\$sync\.RenderedAssetCache\[\$cacheKey\] = \$bitmapImage' + } + + It "renders only the logo overlay before first paint and defers status overlays" { + $mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw + + $mainScript | Should -Match 'Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo \$true -IncludeStatusAssets \$false' + $mainScript | Should -Match 'Dispatcher\.BeginInvoke\(\[System\.Windows\.Threading\.DispatcherPriority\]::Background, \[action\]\{ Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo \$false -IncludeStatusAssets \$true \}' + $mainScript | Should -Not -Match '\$sync\["checkmarkrender"\] = \(Invoke-WinUtilAssets -Type "checkmark"' + $mainScript | Should -Not -Match '\$sync\["warningrender"\] = \(Invoke-WinUtilAssets -Type "warning"' + } + + It "lazily creates taskbar overlays before assigning them" { + $taskbarScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Set-WinUtilTaskbarItem.ps1") -Raw + + $taskbarScript | Should -Match 'Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo \$true -IncludeStatusAssets \$false' + $taskbarScript | Should -Match 'Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo \$false -IncludeStatusAssets \$true' + } + + It "records individual taskbar overlay render checkpoints" { + $overlayScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTaskbarOverlayAssets.ps1") -Raw + + $overlayScript | Should -Match 'Taskbar logo asset rendered' + $overlayScript | Should -Match 'Taskbar checkmark asset rendered' + $overlayScript | Should -Match 'Taskbar warning asset rendered' + } +} diff --git a/pester/performance.Tests.ps1 b/pester/performance.Tests.ps1 index 7fafd8bc..5982dba0 100644 --- a/pester/performance.Tests.ps1 +++ b/pester/performance.Tests.ps1 @@ -71,7 +71,8 @@ Describe "Startup performance checkpoints" { $mainScript = Get-Content -Path (Join-Path $script:repoRoot "scripts\main.ps1") -Raw $lazyTabScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTabContent.ps1") -Raw $runspaceScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilRunspacePool.ps1") -Raw - $startupText = "$mainScript`n$lazyTabScript`n$runspaceScript" + $overlayScript = Get-Content -Path (Join-Path $script:repoRoot "functions\private\Initialize-WinUtilTaskbarOverlayAssets.ps1") -Raw + $startupText = "$mainScript`n$lazyTabScript`n$runspaceScript`n$overlayScript" foreach ($checkpoint in @( "Runspace pool initialized", @@ -81,7 +82,7 @@ Describe "Startup performance checkpoints" { "Tweaks UI created", "Features UI created", "AppX UI created", - "Assets rendered", + "Taskbar logo asset rendered", "First content rendered" )) { $startupText | Should -Match ([regex]::Escape($checkpoint)) diff --git a/pester/xaml.Tests.ps1 b/pester/xaml.Tests.ps1 index b48b386f..4d4e8729 100644 --- a/pester/xaml.Tests.ps1 +++ b/pester/xaml.Tests.ps1 @@ -302,6 +302,7 @@ Describe "XAML and sync wiring" { "InitializedTabs", "PerformanceTrace", "PerformanceTraceEnabled", + "RenderedAssetCache", "ToggleStatusCache", "InstallAppAreaBorder", "InstallAppAreaScrollViewer", diff --git a/scripts/main.ps1 b/scripts/main.ps1 index b881fb42..5bd2987a 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -340,6 +340,7 @@ $sync["Form"].Add_ContentRendered({ $sync["Form"].Focus() $sync["Form"].Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Initialize-WinUtilRunspacePool | Out-Null }) | Out-Null + $sync["Form"].Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $false -IncludeStatusAssets $true }) | Out-Null }) # The SearchBarTimer is used to delay the search operation until the user has stopped typing for a short period @@ -384,11 +385,7 @@ $sync["Form"].Add_Loaded({ $NavLogoPanel = $sync["Form"].FindName("NavLogoPanel") $NavLogoPanel.Children.Add((Invoke-WinUtilAssets -Type "logo" -Size 25)) | Out-Null - -$sync["logorender"] = (Invoke-WinUtilAssets -Type "Logo" -Size 90 -Render) -$sync["checkmarkrender"] = (Invoke-WinUtilAssets -Type "checkmark" -Size 512 -Render) -$sync["warningrender"] = (Invoke-WinUtilAssets -Type "warning" -Size 512 -Render) -Write-WinUtilPerformanceCheckpoint -Name "Assets rendered" +Initialize-WinUtilTaskbarOverlayAssets -IncludeLogo $true -IncludeStatusAssets $false Set-WinUtilTaskbaritem -overlay "logo" diff --git a/speed-todo.md b/speed-todo.md index 9550328d..5295e69d 100644 --- a/speed-todo.md +++ b/speed-todo.md @@ -52,10 +52,10 @@ WinUtil currently does too much work before the first GUI paint. Work from the t ## P6 - Asset And Theme Cost -- [ ] Measure `Invoke-WinUtilAssets -Render` calls during startup. -- [ ] Cache rendered logo, checkmark, and warning images once per process. -- [ ] Defer non-critical taskbar overlay assets until after first render if measurable. -- [ ] Verify taskbar overlay success/error states still display correctly. +- [x] Measure `Invoke-WinUtilAssets -Render` calls during startup. +- [x] Cache rendered logo, checkmark, and warning images once per process. +- [x] Defer non-critical taskbar overlay assets until after first render if measurable. +- [x] Verify taskbar overlay success/error states still display correctly. ## P7 - Verification