Optimization (multithreading)

This commit is contained in:
Jakub Melka 2021-02-13 17:09:57 +01:00
parent 14cd4c3dd0
commit f020098435
5 changed files with 211 additions and 39 deletions

View File

@ -16,6 +16,7 @@
// along with Pdf4Qt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfcms.h"
#include "pdfexecutionpolicy.h"
#include <QApplication>
#include <QReadWriteLock>
@ -329,7 +330,52 @@ bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams&
inputPixelCount == outputPixelCount)
{
PDFColorBuffer outputBuffer = params.output;
if (inputPixelCount > params.multithreadingThreshold)
{
struct TransformInfo
{
inline TransformInfo(const float* source, float* target, cmsUInt32Number pixelCount) :
source(source),
target(target),
pixelCount(pixelCount)
{
}
const float* source = nullptr;
float* target = nullptr;
cmsUInt32Number pixelCount = 0;
};
const cmsUInt32Number blockSize = 4096;
std::vector<TransformInfo> infos;
infos.reserve(inputPixelCount / blockSize + 1);
const float* sourcePointer = inputColors;
float* targetPointer = outputBuffer.begin();
cmsUInt32Number remainingPixelCount = inputPixelCount;
while (remainingPixelCount > 0)
{
const cmsUInt32Number currentPixelCount = qMin(blockSize, remainingPixelCount);
infos.emplace_back(sourcePointer, targetPointer, currentPixelCount);
sourcePointer += currentPixelCount * inputChannels;
targetPointer += currentPixelCount * outputChannels;
remainingPixelCount -= currentPixelCount;
}
auto processEntry = [transform](const TransformInfo& info)
{
cmsDoTransform(transform, info.source, info.target, info.pixelCount);
};
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, infos.begin(), infos.end(), processEntry);
}
else
{
// Single-threaded transform
cmsDoTransform(transform, inputColors, outputBuffer.begin(), inputPixelCount);
}
if (isOutputCMYK)
{

View File

@ -225,6 +225,8 @@ public:
PDFColorBuffer output;
RenderingIntent intent = RenderingIntent::Unknown;
PDFInteger multithreadingThreshold = 4096;
};
/// Transforms color between two color spaces. Doesn't do soft-proofing,

View File

