Commit bec0951d authored by Tom Anderson's avatar Tom Anderson Committed by Commit Bot

Avoid memcpy after converting YUV to RGB pixels for video frames

This CL avoids using "canvas->drawImage()" in PaintCanvasVideoRenderer::Paint().
Most of the time (when the video isn't rotated etc), all that's necessary is to
convert YUV to RGB pixels. But the drawImage() call will first create a
temporary SkBitmap, copy the frame into that, and then redundantly memcpy() the
bitmap to the resulting canvas.

The memcpy() is very expensive and results in a lot of dropped frames when
playing back 8K video. A single 8K frame is 145MiB, so at 60fps we'd be copying
8.24GiB/s, which uses up most of the RAM bandwidth.

With this change, I can playback 8K video at 60fps with only 49% dropped frames
when I was getting 76% dropped before.

The remaining bottleneck is in ConvertVideoFrameToRGBPixels(): if I remove that
function, I can playback 8K video at 75fps with zero dropped frames (but with no
video :P), so the current plan is to try to parallelize that function after
landing this CL.

BUG=1001207

Change-Id: I0f69988e224d28cd06973beedf00d461ebd5e67e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1788320
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: default avatarKhushal <khushalsagar@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#696919}
parent 6b2e17f0
......@@ -50,6 +50,10 @@ class CC_PAINT_EXPORT PaintCanvas {
// recording or not, so could be simplified or removed.
virtual SkImageInfo imageInfo() const = 0;
virtual void* accessTopLayerPixels(SkImageInfo* info,
size_t* rowBytes,
SkIPoint* origin = nullptr) = 0;
// TODO(enne): It would be nice to get rid of flush() entirely, as it
// doesn't really make sense for recording. However, this gets used by
// PaintCanvasVideoRenderer which takes a PaintCanvas to paint both
......
......@@ -28,6 +28,13 @@ SkImageInfo RecordPaintCanvas::imageInfo() const {
return GetCanvas()->imageInfo();
}
void* RecordPaintCanvas::accessTopLayerPixels(SkImageInfo* info,
size_t* rowBytes,
SkIPoint* origin) {
// Modifications to the underlying pixels cannot be saved.
return nullptr;
}
void RecordPaintCanvas::flush() {
// This is a noop when recording.
}
......
......@@ -31,6 +31,10 @@ class CC_PAINT_EXPORT RecordPaintCanvas final : public PaintCanvas {
SkImageInfo imageInfo() const override;
void* accessTopLayerPixels(SkImageInfo* info,
size_t* rowBytes,
SkIPoint* origin = nullptr) override;
void flush() override;
int save() override;
......
......@@ -24,12 +24,13 @@ SkiaPaintCanvas::SkiaPaintCanvas(SkCanvas* canvas,
SkiaPaintCanvas::SkiaPaintCanvas(const SkBitmap& bitmap,
ImageProvider* image_provider)
: canvas_(new SkCanvas(bitmap)),
bitmap_(bitmap),
owned_(canvas_),
image_provider_(image_provider) {}
SkiaPaintCanvas::SkiaPaintCanvas(const SkBitmap& bitmap,
const SkSurfaceProps& props)
: canvas_(new SkCanvas(bitmap, props)), owned_(canvas_) {}
: canvas_(new SkCanvas(bitmap, props)), bitmap_(bitmap), owned_(canvas_) {}
SkiaPaintCanvas::~SkiaPaintCanvas() = default;
......@@ -37,6 +38,14 @@ SkImageInfo SkiaPaintCanvas::imageInfo() const {
return canvas_->imageInfo();
}
void* SkiaPaintCanvas::accessTopLayerPixels(SkImageInfo* info,
size_t* rowBytes,
SkIPoint* origin) {
if (bitmap_.isNull() || bitmap_.isImmutable())
return nullptr;
return canvas_->accessTopLayerPixels(info, rowBytes, origin);
}
void SkiaPaintCanvas::flush() {
canvas_->flush();
}
......
......@@ -53,6 +53,10 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas {
SkImageInfo imageInfo() const override;
void* accessTopLayerPixels(SkImageInfo* info,
size_t* rowBytes,
SkIPoint* origin = nullptr) override;
void flush() override;
int save() override;
......@@ -130,9 +134,9 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas {
sk_sp<SkData> data) override;
// Don't shadow non-virtual helper functions.
using PaintCanvas::clipPath;
using PaintCanvas::clipRect;
using PaintCanvas::clipRRect;
using PaintCanvas::clipPath;
using PaintCanvas::drawColor;
using PaintCanvas::drawImage;
using PaintCanvas::drawPicture;
......@@ -152,6 +156,7 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas {
}
SkCanvas* canvas_;
SkBitmap bitmap_;
std::unique_ptr<SkCanvas> owned_;
ImageProvider* image_provider_ = nullptr;
......
......@@ -648,7 +648,23 @@ void PaintCanvasVideoRenderer::Paint(scoped_refptr<VideoFrame> video_frame,
.set_image(std::move(non_texture_image), image.content_id())
.TakePaintImage();
}
canvas->drawImage(image, 0, 0, &video_flags);
SkImageInfo info;
size_t row_bytes;
SkIPoint origin;
void* pixels = nullptr;
if (!need_transform && video_frame->IsMappable() &&
flags.getAlpha() == SK_AlphaOPAQUE &&
flags.getBlendMode() == SkBlendMode::kSrc &&
flags.getFilterQuality() == kLow_SkFilterQuality &&
(pixels = canvas->accessTopLayerPixels(&info, &row_bytes, &origin)) &&
info.colorType() == kBGRA_8888_SkColorType) {
const size_t offset = info.computeOffset(origin.x(), origin.y(), row_bytes);
void* const pixels_offset = reinterpret_cast<char*>(pixels) + offset;
ConvertVideoFrameToRGBPixels(video_frame.get(), pixels_offset, row_bytes);
} else {
canvas->drawImage(image, 0, 0, &video_flags);
}
if (need_transform)
canvas->restore();
......
......@@ -19,6 +19,8 @@ namespace blink {
class MockPaintCanvas : public cc::PaintCanvas {
public:
MOCK_CONST_METHOD0(imageInfo, SkImageInfo());
MOCK_METHOD3(accessTopLayerPixels,
void*(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin));
MOCK_METHOD0(flush, void());
MOCK_METHOD0(save, int());
MOCK_METHOD2(saveLayer, int(const SkRect* bounds, const PaintFlags* flags));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment