Files
renderdoc/renderdocui/Code/Core.cs
T
Michael Vance 973ee146a8 Support for a new 'Statistics' pane.
Notes
======
- Create a (hopefully) cross-backend performance statistics abstraction as part of FetchFrameInfo. This currently collects statistics about constant buffer binds, sampler binds, resource binds, client and server style resource updates (e.g. Map and UpdateSubresource), index & vertex buffer binds, and draws and dispatches. In my captures this covers approximately half of all API traffic. The rest is often shader sets, and then usual RS, OM, etc., that aren't currently tracked. During READING state parsing on the wrapped device context, record statistics about the calls and store them into the current frame info. We inspect objects occasionally to get things like their type for recording. It may be useful to expand this in the future to check bind types, etc. On the GL/Vulkan backends the stats data is never initialized and we display the same statistics data as before.
- Add a new statistics pane in the UI. A variety of shim arbitration/marshalling is provided to get the statistics data across the managed/native boundary. Removed the old statistics menu item. Currently the statistics pane is just a text log of various gathered data, with ASCII art (woo!) histograms. However in the future we would like to have image based historgrams as well as support for gathering statistics on a mask of stages, etc.
- Remove 'diagnostic' events (e.g. push/pop/marker) from the API call count as it distorts the API:draw call ratio.
- Add _First and _Count to ShaderResourceType and ShaderStageType for weak enumeration (weak due to int cast requirement).
- Provide utility functions Log2Floor for 32 and 64 bit types. These require OS specific clz, which are provided for Windows/VS and Linux/gcc/clang.

TODO
======
- UI toolkit based historgram, ability to set stages enabled for statistics, etc.
- Revisit necessity of gathering statistics across frames--apparently there is no multi-frame capture in a single log anymore?
- Revisit min/max/etc. gathering on the data--is there some way to improve this to not be so mechanical? Perhaps with field enumeration? However if moving to C++/Qt in future, possibly not a good time investment.
2016-04-11 21:23:05 -04:00

924 lines
32 KiB
C#

