この記事は現在英語版のみです。

(記事)

Measuring Web 3D Performance: Common Errors and Methods That Hold Up

A 3D performance number counts only when measured on a real GPU, after loading finishes, and from a run that returns to baseline. Compare two builds by interleaving runs and taking the median.

Polish Design engineering team・2026-07-22

Contents

On the same 3D page, memory measured during loading is only about 1/3 of memory after loading finishes. The canvas during loading is just 0.6 times its final size on each side, about 36% of the final area, and the remaining assets have not arrived yet. The number is not wrong. The moment of measurement is. If a vendor's report was taken at that moment, the figure looks far better than what users actually get. The overall approach to web 3D performance engineering covers what to measure first and what to change next; this article covers only how the measuring step goes wrong.

The development team accepts a performance number only under three conditions: rendering ran on a real GPU, measurement started after loading finished, and the app returns to baseline after a full round of interactions. If any one is missing, the number stays out of the comparison. The sections below cover where false numbers come from, how turning off vertical sync misleads, how to compare two builds, and what to check in a vendor's performance report.

Where False Numbers Come From

Screens captured during loading

During loading the scene is incomplete. Canvas size, textures, and geometry arrive in stages, so memory or frame rate measured at that point describes a half-built scene. A measurement script should assert that loading has finished before it starts sampling.

A person leans in with both hands against the rim of a large semicircular gauge whose face runs from teal through yellow to red, with the black needle pointing to the right.

Comparing two builds needs the same discipline. The team builds each version separately and measures both with one script, never against a number remembered from earlier. On an interactive 3D product page, the older build measured about 800 MB and the newer build about 690 MB. Both numbers came from the same script, so the drop in memory is real.

Hidden tabs

In most browsers, requestAnimationFrame callbacks are paused in background tabs and hidden iframes to save performance and power, as MDN Web Docs, 2026 describes. If a render loop depends on that callback, a frame rate measured in a hidden tab cannot be taken as what users see. That last step is this article's inference; the MDN page does not discuss frame-rate measurement.

Software rendering

Without a GPU, Chromium can draw WebGL content on the CPU through SwiftShader, a software implementation of Vulkan and OpenGL ES. The Chromium project documentation describes this mechanism and notes that automatic fallback to SwiftShader is deprecated, so WebGL creation may simply fail. A headless browser on default settings runs software rendering, and the development team measured only about 1 to 3 fps that way. Software rendering also decodes KTX2 compressed textures into RGBA, which inflates memory numbers. The documentation does not say whether software-rendered performance represents real hardware, so the conclusion that it does not rests on those measurements.

Hidden tabs and software rendering together produce numbers absurd enough to spot at a glance: identical frame times for every setting, or about 2,200 frames per second, far above a typical display's refresh rate.

The wrong process

The memory figure shown for a Chrome tab covers the tab process only, not the GPU process, so both processes must be measured. On the same product page, lowering the desktop pixel-ratio cap reduced GPU process memory by about 70 MB, and that change does not appear in the tab process figure. The measurement used Playwright in headless mode with a real GPU, reading each process with top after fetching process IDs over CDP. Headless mode itself is not the problem; what matters is whether a real GPU is attached.

Normal numbers, empty scene

Some failures raise no error. Some Gaussian splat viewers with level of detail (LoD) enabled wait about 6 seconds and return 0 splats, with no error thrown. With LoD disabled, the same data returned about 3,000,000 splats. A script that only captures elapsed time would file the empty scene as an ordinary number. A measurement also needs to assert that content was actually drawn, for example that the drawn splat count is above 0.

Return to baseline

Absolute values are hard to judge in isolation. A more reliable test is whether the app returns to baseline after a full round of interactions. In a stacking test repeated for 4 rounds, the team counts geometries, textures, and shader programs after each round; flat counts mean nothing is accumulating with the interactions.

Fig. 1A number is used only when it comes from a real GPU, after loading, and returns to baseline
  1. Confirm rendering ran on a real GPUFrame rate and memory from software rendering are rejected
  2. Confirm loading finished, the tab was in the foreground, and content was drawnNumbers from loading and hidden tabs are rejected
  3. Finish a round of interactions and compare with baselineMeasure both the tab and GPU processes
  • All three hold
    Accept as a single-run numberStill compare against the other build, interleaved
  • Any one condition fails
    Reject and measure againNumbers are not corrected afterward
  • The app does not return to baseline after a round
    Look for what accumulatesAbsolute values are not compared

