ImBuf: Use templating for IMB_transform.

Reduce the inner loop of IMB_transform by extracting writing to an
output buffer in a template. This reduces a branch in the inner loop and
would allow different number of channels in the future.
This commit is contained in:
Jeroen Bakker 2021-12-10 15:19:41 +01:00
parent 37e799e299
commit f76e04bf4d
Notes: blender-bot 2023-02-14 06:23:08 +01:00
Referenced by commit 5a3d5f751f, Revert recent changes to ImBuf transform
1 changed files with 133 additions and 31 deletions

View File

@ -83,23 +83,137 @@ struct TransformUserData {
}
};
template<eIMBTransformMode Mode, InterpolationColorFunction ColorInterpolation, int ChannelLen = 4>
class ScanlineProcessor {
private:
void pixel_from_buffer(const struct ImBuf *ibuf, unsigned char **outI, float **outF, int y) const
/**
* \brief Base class for source discarding.
*
* The class decides if a specific uv coordinate from the source buffer should be ignored.
* This is used to mix multiple images over a single output buffer. Discarded pixels will
* not change the output buffer.
*/
class BaseDiscard {
public:
virtual ~BaseDiscard() = default;
/**
* \brief Should the source pixel at the given uv coordinate be discarded.
*/
virtual bool should_discard(const TransformUserData &user_data, const float uv[2]) = 0;
};
/**
* \brief Crop uv-coordinates that are outside the user data src_crop rect.
*/
class CropSource : public BaseDiscard {
public:
/**
* \brief Should the source pixel at the given uv coordinate be discarded.
*
* Uses user_data.src_crop to determine if the uv coordinate should be skipped.
*/
virtual bool should_discard(const TransformUserData &user_data, const float uv[2])
{
const size_t offset = ((size_t)ibuf->x) * y * ChannelLen;
return uv[0] < user_data.src_crop.xmin && uv[0] >= user_data.src_crop.xmax &&
uv[1] < user_data.src_crop.ymin && uv[1] >= user_data.src_crop.ymax;
}
};
if (ibuf->rect) {
*outI = (unsigned char *)ibuf->rect + offset;
/**
* \brief Discard that does not discard anything.
*/
class NoDiscard : public BaseDiscard {
public:
/**
* \brief Should the source pixel at the given uv coordinate be discarded.
*
* Will never discard any pixels.
*/
virtual bool should_discard(const TransformUserData &UNUSED(user_data),
const float UNUSED(uv[2]))
{
return false;
}
};
/**
* \brief pointer to a texel to write or read serial.
*/
template<
/**
* \brief Kind of buffer.
* Possible options: float, unsigned char.
*/
typename ImBufStorageType = float,
/**
* \brief Number of channels of a single pixel.
*/
int NumChannels = 4>
class TexelPointer {
ImBufStorageType *pointer;
public:
void init_pixel_pointer(const ImBuf *image_buffer, int x, int y)
{
const size_t offset = (y * (size_t)image_buffer->x + x) * NumChannels;
if constexpr (std::is_same_v<ImBufStorageType, float>) {
pointer = image_buffer->rect_float + offset;
}
if (ibuf->rect_float) {
*outF = ibuf->rect_float + offset;
else if constexpr (std::is_same_v<ImBufStorageType, unsigned char>) {
pointer = image_buffer->rect + offset;
}
else {
pointer = nullptr;
}
}
float *get_float_pointer()
{
if constexpr (std::is_same_v<ImBufStorageType, float>) {
return pointer;
}
else {
return nullptr;
}
}
unsigned char *get_uchar_pointer()
{
if constexpr (std::is_same_v<ImBufStorageType, unsigned char>) {
return pointer;
}
else {
return nullptr;
}
}
void increase_pixel_pointer()
{
pointer += NumChannels;
}
};
template<
/**
* \brief Discard function to use.
*
* \attention Should be a subclass of BaseDiscard.
*/
typename Discard,
/**
* \brief Color interpolation function to read from the source buffer.
*/
InterpolationColorFunction ColorInterpolation,
/**
* \brief Kernel to store to the destination buffer.
* Should be an TexelPointer
*/
typename OutputTexelPointer>
class ScanlineProcessor {
Discard discarder;
OutputTexelPointer output;
public:
void process(const TransformUserData *user_data, int scanline)
{
@ -108,27 +222,15 @@ class ScanlineProcessor {
float uv[2];
madd_v2_v2v2fl(uv, user_data->start_uv, user_data->add_y, scanline);
unsigned char *outI = nullptr;
float *outF = nullptr;
pixel_from_buffer(user_data->dst, &outI, &outF, scanline);
output.init_pixel_pointer(user_data->dst, 0, scanline);
for (int xi = 0; xi < width; xi++) {
if constexpr (Mode == IMB_TRANSFORM_MODE_CROP_SRC) {
if (uv[0] >= user_data->src_crop.xmin && uv[0] < user_data->src_crop.xmax &&
uv[1] >= user_data->src_crop.ymin && uv[1] < user_data->src_crop.ymax) {
ColorInterpolation(user_data->src, outI, outF, uv[0], uv[1]);
}
}
else {
ColorInterpolation(user_data->src, outI, outF, uv[0], uv[1]);
if (!discarder.should_discard(*user_data, uv)) {
ColorInterpolation(
user_data->src, output.get_uchar_pointer(), output.get_float_pointer(), uv[0], uv[1]);
}
add_v2_v2(uv, user_data->add_x);
if (outI) {
outI += ChannelLen;
}
if (outF) {
outF += ChannelLen;
}
output.increase_pixel_pointer();
}
}
};
@ -147,13 +249,13 @@ ScanlineThreadFunc get_scanline_function(const eIMBTransformMode mode)
switch (mode) {
case IMB_TRANSFORM_MODE_REGULAR:
return transform_scanline_function<
ScanlineProcessor<IMB_TRANSFORM_MODE_REGULAR, DefaultFunction>>;
ScanlineProcessor<NoDiscard, DefaultFunction, TexelPointer<float, 4>>>;
case IMB_TRANSFORM_MODE_CROP_SRC:
return transform_scanline_function<
ScanlineProcessor<IMB_TRANSFORM_MODE_CROP_SRC, DefaultFunction>>;
ScanlineProcessor<CropSource, DefaultFunction, TexelPointer<float, 4>>>;
case IMB_TRANSFORM_MODE_WRAP_REPEAT:
return transform_scanline_function<
ScanlineProcessor<IMB_TRANSFORM_MODE_WRAP_REPEAT, WrapRepeatFunction>>;
ScanlineProcessor<NoDiscard, WrapRepeatFunction, TexelPointer<float, 4>>>;
}
BLI_assert_unreachable();