1 //
2 // Copyright (c) 2010-2022 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using Antmicro.Renode.Backends.Video;
9 using Antmicro.Renode.Peripherals.Video;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Peripherals.Input;
12 using Xwt;
13 using Antmicro.Renode.Utilities;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Migrant;
16 using System.IO;
17 using Xwt.Drawing;
18 using System.Collections.Generic;
19 using Antmicro.Renode.Peripherals;
20 using System;
21 using Antmicro.Renode.UI;
22 
23 namespace Antmicro.Renode.Extensions.Analyzers.Video
24 {
25     [Transient]
26     public class VideoAnalyzer : GUIPeripheralBackendAnalyzer<VideoBackend>, IExternal, IConnectable<IPointerInput>, IConnectable<IKeyboard>
27     {
28         public override Widget Widget { get { return analyserWidget; } }
29 
AttachTo(IKeyboard keyboardToAttach)30         public void AttachTo(IKeyboard keyboardToAttach)
31         {
32             displayWidget.AttachTo(keyboardToAttach);
33         }
34 
AttachTo(IPointerInput inputToAttach)35         public void AttachTo(IPointerInput inputToAttach)
36         {
37             displayWidget.AttachTo(inputToAttach);
38         }
39 
DetachFrom(IPointerInput inputToDetach)40         public void DetachFrom(IPointerInput inputToDetach)
41         {
42             displayWidget.DetachFrom(inputToDetach);
43         }
44 
DetachFrom(IKeyboard keyboardToDetach)45         public void DetachFrom(IKeyboard keyboardToDetach)
46         {
47             displayWidget.DetachFrom(keyboardToDetach);
48         }
49 
OnAttach(VideoBackend backend)50         protected override void OnAttach(VideoBackend backend)
51         {
52             var videoPeripheral = (AutoRepaintingVideo)backend.Video;
53             element = videoPeripheral;
54             lastRewrite = CustomDateTime.Now;
55             EnsureAnalyserWidget();
56 
57             videoPeripheral.ConfigurationChanged += (w, h, f, e) => ApplicationExtensions.InvokeInUIThread(() => displayWidget.SetDisplayParameters(w, h, f, e));
58             videoPeripheral.FrameRendered += (f) =>
59             {
60                 ApplicationExtensions.InvokeInUIThread(() =>
61                 {
62                     displayWidget.DrawFrame(f);
63                     snapshotButton.Sensitive = true;
64                 });
65             };
66 
67             displayWidget.InputAttached += i =>
68             {
69                 if (i is IKeyboard)
70                 {
71                     keyboardsComboBox.SelectedItem = i;
72                 }
73                 else if (i is IPointerInput)
74                 {
75                     pointersComboBox.SelectedItem = i;
76                 }
77             };
78 
79             if(backend.Frame != null)
80             {
81                 // this must be called after setting `ConfigurationChanged` event;
82                 // otherwise the frame set here would be overrwritten by a new, empty, instance
83                 ApplicationExtensions.InvokeInUIThreadAndWait(() =>
84                 {
85                     displayWidget.SetDisplayParameters(backend.Width, backend.Height, backend.Format, backend.Endianess);
86                     displayWidget.DrawFrame(backend.Frame);
87                 });
88             }
89         }
90 
EnsureAnalyserWidget()91         private void EnsureAnalyserWidget()
92         {
93             var emulation = EmulationManager.Instance.CurrentEmulation;
94 
95             if(analyserWidget == null)
96             {
97                 // create display widget and attach it to the emulation
98                 displayWidget = new FrameBufferDisplayWidget();
99 
100                 var keyboards = FindKeyboards();
101                 var pointers = FindPointers();
102 
103                 // create other widgets
104                 var displayModeComboBox = new ComboBox();
105                 displayModeComboBox.Items.Add(DisplayMode.Stretch);
106                 displayModeComboBox.Items.Add(DisplayMode.Fit);
107                 displayModeComboBox.Items.Add(DisplayMode.Center);
108 
109                 displayModeComboBox.SelectionChanged += (sender, e) => displayWidget.Mode = (DisplayMode)displayModeComboBox.SelectedItem;
110                 ApplicationExtensions.InvokeInUIThread(() =>
111                 {
112                     displayModeComboBox.SelectedIndex = 1;
113                 });
114 
115                 keyboardsComboBox = new ComboBox();
116                 if(keyboards != null)
117                 {
118                     foreach(var kbd in keyboards)
119                     {
120                         string name;
121                         emulation.TryGetEmulationElementName(kbd, out name);
122                         keyboardsComboBox.Items.Add(kbd, name);
123                     }
124                     keyboardsComboBox.SelectionChanged += (sender, e) =>
125                         emulation.Connector.Connect((IKeyboard)keyboardsComboBox.SelectedItem, displayWidget);
126                 }
127                 keyboardsComboBox.SelectedIndex = 0;
128 
129                 pointersComboBox = new ComboBox();
130                 if(pointers != null)
131                 {
132                     foreach(var ptr in pointers)
133                     {
134                         string name;
135                         emulation.TryGetEmulationElementName(ptr, out name);
136                         pointersComboBox.Items.Add(ptr, name);
137                     }
138                     pointersComboBox.SelectionChanged += (sender, e) =>
139                         emulation.Connector.Connect((IPointerInput)pointersComboBox.SelectedItem, displayWidget);
140                 }
141                 pointersComboBox.SelectedIndex = 0;
142 
143                 snapshotButton = new Button("Take screenshot!") { Sensitive = false };
144                 snapshotButton.Clicked += (sender, e) =>
145                 {
146                     var screenshotDir = Path.Combine(Emulator.UserDirectoryPath, "screenshots");
147                     Directory.CreateDirectory(screenshotDir);
148                     var filename = Path.Combine(screenshotDir, string.Format("screenshot-{0:yyyy_M_d_HHmmss}.png", CustomDateTime.Now));
149                     displayWidget.SaveCurrentFrameToFile(filename);
150                     MessageDialog.ShowMessage("Screenshot saved in {0}".FormatWith(filename));
151                 };
152 
153                 var configurationPanel = new HBox();
154                 configurationPanel.PackStart(new Label("Display mode:"));
155                 configurationPanel.PackStart(displayModeComboBox);
156                 configurationPanel.PackStart(new Label(), true);
157                 configurationPanel.PackStart(new Label("Keyboard:"));
158                 configurationPanel.PackStart(keyboardsComboBox);
159                 configurationPanel.PackStart(new Label("Pointer:"));
160                 configurationPanel.PackStart(pointersComboBox);
161                 configurationPanel.PackStart(new Label(), true);
162                 configurationPanel.PackStart(snapshotButton);
163 
164                 var svc = new VBox();
165                 svc.PackStart(configurationPanel);
166                 svc.PackStart(new Label());
167                 var sv = new ScrollView();
168                 sv.Content = svc;
169                 sv.HeightRequest = 50;
170                 sv.BorderVisible = false;
171                 sv.VerticalScrollPolicy = ScrollPolicy.Never;
172 
173                 var summaryVB = new HBox();
174                 var resolutionL = new Label("unknown");
175                 displayWidget.DisplayParametersChanged += (w, h, f) => ApplicationExtensions.InvokeInUIThread(() => resolutionL.Text = string.Format("{0} x {1} ({2})", w, h, f));
176                 summaryVB.PackStart(new Label("Resolution: "));
177                 summaryVB.PackStart(resolutionL);
178                 summaryVB.PackStart(new Label(), true);
179                 var cursorPositionL = new Label("unknown");
180                 displayWidget.PointerMoved += (x, y) => ApplicationExtensions.InvokeInUIThread(() => cursorPositionL.Text = (x == -1 && y == -1) ? "unknown" : string.Format("{0} x {1}", x, y));
181                 summaryVB.PackStart(new Label("Cursor position: "));
182                 summaryVB.PackStart(cursorPositionL);
183                 summaryVB.PackStart(new Label(), true);
184                 summaryVB.PackStart(new Label("Framerate: "));
185                 framerateL = new Label("unknown");
186                 displayWidget.FrameDrawn += RefreshFramerate;
187                 summaryVB.PackStart(framerateL);
188 
189                 var vbox = new VBox();
190                 vbox.PackStart(sv);
191                 vbox.PackStart(displayWidget, true, true);
192                 vbox.PackStart(summaryVB);
193                 analyserWidget = vbox;
194             }
195         }
196 
FindKeyboards()197         private IEnumerable<IKeyboard> FindKeyboards()
198         {
199             return EmulationManager.Instance.CurrentEmulation.TryGetMachineForPeripheral(element, out var machine) ? machine.GetPeripheralsOfType<IKeyboard>() : null;
200         }
201 
FindPointers()202         private IEnumerable<IPointerInput> FindPointers()
203         {
204             return EmulationManager.Instance.CurrentEmulation.TryGetMachineForPeripheral(element, out var machine) ? machine.GetPeripheralsOfType<IPointerInput>() : null;
205         }
206 
RefreshFramerate()207         private void RefreshFramerate()
208         {
209             var now = CustomDateTime.Now;
210             if(prev == null)
211             {
212                 prev = now;
213                 return;
214             }
215 
216             if((now - lastRewrite).TotalSeconds > 1)
217             {
218                 var framerate = (int)(1 / (now - prev).Value.TotalSeconds);
219                 var deviation = (lastOffendingFramerate > framerate ? (float)lastOffendingFramerate / framerate : (float)framerate / lastOffendingFramerate) - 1.0;
220                 if(framerate >= HighFramerateThreshold && deviation > OffendingFramerateDeviation)
221                 {
222                     lastOffendingFramerate = framerate;
223                     this.Log(LogLevel.Info, "The framebuffer fps is very high and can cause high CPU usage. Consider decreasing FramesPerVirtualSecond in video peripheral.");
224                 }
225                 if(framerate <= LowFramerateThreshold && Math.Abs(lastOffendingFramerate - framerate) > MinimalFramerateDelta && deviation > OffendingFramerateDeviation)
226                 {
227                     lastOffendingFramerate = framerate;
228                     this.Log(LogLevel.Info, "The framebuffer fps is very low and can cause video playback to be choppy. Consider increasing FramesPerVirtualSecond in video peripheral.");
229                 }
230 
231                 framerateL.Text = string.Format("{0} fps", framerate);
232                 lastRewrite = now;
233             }
234             prev = now;
235         }
236 
237         private const int HighFramerateThreshold = 100;
238         private const int LowFramerateThreshold = 10;
239         private const int MinimalFramerateDelta = 5;
240         private const float OffendingFramerateDeviation = 0.4F;
241 
242         private FrameBufferDisplayWidget displayWidget;
243         private Widget analyserWidget;
244         private Button snapshotButton;
245         private ComboBox keyboardsComboBox;
246         private ComboBox pointersComboBox;
247         private IPeripheral element;
248         private Label framerateL;
249         private DateTime? prev;
250         private DateTime lastRewrite;
251         private int lastOffendingFramerate;
252     }
253 }
254 
255