A game framework written with osu! in mind.
at master 280 lines 10 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Linq; 6using osu.Framework.Graphics.Colour; 7using osu.Framework.Graphics.Containers; 8using osu.Framework.Graphics.Cursor; 9using osu.Framework.Graphics.OpenGL.Textures; 10using osu.Framework.Graphics.OpenGL.Vertices; 11using osu.Framework.Graphics.Shapes; 12using osu.Framework.Graphics.Sprites; 13using osu.Framework.Localisation; 14using osu.Framework.Utils; 15using osuTK; 16using osuTK.Graphics; 17 18namespace osu.Framework.Graphics.Visualisation 19{ 20 internal class TextureVisualiser : ToolWindow 21 { 22 private readonly FillFlowContainer<TexturePanel> atlasFlow; 23 private readonly FillFlowContainer<TexturePanel> textureFlow; 24 25 public TextureVisualiser() 26 : base("Textures", "(Ctrl+F3 to toggle)") 27 { 28 ScrollContent.Child = new FillFlowContainer 29 { 30 RelativeSizeAxes = Axes.X, 31 AutoSizeAxes = Axes.Y, 32 Children = new Drawable[] 33 { 34 new SpriteText 35 { 36 Text = "Atlases", 37 Padding = new MarginPadding(5), 38 Font = FrameworkFont.Condensed.With(weight: "Bold") 39 }, 40 atlasFlow = new FillFlowContainer<TexturePanel> 41 { 42 RelativeSizeAxes = Axes.X, 43 AutoSizeAxes = Axes.Y, 44 Spacing = new Vector2(22), 45 Padding = new MarginPadding(10), 46 }, 47 new SpriteText 48 { 49 Text = "Textures", 50 Padding = new MarginPadding(5), 51 Font = FrameworkFont.Condensed.With(weight: "Bold") 52 }, 53 textureFlow = new FillFlowContainer<TexturePanel> 54 { 55 RelativeSizeAxes = Axes.X, 56 AutoSizeAxes = Axes.Y, 57 Spacing = new Vector2(22), 58 Padding = new MarginPadding(10), 59 } 60 } 61 }; 62 } 63 64 protected override void PopIn() 65 { 66 base.PopIn(); 67 68 foreach (var tex in TextureGLSingle.GetAllTextures()) 69 addTexture(tex); 70 71 TextureGLSingle.TextureCreated += addTexture; 72 } 73 74 protected override void PopOut() 75 { 76 base.PopOut(); 77 78 atlasFlow.Clear(); 79 textureFlow.Clear(); 80 81 TextureGLSingle.TextureCreated -= addTexture; 82 } 83 84 private void addTexture(TextureGLSingle texture) => Schedule(() => 85 { 86 var target = texture is TextureGLAtlas ? atlasFlow : textureFlow; 87 88 if (target.Any(p => p.Texture == texture)) 89 return; 90 91 target.Add(new TexturePanel(texture)); 92 }); 93 94 private class TexturePanel : CompositeDrawable 95 { 96 private readonly WeakReference<TextureGLSingle> textureReference; 97 98 public TextureGLSingle Texture => textureReference.TryGetTarget(out var tex) ? tex : null; 99 100 private readonly SpriteText titleText; 101 private readonly SpriteText footerText; 102 103 private readonly UsageBackground usage; 104 105 public TexturePanel(TextureGLSingle texture) 106 { 107 textureReference = new WeakReference<TextureGLSingle>(texture); 108 109 Size = new Vector2(100, 132); 110 111 InternalChild = new FillFlowContainer 112 { 113 RelativeSizeAxes = Axes.X, 114 AutoSizeAxes = Axes.Y, 115 Direction = FillDirection.Vertical, 116 Children = new Drawable[] 117 { 118 titleText = new SpriteText 119 { 120 Anchor = Anchor.TopCentre, 121 Origin = Anchor.TopCentre, 122 Font = FrameworkFont.Regular.With(size: 16) 123 }, 124 new Container 125 { 126 Anchor = Anchor.TopCentre, 127 Origin = Anchor.TopCentre, 128 RelativeSizeAxes = Axes.X, 129 AutoSizeAxes = Axes.Y, 130 Children = new Drawable[] 131 { 132 usage = new UsageBackground(textureReference) 133 { 134 Size = new Vector2(100) 135 }, 136 }, 137 }, 138 footerText = new SpriteText 139 { 140 Anchor = Anchor.TopCentre, 141 Origin = Anchor.TopCentre, 142 Font = FrameworkFont.Regular.With(size: 16), 143 }, 144 } 145 }; 146 } 147 148 protected override void Update() 149 { 150 base.Update(); 151 152 try 153 { 154 var texture = Texture; 155 156 if (texture?.Available != true) 157 { 158 Expire(); 159 return; 160 } 161 162 titleText.Text = $"{texture.TextureId}. {texture.Width}x{texture.Height} "; 163 footerText.Text = Precision.AlmostBigger(usage.AverageUsagesPerFrame, 1) ? $"{usage.AverageUsagesPerFrame:N0} binds" : string.Empty; 164 } 165 catch { } 166 } 167 } 168 169 private class UsageBackground : Box, IHasTooltip 170 { 171 private readonly WeakReference<TextureGLSingle> textureReference; 172 173 private ulong lastBindCount; 174 175 public float AverageUsagesPerFrame { get; private set; } 176 177 public UsageBackground(WeakReference<TextureGLSingle> textureReference) 178 { 179 this.textureReference = textureReference; 180 } 181 182 protected override DrawNode CreateDrawNode() => new UsageBackgroundDrawNode(this); 183 184 private class UsageBackgroundDrawNode : SpriteDrawNode 185 { 186 protected new UsageBackground Source => (UsageBackground)base.Source; 187 188 private ColourInfo drawColour; 189 190 private WeakReference<TextureGLSingle> textureReference; 191 192 public UsageBackgroundDrawNode(Box source) 193 : base(source) 194 { 195 } 196 197 public override void ApplyState() 198 { 199 base.ApplyState(); 200 201 textureReference = Source.textureReference; 202 } 203 204 public override void Draw(Action<TexturedVertex2D> vertexAction) 205 { 206 if (!textureReference.TryGetTarget(out var texture)) 207 return; 208 209 if (!texture.Available) 210 return; 211 212 ulong delta = texture.BindCount - Source.lastBindCount; 213 214 Source.AverageUsagesPerFrame = Source.AverageUsagesPerFrame * 0.9f + delta * 0.1f; 215 216 drawColour = DrawColourInfo.Colour; 217 drawColour.ApplyChild( 218 Precision.AlmostBigger(Source.AverageUsagesPerFrame, 1) 219 ? Interpolation.ValueAt(Source.AverageUsagesPerFrame, Color4.DarkGray, Color4.Red, 0, 200) 220 : Color4.Transparent); 221 222 base.Draw(vertexAction); 223 224 // intentionally after draw to avoid counting our own bind. 225 Source.lastBindCount = texture.BindCount; 226 } 227 228 protected override void Blit(Action<TexturedVertex2D> vertexAction) 229 { 230 if (!textureReference.TryGetTarget(out var texture)) 231 return; 232 233 const float border_width = 4; 234 235 // border 236 DrawQuad(Texture, ScreenSpaceDrawQuad, drawColour, null, vertexAction); 237 238 var shrunkenQuad = ScreenSpaceDrawQuad.AABBFloat.Shrink(border_width); 239 240 // background 241 DrawQuad(Texture, shrunkenQuad, Color4.Black, null, vertexAction); 242 243 float aspect = (float)texture.Width / texture.Height; 244 245 if (aspect > 1) 246 { 247 float newHeight = shrunkenQuad.Height / aspect; 248 249 shrunkenQuad.Y += (shrunkenQuad.Height - newHeight) / 2; 250 shrunkenQuad.Height = newHeight; 251 } 252 else if (aspect < 1) 253 { 254 float newWidth = shrunkenQuad.Width / (1 / aspect); 255 256 shrunkenQuad.X += (shrunkenQuad.Width - newWidth) / 2; 257 shrunkenQuad.Width = newWidth; 258 } 259 260 // texture 261 texture.Bind(); 262 DrawQuad(texture, shrunkenQuad, Color4.White, null, vertexAction); 263 } 264 265 protected internal override bool CanDrawOpaqueInterior => false; 266 } 267 268 public LocalisableString TooltipText 269 { 270 get 271 { 272 if (!textureReference.TryGetTarget(out var texture)) 273 return string.Empty; 274 275 return $"type: {texture.GetType().Name}, size: {(float)texture.GetByteSize() / 1024 / 1024:N2}mb"; 276 } 277 } 278 } 279 } 280}