Progressive textures stay low-res after reloading the page while in VR (Quest, sessiongranted)

Versions: Needle Engine 5.1.10, @needle-tools/gltf-progressive 3.6.0, Quest Browser

Steps to reproduce:

  1. Open the scene in Quest Browser and press “Enter VR”. Textures load in high resolution.
  2. While in VR, call window.location.reload() aka - touch the grey sphere floating near the box
  3. Needle re-enters VR on its own through sessiongranted and shows the TemporaryXRContext loading room, then the scene.

Expected: textures load in high resolution, same as step 1.
Actual: every progressive texture stays at a low LOD for the whole session. Taking the headset off and pressing “Enter VR” again fixes it.

Progressive upgrades are visibility-driven: nothing requests a higher LOD until the LOD manager evaluates a visible material. On the sessiongranted path VR spins up before that pass runs against the real scene, so the slots stay low-res until you exit/re-enter.

Quickest check — force it and see if it holds:

context.lodsManager.manager?.awaitLoading();
// or per-material:
NEEDLE_progressive.assignTextureLOD(material, 0);

API details are in [2] and the changelog notes in [7].

Also confirm Quest isn’t being treated as mobile — high-res textures are auto-skipped on mobile to save bandwidth [3], which would explain persistent low LOD if detection misfires.

If forcing the LOD fixes it, it’s entry timing, not compression. If not, drop a deployed URL or minimal repro at Needle-engine - Vite Template (duplicated) - StackBlitz so we can dig in.

Sources: Needle Engine Docs (66%), Needle Engine Docs (61%), Needle Engine Docs (60%), Needle Engine Docs (57%), Needle Engine Docs (57%), Needle Engine Docs (55%), Needle Engine Docs (55%), Needle Engine Docs (55%), Needle Engine Docs (55%), Needle Engine Docs (54%)

4 replies left
Reply to this post directly if you need further help (bottom right button)

That is what my ai says to this answer :

I checked the bot’s points against the Needle source in node_modules. It’s partly right: forcing the LOD is a good first test. But its explanation of the cause doesn’t fit the code, and two of its suggestions won’t help. None of this has been tried on a Quest yet.

Where the bot is wrong:

  • “VR starts before the LOD pass runs, so textures stay low-res”: the LOD manager isn’t a one-time pass. It hooks into every render, including VR frames, and re-checks each visible mesh continuously (lods.manager.js lines 425–445 and 607). Starting late would only delay the upgrade for a moment, not keep textures low-res forever.
  • awaitLoading(): this only waits for texture loads that are already requested. It never asks for a higher resolution, so it can’t fix anything.
  • “Quest treated as mobile”: mobile only skips textures larger than 4096 px (line 875). It’s also the same device on a normal start and after a restart, so it can’t explain the difference.

Where it’s right: forcing the LOD by hand tells you whether the high-res files load at all. But it would “fix” both its theory and mine, so it doesn’t tell you which one is true.

Hi Katja, please try this for debugging (I assume your Claude knows what project you’re referring to)

Step 1: Paste this into your AI on your PC

Add the temporary probe below to my web project’s main.ts without changing its rendering or LOD behavior. Run the Vite development server with HMR enabled and make sure Quest console logs reach node_modules/.needle/logs/latest.client.needle.log.

After I finish the Quest steps, inspect the [XR-LOD-PROBE] lines. Compare lodHeight, xrPresenting, xrRenderTarget, and lodManagerReady before and after the reload. Tell me whether lodHeight became 0 in the blurry session. If it stayed positive, report that instead. Then remove the temporary probe.

// Temporary Quest progressive-texture probe. Remove after testing.
const probeKey = "needle-xr-lod-probe-page";
const probePage = Number(sessionStorage.getItem(probeKey) || "0") + 1;
sessionStorage.setItem(probeKey, String(probePage));

let probeTicks = 0;
const probeTimer = window.setInterval(() => {
    const element = document.querySelector("needle-engine") as any;
    const context = element?.context;
    const renderer = context?.renderer;
    const canvas = renderer?.domElement;
    const target = renderer?.getRenderTarget?.();

    console.log("[XR-LOD-PROBE]", JSON.stringify({
        page: probePage,
        seconds: probeTicks * 2,
        contextReady: !!context,
        xrPresenting: renderer?.xr?.isPresenting ?? null,
        elementHeight: element?.clientHeight ?? null,
        canvasClientHeight: canvas?.clientHeight ?? null,
        canvasHeight: canvas?.height ?? null,
        lodHeight: canvas ? (canvas.clientHeight || canvas.height) : null,
        pixelRatio: renderer?.getPixelRatio?.() ?? null,
        xrRenderTarget: target ? target.isXRRenderTarget === true : null,
        lodManagerReady: !!context?.lodsManager?.manager,
    }));

    if (++probeTicks >= 60) window.clearInterval(probeTimer);
}, 2000);

Step 2: Do this on Quest

  1. Open the HTTPS development URL and enter VR. Wait until the textures are sharp.
  2. Trigger the in-VR page reload. If the textures stay blurry, wait about 10 seconds.
  3. Exit VR and enter again. Note whether the textures become sharp.
  4. Tell your AI whether textures were sharp after each step. It can inspect the log and send us the [XR-LOD-PROBE] lines with its findings.

The probe only logs measurements. If the client log has no probe lines, check the Quest connection to Vite’s HMR WebSocket before interpreting the texture result. But that should just work :slight_smile: It hopefully helps what is the differentiating factor in your app!

Thanks! Results: first Enter VR → sharp. Reload inside VR, waited 10 s → blurry. Exit VR → sharp in browser, Enter VR again → sharp.

lodHeight did not become 0. It stayed positive in the blurry session:

Session Textures xrPresenting canvasClientHeight canvasHeight lodHeight pixelRatio xrRenderTarget lodManagerReady
First Enter VR sharp true 587 1760 587 1 true true
After reload in VR blurry true 670 1760 670 1 true true
Exit + re-enter VR sharp true 587 1760 587 1 true true

The only difference is that clientHeight keeps the 2D value (670) after the granted-session load, but that’s larger, not smaller. xrRenderTarget and lodManagerReady are true in all cases, and there are no errors in the client log. Needle Engine 5.1.10, gltf-progressive 3.6.0, Quest (Adreno 7xx).