/*------------------------------------------------------------------------- * 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 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 m_nativeWindow; MovePtr m_surface; MovePtr 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 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( windowFactory.createWindow(&nativeDisplay, eglDisplay, eglConfig, DE_NULL, windowParams)); const EGLSurface eglSurface = eglu::createWindowSurface(nativeDisplay, *nativeWindow, eglDisplay, eglConfig, DE_NULL); MovePtr 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 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 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 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(m_eglTestCtx, "surface_size", "EGL surface size update")); addChild(createCaseGroup(m_eglTestCtx, "back_buffer", "Back buffer contents")); addChild(createCaseGroup(m_eglTestCtx, "pixel_density", "Pixel density")); } } // namespace egl } // namespace deqp