| // |
| // Copyright 2020 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| // BootAnimationTest.cpp: Tests that make the same gl calls as Android's boot animations |
| |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| #include "common/debug.h" |
| #include "util/test_utils.h" |
| |
| using namespace angle; |
| |
| // Makes the same GLES 1 calls as Android's default boot animation |
| // The original animation uses 2 different images - |
| // One image acts as a mask and one that moves(a gradient that acts as a shining light) |
| // We do the same here except with different images of much smaller resolution |
| // The results of each frame of the animation are compared against expected values |
| // The original source of the boot animation can be found here: |
| // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/cmds/bootanimation/BootAnimation.cpp#422 |
| class BootAnimationTest : public ANGLETest<> |
| { |
| protected: |
| BootAnimationTest() |
| { |
| setWindowWidth(kWindowWidth); |
| setWindowHeight(kWindowHeight); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| } |
| |
| void initTextureWithData(GLuint *texture, |
| const void *data, |
| GLint width, |
| GLint height, |
| unsigned int channels) |
| { |
| GLint crop[4] = {0, height, width, -height}; |
| |
| glGenTextures(1, texture); |
| glBindTexture(GL_TEXTURE_2D, *texture); |
| |
| switch (channels) |
| { |
| case 3: |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, |
| GL_UNSIGNED_SHORT_5_6_5, data); |
| break; |
| case 4: |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| data); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| |
| glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); |
| glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); |
| glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); |
| } |
| |
| void testSetUp() override |
| { |
| EGLWindow *window = getEGLWindow(); |
| mDisplay = window->getDisplay(); |
| mSurface = window->getSurface(); |
| |
| /** |
| * The mask is a 4 by 1 texture colored: |
| * --- --- --- --- |
| * |B| |A| |B| |A| |
| * --- --- --- --- |
| * B is black, A is black with alpha of 0xFF |
| */ |
| constexpr GLubyte kMask[] = { |
| 0x0, 0x0, 0x0, 0xff, // black |
| 0x0, 0x0, 0x0, 0x0, // transparent black |
| 0x0, 0x0, 0x0, 0xff, // black |
| 0x0, 0x0, 0x0, 0x0 // transparent black |
| }; |
| /** |
| * The shine is a 8 by 1 texture colored: |
| * --- --- --- --- --- --- --- --- |
| * |R| |R| |G| |G| |B| |B| |W| |W| |
| * --- --- --- --- --- --- --- --- |
| * R is red, G is green, B is blue, W is white |
| */ |
| constexpr GLushort kShine[] = {0xF800, // 2 red pixels |
| 0xF800, |
| 0x07E0, // 2 green pixels |
| 0x07E0, |
| 0x001F, // 2 blue pixels |
| 0x001F, |
| 0xFFFF, // 2 white pixels |
| 0xFFFF}; |
| |
| constexpr unsigned int kMaskColorChannels = 4; |
| constexpr unsigned int kShineColorChannels = 3; |
| |
| initTextureWithData(&mTextureNames[0], kMask, kMaskWidth, kMaskHeight, kMaskColorChannels); |
| initTextureWithData(&mTextureNames[1], kShine, kShineWidth, kShineHeight, |
| kShineColorChannels); |
| |
| // clear screen |
| glShadeModel(GL_FLAT); |
| glDisable(GL_DITHER); |
| glDisable(GL_SCISSOR_TEST); |
| glClearColor(0, 1, 1, 1); |
| glClear(GL_COLOR_BUFFER_BIT); |
| eglSwapBuffers(mDisplay, mSurface); |
| glEnable(GL_TEXTURE_2D); |
| glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); |
| |
| glScissor(kMaskBoundaryLeft, kMaskBoundaryBottom, kMaskWidth, kMaskHeight); |
| |
| // Blend state |
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); |
| } |
| |
| void testTearDown() override |
| { |
| glDeleteTextures(1, &mTextureNames[0]); |
| glDeleteTextures(1, &mTextureNames[1]); |
| } |
| |
| void checkMaskColor(unsigned int iterationCount, unsigned int maskSlot) |
| { |
| // kOffset is necessary as the visible part is the left most section of the shine |
| // but then we shift the images right |
| constexpr unsigned int kOffset = 7; |
| |
| // this solves for the color at any given position in our shine equivalent |
| constexpr unsigned int kPossibleColors = 4; |
| constexpr unsigned int kColorsInARow = 2; |
| unsigned int color = |
| ((iterationCount - maskSlot + kOffset) / kColorsInARow) % kPossibleColors; |
| switch (color) |
| { |
| case 0: // white |
| EXPECT_PIXEL_EQ(kMaskBoundaryLeft + maskSlot, kMaskBoundaryBottom, 0xFF, 0xFF, 0xFF, |
| 0xFF); |
| break; |
| case 1: // blue |
| EXPECT_PIXEL_EQ(kMaskBoundaryLeft + maskSlot, kMaskBoundaryBottom, 0x00, 0x00, 0xFF, |
| 0xFF); |
| break; |
| case 2: // green |
| EXPECT_PIXEL_EQ(kMaskBoundaryLeft + maskSlot, kMaskBoundaryBottom, 0x00, 0xFF, 0x00, |
| 0xFF); |
| break; |
| case 3: // red |
| EXPECT_PIXEL_EQ(kMaskBoundaryLeft + maskSlot, kMaskBoundaryBottom, 0xFF, 0x00, 0x00, |
| 0xFF); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void checkClearColor() |
| { |
| // Areas outside of the 4x1 mask area should be the clear color due to our glScissor call |
| constexpr unsigned int kImageHeight = 1; |
| EXPECT_PIXEL_RECT_EQ(0, 0, kWindowWidth, kMaskBoundaryBottom, GLColor::cyan); |
| EXPECT_PIXEL_RECT_EQ(0, kMaskBoundaryBottom + kImageHeight, kWindowWidth, |
| (kWindowHeight - (kMaskBoundaryBottom + kImageHeight)), GLColor::cyan); |
| EXPECT_PIXEL_RECT_EQ(0, kMaskBoundaryBottom, kMaskBoundaryLeft, kImageHeight, |
| GLColor::cyan); |
| EXPECT_PIXEL_RECT_EQ(kMaskBoundaryLeft + kMaskWidth, kMaskBoundaryBottom, |
| (kWindowWidth - (kMaskBoundaryLeft + kMaskWidth)), kImageHeight, |
| GLColor::cyan); |
| } |
| |
| void validateColors(unsigned int iterationCount) |
| { |
| // validate all slots in our mask |
| for (unsigned int maskSlot = 0; maskSlot < kMaskWidth; ++maskSlot) |
| { |
| // parts that are blocked in our mask are black |
| switch (maskSlot) |
| { |
| case kBlackMask[0]: |
| case kBlackMask[1]: |
| // slots with non zero alpha are black |
| EXPECT_PIXEL_EQ(kMaskBoundaryLeft + maskSlot, kMaskBoundaryBottom, 0x00, 0x00, |
| 0x00, 0xFF); |
| continue; |
| default: |
| checkMaskColor(iterationCount, maskSlot); |
| } |
| } |
| // validate surrounding pixels are equal to clear color |
| checkClearColor(); |
| } |
| |
| EGLDisplay mDisplay = EGL_NO_DISPLAY; |
| EGLSurface mSurface = EGL_NO_SURFACE; |
| GLuint mTextureNames[2]; |
| // This creates a kWindowWidth x kWindowHeight window. |
| // A kMaskWidth by kMaskHeight rectangle is lit up by the shine |
| // This lit up rectangle is positioned at (kMaskBoundaryLeft, kMaskBoundaryBottom) |
| // The border around the area is cleared to GLColor::cyan |
| static constexpr GLint kMaskBoundaryLeft = 15; |
| static constexpr GLint kMaskBoundaryBottom = 7; |
| static constexpr unsigned int kMaskWidth = 4; |
| static constexpr unsigned int kMaskHeight = 1; |
| static constexpr unsigned int kShineWidth = 8; |
| static constexpr unsigned int kShineHeight = 1; |
| static constexpr unsigned int kWindowHeight = 16; |
| static constexpr unsigned int kWindowWidth = 32; |
| static constexpr unsigned int kBlackMask[2] = {0, 2}; |
| }; |
| |
| TEST_P(BootAnimationTest, DefaultBootAnimation) |
| { |
| // http://anglebug.com/42263653 |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsVulkan()); |
| |
| constexpr uint64_t kMaxIterationCount = 8; // number of times we shift the shine textures |
| constexpr int kStartingShinePosition = kMaskBoundaryLeft - kShineWidth; |
| constexpr int kEndingShinePosition = kMaskBoundaryLeft; |
| GLint x = kStartingShinePosition; |
| uint64_t iterationCount = 0; |
| do |
| { |
| glDisable(GL_SCISSOR_TEST); |
| glClear(GL_COLOR_BUFFER_BIT); |
| glEnable(GL_SCISSOR_TEST); |
| glDisable(GL_BLEND); |
| glBindTexture(GL_TEXTURE_2D, mTextureNames[1]); |
| glDrawTexiOES(x, kMaskBoundaryBottom, 0, kShineWidth, kShineHeight); |
| glDrawTexiOES(x + kShineWidth, kMaskBoundaryBottom, 0, kShineWidth, kShineHeight); |
| glEnable(GL_BLEND); |
| glBindTexture(GL_TEXTURE_2D, mTextureNames[0]); |
| glDrawTexiOES(kMaskBoundaryLeft, kMaskBoundaryBottom, 0, kMaskWidth, kMaskHeight); |
| validateColors(iterationCount); |
| EGLBoolean res = eglSwapBuffers(mDisplay, mSurface); |
| if (res == EGL_FALSE) |
| { |
| break; |
| } |
| |
| if (x == kEndingShinePosition) |
| { |
| x = kStartingShinePosition; |
| } |
| ++x; |
| ++iterationCount; |
| } while (iterationCount < kMaxIterationCount); |
| } |
| |
| ANGLE_INSTANTIATE_TEST_ES1(BootAnimationTest); |