mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-05 01:20:42 +00:00
968 lines
33 KiB
C#
968 lines
33 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_LogLocal = false;
|
|
private bool m_LogLoaded = false;
|
|
|
|
private FileSystemWatcher m_LogWatcher = null;
|
|
|
|
private string m_LogFile = "";
|
|
|
|
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 BufferViewer m_MeshViewer = 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 bool IsLogLocal { get { return m_LogLocal; } set { m_LogLocal = value; } }
|
|
|
|
public FetchFrameInfo FrameInfo { get { return m_FrameInfo; } }
|
|
|
|
public APIProperties APIProps { get { return m_APIProperties; } }
|
|
|
|
public UInt32 CurEvent { get { return m_DeferredEvent > 0 ? m_DeferredEvent : m_EventID; } }
|
|
|
|
public FetchDrawcall[] CurDrawcalls { get { return GetDrawcalls(); } }
|
|
|
|
public FetchDrawcall CurDrawcall { get { return GetDrawcall(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(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.immContextId ||
|
|
draws[refdraw].context != m_FrameInfo.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[start].eventID;
|
|
mark.drawcallID = draws[start].drawcallID;
|
|
mark.markerColour = new float[] { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
|
|
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();
|
|
}
|
|
|
|
// because some engines (*cough*unreal*cough*) provide a valid marker colour of
|
|
// opaque black for every marker, instead of transparent black (i.e. just 0) we
|
|
// want to check for that case and remove the colors, instead of displaying all
|
|
// the markers as black which is not what's intended.
|
|
//
|
|
// Valid marker colors = has at least one color somewhere that isn't (0.0, 0.0, 0.0, 1.0)
|
|
// or (0.0, 0.0, 0.0, 0.0)
|
|
//
|
|
// This will fail if no marker colors are set anyway, but then removing them is
|
|
// harmless.
|
|
private bool HasValidMarkerColors(FetchDrawcall[] draws)
|
|
{
|
|
if (draws.Length == 0)
|
|
return false;
|
|
|
|
foreach (var d in draws)
|
|
{
|
|
if (d.markerColour[0] != 0.0f ||
|
|
d.markerColour[1] != 0.0f ||
|
|
d.markerColour[2] != 0.0f ||
|
|
(d.markerColour[3] != 1.0f && d.markerColour[3] != 0.0f))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (HasValidMarkerColors(d.children))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void RemoveMarkerColors(FetchDrawcall[] draws)
|
|
{
|
|
for (int i = 0; i < draws.Length; i++)
|
|
{
|
|
draws[i].markerColour[0] = 0.0f;
|
|
draws[i].markerColour[1] = 0.0f;
|
|
draws[i].markerColour[2] = 0.0f;
|
|
draws[i].markerColour[3] = 0.0f;
|
|
|
|
RemoveMarkerColors(draws[i].children);
|
|
}
|
|
}
|
|
|
|
public void LoadLogfile(string logFile, bool temporary, bool local)
|
|
{
|
|
m_LogFile = logFile;
|
|
|
|
m_LogLocal = local;
|
|
|
|
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)
|
|
ProgressPopup modal = new ProgressPopup(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.OpenCapture(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();
|
|
|
|
MessageBox.Show(String.Format("{0}\nFailed to open file 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 && local)
|
|
{
|
|
m_Config.AddRecentFile(m_Config.RecentLogFiles, logFile, 10);
|
|
|
|
if (File.Exists(Core.ConfigFilename))
|
|
m_Config.Serialize(Core.ConfigFilename);
|
|
}
|
|
|
|
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 = FakeProfileMarkers(r.GetDrawcalls());
|
|
|
|
bool valid = HasValidMarkerColors(m_DrawCalls);
|
|
|
|
if (!valid)
|
|
RemoveMarkerColors(m_DrawCalls);
|
|
|
|
postloadProgress = 0.4f;
|
|
|
|
m_Buffers = r.GetBuffers();
|
|
|
|
postloadProgress = 0.7f;
|
|
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.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;
|
|
|
|
if (local)
|
|
{
|
|
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_EventID > 0 ? m_EventID-1 : 1, true);
|
|
});
|
|
|
|
SetEventID(null, CurEvent);
|
|
}
|
|
|
|
public void CloseLogfile()
|
|
{
|
|
if (!m_LogLoaded) return;
|
|
|
|
m_LogFile = "";
|
|
|
|
m_Renderer.CloseThreadSync();
|
|
|
|
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()
|
|
{
|
|
return m_DrawCalls;
|
|
}
|
|
|
|
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 eventID)
|
|
{
|
|
if (m_DrawCalls == null)
|
|
return null;
|
|
|
|
return GetDrawcall(m_DrawCalls, 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 BufferViewer GetMeshViewer()
|
|
{
|
|
if (m_MeshViewer == null || m_MeshViewer.IsDisposed)
|
|
{
|
|
m_MeshViewer = new BufferViewer(this, true);
|
|
AddLogViewer(m_MeshViewer);
|
|
}
|
|
|
|
return m_MeshViewer;
|
|
}
|
|
|
|
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(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 eventID,
|
|
ResourceId ctx, UInt32 firstDeferred, UInt32 lastDeferred)
|
|
{
|
|
m_EventID = eventID;
|
|
|
|
m_DeferredEvent = lastDeferred;
|
|
|
|
m_Renderer.Invoke((ReplayRenderer r) => { r.SetContextFilter(ctx, firstDeferred, lastDeferred); });
|
|
m_Renderer.Invoke((ReplayRenderer r) => {
|
|
r.SetFrameEvent(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(eventID)));
|
|
else
|
|
logviewer.OnEventSelected(eventID);
|
|
}
|
|
}
|
|
|
|
public void RefreshStatus()
|
|
{
|
|
SetEventID(null, m_EventID, true);
|
|
}
|
|
|
|
public void SetEventID(ILogViewerForm exclude, UInt32 eventID)
|
|
{
|
|
SetEventID(exclude, eventID, false);
|
|
}
|
|
|
|
private void SetEventID(ILogViewerForm exclude, UInt32 eventID, bool force)
|
|
{
|
|
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_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(eventID)));
|
|
else
|
|
logviewer.OnEventSelected(eventID);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|