/******************************************************************************
* 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.Data;
using System.Drawing;
using System.Diagnostics;
using System.Linq;
using System.IO;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using renderdocui.Windows;
using renderdocui.Windows.Dialogs;
using renderdocui.Windows.PipelineState;
using renderdoc;
namespace renderdocui.Code
{
// Single core class. Between this and the RenderManager these classes govern the interaction
// between the UI and the actual implementation.
//
// This class primarily controls things that need to be propogated globally, it keeps a list of
// ILogViewerForms which are windows that would like to be notified of changes to the current event,
// when a log is opened or closed, etc. It also contains data that potentially every window will
// want access to - like a list of all buffers in the log and their properties, etc.
public class Core
{
#region Privates
private RenderManager m_Renderer = new RenderManager();
private PersistantConfig m_Config = null;
private bool m_LogLoaded = false;
private FileSystemWatcher m_LogWatcher = null;
private string m_LogFile = "";
private UInt32 m_FrameID = 0;
private UInt32 m_EventID = 0;
private UInt32 m_DeferredEvent = 0;
private APIProperties m_APIProperties = null;
private FetchFrameInfo[] m_FrameInfo = null;
private FetchDrawcall[][] m_DrawCalls = null;
private FetchBuffer[] m_Buffers = null;
private FetchTexture[] m_Textures = null;
private D3D11PipelineState m_D3D11PipelineState = null;
private GLPipelineState m_GLPipelineState = null;
private VulkanPipelineState m_VulkanPipelineState = null;
private CommonPipelineState m_PipelineState = new CommonPipelineState();
private List<ILogViewerForm> m_LogViewers = new List<ILogViewerForm>();
private List<ILogLoadProgressListener> m_ProgressListeners = new List<ILogLoadProgressListener>();
private MainWindow m_MainWindow = null;
private EventBrowser m_EventBrowser = null;
private APIInspector m_APIInspector = null;
private DebugMessages m_DebugMessages = null;
private TimelineBar m_TimelineBar = null;
private TextureViewer m_TextureViewer = null;
private PipelineStateViewer m_PipelineStateViewer = null;
private StatisticsViewer m_StatisticsViewer = null;
#endregion
#region Properties
public static string ConfigDirectory
{
get
{
string appdata = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
return Path.Combine(appdata, "renderdoc");
}
}
public static string ConfigFilename
{
get
{
return Path.Combine(ConfigDirectory, "UI.config");
}
}
public PersistantConfig Config { get { return m_Config; } }
public bool LogLoaded { get { return m_LogLoaded; } }
public bool LogLoading { get { return m_LogLoadingInProgress; } }
public string LogFileName { get { return m_LogFile; } set { if (LogLoaded) m_LogFile = value; } }
public FetchFrameInfo[] FrameInfo { get { return m_FrameInfo; } }
public APIProperties APIProps { get { return m_APIProperties; } }
// typically 0 right now as we haven't supported multiple frames in logs for a loooong time.
public UInt32 CurFrame { get { return m_FrameID; } }
public UInt32 CurEvent { get { return m_DeferredEvent > 0 ? m_DeferredEvent : m_EventID; } }
public FetchDrawcall[] CurDrawcalls { get { return GetDrawcalls(CurFrame); } }
public FetchDrawcall CurDrawcall { get { return GetDrawcall(CurFrame, CurEvent); } }
public FetchTexture[] CurTextures { get { return m_Textures; } }
public FetchBuffer[] CurBuffers { get { return m_Buffers; } }
public FetchTexture GetTexture(ResourceId id)
{
if (id == ResourceId.Null) return null;
for (int t = 0; t < m_Textures.Length; t++)
if (m_Textures[t].ID == id)
return m_Textures[t];
return null;
}
public FetchBuffer GetBuffer(ResourceId id)
{
if (id == ResourceId.Null) return null;
for (int b = 0; b < m_Buffers.Length; b++)
if (m_Buffers[b].ID == id)
return m_Buffers[b];
return null;
}
public List<DebugMessage> DebugMessages = new List<DebugMessage>();
public int UnreadMessageCount = 0;
public void AddMessages(DebugMessage[] msgs)
{
UnreadMessageCount += msgs.Length;
foreach(var msg in msgs)
DebugMessages.Add(msg);
}
// the RenderManager can be used when you want to perform an operation, it will let you Invoke or
// BeginInvoke onto the thread that's used to access the renderdoc project.
public RenderManager Renderer { get { return m_Renderer; } }
public Form AppWindow { get { return m_MainWindow; } }
#endregion
#region Pipeline State
// direct access (note that only one of these will be valid for a log, check APIProps.pipelineType)
public D3D11PipelineState CurD3D11PipelineState { get { return m_D3D11PipelineState; } }
public GLPipelineState CurGLPipelineState { get { return m_GLPipelineState; } }
public VulkanPipelineState CurVulkanPipelineState { get { return m_VulkanPipelineState; } }
public CommonPipelineState CurPipelineState { get { return m_PipelineState; } }
#endregion
#region Init and Shutdown
public Core(string paramFilename, string remoteHost, uint remoteIdent, bool temp, PersistantConfig config)
{
if (!Directory.Exists(ConfigDirectory))
Directory.CreateDirectory(ConfigDirectory);
m_Config = config;
m_MainWindow = new MainWindow(this, paramFilename, remoteHost, remoteIdent, temp);
}
public void Shutdown()
{
if (m_Renderer != null)
m_Renderer.CloseThreadSync();
}
#endregion
#region Log Loading & Capture
private bool m_LogLoadingInProgress = false;
private bool LogLoadCallback()
{
return !m_LogLoadingInProgress;
}
// used to determine if two drawcalls can be considered in the same 'pass',
// ie. writing to similar targets, same type of call, etc.
//
// When a log has no markers, this is used to group up drawcalls into fake markers
private bool PassEquivalent(FetchDrawcall a, FetchDrawcall b)
{
// executing command lists can have children
if(a.children.Length > 0 || b.children.Length > 0)
return false;
// don't group draws and compute executes
if ((a.flags & DrawcallFlags.Dispatch) != (b.flags & DrawcallFlags.Dispatch))
return false;
// don't group present with anything
if ((a.flags & DrawcallFlags.Present) != (b.flags & DrawcallFlags.Present))
return false;
// don't group things run on different multithreaded contexts
if(a.context != b.context)
return false;
// don't group things with different depth outputs
if (a.depthOut != b.depthOut)
return false;
int numAOuts = 0, numBOuts = 0;
for (int i = 0; i < 8; i++)
{
if (a.outputs[i] != ResourceId.Null) numAOuts++;
if (b.outputs[i] != ResourceId.Null) numBOuts++;
}
int numSame = 0;
if (a.depthOut != ResourceId.Null)
{
numAOuts++;
numBOuts++;
numSame++;
}
for (int i = 0; i < 8; i++)
{
if (a.outputs[i] != ResourceId.Null)
{
for (int j = 0; j < 8; j++)
{
if (a.outputs[i] == b.outputs[j])
{
numSame++;
break;
}
}
}
else if (b.outputs[i] != ResourceId.Null)
{
for (int j = 0; j < 8; j++)
{
if (a.outputs[j] == b.outputs[i])
{
numSame++;
break;
}
}
}
}
// use a kind of heuristic to group together passes where the outputs are similar enough.
// could be useful for example if you're rendering to a gbuffer and sometimes you render
// without one target, but the draws are still batched up.
if (numSame > Math.Max(numAOuts, numBOuts) / 2 && Math.Max(numAOuts, numBOuts) > 1)
return true;
if (numSame == Math.Max(numAOuts, numBOuts))
return true;
return false;
}
private bool ContainsMarker(FetchDrawcall[] draws)
{
bool ret = false;
foreach (var d in draws)
{
ret |= (d.flags & DrawcallFlags.PushMarker) > 0 && (d.flags & DrawcallFlags.CmdList) == 0;
ret |= ContainsMarker(d.children);
}
return ret;
}
// if a log doesn't contain any markers specified at all by the user, then we can
// fake some up by determining batches of draws that are similar and giving them a
// pass number
private FetchDrawcall[] FakeProfileMarkers(int frameID, FetchDrawcall[] draws)
{
if (ContainsMarker(draws))
return draws;
var ret = new List<FetchDrawcall>();
int depthpassID = 1;
int computepassID = 1;
int passID = 1;
int start = 0;
int refdraw = 0;
for (int i = 1; i < draws.Length; i++)
{
if ((draws[refdraw].flags & (DrawcallFlags.Copy | DrawcallFlags.Resolve | DrawcallFlags.SetMarker)) > 0)
{
refdraw = i;
continue;
}
if ((draws[i].flags & (DrawcallFlags.Copy | DrawcallFlags.Resolve | DrawcallFlags.SetMarker)) > 0)
continue;
if (PassEquivalent(draws[i], draws[refdraw]))
continue;
int end = i-1;
if (end - start < 2 ||
draws[i].children.Length > 0 || draws[refdraw].children.Length > 0 ||
draws[i].context != m_FrameInfo[frameID].immContextId ||
draws[refdraw].context != m_FrameInfo[frameID].immContextId)
{
for (int j = start; j <= end; j++)
ret.Add(draws[j]);
start = i;
refdraw = i;
continue;
}
int minOutCount = 100;
int maxOutCount = 0;
for (int j = start; j <= end; j++)
{
int outCount = 0;
foreach (var o in draws[j].outputs)
if (o != ResourceId.Null)
outCount++;
minOutCount = Math.Min(minOutCount, outCount);
maxOutCount = Math.Max(maxOutCount, outCount);
}
FetchDrawcall mark = new FetchDrawcall();
mark.eventID = draws[end].eventID;
mark.drawcallID = draws[end].drawcallID;
mark.context = draws[end].context;
mark.flags = DrawcallFlags.PushMarker;
mark.outputs = draws[end].outputs;
mark.depthOut = draws[end].depthOut;
mark.name = "Guessed Pass";
minOutCount = Math.Max(1, minOutCount);
if ((draws[refdraw].flags & DrawcallFlags.Dispatch) != 0)
mark.name = String.Format("Compute Pass #{0}", computepassID++);
else if (maxOutCount == 0)
mark.name = String.Format("Depth-only Pass #{0}", depthpassID++);
else if (minOutCount == maxOutCount)
mark.name = String.Format("Colour Pass #{0} ({1} Targets{2})", passID++, minOutCount, draws[end].depthOut == ResourceId.Null ? "" : " + Depth");
else
mark.name = String.Format("Colour Pass #{0} ({1}-{2} Targets{3})", passID++, minOutCount, maxOutCount, draws[end].depthOut == ResourceId.Null ? "" : " + Depth");
mark.children = new FetchDrawcall[end - start + 1];
for (int j = start; j <= end; j++)
{
mark.children[j - start] = draws[j];
draws[j].parent = mark;
}
ret.Add(mark);
start = i;
refdraw = i;
}
if (start < draws.Length)
{
for (int j = start; j < draws.Length; j++)
ret.Add(draws[j]);
}
return ret.ToArray();
}
// loading a local log, no remote replay
public void LoadLogfile(string logFile, bool temporary)
{
LoadLogfile(-1, "", logFile, temporary);
}
// when loading a log while replaying remotely, provide the proxy renderer that will be used
// as well as the hostname to replay on.
public void LoadLogfile(int proxyRenderer, string replayHost, string logFile, bool temporary)
{
m_LogFile = logFile;
m_LogLoadingInProgress = true;
if (File.Exists(Core.ConfigFilename))
m_Config.Serialize(Core.ConfigFilename);
float postloadProgress = 0.0f;
bool progressThread = true;
// start a modal dialog to prevent the user interacting with the form while the log is loading.
// We'll close it down when log loading finishes (whether it succeeds or fails)
ModalPopup modal = new ModalPopup(LogLoadCallback, true);
Thread modalThread = Helpers.NewThread(new ThreadStart(() =>
{
modal.SetModalText(string.Format("Loading Log {0}.", m_LogFile));
AppWindow.BeginInvoke(new Action(() =>
{
modal.ShowDialog(AppWindow);
}));
}));
modalThread.Start();
// this thread continually ticks and notifies any threads of the progress, through a float
// that is updated by the main loading code
Thread thread = Helpers.NewThread(new ThreadStart(() =>
{
modal.LogfileProgressBegin();
foreach (var p in m_ProgressListeners)
p.LogfileProgressBegin();
while (progressThread)
{
Thread.Sleep(2);
float progress = 0.8f * m_Renderer.LoadProgress + 0.19f * postloadProgress + 0.01f;
modal.LogfileProgress(progress);
foreach (var p in m_ProgressListeners)
p.LogfileProgress(progress);
}
}));
thread.Start();
// this function call will block until the log is either loaded, or there's some failure
m_Renderer.Init(proxyRenderer, replayHost, logFile);
// if the renderer isn't running, we hit a failure case so display an error message
if (!m_Renderer.Running)
{
string errmsg = "Unknown error message";
if (m_Renderer.InitException.Data.Contains("status"))
errmsg = ((ReplayCreateStatus)m_Renderer.InitException.Data["status"]).Str();
if(proxyRenderer >= 0)
MessageBox.Show(String.Format("{0}\nFailed to transfer and replay on remote host {1}: {2}.\n\n" +
"Check diagnostic log in Help menu for more details.", logFile, replayHost, errmsg),
"Error opening log", MessageBoxButtons.OK, MessageBoxIcon.Error);
else
MessageBox.Show(String.Format("{0}\nFailed to open logfile for replay: {1}.\n\n" +
"Check diagnostic log in Help menu for more details.", logFile, errmsg),
"Error opening log", MessageBoxButtons.OK, MessageBoxIcon.Error);
progressThread = false;
thread.Join();
m_LogLoadingInProgress = false;
modal.LogfileProgress(-1.0f);
foreach (var p in m_ProgressListeners)
p.LogfileProgress(-1.0f);
return;
}
if (!temporary)
{
m_Config.AddRecentFile(m_Config.RecentLogFiles, logFile, 10);
if (File.Exists(Core.ConfigFilename))
m_Config.Serialize(Core.ConfigFilename);
}
m_FrameID = 0;
m_EventID = 0;
m_FrameInfo = null;
m_APIProperties = null;
// fetch initial data like drawcalls, textures and buffers
m_Renderer.Invoke((ReplayRenderer r) =>
{
m_FrameInfo = r.GetFrameInfo();
m_APIProperties = r.GetAPIProperties();
postloadProgress = 0.2f;
m_DrawCalls = new FetchDrawcall[m_FrameInfo.Length][];
postloadProgress = 0.4f;
for (int i = 0; i < m_FrameInfo.Length; i++)
m_DrawCalls[i] = FakeProfileMarkers(i, r.GetDrawcalls((UInt32)i));
postloadProgress = 0.7f;
m_Buffers = r.GetBuffers();
postloadProgress = 0.8f;
var texs = new List<FetchTexture>(r.GetTextures());
m_Textures = texs.OrderBy(o => o.name).ToArray();
postloadProgress = 0.9f;
m_D3D11PipelineState = r.GetD3D11PipelineState();
m_GLPipelineState = r.GetGLPipelineState();
m_VulkanPipelineState = r.GetVulkanPipelineState();
m_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState, m_VulkanPipelineState);
UnreadMessageCount = 0;
AddMessages(m_FrameInfo[0].debugMessages);
postloadProgress = 1.0f;
});
Thread.Sleep(20);
DateTime today = DateTime.Now;
DateTime compare = today.AddDays(-21);
if (compare.CompareTo(Config.DegradedLog_LastUpdate) >= 0 && m_APIProperties.degraded)
{
Config.DegradedLog_LastUpdate = today;
MessageBox.Show(String.Format("{0}\nThis log opened with degraded support - " +
"this could mean missing hardware support caused a fallback to software rendering.\n\n" +
"This warning will not appear every time this happens, " +
"check debug errors/warnings window for more details.", logFile),
"Degraded support of log", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
m_LogLoaded = true;
progressThread = false;
m_LogWatcher = new FileSystemWatcher(Path.GetDirectoryName(m_LogFile), Path.GetFileName(m_LogFile));
m_LogWatcher.EnableRaisingEvents = true;
m_LogWatcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite;
m_LogWatcher.Created += new FileSystemEventHandler(OnLogfileChanged);
m_LogWatcher.Changed += new FileSystemEventHandler(OnLogfileChanged);
m_LogWatcher.SynchronizingObject = m_MainWindow; // callbacks on UI thread please
List<ILogViewerForm> logviewers = new List<ILogViewerForm>();
logviewers.AddRange(m_LogViewers);
// notify all the registers log viewers that a log has been loaded
foreach (var logviewer in logviewers)
{
if (logviewer == null || !(logviewer is Control)) continue;
Control c = (Control)logviewer;
if (c.InvokeRequired)
{
if (!c.IsDisposed)
{
c.Invoke(new Action(() => {
try
{
logviewer.OnLogfileLoaded();
}
catch (Exception ex)
{
throw new AccessViolationException("Rethrown from Invoke:\n" + ex.ToString());
}
}));
}
}
else if (!c.IsDisposed)
logviewer.OnLogfileLoaded();
}
m_LogLoadingInProgress = false;
modal.LogfileProgress(1.0f);
foreach (var p in m_ProgressListeners)
p.LogfileProgress(1.0f);
}
void OnLogfileChanged(object sender, FileSystemEventArgs e)
{
m_Renderer.Invoke((ReplayRenderer r) =>
{
r.FileChanged();
r.SetFrameEvent(m_FrameID, m_EventID > 0 ? m_EventID-1 : 1, true);
});
SetEventID(null, CurFrame, CurEvent);
}
public void CloseLogfile()
{
if (!m_LogLoaded) return;
m_LogFile = "";
m_Renderer.CloseThreadSync();
m_Renderer = new RenderManager();
m_APIProperties = null;
m_FrameInfo = null;
m_DrawCalls = null;
m_Buffers = null;
m_Textures = null;
m_D3D11PipelineState = null;
m_GLPipelineState = null;
m_VulkanPipelineState = null;
m_PipelineState.SetStates(null, null, null, null);
DebugMessages.Clear();
UnreadMessageCount = 0;
m_LogLoaded = false;
if (m_LogWatcher != null)
m_LogWatcher.EnableRaisingEvents = false;
m_LogWatcher = null;
foreach (var logviewer in m_LogViewers)
{
Control c = (Control)logviewer;
if (c.InvokeRequired)
c.Invoke(new Action(() => logviewer.OnLogfileClosed()));
else
logviewer.OnLogfileClosed();
}
}
public String TempLogFilename(String appname)
{
string folder = Config.CaptureSavePath;
try
{
if (folder.Length == 0 || !Directory.Exists(folder))
folder = Path.GetTempPath();
}
catch (ArgumentException)
{
// invalid path or similar
folder = Path.GetTempPath();
}
return Path.Combine(folder, appname + "_" + DateTime.Now.ToString(@"yyyy.MM.dd_HH.mm.ss") + ".rdc");
}
#endregion
#region Log drawcalls
public FetchDrawcall[] GetDrawcalls(UInt32 frameIdx)
{
if (m_DrawCalls == null) return null;
return m_DrawCalls[frameIdx];
}
private FetchDrawcall GetDrawcall(FetchDrawcall[] draws, UInt32 eventID)
{
foreach (var d in draws)
{
if (d.children != null && d.children.Length > 0)
{
var draw = GetDrawcall(d.children, eventID);
if (draw != null) return draw;
}
if (d.eventID == eventID)
return d;
}
return null;
}
public FetchDrawcall GetDrawcall(UInt32 frameID, UInt32 eventID)
{
if (frameID < 0 || m_DrawCalls == null || frameID >= m_DrawCalls.Length)
return null;
return GetDrawcall(m_DrawCalls[frameID], eventID);
}
#endregion
#region Viewers
// Some viewers we only allow one to exist at once, so we keep the instance here.
public EventBrowser GetEventBrowser()
{
if (m_EventBrowser == null || m_EventBrowser.IsDisposed)
{
m_EventBrowser = new EventBrowser(this);
AddLogViewer(m_EventBrowser);
}
return m_EventBrowser;
}
public TextureViewer GetTextureViewer()
{
if (m_TextureViewer == null || m_TextureViewer.IsDisposed)
{
m_TextureViewer = new TextureViewer(this);
AddLogViewer(m_TextureViewer);
}
return m_TextureViewer;
}
public PipelineStateViewer GetPipelineStateViewer()
{
if (m_PipelineStateViewer == null || m_PipelineStateViewer.IsDisposed)
{
m_PipelineStateViewer = new PipelineStateViewer(this);
AddLogViewer(m_PipelineStateViewer);
}
return m_PipelineStateViewer;
}
public APIInspector GetAPIInspector()
{
if (m_APIInspector == null || m_APIInspector.IsDisposed)
{
m_APIInspector = new APIInspector(this);
AddLogViewer(m_APIInspector);
}
return m_APIInspector;
}
public DebugMessages GetDebugMessages()
{
if (m_DebugMessages == null || m_DebugMessages.IsDisposed)
{
m_DebugMessages = new DebugMessages(this);
AddLogViewer(m_DebugMessages);
}
return m_DebugMessages;
}
public TimelineBar TimelineBar
{
get
{
if (m_TimelineBar == null || m_TimelineBar.IsDisposed)
return null;
return m_TimelineBar;
}
}
private CaptureDialog m_CaptureDialog = null;
public CaptureDialog CaptureDialog
{
get
{
return m_CaptureDialog == null || m_CaptureDialog.IsDisposed ? null : m_CaptureDialog;
}
set
{
if (m_CaptureDialog == null || m_CaptureDialog.IsDisposed)
m_CaptureDialog = value;
}
}
public TimelineBar GetTimelineBar()
{
if (m_TimelineBar == null || m_TimelineBar.IsDisposed)
{
m_TimelineBar = new TimelineBar(this);
AddLogViewer(m_TimelineBar);
}
return m_TimelineBar;
}
public StatisticsViewer GetStatisticsViewer()
{
if (m_StatisticsViewer == null || m_StatisticsViewer.IsDisposed)
{
m_StatisticsViewer = new StatisticsViewer(this);
AddLogViewer(m_StatisticsViewer);
}
return m_StatisticsViewer;
}
public void AddLogProgressListener(ILogLoadProgressListener p)
{
m_ProgressListeners.Add(p);
}
public void AddLogViewer(ILogViewerForm f)
{
m_LogViewers.Add(f);
if (LogLoaded)
{
f.OnLogfileLoaded();
f.OnEventSelected(CurFrame, CurEvent);
}
}
public void RemoveLogViewer(ILogViewerForm f)
{
m_LogViewers.Remove(f);
}
#endregion
#region Log Browsing
// setting a context filter allows replaying of deferred events. You can set the deferred
// events to replay in a context, after replaying up to a given event on the main thread
public void SetContextFilter(ILogViewerForm exclude, UInt32 frameID, UInt32 eventID,
ResourceId ctx, UInt32 firstDeferred, UInt32 lastDeferred)
{
m_FrameID = frameID;
m_EventID = eventID;
m_DeferredEvent = lastDeferred;
m_Renderer.Invoke((ReplayRenderer r) => { r.SetContextFilter(ctx, firstDeferred, lastDeferred); });
m_Renderer.Invoke((ReplayRenderer r) => {
r.SetFrameEvent(m_FrameID, m_EventID, true);
m_D3D11PipelineState = r.GetD3D11PipelineState();
m_GLPipelineState = r.GetGLPipelineState();
m_VulkanPipelineState = r.GetVulkanPipelineState();
m_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState, m_VulkanPipelineState);
});
foreach (var logviewer in m_LogViewers)
{
if (logviewer == exclude)
continue;
Control c = (Control)logviewer;
if (c.InvokeRequired)
c.BeginInvoke(new Action(() => logviewer.OnEventSelected(frameID, eventID)));
else
logviewer.OnEventSelected(frameID, eventID);
}
}
public void RefreshStatus()
{
SetEventID(null, m_FrameID, m_EventID, true);
}
public void SetEventID(ILogViewerForm exclude, UInt32 frameID, UInt32 eventID)
{
SetEventID(exclude, frameID, eventID, false);
}
private void SetEventID(ILogViewerForm exclude, UInt32 frameID, UInt32 eventID, bool force)
{
m_FrameID = frameID;
m_EventID = eventID;
m_DeferredEvent = 0;
m_Renderer.Invoke((ReplayRenderer r) => { r.SetContextFilter(ResourceId.Null, 0, 0); });
m_Renderer.Invoke((ReplayRenderer r) =>
{
r.SetFrameEvent(m_FrameID, m_EventID, force);
m_D3D11PipelineState = r.GetD3D11PipelineState();
m_GLPipelineState = r.GetGLPipelineState();
m_VulkanPipelineState = r.GetVulkanPipelineState();
m_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState, m_VulkanPipelineState);
});
foreach (var logviewer in m_LogViewers)
{
if(logviewer == exclude)
continue;
Control c = (Control)logviewer;
if (c.InvokeRequired)
c.Invoke(new Action(() => logviewer.OnEventSelected(frameID, eventID)));
else
logviewer.OnEventSelected(frameID, eventID);
}
}
#endregion
}
}