| /*------------------------------------------------------------------------- |
| * 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 std::vector; |
| using std::string; |
| using std::ostringstream; |
| using de::MovePtr; |
| using tcu::CommandLine; |
| using tcu::ConstPixelBufferAccess; |
| using tcu::Interval; |
| using tcu::IVec2; |
| using tcu::Vec3; |
| using tcu::Vec4; |
| using tcu::UVec4; |
| using tcu::ResultCollector; |
| using tcu::Surface; |
| using tcu::TestLog; |
| using eglu::AttribMap; |
| using eglu::NativeDisplay; |
| using eglu::NativeWindow; |
| using eglu::ScopedCurrentContext; |
| using eglu::UniqueSurface; |
| using eglu::UniqueContext; |
| using eglu::NativeWindowFactory; |
| using eglu::WindowParams; |
| 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& params) |
| : 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) |
| { |
| if (m_display != EGL_NO_DISPLAY) |
| m_eglTestCtx.getLibrary().terminate(m_display); |
| |
| m_config = DE_NULL; |
| m_display = EGL_NO_DISPLAY; |
| m_context.clear(); |
| m_surface.clear(); |
| m_nativeWindow.clear(); |
| } |
| |
| 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& params) |
| : 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); |
| 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& params) |
| : 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.check(compareCorners(oldSurface, newSurface), |
| "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& params) |
| : 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")); |
| } |
| |
| } // egl |
| } // deqp |