(Notes)

Memory Budgets for 3D on Phones: Which Features Stay on Mobile and Which Are Desktop Only

Set a memory ceiling for the whole session first, then convert textures, geometry, and caches into MB one by one. Features that push the total over the ceiling are downgraded or kept desktop only, not cut after launch.

Polish Design engineering team・2026-10-29

Contents

What a phone can carry in 3D can be worked out before any build starts. Running out of memory does more than slow things down; in severe cases the 3D scene disappears entirely. So the ceiling comes first, and every feature is converted into MB against it; features that push the total over get downgraded, and those that still do not fit stay on desktop. The tiers are calculated, not discovered by cutting whatever crashes first after launch.

The client sets the budget; the engineering team converts it item by item

A phone budget for 3D web development is settled early in the project and allocated line by line, like a spreadsheet. A property-viewing website shows the order. Its simulated phone Chromium memory was about 2.6 GB and came down to about 0.9 GB in the first round, then tightened toward a lower peak for the whole flow. Every later addition (trees, a night scene, progressive sharpening) has to fit inside that number.

A person walks forward carrying three string-tied boxes in both arms.

After the item-by-item adjustments, measurements for the whole flow went from about 910 to 960 MB down to about 510 to 710 MB, with a spike near 745 MB. These are simulated phone numbers from Chromium, and the measurement noise is about ±20 to 40 MB. The method is to convert these items one by one:

  • Decoding: the decoder worker shuts down after each batch finishes loading instead of staying resident.
  • Release after upload: once textures and geometry are on the GPU, the CPU-side copies are freed.
  • Downgrades: on phones, the environment map, shadows, water reflections, and panorama cache are each stepped down one level.
  • Texture size: set by the client. Phones use 1024 pixels, and desktops pick by device.
  • Geometry: on phones, each geometry chunk stays under 65,536 vertices, indices switch to 16-bit, positions are stored as grid integers, and normals as Int16.

One detail in the geometry item is easy to miss: a 3-component 16-bit attribute has a 6-byte stride, which gets converted into a second copy, so attributes always use 4 components. This kind of quantization has a formal glTF extension, and Kapoulkine et al., 2018–2020 describes it as trading precision for memory and bandwidth. The effect on quality is often negligible but not guaranteed, so each item still has to be checked on screen.

Each fine alone, over budget together

Item-by-item conversion cannot be a simple sum. On the same property website, trees and the night scene each added little memory when switched on alone. Switched on together, the increase jumped.

Feature switched onMemory added per step of the flow
Trees only0 to 5 MB
Night scene only0 to 5 MB
Both togetherabout 40 MB (peak up about 45 MB)

The cause is the process's memory high-water mark, not two increments added together. A feature measured alone is therefore not enough; it also has to be measured together with the features already on. The decision was to keep trees on phones and leave the night city to desktop, and the client agreed. A lighter phone night scene that lights only the towers was built afterward, and its peak increase over a long flow is about 0 MB.

Fig. 1Whether a feature stays on phones depends on the combined peak, not on the single item
  1. Measure it aloneHow many MB the flow's peak rises, compared with the same build with the feature flag off
  2. Measure it with the features already onFine alone does not mean fine together
  3. Compare with the ceilingA combined peak under the ceiling stays; over it, the paths below
  • Combined peak is under the ceiling
    Stays on phonesExample: trees on the phone build
  • Over the ceiling, but under it after downgrading
    Stays, downgradedTexture size, shadows, reflections, and caches each step down one level
  • Still over the ceiling after downgrading
    Desktop onlyExample: the night city
  • The point of the feature can be shrunk
    Build a lighter phone versionExample: a night scene that lights only the towers

Based on the property-viewing website measured in Chromium phone emulation.

Example: the 3D city on the CHYIHAUR homepage

The 3D city scene on the CHYIHAUR website homepage originally carried 8 textures at 4096 × 4096. The conversion fits in one line: 8 textures × 4096 × 4096 pixels × 4 bytes per pixel × 4/3 for mipmaps comes to about 683 MiB, which matches the roughly 681 MB recorded at the time. After the textures dropped to 2048 × 2048, the calculation gives about 171 MiB against a recorded 170 MB, and the download fell from 3.38 MB to 2.84 MB.

The next step converted the textures to a GPU compression format, which brought memory to about 30 MB. How to choose the format is covered in GLB file size and textures, so only the converted result matters here. Textures in a KTX2 container with Basis Universal are transcoded by the engine into block-compressed formats the platform supports (Callow et al., 2020). The specification gives no savings figures, so the 30 MB is this project's own result. The number answers the memory question only; quality after compression is checked texture by texture separately.

Fig. 2Texture memory fell from about 681 MB to about 30 MB, at the cost of halving each texture's edge
GPU memory for texturesEdge length per texture (pixels)
  • about 681 MB
    Original 8 texturesStart
    4096
  • about 170 MB
    Edge reduced to 2048
    2048
  • about 30 MB
    Then GPU compressionFinal
    2048

