Skip to content

finalscale: fix tiled scaled exports and upscale on OpenCL - #22912

Merged
TurboGit merged 2 commits into
darktable-org:masterfrom
nicknezis:finalscale-tile-alignment
Nov 1, 2026
Merged

TurboGit merged 2 commits into
darktable-org:masterfrom
nicknezis:finalscale-tile-alignment

Conversation

@nicknezis

Copy link
Copy Markdown
Contributor

Summary

When finalscale has to tile an export, each tile comes out shifted by up to two output pixels, which leaves visible seams. This happens on the CPU today, and on OpenCL once #22901 lets upscales run there. Found while testing #22901 for review.

dt_iop_clip_and_zoom_roi() resamples output pixel x of its roi at input position x / scale, counted from the start of the input buffer. It ignores the roi offsets. That works for the whole image. A tile only lines up if its output origin is exactly its input origin times the scale, and the roi tiling doesn't guarantee that. A 3x export with OpenCL off and resourcelevel=notebook got a tile at output row 2585 for input row 861, which should have been 2583.

The fix passes the tile's offset in the image to the resampling plan as a float, so non-integer scales line up as well. This goes through new dt_iop_clip_and_zoom_tile() and dt_iop_clip_and_zoom_tile_cl(), and finalscale uses them for exports while the pipe is tiling. Untiled exports and the other callers of the roi functions (demosaic, highlights) are unchanged. The plan's shift is now a float, but integer callers get the same values as before.

Checklist

  • I have read CONTRIBUTING.md and the coding style.
  • I have not merged master into the topic branch.
  • The pull request is one logical change, and every commit compiles on its own (also built with OpenCL off).
  • I ran the relevant tests.
  • No new strings or preferences.
  • A RELEASE_NOTES.md entry was added (Bug Fixes).

Test instructions

mire1.cr2 with default history, --upscale true --hq true, at widths 10316 (2.64x), 11724 (3x) and 15632 (4x). Each export was compared with an untiled CPU export of the same size (resourcelevel=unrestricted). The table shows 8-bit JPEG differences; two untiled exports already differ by up to 9.

export before after
CPU, notebook, 2.64x (2x2 tiles) max 62 max 11, 5 px > 8
CPU, mini, 2.64x (25x26 tiles) max 55 max 11, 5 px > 8
CPU, notebook, 3x (3x3 tiles) max 152 max 9, 1 px > 8
CPU, notebook, 4x (6x6 tiles) max 154 identical

Together with #22901 on PoCL, finalscale tiled on OpenCL at reference, notebook and mini. All of those match the untiled export too (max 12, at most 6 px over 8). Before this fix they were up to 154 off.

On an M4 Pro with Apple OpenCL (with #22901), tiled GPU exports were up to 200 off before (2.64x, notebook) and are at most 11 off after. CPU tiling on the Mac goes from 152-167 off to 0-11. 4x at 200 MB runs without errors. finalscale's own time doesn't change.

0001 and 0004 give the same CPU vs GPU pixel counts as before; those exports don't tile.

Not tested: a tiled downscale (I couldn't make finalscale tile one on these machines), and a discrete GPU.

AI assistance

Written with Claude Code. I checked it on PoCL and on my M4 Pro.

Comment thread src/iop/finalscale.c Resolved
Comment thread src/common/interpolation.h Resolved
@jenshannoschwalm

Copy link
Copy Markdown
Collaborator

All together a very nice fix!
Maybe squash all together including the other finalscale related PR so there is just one thing belonging closely together? And a simpler release note maybe "Upscaling for exports has precision bugs fixed and also supports OpenCL"

Tiled scaled exports were off in two ways. dt_iop_clip_and_zoom_roi()
resamples a buffer from its origin and ignores the roi offsets, but the
roi tilers can give a tile an output origin that is not its input origin
times the scale (input row 861 -> output row 2585 at 3x, instead of
2583). Each tile was then resampled up to two pixels off, which left
seams of up to 150/255. And modify_roi_in() truncates x/y and sizes the
input from the output size alone, so the last row or column of tiles
could end one input row short of the image: a 17000 px wide export read
rows up to 2600 of 2602 and was off by up to 70/255 in its bottom rows.

New dt_iop_clip_and_zoom_tile(_cl)() and dt_interpolation_resample_tile
(_cl)() pass the tile's position in the image to the resampling plan as
a float shift, so non-integer scales line up as well. finalscale uses
them for exports while the pipe is tiling, and sizes a tile's input from
the end of its output.

finalscale now also upscales on OpenCL, instead of always moving the
image back to the CPU for the last step. It does so only when the
upscale fits the device untiled: a tiled upscale reads every output tile
back to the host, which is no faster than the CPU path and slower over
PCIe. dt_opencl_image_fits_device() only sees roi_in, so the pipe now
also checks roi_out against the device's maximum image size; otherwise
an upscaled output above it ran untiled and failed to allocate.

_default_process_tiling_cl_roi() computed the tile offsets in int, which
wrapped once a tile started past 2 GiB of the output (a 4x export with
1 GB of cl_mem crashed); they are size_t now, as on the CPU.
@nicknezis
nicknezis force-pushed the finalscale-tile-alignment branch from 728412b to f15ff67 Compare November 1, 2026 19:45
@nicknezis nicknezis changed the title finalscale: place tiles of a scaled export where the image has them finalscale: fix tiled scaled exports and upscale on OpenCL Nov 1, 2026
@nicknezis

Copy link
Copy Markdown
Contributor Author

Done. #22901 is squashed in here: one commit, plus a single release note under Bug Fixes ("Upscaled exports no longer show shifted bands where they were processed in tiles, and the upscale now runs on OpenCL when it fits the device."). The FIXME is gone with it.

On the name: the _tile variants aren't specific to upscaling. finalscale uses them for any tiled export, and a downscale can tile too. I'd keep _tile, but I'm happy to rename if you'd rather.

This also takes your point from #22901 about tiled OpenCL upscales, see there.

@TurboGit TurboGit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@TurboGit TurboGit added this to the 5.8 milestone Nov 1, 2026
@TurboGit TurboGit added the OpenCL Related to darktable OpenCL code label Nov 1, 2026
@TurboGit
TurboGit merged commit 056e2fd into darktable-org:master Nov 1, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix pull request fixing a bug OpenCL Related to darktable OpenCL code scope: image processing correcting pixels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants