/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2015-2016 Baldur Karlsson * Copyright (c) 2014 Crytek * * 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. ******************************************************************************/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Diagnostics; using System.Net; using System.Text; using System.Reflection; using System.Threading; using System.Windows.Forms; using System.Runtime.InteropServices; using WeifenLuo.WinFormsUI.Docking; using renderdocui.Code; using renderdoc; namespace renderdocui.Windows { public partial class MainWindow : Form, ILogViewerForm, ILogLoadProgressListener, IMessageFilter { public bool PreFilterMessage(ref Message m) { if (m.Msg == (int)Win32PInvoke.Win32Message.WM_MOUSEWHEEL) { int pos = m.LParam.ToInt32(); short x = (short)((pos >> 0) & 0xffff); short y = (short)((pos >> 16) & 0xffff); Win32PInvoke.POINT pt = new Win32PInvoke.POINT((int)x, (int)y); IntPtr wnd = Win32PInvoke.WindowFromPoint(pt); if (wnd != IntPtr.Zero && wnd != m.HWnd && Control.FromHandle(wnd) != null) { Win32PInvoke.SendMessage(wnd, m.Msg, m.WParam, m.LParam); return true; } return false; } return false; } private Core m_Core; private string m_InitFilename; private string m_InitRemoteHost; private uint m_InitRemoteIdent; private List m_LiveCaptures = new List(); private renderdocplugin.ReplayManagerPlugin m_ReplayHost = null; private string m_RemoteReplay = ""; private string InformationalVersion { get { var assembly = Assembly.GetExecutingAssembly(); var attrs = assembly.GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false); if (attrs != null && attrs.Length > 0) { AssemblyInformationalVersionAttribute attribute = (AssemblyInformationalVersionAttribute)attrs[0]; if (attribute != null) return attribute.InformationalVersion; } return ""; } } private string GitCommitHash { get { return InformationalVersion.Replace("-official", "").Replace("-beta", ""); } } private bool BetaVersion { get { return InformationalVersion.Contains("-beta"); } } private bool OfficialVersion { get { return InformationalVersion.Contains("-official"); } } private string BareVersionString { get { return Assembly.GetEntryAssembly().GetName().Version.ToString(2); } } private string VersionString { get { return "v" + Assembly.GetEntryAssembly().GetName().Version.ToString(2); } } public MainWindow(Core core, string initFilename, string remoteHost, uint remoteIdent, bool temp) { InitializeComponent(); Icon = global::renderdocui.Properties.Resources.icon; renderdocplugin.PluginHelpers.GetPlugins(); statusIcon.Text = ""; statusIcon.Image = null; statusText.Text = ""; SetTitle(); Application.AddMessageFilter(this); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); m_Core = core; m_InitFilename = initFilename; m_InitRemoteHost = remoteHost; m_InitRemoteIdent = remoteIdent; OwnTemporaryLog = temp; resolveSymbolsToolStripMenuItem.Enabled = false; resolveSymbolsToolStripMenuItem.Text = "Resolve Symbols"; m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); m_Core.AddLogViewer(this); m_Core.AddLogProgressListener(this); } private void MainWindow_Load(object sender, EventArgs e) { bool loaded = LoadLayout(0); CheckUpdates(); sendErrorReportToolStripMenuItem.Enabled = OfficialVersion || BetaVersion; // create default layout if layout failed to load if (!loaded) { m_Core.GetAPIInspector().Show(dockPanel); m_Core.GetEventBrowser().Show(m_Core.GetAPIInspector().Pane, DockAlignment.Top, 0.5); m_Core.GetPipelineStateViewer().Show(dockPanel); var bv = new BufferViewer(m_Core, true); bv.InitFromPersistString(""); bv.Show(dockPanel); var tv = m_Core.GetTextureViewer(); tv.InitFromPersistString(""); tv.Show(dockPanel); m_Core.GetTimelineBar().Show(dockPanel); if (m_Core.CaptureDialog == null) m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); m_Core.CaptureDialog.InjectMode = false; m_Core.CaptureDialog.Show(dockPanel); } PopulateRecentFiles(); PopulateRecentCaptures(); if (m_InitRemoteIdent != 0) { var live = new LiveCapture(m_Core, m_InitRemoteHost, m_InitRemoteIdent, this); ShowLiveCapture(live); } if (m_InitFilename.Length > 0) { LoadFromFilename(m_InitFilename); m_InitFilename = ""; } } #region ILogLoadProgressListener public void LogfileProgressBegin() { BeginInvoke(new Action(() => { statusProgress.Visible = true; })); } public void LogfileProgress(float f) { BeginInvoke(new Action(() => { if (statusProgress.Visible) { if (f <= 0.0f || f >= 0.999f) { statusProgress.Visible = false; statusText.Text = ""; statusIcon.Image = null; } else { statusProgress.Value = (int)(statusProgress.Maximum * f); } } })); } #endregion #region ILogViewerForm public void OnLogfileClosed() { statusText.Text = ""; statusIcon.Image = null; statusProgress.Visible = false; m_MessageTick.Dispose(); m_MessageTick = null; resolveSymbolsToolStripMenuItem.Enabled = false; resolveSymbolsToolStripMenuItem.Text = "Resolve Symbols"; if (m_ReplayHost != null) m_ReplayHost.CloseReplay(); m_ReplayHost = null; m_RemoteReplay = ""; SetTitle(); } private static void MessageCheck(object m) { if (!(m is MainWindow)) return; var me = (MainWindow)m; if (me.m_Core.LogLoaded) { me.m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { DebugMessage[] msgs = r.GetDebugMessages(); me.BeginInvoke(new Action(() => { if (msgs.Length > 0) { me.m_Core.AddMessages(msgs); me.m_Core.GetDebugMessages().RefreshMessageList(); } if (me.m_Core.UnreadMessageCount > 0) { me.m_MessageAlternate = !me.m_MessageAlternate; } else { me.m_MessageAlternate = false; } me.LogHasErrors = (me.m_Core.DebugMessages.Count > 0); })); }); } if (me == null) return; if (me.m_MessageTick != null) me.m_MessageTick.Change(500, System.Threading.Timeout.Infinite); } private System.Threading.Timer m_MessageTick = null; private bool m_MessageAlternate = false; private bool LogHasErrors { set { if (value == true) { statusIcon.Image = m_MessageAlternate ? null : global::renderdocui.Properties.Resources.delete; statusText.Text = String.Format("{0} loaded. Log has {1} errors, warnings or performance notes. " + "See the 'Log Errors and Warnings' window.", Path.GetFileName(m_Core.LogFileName), m_Core.DebugMessages.Count); if (m_Core.UnreadMessageCount > 0) { statusText.Text += String.Format(" {0} Unread.", m_Core.UnreadMessageCount); } } else { statusIcon.Image = global::renderdocui.Properties.Resources.tick; statusText.Text = String.Format("{0} loaded. No problems detected.", Path.GetFileName(m_Core.LogFileName)); } } } private void status_DoubleClick(object sender, EventArgs e) { m_Core.GetDebugMessages().Show(dockPanel); } public void OnLogfileLoaded() { LogHasErrors = (m_Core.DebugMessages.Count > 0); m_MessageTick = new System.Threading.Timer(MessageCheck, this as object, 500, System.Threading.Timeout.Infinite); statusProgress.Visible = false; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { bool hasResolver = r.HasCallstacks(); this.BeginInvoke(new Action(() => { resolveSymbolsToolStripMenuItem.Enabled = hasResolver; resolveSymbolsToolStripMenuItem.Text = hasResolver ? "Resolve Symbols" : "Resolve Symbols - None in log"; })); }); saveLogToolStripMenuItem.Enabled = true; SetTitle(); PopulateRecentFiles(); m_Core.GetEventBrowser().Focus(); } public void OnEventSelected(UInt32 eventID) { } #endregion #region Layout & Dock Container private void LoadCustomString(string persistString) { string[] parsedStrings = persistString.Split(new char[] { ',' }); if (parsedStrings.Length == 6 && parsedStrings[0] == "WinSize") { bool maximised = Convert.ToBoolean(parsedStrings[5]); Point location = new Point(Convert.ToInt32(parsedStrings[1]), Convert.ToInt32(parsedStrings[2])); Rectangle bounds = Screen.FromPoint(location).Bounds; if (location.X <= bounds.Left) location.X = bounds.Left + 100; if (location.X >= bounds.Right) location.X = bounds.Right - 100; if (location.Y <= bounds.Top) location.Y = bounds.Top + 100; if (location.Y >= bounds.Bottom) location.Y = bounds.Bottom - 100; Size winsize = new Size(Convert.ToInt32(parsedStrings[3]), Convert.ToInt32(parsedStrings[4])); winsize.Width = Math.Max(200, winsize.Width); winsize.Height = Math.Max(200, winsize.Height); SetBounds(location.X, location.Y, winsize.Width, winsize.Height); var desired = FormWindowState.Normal; if (maximised) desired = FormWindowState.Maximized; if(WindowState != desired) WindowState = desired; } } private string SaveCustomString() { var r = this.WindowState == FormWindowState.Maximized ? RestoreBounds : Bounds; return "WinSize," + r.X + "," + r.Y + "," + r.Width + "," + r.Height + "," + (this.WindowState == FormWindowState.Maximized); } private bool IsPersist(string persiststring, string typestring) { if (persiststring.Length < typestring.Length) return false; return persiststring.Substring(0, typestring.Length) == typestring; } private IDockContent GetContentFromPersistString(string persistString) { if (IsPersist(persistString, typeof(EventBrowser).ToString())) { var ret = m_Core.GetEventBrowser(); ret.InitFromPersistString(persistString); return ret; } else if (IsPersist(persistString, typeof(TextureViewer).ToString())) { var ret = m_Core.GetTextureViewer(); ret.InitFromPersistString(persistString); return ret; } else if (IsPersist(persistString, typeof(BufferViewer).ToString())) { var ret = new BufferViewer(m_Core, true); ret.InitFromPersistString(persistString); return ret; } else if (IsPersist(persistString, typeof(APIInspector).ToString())) return m_Core.GetAPIInspector(); else if (IsPersist(persistString, typeof(PipelineState.PipelineStateViewer).ToString())) { var ret = m_Core.GetPipelineStateViewer(); ret.InitFromPersistString(persistString); return ret; } else if (IsPersist(persistString, typeof(DebugMessages).ToString())) return m_Core.GetDebugMessages(); else if (IsPersist(persistString, typeof(TimelineBar).ToString())) return m_Core.GetTimelineBar(); else if (IsPersist(persistString, typeof(StatisticsViewer).ToString())) return m_Core.GetStatisticsViewer(); else if (IsPersist(persistString, typeof(Dialogs.PythonShell).ToString())) { return new Dialogs.PythonShell(m_Core); } else if (IsPersist(persistString, typeof(Dialogs.CaptureDialog).ToString())) { if (m_Core.CaptureDialog == null) m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); return m_Core.CaptureDialog; } else if (persistString != null && persistString.Length > 0) LoadCustomString(persistString); return null; } private string GetConfigPath(int layout) { string dir = Core.ConfigDirectory; string filename = "DefaultLayout.config"; if (layout > 0) { filename = "Layout" + layout.ToString() + ".config"; } return Path.Combine(dir, filename); } private bool LoadLayout(int layout) { string configFile = GetConfigPath(layout); if (File.Exists(configFile)) { int cnt = dockPanel.Contents.Count; for (int i = 0; i < cnt; i++) if(dockPanel.Contents.Count > 0) (dockPanel.Contents[0] as Form).Close(); try { dockPanel.LoadFromXml(configFile, new DeserializeDockContent(GetContentFromPersistString)); } catch (System.Xml.XmlException) { // file is invalid return false; } catch (InvalidOperationException) { // file is invalid return false; } return true; } return false; } private void SaveLayout(int layout) { string path = GetConfigPath(layout); try { Directory.CreateDirectory(Path.GetDirectoryName(path)); dockPanel.SaveAsXml(path, SaveCustomString()); } catch (System.Exception) { MessageBox.Show(String.Format("Error saving config file\n{0}\nNo config will be saved out.", path)); } } private void LoadSaveLayout(ToolStripItem c, bool save) { if (c.Tag is string) { int i = 0; if (int.TryParse((string)c.Tag, out i)) { if (save) SaveLayout(i); else LoadLayout(i); } } } private void saveLayout_Click(object sender, EventArgs e) { if (sender is ToolStripItem) LoadSaveLayout((ToolStripItem)sender, true); } private void loadLayout_Click(object sender, EventArgs e) { if (sender is ToolStripItem) LoadSaveLayout((ToolStripItem)sender, false); } private void SetTitle(string filename) { string prefix = ""; if (m_Core != null && m_Core.LogLoaded) { prefix = Path.GetFileName(filename); if (m_Core.APIProps.degraded) prefix += " !DEGRADED PERFORMANCE!"; if (m_RemoteReplay.Length > 0) prefix += String.Format(" (Remote replay on {0})", m_RemoteReplay); prefix += " - "; } Text = prefix + "RenderDoc "; if(OfficialVersion) Text += VersionString; else if(BetaVersion) Text += String.Format("{0}-beta - {1}", VersionString, GitCommitHash); else Text += String.Format("Unofficial release ({0} - {1})", VersionString, GitCommitHash); if (IsVersionMismatched()) Text += " - !! VERSION MISMATCH DETECTED !!"; } private void SetTitle() { SetTitle(m_Core != null ? m_Core.LogFileName : ""); } #endregion #region Capture & Log Loading private void LoadLogAsync(string filename, bool temporary) { if (m_Core.LogLoading) return; string driver = ""; bool support = StaticExports.SupportLocalReplay(filename, out driver); Thread thread = null; if (!m_Core.Config.ReplayHosts.ContainsKey(driver) && driver.Trim().Length > 0) m_Core.Config.ReplayHosts.Add(driver, ""); // if driver is empty something went wrong loading the log, let it be handled as usual // below. Otherwise prompt to replay remotely. if (driver.Length > 0 && (!support || m_Core.Config.ReplayHosts[driver].Length > 0)) { string remoteMessage = String.Format("This log was captured with {0}", driver); if(!support) remoteMessage += " and cannot be replayed locally.\n"; else remoteMessage += " and your settings say to replay this remotely.\n"; if (m_Core.Config.ReplayHosts[driver].Length == 0) remoteMessage += "Do you wish to select a remote host to replay on?\n\n" + "You can set up a default host for this driver on the next screen."; else remoteMessage += String.Format("Would you like to launch replay on remote host {0}?\n\n" + "You can change this default via Tools -> Manage Replay Devices.", m_Core.Config.ReplayHosts[driver]); DialogResult res = MessageBox.Show(remoteMessage, "Launch remote replay?", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation); if (res == DialogResult.Yes) { if (m_Core.Config.ReplayHosts[driver].Length == 0) { (new Dialogs.ReplayHostManager(m_Core, this)).ShowDialog(); if (m_Core.Config.ReplayHosts[driver].Length == 0) return; } m_RemoteReplay = m_Core.Config.ReplayHosts[driver]; var plugins = renderdocplugin.PluginHelpers.GetPlugins(); // search plugins for to find manager for this driver and launch replay foreach (var plugin in plugins) { var replayman = renderdocplugin.PluginHelpers.GetPluginInterface(plugin); if (replayman != null && replayman.GetTargetType() == driver) { var targets = replayman.GetOnlineTargets(); if (targets.Contains(m_RemoteReplay)) { replayman.RunReplay(m_RemoteReplay); // save replay connection so we can close replay m_ReplayHost = replayman; break; } } } thread = Helpers.NewThread(new ThreadStart(() => { string[] drivers = new string[0]; try { var dummy = StaticExports.CreateRemoteReplayConnection(m_RemoteReplay); drivers = dummy.RemoteSupportedReplays(); dummy.Shutdown(); } catch (ApplicationException ex) { string errmsg = "Unknown error message"; if (ex.Data.Contains("status")) errmsg = ((ReplayCreateStatus)ex.Data["status"]).Str(); MessageBox.Show(String.Format("Failed to fetch supported drivers on host {0}: {1}.\n\nCheck diagnostic log in Help menu for more details.", m_RemoteReplay, errmsg), "Error getting driver list", MessageBoxButtons.OK, MessageBoxIcon.Error); } // no drivers means we didn't connect if (drivers.Length == 0) { } else { bool found = false; foreach (var d in drivers) if (d == driver) found = true; if (!found) { MessageBox.Show(String.Format("Remote host {0} doesn't support {1}.", m_RemoteReplay, driver), "Unsupported remote replay", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { string[] proxies = new string[0]; try { var dummy = StaticExports.CreateRemoteReplayConnection("-"); proxies = dummy.LocalProxies(); dummy.Shutdown(); } catch (ApplicationException ex) { string errmsg = "Unknown error message"; if (ex.Data.Contains("status")) errmsg = ((ReplayCreateStatus)ex.Data["status"]).Str(); MessageBox.Show(String.Format("Failed to fetch local proxy drivers: {0}.\n\nCheck diagnostic log in Help menu for more details.", errmsg), "Error getting driver list", MessageBoxButtons.OK, MessageBoxIcon.Error); } if (proxies.Length > 0) { m_Core.Config.LocalProxy = Helpers.Clamp(m_Core.Config.LocalProxy, 0, proxies.Length - 1); m_Core.LoadLogfile(m_Core.Config.LocalProxy, m_RemoteReplay, filename, temporary); if (m_Core.LogLoaded) return; } } } // clean up. if (m_ReplayHost != null) m_ReplayHost.CloseReplay(); m_RemoteReplay = ""; BeginInvoke(new Action(() => { statusText.Text = ""; statusIcon.Image = null; statusProgress.Visible = false; })); })); } else { return; } } else { thread = Helpers.NewThread(new ThreadStart(() => m_Core.LoadLogfile(filename, temporary))); } thread.Start(); m_Core.Config.LastLogPath = Path.GetDirectoryName(filename); statusText.Text = "Loading " + filename + "..."; } public void PopulateRecentFiles() { while (recentFilesToolStripMenuItem.DropDownItems.Count > 0) { if (recentFilesToolStripMenuItem.DropDownItems[0] is ToolStripSeparator) break; recentFilesToolStripMenuItem.DropDownItems.RemoveAt(0); } recentFilesToolStripMenuItem.Enabled = false; int i = m_Core.Config.RecentLogFiles.Count; int idx = 0; foreach (var recentLog in m_Core.Config.RecentLogFiles) { var item = new ToolStripMenuItem("&" + i.ToString() + " " + recentLog, null, recentLogMenuItem_Click); item.Tag = idx; recentFilesToolStripMenuItem.DropDownItems.Insert(0, item); i--; idx++; recentFilesToolStripMenuItem.Enabled = true; } } private void PopulateRecentCaptures() { while (recentCapturesToolStripMenuItem.DropDownItems.Count > 0) { if (recentCapturesToolStripMenuItem.DropDownItems[0] is ToolStripSeparator) break; recentCapturesToolStripMenuItem.DropDownItems.RemoveAt(0); } recentCapturesToolStripMenuItem.Enabled = false; int i = m_Core.Config.RecentCaptureSettings.Count; int idx = 0; foreach (var recentCapture in m_Core.Config.RecentCaptureSettings) { var item = new ToolStripMenuItem("&" + i.ToString() + " " + recentCapture, null, recentCaptureMenuItem_Click); item.Tag = idx; recentCapturesToolStripMenuItem.DropDownItems.Insert(0, item); i--; idx++; recentCapturesToolStripMenuItem.Enabled = true; } } public bool OwnTemporaryLog = false; private bool SavedTemporaryLog = false; public void ShowLiveCapture(LiveCapture live) { m_LiveCaptures.Add(live); live.Show(dockPanel); } public void LiveCaptureClosed(LiveCapture live) { m_LiveCaptures.Remove(live); } public void LoadLogfile(string fn, bool temporary) { if (PromptCloseLog()) LoadLogAsync(fn, temporary); } private void OpenCaptureConfigFile(String filename, bool exe) { if (m_Core.CaptureDialog == null) m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); if(exe) m_Core.CaptureDialog.SetExecutableFilename(filename); else m_Core.CaptureDialog.LoadSettings(filename); m_Core.CaptureDialog.Show(dockPanel); // workaround for Show() not doing this if (m_Core.CaptureDialog.DockState == DockState.DockBottomAutoHide || m_Core.CaptureDialog.DockState == DockState.DockLeftAutoHide || m_Core.CaptureDialog.DockState == DockState.DockRightAutoHide || m_Core.CaptureDialog.DockState == DockState.DockTopAutoHide) { dockPanel.ActiveAutoHideContent = m_Core.CaptureDialog; } } private void LoadFromFilename(string filename) { if (Path.GetExtension(filename) == ".rdc") { LoadLogfile(filename, false); } else if (Path.GetExtension(filename) == ".cap") { OpenCaptureConfigFile(filename, false); } else if (Path.GetExtension(filename) == ".exe") { OpenCaptureConfigFile(filename, true); } else { // not a recognised filetype, see if we can load it anyway LoadLogfile(filename, false); } } public void CloseLogfile() { m_Core.CloseLogfile(); saveLogToolStripMenuItem.Enabled = false; } public string GetSavePath() { DialogResult res = saveDialog.ShowDialog(); if (res == DialogResult.OK) return saveDialog.FileName; return ""; } private LiveCapture OnCaptureTrigger(string exe, string workingDir, string cmdLine, CaptureOptions opts) { if (!PromptCloseLog()) return null; string logfile = m_Core.TempLogFilename(Path.GetFileNameWithoutExtension(exe)); UInt32 ret = StaticExports.ExecuteAndInject(exe, workingDir, cmdLine, logfile, opts); if (ret == 0) { MessageBox.Show(string.Format("Error launching {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", exe), "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } var live = new LiveCapture(m_Core, "", ret, this); ShowLiveCapture(live); return live; } private LiveCapture OnInjectTrigger(UInt32 PID, string name, CaptureOptions opts) { if (!PromptCloseLog()) return null; string logfile = m_Core.TempLogFilename(name); UInt32 ret = StaticExports.InjectIntoProcess(PID, logfile, opts); if (ret == 0) { MessageBox.Show(string.Format("Error injecting into process {0} for capture.\n\nCheck diagnostic log in Help menu for more details.", PID), "Error kicking capture", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } var live = new LiveCapture(m_Core, "", ret, this); ShowLiveCapture(live); return live; } private void captureLogToolStripMenuItem_Click(object sender, EventArgs e) { if (m_Core.CaptureDialog == null) m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); m_Core.CaptureDialog.InjectMode = false; m_Core.CaptureDialog.Show(dockPanel); // workaround for Show() not doing this if (m_Core.CaptureDialog.DockState == DockState.DockBottomAutoHide || m_Core.CaptureDialog.DockState == DockState.DockLeftAutoHide || m_Core.CaptureDialog.DockState == DockState.DockRightAutoHide || m_Core.CaptureDialog.DockState == DockState.DockTopAutoHide) { dockPanel.ActiveAutoHideContent = m_Core.CaptureDialog; } } private void attachToInstanceToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.RemoteHostSelect(m_Core, this)).ShowDialog(); } private void injectIntoProcessToolStripMenuItem_Click(object sender, EventArgs e) { if (m_Core.CaptureDialog == null) m_Core.CaptureDialog = new Dialogs.CaptureDialog(m_Core, OnCaptureTrigger, OnInjectTrigger); m_Core.CaptureDialog.InjectMode = true; m_Core.CaptureDialog.Show(dockPanel); // workaround for Show() not doing this if (m_Core.CaptureDialog.DockState == DockState.DockBottomAutoHide || m_Core.CaptureDialog.DockState == DockState.DockLeftAutoHide || m_Core.CaptureDialog.DockState == DockState.DockRightAutoHide || m_Core.CaptureDialog.DockState == DockState.DockTopAutoHide) { dockPanel.ActiveAutoHideContent = m_Core.CaptureDialog; } } private void openLogToolStripMenuItem_Click(object sender, EventArgs e) { if(!PromptCloseLog()) return; if (m_Core.Config.LastLogPath.Length > 0) openDialog.InitialDirectory = m_Core.Config.LastLogPath; DialogResult res = openDialog.ShowDialog(); if (res == DialogResult.OK) { LoadFromFilename(openDialog.FileName); } } #endregion #region Menu Handlers private void SetUpdateAvailable() { helpToolStripMenuItem.Image = global::renderdocui.Properties.Resources.hourglass; updateToolStripMenuItem.Enabled = true; updateToolStripMenuItem.Text = "An update is available"; } private void SetNoUpdate() { helpToolStripMenuItem.Image = null; updateToolStripMenuItem.Enabled = false; updateToolStripMenuItem.Text = "No update available"; } private void UpdatePopup() { (new Dialogs.UpdateDialog(m_Core)).ShowDialog(); } private enum UpdateResult { Disabled, Unofficial, Toosoon, Latest, Upgrade, }; private delegate void UpdateResultMethod(UpdateResult res); private bool IsVersionMismatched() { return "v" + StaticExports.GetVersionString() != VersionString; } private bool HandleMismatchedVersions() { if (IsVersionMismatched()) { if (!OfficialVersion && !BetaVersion) { MessageBox.Show("You are running an unofficial build with mismatched core and UI versions.\n" + "Double check where you got your build from and do a sanity check!", "Unofficial build - mismatched versions", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { DialogResult mb = MessageBox.Show("RenderDoc has detected mismatched versions between its internal module and UI.\n" + "This is likely caused by a buggy update in the past which partially updated your install. Likely because a " + "program was running with renderdoc while the update happened.\n" + "You should reinstall RenderDoc immediately as this configuration is almost guaranteed to crash.\n\n" + "Would you like to open the downloads page?", "Mismatched versions", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation); if (mb == DialogResult.Yes) Process.Start("https://renderdoc.org/builds"); SetUpdateAvailable(); } return true; } return false; } private void CheckUpdates(bool forceCheck = false, UpdateResultMethod callback = null) { bool mismatch = HandleMismatchedVersions(); if (mismatch) return; if (!forceCheck && !m_Core.Config.CheckUpdate_AllowChecks) { updateToolStripMenuItem.Text = "Update checks disabled"; if (callback != null) callback(UpdateResult.Disabled); return; } if (!OfficialVersion && !BetaVersion) { if (callback != null) callback(UpdateResult.Unofficial); return; } if (m_Core.Config.CheckUpdate_UpdateAvailable) { if (m_Core.Config.CheckUpdate_UpdateResponse.Length == 0) { forceCheck = true; } else if(!forceCheck) { SetUpdateAvailable(); return; } } DateTime today = DateTime.Now; DateTime compare = today.AddDays(-2); if (!forceCheck && compare.CompareTo(m_Core.Config.CheckUpdate_LastUpdate) < 0) { if (callback != null) callback(UpdateResult.Toosoon); return; } m_Core.Config.CheckUpdate_LastUpdate = today; string versionCheck = BareVersionString; if (BetaVersion) versionCheck += String.Format("-{0}-beta", GitCommitHash.Substring(0, 8)); statusText.Text = "Checking for updates..."; statusProgress.Visible = true; statusProgress.Style = ProgressBarStyle.Marquee; statusProgress.MarqueeAnimationSpeed = 50; var updateThread = Helpers.NewThread(new ThreadStart(() => { // spawn thread to check update WebRequest g = HttpWebRequest.Create(String.Format("https://renderdoc.org/getupdateurl/{0}/{1}", IntPtr.Size == 4 ? "32" : "64", versionCheck)); UpdateResult result = UpdateResult.Disabled; try { var webresp = g.GetResponse(); using (var sr = new StreamReader(webresp.GetResponseStream())) { string response = sr.ReadToEnd(); if (response != "") { // window may have been closed while update check was on-going. If so, just return if (Visible) { BeginInvoke((MethodInvoker)delegate { m_Core.Config.CheckUpdate_UpdateAvailable = true; m_Core.Config.CheckUpdate_UpdateResponse = response; SetUpdateAvailable(); UpdatePopup(); }); } result = UpdateResult.Upgrade; } else if (callback != null) { if (Visible) { BeginInvoke((MethodInvoker)delegate { m_Core.Config.CheckUpdate_UpdateAvailable = false; m_Core.Config.CheckUpdate_UpdateResponse = ""; SetNoUpdate(); }); } result = UpdateResult.Latest; } } webresp.Close(); } catch (WebException ex) { StaticExports.LogText(String.Format("Problem checking for updates - {0}", ex.Message)); } catch (Exception) { // just want to swallow the exception, checking for updates doesn't need to be handled // and it's not worth trying to retry. } // window may have been closed while update check was on-going. If so, just return if (Visible) { BeginInvoke((MethodInvoker)delegate { statusText.Text = ""; statusProgress.Visible = false; statusProgress.Style = ProgressBarStyle.Continuous; statusProgress.MarqueeAnimationSpeed = 0; if (callback != null && result != UpdateResult.Disabled) callback(result); }); } })); updateThread.Start(); } private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) { CheckUpdates(true, (UpdateResult res) => { switch (res) { case UpdateResult.Disabled: case UpdateResult.Toosoon: { // won't happen, we forced the check break; } case UpdateResult.Unofficial: { DialogResult mb = MessageBox.Show("You are running an unofficial build, not beta or stable release.\n" + "Updates are only available for installed release builds\n\n" + "Would you like to open the builds list in a browser?", "Unofficial build", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (mb == DialogResult.Yes) Process.Start("https://renderdoc.org/builds"); break; } case UpdateResult.Latest: { MessageBox.Show("You are running the latest version.\n", "Latest version", MessageBoxButtons.OK, MessageBoxIcon.Information); break; } case UpdateResult.Upgrade: { // CheckUpdates() will have shown a dialog for this break; } } }); } private void updateToolStripMenuItem_Click(object sender, EventArgs e) { bool mismatch = HandleMismatchedVersions(); if (mismatch) return; SetUpdateAvailable(); UpdatePopup(); } private bool PromptCloseLog() { if (!m_Core.LogLoaded) return true; string deletepath = ""; if (OwnTemporaryLog) { string temppath = m_Core.LogFileName; DialogResult res = DialogResult.No; // unless we've saved the log, prompt to save if(!SavedTemporaryLog) res = MessageBox.Show("Save this logfile?", "Unsaved log", MessageBoxButtons.YesNoCancel); if (res == DialogResult.Cancel) { return false; } if (res == DialogResult.Yes) { bool success = PromptSaveLog(); if (!success) { return false; } } if (temppath != m_Core.LogFileName || res == DialogResult.No) deletepath = temppath; OwnTemporaryLog = false; SavedTemporaryLog = false; } CloseLogfile(); try { if (deletepath.Length > 0) File.Delete(deletepath); } catch (System.Exception) { // can't delete it! maybe already deleted? } return true; } private bool PromptSaveLog() { DialogResult res = saveDialog.ShowDialog(); if (res == DialogResult.OK) { if (!File.Exists(m_Core.LogFileName)) { MessageBox.Show("Logfile " + m_Core.LogFileName + " couldn't be found, cannot save.", "File not found", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } try { // we copy the (possibly) temp log to the desired path, but the log item remains referring to the original path. // This ensures that if the user deletes the saved path we can still open or re-save it. File.Copy(m_Core.LogFileName, saveDialog.FileName, true); m_Core.Config.AddRecentFile(m_Core.Config.RecentLogFiles, saveDialog.FileName, 10); PopulateRecentFiles(); SetTitle(saveDialog.FileName); } catch (System.Exception ex) { MessageBox.Show("Couldn't save to " + saveDialog.FileName + Environment.NewLine + ex.ToString(), "Cannot save", MessageBoxButtons.OK, MessageBoxIcon.Error); } // we don't prompt to save on closing - if the user deleted the log that we just saved, then // that is up to them. SavedTemporaryLog = true; return true; } return false; } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { // bookmark keys are handled globally if ((keyData & Keys.Control) == Keys.Control) { Keys[] digits = { Keys.D1, Keys.D2, Keys.D3, Keys.D4, Keys.D5, Keys.D6, Keys.D7, Keys.D8, Keys.D9, Keys.D0 }; for (int i = 0; i < digits.Length; i++) { if (keyData == (Keys.Control|digits[i])) { EventBrowser eb = m_Core.GetEventBrowser(); if (eb.Visible && eb.HasBookmark(i)) { m_Core.SetEventID(null, eb.GetBookmark(i)); return true; } } } } return base.ProcessCmdKey(ref msg, keyData); } private void MainWindow_FormClosing(object sender, FormClosingEventArgs e) { foreach (var live in m_LiveCaptures) { if (live.CheckAllowClose() == false) { e.Cancel = true; return; } } if (!PromptCloseLog()) { e.Cancel = true; return; } foreach (var live in m_LiveCaptures.ToArray()) { live.CleanItems(); live.Close(); } SaveLayout(0); m_Core.Shutdown(); } private void exitToolStripMenuItem_Click(object sender, EventArgs e) { this.Close(); } private void aboutToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.AboutDialog(VersionString)).ShowDialog(); } private void optionsToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.SettingsDialog(m_Core)).ShowDialog(); } private void viewLogFileToolStripMenuItem_Click(object sender, EventArgs e) { string fn = StaticExports.GetLogFilename(); if (File.Exists(fn)) { Process.Start(fn); } } private void recentLogMenuItem_Click(object sender, EventArgs e) { ToolStripDropDownItem item = (ToolStripDropDownItem)sender; String filename = item.Text.Substring(3); if(File.Exists(filename)) { LoadLogfile(filename, false); } else { DialogResult res = MessageBox.Show("File " + filename + " couldn't be found.\nRemove from recent list?", "File not found", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information); if (res == DialogResult.Yes) { int index = (int)item.Tag; m_Core.Config.RecentLogFiles.RemoveAt(index); PopulateRecentFiles(); } } } private void recentCaptureMenuItem_Click(object sender, EventArgs e) { ToolStripDropDownItem item = (ToolStripDropDownItem)sender; String filename = item.Text.Substring(3); if (File.Exists(filename)) { OpenCaptureConfigFile(filename, false); } else { DialogResult res = MessageBox.Show("File " + filename + " couldn't be found.\nRemove from recent list?", "File not found", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information); if (res == DialogResult.Yes) { int index = (int)item.Tag; m_Core.Config.RecentCaptureSettings.RemoveAt(index); PopulateRecentCaptures(); } } } private void clearHistoryToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.Config.RecentLogFiles.Clear(); PopulateRecentFiles(); } private void saveLogToolStripMenuItem_Click(object sender, EventArgs e) { PromptSaveLog(); } private void closeLogToolStripMenuItem_Click(object sender, EventArgs e) { PromptCloseLog(); } private void clearHistoryToolStripMenuItem1_Click(object sender, EventArgs e) { m_Core.Config.RecentCaptureSettings.Clear(); PopulateRecentCaptures(); } private void sendErrorReportToolStripMenuItem_Click(object sender, EventArgs e) { StaticExports.TriggerExceptionHandler(IntPtr.Zero, false); } private void manageReplayDevicesToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.ReplayHostManager(m_Core, this)).ShowDialog(); } private void launchReplayHostToolStripMenuItem_Click(object sender, EventArgs e) { bool killReplay = false; Thread thread = Helpers.NewThread(new ThreadStart(() => { StaticExports.SpawnReplayHost(ref killReplay); })); thread.Start(); MessageBox.Show("Remote Replay is now running. Click OK to close.", "Remote replay", MessageBoxButtons.OK, MessageBoxIcon.Information); killReplay = true; } private void viewDocsToolStripMenuItem_Click(object sender, EventArgs e) { // Help.ShowHelp(this, "renderdoc.chm", HelpNavigator.Topic, "html/b97b19f8-2b97-4dca-8a7a-ed7026eb43fe.htm"); Help.ShowHelp(this, "renderdoc.chm"); } private void showTipsToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.TipsDialog(m_Core)).ShowDialog(); } private void nightlybuildsToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://renderdoc.org/builds"); } private void sourceOnGithubToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://github.com/baldurk/renderdoc"); } #endregion #region Dock Content showers private void eventViewerToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetEventBrowser().Show(dockPanel); } private void textureToolStripMenuItem_Click(object sender, EventArgs e) { var t = m_Core.GetTextureViewer(); if(!t.Visible) t.InitFromPersistString(""); t.Show(dockPanel); } private void pythonShellToolStripMenuItem_Click(object sender, EventArgs e) { (new Dialogs.PythonShell(m_Core)).Show(dockPanel); } private void PipelineStateToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetPipelineStateViewer().Show(dockPanel); } private void APIInspectorToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetAPIInspector().Show(dockPanel); } private void debugMessagesToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetDebugMessages().Show(dockPanel); } private void meshOutputToolStripMenuItem_Click(object sender, EventArgs e) { BufferViewer b = new BufferViewer(m_Core, true); b.InitFromPersistString(""); b.Show(dockPanel); } private void timelineToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetTimelineBar().Show(dockPanel); } private void statisticsViewerToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.GetStatisticsViewer().Show(dockPanel); } #endregion #region Symbol resolving private bool SymbolResolveCallback() { bool ret = false; // just bail if we managed to get here without a resolver. m_Core.Renderer.Invoke((ReplayRenderer r) => { ret = !r.HasCallstacks() || r.InitResolver(); }); return ret; } private void resolveSymbolsToolStripMenuItem_Click(object sender, EventArgs e) { m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { r.InitResolver(); }); ModalPopup modal = new ModalPopup(SymbolResolveCallback, false); modal.SetModalText("Please Wait - Resolving Symbols."); modal.ShowDialog(); m_Core.GetAPIInspector().FillCallstack(); } #endregion #region Drag & Drop private string ValidData(IDataObject d) { var fmts = new List(d.GetFormats()); if (fmts.Contains("FileName")) { var data = d.GetData("FileName") as Array; if (data != null && data.Length == 1 && data.GetValue(0) is string) { var filename = (string)data.GetValue(0); if (File.Exists(filename)) { return Path.GetFullPath(filename); } } } return ""; } private void MainWindow_DragDrop(object sender, DragEventArgs e) { string fn = ValidData(e.Data); if (fn.Length > 0) { LoadFromFilename(fn); } } private void MainWindow_DragEnter(object sender, DragEventArgs e) { if (ValidData(e.Data).Length > 0) e.Effect = DragDropEffects.Copy; else e.Effect = DragDropEffects.None; } #endregion } }