CHYIHAUR homepage 3D city, 8 textures. Memory figures are calculated and recorded values; quality after compression is not part of these figures.

Shaders and textures are precompiled before the scene is entered, so the first frame inside the city does not stall. The interface enters only once the city is fully ready, rather than popping in while assets load. If 3D fails to load, the page falls back to a static cover, and a server-rendered content block below the first screen keeps the page readable by search engines.

Whether a 3D homepage slows the site can be checked against LCP, the render time of the largest image, text block, or video in the first screen; 2.5 seconds or less counts as good (Walton et al., 2019).

A large ground texture can eat the whole budget

The ground is the single item most likely to consume the budget. An uncompressed 4096 texture decodes to about 85 MB on the GPU, which already exceeds the 64 MB texture budget of the lowest phone tier. A ground texture is always on screen, so it counts as in use and the cache sweep cannot clear it, which is a direct out-of-memory risk on iOS. With a GPU compression format, a 4096 texture takes about 21 MB and a 2048 about 5 MB, so desktops use 4096 and everything else uses 2048.

The second trap is repetition. A ground baked into one texture has a physical limit: spread over a 4096 texture, each pixel covers about 2.6 cm, so gravel 5 cm across gets about 2 pixels. Close-up detail has only two routes: a tileable detail normal layered at runtime, where a 1024 image tiled 40 times costs about 1.3 MB, or a full GLB environment, which tends to run phones out of memory.

In another outdoor scene, the ground is about 60 meters square and a 1024 base map repeats about every 3 meters, while the car body is only about 3.6 meters long, so a top-down view shows a grid. Resolution was not the problem, so the asset stayed. The same image is sampled again at a different scale and blended with low-frequency noise, and the same noise nudges brightness and roughness. The cost is one extra texture sample.

Long sessions need GPU memory management

Passing the first load does not mean a long session will survive. In a 3D configurator, users keep switching styles and colors, and after long use the scene can disappear entirely, caused by accumulating GPU memory and a WebGL context loss. The first step was to measure what is bounded. Switching styles mostly toggles visibility without loading new files, and after 60 round trips the geometry count leveled off at roughly double. The color texture cache, however, only grew: after switching through 12 colors the texture count rose steadily and the cache grew from about 50 MB to about 290 MB.

The fix is a per-device budget plus mark-and-sweep. The caps for the low, mid, and high tiers are 64 MB, 128 MB, and 256 MB. Eviction starts only when the cap is exceeded, removes only textures that are not in use and are past a 30-second keep-alive window, and continues down to 80% of the cap. If everything is blocked by keep-alive, it retries after 30 seconds, at most 2 times. Triggers are 2.5 seconds after the cache grows and when the tab is hidden. There is no permanent polling, so the page does nothing while idle.

The mark step has to cover every texture slot on the main scene (hidden parts included) and on cached parts, every shader uniform (the previous color used for fades, the flake texture), and textures stashed in userData (the roughness map that must be restored after a material switch). Scanning only material.map sweeps away the fade's previous color and the flake texture, and the screen flashes white. Another point: the failure counter for context loss must not only go up, or after 3 failures the screen stays white for good; it should reset once the context survives 30 seconds. After two sweeps, the cache returned to about 200 MB. That figure comes from in-page statistics.

Slow loading is not always about file size

Loading in large-scene sites can stall partway through the progress bar. In one load that took 45 seconds on a desktop test, about 20 seconds went to synchronously waiting on shader compilation and about 14 seconds to warming up the section-cut variants. The MDN documentation recommends compiling all shaders first and linking afterward, and using KHR_parallel_shader_compile to avoid blocking (MDN Web Docs, 2026). That page offers principles only, with no measured figures.

There were two fixes. First, the section-cut planes stay resident: three planes are attached before compilation and parked far away when unused, and toggling only changes a constant, so each material has a single program. Second, the production build turns off shader error checking, so parallel compilation is truly parallel. On desktop, loading dropped from 45 seconds to 8 seconds.

Two things must not be done. Swapping the clipping-plane array when a floor opens recompiles the whole scene, because the plane count is part of the program key. The day and night environment maps must also be the same size, or switching recompiles everything; measured freezes ran 1.3 to 3 seconds.

Smooth takes more than memory

A page can stay inside its memory budget and still feel rough. The common metric for interaction response is INP: 200 milliseconds or less is good, and over 500 milliseconds is poor (Wagner et al., 2022). On a map-centered interactive site, zooming stutter often comes from the DOM and styles rather than 3D rendering. A tracing tool splits each janky frame into JavaScript, style recalculation, layout, and paint, and can find two root causes.

