Files
renderdoc/renderdocui/Windows/BufferViewer.cs
T

3564 lines
128 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.IO;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using renderdocui.Code;
using renderdocui.Windows.Dialogs;
using renderdoc;
namespace renderdocui.Windows
{
// since they're quite similar, the BufferViewer class displays both the geometry mesh
// data as well as raw views of buffers (with custom formatting). See the two different constructors.
//
// When we go to fetch data we do that on a separate thread to parse the byte-stream that comes
// back according to the format (since there can be mismatches between pipeline stages, we always take
// the "output" of each stage.
//
// Once we have the data parsed we flag that and the UI then uses the VirtualMode on the datagridview
// to populate rows lazily as and when they're needed.
//
// The threading is messy and with the 'hiding' of threading details behind invokes for the UI I suspect
// this whole setup is fragile and/or not thread-safe in areas. It would be nice to control all the threading
// explicitly myself but the UI interaction makes that murky, so bear in mind that you need to be able to
// handle changing events while a thread is still going and about to populate some data etc, and be able
// to abort that and start anew without anything breaking or racing.
public partial class BufferViewer : DockContent, ILogViewerForm, IBufferFormatProcessor
{
#region Data Privates
// we try to bundle up data so that as much as possible things don't change out from under a thread
// or invoke and get us into an 'impossible' state.
// This class describes the format/structure that we'll use to interpret the byte stream
private class Input
{
public FormatElement[] BufferFormats = null;
public ResourceId[] Buffers = null;
public object[][] GenericValues = null;
public uint[] Strides = null;
public ulong[] Offsets = null;
public PrimitiveTopology Topology = PrimitiveTopology.Unknown;
public FetchDrawcall Drawcall = null;
public ResourceId IndexBuffer = ResourceId.Null;
public ulong IndexOffset = 0;
public bool IndexRestart = true;
public uint IndexRestartValue = uint.MaxValue;
}
// contains the raw bytes (and any state necessary from the drawcall itself)
private class Dataset
{
public uint IndexCount = 0;
public MeshFormat PostVS;
public PrimitiveTopology Topology = PrimitiveTopology.Unknown;
public byte[][] Buffers = null;
public uint[] Indices = null; // 'displayed' indices from index buffer
public uint[] DataIndices = null; // where to find the data, different only for PostVS
}
// we generate a UIState object with everything needed to populate the actual
// visible data in the UI.
private class UIState
{
public UIState(MeshDataStage stage)
{
m_Stage = stage;
}
public Input m_Input = null;
public MeshDataStage m_Stage = MeshDataStage.VSIn;
public Dataset m_Data = null;
public Stream[] m_Stream = null;
public BinaryReader[] m_Reader = null;
public object[][] m_Rows = null;
public byte[] m_RawData = null;
public uint m_RawStride = 0;
public DataGridView m_GridView = null;
public DockContent m_DockContent = null;
public Thread m_DataParseThread = null;
private Object m_ThreadLock = new Object();
public Vec3f[] m_MinBounds = null;
public Vec3f[] m_MaxBounds = null;
public void AbortThread()
{
lock (m_ThreadLock)
{
if (m_DataParseThread != null)
{
if (m_DataParseThread.ThreadState != ThreadState.Aborted &&
m_DataParseThread.ThreadState != ThreadState.Stopped)
{
m_DataParseThread.Abort();
m_DataParseThread.Join();
}
m_DataParseThread = null;
}
}
}
}
// one UI state for each stage
private UIState m_VSIn = new UIState(MeshDataStage.VSIn);
private UIState m_VSOut = new UIState(MeshDataStage.VSOut);
private UIState m_GSOut = new UIState(MeshDataStage.GSOut);
// this points to the 'highlighted'/current UI state.
private UIState m_ContextUIState = null;
private bool m_Loaded = false;
// this becomes a 'cancel' flag for any in-flight invokes
// to set data. Since we can't cancel then wait on an invoke
// from the UI thread synchronously, we can just increment this
// and anything in flight will bail out as soon as it notices this
// is different.
private int m_ReqID = 0;
private const int CellDefaultWidth = 100;
private int CellFloatWidth = CellDefaultWidth;
private BufferFormatSpecifier m_FormatSpecifier = null;
private string m_FormatText = "";
private UIState GetUIState(MeshDataStage type)
{
if (type == MeshDataStage.VSIn)
return m_VSIn;
if (type == MeshDataStage.VSOut)
return m_VSOut;
if (type == MeshDataStage.GSOut)
return m_GSOut;
return null;
}
private UIState GetUIState(object sender)
{
if (sender == vsInBufferView)
return m_VSIn;
if (sender == vsOutBufferView)
return m_VSOut;
if (sender == gsOutBufferView)
return m_GSOut;
return null;
}
#endregion
#region Privates
private Core m_Core;
private ReplayOutput m_Output = null;
private byte[] m_Zeroes = null;
private OutputConfig m_OutConfig = new OutputConfig();
private MeshDisplay m_MeshDisplay = new MeshDisplay();
private IntPtr RenderHandle = IntPtr.Zero;
// Cameras
private TimedUpdate m_Updater = null;
private ArcballCamera m_Arcball = null;
private FlyCamera m_Flycam = null;
private CameraControls m_CurrentCamera = null;
#endregion
public BufferViewer(Core core, bool meshview)
{
InitializeComponent();
Icon = global::renderdocui.Properties.Resources.icon;
UI_SetupDocks(meshview);
m_Zeroes = new byte[512];
for (int i = 0; i < 512; i++) m_Zeroes[i] = 0;
m_VSIn.m_GridView = vsInBufferView;
m_VSOut.m_GridView = vsOutBufferView;
m_GSOut.m_GridView = gsOutBufferView;
largeBufferWarning.Visible = false;
byteOffset.Enabled = false;
rowOffset.Font =
byteOffset.Font =
instanceIdxToolitem.Font =
camSpeed.Font =
fovGuess.Font =
aspectGuess.Font =
nearGuess.Font =
farGuess.Font =
core.Config.PreferredFont;
m_ContextUIState = m_VSIn;
DockHandler.GetPersistStringCallback = PersistString;
exportToToolStripMenuItem.Enabled = exportToolItem.Enabled = false;
m_Core = core;
this.DoubleBuffered = true;
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
RenderHandle = render.Handle;
render.Painting = true;
render.MouseWheel += render_MouseWheel;
render.MouseWheelHandler = render_MouseWheel;
(render as Control).KeyDown += new KeyEventHandler(BufferViewer_KeyDown);
(render as Control).KeyUp += new KeyEventHandler(BufferViewer_KeyUp);
ResetConfig();
MeshView = meshview;
if (!MeshView)
{
debugVertexToolItem.Visible = debugSep.Visible = false;
instLabel.Visible = instSep.Visible = instanceIdxToolitem.Visible = false;
syncViewsToolItem.Visible = false;
highlightVerts.Visible = false;
byteOffset.Visible = true; byteOffsLab.Visible = true;
rowRange.Visible = true; rowRangeLab.Visible = true;
byteOffset.Text = "0";
rowRange.Text = DefaultMaxRows.ToString();
Text = "Buffer Contents";
}
else
{
byteOffset.Visible = false; byteOffsLab.Visible = false;
rowRange.Visible = false; rowRangeLab.Visible = false;
byteOffset.Text = "0";
rowRange.Text = DefaultMaxRows.ToString();
Text = "Mesh Output";
}
m_Core.AddLogViewer(this);
}
private void ResetConfig()
{
m_OutConfig.m_Type = OutputType.MeshDisplay;
m_MeshDisplay = new MeshDisplay();
m_MeshDisplay.type = MeshDataStage.VSIn;
m_MeshDisplay.fov = 90.0f;
m_MeshDisplay.solidShadeMode = SolidShadeMode.None;
solidShading.SelectedIndex = 0;
m_MeshDisplay.showPrevInstances = false;
m_MeshDisplay.showAllInstances = false;
m_MeshDisplay.showWholePass = false;
drawRange.SelectedIndex = 0;
if (m_Arcball != null)
m_Arcball.Camera.Shutdown();
if (m_Flycam != null)
m_Flycam.Camera.Shutdown();
m_Arcball = new ArcballCamera();
m_Flycam = new FlyCamera();
m_CurrentCamera = m_Arcball;
m_Updater = new TimedUpdate(10, TimerUpdate);
m_Arcball.SpeedMultiplier = m_Flycam.SpeedMultiplier = (float)camSpeed.Value;
fovGuess.Text = m_MeshDisplay.fov.ToString("G");
controlType.SelectedIndex = 0;
}
private void UI_SetupDocks(bool meshview)
{
if (meshview)
{
var w = Helpers.WrapDockContent(dockPanel, previewTab, "Preview");
w.CloseButton = false;
w.CloseButtonVisible = false;
w.Show(dockPanel, DockState.DockBottom);
m_VSIn.m_DockContent = Helpers.WrapDockContent(dockPanel, vsInBufferView, "VS Input");
m_VSIn.m_DockContent.CloseButton = false;
m_VSIn.m_DockContent.CloseButtonVisible = false;
m_VSIn.m_DockContent.Show(dockPanel, DockState.Document);
m_GSOut.m_DockContent = Helpers.WrapDockContent(dockPanel, gsOutBufferView, "GS/DS Output");
m_GSOut.m_DockContent.CloseButton = false;
m_GSOut.m_DockContent.CloseButtonVisible = false;
m_GSOut.m_DockContent.Show(m_VSIn.m_DockContent.Pane, DockAlignment.Right, 0.5);
m_VSOut.m_DockContent = Helpers.WrapDockContent(dockPanel, vsOutBufferView, "VS Output");
m_VSOut.m_DockContent.CloseButton = false;
m_VSOut.m_DockContent.CloseButtonVisible = false;
m_VSOut.m_DockContent.Show(m_GSOut.m_DockContent.Pane, m_GSOut.m_DockContent);
}
else
{
previewTab.Visible = false;
vsOutBufferView.Visible = false;
gsOutBufferView.Visible = false;
var w = Helpers.WrapDockContent(dockPanel, vsInBufferView, "Buffer Contents");
w.DockState = DockState.Document;
w.Show();
}
}
public class PersistData
{
public static int currentPersistVersion = 3;
public int persistVersion = currentPersistVersion;
public string panelLayout;
public bool meshView;
public static PersistData GetDefaults()
{
PersistData data = new PersistData();
data.panelLayout = "";
data.meshView = true;
return data;
}
}
public void InitFromPersistString(string str)
{
PersistData data = null;
try
{
if (str.Length > GetType().ToString().Length)
{
var reader = new StringReader(str.Substring(GetType().ToString().Length));
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(PersistData));
data = (PersistData)xs.Deserialize(reader);
reader.Close();
}
}
catch (System.Xml.XmlException)
{
}
catch (InvalidOperationException)
{
// don't need to handle it. Leave data null and pick up defaults below
}
if (data == null || data.persistVersion != PersistData.currentPersistVersion)
{
data = PersistData.GetDefaults();
}
ApplyPersistData(data);
}
private string onloadLayout = "";
Control[] LayoutPersistors
{
get
{
return new Control[] {
previewTab,
vsInBufferView,
gsOutBufferView,
vsOutBufferView,
};
}
}
private IDockContent GetContentFromPersistString(string persistString)
{
foreach (var p in LayoutPersistors)
if (persistString == p.Name && p.Parent is IDockContent && (p.Parent as DockContent).DockPanel == null)
return p.Parent as IDockContent;
return null;
}
private void ApplyPersistData(PersistData data)
{
MeshView = data.meshView;
onloadLayout = data.panelLayout;
}
// note that raw buffer viewers do not persist deliberately
private string PersistString()
{
if (!MeshView) return "";
var writer = new StringWriter();
writer.Write(GetType().ToString());
PersistData data = new PersistData();
// passing in a MemoryStream gets disposed - can't see a way to retrieve this
// in-memory.
var enc = new UnicodeEncoding();
var path = Path.GetTempFileName();
dockPanel.SaveAsXml(path, "", enc);
try
{
data.panelLayout = File.ReadAllText(path, enc);
File.Delete(path);
}
catch (System.Exception)
{
// can't recover
return writer.ToString();
}
data.meshView = MeshView;
System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(PersistData));
xs.Serialize(writer, data);
return writer.ToString();
}
#region ILogViewerForm
public void OnLogfileClosed()
{
if (IsDisposed) return;
m_Output = null;
ResetConfig();
ClearStoredData();
exportToToolStripMenuItem.Enabled = exportToolItem.Enabled = false;
}
public void OnLogfileLoaded()
{
ClearStoredData();
exportToToolStripMenuItem.Enabled = exportToolItem.Enabled = true;
var draw = m_Core.CurDrawcall;
previewTab.SelectedIndex = 0;
ulong byteoffs = ByteOffset;
if (MeshView)
{
if (draw == null)
{
m_VSIn.AbortThread();
m_VSOut.AbortThread();
m_GSOut.AbortThread();
return;
}
int curReq = m_ReqID;
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
if (curReq != m_ReqID)
return;
m_Output = r.CreateOutput(RenderHandle, OutputType.MeshDisplay);
m_Output.SetOutputConfig(m_OutConfig);
RT_UpdateRenderOutput(r);
m_Output.Display(); // pump the display once, this will fetch postvs data
m_VSIn.m_Input = GetCurrentMeshInput(draw, MeshDataStage.VSIn);
m_VSOut.m_Input = GetCurrentMeshInput(draw, MeshDataStage.VSOut);
m_GSOut.m_Input = GetCurrentMeshInput(draw, MeshDataStage.GSOut);
var contentsVSIn = RT_FetchBufferContents(MeshDataStage.VSIn, r, m_VSIn.m_Input, byteoffs);
var contentsVSOut = RT_FetchBufferContents(MeshDataStage.VSOut, r, m_VSOut.m_Input, byteoffs);
var contentsGSOut = RT_FetchBufferContents(MeshDataStage.GSOut, r, m_GSOut.m_Input, byteoffs);
if (curReq != m_ReqID)
return;
this.BeginInvoke(new Action(() =>
{
if (curReq != m_ReqID)
return;
UI_AutoFetchRenderComponents(MeshDataStage.VSIn, true);
UI_AutoFetchRenderComponents(MeshDataStage.VSOut, true);
UI_AutoFetchRenderComponents(MeshDataStage.GSOut, true);
UI_AutoFetchRenderComponents(MeshDataStage.VSIn, false);
UI_AutoFetchRenderComponents(MeshDataStage.VSOut, false);
UI_AutoFetchRenderComponents(MeshDataStage.GSOut, false);
UI_UpdateMeshRenderComponents();
UI_SetAllColumns();
UI_SetRowsData(MeshDataStage.VSIn, contentsVSIn, 0);
if (m_VSOut.m_Input != null)
UI_SetRowsData(MeshDataStage.VSOut, contentsVSOut, 0);
if (m_GSOut.m_Input != null)
UI_SetRowsData(MeshDataStage.GSOut, contentsGSOut, 0);
camGuess_PropChanged();
m_Loaded = true;
}));
});
}
else
{
m_Loaded = true;
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
if (IsDisposed) return;
m_Output = null;
});
}
}
private void CalcCellFloatWidth()
{
Graphics g = vsInBufferView.CreateGraphics();
Font f = vsInBufferView.DefaultCellStyle.Font;
// measure a few doubles at different extremes to get a max column width
// for floats under the current float formatter settings
// all numbers are negative to account for negative sign
double[] testNums = new double[] {
-1.0, // some default number
-1.2345e-200, // something that will definitely be exponential notation
-123456.7890123456789, // some number with a 'large' value before the decimal, and numbers after the decimal
};
CellFloatWidth = CellDefaultWidth;
foreach (double d in testNums)
{
float stringWidth = g.MeasureString(Formatter.Format(d), f).Width;
CellFloatWidth = Math.Max(CellFloatWidth, (int)stringWidth + 5);
}
g.Dispose();
}
public void OnEventSelected(UInt32 eventID)
{
if (IsDisposed) return;
// ignore OnEventSelected until we've loaded
if (!m_Loaded)
return;
ClearStoredData();
var draw = m_Core.CurDrawcall;
byteOffset.Enabled = false;
instanceIdxToolitem.Enabled = (draw != null && draw.numInstances > 1);
if (!instanceIdxToolitem.Enabled)
instanceIdxToolitem.Text = "0";
if (MeshView && draw == null)
{
m_VSIn.AbortThread();
m_VSOut.AbortThread();
m_GSOut.AbortThread();
return;
}
int[] horizscroll = new int[] {
m_VSIn.m_GridView.HorizontalScrollingOffset,
m_VSOut.m_GridView.HorizontalScrollingOffset,
m_GSOut.m_GridView.HorizontalScrollingOffset,
};
int curReq = m_ReqID;
ulong byteoffs = ByteOffset;
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
m_VSIn.AbortThread();
m_VSOut.AbortThread();
m_GSOut.AbortThread();
if (curReq != m_ReqID)
return;
if (MeshView)
{
MeshDataStage[] stages = new MeshDataStage[] { MeshDataStage.VSIn, MeshDataStage.VSOut, MeshDataStage.GSOut };
FormatElement[] prevPos = new FormatElement[3];
FormatElement[] prevSecond = new FormatElement[3];
for (int i = 0; i < 3; i++)
{
prevPos[i] = GetPosHighlightFormatElement(stages[i]);
prevSecond[i] = GetSecondHighlightFormatElement(stages[i]);
}
m_VSIn.m_Input = GetCurrentMeshInput(draw, MeshDataStage.VSIn);
m_VSOut.m_Input = GetCurrentMeshInput(draw, MeshDataStage.VSOut);
m_GSOut.m_Input = GetCurrentMeshInput(draw, MeshDataStage.GSOut);
for(int i=0; i < 3; i++)
{
FormatElement curPos = GetPosHighlightFormatElement(stages[i]);
FormatElement curSecond = GetSecondHighlightFormatElement(stages[i]);
if (prevPos[i] == null || prevPos[i] != curPos) UI_AutoFetchRenderComponents(stages[i], true);
if (prevSecond[i] == null || prevSecond[i] != curSecond) UI_AutoFetchRenderComponents(stages[i], false);
}
}
var contentsVSIn = RT_FetchBufferContents(MeshDataStage.VSIn, r, m_VSIn.m_Input, byteoffs);
var contentsVSOut = RT_FetchBufferContents(MeshDataStage.VSOut, r, m_VSOut.m_Input, byteoffs);
var contentsGSOut = RT_FetchBufferContents(MeshDataStage.GSOut, r, m_GSOut.m_Input, byteoffs);
if (curReq != m_ReqID)
return;
this.BeginInvoke(new Action(() =>
{
if (curReq != m_ReqID)
return;
m_VSIn.AbortThread();
m_VSOut.AbortThread();
m_GSOut.AbortThread();
if (m_VSIn.m_Input != null)
UI_SetRowsData(MeshDataStage.VSIn, contentsVSIn, horizscroll[0]);
if (m_VSOut.m_Input != null)
UI_SetRowsData(MeshDataStage.VSOut, contentsVSOut, horizscroll[1]);
if (m_GSOut.m_Input != null)
UI_SetRowsData(MeshDataStage.GSOut, contentsGSOut, horizscroll[2]);
if (MeshView)
UI_UpdateMeshRenderComponents();
UI_SetAllColumns();
camGuess_PropChanged();
render.Invalidate();
}));
});
}
#endregion
#region Data Setting
private void ClearStoredData()
{
UIState[] states = { m_VSIn, m_VSOut, m_GSOut };
m_ReqID++;
foreach (var s in states)
{
s.AbortThread();
if(s.m_Reader != null)
{
for (int i = 0; i < s.m_Reader.Length; i++)
{
if(s.m_Reader[i] != null)
s.m_Reader[i].Dispose();
s.m_Reader[i] = null;
}
}
if (s.m_Stream != null)
{
for (int i = 0; i < s.m_Stream.Length; i++)
{
if (s.m_Reader[i] != null)
s.m_Stream[i].Dispose();
s.m_Stream[i] = null;
}
}
s.m_Stream = null;
s.m_Reader = null;
s.m_RawData = null;
if (s.m_Data != null && s.m_Data.Buffers != null)
{
for (int i = 0; i < s.m_Data.Buffers.Length; i++)
s.m_Data.Buffers[i] = null;
}
if (s.m_Data != null)
{
s.m_Data.DataIndices = null;
}
s.m_Data = null;
s.m_RawStride = 0;
s.m_Rows = null;
s.m_GridView.RowCount = 0;
}
ClearHighlightVerts();
}
public bool MeshView;
public int RowOffset
{
get
{
int row = 0;
int.TryParse(rowOffset.Text, out row);
return row;
}
set
{
rowOffset.Text = value.ToString();
}
}
private uint DefaultMaxRows { get { return 200000; } }
private int MaxRowCount
{
get
{
// for now, don't clamp rows on mesh view
if (IsDisposed || MeshView) return int.MaxValue;
int maxrows = 0;
int.TryParse(rowRange.Text, out maxrows);
return maxrows;
}
}
private ulong ByteOffset
{
get
{
if (IsDisposed) return 0;
ulong offs = 0;
ulong.TryParse(byteOffset.Text, out offs);
return offs;
}
}
#region Get Data Formats/Organisation
public void ViewRawBuffer(bool isBuffer, ulong offset, ulong size, ResourceId id)
{
ViewRawBuffer(isBuffer, offset, size, id, "");
}
public void ViewRawBuffer(bool isBuffer, ulong offset, ulong size, ResourceId id, string formatString)
{
if (m_Core.CurBuffers == null) return;
m_FormatText = formatString;
UInt64 len = 0;
Text = "Buffer Contents";
foreach (var b in m_Core.CurBuffers)
{
if (b.ID == id)
{
Text = b.name + " - Contents";
len = b.length;
break;
}
}
Input input = new Input();
string errors = "";
FormatElement[] elems = FormatElement.ParseFormatString(formatString, len, true, out errors);
input.Strides = new uint[] { elems.Last().offset + elems.Last().ByteSize };
input.Buffers = new ResourceId[] { isBuffer ? id : ResourceId.Null, isBuffer ? ResourceId.Null : id };
input.Offsets = new ulong[] { 0 };
input.IndexBuffer = ResourceId.Null;
input.BufferFormats = elems;
input.IndexOffset = 0;
largeBufferWarning.Visible = false;
byteOffset.Text = offset.ToString();
if (size == ulong.MaxValue)
{
rowRange.Text = DefaultMaxRows.ToString();
}
else
{
uint rows = (uint)(size / input.Strides[0]);
if (rows * input.Strides[0] < size)
rows++;
rowRange.Text = rows.ToString();
}
m_VSIn.m_Input = input;
ShowFormatSpecifier();
m_FormatSpecifier.SetErrors(errors);
ClearStoredData();
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
if (IsDisposed) return;
var contents = RT_FetchBufferContents(MeshDataStage.VSIn, r, input, offset);
this.BeginInvoke(new Action(() =>
{
if (IsDisposed) return;
UI_SetRowsData(MeshDataStage.VSIn, contents, 0);
UI_SetColumns(MeshDataStage.VSIn, input.BufferFormats);
}));
});
}
// used for the mesh view, to get the format of the mesh input from whichever stage that
// we're looking at
private Input GetCurrentMeshInput(FetchDrawcall draw, MeshDataStage type)
{
if (!MeshView)
return null;
Input ret = new Input();
ret.Drawcall = draw;
ret.Topology = draw != null ? draw.topology : PrimitiveTopology.Unknown;
ResourceId ibuffer = ResourceId.Null;
ulong ioffset = 0;
m_Core.CurPipelineState.GetIBuffer(out ibuffer, out ioffset);
if (draw != null && (draw.flags & DrawcallFlags.UseIBuffer) == 0)
{
ibuffer = ResourceId.Null;
ioffset = 0;
}
ret.IndexBuffer = ibuffer;
ret.IndexOffset = ioffset;
ret.IndexRestart = m_Core.CurPipelineState.IsStripRestartEnabled();
ret.IndexRestartValue = m_Core.CurPipelineState.GetStripRestartIndex(draw != null ? draw.indexByteWidth : 0);
if (type != MeshDataStage.VSIn)
{
ShaderReflection details = null;
if (type == MeshDataStage.VSOut)
details = m_Core.CurPipelineState.GetShaderReflection(ShaderStageType.Vertex);
else if (type == MeshDataStage.GSOut)
{
details = m_Core.CurPipelineState.GetShaderReflection(ShaderStageType.Geometry);
if (details == null)
details = m_Core.CurPipelineState.GetShaderReflection(ShaderStageType.Domain);
}
if (details == null)
return null;
List<FormatElement> f = new List<FormatElement>();
int posidx = -1;
for (int i = 0; i < details.OutputSig.Length; i++)
{
var sig = details.OutputSig[i];
f.Add(new FormatElement());
f[i].buffer = 0;
f[i].name = details.OutputSig[i].varName.Length > 0 ? details.OutputSig[i].varName : details.OutputSig[i].semanticIdxName;
f[i].format.compByteWidth = sizeof(float);
f[i].format.compCount = sig.compCount;
f[i].format.compType = sig.compType;
f[i].format.special = false;
f[i].format.rawType = 0;
f[i].perinstance = false;
f[i].instancerate = 1;
f[i].rowmajor = false;
f[i].matrixdim = 1;
f[i].systemValue = sig.systemValue;
if(f[i].systemValue == SystemAttribute.Position)
posidx = i;
}
// shift position attribute up to first, keeping order otherwise
// the same
if (posidx > 0)
{
FormatElement pos = f[posidx];
f.RemoveAt(posidx);
f.Insert(0, pos);
}
uint offset = 0;
for (int i = 0; i < details.OutputSig.Length; i++)
{
uint numComps = f[i].format.compCount;
uint elemSize = f[i].format.compType == FormatComponentType.Double ? 8U : 4U;
if (m_Core.CurPipelineState.HasAlignedPostVSData)
{
if (numComps == 2)
offset = offset.AlignUp(2U * elemSize);
else if (numComps > 2)
offset = offset.AlignUp(4U * elemSize);
}
f[i].offset = offset;
offset += numComps * elemSize;
}
ret.BufferFormats = f.ToArray();
ret.Strides = new uint[] { offset };
ret.Offsets = new ulong[] { 0 };
ret.Buffers = null;
return ret;
}
BoundVBuffer[] vbs = m_Core.CurPipelineState.GetVBuffers();
ResourceId[] bs = new ResourceId[vbs.Length];
uint[] s = new uint[vbs.Length];
ulong[] o = new ulong[vbs.Length];
for (int i = 0; i < vbs.Length; i++)
{
bs[i] = vbs[i].Buffer;
s[i] = vbs[i].ByteStride;
o[i] = vbs[i].ByteOffset;
}
{
FormatElement[] f = null;
var vinputs = m_Core.CurPipelineState.GetVertexInputs();
f = new FormatElement[vinputs.Length];
ret.GenericValues = new object[vinputs.Length][];
int numinputs = vinputs.Length;
int i = 0;
foreach (var a in vinputs)
{
if (!a.Used)
{
numinputs--;
Array.Resize(ref f, numinputs);
Array.Resize(ref ret.GenericValues, numinputs);
continue;
}
f[i] = new FormatElement(a.Name,
a.VertexBuffer,
a.RelativeByteOffset,
a.PerInstance,
a.InstanceRate,
false, // row major matrix
1, // matrix dimension
a.Format,
false);
ret.GenericValues[i] = a.GenericValue;
i++;
}
ret.BufferFormats = f;
ret.Strides = s;
ret.Offsets = o;
ret.Buffers = bs;
}
return ret;
}
#endregion
#region Get Actual Bytes
private Dataset RT_FetchBufferContents(MeshDataStage type, ReplayRenderer r, Input input, ulong byteoffs)
{
Dataset ret = new Dataset();
ret.IndexCount = 0;
if (input == null)
return ret;
if (!MeshView)
{
if (input != null && (input.Buffers[0] != ResourceId.Null || input.Buffers[1] != ResourceId.Null))
{
ret.Buffers = new byte[1][];
if(input.Buffers[0] != ResourceId.Null)
ret.Buffers[0] = r.GetBufferData(input.Buffers[0], byteoffs, 0);
else if (input.Buffers[1] != ResourceId.Null)
ret.Buffers[0] = r.GetTextureData(input.Buffers[1], (uint)byteoffs, 0);
ret.Indices = null;
ret.DataIndices = null;
ret.IndexCount = (uint)ret.Buffers[0].Length / input.Strides[0];
}
return ret;
}
if (input.Drawcall == null) return ret;
ret.IndexCount = input.Drawcall.numIndices;
ret.Topology = input.Topology;
ret.PostVS.stride = 0;
if (type != MeshDataStage.VSIn)
{
ret.PostVS = r.GetPostVSData(Math.Min(m_MeshDisplay.curInstance, Math.Max(1U, input.Drawcall.numInstances)), type);
ret.Buffers = new byte[1][];
if (ret.PostVS.buf == ResourceId.Null)
{
ret.IndexCount = 0;
ret.Topology = PrimitiveTopology.Unknown;
}
else
{
ret.Topology = ret.PostVS.topo;
ret.IndexCount = ret.PostVS.numVerts;
}
ret.Indices = null;
ret.DataIndices = null;
uint maxIndex = Math.Max(ret.IndexCount, 1) - 1;
if (ret.PostVS.buf != ResourceId.Null && type == MeshDataStage.VSOut &&
(input.Drawcall.flags & DrawcallFlags.UseIBuffer) > 0 && input.IndexBuffer != ResourceId.Null)
{
ret.IndexCount = input.Drawcall.numIndices;
byte[] rawidxs = r.GetBufferData(input.IndexBuffer,
input.IndexOffset + input.Drawcall.indexOffset * input.Drawcall.indexByteWidth,
ret.IndexCount * input.Drawcall.indexByteWidth);
if (input.Drawcall.indexByteWidth == 0 || rawidxs == null || rawidxs.Length == 0)
{
ret.Indices = new uint[0] { };
}
else
{
ret.Indices = new uint[rawidxs.Length / input.Drawcall.indexByteWidth];
if (input.Drawcall.indexByteWidth == 1)
{
for (int i = 0; i < rawidxs.Length; i++)
ret.Indices[i] = rawidxs[i];
}
else if (input.Drawcall.indexByteWidth == 2)
{
ushort[] tmp = new ushort[rawidxs.Length / 2];
Buffer.BlockCopy(rawidxs, 0, tmp, 0, tmp.Length * sizeof(ushort));
for (int i = 0; i < tmp.Length; i++)
{
ret.Indices[i] = tmp[i];
}
}
else if (input.Drawcall.indexByteWidth == 4)
{
Buffer.BlockCopy(rawidxs, 0, ret.Indices, 0, ret.Indices.Length * sizeof(uint));
}
}
rawidxs = r.GetBufferData(ret.PostVS.idxbuf, 0, 0);
if (input.Drawcall.indexByteWidth == 0 || rawidxs == null || rawidxs.Length == 0)
{
ret.DataIndices = new uint[0] { };
}
else
{
ret.DataIndices = new uint[rawidxs.Length / input.Drawcall.indexByteWidth];
if (input.Drawcall.indexByteWidth == 1)
{
for (int i = 0; i < rawidxs.Length; i++)
ret.DataIndices[i] = rawidxs[i];
}
else if (input.Drawcall.indexByteWidth == 2)
{
ushort[] tmp = new ushort[rawidxs.Length / 2];
Buffer.BlockCopy(rawidxs, 0, tmp, 0, tmp.Length * sizeof(ushort));
for (int i = 0; i < tmp.Length; i++)
{
ret.DataIndices[i] = tmp[i];
}
}
else if (input.Drawcall.indexByteWidth == 4)
{
Buffer.BlockCopy(rawidxs, 0, ret.DataIndices, 0, ret.DataIndices.Length * sizeof(uint));
}
}
maxIndex = 0;
foreach (var i in ret.DataIndices)
{
if (i == input.IndexRestartValue && input.IndexRestart)
continue;
maxIndex = Math.Max(maxIndex, i);
}
}
if (ret.PostVS.buf != ResourceId.Null)
{
ret.Buffers[0] = r.GetBufferData(ret.PostVS.buf, ret.PostVS.offset, (maxIndex + 1) * ret.PostVS.stride);
uint stride = ret.PostVS.stride;
if (stride != 0 && (input.Drawcall.flags & DrawcallFlags.UseIBuffer) == 0)
ret.IndexCount = Math.Min(ret.IndexCount, (uint)ret.Buffers[0].Length / stride);
}
return ret;
}
else if (input.Buffers != null && m_Output != null)
{
uint maxIndex = Math.Max(ret.IndexCount, 1) - 1;
uint maxInstIndex = Math.Max(input.Drawcall.numInstances, 1) - 1;
if ((input.Drawcall.flags & DrawcallFlags.UseIBuffer) != 0 &&
input.IndexBuffer != ResourceId.Null)
{
byte[] rawidxs = r.GetBufferData(input.IndexBuffer,
input.IndexOffset + input.Drawcall.indexOffset * input.Drawcall.indexByteWidth,
ret.IndexCount * input.Drawcall.indexByteWidth);
if (input.Drawcall.indexByteWidth == 0 || rawidxs == null || rawidxs.Length == 0)
{
ret.Indices = new uint[0];
}
else
{
ret.Indices = new uint[rawidxs.Length / input.Drawcall.indexByteWidth];
if (input.Drawcall.indexByteWidth == 1)
{
for (int i = 0; i < rawidxs.Length; i++)
ret.Indices[i] = rawidxs[i];
}
else if (input.Drawcall.indexByteWidth == 2)
{
ushort[] tmp = new ushort[rawidxs.Length / 2];
Buffer.BlockCopy(rawidxs, 0, tmp, 0, tmp.Length * sizeof(ushort));
for (int i = 0; i < tmp.Length; i++)
{
ret.Indices[i] = tmp[i];
}
}
else if (input.Drawcall.indexByteWidth == 4)
{
Buffer.BlockCopy(rawidxs, 0, ret.Indices, 0, ret.Indices.Length * sizeof(uint));
}
}
maxIndex = 0;
foreach (var i in ret.Indices)
{
if (i == input.IndexRestartValue && input.IndexRestart)
continue;
maxIndex = Math.Max(maxIndex, i);
}
}
ret.DataIndices = ret.Indices;
ret.Buffers = new byte[input.Buffers.Length][];
for (int i = 0; i < input.Buffers.Length; i++)
{
bool used = false;
bool pi = false;
bool pv = false;
foreach (var f in input.BufferFormats)
{
if (f.buffer == i)
{
if (f.perinstance)
pi = true;
else
pv = true;
used = true;
}
}
uint maxIdx = 0;
uint offset = 0;
if(used)
{
if (pi)
{
maxIdx = maxInstIndex;
offset = input.Drawcall.instanceOffset;
}
if (pv)
{
maxIdx = Math.Max(maxIndex, maxIdx);
offset = input.Drawcall.vertexOffset;
if (input.Drawcall.baseVertex > 0)
maxIdx += (uint)input.Drawcall.baseVertex;
}
System.Diagnostics.Debug.Assert(pi != pv || (pi == false && pv == false));
}
if (!used || input.Buffers[i] == ResourceId.Null)
{
ret.Buffers[i] = null;
}
else
{
ret.Buffers[i] = r.GetBufferData(input.Buffers[i],
input.Offsets[i] + offset * input.Strides[i],
(maxIdx + 1) * input.Strides[i]);
if (ret.Buffers[i].Length < (maxIdx + 1) * input.Strides[i])
{
// clamped
}
}
}
return ret;
}
return ret;
}
#endregion
#region Setting Column Headers
private void UI_UpdateMeshColumns(MeshDataStage type, FormatElement[] el)
{
bool active = (type == m_MeshDisplay.type);
bool input = (type == MeshDataStage.VSIn);
var bufView = GetUIState(type).m_GridView;
if (bufView.ColumnCount == 0)
return;
int colidx = 2; // skip VTX and IDX columns
Color defaultCol = bufView.Columns[0].DefaultCellStyle.BackColor;
for (int e = 0; e < el.Length; e++)
{
for (int i = 0; i < el[e].format.compCount; i++)
{
if (colidx >= bufView.ColumnCount)
return;
DataGridViewColumn col = bufView.Columns[colidx];
colidx++;
if (e == m_PosElement[(int)type] && active)
{
if (i != 3 || !input)
col.DefaultCellStyle.BackColor = Color.SkyBlue;
else
col.DefaultCellStyle.BackColor = Color.LightCyan;
}
else if (e == m_SecondElement[(int)type] && active && m_MeshDisplay.solidShadeMode == SolidShadeMode.Secondary)
{
if ((m_MeshDisplay.secondary.showAlpha && i == 3) ||
(!m_MeshDisplay.secondary.showAlpha && i != 3))
col.DefaultCellStyle.BackColor = Color.LightGreen;
else
col.DefaultCellStyle.BackColor = Color.FromArgb(200, 238, 200);
}
else
col.DefaultCellStyle.BackColor = defaultCol;
}
}
}
private void UI_SetMeshColumns(MeshDataStage type, FormatElement[] el)
{
var bufView = GetUIState(type).m_GridView;
bufView.Columns.Clear();
DataGridViewTextBoxColumn Column = new DataGridViewTextBoxColumn();
Column.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
Column.HeaderText = "VTX";
Column.ReadOnly = true;
Column.Frozen = true;
Column.Width = 50;
Column.DividerWidth = 0;
Column.SortMode = DataGridViewColumnSortMode.Programmatic;
Column.DefaultCellStyle.Padding = new Padding(0, 2, 0, 2);
bufView.Columns.Add(Column);
Column = new DataGridViewTextBoxColumn();
Column.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
Column.HeaderText = "IDX";
Column.ReadOnly = true;
Column.Frozen = true;
Column.Width = 50;
Column.DividerWidth = 10;
Column.SortMode = DataGridViewColumnSortMode.Programmatic;
Column.DefaultCellStyle.Padding = new Padding(0, 2, 0, 2);
bufView.Columns.Add(Column);
for(int e=0; e < el.Length; e++)
{
for (int i = 0; i < el[e].format.compCount; i++)
{
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
col.MinimumWidth = 50;
col.Width =
(el[e].format.compType == FormatComponentType.Double ||
el[e].format.compType == FormatComponentType.Float)
? CellFloatWidth : CellDefaultWidth;
col.HeaderText = el[e].name;
col.ReadOnly = true;
col.SortMode = DataGridViewColumnSortMode.Programmatic;
col.DefaultCellStyle.Padding = new Padding(0, 2, 0, 2);
bufView.Columns.Add(col);
}
if (e < el.Length-1)
bufView.Columns[bufView.Columns.Count - 1].DividerWidth = 10;
}
UI_UpdateMeshColumns(type, el);
}
private void UI_SetRawColumns(FormatElement[] el)
{
vsInBufferView.Columns.Clear();
DataGridViewTextBoxColumn Column = new DataGridViewTextBoxColumn();
Column.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
Column.HeaderText = "Element";
Column.ReadOnly = true;
Column.Frozen = true;
Column.Width = 70;
Column.DividerWidth = 10;
Column.SortMode = DataGridViewColumnSortMode.Programmatic;
Column.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
Column.DefaultCellStyle.Padding = new Padding(0, 2, 0, 2);
vsInBufferView.Columns.Add(Column);
for (int e = 0; e < el.Length; e++)
{
for (int i = 0; i < el[e].format.compCount; i++)
{
DataGridViewTextBoxColumn col = new DataGridViewTextBoxColumn();
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.None;
col.MinimumWidth = 50;
col.Width =
(el[e].format.compType == FormatComponentType.Double ||
el[e].format.compType == FormatComponentType.Float)
? CellFloatWidth : CellDefaultWidth;
col.HeaderText = el[e].name;
col.ReadOnly = true;
col.SortMode = DataGridViewColumnSortMode.Programmatic;
col.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
col.DefaultCellStyle.Padding = new Padding(0, 2, 0, 2);
vsInBufferView.Columns.Add(col);
}
if (e < el.Length - 1)
vsInBufferView.Columns[vsInBufferView.Columns.Count - 1].DividerWidth = 10;
}
}
private void UI_SetColumns(MeshDataStage type, FormatElement[] el)
{
if (MeshView)
UI_SetMeshColumns(type, el);
else
UI_SetRawColumns(el);
}
private void UI_SetAllColumns()
{
if (m_VSIn.m_Input != null)
UI_SetColumns(MeshDataStage.VSIn, m_VSIn.m_Input.BufferFormats);
if (m_VSOut.m_Input != null)
UI_SetColumns(MeshDataStage.VSOut, m_VSOut.m_Input.BufferFormats);
if (m_GSOut.m_Input != null)
UI_SetColumns(MeshDataStage.GSOut, m_GSOut.m_Input.BufferFormats);
}
private void UI_UpdateAllColumns()
{
if (m_VSIn.m_Input != null)
UI_UpdateMeshColumns(MeshDataStage.VSIn, m_VSIn.m_Input.BufferFormats);
if (m_VSOut.m_Input != null)
UI_UpdateMeshColumns(MeshDataStage.VSOut, m_VSOut.m_Input.BufferFormats);
if (m_GSOut.m_Input != null)
UI_UpdateMeshColumns(MeshDataStage.GSOut, m_GSOut.m_Input.BufferFormats);
}
#endregion
#region Setting Row Data
private void UI_SetRowsData(MeshDataStage type, Dataset data, int horizScroll)
{
var state = GetUIState(type);
// only do this once, VSIn is guaranteed to be set (even if it's empty data)
if(type == MeshDataStage.VSIn)
CalcCellFloatWidth();
Input input = state.m_Input;
if(data.Buffers == null)
return;
var bufView = state.m_GridView;
bufView.RowCount = 0;
state.m_Data = data;
{
byte[][] d = data.Buffers;
state.m_Stream = new Stream[d.Length];
state.m_Reader = new BinaryReader[d.Length];
state.m_MinBounds = null;
state.m_MaxBounds = null;
var bufferFormats = input.BufferFormats;
foreach (var el in bufferFormats)
{
if (el.buffer < state.m_Stream.Length && state.m_Stream[el.buffer] == null)
{
if (d[el.buffer] == null)
{
state.m_Stream[el.buffer] = new MemoryStream(m_Zeroes);
state.m_Reader[el.buffer] = new BinaryReader(state.m_Stream[el.buffer]);
}
else
{
state.m_Stream[el.buffer] = new MemoryStream(d[el.buffer]);
state.m_Reader[el.buffer] = new BinaryReader(state.m_Stream[el.buffer]);
}
}
}
state.m_RawStride = 0;
foreach (var el in bufferFormats)
state.m_RawStride += el.ByteSize;
state.m_Rows = new object[data.IndexCount][];
if (!MeshView)
{
state.m_RawData = d[0];
this.BeginInvoke(new Action(() =>
{
UI_ShowRows(state, horizScroll);
}));
}
else
{
state.m_RawData = new byte[state.m_RawStride * data.IndexCount];
UI_FillRawData(state, horizScroll);
}
}
}
private void UI_FillRawData(UIState state, int horizScroll)
{
var data = state.m_Data;
Input input = state.m_Input;
uint instance = m_MeshDisplay.curInstance;
Thread th = Helpers.NewThread(new ThreadStart(() =>
{
byte[][] d = data.Buffers;
Stream rawStream = new MemoryStream(state.m_RawData);
BinaryWriter rawWriter = new BinaryWriter(rawStream);
uint rownum = 0;
bool finished = false;
var bufferFormats = input.BufferFormats;
var generics = input.GenericValues;
Vec3f[] minBounds = new Vec3f[bufferFormats.Length];
Vec3f[] maxBounds = new Vec3f[bufferFormats.Length];
for (int el = 0; el < bufferFormats.Length; el++)
{
minBounds[el] = new Vec3f(float.MaxValue, float.MaxValue, float.MaxValue);
maxBounds[el] = new Vec3f(-float.MaxValue, -float.MaxValue, -float.MaxValue);
if (bufferFormats[el].format.compCount == 1)
minBounds[el].y = maxBounds[el].y = minBounds[el].z = maxBounds[el].z = 0.0f;
if (bufferFormats[el].format.compCount == 2)
minBounds[el].z = maxBounds[el].z = 0.0f;
}
while (!finished)
{
if (rownum >= data.IndexCount)
{
finished = true;
break;
}
uint index = rownum;
if (data.Indices != null)
{
if (rownum >= data.Indices.Length)
{
index = 0;
}
else
{
index = data.Indices[rownum];
// apply base vertex but clamp to 0 if subtracting
if (input.Drawcall.baseVertex < 0)
{
uint subtract = (uint)(-input.Drawcall.baseVertex);
if (index < subtract)
index = 0;
else
index -= subtract;
}
else if (input.Drawcall.baseVertex > 0)
{
index += (uint)input.Drawcall.baseVertex;
}
}
}
else if ((input.Drawcall.flags & DrawcallFlags.UseIBuffer) != 0 && state == m_VSIn)
{
// no index buffer, but indexed drawcall
index = 0;
}
int elemsWithData = 0;
for (int el = 0; el < bufferFormats.Length; el++)
{
if (generics != null && generics[el] != null)
{
for(int g=0; g < generics[el].Length; g++)
{
if (generics[el][g] is uint)
rawWriter.Write((uint)generics[el][g]);
else if (generics[el][g] is int)
rawWriter.Write((int)generics[el][g]);
else if (generics[el][g] is float)
rawWriter.Write((float)generics[el][g]);
}
continue;
}
try
{
byte[] bytedata = null;
Stream strm = null;
BinaryReader read = null;
uint stride = 0;
if (bufferFormats[el].buffer < d.Length)
{
bytedata = d[bufferFormats[el].buffer];
strm = state.m_Stream[bufferFormats[el].buffer];
read = state.m_Reader[bufferFormats[el].buffer];
stride = input.Strides[bufferFormats[el].buffer];
}
uint instIdx = 0;
// for instancing, need to handle instance rate being 0 (every instance takes index 0 in that case)
if (bufferFormats[el].perinstance)
instIdx = bufferFormats[el].instancerate > 0 ? (instance / (uint)bufferFormats[el].instancerate) : 0;
else
instIdx = index;
if (data.PostVS.stride != 0)
stride = data.PostVS.stride;
uint offs = stride * instIdx + bufferFormats[el].offset;
bool outofBounds = false;
if (bufferFormats[el].buffer >= d.Length)
{
outofBounds = true;
strm = null;
read = new BinaryReader(new MemoryStream(m_Zeroes));
}
else if (bytedata == null)
{
strm.Seek(0, SeekOrigin.Begin);
}
else if (offs >= bytedata.Length)
{
outofBounds = true;
strm = null;
read = new BinaryReader(new MemoryStream(m_Zeroes));
}
else
{
strm.Seek(offs, SeekOrigin.Begin);
}
string elname = bufferFormats[el].name.ToUpperInvariant();
var fmt = bufferFormats[el].format;
int byteWidth = (int)fmt.compByteWidth;
int bytesToRead = (int)bufferFormats[el].ByteSize;
byte[] bytes = read.ReadBytes(bytesToRead);
rawWriter.Write(bytes);
if (bytes.Length != bytesToRead)
continue;
object[] elements = null;
{
MemoryStream tempStream = new MemoryStream(bytes);
BinaryReader tempReader = new BinaryReader(tempStream);
elements = bufferFormats[el].GetObjects(tempReader);
tempReader.Dispose();
tempStream.Dispose();
}
// update min/max for this element
{
for (int i = 0; !outofBounds && i < elements.Length; i++)
{
object o = elements[i];
float val;
if (o is uint)
val = (float)(uint)elements[i];
else if (o is int)
val = (float)(int)elements[i];
else if (o is UInt16)
val = (float)(int)elements[i];
else if (o is Int16)
val = (float)(int)elements[i];
else if (o is double)
val = (float)(double)elements[i];
else
val = (float)elements[i];
if (i == 0)
{
minBounds[el].x = Math.Min(minBounds[el].x, val);
maxBounds[el].x = Math.Max(maxBounds[el].x, val);
}
else if (i == 1)
{
minBounds[el].y = Math.Min(minBounds[el].y, val);
maxBounds[el].y = Math.Max(maxBounds[el].y, val);
}
else if (i == 2)
{
minBounds[el].z = Math.Min(minBounds[el].z, val);
maxBounds[el].z = Math.Max(maxBounds[el].z, val);
}
}
}
elemsWithData++;
}
catch (System.IO.EndOfStreamException)
{
// don't increment elemsWithData
}
}
finished = (elemsWithData == 0);
rownum++;
}
this.BeginInvoke(new Action(() =>
{
state.m_MinBounds = minBounds;
state.m_MaxBounds = maxBounds;
UI_UpdateBoundingBox();
UI_ShowRows(state, horizScroll);
}));
}));
th.Start();
state.m_DataParseThread = th;
}
private bool SuppressCaching = false;
private void UI_ShowRows(UIState state, int horizScroll)
{
var bufView = state.m_GridView;
if (bufView.IsDisposed)
return;
SuppressCaching = true;
bufView.RowCount = 0;
if(!MeshView) byteOffset.Enabled = true;
if (state.m_Rows != null)
{
bufView.RowCount = Math.Min(state.m_Rows.Length, MaxRowCount);
if (state.m_Rows.Length > MaxRowCount && MaxRowCount >= DefaultMaxRows)
largeBufferWarning.Visible = true;
ScrollToRow(bufView, RowOffset);
SuppressCaching = false;
bufView.HorizontalScrollingOffset = horizScroll;
}
if (vsInBufferView.Focused && m_Core.LogLoaded)
{
debugVertex.Enabled = debugVertexToolItem.Enabled = true;
}
}
private string ElementString(FormatElement el, object o)
{
if (o is float)
{
// pad with space on left if sign is missing, to better align
float f = (float)o;
if (f < 0.0f)
return Formatter.Format(f);
else if (f > 0.0f)
return " " + Formatter.Format(f);
else if (float.IsNaN(f))
return " NaN";
else
return " " + Formatter.Format(0.0f); // force negative and positive 0 together
}
else if (o is double)
{
// pad with space on left if sign is missing, to better align
double f = (double)o;
if (f < 0.0)
return Formatter.Format(f);
else if (f > 0.0)
return " " + Formatter.Format(f);
else if (double.IsNaN(f))
return " NaN";
else
return " " + Formatter.Format(0.0); // force negative and positive 0 together
}
else if (o is uint)
{
uint u = (uint)o;
if (el.format.compByteWidth == 4) return String.Format(el.hex ? "{0:X8}" : "{0}", u);
if (el.format.compByteWidth == 2) return String.Format(el.hex ? "{0:X4}" : "{0}", u);
if (el.format.compByteWidth == 1) return String.Format(el.hex ? "{0:X2}" : "{0}", u);
return String.Format("{0}", (uint)o);
}
else if (o is int)
{
return String.Format("{0}", (int)o);
}
return o.ToString();
}
private void UI_CacheRow(UIState state, int rowIdx)
{
if (state.m_Rows[rowIdx] != null || SuppressCaching)
return;
var data = state.m_Data;
Input input = state.m_Input;
uint instance = m_MeshDisplay.curInstance;
if (data.Buffers == null)
return;
{
byte[][] d = data.Buffers;
var bufferFormats = input.BufferFormats;
var generics = input.GenericValues;
uint rowlen = 0;
foreach(var el in bufferFormats)
rowlen += el.format.compCount;
{
if (rowIdx >= data.IndexCount)
{
return;
}
uint dataIndex = (uint)rowIdx;
bool outOfBoundsIdx = false;
if (data.DataIndices != null)
{
if (rowIdx >= data.DataIndices.Length)
{
dataIndex = 0;
outOfBoundsIdx = true;
}
else
{
dataIndex = data.DataIndices[rowIdx];
if (state == m_VSIn)
{
// apply base vertex but clamp to 0 if subtracting
if (input.Drawcall.baseVertex < 0)
{
uint subtract = (uint)(-input.Drawcall.baseVertex);
if (dataIndex < subtract)
{
dataIndex = 0;
outOfBoundsIdx = true;
}
else
{
dataIndex -= subtract;
}
}
else if (input.Drawcall.baseVertex > 0)
{
dataIndex += (uint)input.Drawcall.baseVertex;
}
}
}
}
else if (input.Drawcall != null && (input.Drawcall.flags & DrawcallFlags.UseIBuffer) != 0 &&
(state == m_VSIn || state == m_VSOut))
{
// no index buffer, but indexed drawcall
dataIndex = 0;
outOfBoundsIdx = true;
}
uint displayIndex = dataIndex;
if (data.Indices != null && rowIdx < data.Indices.Length)
{
displayIndex = data.Indices[rowIdx];
if (input.Drawcall != null && (input.Drawcall.flags & DrawcallFlags.UseIBuffer) != 0 &&
(state == m_VSIn || state == m_VSOut))
{
// apply base vertex but clamp to 0 if subtracting
if (input.Drawcall.baseVertex < 0)
{
uint subtract = (uint)(-input.Drawcall.baseVertex);
if (displayIndex < subtract)
{
displayIndex = 0;
outOfBoundsIdx = true;
}
else
{
displayIndex -= subtract;
}
}
else if (input.Drawcall.baseVertex > 0)
{
displayIndex += (uint)input.Drawcall.baseVertex;
}
}
}
object[] rowdata = null;
int x = 0;
if (MeshView)
{
rowdata = new object[2 + rowlen];
rowdata[0] = rowIdx;
if (outOfBoundsIdx)
rowdata[1] = "-";
else
rowdata[1] = displayIndex;
bool strip = state.m_Data.Topology == PrimitiveTopology.LineStrip ||
state.m_Data.Topology == PrimitiveTopology.LineStrip_Adj ||
state.m_Data.Topology == PrimitiveTopology.TriangleStrip ||
state.m_Data.Topology == PrimitiveTopology.TriangleStrip_Adj;
if (state.m_Input.Drawcall.indexByteWidth == 2 && dataIndex == state.m_Input.IndexRestartValue && state.m_Input.IndexRestart && strip)
rowdata[1] = "-1";
if (state.m_Input.Drawcall.indexByteWidth == 4 && dataIndex == state.m_Input.IndexRestartValue && state.m_Input.IndexRestart && strip)
rowdata[1] = "-1";
x = 2;
}
else
{
rowdata = new object[1 + rowlen];
rowdata[0] = rowIdx;
x = 1;
}
for (int el = 0; el < bufferFormats.Length; el++)
{
int xstart = x;
if (generics != null && generics[el] != null)
{
for (int g = 0; g < generics[el].Length; g++)
rowdata[x++] = generics[el][g];
continue;
}
try
{
byte[] bytedata = null;
Stream strm = null;
BinaryReader read = null;
uint stride = 0;
if (bufferFormats[el].buffer < d.Length)
{
bytedata = d[bufferFormats[el].buffer];
strm = state.m_Stream[bufferFormats[el].buffer];
read = state.m_Reader[bufferFormats[el].buffer];
stride = input.Strides[bufferFormats[el].buffer];
}
uint instIdx = 0;
// for instancing, need to handle instance rate being 0 (every instance takes index 0 in that case)
if (bufferFormats[el].perinstance)
instIdx = bufferFormats[el].instancerate > 0 ? (instance / (uint)bufferFormats[el].instancerate) : 0;
else
instIdx = dataIndex;
if (data.PostVS.stride != 0)
stride = data.PostVS.stride;
uint offs = stride * instIdx + bufferFormats[el].offset;
if (bufferFormats[el].buffer >= d.Length)
{
for (int i = 0; i < bufferFormats[el].format.compCount; i++, x++)
{
rowdata[x] = "-";
}
continue;
}
else if (bytedata == null)
{
strm.Seek(0, SeekOrigin.Begin);
}
else if (offs >= bytedata.Length)
{
for (int i = 0; i < bufferFormats[el].format.compCount; i++, x++)
{
rowdata[x] = "-";
}
continue;
}
else
{
strm.Seek(offs, SeekOrigin.Begin);
}
object[] elements = bufferFormats[el].GetObjects(read);
if (bufferFormats[el].matrixdim == 1)
{
for (int i = 0; i < elements.Length; i++)
rowdata[x + i] = ElementString(bufferFormats[el], elements[i]);
x += elements.Length;
}
else
{
int cols = (int)bufferFormats[el].format.compCount;
int rows = (int)bufferFormats[el].matrixdim;
for (int col = 0; col < cols; col++)
{
string[] colarr = new string[rows];
for (int row = 0; row < rows; row++)
{
if (!bufferFormats[el].rowmajor)
colarr[row] = ElementString(bufferFormats[el], elements[col * rows + row]);
else
colarr[row] = ElementString(bufferFormats[el], elements[row * cols + col]);
}
rowdata[x++] = colarr;
}
}
}
catch (System.IO.EndOfStreamException)
{
for (int i = 0; i < bufferFormats[el].format.compCount; i++)
{
rowdata[xstart + i] = "-";
}
x = (int)(xstart + bufferFormats[el].format.compCount);
}
}
if (rowdata != null)
{
state.m_Rows[rowIdx] = rowdata;
}
}
}
}
private void ScrollToRow(DataGridView view, int r)
{
try
{
int row = Math.Max(0, Math.Min(r, view.RowCount - 1));
if (row < view.RowCount)
{
view.FirstDisplayedScrollingRowIndex = row;
view.ClearSelection();
view.Rows[row].Selected = true;
}
}
catch (InvalidOperationException)
{
// this can happen when the window is too small, all we can do is ignore it.
}
}
private string CellValueToString(object val)
{
if (val == null)
return "-";
else if (val is float)
return Formatter.Format((float)val);
return val.ToString();
}
private void bufferView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
if (!m_Core.LogLoaded)
{
e.Value = "";
return;
}
var state = GetUIState(sender);
if (state == null ||
state.m_Rows == null ||
state.m_Rows.Length == 0)
{
if (e.ColumnIndex == 1)
e.Value = "Loading...";
else
e.Value = "";
return;
}
UI_CacheRow(state, e.RowIndex);
if (e.RowIndex >= state.m_Rows.Length ||
state.m_Rows[e.RowIndex] == null ||
e.ColumnIndex >= state.m_Rows[e.RowIndex].Length)
{
e.Value = "";
return;
}
object val = state.m_Rows[e.RowIndex][e.ColumnIndex];
if (val is object[])
{
string s = "";
object[] arr = (object[])val;
for(int i=0; i < arr.Length; i++)
{
s += CellValueToString(arr[i]);
if (i + 1 < arr.Length)
s += Environment.NewLine;
}
e.Value = s;
}
else
{
e.Value = CellValueToString(val);
}
}
#endregion
#endregion
#region Camera Controls
private bool RasterizedOutputStage
{
get
{
if (m_MeshDisplay.type == MeshDataStage.VSIn)
{
return false;
}
else if (m_MeshDisplay.type == MeshDataStage.VSOut)
{
if(m_Core.LogLoaded && m_Core.CurPipelineState.IsTessellationEnabled)
return false;
return true;
}
else if (m_MeshDisplay.type == MeshDataStage.GSOut)
{
return true;
}
return false;
}
}
private void enableCameraControls()
{
if (RasterizedOutputStage)
aspectGuess.Enabled = nearGuess.Enabled = farGuess.Enabled = true;
else
aspectGuess.Enabled = nearGuess.Enabled = farGuess.Enabled = false;
}
private void configureCam_CheckedChanged(object sender, EventArgs e)
{
configCamControls.Visible = configureCam.Checked;
enableCameraControls();
}
private void resetCam_Click(object sender, EventArgs e)
{
if (RasterizedOutputStage)
controlType.SelectedIndex = 1;
else
controlType.SelectedIndex = 0;
// make sure callback is called even if we're re-selecting same
// camera type
controlType_SelectedIndexChanged(sender, e);
UI_UpdateBoundingBox();
render.Invalidate();
}
private void fitScreen_Click(object sender, EventArgs e)
{
if(m_MeshDisplay.type != MeshDataStage.VSIn)
return;
controlType.SelectedIndex = 1;
var state = GetUIState(m_MeshDisplay.type);
if (state.m_MinBounds == null || state.m_MaxBounds == null)
return;
if (CurPosElement < 0 || CurPosElement >= state.m_MinBounds.Length || CurPosElement >= state.m_MaxBounds.Length)
return;
Vec3f diag = state.m_MaxBounds[CurPosElement].Sub(state.m_MinBounds[CurPosElement]);
if (diag.x < 0.0f || diag.y < 0.0f || diag.z < 0.0f || diag.Length() <= 1e-6f)
return;
Vec3f middle = new Vec3f(state.m_MinBounds[CurPosElement].x + diag.x / 2.0f,
state.m_MinBounds[CurPosElement].y + diag.y / 2.0f,
state.m_MinBounds[CurPosElement].z + diag.z / 2.0f);
Vec3f pos = new Vec3f(middle);
pos.z -= diag.Length();
m_Flycam.Reset(pos);
UI_UpdateBoundingBox();
render.Invalidate();
}
private void TimerUpdate()
{
if (m_CurrentCamera == null) return;
if (m_CurrentCamera.Update())
render.Invalidate();
}
private void PickVert(Point p)
{
if (!m_Core.LogLoaded)
return;
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
UInt32 vertSelected = m_Output.PickVertex(m_Core.CurEvent, (UInt32)p.X, (UInt32)p.Y);
if (vertSelected != UInt32.MaxValue)
{
this.BeginInvoke(new Action(() =>
{
var ui = GetUIState(m_MeshDisplay.type);
int row = (int)vertSelected;
if (row >= 0 && row < ui.m_GridView.RowCount)
{
if (ui.m_GridView.SelectedRows.Count == 0 || ui.m_GridView.SelectedRows[0] != ui.m_GridView.Rows[row])
{
ScrollToRow(ui.m_GridView, row);
ui.m_GridView.ClearSelection();
ui.m_GridView.Rows[row].Selected = true;
}
SyncViews(ui.m_GridView, true, true);
}
}));
}
});
}
void BufferViewer_KeyUp(object sender, KeyEventArgs e)
{
m_CurrentCamera.KeyUp(sender, e);
}
void BufferViewer_KeyDown(object sender, KeyEventArgs e)
{
m_CurrentCamera.KeyDown(sender, e);
}
private void render_MouseWheel(object sender, MouseEventArgs e)
{
m_CurrentCamera.MouseWheel(sender, e);
render.Invalidate();
}
private void render_MouseMove(object sender, MouseEventArgs e)
{
m_CurrentCamera.MouseMove(sender, e);
render.Invalidate();
if (e.Button == MouseButtons.Right)
PickVert(e.Location);
}
private void render_MouseClick(object sender, MouseEventArgs e)
{
m_CurrentCamera.MouseClick(sender, e);
render.Invalidate();
if (e.Button == MouseButtons.Right)
PickVert(e.Location);
}
private void render_MouseDown(object sender, MouseEventArgs e)
{
render.Focus();
}
private void camSpeed_ValueChanged(object sender, EventArgs e)
{
m_Arcball.SpeedMultiplier = m_Flycam.SpeedMultiplier = (float)camSpeed.Value;
}
private void controlType_SelectedIndexChanged(object sender, EventArgs e)
{
m_Arcball.Reset(new Vec3f(0.0f, 0.0f, 0.0f), 10.0f);
m_Flycam.Reset(new Vec3f(0.0f, 0.0f, 0.0f));
if (controlType.SelectedIndex == 0)
{
m_CurrentCamera = m_Arcball;
m_Arcball.Reset(new Vec3f(0.0f, 0.0f, 0.0f), 10.0f);
}
else
{
m_CurrentCamera = m_Flycam;
if (RasterizedOutputStage)
m_Flycam.Reset(new Vec3f(0.0f, 0.0f, 0.0f));
else
m_Flycam.Reset(new Vec3f(0.0f, 0.0f, -10.0f));
}
UpdateHighlightVerts(GetUIState(m_MeshDisplay.type));
render.Invalidate();
}
#endregion
#region Handlers and Painting
private void RT_UpdateRenderOutput(ReplayRenderer r)
{
if (m_Output == null) return;
m_MeshDisplay.cam = m_CurrentCamera.Camera.Real;
m_Output.SetMeshDisplay(m_MeshDisplay);
}
private void bufferView_Paint(object sender, PaintEventArgs e)
{
Input input = GetUIState(sender).m_Input;
if (input == null) return;
var grid = (DataGridView)sender;
int i = MeshView ? 2 : 1;
using (Graphics g = grid.CreateGraphics())
{
foreach (var el in input.BufferFormats)
{
int baseCol = i;
i += (int)el.format.compCount;
if(i > grid.ColumnCount)
break;
Rectangle stringBounds = Rectangle.Empty;
for (int f = baseCol; f < i; f++)
{
Rectangle r = grid.GetCellDisplayRectangle(f, -1, true);
if (r.Width > 0)
{
r.Width -= grid.Columns[f].DividerWidth;
if (stringBounds.Width == 0)
stringBounds = r;
else
stringBounds = Rectangle.Union(stringBounds, r);
}
}
stringBounds.X += 1;
stringBounds.Y += 2;
stringBounds.Width -= 2;
stringBounds.Height -= 3;
var sf = new StringFormat(StringFormat.GenericDefault);
sf.Trimming = StringTrimming.EllipsisCharacter;
sf.FormatFlags |= StringFormatFlags.NoWrap;
using (Brush b = new SolidBrush(grid.ColumnHeadersDefaultCellStyle.BackColor))
g.FillRectangle(b, stringBounds);
using (Brush b = new SolidBrush(ForeColor))
g.DrawString(el.name, grid.Font, b, stringBounds, sf);
}
}
}
private void bufferView_CellPaint(object sender, DataGridViewCellPaintingEventArgs e)
{
// only paint the cell headers
if (e.RowIndex != -1) return;
Input input = GetUIState(sender).m_Input;
if (input == null) return;
// display the initial columns as normal
if (e.ColumnIndex < (MeshView ? 2 : 1))
return;
// for others, just paint the border
e.Paint(e.CellBounds, DataGridViewPaintParts.Border);
e.Handled = true;
}
private void render_Paint(object sender, PaintEventArgs e)
{
if (m_Output == null || m_Core.Renderer == null)
{
e.Graphics.Clear(Color.Black);
return;
}
m_Core.Renderer.Invoke((ReplayRenderer r) => { RT_UpdateRenderOutput(r); if (m_Output != null) m_Output.Display(); });
}
private void BufferViewer_Load(object sender, EventArgs e)
{
matrixType.SelectedIndex = 0;
configCamControls.Visible = false;
if (onloadLayout.Length > 0)
{
foreach (var p in LayoutPersistors)
(p.Parent as DockContent).DockPanel = null;
var enc = new UnicodeEncoding();
using (var strm = new MemoryStream(enc.GetBytes(onloadLayout)))
{
strm.Flush();
strm.Position = 0;
dockPanel.LoadFromXml(strm, new DeserializeDockContent(GetContentFromPersistString));
}
onloadLayout = "";
}
}
private void BufferViewer_FormClosed(object sender, FormClosedEventArgs e)
{
m_Core.RemoveLogViewer(this);
m_Updater.Stop();
m_Updater = null;
m_ReqID++;
}
private void CSVToolStripMenuItem_Click(object sender, EventArgs e)
{
if (!m_Core.LogLoaded) return;
csvSaveDialog.Title = "Saving CSV data for " + (m_ContextUIState == m_VSIn ? "VS Input" : "VS Output");
DialogResult res = csvSaveDialog.ShowDialog();
if (res == DialogResult.OK)
{
try
{
StreamWriter writer = File.CreateText(csvSaveDialog.FileName);
if (MeshView)
{
writer.Write("Vertex,");
writer.Write("Index,");
}
else
{
writer.Write("Row,");
}
UIState ui = m_ContextUIState;
for (int i = 0; i < ui.m_Input.BufferFormats.Length; i++)
{
for (int j = 0; j < ui.m_Input.BufferFormats[i].format.compCount - 1; j++)
writer.Write(ui.m_Input.BufferFormats[i].name + " " + j + ",");
writer.Write(ui.m_Input.BufferFormats[i].name + " " + (ui.m_Input.BufferFormats[i].format.compCount - 1));
if (i < ui.m_Input.BufferFormats.Length - 1)
writer.Write(",");
}
writer.Write(Environment.NewLine);
foreach (DataGridViewRow row in ui.m_GridView.Rows)
{
for (int i = 0; i < row.Cells.Count; i++)
{
writer.Write(row.Cells[i].Value.ToString());
if (i < row.Cells.Count - 1)
writer.Write(",");
}
writer.Write(Environment.NewLine);
}
writer.Flush();
writer.Close();
}
catch (System.Exception ex)
{
MessageBox.Show("Couldn't save to " + csvSaveDialog.FileName + Environment.NewLine + ex.ToString(), "Cannot save",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void rawToolStripMenuItem_Click(object sender, EventArgs e)
{
if (!m_Core.LogLoaded) return;
rawSaveDialog.Title = "Saving raw bytes for " + (m_ContextUIState == m_VSIn ? "VS Input" : "VS Output");
DialogResult res = rawSaveDialog.ShowDialog();
if (res == DialogResult.OK)
{
try
{
FileStream writer = File.Create(rawSaveDialog.FileName);
UIState ui = m_ContextUIState;
writer.Write(ui.m_RawData, 0, ui.m_RawData.Length);
writer.Flush();
writer.Close();
}
catch (System.Exception ex)
{
MessageBox.Show("Couldn't save to " + csvSaveDialog.FileName + Environment.NewLine + ex.ToString(), "Cannot save",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void meshStageDraw_SelectedIndexChanged(object sender, EventArgs e)
{
if (previewTab.SelectedIndex == 0)
m_MeshDisplay.type = MeshDataStage.VSIn;
else if (previewTab.SelectedIndex == 1)
m_MeshDisplay.type = MeshDataStage.VSOut;
else if (previewTab.SelectedIndex == 2)
m_MeshDisplay.type = MeshDataStage.GSOut;
drawRange.Enabled = (m_MeshDisplay.type != MeshDataStage.VSIn);
UI_UpdateMeshRenderComponents();
if (RasterizedOutputStage)
{
controlType.SelectedIndex = 1;
fitScreen.Enabled = false;
}
else
{
controlType.SelectedIndex = 0;
fitScreen.Enabled = true;
}
enableCameraControls();
controlType_SelectedIndexChanged(sender, e);
UI_UpdateBoundingBox();
previewTable.Parent = previewTab.SelectedTab;
}
private int[] m_PosElement = new int[] { -1, -1, -1, -1 };
private int[] m_SecondElement = new int[] { -1, -1, -1, -1 };
private bool[] m_SecondShowAlpha = new bool[] { false, false, false, false };
private int CurPosElement { get { return m_PosElement[(int)m_MeshDisplay.type]; } }
private int CurSecondElement { get { return m_SecondElement[(int)m_MeshDisplay.type]; } }
private bool CurSecondShowAlpha { get { return m_SecondShowAlpha[(int)m_MeshDisplay.type]; } }
private FormatElement GetPosHighlightFormatElement(MeshDataStage stage)
{
var ui = GetUIState(stage);
int idx = m_PosElement[(int)stage];
if (ui.m_Input == null || ui.m_Input.BufferFormats == null ||
idx == -1 || idx >= ui.m_Input.BufferFormats.Length)
return null;
return ui.m_Input.BufferFormats[idx];
}
private FormatElement GetSecondHighlightFormatElement(MeshDataStage stage)
{
var ui = GetUIState(stage);
int idx = m_SecondElement[(int)stage];
if (ui.m_Input == null || ui.m_Input.BufferFormats == null ||
idx == -1 || idx >= ui.m_Input.BufferFormats.Length)
return null;
return ui.m_Input.BufferFormats[idx];
}
private void UI_AutoFetchRenderComponents(MeshDataStage stage, bool pos)
{
var ui = GetUIState(stage);
if (pos)
{
int posEl = -1;
if (ui.m_Input != null && ui.m_Input.BufferFormats != null)
{
// prioritise system value over general "POSITION" string matching
for (int i = 0; i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.systemValue == SystemAttribute.Position)
{
posEl = i;
break;
}
}
// look for an exact match
for (int i = 0; posEl == -1 && i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.name.ToUpperInvariant() == "POSITION" ||
el.name.ToUpperInvariant() == "POSITION0" ||
el.name.ToUpperInvariant() == "POS" ||
el.name.ToUpperInvariant() == "POS0")
{
posEl = i;
break;
}
}
// try anything containing position
for (int i = 0; posEl == -1 && i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.name.ToUpperInvariant().Contains("POSITION"))
{
posEl = i;
break;
}
}
// OK last resort, just look for 'pos'
for (int i = 0; posEl == -1 && i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.name.ToUpperInvariant().Contains("POS"))
{
posEl = i;
break;
}
}
// if we still have absolutely nothing, just use the first available element
if (posEl == -1 && ui.m_Input.BufferFormats.Length > 0)
{
posEl = 0;
}
}
m_PosElement[(int)stage] = posEl;
}
else
{
int secondEl = -1;
if (ui.m_Input != null && ui.m_Input.BufferFormats != null)
{
// prioritise TEXCOORD over general COLOR
for (int i = 0; i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.name.ToUpperInvariant() == "TEXCOORD" ||
el.name.ToUpperInvariant() == "TEXCOORD0" ||
el.name.ToUpperInvariant() == "TEX" ||
el.name.ToUpperInvariant() == "TEX0" ||
el.name.ToUpperInvariant() == "UV" ||
el.name.ToUpperInvariant() == "UV0")
{
secondEl = i;
break;
}
}
for (int i = 0; secondEl == -1 && i < ui.m_Input.BufferFormats.Length; i++)
{
FormatElement el = ui.m_Input.BufferFormats[i];
if (el.name.ToUpperInvariant() == "COLOR" ||
el.name.ToUpperInvariant() == "COLOR0" ||
el.name.ToUpperInvariant() == "COL" ||
el.name.ToUpperInvariant() == "COL0")
{
secondEl = i;
break;
}
}
}
m_SecondElement[(int)stage] = secondEl;
}
}
private void UI_UpdateBoundingBox()
{
var ui = GetUIState(m_MeshDisplay.type);
m_MeshDisplay.showBBox = false;
if (!RasterizedOutputStage &&
CurPosElement >= 0 &&
ui.m_MinBounds != null && CurPosElement < ui.m_MinBounds.Length &&
ui.m_MaxBounds != null && CurPosElement < ui.m_MaxBounds.Length)
{
m_MeshDisplay.showBBox = true;
m_MeshDisplay.minBounds = new FloatVector(ui.m_MinBounds[CurPosElement]);
m_MeshDisplay.maxBounds = new FloatVector(ui.m_MaxBounds[CurPosElement]);
Vec3f diag = ui.m_MaxBounds[CurPosElement].Sub(ui.m_MinBounds[CurPosElement]);
if (diag.x < 0.0f || diag.y < 0.0f || diag.z < 0.0f || diag.Length() <= 1e-6f)
return;
m_Arcball.LookAtPos = new Vec3f(ui.m_MinBounds[CurPosElement].x + diag.x / 2.0f,
ui.m_MinBounds[CurPosElement].y + diag.y / 2.0f,
ui.m_MinBounds[CurPosElement].z + diag.z / 2.0f);
m_Arcball.SetDistance(diag.Length()*0.7f);
try
{
camSpeed.Value = Helpers.Clamp((decimal)(diag.Length() / 200.0f), camSpeed.Minimum, camSpeed.Maximum);
}
catch (OverflowException)
{
camSpeed.Value = (decimal)1.0f;
}
render.Invalidate();
}
}
private void UI_UpdateMeshRenderComponents()
{
var ui = GetUIState(m_MeshDisplay.type);
// set position data etc from postvs if relevant
// also need to bake in drawcall offsets etc
// set numVerts from drawcall or postvs data
if (ui.m_Input == null || ui.m_Input.BufferFormats == null ||
CurPosElement == -1 || CurPosElement >= ui.m_Input.BufferFormats.Length)
{
m_MeshDisplay.position.idxbuf = ResourceId.Null;
m_MeshDisplay.position.idxoffs = 0;
m_MeshDisplay.position.idxByteWidth = 0;
m_MeshDisplay.position.baseVertex = 0;
m_MeshDisplay.position.buf = ResourceId.Null;
m_MeshDisplay.position.offset = 0;
m_MeshDisplay.position.stride = 0;
m_MeshDisplay.position.compCount = 0;
m_MeshDisplay.position.compByteWidth = 0;
m_MeshDisplay.position.compType = FormatComponentType.None;
m_MeshDisplay.position.specialFormat = SpecialFormat.Unknown;
m_MeshDisplay.position.showAlpha = false;
m_MeshDisplay.position.topo = PrimitiveTopology.Unknown;
m_MeshDisplay.position.numVerts = 0;
m_MeshDisplay.position.unproject = false;
// near and far plane handled elsewhere
}
else
{
FormatElement pos = ui.m_Input.BufferFormats[CurPosElement];
m_MeshDisplay.position.idxbuf = ResourceId.Null;
m_MeshDisplay.position.idxoffs = 0;
m_MeshDisplay.position.idxByteWidth = 0;
m_MeshDisplay.position.baseVertex = 0;
m_MeshDisplay.position.buf = ResourceId.Null;
m_MeshDisplay.position.offset = 0;
m_MeshDisplay.position.stride = 0;
m_MeshDisplay.position.compCount = pos.format.compCount;
m_MeshDisplay.position.compByteWidth = pos.format.compByteWidth;
m_MeshDisplay.position.compType = pos.format.compType;
m_MeshDisplay.position.bgraOrder = pos.format.bgraOrder;
m_MeshDisplay.position.specialFormat = pos.format.special ? pos.format.specialFormat : SpecialFormat.Unknown;
m_MeshDisplay.position.showAlpha = false;
m_MeshDisplay.position.topo = PrimitiveTopology.Unknown;
m_MeshDisplay.position.numVerts = 0;
if (ui.m_Stage == MeshDataStage.VSIn && ui.m_Input.Drawcall != null)
{
m_MeshDisplay.position.idxbuf = m_VSIn.m_Input.IndexBuffer;
m_MeshDisplay.position.idxoffs = m_VSIn.m_Input.IndexOffset +
ui.m_Input.Drawcall.indexOffset * ui.m_Input.Drawcall.indexByteWidth;
m_MeshDisplay.position.idxByteWidth = ui.m_Input.Drawcall.indexByteWidth;
m_MeshDisplay.position.baseVertex = ui.m_Input.Drawcall.baseVertex;
if (pos.buffer < m_VSIn.m_Input.Buffers.Length)
{
m_MeshDisplay.position.buf = m_VSIn.m_Input.Buffers[pos.buffer];
m_MeshDisplay.position.offset = pos.offset + ui.m_Input.Offsets[pos.buffer] +
ui.m_Input.Drawcall.vertexOffset * ui.m_Input.Strides[pos.buffer];
m_MeshDisplay.position.stride = ui.m_Input.Strides[pos.buffer];
}
else
{
m_MeshDisplay.position.buf = ResourceId.Null;
m_MeshDisplay.position.offset = 0;
m_MeshDisplay.position.stride = 0;
}
m_MeshDisplay.position.topo = ui.m_Input.Drawcall.topology;
m_MeshDisplay.position.numVerts = ui.m_Input.Drawcall.numIndices;
}
else if (ui.m_Stage != MeshDataStage.VSIn && ui.m_Data != null && ui.m_Data.PostVS.buf != ResourceId.Null)
{
m_MeshDisplay.position.idxbuf = ui.m_Data.PostVS.idxbuf;
m_MeshDisplay.position.idxoffs = 0;
m_MeshDisplay.position.idxByteWidth = ui.m_Input.Drawcall.indexByteWidth;
m_MeshDisplay.position.baseVertex = ui.m_Data.PostVS.baseVertex;
m_MeshDisplay.position.buf = ui.m_Data.PostVS.buf;
m_MeshDisplay.position.offset = ui.m_Data.PostVS.offset + pos.offset;
m_MeshDisplay.position.stride = ui.m_Data.PostVS.stride;
m_MeshDisplay.position.topo = ui.m_Data.PostVS.topo;
m_MeshDisplay.position.numVerts = ui.m_Data.PostVS.numVerts;
}
if ((ui.m_Input.Drawcall.flags & DrawcallFlags.UseIBuffer) == 0 || ui.m_Stage == MeshDataStage.GSOut)
{
m_MeshDisplay.position.idxbuf = ResourceId.Null;
m_MeshDisplay.position.idxoffs = 0;
m_MeshDisplay.position.idxByteWidth = 0;
m_MeshDisplay.position.baseVertex = 0;
}
else
{
m_MeshDisplay.position.idxByteWidth = Math.Max(1, m_MeshDisplay.position.idxByteWidth);
}
m_MeshDisplay.position.unproject = false;
// near and far plane handled elsewhere
if (RasterizedOutputStage)
{
m_MeshDisplay.position.unproject = pos.systemValue == SystemAttribute.Position;
}
}
UI_UpdateBoundingBox();
if (ui.m_Input == null || ui.m_Input.BufferFormats == null ||
CurSecondElement == -1 || CurSecondElement >= ui.m_Input.BufferFormats.Length)
{
m_MeshDisplay.secondary.idxbuf = ResourceId.Null;
m_MeshDisplay.secondary.idxoffs = 0;
m_MeshDisplay.secondary.idxByteWidth = 0;
m_MeshDisplay.secondary.baseVertex = 0;
m_MeshDisplay.secondary.buf = ResourceId.Null;
m_MeshDisplay.secondary.offset = 0;
m_MeshDisplay.secondary.stride = 0;
m_MeshDisplay.secondary.compCount = 0;
m_MeshDisplay.secondary.compByteWidth = 0;
m_MeshDisplay.secondary.compType = FormatComponentType.None;
m_MeshDisplay.secondary.bgraOrder = false;
m_MeshDisplay.secondary.specialFormat = SpecialFormat.Unknown;
m_MeshDisplay.secondary.showAlpha = false;
m_MeshDisplay.secondary.topo = PrimitiveTopology.Unknown;
m_MeshDisplay.secondary.numVerts = 0;
m_MeshDisplay.secondary.unproject = false;
}
else
{
FormatElement tex = ui.m_Input.BufferFormats[CurSecondElement];
m_MeshDisplay.secondary.compCount = tex.format.compCount;
m_MeshDisplay.secondary.compByteWidth = tex.format.compByteWidth;
m_MeshDisplay.secondary.compType = tex.format.compType;
m_MeshDisplay.secondary.bgraOrder = tex.format.bgraOrder;
m_MeshDisplay.secondary.specialFormat = tex.format.special ? tex.format.specialFormat : SpecialFormat.Unknown;
m_MeshDisplay.secondary.showAlpha = CurSecondShowAlpha;
if (ui.m_Stage == MeshDataStage.VSIn && ui.m_Input.Drawcall != null)
{
if (tex.buffer < m_VSIn.m_Input.Buffers.Length)
{
m_MeshDisplay.secondary.buf = m_VSIn.m_Input.Buffers[tex.buffer];
m_MeshDisplay.secondary.offset = tex.offset + ui.m_Input.Offsets[tex.buffer] +
ui.m_Input.Drawcall.vertexOffset * m_MeshDisplay.position.stride;
m_MeshDisplay.secondary.stride = ui.m_Input.Strides[tex.buffer];
}
else
{
m_MeshDisplay.secondary.buf = ResourceId.Null;
m_MeshDisplay.secondary.offset = 0;
m_MeshDisplay.secondary.stride = 0;
}
}
else if (ui.m_Stage != MeshDataStage.VSIn && ui.m_Data != null && ui.m_Data.PostVS.buf != ResourceId.Null)
{
m_MeshDisplay.secondary.buf = ui.m_Data.PostVS.buf;
m_MeshDisplay.secondary.offset = ui.m_Data.PostVS.offset + tex.offset;
m_MeshDisplay.secondary.stride = ui.m_Data.PostVS.stride;
}
}
UI_UpdateAllColumns();
}
private void camGuess_PropChanged()
{
m_MeshDisplay.ortho = matrixType.SelectedIndex == 1;
float fov = 90.0f;
float.TryParse(fovGuess.Text, out fov);
m_MeshDisplay.fov = fov;
fovGuess.Text = m_MeshDisplay.fov.ToString("G");
m_MeshDisplay.aspect = 1.0f;
// take a guess for the aspect ratio, for if the user hasn't overridden it
m_MeshDisplay.aspect = m_Core.CurPipelineState.GetViewport(0).width / m_Core.CurPipelineState.GetViewport(0).height;
if (aspectGuess.Text.Length > 0 && float.TryParse(aspectGuess.Text, out m_MeshDisplay.aspect))
aspectGuess.Text = m_MeshDisplay.aspect.ToString("G");
else
aspectGuess.Text = "";
// use estimates from post vs data (calculated from vertex position data) if the user
// hasn't overridden the values
m_MeshDisplay.position.nearPlane = 0.1f;
if (m_VSOut.m_Data != null && m_VSOut.m_Data.PostVS.buf != ResourceId.Null)
m_MeshDisplay.position.nearPlane = m_VSOut.m_Data.PostVS.nearPlane;
if (nearGuess.Text.Length > 0 && float.TryParse(nearGuess.Text, out m_MeshDisplay.position.nearPlane))
nearGuess.Text = m_MeshDisplay.position.nearPlane.ToString("G");
else
nearGuess.Text = "";
m_MeshDisplay.position.farPlane = 100.0f;
if (m_VSOut.m_Data != null && m_VSOut.m_Data.PostVS.buf != ResourceId.Null)
m_MeshDisplay.position.farPlane = m_VSOut.m_Data.PostVS.farPlane;
if (farGuess.Text.Length > 0 && float.TryParse(farGuess.Text, out m_MeshDisplay.position.farPlane))
farGuess.Text = m_MeshDisplay.position.farPlane.ToString("G");
else
farGuess.Text = "";
render.Invalidate();
}
private void camGuess_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
camGuess_PropChanged();
e.Handled = true;
}
}
private void matrixType_SelectedIndexChanged(object sender, EventArgs e)
{
camGuess_PropChanged();
}
private void drawRange_SelectedIndexChanged(object sender, EventArgs e)
{
/*
"Only this draw",
"Show previous instances",
"Show all instances",
"Show whole pass"
*/
m_MeshDisplay.showPrevInstances = (drawRange.SelectedIndex >= 1);
m_MeshDisplay.showAllInstances = (drawRange.SelectedIndex >= 2);
m_MeshDisplay.showWholePass = (drawRange.SelectedIndex >= 3);
render.Invalidate();
}
private void solidShading_SelectedIndexChanged(object sender, EventArgs e)
{
m_MeshDisplay.solidShadeMode = (SolidShadeMode)solidShading.SelectedIndex;
if (solidShading.SelectedIndex == 0 && !wireframeDraw.Checked)
wireframeDraw.Checked = true;
UI_UpdateAllColumns();
render.Invalidate();
}
private void wireframeDraw_CheckedChanged(object sender, EventArgs e)
{
if (solidShading.SelectedIndex == 0 && !wireframeDraw.Checked)
{
wireframeDraw.Checked = true;
return;
}
m_MeshDisplay.wireframeDraw = wireframeDraw.Checked;
render.Invalidate();
}
private void instanceIdx_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
instanceIdxToolitem.Text = instanceIdx.Text;
rightclickMenu.Close();
}
}
private void instanceIdxToolitem_KeyPress(object sender, KeyPressEventArgs e)
{
if (!m_Core.LogLoaded) return;
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
instanceIdx.Text = instanceIdxToolitem.Text;
uint inst = 0;
if (uint.TryParse(instanceIdxToolitem.Text, out inst))
{
if (inst != m_MeshDisplay.curInstance && inst >= 0 && m_Core.CurDrawcall != null && inst < m_Core.CurDrawcall.numInstances)
{
m_MeshDisplay.curInstance = inst;
OnEventSelected(m_Core.CurEvent);
}
}
}
}
private int m_ContextColumn = 0;
private void resetSelectedColumnsToolStripMenuItem_Click(object sender, EventArgs e)
{
UI_AutoFetchRenderComponents(m_ContextUIState.m_Stage, true);
UI_AutoFetchRenderComponents(m_ContextUIState.m_Stage, false);
UI_UpdateMeshRenderComponents();
render.Invalidate();
}
private void selectColumnAsPositionToolStripMenuItem_Click(object sender, EventArgs e)
{
m_PosElement[(int)m_ContextUIState.m_Stage] = m_ContextColumn;
UI_UpdateMeshRenderComponents();
render.Invalidate();
}
private void selectColumnAsSecondaryToolStripMenuItem_Click(object sender, EventArgs e)
{
m_SecondElement[(int)m_ContextUIState.m_Stage] = m_ContextColumn;
m_SecondShowAlpha[(int)m_ContextUIState.m_Stage] = false;
UI_UpdateMeshRenderComponents();
render.Invalidate();
}
private void selectAlphaAsSecondaryToolStripMenuItem_Click(object sender, EventArgs e)
{
m_SecondElement[(int)m_ContextUIState.m_Stage] = m_ContextColumn;
m_SecondShowAlpha[(int)m_ContextUIState.m_Stage] = true;
UI_UpdateMeshRenderComponents();
render.Invalidate();
}
private void bufferView_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (m_Core.LogLoaded && MeshView)
{
m_ContextUIState = GetUIState(sender);
if (e.Button == MouseButtons.Right &&
m_ContextUIState.m_Input != null &&
m_ContextUIState.m_Input.BufferFormats != null)
{
selectColumnAsPositionToolStripMenuItem.Visible = true;
selectAlphaAsSecondaryToolStripMenuItem.Visible = true;
m_ContextColumn = 0;
int colidx = 2; // skip VTX and IDX columns
for (int el = 0; el < m_ContextUIState.m_Input.BufferFormats.Length; el++)
{
for (int i = 0; i < m_ContextUIState.m_Input.BufferFormats[el].format.compCount; i++)
{
if (colidx == e.ColumnIndex)
{
m_ContextColumn = el;
selectAlphaAsSecondaryToolStripMenuItem.Visible = (m_ContextUIState.m_Input.BufferFormats[el].format.compCount >= 4);
}
colidx++;
}
}
columnContextMenu.Show(Cursor.Position);
}
}
}
private void bufferView_MouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
if (m_Core.LogLoaded && e.RowIndex >= 0)
{
m_ContextUIState = GetUIState(sender);
if (e.Button == MouseButtons.Right)
{
openFormat.Visible = !MeshView;
debugVertex.Visible = MeshView &&
m_Core.LogLoaded &&
sender == vsInBufferView &&
vsInBufferView.SelectedRows.Count == 1;
setInstanceToolStripMenuItem.Enabled = (m_Core.CurDrawcall != null && m_Core.CurDrawcall.numInstances > 1);
rightclickMenu.Show(Cursor.Position);
}
}
}
private void rightclickMenu_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
uint inst = 0;
if (uint.TryParse(instanceIdx.Text, out inst))
{
if (inst != m_MeshDisplay.curInstance && inst >= 0 && m_Core.CurDrawcall != null && inst < m_Core.CurDrawcall.numInstances)
{
m_MeshDisplay.curInstance = inst;
OnEventSelected(m_Core.CurEvent);
}
}
}
private void syncViewsToolItem_Click(object sender, EventArgs e)
{
syncViews.Checked = syncViewsToolItem.Checked;
SyncViews(null, true, true);
}
private void syncViews_Click(object sender, EventArgs e)
{
syncViewsToolItem.Checked = syncViews.Checked;
SyncViews(null, true, true);
}
private void UpdateRowOffset()
{
ScrollToRow(vsInBufferView, RowOffset);
ScrollToRow(vsOutBufferView, RowOffset);
ScrollToRow(gsOutBufferView, RowOffset);
}
private void rowOffset_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\n' || e.KeyChar == '\r')
{
rowOffset.Text = RowOffset.ToString();
UpdateRowOffset();
}
}
private void SetByteOffset()
{
largeBufferWarning.Visible = false;
m_VSIn.m_GridView.RowCount = 0;
m_VSIn.m_Rows = null;
m_VSIn.m_GridView.RowCount = 1;
byteOffset.Enabled = false;
ulong byteoffset = ByteOffset;
m_Core.Renderer.BeginInvoke((ReplayRenderer r) =>
{
var contents = RT_FetchBufferContents(MeshDataStage.VSIn, r, m_VSIn.m_Input, byteoffset);
this.BeginInvoke(new Action(() =>
{
if (m_VSIn.m_Input != null)
{
UI_SetRowsData(MeshDataStage.VSIn, contents, 0);
}
}));
});
}
private void largeBufferWarning_Click(object sender, EventArgs e)
{
byteOffset.Text = (ByteOffset + (ulong)(m_VSIn.m_Input.Strides[0]*MaxRowCount)).ToString();
SetByteOffset();
}
private void byteOffset_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\n' || e.KeyChar == '\r')
{
SetByteOffset();
}
}
private void rowRange_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\n' || e.KeyChar == '\r')
{
if (MaxRowCount > DefaultMaxRows)
rowRange.Text = DefaultMaxRows.ToString();
SetByteOffset();
}
}
private void offsetCancel_Click(object sender, EventArgs e)
{
rowOffset.Text = "0";
UpdateRowOffset();
}
private void openFormat_Click(object sender, EventArgs e)
{
ShowFormatSpecifier();
}
public void ProcessBufferFormat(string formatText)
{
ResourceId id = GetUIState(MeshDataStage.VSIn).m_Input.Buffers[0];
bool isBuffer = true;
if (id == ResourceId.Null)
{
isBuffer = false;
id = GetUIState(MeshDataStage.VSIn).m_Input.Buffers[1];
}
ViewRawBuffer(isBuffer, ByteOffset, ulong.MaxValue, id, formatText);
}
private void ShowFormatSpecifier()
{
if (m_FormatSpecifier == null)
{
m_FormatSpecifier = new BufferFormatSpecifier(this, m_FormatText);
var dock = Helpers.WrapDockContent(dockPanel, m_FormatSpecifier, m_FormatSpecifier.Text);
dock.CloseButton = false;
dock.CloseButtonVisible = false;
}
(m_FormatSpecifier.Parent as DockContent).Show(dockPanel, DockState.DockBottom);
}
private void debugVertex_Click(object sender, EventArgs e)
{
if (!m_Core.LogLoaded) return;
ShaderReflection shaderDetails = null;
ShaderDebugTrace trace = null;
shaderDetails = m_Core.CurPipelineState.GetShaderReflection(ShaderStageType.Vertex);
UIState ui = GetUIState(MeshDataStage.VSIn);
if (ui.m_GridView.SelectedRows.Count == 0)
{
MessageBox.Show("You must select a vertex to debug", "No Vertex Selected",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
int row = ui.m_GridView.SelectedRows[0].Index;
if (row >= ui.m_Rows.Length || ui.m_Rows[row].Length <= 1) return;
UInt32 idx = 0;
if (ui.m_Rows[row][1] is UInt32)
{
idx = (UInt32)ui.m_Rows[row][1];
}
var draw = m_Core.CurDrawcall;
m_Core.Renderer.Invoke((ReplayRenderer r) =>
{
trace = r.DebugVertex((UInt32)row, m_MeshDisplay.curInstance, idx, draw.instanceOffset, draw.vertexOffset);
});
this.BeginInvoke(new Action(() =>
{
string debugContext = String.Format("Vertex {0}", row);
if (draw.numInstances > 1)
debugContext += String.Format(", Instance {0}", m_MeshDisplay.curInstance);
ShaderViewer s = new ShaderViewer(m_Core, shaderDetails, ShaderStageType.Vertex, trace, debugContext);
s.Show(this.DockPanel);
}));
}
private void SyncViews(DataGridView primary, bool selection, bool scroll)
{
if (!syncViews.Checked)
return;
DataGridView[] grids = { vsInBufferView, vsOutBufferView, gsOutBufferView };
if (primary == null)
{
foreach (DataGridView g in grids)
{
if (g.Focused)
{
primary = g;
break;
}
}
}
if (primary == null)
primary = vsInBufferView;
foreach (DataGridView g in grids)
{
if (g == primary)
continue;
if (selection)
{
g.ClearSelection();
if (primary.SelectedRows.Count == 1 && primary.SelectedRows[0].Index < g.Rows.Count)
g.Rows[primary.SelectedRows[0].Index].Selected = true;
}
if (scroll)
{
if (g.RowCount > 0 && primary.FirstDisplayedScrollingRowIndex >= 0 &&
g.RowCount > primary.FirstDisplayedScrollingRowIndex)
g.FirstDisplayedScrollingRowIndex = primary.FirstDisplayedScrollingRowIndex;
}
}
}
private bool selectNoRecurse = false;
private void bufferView_SelectionChanged(object sender, EventArgs e)
{
if (selectNoRecurse) return;
selectNoRecurse = true;
SyncViews(null, true, false);
if (vsInBufferView.Focused && m_Core.LogLoaded)
{
debugVertex.Enabled = debugVertexToolItem.Enabled = true;
}
else
{
debugVertex.Enabled = debugVertexToolItem.Enabled = false;
}
if (!MeshView)
{
debugVertex.Enabled = debugVertexToolItem.Visible = debugSep.Visible = false;
}
UpdateHighlightVerts(GetUIState(m_MeshDisplay.type));
selectNoRecurse = false;
}
private void bufferView_Scroll(object sender, ScrollEventArgs e)
{
SyncViews(null, false, true);
}
#endregion
#region Vertex Highlighting
private void highlightVerts_CheckedChanged(object sender, EventArgs e)
{
UpdateHighlightVerts(GetUIState(m_MeshDisplay.type));
}
private void ClearHighlightVerts()
{
m_MeshDisplay.highlightVert = ~0U;
render.Invalidate();
}
private void UpdateHighlightVerts(UIState ui)
{
if (ui == null || ui.m_RawData == null) return;
if (ui.m_GridView.SelectedRows.Count == 0) return;
if (!MeshView) return;
if(highlightVerts.Checked)
m_MeshDisplay.highlightVert = (uint)ui.m_GridView.SelectedRows[0].Index;
else
m_MeshDisplay.highlightVert = ~0U;
render.Invalidate();
}
#endregion
private void bufferView_EnterLeave(object sender, EventArgs e)
{
if (vsInBufferView.Focused && m_Core.LogLoaded)
{
debugVertex.Enabled = debugVertexToolItem.Enabled = true;
}
else
{
debugVertex.Enabled = debugVertexToolItem.Enabled = false;
}
}
}
}