| /*------------------------------------------------------------------------- |
| * drawElements Quality Program EGL Module |
| * --------------------------------------- |
| * |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| *//*! |
| * \file |
| * \brief Tests for resizing the native window of a surface. |
| *//*--------------------------------------------------------------------*/ |
| |
| #include "teglResizeTests.hpp" |
| |
| #include "teglSimpleConfigCase.hpp" |
| |
| #include "tcuImageCompare.hpp" |
| #include "tcuSurface.hpp" |
| #include "tcuPlatform.hpp" |
| #include "tcuTestLog.hpp" |
| #include "tcuInterval.hpp" |
| #include "tcuTextureUtil.hpp" |
| #include "tcuResultCollector.hpp" |
| |
| #include "egluNativeDisplay.hpp" |
| #include "egluNativeWindow.hpp" |
| #include "egluNativePixmap.hpp" |
| #include "egluUnique.hpp" |
| #include "egluUtil.hpp" |
| |
| #include "eglwLibrary.hpp" |
| #include "eglwEnums.hpp" |
| |
| #include "gluDefs.hpp" |
| #include "glwFunctions.hpp" |
| #include "glwEnums.hpp" |
| |
| #include "tcuTestLog.hpp" |
| #include "tcuVector.hpp" |
| |
| #include "deThread.h" |
| #include "deUniquePtr.hpp" |
| |
| #include <sstream> |
| |
| namespace deqp |
| { |
| namespace egl |
| { |
| |
| using de::MovePtr; |
| using eglu::AttribMap; |
| using eglu::NativeDisplay; |
| using eglu::NativeWindow; |
| using eglu::NativeWindowFactory; |
| using eglu::ScopedCurrentContext; |
| using eglu::UniqueContext; |
| using eglu::UniqueSurface; |
| using eglu::WindowParams; |
| using std::ostringstream; |
| using std::string; |
| using std::vector; |
| using tcu::CommandLine; |
| using tcu::ConstPixelBufferAccess; |
| using tcu::Interval; |
| using tcu::IVec2; |
| using tcu::ResultCollector; |
| using tcu::Surface; |
| using tcu::TestLog; |
| using tcu::UVec4; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| using namespace eglw; |
| |
| typedef eglu::WindowParams::Visibility Visibility; |
| typedef TestCase::IterateResult IterateResult; |
| |
| struct ResizeParams |
| { |
| string name; |
| string description; |
| IVec2 oldSize; |
| IVec2 newSize; |
| }; |
| |
| class ResizeTest : public TestCase |
| { |
| public: |
| ResizeTest(EglTestContext &eglTestCtx, const ResizeParams ¶ms) |
| : TestCase(eglTestCtx, params.name.c_str(), params.description.c_str()) |
| , m_oldSize(params.oldSize) |
| , m_newSize(params.newSize) |
| , m_display(EGL_NO_DISPLAY) |
| , m_config(DE_NULL) |
| , m_log(m_testCtx.getLog()) |
| , m_status(m_log) |
| { |
| } |
| |
| void init(void); |
| void deinit(void); |
| |
| protected: |
| virtual EGLenum surfaceType(void) const |
| { |
| return EGL_WINDOW_BIT; |
| } |
| void resize(IVec2 size); |
| |
| const IVec2 m_oldSize; |
| const IVec2 m_newSize; |
| EGLDisplay m_display; |
| EGLConfig m_config; |
| MovePtr<NativeWindow> m_nativeWindow; |
| MovePtr<UniqueSurface> m_surface; |
| MovePtr<UniqueContext> m_context; |
| TestLog &m_log; |
| ResultCollector m_status; |
| glw::Functions m_gl; |
| }; |
| |
| EGLConfig getEGLConfig(const Library &egl, const EGLDisplay eglDisplay, EGLenum surfaceType) |
| { |
| AttribMap attribMap; |
| |
| attribMap[EGL_SURFACE_TYPE] = surfaceType; |
| attribMap[EGL_RENDERABLE_TYPE] = EGL_OPENGL_ES2_BIT; |
| |
| return eglu::chooseSingleConfig(egl, eglDisplay, attribMap); |
| } |
| |
| void ResizeTest::init(void) |
| { |
| TestCase::init(); |
| |
| const Library &egl = m_eglTestCtx.getLibrary(); |
| const CommandLine &cmdLine = m_testCtx.getCommandLine(); |
| const EGLDisplay eglDisplay = eglu::getAndInitDisplay(m_eglTestCtx.getNativeDisplay()); |
| const EGLConfig eglConfig = getEGLConfig(egl, eglDisplay, surfaceType()); |
| const EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; |
| EGLContext eglContext = egl.createContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, ctxAttribs); |
| EGLU_CHECK_MSG(egl, "eglCreateContext()"); |
| MovePtr<UniqueContext> context(new UniqueContext(egl, eglDisplay, eglContext)); |
| const EGLint configId = eglu::getConfigAttribInt(egl, eglDisplay, eglConfig, EGL_CONFIG_ID); |
| const Visibility visibility = eglu::parseWindowVisibility(cmdLine); |
| NativeDisplay &nativeDisplay = m_eglTestCtx.getNativeDisplay(); |
| const NativeWindowFactory &windowFactory = |
| eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), cmdLine); |
| |
| const WindowParams windowParams(m_oldSize.x(), m_oldSize.y(), visibility); |
| MovePtr<NativeWindow> nativeWindow( |
| windowFactory.createWindow(&nativeDisplay, eglDisplay, eglConfig, DE_NULL, windowParams)); |
| const EGLSurface eglSurface = |
| eglu::createWindowSurface(nativeDisplay, *nativeWindow, eglDisplay, eglConfig, DE_NULL); |
| MovePtr<UniqueSurface> surface(new UniqueSurface(egl, eglDisplay, eglSurface)); |
| |
| m_log << TestLog::Message << "Chose EGLConfig with id " << configId << ".\n" |
| << "Created initial surface with size " << m_oldSize << TestLog::EndMessage; |
| |
| m_eglTestCtx.initGLFunctions(&m_gl, glu::ApiType::es(2, 0)); |
| m_config = eglConfig; |
| m_surface = surface; |
| m_context = context; |
| m_display = eglDisplay; |
| m_nativeWindow = nativeWindow; |
| EGLU_CHECK_MSG(egl, "init"); |
| } |
| |
| void ResizeTest::deinit(void) |
| { |
| m_config = DE_NULL; |
| m_context.clear(); |
| m_surface.clear(); |
| m_nativeWindow.clear(); |
| |
| if (m_display != EGL_NO_DISPLAY) |
| { |
| m_eglTestCtx.getLibrary().terminate(m_display); |
| m_display = EGL_NO_DISPLAY; |
| } |
| } |
| |
| void ResizeTest::resize(IVec2 size) |
| { |
| m_nativeWindow->setSurfaceSize(size); |
| m_testCtx.getPlatform().processEvents(); |
| m_log << TestLog::Message << "Resized surface to size " << size << TestLog::EndMessage; |
| } |
| |
| class ChangeSurfaceSizeCase : public ResizeTest |
| { |
| public: |
| ChangeSurfaceSizeCase(EglTestContext &eglTestCtx, const ResizeParams ¶ms) : ResizeTest(eglTestCtx, params) |
| { |
| } |
| |
| IterateResult iterate(void); |
| }; |
| |
| void drawRectangle(const glw::Functions &gl, IVec2 pos, IVec2 size, Vec3 color) |
| { |
| gl.clearColor(color.x(), color.y(), color.z(), 1.0); |
| gl.scissor(pos.x(), pos.y(), size.x(), size.y()); |
| gl.enable(GL_SCISSOR_TEST); |
| gl.clear(GL_COLOR_BUFFER_BIT); |
| gl.disable(GL_SCISSOR_TEST); |
| GLU_EXPECT_NO_ERROR(gl.getError(), "Rectangle drawing with glScissor and glClear failed."); |
| } |
| |
| void initSurface(const glw::Functions &gl, IVec2 oldSize) |
| { |
| const Vec3 frameColor(0.0f, 0.0f, 1.0f); |
| const Vec3 fillColor(1.0f, 0.0f, 0.0f); |
| const Vec3 markColor(0.0f, 1.0f, 0.0f); |
| |
| drawRectangle(gl, IVec2(0, 0), oldSize, frameColor); |
| drawRectangle(gl, IVec2(2, 2), oldSize - IVec2(4, 4), fillColor); |
| |
| drawRectangle(gl, IVec2(0, 0), IVec2(8, 4), markColor); |
| drawRectangle(gl, oldSize - IVec2(16, 16), IVec2(8, 4), markColor); |
| drawRectangle(gl, IVec2(0, oldSize.y() - 16), IVec2(8, 4), markColor); |
| drawRectangle(gl, IVec2(oldSize.x() - 16, 0), IVec2(8, 4), markColor); |
| } |
| |
| bool compareRectangles(const ConstPixelBufferAccess &rectA, const ConstPixelBufferAccess &rectB) |
| { |
| const int width = rectA.getWidth(); |
| const int height = rectA.getHeight(); |
| const int depth = rectA.getDepth(); |
| |
| if (rectB.getWidth() != width || rectB.getHeight() != height || rectB.getDepth() != depth) |
| return false; |
| |
| for (int z = 0; z < depth; ++z) |
| for (int y = 0; y < height; ++y) |
| for (int x = 0; x < width; ++x) |
| if (rectA.getPixel(x, y, z) != rectB.getPixel(x, y, z)) |
| return false; |
| |
| return true; |
| } |
| |
| // Check whether `oldSurface` and `newSurface` share a common corner. |
| bool compareCorners(const Surface &oldSurface, const Surface &newSurface) |
| { |
| const int oldWidth = oldSurface.getWidth(); |
| const int oldHeight = oldSurface.getHeight(); |
| const int newWidth = newSurface.getWidth(); |
| const int newHeight = newSurface.getHeight(); |
| const int minWidth = de::min(oldWidth, newWidth); |
| const int minHeight = de::min(oldHeight, newHeight); |
| |
| for (int xCorner = 0; xCorner < 2; ++xCorner) |
| { |
| const int oldX = xCorner == 0 ? 0 : oldWidth - minWidth; |
| const int newX = xCorner == 0 ? 0 : newWidth - minWidth; |
| |
| for (int yCorner = 0; yCorner < 2; ++yCorner) |
| { |
| const int oldY = yCorner == 0 ? 0 : oldHeight - minHeight; |
| const int newY = yCorner == 0 ? 0 : newHeight - minHeight; |
| ConstPixelBufferAccess oldAccess = getSubregion(oldSurface.getAccess(), oldX, oldY, minWidth, minHeight); |
| ConstPixelBufferAccess newAccess = getSubregion(newSurface.getAccess(), newX, newY, minWidth, minHeight); |
| |
| if (compareRectangles(oldAccess, newAccess)) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| Surface readSurface(const glw::Functions &gl, IVec2 size) |
| { |
| Surface ret(size.x(), size.y()); |
| gl.readPixels(0, 0, size.x(), size.y(), GL_RGBA, GL_UNSIGNED_BYTE, ret.getAccess().getDataPtr()); |
| |
| GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels() failed"); |
| return ret; |
| } |
| |
| template <typename T> |
| inline bool hasBits(T bitSet, T requiredBits) |
| { |
| return (bitSet & requiredBits) == requiredBits; |
| } |
| |
| IVec2 getNativeSurfaceSize(const NativeWindow &nativeWindow, IVec2 reqSize) |
| { |
| if (hasBits(nativeWindow.getCapabilities(), NativeWindow::CAPABILITY_GET_SURFACE_SIZE)) |
| return nativeWindow.getSurfaceSize(); |
| return reqSize; // assume we got the requested size |
| } |
| |
| IVec2 checkSurfaceSize(const Library &egl, EGLDisplay eglDisplay, EGLSurface eglSurface, |
| const NativeWindow &nativeWindow, IVec2 reqSize, ResultCollector &status) |
| { |
| const IVec2 nativeSize = getNativeSurfaceSize(nativeWindow, reqSize); |
| IVec2 eglSize = eglu::getSurfaceSize(egl, eglDisplay, eglSurface); |
| ostringstream oss; |
| |
| oss << "Size of EGL surface " << eglSize << " differs from size of native window " << nativeSize; |
| status.check(eglSize == nativeSize, oss.str()); |
| |
| return eglSize; |
| } |
| |
| IterateResult ChangeSurfaceSizeCase::iterate(void) |
| { |
| const Library &egl = m_eglTestCtx.getLibrary(); |
| ScopedCurrentContext currentCtx(egl, m_display, **m_surface, **m_surface, **m_context); |
| egl.swapBuffers(m_display, **m_surface); |
| IVec2 oldEglSize = checkSurfaceSize(egl, m_display, **m_surface, *m_nativeWindow, m_oldSize, m_status); |
| |
| initSurface(m_gl, oldEglSize); |
| |
| this->resize(m_newSize); |
| |
| egl.swapBuffers(m_display, **m_surface); |
| EGLU_CHECK_MSG(egl, "eglSwapBuffers()"); |
| checkSurfaceSize(egl, m_display, **m_surface, *m_nativeWindow, m_newSize, m_status); |
| |
| m_status.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| class PreserveBackBufferCase : public ResizeTest |
| { |
| public: |
| PreserveBackBufferCase(EglTestContext &eglTestCtx, const ResizeParams ¶ms) : ResizeTest(eglTestCtx, params) |
| { |
| } |
| |
| IterateResult iterate(void); |
| EGLenum surfaceType(void) const; |
| }; |
| |
| EGLenum PreserveBackBufferCase::surfaceType(void) const |
| { |
| return EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT; |
| } |
| |
| IterateResult PreserveBackBufferCase::iterate(void) |
| { |
| const Library &egl = m_eglTestCtx.getLibrary(); |
| ScopedCurrentContext currentCtx(egl, m_display, **m_surface, **m_surface, **m_context); |
| |
| EGLU_CHECK_CALL(egl, surfaceAttrib(m_display, **m_surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED)); |
| |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "GL state erroneous upon initialization!"); |
| |
| { |
| const IVec2 oldEglSize = eglu::getSurfaceSize(egl, m_display, **m_surface); |
| initSurface(m_gl, oldEglSize); |
| |
| m_gl.finish(); |
| GLU_EXPECT_NO_ERROR(m_gl.getError(), "glFinish() failed"); |
| |
| { |
| const Surface oldSurface = readSurface(m_gl, oldEglSize); |
| |
| egl.swapBuffers(m_display, **m_surface); |
| this->resize(m_newSize); |
| egl.swapBuffers(m_display, **m_surface); |
| EGLU_CHECK_MSG(egl, "eglSwapBuffers()"); |
| |
| { |
| const IVec2 newEglSize = eglu::getSurfaceSize(egl, m_display, **m_surface); |
| const Surface newSurface = readSurface(m_gl, newEglSize); |
| |
| m_log << TestLog::ImageSet("Corner comparison", "Comparing old and new surfaces at all corners") |
| << TestLog::Image("Before resizing", "Before resizing", oldSurface) |
| << TestLog::Image("After resizing", "After resizing", newSurface) << TestLog::EndImageSet; |
| |
| m_status.checkResult(compareCorners(oldSurface, newSurface), QP_TEST_RESULT_QUALITY_WARNING, |
| "Resizing the native window changed the contents of " |
| "the EGL surface"); |
| } |
| } |
| } |
| |
| m_status.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| typedef tcu::Vector<Interval, 2> IvVec2; |
| |
| class UpdateResolutionCase : public ResizeTest |
| { |
| public: |
| UpdateResolutionCase(EglTestContext &eglTestCtx, const ResizeParams ¶ms) : ResizeTest(eglTestCtx, params) |
| { |
| } |
| |
| IterateResult iterate(void); |
| |
| private: |
| IvVec2 getNativePixelsPerInch(void); |
| }; |
| |
| IvVec2 ivVec2(const IVec2 &vec) |
| { |
| return IvVec2(double(vec.x()), double(vec.y())); |
| } |
| |
| Interval approximateInt(int i) |
| { |
| const Interval margin(-1.0, 1.0); // The resolution may be rounded |
| |
| return (Interval(i) + margin) & Interval(0.0, TCU_INFINITY); |
| } |
| |
| IvVec2 UpdateResolutionCase::getNativePixelsPerInch(void) |
| { |
| const Library &egl = m_eglTestCtx.getLibrary(); |
| const int inchPer10km = 254 * EGL_DISPLAY_SCALING; |
| const IVec2 bufSize = eglu::getSurfaceSize(egl, m_display, **m_surface); |
| const IVec2 winSize = m_nativeWindow->getScreenSize(); |
| const IVec2 bufPp10km = eglu::getSurfaceResolution(egl, m_display, **m_surface); |
| const IvVec2 bufPpiI = |
| (IvVec2(approximateInt(bufPp10km.x()), approximateInt(bufPp10km.y())) / Interval(inchPer10km)); |
| const IvVec2 winPpiI = ivVec2(winSize) * bufPpiI / ivVec2(bufSize); |
| const IVec2 winPpi(int(winPpiI.x().midpoint()), int(winPpiI.y().midpoint())); |
| |
| m_log << TestLog::Message << "EGL surface size: " << bufSize << "\n" |
| << "EGL surface pixel density (pixels / 10 km): " << bufPp10km << "\n" |
| << "Native window size: " << winSize << "\n" |
| << "Native pixel density (ppi): " << winPpi << "\n" |
| << TestLog::EndMessage; |
| |
| m_status.checkResult(bufPp10km.x() >= 1 && bufPp10km.y() >= 1, QP_TEST_RESULT_QUALITY_WARNING, |
| "Surface pixel density is less than one pixel per 10 km. " |
| "Is the surface really visible from space?"); |
| |
| return winPpiI; |
| } |
| |
| IterateResult UpdateResolutionCase::iterate(void) |
| { |
| const Library &egl = m_eglTestCtx.getLibrary(); |
| ScopedCurrentContext currentCtx(egl, m_display, **m_surface, **m_surface, **m_context); |
| |
| if (!hasBits(m_nativeWindow->getCapabilities(), NativeWindow::CAPABILITY_GET_SCREEN_SIZE)) |
| TCU_THROW(NotSupportedError, "Unable to determine surface size in screen pixels"); |
| |
| { |
| const IVec2 oldEglSize = eglu::getSurfaceSize(egl, m_display, **m_surface); |
| initSurface(m_gl, oldEglSize); |
| } |
| { |
| const IvVec2 oldPpi = this->getNativePixelsPerInch(); |
| this->resize(m_newSize); |
| EGLU_CHECK_CALL(egl, swapBuffers(m_display, **m_surface)); |
| { |
| const IvVec2 newPpi = this->getNativePixelsPerInch(); |
| m_status.check(oldPpi.x().intersects(newPpi.x()) && oldPpi.y().intersects(newPpi.y()), |
| "Window PPI differs after resizing"); |
| } |
| } |
| |
| m_status.setTestContextResult(m_testCtx); |
| return STOP; |
| } |
| |
| ResizeTests::ResizeTests(EglTestContext &eglTestCtx) |
| : TestCaseGroup(eglTestCtx, "resize", "Tests resizing the native surface") |
| { |
| } |
| |
| template <class Case> |
| TestCaseGroup *createCaseGroup(EglTestContext &eglTestCtx, const string &name, const string &desc) |
| { |
| const ResizeParams params[] = { |
| {"shrink", "Shrink in both dimensions", IVec2(128, 128), IVec2(32, 32)}, |
| {"grow", "Grow in both dimensions", IVec2(32, 32), IVec2(128, 128)}, |
| {"stretch_width", "Grow horizontally, shrink vertically", IVec2(32, 128), IVec2(128, 32)}, |
| {"stretch_height", "Grow vertically, shrink horizontally", IVec2(128, 32), IVec2(32, 128)}, |
| }; |
| TestCaseGroup *const group = new TestCaseGroup(eglTestCtx, name.c_str(), desc.c_str()); |
| |
| for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(params); ++ndx) |
| group->addChild(new Case(eglTestCtx, params[ndx])); |
| |
| return group; |
| } |
| |
| void ResizeTests::init(void) |
| { |
| addChild(createCaseGroup<ChangeSurfaceSizeCase>(m_eglTestCtx, "surface_size", "EGL surface size update")); |
| addChild(createCaseGroup<PreserveBackBufferCase>(m_eglTestCtx, "back_buffer", "Back buffer contents")); |
| addChild(createCaseGroup<UpdateResolutionCase>(m_eglTestCtx, "pixel_density", "Pixel density")); |
| } |
| |
| } // namespace egl |
| } // namespace deqp |