---
title: "Graphics quality on phones and desktops: the device's quality tier"
description: "How the engine fits its rendering to the device a world is played on: a phone, a tablet, a laptop, a desktop or a browser."
section: "Topics"
slug: "topics-graphics-quality"
canonical: "https://origozero.ai/docs/topics-graphics-quality"
updated: "2026-10-16T13:27:29.260439432+00:00"
---

# Graphics quality on phones and desktops: the device's quality tier

A world is authored on one machine and played on every other. The engine
assigns each device a **quality tier** at boot, `"low"`, `"medium"` or
`"high"`, from what the device states about itself: its GPU family and limits,
its memory, and its display (a phone or a touch browser states a density; a
desktop window states none). Every renderer default a world's settings leave
unstated comes from that tier, so a world that never mentions phones still
plays on one at the phone's defaults.

```lua
local q = renderer.qualityTier()
print(q.tier, q.reason)
-- medium   Mali-G78 is a mali GPU of the medium class; 7.0 GiB of memory
print(q.defaults.resolutionScale, q.defaults.anisotropy)
```

| Default | high | medium | low |
|---|---|---|---|
| Texture budget | the device's own | at most 768 MiB | at most 384 MiB |
| Anisotropy (`render.anisotropy`) | 16 | 8 | 4 |
| Scene on a handheld display | 2.25 px per point, 2.4 Mpx at most | 2.0 px per point, 1.3 Mpx | 1.75 px per point, 0.8 Mpx |
| Scene on a desktop display | every pixel | 1.3 Mpx at most | 0.8 Mpx at most |

A desktop, and any device whose GPU is outside the handheld families, is
`"high"`: the renderer exactly as a desktop has always run it, one scene pixel
per display pixel. A handheld starts at the tier its GPU's generation affords
(Adreno 7xx, Immortalis and Mali-G7xx are high; Adreno 6xx and Mali-G57 to G78
medium; PowerVR, Mali-G52 and older low) and is held down by its memory (under
about 4.5 GiB is low, under 6.5 GiB medium) and by a GPU that cannot hold an
8192-texel texture.

**The scene on a dense phone screen.** A phone panel packs far more pixels than
a 3D scene shows at arm's length, and shading cost is pixel count. On a
handheld the tier renders the scene at its own density within its pixel budget
(`renderer.resolutionScale()` reads below 1 on a 1080x2400 phone) under
`renderer.setResolutionScaleMode("reconstruct")`, so the UI and text compose at
the display's full resolution on top of the upscaled scene. A desktop renders
every pixel at the high tier it is assigned; a lower tier chosen on one (a
player's "low graphics") renders the scene within that tier's pixel budget the
same way, so `renderer.setQualityTier` alone is a whole graphics setting on
every platform.

**Overriding.** Three levels, each standing over the one before:

1. The world's settings: a `render.*` key the world's settings state is used on
   every device, whatever its tier.
2. `renderer.setQualityTier("low" | "medium" | "high")` chooses another tier
   for the process (a settings screen offering the player a quality choice is
   this call); `renderer.setQualityTier()` returns to the device's own. The
   choice holds across scene loads, and a change of tier applies the new
   tier's defaults at the call, replacing what an earlier renderer call set for
   them; choosing the tier already in force changes no setting.
3. A single renderer call (`setResolutionScale`, `setTextureBudget`,
   `setAnisotropy`) overrides that one default until the tier changes again,
   including a call made in the same script right after `setQualityTier`. A
   `setTextureBudget` budget stands over the tier's texture cap until
   `setTextureBudget()` returns it to the device's own.

The engine log states the tier and every default it chose at boot, and again
whenever the tier changes (`[quality]` lines).

## A graphics setting players choose

A settings panel offering "Low / Medium / High" needs one call per choice; the
tier carries every default with it, the render scale included, on a phone and
on a desktop alike:

```lua
-- A button's handler. The choice holds for the process, across scene loads
-- and reloads, so nothing has to re-apply it when a scene loads.
local function choose(tier: string?)
    local q = renderer.setQualityTier(tier)  -- nil: back to the device's own
    print(q.tier, q.defaults.resolutionScale, q.defaults.anisotropy)
end
```

`renderer.qualityTier().tier` is the choice in force (the one to highlight in
the panel) and `deviceTier` the device's own. A choice that should outlast the
process (the next launch of the app) is the world's to store, in the player's
`runtime_data`, and to hand back to `renderer.setQualityTier` at startup.

## Seeing what it costs

`renderer.renderTargets()` and `renderer.shadowMemory()` name the memory the
tier's defaults buy, and `renderer.viewportSize()` / `renderer.compositeSize()`
the scene raster and the display it is carried up to. Read them before and
after `renderer.setQualityTier` to see what a tier spends on this device.
