From eaa85bf1fa7a28e9b50433671ad0571b82b9e008 Mon Sep 17 00:00:00 2001 From: Adrian Bucur Date: Thu, 29 Sep 2016 11:58:24 +0100 Subject: [PATCH] Enable SPIR-V External Disassembler Tool - Added the ability to select an external SPIR-V disassembler and to use it for editing any of the shaders in the pipeline. Tested with SPIRV-Cross (https://github.com/KhronosGroup/SPIRV-Cross) Fragment, Vertex, Geometry, Compute shaders --- renderdocui/Code/PersistantConfig.cs | 90 +++++++ .../Dialogs/SettingsDialog.Designer.cs | 246 ++++++++++++++---- renderdocui/Windows/Dialogs/SettingsDialog.cs | 48 ++++ .../Windows/Dialogs/SettingsDialog.resx | 159 +++++++---- .../VulkanPipelineStateViewer.Designer.cs | 15 +- .../VulkanPipelineStateViewer.cs | 156 ++++++++++- .../VulkanPipelineStateViewer.resx | 246 +++++++++--------- 7 files changed, 725 insertions(+), 235 deletions(-) diff --git a/renderdocui/Code/PersistantConfig.cs b/renderdocui/Code/PersistantConfig.cs index 7324ebb9b..21cd8b3b2 100644 --- a/renderdocui/Code/PersistantConfig.cs +++ b/renderdocui/Code/PersistantConfig.cs @@ -120,6 +120,52 @@ namespace renderdocui.Code } } + [Serializable] + public class ExternalDisassembler + { + //indicates to the system that this placeholder points to the SPIR-V binary + [NonSerialized] + public static readonly string SPV_BIN_TAG = "{spv_bin}"; + //indicates to the system that this placeholder points to the disassembled SPIR-V + [NonSerialized] + public static readonly string SPV_DISAS_TAG = "{spv_disas}"; + //The TAGs below are for future usage to help identify in the argument list, + //the shader entry point and the shader stage (for flexibility) + [NonSerialized] + public static readonly string SPV_ENTRY_POINT_TAG = "{spv_entry_point}"; + [NonSerialized] + public static readonly string SPV_SHADER_STAGE_TAG = "{spv_shader_stage}"; + + public ExternalDisassembler(uint id, string name, + string executable, string args) + { + this.id = id; + this.name = name; + this.executable = executable; + this.args = args; + } + + protected ExternalDisassembler() + { + + } + + public uint id { get; set; } + public string name { get; set; } + public string executable { get; set; } + public string args { get; set; } + + public override bool Equals(object obj) + { + return obj.GetType() == typeof(ExternalDisassembler) && id == ((ExternalDisassembler)obj).id; + } + + public override int GetHashCode() + { + return GetType().GetHashCode() ^ id.GetHashCode(); + } + } + [Serializable] public class PersistantConfig { @@ -135,6 +181,36 @@ namespace renderdocui.Code public string TemporaryCaptureDirectory = ""; public string DefaultCaptureSaveDirectory = ""; + //The list should contain all the pre-configured, external, SPIR-V disassemblers + [XmlIgnore] // not directly serializable + private Dictionary ExternalDisassemblers = new Dictionary(); + public List> ExternalDisassemblersValues = new List>(); + public bool ExternalDisassemblerEnabled = false; + + public void SetExternalDisassemblers(int id, ExternalDisassembler value) + { + ExternalDisassemblers[id] = value; + } + + public ExternalDisassembler GetExternalDisassembler(int id) + { + if (ExternalDisassemblers.ContainsKey(id)) + return ExternalDisassemblers[id]; + + return null; + } + + //the default disassembler is at 0 + public ExternalDisassembler GetDefaultExternalDisassembler() + { + if(!ExternalDisassemblers.ContainsKey(0)) + { + //Add default external disassembler at key 0 + ExternalDisassemblers.Add(0, new ExternalDisassembler(0, "SPIRV-Cross", "", "")); + } + return ExternalDisassemblers[0]; + } + public bool TextureViewer_ResetRange = false; public bool TextureViewer_PerTexSettings = true; public bool ShaderViewer_FriendlyNaming = true; @@ -261,6 +337,11 @@ namespace renderdocui.Code foreach (var kv in ConfigSettings) ConfigSettingsValues.Add(new SerializableKeyValuePair(kv.Key, kv.Value)); + //external disassemblers + ExternalDisassemblersValues.Clear(); + foreach (var kv in ExternalDisassemblers) + ExternalDisassemblersValues.Add(new SerializableKeyValuePair(kv.Key, kv.Value)); + XmlSerializer xs = new XmlSerializer(this.GetType()); StreamWriter writer = File.CreateText(file); xs.Serialize(writer, this); @@ -290,6 +371,15 @@ namespace renderdocui.Code } } + //external disassemblers + foreach (var kv in c.ExternalDisassemblersValues) + { + if (kv.Key >= 0 && kv.Value != null) + { + c.SetExternalDisassemblers(kv.Key, kv.Value); + } + } + // localhost should always be available bool foundLocalhost = false; diff --git a/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs b/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs index 0e8ff9388..933a579cd 100644 --- a/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs +++ b/renderdocui/Windows/Dialogs/SettingsDialog.Designer.cs @@ -42,9 +42,9 @@ System.Windows.Forms.Label label5; System.Windows.Forms.Label label7; System.Windows.Forms.Label label3; - System.Windows.Forms.Label label11; System.Windows.Forms.Label label15; System.Windows.Forms.Label label18; + System.Windows.Forms.Label label11; System.Windows.Forms.TableLayoutPanel tableLayoutPanel6; System.Windows.Forms.Label label19; System.Windows.Forms.GroupBox groupBox2; @@ -57,9 +57,6 @@ System.Windows.Forms.Label label16; System.Windows.Forms.Label label17; TreelistView.TreeListColumn treeListColumn3 = ((TreelistView.TreeListColumn)(new TreelistView.TreeListColumn("Section", "Section"))); - this.ok = new System.Windows.Forms.Button(); - this.toolTip = new System.Windows.Forms.ToolTip(this.components); - this.browserCaptureDialog = new System.Windows.Forms.FolderBrowserDialog(); this.settingsTabs = new renderdocui.Controls.TablessControl(); this.generalTab = new System.Windows.Forms.TabPage(); this.AllowGlobalHook = new System.Windows.Forms.CheckBox(); @@ -75,6 +72,7 @@ this.AlwaysReplayLocally = new System.Windows.Forms.CheckBox(); this.browseSaveCaptureDirectory = new System.Windows.Forms.Button(); this.saveDirectory = new System.Windows.Forms.TextBox(); + this.tempDirectory = new System.Windows.Forms.TextBox(); this.corePage = new System.Windows.Forms.TabPage(); this.groupBox5 = new System.Windows.Forms.GroupBox(); this.chooseSearchPaths = new System.Windows.Forms.Button(); @@ -84,6 +82,16 @@ this.TextureViewer_PerTexSettings = new System.Windows.Forms.CheckBox(); this.label14 = new System.Windows.Forms.Label(); this.shadViewTab = new System.Windows.Forms.TabPage(); + this.groupBox6 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel7 = new System.Windows.Forms.TableLayoutPanel(); + this.label23 = new System.Windows.Forms.Label(); + this.externalDisassemblerEnabledCheckbox = new System.Windows.Forms.CheckBox(); + this.label21 = new System.Windows.Forms.Label(); + this.label22 = new System.Windows.Forms.Label(); + this.externalDisassemblePath = new System.Windows.Forms.TextBox(); + this.browseExtDisasemble = new System.Windows.Forms.Button(); + this.externalDisassemblerArgs = new System.Windows.Forms.TextBox(); + this.label24 = new System.Windows.Forms.Label(); this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel(); this.ShaderViewer_FriendlyNaming = new System.Windows.Forms.CheckBox(); this.eventTab = new System.Windows.Forms.TabPage(); @@ -93,7 +101,10 @@ this.EventBrowser_ApplyColours = new System.Windows.Forms.CheckBox(); this.EventBrowser_ColourEventRow = new System.Windows.Forms.CheckBox(); this.pagesTree = new TreelistView.TreeListView(); - this.tempDirectory = new System.Windows.Forms.TextBox(); + this.ok = new System.Windows.Forms.Button(); + this.toolTip = new System.Windows.Forms.ToolTip(this.components); + this.browserCaptureDialog = new System.Windows.Forms.FolderBrowserDialog(); + this.browseExtDisassembleDialog = new System.Windows.Forms.OpenFileDialog(); tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); groupBox1 = new System.Windows.Forms.GroupBox(); tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); @@ -106,9 +117,9 @@ label5 = new System.Windows.Forms.Label(); label7 = new System.Windows.Forms.Label(); label3 = new System.Windows.Forms.Label(); - label11 = new System.Windows.Forms.Label(); label15 = new System.Windows.Forms.Label(); label18 = new System.Windows.Forms.Label(); + label11 = new System.Windows.Forms.Label(); tableLayoutPanel6 = new System.Windows.Forms.TableLayoutPanel(); label19 = new System.Windows.Forms.Label(); groupBox2 = new System.Windows.Forms.GroupBox(); @@ -137,6 +148,8 @@ this.tableLayoutPanel3.SuspendLayout(); this.shadViewTab.SuspendLayout(); groupBox3.SuspendLayout(); + this.groupBox6.SuspendLayout(); + this.tableLayoutPanel7.SuspendLayout(); this.tableLayoutPanel4.SuspendLayout(); this.eventTab.SuspendLayout(); groupBox4.SuspendLayout(); @@ -161,21 +174,6 @@ tableLayoutPanel1.Size = new System.Drawing.Size(537, 451); tableLayoutPanel1.TabIndex = 1; // - // ok - // - this.ok.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ok.Location = new System.Drawing.Point(459, 425); - this.ok.Name = "ok"; - this.ok.Size = new System.Drawing.Size(75, 23); - this.ok.TabIndex = 100; - this.ok.Text = "OK"; - this.ok.UseVisualStyleBackColor = true; - this.ok.Click += new System.EventHandler(this.ok_Click); - // - // browserCaptureDialog - // - this.browserCaptureDialog.RootFolder = System.Environment.SpecialFolder.MyComputer; - // // settingsTabs // this.settingsTabs.Alignment = System.Windows.Forms.TabAlignment.Left; @@ -538,22 +536,6 @@ "ew versions."); this.CheckUpdate_AllowChecks.UseVisualStyleBackColor = true; this.CheckUpdate_AllowChecks.CheckedChanged += new System.EventHandler(this.CheckUpdate_AllowChecks_CheckedChanged); - // - // label11 - // - label11.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - label11.AutoSize = true; - label11.Location = new System.Drawing.Point(3, 162); - label11.MinimumSize = new System.Drawing.Size(0, 20); - label11.Name = "label11"; - label11.Size = new System.Drawing.Size(229, 20); - label11.TabIndex = 14; - label11.Text = "Directory for temporary capture files"; - label11.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.toolTip.SetToolTip(label11, "Changes the directory where capture files are saved after being created, until sa" + - "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); // // browseTempCaptureDirectory // @@ -647,6 +629,34 @@ "efaults to blank, which follows system default behaviour."); this.saveDirectory.TextChanged += new System.EventHandler(this.saveDirectory_TextChanged); // + // label11 + // + label11.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + label11.AutoSize = true; + label11.Location = new System.Drawing.Point(3, 162); + label11.MinimumSize = new System.Drawing.Size(0, 20); + label11.Name = "label11"; + label11.Size = new System.Drawing.Size(229, 20); + label11.TabIndex = 14; + label11.Text = "Directory for temporary capture files"; + label11.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + this.toolTip.SetToolTip(label11, "Changes the directory where capture files are saved after being created, until sa" + + "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); + // + // tempDirectory + // + this.tempDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tempDirectory.Location = new System.Drawing.Point(3, 185); + this.tempDirectory.Name = "tempDirectory"; + this.tempDirectory.Size = new System.Drawing.Size(229, 20); + this.tempDirectory.TabIndex = 24; + this.toolTip.SetToolTip(this.tempDirectory, "Changes the directory where capture files are saved after being created, until sa" + + "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); + this.tempDirectory.TextChanged += new System.EventHandler(this.tempDirectory_TextChanged); + // // corePage // this.corePage.Controls.Add(this.groupBox5); @@ -826,6 +836,7 @@ // // groupBox3 // + groupBox3.Controls.Add(this.groupBox6); groupBox3.Controls.Add(this.tableLayoutPanel4); groupBox3.Dock = System.Windows.Forms.DockStyle.Fill; groupBox3.Location = new System.Drawing.Point(3, 3); @@ -834,6 +845,123 @@ groupBox3.TabIndex = 0; groupBox3.TabStop = false; groupBox3.Text = "Shader Viewer"; + // + // groupBox6 + // + this.groupBox6.Controls.Add(this.tableLayoutPanel7); + this.groupBox6.Dock = System.Windows.Forms.DockStyle.Top; + this.groupBox6.Location = new System.Drawing.Point(3, 60); + this.groupBox6.Name = "groupBox6"; + this.groupBox6.Size = new System.Drawing.Size(331, 166); + this.groupBox6.TabIndex = 46; + this.groupBox6.TabStop = false; + this.groupBox6.Text = "Vulkan"; + // + // tableLayoutPanel7 + // + this.tableLayoutPanel7.ColumnCount = 2; + this.tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 80F)); + this.tableLayoutPanel7.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); + this.tableLayoutPanel7.Controls.Add(this.label23, 0, 3); + this.tableLayoutPanel7.Controls.Add(this.externalDisassemblerEnabledCheckbox, 1, 0); + this.tableLayoutPanel7.Controls.Add(this.label21, 0, 0); + this.tableLayoutPanel7.Controls.Add(this.label22, 0, 1); + this.tableLayoutPanel7.Controls.Add(this.externalDisassemblePath, 0, 2); + this.tableLayoutPanel7.Controls.Add(this.browseExtDisasemble, 1, 2); + this.tableLayoutPanel7.Controls.Add(this.externalDisassemblerArgs, 0, 4); + this.tableLayoutPanel7.Controls.Add(this.label24, 0, 5); + this.tableLayoutPanel7.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel7.Location = new System.Drawing.Point(3, 16); + this.tableLayoutPanel7.Name = "tableLayoutPanel7"; + this.tableLayoutPanel7.RowCount = 6; + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel7.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel7.Size = new System.Drawing.Size(325, 147); + this.tableLayoutPanel7.TabIndex = 0; + // + // label23 + // + this.label23.AutoSize = true; + this.label23.Dock = System.Windows.Forms.DockStyle.Fill; + this.label23.Location = new System.Drawing.Point(3, 62); + this.label23.Name = "label23"; + this.label23.Size = new System.Drawing.Size(254, 13); + this.label23.TabIndex = 50; + this.label23.Text = "Specify the command line arguments"; + // + // externalDisassemblerEnabledCheckbox + // + this.externalDisassemblerEnabledCheckbox.AutoSize = true; + this.externalDisassemblerEnabledCheckbox.Location = new System.Drawing.Point(263, 3); + this.externalDisassemblerEnabledCheckbox.Name = "externalDisassemblerEnabledCheckbox"; + this.externalDisassemblerEnabledCheckbox.Size = new System.Drawing.Size(15, 14); + this.externalDisassemblerEnabledCheckbox.TabIndex = 44; + this.externalDisassemblerEnabledCheckbox.UseVisualStyleBackColor = true; + this.externalDisassemblerEnabledCheckbox.CheckedChanged += new System.EventHandler(this.externalDisassemblerEnabledCheckbox_CheckedChanged); + // + // label21 + // + this.label21.AutoSize = true; + this.label21.Location = new System.Drawing.Point(3, 0); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(146, 13); + this.label21.TabIndex = 45; + this.label21.Text = "Vulkan External Disassembler"; + // + // label22 + // + this.label22.AutoSize = true; + this.label22.Dock = System.Windows.Forms.DockStyle.Fill; + this.label22.Location = new System.Drawing.Point(3, 20); + this.label22.Name = "label22"; + this.label22.Size = new System.Drawing.Size(254, 13); + this.label22.TabIndex = 49; + this.label22.Text = "Select external SPIR-V disassembler executable"; + // + // externalDisassemblePath + // + this.externalDisassemblePath.Dock = System.Windows.Forms.DockStyle.Fill; + this.externalDisassemblePath.Location = new System.Drawing.Point(3, 36); + this.externalDisassemblePath.Name = "externalDisassemblePath"; + this.externalDisassemblePath.ReadOnly = false; + this.externalDisassemblePath.Size = new System.Drawing.Size(254, 20); + this.externalDisassemblePath.TabIndex = 47; + this.externalDisassemblePath.TextChanged += new System.EventHandler(this.externalDisassemblePath_TextChanged); + // + // browseExtDisasemble + // + this.browseExtDisasemble.Dock = System.Windows.Forms.DockStyle.Left; + this.browseExtDisasemble.Location = new System.Drawing.Point(263, 36); + this.browseExtDisasemble.Name = "browseExtDisasemble"; + this.browseExtDisasemble.Size = new System.Drawing.Size(57, 23); + this.browseExtDisasemble.TabIndex = 48; + this.browseExtDisasemble.Text = "Browse"; + this.browseExtDisasemble.UseVisualStyleBackColor = true; + this.browseExtDisasemble.Click += new System.EventHandler(this.browseExtDisasemble_Click); + // + // externalDisassemblerArgs + // + this.externalDisassemblerArgs.Dock = System.Windows.Forms.DockStyle.Fill; + this.externalDisassemblerArgs.Location = new System.Drawing.Point(3, 78); + this.externalDisassemblerArgs.Name = "externalDisassemblerArgs"; + this.externalDisassemblerArgs.Size = new System.Drawing.Size(254, 20); + this.externalDisassemblerArgs.TabIndex = 46; + this.externalDisassemblerArgs.TextChanged += new System.EventHandler(this.textBox1_TextChanged); + // + // label24 + // + this.label24.AutoSize = true; + this.label24.Dock = System.Windows.Forms.DockStyle.Fill; + this.label24.Location = new System.Drawing.Point(3, 101); + this.label24.Name = "label24"; + this.label24.Size = new System.Drawing.Size(254, 46); + this.label24.TabIndex = 51; + this.label24.Text = "NOTE: Use the {spv_bin} and {spv_disas} tags to indicate to the external disassembler the input SP" + + "IR-V binary and the output SPIR-V disassembly respectively."; // // tableLayoutPanel4 // @@ -842,13 +970,14 @@ this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 20F)); this.tableLayoutPanel4.Controls.Add(this.ShaderViewer_FriendlyNaming, 1, 0); this.tableLayoutPanel4.Controls.Add(label12, 0, 0); - this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Top; this.tableLayoutPanel4.Location = new System.Drawing.Point(3, 16); this.tableLayoutPanel4.Name = "tableLayoutPanel4"; this.tableLayoutPanel4.RowCount = 2; this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel4.Size = new System.Drawing.Size(331, 383); + this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel4.Size = new System.Drawing.Size(331, 44); this.tableLayoutPanel4.TabIndex = 1; // // ShaderViewer_FriendlyNaming @@ -1049,17 +1178,20 @@ this.pagesTree.ViewOptions.ShowPlusMinus = false; this.pagesTree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.pagesTree_AfterSelect); // - // tempDirectory + // ok // - this.tempDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.tempDirectory.Location = new System.Drawing.Point(3, 185); - this.tempDirectory.Name = "tempDirectory"; - this.tempDirectory.Size = new System.Drawing.Size(229, 20); - this.tempDirectory.TabIndex = 24; - this.toolTip.SetToolTip(this.tempDirectory, "Changes the directory where capture files are saved after being created, until sa" + - "ved manually or deleted.\r\n\r\nDefaults to %TEMP%."); - this.tempDirectory.TextChanged += new System.EventHandler(this.tempDirectory_TextChanged); + this.ok.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.ok.Location = new System.Drawing.Point(459, 425); + this.ok.Name = "ok"; + this.ok.Size = new System.Drawing.Size(75, 23); + this.ok.TabIndex = 100; + this.ok.Text = "OK"; + this.ok.UseVisualStyleBackColor = true; + this.ok.Click += new System.EventHandler(this.ok_Click); + // + // browserCaptureDialog + // + this.browserCaptureDialog.RootFolder = System.Environment.SpecialFolder.MyComputer; // // SettingsDialog // @@ -1091,6 +1223,9 @@ this.tableLayoutPanel3.PerformLayout(); this.shadViewTab.ResumeLayout(false); groupBox3.ResumeLayout(false); + this.groupBox6.ResumeLayout(false); + this.tableLayoutPanel7.ResumeLayout(false); + this.tableLayoutPanel7.PerformLayout(); this.tableLayoutPanel4.ResumeLayout(false); this.tableLayoutPanel4.PerformLayout(); this.eventTab.ResumeLayout(false); @@ -1141,5 +1276,16 @@ private System.Windows.Forms.Button browseSaveCaptureDirectory; private System.Windows.Forms.TextBox saveDirectory; private System.Windows.Forms.TextBox tempDirectory; + private System.Windows.Forms.CheckBox externalDisassemblerEnabledCheckbox; + private System.Windows.Forms.Label label21; + private System.Windows.Forms.GroupBox groupBox6; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel7; + private System.Windows.Forms.TextBox externalDisassemblerArgs; + private System.Windows.Forms.TextBox externalDisassemblePath; + private System.Windows.Forms.Button browseExtDisasemble; + private System.Windows.Forms.OpenFileDialog browseExtDisassembleDialog; + private System.Windows.Forms.Label label23; + private System.Windows.Forms.Label label22; + private System.Windows.Forms.Label label24; } } \ No newline at end of file diff --git a/renderdocui/Windows/Dialogs/SettingsDialog.cs b/renderdocui/Windows/Dialogs/SettingsDialog.cs index 2105f3198..494d7b8b1 100644 --- a/renderdocui/Windows/Dialogs/SettingsDialog.cs +++ b/renderdocui/Windows/Dialogs/SettingsDialog.cs @@ -61,6 +61,10 @@ namespace renderdocui.Windows.Dialogs saveDirectory.Text = m_Core.Config.DefaultCaptureSaveDirectory; tempDirectory.Text = m_Core.Config.TemporaryCaptureDirectory; + externalDisassemblerEnabledCheckbox.Checked = m_Core.Config.ExternalDisassemblerEnabled; + externalDisassemblerArgs.Text = m_Core.Config.GetDefaultExternalDisassembler().args; + externalDisassemblePath.Text = m_Core.Config.GetDefaultExternalDisassembler().executable; + TextureViewer_ResetRange.Checked = m_Core.Config.TextureViewer_ResetRange; TextureViewer_PerTexSettings.Checked = m_Core.Config.TextureViewer_PerTexSettings; ShaderViewer_FriendlyNaming.Checked = m_Core.Config.ShaderViewer_FriendlyNaming; @@ -337,5 +341,49 @@ namespace renderdocui.Windows.Dialogs if (res == DialogResult.OK) m_Core.Config.SetConfigSetting("shader.debug.searchPaths", String.Join(";", editor.GetItems())); } + + private void browseExtDisasemble_Click(object sender, EventArgs e) + { + var res = browseExtDisassembleDialog.ShowDialog(); + + if (res == DialogResult.Yes || res == DialogResult.OK) + { + try + { + m_Core.Config.GetDefaultExternalDisassembler().executable = browseExtDisassembleDialog.FileName; + externalDisassemblePath.Text = browseExtDisassembleDialog.FileName; + } + catch (Exception) + { + } + } + } + + private void externalDisassemblePath_TextChanged(object sender, EventArgs e) + { + try + { + m_Core.Config.GetDefaultExternalDisassembler().executable = externalDisassemblePath.Text; + } + catch (Exception) + { + } + } + + private void textBox1_TextChanged(object sender, EventArgs e) + { + try + { + m_Core.Config.GetDefaultExternalDisassembler().args = externalDisassemblerArgs.Text; + } + catch (Exception) + { + } + } + + private void externalDisassemblerEnabledCheckbox_CheckedChanged(object sender, EventArgs e) + { + m_Core.Config.ExternalDisassemblerEnabled = externalDisassemblerEnabledCheckbox.Checked; + } } } diff --git a/renderdocui/Windows/Dialogs/SettingsDialog.resx b/renderdocui/Windows/Dialogs/SettingsDialog.resx index 3f198e3a7..6596e9b0c 100644 --- a/renderdocui/Windows/Dialogs/SettingsDialog.resx +++ b/renderdocui/Windows/Dialogs/SettingsDialog.resx @@ -123,12 +123,69 @@ False + + False + + + False + + + False + + + False + + + False + + + False + False False + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + 17, 17 + 17, 17 @@ -194,73 +251,61 @@ This option overrides that and will always replay locally if the local context i False + + False + + + False + + + False + + + False + False - + + False + + + False + + False False + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + 102, 17 - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False - - - False + + 276, 17 \ No newline at end of file diff --git a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.Designer.cs b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.Designer.cs index adec06b56..7101fe416 100644 --- a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.Designer.cs +++ b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.Designer.cs @@ -209,6 +209,7 @@ this.showDisabledToolitem = new System.Windows.Forms.ToolStripButton(); this.showEmptyToolitem = new System.Windows.Forms.ToolStripButton(); this.export = new System.Windows.Forms.ToolStripButton(); + this.extDisassemblerProgBar = new System.Windows.Forms.ProgressBar(); this.stageTabControl = new renderdocui.Controls.TablessControl(); this.tabIA = new System.Windows.Forms.TabPage(); this.panel1 = new System.Windows.Forms.Panel(); @@ -529,6 +530,7 @@ toolstripTable.Controls.Add(this.pipeFlow, 0, 1); toolstripTable.Controls.Add(this.flowLayoutPanel6, 0, 0); toolstripTable.Controls.Add(this.stageTabControl, 0, 2); + toolstripTable.Controls.Add(this.extDisassemblerProgBar, 0, 2); toolstripTable.Dock = System.Windows.Forms.DockStyle.Fill; toolstripTable.Location = new System.Drawing.Point(0, 0); toolstripTable.Name = "toolstripTable"; @@ -608,7 +610,17 @@ this.export.Name = "export"; this.export.Size = new System.Drawing.Size(59, 22); this.export.Text = "Export"; - this.export.Click += new System.EventHandler(this.export_Click); + this.export.Click += new System.EventHandler(this.export_Click); + // + // extDisassemblerProgBar + // + this.extDisassemblerProgBar.Dock = System.Windows.Forms.DockStyle.Bottom; + this.extDisassemblerProgBar.Location = new System.Drawing.Point(3, 574); + this.extDisassemblerProgBar.Name = "extDisassemblerProgBar"; + this.extDisassemblerProgBar.Size = new System.Drawing.Size(1106, 23); + this.extDisassemblerProgBar.TabIndex = 2; + this.extDisassemblerProgBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.extDisassemblerProgBar.Visible = false; // // stageTabControl // @@ -3985,5 +3997,6 @@ private System.Windows.Forms.PictureBox gsShaderSave; private System.Windows.Forms.PictureBox psShaderSave; private System.Windows.Forms.PictureBox csShaderSave; + private System.Windows.Forms.ProgressBar extDisassemblerProgBar; } } \ No newline at end of file diff --git a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs index ca4062cd7..402a9f35c 100644 --- a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs +++ b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.cs @@ -2231,10 +2231,158 @@ namespace renderdocui.Windows.PipelineState var files = new Dictionary(); - // use disassembly for now. It's not compilable GLSL but it's better than - // starting with a blank canvas - files.Add("Disassembly", shaderDetails.Disassembly); + if (m_Core.Config.ExternalDisassemblerEnabled) + { + BackgroundWorker bgWorker = new BackgroundWorker(); + extDisassemblerProgBar.Visible = true; + bgWorker.RunWorkerCompleted += (obj, eventArgs) => + { + if((bool)eventArgs.Result == true) + { + ShowShaderViewer(stage, files); + } + extDisassemblerProgBar.Visible = false; + }; + bgWorker.DoWork += (obj, eventArgs) => + { + eventArgs.Result = true; + //try to use the external disassembler to get back the shader source + string spv_bin_file = "spv_bin.spv"; + string spv_disas_file = "spv_disas.txt"; + + spv_bin_file = Path.Combine(Path.GetTempPath(), spv_bin_file); + spv_disas_file = Path.Combine(Path.GetTempPath(), spv_disas_file); + + //replace the {spv_bin} tag with the correct SPIR-V binary source + string args = m_Core.Config.GetDefaultExternalDisassembler().args; + if (args.Contains(ExternalDisassembler.SPV_BIN_TAG)) + { + args = args.Replace(ExternalDisassembler.SPV_BIN_TAG, spv_bin_file); + + //write to temp file + try + { + // Open file for reading + using (FileStream fileStream = + new FileStream(spv_bin_file, FileMode.Create, + FileAccess.Write)) + { + // Writes a block of bytes to this stream using data from + // a byte array. + fileStream.Write(shaderDetails.RawBytes, 0, shaderDetails.RawBytes.Length); + + // close file stream + fileStream.Close(); + } + } + catch (Exception ex) + { + // Error + MessageBox.Show("Couldn't save SPIR-V binary to file: " + spv_bin_file + + Environment.NewLine + ex.ToString(), "Cannot save", + MessageBoxButtons.OK, MessageBoxIcon.Error); + eventArgs.Result = false; + } + } + else + { + MessageBox.Show("Please indicate the " + ExternalDisassembler.SPV_BIN_TAG + + " in the arguments list!", "Error disassembling", + MessageBoxButtons.OK, MessageBoxIcon.Error); + eventArgs.Result = false; + } + bool useStdout = true; + if (args.Contains(ExternalDisassembler.SPV_DISAS_TAG)) + { + args = args.Replace(ExternalDisassembler.SPV_DISAS_TAG, spv_disas_file); + useStdout = false; + } + // else we assume the disassembler will return the result to stdout + + //check if any errors + if ((bool)eventArgs.Result == false) + return; + + //run the disassembler + try + { + using (System.Diagnostics.Process disassemblerProcess = new System.Diagnostics.Process()) + { + disassemblerProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden; + //settings up parameters for the install process + disassemblerProcess.StartInfo.FileName = m_Core.Config.GetDefaultExternalDisassembler().executable; + + disassemblerProcess.StartInfo.Arguments = args; + disassemblerProcess.StartInfo.RedirectStandardOutput = true; + disassemblerProcess.StartInfo.UseShellExecute = false; + disassemblerProcess.StartInfo.CreateNoWindow = true; + + disassemblerProcess.Start(); + + string shaderDisassembly = ""; + if(useStdout) + { + StringBuilder q = new StringBuilder(); + while (!disassemblerProcess.HasExited) + { + q.Append(disassemblerProcess.StandardOutput.ReadToEnd()); + } + shaderDisassembly = q.ToString(); + } else + { + //read from the output file after the process has finished + bool ok = disassemblerProcess.WaitForExit(5*1000); //wait for 5 sec max + if(ok) + { + shaderDisassembly = File.ReadAllText(spv_disas_file); + } + else + { + eventArgs.Result = false; + } + } + + files.Add("Disassembly", shaderDisassembly); + + // Check for sucessful completion + if (disassemblerProcess.ExitCode != 0) + { + MessageBox.Show("Error wile running external disassembler: " + m_Core.Config.GetDefaultExternalDisassembler().name + + Environment.NewLine + "Return code: " + disassemblerProcess.ExitCode, "Error disassembling", + MessageBoxButtons.OK, MessageBoxIcon.Error); + eventArgs.Result = false; + } + } + + //delete the temp files + if(File.Exists(spv_bin_file)) + File.Delete(spv_bin_file); + if(File.Exists(spv_disas_file)) + File.Delete(spv_disas_file); + } + catch (Exception ex) + { + // Error + MessageBox.Show("Error using external disassembler: " + m_Core.Config.GetDefaultExternalDisassembler().name + + Environment.NewLine + ex.ToString(), "Error disassembling", + MessageBoxButtons.OK, MessageBoxIcon.Error); + eventArgs.Result = false; + } + }; + bgWorker.RunWorkerAsync(); + } + else + { + // use disassembly for now. It's not compilable GLSL but it's better than + // starting with a blank canvas + files.Add("Disassembly", shaderDetails.Disassembly); + ShowShaderViewer(stage, files); + } + } + + private void ShowShaderViewer(VulkanPipelineState.ShaderStage stage, Dictionary files) + { VulkanPipelineStateViewer pipeviewer = this; ShaderViewer sv = new ShaderViewer(m_Core, false, "main", files, @@ -2253,7 +2401,7 @@ namespace renderdocui.Windows.PipelineState string errs = ""; ResourceId from = stage.Shader; - ResourceId to = r.BuildTargetShader("main", compileSource, shaderDetails.DebugInfo.compileFlags, stage.stage, out errs); + ResourceId to = r.BuildTargetShader("main", compileSource, stage.ShaderDetails.DebugInfo.compileFlags, stage.stage, out errs); viewer.BeginInvoke((MethodInvoker)delegate { viewer.ShowErrors(errs); }); if (to == ResourceId.Null) diff --git a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.resx b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.resx index cad4985ae..3e79ec986 100644 --- a/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.resx +++ b/renderdocui/Windows/PipelineState/VulkanPipelineStateViewer.resx @@ -153,6 +153,30 @@ False + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + False + False @@ -171,87 +195,6 @@ False - - False - - - False - - - False - - - False - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - - - False - - - False - - - False - - - False - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - False @@ -261,48 +204,6 @@ False - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - - - - iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m - dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHCSURBVDhPpZFZL0NBHMW94X4Xe6vW2BJSa6ql1OWW - Wkvjheptq9VSgliuR7GvkdhiDSLxqJYKn0F8CdVj5oqG9tri4TeZOfM//5k5EwHgX0iKf0FS/A7tUqlQ - PlvofF+HFXxH5WKJYNo3oG2Hg1LI4akmWShFxUKx0LHXgIl7NzyXVujXtMgaSLFJFoeimS8SjLt6jN25 - MHrnRP16FbI9qUhzyKKCRcYzLq/lmG36aKSo54oEeuURnxPDPod4cqZb4U/tTYqi+2/mUy6j+Yh94i9M - 4DYrOt/NJCyhdbsOw7e9GLq1g1utRIYr2Z9iTxTNYoO2k7r85sOax0EvjzFfH3RrKj8Jq1M1oxxt2WIx - eGOH55pH7YoG6X1yv8KWEDSLDZoOdNGG3WqYz9sx+TAAj9cK454epgMD+r0WuK8tYJfVSHPKXpKtn81i - AzqwG2qGnAzToUF859CNDa4rM1xeM3RL5TSsgJyPDzNTghMSFqOaVqJxswbuqx44LrtQvaACCSsgt8RJ - mimfFsqpXKZgPJtcWQPtfBlIWAFZT+yXZkqYQP6XyexXgIT1nGSOjQzdD0VSJGExid0xP5opkuLvQcQr - vqKpPDRN9lYAAAAASUVORK5CYII= - - False @@ -312,6 +213,105 @@ False + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29m + dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAG4SURBVDhPpZNZLwNhGIXdYf6LrVpVKraE1JpqKTU6 + pdbSuKHtdNVhCI1lXIql9khssQaRuFRLhd8g/oTq8XUSTVChcfFdnvOe93nPlwQg6T/vX+Lo4IQNdCs1 + Qt1ChfcjdUIGDcvVguXQhJ49BiqhmE0oQX2gSug7aMP0Iwf+2gHjhg6FI7nOPyXQLlUK5n0jJh988D94 + 0brZiCJegTyPNCVmYL5gSrtO6Y6vF9EsVgrRyBMhL8ZDHnFyAScPK9zZKbEVzOeMsvOEfmGvLGC26/s/ + TAgsoXvXgPF7N8buXWDWG6D05YRzXRJRLBr0nBnKOo+bn0eDLCZDQ9BvqMMEVr96XuXv2qExeucCf8ui + ZU2L/CFZWO7MiolFg44jfappvwnWy17MPI2ADzpgPjDCcmTCcNAO7tYOelWDPK/0LcfxWRxbgd7SUGQy + LMcmcc+xOyd8N1b4glboV+qisCIyNvPT5G89ILAo9ZwK7dvN4G5s8FwPoCmgBoEVkdkz4oq/9UA1W0KV + TxWRyFrolmpBYEWktvQfxXGLRO5LFQzLQWC9ZlvTk3/7aHGLRGBRksG0X8UJVfmnJO++oqk8e7s/KAAA + AABJRU5ErkJggg== + + 17, 17