/****************************************************************************** * 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.Drawing; using System.Linq; using System.Text; using System.IO; using System.Windows.Forms; using WeifenLuo.WinFormsUI.Docking; using renderdocui.Code; using renderdocui.Controls; using renderdocui.Windows.Dialogs; using renderdoc; using System.Threading; namespace renderdocui.Windows { public partial class TextureViewer : DockContent, ILogViewerForm { #region Privates private Core m_Core; private ReplayOutput m_Output = null; private TextureDisplay m_TexDisplay = new TextureDisplay(); private ToolStripControlHost depthStencilToolstrip = null; private DockContent m_PreviewPanel = null; private DockContent m_TexlistDockPanel = null; private FileSystemWatcher m_FSWatcher = null; private int m_HighWaterStatusLength = 0; public enum FollowType { OutputColour, OutputDepth, ReadWrite, ReadOnly } struct Following { public FollowType Type; public ShaderStageType Stage; public int index; public int arrayEl; public static Following Default = new Following(FollowType.OutputColour, ShaderStageType.Pixel, 0, 0); public Following(FollowType t, ShaderStageType s, int i, int a) { Type = t; Stage = s; index = i; arrayEl = a; } public override int GetHashCode() { return Type.GetHashCode() + Stage.GetHashCode() + index.GetHashCode(); } public override bool Equals(object obj) { return obj is Following && this == (Following)obj; } public static bool operator ==(Following s1, Following s2) { return s1.Type == s2.Type && s1.Stage == s2.Stage && s1.index == s2.index; } public static bool operator !=(Following s1, Following s2) { return !(s1 == s2); } public static void GetDrawContext(Core core, out bool copy, out bool compute) { var curDraw = core.CurDrawcall; copy = curDraw != null && (curDraw.flags & (DrawcallFlags.Copy | DrawcallFlags.Resolve)) != 0; compute = curDraw != null && (curDraw.flags & DrawcallFlags.Dispatch) != 0 && core.CurPipelineState.GetShader(ShaderStageType.Compute) != ResourceId.Null; } public int GetHighestMip(Core core) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy || compute) return -1; return GetBoundResource(core, arrayEl).HighestMip; } public int GetFirstArraySlice(Core core) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy || compute) return -1; return GetBoundResource(core, arrayEl).FirstSlice; } public FormatComponentType GetTypeHint(Core core) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy || compute) return FormatComponentType.None; return GetBoundResource(core, arrayEl).typeHint; } public ResourceId GetResourceId(Core core) { return GetBoundResource(core, arrayEl).Id; } public int GetBoundResourceArraySize(Core core) { if (Type == FollowType.ReadWrite) { var rw = GetReadWriteResources(core); var mapping = GetMapping(core); if (index < mapping.ReadWriteResources.Length) { var key = mapping.ReadWriteResources[index]; if (rw.ContainsKey(key)) return rw[key].Length; } } else if (Type == FollowType.ReadOnly) { var res = GetReadOnlyResources(core); var mapping = GetMapping(core); if (index < mapping.ReadOnlyResources.Length) { var key = mapping.ReadOnlyResources[index]; if (res.ContainsKey(key)) return res[key].Length; } } return 1; } public BoundResource GetBoundResource(Core core, int arrayIdx) { BoundResource ret = new BoundResource(); if (Type == FollowType.OutputColour) { var outputs = GetOutputTargets(core); if (index < outputs.Length) ret = outputs[index]; } else if (Type == FollowType.OutputDepth) { ret = GetDepthTarget(core); } else if (Type == FollowType.ReadWrite) { var rw = GetReadWriteResources(core); var mapping = GetMapping(core); if (index < mapping.ReadWriteResources.Length) { var key = mapping.ReadWriteResources[index]; if (rw.ContainsKey(key)) ret = rw[key][arrayIdx]; } } else if (Type == FollowType.ReadOnly) { var res = GetReadOnlyResources(core); var mapping = GetMapping(core); if (index < mapping.ReadOnlyResources.Length) { var key = mapping.ReadOnlyResources[index]; if (res.ContainsKey(key)) ret = res[key][arrayIdx]; } } return ret; } public static BoundResource[] GetOutputTargets(Core core) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy) return new BoundResource[] { new BoundResource(curDraw.copyDestination) }; else if(compute) return new BoundResource[0]; else { var ret = core.CurPipelineState.GetOutputTargets(); if (ret.Length == 0 && curDraw != null && (curDraw.flags & DrawcallFlags.Present) != 0) { if (curDraw.copyDestination != ResourceId.Null) return new BoundResource[] { new BoundResource(curDraw.copyDestination) }; foreach (var t in core.CurTextures) if ((t.creationFlags & TextureCreationFlags.SwapBuffer) != 0) return new BoundResource[] { new BoundResource(t.ID) }; } return ret; } } public static BoundResource GetDepthTarget(Core core) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy || compute) return new BoundResource(ResourceId.Null); else return core.CurPipelineState.GetDepthTarget(); } public Dictionary GetReadWriteResources(Core core) { return GetReadWriteResources(core, Stage); } public static Dictionary GetReadWriteResources(Core core, ShaderStageType stage) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy) { return new Dictionary(); } else if (compute) { // only return compute resources for one stage if (stage == ShaderStageType.Pixel || stage == ShaderStageType.Compute) return core.CurPipelineState.GetReadWriteResources(ShaderStageType.Compute); else return new Dictionary(); } else { return core.CurPipelineState.GetReadWriteResources(stage); } } public Dictionary GetReadOnlyResources(Core core) { return GetReadOnlyResources(core, Stage); } public static Dictionary GetReadOnlyResources(Core core, ShaderStageType stage) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy) { var ret = new Dictionary(); // only return copy source for one stage if(stage == ShaderStageType.Pixel) ret.Add(new BindpointMap(0, 0), new BoundResource[] { new BoundResource(curDraw.copySource) }); return ret; } else if (compute) { // only return compute resources for one stage if (stage == ShaderStageType.Pixel || stage == ShaderStageType.Compute) return core.CurPipelineState.GetReadOnlyResources(ShaderStageType.Compute); else return new Dictionary(); } else { return core.CurPipelineState.GetReadOnlyResources(stage); } } public ShaderReflection GetReflection(Core core) { return GetReflection(core, Stage); } public static ShaderReflection GetReflection(Core core, ShaderStageType stage) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy) return null; else if (compute) return core.CurPipelineState.GetShaderReflection(ShaderStageType.Compute); else return core.CurPipelineState.GetShaderReflection(stage); } public ShaderBindpointMapping GetMapping(Core core) { return GetMapping(core, Stage); } public static ShaderBindpointMapping GetMapping(Core core, ShaderStageType stage) { var curDraw = core.CurDrawcall; bool copy, compute; GetDrawContext(core, out copy, out compute); if (copy) { ShaderBindpointMapping mapping = new ShaderBindpointMapping(); mapping.ConstantBlocks = new BindpointMap[0]; mapping.ReadWriteResources = new BindpointMap[0]; mapping.InputAttributes = new int[0]; // for PS only add a single mapping to get the copy source if (stage == ShaderStageType.Pixel) mapping.ReadOnlyResources = new BindpointMap[] { new BindpointMap(0, 0) }; else mapping.ReadOnlyResources = new BindpointMap[0]; return mapping; } else if (compute) { return core.CurPipelineState.GetBindpointMapping(ShaderStageType.Compute); } else { return core.CurPipelineState.GetBindpointMapping(stage); } } } private Following m_Following = Following.Default; public class TexSettings { public TexSettings() { r = g = b = true; a = false; mip = 0; slice = 0; minrange = 0.0f; maxrange = 1.0f; typeHint = FormatComponentType.None; } public bool r, g, b, a; public bool depth, stencil; public int mip, slice; public float minrange, maxrange; public FormatComponentType typeHint; } private Dictionary m_TextureSettings = new Dictionary(); #endregion public TextureViewer(Core core) { m_Core = core; InitializeComponent(); textureList.Font = texturefilter.Font = rangeBlack.Font = rangeWhite.Font = customShader.Font = hdrMul.Font = channels.Font = mipLevel.Font = sliceFace.Font = zoomOption.Font = core.Config.PreferredFont; Icon = global::renderdocui.Properties.Resources.icon; textureList.m_Core = core; textureList.GoIconClick += new EventHandler(textureList_GoIconClick); UI_SetupToolstrips(); UI_SetupDocks(); UI_UpdateTextureDetails(); statusLabel.Text = ""; zoomOption.SelectedText = ""; mipLevel.Enabled = false; sliceFace.Enabled = false; rangeBlack.ResizeToFit = false; rangeWhite.ResizeToFit = false; PixelPicked = false; mainLayout.Dock = DockStyle.Fill; render.Painting = true; pixelContext.Painting = true; saveTex.Enabled = false; DockHandler.GetPersistStringCallback = PersistString; renderContainer.MouseWheelHandler = render_MouseWheel; render.MouseWheel += render_MouseWheel; renderContainer.MouseDown += render_MouseClick; renderContainer.MouseMove += render_MouseMove; render.KeyHandler = render_KeyDown; pixelContext.KeyHandler = render_KeyDown; rangeHistogram.RangeUpdated += new EventHandler(rangeHistogram_RangeUpdated); this.DoubleBuffered = true; SetStyle(ControlStyles.OptimizedDoubleBuffer, true); channels.SelectedIndex = 0; FitToWindow = true; overlay.SelectedIndex = 0; m_Following = Following.Default; texturefilter.SelectedIndex = 0; } private void UI_SetupDocks() { m_PreviewPanel = Helpers.WrapDockContent(dockPanel, renderToolstripContainer, "Current"); m_PreviewPanel.DockState = DockState.Document; m_PreviewPanel.AllowEndUserDocking = false; m_PreviewPanel.Show(); m_PreviewPanel.CloseButton = false; m_PreviewPanel.CloseButtonVisible = false; m_PreviewPanel.DockHandler.TabPageContextMenuStrip = tabContextMenu; dockPanel.ActiveDocumentChanged += new EventHandler(dockPanel_ActiveDocumentChanged); var w3 = Helpers.WrapDockContent(dockPanel, roPanel, "Inputs"); w3.DockAreas &= ~DockAreas.Document; w3.DockState = DockState.DockRight; w3.Show(); w3.CloseButton = false; w3.CloseButtonVisible = false; var w5 = Helpers.WrapDockContent(dockPanel, rwPanel, "Outputs"); w5.DockAreas &= ~DockAreas.Document; w5.DockState = DockState.DockRight; w5.Show(w3.Pane, w3); w5.CloseButton = false; w5.CloseButtonVisible = false; m_TexlistDockPanel = Helpers.WrapDockContent(dockPanel, texlistContainer, "Texture List"); m_TexlistDockPanel.DockAreas &= ~DockAreas.Document; m_TexlistDockPanel.DockState = DockState.DockLeft; m_TexlistDockPanel.Hide(); m_TexlistDockPanel.HideOnClose = true; var w4 = Helpers.WrapDockContent(dockPanel, pixelContextPanel, "Pixel Context"); w4.DockAreas &= ~DockAreas.Document; w4.Show(w3.Pane, DockAlignment.Bottom, 0.3); w4.CloseButton = false; w4.CloseButtonVisible = false; } private void UI_SetupToolstrips() { int idx = rangeStrip.Items.IndexOf(rangeWhite); rangeStrip.Items.Insert(idx, new ToolStripControlHost(rangeHistogram)); for (int i = 0; i < channelStrip.Items.Count; i++) { if (channelStrip.Items[i] == mulSep) { depthStencilToolstrip = new ToolStripControlHost(depthstencilPanel); channelStrip.Items.Insert(i, depthStencilToolstrip); break; } } } public class PersistData { public static int currentPersistVersion = 4; public int persistVersion = currentPersistVersion; public string panelLayout; public FloatVector darkBack = new FloatVector(0, 0, 0, 0); public FloatVector lightBack = new FloatVector(0, 0, 0, 0); public static PersistData GetDefaults() { PersistData data = new PersistData(); data.panelLayout = ""; data.darkBack = new FloatVector(0, 0, 0, 0); data.lightBack = new FloatVector(0, 0, 0, 0); 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(); } // fixup old incorrect checkerboard colours if (data.lightBack.x != data.darkBack.x) { TextureDisplay defaults = new TextureDisplay(); data.lightBack = defaults.lightBackgroundColour; data.darkBack = defaults.darkBackgroundColour; } ApplyPersistData(data); } private IDockContent GetContentFromPersistString(string persistString) { Control[] persistors = { renderToolstripContainer, roPanel, rwPanel, texlistContainer, pixelContextPanel }; foreach(var p in persistors) if (persistString == p.Name && p.Parent is IDockContent && (p.Parent as DockContent).DockPanel == null) return p.Parent as IDockContent; // backwards compatibilty for rename if(persistString == "texPanel") return roPanel.Parent as IDockContent; if(persistString == "rtPanel") return rwPanel.Parent as IDockContent; return null; } private string onloadLayout = ""; private FloatVector darkBack = new FloatVector(0, 0, 0, 0); private FloatVector lightBack = new FloatVector(0, 0, 0, 0); private void ApplyPersistData(PersistData data) { onloadLayout = data.panelLayout; darkBack = data.darkBack; lightBack = data.lightBack; } private void TextureViewer_Load(object sender, EventArgs e) { if (onloadLayout.Length > 0) { Control[] persistors = { renderToolstripContainer, roPanel, rwPanel, texlistContainer, pixelContextPanel }; foreach (var p in persistors) (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 = ""; } if (darkBack.x != lightBack.x) { backcolorPick.Checked = false; checkerBack.Checked = true; } else { backcolorPick.Checked = true; checkerBack.Checked = false; colorDialog.Color = Color.FromArgb((int)(255 * darkBack.x), (int)(255 * darkBack.y), (int)(255 * darkBack.z)); } } private string PersistString() { 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.darkBack = darkBack; data.lightBack = lightBack; System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(PersistData)); xs.Serialize(writer, data); return writer.ToString(); } #region Public Functions private Dictionary lockedTabs = new Dictionary(); public void ViewTexture(ResourceId ID, bool focus) { if (this.InvokeRequired) { this.BeginInvoke(new Action(() => { this.ViewTexture(ID, focus); })); return; } TextureViewer_Load(null, null); if (lockedTabs.ContainsKey(ID)) { if (!lockedTabs[ID].IsDisposed && !lockedTabs[ID].IsHidden) { if (focus) Show(); lockedTabs[ID].Show(); m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); return; } lockedTabs.Remove(ID); } for (int i = 0; i < m_Core.CurTextures.Length; i++) { if (m_Core.CurTextures[i].ID == ID) { FetchTexture current = m_Core.CurTextures[i]; var newPanel = Helpers.WrapDockContent(dockPanel, renderToolstripContainer, current.name); newPanel.DockState = DockState.Document; newPanel.AllowEndUserDocking = false; newPanel.Icon = Icon.FromHandle(global::renderdocui.Properties.Resources.page_white_link.GetHicon()); newPanel.Tag = current; newPanel.DockHandler.TabPageContextMenuStrip = tabContextMenu; newPanel.FormClosing += new FormClosingEventHandler(PreviewPanel_FormClosing); newPanel.Show(m_PreviewPanel.Pane, null); newPanel.Show(); if (focus) Show(); lockedTabs.Add(ID, newPanel); m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); return; } } for (int i = 0; i < m_Core.CurBuffers.Length; i++) { if (m_Core.CurBuffers[i].ID == ID) { var viewer = new BufferViewer(m_Core, false); viewer.ViewRawBuffer(true, 0, ulong.MaxValue, ID); viewer.Show(DockPanel); return; } } } #endregion #region Custom Shader handling private List m_CustomShadersBusy = new List(); private Dictionary m_CustomShaders = new Dictionary(); private Dictionary m_CustomShaderEditor = new Dictionary(); private void ReloadCustomShaders(string filter) { if (!m_Core.LogLoaded) return; if (filter.Length > 0) { var shaders = m_CustomShaders.Values.ToArray(); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { foreach (var s in shaders) r.FreeCustomShader(s); }); customShader.Items.Clear(); m_CustomShaders.Clear(); } else { var fn = Path.GetFileNameWithoutExtension(filter); var key = fn.ToUpperInvariant(); if (m_CustomShaders.ContainsKey(key)) { if (m_CustomShadersBusy.Contains(key)) return; ResourceId freed = m_CustomShaders[key]; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { r.FreeCustomShader(freed); }); m_CustomShaders.Remove(key); var text = customShader.Text; for (int i = 0; i < customShader.Items.Count; i++) { if (customShader.Items[i].ToString() == fn) { customShader.Items.RemoveAt(i); break; } } customShader.Text = text; } } foreach (var f in Directory.EnumerateFiles(Core.ConfigDirectory, "*" + m_Core.APIProps.ShaderExtension)) { var fn = Path.GetFileNameWithoutExtension(f); var key = fn.ToUpperInvariant(); if (!m_CustomShaders.ContainsKey(key) && !m_CustomShadersBusy.Contains(key)) { try { string source = File.ReadAllText(f); m_CustomShaders.Add(key, ResourceId.Null); m_CustomShadersBusy.Add(key); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { string errors = ""; ResourceId id = r.BuildCustomShader("main", source, 0, ShaderStageType.Pixel, out errors); if (m_CustomShaderEditor.ContainsKey(key)) { BeginInvoke((MethodInvoker)delegate { m_CustomShaderEditor[key].ShowErrors(errors); }); } BeginInvoke((MethodInvoker)delegate { customShader.Items.Add(fn); m_CustomShaders[key] = id; m_CustomShadersBusy.Remove(key); customShader.AutoCompleteSource = AutoCompleteSource.None; customShader.AutoCompleteSource = AutoCompleteSource.ListItems; UI_UpdateChannels(); }); }); } catch (System.Exception) { // just continue, skip this file } } } } private void customCreate_Click(object sender, EventArgs e) { if (customShader.Text == null || customShader.Text.Length == 0) { MessageBox.Show("No name entered.\nEnter a name in the textbox.", "Error Creating Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (m_CustomShaders.ContainsKey(customShader.Text.ToUpperInvariant())) { MessageBox.Show("Selected shader already exists.\nEnter a new name in the textbox.", "Error Creating Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var path = Path.Combine(Core.ConfigDirectory, customShader.Text + m_Core.APIProps.ShaderExtension); string src = ""; if (m_Core.APIProps.pipelineType == APIPipelineStateType.D3D11) { src = String.Format( "float4 main(float4 pos : SV_Position, float4 uv : TEXCOORD0) : SV_Target0{0}" + "{{{0}" + " return float4(0,0,0,1);{0}" + "}}{0}" , Environment.NewLine); } else if (m_Core.APIProps.pipelineType == APIPipelineStateType.OpenGL || m_Core.APIProps.pipelineType == APIPipelineStateType.Vulkan) { src = String.Format( "#version 420 core{0}" + "layout (location = 0) out vec4 color_out;{0}" + "void main(){0}" + "{{{0}" + " color_out = vec4(0,0,0,1);{0}" + "}}{0}" , Environment.NewLine); } try { File.WriteAllText(path, src); } catch (System.Exception) { // ignore this file } // auto-open edit window customEdit_Click(sender, e); } private void customEdit_Click(object sender, EventArgs e) { var filename = customShader.Text; var key = filename.ToUpperInvariant(); string src = ""; try { src = File.ReadAllText(Path.Combine(Core.ConfigDirectory, filename + m_Core.APIProps.ShaderExtension)); } catch (System.Exception ex) { MessageBox.Show("Couldn't open file for shader " + filename + Environment.NewLine + ex.ToString(), "Cannot open shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } var files = new Dictionary(); files.Add(filename, src); ShaderViewer s = new ShaderViewer(m_Core, true, "Custom Shader", files, // Save Callback (ShaderViewer viewer, Dictionary updatedfiles) => { foreach (var f in updatedfiles) { var path = Path.Combine(Core.ConfigDirectory, f.Key + m_Core.APIProps.ShaderExtension); try { File.WriteAllText(path, f.Value); } catch (System.Exception ex) { MessageBox.Show("Couldn't save file for shader " + filename + Environment.NewLine + ex.ToString(), "Cannot save shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } }, // Close Callback () => { m_CustomShaderEditor.Remove(key); }); m_CustomShaderEditor[key] = s; s.Show(this.DockPanel); } private void customDelete_Click(object sender, EventArgs e) { if (customShader.Text == null || customShader.Text.Length == 0) { MessageBox.Show("No shader selected.\nSelect a custom shader from the drop-down", "Error Deleting Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (!m_CustomShaders.ContainsKey(customShader.Text.ToUpperInvariant())) { MessageBox.Show("Selected shader doesn't exist.\nSelect a custom shader from the drop-down", "Error Deleting Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } DialogResult res = MessageBox.Show(String.Format("Really delete {0}?", customShader.Text), "Deleting Custom Shader", MessageBoxButtons.YesNoCancel); if (res == DialogResult.Yes) { var path = Path.Combine(Core.ConfigDirectory, customShader.Text + m_Core.APIProps.ShaderExtension); if(!File.Exists(path)) { MessageBox.Show(String.Format("Shader file {0} can't be found.\nSelect a custom shader from the drop-down", customShader.Text), "Error Deleting Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } try { File.Delete(path); } catch (Exception) { MessageBox.Show(String.Format("Error deleting shader {0}.\nSelect a custom shader from the drop-down", customShader.Text), "Error Deleting Shader", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } customShader.Text = ""; UI_UpdateChannels(); } } #endregion #region ILogViewerForm public void OnLogfileLoaded() { var outConfig = new OutputConfig(); outConfig.m_Type = OutputType.TexDisplay; saveTex.Enabled = true; m_Following = Following.Default; rwPanel.ClearThumbnails(); roPanel.ClearThumbnails(); m_HighWaterStatusLength = 0; IntPtr contextHandle = pixelContext.Handle; IntPtr renderHandle = render.Handle; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { m_Output = r.CreateOutput(renderHandle, OutputType.TexDisplay); m_Output.SetPixelContext(contextHandle); m_Output.SetOutputConfig(outConfig); this.BeginInvoke(new Action(UI_CreateThumbnails)); }); m_FSWatcher = new FileSystemWatcher(Core.ConfigDirectory, "*" + m_Core.APIProps.ShaderExtension); m_FSWatcher.EnableRaisingEvents = true; m_FSWatcher.Changed += new FileSystemEventHandler(CustomShaderModified); m_FSWatcher.Renamed += new RenamedEventHandler(CustomShaderModified); m_FSWatcher.Created += new FileSystemEventHandler(CustomShaderModified); m_FSWatcher.Deleted += new FileSystemEventHandler(CustomShaderModified); ReloadCustomShaders(""); texturefilter.SelectedIndex = 0; texturefilter.Text = ""; textureList.FillTextureList("", true, true); m_TexDisplay.darkBackgroundColour = darkBack; m_TexDisplay.lightBackgroundColour = lightBack; m_TexDisplay.typeHint = FormatComponentType.None; m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); } void CustomShaderModified(object sender, FileSystemEventArgs e) { Thread.Sleep(5); BeginInvoke((MethodInvoker)delegate { ReloadCustomShaders(e.Name); }); } public void OnLogfileClosed() { if (IsDisposed) return; if(m_FSWatcher != null) m_FSWatcher.EnableRaisingEvents = false; m_FSWatcher = null; m_Output = null; m_TextureSettings.Clear(); m_PrevSize = PointF.Empty; m_HighWaterStatusLength = 0; saveTex.Enabled = false; rwPanel.ClearThumbnails(); roPanel.ClearThumbnails(); texturefilter.SelectedIndex = 0; m_TexDisplay = new TextureDisplay(); m_TexDisplay.darkBackgroundColour = darkBack; m_TexDisplay.lightBackgroundColour = lightBack; m_TexDisplay.typeHint = FormatComponentType.None; PixelPicked = false; statusLabel.Text = ""; m_PreviewPanel.Text = "Current"; zoomOption.Text = ""; mipLevel.Items.Clear(); sliceFace.Items.Clear(); rangeHistogram.SetRange(0.0f, 1.0f); channels.SelectedIndex = 0; overlay.SelectedIndex = 0; customShader.Items.Clear(); m_CustomShaders.Clear(); textureList.Items.Clear(); render.Invalidate(); renderHScroll.Enabled = false; renderVScroll.Enabled = false; hoverSwatch.BackColor = Color.Black; var tabs = m_PreviewPanel.Pane.TabStripControl.Tabs; for (int i = 0; i < tabs.Count; i++) { if (tabs[i].Content != m_PreviewPanel) { (tabs[i].Content as DockContent).Close(); i--; } } (m_PreviewPanel as DockContent).Show(); UI_UpdateTextureDetails(); UI_UpdateChannels(); } private void InitResourcePreview(ResourcePreview prev, ResourceId id, FormatComponentType typeHint, bool force, Following follow, string bindName, string slotName) { if (id != ResourceId.Null || force) { FetchTexture tex = null; foreach (var t in m_Core.CurTextures) if (t.ID == id) tex = t; FetchBuffer buf = null; foreach (var b in m_Core.CurBuffers) if (b.ID == id) buf = b; if (tex != null) { string fullname = bindName; if (tex.customName) { if (fullname.Length > 0) fullname += " = "; fullname += tex.name; } if (fullname.Length == 0) fullname = tex.name; prev.Init(fullname, tex.width, tex.height, tex.depth, tex.mips); IntPtr handle = prev.ThumbnailHandle; m_Core.Renderer.BeginInvoke((ReplayRenderer rep) => { m_Output.AddThumbnail(handle, id, typeHint); }); } else if (buf != null) { string fullname = bindName; if (buf.customName) { if (fullname.Length > 0) fullname += " = "; fullname += buf.name; } if (fullname.Length == 0) fullname = buf.name; prev.Init(fullname, buf.length, 0, 0, 1); IntPtr handle = prev.ThumbnailHandle; m_Core.Renderer.BeginInvoke((ReplayRenderer rep) => { m_Output.AddThumbnail(handle, ResourceId.Null, FormatComponentType.None); }); } else { prev.Init(); IntPtr handle = prev.ThumbnailHandle; m_Core.Renderer.BeginInvoke((ReplayRenderer rep) => { m_Output.AddThumbnail(handle, ResourceId.Null, FormatComponentType.None); }); } prev.Tag = follow; prev.SlotName = slotName; prev.Visible = true; } else if (prev.Selected) { FetchTexture tex = null; if(id != ResourceId.Null) foreach (var t in m_Core.CurTextures) if (t.ID == id) tex = t; IntPtr handle = prev.ThumbnailHandle; if (id == ResourceId.Null || tex == null) prev.Init(); else prev.Init("Unused", tex.width, tex.height, tex.depth, tex.mips); m_Core.Renderer.BeginInvoke((ReplayRenderer rep) => { m_Output.AddThumbnail(handle, ResourceId.Null, FormatComponentType.None); }); } else { prev.Init(); prev.Visible = false; } } private void InitStageResourcePreviews(ShaderStageType stage, ShaderResource[] resourceDetails, BindpointMap[] mapping, Dictionary ResList, ThumbnailStrip prevs, ref int prevIndex, bool copy, bool rw) { for (int idx = 0; idx < mapping.Length; idx++) { var key = mapping[idx]; BoundResource[] resArray = null; if (ResList.ContainsKey(key)) resArray = ResList[key]; int arrayLen = resArray != null ? resArray.Length : 1; for (int arrayIdx = 0; arrayIdx < arrayLen; arrayIdx++) { ResourceId id = resArray != null ? resArray[arrayIdx].Id : ResourceId.Null; FormatComponentType typeHint = resArray != null ? resArray[arrayIdx].typeHint : FormatComponentType.None; bool used = key.used; bool skip = false; string bindName = ""; foreach (var bind in resourceDetails) { if (bind.bindPoint == idx && bind.IsSRV) { bindName = bind.name; break; } } if (skip) continue; if (copy) { used = true; bindName = "Source"; } Following follow = new Following(rw ? FollowType.ReadWrite : FollowType.ReadOnly, stage, idx, arrayIdx); string slotName = String.Format("{0} {1}{2}", m_Core.CurPipelineState.Abbrev(stage), rw ? "RW " : "", idx); if (arrayLen > 1) slotName += String.Format("[{0}]", arrayIdx); if (copy) slotName = "SRC"; // show if bool show = (used || // it's referenced by the shader - regardless of empty or not (showDisabled.Checked && !used && id != ResourceId.Null) || // it's bound, but not referenced, and we have "show disabled" (showEmpty.Checked && id == ResourceId.Null) // it's empty, and we have "show empty" ); ResourcePreview prev; if (prevIndex < prevs.Thumbnails.Length) { prev = prevs.Thumbnails[prevIndex]; } else { // don't create it if we're not actually going to show it if (!show) continue; prev = UI_CreateThumbnail(prevs); } prevIndex++; InitResourcePreview(prev, show ? id : ResourceId.Null, typeHint, show, follow, bindName, slotName); } } } public void OnEventSelected(UInt32 eventID) { if (IsDisposed) return; UI_OnTextureSelectionChanged(); UI_CreateThumbnails(); if (m_Output == null) return; BoundResource[] RTs = Following.GetOutputTargets(m_Core); BoundResource Depth = Following.GetDepthTarget(m_Core); int rwIndex = 0; int roIndex = 0; var curDraw = m_Core.GetDrawcall(eventID); bool copy = curDraw != null && (curDraw.flags & (DrawcallFlags.Copy|DrawcallFlags.Resolve)) != 0; bool compute = curDraw != null && (curDraw.flags & (DrawcallFlags.Dispatch)) != 0; for(int rt=0; rt < RTs.Length; rt++) { ResourcePreview prev; if (rwIndex < rwPanel.Thumbnails.Length) prev = rwPanel.Thumbnails[rwIndex]; else prev = UI_CreateThumbnail(rwPanel); rwIndex++; Following follow = new Following(FollowType.OutputColour, ShaderStageType.Pixel, rt, 0); string bindName = copy ? "Destination" : ""; string slotName = copy ? "DST" : String.Format("{0}{1}", m_Core.CurPipelineState.OutputAbbrev(), rt); InitResourcePreview(prev, RTs[rt].Id, RTs[rt].typeHint, false, follow, bindName, slotName); } // depth { ResourcePreview prev; if (rwIndex < rwPanel.Thumbnails.Length) prev = rwPanel.Thumbnails[rwIndex]; else prev = UI_CreateThumbnail(rwPanel); rwIndex++; Following follow = new Following(FollowType.OutputDepth, ShaderStageType.Pixel, 0, 0); InitResourcePreview(prev, Depth.Id, Depth.typeHint, false, follow, "", "DS"); } ShaderStageType[] stages = new ShaderStageType[] { ShaderStageType.Vertex, ShaderStageType.Hull, ShaderStageType.Domain, ShaderStageType.Geometry, ShaderStageType.Pixel }; if (compute) stages = new ShaderStageType[] { ShaderStageType.Compute }; // display resources used for all stages foreach (ShaderStageType stage in stages) { Dictionary RWs = Following.GetReadWriteResources(m_Core, stage); Dictionary ROs = Following.GetReadOnlyResources(m_Core, stage); ShaderReflection details = Following.GetReflection(m_Core, stage); ShaderBindpointMapping mapping = Following.GetMapping(m_Core, stage); InitStageResourcePreviews(stage, details != null ? details.ReadWriteResources : new ShaderResource[0], mapping.ReadWriteResources, RWs, rwPanel, ref rwIndex, copy, true); InitStageResourcePreviews(stage, details != null ? details.ReadOnlyResources : new ShaderResource[0], mapping.ReadOnlyResources, ROs, roPanel, ref roIndex, copy, false); } // hide others for (; rwIndex < rwPanel.Thumbnails.Length - 1; rwIndex++) { rwPanel.Thumbnails[rwIndex].Init(); rwPanel.Thumbnails[rwIndex].Visible = false; } rwPanel.RefreshLayout(); for (; roIndex < roPanel.Thumbnails.Length - 1; roIndex++) { roPanel.Thumbnails[roIndex].Init(); roPanel.Thumbnails[roIndex].Visible = false; } roPanel.RefreshLayout(); m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); if(autoFit.Checked) AutoFitRange(); } #endregion #region Update UI state private ResourcePreview UI_CreateThumbnail(ThumbnailStrip strip) { var prev = new ResourcePreview(m_Core, m_Output); prev.Anchor = AnchorStyles.Top | AnchorStyles.Bottom; prev.MouseClick += thumbsLayout_MouseClick; prev.MouseDoubleClick += thumbsLayout_MouseDoubleClick; prev.Visible = false; strip.AddThumbnail(prev); return prev; } private void UI_CreateThumbnails() { if (rwPanel.Thumbnails.Length > 0 || roPanel.Thumbnails.Length > 0) return; rwPanel.SuspendLayout(); roPanel.SuspendLayout(); // these will expand, but we make sure that there is a good set reserved for (int i = 0; i < 9; i++) { var prev = UI_CreateThumbnail(rwPanel); if(i == 0) prev.Selected = true; } for (int i = 0; i < 128; i++) UI_CreateThumbnail(roPanel); rwPanel.ResumeLayout(); roPanel.ResumeLayout(); } private int prevFirstArraySlice = -1; private int prevHighestMip = -1; private PointF m_PrevSize = PointF.Empty; private void UI_SetHistogramRange(FetchTexture tex, FormatComponentType typeHint) { if (tex != null && (tex.format.compType == FormatComponentType.SNorm || typeHint == FormatComponentType.SNorm)) rangeHistogram.SetRange(-1.0f, 1.0f); else rangeHistogram.SetRange(0.0f, 1.0f); } private void UI_OnTextureSelectionChanged() { FetchTexture tex = CurrentTexture; // reset high-water mark m_HighWaterStatusLength = 0; if (tex == null) return; bool newtex = (m_TexDisplay.texid != tex.ID); // save settings for this current texture if (m_Core.Config.TextureViewer_PerTexSettings) { if (!m_TextureSettings.ContainsKey(m_TexDisplay.texid)) m_TextureSettings.Add(m_TexDisplay.texid, new TexSettings()); m_TextureSettings[m_TexDisplay.texid].r = customRed.Checked; m_TextureSettings[m_TexDisplay.texid].g = customGreen.Checked; m_TextureSettings[m_TexDisplay.texid].b = customBlue.Checked; m_TextureSettings[m_TexDisplay.texid].a = customAlpha.Checked; m_TextureSettings[m_TexDisplay.texid].depth = depthDisplay.Checked; m_TextureSettings[m_TexDisplay.texid].stencil = stencilDisplay.Checked; m_TextureSettings[m_TexDisplay.texid].mip = mipLevel.SelectedIndex; m_TextureSettings[m_TexDisplay.texid].slice = sliceFace.SelectedIndex; m_TextureSettings[m_TexDisplay.texid].minrange = rangeHistogram.BlackPoint; m_TextureSettings[m_TexDisplay.texid].maxrange = rangeHistogram.WhitePoint; m_TextureSettings[m_TexDisplay.texid].typeHint = m_Following.GetTypeHint(m_Core); } m_TexDisplay.texid = tex.ID; // interpret the texture according to the currently following type. m_TexDisplay.typeHint = m_Following.GetTypeHint(m_Core); // if there is no such type or it isn't being followed, use the last seen interpretation if (m_TexDisplay.typeHint == FormatComponentType.None && m_TextureSettings.ContainsKey(m_TexDisplay.texid)) m_TexDisplay.typeHint = m_TextureSettings[m_TexDisplay.texid].typeHint; m_CurPixelValue = null; m_CurRealValue = null; // try to maintain the pan in the new texture. If the new texture // is approx an integer multiple of the old texture, just changing // the scale will keep everything the same. This is useful for // downsample chains and things where you're flipping back and forth // between overlapping textures, but even in the non-integer case // pan will be kept approximately the same. PointF curSize = new PointF((float)CurrentTexture.width, (float)CurrentTexture.height); float curArea = curSize.Area(); float prevArea = m_PrevSize.Area(); if (prevArea > 0.0f) { float prevX = m_TexDisplay.offx; float prevY = m_TexDisplay.offy; float prevScale = m_TexDisplay.scale; // allow slight difference in aspect ratio for rounding errors // in downscales (e.g. 1680x1050 -> 840x525 -> 420x262 in the // last downscale the ratios are 1.6 and 1.603053435). if (Math.Abs(curSize.Aspect() - m_PrevSize.Aspect()) < 0.01f) { m_TexDisplay.scale *= m_PrevSize.X / curSize.X; CurrentZoomValue = m_TexDisplay.scale; } else { // this scale factor is arbitrary really, only intention is to have // integer scales come out precisely, other 'similar' sizes will be // similar ish float scaleFactor = (float)(Math.Sqrt(curArea) / Math.Sqrt(prevArea)); m_TexDisplay.offx = prevX * scaleFactor; m_TexDisplay.offy = prevY * scaleFactor; } } m_PrevSize = curSize; // refresh scroll position ScrollPosition = ScrollPosition; UI_UpdateStatusText(); mipLevel.Items.Clear(); sliceFace.Items.Clear(); m_TexDisplay.mip = 0; m_TexDisplay.sliceFace = 0; bool usemipsettings = true; bool useslicesettings = true; if (tex.msSamp > 1) { for (int i = 0; i < tex.msSamp; i++) mipLevel.Items.Add(String.Format("Sample {0}", i)); // add an option to display unweighted average resolved value, // to get an idea of how the samples average if(tex.format.compType != FormatComponentType.UInt && tex.format.compType != FormatComponentType.SInt && tex.format.compType != FormatComponentType.Depth && (tex.creationFlags & TextureCreationFlags.DSV) == 0) mipLevel.Items.Add("Average val"); mipLevelLabel.Text = "Sample"; mipLevel.SelectedIndex = 0; } else { for (int i = 0; i < tex.mips; i++) mipLevel.Items.Add(i + " - " + Math.Max(1, tex.width >> i) + "x" + Math.Max(1, tex.height >> i)); mipLevelLabel.Text = "Mip"; int highestMip = m_Following.GetHighestMip(m_Core); // assuming we get a valid mip for the highest mip, only switch to it // if we've selected a new texture, or if it's different than the last mip. // This prevents the case where the user has clicked on another mip and // we don't want to snap their view back when stepping between events with the // same mip used. But it does mean that if they are stepping between // events with different mips used, then we will update in that case. if (highestMip >= 0 && (newtex || highestMip != prevHighestMip)) { usemipsettings = false; mipLevel.SelectedIndex = Helpers.Clamp(highestMip, 0, (int)tex.mips - 1); } prevHighestMip = highestMip; } if (tex.mips == 1 && tex.msSamp <= 1) { mipLevel.Enabled = false; } else { mipLevel.Enabled = true; } if (tex.numSubresources == tex.mips && tex.depth <= 1) { sliceFace.Enabled = false; } else { sliceFace.Enabled = true; sliceFace.Visible = sliceFaceLabel.Visible = true; String[] cubeFaces = { "X+", "X-", "Y+", "Y-", "Z+", "Z-" }; UInt32 numSlices = (Math.Max(1, tex.depth) * tex.numSubresources) / tex.mips; for (UInt32 i = 0; i < numSlices; i++) { if (tex.cubemap) { String name = cubeFaces[i%6]; if (numSlices > 6) name = string.Format("[{0}] {1}", (i / 6), cubeFaces[i%6]); // Front 1, Back 2, 3, 4 etc for cube arrays sliceFace.Items.Add(name); } else { sliceFace.Items.Add("Slice " + i); } } int firstArraySlice = m_Following.GetFirstArraySlice(m_Core); // see above with highestMip and prevHighestMip for the logic behind this if (firstArraySlice >= 0 && (newtex || firstArraySlice != prevFirstArraySlice)) { useslicesettings = false; sliceFace.SelectedIndex = Helpers.Clamp(firstArraySlice, 0, (int)numSlices - 1); } prevFirstArraySlice = firstArraySlice; } // because slice and mip are specially set above, we restore any per-tex settings to apply // even if we don't switch to a new texture. // Note that if the slice or mip was changed because that slice or mip is the selected one // at the API level, we leave this alone. if (m_Core.Config.TextureViewer_PerTexSettings && m_TextureSettings.ContainsKey(tex.ID)) { if (usemipsettings) mipLevel.SelectedIndex = m_TextureSettings[tex.ID].mip; if (useslicesettings) sliceFace.SelectedIndex = m_TextureSettings[tex.ID].slice; } // handling for if we've switched to a new texture if (newtex) { // if we save certain settings per-texture, restore them (if we have any) if (m_Core.Config.TextureViewer_PerTexSettings && m_TextureSettings.ContainsKey(tex.ID)) { customRed.Checked = m_TextureSettings[tex.ID].r; customGreen.Checked = m_TextureSettings[tex.ID].g; customBlue.Checked = m_TextureSettings[tex.ID].b; customAlpha.Checked = m_TextureSettings[tex.ID].a; depthDisplay.Checked = m_TextureSettings[m_TexDisplay.texid].depth; stencilDisplay.Checked = m_TextureSettings[m_TexDisplay.texid].stencil; norangePaint = true; rangeHistogram.SetRange(m_TextureSettings[m_TexDisplay.texid].minrange, m_TextureSettings[m_TexDisplay.texid].maxrange); norangePaint = false; } else if (m_Core.Config.TextureViewer_PerTexSettings) { // if we are using per-tex settings, reset back to RGB customRed.Checked = true; customGreen.Checked = true; customBlue.Checked = true; customAlpha.Checked = false; stencilDisplay.Checked = false; depthDisplay.Checked = true; norangePaint = true; UI_SetHistogramRange(tex, m_TexDisplay.typeHint); norangePaint = false; } // reset the range if desired if (m_Core.Config.TextureViewer_ResetRange) { UI_SetHistogramRange(tex, m_TexDisplay.typeHint); } } UI_UpdateFittedScale(); //render.Width = (int)(CurrentTexDisplayWidth * m_TexDisplay.scale); //render.Height = (int)(CurrentTexDisplayHeight * m_TexDisplay.scale); UI_UpdateTextureDetails(); UI_UpdateChannels(); if (autoFit.Checked) AutoFitRange(); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { RT_UpdateVisualRange(r); RT_UpdateAndDisplay(r); if (tex.ID != ResourceId.Null) { var us = r.GetUsage(tex.ID); var tb = m_Core.TimelineBar; if (tb != null && tb.Visible && !tb.IsDisposed) { this.BeginInvoke(new Action(() => { tb.HighlightResource(tex.ID, tex.name, us); })); } } }); } void dockPanel_ActiveDocumentChanged(object sender, EventArgs e) { var d = dockPanel.ActiveDocument as DockContent; if (d == null) return; if (d.Visible) d.Controls.Add(renderToolstripContainer); UI_OnTextureSelectionChanged(); } void PreviewPanel_FormClosing(object sender, FormClosingEventArgs e) { if ((sender as Control).Visible == false && renderToolstripContainer.Parent != sender) return; var tabs = m_PreviewPanel.Pane.TabStripControl.Tabs; for (int i = 0; i < tabs.Count; i++) { if (tabs[i].Content == sender) { var dc = m_PreviewPanel; if (i > 0) { dc = (tabs[i - 1].Content as DockContent); } else if (i < tabs.Count - 1) { dc = (tabs[i + 1].Content as DockContent); } dc.Controls.Add(renderToolstripContainer); dc.Show(); return; } } m_PreviewPanel.Controls.Add(renderToolstripContainer); m_PreviewPanel.Show(); } private void UI_UpdateTextureDetails() { texStatusDim.Text = ""; if (m_Core.CurTextures == null || CurrentTexture == null) { m_PreviewPanel.Text = "Unbound"; texStatusDim.Text = ""; return; } FetchTexture current = CurrentTexture; ResourceId followID = m_Following.GetResourceId(m_Core); { bool found = false; string name = ""; foreach (var t in m_Core.CurTextures) { if (t.ID == followID) { name = t.name; found = true; } } foreach (var b in m_Core.CurBuffers) { if (b.ID == followID) { name = b.name; found = true; } } if (followID == ResourceId.Null) { m_PreviewPanel.Text = "Unbound"; } else if(found) { switch (m_Following.Type) { case FollowType.OutputColour: m_PreviewPanel.Text = string.Format("Cur Output {0} - {1}", m_Following.index, name); break; case FollowType.OutputDepth: m_PreviewPanel.Text = string.Format("Cur Depth Output - {0}", name); break; case FollowType.ReadWrite: m_PreviewPanel.Text = string.Format("Cur RW Output - {0}", name); break; case FollowType.ReadOnly: m_PreviewPanel.Text = string.Format("Cur Input {0} - {1}", m_Following.index, name); break; } } else { switch (m_Following.Type) { case FollowType.OutputColour: m_PreviewPanel.Text = string.Format("Cur Output {0}", m_Following.index); break; case FollowType.OutputDepth: m_PreviewPanel.Text = string.Format("Cur Depth Output"); break; case FollowType.ReadWrite: m_PreviewPanel.Text = string.Format("Cur RW Output"); break; case FollowType.ReadOnly: m_PreviewPanel.Text = string.Format("Cur Input {0}", m_Following.index); break; } } } texStatusDim.Text = current.name + " - "; if (current.dimension >= 1) texStatusDim.Text += current.width; if (current.dimension >= 2) texStatusDim.Text += "x" + current.height; if (current.dimension >= 3) texStatusDim.Text += "x" + current.depth; if (current.arraysize > 1) texStatusDim.Text += "[" + current.arraysize + "]"; if(current.msQual > 0 || current.msSamp > 1) texStatusDim.Text += string.Format(" MS{{{0}x {1}Q}}", current.msSamp, current.msQual); texStatusDim.Text += " " + current.mips + " mips"; texStatusDim.Text += " - " + current.format.ToString(); if (current.format.compType != m_TexDisplay.typeHint && m_TexDisplay.typeHint != FormatComponentType.None) { texStatusDim.Text += " Viewed as type " + m_TexDisplay.typeHint.Str(); } } private bool PixelPicked { get { return (m_CurPixelValue != null); } set { if (value == true) { debugPixelContext.Enabled = true; toolTip.RemoveAll(); toolTip.SetToolTip(debugPixelContext, "Debug this pixel"); toolTip.SetToolTip(pixelHistory, "Show history for this pixel"); pixelHistory.Enabled = true; } else { m_CurPixelValue = null; m_CurRealValue = null; debugPixelContext.Enabled = false; toolTip.RemoveAll(); toolTip.SetToolTip(debugPixelContext, "Right Click to choose a pixel"); toolTip.SetToolTip(pixelHistory, "Right Click to choose a pixel"); pixelHistory.Enabled = false; } pixelContext.Invalidate(); } } private void UI_UpdateStatusText() { if (textureList.InvokeRequired) { this.BeginInvoke(new Action(UI_UpdateStatusText)); return; } FetchTexture tex = CurrentTexture; if (tex == null) return; bool dsv = ((tex.creationFlags & TextureCreationFlags.DSV) != 0) || (tex.format.compType == FormatComponentType.Depth); bool uintTex = (tex.format.compType == FormatComponentType.UInt); bool sintTex = (tex.format.compType == FormatComponentType.SInt); if (m_TexDisplay.overlay == TextureDisplayOverlay.QuadOverdrawPass || m_TexDisplay.overlay == TextureDisplayOverlay.QuadOverdrawDraw) { dsv = false; uintTex = false; sintTex = true; } if (m_CurHoverValue != null) { if (dsv || uintTex || sintTex) { hoverSwatch.BackColor = Color.Black; } else { float r = Helpers.Clamp(m_CurHoverValue.value.f[0], 0.0f, 1.0f); float g = Helpers.Clamp(m_CurHoverValue.value.f[1], 0.0f, 1.0f); float b = Helpers.Clamp(m_CurHoverValue.value.f[2], 0.0f, 1.0f); if (tex.format.srgbCorrected || (tex.creationFlags & TextureCreationFlags.SwapBuffer) > 0) { r = (float)Math.Pow(r, 1.0f / 2.2f); g = (float)Math.Pow(g, 1.0f / 2.2f); b = (float)Math.Pow(b, 1.0f / 2.2f); } hoverSwatch.BackColor = Color.FromArgb((int)(255.0f * r), (int)(255.0f * g), (int)(255.0f * b)); } } int y = m_CurHoverPixel.Y >> (int)m_TexDisplay.mip; if (m_Core.APIProps.pipelineType == APIPipelineStateType.OpenGL) y = (int)(tex.height - 1) - y; if (m_TexDisplay.FlipY) y = (int)(tex.height - 1) - y; y = Math.Max(0, y); int x = m_CurHoverPixel.X >> (int)m_TexDisplay.mip; float invWidth = tex.width > 0 ? 1.0f / tex.width : 0.0f; float invHeight = tex.height > 0 ? 1.0f /tex.height : 0.0f; string hoverCoords = String.Format("{0,4}, {1,4} ({2:0.0000}, {3:0.0000})", x, y, (x * invWidth), (y * invHeight)); string statusText = "Hover - " + hoverCoords; if (m_CurHoverPixel.X > tex.width || m_CurHoverPixel.Y > tex.height || m_CurHoverPixel.X < 0 || m_CurHoverPixel.Y < 0) statusText = "Hover - [" + hoverCoords + "]"; if (m_CurPixelValue != null) { x = m_PickedPoint.X >> (int)m_TexDisplay.mip; y = m_PickedPoint.Y >> (int)m_TexDisplay.mip; if (m_Core.APIProps.pipelineType == APIPipelineStateType.OpenGL) y = (int)(tex.height - 1) - y; if (m_TexDisplay.FlipY) y = (int)(tex.height - 1) - y; y = Math.Max(0, y); statusText += " - Right click - " + String.Format("{0,4}, {1,4}: ", x, y); PixelValue val = m_CurPixelValue; if (m_TexDisplay.CustomShader != ResourceId.Null && m_CurRealValue != null) { statusText += Formatter.Format(val.value.f[0]) + ", " + Formatter.Format(val.value.f[1]) + ", " + Formatter.Format(val.value.f[2]) + ", " + Formatter.Format(val.value.f[3]); val = m_CurRealValue; statusText += " (Real: "; } if (dsv) { statusText += "Depth "; if (uintTex) { if(tex.format.compByteWidth == 2) statusText += Formatter.Format(val.value.u16[0]); else statusText += Formatter.Format(val.value.u[0]); } else { statusText += Formatter.Format(val.value.f[0]); } statusText += String.Format(", Stencil {0} / 0x{0:X2}", (int)(255.0f * val.value.f[1])); } else { if (uintTex) { statusText += val.value.u[0].ToString() + ", " + val.value.u[1].ToString() + ", " + val.value.u[2].ToString() + ", " + val.value.u[3].ToString(); } else if (sintTex) { statusText += val.value.i[0].ToString() + ", " + val.value.i[1].ToString() + ", " + val.value.i[2].ToString() + ", " + val.value.i[3].ToString(); } else { statusText += Formatter.Format(val.value.f[0]) + ", " + Formatter.Format(val.value.f[1]) + ", " + Formatter.Format(val.value.f[2]) + ", " + Formatter.Format(val.value.f[3]); } } if (m_TexDisplay.CustomShader != ResourceId.Null) statusText += ")"; PixelPicked = true; } else { statusText += " - Right click to pick a pixel"; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) m_Output.DisablePixelContext(); }); PixelPicked = false; } // try and keep status text consistent by sticking to the high water mark // of length (prevents nasty oscillation when the length of the string is // just popping over/under enough to overflow onto the next line). if (statusText.Length > m_HighWaterStatusLength) m_HighWaterStatusLength = statusText.Length; if (statusText.Length < m_HighWaterStatusLength) statusText += new String(' ', m_HighWaterStatusLength - statusText.Length); statusLabel.Text = statusText; } private void UI_UpdateChannels() { FetchTexture tex = CurrentTexture; channelStrip.SuspendLayout(); if (tex != null && (tex.creationFlags & TextureCreationFlags.SwapBuffer) != 0) { // swapbuffer is always srgb for 8-bit types, linear for 16-bit types gammaDisplay.Enabled = false; if (tex.format.compByteWidth == 2 && !tex.format.special) m_TexDisplay.linearDisplayAsGamma = false; else m_TexDisplay.linearDisplayAsGamma = true; } else { if (tex != null && !tex.format.srgbCorrected) gammaDisplay.Enabled = true; else gammaDisplay.Enabled = false; m_TexDisplay.linearDisplayAsGamma = gammaDisplay.Checked; } bool dsv = false; if(tex != null) dsv = ((tex.creationFlags & TextureCreationFlags.DSV) != 0) || (tex.format.compType == FormatComponentType.Depth); if (dsv && (string)channels.SelectedItem != "Custom") { customRed.Visible = false; customGreen.Visible = false; customBlue.Visible = false; customAlpha.Visible = false; mulLabel.Visible = false; hdrMul.Visible = false; customShader.Visible = false; customCreate.Visible = false; customEdit.Visible = false; customDelete.Visible = false; depthStencilToolstrip.Visible = true; backcolorPick.Visible = false; checkerBack.Visible = false; mulSep.Visible = false; m_TexDisplay.Red = depthDisplay.Checked; m_TexDisplay.Green = stencilDisplay.Checked; m_TexDisplay.Blue = false; m_TexDisplay.Alpha = false; m_TexDisplay.HDRMul = -1.0f; if (m_TexDisplay.CustomShader != ResourceId.Null) { m_CurPixelValue = null; m_CurRealValue = null; UI_UpdateStatusText(); } m_TexDisplay.CustomShader = ResourceId.Null; } else if ((string)channels.SelectedItem == "RGBA" || !m_Core.LogLoaded) { customRed.Visible = true; customGreen.Visible = true; customBlue.Visible = true; customAlpha.Visible = true; mulLabel.Visible = false; hdrMul.Visible = false; customShader.Visible = false; customCreate.Visible = false; customEdit.Visible = false; customDelete.Visible = false; depthStencilToolstrip.Visible = false; backcolorPick.Visible = true; checkerBack.Visible = true; mulSep.Visible = false; m_TexDisplay.Red = customRed.Checked; m_TexDisplay.Green = customGreen.Checked; m_TexDisplay.Blue = customBlue.Checked; m_TexDisplay.Alpha = customAlpha.Checked; m_TexDisplay.HDRMul = -1.0f; if (m_TexDisplay.CustomShader != ResourceId.Null) { m_CurPixelValue = null; m_CurRealValue = null; UI_UpdateStatusText(); } m_TexDisplay.CustomShader = ResourceId.Null; } else if ((string)channels.SelectedItem == "RGBM") { customRed.Visible = true; customGreen.Visible = true; customBlue.Visible = true; customAlpha.Visible = false; mulLabel.Visible = true; hdrMul.Visible = true; customShader.Visible = false; customCreate.Visible = false; customEdit.Visible = false; customDelete.Visible = false; depthStencilToolstrip.Visible = false; backcolorPick.Visible = false; checkerBack.Visible = false; mulSep.Visible = true; m_TexDisplay.Red = customRed.Checked; m_TexDisplay.Green = customGreen.Checked; m_TexDisplay.Blue = customBlue.Checked; m_TexDisplay.Alpha = false; float mul = 32.0f; if (!float.TryParse(hdrMul.Text, out mul)) hdrMul.Text = mul.ToString(); m_TexDisplay.HDRMul = mul; if (m_TexDisplay.CustomShader != ResourceId.Null) { m_CurPixelValue = null; m_CurRealValue = null; UI_UpdateStatusText(); } m_TexDisplay.CustomShader = ResourceId.Null; } else if ((string)channels.SelectedItem == "Custom") { customRed.Visible = true; customGreen.Visible = true; customBlue.Visible = true; customAlpha.Visible = true; mulLabel.Visible = false; hdrMul.Visible = false; customShader.Visible = true; customCreate.Visible = true; customEdit.Visible = true; customDelete.Visible = true; depthStencilToolstrip.Visible = false; backcolorPick.Visible = false; checkerBack.Visible = false; mulSep.Visible = false; m_TexDisplay.Red = customRed.Checked; m_TexDisplay.Green = customGreen.Checked; m_TexDisplay.Blue = customBlue.Checked; m_TexDisplay.Alpha = customAlpha.Checked; m_TexDisplay.HDRMul = -1.0f; m_TexDisplay.CustomShader = ResourceId.Null; if (m_CustomShaders.ContainsKey(customShader.Text.ToUpperInvariant())) { if (m_TexDisplay.CustomShader == ResourceId.Null) { m_CurPixelValue = null; m_CurRealValue = null; UI_UpdateStatusText(); } m_TexDisplay.CustomShader = m_CustomShaders[customShader.Text.ToUpperInvariant()]; customDelete.Enabled = customEdit.Enabled = true; customCreate.Enabled = false; } else { customDelete.Enabled = customEdit.Enabled = false; customCreate.Enabled = true; } } m_TexDisplay.FlipY = flip_y.Checked; channelStrip.ResumeLayout(); m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); } private void RT_UpdateAndDisplay(ReplayRenderer r) { if (m_Output == null) return; m_Output.SetTextureDisplay(m_TexDisplay); render.Invalidate(); } private void DrawCheckerboard(Graphics g, Rectangle rect) { int numX = (int)Math.Ceiling((float)rect.Width / 64.0f); int numY = (int)Math.Ceiling((float)rect.Height / 64.0f); Brush dark = new SolidBrush(Color.FromArgb((int)(255 * darkBack.x), (int)(255 * darkBack.y), (int)(255 * darkBack.z))); Brush light = new SolidBrush(Color.FromArgb((int)(255 * lightBack.x), (int)(255 * lightBack.y), (int)(255 * lightBack.z))); for (int x = 0; x < numX; x++) { for (int y = 0; y < numY; y++) { var brush = ((x%2) == (y%2)) ? dark : light; g.FillRectangle(brush, x * 64, y * 64, 64, 64); } } dark.Dispose(); light.Dispose(); } private void pixelContext_Paint(object sender, PaintEventArgs e) { if (m_Output == null || m_Core.Renderer == null) { DrawCheckerboard(e.Graphics, pixelContext.DisplayRectangle); return; } m_Core.Renderer.Invoke((ReplayRenderer r) => { if (m_Output != null) m_Output.Display(); }); } private void render_Paint(object sender, PaintEventArgs e) { renderContainer.Invalidate(); if (m_Output == null || m_Core.Renderer == null) { DrawCheckerboard(e.Graphics, render.DisplayRectangle); return; } foreach (var prev in rwPanel.Thumbnails) if (prev.Unbound) prev.Clear(); foreach (var prev in roPanel.Thumbnails) if (prev.Unbound) prev.Clear(); m_Core.Renderer.Invoke((ReplayRenderer r) => { if (m_Output != null) m_Output.Display(); }); } #endregion #region Scale Handling private FetchTexture FollowingTexture { get { if (!m_Core.LogLoaded || m_Core.CurTextures == null) return null; ResourceId ID = m_Following.GetResourceId(m_Core); if (ID == ResourceId.Null) ID = m_TexDisplay.texid; for (int i = 0; i < m_Core.CurTextures.Length; i++) { if (m_Core.CurTextures[i].ID == ID) { return m_Core.CurTextures[i]; } } return null; } } private FetchTexture CurrentTexture { get { var dc = renderToolstripContainer.Parent as DockContent; if (dc != null && dc.Tag != null) return dc.Tag as FetchTexture; return FollowingTexture; } } private UInt32 CurrentTexDisplayWidth { get { if (CurrentTexture == null) return 1; return CurrentTexture.width; } } private UInt32 CurrentTexDisplayHeight { get { if (CurrentTexture == null) return 1; if (CurrentTexture.dimension == 1) return 100; return CurrentTexture.height; } } private bool FitToWindow { get { return fitToWindow.Checked; } set { if (!FitToWindow && value) { fitToWindow.Checked = true; } else if (FitToWindow && !value) { fitToWindow.Checked = false; float curScale = m_TexDisplay.scale; zoomOption.SelectedText = ""; CurrentZoomValue = curScale; } } } private float GetFitScale() { if (CurrentTexture == null) return 1.0f; float xscale = (float)render.Width / (float)CurrentTexDisplayWidth; float yscale = (float)render.Height / (float)CurrentTexDisplayHeight; return Math.Min(xscale, yscale); } private void UI_UpdateFittedScale() { if (FitToWindow) UI_SetScale(1.0f); } private void UI_SetScale(float s) { UI_SetScale(s, render.ClientRectangle.Width / 2, render.ClientRectangle.Height / 2); } bool ScrollUpdateScrollbars = true; float CurMaxScrollX { get { return render.Width - CurrentTexDisplayWidth * m_TexDisplay.scale; } } float CurMaxScrollY { get { return render.Height - CurrentTexDisplayHeight * m_TexDisplay.scale; } } Point ScrollPosition { get { return new Point((int)m_TexDisplay.offx, (int)m_TexDisplay.offy); } set { m_TexDisplay.offx = Math.Max(CurMaxScrollX, value.X); m_TexDisplay.offy = Math.Max(CurMaxScrollY, value.Y); m_TexDisplay.offx = Math.Min(0.0f, (float)m_TexDisplay.offx); m_TexDisplay.offy = Math.Min(0.0f, (float)m_TexDisplay.offy); if (ScrollUpdateScrollbars) { if (renderHScroll.Enabled) { // this is so stupid. float actualMaximum = (float)(renderHScroll.Maximum - renderHScroll.LargeChange); float delta = m_TexDisplay.offx / (float)CurMaxScrollX; renderHScroll.Value = (int)Helpers.Clamp( (int)(delta * actualMaximum), 0, renderHScroll.Maximum); } if (renderVScroll.Enabled) { // really. So stupid. float actualMaximum = (float)(renderVScroll.Maximum - renderVScroll.LargeChange); float delta = m_TexDisplay.offy / (float)CurMaxScrollY; renderVScroll.Value = (int)Helpers.Clamp((int)(delta * actualMaximum), 0, renderVScroll.Maximum); } } m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); } } private void UI_SetScale(float s, int x, int y) { if (FitToWindow) s = GetFitScale(); float prevScale = m_TexDisplay.scale; m_TexDisplay.scale = Math.Max(0.1f, Math.Min(256.0f, s)); m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); float scaleDelta = (m_TexDisplay.scale / prevScale); Point newPos = ScrollPosition; newPos -= new Size(x, y); newPos = new Point((int)(newPos.X * scaleDelta), (int)(newPos.Y * scaleDelta)); newPos += new Size(x, y); ScrollPosition = newPos; CurrentZoomValue = m_TexDisplay.scale; CalcScrollbars(); } private void CalcScrollbars() { if (Math.Floor(CurrentTexDisplayWidth * m_TexDisplay.scale) <= render.Width) { renderHScroll.Enabled = false; } else { renderHScroll.Enabled = true; renderHScroll.Maximum = (int)Math.Ceiling(CurrentTexDisplayWidth * m_TexDisplay.scale - (float)render.Width); renderHScroll.LargeChange = Math.Max(1, renderHScroll.Maximum/6); } if (Math.Floor(CurrentTexDisplayHeight * m_TexDisplay.scale) <= render.Height) { renderVScroll.Enabled = false; } else { renderVScroll.Enabled = true; renderVScroll.Maximum = (int)Math.Ceiling(CurrentTexDisplayHeight * m_TexDisplay.scale - (float)render.Height); renderVScroll.LargeChange = Math.Max(1, renderVScroll.Maximum / 6); } } private void render_Layout(object sender, LayoutEventArgs e) { UI_UpdateFittedScale(); CalcScrollbars(); renderContainer.Invalidate(); } #endregion #region Mouse movement and scrolling private Point m_DragStartScroll = Point.Empty; private Point m_DragStartPos = Point.Empty; private Point m_CurHoverPixel = Point.Empty; private Point m_PickedPoint = Point.Empty; private PixelValue m_CurRealValue = null; private PixelValue m_CurPixelValue = null; private PixelValue m_CurHoverValue = null; private void RT_UpdateHoverColour(PixelValue v) { m_CurHoverValue = v; this.BeginInvoke(new Action(UI_UpdateStatusText)); } private void RT_PickPixelsAndUpdate(int x, int y, bool ctx) { FetchTexture tex = CurrentTexture; if (tex == null) return; if(ctx) m_Output.SetPixelContextLocation((UInt32)x, (UInt32)y); if (m_TexDisplay.FlipY) y = (int)(tex.height - 1) - y; var pickValue = m_Output.PickPixel(m_TexDisplay.texid, true, (UInt32)x, (UInt32)y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx); PixelValue realValue = null; if (m_TexDisplay.CustomShader != ResourceId.Null) realValue = m_Output.PickPixel(m_TexDisplay.texid, false, (UInt32)x, (UInt32)y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx); RT_UpdatePixelColour(pickValue, realValue, false); } private void RT_UpdatePixelColour(PixelValue withCustom, PixelValue realValue, bool UpdateHover) { m_CurPixelValue = withCustom; if (UpdateHover) m_CurHoverValue = withCustom; m_CurRealValue = realValue; this.BeginInvoke(new Action(UI_UpdateStatusText)); } private void render_KeyDown(object sender, KeyEventArgs e) { bool nudged = false; FetchTexture tex = CurrentTexture; if (tex == null) return; if (e.KeyCode == Keys.C && e.Control) { try { Clipboard.SetText(texStatusDim.Text + " | " + statusLabel.Text); } catch (System.Exception) { try { Clipboard.SetDataObject(texStatusDim.Text + " | " + statusLabel.Text); } catch (System.Exception) { // give up! } } } if (!m_Core.LogLoaded) return; if (e.KeyCode == Keys.Up && m_PickedPoint.Y > 0) { m_PickedPoint = new Point(m_PickedPoint.X, m_PickedPoint.Y - 1); nudged = true; } else if (e.KeyCode == Keys.Down && m_PickedPoint.Y < tex.height-1) { m_PickedPoint = new Point(m_PickedPoint.X, m_PickedPoint.Y + 1); nudged = true; } else if (e.KeyCode == Keys.Left && m_PickedPoint.X > 0) { m_PickedPoint = new Point(m_PickedPoint.X - 1, m_PickedPoint.Y); nudged = true; } else if (e.KeyCode == Keys.Right && m_PickedPoint.X < tex.width - 1) { m_PickedPoint = new Point(m_PickedPoint.X + 1, m_PickedPoint.Y); nudged = true; } if(nudged) { e.Handled = true; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); RT_UpdateAndDisplay(r); }); UI_UpdateStatusText(); } } private void renderHScroll_Scroll(object sender, ScrollEventArgs e) { ScrollUpdateScrollbars = false; if (e.Type != ScrollEventType.EndScroll) { float actualMaximum = (float)(renderHScroll.Maximum - renderHScroll.LargeChange); float delta = (float)e.NewValue / actualMaximum; ScrollPosition = new Point((int)(CurMaxScrollX * delta), ScrollPosition.Y); } ScrollUpdateScrollbars = true; } private void renderVScroll_Scroll(object sender, ScrollEventArgs e) { ScrollUpdateScrollbars = false; if (e.Type != ScrollEventType.EndScroll) { float actualMaximum = (float)(renderVScroll.Maximum - renderVScroll.LargeChange); float delta = (float)e.NewValue / actualMaximum; ScrollPosition = new Point(ScrollPosition.X, (int)(CurMaxScrollY * delta) ); } ScrollUpdateScrollbars = true; } private void render_MouseLeave(object sender, EventArgs e) { Cursor = Cursors.Default; } private void render_MouseUp(object sender, MouseEventArgs e) { Cursor = Cursors.Default; } private void render_MouseMove(object sender, MouseEventArgs e) { m_CurHoverPixel = render.PointToClient(Cursor.Position); m_CurHoverPixel.X = (int)(((float)m_CurHoverPixel.X - m_TexDisplay.offx) / m_TexDisplay.scale); m_CurHoverPixel.Y = (int)(((float)m_CurHoverPixel.Y - m_TexDisplay.offy) / m_TexDisplay.scale); if (e.Button == MouseButtons.Right && m_TexDisplay.texid != ResourceId.Null) { FetchTexture tex = CurrentTexture; if (tex != null) { m_PickedPoint = m_CurHoverPixel; m_PickedPoint.X = Helpers.Clamp(m_PickedPoint.X, 0, (int)tex.width-1); m_PickedPoint.Y = Helpers.Clamp(m_PickedPoint.Y, 0, (int)tex.height - 1); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); }); } Cursor = Cursors.Cross; } if (e.Button == MouseButtons.None && m_TexDisplay.texid != ResourceId.Null) { FetchTexture tex = CurrentTexture; if (tex != null) { m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) { UInt32 y = (UInt32)m_CurHoverPixel.Y; if (m_TexDisplay.FlipY) y = (uint)(tex.height - 1) - y; RT_UpdateHoverColour(m_Output.PickPixel(m_TexDisplay.texid, true, (UInt32)m_CurHoverPixel.X, y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx)); } }); } } Panel p = renderContainer; Point curpos = Cursor.Position; if (e.Button == MouseButtons.Left) { if (Math.Abs(m_DragStartPos.X - curpos.X) > p.HorizontalScroll.SmallChange || Math.Abs(m_DragStartPos.Y - curpos.Y) > p.VerticalScroll.SmallChange) { ScrollPosition = new Point(m_DragStartScroll.X + (curpos.X - m_DragStartPos.X), m_DragStartScroll.Y + (curpos.Y - m_DragStartPos.Y)); } Cursor = Cursors.NoMove2D; } if (e.Button != MouseButtons.Left && e.Button != MouseButtons.Right) { Cursor = Cursors.Default; } UI_UpdateStatusText(); } private void pixelContext_MouseClick(object sender, MouseEventArgs e) { pixelContext.Focus(); if (e.Button == MouseButtons.Right) { pixelContextMenu.Show(pixelContext, e.Location); } } private void render_MouseClick(object sender, MouseEventArgs e) { render.Focus(); if (e.Button == MouseButtons.Right) { FetchTexture tex = CurrentTexture; if (tex != null) { m_PickedPoint = m_CurHoverPixel; m_PickedPoint.X = Helpers.Clamp(m_PickedPoint.X, 0, (int)tex.width - 1); m_PickedPoint.Y = Helpers.Clamp(m_PickedPoint.Y, 0, (int)tex.height - 1); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); }); } Cursor = Cursors.Cross; } if (e.Button == MouseButtons.Left) { m_DragStartPos = Cursor.Position; m_DragStartScroll = ScrollPosition; Cursor = Cursors.NoMove2D; } } private void render_MouseWheel(object sender, MouseEventArgs e) { Point cursorPos = renderContainer.PointToClient(Cursor.Position); FitToWindow = false; // scroll in logarithmic scale double logScale = Math.Log(m_TexDisplay.scale); logScale += e.Delta / 2500.0; UI_SetScale((float)Math.Exp(logScale), cursorPos.X, cursorPos.Y); ((HandledMouseEventArgs)e).Handled = true; } #endregion #region Texture Display Options private float CurrentZoomValue { get { if (FitToWindow) return m_TexDisplay.scale; int zoom = 100; Int32.TryParse(zoomOption.Text.ToString().Replace('%', ' '), out zoom); return (float)(zoom) / 100.0f; } set { if(!zoomOption.IsDisposed) zoomOption.Text = (Math.Ceiling(value * 100)).ToString() + "%"; } } private void zoomOption_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == '\n' || e.KeyChar == '\r') { string txt = zoomOption.Text; FitToWindow = false; zoomOption.Text = txt; UI_SetScale(CurrentZoomValue); } } private void zoomOption_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { string txt = zoomOption.Text; FitToWindow = false; zoomOption.Text = txt; UI_SetScale(CurrentZoomValue); } } private void zoomOption_SelectedIndexChanged(object sender, EventArgs e) { if ((zoomOption.Focused || zoomOption.ContentRectangle.Contains(Cursor.Position)) && zoomOption.SelectedItem != null) { var item = zoomOption.SelectedItem.ToString(); FitToWindow = false; zoomOption.Text = item; UI_SetScale(CurrentZoomValue); } } private void zoomOption_DropDownClosed(object sender, EventArgs e) { if (zoomOption.SelectedItem != null) { var item = zoomOption.SelectedItem.ToString(); FitToWindow = false; zoomOption.Text = item; UI_SetScale(CurrentZoomValue); } } private void fitToWindow_CheckedChanged(object sender, EventArgs e) { UI_UpdateFittedScale(); } private void zoomExactSize_Click(object sender, EventArgs e) { fitToWindow.Checked = false; UI_SetScale(1.0f); } private void backcolorPick_Click(object sender, EventArgs e) { var result = colorDialog.ShowDialog(); if (result == DialogResult.OK || result == DialogResult.Yes) { darkBack = lightBack = m_TexDisplay.darkBackgroundColour = m_TexDisplay.lightBackgroundColour = new FloatVector( ((float)colorDialog.Color.R) / 255.0f, ((float)colorDialog.Color.G) / 255.0f, ((float)colorDialog.Color.B) / 255.0f); backcolorPick.Checked = true; checkerBack.Checked = false; } m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); if (m_Output == null) { render.Invalidate(); pixelContext.Invalidate(); } } private void checkerBack_Click(object sender, EventArgs e) { var defaults = new TextureDisplay(); darkBack = m_TexDisplay.darkBackgroundColour = defaults.darkBackgroundColour; lightBack = m_TexDisplay.lightBackgroundColour = defaults.lightBackgroundColour; backcolorPick.Checked = false; checkerBack.Checked = true; m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); if (m_Output == null) { render.Invalidate(); pixelContext.Invalidate(); } } private void mipLevel_SelectedIndexChanged(object sender, EventArgs e) { if (CurrentTexture == null) return; if (CurrentTexture.mips > 1) { m_TexDisplay.mip = (UInt32)mipLevel.SelectedIndex; m_TexDisplay.sampleIdx = 0; } else { m_TexDisplay.mip = 0; m_TexDisplay.sampleIdx = (UInt32)mipLevel.SelectedIndex; if (mipLevel.SelectedIndex == CurrentTexture.msSamp) m_TexDisplay.sampleIdx = ~0U; } m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); if (m_Output != null && m_PickedPoint.X >= 0 && m_PickedPoint.Y >= 0) { m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); }); } m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); } private void overlay_SelectedIndexChanged(object sender, EventArgs e) { m_TexDisplay.overlay = TextureDisplayOverlay.None; if (overlay.SelectedIndex > 0) m_TexDisplay.overlay = (TextureDisplayOverlay)overlay.SelectedIndex; m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); } private void sliceFace_SelectedIndexChanged(object sender, EventArgs e) { m_TexDisplay.sliceFace = (UInt32)sliceFace.SelectedIndex; m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); if (m_Output != null && m_PickedPoint.X >= 0 && m_PickedPoint.Y >= 0) { m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); }); } m_Core.Renderer.BeginInvoke(RT_UpdateAndDisplay); } private void updateChannelsHandler(object sender, EventArgs e) { UI_UpdateChannels(); } private void channelButton_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right && sender is ToolStripButton) { bool checkd = false; var butts = new ToolStripButton[] { customRed, customGreen, customBlue, customAlpha }; foreach (var b in butts) { if(b.Checked && b != sender) checkd = true; if(!b.Checked && b == sender) checkd = true; } customRed.Checked = !checkd; customGreen.Checked = !checkd; customBlue.Checked = !checkd; customAlpha.Checked = !checkd; (sender as ToolStripButton).Checked = checkd; } } bool norangePaint = false; Thread rangePaintThread = null; void rangeHistogram_RangeUpdated(object sender, Controls.RangeHistogramEventArgs e) { m_TexDisplay.rangemin = e.BlackPoint; m_TexDisplay.rangemax = e.WhitePoint; rangeBlack.Text = Formatter.Format(e.BlackPoint); rangeWhite.Text = Formatter.Format(e.WhitePoint); if (rangePaintThread != null && rangePaintThread.ThreadState != ThreadState.Aborted && rangePaintThread.ThreadState != ThreadState.Stopped) { return; } if (norangePaint) return; rangePaintThread = Helpers.NewThread(new ThreadStart(() => { m_Core.Renderer.Invoke((ReplayRenderer r) => { RT_UpdateAndDisplay(r); if (m_Output != null) m_Output.Display(); }); Thread.Sleep(8); })); rangePaintThread.Start(); } #endregion #region Handlers private void zoomRange_Click(object sender, EventArgs e) { float black = rangeHistogram.BlackPoint; float white = rangeHistogram.WhitePoint; autoFit.Checked = false; rangeHistogram.SetRange(black, white); m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); } private void reset01_Click(object sender, EventArgs e) { UI_SetHistogramRange(CurrentTexture, m_TexDisplay.typeHint); autoFit.Checked = false; m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); } private void autoFit_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { autoFit.Checked = !autoFit.Checked; if (autoFit.Checked) AutoFitRange(); } } private void autoFit_Click(object sender, EventArgs e) { AutoFitRange(); } private void AutoFitRange() { // no log loaded or buffer/empty texture currently being viewed - don't autofit if (!m_Core.LogLoaded || CurrentTexture == null) return; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { PixelValue min, max; bool success = r.GetMinMax(m_TexDisplay.texid, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx, m_TexDisplay.typeHint, out min, out max); if (success) { float minval = float.MaxValue; float maxval = -float.MaxValue; bool changeRange = false; ResourceFormat fmt = CurrentTexture.format; for (int i = 0; i < 4; i++) { if (fmt.compType == FormatComponentType.UInt) { min.value.f[i] = min.value.u[i]; max.value.f[i] = max.value.u[i]; } else if (fmt.compType == FormatComponentType.SInt) { min.value.f[i] = min.value.i[i]; max.value.f[i] = max.value.i[i]; } } if (m_TexDisplay.Red) { minval = Math.Min(minval, min.value.f[0]); maxval = Math.Max(maxval, max.value.f[0]); changeRange = true; } if (m_TexDisplay.Green && fmt.compCount > 1) { minval = Math.Min(minval, min.value.f[1]); maxval = Math.Max(maxval, max.value.f[1]); changeRange = true; } if (m_TexDisplay.Blue && fmt.compCount > 2) { minval = Math.Min(minval, min.value.f[2]); maxval = Math.Max(maxval, max.value.f[2]); changeRange = true; } if (m_TexDisplay.Alpha && fmt.compCount > 3) { minval = Math.Min(minval, min.value.f[3]); maxval = Math.Max(maxval, max.value.f[3]); changeRange = true; } if (changeRange) { this.BeginInvoke(new Action(() => { rangeHistogram.SetRange(minval, maxval); m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); })); } } }); } private bool m_Visualise = false; private void visualiseRange_CheckedChanged(object sender, EventArgs e) { if (visualiseRange.Checked) { rangeHistogram.MinimumSize = new Size(300, 90); m_Visualise = true; m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); } else { m_Visualise = false; rangeHistogram.MinimumSize = new Size(200, 20); rangeHistogram.HistogramData = null; } } private void RT_UpdateVisualRange(ReplayRenderer r) { if (!m_Visualise || CurrentTexture == null) return; ResourceFormat fmt = CurrentTexture.format; bool success = true; uint[] histogram; success = r.GetHistogram(m_TexDisplay.texid, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx, m_TexDisplay.typeHint, rangeHistogram.RangeMin, rangeHistogram.RangeMax, m_TexDisplay.Red, m_TexDisplay.Green && fmt.compCount > 1, m_TexDisplay.Blue && fmt.compCount > 2, m_TexDisplay.Alpha && fmt.compCount > 3, out histogram); if (success) { this.BeginInvoke(new Action(() => { rangeHistogram.SetHistogramRange(rangeHistogram.RangeMin, rangeHistogram.RangeMax); rangeHistogram.HistogramData = histogram; })); } } bool rangePoint_Dirty = false; private void rangePoint_Changed(object sender, EventArgs e) { rangePoint_Dirty = true; } private void rangePoint_Update() { float black = rangeHistogram.BlackPoint; float white = rangeHistogram.WhitePoint; float.TryParse(rangeBlack.Text, out black); float.TryParse(rangeWhite.Text, out white); rangeHistogram.SetRange(black, white); m_Core.Renderer.BeginInvoke(RT_UpdateVisualRange); } private void rangePoint_Leave(object sender, EventArgs e) { if (!rangePoint_Dirty) return; rangePoint_Update(); rangePoint_Dirty = false; } private void rangePoint_KeyPress(object sender, KeyPressEventArgs e) { // escape key if (e.KeyChar == '\0') { rangePoint_Dirty = false; rangeHistogram.SetRange(rangeHistogram.BlackPoint, rangeHistogram.WhitePoint); } if (e.KeyChar == '\n' || e.KeyChar == '\r') { rangePoint_Update(); } } private void tabContextMenu_Opening(object sender, CancelEventArgs e) { if (tabContextMenu.SourceControl == m_PreviewPanel.Pane.TabStripControl) { int idx = m_PreviewPanel.Pane.TabStripControl.Tabs.IndexOf(m_PreviewPanel.Pane.ActiveContent); if (idx == -1) e.Cancel = true; if (m_PreviewPanel.Pane.ActiveContent == m_PreviewPanel) closeTab.Enabled = false; else closeTab.Enabled = true; } } private void closeTab_Click(object sender, EventArgs e) { if (tabContextMenu.SourceControl == m_PreviewPanel.Pane.TabStripControl) { if (m_PreviewPanel.Pane.ActiveContent != m_PreviewPanel) { (m_PreviewPanel.Pane.ActiveContent as DockContent).Close(); } } } private void closeOtherTabs_Click(object sender, EventArgs e) { if (tabContextMenu.SourceControl == m_PreviewPanel.Pane.TabStripControl) { IDockContent active = m_PreviewPanel.Pane.ActiveContent; var tabs = m_PreviewPanel.Pane.TabStripControl.Tabs; for(int i=0; i < tabs.Count; i++) { if (tabs[i].Content != active && tabs[i].Content != m_PreviewPanel) { (tabs[i].Content as DockContent).Close(); i--; } } (active as DockContent).Show(); } } private void closeTabsToRight_Click(object sender, EventArgs e) { if (tabContextMenu.SourceControl == m_PreviewPanel.Pane.TabStripControl) { int idx = m_PreviewPanel.Pane.TabStripControl.Tabs.IndexOf(m_PreviewPanel.Pane.ActiveContent); var tabs = m_PreviewPanel.Pane.TabStripControl.Tabs; while (tabs.Count > idx+1) { (m_PreviewPanel.Pane.TabStripControl.Tabs[idx + 1].Content as DockContent).Close(); } } } private void TextureViewer_Resize(object sender, EventArgs e) { render.Invalidate(); } private void pixelHistory_Click(object sender, EventArgs e) { PixelModification[] history = null; int x = m_PickedPoint.X >> (int)m_TexDisplay.mip; int y = m_PickedPoint.Y >> (int)m_TexDisplay.mip; PixelHistoryView hist = new PixelHistoryView(m_Core, CurrentTexture, new Point(x, y), m_TexDisplay.sampleIdx, m_TexDisplay.rangemin, m_TexDisplay.rangemax, new bool[] { m_TexDisplay.Red, m_TexDisplay.Green, m_TexDisplay.Blue, m_TexDisplay.Alpha }); hist.Show(DockPanel); // add a short delay so that controls repainting after a new panel appears can get at the // render thread before we insert the long blocking pixel history task var delayedHistory = new BackgroundWorker(); delayedHistory.DoWork += delegate { Thread.Sleep(100); m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { history = r.PixelHistory(CurrentTexture.ID, (UInt32)x, (UInt32)y, m_TexDisplay.sliceFace, m_TexDisplay.mip, m_TexDisplay.sampleIdx, m_TexDisplay.typeHint); this.BeginInvoke(new Action(() => { hist.SetHistory(history); })); }); }; delayedHistory.RunWorkerAsync(); } private void debugPixel_Click(object sender, EventArgs e) { ShaderDebugTrace trace = null; ShaderReflection shaderDetails = m_Core.CurPipelineState.GetShaderReflection(ShaderStageType.Pixel); if(m_PickedPoint.X < 0 || m_PickedPoint.Y < 0) return; int x = m_PickedPoint.X >> (int)m_TexDisplay.mip; int y = m_PickedPoint.Y >> (int)m_TexDisplay.mip; m_Core.Renderer.Invoke((ReplayRenderer r) => { trace = r.DebugPixel((UInt32)x, (UInt32)y, m_TexDisplay.sampleIdx, uint.MaxValue); }); if (trace == null || trace.states.Length == 0) { // if we couldn't debug the pixel on this event, open up a pixel history pixelHistory_Click(sender, e); return; } this.BeginInvoke(new Action(() => { string debugContext = String.Format("Pixel {0},{1}", x, y); ShaderViewer s = new ShaderViewer(m_Core, shaderDetails, ShaderStageType.Pixel, trace, debugContext); s.Show(this.DockPanel); })); } private void viewTexBuffer_Click(object sender, EventArgs e) { if (CurrentTexture != null) { var viewer = new BufferViewer(m_Core, false); viewer.ViewRawBuffer(false, 0, ulong.MaxValue, CurrentTexture.ID); viewer.Show(this.DockPanel); } } private TextureSaveDialog m_SaveDialog = null; private void saveTex_Click(object sender, EventArgs e) { if (m_SaveDialog == null) m_SaveDialog = new TextureSaveDialog(m_Core); m_SaveDialog.saveData.id = m_TexDisplay.texid; m_SaveDialog.saveData.typeHint = m_TexDisplay.typeHint; m_SaveDialog.saveData.slice.sliceIndex = (int)m_TexDisplay.sliceFace; m_SaveDialog.saveData.mip = (int)m_TexDisplay.mip; m_SaveDialog.saveData.channelExtract = -1; if (m_TexDisplay.Red && !m_TexDisplay.Green && !m_TexDisplay.Blue && !m_TexDisplay.Alpha) m_SaveDialog.saveData.channelExtract = 0; if (!m_TexDisplay.Red && m_TexDisplay.Green && !m_TexDisplay.Blue && !m_TexDisplay.Alpha) m_SaveDialog.saveData.channelExtract = 1; if (!m_TexDisplay.Red && !m_TexDisplay.Green && m_TexDisplay.Blue && !m_TexDisplay.Alpha) m_SaveDialog.saveData.channelExtract = 2; if (!m_TexDisplay.Red && !m_TexDisplay.Green && !m_TexDisplay.Blue && m_TexDisplay.Alpha) m_SaveDialog.saveData.channelExtract = 3; m_SaveDialog.saveData.comp.blackPoint = m_TexDisplay.rangemin; m_SaveDialog.saveData.comp.whitePoint = m_TexDisplay.rangemax; m_SaveDialog.saveData.alphaCol = m_TexDisplay.lightBackgroundColour; m_SaveDialog.saveData.alpha = m_TexDisplay.Alpha ? AlphaMapping.BlendToCheckerboard : AlphaMapping.Discard; if (m_TexDisplay.Alpha && !checkerBack.Checked) m_SaveDialog.saveData.alpha = AlphaMapping.BlendToColour; m_SaveDialog.tex = CurrentTexture; if (m_TexDisplay.CustomShader != ResourceId.Null) { m_Core.Renderer.Invoke((ReplayRenderer r) => { ResourceId id = m_Output.GetCustomShaderTexID(); if(id != ResourceId.Null) m_SaveDialog.saveData.id = id; }); } if(m_SaveDialog.ShowDialog() == DialogResult.OK) { bool ret = false; m_Core.Renderer.Invoke((ReplayRenderer r) => { ret = r.SaveTexture(m_SaveDialog.saveData, m_SaveDialog.Filename); }); if(!ret) MessageBox.Show(string.Format("Error saving texture {0}.\n\nCheck diagnostic log in Help menu for more details.", saveTextureDialog.FileName), "Error saving texture", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void texturefilter_TextChanged(object sender, EventArgs e) { textureList.FillTextureList(texturefilter.SelectedIndex <= 0 ? texturefilter.Text : "", texturefilter.SelectedIndex == 1, texturefilter.SelectedIndex == 2); } private void texturefilter_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) { texturefilter.SelectedIndex = 0; texturefilter.Text = ""; } } private void clearTexFilter_Click(object sender, EventArgs e) { texturefilter.SelectedIndex = 0; texturefilter.Text = ""; } private void toolstripEnabledChanged(object sender, EventArgs e) { overlayStrip.Visible = overlayStripEnabled.Checked; overlayStrip.Visible = overlayStripEnabled.Checked; channelStrip.Visible = channelsStripEnabled.Checked; zoomStrip.Visible = zoomStripEnabled.Checked; rangeStrip.Visible = rangeStripEnabled.Checked; } private void mainToolstrips_TopToolStripPanel_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) toolstripMenu.Show((Control)sender, e.Location); } private void texListShow_Click(object sender, EventArgs e) { if (!m_TexlistDockPanel.Visible) { texturefilter.SelectedIndex = 0; texturefilter.Text = ""; m_TexlistDockPanel.Show(); } else { m_TexlistDockPanel.Hide(); } } void textureList_GoIconClick(object sender, GoIconClickEventArgs e) { ViewTexture(e.ID, false); } #endregion #region Thumbnail strip private void AddResourceUsageEntry(List items, uint start, uint end, ResourceUsage usage) { ToolStripItem item = null; if (start == end) item = new ToolStripLabel("EID " + start + ": " + usage.Str(m_Core.APIProps.pipelineType)); else item = new ToolStripLabel("EID " + start + "-" + end + ": " + usage.Str(m_Core.APIProps.pipelineType)); item.Click += new EventHandler(resourceContextItem_Click); item.Tag = end; items.Add(item); } private void OpenResourceContextMenu(ResourceId id, bool thumbStripMenu, Control c, Point p) { var menuItems = new List(); int i = 0; for (i = 0; i < rightclickMenu.Items.Count; i++) { menuItems.Add(rightclickMenu.Items[i]); if (rightclickMenu.Items[i] == usedStartLabel) break; menuItems[i].Visible = thumbStripMenu; } if (m_Core.CurPipelineState.SupportsBarriers) { imageInLayoutMenuItem.Visible = true; imageInLayoutMenuItem.Text = "Image is in layout " + m_Core.CurPipelineState.GetImageLayout(id); } else { imageInLayoutMenuItem.Visible = false; } if (id != ResourceId.Null) { usedSep.Visible = true; usedStartLabel.Visible = true; openNewTab.Visible = true; openNewTab.Tag = id; m_Core.Renderer.BeginInvoke((ReplayRenderer r) => { EventUsage[] usage = r.GetUsage(id); this.BeginInvoke(new Action(() => { uint start = 0; uint end = 0; ResourceUsage us = ResourceUsage.IndexBuffer; foreach (var u in usage) { if (start == 0) { start = end = u.eventID; us = u.usage; continue; } var curDraw = m_Core.GetDrawcall(u.eventID); bool distinct = false; // if the usage is different from the last, add a new entry, // or if the previous draw link is broken. if (u.usage != us || curDraw.previous == null) { distinct = true; } else { // otherwise search back through real draws, to see if the // last event was where we were - otherwise it's a new // distinct set of drawcalls and should have a separate // entry in the context menu FetchDrawcall prev = curDraw.previous; while(prev != null && prev.eventID > end) { if((prev.flags & (DrawcallFlags.Dispatch|DrawcallFlags.Drawcall|DrawcallFlags.CmdList)) == 0) { prev = prev.previous; } else { distinct = true; break; } if(prev == null) distinct = true; } } if(distinct) { AddResourceUsageEntry(menuItems, start, end, us); start = end = u.eventID; us = u.usage; } end = u.eventID; } if (start != 0) AddResourceUsageEntry(menuItems, start, end, us); rightclickMenu.Items.Clear(); rightclickMenu.Items.AddRange(menuItems.ToArray()); rightclickMenu.Show(c, p); })); }); } else { usedSep.Visible = false; usedStartLabel.Visible = false; openNewTab.Visible = false; rightclickMenu.Show(c, p); } } private void thumbsLayout_MouseDoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left && sender is ResourcePreview) { var id = m_Following.GetResourceId(m_Core); if (id != ResourceId.Null) ViewTexture(id, false); } } private void thumbsLayout_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left && sender is ResourcePreview) { var prev = (ResourcePreview)sender; var follow = (Following)prev.Tag; foreach (var p in rwPanel.Thumbnails) p.Selected = false; foreach (var p in roPanel.Thumbnails) p.Selected = false; m_Following = follow; prev.Selected = true; var id = m_Following.GetResourceId(m_Core); if (id != ResourceId.Null) { UI_OnTextureSelectionChanged(); m_PreviewPanel.Show(); } } if (e.Button == MouseButtons.Right) { ResourceId id = ResourceId.Null; if (sender is ResourcePreview) { var prev = (ResourcePreview)sender; var tagdata = (Following)prev.Tag; id = tagdata.GetResourceId(m_Core); if (id == ResourceId.Null && tagdata == m_Following) id = m_TexDisplay.texid; } OpenResourceContextMenu(id, true, (Control)sender, e.Location); } } private void textureList_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) OpenResourceContextMenu(FollowingTexture == null ? ResourceId.Null : FollowingTexture.ID, false, (Control)sender, e.Location); } void resourceContextItem_Click(object sender, EventArgs e) { if (sender is ToolStripItem) { var c = (ToolStripItem)sender; if (c.Tag is uint) m_Core.SetEventID(null, (uint)c.Tag); else if (c.Tag is ResourceId) ViewTexture((ResourceId)c.Tag, false); } } private void showDisabled_Click(object sender, EventArgs e) { showDisabled.Checked = !showDisabled.Checked; if (m_Core.LogLoaded) OnEventSelected(m_Core.CurEvent); } private void showEmpty_Click(object sender, EventArgs e) { showEmpty.Checked = !showEmpty.Checked; if (m_Core.LogLoaded) OnEventSelected(m_Core.CurEvent); } private void TextureViewer_FormClosed(object sender, FormClosedEventArgs e) { m_Core.RemoveLogViewer(this); } #endregion } }