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
+6 -4
View File
@@ -7,8 +7,10 @@
// Eiger firmware 1.x. Nothing here produces it. It is last so the values of the others do not move -
// the enum crosses the CBOR stream.
//
// CBF_BYTE_OFFSET is READ-ONLY too, and never leaves the process: it is how the miniCBF reader hands
// over a frame as the file stores it, so that the GPU path can upload those bytes and decode them on
// the device. The image's mode is what it decodes to (Int32).
// CBF_BYTE_OFFSET and UINT16_WIDENED are READ-ONLY too, and never leave the process: they are how the
// miniCBF and marCCD readers hand over a frame as the file stores it, so that the GPU path can upload
// those bytes and decode them on the device. The image's mode is what they decode to (Int32). CBF_BYTE_OFFSET
// is the x-CBF_BYTE_OFFSET payload; UINT16_WIDENED is unsigned 16-bit pixels, each one the int32 of the
// same value - which is not CompressedImageMode::Uint16, whose largest value means an error pixel.
enum class CompressionAlgorithm {BSHUF_LZ4, BSHUF_ZSTD, BSHUF_ZSTD_RLE, BSHUF_ZSTD_RLE_HUFF, NO_COMPRESSION,
LZ4_NO_SHUFFLE, CBF_BYTE_OFFSET};
LZ4_NO_SHUFFLE, CBF_BYTE_OFFSET, UINT16_WIDENED};
+9
View File
@@ -240,6 +240,15 @@ inline void JFJochDecompressPtr(uint8_t *output,
DecodeCBFByteOffset(source, source_size, reinterpret_cast<int32_t *>(output), nelements);
return;
}
if (algorithm == CompressionAlgorithm::UINT16_WIDENED) {
if (elem_size != sizeof(int32_t) || source_size != nelements * sizeof(uint16_t))
throw JFJochException(JFJochExceptionCategory::Compression, "Mismatch in size");
const auto *src = reinterpret_cast<const uint16_t *>(source);
auto *dst = reinterpret_cast<int32_t *>(output);
for (size_t i = 0; i < nelements; i++)
dst[i] = src[i];
return;
}
size_t block_size = 0;
if (algorithm != CompressionAlgorithm::NO_COMPRESSION)