Commit 8e76b3b6 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Added Vc implementation of the "over" composite

There is still one bug in both the composites: the calculation
of a single pixel compositions should be done in float instead of
integers, otherwise it causes artifacts on the canvas during painting.
parent c655e2ad
......@@ -26,9 +26,11 @@
#include <KoColorSpaceTraits.h>
#include <KoCompositeOpAlphaDarken.h>
#include <KoCompositeOpOver.h>
#include "KoOptimizedCompositeOpFactory.h"
// for calculation of the needed alignment
#include "config-vc.h"
#ifdef HAVE_VC
#include <Vc/Vc>
#include <Vc/IO>
......@@ -65,10 +67,10 @@ void generateDataLine(uint seed, int numPixels, quint8 *srcPixels, quint8 *dstPi
for (int i = 0; i < numPixels; i++) {
for (int j = 0; j < 4; j++) {
*(srcPixels++) = 50 + qrand() % 205;
*(dstPixels++) = 50 + qrand() % 205;
*(srcPixels++) = 1 + qrand() % 254;
*(dstPixels++) = 1 + qrand() % 254;
}
*(mask++) = 50 + qrand() % 205;
*(mask++) = 1 + qrand() % 254;
}
}
......@@ -102,13 +104,12 @@ struct Tile {
quint8 *dst;
quint8 *mask;
};
void benchmarkCompositeOp(const KoCompositeOp *op,
bool haveMask,
const int srcAlignmentShift,
const int dstAlignmentShift)
#include <stdint.h>
QVector<Tile> generateTiles(int size,
const int srcAlignmentShift,
const int dstAlignmentShift)
{
QVector<Tile> tiles(numTiles);
QVector<Tile> tiles(size);
#ifdef HAVE_VC
const int vecSize = Vc::float_v::Size;
......@@ -116,7 +117,7 @@ void benchmarkCompositeOp(const KoCompositeOp *op,
const int vecSize = 1;
#endif
for (int i = 0; i < numTiles; i++) {
for (int i = 0; i < size; i++) {
tiles[i].src = (quint8*)memalign(vecSize * 4, numPixels * 4 + srcAlignmentShift) + srcAlignmentShift;
tiles[i].dst = (quint8*)memalign(vecSize * 4, numPixels * 4 + dstAlignmentShift) + dstAlignmentShift;
tiles[i].mask = (quint8*)memalign(vecSize, numPixels);
......@@ -124,6 +125,88 @@ void benchmarkCompositeOp(const KoCompositeOp *op,
generateDataLine(1, numPixels, tiles[i].src, tiles[i].dst, tiles[i].mask);
}
return tiles;
}
void freeTiles(QVector<Tile> tiles,
const int srcAlignmentShift,
const int dstAlignmentShift)
{
foreach (const Tile &tile, tiles) {
free(tile.src - srcAlignmentShift);
free(tile.dst - dstAlignmentShift);
free(tile.mask);
}
}
inline bool fuzzyCompare(quint8 a, quint8 b, quint8 prec) {
return qAbs(a - b) <= prec;
}
bool compareTwoOps(bool haveMask, const KoCompositeOp *op1, const KoCompositeOp *op2)
{
QVector<Tile> tiles = generateTiles(2, 16, 16);
KoCompositeOp::ParameterInfo params;
params.dstRowStride = 4 * rowStride;
params.srcRowStride = 4 * rowStride;
params.maskRowStride = rowStride;
params.rows = processRect.height();
params.cols = processRect.width();
params.opacity = 0.5*1.0f;
params.flow = 0.3*1.0f;
params.channelFlags = QBitArray();
params.dstRowStart = tiles[0].dst;
params.srcRowStart = tiles[0].src;
params.maskRowStart = haveMask ? tiles[0].mask : 0;
op1->composite(params);
params.dstRowStart = tiles[1].dst;
params.srcRowStart = tiles[1].src;
params.maskRowStart = haveMask ? tiles[1].mask : 0;
op2->composite(params);
quint8 *dst1 = tiles[0].dst;
quint8 *dst2 = tiles[1].dst;
for (int i = 0; i < numPixels; i++) {
if (!fuzzyCompare(dst1[0], dst2[0], 2) ||
!fuzzyCompare(dst1[1], dst2[1], 2) ||
!fuzzyCompare(dst1[2], dst2[2], 2) ||
!fuzzyCompare(dst1[3], dst2[3], 2)) {
qDebug() << "Wrong result:" << i;
qDebug() << "Act: " << dst1[0] << dst1[1] << dst1[2] << dst1[3];
qDebug() << "Exp: " << dst2[0] << dst2[1] << dst2[2] << dst2[3];
quint8 *src1 = tiles[0].src + 4 * i;
quint8 *src2 = tiles[1].src + 4 * i;
qDebug() << "SrcA:" << src1[0] << src1[1] << src1[2] << src1[3];
qDebug() << "SrcE:" << src2[0] << src2[1] << src2[2] << src2[3];
qDebug() << "MskA:" << tiles[0].mask[i];
qDebug() << "MskE:" << tiles[1].mask[i];
return false;
}
dst1 += 4;
dst2 += 4;
}
freeTiles(tiles, 16, 16);
return true;
}
void benchmarkCompositeOp(const KoCompositeOp *op,
bool haveMask,
const int srcAlignmentShift,
const int dstAlignmentShift)
{
QVector<Tile> tiles =
generateTiles(numTiles, srcAlignmentShift, dstAlignmentShift);
// qDebug() << "Initial values:";
// printData(8, tiles[0].src, tiles[0].dst, tiles[0].mask);
......@@ -151,11 +234,31 @@ void benchmarkCompositeOp(const KoCompositeOp *op,
// qDebug() << "Final values:";
// printData(8, tiles[0].src, tiles[0].dst, tiles[0].mask);
foreach (const Tile &tile, tiles) {
free(tile.src - srcAlignmentShift);
free(tile.dst - dstAlignmentShift);
free(tile.mask);
}
freeTiles(tiles, srcAlignmentShift, dstAlignmentShift);
}
void KisCompositionBenchmark::compareAlphaDarkenOps()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs);
KoCompositeOp *opExp = new KoCompositeOpAlphaDarken<KoBgrU8Traits>(cs);
QVERIFY(compareTwoOps(false, opAct, opExp));
delete opExp;
delete opAct;
}
void KisCompositionBenchmark::compareOverOps()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *opAct = KoOptimizedCompositeOpFactory::createOverOp32(cs);
KoCompositeOp *opExp = new KoCompositeOpOver<KoBgrU8Traits>(cs);
QVERIFY(compareTwoOps(true, opAct, opExp));
delete opExp;
delete opAct;
}
void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenLegacy_Aligned()
......@@ -213,6 +316,46 @@ void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenOptimized_Aligned_NoMa
}
void KisCompositionBenchmark::testRgb8CompositeOverLegacy_Aligned()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *op = new KoCompositeOpOver<KoBgrU8Traits>(cs);
benchmarkCompositeOp(op, true, 0, 0);
delete op;
}
void KisCompositionBenchmark::testRgb8CompositeOverOptimized_Aligned()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp32(cs);
benchmarkCompositeOp(op, true, 0, 0);
delete op;
}
void KisCompositionBenchmark::testRgb8CompositeOverOptimized_Unaligned()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp32(cs);
benchmarkCompositeOp(op, true, 4, 8);
delete op;
}
void KisCompositionBenchmark::testRgb8CompositeOverLegacy_Aligned_NoMask()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *op = new KoCompositeOpOver<KoBgrU8Traits>(cs);
benchmarkCompositeOp(op, false, 0, 0);
delete op;
}
void KisCompositionBenchmark::testRgb8CompositeOverOptimized_Aligned_NoMask()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoCompositeOp *op = KoOptimizedCompositeOpFactory::createOverOp32(cs);
benchmarkCompositeOp(op, false, 0, 0);
delete op;
}
void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenReal_Aligned()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
......@@ -220,5 +363,12 @@ void KisCompositionBenchmark::testRgb8CompositeAlphaDarkenReal_Aligned()
benchmarkCompositeOp(op, true, 0, 0);
}
void KisCompositionBenchmark::testRgb8CompositeOverReal_Aligned()
{
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
const KoCompositeOp *op = cs->compositeOp(COMPOSITE_OVER);
benchmarkCompositeOp(op, true, 0, 0);
}
QTEST_KDEMAIN(KisCompositionBenchmark, GUI)
......@@ -25,6 +25,9 @@ class KisCompositionBenchmark : public QObject
{
Q_OBJECT
private slots:
void compareAlphaDarkenOps();
void compareOverOps();
void testRgb8CompositeAlphaDarkenLegacy_Aligned();
void testRgb8CompositeAlphaDarkenOptimized_Aligned();
......@@ -35,7 +38,15 @@ private slots:
void testRgb8CompositeAlphaDarkenLegacy_Aligned_NoMask();
void testRgb8CompositeAlphaDarkenOptimized_Aligned_NoMask();
void testRgb8CompositeOverLegacy_Aligned();
void testRgb8CompositeOverOptimized_Aligned();
void testRgb8CompositeOverOptimized_Unaligned();
void testRgb8CompositeOverLegacy_Aligned_NoMask();
void testRgb8CompositeOverOptimized_Aligned_NoMask();
void testRgb8CompositeAlphaDarkenReal_Aligned();
void testRgb8CompositeOverReal_Aligned();
};
#endif /* __KIS_COMPOSITION_BENCHMARK_H */
......@@ -24,6 +24,7 @@
#include "KoCompositeOp.h"
#include "KoColorSpace.h"
#include "KoColorSpaceMaths.h"
QString KoCompositeOp::categoryColor()
{
......@@ -94,11 +95,13 @@ void KoCompositeOp::composite(quint8 *dstRowStart, qint32 dstRowStride,
void KoCompositeOp::composite(const KoCompositeOp::ParameterInfo& params) const
{
using namespace Arithmetic;
composite(params.dstRowStart , params.dstRowStride ,
params.srcRowStart , params.srcRowStride ,
params.maskRowStart , params.maskRowStride,
params.rows , params.cols ,
quint8(params.opacity*255.0f), params.channelFlags );
scale<quint8>(params.opacity), params.channelFlags );
}
......
......@@ -65,6 +65,9 @@ struct OptimizedOpsSelector
static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) {
return new KoCompositeOpAlphaDarken<Traits>(cs);
}
static KoCompositeOp* createOverOp(const KoColorSpace *cs) {
return new KoCompositeOpOver<Traits>(cs);
}
};
template<>
......@@ -73,6 +76,9 @@ struct OptimizedOpsSelector<KoRgbU8Traits>
static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs);
}
static KoCompositeOp* createOverOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createOverOp32(cs);
}
};
template<>
......@@ -81,6 +87,9 @@ struct OptimizedOpsSelector<KoBgrU8Traits>
static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs);
}
static KoCompositeOp* createOverOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createOverOp32(cs);
}
};
template<>
......@@ -89,6 +98,9 @@ struct OptimizedOpsSelector<KoLabU8Traits>
static KoCompositeOp* createAlphaDarkenOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(cs);
}
static KoCompositeOp* createOverOp(const KoColorSpace *cs) {
return KoOptimizedCompositeOpFactory::createOverOp32(cs);
}
};
template<class Traits>
......@@ -104,7 +116,7 @@ struct AddGeneralOps<Traits, true>
}
static void add(KoColorSpace* cs) {
cs->addCompositeOp(new KoCompositeOpOver<Traits>(cs));
cs->addCompositeOp(OptimizedOpsSelector<Traits>::createOverOp(cs));
cs->addCompositeOp(OptimizedOpsSelector<Traits>::createAlphaDarkenOp(cs));
cs->addCompositeOp(new KoCompositeOpCopy2<Traits>(cs));
cs->addCompositeOp(new KoCompositeOpErase<Traits>(cs));
......
......@@ -83,24 +83,41 @@ struct AlphaDarkenCompositor32 {
KoStreamedMath::fetch_colors_32<src_aligned>(src, src_c1, src_c2, src_c3);
Vc::float_v dst_blend = src_alpha * uint8MaxRec1;
KoStreamedMath::fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
Vc::float_v alpha1 = src_alpha + dst_alpha -
dst_blend * dst_alpha;
// TODO: if (dstAlpha == 0) dstC = srcC;
dst_c1 = dst_blend * (src_c1 - dst_c1) + dst_c1;
dst_c2 = dst_blend * (src_c2 - dst_c2) + dst_c2;
dst_c3 = dst_blend * (src_c3 - dst_c3) + dst_c3;
Vc::float_m empty_pixels_mask = dst_alpha == Vc::float_v(Vc::Zero);
if (empty_pixels_mask.isFull()) {
dst_c1 = src_c1;
dst_c2 = src_c2;
dst_c3 = src_c3;
} else if (empty_pixels_mask.isEmpty()) {
KoStreamedMath::fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
dst_c1 = dst_blend * (src_c1 - dst_c1) + dst_c1;
dst_c2 = dst_blend * (src_c2 - dst_c2) + dst_c2;
dst_c3 = dst_blend * (src_c3 - dst_c3) + dst_c3;
} else {
KoStreamedMath::fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
dst_c1(empty_pixels_mask) = src_c1;
dst_c2(empty_pixels_mask) = src_c2;
dst_c3(empty_pixels_mask) = src_c3;
Vc::float_m not_empty_pixels_mask = !empty_pixels_mask;
dst_c1(not_empty_pixels_mask) = dst_blend * (src_c1 - dst_c1) + dst_c1;
dst_c2(not_empty_pixels_mask) = dst_blend * (src_c2 - dst_c2) + dst_c2;
dst_c3(not_empty_pixels_mask) = dst_blend * (src_c3 - dst_c3) + dst_c3;
}
Vc::float_m alpha2_mask = opacity > dst_alpha;
Vc::float_m alpha2_mask = opacity_vec > dst_alpha;
Vc::float_v opt1 = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha;
Vc::float_v alpha2;
alpha2(!alpha2_mask) = dst_alpha;
alpha2(alpha2_mask) = opt1;
dst_alpha = (alpha2 - alpha1) * flow_norm_vec + alpha1;
KoStreamedMath::write_channels_32(dst, dst_alpha, dst_c1, dst_c2, dst_c3);
......@@ -112,8 +129,10 @@ struct AlphaDarkenCompositor32 {
* Composes one pixel of the source into the destination
*/
template <bool haveMask>
static ALWAYS_INLINE void compositeOnePixel(const channels_type *src, channels_type *dst, const quint8 *mask, channels_type opacity, channels_type flow)
static ALWAYS_INLINE void compositeOnePixel(const channels_type *src, channels_type *dst, const quint8 *mask, channels_type opacity, channels_type flow, const QBitArray &channelFlags)
{
Q_UNUSED(channelFlags);
using namespace Arithmetic;
const qint32 alpha_pos = 3;
......
......@@ -19,9 +19,15 @@
#include "KoOptimizedCompositeOpFactory.h"
#include "KoOptimizedCompositeOpAlphaDarken32.h"
#include "KoOptimizedCompositeOpOver32.h"
KoCompositeOp* KoOptimizedCompositeOpFactory::createAlphaDarkenOp32(const KoColorSpace *cs)
{
return new KoOptimizedCompositeOpAlphaDarken32(cs);
}
KoCompositeOp* KoOptimizedCompositeOpFactory::createOverOp32(const KoColorSpace *cs)
{
return new KoOptimizedCompositeOpOver32(cs);
}
......@@ -40,6 +40,7 @@ class PIGMENTCMS_EXPORT KoOptimizedCompositeOpFactory
{
public:
static KoCompositeOp* createAlphaDarkenOp32(const KoColorSpace *cs);
static KoCompositeOp* createOverOp32(const KoColorSpace *cs);
};
#endif /* KOOPTIMIZEDCOMPOSITEOPFACTORY_H */
/*
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Silvio Heinrich <plassy@web.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOOPTIMIZEDCOMPOSITEOPOVER32_H_
#define KOOPTIMIZEDCOMPOSITEOPOVER32_H_
#include "KoCompositeOpFunctions.h"
#include "KoCompositeOpBase.h"
#include "KoStreamedMath.h"
template<typename channels_type, typename pixel_type, bool alphaLocked, bool allChannelsFlag>
struct OverCompositor32 {
// \see docs in AlphaDarkenCompositor32
#ifdef HAVE_VC
template<bool haveMask, bool src_aligned>
static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, float flow)
{
Vc::float_v src_alpha;
Vc::float_v dst_alpha;
src_alpha = KoStreamedMath::fetch_alpha_32<src_aligned>(src);
Vc::float_v opacity_norm_vec(opacity);
Vc::float_v uint8Max((float)255.0);
Vc::float_v uint8MaxRec1((float)1.0 / 255.0);
src_alpha *= opacity_norm_vec;
if (haveMask) {
Vc::float_v mask_vec = KoStreamedMath::fetch_mask_8(mask);
src_alpha *= mask_vec * uint8MaxRec1;
}
dst_alpha = KoStreamedMath::fetch_alpha_32<true>(dst);
Vc::float_v src_c1;
Vc::float_v src_c2;
Vc::float_v src_c3;
Vc::float_v dst_c1;
Vc::float_v dst_c2;
Vc::float_v dst_c3;
Vc::float_v new_alpha =
dst_alpha + (uint8Max - dst_alpha) * src_alpha * uint8MaxRec1;
KoStreamedMath::fetch_colors_32<src_aligned>(src, src_c1, src_c2, src_c3);
Vc::float_v src_blend = src_alpha / new_alpha;
KoStreamedMath::fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
dst_c1 = src_blend * (src_c1 - dst_c1) + dst_c1;
dst_c2 = src_blend * (src_c2 - dst_c2) + dst_c2;
dst_c3 = src_blend * (src_c3 - dst_c3) + dst_c3;
KoStreamedMath::write_channels_32(dst, new_alpha, dst_c1, dst_c2, dst_c3);
}
#endif /* HAVE_VC */
template <bool haveMask>
static ALWAYS_INLINE void compositeOnePixel(const channels_type *src, channels_type *dst, const quint8 *mask, channels_type opacity, channels_type flow, const QBitArray &channelFlags)
{
using namespace Arithmetic;
const qint32 alpha_pos = 3;
channels_type srcAlpha = src[alpha_pos];
if (haveMask) {
srcAlpha = mul(scale<channels_type>(*mask), srcAlpha, opacity);
} else if (opacity != unitValue<channels_type>()) {
srcAlpha = mul(srcAlpha, opacity);
}
if (srcAlpha != zeroValue<channels_type>()) {
channels_type dstAlpha = dst[alpha_pos];
channels_type srcBlend;
if (dstAlpha == unitValue<channels_type>()) {
srcBlend = srcAlpha;
} else {
dstAlpha += mul(channels_type(unitValue<channels_type>() - dstAlpha), srcAlpha);
if (dstAlpha != zeroValue<channels_type>()) {
srcBlend = div<channels_type>(srcAlpha, dstAlpha);
} else {
srcBlend = srcAlpha;
}
}
if(allChannelsFlag) {
if (srcBlend != zeroValue<channels_type>()) {
dst[0] = lerp(dst[0], src[0], srcBlend);
dst[1] = lerp(dst[1], src[1], srcBlend);
dst[2] = lerp(dst[2], src[2], srcBlend);
} else {
const pixel_type *s = reinterpret_cast<const pixel_type*>(src);
pixel_type *d = reinterpret_cast<pixel_type*>(dst);
*d = *s;
}
} else {
if (srcBlend != zeroValue<channels_type>()) {
if(channelFlags.at(0)) dst[0] = lerp(dst[0], src[0], srcBlend);
if(channelFlags.at(1)) dst[1] = lerp(dst[1], src[1], srcBlend);
if(channelFlags.at(2)) dst[2] = lerp(dst[2], src[2], srcBlend);
} else {
if(channelFlags.at(0)) dst[0] = src[0];
if(channelFlags.at(1)) dst[1] = src[1];
if(channelFlags.at(2)) dst[2] = src[2];
}
}
if (!alphaLocked) {
dst[alpha_pos] = dstAlpha;
}
}
}
};
/**
* An optimized version of a composite op for the use in 4 byte
* colorspaces with alpha channel placed at the last byte of
* the pixel: C1_C2_C3_A.
*/
class KoOptimizedCompositeOpOver32 : public KoCompositeOp
{
public:
KoOptimizedCompositeOpOver32(const KoColorSpace* cs)
: KoCompositeOp(cs, COMPOSITE_OVER, i18n("Normal"), KoCompositeOp::categoryMix()) {}
using KoCompositeOp::composite;
virtual void composite(const KoCompositeOp::ParameterInfo& params) const
{
if(params.maskRowStart) {
composite<true>(params);
} else {
composite<false>(params);
}
}
template <bool haveMask>
inline void composite(const KoCompositeOp::ParameterInfo& params) const {
if (params.channelFlags.isEmpty() ||
params.channelFlags == QBitArray(4, true)) {
KoStreamedMath::genericComposite32<haveMask, false, OverCompositor32<quint8, quint32, false, true> >(params);
} else {
const bool allChannelsFlag =
params.channelFlags.at(0) &&
params.channelFlags.at(1) &&
params.channelFlags.at(2);
const bool alphaLocked =
!params.channelFlags.at(3);
if (allChannelsFlag && alphaLocked) {
KoStreamedMath::genericComposite32_novector<haveMask, false, OverCompositor32<quint8, quint32, true, true> >(params);
} else if (!allChannelsFlag && !alphaLocked) {
KoStreamedMath::genericComposite32_novector<haveMask, false, OverCompositor32<quint8, quint32, false, true> >(params);
} else /*if (!allChannelsFlag && alphaLocked) */{
KoStreamedMath::genericComposite32_novector<haveMask, false, OverCompositor32<quint8, quint32, true, false> >(params);
}
}
}
};
#endif // KOOPTIMIZEDCOMPOSITEOPOVER32_H_