mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 17:40:39 +00:00
04f1d4a378
* This will allow the adding of things like 'redundant api call' for calls that have no effect, as well as potential problems like drawing with an empty viewport, or similar things that are common problems. Reading out- of-bounds on buffers etc is a good example of 'defined' behaviour that is probably not desired. * These heuristics could also identify potential performance problems. * It also supports adding debug messages after log-load time, so you could do an additional extra-strength pass, or do a detailed check of one draw call (e.g. a broken draw, to try and figure out the problem). If there are any unread debug messages, the status bar will flash and the debug messages window will show a count as (N).
832 lines
28 KiB
C#
832 lines
28 KiB
C#
/******************************************************************************
|
|
* The MIT License (MIT)
|
|
*
|
|
* 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 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 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;
|
|
|
|
#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 { while (m_FrameInfo == null); 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 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 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 | DrawcallFlags.SetMarker)) > 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 counter = 1;
|
|
|
|
for (int i = 1; i < draws.Length; i++)
|
|
{
|
|
if (PassEquivalent(draws[i], draws[start]))
|
|
continue;
|
|
|
|
int end = i-1;
|
|
|
|
if (end - start < 2 ||
|
|
draws[i].children.Length > 0 || draws[start].children.Length > 0 ||
|
|
draws[i].context != m_FrameInfo[frameID].immContextId ||
|
|
draws[start].context != m_FrameInfo[frameID].immContextId)
|
|
{
|
|
for (int j = start; j <= end; j++)
|
|
{
|
|
ret.Add(draws[j]);
|
|
counter++;
|
|
}
|
|
|
|
start = 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";
|
|
|
|
|
|
if((draws[end].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;
|
|
counter++;
|
|
}
|
|
|
|
if (start < draws.Length)
|
|
ret.Add(draws[start]);
|
|
|
|
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.5f * m_Renderer.LoadProgress + 0.49f * 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);
|
|
|
|
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, false));
|
|
|
|
m_TimedDrawcalls = false;
|
|
|
|
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_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState);
|
|
|
|
UnreadMessageCount = 0;
|
|
AddMessages(m_FrameInfo[0].debugMessages);
|
|
|
|
postloadProgress = 1.0f;
|
|
});
|
|
|
|
Thread.Sleep(20);
|
|
|
|
m_LogLoaded = true;
|
|
progressThread = false;
|
|
|
|
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);
|
|
}
|
|
|
|
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_PipelineState.SetStates(null, null, null);
|
|
|
|
DebugMessages.Clear();
|
|
UnreadMessageCount = 0;
|
|
|
|
m_LogLoaded = false;
|
|
|
|
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
|
|
|
|
private bool m_TimedDrawcalls = false;
|
|
public void TimeDrawcalls(ReplayRenderer r)
|
|
{
|
|
if (m_TimedDrawcalls) return;
|
|
m_TimedDrawcalls = true;
|
|
|
|
for (int i = 0; i < m_FrameInfo.Length; i++)
|
|
m_DrawCalls[i] = FakeProfileMarkers(i, r.GetDrawcalls((UInt32)i, true));
|
|
}
|
|
|
|
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 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);
|
|
m_D3D11PipelineState = r.GetD3D11PipelineState();
|
|
m_GLPipelineState = r.GetGLPipelineState();
|
|
m_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState);
|
|
});
|
|
|
|
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 SetEventID(ILogViewerForm exclude, UInt32 frameID, UInt32 eventID)
|
|
{
|
|
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);
|
|
m_D3D11PipelineState = r.GetD3D11PipelineState();
|
|
m_GLPipelineState = r.GetGLPipelineState();
|
|
m_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState);
|
|
});
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|