YUV software decode path stride fixes.
Fix YUV software-decoded video playback for frame with padded strides. * Use per-plane stride info from VideoFrame for source image rect width, when uploading a CPU-allocated video frame. * Modify cc::TextureUploader to handle non-4-byte-aligned texture uploads. * Add testcase UploadContentsTest to check texture upload paths. * Add RoundDown()/RoundUp() to base::bits and use them in media::VideoFrame and cc::TextureUploader. BUG=chromium:160622, chromium:161023 TEST=local build, run on x86 Review URL: https://chromiumcodereview.appspot.com/11413005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168970 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
@ -32,6 +32,11 @@ static const double defaultEstimatedTexturesPerSecond = 48.0 * 60.0;
|
||||
// Flush interval when performing texture uploads.
|
||||
const int textureUploadFlushPeriod = 4;
|
||||
|
||||
unsigned int RoundUp(unsigned int n, unsigned int mul)
|
||||
{
|
||||
return (((n - 1) / mul) * mul) + mul;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
namespace cc {
|
||||
@ -229,11 +234,15 @@ void TextureUploader::uploadWithTexSubImage(const uint8* image,
|
||||
|
||||
const uint8* pixel_source;
|
||||
unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
|
||||
// Use 4-byte row alignment (OpenGL default) for upload performance.
|
||||
// Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
|
||||
unsigned int upload_image_stride =
|
||||
RoundUp(bytes_per_pixel * source_rect.width(), 4);
|
||||
|
||||
if (image_rect.width() == source_rect.width() && !offset.x()) {
|
||||
pixel_source = &image[bytes_per_pixel * offset.y() * image_rect.width()];
|
||||
if (upload_image_stride == image_rect.width() * bytes_per_pixel && !offset.x()) {
|
||||
pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()];
|
||||
} else {
|
||||
size_t needed_size = source_rect.width() * source_rect.height() * bytes_per_pixel;
|
||||
size_t needed_size = upload_image_stride * source_rect.height();
|
||||
if (m_subImageSize < needed_size) {
|
||||
m_subImage.reset(new uint8[needed_size]);
|
||||
m_subImageSize = needed_size;
|
||||
@ -241,7 +250,7 @@ void TextureUploader::uploadWithTexSubImage(const uint8* image,
|
||||
// Strides not equal, so do a row-by-row memcpy from the
|
||||
// paint results into a temp buffer for uploading.
|
||||
for (int row = 0; row < source_rect.height(); ++row)
|
||||
memcpy(&m_subImage[source_rect.width() * bytes_per_pixel * row],
|
||||
memcpy(&m_subImage[upload_image_stride * row],
|
||||
&image[bytes_per_pixel * (offset.x() +
|
||||
(offset.y() + row) * image_rect.width())],
|
||||
source_rect.width() * bytes_per_pixel);
|
||||
@ -294,6 +303,12 @@ void TextureUploader::uploadWithMapTexSubImage(const uint8* image,
|
||||
// Offset from image-rect to source-rect.
|
||||
gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
|
||||
|
||||
unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
|
||||
// Use 4-byte row alignment (OpenGL default) for upload performance.
|
||||
// Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
|
||||
unsigned int upload_image_stride =
|
||||
RoundUp(bytes_per_pixel * source_rect.width(), 4);
|
||||
|
||||
// Upload tile data via a mapped transfer buffer
|
||||
uint8* pixel_dest = static_cast<uint8*>(
|
||||
m_context->mapTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
|
||||
@ -312,17 +327,15 @@ void TextureUploader::uploadWithMapTexSubImage(const uint8* image,
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
|
||||
|
||||
if (image_rect.width() == source_rect.width() && !offset.x()) {
|
||||
if (upload_image_stride == image_rect.width() * bytes_per_pixel && !offset.x()) {
|
||||
memcpy(pixel_dest,
|
||||
&image[offset.y() * image_rect.width() * bytes_per_pixel],
|
||||
image_rect.width() * source_rect.height() * bytes_per_pixel);
|
||||
&image[image_rect.width() * bytes_per_pixel * offset.y()],
|
||||
source_rect.height() * image_rect.width() * bytes_per_pixel);
|
||||
} else {
|
||||
// Strides not equal, so do a row-by-row memcpy from the
|
||||
// paint results into the pixelDest
|
||||
for (int row = 0; row < source_rect.height(); ++row)
|
||||
memcpy(&pixel_dest[source_rect.width() * row * bytes_per_pixel],
|
||||
memcpy(&pixel_dest[upload_image_stride * row],
|
||||
&image[bytes_per_pixel * (offset.x() +
|
||||
(offset.y() + row) * image_rect.width())],
|
||||
source_rect.width() * bytes_per_pixel);
|
||||
|
@ -16,12 +16,42 @@ using namespace WebKit;
|
||||
namespace cc {
|
||||
namespace {
|
||||
|
||||
class FakeWebGraphicsContext3DWithQueryTesting : public FakeWebGraphicsContext3D {
|
||||
unsigned int RoundUp(unsigned int n, unsigned int mul)
|
||||
{
|
||||
return (((n - 1) / mul) * mul) + mul;
|
||||
}
|
||||
|
||||
class FakeWebGraphicsContext3DTextureUpload : public FakeWebGraphicsContext3D {
|
||||
public:
|
||||
FakeWebGraphicsContext3DWithQueryTesting() : m_resultAvailable(0)
|
||||
FakeWebGraphicsContext3DTextureUpload()
|
||||
: m_resultAvailable(0)
|
||||
, m_unpackAlignment(4)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void pixelStorei(WGC3Denum pname, WGC3Dint param)
|
||||
{
|
||||
switch (pname) {
|
||||
case GL_UNPACK_ALIGNMENT:
|
||||
// param should be a power of two <= 8
|
||||
EXPECT_EQ(0, param & (param - 1));
|
||||
EXPECT_GE(8, param);
|
||||
switch (param) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
m_unpackAlignment = param;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void getQueryObjectuivEXT(WebGLId, WGC3Denum type, WGC3Duint* value)
|
||||
{
|
||||
switch (type) {
|
||||
@ -34,66 +64,168 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void texSubImage2D(WGC3Denum target, WGC3Dint level, WGC3Dint xoffset, WGC3Dint yoffset, WGC3Dsizei width, WGC3Dsizei height, WGC3Denum format, WGC3Denum type, const void* pixels)
|
||||
{
|
||||
EXPECT_EQ(GL_TEXTURE_2D, target);
|
||||
EXPECT_EQ(0, level);
|
||||
EXPECT_LE(0, width);
|
||||
EXPECT_LE(0, height);
|
||||
EXPECT_LE(0, xoffset);
|
||||
EXPECT_LE(0, yoffset);
|
||||
EXPECT_LE(0, width);
|
||||
EXPECT_LE(0, height);
|
||||
|
||||
// Check for allowed format/type combination.
|
||||
unsigned int bytesPerPixel = 0;
|
||||
switch (format) {
|
||||
case GL_ALPHA:
|
||||
EXPECT_EQ(GL_UNSIGNED_BYTE, type);
|
||||
bytesPerPixel = 1;
|
||||
break;
|
||||
case GL_RGB:
|
||||
EXPECT_NE(GL_UNSIGNED_SHORT_4_4_4_4, type);
|
||||
EXPECT_NE(GL_UNSIGNED_SHORT_5_5_5_1, type);
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
bytesPerPixel = 3;
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT_5_6_5:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GL_RGBA:
|
||||
EXPECT_NE(GL_UNSIGNED_SHORT_5_6_5, type);
|
||||
switch (type) {
|
||||
case GL_UNSIGNED_BYTE:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT_4_4_4_4:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
case GL_UNSIGNED_SHORT_5_5_5_1:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GL_LUMINANCE:
|
||||
EXPECT_EQ(GL_UNSIGNED_BYTE, type);
|
||||
bytesPerPixel = 1;
|
||||
break;
|
||||
case GL_LUMINANCE_ALPHA:
|
||||
EXPECT_EQ(GL_UNSIGNED_BYTE, type);
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// If NULL, we aren't checking texture contents.
|
||||
if (pixels == NULL)
|
||||
return;
|
||||
|
||||
const uint8* bytes = static_cast<const uint8*>(pixels);
|
||||
// We'll expect the first byte of every row to be 0x1, and the last byte to be 0x2
|
||||
const unsigned int stride = RoundUp(bytesPerPixel * width, m_unpackAlignment);
|
||||
for (WGC3Dsizei row = 0; row < height; ++row) {
|
||||
const uint8* rowBytes = bytes + (xoffset * bytesPerPixel + (yoffset + row) * stride);
|
||||
EXPECT_EQ(0x1, rowBytes[0]);
|
||||
EXPECT_EQ(0x2, rowBytes[width * bytesPerPixel - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void setResultAvailable(unsigned resultAvailable) { m_resultAvailable = resultAvailable; }
|
||||
|
||||
private:
|
||||
unsigned m_unpackAlignment;
|
||||
unsigned m_resultAvailable;
|
||||
};
|
||||
|
||||
void uploadTexture(TextureUploader* uploader)
|
||||
void uploadTexture(TextureUploader* uploader, WGC3Denum format, const gfx::Size& size, const uint8* data)
|
||||
{
|
||||
gfx::Size size(256, 256);
|
||||
uploader->upload(NULL,
|
||||
uploader->upload(data,
|
||||
gfx::Rect(gfx::Point(0, 0), size),
|
||||
gfx::Rect(gfx::Point(0, 0), size),
|
||||
gfx::Vector2d(),
|
||||
GL_RGBA,
|
||||
format,
|
||||
size);
|
||||
}
|
||||
|
||||
TEST(TextureUploaderTest, NumBlockingUploads)
|
||||
{
|
||||
scoped_ptr<FakeWebGraphicsContext3DWithQueryTesting> fakeContext(new FakeWebGraphicsContext3DWithQueryTesting);
|
||||
scoped_ptr<FakeWebGraphicsContext3DTextureUpload> fakeContext(new FakeWebGraphicsContext3DTextureUpload);
|
||||
scoped_ptr<TextureUploader> uploader = TextureUploader::create(fakeContext.get(), false, false);
|
||||
|
||||
fakeContext->setResultAvailable(0);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(1, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(2, uploader->numBlockingUploads());
|
||||
|
||||
fakeContext->setResultAvailable(1);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
}
|
||||
|
||||
TEST(TextureUploaderTest, MarkPendingUploadsAsNonBlocking)
|
||||
{
|
||||
scoped_ptr<FakeWebGraphicsContext3DWithQueryTesting> fakeContext(new FakeWebGraphicsContext3DWithQueryTesting);
|
||||
scoped_ptr<FakeWebGraphicsContext3DTextureUpload> fakeContext(new FakeWebGraphicsContext3DTextureUpload);
|
||||
scoped_ptr<TextureUploader> uploader = TextureUploader::create(fakeContext.get(), false, false);
|
||||
|
||||
fakeContext->setResultAvailable(0);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(2, uploader->numBlockingUploads());
|
||||
|
||||
uploader->markPendingUploadsAsNonBlocking();
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
EXPECT_EQ(1, uploader->numBlockingUploads());
|
||||
|
||||
fakeContext->setResultAvailable(1);
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
uploadTexture(uploader.get());
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(0, 0), NULL);
|
||||
uploader->markPendingUploadsAsNonBlocking();
|
||||
EXPECT_EQ(0, uploader->numBlockingUploads());
|
||||
}
|
||||
|
||||
TEST(TextureUploaderTest, UploadContentsTest)
|
||||
{
|
||||
scoped_ptr<FakeWebGraphicsContext3DTextureUpload> fakeContext(new FakeWebGraphicsContext3DTextureUpload);
|
||||
scoped_ptr<TextureUploader> uploader = TextureUploader::create(fakeContext.get(), false, false);
|
||||
uint8 buffer[256 * 256 * 4];
|
||||
|
||||
// Upload a tightly packed 256x256 RGBA texture.
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
// Mark the beginning and end of each row, for the test.
|
||||
buffer[i * 4 * 256] = 0x1;
|
||||
buffer[(i + 1) * 4 * 256 - 1] = 0x2;
|
||||
}
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(256, 256), buffer);
|
||||
|
||||
// Upload a tightly packed 41x43 RGBA texture.
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
for (int i = 0; i < 43; ++i) {
|
||||
// Mark the beginning and end of each row, for the test.
|
||||
buffer[i * 4 * 41] = 0x1;
|
||||
buffer[(i + 1) * 4 * 41 - 1] = 0x2;
|
||||
}
|
||||
uploadTexture(uploader.get(), GL_RGBA, gfx::Size(41, 43), buffer);
|
||||
|
||||
// Upload a tightly packed 82x86 LUMINANCE texture.
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
for (int i = 0; i < 86; ++i) {
|
||||
// Mark the beginning and end of each row, for the test.
|
||||
buffer[i * 1 * 82] = 0x1;
|
||||
buffer[(i + 1) * 82 - 1] = 0x2;
|
||||
}
|
||||
uploadTexture(uploader.get(), GL_LUMINANCE, gfx::Size(82, 86), buffer);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cc
|
||||
|
@ -374,8 +374,9 @@ bool VideoLayerImpl::copyPlaneData(ResourceProvider* resourceProvider)
|
||||
// Only non-FormatNativeTexture planes should need upload.
|
||||
DCHECK_EQ(plane.format, GL_LUMINANCE);
|
||||
const uint8_t* softwarePlanePixels = m_frame->data(planeIndex);
|
||||
gfx::Rect planeRect(gfx::Point(), plane.size);
|
||||
resourceProvider->setPixels(plane.resourceId, softwarePlanePixels, planeRect, planeRect, gfx::Vector2d());
|
||||
gfx::Rect imageRect(0, 0, m_frame->stride(planeIndex), plane.size.height());
|
||||
gfx::Rect sourceRect(gfx::Point(), plane.size);
|
||||
resourceProvider->setPixels(plane.resourceId, softwarePlanePixels, imageRect, sourceRect, gfx::Vector2d());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
Reference in New Issue
Block a user