The first was a CSS custom property for place-name fading, written on a parent element. When it changed, every place name and landmark in the subtree had its styles recalculated, 10 to 15 milliseconds per frame during the fade. The fix was to write opacity, a non-inherited property, on each name individually. The second was reading clientWidth and getBoundingClientRect every frame, which forces the browser to lay out the label positions written earlier in the same frame. Fixing one spot just moves the cost to the next reader, so all three modules now use a ResizeObserver-cached size, and the visible area is measured once every 8 frames.

On a simulated phone with a 4x CPU slowdown, with alternating A/B runs and vertical sync on, frames over one frame budget during zoom fell from 2.4% to 0.5%. The rule became a site-wide standard: visuals that change continuously with zoom are written only on the non-inherited properties of the element that moves.

Two people stand on either side of a conveyor, watching a row of jars pass along it.

Some phone problems never reproduce in a desktop emulator. Take a mobile bottom drawer: scrolling to the bottom and then swiping up stopped responding, but only when the finger rested on text; over blank space it worked. The cause is that iOS WebKit folds touch-action into event regions computed at paint time. After a dynamic switch, the region over the text was not updated and stayed pan-x, so vertical gestures were blocked. Desktop Chrome and Playwright's WebKit have no iOS event regions, so the fault could not be measured there.

The method was to write a measurement script first, pinning the behavior down as a regression guard, then rule out causes one by one on a real device, keeping a list of what had already been checked to avoid repeats. The fix calls preventDefault on touchmove at half height and no longer switches touch-action dynamically, and that rule went into the project rules too.

When a screen is built from several asynchronous pieces of data, each piece lands on its own and pushes the content below down. The fix is to keep the content hidden, let each component keep loading and report that it is settled, and fade in only when all are settled, waiting at most 4 seconds; the cost is that display time is set by the slowest piece. On such pages CLS can land roughly between 0.15 and 0.45. CLS is the sum of the largest burst of unexpected layout-shift scores; 0.1 or below is good and above 0.25 is poor (Mihajlija et al., web.dev). The pass threshold for each page was set at 0.05, stricter than the common bar for good, and a script lists every shift on each page over a slow network with the block that caused it.

How to verify, and the traps in measuring

A budget counts only if it can be verified. For controlling measurement error, see 3D performance measurement; for the overall process and acceptance, see Web 3D performance engineering. The practices and traps tied directly to the memory budget follow:

  • Use the same build with the feature flag off as the baseline, not the live site, so a single item's increase is not mixed with other changes.
  • Measure the noise first. On the property website it was about ±20 to 40 MB, and about ±40 MB at the peak. Differences below the noise do not support a conclusion.
  • Emulation is not a real device. The memory figures in this article come from Chromium emulation or in-page statistics; iOS behavior can only be confirmed on a real device.
  • Measure frame rate in a foreground tab. Most browsers pause requestAnimationFrame callbacks in background tabs (MDN Web Docs, 2026), so by inference a frame rate measured in a background tab is not reliable.

How this budget plays out across a complete viewing path is shown in the path of a 3D property showroom.

When this approach does not fit

  • The phone is not the main entry point: on phones it is enough to confirm that a failed 3D load falls back to a static cover, with no need to convert the whole budget item by item.
  • No one can sign off the budget: every downgrade trades image quality, features, and memory, and the client has to decide. The engineering team only converts and measures.
  • There is no fixed measurement baseline: when one feature cannot be toggled on the same build for comparison, a single item's increase disappears into the noise and the conversion cannot start.
  • The real problem is interaction jank: when memory is within budget but the page still feels rough, split the frame cost first instead of cutting more memory.

Next step: what to have ready

  1. The lowest phone models and the browser range to support.
  2. The list of features to put on phones, in order of importance.
  3. Existing models and textures, or the source of each.
  4. The person who can decide the memory ceiling and the image-quality trade-offs.

With these ready, contact the engineering team to scope 3D on phones and start converting from the first feature.

(Contact)

Start a project.

Three steps to a clear direction and budget range.

  1. 01Book the first meeting: choose a time. A full specification is not required.
  2. 02A 15 to 30 minute online consultation: direction, budget range, and draft timeline.
  3. 03After materials are reviewed, a quote and delivery specification follows in about 3 to 5 working days.
Email
hello@polish-design.com.tw
Mailing address
3F, No. 191-1, Zhifu Rd., Zhongshan Dist., Taipei 104, Taiwan

Book the first meeting

Monday to Friday, 16:00–18:0030 minutes online

Choose a time, then confirm with a Google account or an email address. A confirmed booking can be added to the calendar.

Choose a time

Or send the details first

Not ready to pick a time yet: leave the project details here and the team replies by email. A meeting can still be booked right after sending.

More details (optional): service, stage, links, source

Information is used only to evaluate this project.

ISO/IEC 27001:2022 certificate

(Certificate)

ISO/IEC 27001:2022

Certification body
ARES International Certification
Scope
The development process for interactive immersive 3D systems, and network and infrastructure operations and management.

Verify with ARES