// Copyright (C) 2018 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. #include "GLSnapshotTesting.h" #include "base/PathUtils.h" #include "base/StdioStream.h" #include "base/System.h" #include "base/testing/TestSystem.h" #include "snapshot/TextureLoader.h" #include "snapshot/TextureSaver.h" #include "GLTestUtils.h" #include "OpenGLTestContext.h" #include #include #include #include namespace emugl { using android::base::StdioStream; using android::snapshot::TextureLoader; using android::snapshot::TextureSaver; std::string describeGlEnum(GLenum enumValue) { std::ostringstream description; description << "0x" << std::hex << enumValue << " (" << getEnumString(enumValue) << ")"; return description.str(); } template testing::AssertionResult compareValue(T expected, T actual, const std::string& description) { if (expected != actual) { return testing::AssertionFailure() << description << "\n\tvalue was:\t" << testing::PrintToString(actual) << "\n\t expected:\t" << testing::PrintToString(expected) << "\t"; } return testing::AssertionSuccess(); } template testing::AssertionResult compareValue(GLboolean, GLboolean, const std::string&); template testing::AssertionResult compareValue(GLint, GLint, const std::string&); template testing::AssertionResult compareValue(GLfloat, GLfloat, const std::string&); testing::AssertionResult compareGlobalGlBoolean(const GLESv2Dispatch* gl, GLenum name, GLboolean expected) { GLboolean current; gl->glGetBooleanv(name, ¤t); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareValue(expected, current, "GL global boolean mismatch for parameter " + describeGlEnum(name) + ":"); } testing::AssertionResult compareGlobalGlInt(const GLESv2Dispatch* gl, GLenum name, GLint expected) { GLint current; gl->glGetIntegerv(name, ¤t); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareValue(expected, current, "GL global int mismatch for parameter " + describeGlEnum(name) + ":"); } testing::AssertionResult compareGlobalGlFloat(const GLESv2Dispatch* gl, GLenum name, GLfloat expected) { GLfloat current; gl->glGetFloatv(name, ¤t); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareValue(expected, current, "GL global float mismatch for parameter " + describeGlEnum(name) + ":"); } template testing::AssertionResult compareVector(const std::vector& expected, const std::vector& actual, const std::string& description) { std::stringstream message; if (expected.size() != actual.size()) { message << " (!) sizes do not match (actual " << actual.size() << ", expected " << expected.size() << ")\n"; } int mismatches = 0; for (int i = 0; i < expected.size(); i++) { if (i >= actual.size()) { if (mismatches < 10) { mismatches++; message << " no match for:\t" << testing::PrintToString(expected[i]) << "\n"; } else { mismatches += expected.size() - i; message << "\n nothing can match remaining elements.\n"; break; } } else if (expected[i] != actual[i]) { mismatches++; if (mismatches < 15) { message << " at index " << i << ":\n\tvalue was:\t" << testing::PrintToString(actual[i]) << "\n\t expected:\t" << testing::PrintToString(expected[i]) << "\n"; } else if (mismatches == 15) { message << " ... and indices " << i; } else if (mismatches < 50) { message << ", " << i; } else if (mismatches == 50) { message << ", etc..."; } } } if (mismatches > 0) { return testing::AssertionFailure() << description << " had " << mismatches << " mismatches.\n" << " expected: " << testing::PrintToString(expected) << "\n" << " actual: " << testing::PrintToString(actual) << "\n" << message.str() << " \n"; } return testing::AssertionSuccess(); } template testing::AssertionResult compareVector( const std::vector&, const std::vector&, const std::string&); template testing::AssertionResult compareVector( const std::vector&, const std::vector&, const std::string&); template testing::AssertionResult compareVector( const std::vector&, const std::vector&, const std::string&); testing::AssertionResult compareGlobalGlBooleanv( const GLESv2Dispatch* gl, GLenum name, const std::vector& expected, GLuint size) { std::vector current; current.resize(std::max(size, static_cast(expected.size()))); gl->glGetBooleanv(name, ¤t[0]); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareVector( expected, current, "GL global booleanv parameter " + describeGlEnum(name)); } testing::AssertionResult compareGlobalGlIntv(const GLESv2Dispatch* gl, GLenum name, const std::vector& expected, GLuint size) { std::vector current; current.resize(std::max(size, static_cast(expected.size()))); gl->glGetIntegerv(name, ¤t[0]); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareVector( expected, current, "GL global intv parameter " + describeGlEnum(name)); } testing::AssertionResult compareGlobalGlFloatv( const GLESv2Dispatch* gl, GLenum name, const std::vector& expected, GLuint size) { std::vector current; current.resize(std::max(size, static_cast(expected.size()))); gl->glGetFloatv(name, ¤t[0]); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); return compareVector( expected, current, "GL global floatv parameter " + describeGlEnum(name)); } void SnapshotTest::SetUp() { GLTest::SetUp(); mTestSystem.getTempRoot()->makeSubDir("Snapshots"); mSnapshotPath = mTestSystem.getTempRoot()->makeSubPath("Snapshots"); } void SnapshotTest::saveSnapshot(const std::string streamFile, const std::string textureFile) { const EGLDispatch* egl = LazyLoadedEGLDispatch::get(); std::unique_ptr m_stream(new StdioStream( android_fopen(streamFile.c_str(), "wb"), StdioStream::kOwner)); auto egl_stream = static_cast(m_stream.get()); std::unique_ptr m_texture_saver(new TextureSaver(StdioStream( android_fopen(textureFile.c_str(), "wb"), StdioStream::kOwner))); egl->eglPreSaveContext(m_display, m_context, egl_stream); egl->eglSaveAllImages(m_display, egl_stream, &m_texture_saver); egl->eglSaveContext(m_display, m_context, egl_stream); // Skip saving a bunch of FrameBuffer's fields // Skip saving colorbuffers // Skip saving window surfaces egl->eglSaveConfig(m_display, m_config, egl_stream); // Skip saving a bunch of process-owned objects egl->eglPostSaveContext(m_display, m_context, egl_stream); m_stream->close(); m_texture_saver->done(); } void SnapshotTest::loadSnapshot(const std::string streamFile, const std::string textureFile) { const EGLDispatch* egl = LazyLoadedEGLDispatch::get(); std::unique_ptr m_stream(new StdioStream( android_fopen(streamFile.c_str(), "rb"), StdioStream::kOwner)); auto egl_stream = static_cast(m_stream.get()); std::shared_ptr m_texture_loader( new TextureLoader(StdioStream(android_fopen(textureFile.c_str(), "rb"), StdioStream::kOwner))); egl->eglLoadAllImages(m_display, egl_stream, &m_texture_loader); EGLint contextAttribs[5] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_CONTEXT_MINOR_VERSION_KHR, 0, EGL_NONE}; m_context = egl->eglLoadContext(m_display, &contextAttribs[0], egl_stream); m_config = egl->eglLoadConfig(m_display, egl_stream); m_surface = pbufferSurface(m_display, m_config, kTestSurfaceSize[0], kTestSurfaceSize[0]); egl->eglPostLoadAllImages(m_display, egl_stream); m_stream->close(); m_texture_loader->join(); egl->eglMakeCurrent(m_display, m_surface, m_surface, m_context); } void SnapshotTest::preloadReset() { GLTest::TearDown(); GLTest::SetUp(); } void SnapshotTest::doSnapshot(std::function preloadCheck = [] {}) { std::string timeStamp = std::to_string(android::base::getUnixTimeUs()); std::string snapshotFile = android::base::pj({mSnapshotPath, std::string("snapshot_") + timeStamp + ".snap"}); std::string textureFile = android::base::pj({mSnapshotPath, std::string("textures_") + timeStamp + ".stex"}); saveSnapshot(snapshotFile, textureFile); preloadReset(); preloadCheck(); loadSnapshot(snapshotFile, textureFile); EXPECT_NE(m_context, EGL_NO_CONTEXT); EXPECT_NE(m_surface, EGL_NO_SURFACE); } void SnapshotPreserveTest::doCheckedSnapshot() { { SCOPED_TRACE("during pre-snapshot default state check"); defaultStateCheck(); ASSERT_EQ(GL_NO_ERROR, gl->glGetError()); } { SCOPED_TRACE("during pre-snapshot state change"); stateChange(); ASSERT_EQ(GL_NO_ERROR, gl->glGetError()); } { SCOPED_TRACE("during pre-snapshot changed state check"); changedStateCheck(); } SnapshotTest::doSnapshot([this] { SCOPED_TRACE("during post-reset default state check"); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); defaultStateCheck(); }); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); { SCOPED_TRACE("during post-snapshot changed state check"); changedStateCheck(); EXPECT_EQ(GL_NO_ERROR, gl->glGetError()); } } } // namespace emugl