@ -18,6 +18,7 @@
#include "pdftransparencyrenderer.h"
#include "pdfdocument.h"
#include "pdfcms.h"
#include "pdfexecutionpolicy.h"
namespace pdf
{
@ -566,10 +567,12 @@ PDFTransparencyRenderer::PDFTransparencyRenderer(const PDFPage* page,
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFInkMapper* inkMapper,
PDFTransparencyRendererSettings settings,
QMatrix pagePointToDevicePointMatrix) :
BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, PDFMeshQualitySettings()),
m_inkMapper(inkMapper),
m_active(false)
m_active(false),
m_settings(settings)
{
m_deviceColorSpace.reset(new PDFDeviceRGBColorSpace());
m_processColorSpace.reset(new PDFDeviceCMYKColorSpace());
@ -751,6 +754,39 @@ QImage PDFTransparencyRenderer::toImage(bool use16Bit, bool usePaper, PDFRGB pap
return image;
}
void PDFTransparencyRenderer::performPixelSampling(const PDFReal shape,
const PDFReal opacity,
const uint8_t shapeChannel,
const uint8_t opacityChannel,
const uint8_t colorChannelStart,
const uint8_t colorChannelEnd,
int x,
int y,
const PDFMappedColor& fillColor,
const PDFPainterPathSampler& clipSampler,
const PDFPainterPathSampler& pathSampler)
{
const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
const PDFColorComponent shapeValue = objectShapeValue * clipValue * shape;
if (shapeValue > 0.0f)
{
// We consider old object shape - we use Union function to
// set shape channel value.
PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
pixel[opacityChannel] = pixel[shapeChannel] * opacity;
// Copy color
for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
{
pixel[colorChannelIndex] = fillColor.mappedColor[colorChannelIndex];
}
}
}
void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
Q_UNUSED(fillRule);
@ -784,28 +820,42 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, fillRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
const PDFMappedColor& fillColor = getMappedFillColor();
for (int x = fillRect.left(); x < fillRect.right(); ++x)
if (isMultithreadedPathSamplingUsed(fillRect))
{
for (int y = fillRect.top(); y < fillRect.bottom(); ++y)
if (fillRect.width() > fillRect.height())
{
const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
const PDFColorComponent shapeValue = objectShapeValue * clipValue * shapeFilling;
if (shapeValue > 0.0f)
// Columns
PDFIntegerRange<int> range(fillRect.left(), fillRect.right() + 1);
auto processEntry = [&, this](int x)
{
// We consider old object shape - we use Union function to
// set shape channel value.
PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
pixel[opacityChannel] = pixel[shapeChannel] * opacityFilling;
// Copy color
for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
for (int y = fillRect.top(); y <= fillRect.bottom(); ++y)
{
pixel[colorChannelIndex] = fillColor.mappedColor[colorChannelIndex];
performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
}
};
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
}
else
{
// Rows
PDFIntegerRange<int> range(fillRect.top(), fillRect.bottom() + 1);
auto processEntry = [&, this](int y)
{
for (int x = fillRect.left(); x <= fillRect.right(); ++x)
{
performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
}
};
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
}
}
else
{
for (int x = fillRect.left(); x <= fillRect.right(); ++x)
{
for (int y = fillRect.top(); y <= fillRect.bottom(); ++y)
{
performPixelSampling(shapeFilling, opacityFilling, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, fillColor, clipSampler, pathSampler);
}
}
}
@ -843,28 +893,42 @@ void PDFTransparencyRenderer::performPathPainting(const QPainterPath& path, bool
PDFPainterPathSampler pathSampler(worldPath, m_settings.samplesCount, 0.0f, strokeRect, m_settings.flags.testFlag(PDFTransparencyRendererSettings::PrecisePathSampler));
const PDFMappedColor& strokeColor = getMappedStrokeColor();
for (int x = strokeRect.left(); x < strokeRect.right(); ++x)
if (isMultithreadedPathSamplingUsed(strokeRect))
{
for (int y = strokeRect.top(); y < strokeRect.bottom(); ++y)
if (strokeRect.width() > strokeRect.height())
{
const PDFColorComponent clipValue = clipSampler.sample(QPoint(x, y));
const PDFColorComponent objectShapeValue = pathSampler.sample(QPoint(x, y));
const PDFColorComponent shapeValue = objectShapeValue * clipValue * shapeStroking;
if (shapeValue > 0.0f)
// Columns
PDFIntegerRange<int> range(strokeRect.left(), strokeRect.right() + 1);
auto processEntry = [&, this](int x)
{
// We consider old object shape - we use Union function to
// set shape channel value.
PDFColorBuffer pixel = m_drawBuffer.getPixel(x, y);
pixel[shapeChannel] = PDFBlendFunction::blend_Union(shapeValue, pixel[shapeChannel]);
pixel[opacityChannel] = pixel[shapeChannel] * opacityStroking;
// Copy color
for (uint8_t colorChannelIndex = colorChannelStart; colorChannelIndex < colorChannelEnd; ++colorChannelIndex)
for (int y = strokeRect.top(); y <= strokeRect.bottom(); ++y)
{
pixel[colorChannelIndex] = strokeColor.mappedColor[colorChannelIndex];
performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
}
};
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
}
else
{
// Rows
PDFIntegerRange<int> range(strokeRect.top(), strokeRect.bottom() + 1);
auto processEntry = [&, this](int y)
{
for (int x = strokeRect.left(); x <= strokeRect.right(); ++x)
{
performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
}
};
PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), processEntry);
}
}
else
{
for (int x = strokeRect.left(); x <= strokeRect.right(); ++x)
{
for (int y = strokeRect.top(); y <= strokeRect.bottom(); ++y)
{
performPixelSampling(shapeStroking, opacityStroking, shapeChannel, opacityChannel, colorChannelStart, colorChannelEnd, x, y, strokeColor, clipSampler, pathSampler);
}
}
}
@ -1349,6 +1413,16 @@ void PDFTransparencyRenderer::flushDrawBuffer()
}
}
bool PDFTransparencyRenderer::isMultithreadedPathSamplingUsed(QRect fillRect) const
{
if (!m_settings.flags.testFlag(PDFTransparencyRendererSettings::MultithreadedPathSampler))
{
return false;
}
return fillRect.width() * fillRect.height() > m_settings.multithreadingPathSampleThreshold && fillRect.width() > 1;
}
PDFInkMapper::PDFInkMapper(const PDFDocument* document) :
m_document(document)
{
@ -1746,7 +1820,7 @@ void PDFPainterPathSampler::prepareScanLines()
return;
}
for (int yOffset = m_fillRect.top(); yOffset < m_fillRect.bottom(); ++yOffset)
for (int yOffset = m_fillRect.top(); yOffset <= m_fillRect.bottom(); ++yOffset)
{
const qreal coordY1 = yOffset;
const qreal coordY2 = coordY1 + 1.0;

View File

@ -439,13 +439,24 @@ struct PDFTransparencyRendererSettings
/// Sample count for MSAA antialiasing
int samplesCount = 16;
/// Threshold for turning on painter path
/// multithreaded painting. When number of potential
/// pixels of painter path is greater than this constant,
/// and MultithreadedPathSampler flag is turned on,
/// multithreaded painting is performed.
int multithreadingPathSampleThreshold = 128;
enum Flag
{
None = 0x0000,
/// Use precise path sampler, which uses paths instead
/// of filling polygon.
PrecisePathSampler = 0x0001
PrecisePathSampler = 0x0001,
/// Use multithreading when painter paths are painted?
/// Multithreading is used to
MultithreadedPathSampler = 0x0002
};
Q_DECLARE_FLAGS(Flags, Flag)
@ -471,6 +482,7 @@ public:
const PDFCMS* cms,
const PDFOptionalContentActivity* optionalContentActivity,
const PDFInkMapper* inkMapper,
PDFTransparencyRendererSettings settings,
QMatrix pagePointToDevicePointMatrix);
/// Sets device color space. This is final color space, to which
@ -596,6 +608,37 @@ private:
/// Flushes draw buffer
void flushDrawBuffer();
/// Returns true, if multithreaded painter path sampling should be used
/// for a given fill rectangle.
/// \param fillRect Fill rectangle
/// \returns true, if multithreading should be used
bool isMultithreadedPathSamplingUsed(QRect fillRect) const;
/// Performs sampling of single pixel. Sampled pixel is painted
/// into the draw buffer.
/// \param shape Constant shape value
/// \param opacity Constant opacity value
/// \param x Horizontal coordinate of the pixel
/// \param y Vertical coordinate of the pixel
/// \param shapeChannel Shape channel (draw buffer)
/// \param opacityChannel Opacity channel (draw buffer)
/// \param colorChannelStart Color channel start (draw buffer)
/// \param colorChannelEnd Color channel end (draw buffer)
/// \param fillColor Fill color
/// \param clipSample Clipping sampler
/// \param pathSampler Path sampler
void performPixelSampling(const PDFReal shape,
const PDFReal opacity,
const uint8_t shapeChannel,
const uint8_t opacityChannel,
const uint8_t colorChannelStart,
const uint8_t colorChannelEnd,
int x,
int y,
const PDFMappedColor& fillColor,
const PDFPainterPathSampler& clipSampler,
const PDFPainterPathSampler& pathSampler);
PDFColorSpacePointer m_deviceColorSpace; ///< Device color space (color space for final result)
PDFColorSpacePointer m_processColorSpace; ///< Process color space (color space, in which is page graphic's blended)
std::unique_ptr<PDFTransparencyGroupGuard> m_pageTransparencyGroupGuard;

View File

@ -71,10 +71,17 @@ void OutputPreviewDialog::updateImage()
ui->imageLabel->setPixmap(QPixmap());
}
pdf::PDFTransparencyRendererSettings settings;
// Jakub Melka: debug is very slow, use multithreading
#ifdef QT_DEBUG
settings.flags.setFlag(pdf::PDFTransparencyRendererSettings::MultithreadedPathSampler, true);
#endif
QMatrix pagePointToDevicePoint = pdf::PDFRenderer::createPagePointToDevicePointMatrix(page, QRect(QPoint(0, 0), imageSize));
pdf::PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy();
pdf::PDFCMSPointer cms = proxy->getCMSManager()->getCurrentCMS();
pdf::PDFTransparencyRenderer renderer(page, m_document, proxy->getFontCache(), cms.data(), proxy->getOptionalContentActivity(), &m_inkMapper, pagePointToDevicePoint);
pdf::PDFTransparencyRenderer renderer(page, m_document, proxy->getFontCache(), cms.data(), proxy->getOptionalContentActivity(), &m_inkMapper, settings, pagePointToDevicePoint);
renderer.beginPaint(imageSize);
renderer.processContents();