openvibers/openvibe.games Commits
Games M6: the low quality tier loads textures at most 512 px (a quarter of the GPU memory on weak phones) (#69)
Changes
@@@ -213,7 +213,12 @@ describe('qualityForTier', () => {it('turns antialiasing off below high and shadows off at low', () => {expect(qualityForTier('high')).toMatchObject({ antialias: true, shadows: true })expect(qualityForTier('medium')).toMatchObject({ antialias: false, shadows: true })-- expect(qualityForTier('low')).toMatchObject({ antialias: false, shadows: false })++ expect(qualityForTier('low')).toMatchObject({++ antialias: false,++ shadows: false,++ maxTextureSize: 512,++ })++ expect(qualityForTier('high').maxTextureSize).toBeNull()})})@@@ -249,6 +254,25 @@ describe('applying settings', () => {expect(setHardwareScalingLevel).toHaveBeenCalledWith(1.25)})++ it('caps texture size at the low tier and gives the device limit back above it', () => {++ const caps = { maxTextureSize: 4096 }++ const engine = {++ setHardwareScalingLevel: vi.fn(),++ getCaps: () => caps,++ } as unknown as AbstractEngine++ applyEngineQuality(engine, qualityForTier('low'))++ expect(caps.maxTextureSize).toBe(512)++ applyEngineQuality(engine, qualityForTier('high'))++ expect(caps.maxTextureSize).toBe(4096)++ const small = { maxTextureSize: 256 }++ const weak = {++ setHardwareScalingLevel: vi.fn(),++ getCaps: () => small,++ } as unknown as AbstractEngine++ applyEngineQuality(weak, qualityForTier('low'))++ expect(small.maxTextureSize).toBe(256)++ })++it('toggles the scene shadow switch', () => {const scene = { shadowsEnabled: true }applySceneQuality(scene, qualityForTier('low'))@@@ -33,6 +33,12 @@ export interface QualitySettings {hardwareScalingLevel: numberantialias: booleanshadows: boolean++ /**++ * The largest texture side the tier loads (null = whatever the GPU allows). WebGL's loader scales a bigger image++ * down as it uploads it, so the low tier's 1024 px terrain textures take a quarter of the memory. Textures already++ * loaded keep their size; WebGPU's loader ignores the cap.++ */++ maxTextureSize: number | null}/**@@@ -41,9 +47,9 @@ export interface QualitySettings {* scene's authored lighting) but drops AA and renders at 80% resolution.*/const QUALITY_SETTINGS: Record<QualityTier, Omit<QualitySettings, 'tier'>> = {-- high: { hardwareScalingLevel: 1, antialias: true, shadows: true },-- medium: { hardwareScalingLevel: 1.25, antialias: false, shadows: true },-- low: { hardwareScalingLevel: 1.5, antialias: false, shadows: false },++ high: { hardwareScalingLevel: 1, antialias: true, shadows: true, maxTextureSize: null },++ medium: { hardwareScalingLevel: 1.25, antialias: false, shadows: true, maxTextureSize: null },++ low: { hardwareScalingLevel: 1.5, antialias: false, shadows: false, maxTextureSize: 512 },}/** The full renderer settings for a tier. Pure — safe to unit-test. */@@@ -167,8 +173,16 @@ export function resolveStartupQuality(*/export function applyEngineQuality(engine: AbstractEngine, settings: QualitySettings): void {engine.setHardwareScalingLevel(settings.hardwareScalingLevel)++ const caps = engine.getCaps?.()++ if (!caps) return++ // The device's own limit, read once: a later tier change can raise the cap back to it.++ if (!deviceTextureLimit.has(engine)) deviceTextureLimit.set(engine, caps.maxTextureSize)++ const device = deviceTextureLimit.get(engine)!++ caps.maxTextureSize = settings.maxTextureSize ? Math.min(device, settings.maxTextureSize) : device}++const deviceTextureLimit = new WeakMap<AbstractEngine, number>()++/** Shadow maps are a Scene concern, not an engine one. */export function applySceneQuality(scene: { shadowsEnabled: boolean },
@@ -213,7 +213,12 @@ describe('qualityForTier', () => {
@@ -213,7 +213,12 @@ describe('qualityForTier', () => {
it('turns antialiasing off below high and shadows off at low', () => {
it('turns antialiasing off below high and shadows off at low', () => {
expect(qualityForTier('high')).toMatchObject({ antialias: true, shadows: true })
expect(qualityForTier('high')).toMatchObject({ antialias: true, shadows: true })
expect(qualityForTier('medium')).toMatchObject({ antialias: false, shadows: true })
expect(qualityForTier('medium')).toMatchObject({ antialias: false, shadows: true })
- expect(qualityForTier('low')).toMatchObject({ antialias: false, shadows: false })
+ expect(qualityForTier('low')).toMatchObject({
+ antialias: false,
+ shadows: false,
+ maxTextureSize: 512,
+ })
+ expect(qualityForTier('high').maxTextureSize).toBeNull()
})
})
})
})
@@ -249,6 +254,25 @@ describe('applying settings', () => {
@@ -249,6 +254,25 @@ describe('applying settings', () => {
expect(setHardwareScalingLevel).toHaveBeenCalledWith(1.25)
expect(setHardwareScalingLevel).toHaveBeenCalledWith(1.25)
})
})
+ it('caps texture size at the low tier and gives the device limit back above it', () => {
+ const caps = { maxTextureSize: 4096 }
+ const engine = {
+ setHardwareScalingLevel: vi.fn(),
+ getCaps: () => caps,
+ } as unknown as AbstractEngine
+ applyEngineQuality(engine, qualityForTier('low'))
+ expect(caps.maxTextureSize).toBe(512)
+ applyEngineQuality(engine, qualityForTier('high'))
+ expect(caps.maxTextureSize).toBe(4096)
+ const small = { maxTextureSize: 256 }
+ const weak = {
+ setHardwareScalingLevel: vi.fn(),
+ getCaps: () => small,
+ } as unknown as AbstractEngine
+ applyEngineQuality(weak, qualityForTier('low'))
+ expect(small.maxTextureSize).toBe(256)
+ })
+
it('toggles the scene shadow switch', () => {
it('toggles the scene shadow switch', () => {
const scene = { shadowsEnabled: true }
const scene = { shadowsEnabled: true }
applySceneQuality(scene, qualityForTier('low'))
applySceneQuality(scene, qualityForTier('low'))
@@ -33,6 +33,12 @@ export interface QualitySettings {
@@ -33,6 +33,12 @@ export interface QualitySettings {
hardwareScalingLevel: number
hardwareScalingLevel: number
antialias: boolean
antialias: boolean
shadows: boolean
shadows: boolean
+ /**
+ * The largest texture side the tier loads (null = whatever the GPU allows). WebGL's loader scales a bigger image
+ * down as it uploads it, so the low tier's 1024 px terrain textures take a quarter of the memory. Textures already
+ * loaded keep their size; WebGPU's loader ignores the cap.
+ */
+ maxTextureSize: number | null
}
}
/**
/**
@@ -41,9 +47,9 @@ export interface QualitySettings {
@@ -41,9 +47,9 @@ export interface QualitySettings {
* scene's authored lighting) but drops AA and renders at 80% resolution.
* scene's authored lighting) but drops AA and renders at 80% resolution.
*/
*/
const QUALITY_SETTINGS: Record<QualityTier, Omit<QualitySettings, 'tier'>> = {
const QUALITY_SETTINGS: Record<QualityTier, Omit<QualitySettings, 'tier'>> = {
- high: { hardwareScalingLevel: 1, antialias: true, shadows: true },
- medium: { hardwareScalingLevel: 1.25, antialias: false, shadows: true },
- low: { hardwareScalingLevel: 1.5, antialias: false, shadows: false },
+ high: { hardwareScalingLevel: 1, antialias: true, shadows: true, maxTextureSize: null },
+ medium: { hardwareScalingLevel: 1.25, antialias: false, shadows: true, maxTextureSize: null },
+ low: { hardwareScalingLevel: 1.5, antialias: false, shadows: false, maxTextureSize: 512 },
}
}
/** The full renderer settings for a tier. Pure — safe to unit-test. */
/** The full renderer settings for a tier. Pure — safe to unit-test. */
@@ -167,8 +173,16 @@ export function resolveStartupQuality(
@@ -167,8 +173,16 @@ export function resolveStartupQuality(
*/
*/
export function applyEngineQuality(engine: AbstractEngine, settings: QualitySettings): void {
export function applyEngineQuality(engine: AbstractEngine, settings: QualitySettings): void {
engine.setHardwareScalingLevel(settings.hardwareScalingLevel)
engine.setHardwareScalingLevel(settings.hardwareScalingLevel)
+ const caps = engine.getCaps?.()
+ if (!caps) return
+ // The device's own limit, read once: a later tier change can raise the cap back to it.
+ if (!deviceTextureLimit.has(engine)) deviceTextureLimit.set(engine, caps.maxTextureSize)
+ const device = deviceTextureLimit.get(engine)!
+ caps.maxTextureSize = settings.maxTextureSize ? Math.min(device, settings.maxTextureSize) : device
}
}
+const deviceTextureLimit = new WeakMap<AbstractEngine, number>()
+
/** Shadow maps are a Scene concern, not an engine one. */
/** Shadow maps are a Scene concern, not an engine one. */
export function applySceneQuality(
export function applySceneQuality(
scene: { shadowsEnabled: boolean },
scene: { shadowsEnabled: boolean },