blob: d4f304f8a9b3ff9496893257e3fbe2a1f17c4553 [file]
// Copyright 2026 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fxge/dib/cfx_scanlinecompositor_hwy.h"
#include <stddef.h>
#include <stdint.h>
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/span.h"
#include "core/fxcrt/span_util.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "core/fxge/dib/cfx_scanlinecompositor_hwy.cpp"
#include "third_party/highway/src/hwy/foreach_target.h" // IWYU pragma: keep
#include "third_party/highway/src/hwy/highway.h"
HWY_BEFORE_NAMESPACE();
namespace fxge_composite_simd {
namespace HWY_NAMESPACE {
namespace {
namespace hn = hwy::HWY_NAMESPACE;
// exact floor(x/255), x in [0, 65025]
template <class V>
HWY_INLINE V Div255(V x) {
const hn::DFromV<V> d;
return hn::ShiftRight<8>(
hn::Add(hn::Add(x, hn::Set(d, 1)), hn::ShiftRight<8>(x)));
}
// (back*(255-a) + src*a)/255; operands <= 255 -> sum <= 65025, Div255 exact
template <class V>
HWY_INLINE V AlphaMergeLanes(V back, V src, V alpha, V v255) {
return Div255(
hn::Add(hn::Mul(back, hn::Sub(v255, alpha)), hn::Mul(src, alpha)));
}
// scalar tail; bit-exact with CompositeRowBgra2Bgra normal/no-clip
inline uint32_t CompositePixelBgra2Bgra(uint32_t src, uint32_t back) {
const uint32_t back_alpha = FXARGB_A(back);
if (back_alpha == 0) {
return src;
}
const uint32_t src_alpha = FXARGB_A(src);
if (src_alpha == 0) {
return back;
}
if (src_alpha == 255) {
return src;
}
const uint32_t dest_alpha =
back_alpha + src_alpha - (back_alpha * src_alpha / 255);
const uint32_t ratio = src_alpha * 255 / dest_alpha;
return ArgbEncode(dest_alpha,
AlphaMerge(FXARGB_R(back), FXARGB_R(src), ratio),
AlphaMerge(FXARGB_G(back), FXARGB_G(src), ratio),
AlphaMerge(FXARGB_B(back), FXARGB_B(src), ratio));
}
// Porter-Duff over, packed BGRA, normal, no clip. Per pixel:
// dest a == 0 -> copy src src a == 255 -> copy src
// src a == 0 -> keep dest else AlphaMerge/AlphaUnion over
// Byte spans bitcast to u32 lanes -> no 4-byte scanline alignment needed.
void RunBgra2Bgra(pdfium::span<const uint8_t> src, pdfium::span<uint8_t> dest) {
const hn::ScalableTag<uint32_t> d;
const hn::ScalableTag<uint8_t> d8;
const size_t n = hn::Lanes(d); // pixels/vector
const auto m255 = hn::Set(d, 0xFF); // byte mask and the value 255
const auto one = hn::Set(d, 1);
const auto zero = hn::Zero(d);
size_t count = src.size() / 4;
while (count >= n) {
const auto s = hn::BitCast(d, hn::LoadU(d8, src.data()));
const auto back = hn::BitCast(d, hn::LoadU(d8, dest.data()));
const auto sa = hn::ShiftRight<24>(s);
const auto da = hn::ShiftRight<24>(back);
const auto copy_src = hn::Or(hn::Eq(da, zero), hn::Eq(sa, m255));
const auto keep_dest = hn::Eq(sa, zero);
// all copy/keep -> skip divide; copy_src ? src : dest
if (hn::AllTrue(d, hn::Or(copy_src, keep_dest))) {
const auto result = hn::IfThenElse(copy_src, s, back);
hn::StoreU(hn::BitCast(d8, result), d8, dest.data());
src = src.subspan(n * 4);
dest = dest.subspan(n * 4);
count -= n;
continue;
}
// divisor forced >= 1: copy/keep lanes may yield da2 == 0 (unused)
const auto da2 = hn::Sub(hn::Add(da, sa), Div255(hn::Mul(da, sa)));
const auto ratio = hn::Div(hn::Mul(sa, m255), hn::Max(da2, one));
const auto db = hn::And(back, m255);
const auto dg = hn::And(hn::ShiftRight<8>(back), m255);
const auto dr = hn::And(hn::ShiftRight<16>(back), m255);
const auto sb = hn::And(s, m255);
const auto sg = hn::And(hn::ShiftRight<8>(s), m255);
const auto sr = hn::And(hn::ShiftRight<16>(s), m255);
const auto nb = AlphaMergeLanes(db, sb, ratio, m255);
const auto ng = AlphaMergeLanes(dg, sg, ratio, m255);
const auto nr = AlphaMergeLanes(dr, sr, ratio, m255);
const auto gen =
hn::Or(hn::Or(nb, hn::ShiftLeft<8>(ng)),
hn::Or(hn::ShiftLeft<16>(nr), hn::ShiftLeft<24>(da2)));
auto result = hn::IfThenElse(keep_dest, back, gen);
result = hn::IfThenElse(copy_src, s, result);
hn::StoreU(hn::BitCast(d8, result), d8, dest.data());
src = src.subspan(n * 4);
dest = dest.subspan(n * 4);
count -= n;
}
for (size_t i = 0; i < count; ++i) {
const uint32_t s = FXARGB_GetDIB(src.first<4u>());
const uint32_t back = FXARGB_GetDIB(dest.first<4u>());
FXARGB_SetDIB(dest.first<4u>(), CompositePixelBgra2Bgra(s, back));
src = src.subspan(4u);
dest = dest.subspan(4u);
}
}
// Opaque BGRA(4) -> BGRA, alpha forced 0xFF (src alpha ignored). Same byte
// order required: src and dest both packed BGRA.
void RunBgra2BgrxForceOpaque(pdfium::span<const uint8_t> src,
pdfium::span<uint8_t> dest) {
const hn::ScalableTag<uint32_t> d;
const hn::ScalableTag<uint8_t> d8;
const size_t n = hn::Lanes(d);
const auto alpha_ff = hn::Set(d, 0xFF000000u);
size_t count = src.size() / 4;
while (count >= n) {
const auto v = hn::BitCast(d, hn::LoadU(d8, src.data()));
hn::StoreU(hn::BitCast(d8, hn::Or(v, alpha_ff)), d8, dest.data());
src = src.subspan(n * 4);
dest = dest.subspan(n * 4);
count -= n;
}
for (size_t i = 0; i < count; ++i) {
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest[3] = 0xFF;
src = src.subspan(4u);
dest = dest.subspan(4u);
}
}
} // namespace
} // namespace HWY_NAMESPACE
} // namespace fxge_composite_simd
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace fxge_composite_simd {
HWY_EXPORT(RunBgra2Bgra);
HWY_EXPORT(RunBgra2BgrxForceOpaque);
// Unqualified name, in-namespace: a qualified name breaks single-target
// HWY_STATIC_DISPATCH.
void DispatchBgra2Bgra(pdfium::span<const uint8_t> src,
pdfium::span<uint8_t> dest) {
// SAFETY: dispatch indexes Highway's per-target table; no span macro form.
UNSAFE_BUFFERS(HWY_DYNAMIC_DISPATCH(RunBgra2Bgra)(src, dest));
}
void DispatchBgra2BgrxForceOpaque(pdfium::span<const uint8_t> src,
pdfium::span<uint8_t> dest) {
// SAFETY: see DispatchBgra2Bgra().
UNSAFE_BUFFERS(HWY_DYNAMIC_DISPATCH(RunBgra2BgrxForceOpaque)(src, dest));
}
} // namespace fxge_composite_simd
void CompositeRowBgra2BgraSimd(
pdfium::span<const FX_BGRA_STRUCT<uint8_t>> src_span,
pdfium::span<FX_BGRA_STRUCT<uint8_t>> dest_span) {
// `src_span.size()` pixels, like `fxcrt::Zip(src_span, dest_span)`.
CHECK_LE(src_span.size(), dest_span.size());
fxge_composite_simd::DispatchBgra2Bgra(
fxcrt::reinterpret_span<const uint8_t>(src_span),
fxcrt::reinterpret_span<uint8_t>(dest_span));
}
void CompositeRowBgra2BgrxForceOpaqueSimd(
pdfium::span<const FX_BGRA_STRUCT<uint8_t>> src_span,
pdfium::span<FX_BGRA_STRUCT<uint8_t>> dest_span) {
// `src_span.size()` pixels, like `fxcrt::Zip(src_span, dest_span)`.
CHECK_LE(src_span.size(), dest_span.size());
fxge_composite_simd::DispatchBgra2BgrxForceOpaque(
fxcrt::reinterpret_span<const uint8_t>(src_span),
fxcrt::reinterpret_span<uint8_t>(dest_span));
}
#endif // HWY_ONCE