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