0

Use nearest neighbor filtering for non-translated quads

Draw tiled layer quads that only have a translation transformation
component using nearest neighbor filtering instead of bilinear
filtering. This has two advantages:

1. On some GPUs this can reduce memory bandwidth usage due to increased
   texture cache hit rate.

2. Linear filtering is known to give slightly different results on
   different GPUs because of differences in the texture sampling
   hardware. Avoiding bilinear filtering in the common case (i.e., when
   CSS transformation aren't used) makes WebKit layout test pixel dumps
   better comparable across different devices.

TEST=ResourceProviderTest.ScopedSampler, GLRendererTest2.activeTextureState, manual testing with composited websites.

BUG=


Review URL: https://chromiumcodereview.appspot.com/11358181

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@170944 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
skyostil@chromium.org
2012-12-04 12:18:30 +00:00
parent 364aaef811
commit d1a56f05be
11 changed files with 241 additions and 44 deletions

@ -539,15 +539,6 @@ void GLRenderer::drawRenderPassQuad(DrawingFrame& frame, const RenderPassDrawQua
} else {
filterBitmap = applyFilters(this, renderPass->filters, contentsTexture, m_client->hasImplThread());
}
scoped_ptr<ResourceProvider::ScopedReadLockGL> contentsResourceLock;
unsigned contentsTextureId = 0;
if (filterBitmap.getTexture()) {
GrTexture* texture = reinterpret_cast<GrTexture*>(filterBitmap.getTexture());
contentsTextureId = texture->getTextureHandle();
} else {
contentsResourceLock = make_scoped_ptr(new ResourceProvider::ScopedReadLockGL(m_resourceProvider, contentsTexture->id()));
contentsTextureId = contentsResourceLock->textureId();
}
// Draw the background texture if there is one.
if (backgroundTexture) {
@ -578,7 +569,13 @@ void GLRenderer::drawRenderPassQuad(DrawingFrame& frame, const RenderPassDrawQua
// FIXME: use the backgroundTexture and blend the background in with this draw instead of having a separate copy of the background texture.
context()->bindTexture(GL_TEXTURE_2D, contentsTextureId);
scoped_ptr<ResourceProvider::ScopedReadLockGL> contentsResourceLock;
if (filterBitmap.getTexture()) {
GrTexture* texture = reinterpret_cast<GrTexture*>(filterBitmap.getTexture());
context()->bindTexture(GL_TEXTURE_2D, texture->getTextureHandle());
} else
contentsResourceLock = make_scoped_ptr(new ResourceProvider::ScopedSamplerGL(m_resourceProvider, contentsTexture->id(),
GL_TEXTURE_2D, GL_LINEAR));
int shaderQuadLocation = -1;
int shaderEdgeLocation = -1;
@ -634,7 +631,7 @@ void GLRenderer::drawRenderPassQuad(DrawingFrame& frame, const RenderPassDrawQua
GLC(context(), context()->uniform1i(shaderMaskSamplerLocation, 1));
GLC(context(), context()->uniform2f(shaderMaskTexCoordScaleLocation, quad->mask_tex_coord_scale_x, quad->mask_tex_coord_scale_y));
GLC(context(), context()->uniform2f(shaderMaskTexCoordOffsetLocation, quad->mask_tex_coord_offset_x, quad->mask_tex_coord_offset_y));
context()->bindTexture(GL_TEXTURE_2D, maskTextureId);
m_resourceProvider->bindForSampling(quad->mask_resource_id, GL_TEXTURE_2D, GL_LINEAR);
GLC(context(), context()->activeTexture(GL_TEXTURE0));
}
@ -782,8 +779,9 @@ void GLRenderer::drawTileQuad(const DrawingFrame& frame, const TileDrawQuad* qua
setUseProgram(uniforms.program);
GLC(context(), context()->uniform1i(uniforms.samplerLocation, 0));
ResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resource_id);
GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId()));
bool scaled = (texToGeomScaleX != 1 || texToGeomScaleY != 1);
GLenum filter = (quad->IsAntialiased() || scaled || !quad->quadTransform().IsIdentityOrIntegerTranslation()) ? GL_LINEAR : GL_NEAREST;
ResourceProvider::ScopedSamplerGL quadResourceLock(m_resourceProvider, quad->resource_id, GL_TEXTURE_2D, filter);
bool useAA = !clipped && quad->IsAntialiased();
if (useAA) {
@ -887,15 +885,12 @@ void GLRenderer::drawYUVVideoQuad(const DrawingFrame& frame, const YUVVideoDrawQ
const VideoLayerImpl::FramePlane& uPlane = quad->u_plane;
const VideoLayerImpl::FramePlane& vPlane = quad->v_plane;
ResourceProvider::ScopedReadLockGL yPlaneLock(m_resourceProvider, yPlane.resourceId);
ResourceProvider::ScopedReadLockGL uPlaneLock(m_resourceProvider, uPlane.resourceId);
ResourceProvider::ScopedReadLockGL vPlaneLock(m_resourceProvider, vPlane.resourceId);
GLC(context(), context()->activeTexture(GL_TEXTURE1));
GLC(context(), context()->bindTexture(GL_TEXTURE_2D, yPlaneLock.textureId()));
ResourceProvider::ScopedSamplerGL yPlaneLock(m_resourceProvider, yPlane.resourceId, GL_TEXTURE_2D, GL_LINEAR);
GLC(context(), context()->activeTexture(GL_TEXTURE2));
GLC(context(), context()->bindTexture(GL_TEXTURE_2D, uPlaneLock.textureId()));
ResourceProvider::ScopedSamplerGL uPlaneLock(m_resourceProvider, uPlane.resourceId, GL_TEXTURE_2D, GL_LINEAR);
GLC(context(), context()->activeTexture(GL_TEXTURE3));
GLC(context(), context()->bindTexture(GL_TEXTURE_2D, vPlaneLock.textureId()));
ResourceProvider::ScopedSamplerGL vPlaneLock(m_resourceProvider, vPlane.resourceId, GL_TEXTURE_2D, GL_LINEAR);
setUseProgram(program->program());
@ -1087,8 +1082,7 @@ void GLRenderer::drawTextureQuad(const DrawingFrame& frame, const TextureDrawQua
const gfx::RectF& uvRect = quad->uv_rect;
GLC(context(), context()->uniform4f(binding.texTransformLocation, uvRect.x(), uvRect.y(), uvRect.width(), uvRect.height()));
ResourceProvider::ScopedReadLockGL quadResourceLock(m_resourceProvider, quad->resource_id);
GLC(context(), context()->bindTexture(GL_TEXTURE_2D, quadResourceLock.textureId()));
ResourceProvider::ScopedSamplerGL quadResourceLock(m_resourceProvider, quad->resource_id, GL_TEXTURE_2D, GL_LINEAR);
if (!quad->premultiplied_alpha) {
// As it turns out, the premultiplied alpha blending function (ONE, ONE_MINUS_SRC_ALPHA)

@ -18,6 +18,11 @@
using namespace WebKit;
using namespace WebKitTests;
using testing::_;
using testing::AnyNumber;
using testing::InSequence;
using testing::Mock;
namespace cc {
namespace {
@ -481,12 +486,10 @@ TEST(GLRendererTest2, visibilityChangeIsLastCall)
EXPECT_TRUE(lastCallWasSetVisiblity);
}
class TextureStateTrackingContext : public FakeWebGraphicsContext3D {
public:
TextureStateTrackingContext()
: m_activeTexture(GL_INVALID_ENUM)
, m_inDraw(false)
{
}
@ -497,15 +500,8 @@ public:
return WebString();
}
// We shouldn't set any texture parameters during the draw sequence, although
// we might when creating the quads.
void setInDraw() { m_inDraw = true; }
virtual void texParameteri(WGC3Denum target, WGC3Denum pname, WGC3Dint param)
{
if (m_inDraw)
ADD_FAILURE();
}
MOCK_METHOD3(texParameteri, void(WGC3Denum target, WGC3Denum pname, WGC3Dint param));
MOCK_METHOD4(drawElements, void(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset));
virtual void activeTexture(WGC3Denum texture)
{
@ -516,7 +512,6 @@ public:
WGC3Denum activeTexture() const { return m_activeTexture; }
private:
bool m_inDraw;
WGC3Denum m_activeTexture;
};
@ -528,6 +523,8 @@ TEST(GLRendererTest2, activeTextureState)
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
FakeRendererGL renderer(&fakeClient, resourceProvider.get());
// During initialization we are allowed to set any texture parameters.
EXPECT_CALL(*context, texParameteri(_, _, _)).Times(AnyNumber());
EXPECT_TRUE(renderer.initialize());
cc::RenderPass::Id id(1, 1);
@ -535,7 +532,33 @@ TEST(GLRendererTest2, activeTextureState)
pass->SetNew(id, gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 100, 100), gfx::Transform());
pass->AppendOneOfEveryQuadType(resourceProvider.get());
context->setInDraw();
// Set up expected texture filter state transitions that match the quads
// created in AppendOneOfEveryQuadType().
Mock::VerifyAndClearExpectations(context);
{
InSequence sequence;
// yuv_quad is drawn with the default filter.
EXPECT_CALL(*context, drawElements(_, _, _, _));
// tile_quad is drawn with GL_NEAREST because it is not transformed or
// scaled.
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
EXPECT_CALL(*context, drawElements(_, _, _, _));
// transformed_tile_quad uses GL_LINEAR.
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
EXPECT_CALL(*context, drawElements(_, _, _, _));
// scaled_tile_quad also uses GL_LINEAR.
EXPECT_CALL(*context, drawElements(_, _, _, _));
// The remaining quads also use GL_LINEAR because nearest neighbor
// filtering is currently only used with tile quads.
EXPECT_CALL(*context, drawElements(_, _, _, _)).Times(6);
}
cc::DirectRenderer::DrawingFrame drawingFrame;
renderer.beginDrawingFrame(drawingFrame);
@ -547,6 +570,7 @@ TEST(GLRendererTest2, activeTextureState)
}
renderer.finishDrawingQuadList();
EXPECT_EQ(context->activeTexture(), GL_TEXTURE0);
Mock::VerifyAndClearExpectations(context);
}
} // namespace

@ -60,11 +60,12 @@ ResourceProvider::Resource::Resource()
, markedForDeletion(false)
, size()
, format(0)
, filter(0)
, type(static_cast<ResourceType>(0))
{
}
ResourceProvider::Resource::Resource(unsigned textureId, int pool, const gfx::Size& size, GLenum format)
ResourceProvider::Resource::Resource(unsigned textureId, int pool, const gfx::Size& size, GLenum format, GLenum filter)
: glId(textureId)
, glPixelBufferId(0)
, pixels(0)
@ -77,11 +78,12 @@ ResourceProvider::Resource::Resource(unsigned textureId, int pool, const gfx::Si
, markedForDeletion(false)
, size(size)
, format(format)
, filter(filter)
, type(GLTexture)
{
}
ResourceProvider::Resource::Resource(uint8_t* pixels, int pool, const gfx::Size& size, GLenum format)
ResourceProvider::Resource::Resource(uint8_t* pixels, int pool, const gfx::Size& size, GLenum format, GLenum filter)
: glId(0)
, glPixelBufferId(0)
, pixels(pixels)
@ -94,6 +96,7 @@ ResourceProvider::Resource::Resource(uint8_t* pixels, int pool, const gfx::Size&
, markedForDeletion(false)
, size(size)
, format(format)
, filter(filter)
, type(Bitmap)
{
}
@ -177,7 +180,7 @@ ResourceProvider::ResourceId ResourceProvider::createGLTexture(int pool, const g
GLC(context3d, context3d->texImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0, format, GL_UNSIGNED_BYTE, 0));
ResourceId id = m_nextId++;
Resource resource(textureId, pool, size, format);
Resource resource(textureId, pool, size, format, GL_LINEAR);
m_resources[id] = resource;
return id;
}
@ -189,7 +192,7 @@ ResourceProvider::ResourceId ResourceProvider::createBitmap(int pool, const gfx:
uint8_t* pixels = new uint8_t[size.width() * size.height() * 4];
ResourceId id = m_nextId++;
Resource resource(pixels, pool, size, GL_RGBA);
Resource resource(pixels, pool, size, GL_RGBA, GL_LINEAR);
m_resources[id] = resource;
return id;
}
@ -207,7 +210,7 @@ ResourceProvider::ResourceId ResourceProvider::createResourceFromExternalTexture
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
ResourceId id = m_nextId++;
Resource resource(textureId, 0, gfx::Size(), 0);
Resource resource(textureId, 0, gfx::Size(), 0, GL_LINEAR);
resource.external = true;
m_resources[id] = resource;
return id;
@ -424,6 +427,12 @@ ResourceProvider::ScopedReadLockGL::~ScopedReadLockGL()
m_resourceProvider->unlockForRead(m_resourceId);
}
ResourceProvider::ScopedSamplerGL::ScopedSamplerGL(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId, GLenum target, GLenum filter)
: ScopedReadLockGL(resourceProvider, resourceId)
{
resourceProvider->bindForSampling(resourceId, target, filter);
}
ResourceProvider::ScopedWriteLockGL::ScopedWriteLockGL(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId)
: m_resourceProvider(resourceProvider)
, m_resourceId(resourceId)
@ -616,7 +625,7 @@ void ResourceProvider::receiveFromChild(int child, const TransferableResourceLis
GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, textureId));
GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D, it->mailbox.name));
ResourceId id = m_nextId++;
Resource resource(textureId, childInfo.pool, it->size, it->format);
Resource resource(textureId, childInfo.pool, it->size, it->format, it->filter);
resource.mailbox.setName(it->mailbox.name);
m_resources[id] = resource;
childInfo.parentToChildMap[id] = it->id;
@ -662,6 +671,7 @@ bool ResourceProvider::transferResource(WebGraphicsContext3D* context, ResourceI
return false;
resource->id = id;
resource->format = source->format;
resource->filter = source->filter;
resource->size = source->size;
if (source->mailbox.isZero()) {
@ -840,4 +850,22 @@ void ResourceProvider::setPixelsFromBuffer(ResourceId id)
}
}
void ResourceProvider::bindForSampling(ResourceProvider::ResourceId resourceId, GLenum target, GLenum filter)
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
ResourceMap::iterator it = m_resources.find(resourceId);
DCHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(resource->lockForReadCount);
DCHECK(!resource->lockedForWrite);
GLC(context3d, context3d->bindTexture(target, resource->glId));
if (filter != resource->filter) {
GLC(context3d, context3d->texParameteri(target, GL_TEXTURE_MIN_FILTER, filter));
GLC(context3d, context3d->texParameteri(target, GL_TEXTURE_MAG_FILTER, filter));
resource->filter = filter;
}
}
} // namespace cc

