three.js UltraHDRLoader (CPU decode into a HalfFloat DataTexture) vs
gainmap-js HDRJPGNodeLoader (GPU decode into a HalfFloat render target).
Both receive the same bytes from memory (one blob URL per instance, so nothing is deduplicated or cached).
A run ends when every texture is resident on the GPU with mipmaps: renderer.initTexture() for three.js, node.decode() for the node,
then device.queue.onSubmittedWorkDone(). A warmup round is discarded, the order alternates every round.