Skip to content

Commit 1e4c420

Browse files
committed
added my avatar section to settings, snap turn gated aswell as smoothing added for rotation
1 parent 3cdd689 commit 1e4c420

12 files changed

Lines changed: 564 additions & 2 deletions
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
using UnityEngine.Rendering;
4+
5+
/// <summary>
6+
/// Scans an avatar's renderers at runtime and collects texture statistics:
7+
/// VRAM estimates, streaming mipmap status, and performance cost of non-streaming textures.
8+
/// </summary>
9+
public struct BasisAvatarTextureStats
10+
{
11+
public int TotalTextureCount;
12+
public int StreamingTextureCount;
13+
public int NonStreamingTextureCount;
14+
15+
/// <summary>Estimated total VRAM in bytes consumed by all unique textures.</summary>
16+
public long TotalVRAMBytes;
17+
18+
/// <summary>Estimated VRAM in bytes consumed by textures that lack streaming mipmaps.</summary>
19+
public long NonStreamingVRAMBytes;
20+
21+
/// <summary>Estimated VRAM that could be saved if all textures had streaming mipmaps enabled (roughly upper mips that would not be resident).</summary>
22+
public long EstimatedSavingsBytes;
23+
24+
/// <summary>Bundle download size in bytes (from metadata). 0 if unknown.</summary>
25+
public long DownloadSizeBytes;
26+
27+
/// <summary>Per-texture breakdown for detailed display.</summary>
28+
public List<TextureInfo> Textures;
29+
30+
public struct TextureInfo
31+
{
32+
public string Name;
33+
public int Width;
34+
public int Height;
35+
public TextureFormat Format;
36+
public int MipCount;
37+
public bool IsStreamingMipmaps;
38+
public long EstimatedVRAMBytes;
39+
}
40+
41+
/// <summary>
42+
/// Percentage of textures using streaming mipmaps (0-100).
43+
/// </summary>
44+
public float StreamingPercentage => TotalTextureCount > 0
45+
? (StreamingTextureCount / (float)TotalTextureCount) * 100f
46+
: 0f;
47+
48+
/// <summary>
49+
/// Collects texture statistics from an avatar's renderer array.
50+
/// </summary>
51+
public static BasisAvatarTextureStats Collect(Renderer[] renderers, long downloadSizeBytes = 0)
52+
{
53+
var stats = new BasisAvatarTextureStats
54+
{
55+
Textures = new List<TextureInfo>(),
56+
DownloadSizeBytes = downloadSizeBytes,
57+
};
58+
59+
if (renderers == null || renderers.Length == 0)
60+
return stats;
61+
62+
// Track unique textures by instance ID to avoid double-counting shared textures.
63+
var seen = new HashSet<int>();
64+
65+
for (int r = 0; r < renderers.Length; r++)
66+
{
67+
Renderer renderer = renderers[r];
68+
if (renderer == null) continue;
69+
70+
Material[] materials = renderer.sharedMaterials;
71+
if (materials == null) continue;
72+
73+
for (int m = 0; m < materials.Length; m++)
74+
{
75+
Material mat = materials[m];
76+
if (mat == null) continue;
77+
78+
Shader shader = mat.shader;
79+
if (shader == null) continue;
80+
81+
int propCount = shader.GetPropertyCount();
82+
for (int p = 0; p < propCount; p++)
83+
{
84+
if (shader.GetPropertyType(p) != ShaderPropertyType.Texture)
85+
continue;
86+
87+
string propName = shader.GetPropertyName(p);
88+
Texture tex = mat.GetTexture(propName);
89+
90+
if (tex == null) continue;
91+
if (!seen.Add(tex.GetInstanceID())) continue;
92+
93+
Texture2D tex2D = tex as Texture2D;
94+
bool isStreaming = tex2D != null && tex2D.streamingMipmaps;
95+
int mipCount = tex2D != null ? tex2D.mipmapCount : 1;
96+
TextureFormat format = tex2D != null ? tex2D.format : TextureFormat.RGBA32;
97+
98+
long vramEstimate = EstimateVRAM(tex.width, tex.height, mipCount, format);
99+
100+
stats.Textures.Add(new TextureInfo
101+
{
102+
Name = tex.name,
103+
Width = tex.width,
104+
Height = tex.height,
105+
Format = format,
106+
MipCount = mipCount,
107+
IsStreamingMipmaps = isStreaming,
108+
EstimatedVRAMBytes = vramEstimate,
109+
});
110+
111+
stats.TotalTextureCount++;
112+
stats.TotalVRAMBytes += vramEstimate;
113+
114+
if (isStreaming)
115+
{
116+
stats.StreamingTextureCount++;
117+
}
118+
else
119+
{
120+
stats.NonStreamingTextureCount++;
121+
stats.NonStreamingVRAMBytes += vramEstimate;
122+
123+
// Streaming would keep only the top ~2 mip levels resident most of the time.
124+
// Estimate savings as the difference between full mip chain and top 2 mips.
125+
if (mipCount > 2)
126+
{
127+
long top2Estimate = EstimateVRAM(tex.width, tex.height, 2, format);
128+
stats.EstimatedSavingsBytes += (vramEstimate - top2Estimate);
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
return stats;
136+
}
137+
138+
/// <summary>
139+
/// Estimates VRAM usage in bytes for a texture with the given parameters.
140+
/// Accounts for mip chain and block-compressed formats.
141+
/// </summary>
142+
public static long EstimateVRAM(int width, int height, int mipCount, TextureFormat format)
143+
{
144+
int bitsPerPixel = GetBitsPerPixel(format);
145+
int blockSize = IsBlockCompressed(format) ? 4 : 1;
146+
147+
long totalBytes = 0;
148+
int mipWidth = width;
149+
int mipHeight = height;
150+
151+
for (int mip = 0; mip < mipCount; mip++)
152+
{
153+
int w = Mathf.Max(mipWidth, blockSize);
154+
int h = Mathf.Max(mipHeight, blockSize);
155+
156+
if (blockSize > 1)
157+
{
158+
// Block-compressed: round up to block boundaries
159+
int blocksX = (w + blockSize - 1) / blockSize;
160+
int blocksY = (h + blockSize - 1) / blockSize;
161+
totalBytes += (long)blocksX * blocksY * (bitsPerPixel * blockSize * blockSize / 8);
162+
}
163+
else
164+
{
165+
totalBytes += (long)w * h * bitsPerPixel / 8;
166+
}
167+
168+
mipWidth = Mathf.Max(1, mipWidth / 2);
169+
mipHeight = Mathf.Max(1, mipHeight / 2);
170+
}
171+
172+
return totalBytes;
173+
}
174+
175+
static bool IsBlockCompressed(TextureFormat format)
176+
{
177+
switch (format)
178+
{
179+
case TextureFormat.DXT1:
180+
case TextureFormat.DXT1Crunched:
181+
case TextureFormat.DXT5:
182+
case TextureFormat.DXT5Crunched:
183+
case TextureFormat.BC4:
184+
case TextureFormat.BC5:
185+
case TextureFormat.BC6H:
186+
case TextureFormat.BC7:
187+
case TextureFormat.ETC_RGB4:
188+
case TextureFormat.ETC2_RGB:
189+
case TextureFormat.ETC2_RGBA8:
190+
case TextureFormat.ASTC_4x4:
191+
case TextureFormat.ASTC_5x5:
192+
case TextureFormat.ASTC_6x6:
193+
case TextureFormat.ASTC_8x8:
194+
case TextureFormat.ASTC_10x10:
195+
case TextureFormat.ASTC_12x12:
196+
return true;
197+
default:
198+
return false;
199+
}
200+
}
201+
202+
static int GetBitsPerPixel(TextureFormat format)
203+
{
204+
switch (format)
205+
{
206+
case TextureFormat.DXT1:
207+
case TextureFormat.DXT1Crunched:
208+
case TextureFormat.ETC_RGB4:
209+
case TextureFormat.ETC2_RGB:
210+
case TextureFormat.BC4:
211+
return 4;
212+
213+
case TextureFormat.DXT5:
214+
case TextureFormat.DXT5Crunched:
215+
case TextureFormat.ETC2_RGBA8:
216+
case TextureFormat.BC5:
217+
case TextureFormat.BC6H:
218+
case TextureFormat.BC7:
219+
case TextureFormat.ASTC_4x4:
220+
return 8;
221+
222+
case TextureFormat.ASTC_5x5:
223+
return 5; // ~5.12 bpp
224+
case TextureFormat.ASTC_6x6:
225+
return 4; // ~3.56 bpp
226+
case TextureFormat.ASTC_8x8:
227+
return 2;
228+
case TextureFormat.ASTC_10x10:
229+
return 1; // ~1.28 bpp
230+
case TextureFormat.ASTC_12x12:
231+
return 1; // ~0.89 bpp
232+
233+
case TextureFormat.Alpha8:
234+
case TextureFormat.R8:
235+
return 8;
236+
237+
case TextureFormat.R16:
238+
case TextureFormat.RG16:
239+
case TextureFormat.RGB565:
240+
case TextureFormat.RGBA4444:
241+
case TextureFormat.ARGB4444:
242+
return 16;
243+
244+
case TextureFormat.RGB24:
245+
return 24;
246+
247+
case TextureFormat.RGBA32:
248+
case TextureFormat.ARGB32:
249+
case TextureFormat.BGRA32:
250+
case TextureFormat.RFloat:
251+
case TextureFormat.RG32:
252+
return 32;
253+
254+
case TextureFormat.RGBAFloat:
255+
return 128;
256+
257+
case TextureFormat.RGBAHalf:
258+
case TextureFormat.RGB48:
259+
return 64;
260+
261+
default:
262+
return 32; // conservative fallback
263+
}
264+
}
265+
266+
/// <summary>
267+
/// Formats a byte count into a human-readable string (B, KB, MB, GB).
268+
/// </summary>
269+
public static string FormatBytes(long bytes)
270+
{
271+
if (bytes < 1024) return $"{bytes} B";
272+
if (bytes < 1024 * 1024) return $"{bytes / 1024f:F1} KB";
273+
if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024f * 1024f):F1} MB";
274+
return $"{bytes / (1024f * 1024f * 1024f):F2} GB";
275+
}
276+
277+
/// <summary>
278+
/// Returns a rating string based on the streaming mipmap percentage.
279+
/// </summary>
280+
public string GetStreamingRating()
281+
{
282+
if (TotalTextureCount == 0) return "No Textures";
283+
float pct = StreamingPercentage;
284+
if (pct >= 100f) return "Excellent - All textures streaming";
285+
if (pct >= 75f) return "Good - Most textures streaming";
286+
if (pct >= 25f) return "Poor - Many textures not streaming";
287+
return "Bad - Few or no textures streaming";
288+
}
289+
290+
/// <summary>
291+
/// Returns a description of the estimated performance impact from non-streaming textures.
292+
/// </summary>
293+
public string GetPerformanceImpact()
294+
{
295+
if (NonStreamingTextureCount == 0)
296+
return "No impact - all textures use streaming mipmaps.";
297+
298+
string wastedVRAM = FormatBytes(EstimatedSavingsBytes);
299+
string nonStreamVRAM = FormatBytes(NonStreamingVRAMBytes);
300+
301+
if (EstimatedSavingsBytes > 256L * 1024 * 1024)
302+
return $"Severe - {NonStreamingTextureCount} textures without streaming waste ~{wastedVRAM} VRAM. " +
303+
"This causes excessive GPU memory pressure, stalls, and frame drops especially in crowded instances.";
304+
305+
if (EstimatedSavingsBytes > 64L * 1024 * 1024)
306+
return $"High - {NonStreamingTextureCount} textures without streaming waste ~{wastedVRAM} VRAM. " +
307+
"Other players may experience hitches when your avatar loads.";
308+
309+
if (EstimatedSavingsBytes > 16L * 1024 * 1024)
310+
return $"Moderate - {NonStreamingTextureCount} textures without streaming waste ~{wastedVRAM} VRAM. " +
311+
"Noticeable on lower-end hardware.";
312+
313+
return $"Low - {NonStreamingTextureCount} textures without streaming use {nonStreamVRAM} total. Minimal impact.";
314+
}
315+
}

Basis/Packages/com.basis.framework/Avatar/BasisAvatarTextureStats.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ public static class BasisSettingsDefaults
104104

105105
public static BasisSettingsBinding<bool> usesnapturn = new("usesnapturn", new BasisPlatformDefault<bool>(false));
106106

107+
public static BasisSettingsBinding<float> SmoothTurnSpeed = new("smoothturnspeed", new BasisPlatformDefault<float>(200f));
108+
107109
public static BasisSettingsBinding<string> QualityLevel = new("qualitylevel", new BasisPlatformDefault<string>
108110
{
109111
windows = "Ultra",
@@ -272,6 +274,9 @@ public static class BasisSettingsDefaults
272274
public const string SwapMode_Shutdown = "Shutdown Runtime";
273275
public const string SwapMode_AutoSwap = "Auto Swap";
274276

277+
// ---------------- INTERACTIONS ----------------
278+
public static BasisSettingsBinding<bool> DisableSeats = new("disableseats", new BasisPlatformDefault<bool>(false));
279+
275280
// ---------------- NOTIFICATIONS ----------------
276281
public static BasisSettingsBinding<bool> JoinNotifications = new("joinnotifications", new BasisPlatformDefault<bool>(false));
277282
public static BasisSettingsBinding<bool> LeaveNotifications = new("leavenotifications", new BasisPlatformDefault<bool>(false));
@@ -693,6 +698,7 @@ public static void LoadAll()
693698
InvertMouse.LoadBindingValue();
694699
DominantHand.LoadBindingValue();
695700
usesnapturn.LoadBindingValue();
701+
SmoothTurnSpeed.LoadBindingValue();
696702

697703
// Avatar / IK / Body
698704
SelectedHeight.LoadBindingValue();

Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public override void RunAction()
101101
AddLazyTab(tabGroup, "Chat", () => ChatTab(tabGroup));
102102
AddLazyTab(tabGroup, "Body Tracking", () => SettingsProviderIK.IKTab(tabGroup));
103103
AddLazyTab(tabGroup, "Nameplates", () => SettingsProviderNamePlate.NamePlateTab(tabGroup));
104+
AddLazyTab(tabGroup, "My Avatar", () => SettingsProviderAvatarStats.AvatarStatsTab(tabGroup));
104105
AddLazyTab(tabGroup, "Downloads & Cache", () => SettingsProviderStorage.StorageTab(tabGroup));
105106
AddLazyTab(tabGroup, "Trusted URLs", () => SettingsProviderTrustedUrls.TrustedUrlsTab(tabGroup));
106107
// AddLazyTab(tabGroup, "UI Style", () => SettingsProviderUIStyle.UIStyleTab(tabGroup));
@@ -275,6 +276,15 @@ public static PanelTabPage GeneralTab(PanelTabGroup tabGroup)
275276
// toggleAvatarPreview.Descriptor.SetTitle("Avatar Preview");
276277
// toggleAvatarPreview.Descriptor.SetDescription("Show a live preview of your avatar on the HUD.");
277278

279+
PanelElementDescriptor interactionsGroup =
280+
PanelElementDescriptor.CreateNew(PanelElementDescriptor.ElementStyles.Group, container);
281+
interactionsGroup.SetTitle("Interactions");
282+
283+
PanelToggle toggleDisableSeats = PanelToggle.CreateNewEntry(interactionsGroup);
284+
toggleDisableSeats.AssignBinding(BasisSettingsDefaults.DisableSeats);
285+
toggleDisableSeats.Descriptor.SetTitle("Disable Seats");
286+
toggleDisableSeats.Descriptor.SetDescription("Prevent sitting in seats placed in the world.");
287+
278288
SettingsProviderPlatform.BuildAutoSwapUI(container);
279289

280290
// One reset button for this whole page
@@ -293,6 +303,7 @@ private static void ResetGeneralDefaults()
293303
BasisSettingsDefaults.ViewConeAngle.ResetToDefault();
294304
BasisSettingsDefaults.HearingRange.ResetToDefault();
295305
BasisSettingsDefaults.AvatarPreview.ResetToDefault();
306+
BasisSettingsDefaults.DisableSeats.ResetToDefault();
296307
BasisSettingsDefaults.SwapMode.ResetToDefault();
297308
#if !BASIS_DISABLE_MICROPHONE
298309
BasisSettingsDefaults.MicrophoneRange.ResetToDefault();

0 commit comments

Comments
 (0)