marCCD frames are widened on the device

The marCCD reader read each frame's 16-bit scanlines into scratch and widened them to int32 on the
worker thread (marccd::ReadInto, ~20 % of the image-loop worker CPU on 4nwv), and the 4-byte image was
uploaded. ReadRawImage now reads the scanlines straight into the image's buffer and hands them over as
a new read-only CompressionAlgorithm::UINT16_WIDENED (mode Int32): two bytes a pixel cross PCIe, from
page-locked memory, and the preprocessing kernel widens them itself.

It is not CompressedImageMode::Uint16, which calls 65535 an error pixel; a marCCD's 65535 is its
saturation marker. So the kernel compares each pixel with int32's error marker and the int32 saturation
cap - the comparisons are made on the pixel's value, in int64, rather than in the storage type - and
decides every pixel exactly as for the int32 image the host would have widened it to. The host widening
(JFJochDecompressPtr) is the CPU path.

Test: ImagePreprocessorGPU_StoredEncodingsMatchCPU, with a frame carrying 65535 at a saturation of
65535, against the CPU preprocessor on the host-widened image.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
This commit is contained in:
2026-10-12 22:37:47 +02:00
co-authored by Claude Opus 5.5
parent f4252ad39d
commit dfd69fcb91
10 changed files with 94 additions and 30 deletions
+15 -4
View File
@@ -299,7 +299,7 @@ Header ReadHeader(const std::string &path) {
return Parse(h, path);
}
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch) {
Header ReadRaw(const std::string &path, uint16_t *out, size_t capacity) {
const Header header = ReadHeader(path);
const size_t npixel = static_cast<size_t>(header.nx) * static_cast<size_t>(header.ny);
if (npixel > capacity)
@@ -331,14 +331,25 @@ Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vec
if (scanline_bytes != static_cast<size_t>(TIFFScanlineSize(tiff)))
throw JFJochException(JFJochExceptionCategory::TIFFGeneratorError,
path + ": TIFFScanlineSize mismatch");
scratch.resize(scanline_bytes * length);
auto *dst = reinterpret_cast<uint8_t *>(out);
for (uint32_t row = 0; row < length; row++) {
if (TIFFReadScanline(tiff, scratch.data() + static_cast<size_t>(row) * scanline_bytes,
row, 0) < 0)
if (TIFFReadScanline(tiff, dst + static_cast<size_t>(row) * scanline_bytes, row, 0) < 0)
throw JFJochException(JFJochExceptionCategory::TIFFGeneratorError,
path + ": TIFFReadScanline error");
}
return header;
}
Header ReadInto(const std::string &path, int32_t *out, size_t capacity, std::vector<uint8_t> &scratch) {
const Header header = ReadHeader(path);
const size_t npixel = static_cast<size_t>(header.nx) * static_cast<size_t>(header.ny);
if (npixel > capacity)
throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds,
path + ": image is larger than the buffer given for it");
scratch.resize(npixel * sizeof(uint16_t));
ReadRaw(path, reinterpret_cast<uint16_t *>(scratch.data()), npixel);
// The stored pixels are unsigned 16-bit; the rest of the code works in signed 32-bit, so they
// are widened here and nothing downstream has to know what the file held.
const auto *src = reinterpret_cast<const uint16_t *>(scratch.data());