Distortion From Turning Off Vertical Sync

Vertical sync aligns drawing with the display refresh, commonly 60 Hz. With vertical sync and the frame-rate cap turned off, a GPU-heavy scene leaves the CPU waiting on the GPU. The measurement shows each render taking a long time, which is easy to read as slow code, while the actual cause is the CPU waiting.

Take a 3D map built to run smoothly on phones. Measured with vertical sync off, each render after entry took about 20 ms, which looked like the cost of drawing every object for the first time, which makes a sliced warm-up tempting as a way to spread that cost. Measured again with vertical sync on, the first 40 renders after entry took only about 1 to 6 ms, and the existing precompilation plus warm-up was already enough. Sliced warm-up showed no measurable benefit and is marked not adopted.

Fig. 2On the same scene, a single render looks several times more expensive with vertical sync off than with it on
Vertical sync offVertical sync on
  • about 20 ms
    Time per render after entryUse the sync-on figure
    about 1 to 6 ms

First 40 renders after entry in a 3D map, approximate values; vertical sync on means a true 60 Hz

Jank needs to be classified before it is fixed. The team uses Chrome traces together with V8 sampling to sort long frames into JavaScript, style recalculation, layout, paint, garbage collection, and waiting on the GPU. A long frame of about 300 ms on desktop turned out to come mainly from the browser starting the audio device; with that step removed, the long frame dropped to about 30 ms. Without the classification, that long frame is easy to blame on code.

Comparing Two Builds

Noise in a single run can exceed the improvement. When other jobs were running on the machine and the system load average climbed from tens to over a hundred, the noise floor for the share of janky frames in each area was already about 2%. When an improvement is only a few percentage points, noise of that size can hide it, so single-run frame counts cannot be compared. The longest frame and the JavaScript milliseconds in a trace were affected less, but still need confirmation across multiple rounds.

  1. Turn vertical sync on and measure under a true 60 Hz condition.
  2. Simulate a phone by throttling the CPU 4 times, and label the result as simulated along with the throttling rate.
  3. Measure the noise floor first: repeat one build and see how widely its own numbers scatter. If the improvement is smaller than that spread, draw no conclusion.
  4. Interleave the two builds, A and B, so noise is shared evenly between the two.
  5. Run multiple rounds and take the median, not the single best run.
Fig. 3Measuring the two builds in turn within the same window puts noise on both at once
Noise floor first
Round one
Later rounds
Reading
Build A
Build B
1
Build AMeasure A repeatedlySee how widely one build scatters on its own
2
Build AMeasure A once
3
Build BMeasure B right afterNoise lands on both builds
4
Build AMeasure A again
5
Build BMeasure B againRounds follow variation
6
Build AMedian of A
7
Build BMedian of B, compared with AAn improvement only if the gap exceeds the noise floor

Each build is built separately and measured with the same script; the number of rounds follows the noise floor and variation

This problem is not unique to 3D. Mytkowicz et al. argue that seemingly insignificant details of an experimental setup can lead systems researchers to wrong conclusions, which is measurement bias; the authors observed the effect across several architectures and compilers (Mytkowicz et al., 2009).

Performance comparison calls for statistical methods rather than single runs. Georges et al. make this case for Java performance evaluation (Georges et al., 2007); in an example from the paper, taking the best of 30 runs makes two garbage collectors look similar, while a 95% confidence interval analysis shows one of the two is clearly better. This article quotes only that claim and example, not conclusions from the full text. The number of repetitions should follow variation: Kalibera and Jones, 2013 use a cost model of experiments to derive the repetitions needed at each level for a given precision, and note that repeating is most useful where uncertainty is greatest.

Optimizations Must Be Rejectable

Measurement has another use: rejecting optimizations. An adjustment that shows no measurable benefit is marked not adopted, with its measurement conditions and the reason written down so the next person does not repeat it. The sliced warm-up above is one such case.

A person in glasses holds a pointer beside an easel; the bar chart on it shows four bars rising from left to right.

Lowering resolution is a common adjustment, but it works only when pixels are the bottleneck. On an interactive 3D product page, cutting pixels to 36% reduced desktop frame time by only about a quarter, and every resolution switch reallocates the drawing buffer: one drop and restore cost about 20 ms, and break-even came only after dragging continuously for about 370 ms. So resolution is lowered only during loading, not during interaction.

Reducing how often the water reflection updates while the camera moves was measured on a simulated phone (CPU throttled 4 times, vertical sync on, two builds interleaved), and it cut about 2 ms from per-frame JavaScript time during dragging. Gains like this are counted in milliseconds, so conditions have to be stated. In the same batch of work, adding will-change: transform to landmarks and icon buttons made no difference in A/B, and the notes say not to try it again.

Rejection also happens at a larger scale. For a Gaussian splatting walkthrough that writes depth first with a mesh to cull splats behind walls, the occluder model lines up with the scan at walls and floors, but indoor objects do not line up, and at eye level, where nearby objects fill much of the screen, the error grows into large holes. This approach does not work for indoor scan assets of this kind, so it is off by default and the reason is written down to avoid repeated effort.

The MDN guidance on WebGL (MDN Web Docs, 2026) has three parts: reduce draw calls, manage texture memory, and compile shaders before linking. The page gives principles, with no measured numbers. It can point to where to look first, but whether a given change helps in a specific scene still has to be measured.

When memory changes, the cause matters too. The team takes a heap snapshot, finds ArrayBuffer nodes, and follows the retainers upward to the first known class. In one change, ArrayBuffer memory fell from about 230 MB to about 160 MB after GLB parsing finished and the parser was released, so the attribution has evidence behind it. For the model-side before-and-after comparison, see 3D model optimization before and after.

Checking a Vendor's Performance Report

When reviewing a vendor's performance report, read the conditions before the numbers. The table below lists each condition, what should be visible, and the risk when it is missing. It can be used alongside how to evaluate and choose a software vendor.

ConditionWhat should be visibleRisk when missing
Rendering environmentA real GPU and device model stated; for headless, how a GPU was obtainedMay be software rendering, so frame rate and memory carry no weight
Measurement timingSampling begins after loading finishes, with a stated way to confirm itNumbers taken during loading run low; memory is only about 1/3 of the finished state
DeviceA real device model; if simulated, labeled as simulated with the throttling rateSimulated and real-device numbers are not interchangeable
Vertical syncStated as on or off, with the refresh rate when onWith it off, GPU waiting is counted as code cost
Rounds and statisticsThe number of interleaved rounds, and the median or other statistic usedA single number cannot rule out noise
Noise floorThe spread of repeated runs of one buildNo way to tell whether the gain exceeds noise
Processes measuredWhether both the tab process and the GPU process were measuredChanges in the GPU process go unseen
Return to baselineWhether geometry, texture, and program counts return to the start after a round of interactionsResources piling up round after round are mistaken for normal usage

If any row cannot be filled in, the number may still be true, but there is no way to tell. In 3D development, the development team measures performance under this same set of conditions.

When This Does Not Fit

  • The page has no interactive 3D: ordinary Web Vitals tools are enough, and the interleaved, multi-round approach in this article is not needed.
  • A single number is all a slide needs and it does not have to be reproduced: one measurement is enough, with no interleaving or extra rounds, but the real GPU and finished loading conditions still apply.

Next Step

Prepare the existing performance report with its measurement conditions, and the devices that matter most. To see what the development team has built first, browse the case studies; when ready, contact the development team.

(お問い合わせ)

Start a project.

三つのステップで、判断に足る進め方と予算帯が得られます。

  1. 01最初の打ち合わせを予約:時間を選びます。完全な仕様は不要です。
  2. 02オンライン相談(15〜30分):進め方の方向性、予算帯、スケジュール案を提示。
  3. 03素材を確認したうえで、約3〜5営業日で見積書兼納品仕様明細書を提出します。
メール
hello@polish-design.com.tw
郵送先住所
(104) 台湾 台北市 中山区 植福路191-1号 3F

最初の打ち合わせを予約

平日 16:00〜18:00オンライン30分

先に時間帯を選び、Google アカウントまたはメールアドレスで確認します。予約が確定するとカレンダーに追加できます。

時間を選ぶ

または、先に内容を送信

まだ時間を決めていない場合は、先にプロジェクトの内容をお送りください。担当者がメールで返信します。送信後に続けて予約することもできます。

補足情報(任意):サービス項目、現在のフェーズ、既存リンク、知ったきっかけ

入力内容は、今回のプロジェクト評価にのみ使用します。

ISO/IEC 27001:2022 認証書

(Certificate)

ISO/IEC 27001:2022

認証機関
ARES International Certification
認証範囲
インタラクティブ没入型3Dシステムの開発プロセス、ならびにネットワークおよびインフラの運用・管理。

認証機関サイトで確認