private void UI_OnTextureSelectionChanged(bool newdraw) { 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].displayType = channels.SelectedIndex; m_TextureSettings[m_TexDisplay.texid].customShader = customShader.Text; 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. if(!CurrentTextureIsLocked) m_TexDisplay.typeHint = m_Following.GetTypeHint(m_Core); else m_TexDisplay.typeHint = FormatComponentType.None; // 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; // 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(); 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 = -1; // only switch to the selected mip for outputs, and when changing drawcall if (!CurrentTextureIsLocked && m_Following.Type != FollowType.ReadOnly && newdraw) 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); } if (mipLevel.SelectedIndex == -1) mipLevel.SelectedIndex = Helpers.Clamp(prevHighestMip, 0, (int)tex.mips - 1); prevHighestMip = highestMip; } if (tex.mips == 1 && tex.msSamp <= 1) { mipLevel.Enabled = false; } else { mipLevel.Enabled = true; } sliceFace.Items.Clear(); 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 3D textures, display the number of slices at this mip if(tex.depth > 1) numSlices = Math.Max(1, tex.depth >> (int)mipLevel.SelectedIndex); 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 = -1; // only switch to the selected mip for outputs, and when changing drawcall if (!CurrentTextureIsLocked && m_Following.Type != FollowType.ReadOnly && newdraw) 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); } if (sliceFace.SelectedIndex == -1) sliceFace.SelectedIndex = Helpers.Clamp(prevFirstArraySlice, 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)) { channels.SelectedIndex = m_TextureSettings[tex.ID].displayType; customShader.Text = m_TextureSettings[tex.ID].customShader; 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 channels.SelectedIndex = 0; customShader.Text = ""; 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(); 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) { if (m_Output != null) RT_PickPixelsAndUpdate(m_PickedPoint.X, m_PickedPoint.Y, true); 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); })); } } else { m_CurPixelValue = null; m_CurRealValue = null; } }); }