blob: b5eff37b4eabc28b77d99d84628413913f280695 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <array>
#include <memory>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "cc/paint/image_transfer_cache_entry.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_context_egl.h"
#include "ui/gl/gl_share_group.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/init/create_gr_gl_interface.h"
#include "ui/gl/init/gl_factory.h"
namespace cc {
namespace {
void MarkTextureAsReleased(SkImage::ReleaseContext context) {
auto* released = static_cast<bool*>(context);
DCHECK(!*released);
*released = true;
}
// Uploads a texture corresponding to a single plane in a YUV image. All the
// samples in the plane are set to |value|. The texture is not owned by Skia:
// when Skia doesn't need it anymore, MarkTextureAsReleased() will be called.
sk_sp<SkImage> CreateSolidPlane(GrContext* gr_context,
int width,
int height,
uint8_t value,
bool* released) {
SkBitmap bitmap;
if (!bitmap.tryAllocPixelsFlags(
SkImageInfo::Make(width, height, kGray_8_SkColorType,
kOpaque_SkAlphaType),
SkBitmap::kZeroPixels_AllocFlag)) {
return nullptr;
}
bitmap.eraseColor(SkColorSetRGB(value, value, value));
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
if (!image)
return nullptr;
image = image->makeTextureImage(gr_context, nullptr /* dstColorSpace */,
GrMipMapped::kNo);
if (!image)
return nullptr;
// Take ownership of the backing texture;
GrSurfaceOrigin origin;
image->getBackendTexture(false /* flushPendingGrContextIO */, &origin);
DCHECK_EQ(kGray_8_SkColorType, image->colorType());
GrBackendTexture backend_texture;
SkImage::BackendTextureReleaseProc release_proc;
if (!SkImage::MakeBackendTextureFromSkImage(
gr_context, std::move(image), &backend_texture, &release_proc)) {
return nullptr;
}
*released = false;
return SkImage::MakeFromTexture(gr_context, backend_texture, origin,
kGray_8_SkColorType, kOpaque_SkAlphaType,
nullptr /* colorSpace */,
MarkTextureAsReleased, released);
}
// Checks if all the pixels in |image| are |expected_color|.
bool CheckImageIsSolidColor(const sk_sp<SkImage>& image,
SkColor expected_color) {
DCHECK_GE(image->width(), 1);
DCHECK_GE(image->height(), 1);
SkBitmap bitmap;
if (!bitmap.tryAllocN32Pixels(image->width(), image->height()))
return false;
SkPixmap pixmap;
if (!bitmap.peekPixels(&pixmap))
return false;
if (!image->readPixels(pixmap, 0 /* srcX */, 0 /* srcY */))
return false;
for (int y = 0; y < image->height(); y++) {
for (int x = 0; x < image->width(); x++) {
if (bitmap.getColor(x, y) != expected_color)
return false;
}
}
return true;
}
class ImageTransferCacheEntryTest : public testing::Test {
public:
void SetUp() override {
// Initialize a GL GrContext for Skia.
surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size());
ASSERT_TRUE(surface_);
share_group_ = base::MakeRefCounted<gl::GLShareGroup>();
gl_context_ = base::MakeRefCounted<gl::GLContextEGL>(share_group_.get());
ASSERT_TRUE(gl_context_);
ASSERT_TRUE(
gl_context_->Initialize(surface_.get(), gl::GLContextAttribs()));
ASSERT_TRUE(gl_context_->MakeCurrent(surface_.get()));
sk_sp<GrGLInterface> interface(gl::init::CreateGrGLInterface(
*gl_context_->GetVersionInfo(), false /* use_version_es2 */));
gr_context_ = GrContext::MakeGL(std::move(interface));
ASSERT_TRUE(gr_context_);
}
// Creates the three textures for a 64x64 YUV 4:2:0 image where all the
// samples in all planes are 255u. This corresponds to an RGB color of
// (255, 121, 255) assuming the JPEG YUV-to-RGB conversion formulas. Returns a
// list of 3 SkImages backed by the Y, U, and V textures in that order.
// |release_flags| is set to a list of 3 boolean flags initialized to false.
// Each flag corresponds to a plane (YUV order). When the texture for that
// plane is released by Skia, that flag will be set to true. Returns an empty
// vector on failure.
std::vector<sk_sp<SkImage>> CreateTestYUVImage(
std::array<bool, 3u>* release_flags) {
*release_flags = {false, false, false};
std::vector<sk_sp<SkImage>> plane_images = {
CreateSolidPlane(gr_context(), 64, 64, 255u, &release_flags->at(0)),
CreateSolidPlane(gr_context(), 32, 32, 255u, &release_flags->at(1)),
CreateSolidPlane(gr_context(), 32, 32, 255u, &release_flags->at(2))};
for (const auto& plane_image : plane_images) {
if (plane_image)
textures_to_free_.push_back(plane_image->getBackendTexture(false));
}
if (plane_images[0] && plane_images[1] && plane_images[2])
return plane_images;
return {};
}
void DeletePendingTextures() {
DCHECK(gr_context_);
for (const auto& texture : textures_to_free_) {
if (texture.isValid())
gr_context_->deleteBackendTexture(texture);
}
gr_context_->flush();
textures_to_free_.clear();
}
void TearDown() override {
DeletePendingTextures();
gr_context_.reset();
surface_->PrepareToDestroy(gl_context_->IsCurrent(surface_.get()));
surface_.reset();
gl_context_.reset();
share_group_.reset();
}
GrContext* gr_context() const { return gr_context_.get(); }
private:
std::vector<GrBackendTexture> textures_to_free_;
scoped_refptr<gl::GLSurface> surface_;
scoped_refptr<gl::GLShareGroup> share_group_;
scoped_refptr<gl::GLContext> gl_context_;
sk_sp<GrContext> gr_context_;
};
TEST_F(ImageTransferCacheEntryTest, HardwareDecodedNoMipsAtCreation) {
std::array<bool, 3u> release_flags;
std::vector<sk_sp<SkImage>> plane_images = CreateTestYUVImage(&release_flags);
ASSERT_EQ(3u, plane_images.size());
// Create a service-side image cache entry backed by these planes and do not
// request generating mipmap chains. The |buffer_byte_size| is only used for
// accounting, so we just set it to 0u.
auto entry(std::make_unique<ServiceImageTransferCacheEntry>());
EXPECT_TRUE(entry->BuildFromHardwareDecodedImage(
gr_context(), std::move(plane_images), 0u /* buffer_byte_size */,
false /* needs_mips */, nullptr /* target_color_space */));
// We didn't request generating mipmap chains, so the textures we created
// above should stay alive until after the cache entry is deleted.
EXPECT_FALSE(release_flags[0]);
EXPECT_FALSE(release_flags[1]);
EXPECT_FALSE(release_flags[2]);
entry.reset();
EXPECT_TRUE(release_flags[0]);
EXPECT_TRUE(release_flags[1]);
EXPECT_TRUE(release_flags[2]);
}
TEST_F(ImageTransferCacheEntryTest, HardwareDecodedMipsAtCreation) {
std::array<bool, 3u> release_flags;
std::vector<sk_sp<SkImage>> plane_images = CreateTestYUVImage(&release_flags);
ASSERT_EQ(3u, plane_images.size());
// Create a service-side image cache entry backed by these planes and request
// generating mipmap chains at creation time. The |buffer_byte_size| is only
// used for accounting, so we just set it to 0u.
auto entry(std::make_unique<ServiceImageTransferCacheEntry>());
EXPECT_TRUE(entry->BuildFromHardwareDecodedImage(
gr_context(), std::move(plane_images), 0u /* buffer_byte_size */,
true /* needs_mips */, nullptr /* target_color_space */));
// We requested generating mipmap chains at creation time, so the textures we
// created above should be released by now.
EXPECT_TRUE(release_flags[0]);
EXPECT_TRUE(release_flags[1]);
EXPECT_TRUE(release_flags[2]);
DeletePendingTextures();
// Make sure that when we read the pixels from the YUV image, we get the
// correct RGB color corresponding to the planes created previously. This
// basically checks that after deleting the original YUV textures, the new
// YUV image is backed by the correct mipped planes.
ASSERT_TRUE(entry->image());
EXPECT_TRUE(
CheckImageIsSolidColor(entry->image(), SkColorSetRGB(255, 121, 255)));
}
TEST_F(ImageTransferCacheEntryTest, HardwareDecodedMipsAfterCreation) {
std::array<bool, 3u> release_flags;
std::vector<sk_sp<SkImage>> plane_images = CreateTestYUVImage(&release_flags);
ASSERT_EQ(3u, plane_images.size());
// Create a service-side image cache entry backed by these planes and do not
// request generating mipmap chains at creation time. The |buffer_byte_size|
// is only used for accounting, so we just set it to 0u.
auto entry(std::make_unique<ServiceImageTransferCacheEntry>());
EXPECT_TRUE(entry->BuildFromHardwareDecodedImage(
gr_context(), std::move(plane_images), 0u /* buffer_byte_size */,
false /* needs_mips */, nullptr /* target_color_space */));
// We didn't request generating mip chains, so the textures we created above
// should stay alive for now.
EXPECT_FALSE(release_flags[0]);
EXPECT_FALSE(release_flags[1]);
EXPECT_FALSE(release_flags[2]);
// Now request generating the mip chains.
entry->EnsureMips();
// Now the original textures should have been released.
EXPECT_TRUE(release_flags[0]);
EXPECT_TRUE(release_flags[1]);
EXPECT_TRUE(release_flags[2]);
DeletePendingTextures();
// Make sure that when we read the pixels from the YUV image, we get the
// correct RGB color corresponding to the planes created previously. This
// basically checks that after deleting the original YUV textures, the new
// YUV image is backed by the correct mipped planes.
ASSERT_TRUE(entry->image());
EXPECT_TRUE(
CheckImageIsSolidColor(entry->image(), SkColorSetRGB(255, 121, 255)));
}
} // namespace
} // namespace cc