blob: ba6b3535cba9f52ed47c5b9b4e2aedbf98864c39 [file] [log] [blame]
//
// 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);