D
P
0
← All articles Baca dalam Bahasa Indonesia

CSS Layout, Overflow & Cascade

A 196-Frame Hero Upscale Came Out Striped and Ran on the Wrong GPU? Both of Those Were Defaults

· · 7 min read
A 196-Frame Hero Upscale Came Out Striped and Ran on the Wrong GPU? Both of Those Were Defaults

The revision package landed all at once over WhatsApp, more than 15 items, a mix of bugs, UX notes, translations, and new feature requests, all of it to be triaged against the reality of a 7 million IDR budget. Row number 8 in my triage table was short: intro video or hero pixelated on mobile, category Asset upgrade. The client flagged it, not me, and the complaint was specific, pointing at the scroll-driven hero frame sequence on the homepage.

The word "upgrade" in that category promises there is a better master to go fetch from somewhere. When I went looking, there was none. The webp files sitting on disk were the only masters available: lossy, 1920x1004, VP8 at quality around 75 to 80, roughly 25MB across 196 frames. No original PNG folder behind them. So there was only one road left, which was adding pixels to material that had already been compressed, and that meant a model-based upscaler.

The model ran to completion, and the output came out striped

The reasonable starting point for photographic material is realesrgan-x4plus, the general purpose model most people reach for first. Upscaling the 196-frame hero sequence with that model finished without an error, and the output carried visible tile-seam artifacts.

If you have never seen the shape of those, the mechanism goes like this. An ncnn upscaler cuts an image into tiles so each piece fits in GPU memory, processes them one by one, then stitches them back into a single frame. When that stitching does not truly blend, what is left is a thin grid of lines that traces the tile boundaries exactly, and the grid reads most easily across flat areas such as sky or a plain wall.

The note I left at the time says the seams showed up in the binary build I was using, not as a general property of the x4plus model. That same note blames a known bug in the Real-ESRGAN-ncnn-vulkan v0.2.x Windows binary build, but there is not a single issue number or reference recorded alongside it. So treat the cause as a guess I wrote down myself rather than something that was verified.

What does not need guessing is the version. The binary I ran was Real-ESRGAN-ncnn-vulkan v0.2.5.0, the Windows bundle with the models included, taken from the xinntao/Real-ESRGAN main repo. It is not the Real-ESRGAN-ncnn-vulkan repo, which ships without models, and that distinction is worth confirming before downloading anything.

Device 0 turned out not to be the card I assumed

The NVIDIA driver and the Vulkan SDK were already installed on that machine, and the binary detected two devices on its own, an RTX 5060 and an Intel iGPU. Up to that point everything sounded fine.

What I did not expect is that it auto-selected the Intel iGPU as device 0 by default instead of the NVIDIA GPU. No warning, no prompt, and if the detection output only gets skimmed, this entire job runs on the wrong card while announcing nothing. So I forced the NVIDIA GPU with the -g 1 flag, that GPU being an NVIDIA RTX 5060 Laptop GPU.

One honest limit belongs here: the notes from this job do not record run times on both devices, so there is no speedup figure for me to show. All I can say is that the default device was not the device I meant.

The command that ended up being used

I swapped the model to realesr-animevideov3, and the tile seams stopped appearing.

# x4plus -> tile seams (v0.2.x binary bug); use animevideov3 + force NVIDIA:
realesrgan-ncnn-vulkan -n realesr-animevideov3 -s 2 -g 1 ...

With -s 2, those 196 frames finished in 57 seconds, around 0.3 seconds per frame. The figure is easy to check yourself, 57 divided by 196 comes out at 0.29 seconds.

Two small notes about the environment, because both decide whether the command above can just run again tomorrow. The binary was extracted into a temporary folder, and that got written down deliberately, because the moment that temporary folder gets swept up in a cleanup, the binary has to be downloaded again from scratch. ffmpeg itself came from a WinGet install and was already available on the machine, so the next stage adds no installation at all.

Up to 3840, then back down to 2560

-s 2 on top of a 1920x1004 master gives 3840x2008. That is straight multiplication, 1920 times 2 equals 3840, and 1004 times 2 equals 2008.

That output was not shipped as is. I scaled it back down to 2560x1338 with lanczos, the reason being a 2K width target while preserving the original frame aspect. Anyone can redo the reduction: 2560 divided by 3840 equals exactly two thirds, and 2008 times two thirds comes out at 1338.67, which lands on 1338. The aspect really does hold, since 1920 divided by 1004 equals 1.912 while 2560 divided by 1338 equals 1.913.

Put together, the two steps are times 2 followed by times two thirds, which equals times four thirds. So the frames never doubled, they grew by a third over the original.

ffmpeg ... -vf scale=2560:1338:flags=lanczos  # then webp q92

The WebP was re-encoded at quality 92 with threads auto, and 196 frames finished in 45 seconds. 45 divided by 196 comes out at 0.23 seconds per frame, so both stages together cost 57 plus 45 equals 102 seconds, one minute and 42 seconds for the whole set. Quality 92 is itself a step up from the roughly 75 to 80 the legacy assets came at, so the detail the upscaler recovered does not get thrown away again by the encoder in the last step.

What changed, and what was deliberately left alone

The net effect is two numbers. Dimensions went from 1920x1004 to 2560x1338, and total size from 25MB to 50MB. The width grew by a third, since 2560 divided by 1920 equals 1.333, while the size exactly doubled. Averaged per frame, 25MB divided by 196 comes out around 128KB and 50MB divided by 196 comes out around 255KB. What I recorded when comparing the results was visibly sharper texture and denser detail recovery in vegetation, concrete, and glass reflections.

The JavaScript side was not touched at all. The canvas already auto-scales through Math.max, so larger frames simply hand it more pixel headroom on retina and 2K screens, without a single line needing to move. The only edit in code was the header comment in the hero script file, which I updated so the new pipeline is documented and whoever picks this file up next knows how to regenerate the set.

The work shipped as release 1.11.3 in commit ef45781, and that release is what closed item number 8 in the triage table. The eleven items actually worked from that revision package were click-verified through Playwright across desktop and mobile breakpoints.

What I took away from this one