Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Notable changes to Wisp are recorded here.

## 2.6.12 - 2026-10-16

- Add Prioritize HUD updates on the GPU, a HUD setting that is off by default so the game keeps the GPU; turn it on to give HUD updates priority when the GPU is under heavy load.
- Keep tune performance results with the current tune after leaving Forza's Tune screen, as long as the car, parts and settings are unchanged.
- Report feature pauses from Wisp Tools when they apply and when a policy is rejected or cannot be cached.
- Let Chat with Views messages carry a subject.
- Complete application exit promptly even when a renderer thread is busy.
- Report clip action problems on the Clips page and allow a retry.

## 2.6.11 - 2026-10-15

- Chat with Views in a compact popup with a chronological message history, replies and earlier messages.
Expand Down
4 changes: 2 additions & 2 deletions LICENSES/library-sources-distribution.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schemaVersion": 1,
"version": "2.6.11",
"version": "2.6.12",
"archive": {
"filename": "Wisp-2.6.11-library-sources.zip",
"filename": "Wisp-2.6.12-library-sources.zip",
"sourceVersion": "2.6.8",
"url": "https://github.com/Views2k/Wisp/releases/download/library-sources-2.6.8/Wisp-2.6.8-library-sources.zip",
"bytes": 879043478,
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
<a href="https://buymeacoffee.com/Views2k">Buy Me a Coffee</a>
</p>

## Wisp 2.6.11
## Wisp 2.6.12

Wisp 2.6.11 brings conversation with the developer into a compact chat popup, with message history, replies and optional diagnostic attachments. Available updates stay visible on the dashboard with a working Update action.
Wisp 2.6.12 adds a HUD setting that decides whether HUD updates take priority over the game on the GPU, off by default, keeps tune performance results with your current tune after you leave Forza's Tune screen, and lets you give a message to the developer a subject. Closing Wisp completes promptly and clip actions report problems on the Clips page.

[Clips](#clips) · [Tune](#tune) · [Lap review](#lap-review) · [Live map and lap delta](#live-map-and-lap-delta) · [Install](#install) · [2.6.11 release notes](docs/releases/Wisp-2.6.11-release-notes.md)
[Clips](#clips) · [Tune](#tune) · [Lap review](#lap-review) · [Live map and lap delta](#live-map-and-lap-delta) · [Install](#install) · [2.6.12 release notes](docs/releases/Wisp-2.6.12-release-notes.md)

### Clips

Expand Down
22 changes: 22 additions & 0 deletions docs/releases/Wisp-2.6.12-release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Wisp 2.6.12

[Download the latest Wisp release for Windows](https://github.com/Views2k/Wisp/releases/latest)

## HUD GPU priority

- Prioritize HUD updates on the GPU is a new HUD setting, off by default, so the game keeps the GPU to itself.
- Turn it on next to CPU rendering to give HUD updates priority when the GPU is under heavy load. It applies immediately.

## Tune performance results

- Performance results read from Forza's Tune screen stay with your current tune after you leave the screen.
- Refresh the current tune at any time and keep comparing braking, grip, acceleration and top speed.

## Chat with Views

- Add a subject to your messages so they are easy to find later.

## Reliability

- Closing Wisp completes promptly even when a renderer thread is busy.
- Clip actions report problems on the Clips page and can be retried.
2 changes: 1 addition & 1 deletion installer/Wisp.iss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#define MyAppName "Wisp"
#define MyAppVersion "2.6.11"
#define MyAppVersion "2.6.12"
#define MyAppDisplayVersion MyAppVersion
#define MyAppOutputVersion MyAppVersion
#define MyAppPublisher "Wisp"
Expand Down
9 changes: 6 additions & 3 deletions src/Wisp.App/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,11 +292,14 @@ protected override void OnExit(ExitEventArgs e)
{
_activationCancellation.Cancel();
_activationEvent?.Set();
_activationListener?.GetAwaiter().GetResult();
_activationListener?.Wait(TimeSpan.FromSeconds(2));
}

_controller?.DisposeAsync().AsTask().GetAwaiter().GetResult();
shutdownCompleted = true;
// Bounded: a worker stuck in a driver or file call must not keep the process
// alive after the user closed Wisp. An expired wait leaves the exit marker
// incomplete, which is what happened.
var disposal = _controller?.DisposeAsync().AsTask();
shutdownCompleted = disposal is null || disposal.Wait(TimeSpan.FromSeconds(10));
}
finally
{
Expand Down
3 changes: 3 additions & 0 deletions src/Wisp.App/AppController.FeatureControls.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ private bool TryUseHudProfiles(out string error)
internal Task ApplyFeatureControlsAsync(FeatureControlState state)
{
if (_disposed || !FeatureControls.Apply(state)) return Task.CompletedTask;
// Tools sees that this installation applied the revision, so a pause can be verified end to end.
SupplementaryObservations.Record("feature", "app", state.Disabled.Count > 0 ? "detected" : "none", "apply");
var recording = FeatureAllowed("run-recording");
var clipping = FeatureAllowed("clip-recording");
_tuneCapture?.SetFeatureAvailable(FeatureAllowed("tune-read"));
Expand Down Expand Up @@ -72,6 +74,7 @@ internal Task ApplyFeatureControlsAsync(FeatureControlState state)
_ = ConfigureClipShortcut(false, settings.ToggleShortcutEnabled, settings.ToggleShortcut);
_ = ConfigureClipShortcut(true, settings.SaveShortcutEnabled, settings.SaveShortcut);
}
ApplyHudGpuPriority();
FeatureControlsChanged?.Invoke(this, EventArgs.Empty);
UpdateShiftCueObservation();
Overlay?.RefreshFeatureControls();
Expand Down
22 changes: 22 additions & 0 deletions src/Wisp.App/AppController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,28 @@ public void SetCpuRenderingEnabled(bool enabled)
ViewModel.UpdateCpuRendering(Settings.CpuRenderingEnabled, saved);
}

public void SetHudGpuPriorityEnabled(bool enabled)
{
if (_disposed) return;
var previous = Settings.HudGpuPriority;
Settings.HudGpuPriority = enabled;
var saved = SaveSettings();
if (!saved) Settings.HudGpuPriority = previous;
PublishSupplementarySettings();
ApplyHudGpuPriority(saved);
}

// Effective only when the user turned it on and Tools has not paused it for this version.
internal void ApplyHudGpuPriority(bool saved = true)
{
var active = Settings.HudGpuPriority && FeatureAllowed("gpu-priority");
ViewModel.UpdateHudGpuPriority(Settings.HudGpuPriority, active,
!saved ? "Could not save the renderer option. Try again."
: Settings.HudGpuPriority && !active
? FeatureControls.Reason("gpu-priority") ?? "GPU priority is temporarily paused."
: "");
}

public async Task SetDebugLoggingEnabledAsync(bool enabled)
{
if (_disposed)
Expand Down
2 changes: 2 additions & 0 deletions src/Wisp.App/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ public double TorqueGaugeScale
public bool AutomaticApplicationUpdateChecks { get; set; } = true;
public DateTimeOffset? LastApplicationUpdateCheckUtc { get; set; }
public bool CpuRenderingEnabled { get; set; }
// Off by default: the game keeps the GPU unless the user chooses otherwise.
public bool HudGpuPriority { get; set; }
public bool DebugLoggingEnabled { get; set; }
public DateTimeOffset? DebugLoggingExpiresAtUtc { get; set; }
public string ColorTheme { get; set; } = AppColorThemes.DefaultName;
Expand Down
25 changes: 21 additions & 4 deletions src/Wisp.App/Clips/ClipsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,14 @@ private void NotifyDashboardNotice()
private void OnChanged([CallerMemberName] string? name = null) => PropertyChanged?.Invoke(this, new(name));
private ICommand Command(Func<Task> execute, Func<bool> canExecute)
{
var command = new ClipCommand(execute, canExecute); _commands.Add(command); return command;
var command = new ClipCommand(execute, canExecute, CommandFailed); _commands.Add(command); return command;
}
private void CommandFailed(Exception error)
{
if (_disposed) return;
ErrorText(error is IOException or UnauthorizedAccessException
? "That clip action could not reach the clip folder. Check the storage status, then try again."
: "That clip action did not complete. Try again.");
}
public void Dispose()
{
Expand All @@ -1178,11 +1185,21 @@ public void Dispose()
if (_thumbnails is IDisposable ownedThumbnails) ownedThumbnails.Dispose();
// Operations retain the captured token, never access a disposed token source.
}
private sealed class ClipCommand(Func<Task> action, Func<bool> canExecute) : ICommand
// A failing action reports on the page instead of ending the process, and a running
// action cannot be started twice (the Runs and Tunes commands behave the same way).
private sealed class ClipCommand(Func<Task> action, Func<bool> canExecute, Action<Exception> failed) : ICommand
{
private bool _running;
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => canExecute();
public async void Execute(object? parameter) { if (CanExecute(parameter)) await action(); }
public bool CanExecute(object? parameter) => !_running && canExecute();
public async void Execute(object? parameter)
{
if (!CanExecute(parameter)) return;
_running = true; Raise();
try { await action(); }
catch (Exception error) when (error is not OutOfMemoryException) { failed(error); }
finally { _running = false; Raise(); }
}
public void Raise() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
2 changes: 1 addition & 1 deletion src/Wisp.App/ControlPanelWindow.SupportInbox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private void OpenSupportChat()
if (_chatWindow is null)
{
_chatView = new SupportChatView(
(message, diagnostics) => Task.Run(() => app.CreateSupplementarySupportPreview("feedback", "Chat with Views", message, diagnostics)),
(title, message, diagnostics) => Task.Run(() => app.CreateSupplementarySupportPreview("feedback", title, message, diagnostics)),
(report, cancellation) => app.SubmitSupplementarySupportAsync(report, cancellation, chat: true),
(older, ids) => app.RefreshSupportChatAsync(older, ids));
var frame = _chatView.CreatePopupSurface(() => _chatWindow?.Hide(), () => _chatWindow?.DragMove());
Expand Down
7 changes: 7 additions & 0 deletions src/Wisp.App/ControlPanelWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public abstract partial class ControlPanelWindow : Window
private Button OverlayHotkeyCaptureButton => FindControl<Button>(nameof(OverlayHotkeyCaptureButton));
private TextBlock PortApplyFeedback => FindControl<TextBlock>(nameof(PortApplyFeedback));
private CheckBox CpuRenderingToggle => FindControl<CheckBox>(nameof(CpuRenderingToggle));
private CheckBox HudGpuPriorityToggle => FindControl<CheckBox>(nameof(HudGpuPriorityToggle));
private CheckBox DebugLoggingToggle => FindControl<CheckBox>(nameof(DebugLoggingToggle));
private Button DebugLogExportButton => FindControl<Button>(nameof(DebugLogExportButton));
private Button DebugLogDeleteButton => FindControl<Button>(nameof(DebugLogDeleteButton));
Expand Down Expand Up @@ -982,6 +983,12 @@ protected void CpuRenderingToggle_Click(object sender, RoutedEventArgs e)
CpuRenderingToggle.GetBindingExpression(System.Windows.Controls.Primitives.ToggleButton.IsCheckedProperty)?.UpdateTarget();
}

protected void HudGpuPriorityToggle_Click(object sender, RoutedEventArgs e)
{
_controller.SetHudGpuPriorityEnabled(HudGpuPriorityToggle.IsChecked == true);
HudGpuPriorityToggle.GetBindingExpression(System.Windows.Controls.Primitives.ToggleButton.IsCheckedProperty)?.UpdateTarget();
}

protected async void DebugLoggingToggle_Click(object sender, RoutedEventArgs e)
{
DebugLoggingToggle.IsEnabled = false;
Expand Down
16 changes: 16 additions & 0 deletions src/Wisp.App/DiagnosticsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ public sealed partial class DiagnosticsViewModel : INotifyPropertyChanged
private bool _automaticApplicationUpdateChecks;
private bool _cpuRenderingEnabled;
private string _cpuRenderingChangeStatus = "";
private bool _hudGpuPriorityEnabled;
private bool _hudGpuPriorityActive;
private string _hudGpuPriorityChangeStatus = "";
private bool _debugLoggingEnabled;
private string _debugLoggingStatus = "Detailed logging off";
private bool _tractionCueEnabled;
Expand Down Expand Up @@ -189,6 +192,8 @@ internal DiagnosticsViewModel(AppSettings settings, Func<long> shiftTimestamp)
_automaticApplicationUpdateChecks = settings.AutomaticApplicationUpdateChecks;
_cpuRenderingEnabled = settings.CpuRenderingEnabled;
ActiveCpuRendering = settings.CpuRenderingEnabled;
_hudGpuPriorityEnabled = settings.HudGpuPriority;
_hudGpuPriorityActive = settings.HudGpuPriority;
_debugLoggingEnabled = settings.DebugLoggingEnabled;
if (_debugLoggingEnabled && settings.DebugLoggingExpiresAtUtc is { } expiresAtUtc)
{
Expand Down Expand Up @@ -295,6 +300,17 @@ internal void UpdateCpuRendering(bool enabled, bool saved)
: enabled != ActiveCpuRendering ? "Restart Wisp to apply this change." : "";
}

// Enabled is the saved choice; Active is what the renderer applies (Tools can pause it).
public bool HudGpuPriorityEnabled { get => _hudGpuPriorityEnabled; private set => Set(ref _hudGpuPriorityEnabled, value); }
public bool HudGpuPriorityActive { get => _hudGpuPriorityActive; private set => Set(ref _hudGpuPriorityActive, value); }
public string HudGpuPriorityChangeStatus { get => _hudGpuPriorityChangeStatus; private set => Set(ref _hudGpuPriorityChangeStatus, value); }
internal void UpdateHudGpuPriority(bool enabled, bool active, string status)
{
HudGpuPriorityEnabled = enabled;
HudGpuPriorityActive = active;
HudGpuPriorityChangeStatus = status;
}

public bool DebugLoggingEnabled { get => _debugLoggingEnabled; private set => Set(ref _debugLoggingEnabled, value); }
public string DebugLoggingStatus { get => _debugLoggingStatus; private set => Set(ref _debugLoggingStatus, value); }
public string LateralGText { get => _lateralGText; private set => Set(ref _lateralGText, value); }
Expand Down
9 changes: 9 additions & 0 deletions src/Wisp.App/LegacyMainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,15 @@
Foreground="{DynamicResource FaintBrush}" TextWrapping="Wrap" FontSize="10" Margin="0,6,0,0" />
<TextBlock Text="{Binding CpuRenderingChangeStatus}" TextWrapping="Wrap" FontSize="11"
Foreground="{DynamicResource TextBrush}" Margin="0,4,0,0" AutomationProperties.LiveSetting="Polite" />
<CheckBox x:Name="HudGpuPriorityToggle" Content="Prioritize HUD updates on the GPU"
IsChecked="{Binding HudGpuPriorityEnabled, Mode=OneWay}" Click="HudGpuPriorityToggle_Click"
Style="{StaticResource ToggleSwitchStyle}" FontSize="11" Margin="0,18,0,0"
AutomationProperties.Name="Prioritize HUD updates on the GPU"
AutomationProperties.HelpText="Gives the HUD priority over the game on the GPU. Can cost game frame rate when the GPU is fully used." />
<TextBlock Text="Gives the HUD priority over the game on the GPU. Can cost game frame rate when the GPU is fully used."
Foreground="{DynamicResource MutedBrush}" TextWrapping="Wrap" FontSize="11" Margin="0,6,0,0" />
<TextBlock Text="{Binding HudGpuPriorityChangeStatus}" TextWrapping="Wrap" FontSize="11"
Foreground="{DynamicResource TextBrush}" Margin="0,4,0,0" AutomationProperties.LiveSetting="Polite" />
</StackPanel>
<Border BorderBrush="{DynamicResource StrokeBrush}" BorderThickness="0,1,0,0"
Margin="0,10,0,0" Padding="0,10,0,0">
Expand Down
12 changes: 12 additions & 0 deletions src/Wisp.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -1961,6 +1961,18 @@
<TextBlock Text="Please enable CPU rendering if you are experiencing lag or hitching in the overlay." Foreground="{DynamicResource MutedBrush}" TextWrapping="Wrap" FontSize="13" Margin="0,6,0,0"/>
<TextBlock Text="Applies to the Analogue tachometer. Exit Wisp from the tray icon and reopen it to apply. CPU usage may increase." Foreground="{DynamicResource FaintBrush}" TextWrapping="Wrap" FontSize="13" Margin="0,6,0,0"/>
<TextBlock Text="{Binding CpuRenderingChangeStatus}" TextWrapping="Wrap" FontSize="13" Foreground="{DynamicResource TextBrush}" Margin="0,4,0,0" AutomationProperties.LiveSetting="Polite"/>
<CheckBox
x:Name="HudGpuPriorityToggle"
Content="Prioritize HUD updates on the GPU"
IsChecked="{Binding HudGpuPriorityEnabled, Mode=OneWay}"
Click="HudGpuPriorityToggle_Click"
Style="{StaticResource ToggleSwitchStyle}"
FontSize="11"
Margin="0,18,0,0"
AutomationProperties.Name="Prioritize HUD updates on the GPU"
AutomationProperties.HelpText="Gives the HUD priority over the game on the GPU. Can cost game frame rate when the GPU is fully used." />
<TextBlock Text="Gives the HUD priority over the game on the GPU. Can cost game frame rate when the GPU is fully used." Foreground="{DynamicResource MutedBrush}" TextWrapping="Wrap" FontSize="13" Margin="0,6,0,0"/>
<TextBlock Text="{Binding HudGpuPriorityChangeStatus}" TextWrapping="Wrap" FontSize="13" Foreground="{DynamicResource TextBrush}" Margin="0,4,0,0" AutomationProperties.LiveSetting="Polite"/>
</StackPanel>
</StackPanel>
</local:OrbitSurface>
Expand Down
Loading
Loading