Update to 0.3.1
This commit is contained in:
parent
93d16fb3da
commit
d0ec488a30
@ -7,12 +7,31 @@ namespace Byway.Quality
|
||||
|
||||
public class BywaySystemMemoryRuleMatcher : SystemMemoryRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayMaliGenerationRuleMatcher : MaliGenerationRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayEmulatorDetectionRuleMatcher : EmulatorDetectionRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayGraphicsApiRuleMatcher : GraphicsApiRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywaySocNameRuleMatcher : SocNameRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayAndroidApiLevelRuleMatcher : AndroidApiLevelRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayScreenResolutionRuleMatcher : ScreenResolutionRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayCompositeAndRuleMatcher : CompositeAndRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayCompositeOrRuleMatcher : CompositeOrRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public class BywayDeviceNameContainsRuleMatcher : DeviceNameContainsRuleMatcherBase<BywayQualityLevel> { }
|
||||
|
||||
public enum BywayQualityLevel
|
||||
{
|
||||
Lowest,
|
||||
VeryLow,
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Highest
|
||||
VeryHigh,
|
||||
Ultra
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
namespace Byway.Quality
|
||||
{
|
||||
/// <summary>
|
||||
/// Graphics API category, used by <c>GraphicsApiRuleMatcherBase</c> to gate quality on the actual
|
||||
/// rendering backend selected by Unity at runtime (Vulkan vs OpenGLES3 vs OpenGLES2).
|
||||
/// </summary>
|
||||
public enum GraphicsApi
|
||||
{
|
||||
/// <summary>Match any graphics API. Effectively skips the API check.</summary>
|
||||
Any = 0,
|
||||
|
||||
Vulkan = 1,
|
||||
OpenGLES3 = 2,
|
||||
OpenGLES2 = 3
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b0e8c2f4d3a5758d9c0f1a2e3b4c5d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -8,6 +8,8 @@ namespace Byway.Quality
|
||||
{
|
||||
// Build.VERSION_CODES.S (Android 12)
|
||||
private const int ANDROID_VERSION_CODES_S = 31;
|
||||
// Build.VERSION_CODES.Q (Android 10) — getCurrentThermalStatus available
|
||||
private const int ANDROID_VERSION_CODES_Q = 29;
|
||||
|
||||
private static int _sdkVersion;
|
||||
|
||||
@ -30,6 +32,17 @@ namespace Byway.Quality
|
||||
_ => (GpuMinorSeries.Unknown, 0)
|
||||
};
|
||||
stats.SocName = GetSocName();
|
||||
|
||||
stats.AndroidSdkInt = GetAndroidSdkVersion();
|
||||
stats.BuildManufacturer = GetBuildString("MANUFACTURER");
|
||||
stats.BuildBrand = GetBuildString("BRAND");
|
||||
stats.BuildModel = GetBuildString("MODEL");
|
||||
stats.BuildHardware = GetBuildString("HARDWARE");
|
||||
stats.BuildProduct = GetBuildString("PRODUCT");
|
||||
stats.BuildSocManufacturer = GetBuildSocManufacturer();
|
||||
stats.MaliGeneration = DeriveMaliGeneration(stats.GpuMajorSeries, stats.GpuMinorSeries, stats.GpuSeriesNumber, gpuName);
|
||||
stats.IsEmulator = DetectEmulator(stats);
|
||||
stats.ThermalStatus = GetCurrentThermalStatus();
|
||||
}
|
||||
|
||||
public static GpuMajorSeries ParseGpuMajorSeries(string gpuName)
|
||||
@ -175,7 +188,159 @@ namespace Byway.Quality
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetAndroidSdkVersion()
|
||||
// ---------------------------------------------------------------------
|
||||
// v0.3.0 additions
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
public static string GetBuildSocManufacturer()
|
||||
{
|
||||
if (GetAndroidSdkVersion() < ANDROID_VERSION_CODES_S)
|
||||
return "";
|
||||
|
||||
try
|
||||
{
|
||||
using var buildClass = new AndroidJavaClass("android.os.Build");
|
||||
var socManufacturer = buildClass.GetStatic<string>("SOC_MANUFACTURER");
|
||||
return socManufacturer ?? "";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetBuildString(string fieldName)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
return "";
|
||||
#endif
|
||||
#if UNITY_ANDROID
|
||||
try
|
||||
{
|
||||
using var buildClass = new AndroidJavaClass("android.os.Build");
|
||||
var value = buildClass.GetStatic<string>(fieldName);
|
||||
return value ?? "";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
return "";
|
||||
}
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MaliGeneration DeriveMaliGeneration(GpuMajorSeries major, GpuMinorSeries minor, int seriesNumber, string gpuName)
|
||||
{
|
||||
if (major == GpuMajorSeries.Immortalis)
|
||||
return MaliGeneration.Immortalis;
|
||||
|
||||
if (major != GpuMajorSeries.Mali)
|
||||
return MaliGeneration.Unknown;
|
||||
|
||||
// Utgard: "Mali-400/450/470" — minor parsed as Mali (no letter)
|
||||
if (minor == GpuMinorSeries.Mali)
|
||||
return MaliGeneration.Utgard;
|
||||
|
||||
// Midgard: Mali-T6xx/T7xx/T8xx
|
||||
if (minor == GpuMinorSeries.MaliT)
|
||||
return MaliGeneration.Midgard;
|
||||
|
||||
if (minor != GpuMinorSeries.MaliG)
|
||||
return MaliGeneration.Unknown;
|
||||
|
||||
// Mali-G family disambiguation by series number band:
|
||||
// G31/G52/G57/G68 → BifrostLegacy (low-end Bifrost+early Valhall, all <70)
|
||||
// G71/G72/G76/G77/G78 → Valhall1 (legacy Valhall, 70-99)
|
||||
// G310/G510/G610/G615/G710/G715/G720/G725 → Valhall2 (modern, >=100)
|
||||
if (seriesNumber >= 100)
|
||||
return MaliGeneration.Valhall2;
|
||||
if (seriesNumber >= 70)
|
||||
return MaliGeneration.Valhall1;
|
||||
if (seriesNumber > 0)
|
||||
return MaliGeneration.BifrostLegacy;
|
||||
|
||||
return MaliGeneration.Unknown;
|
||||
}
|
||||
|
||||
public static bool DetectEmulator(HardwareStats stats)
|
||||
{
|
||||
// x86/x86_64 CPU on Android is the strongest single signal — no shipping
|
||||
// Android phone uses x86 in 2024+. Check first.
|
||||
var cpu = stats.ProcessorType ?? "";
|
||||
if (cpu.IndexOf("x86", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return true;
|
||||
|
||||
var hardware = (stats.BuildHardware ?? "").ToLowerInvariant();
|
||||
if (hardware == "goldfish" || hardware == "ranchu" ||
|
||||
hardware.Contains("vbox") || hardware.Contains("ttvm") ||
|
||||
hardware.Contains("nox") || hardware == "intel")
|
||||
return true;
|
||||
|
||||
var manufacturer = stats.BuildManufacturer ?? "";
|
||||
if (manufacturer.Equals("unknown", StringComparison.OrdinalIgnoreCase) ||
|
||||
manufacturer.Equals("Genymotion", StringComparison.OrdinalIgnoreCase) ||
|
||||
manufacturer.Equals("BlueStacks", StringComparison.OrdinalIgnoreCase) ||
|
||||
manufacturer.Equals("ldplayer", StringComparison.OrdinalIgnoreCase) ||
|
||||
manufacturer.Equals("Netease", StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
var product = (stats.BuildProduct ?? "").ToLowerInvariant();
|
||||
if (product.StartsWith("sdk_", StringComparison.Ordinal) ||
|
||||
product.Contains("vbox") || product.Contains("ldplayer") ||
|
||||
product == "nox" || product.Contains("ttvm"))
|
||||
return true;
|
||||
|
||||
var model = stats.BuildModel ?? "";
|
||||
if (model.IndexOf("sdk", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
model.IndexOf("Emulator", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
model.IndexOf("Android SDK built for", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
model.IndexOf("BlueStacks", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
model.IndexOf("MuMu", StringComparison.Ordinal) >= 0)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int GetCurrentThermalStatus()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
return -1;
|
||||
#endif
|
||||
#if UNITY_ANDROID
|
||||
if (GetAndroidSdkVersion() < ANDROID_VERSION_CODES_Q)
|
||||
return -1;
|
||||
|
||||
try
|
||||
{
|
||||
using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
|
||||
using var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
if (activity == null)
|
||||
return -1;
|
||||
|
||||
using var pmObj = activity.Call<AndroidJavaObject>("getSystemService", "power");
|
||||
if (pmObj == null)
|
||||
return -1;
|
||||
|
||||
return pmObj.Call<int>("getCurrentThermalStatus");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
public static int GetAndroidSdkVersion()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isEditor)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
@ -53,6 +54,64 @@ namespace Byway.Quality
|
||||
/// </summary>
|
||||
public int SystemMemorySizeMb { get; internal set; }
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// v0.3.0 additions (additive only; existing matchers/assets unaffected).
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Android <c>Build$VERSION.SDK_INT</c>; 0 on non-Android / editor.</summary>
|
||||
public int AndroidSdkInt { get; internal set; }
|
||||
|
||||
/// <summary>True when the device is detected as an Android emulator (BlueStacks/MuMu/LDPlayer/AVD/etc.). False on iOS and physical Android.</summary>
|
||||
public bool IsEmulator { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.SystemInfo.graphicsDeviceType" />.</summary>
|
||||
public GraphicsDeviceType GraphicsDeviceType { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.SystemInfo.graphicsShaderLevel" />.</summary>
|
||||
public int GraphicsShaderLevel { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.Screen.width" /> at hardware-stats capture time.</summary>
|
||||
public int ScreenWidth { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.Screen.height" /> at hardware-stats capture time.</summary>
|
||||
public int ScreenHeight { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.Screen.dpi" /> at hardware-stats capture time. May be 0 if unknown.</summary>
|
||||
public float ScreenDpi { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.SystemInfo.processorCount" />.</summary>
|
||||
public int ProcessorCount { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.SystemInfo.processorFrequency" /> in MHz.</summary>
|
||||
public int ProcessorFrequencyMHz { get; internal set; }
|
||||
|
||||
/// <summary><see cref="UnityEngine.SystemInfo.processorType" />. Strong x86 emulator signal on Android.</summary>
|
||||
public string ProcessorType { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.MANUFACTURER</c>; empty on non-Android.</summary>
|
||||
public string BuildManufacturer { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.BRAND</c>; empty on non-Android.</summary>
|
||||
public string BuildBrand { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.MODEL</c>; empty on non-Android. Distinct from <see cref="DeviceModel" /> which is Unity's composed string.</summary>
|
||||
public string BuildModel { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.HARDWARE</c>; empty on non-Android. Useful emulator signal (<c>goldfish</c>/<c>ranchu</c>).</summary>
|
||||
public string BuildHardware { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.PRODUCT</c>; empty on non-Android. Useful emulator signal (<c>sdk_*</c>/<c>vbox*</c>).</summary>
|
||||
public string BuildProduct { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>Build.SOC_MANUFACTURER</c> (API 31+); empty otherwise.</summary>
|
||||
public string BuildSocManufacturer { get; internal set; }
|
||||
|
||||
/// <summary>Derived Mali architectural generation; <see cref="Quality.MaliGeneration.Unknown" /> on non-Mali / non-Android.</summary>
|
||||
public MaliGeneration MaliGeneration { get; internal set; }
|
||||
|
||||
/// <summary>Android <c>PowerManager.getCurrentThermalStatus()</c> (API 29+); -1 if unsupported / not Android.</summary>
|
||||
public int ThermalStatus { get; internal set; }
|
||||
|
||||
internal static HardwareStats CreateDefault()
|
||||
{
|
||||
return new HardwareStats
|
||||
@ -64,7 +123,26 @@ namespace Byway.Quality
|
||||
GpuMinorSeries = GpuMinorSeries.Unknown,
|
||||
GpuSeriesNumber = 0,
|
||||
SocName = "",
|
||||
SystemMemorySizeMb = SystemInfo.systemMemorySize
|
||||
SystemMemorySizeMb = SystemInfo.systemMemorySize,
|
||||
|
||||
AndroidSdkInt = 0,
|
||||
IsEmulator = false,
|
||||
GraphicsDeviceType = SystemInfo.graphicsDeviceType,
|
||||
GraphicsShaderLevel = SystemInfo.graphicsShaderLevel,
|
||||
ScreenWidth = Screen.width,
|
||||
ScreenHeight = Screen.height,
|
||||
ScreenDpi = Screen.dpi,
|
||||
ProcessorCount = SystemInfo.processorCount,
|
||||
ProcessorFrequencyMHz = SystemInfo.processorFrequency,
|
||||
ProcessorType = SystemInfo.processorType,
|
||||
BuildManufacturer = "",
|
||||
BuildBrand = "",
|
||||
BuildModel = "",
|
||||
BuildHardware = "",
|
||||
BuildProduct = "",
|
||||
BuildSocManufacturer = "",
|
||||
MaliGeneration = MaliGeneration.Unknown,
|
||||
ThermalStatus = -1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ namespace Byway.Quality
|
||||
stats.GpuMajorSeries = GpuMajorSeries.Apple;
|
||||
stats.GpuMinorSeries = ParseGpuMinorSeries(stats.GpuName);
|
||||
stats.GpuSeriesNumber = ParseAppleGpuSeriesNumber(stats.GpuName);
|
||||
stats.IsEmulator = false;
|
||||
}
|
||||
|
||||
public static GpuMinorSeries ParseGpuMinorSeries(string gpuName)
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
namespace Byway.Quality
|
||||
{
|
||||
/// <summary>
|
||||
/// Generation taxonomy for ARM Mali / Immortalis GPUs.
|
||||
/// Used by <c>MaliGenerationRuleMatcherBase</c> to disambiguate the legacy/modern Mali numeric collision
|
||||
/// (e.g. Mali-G77 vs Mali-G310 — both <c>MaliG</c>, but very different architectures).
|
||||
/// </summary>
|
||||
public enum MaliGeneration
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>Mali-400 / 450 / 470 (no letter suffix).</summary>
|
||||
Utgard = 1,
|
||||
|
||||
/// <summary>Mali-T6xx / T7xx / T8xx.</summary>
|
||||
Midgard = 2,
|
||||
|
||||
/// <summary>Mali-G31 / G52 / G57 / G68 (low-end Bifrost / early Valhall band).</summary>
|
||||
BifrostLegacy = 3,
|
||||
|
||||
/// <summary>Mali-G71 / G72 / G76 / G77 / G78 (Valhall first wave, legacy numbering < 100).</summary>
|
||||
Valhall1 = 4,
|
||||
|
||||
/// <summary>Mali-G310 / G510 / G610 / G615 / G710 / G715 / G720 / G725 (modern Valhall numbering >= 100).</summary>
|
||||
Valhall2 = 5,
|
||||
|
||||
/// <summary>ARM Immortalis-G715 / G720 / G725 / G925 (top-tier ray-tracing line).</summary>
|
||||
Immortalis = 6
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f8c6a9d2e1b4536b7a8d0e1c2f3a4b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a4c3e9d2b1f4a5e8c6d0f2b3a4e5c7d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class AndroidApiLevelRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Minimum Android SDK_INT (inclusive); 0 = no min")]
|
||||
public int apiLevelMin;
|
||||
|
||||
[Tooltip("Maximum Android SDK_INT (inclusive); 0 = no max → treated as 999")]
|
||||
public int apiLevelMax;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (stats.AndroidSdkInt <= 0)
|
||||
return false;
|
||||
|
||||
var max = apiLevelMax <= 0 ? 999 : apiLevelMax;
|
||||
if (stats.AndroidSdkInt < apiLevelMin || stats.AndroidSdkInt > max)
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0513c6d8e7f9a6cb1f2c3d4e5f60718
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class CompositeAndRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("All children must match for this rule to fire. Children's own quality levels are discarded.")]
|
||||
[SerializeReference]
|
||||
[SelectableSerializeReference]
|
||||
public IMatcher[] children;
|
||||
|
||||
[Tooltip("Quality level returned when all children match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (children == null || children.Length == 0)
|
||||
return false;
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child == null)
|
||||
return false;
|
||||
if (!child.TryMatch<T>(stats, out _))
|
||||
return false;
|
||||
}
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class CompositeOrRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Any one child matching causes this rule to fire. Children's own quality levels are discarded.")]
|
||||
[SerializeReference]
|
||||
[SelectableSerializeReference]
|
||||
public IMatcher[] children;
|
||||
|
||||
[Tooltip("Quality level returned when any child matches")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (children == null || children.Length == 0)
|
||||
return false;
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (child == null)
|
||||
continue;
|
||||
if (child.TryMatch<T>(stats, out _))
|
||||
{
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2735e8fa09b1c8ed3f4e5f60718293a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class DeviceNameContainsRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Pattern to compare against SystemInfo.deviceModel")]
|
||||
public string deviceModelPattern;
|
||||
|
||||
[Tooltip("How to compare the pattern against deviceModel")]
|
||||
public StringMatchMode matchMode;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (string.IsNullOrEmpty(stats.DeviceModel))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(deviceModelPattern))
|
||||
return false;
|
||||
|
||||
if (!StringMatchModeUtility.IsMatch(stats.DeviceModel, deviceModelPattern, matchMode))
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3846f90b1ac2d9fe4f5061728293a4b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class EmulatorDetectionRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("true: match if emulator detected; false: match if NOT emulator")]
|
||||
public bool matchEmulator;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
if (stats.IsEmulator != matchEmulator)
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d2e0f3a5b4c6749e8d9f0a1b2c3d4e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class GraphicsApiRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Required graphics API. Any = skip API check.")]
|
||||
public GraphicsApi requiredApi;
|
||||
|
||||
[Tooltip("Minimum SystemInfo.graphicsShaderLevel (inclusive); 0 = no min")]
|
||||
public int requiredShaderLevelMin;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (requiredApi != GraphicsApi.Any && !ApiMatches(requiredApi, stats.GraphicsDeviceType))
|
||||
return false;
|
||||
|
||||
if (stats.GraphicsShaderLevel < requiredShaderLevelMin)
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ApiMatches(GraphicsApi required, GraphicsDeviceType actual)
|
||||
{
|
||||
return required switch
|
||||
{
|
||||
GraphicsApi.Vulkan => actual == GraphicsDeviceType.Vulkan,
|
||||
GraphicsApi.OpenGLES3 => actual == GraphicsDeviceType.OpenGLES3,
|
||||
GraphicsApi.OpenGLES2 => actual == GraphicsDeviceType.OpenGLES2,
|
||||
_ => true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae3f1a4b6c5d784af9e0a1b2c3d4e5f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class MaliGenerationRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Mali architectural generation that match")]
|
||||
public MaliGeneration generation;
|
||||
|
||||
[Tooltip("Minimum of GPU series number that match (inclusive); 0 = no min")]
|
||||
public int gpuSeriesNumberMin;
|
||||
|
||||
[Tooltip("Maximum of GPU series number that match (inclusive); 0 = no max → treated as int.MaxValue")]
|
||||
public int gpuSeriesNumberMax;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (stats.MaliGeneration == MaliGeneration.Unknown)
|
||||
return false;
|
||||
if (stats.MaliGeneration != generation)
|
||||
return false;
|
||||
|
||||
var max = gpuSeriesNumberMax <= 0 ? int.MaxValue : gpuSeriesNumberMax;
|
||||
if (stats.GpuSeriesNumber < gpuSeriesNumberMin || stats.GpuSeriesNumber > max)
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c1d9e2f4a3b5648d7c8e9f0a1b2c3d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class ScreenResolutionRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Minimum total pixel count (width*height), inclusive; 0 = no min")]
|
||||
public int pixelCountMin;
|
||||
|
||||
[Tooltip("Maximum total pixel count (width*height), inclusive; 0 = no max → unbounded")]
|
||||
public int pixelCountMax;
|
||||
|
||||
[Tooltip("Minimum DPI, inclusive; 0 = no min (DPI also skipped if Screen.dpi reports 0)")]
|
||||
public float dpiMin;
|
||||
|
||||
[Tooltip("Maximum DPI, inclusive; 0 = no max → unbounded")]
|
||||
public float dpiMax;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
var pixelCount = stats.ScreenWidth * stats.ScreenHeight;
|
||||
var pxMax = pixelCountMax <= 0 ? int.MaxValue : pixelCountMax;
|
||||
if (pixelCount < pixelCountMin || pixelCount > pxMax)
|
||||
return false;
|
||||
|
||||
if (stats.ScreenDpi > 0f)
|
||||
{
|
||||
var dMax = dpiMax <= 0f ? float.MaxValue : dpiMax;
|
||||
if (stats.ScreenDpi < dpiMin || stats.ScreenDpi > dMax)
|
||||
return false;
|
||||
}
|
||||
else if (dpiMin > 0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1624d7e9f8a0b7dc2f3d4e5f6071829
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Byway.Quality
|
||||
{
|
||||
[Serializable]
|
||||
public class SocNameRuleMatcherBase<T> : RuleMatcherBase<T>
|
||||
{
|
||||
public Rule[] rules;
|
||||
|
||||
protected override IEnumerable<IMatcher<T>> Rules => rules;
|
||||
|
||||
[Serializable]
|
||||
public sealed class Rule : IMatcher<T>
|
||||
{
|
||||
[Tooltip("Pattern to compare against HardwareStats.SocName (Build.SOC_MODEL on Android 12+)")]
|
||||
public string socNamePattern;
|
||||
|
||||
[Tooltip("How to compare the pattern against SocName")]
|
||||
public StringMatchMode matchMode;
|
||||
|
||||
[Tooltip("Quality level for the device that match")]
|
||||
public T qualityLevel;
|
||||
|
||||
public bool TryMatch(HardwareStats stats, out T matchedQualityLevel)
|
||||
{
|
||||
matchedQualityLevel = default;
|
||||
|
||||
if (string.IsNullOrEmpty(stats.SocName))
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(socNamePattern))
|
||||
return false;
|
||||
|
||||
if (!StringMatchModeUtility.IsMatch(stats.SocName, socNamePattern, matchMode))
|
||||
return false;
|
||||
|
||||
matchedQualityLevel = qualityLevel;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class StringMatchModeUtility
|
||||
{
|
||||
private static bool _regexFailureLogged;
|
||||
|
||||
public static bool IsMatch(string input, string pattern, StringMatchMode mode)
|
||||
{
|
||||
return mode switch
|
||||
{
|
||||
StringMatchMode.Exact => input.Equals(pattern, StringComparison.Ordinal),
|
||||
StringMatchMode.StartsWith => input.StartsWith(pattern, StringComparison.Ordinal),
|
||||
StringMatchMode.Contains => input.IndexOf(pattern, StringComparison.Ordinal) >= 0,
|
||||
StringMatchMode.Regex => TryRegex(input, pattern),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool TryRegex(string input, string pattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Regex.IsMatch(input, pattern);
|
||||
}
|
||||
catch (ArgumentException e)
|
||||
{
|
||||
if (!_regexFailureLogged)
|
||||
{
|
||||
_regexFailureLogged = true;
|
||||
Debug.LogWarning($"[QualityTuner] invalid regex pattern '{pattern}': {e.Message}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf402b5c7d6e895ba0f1b2c3d4e5f607
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,20 @@
|
||||
namespace Byway.Quality
|
||||
{
|
||||
/// <summary>
|
||||
/// String matching mode shared by <c>SocNameRuleMatcherBase</c> and <c>DeviceNameContainsRuleMatcherBase</c>.
|
||||
/// </summary>
|
||||
public enum StringMatchMode
|
||||
{
|
||||
/// <summary>Exact ordinal match.</summary>
|
||||
Exact = 0,
|
||||
|
||||
/// <summary>Ordinal StartsWith match.</summary>
|
||||
StartsWith = 1,
|
||||
|
||||
/// <summary>Ordinal Contains match.</summary>
|
||||
Contains = 2,
|
||||
|
||||
/// <summary>.NET regex match. Invalid patterns return false (logged once).</summary>
|
||||
Regex = 3
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a9d7b1e3c2f4647c8b9e0f1d2a3b4c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "com.bywaystudios.qualitytuner",
|
||||
"displayName": "Quality Tuner",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.1",
|
||||
"description": "Tools to support deciding quality level by hardware spec of mobile devices.",
|
||||
"repository": {
|
||||
"url": "git@gitea.bywaystudios.com:wangshiyao/QualitySettingsPackage.git",
|
||||
"type": "git",
|
||||
"revision": null
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user