@ -132,6 +132,11 @@ public:
// will wait on it.
void receiveFromParent(const TransferableResourceList&);
// Bind the given GL resource to a texture target for sampling using the
// specified filter for both minification and magnification. The resource
// must be locked for reading.
void bindForSampling(ResourceProvider::ResourceId, GLenum target, GLenum filter);
// The following lock classes are part of the ResourceProvider API and are
// needed to read and write the resource contents. The user must ensure
// that they only use GL locks on GL resources, etc, and this is enforced
@ -151,6 +156,14 @@ public:
DISALLOW_COPY_AND_ASSIGN(ScopedReadLockGL);
};
class CC_EXPORT ScopedSamplerGL : public ScopedReadLockGL {
public:
ScopedSamplerGL(ResourceProvider*, ResourceProvider::ResourceId, GLenum target, GLenum filter);
private:
DISALLOW_COPY_AND_ASSIGN(ScopedSamplerGL);
};
class CC_EXPORT ScopedWriteLockGL {
public:
ScopedWriteLockGL(ResourceProvider*, ResourceProvider::ResourceId);
@ -212,8 +225,8 @@ public:
private:
struct Resource {
Resource();
Resource(unsigned textureId, int pool, const gfx::Size& size, GLenum format);
Resource(uint8_t* pixels, int pool, const gfx::Size& size, GLenum format);
Resource(unsigned textureId, int pool, const gfx::Size& size, GLenum format, GLenum filter);
Resource(uint8_t* pixels, int pool, const gfx::Size& size, GLenum format, GLenum filter);
unsigned glId;
// Pixel buffer used for set pixels without unnecessary copying.
@ -229,6 +242,8 @@ private:
bool markedForDeletion;
gfx::Size size;
GLenum format;
// TODO(skyostil): Use a separate sampler object for filter state.
GLenum filter;
ResourceType type;
};
typedef base::hash_map<ResourceId, Resource> ResourceMap;

@ -10,6 +10,7 @@
#include "cc/scoped_ptr_hash_map.h"
#include "cc/test/compositor_fake_web_graphics_context_3d.h"
#include "cc/test/fake_web_compositor_output_surface.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
@ -18,6 +19,8 @@
using namespace WebKit;
using testing::Mock;
namespace cc {
namespace {
@ -580,6 +583,61 @@ TEST_P(ResourceProviderTest, DeleteTransferredResources)
EXPECT_EQ(0u, childResourceProvider->numResources());
}
class TextureStateTrackingContext : public FakeWebGraphicsContext3D {
public:
MOCK_METHOD2(bindTexture, void(WGC3Denum target, WebGLId texture));
MOCK_METHOD3(texParameteri, void(WGC3Denum target, WGC3Denum pname, WGC3Dint param));
};
TEST_P(ResourceProviderTest, ScopedSampler)
{
// Sampling is only supported for GL textures.
if (GetParam() != ResourceProvider::GLTexture)
return;
scoped_ptr<GraphicsContext> outputSurface(FakeWebCompositorOutputSurface::create(scoped_ptr<WebKit::WebGraphicsContext3D>(new TextureStateTrackingContext)));
TextureStateTrackingContext* context = static_cast<TextureStateTrackingContext*>(outputSurface->context3D());
scoped_ptr<ResourceProvider> resourceProvider(ResourceProvider::create(outputSurface.get()));
gfx::Size size(1, 1);
WGC3Denum format = GL_RGBA;
int pool = 1;
int textureId = 1;
// Check that the texture gets created with the right sampler settings.
EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, textureId));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
ResourceProvider::ResourceId id = resourceProvider->createResource(pool, size, format, ResourceProvider::TextureUsageAny);
// Creating a sampler with the default filter should not change any texture
// parameters.
{
EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, textureId));
ResourceProvider::ScopedSamplerGL sampler(resourceProvider.get(), id, GL_TEXTURE_2D, GL_LINEAR);
}
// Using a different filter should be reflected in the texture parameters.
{
EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, textureId));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
ResourceProvider::ScopedSamplerGL sampler(resourceProvider.get(), id, GL_TEXTURE_2D, GL_NEAREST);
}
// Test resetting to the default filter.
{
EXPECT_CALL(*context, bindTexture(GL_TEXTURE_2D, textureId));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
EXPECT_CALL(*context, texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
ResourceProvider::ScopedSamplerGL sampler(resourceProvider.get(), id, GL_TEXTURE_2D, GL_LINEAR);
}
Mock::VerifyAndClearExpectations(context);
}
INSTANTIATE_TEST_CASE_P(ResourceProviderTests,
ResourceProviderTest,
::testing::Values(ResourceProvider::GLTexture,

@ -100,6 +100,40 @@ void TestRenderPass::AppendOneOfEveryQuadType(cc::ResourceProvider* resourceProv
false);
AppendQuad(texture_quad.PassAs<DrawQuad>());
scoped_ptr<cc::TileDrawQuad> scaled_tile_quad =
cc::TileDrawQuad::Create();
scaled_tile_quad->SetNew(shared_state.get(),
rect,
opaque_rect,
texture_resource,
gfx::RectF(0, 0, 50, 50),
gfx::Size(50, 50),
false,
false,
false,
false,
false);
AppendQuad(scaled_tile_quad.PassAs<DrawQuad>());
scoped_ptr<cc::SharedQuadState> transformed_state = shared_state->Copy();
gfx::Transform rotation;
rotation.Rotate(45);
transformed_state->content_to_target_transform = transformed_state->content_to_target_transform * rotation;
scoped_ptr<cc::TileDrawQuad> transformed_tile_quad =
cc::TileDrawQuad::Create();
transformed_tile_quad->SetNew(transformed_state.get(),
rect,
opaque_rect,
texture_resource,
gfx::RectF(0, 0, 100, 100),
gfx::Size(100, 100),
false,
false,
false,
false,
false);
AppendQuad(transformed_tile_quad.PassAs<DrawQuad>());
scoped_ptr<cc::TileDrawQuad> tile_quad =
cc::TileDrawQuad::Create();
tile_quad->SetNew(shared_state.get(),
@ -133,6 +167,7 @@ void TestRenderPass::AppendOneOfEveryQuadType(cc::ResourceProvider* resourceProv
planes[2]);
AppendQuad(yuv_quad.PassAs<DrawQuad>());
AppendSharedQuadState(transformed_state.Pass());
AppendSharedQuadState(shared_state.Pass());
}

@ -25,8 +25,9 @@ void Mailbox::setName(const GLbyte* n) {
}
TransferableResource::TransferableResource()
: id(0),
format(0) {
: id(0)
, format(0)
, filter(0) {
}
TransferableResource::~TransferableResource() {

@ -26,6 +26,7 @@ struct CC_EXPORT TransferableResource {
unsigned id;
GLenum format;
GLenum filter;
gfx::Size size;
Mailbox mailbox;
};

@ -248,6 +248,18 @@ bool Transform::IsIdentityOrTranslation() const {
return has_no_perspective && has_no_rotation_or_skew && has_no_scale;
}
bool Transform::IsIdentityOrIntegerTranslation() const {
if (!IsIdentityOrTranslation())
return false;
bool no_fractional_translation =
static_cast<int>(matrix_.getDouble(0, 3)) == matrix_.getDouble(0, 3) &&
static_cast<int>(matrix_.getDouble(1, 3)) == matrix_.getDouble(1, 3) &&
static_cast<int>(matrix_.getDouble(2, 3)) == matrix_.getDouble(2, 3);
return no_fractional_translation;
}
bool Transform::IsScaleOrTranslation() const {
if (matrix_.isIdentity())
return true;

@ -73,6 +73,10 @@ class UI_EXPORT Transform {
// Returns true if the matrix is either identity or pure translation.
bool IsIdentityOrTranslation() const;
// Returns true if the matrix is either identity or pure, non-fractional
// translation.
bool IsIdentityOrIntegerTranslation() const;
// Returns true if the matrix is has only scaling and translation components.
bool IsScaleOrTranslation() const;

@ -1158,6 +1158,31 @@ TEST(XFormTest, FactorTRS) {
}
}
TEST(XFormTest, IntegerTranslation) {
gfx::Transform transform;
EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
transform.Translate3d(1, 2, 3);
EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
transform.MakeIdentity();
transform.Translate3d(-1, -2, -3);
EXPECT_TRUE(transform.IsIdentityOrIntegerTranslation());
transform.MakeIdentity();
transform.Translate3d(4.5, 0, 0);
EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
transform.MakeIdentity();
transform.Translate3d(0, -6.7, 0);
EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
transform.MakeIdentity();
transform.Translate3d(0, 0, 8.9);
EXPECT_FALSE(transform.IsIdentityOrIntegerTranslation());
}
} // namespace
} // namespace gfx