mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 17:10:47 +00:00
6208e3992d
* If hardware support for feature level 11 isn't present, fall back to the WARP software rasterizer. This will support everything needed, but it certainly won't run well. * There are loud warnings - I added a debug message to the debug errors& warnings window so the status bar will indicate that, and it's in the title. * At most once every 3 weeks there will also be a message box pop up when loading a log, to remind the user so that it isn't forgotten, as for obvious reasons this is not the intended use-case (hopefully once per 3 weeks isn't too often to be annoying).
839 lines
29 KiB
C#
839 lines
29 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);
|
|
|
|
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_PipelineState.SetStates(m_APIProperties, m_D3D11PipelineState, m_GLPipelineState);
|
|
|
|
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;
|
|
|
|
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
|
|
|
|
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
|
|
}
|
|
}
|