// Float32 → half, for the one place in the pipeline that hands raw numbers to // Skia: the RAW develop's band plane. // // Skia's GPU backend does not keep a float32 texture. A band uploaded as // RGBA_F32 + an sRGB tag comes back sampled off a 1/255 grid — the shader asked // for fract(v * 100) and got the wrap of round(v * 255) / 255 on every value // (probed in Chromium/ANGLE against the same shader on a raster surface, which // returned the floats untouched). The plane is raw/65535, so the shadows — the // whole point of the develop's black level — sit at 1e-3 and quantise to zero: // the browser's own develop came back with 41189 pixels at luma < 2 and the dark // end speckled blue/yellow, while the same shader on a raster surface produced // none. A half IS uploaded as float, so this keeps the plane exact on both. // // ponytail: 10-bit mantissa is 0.05% relative — a tenth of an output level at // the white point and finer than the plane's own 1/65535 step in the shadows. // Take RGBA_16161616 unorm instead if a develop ever has to be bit-exact. const scratch = new Float32Array(1); const scratchBits = new Uint32Array(scratch.buffer); export function f32ToF16(src: Float32Array, dst: Uint16Array, count: number): void { for (let i = 0; i < count; i++) { scratch[0] = src[i]; const x = scratchBits[0]; const sign = (x >> 16) & 0x8000; let exp = (x >> 23) & 0xff; const man = x & 0x7fffff; if (exp === 0xff) { dst[i] = sign | 0x7c00; continue; } // inf / nan if (exp < 103) { dst[i] = sign; continue; } // underflows the subnormal range if (exp < 113) { // subnormal half const shift = 113 - exp; const full = man | 0x800000; dst[i] = sign | ((full >> (shift + 13)) + ((full >> (shift + 12)) & 1)); continue; } if (exp > 142) { dst[i] = sign | 0x7c00; continue; } // overflows to inf exp -= 112; const rest = man & 0x1fff; // round to nearest even // The mantissa's rounding carry has to reach the exponent, so add — `|` // would drop it, since bit 10 is set in most exponents. dst[i] = sign | ((exp << 10) + (man >> 13) + (rest > 0x1000 || (rest === 0x1000 && ((man >> 13) & 1)) ? 1 : 0)); } }