Add OpenglRender_unittests, fix glGetTexImage unavailability
glGetTexImage isn't necessarily supported on host, so do a fallback.
TODO: Add the GL snapshot tests back
Bug: 171711491
Change-Id: I94b034c20ed5a48fcff4cae17485e9e9abf0dbd9
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 55102ab..e0fad96 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,10 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 11)
+# Uncomment for ASAN support
+# set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
+# set (CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} -fno-omit-frame-pointer -fsanitize=address")
+
# GoogleTest support: uses CMake ExternalProject to pull in external repo
# without manual steps (during CMake build script generation phase)
include(gtest.cmake)
diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt
index a8c099b..8c8806d 100644
--- a/base/CMakeLists.txt
+++ b/base/CMakeLists.txt
@@ -12,6 +12,7 @@
GLObjectCounter.cpp
LayoutResolver.cpp
MemStream.cpp
+ StdioStream.cpp
MemoryTracker.cpp
MessageChannel.cpp
PathUtils.cpp
@@ -64,6 +65,21 @@
endif()
# Tests
+add_library(
+ gfxstream-base-testing-support
+ testing/file_io.cpp)
+target_include_directories(
+ gfxstream-base-testing-support
+ PRIVATE
+ ${GFXSTREAM_REPO_ROOT}
+ PUBLIC
+ ${GFXSTREAM_REPO_ROOT}/base/testing)
+target_link_libraries(
+ gfxstream-base-testing-support
+ PRIVATE
+ gfxstream-base
+ gtest
+ gmock)
add_executable(
gfxstream-base_unittests
AlignedBuf_unittest.cpp
@@ -75,9 +91,9 @@
SubAllocator_unittest.cpp
TypeTraits_unittest.cpp
${gfxstream-base-platform-test-sources})
-
target_link_libraries(
gfxstream-base_unittests
PRIVATE
gfxstream-base
+ gfxstream-base-testing-support
gtest_main)
diff --git a/base/PathUtils.cpp b/base/PathUtils.cpp
index 0c4486e..7f152b1 100644
--- a/base/PathUtils.cpp
+++ b/base/PathUtils.cpp
@@ -223,6 +223,11 @@
return decompose<std::string>(path, hostType);
}
+std::vector<std::string> PathUtils::decompose(const std::string& path,
+ HostType hostType) {
+ return decompose<std::string>(path, hostType);
+}
+
template <class String>
std::string PathUtils::recompose(const std::vector<String>& components,
HostType hostType) {
@@ -357,5 +362,14 @@
return res;
}
+std::string PathUtils::addTrailingDirSeparator(const std::string& path,
+ HostType hostType) {
+ std::string result = path;
+ if (result.size() > 0 && !isDirSeparator(result[result.size() - 1U])) {
+ result += getDirSeparator(hostType);
+ }
+ return result;
+}
+
} // namespace base
} // namespace android
diff --git a/base/PathUtils.h b/base/PathUtils.h
index 1ec3f40..86e6a92 100644
--- a/base/PathUtils.h
+++ b/base/PathUtils.h
@@ -108,11 +108,13 @@
}
// Add a trailing separator if needed.
+ static std::string addTrailingDirSeparator(const std::string& path,
+ HostType hostType);
static std::string addTrailingDirSeparator(const char* path,
HostType hostType);
// Add a trailing separator if needed.
- static std::string addTrailingDirSeparator(const char* path) {
+ static std::string addTrailingDirSeparator(const std::string& path) {
return addTrailingDirSeparator(path, HOST_TYPE);
}
@@ -200,6 +202,8 @@
// for the root prefix, if any.
static std::vector<std::string> decompose(std::string&& path,
HostType hostType);
+ static std::vector<std::string> decompose(const std::string& path,
+ HostType hostType);
template <class String>
static std::vector<String> decompose(const String& path,
@@ -211,6 +215,10 @@
return decompose(std::move(path), HOST_TYPE);
}
+ static std::vector<std::string> decompose(const std::string& path) {
+ return decompose(path, HOST_TYPE);
+ }
+
// Recompose a path from individual components into a file path string.
// |components| is a vector of strings, and |hostType| the target
// host type to use. Return a new file path string. Note that if the
diff --git a/base/System.cpp b/base/System.cpp
index 221801e..936e243 100644
--- a/base/System.cpp
+++ b/base/System.cpp
@@ -1,3 +1,4 @@
+#include "base/EintrWrapper.h"
#include "base/StringFormat.h"
#include "base/System.h"
@@ -10,12 +11,40 @@
#include "dirent.h"
#else
#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/resource.h>
#include <unistd.h>
#endif
#include <string.h>
+using FileSize = uint64_t;
+
+#ifdef _WIN32
+// Return |path| as a Unicode string, while discarding trailing separators.
+Win32UnicodeString win32Path(StringView path) {
+ Win32UnicodeString wpath(path);
+ // Get rid of trailing directory separators, Windows doesn't like them.
+ size_t size = wpath.size();
+ while (size > 0U &&
+ (wpath[size - 1U] == L'\\' || wpath[size - 1U] == L'/')) {
+ size--;
+ }
+ if (size < wpath.size()) {
+ wpath.resize(size);
+ }
+ return wpath;
+}
+
+using PathStat = struct _stat64;
+
+#else // _WIN32
+
+using PathStat = struct stat;
+
+#endif // _WIN32
+
namespace {
struct TickCountImpl {
@@ -117,6 +146,30 @@
return false;
}
+int fdStat(int fd, PathStat* st) {
+#ifdef _WIN32
+ return fstat64(fd, st);
+#else // !_WIN32
+ return HANDLE_EINTR(fstat(fd, st));
+#endif // !_WIN32
+}
+
+bool getFileSize(int fd, uint64_t* outFileSize) {
+ if (fd < 0) {
+ return false;
+ }
+ PathStat st;
+ int ret = fdStat(fd, &st);
+ if (ret < 0 || !S_ISREG(st.st_mode)) {
+ return false;
+ }
+ // This is off_t on POSIX and a 32/64 bit integral type on windows based on
+ // the host / compiler combination. We cast everything to 64 bit unsigned to
+ // play safe.
+ *outFileSize = static_cast<FileSize>(st.st_size);
+ return true;
+}
+
void sleepMs(uint64_t n) {
#ifdef _WIN32
::Sleep(n);
diff --git a/base/System.h b/base/System.h
index caaf7fb..9ed0817 100644
--- a/base/System.h
+++ b/base/System.h
@@ -17,7 +17,7 @@
std::string getProgramDirectory();
std::string getLauncherDirectory();
-uint64_t getFileSize(int fd, uint64_t* size);
+bool getFileSize(int fd, uint64_t* size);
void sleepMs(uint64_t ms);
void sleepUs(uint64_t us);
diff --git a/base/testing/TestSystem.h b/base/testing/TestSystem.h
new file mode 100644
index 0000000..76990f1
--- /dev/null
+++ b/base/testing/TestSystem.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2015 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.
+
+#pragma once
+
+#include "base/PathUtils.h"
+#include "base/System.h"
+#include "base/Thread.h"
+
+#include "TestTempDir.h"
+
+namespace android {
+namespace base {
+
+class TestSystem {
+public:
+ TestTempDir* getTempRoot() const {
+ if (!mTempDir) {
+ mTempDir = new TestTempDir("TestSystem");
+ mTempRootPrefix = PathUtils::addTrailingDirSeparator(mTempDir->pathString());
+ }
+ return mTempDir;
+ }
+
+ ~TestSystem() {
+ if (mTempDir) delete mTempDir;
+ }
+private:
+
+ mutable TestTempDir* mTempDir = nullptr;
+ mutable std::string mTempRootPrefix;
+};
+
+} // namespace base
+} // namespace android
diff --git a/base/testing/TestTempDir.h b/base/testing/TestTempDir.h
new file mode 100644
index 0000000..ecbfad6
--- /dev/null
+++ b/base/testing/TestTempDir.h
@@ -0,0 +1,259 @@
+// Copyright (C) 2015 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.
+
+#pragma once
+
+#include "base/Compiler.h"
+#include "base/StringFormat.h"
+#include "base/PathUtils.h"
+
+#include "file_io.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#undef ERROR
+#include <errno.h>
+#include <stdio.h>
+#endif
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+namespace android {
+namespace base {
+
+// A class used to model a temporary directory used during testing.
+// Usage is simple:
+//
+// {
+// TestTempDir myDir("my_test"); // creates new temp directory.
+// ASSERT_TRUE(myDir.path()); // NULL if error during creation.
+// ... write files into directory path myDir->path()
+// ... do your test
+// } // destructor removes temp directory and all files under it.
+
+class TestTempDir {
+public:
+ // Create new instance. This also tries to create a new temporary
+ // directory. |debugPrefix| is an optional name prefix and can be NULL.
+ TestTempDir(const std::string& debugName) {
+ std::string temp_dir = getTempPath();
+ if (!debugName.empty()) {
+ temp_dir += debugName;
+ temp_dir += ".";
+ }
+
+#if defined(_MSC_VER) || defined(_WIN32)
+ temp_dir += Uuid::generate().toString();
+ if (android_mkdir(temp_dir.c_str(), 0755) != 0) {
+ fprintf(stderr, "Unable to create %s, falling back to tmp dir",
+ temp_dir.c_str());
+ temp_dir = getTempPath();
+ }
+ auto parts = PathUtils::decompose(temp_dir);
+ mPath = PathUtils::recompose(parts);
+#else
+ temp_dir += "XXXXXX";
+ if (mkdtemp(&temp_dir[0])) {
+ // Fix any Win32/Linux naming issues
+ auto parts = PathUtils::decompose(temp_dir);
+ mPath = PathUtils::recompose(parts);
+ } else {
+ fprintf(stderr, "%s: mkdtemp failed!\n", __func__);
+ }
+#endif
+ }
+
+ // Return the path to the temporary directory, or NULL if it could not
+ // be created for some reason.
+ const char* path() const { return mPath.size() ? mPath.c_str() : NULL; }
+
+ // Return the path as a string. It will be empty if the directory could
+ // not be created for some reason.
+ const std::string& pathString() const { return mPath; }
+
+ // Destroy instance, and removes the temporary directory and all files
+ // inside it.
+ ~TestTempDir() {
+ if (mPath.size()) {
+ DeleteRecursive(mPath);
+ }
+ }
+
+ // Create the path of a directory entry under the temporary directory.
+ std::string makeSubPath(const std::string& subpath) {
+ return StringFormat("%s/%s", mPath.c_str(), subpath.c_str());
+ }
+
+ // Create an empty directory under the temporary directory.
+ bool makeSubDir(const std::string& subdir) {
+ std::string path = makeSubPath(subdir);
+ if (android_mkdir(path.c_str(), 0755) < 0) {
+ // PLOG(ERROR) << "Can't create " << path.c_str() << ": ";
+ return false;
+ }
+ return true;
+ }
+
+ // Create an empty file under the temporary directory.
+ bool makeSubFile(const std::string& file) {
+ std::string path = makeSubPath(file);
+ int fd = ::android_open(path.c_str(), O_WRONLY | O_CREAT, 0744);
+ if (fd < 0) {
+ // PLOG(ERROR) << "Can't create " << path.c_str() << ": ";
+ return false;
+ }
+ ::close(fd);
+ return true;
+ }
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(TestTempDir);
+
+ void DeleteRecursive(const std::string& path) {
+ // First remove any files in the dir
+ DIR* dir = opendir(path.c_str());
+ if (!dir) {
+ return;
+ }
+
+ dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
+ continue;
+ }
+ std::string entry_path = StringFormat("%s/%s", path, entry->d_name);
+#ifdef _WIN32
+ struct _stati64 stats;
+ android_lstat(entry_path.c_str(),
+ reinterpret_cast<struct stat*>(&stats));
+#else
+ struct stat stats;
+ android_lstat(entry_path.c_str(), &stats);
+#endif
+
+ if (S_ISDIR(stats.st_mode)) {
+ DeleteRecursive(entry_path);
+ } else {
+ android_unlink(entry_path.c_str());
+ }
+ }
+ closedir(dir);
+ android_rmdir(path.c_str());
+ }
+
+#ifdef _WIN32
+ std::string getTempPath() {
+ std::string result;
+ DWORD len = GetTempPath(0, NULL);
+ if (!len) {
+ fprintf(stderr, "%s: cant find temp path\n", __func__);
+ abort();
+ // LOG(FATAL) << "Can't find temporary path!";
+ }
+ result.resize(static_cast<size_t>(len));
+ GetTempPath(len, &result[0]);
+ // The length returned by GetTempPath() is sometimes too large.
+ result.resize(::strlen(result.c_str()));
+ for (size_t n = 0; n < result.size(); ++n) {
+ if (result[n] == '\\') {
+ result[n] = '/';
+ }
+ }
+ if (result.size() && result[result.size() - 1] != '/') {
+ result += '/';
+ }
+ return result;
+ }
+
+ char* mkdtemp(char* path) {
+ char* sep = ::strrchr(path, '/');
+ if (sep) {
+ struct _stati64 st;
+ int ret;
+ *sep = '\0'; // temporarily zero-terminate the dirname.
+ ret = android_stat(path, reinterpret_cast<struct stat*>(&st));
+ *sep = '/'; // restore full path.
+ if (ret < 0) {
+ return NULL;
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ return NULL;
+ }
+ }
+
+ // Loop. On each iteration, replace the XXXXXX suffix with a random
+ // number.
+ char* path_end = path + ::strlen(path);
+ const size_t kSuffixLen = 6U;
+ for (int tries = 128; tries > 0; tries--) {
+ int random = rand() % 1000000;
+
+ snprintf(path_end - kSuffixLen, kSuffixLen + 1, "%0d", random);
+ if (android_mkdir(path, 0755) == 0) {
+ return path; // Success
+ }
+ if (errno != EEXIST) {
+ return NULL;
+ }
+ }
+ return NULL;
+ }
+#else // !_WIN32
+ std::string getTempPath() {
+ std::string result;
+ // Only check TMPDIR if we're not root.
+ if (getuid() != 0 && getgid() != 0) {
+ const char* tmpdir = ::getenv("TMPDIR");
+ if (tmpdir && tmpdir[0]) {
+ result = tmpdir;
+ }
+ }
+ // Otherwise use P_tmpdir, which defaults to /tmp
+ if (result.empty()) {
+#ifndef P_tmpdir
+#define P_tmpdir "/tmp"
+#endif
+ result = P_tmpdir;
+ }
+ // Check that it exists and is a directory.
+ struct stat st;
+ int ret = android_stat(result.c_str(), &st);
+ if (ret < 0 || !S_ISDIR(st.st_mode)) {
+ fprintf(stderr, "%s: Can't find temp path %s\n", __func__,
+ result.c_str());
+ abort();
+ // LOG(FATAL) << "Can't find temporary path: [" << result.c_str()
+ // << "]";
+ }
+ // Ensure there is a trailing directory separator.
+ if (result.size() && result[result.size() - 1] != '/') {
+ result += '/';
+ }
+ return result;
+ }
+#endif // !_WIN32
+
+ std::string mPath;
+};
+
+} // namespace base
+} // namespace android
diff --git a/base/testing/file_io.cpp b/base/testing/file_io.cpp
new file mode 100644
index 0000000..a93d442
--- /dev/null
+++ b/base/testing/file_io.cpp
@@ -0,0 +1,231 @@
+// Copyright 2020 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 "file_io.h"
+#include "base/Win32UnicodeString.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#include <direct.h>
+#include <windows.h>
+#include <share.h>
+#include "base/PathUtils.h"
+using android::base::PathUtils;
+using android::base::Win32UnicodeString;
+using android::base::ScopedCPtr;
+#endif
+
+// Provide different macros for different number of string arguments where each
+// string argument requires conversion
+#ifdef _WIN32
+#define WIDEN_CALL_1(func, str1, ...) \
+ _w ## func(Win32UnicodeString(str1).c_str() , ##__VA_ARGS__)
+#define WIDEN_CALL_2(func, str1, str2, ...) \
+ _w ## func(Win32UnicodeString(str1).c_str(), \
+ Win32UnicodeString(str2).c_str() , ##__VA_ARGS__)
+#else
+#define WIDEN_CALL_1(func, str1, ...) func(str1 , ##__VA_ARGS__)
+#define WIDEN_CALL_2(func, str1, str2, ...) func(str1, str2 , ##__VA_ARGS__)
+#endif
+
+FILE* android_fopen(const char* path, const char* mode) {
+#if _MSC_VER
+ Win32UnicodeString wmode(mode);
+ auto normalized = PathUtils::recompose(PathUtils::decompose(path));
+ Win32UnicodeString wpath(normalized);
+
+ const wchar_t* wide_path = wpath.c_str();
+ const wchar_t* wide_mode = wmode.c_str();
+
+ FILE* res = NULL;
+ int err = _wfopen_s(&res, wide_path, wide_mode);
+ if (err != 0) {
+ printf("Failed to open %s, err: %d\n", path, err);
+ }
+ return res;
+#else
+ return WIDEN_CALL_2(fopen, path, mode);
+#endif
+}
+
+FILE* android_popen(const char* path, const char* mode) {
+ return WIDEN_CALL_2(popen, path, mode);
+}
+
+void fdSetCloexec(int fd) {
+#ifndef _WIN32
+ int f = ::fcntl(fd, F_GETFD);
+ ::fcntl(fd, F_SETFD, f | FD_CLOEXEC);
+#endif // !_WIN32
+}
+
+int android_open_without_mode(const char* path, int flags) {
+ int res = WIDEN_CALL_1(open, path, flags | O_CLOEXEC);
+ fdSetCloexec(res);
+ return res;
+}
+
+int android_open_with_mode(const char* path, int flags, mode_t mode) {
+ int res = WIDEN_CALL_1(open, path, flags | O_CLOEXEC, mode);
+ fdSetCloexec(res);
+ return res;
+}
+
+#ifdef _WIN32
+int android_stat(const char* path, struct _stati64* buf) {
+ return _wstati64(Win32UnicodeString(path).c_str(),buf);
+}
+#else
+int android_stat(const char* path, struct stat* buf) {
+ return stat(path, buf);
+}
+#endif
+
+#ifndef _WIN32
+int android_lstat(const char* path, struct stat* buf) {
+ return lstat(path, buf);
+}
+#endif
+
+int android_access(const char* path, int mode) {
+ return WIDEN_CALL_1(access, path, mode);
+}
+
+#ifdef _WIN32
+// The Windows version does not have a mode parameter so just drop that name to
+// avoid compiler warnings about unused parameters
+int android_mkdir(const char* path, mode_t) {
+ return _wmkdir(Win32UnicodeString(path).c_str());
+}
+#else
+int android_mkdir(const char* path, mode_t mode) {
+ return mkdir(path, mode);
+}
+#endif
+
+int android_creat(const char* path, mode_t mode) {
+#ifndef _WIN32
+ return android_open(path, O_CREAT | O_WRONLY | O_TRUNC, mode);
+#else
+ int fd = -1;
+ Win32UnicodeString unipath(path);
+ // Be careful here! The security model in windows is very different.
+ _wsopen_s(&fd, unipath.c_str(), _O_CREAT | _O_BINARY | _O_TRUNC | _O_WRONLY, _SH_DENYNO, _S_IWRITE);
+ return fd;
+#endif
+}
+
+int android_unlink(const char* path) {
+ return WIDEN_CALL_1(unlink, path);
+}
+
+int android_chmod(const char* path, mode_t mode) {
+ return WIDEN_CALL_1(chmod, path, mode);
+}
+
+int android_rmdir(const char* path) {
+#ifdef _MSC_VER
+ // Callers expect 0 on success, win api returns true on success.
+ return !RemoveDirectoryW(Win32UnicodeString(path).c_str());
+#else
+ return WIDEN_CALL_1(rmdir, path);
+#endif
+}
+// The code below uses the fact that GCC supports something called weak linking.
+// Several functions in glibc are weakly linked which means that if the same
+// function name is found in the application binary that function will be used
+// instead. On Windows we use this to change these calls to call the wchar_t
+// equivalent function with the parameter converted from UTF-8 to UTF-16.
+//
+// Unfortunately stat is not weakly linked which is why it is not listed here
+// and any code that calls stat should use android_stat instead.
+// TODO(joshuaduong): Looks like we can't use weak linking with MSVC. Either
+// need to find another way to do this or rename all of these calls to
+// android_*.
+#if defined(_WIN32) && !defined(_MSC_VER)
+
+// getcwd cannot use the same macro as the other calls because it places data
+// in one of its parameters and it returns a char pointer, not a result code
+char* __cdecl getcwd(char* buffer, int maxlen) {
+ ScopedCPtr<wchar_t> wideCwd(_wgetcwd(nullptr, 0));
+ if (wideCwd.get() == nullptr) {
+ return nullptr;
+ }
+ if (buffer == nullptr) {
+ // This is a valid use case and we need to allocate memory and return it
+ auto narrowCwd = Win32UnicodeString::convertToUtf8(wideCwd.get());
+ return strdup(narrowCwd.c_str());
+ }
+
+ int written = Win32UnicodeString::convertToUtf8(buffer,
+ maxlen,
+ wideCwd.get());
+ if (written < 0 || written >= maxlen) {
+ return nullptr;
+ }
+ return buffer;
+}
+
+int __cdecl remove(const char* path) {
+ return WIDEN_CALL_1(remove, path);
+}
+
+int __cdecl rmdir(const char* dirname) {
+ return WIDEN_CALL_1(rmdir, dirname);
+}
+
+int __cdecl chmod(const char* filename, int pmode) {
+ return WIDEN_CALL_1(chmod, filename, pmode);
+}
+
+int __cdecl unlink(const char* filename) {
+ return WIDEN_CALL_1(unlink, filename);
+}
+
+int __cdecl mkdir(const char* dirname) {
+ return WIDEN_CALL_1(mkdir, dirname);
+}
+
+int __cdecl creat(const char* path, int mode) {
+ return WIDEN_CALL_1(creat, path, mode);
+}
+
+int __cdecl access(const char* path, int mode) {
+ return WIDEN_CALL_1(access, path, mode);
+}
+
+int __cdecl open(const char* pathname, int flags, ...) {
+ va_list ap;
+
+ // Since open comes in two versions and C does not support function
+ // overloading we use varargs for the mode parameters instead.
+ va_start(ap, flags);
+ int result = WIDEN_CALL_1(open, pathname, flags, va_arg(ap, int));
+ va_end(ap);
+ return result;
+}
+
+FILE* __cdecl fopen(const char* path, const char* mode) {
+ return WIDEN_CALL_2(fopen, path, mode);
+}
+
+#endif // _WIN32 && !_MSC_VER
diff --git a/base/testing/file_io.h b/base/testing/file_io.h
new file mode 100644
index 0000000..89da31a
--- /dev/null
+++ b/base/testing/file_io.h
@@ -0,0 +1,57 @@
+// Copyright 2020 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.
+#pragma once
+
+#ifdef _MSC_VER
+ #include "msvc-posix.h"
+#endif
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+FILE* android_fopen(const char* path, const char* mode);
+FILE* android_popen(const char* path, const char* mode);
+
+// This thing uses macro varargs to select which open function to use.
+// Because __VA_ARGS__ consumes a different amount of arguments the call_name
+// will have a different name depending on how many arguments are passed
+#define DETERMINE_OPEN_CALL(first, second, third, call_name, ...) call_name
+#define android_open(...) DETERMINE_OPEN_CALL(__VA_ARGS__, \
+ android_open_with_mode, \
+ android_open_without_mode) \
+ (__VA_ARGS__)
+
+// WARNING: Do not use these, just use the android_open macro above the way
+// you would use open. The version to use gets picked automatically
+int android_open_without_mode(const char* path, int flags);
+int android_open_with_mode(const char* path, int flags, mode_t mode);
+
+#ifdef _WIN32
+ #define android_lstat(path, buf) android_stat((path), (buf))
+ int android_stat(const char* path, struct _stati64* buf);
+#else
+int android_stat(const char* path, struct stat* buf);
+int android_lstat(const char* path, struct stat* buf);
+#endif
+
+int android_access(const char* path, int mode);
+int android_mkdir(const char* path, mode_t mode);
+
+int android_creat(const char* path, mode_t mode);
+
+int android_unlink(const char* path);
+int android_chmod(const char* path, mode_t mode);
+
+int android_rmdir(const char* path);
diff --git a/host-common/CMakeLists.txt b/host-common/CMakeLists.txt
index dd85d6b..648dbc8 100644
--- a/host-common/CMakeLists.txt
+++ b/host-common/CMakeLists.txt
@@ -79,18 +79,18 @@
# Tests
add_library(
- gfxstream-host-testing-support
+ gfxstream-host-common-testing-support
testing/HostAddressSpace.cpp
testing/MockAndroidAgentFactory.cpp
testing/MockAndroidEmulatorWindowAgent.cpp
testing/MockAndroidMultiDisplayAgent.cpp
testing/MockAndroidVmOperations.cpp)
target_include_directories(
- gfxstream-host-testing-support
+ gfxstream-host-common-testing-support
PRIVATE
${GFXSTREAM_REPO_ROOT})
target_link_libraries(
- gfxstream-host-testing-support
+ gfxstream-host-common-testing-support
PRIVATE
gtest
gmock)
@@ -107,5 +107,5 @@
PRIVATE
gfxstream-base
gfxstream-host-common
- gfxstream-host-testing-support
+ gfxstream-host-common-testing-support
gtest_main)
diff --git a/host-common/HostAddressSpace_unittest.cpp b/host-common/HostAddressSpace_unittest.cpp
index 66c8b11..fbbd6ab 100644
--- a/host-common/HostAddressSpace_unittest.cpp
+++ b/host-common/HostAddressSpace_unittest.cpp
@@ -11,7 +11,7 @@
// 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 "host-common/HostAddressSpace.h"
+#include "testing/HostAddressSpace.h"
#include "base/AlignedBuf.h"
#include "host-common/AndroidAgentFactory.h"
diff --git a/host-common/address_space_graphics_unittests.cpp b/host-common/address_space_graphics_unittests.cpp
index 8dfc8c3..32a4682 100644
--- a/host-common/address_space_graphics_unittests.cpp
+++ b/host-common/address_space_graphics_unittests.cpp
@@ -29,7 +29,7 @@
#include "host-common/address_space_device.hpp" // for goldfish...
#include "host-common/address_space_graphics.h" // for AddressS...
#include "host-common/address_space_graphics_types.h" // for asg_context
-#include "host-common/HostAddressSpace.h" // for HostAddr...
+#include "testing/HostAddressSpace.h" // for HostAddr...
#include "host-common/globals.h" // for android_hw
namespace android {
diff --git a/stream-servers/CMakeLists.txt b/stream-servers/CMakeLists.txt
index 09b7eb9..2464eb5 100644
--- a/stream-servers/CMakeLists.txt
+++ b/stream-servers/CMakeLists.txt
@@ -20,7 +20,7 @@
add_subdirectory(renderControl_dec)
# Stream server core
-set(gfxstream_backend-common-sources
+set(stream-server-core-sources
ChannelStream.cpp
ColorBuffer.cpp
FbConfig.cpp
@@ -44,23 +44,23 @@
RenderWindow.cpp
RenderLibImpl.cpp
RendererImpl.cpp
- FrameBuffer.cpp
- GfxStreamAgents.cpp
- GfxStreamBackend.cpp
- virtio-gpu-gfxstream-renderer.cpp)
+ FrameBuffer.cpp)
if (APPLE)
- set(gfxstream_backend-platform-sources NativeSubWindow_cocoa.m)
+ set(stream-server-core-platform-sources NativeSubWindow_cocoa.m)
elseif (WIN32)
- set(gfxstream_backend-platform-sources NativeSubWindow_win32.cpp)
+ set(stream-server-core-platform-sources NativeSubWindow_win32.cpp)
else()
- set(gfxstream_backend-platform-sources NativeSubWindow_x11.cpp)
+ set(stream-server-core-platform-sources NativeSubWindow_x11.cpp)
endif()
add_library(
gfxstream_backend
SHARED
- ${gfxstream_backend-common-sources}
- ${gfxstream_backend-platform-sources})
+ ${stream-server-core-sources}
+ ${stream-server-core-platform-sources}
+ GfxStreamAgents.cpp
+ GfxStreamBackend.cpp
+ virtio-gpu-gfxstream-renderer.cpp)
target_link_libraries(
gfxstream_backend
PUBLIC
@@ -71,6 +71,7 @@
gles2_dec
renderControl_dec
gfxstream-vulkan-server
+ gfxstream-snapshot
apigen-codec-common)
target_include_directories(
gfxstream_backend
@@ -96,3 +97,48 @@
gfxstream_backend
gfxstream-base
gtest_main)
+
+# Framebuffer unit tests
+add_library(
+ stream-server-testing-support
+ ${stream-server-core-sources}
+ ${stream-server-core-platform-sources}
+ tests/SampleApplication.cpp)
+target_include_directories(
+ stream-server-testing-support
+ PRIVATE
+ ${GFXSTREAM_REPO_ROOT}
+ PUBLIC
+ ${GFXSTREAM_REPO_ROOT}/base/testing
+ ${GFXSTREAM_REPO_ROOT}
+ ${GFXSTREAM_REPO_ROOT}/include
+ ${GFXSTREAM_REPO_ROOT}/stream-servers
+ ${GFXSTREAM_REPO_ROOT}/stream-servers/apigen-codec-common
+ ${GFXSTREAM_REPO_ROOT}/stream-servers/vulkan)
+target_link_libraries(
+ stream-server-testing-support
+ PUBLIC
+ gfxstream-base
+ gfxstream-host-common
+ OpenGLESDispatch
+ gles1_dec
+ gles2_dec
+ renderControl_dec
+ gfxstream-vulkan-server
+ gfxstream-snapshot
+ apigen-codec-common
+ OSWindow)
+add_executable(
+ OpenglRender_unittests
+ tests/FrameBuffer_unittest.cpp
+ tests/GLSnapshotTesting.cpp
+ tests/OpenGLTestContext.cpp
+ tests/GLTestUtils.cpp
+ tests/ShaderUtils.cpp)
+target_link_libraries(
+ OpenglRender_unittests
+ PRIVATE
+ gfxstream-base-testing-support
+ stream-server-testing-support
+ gfxstream-host-common-testing-support
+ gtest)
diff --git a/stream-servers/glestranslator/GLES_V2/GLESv2Imp.cpp b/stream-servers/glestranslator/GLES_V2/GLESv2Imp.cpp
index 6665576..1ef5290 100644
--- a/stream-servers/glestranslator/GLES_V2/GLESv2Imp.cpp
+++ b/stream-servers/glestranslator/GLES_V2/GLESv2Imp.cpp
@@ -3598,7 +3598,110 @@
isCoreProfileEmulatedFormat(format)) {
format = getCoreProfileEmulatedFormat(format);
}
- ctx->dispatcher().glGetTexImage(target,level,format,type,pixels);
+
+ uint8_t* data = (uint8_t*)pixels;
+
+ if (ctx->dispatcher().glGetTexImage) {
+ ctx->dispatcher().glGetTexImage(target,level,format,type,data);
+ } else {
+
+ // Best effort via readPixels, assume gles 3.0 capabilities underneath
+ GLint prevViewport[4];
+ GLint prevFbo;
+ GLint packAlignment;
+
+ auto gl = ctx->dispatcher();
+
+ gl.glGetIntegerv(GL_VIEWPORT, prevViewport);
+ gl.glGetIntegerv(GL_PACK_ALIGNMENT, &packAlignment);
+ gl.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &prevFbo);
+
+ GLint width;
+ GLint height;
+ GLint depth;
+ gl.glGetTexLevelParameteriv(target, level, GL_TEXTURE_WIDTH, &width);
+ gl.glGetTexLevelParameteriv(target, level, GL_TEXTURE_HEIGHT, &height);
+ gl.glGetTexLevelParameteriv(target, level, GL_TEXTURE_DEPTH, &depth);
+
+ GLuint fbo;
+ gl.glGenFramebuffers(1, &fbo);
+ gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
+
+ GLenum attachment = GL_COLOR_ATTACHMENT0;
+
+ switch (format) {
+ case GL_DEPTH_COMPONENT:
+ attachment = GL_DEPTH_ATTACHMENT;
+ break;
+ case GL_DEPTH_STENCIL:
+ attachment = GL_DEPTH_STENCIL_ATTACHMENT;
+ break;
+ }
+
+ unsigned int tex = ctx->getBindedTexture(target);
+ GLuint globalName = ctx->shareGroup()->getGlobalName(
+ NamedObjectType::TEXTURE, tex);
+
+ // Do stuff
+ switch (target) {
+ case GL_TEXTURE_2D:
+ case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+ case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+ case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+ case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+ case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+ case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+ gl.glFramebufferTexture2D(
+ GL_READ_FRAMEBUFFER, attachment, target,
+ globalName, level);
+ gl.glReadPixels(0, 0, width, height,
+ format, type,
+ data);
+ gl.glFramebufferTexture2D(
+ GL_READ_FRAMEBUFFER, attachment, target,
+ 0, level);
+ break;
+ case GL_TEXTURE_3D: {
+ unsigned int layerImgSize = texImageSize(
+ format, type, packAlignment, width, height);
+ for (unsigned int d = 0; d < depth; d++) {
+ gl.glFramebufferTexture3DOES(
+ GL_READ_FRAMEBUFFER, attachment, target,
+ globalName, level, d);
+ gl.glReadPixels(0, 0, width,
+ height, format,
+ type, data +
+ layerImgSize * d);
+ gl.glFramebufferTexture3DOES(
+ GL_READ_FRAMEBUFFER, attachment, target,
+ 0, level, d);
+ }
+ break;
+ }
+ case GL_TEXTURE_2D_ARRAY: {
+ unsigned int layerImgSize = texImageSize(
+ format, type, packAlignment, width, height);
+ for (unsigned int d = 0; d < depth; d++) {
+ gl.glFramebufferTextureLayer(
+ GL_READ_FRAMEBUFFER, attachment,
+ globalName, level, d);
+ gl.glReadPixels(0, 0, width,
+ height, format,
+ type, data +
+ layerImgSize * d);
+ gl.glFramebufferTextureLayer(
+ GL_READ_FRAMEBUFFER, attachment,
+ 0, level, d);
+ }
+ break;
+ }
+ }
+
+ gl.glBindFramebuffer(GL_READ_FRAMEBUFFER, prevFbo);
+ gl.glDeleteFramebuffers(1, &fbo);
+ gl.glViewport(prevViewport[0], prevViewport[1], prevViewport[2], prevViewport[3]);
+ gl.glGetError();
+ }
}
GL_APICALL void GL_APIENTRY glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid* pixels){
diff --git a/stream-servers/glestranslator/GLcommon/GLDispatch.cpp b/stream-servers/glestranslator/GLcommon/GLDispatch.cpp
index 9cfc657..69154fe 100644
--- a/stream-servers/glestranslator/GLcommon/GLDispatch.cpp
+++ b/stream-servers/glestranslator/GLcommon/GLDispatch.cpp
@@ -22,7 +22,7 @@
#include "ErrorLog.h"
-#define GL_LOG(...)
+#define GL_LOG(fmt,...)
#ifdef __linux__
#include <GL/glx.h>
@@ -73,7 +73,7 @@
func_name = (__typeof__(func_name))(address); \
} else { \
GL_LOG("%s not found", #func_name); \
- func_name = (__typeof__(func_name))(dummy_##func_name); \
+ func_name = nullptr; \
} \
} \
} while (0);
diff --git a/stream-servers/glestranslator/GLcommon/SaveableTexture.cpp b/stream-servers/glestranslator/GLcommon/SaveableTexture.cpp
index a83f46c..e8d89b4 100644
--- a/stream-servers/glestranslator/GLcommon/SaveableTexture.cpp
+++ b/stream-servers/glestranslator/GLcommon/SaveableTexture.cpp
@@ -372,6 +372,10 @@
bool shouldUseReadPixels(
GLenum target, GLenum level, GLenum format, GLenum type) {
+
+ auto gl = GLEScontext::dispatcher();
+ if (!gl.glGetTexImage) return true;
+
// TODO: if (isGles2Gles()) return true
// TODO: Query extensions for support for these kinds of things
diff --git a/stream-servers/glestranslator/GLcommon/TextureUtils.cpp b/stream-servers/glestranslator/GLcommon/TextureUtils.cpp
index a688e69..6c4d9c4 100644
--- a/stream-servers/glestranslator/GLcommon/TextureUtils.cpp
+++ b/stream-servers/glestranslator/GLcommon/TextureUtils.cpp
@@ -794,3 +794,274 @@
}
return false;
}
+
+static uint32_t s_texAlign(uint32_t v, uint32_t align) {
+ uint32_t rem = v % align;
+ return rem ? (v + (align - rem)) : v;
+}
+
+// s_computePixelSize is both in the host and the guest. Consider moving it to
+// android-emugl/shared
+
+static int s_computePixelSize(GLenum format, GLenum type) {
+#define FORMAT_ERROR(format, type) \
+ fprintf(stderr, "%s:%d unknown format/type 0x%x 0x%x\n", __FUNCTION__, \
+ __LINE__, format, type);
+
+ switch (type) {
+ case GL_BYTE:
+ switch (format) {
+ case GL_R8:
+ case GL_R8I:
+ case GL_R8_SNORM:
+ case GL_RED:
+ return 1;
+ case GL_RED_INTEGER:
+ return 1;
+ case GL_RG8:
+ case GL_RG8I:
+ case GL_RG8_SNORM:
+ case GL_RG:
+ return 1 * 2;
+ case GL_RG_INTEGER:
+ return 1 * 2;
+ case GL_RGB8:
+ case GL_RGB8I:
+ case GL_RGB8_SNORM:
+ case GL_RGB:
+ return 1 * 3;
+ case GL_RGB_INTEGER:
+ return 1 * 3;
+ case GL_RGBA8:
+ case GL_RGBA8I:
+ case GL_RGBA8_SNORM:
+ case GL_RGBA:
+ return 1 * 4;
+ case GL_RGBA_INTEGER:
+ return 1 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_UNSIGNED_BYTE:
+ switch (format) {
+ case GL_R8:
+ case GL_R8UI:
+ case GL_RED:
+ return 1;
+ case GL_RED_INTEGER:
+ return 1;
+ case GL_ALPHA8_EXT:
+ case GL_ALPHA:
+ return 1;
+ case GL_LUMINANCE8_EXT:
+ case GL_LUMINANCE:
+ return 1;
+ case GL_LUMINANCE8_ALPHA8_EXT:
+ case GL_LUMINANCE_ALPHA:
+ return 1 * 2;
+ case GL_RG8:
+ case GL_RG8UI:
+ case GL_RG:
+ return 1 * 2;
+ case GL_RG_INTEGER:
+ return 1 * 2;
+ case GL_RGB8:
+ case GL_RGB8UI:
+ case GL_SRGB8:
+ case GL_RGB:
+ return 1 * 3;
+ case GL_RGB_INTEGER:
+ return 1 * 3;
+ case GL_RGBA8:
+ case GL_RGBA8UI:
+ case GL_SRGB8_ALPHA8:
+ case GL_RGBA:
+ return 1 * 4;
+ case GL_RGBA_INTEGER:
+ return 1 * 4;
+ case GL_BGRA_EXT:
+ case GL_BGRA8_EXT:
+ return 1 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_SHORT:
+ switch (format) {
+ case GL_R16I:
+ case GL_RED_INTEGER:
+ return 2;
+ case GL_RG16I:
+ case GL_RG_INTEGER:
+ return 2 * 2;
+ case GL_RGB16I:
+ case GL_RGB_INTEGER:
+ return 2 * 3;
+ case GL_RGBA16I:
+ case GL_RGBA_INTEGER:
+ return 2 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_UNSIGNED_SHORT:
+ switch (format) {
+ case GL_DEPTH_COMPONENT16:
+ case GL_DEPTH_COMPONENT:
+ return 2;
+ case GL_R16UI:
+ case GL_RED_INTEGER:
+ return 2;
+ case GL_RG16UI:
+ case GL_RG_INTEGER:
+ return 2 * 2;
+ case GL_RGB16UI:
+ case GL_RGB_INTEGER:
+ return 2 * 3;
+ case GL_RGBA16UI:
+ case GL_RGBA_INTEGER:
+ return 2 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_INT:
+ switch (format) {
+ case GL_R32I:
+ case GL_RED_INTEGER:
+ return 4;
+ case GL_RG32I:
+ case GL_RG_INTEGER:
+ return 4 * 2;
+ case GL_RGB32I:
+ case GL_RGB_INTEGER:
+ return 4 * 3;
+ case GL_RGBA32I:
+ case GL_RGBA_INTEGER:
+ return 4 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_UNSIGNED_INT:
+ switch (format) {
+ case GL_DEPTH_COMPONENT16:
+ case GL_DEPTH_COMPONENT24:
+ case GL_DEPTH_COMPONENT32_OES:
+ case GL_DEPTH_COMPONENT:
+ return 4;
+ case GL_R32UI:
+ case GL_RED_INTEGER:
+ return 4;
+ case GL_RG32UI:
+ case GL_RG_INTEGER:
+ return 4 * 2;
+ case GL_RGB32UI:
+ case GL_RGB_INTEGER:
+ return 4 * 3;
+ case GL_RGBA32UI:
+ case GL_RGBA_INTEGER:
+ return 4 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_UNSIGNED_SHORT_4_4_4_4:
+ case GL_UNSIGNED_SHORT_5_5_5_1:
+ case GL_UNSIGNED_SHORT_5_6_5:
+ case GL_UNSIGNED_SHORT_4_4_4_4_REV_EXT:
+ case GL_UNSIGNED_SHORT_1_5_5_5_REV_EXT:
+ return 2;
+ case GL_UNSIGNED_INT_10F_11F_11F_REV:
+ case GL_UNSIGNED_INT_5_9_9_9_REV:
+ case GL_UNSIGNED_INT_2_10_10_10_REV:
+ case GL_UNSIGNED_INT_24_8_OES:
+ return 4;
+ case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+ return 4 + 4;
+ case GL_FLOAT:
+ switch (format) {
+ case GL_DEPTH_COMPONENT32F:
+ case GL_DEPTH_COMPONENT:
+ return 4;
+ case GL_ALPHA32F_EXT:
+ case GL_ALPHA:
+ return 4;
+ case GL_LUMINANCE32F_EXT:
+ case GL_LUMINANCE:
+ return 4;
+ case GL_LUMINANCE_ALPHA32F_EXT:
+ case GL_LUMINANCE_ALPHA:
+ return 4 * 2;
+ case GL_RED:
+ return 4;
+ case GL_R32F:
+ return 4;
+ case GL_RG:
+ return 4 * 2;
+ case GL_RG32F:
+ return 4 * 2;
+ case GL_RGB:
+ return 4 * 3;
+ case GL_RGB32F:
+ return 4 * 3;
+ case GL_RGBA:
+ return 4 * 4;
+ case GL_RGBA32F:
+ return 4 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ case GL_HALF_FLOAT:
+ case GL_HALF_FLOAT_OES:
+ switch (format) {
+ case GL_ALPHA16F_EXT:
+ case GL_ALPHA:
+ return 2;
+ case GL_LUMINANCE16F_EXT:
+ case GL_LUMINANCE:
+ return 2;
+ case GL_LUMINANCE_ALPHA16F_EXT:
+ case GL_LUMINANCE_ALPHA:
+ return 2 * 2;
+ case GL_RED:
+ return 2;
+ case GL_R16F:
+ return 2;
+ case GL_RG:
+ return 2 * 2;
+ case GL_RG16F:
+ return 2 * 2;
+ case GL_RGB:
+ return 2 * 3;
+ case GL_RGB16F:
+ return 2 * 3;
+ case GL_RGBA:
+ return 2 * 4;
+ case GL_RGBA16F:
+ return 2 * 4;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+ break;
+ default:
+ FORMAT_ERROR(format, type);
+ }
+
+ return 0;
+}
+
+uint32_t texImageSize(GLenum internalformat,
+ GLenum type,
+ int unpackAlignment,
+ GLsizei width,
+ GLsizei height) {
+
+ uint32_t alignedWidth = s_texAlign(width, unpackAlignment);
+ uint32_t pixelSize = s_computePixelSize(internalformat, type);
+ uint32_t totalSize = pixelSize * alignedWidth * height;
+
+ return totalSize;
+}
diff --git a/stream-servers/glestranslator/include/GLcommon/TextureUtils.h b/stream-servers/glestranslator/include/GLcommon/TextureUtils.h
index 4723c26..e16b277 100644
--- a/stream-servers/glestranslator/include/GLcommon/TextureUtils.h
+++ b/stream-servers/glestranslator/include/GLcommon/TextureUtils.h
@@ -90,4 +90,11 @@
bool isEtc2OrAstcFormat(GLenum format);
bool shouldPassthroughCompressedFormat(GLEScontext* ctx, GLenum internalformat);
+
+uint32_t texImageSize(GLenum internalformat,
+ GLenum type,
+ int unpackAlignment,
+ GLsizei width,
+ GLsizei height);
+
#endif
diff --git a/stream-servers/tests/DefaultFramebufferBlit_unittest.cpp b/stream-servers/tests/DefaultFramebufferBlit_unittest.cpp
new file mode 100644
index 0000000..901b981
--- /dev/null
+++ b/stream-servers/tests/DefaultFramebufferBlit_unittest.cpp
@@ -0,0 +1,270 @@
+// 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 <gtest/gtest.h>
+
+#include "Standalone.h"
+#include "GLTestUtils.h"
+
+#include <memory>
+
+using android::base::System;
+
+namespace emugl {
+
+struct ClearColorParam {
+ const GLESApi glVersion;
+ const bool fastBlit;
+
+ ClearColorParam(GLESApi glVersion, bool fastBlit)
+ : glVersion(glVersion), fastBlit(fastBlit) {}
+};
+
+static void PrintTo(const ClearColorParam& param, std::ostream* os) {
+ *os << "ClearColorParam(";
+
+ switch (param.glVersion) {
+ case GLESApi_CM: *os << "GLESApi_CM"; break;
+ case GLESApi_2: *os << "GLESApi_2"; break;
+ case GLESApi_3_0: *os << "GLESApi_3_0"; break;
+ case GLESApi_3_1: *os << "GLESApi_3_1"; break;
+ case GLESApi_3_2: *os << "GLESApi_3_2"; break;
+ default: *os << "GLESApi(" << int(param.glVersion) << ")"; break;
+ }
+
+ *os << ", " << (param.fastBlit ? "fast blit" : "slow blit") << ")";
+}
+
+class ClearColor final : public SampleApplication {
+public:
+ ClearColor(ClearColorParam param) : SampleApplication(256, 256, 60, param.glVersion) {
+ if (!param.fastBlit) {
+ // Disable fast blit and then recreate the color buffer to apply the
+ // change.
+ mFb->disableFastBlit();
+
+ mFb->closeColorBuffer(mColorBuffer);
+ mColorBuffer = mFb->createColorBuffer(
+ mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ mFb->setWindowSurfaceColorBuffer(mSurface, mColorBuffer);
+ }
+ }
+
+ ~ClearColor() {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+ if (mFbo) {
+ gl->glDeleteFramebuffers(1, &mFbo);
+ gl->glDeleteTextures(1, &mTexture);
+ }
+ }
+
+ void setClearColor(float r, float g, float b, float a) {
+ mRed = r;
+ mGreen = g;
+ mBlue = b;
+ mAlpha = a;
+ }
+
+ void setUseFboCombined(bool useFbo) {
+ mUseFboCombined = useFbo;
+ }
+
+ void setUseFbo(bool useFboDraw, bool useFboRead) {
+ mUseFboDraw = useFboDraw;
+ mUseFboRead = useFboRead;
+ }
+
+ void initialize() override {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+ gl->glActiveTexture(GL_TEXTURE0);
+
+ if (!mFbo) {
+ gl->glGenFramebuffers(1, &mFbo);
+ gl->glGenTextures(1, &mTexture);
+
+ gl->glBindTexture(GL_TEXTURE_2D, mTexture);
+ gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+ mWidth, mHeight, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, 0);
+ gl->glBindTexture(GL_TEXTURE_2D, 0);
+
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+ gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, mTexture, 0);
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+ }
+
+ void draw() override {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, mUseFboCombined ? mFbo : 0);
+
+ if (mUseFboDraw) {
+ gl->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFbo);
+ }
+ if (mUseFboRead) {
+ gl->glBindFramebuffer(GL_READ_FRAMEBUFFER, mFbo);
+ }
+
+ gl->glClearColor(mRed, mGreen, mBlue, mAlpha);
+ gl->glClear(GL_COLOR_BUFFER_BIT);
+ }
+
+ void setNonDefaultCombinedFbo() {
+ setUseFboCombined(true);
+ }
+
+ void setNonDefaultDrawFbo() {
+ setUseFbo(true, mUseFboRead);
+ }
+
+ void setNonDefaultReadFbo() {
+ setUseFbo(mUseFboDraw, true);
+ }
+
+ void drawWithColor(const float drawColor[4]) {
+ setClearColor(drawColor[0], drawColor[1], drawColor[2], drawColor[3]);
+ drawOnce();
+ }
+
+ void verifySwappedColor(const float wantedColor[4]) {
+ TestTexture targetBuffer =
+ createTestTextureRGBA8888SingleColor(
+ mWidth, mHeight, wantedColor[0], wantedColor[1], wantedColor[2], wantedColor[3]);
+
+ TestTexture forRead =
+ createTestTextureRGBA8888SingleColor(
+ mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+
+ mFb->readColorBuffer(
+ mColorBuffer, 0, 0, mWidth, mHeight,
+ GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(
+ ImageMatches(mWidth, mHeight, 4, mWidth, targetBuffer.data(), forRead.data()));
+ }
+
+private:
+ bool mUseFboCombined = false;
+ bool mUseFboDraw = false;
+ bool mUseFboRead = false;
+
+ GLuint mFbo = 0;
+ GLuint mTexture = 0;
+
+ float mRed = 1.0f;
+ float mGreen = 1.0f;
+ float mBlue = 1.0f;
+ float mAlpha = 1.0f;
+};
+
+static constexpr float kDrawColorRed[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
+static constexpr float kDrawColorGreen[4] = { 0.0f, 1.0f, 0.0f, 1.0f };
+
+class CombinedFramebufferBlit : public ::testing::Test, public ::testing::WithParamInterface<ClearColorParam> {
+protected:
+ virtual void SetUp() override {
+ mApp.reset(new ClearColor(GetParam()));
+ }
+
+ virtual void TearDown() override {
+ mApp.reset();
+ EXPECT_EQ(EGL_SUCCESS, LazyLoadedEGLDispatch::get()->eglGetError())
+ << "DefaultFramebufferBlitTest TearDown found an EGL error";
+ }
+
+ std::unique_ptr<ClearColor> mApp;
+};
+
+TEST_P(CombinedFramebufferBlit, DefaultDrawDefaultRead) {
+ // Draw to default framebuffer; color should show up
+ mApp->drawWithColor(kDrawColorRed);
+ mApp->verifySwappedColor(kDrawColorRed);
+}
+
+TEST_P(CombinedFramebufferBlit, NonDefault) {
+ mApp->drawWithColor(kDrawColorRed);
+ mApp->verifySwappedColor(kDrawColorRed);
+
+ // Color should not be affected by the draw to a non-default framebuffer.
+ mApp->setNonDefaultCombinedFbo();
+
+ mApp->drawWithColor(kDrawColorGreen);
+ mApp->verifySwappedColor(kDrawColorRed);
+}
+
+// Test blitting both with and without the fast blit path.
+INSTANTIATE_TEST_CASE_P(CombinedFramebufferBlitTest,
+ CombinedFramebufferBlit,
+ testing::Values(
+ ClearColorParam(GLESApi_CM, true),
+ ClearColorParam(GLESApi_CM, false),
+ ClearColorParam(GLESApi_2, true),
+ ClearColorParam(GLESApi_2, false),
+ ClearColorParam(GLESApi_3_0, true),
+ ClearColorParam(GLESApi_3_0, false)));
+
+class NonDefaultFramebufferBlit : public CombinedFramebufferBlit { };
+
+TEST_P(NonDefaultFramebufferBlit, NonDefaultDrawNonDefaultRead) {
+ mApp->drawWithColor(kDrawColorRed);
+ mApp->verifySwappedColor(kDrawColorRed);
+
+ // Color should not be affected by the draw to non-default
+ // draw/read framebuffers.
+ // (we preserve the previous contents of the surface in
+ // SampleApplication::drawOnce)
+ mApp->setNonDefaultDrawFbo();
+ mApp->setNonDefaultReadFbo();
+
+ mApp->drawWithColor(kDrawColorGreen);
+ mApp->verifySwappedColor(kDrawColorRed);
+}
+
+TEST_P(NonDefaultFramebufferBlit, DefaultDrawNonDefaultRead) {
+ mApp->drawWithColor(kDrawColorRed);
+ mApp->verifySwappedColor(kDrawColorRed);
+
+ // Bug: 110996998
+ // Draw to default framebuffer, and have a non-default
+ // read framebuffer bound. Color should show up
+ mApp->setNonDefaultReadFbo();
+
+ mApp->drawWithColor(kDrawColorGreen);
+ mApp->verifySwappedColor(kDrawColorGreen);
+}
+
+TEST_P(NonDefaultFramebufferBlit, NonDefaultDrawDefaultRead) {
+ mApp->drawWithColor(kDrawColorRed);
+ mApp->verifySwappedColor(kDrawColorRed);
+
+ // Draw to non-default framebuffer, and have the default
+ // read framebuffer bound. Color should not show up.
+ // (we preserve the previous contents of the surface in
+ // SampleApplication::drawOnce)
+ mApp->setNonDefaultDrawFbo();
+
+ mApp->drawWithColor(kDrawColorGreen);
+ mApp->verifySwappedColor(kDrawColorRed);
+}
+
+// Test blitting both with and without the fast blit path.
+INSTANTIATE_TEST_CASE_P(DefaultFramebufferBlitTest,
+ NonDefaultFramebufferBlit,
+ testing::Values(
+ ClearColorParam(GLESApi_3_0, true),
+ ClearColorParam(GLESApi_3_0, false)));
+
+
+} // namespace emugl
diff --git a/stream-servers/tests/FrameBuffer_unittest.cpp b/stream-servers/tests/FrameBuffer_unittest.cpp
new file mode 100644
index 0000000..1f477f4
--- /dev/null
+++ b/stream-servers/tests/FrameBuffer_unittest.cpp
@@ -0,0 +1,884 @@
+// 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 "base/PathUtils.h"
+#include "base/StdioStream.h"
+#include "base/GLObjectCounter.h"
+#include "base/System.h"
+#include "base/testing/TestSystem.h"
+#include "host-common/AndroidAgentFactory.h"
+#include "host-common/multi_display_agent.h"
+#include "host-common/window_agent.h"
+#include "host-common/MultiDisplay.h"
+#include "snapshot/TextureLoader.h"
+#include "snapshot/TextureSaver.h"
+
+#include "GLSnapshotTesting.h"
+#include "GLTestUtils.h"
+#include "Standalone.h"
+
+#include <gtest/gtest.h>
+#include <memory>
+
+
+#ifdef _MSC_VER
+#include "msvc-posix.h"
+#else
+#include <sys/time.h>
+#endif
+
+using android::base::StdioStream;
+using android::snapshot::TextureLoader;
+using android::snapshot::TextureSaver;
+
+namespace emugl {
+
+class FrameBufferTest : public ::testing::Test {
+public:
+ FrameBufferTest() = default;
+
+protected:
+
+ virtual void SetUp() override {
+ // setupStandaloneLibrarySearchPaths();
+ emugl::setGLObjectCounter(android::base::GLObjectCounter::get());
+ emugl::set_emugl_window_operations(*getConsoleAgents()->emu);
+ emugl::set_emugl_multi_display_operations(*getConsoleAgents()->multi_display);
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ ASSERT_NE(nullptr, egl);
+ ASSERT_NE(nullptr, LazyLoadedGLESv2Dispatch::get());
+
+ // bool useHostGpu = shouldUseHostGpu();
+ bool useHostGpu = false;
+ mWindow = createOrGetTestWindow(mXOffset, mYOffset, mWidth, mHeight);
+ mUseSubWindow = mWindow != nullptr;
+
+ if (mUseSubWindow) {
+ ASSERT_NE(nullptr, mWindow->getFramebufferNativeWindow());
+
+ EXPECT_TRUE(
+ FrameBuffer::initialize(
+ mWidth, mHeight,
+ mUseSubWindow,
+ !useHostGpu /* egl2egl */));
+ mFb = FrameBuffer::getFB();
+ EXPECT_NE(nullptr, mFb);
+
+ mFb->setupSubWindow(
+ (FBNativeWindowType)(uintptr_t)
+ mWindow->getFramebufferNativeWindow(),
+ 0, 0,
+ mWidth, mHeight, mWidth, mHeight,
+ mWindow->getDevicePixelRatio(), 0, false, false);
+ mWindow->messageLoop();
+ } else {
+ EXPECT_TRUE(
+ FrameBuffer::initialize(
+ mWidth, mHeight,
+ mUseSubWindow,
+ !useHostGpu /* egl2egl */));
+ mFb = FrameBuffer::getFB();
+ ASSERT_NE(nullptr, mFb);
+ }
+ EXPECT_EQ(EGL_SUCCESS, egl->eglGetError());
+
+ mRenderThreadInfo = new RenderThreadInfo();
+
+ // Snapshots
+ mTestSystem.getTempRoot()->makeSubDir("Snapshots");
+ mSnapshotPath = mTestSystem.getTempRoot()->makeSubPath("Snapshots");
+ mTimeStamp = std::to_string(android::base::getUnixTimeUs());
+ mSnapshotFile = android::base::pj({mSnapshotPath, std::string("snapshot_") + mTimeStamp + ".snap"});
+ mTextureFile = android::base::pj({mSnapshotPath, std::string("textures_") + mTimeStamp + ".stex"});
+ }
+
+ virtual void TearDown() override {
+ if (mFb) {
+ delete mFb; // destructor calls finalize
+ }
+
+ delete mRenderThreadInfo;
+ EXPECT_EQ(EGL_SUCCESS, LazyLoadedEGLDispatch::get()->eglGetError())
+ << "FrameBufferTest TearDown found EGL error";
+ }
+
+ void saveSnapshot() {
+ std::unique_ptr<StdioStream> m_stream(new StdioStream(
+ android_fopen(mSnapshotFile.c_str(), "wb"), StdioStream::kOwner));
+ std::shared_ptr<TextureSaver> m_texture_saver(new TextureSaver(StdioStream(
+ android_fopen(mTextureFile.c_str(), "wb"), StdioStream::kOwner)));
+ mFb->onSave(m_stream.get(), m_texture_saver);
+
+ m_stream->close();
+ m_texture_saver->done();
+ }
+
+ void loadSnapshot() {
+ // unbind so load will destroy previous objects
+ mFb->bindContext(0, 0, 0);
+
+ std::unique_ptr<StdioStream> m_stream(new StdioStream(
+ android_fopen(mSnapshotFile.c_str(), "rb"), StdioStream::kOwner));
+ std::shared_ptr<TextureLoader> m_texture_loader(
+ new TextureLoader(StdioStream(android_fopen(mTextureFile.c_str(), "rb"),
+ StdioStream::kOwner)));
+ mFb->onLoad(m_stream.get(), m_texture_loader);
+ m_stream->close();
+ m_texture_loader->join();
+ }
+
+ bool mUseSubWindow = false;
+ OSWindow* mWindow = nullptr;
+ FrameBuffer* mFb = nullptr;
+ RenderThreadInfo* mRenderThreadInfo = nullptr;
+
+ int mWidth = 256;
+ int mHeight = 256;
+ int mXOffset= 400;
+ int mYOffset= 400;
+
+ android::base::TestSystem mTestSystem;
+ std::string mSnapshotPath;
+ std::string mTimeStamp;
+ std::string mSnapshotFile;
+ std::string mTextureFile;
+};
+
+// Tests that framebuffer initialization and finalization works.
+TEST_F(FrameBufferTest, FrameBufferBasic) {
+}
+
+// Tests the creation of a single color buffer for the framebuffer.
+TEST_F(FrameBufferTest, CreateColorBuffer) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ // FramBuffer::finalize handles color buffer destruction here
+}
+
+// Tests both creation and closing a color buffer.
+TEST_F(FrameBufferTest, CreateCloseColorBuffer) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ mFb->closeColorBuffer(handle);
+}
+
+// Tests create, open, and close color buffer.
+TEST_F(FrameBufferTest, CreateOpenCloseColorBuffer) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+ mFb->closeColorBuffer(handle);
+}
+
+// Tests that the color buffer can be update with a test pattern and that
+// the test pattern can be read back from the color buffer.
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestTextureRGBA8888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(), forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer_ReadYUV420) {
+ HandleType handle = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA,
+ FRAMEWORK_FORMAT_YUV_420_888);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA,
+ GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestPatternRGBA8888(mWidth, mHeight);
+ memset(forRead.data(), 0x0, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+ memset(forRead.data(), 0xff, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer_ReadNV12) {
+ HandleType handle = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA,
+ FRAMEWORK_FORMAT_NV12);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA,
+ GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestPatternRGBA8888(mWidth, mHeight);
+ memset(forRead.data(), 0x0, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+ memset(forRead.data(), 0xff, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer_ReadNV12TOYUV420) {
+ // nv12
+ mWidth = 8;
+ mHeight = 8;
+ HandleType handle_nv12 = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA,
+ FRAMEWORK_FORMAT_NV12);
+ EXPECT_NE(0, handle_nv12);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle_nv12));
+
+ uint8_t forUpdate[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
+ 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3};
+
+ uint8_t golden[] = {
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ };
+
+ mFb->updateColorBuffer(handle_nv12, 0, 0, mWidth, mHeight, GL_RGBA,
+ GL_UNSIGNED_BYTE, forUpdate);
+
+ // yuv420
+ HandleType handle_yuv420 = mFb->createColorBuffer(
+ mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_YUV_420_888);
+ EXPECT_NE(0, handle_yuv420);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle_yuv420));
+
+ uint32_t textures[2] = {1, 2};
+
+ mFb->swapTexturesAndUpdateColorBuffer(handle_nv12, 0, 0, mWidth, mHeight,
+ GL_RGBA, GL_UNSIGNED_BYTE,
+ FRAMEWORK_FORMAT_NV12, textures);
+ mFb->swapTexturesAndUpdateColorBuffer(handle_yuv420, 0, 0, mWidth, mHeight,
+ GL_RGBA, GL_UNSIGNED_BYTE,
+ FRAMEWORK_FORMAT_NV12, textures);
+
+ uint8_t forRead[sizeof(golden)];
+ memset(forRead, 0x0, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle_yuv420, 0, 0, mWidth, mHeight, forRead,
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(
+ ImageMatches(mWidth, mHeight * 3 / 2, 1, mWidth, golden, forRead));
+
+ mFb->closeColorBuffer(handle_nv12);
+ mFb->closeColorBuffer(handle_yuv420);
+}
+
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer_ReadYV12) {
+ mWidth = 20 * 16;
+ HandleType handle = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA,
+ FRAMEWORK_FORMAT_YV12);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA,
+ GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestPatternRGBA8888(mWidth, mHeight);
+ memset(forRead.data(), 0x0, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+ memset(forRead.data(), 0xff, mWidth * mHeight * 3 / 2);
+ mFb->readColorBufferYUV(handle, 0, 0, mWidth, mHeight, forRead.data(),
+ mWidth * mHeight * 3 / 2);
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(),
+ forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+// bug: 110105029
+// Tests that color buffer updates should not fail if there is a format change.
+// Needed to accomodate format-changing behavior from the guest gralloc.
+TEST_F(FrameBufferTest, CreateOpenUpdateCloseColorBuffer_FormatChange) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGB888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGB, GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestTextureRGB888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f);
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGB, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 3, mWidth, forUpdate.data(),
+ forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+// Tests obtaining EGL configs from FrameBuffer.
+TEST_F(FrameBufferTest, Configs) {
+ const FbConfigList* configs = mFb->getConfigs();
+ EXPECT_GE(configs->size(), 0);
+}
+
+// Tests creating GL context from FrameBuffer.
+TEST_F(FrameBufferTest, CreateRenderContext) {
+ HandleType handle = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ EXPECT_NE(0, handle);
+}
+
+// Tests creating window surface from FrameBuffer.
+TEST_F(FrameBufferTest, CreateWindowSurface) {
+ HandleType handle = mFb->createWindowSurface(0, mWidth, mHeight);
+ EXPECT_NE(0, handle);
+}
+
+// Tests eglMakeCurrent from FrameBuffer.
+TEST_F(FrameBufferTest, CreateBindRenderContext) {
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+}
+
+// A basic blit test that simulates what the guest system does in one pass
+// of draw + eglSwapBuffers:
+// 1. Draws in OpenGL with glClear.
+// 2. Calls flushWindowSurfaceColorBuffer(), which is the "backing operation" of
+// ANativeWindow::queueBuffer in the guest.
+// 3. Calls post() with the resulting color buffer, the backing operation of fb device "post"
+// in the guest.
+TEST_F(FrameBufferTest, BasicBlit) {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ HandleType colorBuffer =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+ EXPECT_TRUE(mFb->setWindowSurfaceColorBuffer(surface, colorBuffer));
+
+ float colors[3][4] = {
+ { 1.0f, 0.0f, 0.0f, 1.0f},
+ { 0.0f, 1.0f, 0.0f, 1.0f},
+ { 0.0f, 0.0f, 1.0f, 1.0f},
+ };
+
+ for (int i = 0; i < 3; i++) {
+ float* color = colors[i];
+
+ gl->glClearColor(color[0], color[1], color[2], color[3]);
+ gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ mFb->flushWindowSurfaceColorBuffer(surface);
+
+ TestTexture targetBuffer =
+ createTestTextureRGBA8888SingleColor(
+ mWidth, mHeight, color[0], color[1], color[2], color[3]);
+
+ TestTexture forRead =
+ createTestTextureRGBA8888SingleColor(
+ mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+
+ mFb->readColorBuffer(
+ colorBuffer, 0, 0, mWidth, mHeight,
+ GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(
+ ImageMatches(
+ mWidth, mHeight, 4, mWidth,
+ targetBuffer.data(), forRead.data()));
+
+ if (mUseSubWindow) {
+ mFb->post(colorBuffer);
+ mWindow->messageLoop();
+ }
+ }
+
+ EXPECT_TRUE(mFb->bindContext(0, 0, 0));
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->DestroyWindowSurface(surface);
+}
+
+// Tests that snapshot works with an empty FrameBuffer.
+TEST_F(FrameBufferTest, SnapshotSmokeTest) {
+ saveSnapshot();
+ loadSnapshot();
+}
+
+// Tests that the snapshot restores the clear color state, by changing the clear
+// color in between save and load. If this fails, it means failure to restore a
+// number of different states from GL contexts.
+TEST_F(FrameBufferTest, SnapshotPreserveColorClear) {
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+ gl->glClearColor(1, 1, 1, 1);
+ gl->glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, {1, 1, 1, 1}));
+
+ saveSnapshot();
+
+ gl->glClearColor(0.5, 0.5, 0.5, 0.5);
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE,
+ {0.5, 0.5, 0.5, 0.5}));
+
+ loadSnapshot();
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, {1, 1, 1, 1}));
+}
+
+// Tests that snapshot works to save the state of a single ColorBuffer; we
+// upload a test pattern to the ColorBuffer, take a snapshot, load it, and
+// verify that the contents are the same.
+TEST_F(FrameBufferTest, SnapshotSingleColorBuffer) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate.data());
+
+ saveSnapshot();
+ loadSnapshot();
+
+ TestTexture forRead = createTestTextureRGBA8888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(), forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+// bug: 111360779
+// Tests that the ColorBuffer is successfully updated even if a reformat happens
+// on restore; the reformat may mess up the texture restore logic.
+// In ColorBuffer::subUpdate, this test is known to fail if touch() is moved after the reformat.
+TEST_F(FrameBufferTest, SnapshotColorBufferSubUpdateRestore) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+
+ saveSnapshot();
+ loadSnapshot();
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestTextureRGBA8888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(), forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+// bug: 111558407
+// Tests that ColorBuffer's blit path is retained on save/restore.
+TEST_F(FrameBufferTest, SnapshotFastBlitRestore) {
+ HandleType handle = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA,
+ FRAMEWORK_FORMAT_GL_COMPATIBLE);
+
+ EXPECT_TRUE(mFb->isFastBlitSupported());
+
+ mFb->lock();
+ EXPECT_EQ(mFb->isFastBlitSupported(),
+ mFb->getColorBuffer_locked(handle)->isFastBlitSupported());
+ mFb->unlock();
+
+ saveSnapshot();
+ loadSnapshot();
+
+ mFb->lock();
+ EXPECT_EQ(mFb->isFastBlitSupported(),
+ mFb->getColorBuffer_locked(handle)->isFastBlitSupported());
+ mFb->unlock();
+
+ mFb->closeColorBuffer(handle);
+}
+
+// Tests the API to completely replace a ColorBuffer.
+TEST_F(FrameBufferTest, ReplaceContentsTest) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->replaceColorBufferContents(handle, forUpdate.data(), mWidth * mHeight * 4);
+
+ TestTexture forRead = createTestTextureRGBA8888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forRead.data());
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(), forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+// Tests rate of draw calls with no guest/host communication, but with translator.
+static constexpr uint32_t kDrawCallLimit = 50000;
+
+TEST_F(FrameBufferTest, DrawCallRate) {
+ HandleType colorBuffer =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+ EXPECT_TRUE(mFb->setWindowSurfaceColorBuffer(surface, colorBuffer));
+
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ constexpr char vshaderSrc[] = R"(#version 300 es
+ precision highp float;
+
+ layout (location = 0) in vec2 pos;
+ layout (location = 1) in vec3 color;
+
+ uniform mat4 transform;
+
+ out vec3 color_varying;
+
+ void main() {
+ gl_Position = transform * vec4(pos, 0.0, 1.0);
+ color_varying = (transform * vec4(color, 1.0)).xyz;
+ }
+ )";
+ constexpr char fshaderSrc[] = R"(#version 300 es
+ precision highp float;
+
+ in vec3 color_varying;
+
+ out vec4 fragColor;
+
+ void main() {
+ fragColor = vec4(color_varying, 1.0);
+ }
+ )";
+
+ GLuint program = compileAndLinkShaderProgram(vshaderSrc, fshaderSrc);
+
+ GLint transformLoc = gl->glGetUniformLocation(program, "transform");
+
+ struct VertexAttributes {
+ float position[2];
+ float color[3];
+ };
+
+ const VertexAttributes vertexAttrs[] = {
+ { { -0.5f, -0.5f,}, { 0.2, 0.1, 0.9, }, },
+ { { 0.5f, -0.5f,}, { 0.8, 0.3, 0.1,}, },
+ { { 0.0f, 0.5f,}, { 0.1, 0.9, 0.6,}, },
+ };
+
+ GLuint buffer;
+ gl->glGenBuffers(1, &buffer);
+ gl->glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ gl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs,
+ GL_STATIC_DRAW);
+
+ gl->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributes), 0);
+ gl->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
+ sizeof(VertexAttributes),
+ (GLvoid*)offsetof(VertexAttributes, color));
+ gl->glEnableVertexAttribArray(0);
+ gl->glEnableVertexAttribArray(1);
+
+ gl->glUseProgram(program);
+
+ gl->glClearColor(0.2f, 0.2f, 0.3f, 0.0f);
+ gl->glViewport(0, 0, 1, 1);
+
+ float matrix[16] = {
+ 1.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f,
+ };
+
+ gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ uint32_t drawCount = 0;
+
+ auto cpuTimeStart = android::base::cpuTime();
+
+fprintf(stderr, "%s: transform loc %d\n", __func__, transformLoc);
+
+ while (drawCount < kDrawCallLimit) {
+ gl->glUniformMatrix4fv(transformLoc, 1, GL_FALSE, matrix);
+ gl->glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ gl->glDrawArrays(GL_TRIANGLES, 0, 3);
+ ++drawCount;
+ }
+
+ gl->glFinish();
+
+ auto cpuTime = android::base::cpuTime() - cpuTimeStart;
+
+ uint64_t duration_us = cpuTime.wall_time_us;
+ uint64_t duration_cpu_us = cpuTime.usageUs();
+
+ float ms = duration_us / 1000.0f;
+ float sec = duration_us / 1000000.0f;
+ float drawCallHz = (float)kDrawCallLimit / sec;
+
+ printf("Drew %u times in %f ms. Rate: %f Hz\n", kDrawCallLimit, ms, drawCallHz);
+
+ // android::perflogger::logDrawCallOverheadTest(
+ // (const char*)gl->glGetString(GL_VENDOR),
+ // (const char*)gl->glGetString(GL_RENDERER),
+ // (const char*)gl->glGetString(GL_VERSION),
+ // "drawArrays", "decoder",
+ // kDrawCallLimit,
+ // (long)drawCallHz,
+ // duration_us,
+ // duration_cpu_us);
+
+ EXPECT_TRUE(mFb->bindContext(0, 0, 0));
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->DestroyWindowSurface(surface);
+}
+
+// Tests rate of draw calls with only the host driver and no translator.
+TEST_F(FrameBufferTest, HostDrawCallRate) {
+ HandleType colorBuffer =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+ EXPECT_TRUE(mFb->setWindowSurfaceColorBuffer(surface, colorBuffer));
+
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ uint64_t duration_us, duration_cpu_us;
+ gl->glTestHostDriverPerformance(kDrawCallLimit, &duration_us, &duration_cpu_us);
+
+ float ms = duration_us / 1000.0f;
+ float sec = duration_us / 1000000.0f;
+ float drawCallHz = kDrawCallLimit / sec;
+
+ printf("Drew %u times in %f ms. Rate: %f Hz\n", kDrawCallLimit, ms, drawCallHz);
+
+ // android::perflogger::logDrawCallOverheadTest(
+ // (const char*)gl->glGetString(GL_VENDOR),
+ // (const char*)gl->glGetString(GL_RENDERER),
+ // (const char*)gl->glGetString(GL_VERSION),
+ // "drawArrays", "host",
+ // kDrawCallLimit,
+ // (long)drawCallHz,
+ // duration_us,
+ // duration_cpu_us);
+
+ EXPECT_TRUE(mFb->bindContext(0, 0, 0));
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->closeColorBuffer(colorBuffer);
+ mFb->DestroyWindowSurface(surface);
+}
+
+// Tests Vulkan interop query.
+TEST_F(FrameBufferTest, VulkanInteropQuery) {
+ auto egl = LazyLoadedEGLDispatch::get();
+
+ EXPECT_NE(nullptr, egl->eglQueryVulkanInteropSupportANDROID);
+
+ EGLBoolean supported =
+ egl->eglQueryVulkanInteropSupportANDROID();
+
+ // Disregard the result for now
+ (void)supported;
+}
+
+// Tests ColorBuffer with GL_BGRA input.
+TEST_F(FrameBufferTest, CreateColorBufferBGRA) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_BGRA_EXT, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ // FramBuffer::finalize handles color buffer destruction here
+}
+
+// Test ColorBuffer with GL_RGBA, but read back as GL_BGRA, so that R/B are switched.
+// TODO: This doesn't work on NVIDIA EGL, it issues GL_INVALID_OPERATION if the format doesn't match.
+TEST_F(FrameBufferTest, DISABLED_ReadColorBufferSwitchRedBlue) {
+ HandleType handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->openColorBuffer(handle));
+
+ TestTexture forUpdate = createTestPatternRGBA8888(mWidth, mHeight);
+ mFb->updateColorBuffer(handle, 0, 0, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate.data());
+
+ TestTexture forRead = createTestTextureRGBA8888SingleColor(mWidth, mHeight, 0.0f, 0.0f, 0.0f, 0.0f);
+ // Switch red and blue
+ mFb->readColorBuffer(handle, 0, 0, mWidth, mHeight, GL_BGRA_EXT, GL_UNSIGNED_BYTE, forRead.data());
+
+ // Switch them back, so we get the original image
+ uint8_t* forReadBytes = forRead.data();
+
+ for (uint32_t row = 0; row < mHeight; ++row) {
+ for (uint32_t col = 0; col < mWidth; ++col) {
+ uint8_t* pixel = forReadBytes + mWidth * 4 * row + col * 4;
+ // In RGBA8:
+ // 3 2 1 0
+ // 0xAABBGGRR on little endian systems
+ // R component: pixel[0]
+ // B component: pixel[2]
+ uint8_t r = pixel[0];
+ uint8_t b = pixel[2];
+ pixel[0] = b;
+ pixel[2] = r;
+ }
+ }
+
+ EXPECT_TRUE(ImageMatches(mWidth, mHeight, 4, mWidth, forUpdate.data(), forRead.data()));
+
+ mFb->closeColorBuffer(handle);
+}
+
+TEST_F(FrameBufferTest, CreateMultiDisplay) {
+ uint32_t id = 1;
+ mFb->createDisplay(&id);
+ EXPECT_EQ(0, mFb->createDisplay(&id));
+ EXPECT_EQ(0, mFb->destroyDisplay(id));
+}
+
+TEST_F(FrameBufferTest, BindMultiDisplayColorBuffer) {
+ uint32_t id = 2;
+ EXPECT_EQ(0, mFb->createDisplay(&id));
+ uint32_t handle =
+ mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_NE(0, handle);
+ EXPECT_EQ(0, mFb->setDisplayColorBuffer(id, handle));
+ uint32_t getHandle = 0;
+ mFb->getDisplayColorBuffer(id, &getHandle);
+ EXPECT_EQ(handle, getHandle);
+ uint32_t getId = 0;
+ mFb->getColorBufferDisplay(handle, &getId);
+ EXPECT_EQ(id, getId);
+ mFb->closeColorBuffer(handle);
+ EXPECT_EQ(0, mFb->destroyDisplay(id));
+}
+
+TEST_F(FrameBufferTest, SetMultiDisplayPosition) {
+ uint32_t id = FrameBuffer::s_invalidIdMultiDisplay;
+ mFb->createDisplay(&id);
+ EXPECT_NE(0, id);
+ uint32_t w = mWidth / 2, h = mHeight / 2;
+ EXPECT_EQ(0, mFb->setDisplayPose(id, -1, -1, w, h));
+ int32_t x, y;
+ uint32_t width, height;
+ EXPECT_EQ(0, mFb->getDisplayPose(id, &x, &y, &width, &height));
+ EXPECT_EQ(w, width);
+ EXPECT_EQ(h, height);
+ EXPECT_EQ(0, mFb->destroyDisplay(id));
+}
+
+TEST_F(FrameBufferTest, ComposeMultiDisplay) {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ HandleType context = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ HandleType surface = mFb->createWindowSurface(0, mWidth, mHeight);
+ EXPECT_TRUE(mFb->bindContext(context, surface, surface));
+
+ HandleType cb0 =
+ mFb->createColorBuffer(mWidth/2, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ TestTexture forUpdate0 = createTestTextureRGBA8888SingleColor(mWidth/2, mHeight, 1.0f, 1.0f, 1.0f, 1.0f);
+ EXPECT_EQ(0, mFb->openColorBuffer(cb0));
+ mFb->updateColorBuffer(cb0, 0, 0, mWidth/2, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate0.data());
+
+ uint32_t cb1 =
+ mFb->createColorBuffer(mWidth/2, mHeight/2, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_EQ(0, mFb->openColorBuffer(cb1));
+ TestTexture forUpdate1 = createTestTextureRGBA8888SingleColor(mWidth/2, mHeight/2, 1.0f, 0.0f, 0.0f, 1.0f);
+ mFb->updateColorBuffer(cb1, 0, 0, mWidth/2, mHeight/2, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate1.data());
+
+ uint32_t cb2 =
+ mFb->createColorBuffer(mWidth/4, mHeight/2, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_EQ(0, mFb->openColorBuffer(cb2));
+ TestTexture forUpdate2 = createTestTextureRGBA8888SingleColor(mWidth/4, mHeight/2, 0.0f, 1.0f, 0.0f, 1.0f);
+ mFb->updateColorBuffer(cb2, 0, 0, mWidth/4, mHeight/2, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate2.data());
+
+ uint32_t cb3 =
+ mFb->createColorBuffer(mWidth/4, mHeight/4, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ EXPECT_EQ(0, mFb->openColorBuffer(cb3));
+ TestTexture forUpdate3 = createTestTextureRGBA8888SingleColor(mWidth/4, mHeight/4, 0.0f, 0.0f, 1.0f, 1.0f);
+ mFb->updateColorBuffer(cb3, 0, 0, mWidth/4, mHeight/4, GL_RGBA, GL_UNSIGNED_BYTE, forUpdate3.data());
+
+ FrameBuffer::DisplayInfo info[] =
+ {{cb1, -1, -1, (uint32_t)mWidth/2, (uint32_t)mHeight/2, 240},
+ {cb2, -1, -1, (uint32_t)mWidth/4, (uint32_t)mHeight/2, 240},
+ {cb3, -1, -1, (uint32_t)mWidth/4, (uint32_t)mHeight/4, 240}};
+
+ uint32_t ids[] = {1, 2, 3};
+ for (uint32_t i = 0; i < 3 ; i++) {
+ EXPECT_EQ(0, mFb->createDisplay(&ids[i]));
+ EXPECT_EQ(0, mFb->setDisplayPose(ids[i], info[i].pos_x, info[i].pos_y,
+ info[i].width, info[i].height));
+ EXPECT_EQ(0, mFb->setDisplayColorBuffer(ids[i], info[i].cb));
+ }
+
+ if (mUseSubWindow) {
+ mFb->post(cb0);
+ mWindow->messageLoop();
+ }
+
+ EXPECT_TRUE(mFb->bindContext(0, 0, 0));
+ mFb->closeColorBuffer(cb0);
+ mFb->closeColorBuffer(cb1);
+ mFb->closeColorBuffer(cb2);
+ mFb->closeColorBuffer(cb3);
+ mFb->destroyDisplay(ids[0]);
+ mFb->destroyDisplay(ids[1]);
+ mFb->destroyDisplay(ids[2]);
+ mFb->DestroyWindowSurface(surface);
+}
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotBuffers_unittest.cpp b/stream-servers/tests/GLSnapshotBuffers_unittest.cpp
new file mode 100644
index 0000000..d246d90
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotBuffers_unittest.cpp
@@ -0,0 +1,146 @@
+// 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 "GLSnapshotTestStateUtils.h"
+#include "GLSnapshotTesting.h"
+#include "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+#include <map>
+
+namespace emugl {
+
+static const GLenum kGLES2GlobalBufferBindings[] = {
+ GL_ARRAY_BUFFER_BINDING, GL_ELEMENT_ARRAY_BUFFER_BINDING};
+// Buffers could also be bound with vertex attribute arrays.
+
+class SnapshotGlBufferObjectsTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ for (GLenum bindTarget : kGLES2GlobalBufferBindings) {
+ EXPECT_TRUE(compareGlobalGlInt(gl, bindTarget, 0))
+ << "no global buffer object bindings are bound by default";
+ }
+ for (auto& it : m_buffers_data) {
+ EXPECT_EQ(GL_FALSE, gl->glIsBuffer(it.first))
+ << "test-added buffer objects should not exist by default";
+ }
+ }
+
+ void changedStateCheck() override {
+ // Check that right buffers are bound
+ for (auto& it : m_bindings) {
+ GLenum boundTarget;
+ switch (it.first) {
+ case GL_ARRAY_BUFFER:
+ boundTarget = GL_ARRAY_BUFFER_BINDING;
+ break;
+ case GL_ELEMENT_ARRAY_BUFFER:
+ boundTarget = GL_ELEMENT_ARRAY_BUFFER_BINDING;
+ break;
+ default:
+ FAIL() << "Unknown binding location " << it.first;
+ }
+
+ EXPECT_TRUE(compareGlobalGlInt(gl, boundTarget, it.second))
+ << "buffer binding " << describeGlEnum(boundTarget)
+ << " should be bound with " << it.second;
+ }
+
+ // Check that all buffers have the correct attributes
+ for (auto& it : m_buffers_data) {
+ checkBufferData(it.first, it.second);
+ }
+ }
+
+ void stateChange() override { m_buffer_state_change(); }
+
+ // Creates a buffer with the properties in |data| and records its state so
+ // it can be checked for preservation after a snapshot.
+ GLuint addBuffer(GlBufferData data) {
+ GLuint name = createBuffer(gl, data);
+ m_buffers_data[name] = data;
+ return name;
+ }
+
+ // Binds a buffer and records the binding to be checked for preservation
+ // after a snapshot.
+ void bindBuffer(GLenum binding, GLuint buffer) {
+ gl->glBindBuffer(binding, buffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ m_bindings[binding] = buffer;
+ }
+
+ void setObjectStateChange(std::function<void()> func) {
+ m_buffer_state_change = func;
+ }
+
+protected:
+ void checkBufferData(GLuint name, GlBufferData data) {
+ SCOPED_TRACE("checking data for buffer " + std::to_string(name));
+ EXPECT_EQ(GL_TRUE, gl->glIsBuffer(name));
+
+ // We bind to GL_ARRAY_BUFFER in order to read buffer data,
+ // so let's hold on to what the old binding was so we can restore it
+ GLuint currentArrayBuffer;
+ gl->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)¤tArrayBuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ gl->glBindBuffer(GL_ARRAY_BUFFER, name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ EXPECT_TRUE(compareBufferParameter(GL_ARRAY_BUFFER, GL_BUFFER_SIZE,
+ (GLint)data.size));
+ EXPECT_TRUE(compareBufferParameter(GL_ARRAY_BUFFER, GL_BUFFER_USAGE,
+ (GLint)data.usage));
+ // TODO(benzene): compare actual buffer contents?
+ // in GLES2 there doesn't seem to be a way to directly read the buffer
+ // contents
+
+ // Restore the old binding
+ gl->glBindBuffer(GL_ARRAY_BUFFER, currentArrayBuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ }
+
+ testing::AssertionResult compareBufferParameter(GLenum target,
+ GLenum paramName,
+ GLint expected) {
+ GLint value;
+ gl->glGetBufferParameteriv(target, paramName, &value);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareValue<GLint>(
+ expected, value,
+ "mismatch on parameter " + describeGlEnum(paramName) +
+ " for buffer bound to " + describeGlEnum(target));
+ }
+
+ std::map<GLenum, GLuint> m_bindings;
+ std::map<GLuint, GlBufferData> m_buffers_data;
+ std::function<void()> m_buffer_state_change = [] {};
+};
+
+TEST_F(SnapshotGlBufferObjectsTest, BindArrayAndElementBuffers) {
+ setObjectStateChange([this] {
+ GlBufferData arrayData = {128, nullptr, GL_STATIC_DRAW};
+ GlBufferData elementArrayData = {256, nullptr, GL_DYNAMIC_DRAW};
+ GLuint arrayBuff = addBuffer(arrayData);
+ GLuint elementArrayBuff = addBuffer(elementArrayData);
+ bindBuffer(GL_ARRAY_BUFFER, arrayBuff);
+ bindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBuff);
+ });
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotFramebufferControl_unittest.cpp b/stream-servers/tests/GLSnapshotFramebufferControl_unittest.cpp
new file mode 100644
index 0000000..fb12829
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotFramebufferControl_unittest.cpp
@@ -0,0 +1,109 @@
+// 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 <gtest/gtest.h>
+
+namespace emugl {
+
+class SnapshotGlColorMaskTest
+ : public SnapshotSetValueTest<std::vector<GLboolean>> {
+ void stateCheck(std::vector<GLboolean> expected) override {
+ EXPECT_TRUE(compareGlobalGlBooleanv(gl, GL_COLOR_WRITEMASK, expected));
+ }
+ void stateChange() override {
+ std::vector<GLboolean> mask = *m_changed_value;
+ gl->glColorMask(mask[0], mask[1], mask[2], mask[3]);
+ }
+};
+
+TEST_F(SnapshotGlColorMaskTest, SetColorMask) {
+ setExpectedValues({true, true, true, true}, {false, false, false, false});
+ doCheckedSnapshot();
+}
+
+class SnapshotGlDepthMaskTest : public SnapshotSetValueTest<GLboolean> {
+ void stateCheck(GLboolean expected) override {
+ EXPECT_TRUE(compareGlobalGlBoolean(gl, GL_DEPTH_WRITEMASK, expected));
+ }
+ void stateChange() override { gl->glDepthMask(*m_changed_value); }
+};
+
+TEST_F(SnapshotGlDepthMaskTest, SetDepthMask) {
+ setExpectedValues(true, false);
+ doCheckedSnapshot();
+}
+
+// not to be confused with GL_STENCIL_VALUE_MASK
+class SnapshotGlStencilWriteMaskTest : public SnapshotSetValueTest<GLuint> {
+ void stateCheck(GLuint expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_WRITEMASK, expected));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_STENCIL_BACK_WRITEMASK, expected));
+ }
+ void stateChange() override { gl->glStencilMask(*m_changed_value); }
+};
+
+TEST_F(SnapshotGlStencilWriteMaskTest, SetStencilMask) {
+ // different drivers act differently; get the default mask
+ GLint defaultWriteMask;
+ gl->glGetIntegerv(GL_STENCIL_WRITEMASK, &defaultWriteMask);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ setExpectedValues(defaultWriteMask, 0);
+ doCheckedSnapshot();
+}
+
+class SnapshotGlColorClearValueTest
+ : public SnapshotSetValueTest<std::vector<GLclampf>> {
+ void stateCheck(std::vector<GLclampf> expected) override {
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, expected));
+ }
+ void stateChange() override {
+ std::vector<GLclampf> color = *m_changed_value;
+ gl->glClearColor(color[0], color[1], color[2], color[3]);
+ }
+};
+
+TEST_F(SnapshotGlColorClearValueTest, SetClearColor) {
+ setExpectedValues({0, 0, 0, 0}, {1.0f, 0.2f, 0.7f, 0.8f});
+ doCheckedSnapshot();
+}
+
+class SnapshotGlDepthClearValueTest : public SnapshotSetValueTest<GLclampf> {
+ void stateCheck(GLclampf expected) override {
+ EXPECT_TRUE(compareGlobalGlFloat(gl, GL_DEPTH_CLEAR_VALUE, expected));
+ }
+ void stateChange() override { gl->glClearDepthf(*m_changed_value); }
+};
+
+TEST_F(SnapshotGlDepthClearValueTest, SetClearDepth) {
+ setExpectedValues(1.0f, 0.4f);
+ doCheckedSnapshot();
+}
+
+class SnapshotGlStencilClearValueTest : public SnapshotSetValueTest<GLint> {
+ void stateCheck(GLint expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_CLEAR_VALUE, expected));
+ }
+ void stateChange() override { gl->glClearStencil(*m_changed_value); }
+};
+
+TEST_F(SnapshotGlStencilClearValueTest, SetClearStencil) {
+ setExpectedValues(0, 3);
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotFramebuffers_unittest.cpp b/stream-servers/tests/GLSnapshotFramebuffers_unittest.cpp
new file mode 100644
index 0000000..d94b3fe
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotFramebuffers_unittest.cpp
@@ -0,0 +1,157 @@
+// 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 "OpenglCodecCommon/glUtils.h"
+
+#include <gtest/gtest.h>
+
+#include <map>
+
+namespace emugl {
+
+struct GlFramebufferAttachment {
+ GLenum type;
+ GLuint name;
+ GLenum textureLevel;
+ GLenum textureCubeMapFace;
+};
+
+struct GlFramebufferObjectState {
+ std::map<GLenum, GlFramebufferAttachment> attachments;
+};
+
+class SnapshotGlFramebufferObjectTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_EQ(GL_FALSE, gl->glIsFramebuffer(m_framebuffer_name));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_FRAMEBUFFER_BINDING, 0));
+ }
+
+ void changedStateCheck() override {
+ EXPECT_EQ(GL_TRUE, gl->glIsFramebuffer(m_framebuffer_name));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_FRAMEBUFFER_BINDING,
+ m_framebuffer_name));
+
+ // don't lose current framebuffer binding
+ GLint currentBind;
+ gl->glGetIntegerv(GL_FRAMEBUFFER_BINDING, ¤tBind);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ for (auto& pair : m_state.attachments) {
+ const GLenum& attachment = pair.first;
+ GlFramebufferAttachment& expected = pair.second;
+
+ GlFramebufferAttachment current = {};
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_name);
+ gl->glGetFramebufferAttachmentParameteriv(
+ GL_FRAMEBUFFER, attachment,
+ GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,
+ (GLint*)¤t.type);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ if (current.type != GL_NONE) {
+ gl->glGetFramebufferAttachmentParameteriv(
+ GL_FRAMEBUFFER, attachment,
+ GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
+ (GLint*)¤t.name);
+ if (current.type == GL_TEXTURE) {
+ gl->glGetFramebufferAttachmentParameteriv(
+ GL_FRAMEBUFFER, attachment,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,
+ (GLint*)¤t.textureLevel);
+ gl->glGetFramebufferAttachmentParameteriv(
+ GL_FRAMEBUFFER, attachment,
+ GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE,
+ (GLint*)¤t.textureCubeMapFace);
+ }
+ }
+
+ EXPECT_EQ(expected.type, current.type);
+ EXPECT_EQ(expected.name, current.name);
+ EXPECT_EQ(expected.textureLevel, current.textureLevel);
+ EXPECT_EQ(expected.textureCubeMapFace, current.textureCubeMapFace);
+ }
+
+ // restore framebuffer binding
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, currentBind);
+ }
+
+ void stateChange() override {
+ gl->glGenFramebuffers(1, &m_framebuffer_name);
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_name);
+
+ m_state_changer();
+ }
+
+ void setStateChanger(std::function<void()> changer) {
+ m_state_changer = changer;
+ }
+
+protected:
+ GLuint m_framebuffer_name = 0;
+ GlFramebufferObjectState m_state = {};
+ std::function<void()> m_state_changer = [] {};
+};
+
+TEST_F(SnapshotGlFramebufferObjectTest, CreateAndBind) {
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFramebufferObjectTest, BindDepthRenderbuffer) {
+ setStateChanger([this] {
+ GLuint renderbuffer;
+ gl->glGenRenderbuffers(1, &renderbuffer);
+ gl->glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
+ gl->glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, renderbuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ m_state.attachments[GL_DEPTH_ATTACHMENT] = {GL_RENDERBUFFER,
+ renderbuffer, 0, 0};
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFramebufferObjectTest, BindStencilTextureCubeFace) {
+ setStateChanger([this] {
+ GLuint texture;
+ gl->glGenTextures(1, &texture);
+ gl->glBindTexture(GL_TEXTURE_CUBE_MAP, texture);
+ gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_TEXTURE_CUBE_MAP_NEGATIVE_X, texture, 0);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ m_state.attachments[GL_STENCIL_ATTACHMENT] = {
+ GL_TEXTURE, texture, 0, GL_TEXTURE_CUBE_MAP_NEGATIVE_X};
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFramebufferObjectTest, BindColor0Texture2D) {
+ setStateChanger([this] {
+ GLuint texture;
+ gl->glGenTextures(1, &texture);
+ gl->glBindTexture(GL_TEXTURE_2D, texture);
+ // In GLES2, mipmap level must be 0
+ gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, texture, 0);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ m_state.attachments[GL_COLOR_ATTACHMENT0] = {GL_TEXTURE, texture, 0, 0};
+ });
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotMultisampling_unittest.cpp b/stream-servers/tests/GLSnapshotMultisampling_unittest.cpp
new file mode 100644
index 0000000..ecbbdd7
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotMultisampling_unittest.cpp
@@ -0,0 +1,55 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+struct GlSampleCoverage {
+ GLclampf value;
+ GLboolean invert;
+};
+
+// Sample coverage settings to attempt
+static const GlSampleCoverage kGLES2TestSampleCoverages[] = {{0.3, true},
+ {0, false}};
+
+class SnapshotGlSampleCoverageTest
+ : public SnapshotSetValueTest<GlSampleCoverage>,
+ public ::testing::WithParamInterface<GlSampleCoverage> {
+ void stateCheck(GlSampleCoverage expected) {
+ EXPECT_TRUE(compareGlobalGlFloat(gl, GL_SAMPLE_COVERAGE_VALUE,
+ expected.value));
+ EXPECT_TRUE(compareGlobalGlBoolean(gl, GL_SAMPLE_COVERAGE_INVERT,
+ expected.invert));
+ }
+ void stateChange() {
+ gl->glSampleCoverage(GetParam().value, GetParam().invert);
+ }
+};
+
+TEST_P(SnapshotGlSampleCoverageTest, SetSampleCoverage) {
+ GlSampleCoverage defaultCoverage = {1.0f, false};
+ setExpectedValues(defaultCoverage, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotMultisampling,
+ SnapshotGlSampleCoverageTest,
+ ::testing::ValuesIn(kGLES2TestSampleCoverages));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotPixelOperations_unittest.cpp b/stream-servers/tests/GLSnapshotPixelOperations_unittest.cpp
new file mode 100644
index 0000000..2e96dd0
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotPixelOperations_unittest.cpp
@@ -0,0 +1,283 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+struct GlStencilFunc {
+ GLenum func;
+ GLint ref;
+ GLuint mask;
+};
+
+struct GlStencilOp {
+ GLenum sfail;
+ GLenum dpfail;
+ GLenum dppass;
+};
+
+struct GlBlendFunc {
+ GLenum srcRGB;
+ GLenum dstRGB;
+ GLenum srcAlpha;
+ GLenum dstAlpha;
+};
+
+// Scissor box settings to attempt
+static const std::vector<GLint> kGLES2TestScissorBox = {2, 3, 10, 20};
+
+// Default stencil operation modes
+static const GlStencilOp kGLES2DefaultStencilOp = {GL_KEEP, GL_KEEP, GL_KEEP};
+
+// Stencil reference value to attempt
+static const GLint kGLES2TestStencilRef = 1;
+
+// Stencil mask values to attempt
+static const GLuint kGLES2TestStencilMasks[] = {0, 1, 0x1000000, 0x7FFFFFFF};
+
+// Blend function settings to attempt
+static const GlBlendFunc kGLES2TestBlendFuncs[] = {
+ {GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR,
+ GL_ONE_MINUS_DST_COLOR},
+ {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA,
+ GL_ONE_MINUS_DST_ALPHA},
+ {GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA,
+ GL_ONE_MINUS_CONSTANT_ALPHA},
+ {GL_SRC_ALPHA_SATURATE, GL_ONE, GL_SRC_ALPHA_SATURATE, GL_ONE}};
+
+class SnapshotGlScissorBoxTest
+ : public SnapshotSetValueTest<std::vector<GLint>>,
+ public ::testing::WithParamInterface<std::vector<GLint>> {
+ void stateCheck(std::vector<GLint> expected) override {
+ EXPECT_TRUE(compareGlobalGlIntv(gl, GL_SCISSOR_BOX, expected));
+ }
+ void stateChange() override {
+ gl->glScissor(GetParam()[0], GetParam()[1], GetParam()[2],
+ GetParam()[3]);
+ }
+};
+
+TEST_P(SnapshotGlScissorBoxTest, SetScissorBox) {
+ // different drivers act differently; get the default scissorbox
+ std::vector<GLint> defaultBox;
+ defaultBox.resize(4);
+ gl->glGetIntegerv(GL_SCISSOR_BOX, &defaultBox[0]);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ setExpectedValues(defaultBox, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlScissorBoxTest,
+ ::testing::Values(kGLES2TestScissorBox));
+
+// Tests preservation of stencil test conditional state, set by glStencilFunc.
+class SnapshotGlStencilConditionsTest
+ : public SnapshotSetValueTest<GlStencilFunc> {
+ void stateCheck(GlStencilFunc expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_FUNC, expected.func));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_REF, expected.ref));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_STENCIL_VALUE_MASK, expected.mask));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_STENCIL_BACK_FUNC, expected.func));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_BACK_REF, expected.ref));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_BACK_VALUE_MASK,
+ expected.mask));
+ }
+ void stateChange() override {
+ GlStencilFunc sFunc = *m_changed_value;
+ gl->glStencilFunc(sFunc.func, sFunc.ref, sFunc.mask);
+ }
+};
+
+class SnapshotGlStencilFuncTest : public SnapshotGlStencilConditionsTest,
+ public ::testing::WithParamInterface<GLenum> {
+};
+
+class SnapshotGlStencilMaskTest : public SnapshotGlStencilConditionsTest,
+ public ::testing::WithParamInterface<GLuint> {
+};
+
+TEST_P(SnapshotGlStencilFuncTest, SetStencilFunc) {
+ // different drivers act differently; get the default mask
+ GLint defaultStencilMask;
+ gl->glGetIntegerv(GL_STENCIL_VALUE_MASK, &defaultStencilMask);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ GlStencilFunc defaultStencilFunc = {GL_ALWAYS, 0,
+ (GLuint)defaultStencilMask};
+ GlStencilFunc testStencilFunc = {GetParam(), kGLES2TestStencilRef, 0};
+ setExpectedValues(defaultStencilFunc, testStencilFunc);
+ doCheckedSnapshot();
+}
+
+TEST_P(SnapshotGlStencilMaskTest, SetStencilMask) {
+ // different drivers act differently; get the default mask
+ GLint defaultStencilMask;
+ gl->glGetIntegerv(GL_STENCIL_VALUE_MASK, &defaultStencilMask);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ GlStencilFunc defaultStencilFunc = {GL_ALWAYS, 0,
+ (GLuint)defaultStencilMask};
+ GlStencilFunc testStencilFunc = {GL_ALWAYS, kGLES2TestStencilRef,
+ GetParam()};
+ setExpectedValues(defaultStencilFunc, testStencilFunc);
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlStencilFuncTest,
+ ::testing::ValuesIn(kGLES2StencilFuncs));
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlStencilMaskTest,
+ ::testing::ValuesIn(kGLES2TestStencilMasks));
+
+class SnapshotGlStencilConsequenceTest
+ : public SnapshotSetValueTest<GlStencilOp> {
+ void stateCheck(GlStencilOp expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_FAIL, expected.sfail));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_PASS_DEPTH_FAIL,
+ expected.dpfail));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_PASS_DEPTH_PASS,
+ expected.dppass));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_STENCIL_BACK_FAIL, expected.sfail));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_BACK_PASS_DEPTH_FAIL,
+ expected.dpfail));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_STENCIL_BACK_PASS_DEPTH_PASS,
+ expected.dppass));
+ }
+ void stateChange() override {
+ GlStencilOp sOp = *m_changed_value;
+ gl->glStencilOp(sOp.sfail, sOp.dpfail, sOp.dppass);
+ }
+};
+
+class SnapshotGlStencilFailTest : public SnapshotGlStencilConsequenceTest,
+ public ::testing::WithParamInterface<GLenum> {
+};
+
+class SnapshotGlStencilDepthFailTest
+ : public SnapshotGlStencilConsequenceTest,
+ public ::testing::WithParamInterface<GLenum> {};
+
+class SnapshotGlStencilDepthPassTest
+ : public SnapshotGlStencilConsequenceTest,
+ public ::testing::WithParamInterface<GLenum> {};
+
+TEST_P(SnapshotGlStencilFailTest, SetStencilOps) {
+ GlStencilOp testStencilOp = {GetParam(), GL_KEEP, GL_KEEP};
+ setExpectedValues(kGLES2DefaultStencilOp, testStencilOp);
+ doCheckedSnapshot();
+}
+
+TEST_P(SnapshotGlStencilDepthFailTest, SetStencilOps) {
+ GlStencilOp testStencilOp = {GL_KEEP, GetParam(), GL_KEEP};
+ setExpectedValues(kGLES2DefaultStencilOp, testStencilOp);
+ doCheckedSnapshot();
+}
+
+TEST_P(SnapshotGlStencilDepthPassTest, SetStencilOps) {
+ GlStencilOp testStencilOp = {GL_KEEP, GL_KEEP, GetParam()};
+ setExpectedValues(kGLES2DefaultStencilOp, testStencilOp);
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlStencilFailTest,
+ ::testing::ValuesIn(kGLES2StencilOps));
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlStencilDepthFailTest,
+ ::testing::ValuesIn(kGLES2StencilOps));
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlStencilDepthPassTest,
+ ::testing::ValuesIn(kGLES2StencilOps));
+
+class SnapshotGlDepthFuncTest : public SnapshotSetValueTest<GLenum>,
+ public ::testing::WithParamInterface<GLenum> {
+ void stateCheck(GLenum expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_DEPTH_FUNC, expected));
+ }
+ void stateChange() override { gl->glDepthFunc(*m_changed_value); }
+};
+
+TEST_P(SnapshotGlDepthFuncTest, SetDepthFunc) {
+ setExpectedValues(GL_LESS, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlDepthFuncTest,
+ ::testing::ValuesIn(kGLES2StencilFuncs));
+
+class SnapshotGlBlendEquationTest
+ : public SnapshotSetValueTest<GLenum>,
+ public ::testing::WithParamInterface<GLenum> {
+ void stateCheck(GLenum expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_BLEND_EQUATION_RGB, expected));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_BLEND_EQUATION_ALPHA, expected));
+ }
+ void stateChange() override { gl->glBlendEquation(*m_changed_value); }
+};
+
+TEST_P(SnapshotGlBlendEquationTest, SetBlendEquation) {
+ setExpectedValues(GL_FUNC_ADD, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlBlendEquationTest,
+ ::testing::ValuesIn(kGLES2BlendEquations));
+
+class SnapshotGlBlendFuncTest
+ : public SnapshotSetValueTest<GlBlendFunc>,
+ public ::testing::WithParamInterface<GlBlendFunc> {
+ void stateCheck(GlBlendFunc expected) {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_BLEND_SRC_RGB, expected.srcRGB));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_BLEND_DST_RGB, expected.dstRGB));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_BLEND_SRC_ALPHA, expected.srcAlpha));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_BLEND_DST_ALPHA, expected.dstAlpha));
+ }
+ void stateChange() {
+ GlBlendFunc target = *m_changed_value;
+ gl->glBlendFuncSeparate(target.srcRGB, target.dstRGB, target.srcAlpha,
+ target.dstAlpha);
+ }
+};
+
+TEST_P(SnapshotGlBlendFuncTest, SetBlendFunc) {
+ GlBlendFunc defaultBlendFunc = {.srcRGB = GL_ONE,
+ .dstRGB = GL_ZERO,
+ .srcAlpha = GL_ONE,
+ .dstAlpha = GL_ZERO};
+ setExpectedValues(defaultBlendFunc, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixelOps,
+ SnapshotGlBlendFuncTest,
+ ::testing::ValuesIn(kGLES2TestBlendFuncs));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotPixels_unittest.cpp b/stream-servers/tests/GLSnapshotPixels_unittest.cpp
new file mode 100644
index 0000000..00cd3bd
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotPixels_unittest.cpp
@@ -0,0 +1,61 @@
+// 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 <gtest/gtest.h>
+
+namespace emugl {
+
+class SnapshotGlUnpackAlignmentTest
+ : public SnapshotSetValueTest<GLuint>,
+ public ::testing::WithParamInterface<GLuint> {
+ void stateCheck(GLuint expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_UNPACK_ALIGNMENT, expected));
+ }
+ void stateChange() override {
+ gl->glPixelStorei(GL_UNPACK_ALIGNMENT, *m_changed_value);
+ }
+};
+
+TEST_P(SnapshotGlUnpackAlignmentTest, SetUnpackAlign) {
+ setExpectedValues(4, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixels,
+ SnapshotGlUnpackAlignmentTest,
+ ::testing::Values(1, 2, 4, 8));
+
+class SnapshotGlPackAlignmentTest
+ : public SnapshotSetValueTest<GLuint>,
+ public ::testing::WithParamInterface<GLuint> {
+ void stateCheck(GLuint expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_PACK_ALIGNMENT, expected));
+ }
+ void stateChange() override {
+ gl->glPixelStorei(GL_PACK_ALIGNMENT, *m_changed_value);
+ }
+};
+
+TEST_P(SnapshotGlPackAlignmentTest, SetPackAlign) {
+ setExpectedValues(4, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotPixels,
+ SnapshotGlPackAlignmentTest,
+ ::testing::Values(1, 2, 4, 8));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotPrograms_unittest.cpp b/stream-servers/tests/GLSnapshotPrograms_unittest.cpp
new file mode 100644
index 0000000..68ee476
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotPrograms_unittest.cpp
@@ -0,0 +1,417 @@
+// 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 "GLSnapshotTestStateUtils.h"
+#include "GLSnapshotTesting.h"
+#include "OpenglCodecCommon/glUtils.h"
+
+#include <gtest/gtest.h>
+#include <map>
+#include <string>
+
+namespace emugl {
+
+static const char kTestVertexShader[] = R"(
+attribute vec4 position;
+uniform mat4 testFloatMat;
+uniform mat4 transform;
+uniform mat4 screenSpace;
+uniform ivec3 testInts[2];
+varying float linear;
+void main(void) {
+ gl_Position = testFloatMat * transform * position;
+ linear = (screenSpace * position).x;
+ gl_PointSize = linear * 0.5 + float(testInts[1].x);
+}
+)";
+
+static const char kTestFragmentShader[] = R"(
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+)";
+
+struct GlShaderVariable {
+ GLint size;
+ GLenum type;
+ std::vector<GLchar> name;
+ GLint location;
+
+ std::vector<GlValues> values;
+};
+
+struct GlProgramState {
+ GLboolean deleteStatus;
+ GLboolean linkStatus;
+ GLboolean validateStatus;
+
+ std::vector<GLchar> infoLog;
+
+ std::vector<GLuint> shaders;
+
+ GLint activeAttributes;
+ GLint maxAttributeName;
+ std::vector<GlShaderVariable> attributes;
+
+ GLint activeUniforms;
+ GLint maxUniformName;
+ std::vector<GlShaderVariable> uniforms;
+};
+
+// SnapshotGlProgramTest - A helper class for testing the snapshot preservation
+// of program objects' state.
+//
+// This holds state information of a particular single program object whose
+// state is mutated in order to set up tests.
+// Provide a lambda via setStateChanger to set up the state which will be
+// checked for preservation.
+// A test can also verify that the snapshot keeps the correct program in use by
+// calling useProgram during state setup.
+//
+class SnapshotGlProgramTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_EQ(GL_FALSE, gl->glIsProgram(m_program_name));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_CURRENT_PROGRAM, 0));
+ }
+
+ void changedStateCheck() override {
+ SCOPED_TRACE("test program name = " + std::to_string(m_program_name));
+ EXPECT_EQ(GL_TRUE, gl->glIsProgram(m_program_name));
+ EXPECT_TRUE(
+ compareGlobalGlInt(gl, GL_CURRENT_PROGRAM, m_current_program));
+
+ GlProgramState currentState = getProgramState();
+
+ EXPECT_STREQ(m_program_state.infoLog.data(),
+ currentState.infoLog.data());
+
+ EXPECT_EQ(m_program_state.deleteStatus, currentState.deleteStatus);
+ EXPECT_EQ(m_program_state.linkStatus, currentState.linkStatus);
+ EXPECT_EQ(m_program_state.validateStatus, currentState.validateStatus);
+
+ // TODO(benzene): allow test to pass even if these are out of order
+ EXPECT_EQ(m_program_state.shaders, currentState.shaders);
+
+ EXPECT_EQ(m_program_state.activeAttributes,
+ currentState.activeAttributes);
+ EXPECT_EQ(m_program_state.maxAttributeName,
+ currentState.maxAttributeName);
+ ASSERT_EQ(m_program_state.attributes.size(),
+ currentState.attributes.size());
+ for (int i = 0; i < currentState.attributes.size(); i++) {
+ SCOPED_TRACE("active attribute i = " + std::to_string(i));
+ EXPECT_EQ(m_program_state.attributes[i].size,
+ currentState.attributes[i].size);
+ EXPECT_EQ(m_program_state.attributes[i].type,
+ currentState.attributes[i].type);
+ EXPECT_STREQ(m_program_state.attributes[i].name.data(),
+ currentState.attributes[i].name.data());
+ EXPECT_EQ(m_program_state.attributes[i].location,
+ currentState.attributes[i].location);
+
+ // TODO(benzene): check attribute values?
+ }
+
+ EXPECT_EQ(m_program_state.activeUniforms, currentState.activeUniforms);
+ EXPECT_EQ(m_program_state.maxUniformName, currentState.maxUniformName);
+ ASSERT_EQ(m_program_state.uniforms.size(),
+ currentState.uniforms.size());
+ for (int i = 0; i < currentState.uniforms.size(); i++) {
+ SCOPED_TRACE("active uniform i = " + std::to_string(i));
+ EXPECT_EQ(m_program_state.uniforms[i].size,
+ currentState.uniforms[i].size);
+ EXPECT_EQ(m_program_state.uniforms[i].type,
+ currentState.uniforms[i].type);
+ EXPECT_STREQ(m_program_state.uniforms[i].name.data(),
+ currentState.uniforms[i].name.data());
+ EXPECT_EQ(m_program_state.uniforms[i].location,
+ currentState.uniforms[i].location);
+
+ for (int j = 0; j < currentState.uniforms[i].size; j++) {
+ SCOPED_TRACE("value j = " + std::to_string(j));
+ GlValues& expectedVal = m_program_state.uniforms[i].values[j];
+ GlValues& currentVal = currentState.uniforms[i].values[j];
+ if (currentVal.floats.size() > 0 &&
+ currentVal.ints.size() > 0) {
+ ADD_FAILURE() << "Uniform "
+ << currentState.uniforms[i].name.data()
+ << " had both ints and floats at index " << j;
+ }
+ if (currentVal.floats.size() > 0) {
+ EXPECT_EQ(currentVal.floats, expectedVal.floats)
+ << currentState.uniforms[i].name.data();
+ } else {
+ EXPECT_EQ(currentVal.ints, expectedVal.ints)
+ << currentState.uniforms[i].name.data();
+ }
+ }
+ }
+ }
+
+ void stateChange() override {
+ m_program_name = gl->glCreateProgram();
+ m_program_state = getProgramState();
+ m_state_changer();
+ }
+
+ void setStateChanger(std::function<void()> changer) {
+ m_state_changer = changer;
+ }
+
+protected:
+ // As part of state change, have the GL use the test program.
+ void useProgram() {
+ gl->glUseProgram(m_program_name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ m_current_program = m_program_name;
+ }
+
+ // Collects information about the test program object's current state.
+ GlProgramState getProgramState() {
+ GlProgramState ret = {};
+ if (GL_FALSE == gl->glIsProgram(m_program_name)) {
+ ADD_FAILURE() << "cannot get program state: was not a program";
+ return ret;
+ }
+
+ // Info log
+ GLsizei logLength;
+ gl->glGetProgramiv(m_program_name, GL_INFO_LOG_LENGTH, &logLength);
+ ret.infoLog.resize(logLength);
+ GLsizei actualLength;
+ gl->glGetProgramInfoLog(m_program_name, logLength, &actualLength,
+ &ret.infoLog[0]);
+
+ // Boolean statuses
+ GLint val;
+ gl->glGetProgramiv(m_program_name, GL_DELETE_STATUS, &val);
+ ret.deleteStatus = val;
+ gl->glGetProgramiv(m_program_name, GL_LINK_STATUS, &val);
+ ret.linkStatus = val;
+ gl->glGetProgramiv(m_program_name, GL_VALIDATE_STATUS, &val);
+ ret.validateStatus = val;
+
+ // Attached shaders
+ GLint attachedShaders;
+ gl->glGetProgramiv(m_program_name, GL_ATTACHED_SHADERS,
+ &attachedShaders);
+ ret.shaders.resize(attachedShaders);
+ GLsizei shaderCount;
+ gl->glGetAttachedShaders(m_program_name, attachedShaders, &shaderCount,
+ &ret.shaders[0]);
+
+ // Uniforms
+ gl->glGetProgramiv(m_program_name, GL_ACTIVE_UNIFORM_MAX_LENGTH,
+ &ret.maxUniformName);
+ gl->glGetProgramiv(m_program_name, GL_ACTIVE_UNIFORMS,
+ &ret.activeUniforms);
+ for (GLuint i = 0; i < ret.activeUniforms; i++) {
+ GlShaderVariable unif = {};
+ unif.name.resize(ret.maxUniformName);
+ GLsizei unifLen;
+ gl->glGetActiveUniform(m_program_name, i, ret.maxUniformName,
+ &unifLen, &unif.size, &unif.type,
+ &unif.name[0]);
+ unif.location =
+ gl->glGetUniformLocation(m_program_name, unif.name.data());
+
+ if (unif.size > 1) {
+ // uniform array; get values from each index
+ std::string baseName =
+ getUniformBaseName(std::string(unif.name.data()));
+ for (int uniformValueIndex = 0; uniformValueIndex < unif.size;
+ uniformValueIndex++) {
+ std::string indexedName =
+ baseName + '[' + std::to_string(uniformValueIndex) +
+ ']';
+ GLuint indexedLocation = gl->glGetUniformLocation(
+ m_program_name, indexedName.c_str());
+ getUniformValues(indexedLocation, unif.type, unif.values);
+ }
+ } else {
+ getUniformValues(unif.location, unif.type, unif.values);
+ }
+ ret.uniforms.push_back(unif);
+ }
+
+ // Attributes
+ gl->glGetProgramiv(m_program_name, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
+ &ret.maxAttributeName);
+ gl->glGetProgramiv(m_program_name, GL_ACTIVE_ATTRIBUTES,
+ &ret.activeAttributes);
+ for (GLuint i = 0; i < ret.activeAttributes; i++) {
+ GlShaderVariable attr = {};
+ attr.name.resize(ret.maxAttributeName);
+ GLsizei attrLen;
+ gl->glGetActiveAttrib(m_program_name, i, ret.maxAttributeName,
+ &attrLen, &attr.size, &attr.type,
+ &attr.name[0]);
+ attr.location =
+ gl->glGetAttribLocation(m_program_name, &attr.name[0]);
+
+ // TODO(benzene): get attribute values?
+
+ ret.attributes.push_back(attr);
+ }
+
+ return ret;
+ }
+
+ // Retrieves the values of the uniform at |location| for the test program.
+ // Returns them into |values|.
+ void getUniformValues(GLuint location,
+ GLenum type,
+ std::vector<GlValues>& values) {
+ GlValues val = {};
+ switch (type) {
+ case GL_FLOAT:
+ case GL_FLOAT_VEC2:
+ case GL_FLOAT_VEC3:
+ case GL_FLOAT_VEC4:
+ case GL_FLOAT_MAT2:
+ case GL_FLOAT_MAT3:
+ case GL_FLOAT_MAT4:
+ val.floats.resize(glSizeof(type) / sizeof(GLfloat));
+ gl->glGetUniformfv(m_program_name, location, val.floats.data());
+ values.push_back(std::move(val));
+ return;
+ case GL_INT:
+ case GL_INT_VEC2:
+ case GL_INT_VEC3:
+ case GL_INT_VEC4:
+ case GL_BOOL:
+ case GL_BOOL_VEC2:
+ case GL_BOOL_VEC3:
+ case GL_BOOL_VEC4:
+ case GL_SAMPLER_2D:
+ case GL_SAMPLER_CUBE:
+ val.ints.resize(glSizeof(type) / sizeof(GLint));
+ gl->glGetUniformiv(m_program_name, location, val.ints.data());
+ values.push_back(std::move(val));
+ break;
+ default:
+ ADD_FAILURE() << "unsupported uniform type " << type;
+ return;
+ }
+ }
+
+ // If string |name| ends with a subscript ([]) operator, return a substring
+ // with the subscript removed.
+ std::string getUniformBaseName(const std::string& name) {
+ std::string baseName;
+ int length = name.length();
+ if (length < 3)
+ return name;
+ size_t lastBracket = name.find_last_of('[');
+ if (lastBracket != std::string::npos) {
+ baseName = name.substr(0, lastBracket);
+ } else {
+ baseName = name;
+ }
+ return baseName;
+ }
+
+ GLuint m_program_name = 0;
+ GlProgramState m_program_state = {};
+ GLuint m_current_program = 0;
+
+ std::function<void()> m_state_changer = [] {};
+};
+
+TEST_F(SnapshotGlProgramTest, CreateProgram) {
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlProgramTest, AttachDetachShader) {
+ setStateChanger([this] {
+ GLuint vshader =
+ loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
+ GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
+ kTestFragmentShader);
+ gl->glAttachShader(m_program_name, vshader);
+ gl->glAttachShader(m_program_name, fshader);
+ gl->glDetachShader(m_program_name, vshader);
+ m_program_state.shaders.push_back(fshader);
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlProgramTest, LinkAndValidate) {
+ setStateChanger([this] {
+ GLuint vshader =
+ loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
+ GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
+ kTestFragmentShader);
+
+ gl->glAttachShader(m_program_name, vshader);
+ gl->glAttachShader(m_program_name, fshader);
+
+ gl->glLinkProgram(m_program_name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ gl->glValidateProgram(m_program_name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ m_program_state = getProgramState();
+
+ EXPECT_EQ(1, m_program_state.activeAttributes);
+ EXPECT_EQ(4, m_program_state.activeUniforms);
+ EXPECT_EQ(GL_TRUE, m_program_state.linkStatus);
+ EXPECT_EQ(GL_TRUE, m_program_state.validateStatus);
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlProgramTest, UseProgramAndUniforms) {
+ setStateChanger([this] {
+ GLuint vshader =
+ loadAndCompileShader(gl, GL_VERTEX_SHADER, kTestVertexShader);
+ GLuint fshader = loadAndCompileShader(gl, GL_FRAGMENT_SHADER,
+ kTestFragmentShader);
+
+ gl->glAttachShader(m_program_name, vshader);
+ gl->glAttachShader(m_program_name, fshader);
+
+ gl->glLinkProgram(m_program_name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ gl->glValidateProgram(m_program_name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ useProgram();
+
+ GLuint floatMatUnifLocation =
+ gl->glGetUniformLocation(m_program_name, "testFloatMat");
+ const GLfloat testFloatMatrix[16] = {
+ 1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3,
+ 0.2, 0.1, 0.0, -0.1, -0.1, -0.3, -0.4, -0.5,
+ };
+ gl->glUniformMatrix4fv(floatMatUnifLocation, 1, GL_FALSE,
+ testFloatMatrix);
+
+ GLuint intVecUnifLocation =
+ gl->glGetUniformLocation(m_program_name, "testInts");
+ const GLint testIntVec[6] = {
+ 10, 11, 12, 20, 21, 22,
+ };
+ gl->glUniform3iv(intVecUnifLocation, 2, testIntVec);
+
+ m_program_state = getProgramState();
+ });
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotRasterization_unittest.cpp b/stream-servers/tests/GLSnapshotRasterization_unittest.cpp
new file mode 100644
index 0000000..1e160df
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotRasterization_unittest.cpp
@@ -0,0 +1,112 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+// Line width settings to attempt
+static const GLfloat kGLES2TestLineWidths[] = {2.0f};
+
+// Polygon offset settings to attempt
+static const GLfloat kGLES2TestPolygonOffset[] = {0.5f, 0.5f};
+
+class SnapshotGlLineWidthTest : public SnapshotSetValueTest<GLfloat>,
+ public ::testing::WithParamInterface<GLfloat> {
+ void stateCheck(GLfloat expected) override {
+ GLfloat lineWidthRange[2];
+ gl->glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, lineWidthRange);
+
+ GLfloat lineWidth;
+ gl->glGetFloatv(GL_LINE_WIDTH, &lineWidth);
+
+ if (expected <= lineWidthRange[1]) {
+ EXPECT_EQ(expected, lineWidth);
+ }
+ }
+ void stateChange() override { gl->glLineWidth(GetParam()); }
+};
+
+TEST_P(SnapshotGlLineWidthTest, SetLineWidth) {
+ setExpectedValues(1.0f, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotRasterization,
+ SnapshotGlLineWidthTest,
+ ::testing::ValuesIn(kGLES2TestLineWidths));
+
+class SnapshotGlCullFaceTest : public SnapshotSetValueTest<GLenum>,
+ public ::testing::WithParamInterface<GLenum> {
+ void stateCheck(GLenum expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_CULL_FACE_MODE, expected));
+ }
+ void stateChange() override { gl->glCullFace(GetParam()); }
+};
+
+TEST_P(SnapshotGlCullFaceTest, SetCullFaceMode) {
+ setExpectedValues(GL_BACK, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotRasterization,
+ SnapshotGlCullFaceTest,
+ ::testing::ValuesIn(kGLES2CullFaceModes));
+
+class SnapshotGlFrontFaceTest : public SnapshotSetValueTest<GLenum>,
+ public ::testing::WithParamInterface<GLenum> {
+ void stateCheck(GLenum expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_FRONT_FACE, expected));
+ }
+ void stateChange() override { gl->glFrontFace(GetParam()); }
+};
+
+TEST_P(SnapshotGlFrontFaceTest, SetFrontFaceMode) {
+ setExpectedValues(GL_CCW, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotRasterization,
+ SnapshotGlFrontFaceTest,
+ ::testing::ValuesIn(kGLES2FrontFaceModes));
+
+class SnapshotGlPolygonOffsetTest
+ : public SnapshotSetValueTest<GLfloat*>,
+ public ::testing::WithParamInterface<const GLfloat*> {
+ void stateCheck(GLfloat* expected) override {
+ EXPECT_TRUE(compareGlobalGlFloat(gl, GL_POLYGON_OFFSET_FACTOR,
+ expected[0]));
+ EXPECT_TRUE(
+ compareGlobalGlFloat(gl, GL_POLYGON_OFFSET_UNITS, expected[1]));
+ }
+ void stateChange() override {
+ gl->glPolygonOffset(GetParam()[0], GetParam()[1]);
+ }
+};
+
+TEST_P(SnapshotGlPolygonOffsetTest, SetPolygonOffset) {
+ GLfloat defaultOffset[2] = {0.0f, 0.0f};
+ GLfloat testOffset[2] = {GetParam()[0], GetParam()[1]};
+ setExpectedValues(defaultOffset, testOffset);
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotRasterization,
+ SnapshotGlPolygonOffsetTest,
+ ::testing::Values(kGLES2TestPolygonOffset));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotRenderbuffers_unittest.cpp b/stream-servers/tests/GLSnapshotRenderbuffers_unittest.cpp
new file mode 100644
index 0000000..9a7ddc5
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotRenderbuffers_unittest.cpp
@@ -0,0 +1,199 @@
+// 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 <gtest/gtest.h>
+
+namespace emugl {
+
+struct GlRenderbufferFormat {
+ GLenum name;
+ GLsizei red;
+ GLsizei green;
+ GLsizei blue;
+ GLsizei alpha;
+ GLsizei depth;
+ GLsizei stencil;
+};
+
+struct GlRenderbufferState {
+ GLuint width;
+ GLuint height;
+ GlRenderbufferFormat format;
+};
+
+static const GlRenderbufferState kGLES2DefaultRenderbufferState = {
+ 0,
+ 0,
+ {.name = GL_RGBA4}};
+
+static const GlRenderbufferFormat kGLES2RenderbufferFormatFieldSizes[] = {
+ {GL_DEPTH_COMPONENT16, 0, 0, 0, 0, 16, 0},
+ {GL_RGBA4, 4, 4, 4, 4, 0, 0},
+ {GL_RGB5_A1, 5, 5, 5, 1, 0, 0},
+ {GL_RGB565, 5, 6, 5, 0, 0, 0},
+ {GL_STENCIL_INDEX8, 0, 0, 0, 0, 0, 8},
+};
+
+static const GlRenderbufferState kGLES2TestRenderbufferStates[] = {
+ {1, 1, kGLES2RenderbufferFormatFieldSizes[0]},
+ {0, 3, kGLES2RenderbufferFormatFieldSizes[1]},
+ {2, 2, kGLES2RenderbufferFormatFieldSizes[1]},
+ {4, 4, kGLES2RenderbufferFormatFieldSizes[2]},
+ {8, 8, kGLES2RenderbufferFormatFieldSizes[3]},
+ {16, 16, kGLES2RenderbufferFormatFieldSizes[4]},
+};
+
+class SnapshotGlRenderbufferTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_EQ(GL_FALSE, gl->glIsRenderbuffer(m_renderbuffer_name));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_RENDERBUFFER_BINDING, 0));
+ }
+
+ void changedStateCheck() override {
+ EXPECT_EQ(GL_TRUE, gl->glIsRenderbuffer(m_renderbuffer_name));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_RENDERBUFFER_BINDING,
+ m_renderbuffer_name));
+
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_WIDTH, m_state.width));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_HEIGHT, m_state.height));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_INTERNAL_FORMAT,
+ m_state.format.name));
+ EXPECT_TRUE(
+ compareParameter(GL_RENDERBUFFER_RED_SIZE, m_state.format.red));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_GREEN_SIZE,
+ m_state.format.green));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_BLUE_SIZE,
+ m_state.format.blue));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_ALPHA_SIZE,
+ m_state.format.alpha));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_DEPTH_SIZE,
+ m_state.format.depth));
+ EXPECT_TRUE(compareParameter(GL_RENDERBUFFER_STENCIL_SIZE,
+ m_state.format.stencil));
+ }
+
+ void stateChange() override {
+ gl->glGenRenderbuffers(1, &m_renderbuffer_name);
+ gl->glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer_name);
+
+ m_state_changer();
+ }
+
+ void setStateChanger(std::function<void()> changer) {
+ m_state_changer = changer;
+ }
+
+protected:
+ testing::AssertionResult compareParameter(GLenum name, GLint expected) {
+ GLint actual;
+ gl->glGetRenderbufferParameteriv(GL_RENDERBUFFER, name, &actual);
+ return compareValue<GLint>(
+ expected, actual,
+ "GL Renderbuffer object mismatch for param " +
+ describeGlEnum(name) + ":");
+ }
+
+ GLuint m_renderbuffer_name = 0;
+ GlRenderbufferState m_state = kGLES2DefaultRenderbufferState;
+ std::function<void()> m_state_changer = [] {};
+};
+
+TEST_F(SnapshotGlRenderbufferTest, CreateAndBind) {
+ doCheckedSnapshot();
+}
+
+class SnapshotGlRenderbufferFormatTest
+ : public SnapshotGlRenderbufferTest,
+ public ::testing::WithParamInterface<GlRenderbufferState> {};
+
+TEST_P(SnapshotGlRenderbufferFormatTest, SetFormat) {
+ setStateChanger([this] {
+ GLint maxSize;
+ gl->glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
+ m_state = GetParam();
+ if (maxSize < m_state.width || maxSize < m_state.height) {
+ fprintf(stderr,
+ "test dimensions exceed max renderbuffer size %d; "
+ "using max size instead\n",
+ maxSize);
+ m_state.width = maxSize;
+ m_state.height = maxSize;
+ }
+ gl->glRenderbufferStorage(GL_RENDERBUFFER, m_state.format.name,
+ m_state.width, m_state.height);
+
+ // The actual number of bits used for each format doesn't necessarily
+ // match how they are defined.
+ GLint fieldSize;
+ gl->glGetRenderbufferParameteriv(GL_RENDERBUFFER,
+ GL_RENDERBUFFER_RED_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.red) {
+ fprintf(stderr,
+ "format 0x%x internal RED uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.red);
+ m_state.format.red = fieldSize;
+ }
+ gl->glGetRenderbufferParameteriv(
+ GL_RENDERBUFFER, GL_RENDERBUFFER_GREEN_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.green) {
+ fprintf(stderr,
+ "format 0x%x internal GREEN uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.green);
+ m_state.format.green = fieldSize;
+ }
+ gl->glGetRenderbufferParameteriv(GL_RENDERBUFFER,
+ GL_RENDERBUFFER_BLUE_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.blue) {
+ fprintf(stderr,
+ "format 0x%x internal BLUE uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.blue);
+ m_state.format.blue = fieldSize;
+ }
+ gl->glGetRenderbufferParameteriv(
+ GL_RENDERBUFFER, GL_RENDERBUFFER_ALPHA_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.alpha) {
+ fprintf(stderr,
+ "format 0x%x internal ALPHA uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.alpha);
+ m_state.format.alpha = fieldSize;
+ }
+ gl->glGetRenderbufferParameteriv(
+ GL_RENDERBUFFER, GL_RENDERBUFFER_DEPTH_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.depth) {
+ fprintf(stderr,
+ "format 0x%x internal DEPTH uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.depth);
+ m_state.format.depth = fieldSize;
+ }
+ gl->glGetRenderbufferParameteriv(
+ GL_RENDERBUFFER, GL_RENDERBUFFER_STENCIL_SIZE, &fieldSize);
+ if (fieldSize != m_state.format.stencil) {
+ fprintf(stderr,
+ "format 0x%x internal STENCIL uses %d bits instead of %d\n",
+ m_state.format.name, fieldSize, m_state.format.stencil);
+ m_state.format.stencil = fieldSize;
+ }
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ });
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotRenderbuffers,
+ SnapshotGlRenderbufferFormatTest,
+ ::testing::ValuesIn(kGLES2TestRenderbufferStates));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotRendering_unittest.cpp b/stream-servers/tests/GLSnapshotRendering_unittest.cpp
new file mode 100644
index 0000000..af92b48
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotRendering_unittest.cpp
@@ -0,0 +1,96 @@
+// 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 "GLSnapshotTestDispatch.h"
+#include "GLSnapshotTesting.h"
+#include "Standalone.h"
+#include "samples/HelloTriangle.h"
+#include "android/console.h"
+
+#include <gtest/gtest.h>
+
+
+namespace emugl {
+
+
+
+TEST(SnapshotGlRenderingSampleTest, OverrideDispatch) {
+ const GLESv2Dispatch* gl = LazyLoadedGLESv2Dispatch::get();
+ const GLESv2Dispatch* testGl = getSnapshotTestDispatch();
+ EXPECT_NE(nullptr, gl);
+ EXPECT_NE(nullptr, testGl);
+ EXPECT_NE(gl->glDrawArrays, testGl->glDrawArrays);
+ EXPECT_NE(gl->glDrawElements, testGl->glDrawElements);
+}
+
+class SnapshotTestTriangle : public HelloTriangle {
+public:
+ virtual ~SnapshotTestTriangle() {}
+
+ void drawLoop() {
+ this->initialize();
+ while (mFrameCount < 5) {
+ this->draw();
+ mFrameCount++;
+ mFb->flushWindowSurfaceColorBuffer(mSurface);
+ if (mUseSubWindow) {
+ mFb->post(mColorBuffer);
+ mWindow->messageLoop();
+ }
+ }
+ }
+
+protected:
+ const GLESv2Dispatch* getGlDispatch() { return getSnapshotTestDispatch(); }
+
+ int mFrameCount = 0;
+};
+
+template <typename T>
+class SnapshotGlRenderingSampleTest : public ::testing::Test {
+protected:
+ virtual void SetUp() override {
+ setupStandaloneLibrarySearchPaths();
+ emugl::set_emugl_window_operations(*getConsoleAgents()->emu);
+ //const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+
+ const GLESv2Dispatch* gl = LazyLoadedGLESv2Dispatch::get();
+ const GLESv2Dispatch* testGl = getSnapshotTestDispatch();
+
+ mApp.reset(new T());
+ }
+
+ virtual void TearDown() override {
+ mApp.reset();
+ EXPECT_EQ(EGL_SUCCESS, LazyLoadedEGLDispatch::get()->eglGetError())
+ << "SnapshotGlRenderingSampleTest TearDown found an EGL error";
+ }
+
+ std::unique_ptr<T> mApp;
+};
+
+// To test with additional SampleApplications, extend them to override drawLoop
+// and getGlDispatch, then add the type to TestSampleApps.
+using TestSampleApps = ::testing::Types<SnapshotTestTriangle>;
+TYPED_TEST_CASE(SnapshotGlRenderingSampleTest, TestSampleApps);
+
+TYPED_TEST(SnapshotGlRenderingSampleTest, SnapshotDrawOnce) {
+ this->mApp->drawOnce();
+}
+
+TYPED_TEST(SnapshotGlRenderingSampleTest, SnapshotDrawLoop) {
+ this->mApp->drawLoop();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotShaders_unittest.cpp b/stream-servers/tests/GLSnapshotShaders_unittest.cpp
new file mode 100644
index 0000000..37a68e6
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotShaders_unittest.cpp
@@ -0,0 +1,245 @@
+// 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 <gtest/gtest.h>
+
+#include <string>
+
+namespace emugl {
+
+static const char kTestVertexShaderSource[] = R"(
+attribute vec4 position;
+uniform mat4 projection;
+uniform mat4 transform;
+uniform mat4 screenSpace;
+varying float linear;
+void main(void) {
+ vec4 transformedPosition = projection * transform * position;
+ gl_Position = transformedPosition;
+ linear = (screenSpace * position).x;
+}
+)";
+
+static const char kTestFragmentShaderSource[] = R"(
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+)";
+
+struct GlShaderState {
+ GLenum type;
+ GLboolean deleteStatus;
+ GLboolean compileStatus;
+ GLint infoLogLength;
+ std::vector<GLchar> infoLog;
+ GLint sourceLength;
+ std::string source;
+};
+
+// SnapshotGlShaderTest - A helper class for testing snapshot's preservation of
+// a GL shader object's states.
+//
+// It operates like SnapshotPreserveTest, and also holds information about a
+// particular shader object which is manipulated in tests using
+// SnapshotGlShaderTest as a fixture.
+// Helper functions like loadSource first need a created shader identified by
+// |m_shader_name|. This creation happens by default in stateChange. Use them in
+// a lambda, set through setShaderStateChanger, to set up state without
+// overriding doCheckedSnapshot.
+//
+class SnapshotGlShaderTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_EQ(GL_FALSE, gl->glIsShader(m_shader_name));
+ }
+
+ void changedStateCheck() override {
+ SCOPED_TRACE("for shader " + std::to_string(m_shader_name));
+
+ EXPECT_TRUE(compareParameter(GL_SHADER_TYPE, m_shader_state.type));
+ EXPECT_TRUE(compareParameter(GL_DELETE_STATUS,
+ m_shader_state.deleteStatus));
+ EXPECT_TRUE(compareParameter(GL_COMPILE_STATUS,
+ m_shader_state.compileStatus));
+ EXPECT_TRUE(compareParameter(GL_INFO_LOG_LENGTH,
+ m_shader_state.infoLogLength));
+ EXPECT_TRUE(compareParameter(GL_SHADER_SOURCE_LENGTH,
+ m_shader_state.sourceLength));
+
+ std::vector<GLchar> srcData = {};
+ srcData.resize(m_shader_state.sourceLength);
+ gl->glGetShaderSource(m_shader_name, m_shader_state.sourceLength,
+ nullptr, srcData.data());
+ if (srcData.data() == NULL) {
+ EXPECT_EQ(0, m_shader_state.source.length()) << "source is empty";
+ } else {
+ EXPECT_STREQ(m_shader_state.source.c_str(), srcData.data());
+ }
+
+ std::vector<GLchar> infoLogData = {};
+ infoLogData.resize(m_shader_state.infoLogLength);
+ gl->glGetShaderInfoLog(m_shader_name, m_shader_state.infoLogLength,
+ nullptr, infoLogData.data());
+ if (infoLogData.data() == NULL) {
+ EXPECT_EQ(0, m_shader_state.infoLogLength) << "info log is empty";
+ } else {
+ EXPECT_STREQ(m_shader_state.infoLog.data(), infoLogData.data());
+ }
+ }
+
+ void stateChange() override {
+ m_shader_name = gl->glCreateShader(m_shader_state.type);
+ m_shader_state_changer();
+
+ // Store state of info log
+ gl->glGetShaderiv(m_shader_name, GL_INFO_LOG_LENGTH,
+ &m_shader_state.infoLogLength);
+ m_shader_state.infoLog.resize(m_shader_state.infoLogLength);
+ gl->glGetShaderInfoLog(m_shader_name, m_shader_state.infoLogLength,
+ nullptr, m_shader_state.infoLog.data());
+ }
+
+ void loadSource(const std::string& sourceString) {
+ GLboolean compiler;
+ gl->glGetBooleanv(GL_SHADER_COMPILER, &compiler);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ if (compiler == GL_FALSE) {
+ fprintf(stderr, "Shader compiler is not supported.\n");
+ return;
+ }
+
+ if (m_shader_name == 0) {
+ FAIL() << "Cannot set source without a shader name";
+ }
+ m_shader_state.source = sourceString;
+ GLint len = sourceString.length();
+ if (len > 0) {
+ m_shader_state.sourceLength =
+ len + 1; // Counts the null terminator
+ }
+ const char* source = sourceString.c_str();
+ const char** sources = &source;
+ gl->glShaderSource(m_shader_name, 1, sources, &len);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ }
+
+ void compile(GLboolean expectCompileStatus = GL_TRUE) {
+ GLboolean compiler;
+ gl->glGetBooleanv(GL_SHADER_COMPILER, &compiler);
+ if (compiler == GL_FALSE) {
+ fprintf(stderr, "Shader compiler is not supported.\n");
+ return;
+ }
+
+ if (m_shader_name == 0) {
+ ADD_FAILURE() << "Cannot compile shader without a shader name";
+ }
+ if (m_shader_state.source.length() == 0) {
+ ADD_FAILURE() << "Shader needs source to compile";
+ }
+ gl->glCompileShader(m_shader_name);
+ m_shader_state.compileStatus = expectCompileStatus;
+ }
+
+ // Supply a lambda as |changer| to perform additional state setup after the
+ // shader has been created but before the snapshot is performed.
+ void setShaderStateChanger(std::function<void()> changer) {
+ m_shader_state_changer = changer;
+ }
+
+protected:
+ testing::AssertionResult compareParameter(GLenum paramName,
+ GLenum expected) {
+ GLint value;
+ gl->glGetShaderiv(m_shader_name, paramName, &value);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareValue<GLint>(
+ expected, value,
+ "mismatch on parameter " + describeGlEnum(paramName) +
+ " for shader " + std::to_string(m_shader_name));
+ }
+
+ GlShaderState m_shader_state = {};
+ GLuint m_shader_name;
+ std::function<void()> m_shader_state_changer = [] {};
+};
+
+class SnapshotGlVertexShaderTest : public SnapshotGlShaderTest {
+public:
+ SnapshotGlVertexShaderTest() { m_shader_state = {GL_VERTEX_SHADER}; }
+};
+
+TEST_F(SnapshotGlVertexShaderTest, Create) {
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlVertexShaderTest, SetSource) {
+ setShaderStateChanger(
+ [this] { loadSource(std::string(kTestVertexShaderSource)); });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlVertexShaderTest, CompileSuccess) {
+ setShaderStateChanger([this] {
+ loadSource(std::string(kTestVertexShaderSource));
+ compile();
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlVertexShaderTest, CompileFail) {
+ std::string failedShader = "vec3 hi my name is compile failed";
+ setShaderStateChanger([this, &failedShader] {
+ loadSource(failedShader);
+ compile(GL_FALSE);
+ });
+ doCheckedSnapshot();
+}
+
+class SnapshotGlFragmentShaderTest : public SnapshotGlShaderTest {
+public:
+ SnapshotGlFragmentShaderTest() { m_shader_state = {GL_FRAGMENT_SHADER}; }
+};
+
+TEST_F(SnapshotGlFragmentShaderTest, Create) {
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFragmentShaderTest, SetSource) {
+ setShaderStateChanger(
+ [this] { loadSource(std::string(kTestFragmentShaderSource)); });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFragmentShaderTest, CompileSuccess) {
+ setShaderStateChanger([this] {
+ loadSource(std::string(kTestFragmentShaderSource));
+ compile();
+ });
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlFragmentShaderTest, CompileFail) {
+ std::string failedShader = "vec3 nice to meet you compile failed";
+ setShaderStateChanger([this, &failedShader] {
+ loadSource(failedShader);
+ compile(GL_FALSE);
+ });
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTestDispatch.cpp b/stream-servers/tests/GLSnapshotTestDispatch.cpp
new file mode 100644
index 0000000..6c4c2c9
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTestDispatch.cpp
@@ -0,0 +1,177 @@
+#include "GLSnapshotTestDispatch.h"
+
+#include "FrameBuffer.h"
+#include "GLSnapshotTesting.h"
+#include "GLTestUtils.h"
+#include "OpenglCodecCommon/glUtils.h"
+#include "RenderThreadInfo.h"
+
+#include "android/base/files/PathUtils.h"
+#include "android/base/files/StdioStream.h"
+#include "android/base/memory/LazyInstance.h"
+#include "android/base/system/System.h"
+#include "android/base/testing/TestSystem.h"
+#include "android/snapshot/TextureLoader.h"
+#include "android/snapshot/TextureSaver.h"
+
+namespace emugl {
+
+using android::base::LazyInstance;
+using android::base::StdioStream;
+using android::snapshot::TextureLoader;
+using android::snapshot::TextureSaver;
+
+static LazyInstance<SnapshotTestDispatch> sSnapshotTestDispatch =
+ LAZY_INSTANCE_INIT;
+
+// static
+const GLESv2Dispatch* getSnapshotTestDispatch() {
+ return sSnapshotTestDispatch.ptr();
+}
+
+SnapshotTestDispatch::SnapshotTestDispatch()
+ : mTestSystem(PATH_SEP "progdir",
+ android::base::System::kProgramBitness,
+ PATH_SEP "homedir",
+ PATH_SEP "appdir") {
+ mTestSystem.getTempRoot()->makeSubDir("SampleSnapshots");
+ mSnapshotPath = mTestSystem.getTempRoot()->makeSubPath("SampleSnapshots");
+
+ mValid = gles2_dispatch_init(this);
+ if (mValid) {
+ overrideFunctions();
+ } else {
+ fprintf(stderr, "SnapshotTestDispatch failed to initialize.\n");
+ ADD_FAILURE() << "SnapshotTestDispatch could not initialize.";
+ }
+}
+
+void SnapshotTestDispatch::overrideFunctions() {
+ this->glDrawArrays = (glDrawArrays_t)test_glDrawArrays;
+ this->glDrawElements = (glDrawElements_t)test_glDrawElements;
+}
+
+void SnapshotTestDispatch::saveSnapshot() {
+ FrameBuffer* fb = FrameBuffer::getFB();
+ if (!fb) {
+ FAIL() << "Could not get FrameBuffer during snapshot test.";
+ }
+
+ std::string timeStamp =
+ std::to_string(android::base::System::get()->getUnixTime()) + "-" +
+ std::to_string(mLoadCount);
+ mSnapshotFile = mSnapshotPath + PATH_SEP "snapshot_" + timeStamp + ".snap";
+ mTextureFile = mSnapshotPath + PATH_SEP "textures_" + timeStamp + ".stex";
+ std::unique_ptr<StdioStream> m_stream(new StdioStream(
+ android_fopen(mSnapshotFile.c_str(), "wb"), StdioStream::kOwner));
+ auto a_stream = static_cast<android::base::Stream*>(m_stream.get());
+ std::shared_ptr<TextureSaver> m_texture_saver(new TextureSaver(StdioStream(
+ android_fopen(mTextureFile.c_str(), "wb"), StdioStream::kOwner)));
+
+ fb->onSave(a_stream, m_texture_saver);
+
+ // Save thread's context and surface handles so we can restore the bind
+ // after load is complete.
+ RenderThreadInfo* threadInfo = RenderThreadInfo::get();
+ if (threadInfo) {
+ threadInfo->onSave(a_stream);
+ }
+
+ m_stream->close();
+ m_texture_saver->done();
+}
+
+void SnapshotTestDispatch::loadSnapshot() {
+ FrameBuffer* fb = FrameBuffer::getFB();
+ if (!fb) {
+ FAIL() << "Could not get FrameBuffer during snapshot test.";
+ }
+
+ // unbind so load will destroy previous objects
+ fb->bindContext(0, 0, 0);
+
+ std::unique_ptr<StdioStream> m_stream(new StdioStream(
+ android_fopen(mSnapshotFile.c_str(), "rb"), StdioStream::kOwner));
+ std::shared_ptr<TextureLoader> m_texture_loader(
+ new TextureLoader(StdioStream(android_fopen(mTextureFile.c_str(), "rb"),
+ StdioStream::kOwner)));
+
+ fb->onLoad(m_stream.get(), m_texture_loader);
+
+ RenderThreadInfo* threadInfo = RenderThreadInfo::get();
+ if (threadInfo) {
+ threadInfo->onLoad(m_stream.get());
+ // rebind to context
+ fb->bindContext(
+ threadInfo->currContext ? threadInfo->currContext->getHndl()
+ : 0,
+ threadInfo->currDrawSurf ? threadInfo->currDrawSurf->getHndl()
+ : 0,
+ threadInfo->currReadSurf ? threadInfo->currReadSurf->getHndl()
+ : 0);
+ }
+
+ m_stream->close();
+ m_texture_loader->join();
+
+ mLoadCount++;
+}
+
+// static
+void SnapshotTestDispatch::testDraw(std::function<void()> doDraw) {
+ const GLESv2Dispatch* gl = LazyLoadedGLESv2Dispatch::get();
+ ASSERT_NE(nullptr, gl);
+
+ FrameBuffer* fb = FrameBuffer::getFB();
+ if (!fb) {
+ ADD_FAILURE() << "No framebuffer, test cannot snapshot.";
+ doDraw();
+ return;
+ }
+
+ // save then draw
+ ((SnapshotTestDispatch*)getSnapshotTestDispatch())->saveSnapshot();
+ // Since current framebuffer contents are not saved, we need to draw
+ // onto a clean slate in order to check the result of the draw call
+ gl->glClear(GL_COLOR_BUFFER_BIT);
+ doDraw();
+
+ // save the framebuffer contents
+ GLuint width, height, bytesPerPixel;
+ width = fb->getWidth();
+ height = fb->getHeight();
+ bytesPerPixel = glUtilsPixelBitSize(GL_RGBA, GL_UNSIGNED_BYTE) / 8;
+ std::vector<GLubyte> prePixels = {};
+ prePixels.resize(width * height * bytesPerPixel);
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+ prePixels.data());
+
+ // To verify that the snapshot is restoring our context, we modify the
+ // clear color.
+ std::vector<GLfloat> oldClear = {};
+ oldClear.resize(4);
+ gl->glGetFloatv(GL_COLOR_CLEAR_VALUE, oldClear.data());
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, oldClear));
+ gl->glClearColor(1, 1, 1, 1);
+ gl->glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, {1, 1, 1, 1}));
+
+ // load and redraw
+ ((SnapshotTestDispatch*)getSnapshotTestDispatch())->loadSnapshot();
+ gl->glClear(GL_COLOR_BUFFER_BIT);
+ doDraw();
+
+ // check that clear is restored
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_COLOR_CLEAR_VALUE, oldClear));
+
+ // compare the framebuffer contents
+ std::vector<GLubyte> postPixels = {};
+ postPixels.resize(width * height * bytesPerPixel);
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+ postPixels.data());
+
+ EXPECT_TRUE(ImageMatches(width, height, bytesPerPixel, width,
+ prePixels.data(), postPixels.data()));
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTestDispatch.h b/stream-servers/tests/GLSnapshotTestDispatch.h
new file mode 100644
index 0000000..a80d903
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTestDispatch.h
@@ -0,0 +1,71 @@
+// 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.
+
+#pragma once
+
+#include "emugl/common/OpenGLDispatchLoader.h"
+
+#include "android/base/testing/TestSystem.h"
+
+namespace emugl {
+
+// Global dispatch object with functions overridden for snapshot testing
+const GLESv2Dispatch* getSnapshotTestDispatch();
+
+// SnapshotTestDispatch - a GL dispatcher with some of its functions overridden
+// that can act as a drop-in replacement for GLESv2Dispatch. These functions are
+// given wrappers that perform a test of the GL snapshot when they are called.
+//
+// It uses the FrameBuffer to perform the snapshot; thus will only work in an
+// environment where the FrameBuffer exists.
+class SnapshotTestDispatch : public GLESv2Dispatch {
+public:
+ SnapshotTestDispatch();
+
+protected:
+ void overrideFunctions();
+
+ void saveSnapshot();
+
+ void loadSnapshot();
+
+ static void testDraw(std::function<void()> doDraw);
+
+ static void test_glDrawArrays(GLenum mode, GLint first, GLsizei count) {
+ testDraw([&] {
+ LazyLoadedGLESv2Dispatch::get()->glDrawArrays(mode, first, count);
+ });
+ }
+
+ static void test_glDrawElements(GLenum mode,
+ GLsizei count,
+ GLenum type,
+ const GLvoid* indices) {
+ testDraw([&] {
+ LazyLoadedGLESv2Dispatch::get()->glDrawElements(mode, count, type,
+ indices);
+ });
+ }
+
+ bool mValid = false;
+
+ int mLoadCount = 0;
+
+ android::base::TestSystem mTestSystem;
+ std::string mSnapshotPath = {};
+ std::string mSnapshotFile = {};
+ std::string mTextureFile = {};
+};
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTestStateUtils.cpp b/stream-servers/tests/GLSnapshotTestStateUtils.cpp
new file mode 100644
index 0000000..a710c73
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTestStateUtils.cpp
@@ -0,0 +1,110 @@
+// 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 "GLSnapshotTestStateUtils.h"
+
+#include "GLSnapshotTesting.h"
+#include "OpenglCodecCommon/glUtils.h"
+
+#include <gtest/gtest.h>
+
+#include <GLES2/gl2.h>
+#include <GLES3/gl31.h>
+
+namespace emugl {
+
+GLuint createBuffer(const GLESv2Dispatch* gl, GlBufferData data) {
+ // We bind to GL_ARRAY_BUFFER in order to set up buffer data,
+ // so let's hold on to what the old binding was so we can restore it
+ GLuint currentArrayBuffer;
+ gl->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, (GLint*)¤tArrayBuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ GLuint name;
+ gl->glGenBuffers(1, &name);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ gl->glBindBuffer(GL_ARRAY_BUFFER, name);
+ gl->glBufferData(GL_ARRAY_BUFFER, data.size, data.bytes, data.usage);
+
+ // Restore the old binding
+ gl->glBindBuffer(GL_ARRAY_BUFFER, currentArrayBuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return name;
+};
+
+GLuint loadAndCompileShader(const GLESv2Dispatch* gl,
+ GLenum shaderType,
+ const char* src) {
+ GLuint shader = gl->glCreateShader(shaderType);
+ gl->glShaderSource(shader, 1, (const GLchar* const*)&src, nullptr);
+ gl->glCompileShader(shader);
+
+ GLint compileStatus;
+ gl->glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+ EXPECT_EQ(GL_TRUE, compileStatus);
+
+ if (compileStatus != GL_TRUE) {
+ GLsizei infoLogLength;
+ gl->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+ std::vector<char> infoLog;
+ infoLog.resize(infoLogLength);
+ gl->glGetShaderInfoLog(shader, infoLogLength, nullptr, &infoLog[0]);
+ fprintf(stderr, "%s: fail to compile. infolog %s\n", __func__,
+ &infoLog[0]);
+ }
+
+ return shader;
+}
+
+std::vector<GLubyte> getTextureImageData(const GLESv2Dispatch* gl,
+ GLuint texture,
+ GLenum target,
+ GLint level,
+ GLsizei width,
+ GLsizei height,
+ GLenum format,
+ GLenum type) {
+ std::vector<GLubyte> out = {};
+ out.resize(width * height *
+ glUtilsPixelBitSize(GL_RGBA /* format */,
+ GL_UNSIGNED_BYTE /* type */) / 8);
+
+ // switch to auxiliary framebuffer
+ GLint oldFramebuffer;
+ gl->glGetIntegerv(GL_FRAMEBUFFER_BINDING, &oldFramebuffer);
+
+ GLuint auxFramebuffer;
+ gl->glGenFramebuffers(1, &auxFramebuffer);
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, auxFramebuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ gl->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target,
+ texture, level);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+ out.data()); // TODO(benzene): flexible format/type?
+ // seems like RGBA/UNSIGNED_BYTE is the only
+ // guaranteed supported format+type
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ // restore old framebuffer
+ gl->glBindFramebuffer(GL_FRAMEBUFFER, oldFramebuffer);
+ gl->glDeleteFramebuffers(1, &auxFramebuffer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ return out;
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTestStateUtils.h b/stream-servers/tests/GLSnapshotTestStateUtils.h
new file mode 100644
index 0000000..d1cf385
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTestStateUtils.h
@@ -0,0 +1,40 @@
+// 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.
+#pragma once
+
+#include "GLSnapshotTesting.h"
+
+#include <GLES2/gl2.h>
+#include <GLES3/gl31.h>
+
+namespace emugl {
+
+GLuint createBuffer(const GLESv2Dispatch* gl, GlBufferData data);
+
+GLuint loadAndCompileShader(const GLESv2Dispatch* gl,
+ GLenum shaderType,
+ const char* src);
+
+// Binds the active texture in target to a temporary framebuffer object
+// and retrieves its texel data using glReadPixels.
+std::vector<GLubyte> getTextureImageData(const GLESv2Dispatch* gl,
+ GLuint texture,
+ GLenum target,
+ GLint level,
+ GLsizei width,
+ GLsizei height,
+ GLenum format,
+ GLenum type);
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTesting.cpp b/stream-servers/tests/GLSnapshotTesting.cpp
new file mode 100644
index 0000000..d298ec3
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTesting.cpp
@@ -0,0 +1,318 @@
+// 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 <gtest/gtest.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES3/gl31.h>
+
+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 <class T>
+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,
+ GLboolean,
+ const std::string&);
+template testing::AssertionResult compareValue<GLint>(GLint,
+ GLint,
+ const std::string&);
+template testing::AssertionResult compareValue<GLfloat>(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<GLboolean>(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<GLint>(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<GLfloat>(expected, current,
+ "GL global float mismatch for parameter " +
+ describeGlEnum(name) + ":");
+}
+
+template <class T>
+testing::AssertionResult compareVector(const std::vector<T>& expected,
+ const std::vector<T>& 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<GLboolean>(
+ const std::vector<GLboolean>&,
+ const std::vector<GLboolean>&,
+ const std::string&);
+template testing::AssertionResult compareVector<GLint>(
+ const std::vector<GLint>&,
+ const std::vector<GLint>&,
+ const std::string&);
+template testing::AssertionResult compareVector<GLfloat>(
+ const std::vector<GLfloat>&,
+ const std::vector<GLfloat>&,
+ const std::string&);
+
+testing::AssertionResult compareGlobalGlBooleanv(
+ const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLboolean>& expected,
+ GLuint size) {
+ std::vector<GLboolean> current;
+ current.resize(std::max(size, static_cast<GLuint>(expected.size())));
+ gl->glGetBooleanv(name, ¤t[0]);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareVector<GLboolean>(
+ expected, current,
+ "GL global booleanv parameter " + describeGlEnum(name));
+}
+
+testing::AssertionResult compareGlobalGlIntv(const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLint>& expected,
+ GLuint size) {
+ std::vector<GLint> current;
+ current.resize(std::max(size, static_cast<GLuint>(expected.size())));
+ gl->glGetIntegerv(name, ¤t[0]);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareVector<GLint>(
+ expected, current,
+ "GL global intv parameter " + describeGlEnum(name));
+}
+
+testing::AssertionResult compareGlobalGlFloatv(
+ const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLfloat>& expected,
+ GLuint size) {
+ std::vector<GLfloat> current;
+ current.resize(std::max(size, static_cast<GLuint>(expected.size())));
+ gl->glGetFloatv(name, ¤t[0]);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareVector<GLfloat>(
+ 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<StdioStream> m_stream(new StdioStream(
+ android_fopen(streamFile.c_str(), "wb"), StdioStream::kOwner));
+ auto egl_stream = static_cast<EGLStream>(m_stream.get());
+ std::unique_ptr<TextureSaver> 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<StdioStream> m_stream(new StdioStream(
+ android_fopen(streamFile.c_str(), "rb"), StdioStream::kOwner));
+ auto egl_stream = static_cast<EGLStream>(m_stream.get());
+ std::shared_ptr<TextureLoader> 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<void()> 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
diff --git a/stream-servers/tests/GLSnapshotTesting.h b/stream-servers/tests/GLSnapshotTesting.h
new file mode 100644
index 0000000..05026ed
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTesting.h
@@ -0,0 +1,305 @@
+// 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.
+
+#pragma once
+
+#include "base/PathUtils.h"
+#include "base/System.h"
+#include "base/testing/TestSystem.h"
+
+#include "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <vector>
+
+namespace emugl {
+
+struct GlValues {
+ std::vector<GLint> ints;
+ std::vector<GLfloat> floats;
+};
+
+struct GlBufferData {
+ GLsizeiptr size;
+ GLvoid* bytes;
+ GLenum usage;
+};
+
+// Capabilities which, according to the GLES2 spec, start disabled.
+static const GLenum kGLES2CanBeEnabled[] = {GL_BLEND,
+ GL_CULL_FACE,
+ GL_DEPTH_TEST,
+ GL_POLYGON_OFFSET_FILL,
+ GL_SAMPLE_ALPHA_TO_COVERAGE,
+ GL_SAMPLE_COVERAGE,
+ GL_SCISSOR_TEST,
+ GL_STENCIL_TEST};
+
+// Capabilities which, according to the GLES2 spec, start enabled.
+static const GLenum kGLES2CanBeDisabled[] = {GL_DITHER};
+
+// Modes for CullFace
+static const GLenum kGLES2CullFaceModes[] = {GL_BACK, GL_FRONT,
+ GL_FRONT_AND_BACK};
+
+// Modes for FrontFace
+static const GLenum kGLES2FrontFaceModes[] = {GL_CCW, GL_CW};
+
+// Valid Stencil test functions
+static const GLenum kGLES2StencilFuncs[] = {GL_NEVER, GL_ALWAYS, GL_LESS,
+ GL_LEQUAL, GL_EQUAL, GL_GEQUAL,
+ GL_GREATER, GL_NOTEQUAL};
+// Valid Stencil test result operations
+static const GLenum kGLES2StencilOps[] = {GL_KEEP, GL_ZERO, GL_REPLACE,
+ GL_INCR, GL_DECR, GL_INVERT,
+ GL_INCR_WRAP, GL_DECR_WRAP};
+
+// Modes for the BlendEquation
+static const GLenum kGLES2BlendEquations[] = {GL_FUNC_ADD, GL_FUNC_SUBTRACT,
+ GL_FUNC_REVERSE_SUBTRACT};
+
+// Valid Blend functions
+static const GLenum kGLES2BlendFuncs[] = {GL_ZERO,
+ GL_ONE,
+ GL_SRC_COLOR,
+ GL_ONE_MINUS_SRC_COLOR,
+ GL_DST_COLOR,
+ GL_ONE_MINUS_DST_COLOR,
+ GL_SRC_ALPHA,
+ GL_ONE_MINUS_SRC_ALPHA,
+ GL_CONSTANT_COLOR,
+ GL_ONE_MINUS_CONSTANT_COLOR,
+ GL_CONSTANT_ALPHA,
+ GL_ONE_MINUS_CONSTANT_ALPHA,
+ GL_SRC_ALPHA_SATURATE};
+
+// Valid GENERATE_MIPMAP_HINT values
+static const GLenum kGLES2GenerateMipmapHints[] = {GL_DONT_CARE, GL_FASTEST,
+ GL_NICEST};
+
+// Returns a string useful for failure messages describing |enumValue|.
+std::string describeGlEnum(GLenum enumValue);
+
+// For building other compare functions which return AssertionResult.
+// Compares an |actual| against an |expected| value. Returns a failure values
+// do not match; provide |description| to attach details to the failure message.
+template <class T>
+testing::AssertionResult compareValue(T expected,
+ T actual,
+ const std::string& description = "");
+
+// Compares a global GL value, known by |name| and retrieved as a boolean,
+// against an |expected| value.
+testing::AssertionResult compareGlobalGlBoolean(const GLESv2Dispatch* gl,
+ GLenum name,
+ GLboolean expected);
+
+// Compares a global GL value, known by |name| and retrieved as an integer,
+// against an |expected| value.
+testing::AssertionResult compareGlobalGlInt(const GLESv2Dispatch* gl,
+ GLenum name,
+ GLint expected);
+
+// Compares a global GL value, known by |name| and retrieved as a float, against
+// an |expected| value.
+testing::AssertionResult compareGlobalGlFloat(const GLESv2Dispatch* gl,
+ GLenum name,
+ GLfloat expected);
+
+// For building other compare functions which return AssertionResult.
+// Compare the values at each index of a vector |actual| against an |expected|.
+// Returns a failure if any values are mismatched; provide |description| to
+// attach details to the failure message.
+// |actual| is allowed to contain more elements than |expected|.
+template <class T>
+testing::AssertionResult compareVector(
+ const std::vector<T>& expected,
+ const std::vector<T>& actual,
+ const std::string& description = "vector");
+
+// Compares a vector of global GL values, known by |name| and retrieved as a
+// boolean array, against |expected| values.
+// Specify |size| if more space is needed than the size of |expected|.
+testing::AssertionResult compareGlobalGlBooleanv(
+ const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLboolean>& expected,
+ GLuint size = 0);
+
+// Compares a vector of global GL values, known by |name| and retrieved as an
+// integer array, against |expected| values.
+// Specify |size| if more space is needed than the size of |expected|.
+testing::AssertionResult compareGlobalGlIntv(const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLint>& expected,
+ GLuint size = 0);
+
+// Compares a vector of global GL values, known by |name| and retrieved as a
+// float array, against |expected| values.
+// Specify |size| if more space is needed than the size of |expected|.
+testing::AssertionResult compareGlobalGlFloatv(
+ const GLESv2Dispatch* gl,
+ GLenum name,
+ const std::vector<GLfloat>& expected,
+ GLuint size = 0);
+
+// SnapshotTest - A helper class for performing a test related to saving or
+// loading GL translator snapshots. As a test fixture, its setup will prepare a
+// fresh GL state and paths for temporary snapshot files.
+//
+// doSnapshot saves a snapshot, clears the GL state, then loads the snapshot.
+// saveSnapshot and loadSnapshot can be used to perform saves and loads
+// independently.
+//
+// Usage example:
+// TEST_F(SnapshotTest, PreserveFooBar) {
+// // clean GL state is ready
+// EXPECT_TRUE(fooBarState());
+// modifyGlStateFooBar();
+// EXPECT_FALSE(fooBarState()); // GL state has been changed
+// doSnapshot(); // saves, resets, and reloads the state
+// EXPECT_FALSE(fooBarState()); // Snapshot preserved the state change
+// }
+//
+class SnapshotTest : public emugl::GLTest {
+public:
+ SnapshotTest() = default;
+
+ void SetUp() override;
+
+ // Mimics FrameBuffer.onSave, with fewer objects to manage.
+ // |streamFile| is a filename into which the snapshot will be saved.
+ // |textureFile| is a filename into which the textures will be saved.
+ void saveSnapshot(const std::string streamFile,
+ const std::string textureFile);
+
+ // Mimics FrameBuffer.onLoad, with fewer objects to manage.
+ // Assumes that a valid display is present.
+ // |streamFile| is a filename from which the snapshot will be loaded.
+ // |textureFile| is a filename from which the textures will be loaded.
+ void loadSnapshot(const std::string streamFile,
+ const std::string textureFile);
+
+ // Performs a teardown and reset of graphics objects in preparation for
+ // a snapshot load.
+ void preloadReset();
+
+ // Mimics saving and then loading a graphics snapshot.
+ // To verify that state has been reset to some default before the load,
+ // assertions can be performed in |preloadCheck|.
+ void doSnapshot(std::function<void()> preloadCheck);
+
+protected:
+ android::base::TestSystem mTestSystem;
+ std::string mSnapshotPath = {};
+};
+
+// SnapshotPreserveTest - A helper class building on SnapshotTest for granular
+// testing of the GL snapshot. This is specifically for the common case where a
+// piece of GL state has a known default, and our test aims to verify that the
+// snapshot preserves this piece of state when it has been changed from the
+// default.
+//
+// This acts as an abstract class; implementations should override the state
+// check state change functions to perform the assertions and operations
+// relevant to the part of GL state that they are testing.
+// doCheckedSnapshot can be but does not need to be overwritten. It performs the
+// following:
+// - check for default state
+// - make state changes, check that the state changes are in effect
+// - save a snapshot, reset the GL state, then check for default state
+// - load the snapshot, check that the state changes are in effect again
+//
+// Usage example with a subclass:
+// class SnapshotEnableFooTest : public SnapshotPreserveTest {
+// void defaultStateCheck() override { EXPECT_FALSE(isFooEnabled()); }
+// void changedStateCheck() override { EXPECT_TRUE(isFooEnabled()); }
+// void stateChange() override { enableFoo(); }
+// };
+// TEST_F(SnapshotEnableFooTest, PreserveFooEnable) {
+// doCheckedSnapshot();
+// }
+//
+class SnapshotPreserveTest : public SnapshotTest {
+public:
+ // Asserts that we are working from a clean starting state.
+ virtual void defaultStateCheck() {
+ ADD_FAILURE() << "Snapshot test needs a default state check function.";
+ }
+
+ // Asserts that any expected changes to state have occurred.
+ virtual void changedStateCheck() {
+ ADD_FAILURE()
+ << "Snapshot test needs a post-change state check function.";
+ }
+
+ // Modifies the state.
+ virtual void stateChange() {
+ ADD_FAILURE() << "Snapshot test needs a state-changer function.";
+ }
+
+ // Sets up a non-default state and asserts that a snapshot preserves it.
+ virtual void doCheckedSnapshot();
+};
+
+// SnapshotSetValueTest - A helper class for testing preservation of pieces of
+// GL state where default and changed state checks are comparisons against the
+// same type of expected reference value.
+//
+// The expected |m_default_value| and |m_changed_value| should be set before
+// a checked snapshot is attempted.
+//
+// Usage example with a subclass:
+// class SnapshotSetFooTest : public SnapshotSetValueTest<Foo> {
+// void stateCheck(Foo expected) { EXPECT_EQ(expected, getFoo()); }
+// void stateChange() override { setFoo(*m_changed_value); }
+// };
+// TEST_F(SnapshotSetFooTest, SetFooValue) {
+// setExpectedValues(kFooDefaultValue, kFooTestValue);
+// doCheckedSnapshot();
+// }
+//
+template <class T>
+class SnapshotSetValueTest : public SnapshotPreserveTest {
+public:
+ // Configures the test to assert against values which it should consider
+ // default and values which it should expect after changes.
+ void setExpectedValues(T defaultValue, T changedValue) {
+ m_default_value = std::unique_ptr<T>(new T(defaultValue));
+ m_changed_value = std::unique_ptr<T>(new T(changedValue));
+ }
+
+ // Checks part of state against an expected value.
+ virtual void stateCheck(T expected) {
+ ADD_FAILURE() << "Snapshot test needs a state-check function.";
+ };
+
+ void defaultStateCheck() override { stateCheck(*m_default_value); }
+ void changedStateCheck() override { stateCheck(*m_changed_value); }
+
+ void doCheckedSnapshot() override {
+ if (m_default_value == nullptr || m_changed_value == nullptr) {
+ FAIL() << "Snapshot test not provided expected values.";
+ }
+ SnapshotPreserveTest::doCheckedSnapshot();
+ }
+
+protected:
+ std::unique_ptr<T> m_default_value;
+ std::unique_ptr<T> m_changed_value;
+};
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTextures_unittest.cpp b/stream-servers/tests/GLSnapshotTextures_unittest.cpp
new file mode 100644
index 0000000..8173183
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTextures_unittest.cpp
@@ -0,0 +1,481 @@
+// 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 "GLSnapshotTestStateUtils.h"
+#include "GLSnapshotTesting.h"
+#include "OpenglCodecCommon/glUtils.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+struct GlTextureUnitState {
+ GLuint binding2D;
+ GLuint bindingCubeMap;
+};
+
+struct GlTextureImageState {
+ GLenum format;
+ GLenum type;
+ GLsizei width;
+ GLsizei height;
+
+ GLboolean isCompressed;
+ GLsizei compressedSize;
+
+ std::vector<GLubyte> bytes;
+};
+
+using GlMipmapArray = std::vector<GlTextureImageState>;
+
+struct GlTextureObjectState {
+ GLenum minFilter;
+ GLenum magFilter;
+ GLenum wrapS;
+ GLenum wrapT;
+
+ GLenum target;
+ GlMipmapArray images2D;
+ std::vector<GlMipmapArray> imagesCubeMap;
+};
+
+static const GLenum kGLES2TextureCubeMapSides[] = {
+ GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
+ GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
+};
+
+static const GlMipmapArray kGLES2TestTexture2D = {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 4,
+ 4,
+ false,
+ 0,
+ {
+ 0x11,
+ 0x22,
+ 0x33,
+ 0x44,
+ 0x55,
+ 0x66,
+ 0x77,
+ 0x88,
+ 0x99,
+ 0xaa,
+ 0xbb,
+ 0xcc,
+ 0xdd,
+ 0xee,
+ 0xff,
+ 0x00,
+ }},
+ {GL_RGBA,
+ GL_UNSIGNED_SHORT_4_4_4_4,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x51,
+ 0x52,
+ 0x53,
+ 0x54,
+ }},
+ {GL_RGBA,
+ GL_UNSIGNED_SHORT_5_5_5_1,
+ 1,
+ 1,
+ false,
+ 0,
+ {
+ 0xab,
+ }}};
+
+static const std::vector<GlMipmapArray> kGLES2TestTextureCubeMap = {
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x11,
+ 0x12,
+ 0x13,
+ 0x14,
+ }}},
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x21,
+ 0x22,
+ 0x23,
+ 0x24,
+ }}},
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x31,
+ 0x32,
+ 0x33,
+ 0x34,
+ }}},
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x41,
+ 0x42,
+ 0x43,
+ 0x44,
+ }}},
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x51,
+ 0x52,
+ 0x53,
+ 0x54,
+ }}},
+ {{GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ 2,
+ 2,
+ false,
+ 0,
+ {
+ 0x61,
+ 0x62,
+ 0x63,
+ 0x64,
+ }}},
+};
+
+class SnapshotGlTextureUnitActiveTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_ACTIVE_TEXTURE, GL_TEXTURE0));
+ }
+
+ void changedStateCheck() override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_ACTIVE_TEXTURE,
+ GL_TEXTURE0 + m_active_texture_unit));
+ }
+
+ void stateChange() override {
+ gl->glActiveTexture(GL_TEXTURE0 + m_active_texture_unit);
+ }
+
+ void useTextureUnit(GLuint unit) {
+ GLint maxTextureUnits;
+ gl->glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
+ &maxTextureUnits);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ if (unit < maxTextureUnits) {
+ m_active_texture_unit = unit;
+ } else {
+ fprintf(stderr,
+ "Tried to use texture unit %d when max unit was %d."
+ " Defaulting to unit 0.\n",
+ unit, maxTextureUnits);
+ m_active_texture_unit = 0;
+ }
+ }
+
+protected:
+ GLuint m_active_texture_unit;
+};
+
+TEST_F(SnapshotGlTextureUnitActiveTest, ActiveTextureUnit) {
+ useTextureUnit(1);
+ doCheckedSnapshot();
+}
+
+class SnapshotGlTextureUnitBindingsTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ GLint maxTextureUnits;
+ gl->glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
+ &maxTextureUnits);
+ for (int i = 0; i < maxTextureUnits; i++) {
+ gl->glActiveTexture(GL_TEXTURE0 + i);
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_TEXTURE_BINDING_2D, 0));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_TEXTURE_BINDING_CUBE_MAP, 0));
+ }
+ }
+
+ void changedStateCheck() override {
+ GLint maxTextureUnits;
+ gl->glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
+ &maxTextureUnits);
+ EXPECT_EQ(m_unit_states.size(), maxTextureUnits);
+
+ for (int i = 0; i < maxTextureUnits; i++) {
+ gl->glActiveTexture(GL_TEXTURE0 + i);
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_TEXTURE_BINDING_2D,
+ m_unit_states[i].binding2D));
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_TEXTURE_BINDING_CUBE_MAP,
+ m_unit_states[i].bindingCubeMap));
+ }
+ }
+
+ void stateChange() override {
+ GLint maxTextureUnits;
+ gl->glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
+ &maxTextureUnits);
+ m_unit_states.resize(maxTextureUnits);
+
+ m_state_changer();
+ }
+
+ void setStateChanger(std::function<void()> changer) {
+ m_state_changer = changer;
+ }
+
+protected:
+ // Create a texture object, bind to texture unit |unit| at binding point
+ // |bindPoint|, and record that we've done so.
+ GLuint createAndBindTexture(GLuint unit, GLenum bindPoint) {
+ GLint maxTextureUnits;
+ gl->glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
+ &maxTextureUnits);
+ if (unit >= maxTextureUnits) {
+ fprintf(stderr,
+ "Cannot bind to unit %d: max units is %d. Binding to %d "
+ "instead.\n",
+ unit, maxTextureUnits, maxTextureUnits - 1);
+ unit = maxTextureUnits - 1;
+ }
+
+ GLuint testTexture;
+ gl->glGenTextures(1, &testTexture);
+ gl->glActiveTexture(GL_TEXTURE0 + unit);
+ gl->glBindTexture(bindPoint, testTexture);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ switch (bindPoint) {
+ case GL_TEXTURE_2D:
+ m_unit_states[unit].binding2D = testTexture;
+ break;
+ case GL_TEXTURE_CUBE_MAP:
+ m_unit_states[unit].bindingCubeMap = testTexture;
+ break;
+ default:
+ ADD_FAILURE() << "Unsupported texture unit bind point " +
+ describeGlEnum(bindPoint);
+ }
+ return testTexture;
+ }
+
+ std::vector<GlTextureUnitState> m_unit_states;
+ std::function<void()> m_state_changer = [] {};
+};
+
+TEST_F(SnapshotGlTextureUnitBindingsTest, BindTextures) {
+ setStateChanger([this] {
+ createAndBindTexture(1, GL_TEXTURE_2D);
+ createAndBindTexture(8, GL_TEXTURE_CUBE_MAP);
+ createAndBindTexture(16, GL_TEXTURE_2D);
+ createAndBindTexture(32, GL_TEXTURE_CUBE_MAP);
+ });
+ doCheckedSnapshot();
+}
+
+class SnapshotGlTextureObjectTest : public SnapshotPreserveTest {
+public:
+ void defaultStateCheck() override {
+ EXPECT_EQ(GL_FALSE, gl->glIsTexture(m_object_name));
+ }
+
+ void changedStateCheck() override {
+ SCOPED_TRACE("Texture object " + std::to_string(m_object_name) +
+ ", target " + describeGlEnum(m_state.target));
+ EXPECT_EQ(GL_TRUE, gl->glIsTexture(m_object_name));
+
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_ACTIVE_TEXTURE, GL_TEXTURE0));
+ EXPECT_TRUE(compareGlobalGlInt(gl, getTargetBindingName(m_state.target),
+ m_object_name));
+
+ EXPECT_TRUE(compareParameter(GL_TEXTURE_MIN_FILTER, m_state.minFilter));
+ EXPECT_TRUE(compareParameter(GL_TEXTURE_MAG_FILTER, m_state.magFilter));
+ EXPECT_TRUE(compareParameter(GL_TEXTURE_WRAP_S, m_state.wrapS));
+ EXPECT_TRUE(compareParameter(GL_TEXTURE_WRAP_T, m_state.wrapT));
+
+ auto compareImageFunc = [this](GLenum imageTarget,
+ GlMipmapArray& levels) {
+ for (int i = 0; i < levels.size(); i++) {
+ EXPECT_TRUE(compareVector<GLubyte>(
+ levels[i].bytes,
+ getTextureImageData(gl, m_object_name, imageTarget, i,
+ levels[i].width, levels[i].height,
+ levels[i].format, levels[i].type),
+ "mipmap level " + std::to_string(i)));
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ }
+ };
+
+ switch (m_state.target) {
+ case GL_TEXTURE_2D: {
+ compareImageFunc(m_state.target, m_state.images2D);
+ } break;
+ case GL_TEXTURE_CUBE_MAP: {
+ if (m_state.imagesCubeMap.size() > 6) {
+ ADD_FAILURE() << "Test texture cube map had "
+ << m_state.imagesCubeMap.size()
+ << " 'sides' of data.";
+ break;
+ }
+ for (int j = 0; j < m_state.imagesCubeMap.size(); j++) {
+ compareImageFunc(kGLES2TextureCubeMapSides[j],
+ m_state.imagesCubeMap[j]);
+ }
+ } break;
+ default:
+ ADD_FAILURE()
+ << "Unsupported texture target " << m_state.target;
+ break;
+ }
+ }
+
+ void stateChange() override {
+ gl->glGenTextures(1, &m_object_name);
+
+ // Bind to texture unit TEXTURE0 for test simplicity
+ gl->glActiveTexture(GL_TEXTURE0);
+ gl->glBindTexture(m_state.target, m_object_name);
+
+ // Set texture sample parameters
+ gl->glTexParameteri(m_state.target, GL_TEXTURE_MIN_FILTER,
+ m_state.minFilter);
+ gl->glTexParameteri(m_state.target, GL_TEXTURE_MAG_FILTER,
+ m_state.magFilter);
+ gl->glTexParameteri(m_state.target, GL_TEXTURE_WRAP_S, m_state.wrapS);
+ gl->glTexParameteri(m_state.target, GL_TEXTURE_WRAP_T, m_state.wrapT);
+
+ auto initImageFunc = [this](GLenum imageTarget, GlMipmapArray& levels) {
+ for (int i = 0; i < levels.size(); i++) {
+ levels[i].bytes.resize(
+ levels[i].width * levels[i].height *
+ glUtilsPixelBitSize(
+ levels[i].format,
+ GL_UNSIGNED_BYTE /* levels[i].type */) / 8);
+ gl->glTexImage2D(imageTarget, i, levels[i].format,
+ levels[i].width, levels[i].height, 0,
+ levels[i].format,
+ GL_UNSIGNED_BYTE /* levels[i].type */,
+ levels[i].bytes.data());
+ }
+ };
+
+ switch (m_state.target) {
+ case GL_TEXTURE_2D: {
+ initImageFunc(m_state.target, m_state.images2D);
+ } break;
+ case GL_TEXTURE_CUBE_MAP: {
+ if (m_state.imagesCubeMap.size() > 6) {
+ ADD_FAILURE() << "Test texture cube map had "
+ << m_state.imagesCubeMap.size()
+ << " 'sides' of data.";
+ break;
+ }
+ for (int j = 0; j < m_state.imagesCubeMap.size(); j++) {
+ GLenum side = kGLES2TextureCubeMapSides[j];
+ initImageFunc(side, m_state.imagesCubeMap[j]);
+ }
+ } break;
+ default:
+ ADD_FAILURE()
+ << "Unsupported texture target " << m_state.target;
+ break;
+ }
+ }
+
+protected:
+ // Compares a symbolic constant value |expected| against the parameter named
+ // |paramName| of the texture object which is bound in unit TEXTURE0.
+ testing::AssertionResult compareParameter(GLenum paramName,
+ GLenum expected) {
+ GLint actual;
+ gl->glGetTexParameteriv(m_state.target, paramName, &actual);
+ return compareValue<GLint>(
+ expected, actual,
+ "GL texture object " + std::to_string(m_object_name) +
+ " mismatch for param " + describeGlEnum(paramName) +
+ " on target " + describeGlEnum(m_state.target));
+ }
+
+ GLenum getTargetBindingName(GLenum target) {
+ switch (target) {
+ case GL_TEXTURE_2D:
+ return GL_TEXTURE_BINDING_2D;
+ case GL_TEXTURE_CUBE_MAP:
+ return GL_TEXTURE_BINDING_CUBE_MAP;
+ default:
+ ADD_FAILURE() << "Unsupported texture target " << target;
+ return 0;
+ }
+ }
+
+ GLuint m_object_name;
+ GlTextureObjectState m_state = {};
+};
+
+TEST_F(SnapshotGlTextureObjectTest, SetObjectParameters) {
+ m_state = {
+ .minFilter = GL_LINEAR,
+ .magFilter = GL_NEAREST,
+ .wrapS = GL_MIRRORED_REPEAT,
+ .wrapT = GL_CLAMP_TO_EDGE,
+ .target = GL_TEXTURE_2D,
+ };
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlTextureObjectTest, Create2DMipmap) {
+ m_state = {.minFilter = GL_LINEAR,
+ .magFilter = GL_NEAREST,
+ .wrapS = GL_MIRRORED_REPEAT,
+ .wrapT = GL_CLAMP_TO_EDGE,
+ .target = GL_TEXTURE_2D,
+ .images2D = kGLES2TestTexture2D};
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlTextureObjectTest, CreateCubeMap) {
+ m_state = {.minFilter = GL_LINEAR,
+ .magFilter = GL_NEAREST,
+ .wrapS = GL_MIRRORED_REPEAT,
+ .wrapT = GL_CLAMP_TO_EDGE,
+ .target = GL_TEXTURE_CUBE_MAP,
+ .images2D = {}, // mingw compiler cannot deal with gaps
+ .imagesCubeMap = kGLES2TestTextureCubeMap};
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotTransformation_unittest.cpp b/stream-servers/tests/GLSnapshotTransformation_unittest.cpp
new file mode 100644
index 0000000..faa71c3
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotTransformation_unittest.cpp
@@ -0,0 +1,77 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+// Viewport settings to attempt
+static const std::vector<GLint> kGLES2TestViewport = {10, 10, 100, 100};
+
+// Depth range settings to attempt
+static const std::vector<GLclampf> kGLES2TestDepthRange = {0.2f, 0.8f};
+
+class SnapshotGlViewportTest
+ : public SnapshotPreserveTest,
+ public ::testing::WithParamInterface<std::vector<GLint>> {
+ void defaultStateCheck() override {
+ GLint viewport[4] = {};
+ gl->glGetIntegerv(GL_VIEWPORT, viewport);
+ // initial viewport should match surface size
+ EXPECT_EQ(kTestSurfaceSize[0], viewport[2]);
+ EXPECT_EQ(kTestSurfaceSize[1], viewport[3]);
+ }
+ void changedStateCheck() override {
+ EXPECT_TRUE(compareGlobalGlIntv(gl, GL_VIEWPORT, GetParam()));
+ }
+ void stateChange() override {
+ gl->glViewport(GetParam()[0], GetParam()[1], GetParam()[2],
+ GetParam()[3]);
+ }
+};
+
+TEST_P(SnapshotGlViewportTest, SetViewport) {
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotTransformation,
+ SnapshotGlViewportTest,
+ ::testing::Values(kGLES2TestViewport));
+
+class SnapshotGlDepthRangeTest
+ : public SnapshotSetValueTest<std::vector<GLclampf>>,
+ public ::testing::WithParamInterface<std::vector<GLclampf>> {
+ void stateCheck(std::vector<GLclampf> expected) override {
+ EXPECT_TRUE(compareGlobalGlFloatv(gl, GL_DEPTH_RANGE, expected));
+ }
+ void stateChange() override {
+ std::vector<GLclampf> testRange = GetParam();
+ gl->glDepthRangef(testRange[0], testRange[1]);
+ }
+};
+
+TEST_P(SnapshotGlDepthRangeTest, SetDepthRange) {
+ std::vector<GLclampf> defaultRange = {0.0f, 1.0f};
+ setExpectedValues(defaultRange, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotTransformation,
+ SnapshotGlDepthRangeTest,
+ ::testing::Values(kGLES2TestDepthRange));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshotVertexAttributes_unittest.cpp b/stream-servers/tests/GLSnapshotVertexAttributes_unittest.cpp
new file mode 100644
index 0000000..f8438dd
--- /dev/null
+++ b/stream-servers/tests/GLSnapshotVertexAttributes_unittest.cpp
@@ -0,0 +1,305 @@
+// 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 "GLSnapshotTestStateUtils.h"
+#include "GLSnapshotTesting.h"
+#include "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+
+namespace emugl {
+
+enum class GlVertexAttribMode { SingleValue = 0, Array = 1, Buffer = 2 };
+
+struct GlVertexAttrib {
+ GlVertexAttribMode mode;
+ GlValues values;
+ GLint size;
+ GLenum type;
+ GLboolean normalized;
+ GLsizei stride;
+ GLboolean enabled;
+ GLvoid* pointer;
+ GLuint bufferBinding;
+};
+
+static const GlVertexAttrib kGLES2DefaultVertexAttrib = {
+ .mode = GlVertexAttribMode::SingleValue,
+ .values = {.ints = {}, .floats = {0, 0, 0, 1}},
+ .size = 4,
+ .type = GL_FLOAT,
+ .normalized = GL_FALSE,
+ .stride = 0,
+ .enabled = GL_FALSE,
+ .pointer = nullptr,
+ .bufferBinding = 0};
+
+static const GlBufferData kTestAttachedBuffer = {.size = 16,
+ .bytes = nullptr,
+ .usage = GL_STATIC_DRAW};
+
+class SnapshotGlVertexAttributesTest
+ : public SnapshotSetValueTest<GlVertexAttrib> {
+public:
+ virtual void stateCheck(GlVertexAttrib expected) override {
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_ENABLED,
+ expected.enabled));
+ }
+
+ virtual void stateChange() override {
+ GlVertexAttrib changed = *m_changed_value;
+ if (changed.enabled) {
+ gl->glEnableVertexAttribArray(m_index);
+ } else {
+ gl->glDisableVertexAttribArray(m_index);
+ }
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ }
+
+ void selectIndex(GLuint index) {
+ GLint maxAttribs;
+ gl->glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxAttribs);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ if (index >= maxAttribs) {
+ fprintf(stderr,
+ "cannot select index %d: GL_MAX_VERTEX_ATTRIBS is %d.\n",
+ index, maxAttribs);
+ return;
+ }
+ m_index = index;
+ }
+
+protected:
+ testing::AssertionResult compareFloatParameter(GLenum paramName,
+ GLfloat expected) {
+ std::vector<GLfloat> v = {expected};
+ return compareFloatParameter(paramName, v);
+ }
+
+ testing::AssertionResult compareFloatParameter(
+ GLenum paramName,
+ const std::vector<GLfloat>& expected) {
+ std::vector<GLfloat> values;
+ values.resize(std::max((GLuint)4, (GLuint)expected.size()));
+ gl->glGetVertexAttribfv(m_index, paramName, &(values[0]));
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareVector<GLfloat>(
+ expected, values,
+ "float(s) for parameter " + describeGlEnum(paramName) +
+ " of vertex attribute " + std::to_string(m_index));
+ }
+
+ testing::AssertionResult compareIntParameter(GLenum paramName,
+ GLint expected) {
+ std::vector<GLint> v = {expected};
+ return compareIntParameter(paramName, v);
+ }
+
+ testing::AssertionResult compareIntParameter(
+ GLenum paramName,
+ const std::vector<GLint>& expected) {
+ std::vector<GLint> values;
+ values.resize(std::max((GLuint)4, (GLuint)expected.size()));
+ gl->glGetVertexAttribiv(m_index, paramName, &(values[0]));
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ return compareVector<GLint>(
+ expected, values,
+ "int(s) for parameter " + describeGlEnum(paramName) +
+ " of vertex attribute " + std::to_string(m_index));
+ }
+
+ GLuint m_index = 0;
+};
+
+class SnapshotGlVertexAttribSingleValueTest
+ : public SnapshotGlVertexAttributesTest {
+public:
+ void stateCheck(GlVertexAttrib expected) override {
+ SnapshotGlVertexAttributesTest::stateCheck(expected);
+
+ // check current element value
+ switch (expected.type) {
+ case GL_BYTE:
+ case GL_UNSIGNED_BYTE:
+ case GL_SHORT:
+ case GL_UNSIGNED_SHORT:
+ case GL_FIXED:
+ EXPECT_TRUE(compareIntParameter(GL_CURRENT_VERTEX_ATTRIB,
+ expected.values.ints));
+ break;
+ case GL_FLOAT:
+ EXPECT_TRUE(compareFloatParameter(GL_CURRENT_VERTEX_ATTRIB,
+ expected.values.floats));
+ break;
+ default:
+ ADD_FAILURE() << "Unexpected type " << expected.type
+ << " for vertex attribute " << m_index;
+ }
+ }
+
+ void stateChange() override {
+ SnapshotGlVertexAttributesTest::stateChange();
+ GlVertexAttrib changed = *m_changed_value;
+ switch (changed.type) {
+ case GL_BYTE:
+ case GL_UNSIGNED_BYTE:
+ case GL_SHORT:
+ case GL_UNSIGNED_SHORT:
+ case GL_FIXED:
+ // TODO(benzene): support GLES3+
+ FAIL() << "GLES2 only supports float vertex attributes "
+ "(VertexAttrib{1234}f).";
+ case GL_FLOAT:
+ switch (changed.values.floats.size()) {
+ case 1:
+ gl->glVertexAttrib1fv(
+ m_index, (GLfloat*)&changed.values.floats[0]);
+ break;
+ case 2:
+ gl->glVertexAttrib2fv(
+ m_index, (GLfloat*)&changed.values.floats[0]);
+ break;
+ case 3:
+ gl->glVertexAttrib3fv(
+ m_index, (GLfloat*)&changed.values.floats[0]);
+ break;
+ case 4:
+ gl->glVertexAttrib4fv(
+ m_index, (GLfloat*)&changed.values.floats[0]);
+ break;
+ default:
+ ADD_FAILURE() << "Unsupported size " << changed.size
+ << " for vertex attribute " << m_index;
+ }
+ break;
+ default:
+ ADD_FAILURE() << "Unsupported type " << changed.type
+ << " for vertex attribute " << m_index;
+ }
+ }
+};
+
+class SnapshotGlVertexAttribArrayTest : public SnapshotGlVertexAttributesTest {
+public:
+ virtual void stateCheck(GlVertexAttrib expected) override {
+ SnapshotGlVertexAttributesTest::stateCheck(expected);
+ // check parameters
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_SIZE,
+ expected.size));
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_TYPE,
+ expected.type));
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_STRIDE,
+ expected.stride));
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
+ expected.normalized));
+ EXPECT_TRUE(compareIntParameter(GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING,
+ expected.bufferBinding));
+
+ GLvoid* pointer;
+ gl->glGetVertexAttribPointerv(m_index, GL_VERTEX_ATTRIB_ARRAY_POINTER,
+ &pointer);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ EXPECT_EQ(expected.pointer, pointer);
+ }
+
+ virtual void stateChange() override {
+ SnapshotGlVertexAttributesTest::stateChange();
+ GlVertexAttrib changed = *m_changed_value;
+ gl->glVertexAttribPointer(m_index, changed.size, changed.type,
+ changed.normalized, changed.stride,
+ changed.pointer);
+ }
+};
+
+class SnapshotGlVertexAttribBufferTest
+ : public SnapshotGlVertexAttribArrayTest {
+public:
+ void stateCheck(GlVertexAttrib expected) override {
+ SnapshotGlVertexAttribArrayTest::stateCheck(expected);
+ }
+
+ void stateChange() override {
+ GlVertexAttrib changed = *m_changed_value;
+
+ // Set up buffer to be bound before glVertexAttribPointer,
+ // which will copy ARRAY_BUFFER_BINDING into the attrib's binding
+ if (gl->glIsBuffer(changed.bufferBinding) == GL_TRUE) {
+ gl->glBindBuffer(GL_ARRAY_BUFFER, changed.bufferBinding);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError())
+ << "Failed to bind buffer " << changed.bufferBinding;
+ GLint bindresult;
+ gl->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bindresult);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ } else {
+ ADD_FAILURE() << "Tried to bind buffer with vertex attributes but "
+ << changed.bufferBinding << " is not a valid buffer.";
+ }
+
+ SnapshotGlVertexAttribArrayTest::stateChange();
+
+ if (changed.bufferBinding != 0) {
+ // Clear the array buffer binding
+ gl->glBindBuffer(GL_ARRAY_BUFFER, 0);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+ GLint bindresult;
+ gl->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &bindresult);
+ EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+ }
+ }
+};
+
+TEST_F(SnapshotGlVertexAttribSingleValueTest, PreserveCurrentFloatAttrib) {
+ selectIndex(31);
+ GlVertexAttrib testAttrib = kGLES2DefaultVertexAttrib;
+ testAttrib.values = {.ints = {}, .floats = {.1, .3}},
+ setExpectedValues(kGLES2DefaultVertexAttrib, testAttrib);
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlVertexAttribArrayTest, DISABLED_PreserveArrayProperties) {
+ selectIndex(5);
+ GLfloat testArrayContents[] = {2.1f, 2.2f, 2.3f, 2.4f, 2.5f, 2.6f};
+ GlVertexAttrib arrayAttrib = kGLES2DefaultVertexAttrib;
+ arrayAttrib.mode = GlVertexAttribMode::Array;
+ arrayAttrib.size = 3;
+ arrayAttrib.stride = sizeof(GLfloat) * 3;
+ arrayAttrib.normalized = GL_TRUE;
+ arrayAttrib.enabled = GL_TRUE;
+ arrayAttrib.pointer = testArrayContents;
+ setExpectedValues(kGLES2DefaultVertexAttrib, arrayAttrib);
+ doCheckedSnapshot();
+}
+
+TEST_F(SnapshotGlVertexAttribBufferTest, AttachArrayBuffer) {
+ selectIndex(15);
+ GLfloat testBuffContents[] = {
+ 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f,
+ 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f,
+ };
+ GlBufferData data = kTestAttachedBuffer;
+ data.bytes = testBuffContents;
+ GLuint buffer = createBuffer(gl, data);
+ GlVertexAttrib withBuffer = kGLES2DefaultVertexAttrib;
+ withBuffer.mode = GlVertexAttribMode::Buffer;
+ withBuffer.enabled = GL_TRUE;
+ withBuffer.pointer = reinterpret_cast<GLvoid*>(2); // offset
+ withBuffer.bufferBinding = buffer;
+ setExpectedValues(kGLES2DefaultVertexAttrib, withBuffer);
+ doCheckedSnapshot();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLSnapshot_unittest.cpp b/stream-servers/tests/GLSnapshot_unittest.cpp
new file mode 100644
index 0000000..97734bb
--- /dev/null
+++ b/stream-servers/tests/GLSnapshot_unittest.cpp
@@ -0,0 +1,81 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+TEST_F(SnapshotTest, InitDestroy) {}
+
+class SnapshotGlEnableTest : public SnapshotPreserveTest,
+ public ::testing::WithParamInterface<GLenum> {
+ void defaultStateCheck() override {
+ EXPECT_FALSE(gl->glIsEnabled(GetParam()));
+ }
+ void changedStateCheck() override {
+ EXPECT_TRUE(gl->glIsEnabled(GetParam()));
+ }
+ void stateChange() override { gl->glEnable(GetParam()); }
+};
+
+TEST_P(SnapshotGlEnableTest, PreserveEnable) {
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotCapability,
+ SnapshotGlEnableTest,
+ ::testing::ValuesIn(kGLES2CanBeEnabled));
+
+class SnapshotGlDisableTest : public SnapshotPreserveTest,
+ public ::testing::WithParamInterface<GLenum> {
+ void defaultStateCheck() override {
+ EXPECT_TRUE(gl->glIsEnabled(GetParam()));
+ }
+ void changedStateCheck() override {
+ EXPECT_FALSE(gl->glIsEnabled(GetParam()));
+ }
+ void stateChange() override { gl->glDisable(GetParam()); }
+};
+
+TEST_P(SnapshotGlDisableTest, PreserveDisable) {
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotCapability,
+ SnapshotGlDisableTest,
+ ::testing::ValuesIn(kGLES2CanBeDisabled));
+
+class SnapshotGlMipmapHintTest : public SnapshotSetValueTest<GLenum>,
+ public ::testing::WithParamInterface<GLenum> {
+ void stateCheck(GLenum expected) override {
+ EXPECT_TRUE(compareGlobalGlInt(gl, GL_GENERATE_MIPMAP_HINT, expected));
+ }
+ void stateChange() override {
+ gl->glHint(GL_GENERATE_MIPMAP_HINT, *m_changed_value);
+ }
+};
+
+TEST_P(SnapshotGlMipmapHintTest, DISABLED_PreserveHint) {
+ setExpectedValues(GL_DONT_CARE, GetParam());
+ doCheckedSnapshot();
+}
+
+INSTANTIATE_TEST_CASE_P(GLES2SnapshotHints,
+ SnapshotGlMipmapHintTest,
+ ::testing::ValuesIn(kGLES2GenerateMipmapHints));
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLTestUtils.cpp b/stream-servers/tests/GLTestUtils.cpp
new file mode 100644
index 0000000..8e1fc63
--- /dev/null
+++ b/stream-servers/tests/GLTestUtils.cpp
@@ -0,0 +1,627 @@
+// 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 "GLTestUtils.h"
+
+using android::AlignedBuf;
+
+namespace emugl {
+
+testing::AssertionResult RowMatches(int rowIndex, size_t rowBytes,
+ unsigned char* expected, unsigned char* actual) {
+ for (size_t i = 0; i < rowBytes; ++i) {
+ if (expected[i] != actual[i]) {
+ return testing::AssertionFailure()
+ << "row " << rowIndex << " byte " << i
+ << " mismatch. expected: "
+ << "(" << testing::PrintToString(expected[i])
+ << "), actual: "
+ << "(" << testing::PrintToString(actual[i]) << ")";
+ }
+ }
+ return testing::AssertionSuccess();
+}
+
+testing::AssertionResult ImageMatches(int width, int height, int bpp, int rowLength,
+ unsigned char* expected, unsigned char* actual) {
+ int rowMismatches = 0;
+ std::stringstream rowList;
+ std::stringstream rowDetails;
+ rowList << "on ";
+ for (int i = 0; i < height * rowLength; i += rowLength) {
+ size_t rowBytes = width * bpp;
+ testing::AssertionResult rowResult = RowMatches(
+ i / rowLength, rowBytes, expected + i * bpp, actual + i * bpp);
+ if (!rowResult) {
+ ++rowMismatches;
+ if (rowMismatches < 50) {
+ rowList << ((rowMismatches != 1) ? ", " : "")
+ << (i / rowLength);
+ rowDetails << "\t" << rowResult.message() << "\n";
+ }
+ if (rowMismatches == 50) {
+ rowList << " and more";
+ rowDetails << "\t...";
+ }
+ }
+ }
+ if (rowMismatches) {
+ return testing::AssertionFailure()
+ << "Mismatch in image: " << rowMismatches << " rows mismatch "
+ << rowList.str() << ".\n"
+ << rowDetails.str();
+ } else {
+ return testing::AssertionSuccess();
+ }
+}
+
+TestTexture createTestPatternRGB888(int width, int height) {
+ int bpp = 3;
+
+ TestTexture res(bpp * width * height);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ res[i * bpp * width + j * bpp + 0] = i % 0x100;
+ res[i * bpp * width + j * bpp + 1] = j % 0x100;
+ res[i * bpp * width + j * bpp + 2] = (i * width + j) % 0x100;
+ }
+ }
+
+ return res;
+}
+
+TestTexture createTestPatternRGBA8888(int width, int height) {
+ int bpp = 4;
+
+ TestTexture res(bpp * width * height);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ res[i * bpp * width + j * bpp + 0] = i % 0x100;
+ res[i * bpp * width + j * bpp + 1] = j % 0x100;
+ res[i * bpp * width + j * bpp + 2] = (i * width + j) % 0x100;
+ res[i * bpp * width + j * bpp + 3] = (width - j) % 0x100;
+ }
+ }
+
+ return res;
+}
+
+static float clamp(float x, float low, float high) {
+ if (x < low) return low;
+ if (x > high) return high;
+ return x;
+}
+
+TestTexture createTestTextureRGB888SingleColor(int width, int height, float r, float g, float b) {
+ int bpp = 3;
+
+ TestTexture res(bpp * width * height);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ res[i * bpp * width + j * bpp + 0] = clamp(r, 0.0f, 1.0f) * 255.0f;
+ res[i * bpp * width + j * bpp + 1] = clamp(g, 0.0f, 1.0f) * 255.0f;
+ res[i * bpp * width + j * bpp + 2] = clamp(b, 0.0f, 1.0f) * 255.0f;
+ }
+ }
+
+ return res;
+}
+
+TestTexture createTestTextureRGBA8888SingleColor(int width, int height, float r, float g, float b, float a) {
+ int bpp = 4;
+
+ TestTexture res(bpp * width * height);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ res[i * bpp * width + j * bpp + 0] = clamp(r, 0.0f, 1.0f) * 255.0f;
+ res[i * bpp * width + j * bpp + 1] = clamp(g, 0.0f, 1.0f) * 255.0f;
+ res[i * bpp * width + j * bpp + 2] = clamp(b, 0.0f, 1.0f) * 255.0f;
+ res[i * bpp * width + j * bpp + 3] = clamp(a, 0.0f, 1.0f) * 255.0f;
+ }
+ }
+
+ return res;
+}
+
+const char* getEnumString(GLenum v) {
+ switch (v) {
+ case 0x0000: return "GL_ZERO/GL_NO_ERROR/GL_POINTS";
+ case 0x0001: return "GL_ONE/GL_LINES";
+ case 0x0002: return "GL_LINE_LOOP";
+ case 0x0003: return "GL_LINE_STRIP";
+ case 0x0004: return "GL_TRIANGLES";
+ case 0x0005: return "GL_TRIANGLE_STRIP";
+ case 0x0006: return "GL_TRIANGLE_FAN";
+ case 0x0100: return "GL_DEPTH_BUFFER_BIT";
+ case 0x0104: return "GL_ADD";
+ case 0x0200: return "GL_NEVER";
+ case 0x0201: return "GL_LESS";
+ case 0x0202: return "GL_EQUAL";
+ case 0x0203: return "GL_LEQUAL";
+ case 0x0204: return "GL_GREATER";
+ case 0x0205: return "GL_NOTEQUAL";
+ case 0x0206: return "GL_GEQUAL";
+ case 0x0207: return "GL_ALWAYS";
+ case 0x0300: return "GL_SRC_COLOR";
+ case 0x0301: return "GL_ONE_MINUS_SRC_COLOR";
+ case 0x0302: return "GL_SRC_ALPHA";
+ case 0x0303: return "GL_ONE_MINUS_SRC_ALPHA";
+ case 0x0304: return "GL_DST_ALPHA";
+ case 0x0305: return "GL_ONE_MINUS_DST_ALPHA";
+ case 0x0306: return "GL_DST_COLOR";
+ case 0x0307: return "GL_ONE_MINUS_DST_COLOR";
+ case 0x0308: return "GL_SRC_ALPHA_SATURATE";
+ case 0x0400: return "GL_STENCIL_BUFFER_BIT";
+ case 0x0404: return "GL_FRONT";
+ case 0x0405: return "GL_BACK";
+ case 0x0408: return "GL_FRONT_AND_BACK";
+ case 0x0500: return "GL_INVALID_ENUM";
+ case 0x0501: return "GL_INVALID_VALUE";
+ case 0x0502: return "GL_INVALID_OPERATION";
+ case 0x0503: return "GL_STACK_OVERFLOW";
+ case 0x0504: return "GL_STACK_UNDERFLOW";
+ case 0x0505: return "GL_OUT_OF_MEMORY";
+ case 0x0506: return "GL_INVALID_FRAMEBUFFER_OPERATION";
+ case 0x0800: return "GL_EXP";
+ case 0x0801: return "GL_EXP2";
+ case 0x0900: return "GL_CW";
+ case 0x0901: return "GL_CCW";
+ case 0x0B00: return "GL_CURRENT_COLOR";
+ case 0x0B02: return "GL_CURRENT_NORMAL";
+ case 0x0B03: return "GL_CURRENT_TEXTURE_COORDS";
+ case 0x0B10: return "GL_POINT_SMOOTH";
+ case 0x0B11: return "GL_POINT_SIZE";
+ case 0x0B12: return "GL_SMOOTH_POINT_SIZE_RANGE";
+ case 0x0B20: return "GL_LINE_SMOOTH";
+ case 0x0B21: return "GL_LINE_WIDTH";
+ case 0x0B22: return "GL_SMOOTH_LINE_WIDTH_RANGE";
+ case 0x0B44: return "GL_CULL_FACE";
+ case 0x0B45: return "GL_CULL_FACE_MODE";
+ case 0x0B46: return "GL_FRONT_FACE";
+ case 0x0B50: return "GL_LIGHTING";
+ case 0x0B52: return "GL_LIGHT_MODEL_TWO_SIDE";
+ case 0x0B53: return "GL_LIGHT_MODEL_AMBIENT";
+ case 0x0B54: return "GL_SHADE_MODEL";
+ case 0x0B57: return "GL_COLOR_MATERIAL";
+ case 0x0B60: return "GL_FOG";
+ case 0x0B62: return "GL_FOG_DENSITY";
+ case 0x0B63: return "GL_FOG_START";
+ case 0x0B64: return "GL_FOG_END";
+ case 0x0B65: return "GL_FOG_MODE";
+ case 0x0B66: return "GL_FOG_COLOR";
+ case 0x0B70: return "GL_DEPTH_RANGE";
+ case 0x0B71: return "GL_DEPTH_TEST";
+ case 0x0B72: return "GL_DEPTH_WRITEMASK";
+ case 0x0B73: return "GL_DEPTH_CLEAR_VALUE";
+ case 0x0B74: return "GL_DEPTH_FUNC";
+ case 0x0B90: return "GL_STENCIL_TEST";
+ case 0x0B91: return "GL_STENCIL_CLEAR_VALUE";
+ case 0x0B92: return "GL_STENCIL_FUNC";
+ case 0x0B93: return "GL_STENCIL_VALUE_MASK";
+ case 0x0B94: return "GL_STENCIL_FAIL";
+ case 0x0B95: return "GL_STENCIL_PASS_DEPTH_FAIL";
+ case 0x0B96: return "GL_STENCIL_PASS_DEPTH_PASS";
+ case 0x0B97: return "GL_STENCIL_REF";
+ case 0x0B98: return "GL_STENCIL_WRITEMASK";
+ case 0x0BA0: return "GL_MATRIX_MODE";
+ case 0x0BA1: return "GL_NORMALIZE";
+ case 0x0BA2: return "GL_VIEWPORT";
+ case 0x0BA3: return "GL_MODELVIEW_STACK_DEPTH";
+ case 0x0BA4: return "GL_PROJECTION_STACK_DEPTH";
+ case 0x0BA5: return "GL_TEXTURE_STACK_DEPTH";
+ case 0x0BA6: return "GL_MODELVIEW_MATRIX";
+ case 0x0BA7: return "GL_PROJECTION_MATRIX";
+ case 0x0BA8: return "GL_TEXTURE_MATRIX";
+ case 0x0BC0: return "GL_ALPHA_TEST";
+ case 0x0BC1: return "GL_ALPHA_TEST_FUNC";
+ case 0x0BC2: return "GL_ALPHA_TEST_REF";
+ case 0x0BD0: return "GL_DITHER";
+ case 0x0BE0: return "GL_BLEND_DST";
+ case 0x0BE1: return "GL_BLEND_SRC";
+ case 0x0BE2: return "GL_BLEND";
+ case 0x0BF0: return "GL_LOGIC_OP_MODE";
+ case 0x0BF2: return "GL_COLOR_LOGIC_OP";
+ case 0x0C10: return "GL_SCISSOR_BOX";
+ case 0x0C11: return "GL_SCISSOR_TEST";
+ case 0x0C22: return "GL_COLOR_CLEAR_VALUE";
+ case 0x0C23: return "GL_COLOR_WRITEMASK";
+ case 0x0C50: return "GL_PERSPECTIVE_CORRECTION_HINT";
+ case 0x0C51: return "GL_POINT_SMOOTH_HINT";
+ case 0x0C52: return "GL_LINE_SMOOTH_HINT";
+ case 0x0C54: return "GL_FOG_HINT";
+ case 0x0CF5: return "GL_UNPACK_ALIGNMENT";
+ case 0x0D05: return "GL_PACK_ALIGNMENT";
+ case 0x0D1C: return "GL_ALPHA_SCALE";
+ case 0x0D31: return "GL_MAX_LIGHTS";
+ case 0x0D32: return "GL_MAX_CLIP_PLANES";
+ case 0x0D33: return "GL_MAX_TEXTURE_SIZE";
+ case 0x0D36: return "GL_MAX_MODELVIEW_STACK_DEPTH";
+ case 0x0D38: return "GL_MAX_PROJECTION_STACK_DEPTH";
+ case 0x0D39: return "GL_MAX_TEXTURE_STACK_DEPTH";
+ case 0x0D3A: return "GL_MAX_VIEWPORT_DIMS";
+ case 0x0D50: return "GL_SUBPIXEL_BITS";
+ case 0x0D52: return "GL_RED_BITS";
+ case 0x0D53: return "GL_GREEN_BITS";
+ case 0x0D54: return "GL_BLUE_BITS";
+ case 0x0D55: return "GL_ALPHA_BITS";
+ case 0x0D56: return "GL_DEPTH_BITS";
+ case 0x0D57: return "GL_STENCIL_BITS";
+ case 0x0DE1: return "GL_TEXTURE_2D";
+ case 0x1100: return "GL_DONT_CARE";
+ case 0x1101: return "GL_FASTEST";
+ case 0x1102: return "GL_NICEST";
+ case 0x1200: return "GL_AMBIENT";
+ case 0x1201: return "GL_DIFFUSE";
+ case 0x1202: return "GL_SPECULAR";
+ case 0x1203: return "GL_POSITION";
+ case 0x1204: return "GL_SPOT_DIRECTION";
+ case 0x1205: return "GL_SPOT_EXPONENT";
+ case 0x1206: return "GL_SPOT_CUTOFF";
+ case 0x1207: return "GL_CONSTANT_ATTENUATION";
+ case 0x1208: return "GL_LINEAR_ATTENUATION";
+ case 0x1209: return "GL_QUADRATIC_ATTENUATION";
+ case 0x1400: return "GL_BYTE";
+ case 0x1401: return "GL_UNSIGNED_BYTE";
+ case 0x1402: return "GL_SHORT";
+ case 0x1403: return "GL_UNSIGNED_SHORT";
+ case 0x1404: return "GL_INT";
+ case 0x1405: return "GL_UNSIGNED_INT";
+ case 0x1406: return "GL_FLOAT";
+ case 0x140C: return "GL_FIXED";
+ case 0x1500: return "GL_CLEAR";
+ case 0x1501: return "GL_AND";
+ case 0x1502: return "GL_AND_REVERSE";
+ case 0x1503: return "GL_COPY";
+ case 0x1504: return "GL_AND_INVERTED";
+ case 0x1505: return "GL_NOOP";
+ case 0x1506: return "GL_XOR";
+ case 0x1507: return "GL_OR";
+ case 0x1508: return "GL_NOR";
+ case 0x1509: return "GL_EQUIV";
+ case 0x150A: return "GL_INVERT";
+ case 0x150B: return "GL_OR_REVERSE";
+ case 0x150C: return "GL_COPY_INVERTED";
+ case 0x150D: return "GL_OR_INVERTED";
+ case 0x150E: return "GL_NAND";
+ case 0x150F: return "GL_SET";
+ case 0x1600: return "GL_EMISSION";
+ case 0x1601: return "GL_SHININESS";
+ case 0x1602: return "GL_AMBIENT_AND_DIFFUSE";
+ case 0x1700: return "GL_MODELVIEW";
+ case 0x1701: return "GL_PROJECTION";
+ case 0x1702: return "GL_TEXTURE";
+ case 0x1901: return "GL_STENCIL_INDEX";
+ case 0x1902: return "GL_DEPTH_COMPONENT";
+ case 0x1906: return "GL_ALPHA";
+ case 0x1907: return "GL_RGB";
+ case 0x1908: return "GL_RGBA";
+ case 0x1909: return "GL_LUMINANCE";
+ case 0x190A: return "GL_LUMINANCE_ALPHA";
+ case 0x1D00: return "GL_FLAT";
+ case 0x1D01: return "GL_SMOOTH";
+ case 0x1E00: return "GL_KEEP";
+ case 0x1E01: return "GL_REPLACE";
+ case 0x1E02: return "GL_INCR";
+ case 0x1E03: return "GL_DECR";
+ case 0x1F00: return "GL_VENDOR";
+ case 0x1F01: return "GL_RENDERER";
+ case 0x1F02: return "GL_VERSION";
+ case 0x1F03: return "GL_EXTENSIONS";
+ case 0x2100: return "GL_MODULATE";
+ case 0x2101: return "GL_DECAL";
+ case 0x2200: return "GL_TEXTURE_ENV_MODE";
+ case 0x2201: return "GL_TEXTURE_ENV_COLOR";
+ case 0x2300: return "GL_TEXTURE_ENV";
+ case 0x2500: return "GL_TEXTURE_GEN_MODE_OES";
+ case 0x2600: return "GL_NEAREST";
+ case 0x2601: return "GL_LINEAR";
+ case 0x2700: return "GL_NEAREST_MIPMAP_NEAREST";
+ case 0x2701: return "GL_LINEAR_MIPMAP_NEAREST";
+ case 0x2702: return "GL_NEAREST_MIPMAP_LINEAR";
+ case 0x2703: return "GL_LINEAR_MIPMAP_LINEAR";
+ case 0x2800: return "GL_TEXTURE_MAG_FILTER";
+ case 0x2801: return "GL_TEXTURE_MIN_FILTER";
+ case 0x2802: return "GL_TEXTURE_WRAP_S";
+ case 0x2803: return "GL_TEXTURE_WRAP_T";
+ case 0x2901: return "GL_REPEAT";
+ case 0x2A00: return "GL_POLYGON_OFFSET_UNITS";
+ case 0x3000: return "GL_CLIP_PLANE0";
+ case 0x3001: return "GL_CLIP_PLANE1";
+ case 0x3002: return "GL_CLIP_PLANE2";
+ case 0x3003: return "GL_CLIP_PLANE3";
+ case 0x3004: return "GL_CLIP_PLANE4";
+ case 0x3005: return "GL_CLIP_PLANE5";
+ case 0x300E: return "GL_CONTEXT_LOST";
+ case 0x4000: return "GL_LIGHT0";
+ case 0x4001: return "GL_LIGHT1";
+ case 0x4002: return "GL_LIGHT2";
+ case 0x4003: return "GL_LIGHT3";
+ case 0x4004: return "GL_LIGHT4";
+ case 0x4005: return "GL_LIGHT5";
+ case 0x4006: return "GL_LIGHT6";
+ case 0x4007: return "GL_LIGHT7";
+ case 0x8001: return "GL_CONSTANT_COLOR";
+ case 0x8002: return "GL_ONE_MINUS_CONSTANT_COLOR";
+ case 0x8003: return "GL_CONSTANT_ALPHA";
+ case 0x8004: return "GL_ONE_MINUS_CONSTANT_ALPHA";
+ case 0x8005: return "GL_BLEND_COLOR";
+ case 0x8006: return "GL_FUNC_ADD";
+ case 0x8009: return "GL_BLEND_EQUATION";
+ case 0x800A: return "GL_FUNC_SUBTRACT";
+ case 0x800B: return "GL_FUNC_REVERSE_SUBTRACT";
+ case 0x8033: return "GL_UNSIGNED_SHORT_4_4_4_4";
+ case 0x8034: return "GL_UNSIGNED_SHORT_5_5_5_1";
+ case 0x8037: return "GL_POLYGON_OFFSET_FILL";
+ case 0x8038: return "GL_POLYGON_OFFSET_FACTOR";
+ case 0x803A: return "GL_RESCALE_NORMAL";
+ case 0x8056: return "GL_RGBA4";
+ case 0x8057: return "GL_RGB5_A1";
+ case 0x8069: return "GL_TEXTURE_BINDING_2D";
+ case 0x8074: return "GL_VERTEX_ARRAY";
+ case 0x8075: return "GL_NORMAL_ARRAY";
+ case 0x8076: return "GL_COLOR_ARRAY";
+ case 0x8078: return "GL_TEXTURE_COORD_ARRAY";
+ case 0x807A: return "GL_VERTEX_ARRAY_SIZE";
+ case 0x807B: return "GL_VERTEX_ARRAY_TYPE";
+ case 0x807C: return "GL_VERTEX_ARRAY_STRIDE";
+ case 0x807E: return "GL_NORMAL_ARRAY_TYPE";
+ case 0x807F: return "GL_NORMAL_ARRAY_STRIDE";
+ case 0x8081: return "GL_COLOR_ARRAY_SIZE";
+ case 0x8082: return "GL_COLOR_ARRAY_TYPE";
+ case 0x8083: return "GL_COLOR_ARRAY_STRIDE";
+ case 0x8088: return "GL_TEXTURE_COORD_ARRAY_SIZE";
+ case 0x8089: return "GL_TEXTURE_COORD_ARRAY_TYPE";
+ case 0x808A: return "GL_TEXTURE_COORD_ARRAY_STRIDE";
+ case 0x808E: return "GL_VERTEX_ARRAY_POINTER";
+ case 0x808F: return "GL_NORMAL_ARRAY_POINTER";
+ case 0x8090: return "GL_COLOR_ARRAY_POINTER";
+ case 0x8092: return "GL_TEXTURE_COORD_ARRAY_POINTER";
+ case 0x809D: return "GL_MULTISAMPLE";
+ case 0x809E: return "GL_SAMPLE_ALPHA_TO_COVERAGE";
+ case 0x809F: return "GL_SAMPLE_ALPHA_TO_ONE";
+ case 0x80A0: return "GL_SAMPLE_COVERAGE";
+ case 0x80A8: return "GL_SAMPLE_BUFFERS";
+ case 0x80A9: return "GL_SAMPLES";
+ case 0x80AA: return "GL_SAMPLE_COVERAGE_VALUE";
+ case 0x80AB: return "GL_SAMPLE_COVERAGE_INVERT";
+ case 0x80C8: return "GL_BLEND_DST_RGB";
+ case 0x80C9: return "GL_BLEND_SRC_RGB";
+ case 0x80CA: return "GL_BLEND_DST_ALPHA";
+ case 0x80CB: return "GL_BLEND_SRC_ALPHA";
+ case 0x8126: return "GL_POINT_SIZE_MIN";
+ case 0x8127: return "GL_POINT_SIZE_MAX";
+ case 0x8128: return "GL_POINT_FADE_THRESHOLD_SIZE";
+ case 0x8129: return "GL_POINT_DISTANCE_ATTENUATION";
+ case 0x812F: return "GL_CLAMP_TO_EDGE";
+ case 0x8191: return "GL_GENERATE_MIPMAP";
+ case 0x8192: return "GL_GENERATE_MIPMAP_HINT";
+ case 0x81A5: return "GL_DEPTH_COMPONENT16";
+ case 0x81A6: return "GL_DEPTH_COMPONENT24_OES";
+ case 0x81A7: return "GL_DEPTH_COMPONENT32_OES";
+ case 0x8363: return "GL_UNSIGNED_SHORT_5_6_5";
+ case 0x8370: return "GL_MIRRORED_REPEAT";
+ case 0x846D: return "GL_ALIASED_POINT_SIZE_RANGE";
+ case 0x846E: return "GL_ALIASED_LINE_WIDTH_RANGE";
+ case 0x84C0: return "GL_TEXTURE0";
+ case 0x84C1: return "GL_TEXTURE1";
+ case 0x84C2: return "GL_TEXTURE2";
+ case 0x84C3: return "GL_TEXTURE3";
+ case 0x84C4: return "GL_TEXTURE4";
+ case 0x84C5: return "GL_TEXTURE5";
+ case 0x84C6: return "GL_TEXTURE6";
+ case 0x84C7: return "GL_TEXTURE7";
+ case 0x84C8: return "GL_TEXTURE8";
+ case 0x84C9: return "GL_TEXTURE9";
+ case 0x84CA: return "GL_TEXTURE10";
+ case 0x84CB: return "GL_TEXTURE11";
+ case 0x84CC: return "GL_TEXTURE12";
+ case 0x84CD: return "GL_TEXTURE13";
+ case 0x84CE: return "GL_TEXTURE14";
+ case 0x84CF: return "GL_TEXTURE15";
+ case 0x84D0: return "GL_TEXTURE16";
+ case 0x84D1: return "GL_TEXTURE17";
+ case 0x84D2: return "GL_TEXTURE18";
+ case 0x84D3: return "GL_TEXTURE19";
+ case 0x84D4: return "GL_TEXTURE20";
+ case 0x84D5: return "GL_TEXTURE21";
+ case 0x84D6: return "GL_TEXTURE22";
+ case 0x84D7: return "GL_TEXTURE23";
+ case 0x84D8: return "GL_TEXTURE24";
+ case 0x84D9: return "GL_TEXTURE25";
+ case 0x84DA: return "GL_TEXTURE26";
+ case 0x84DB: return "GL_TEXTURE27";
+ case 0x84DC: return "GL_TEXTURE28";
+ case 0x84DD: return "GL_TEXTURE29";
+ case 0x84DE: return "GL_TEXTURE30";
+ case 0x84DF: return "GL_TEXTURE31";
+ case 0x84E0: return "GL_ACTIVE_TEXTURE";
+ case 0x84E1: return "GL_CLIENT_ACTIVE_TEXTURE";
+ case 0x84E2: return "GL_MAX_TEXTURE_UNITS";
+ case 0x84E7: return "GL_SUBTRACT";
+ case 0x84E8: return "GL_MAX_RENDERBUFFER_SIZE";
+ case 0x8507: return "GL_INCR_WRAP";
+ case 0x8508: return "GL_DECR_WRAP";
+ case 0x8511: return "GL_NORMAL_MAP_OES";
+ case 0x8512: return "GL_REFLECTION_MAP_OES";
+ case 0x8513: return "GL_TEXTURE_CUBE_MAP_OES";
+ case 0x8514: return "GL_TEXTURE_BINDING_CUBE_MAP_OES";
+ case 0x8515: return "GL_TEXTURE_CUBE_MAP_POSITIVE_X_OES";
+ case 0x8516: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_X_OES";
+ case 0x8517: return "GL_TEXTURE_CUBE_MAP_POSITIVE_Y_OES";
+ case 0x8518: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_OES";
+ case 0x8519: return "GL_TEXTURE_CUBE_MAP_POSITIVE_Z_OES";
+ case 0x851A: return "GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_OES";
+ case 0x851C: return "GL_MAX_CUBE_MAP_TEXTURE_SIZE_OES";
+ case 0x8570: return "GL_COMBINE";
+ case 0x8571: return "GL_COMBINE_RGB";
+ case 0x8572: return "GL_COMBINE_ALPHA";
+ case 0x8573: return "GL_RGB_SCALE";
+ case 0x8574: return "GL_ADD_SIGNED";
+ case 0x8575: return "GL_INTERPOLATE";
+ case 0x8576: return "GL_CONSTANT";
+ case 0x8577: return "GL_PRIMARY_COLOR";
+ case 0x8578: return "GL_PREVIOUS";
+ case 0x8580: return "GL_SRC0_RGB";
+ case 0x8581: return "GL_SRC1_RGB";
+ case 0x8582: return "GL_SRC2_RGB";
+ case 0x8588: return "GL_SRC0_ALPHA";
+ case 0x8589: return "GL_SRC1_ALPHA";
+ case 0x858A: return "GL_SRC2_ALPHA";
+ case 0x8590: return "GL_OPERAND0_RGB";
+ case 0x8591: return "GL_OPERAND1_RGB";
+ case 0x8592: return "GL_OPERAND2_RGB";
+ case 0x8598: return "GL_OPERAND0_ALPHA";
+ case 0x8599: return "GL_OPERAND1_ALPHA";
+ case 0x859A: return "GL_OPERAND2_ALPHA";
+ case 0x8622: return "GL_VERTEX_ATTRIB_ARRAY_ENABLED";
+ case 0x8623: return "GL_VERTEX_ATTRIB_ARRAY_SIZE";
+ case 0x8624: return "GL_VERTEX_ATTRIB_ARRAY_STRIDE";
+ case 0x8625: return "GL_VERTEX_ATTRIB_ARRAY_TYPE";
+ case 0x8626: return "GL_CURRENT_VERTEX_ATTRIB";
+ case 0x8645: return "GL_VERTEX_ATTRIB_ARRAY_POINTER";
+ case 0x86A2: return "GL_NUM_COMPRESSED_TEXTURE_FORMATS";
+ case 0x86A3: return "GL_COMPRESSED_TEXTURE_FORMATS";
+ case 0x86AE: return "GL_DOT3_RGB";
+ case 0x86AF: return "GL_DOT3_RGBA";
+ case 0x8764: return "GL_BUFFER_SIZE";
+ case 0x8765: return "GL_BUFFER_USAGE";
+ case 0x8800: return "GL_STENCIL_BACK_FUNC";
+ case 0x8801: return "GL_STENCIL_BACK_FAIL";
+ case 0x8802: return "GL_STENCIL_BACK_PASS_DEPTH_FAIL";
+ case 0x8803: return "GL_STENCIL_BACK_PASS_DEPTH_PASS";
+ case 0x883D: return "GL_BLEND_EQUATION_ALPHA";
+ case 0x8861: return "GL_POINT_SPRITE_OES";
+ case 0x8862: return "GL_COORD_REPLACE_OES";
+ case 0x8869: return "GL_MAX_VERTEX_ATTRIBS";
+ case 0x886A: return "GL_VERTEX_ATTRIB_ARRAY_NORMALIZED";
+ case 0x8872: return "GL_MAX_TEXTURE_IMAGE_UNITS";
+ case 0x8892: return "GL_ARRAY_BUFFER";
+ case 0x8893: return "GL_ELEMENT_ARRAY_BUFFER";
+ case 0x8894: return "GL_ARRAY_BUFFER_BINDING";
+ case 0x8895: return "GL_ELEMENT_ARRAY_BUFFER_BINDING";
+ case 0x8896: return "GL_VERTEX_ARRAY_BUFFER_BINDING";
+ case 0x8897: return "GL_NORMAL_ARRAY_BUFFER_BINDING";
+ case 0x8898: return "GL_COLOR_ARRAY_BUFFER_BINDING";
+ case 0x889A: return "GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING";
+ case 0x889F: return "GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING";
+ case 0x88E0: return "GL_STREAM_DRAW";
+ case 0x88E4: return "GL_STATIC_DRAW";
+ case 0x88E8: return "GL_DYNAMIC_DRAW";
+ case 0x898A: return "GL_POINT_SIZE_ARRAY_TYPE_OES";
+ case 0x898B: return "GL_POINT_SIZE_ARRAY_STRIDE_OES";
+ case 0x898C: return "GL_POINT_SIZE_ARRAY_POINTER_OES";
+ case 0x8B30: return "GL_FRAGMENT_SHADER";
+ case 0x8B31: return "GL_VERTEX_SHADER";
+ case 0x8B4C: return "GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS";
+ case 0x8B4D: return "GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS";
+ case 0x8B4F: return "GL_SHADER_TYPE";
+ case 0x8B50: return "GL_FLOAT_VEC2";
+ case 0x8B51: return "GL_FLOAT_VEC3";
+ case 0x8B52: return "GL_FLOAT_VEC4";
+ case 0x8B53: return "GL_INT_VEC2";
+ case 0x8B54: return "GL_INT_VEC3";
+ case 0x8B55: return "GL_INT_VEC4";
+ case 0x8B56: return "GL_BOOL";
+ case 0x8B57: return "GL_BOOL_VEC2";
+ case 0x8B58: return "GL_BOOL_VEC3";
+ case 0x8B59: return "GL_BOOL_VEC4";
+ case 0x8B5A: return "GL_FLOAT_MAT2";
+ case 0x8B5B: return "GL_FLOAT_MAT3";
+ case 0x8B5C: return "GL_FLOAT_MAT4";
+ case 0x8B5E: return "GL_SAMPLER_2D";
+ case 0x8B60: return "GL_SAMPLER_CUBE";
+ case 0x8B80: return "GL_DELETE_STATUS";
+ case 0x8B81: return "GL_COMPILE_STATUS";
+ case 0x8B82: return "GL_LINK_STATUS";
+ case 0x8B83: return "GL_VALIDATE_STATUS";
+ case 0x8B84: return "GL_INFO_LOG_LENGTH";
+ case 0x8B85: return "GL_ATTACHED_SHADERS";
+ case 0x8B86: return "GL_ACTIVE_UNIFORMS";
+ case 0x8B87: return "GL_ACTIVE_UNIFORM_MAX_LENGTH";
+ case 0x8B88: return "GL_SHADER_SOURCE_LENGTH";
+ case 0x8B89: return "GL_ACTIVE_ATTRIBUTES";
+ case 0x8B8A: return "GL_ACTIVE_ATTRIBUTE_MAX_LENGTH";
+ case 0x8B8C: return "GL_SHADING_LANGUAGE_VERSION";
+ case 0x8B8D: return "GL_CURRENT_PROGRAM";
+ case 0x8B90: return "GL_PALETTE4_RGB8_OES";
+ case 0x8B91: return "GL_PALETTE4_RGBA8_OES";
+ case 0x8B92: return "GL_PALETTE4_R5_G6_B5_OES";
+ case 0x8B93: return "GL_PALETTE4_RGBA4_OES";
+ case 0x8B94: return "GL_PALETTE4_RGB5_A1_OES";
+ case 0x8B95: return "GL_PALETTE8_RGB8_OES";
+ case 0x8B96: return "GL_PALETTE8_RGBA8_OES";
+ case 0x8B97: return "GL_PALETTE8_R5_G6_B5_OES";
+ case 0x8B98: return "GL_PALETTE8_RGBA4_OES";
+ case 0x8B99: return "GL_PALETTE8_RGB5_A1_OES";
+ case 0x8B9A: return "GL_IMPLEMENTATION_COLOR_READ_TYPE";
+ case 0x8B9B: return "GL_IMPLEMENTATION_COLOR_READ_FORMAT";
+ case 0x8B9C: return "GL_POINT_SIZE_ARRAY_OES";
+ case 0x8B9D: return "GL_POINT_SIZE_ARRAY_OES";
+ case 0x8B9F: return "GL_POINT_SIZE_ARRAY_BUFFER_BINDING_OES";
+ case 0x8CA3: return "GL_STENCIL_BACK_REF";
+ case 0x8CA4: return "GL_STENCIL_BACK_VALUE_MASK";
+ case 0x8CA5: return "GL_STENCIL_BACK_WRITEMASK";
+ case 0x8CA6: return "GL_FRAMEBUFFER_BINDING";
+ case 0x8CA7: return "GL_RENDERBUFFER_BINDING";
+ case 0x8CD0: return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE";
+ case 0x8CD1: return "GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME";
+ case 0x8CD2: return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL";
+ case 0x8CD3: return "GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE";
+ case 0x8CD5: return "GL_FRAMEBUFFER_COMPLETE";
+ case 0x8CD6: return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+ case 0x8CD7: return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+ case 0x8CD9: return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+ case 0x8CDD: return "GL_FRAMEBUFFER_UNSUPPORTED";
+ case 0x8CE0: return "GL_COLOR_ATTACHMENT0";
+ case 0x8D00: return "GL_DEPTH_ATTACHMENT";
+ case 0x8D20: return "GL_STENCIL_ATTACHMENT";
+ case 0x8D40: return "GL_FRAMEBUFFER";
+ case 0x8D41: return "GL_RENDERBUFFER";
+ case 0x8D42: return "GL_RENDERBUFFER_WIDTH";
+ case 0x8D43: return "GL_RENDERBUFFER_HEIGHT";
+ case 0x8D44: return "GL_RENDERBUFFER_INTERNAL_FORMAT";
+ case 0x8D48: return "GL_STENCIL_INDEX8";
+ case 0x8D50: return "GL_RENDERBUFFER_RED_SIZE";
+ case 0x8D51: return "GL_RENDERBUFFER_GREEN_SIZE";
+ case 0x8D52: return "GL_RENDERBUFFER_BLUE_SIZE";
+ case 0x8D53: return "GL_RENDERBUFFER_ALPHA_SIZE";
+ case 0x8D54: return "GL_RENDERBUFFER_DEPTH_SIZE";
+ case 0x8D55: return "GL_RENDERBUFFER_STENCIL_SIZE";
+ case 0x8D60: return "GL_TEXTURE_GEN_STR_OES";
+ case 0x8D62: return "GL_RGB565";
+ case 0x8D64: return "GL_ETC1_RGB8_OES";
+ case 0x8D65: return "GL_TEXTURE_EXTERNAL_OES";
+ case 0x8D67: return "GL_TEXTURE_BINDING_EXTERNAL_OES";
+ case 0x8D68: return "GL_REQUIRED_TEXTURE_IMAGE_UNITS_OES";
+ case 0x8DF0: return "GL_LOW_FLOAT";
+ case 0x8DF1: return "GL_MEDIUM_FLOAT";
+ case 0x8DF2: return "GL_HIGH_FLOAT";
+ case 0x8DF3: return "GL_LOW_INT";
+ case 0x8DF4: return "GL_MEDIUM_INT";
+ case 0x8DF5: return "GL_HIGH_INT";
+ case 0x8DF8: return "GL_SHADER_BINARY_FORMATS";
+ case 0x8DF9: return "GL_NUM_SHADER_BINARY_FORMATS";
+ case 0x8DFA: return "GL_SHADER_COMPILER";
+ case 0x8DFB: return "GL_MAX_VERTEX_UNIFORM_VECTORS";
+ case 0x8DFC: return "GL_MAX_VARYING_VECTORS";
+ case 0x8DFD: return "GL_MAX_FRAGMENT_UNIFORM_VECTORS";
+ default: return "<unknown>";
+ }
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/GLTestUtils.h b/stream-servers/tests/GLTestUtils.h
new file mode 100644
index 0000000..fac6386
--- /dev/null
+++ b/stream-servers/tests/GLTestUtils.h
@@ -0,0 +1,48 @@
+// 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.
+#pragma once
+
+#include "base/AlignedBuf.h"
+
+#include "render_api_platform_types.h"
+
+#include <GLES3/gl31.h>
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+using TestTexture = android::AlignedBuf<uint8_t, 4>;
+
+testing::AssertionResult RowMatches(int rowIndex, size_t rowBytes,
+ unsigned char* expected, unsigned char* actual);
+
+testing::AssertionResult ImageMatches(int width, int height, int bpp, int rowLength,
+ unsigned char* expected, unsigned char* actual);
+
+
+FBNativeWindowType createTestNativeWindow(int x, int y, int width, int height, int dpr);
+
+// Creates an asymmetric test pattern with various formats.
+TestTexture createTestPatternRGB888(int width, int height);
+TestTexture createTestPatternRGBA8888(int width, int height);
+
+// Creates a test pattern of the specified color.
+TestTexture createTestTextureRGB888SingleColor(int width, int height, float r, float g, float b);
+TestTexture createTestTextureRGBA8888SingleColor(int width, int height, float r, float g, float b, float a);
+
+// Return the name associated with |v| as a string.
+const char* getEnumString(GLenum v);
+
+} // namespace emugl
diff --git a/stream-servers/tests/OpenGLTestContext.cpp b/stream-servers/tests/OpenGLTestContext.cpp
new file mode 100644
index 0000000..503c338
--- /dev/null
+++ b/stream-servers/tests/OpenGLTestContext.cpp
@@ -0,0 +1,146 @@
+// Copyright (C) 2017 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 "OpenGLTestContext.h"
+
+#include "Standalone.h"
+
+namespace emugl {
+
+static bool sDisplayNeedsInit = true;
+
+EGLDisplay getDisplay() {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+
+ if (sDisplayNeedsInit) {
+ egl->eglUseOsEglApi(!shouldUseHostGpu());
+ }
+
+ EGLDisplay dpy = egl->eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ EXPECT_TRUE(dpy != EGL_NO_DISPLAY);
+
+ if (sDisplayNeedsInit) {
+ EGLint eglMaj, eglMin;
+ EGLBoolean init_res = egl->eglInitialize(dpy, &eglMaj, &eglMin);
+ EXPECT_TRUE(init_res != EGL_FALSE);
+ sDisplayNeedsInit = false;
+ }
+
+ return dpy;
+}
+
+EGLConfig createConfig(EGLDisplay dpy, EGLint r, EGLint g, EGLint b, EGLint a, EGLint d, EGLint s, EGLint ms) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, r,
+ EGL_GREEN_SIZE, g,
+ EGL_BLUE_SIZE, b,
+ EGL_ALPHA_SIZE, a,
+ EGL_DEPTH_SIZE, d,
+ EGL_STENCIL_SIZE, s,
+ EGL_SAMPLES, ms,
+ EGL_NONE
+ };
+
+ EGLint nConfigs;
+ EGLConfig configOut;
+ EGLBoolean chooseConfigResult =
+ egl->eglChooseConfig(
+ dpy, configAttribs,
+ &configOut,
+ 1,
+ &nConfigs);
+ EXPECT_TRUE(chooseConfigResult != EGL_FALSE);
+ EXPECT_TRUE(nConfigs > 0);
+ return configOut;
+}
+
+EGLSurface pbufferSurface(EGLDisplay dpy, ::EGLConfig config, EGLint w, EGLint h) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ const EGLint pbufferAttribs[] = {
+ EGL_WIDTH, w,
+ EGL_HEIGHT, h,
+ EGL_NONE,
+ };
+
+ EGLSurface pbuf =
+ egl->eglCreatePbufferSurface(
+ dpy, config, pbufferAttribs);
+
+ EXPECT_TRUE(pbuf != EGL_NO_SURFACE);
+ return pbuf;
+}
+
+EGLContext createContext(EGLDisplay dpy, ::EGLConfig config, EGLint maj, EGLint min) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, maj, EGL_NONE };
+ EGLContext cxt = egl->eglCreateContext(dpy, config, EGL_NO_CONTEXT, contextAttribs);
+ EXPECT_TRUE(cxt != EGL_NO_CONTEXT);
+ return cxt;
+}
+
+void destroyContext(EGLDisplay dpy, EGLContext cxt) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ EGLBoolean destroyContextRes =
+ egl->eglDestroyContext(dpy, cxt);
+ EXPECT_TRUE(destroyContextRes != GL_FALSE);
+}
+
+void destroySurface(EGLDisplay dpy, EGLSurface surface) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ EGLBoolean destroySurfaceRes =
+ egl->eglDestroySurface(dpy, surface);
+ EXPECT_TRUE(destroySurfaceRes != GL_FALSE);
+}
+
+void destroyDisplay(EGLDisplay dpy) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ EGLBoolean terminateRes =
+ egl->eglTerminate(dpy);
+ EXPECT_TRUE(terminateRes != GL_FALSE);
+ sDisplayNeedsInit = true;
+}
+
+void GLTest::SetUp() {
+ // setupStandaloneLibrarySearchPaths();
+
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ gl = LazyLoadedGLESv2Dispatch::get();
+ EXPECT_TRUE(egl != nullptr);
+ EXPECT_TRUE(gl != nullptr);
+
+ m_display = getDisplay();
+ m_config = createConfig(m_display, 8, 8, 8, 8, 24, 8, 0);
+ m_surface = pbufferSurface(m_display, m_config, kTestSurfaceSize[0],
+ kTestSurfaceSize[1]);
+ egl->eglSetMaxGLESVersion(3);
+ m_context = createContext(m_display, m_config, 3, 0);
+ egl->eglMakeCurrent(m_display, m_surface, m_surface, m_context);
+}
+
+void GLTest::TearDown() {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ egl->eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ destroyContext(m_display, m_context);
+ destroySurface(m_display, m_surface);
+ destroyDisplay(m_display);
+
+ EXPECT_EQ(EGL_SUCCESS, egl->eglGetError())
+ << "GLTest TearDown found EGL error";
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/OpenGLTestContext.h b/stream-servers/tests/OpenGLTestContext.h
new file mode 100644
index 0000000..48542f8
--- /dev/null
+++ b/stream-servers/tests/OpenGLTestContext.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2017 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.
+#pragma once
+
+#include "OpenGLESDispatch/OpenGLDispatchLoader.h"
+
+// gtest has its own definitions for None and Bool
+#ifdef None
+ #undef None
+#endif
+#ifdef Bool
+ #undef Bool
+#endif
+#include <gtest/gtest.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES3/gl31.h>
+
+namespace emugl {
+
+// Dimensions for test surface
+static const int kTestSurfaceSize[] = {32, 32};
+
+EGLDisplay getDisplay();
+EGLConfig createConfig(EGLDisplay dpy, EGLint r, EGLint g, EGLint b, EGLint a, EGLint d, EGLint s, EGLint ms);
+EGLSurface pbufferSurface(EGLDisplay dpy, ::EGLConfig config, EGLint w, EGLint h);
+EGLContext createContext(EGLDisplay dpy, EGLConfig config, EGLint maj, EGLint min);
+void destroyContext(EGLDisplay dpy, EGLContext cxt);
+void destroySurface(EGLDisplay dpy, EGLSurface surface);
+void destroyDisplay(EGLDisplay dpy);
+
+class GLTest : public ::testing::Test {
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ const GLESv2Dispatch* gl;
+ EGLDisplay m_display;
+ EGLConfig m_config;
+ EGLSurface m_surface;
+ EGLContext m_context;
+};
+
+} // namespace emugl
diff --git a/stream-servers/tests/OpenGL_unittest.cpp b/stream-servers/tests/OpenGL_unittest.cpp
new file mode 100644
index 0000000..0cf2e2b
--- /dev/null
+++ b/stream-servers/tests/OpenGL_unittest.cpp
@@ -0,0 +1,44 @@
+// 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 "OpenGLTestContext.h"
+
+#include <gtest/gtest.h>
+
+namespace emugl {
+
+TEST_F(GLTest, InitDestroy) {}
+
+TEST_F(GLTest, SetUpMulticontext) {
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ EXPECT_TRUE(egl != nullptr);
+ EXPECT_TRUE(gl != nullptr);
+
+ egl->eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ EGLSurface duoSurface = pbufferSurface(
+ m_display, m_config, kTestSurfaceSize[0], kTestSurfaceSize[1]);
+ EGLContext duoContext = createContext(m_display, m_config, 3, 0);
+
+ egl->eglMakeCurrent(m_display, duoSurface, duoSurface, duoContext);
+ EXPECT_EQ(EGL_SUCCESS, egl->eglGetError());
+
+ destroyContext(m_display, duoContext);
+ destroySurface(m_display, duoSurface);
+
+ egl->eglMakeCurrent(m_display, m_surface, m_surface, m_context);
+ EXPECT_EQ(EGL_SUCCESS, egl->eglGetError());
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/SampleApplication.cpp b/stream-servers/tests/SampleApplication.cpp
new file mode 100644
index 0000000..64c8db9
--- /dev/null
+++ b/stream-servers/tests/SampleApplication.cpp
@@ -0,0 +1,556 @@
+// 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 "SampleApplication.h"
+
+#include "base/GLObjectCounter.h"
+#include "base/ConditionVariable.h"
+#include "base/Lock.h"
+#include "base/System.h"
+#include "base/testing/TestSystem.h"
+#include "base/FunctorThread.h"
+#include "host-common/AndroidAgentFactory.h"
+#include "host-common/multi_display_agent.h"
+#include "host-common/MultiDisplay.h"
+#include "Standalone.h"
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES3/gl3.h>
+
+using android::base::AutoLock;
+using android::base::ConditionVariable;
+using android::base::FunctorThread;
+using android::base::Lock;
+using android::base::MessageChannel;
+using android::base::TestSystem;
+
+namespace emugl {
+
+// Class holding the persistent test window.
+class TestWindow {
+public:
+ TestWindow() {
+ window = CreateOSWindow();
+ }
+
+ ~TestWindow() {
+ if (window) {
+ window->destroy();
+ }
+ }
+
+ void setRect(int xoffset, int yoffset, int width, int height) {
+ if (mFirstResize) {
+ initializeWithRect(xoffset, yoffset, width, height);
+ } else {
+ resizeWithRect(xoffset, yoffset, width, height);
+ }
+ }
+
+ // Check on initialization if windows are available.
+ bool initializeWithRect(int xoffset, int yoffset, int width, int height) {
+ if (!window->initialize("libOpenglRender test", width, height)) {
+ window->destroy();
+ window = nullptr;
+ return false;
+ }
+ window->setVisible(true);
+ window->setPosition(xoffset, yoffset);
+ window->messageLoop();
+ mFirstResize = false;
+ return true;
+ }
+
+ void resizeWithRect(int xoffset, int yoffset, int width, int height) {
+ if (!window) return;
+
+ window->setPosition(xoffset, yoffset);
+ window->resize(width, height);
+ window->messageLoop();
+ }
+
+ OSWindow* window = nullptr;
+private:
+ bool mFirstResize = true;
+};
+
+static TestWindow* sTestWindow() {
+ static TestWindow* w = new TestWindow;
+ return w;
+}
+
+bool shouldUseHostGpu() {
+ bool useHost = android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_HOST_GPU") == "1";
+
+ // Also set the global emugl renderer accordingly.
+ if (useHost) {
+ emugl::setRenderer(SELECTED_RENDERER_HOST);
+ } else {
+ emugl::setRenderer(SELECTED_RENDERER_SWIFTSHADER_INDIRECT);
+ }
+
+ return useHost;
+}
+
+bool shouldUseWindow() {
+ bool useWindow = android::base::getEnvironmentVariable("ANDROID_EMU_TEST_WITH_WINDOW") == "1";
+ return useWindow;
+}
+
+OSWindow* createOrGetTestWindow(int xoffset, int yoffset, int width, int height) {
+ if (!shouldUseWindow()) return nullptr;
+
+ sTestWindow()->setRect(xoffset, yoffset, width, height);
+ return sTestWindow()->window;
+}
+
+class Vsync {
+public:
+ Vsync(int refreshRate = 60) :
+ mRefreshRate(refreshRate),
+ mRefreshIntervalUs(1000000ULL / mRefreshRate),
+ mThread([this] {
+ while (true) {
+ if (mShouldStop) return 0;
+ android::base::sleepUs(mRefreshIntervalUs);
+ AutoLock lock(mLock);
+ mSync = 1;
+ mCv.signal();
+ }
+ return 0;
+ }) {
+ mThread.start();
+ }
+
+ ~Vsync() {
+ mShouldStop = true;
+ }
+
+ void waitUntilNextVsync() {
+ AutoLock lock(mLock);
+ mSync = 0;
+ while (!mSync) {
+ mCv.wait(&mLock);
+ }
+ }
+
+private:
+ int mShouldStop = false;
+ int mRefreshRate = 60;
+ uint64_t mRefreshIntervalUs;
+ volatile int mSync = 0;
+
+ Lock mLock;
+ ConditionVariable mCv;
+
+ FunctorThread mThread;
+};
+
+// app -> SF queue: separate storage, bindTexture blits
+// SF queue -> HWC: shared storage
+class ColorBufferQueue { // Note: we could have called this BufferQueue but there is another
+ // class of name BufferQueue that does something totally different
+
+public:
+ static constexpr int kCapacity = 3;
+ class Item {
+ public:
+ Item(unsigned int cb = 0, FenceSync* s = nullptr) : colorBuffer(cb), sync(s) { }
+ unsigned int colorBuffer = 0;
+ FenceSync* sync = nullptr;
+ };
+
+ ColorBufferQueue() = default;
+
+ void queueBuffer(const Item& item) {
+ mQueue.send(item);
+ }
+
+ void dequeueBuffer(Item* outItem) {
+ mQueue.receive(outItem);
+ }
+
+private:
+ MessageChannel<Item, kCapacity> mQueue;
+};
+
+class AutoComposeDevice {
+public:
+ AutoComposeDevice(uint32_t targetCb, uint32_t layerCnt = 2) :
+ mData(sizeof(ComposeDevice) + layerCnt * sizeof(ComposeLayer))
+ {
+ mComposeDevice = reinterpret_cast<ComposeDevice*>(mData.data());
+ mComposeDevice->version = 1;
+ mComposeDevice->targetHandle = targetCb;
+ mComposeDevice->numLayers = layerCnt;
+ }
+
+ ComposeDevice* get() {
+ return mComposeDevice;
+ }
+
+ uint32_t getSize() {
+ return mData.size();
+ }
+
+ void configureLayer(uint32_t layerId, unsigned int cb,
+ hwc2_composition_t composeMode,
+ hwc_rect_t displayFrame,
+ hwc_frect_t crop,
+ hwc2_blend_mode_t blendMode,
+ float alpha,
+ hwc_color_t color
+ ) {
+ mComposeDevice->layer[layerId].cbHandle = cb;
+ mComposeDevice->layer[layerId].composeMode = composeMode;
+ mComposeDevice->layer[layerId].displayFrame = displayFrame;
+ mComposeDevice->layer[layerId].crop = crop;
+ mComposeDevice->layer[layerId].blendMode = blendMode;
+ mComposeDevice->layer[layerId].alpha = alpha;
+ mComposeDevice->layer[layerId].color = color;
+ mComposeDevice->layer[layerId].transform = HWC_TRANSFORM_FLIP_H;
+ }
+
+private:
+ std::vector<uint8_t> mData;
+ ComposeDevice* mComposeDevice;
+};
+
+extern "C" const QAndroidMultiDisplayAgent* const gMockQAndroidMultiDisplayAgent;
+
+// SampleApplication implementation/////////////////////////////////////////////
+SampleApplication::SampleApplication(int windowWidth, int windowHeight, int refreshRate, GLESApi glVersion, bool compose) :
+ mWidth(windowWidth), mHeight(windowHeight), mRefreshRate(refreshRate), mIsCompose(compose) {
+
+ // setupStandaloneLibrarySearchPaths();
+ emugl::setGLObjectCounter(android::base::GLObjectCounter::get());
+ emugl::set_emugl_window_operations(*getConsoleAgents()->emu);;
+ emugl::set_emugl_multi_display_operations(*getConsoleAgents()->multi_display);
+ LazyLoadedEGLDispatch::get();
+ if (glVersion == GLESApi_CM) LazyLoadedGLESv1Dispatch::get();
+ LazyLoadedGLESv2Dispatch::get();
+
+ bool useHostGpu = shouldUseHostGpu();
+ mWindow = createOrGetTestWindow(mXOffset, mYOffset, mWidth, mHeight);
+ mUseSubWindow = mWindow != nullptr;
+
+ FrameBuffer::initialize(
+ mWidth, mHeight,
+ mUseSubWindow,
+ !useHostGpu /* egl2egl */);
+ mFb.reset(FrameBuffer::getFB());
+
+ if (mUseSubWindow) {
+ mFb->setupSubWindow(
+ (FBNativeWindowType)(uintptr_t)
+ mWindow->getFramebufferNativeWindow(),
+ 0, 0,
+ mWidth, mHeight, mWidth, mHeight,
+ mWindow->getDevicePixelRatio(), 0, false, false);
+ mWindow->messageLoop();
+ }
+
+ mRenderThreadInfo.reset(new RenderThreadInfo());
+
+ mColorBuffer = mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ mContext = mFb->createRenderContext(0, 0, glVersion);
+ mSurface = mFb->createWindowSurface(0, mWidth, mHeight);
+
+ mFb->bindContext(mContext, mSurface, mSurface);
+ mFb->setWindowSurfaceColorBuffer(mSurface, mColorBuffer);
+
+ if (mIsCompose && mTargetCb == 0) {
+ mTargetCb = mFb->createColorBuffer(mFb->getWidth(),
+ mFb->getHeight(),
+ GL_RGBA,
+ FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ mFb->openColorBuffer(mTargetCb);
+ }
+ }
+
+SampleApplication::~SampleApplication() {
+ if (mFb) {
+ if (mTargetCb) {
+ mFb->closeColorBuffer(mTargetCb);
+ }
+ mFb->bindContext(0, 0, 0);
+ mFb->closeColorBuffer(mColorBuffer);
+ mFb->DestroyWindowSurface(mSurface);
+ mFb->finalize();
+ }
+}
+
+void SampleApplication::rebind() {
+ mFb->bindContext(mContext, mSurface, mSurface);
+}
+
+void SampleApplication::drawLoop() {
+ this->initialize();
+
+ Vsync vsync(mRefreshRate);
+
+ while (true) {
+ this->draw();
+ mFb->flushWindowSurfaceColorBuffer(mSurface);
+ vsync.waitUntilNextVsync();
+ if (mUseSubWindow) {
+ mFb->post(mColorBuffer);
+ mWindow->messageLoop();
+ }
+ }
+}
+
+FenceSync* SampleApplication::getFenceSync() {
+ auto gl = getGlDispatch();
+ FenceSync* sync = new FenceSync(false, false);
+ gl->glFlush();
+ return sync;
+}
+
+void SampleApplication::drawWorkerWithCompose(ColorBufferQueue& app2sfQueue,
+ ColorBufferQueue& sf2appQueue) {
+ ColorBufferQueue::Item appItem = {};
+ AutoComposeDevice autoComposeDevice(mTargetCb);
+ hwc_rect_t displayFrame = {0, mHeight/2, mWidth, mHeight};
+ hwc_frect_t crop = {0.0, 0.0, 0.0, 0.0};
+ hwc_color_t color = {200, 0, 0, 255};
+ autoComposeDevice.configureLayer(0, 0,
+ HWC2_COMPOSITION_SOLID_COLOR,
+ displayFrame,
+ crop,
+ HWC2_BLEND_MODE_NONE,
+ 1.0,
+ color);
+
+ while (true) {
+ app2sfQueue.dequeueBuffer(&appItem);
+ if (appItem.sync) { appItem.sync->wait(EGL_FOREVER_KHR); }
+
+ hwc_rect_t displayFrame = {0, 0, mWidth, mHeight/2};
+ hwc_frect_t crop = {0.0, 0.0, (float)mWidth, (float)mHeight};
+ hwc_color_t color = {0, 0, 0, 0};
+ autoComposeDevice.configureLayer(1,
+ appItem.colorBuffer,
+ HWC2_COMPOSITION_DEVICE,
+ displayFrame,
+ crop,
+ HWC2_BLEND_MODE_PREMULTIPLIED,
+ 0.8,
+ color);
+ mFb->compose(autoComposeDevice.getSize(), autoComposeDevice.get());
+
+ if (appItem.sync) { appItem.sync->decRef(); }
+ sf2appQueue.queueBuffer(ColorBufferQueue::Item(appItem.colorBuffer, getFenceSync()));
+ }
+}
+
+void SampleApplication::drawWorker(ColorBufferQueue& app2sfQueue,
+ ColorBufferQueue& sf2appQueue,
+ ColorBufferQueue& sf2hwcQueue,
+ ColorBufferQueue& hwc2sfQueue) {
+ RenderThreadInfo* tInfo = new RenderThreadInfo;
+ unsigned int sfContext = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ unsigned int sfSurface = mFb->createWindowSurface(0, mWidth, mHeight);
+ mFb->bindContext(sfContext, sfSurface, sfSurface);
+
+ auto gl = getGlDispatch();
+
+ static constexpr char blitVshaderSrc[] = R"(#version 300 es
+ precision highp float;
+ layout (location = 0) in vec2 pos;
+ layout (location = 1) in vec2 texcoord;
+ out vec2 texcoord_varying;
+ void main() {
+ gl_Position = vec4(pos, 0.0, 1.0);
+ texcoord_varying = texcoord;
+ })";
+
+ static constexpr char blitFshaderSrc[] = R"(#version 300 es
+ precision highp float;
+ uniform sampler2D tex;
+ in vec2 texcoord_varying;
+ out vec4 fragColor;
+ void main() {
+ fragColor = texture(tex, texcoord_varying);
+ })";
+
+ GLint blitProgram =
+ compileAndLinkShaderProgram(
+ blitVshaderSrc, blitFshaderSrc);
+
+ GLint samplerLoc = gl->glGetUniformLocation(blitProgram, "tex");
+
+ GLuint blitVbo;
+ gl->glGenBuffers(1, &blitVbo);
+ gl->glBindBuffer(GL_ARRAY_BUFFER, blitVbo);
+ const float attrs[] = {
+ -1.0f, -1.0f, 0.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 0.0f,
+ -1.0f, 1.0f, 0.0f, 0.0f,
+ };
+ gl->glBufferData(GL_ARRAY_BUFFER, sizeof(attrs), attrs, GL_STATIC_DRAW);
+ gl->glEnableVertexAttribArray(0);
+ gl->glEnableVertexAttribArray(1);
+
+ gl->glVertexAttribPointer(
+ 0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
+ gl->glVertexAttribPointer(
+ 1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat),
+ (GLvoid*)(uintptr_t)(2 * sizeof(GLfloat)));
+
+ GLuint blitTexture;
+ gl->glActiveTexture(GL_TEXTURE0);
+ gl->glGenTextures(1, &blitTexture);
+ gl->glBindTexture(GL_TEXTURE_2D, blitTexture);
+
+ gl->glUseProgram(blitProgram);
+ gl->glUniform1i(samplerLoc, 0);
+
+ ColorBufferQueue::Item appItem = {};
+ ColorBufferQueue::Item hwcItem = {};
+
+ while (true) {
+ hwc2sfQueue.dequeueBuffer(&hwcItem);
+ if (hwcItem.sync) { hwcItem.sync->wait(EGL_FOREVER_KHR); }
+
+ mFb->setWindowSurfaceColorBuffer(sfSurface, hwcItem.colorBuffer);
+
+ {
+ app2sfQueue.dequeueBuffer(&appItem);
+
+ mFb->bindColorBufferToTexture(appItem.colorBuffer);
+
+ gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ if (appItem.sync) { appItem.sync->wait(EGL_FOREVER_KHR); }
+
+ gl->glDrawArrays(GL_TRIANGLES, 0, 6);
+
+ if (appItem.sync) { appItem.sync->decRef(); }
+ sf2appQueue.queueBuffer(ColorBufferQueue::Item(appItem.colorBuffer, getFenceSync()));
+ }
+
+ mFb->flushWindowSurfaceColorBuffer(sfSurface);
+
+ if (hwcItem.sync) { hwcItem.sync->decRef(); }
+ sf2hwcQueue.queueBuffer(ColorBufferQueue::Item(hwcItem.colorBuffer, getFenceSync()));
+ }
+ delete tInfo;
+}
+
+void SampleApplication::surfaceFlingerComposerLoop() {
+ ColorBufferQueue app2sfQueue;
+ ColorBufferQueue sf2appQueue;
+ ColorBufferQueue sf2hwcQueue;
+ ColorBufferQueue hwc2sfQueue;
+
+ std::vector<unsigned int> sfColorBuffers;
+ std::vector<unsigned int> hwcColorBuffers;
+
+ for (int i = 0; i < ColorBufferQueue::kCapacity; i++) {
+ sfColorBuffers.push_back(mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE));
+ hwcColorBuffers.push_back(mFb->createColorBuffer(mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE));
+ }
+
+ for (int i = 0; i < ColorBufferQueue::kCapacity; i++) {
+ mFb->openColorBuffer(sfColorBuffers[i]);
+ mFb->openColorBuffer(hwcColorBuffers[i]);
+ }
+
+ // prime the queue
+ for (int i = 0; i < ColorBufferQueue::kCapacity; i++) {
+ sf2appQueue.queueBuffer(ColorBufferQueue::Item(sfColorBuffers[i], nullptr));
+ hwc2sfQueue.queueBuffer(ColorBufferQueue::Item(hwcColorBuffers[i], nullptr));
+ }
+
+ FunctorThread appThread([&]() {
+ RenderThreadInfo* tInfo = new RenderThreadInfo;
+ unsigned int appContext = mFb->createRenderContext(0, 0, GLESApi_3_0);
+ unsigned int appSurface = mFb->createWindowSurface(0, mWidth, mHeight);
+ mFb->bindContext(appContext, appSurface, appSurface);
+
+ ColorBufferQueue::Item sfItem = {};
+
+ sf2appQueue.dequeueBuffer(&sfItem);
+ mFb->setWindowSurfaceColorBuffer(appSurface, sfItem.colorBuffer);
+ if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); }
+
+ this->initialize();
+
+ while (true) {
+ this->draw();
+ mFb->flushWindowSurfaceColorBuffer(appSurface);
+ app2sfQueue.queueBuffer(ColorBufferQueue::Item(sfItem.colorBuffer, getFenceSync()));
+
+ sf2appQueue.dequeueBuffer(&sfItem);
+ mFb->setWindowSurfaceColorBuffer(appSurface, sfItem.colorBuffer);
+ if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); }
+ }
+
+ delete tInfo;
+ });
+
+ FunctorThread sfThread([&]() {
+ if (mIsCompose) {
+ drawWorkerWithCompose(app2sfQueue, sf2appQueue);
+ }
+ else {
+ drawWorker(app2sfQueue, sf2appQueue, sf2hwcQueue, hwc2sfQueue);
+ }
+ });
+
+ sfThread.start();
+ appThread.start();
+
+ Vsync vsync(mRefreshRate);
+ ColorBufferQueue::Item sfItem = {};
+ if (!mIsCompose) {
+ while (true) {
+ sf2hwcQueue.dequeueBuffer(&sfItem);
+ if (sfItem.sync) { sfItem.sync->wait(EGL_FOREVER_KHR); sfItem.sync->decRef(); }
+ vsync.waitUntilNextVsync();
+ mFb->post(sfItem.colorBuffer);
+ if (mUseSubWindow) {
+ mWindow->messageLoop();
+ }
+ hwc2sfQueue.queueBuffer(ColorBufferQueue::Item(sfItem.colorBuffer, getFenceSync()));
+ }
+ }
+
+ appThread.wait();
+ sfThread.wait();
+}
+
+void SampleApplication::drawOnce() {
+ this->initialize();
+ this->draw();
+ mFb->flushWindowSurfaceColorBuffer(mSurface);
+ if (mUseSubWindow) {
+ mFb->post(mColorBuffer);
+ mWindow->messageLoop();
+ }
+}
+
+const GLESv2Dispatch* SampleApplication::getGlDispatch() {
+ return LazyLoadedGLESv2Dispatch::get();
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/SampleApplication.h b/stream-servers/tests/SampleApplication.h
new file mode 100644
index 0000000..2fd8d6a
--- /dev/null
+++ b/stream-servers/tests/SampleApplication.h
@@ -0,0 +1,112 @@
+// 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.
+
+#pragma once
+
+#include "OpenGLESDispatch/GLESv2Dispatch.h"
+#include "RenderContext.h"
+#include "base/Compiler.h"
+#include "FenceSync.h"
+#include "Hwc2.h"
+
+#include <cinttypes>
+#include <functional>
+#include <memory>
+
+class FrameBuffer;
+class OSWindow;
+class RenderThreadInfo;
+
+namespace emugl {
+
+// Determines whether the host GPU should be used.
+bool shouldUseHostGpu();
+
+// Creates or adjusts a persistent test window.
+// On some systems, test window creation can fail (such as when on a headless server).
+// In that case, this function will return nullptr.
+OSWindow* createOrGetTestWindow(int xoffset, int yoffset, int width, int height);
+
+
+class ColorBufferQueue;
+// Creates a window (or runs headless) to be used in a sample app.
+class SampleApplication {
+public:
+ SampleApplication(int windowWidth = 256, int windowHeight = 256,
+ int refreshRate = 60, GLESApi glVersion = GLESApi_3_0,
+ bool compose = false);
+ ~SampleApplication();
+
+ // A basic draw loop that works similar to most simple
+ // GL apps that run on desktop.
+ //
+ // Per frame:
+ //
+ // a single GL context for drawing,
+ // a color buffer to blit,
+ // and a call to post that color buffer.
+ void rebind();
+ void drawLoop();
+
+ // A more complex loop that uses 3 separate contexts
+ // to simulate what goes on in Android:
+ //
+ // Per frame
+ //
+ // a GL 'app' context for drawing,
+ // a SurfaceFlinger context for rendering the "Layer",
+ // and a HWC context for posting.
+ void surfaceFlingerComposerLoop();
+
+ // TODO:
+ // void HWC2Loop();
+
+ // Just initialize, draw, and swap buffers once.
+ void drawOnce();
+
+private:
+ void drawWorkerWithCompose(ColorBufferQueue& app2sfQueue, ColorBufferQueue& sf2appQueue);
+ void drawWorker(ColorBufferQueue& app2sfQueue, ColorBufferQueue& sf2appQueue,
+ ColorBufferQueue& sf2hwcQueue, ColorBufferQueue& hwc2sfQueue);
+ FenceSync* getFenceSync();
+
+protected:
+ virtual void initialize() = 0;
+ virtual void draw() = 0;
+
+ virtual const GLESv2Dispatch* getGlDispatch();
+
+ int mWidth = 256;
+ int mHeight = 256;
+ int mRefreshRate = 60;
+
+ bool mUseSubWindow = false;
+ OSWindow* mWindow = nullptr;
+ std::unique_ptr<FrameBuffer> mFb = {};
+ std::unique_ptr<RenderThreadInfo> mRenderThreadInfo = {};
+
+ int mXOffset= 400;
+ int mYOffset= 400;
+
+ unsigned int mColorBuffer = 0;
+ unsigned int mSurface = 0;
+ unsigned int mContext = 0;
+
+ bool mIsCompose = false;
+ uint32_t mTargetCb = 0;
+
+ DISALLOW_COPY_ASSIGN_AND_MOVE(SampleApplication);
+};
+
+} // namespace emugl
diff --git a/stream-servers/tests/ShaderUtils.cpp b/stream-servers/tests/ShaderUtils.cpp
new file mode 100644
index 0000000..a320fb9
--- /dev/null
+++ b/stream-servers/tests/ShaderUtils.cpp
@@ -0,0 +1,181 @@
+// 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 "ShaderUtils.h"
+
+#include "base/PathUtils.h"
+#include "base/Optional.h"
+#include "base/System.h"
+
+#include "OpenGLESDispatch/OpenGLDispatchLoader.h"
+
+#include <fstream>
+#include <vector>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+using android::base::Optional;
+using android::base::pj;
+
+#define DEBUG 0
+
+#if DEBUG
+#define D(fmt,...) fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);
+#else
+#define D(fmt,...)
+#endif
+
+#define E(fmt,...) fprintf(stderr, "%s: " fmt "\n", __func__, ##__VA_ARGS__);
+
+namespace emugl {
+
+GLuint compileShader(GLenum shaderType, const char* src) {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ GLuint shader = gl->glCreateShader(shaderType);
+ gl->glShaderSource(shader, 1, (const GLchar* const*)&src, nullptr);
+ gl->glCompileShader(shader);
+
+ GLint compileStatus;
+ gl->glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
+
+ if (compileStatus != GL_TRUE) {
+ GLsizei infoLogLength = 0;
+ gl->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
+ std::vector<char> infoLog(infoLogLength + 1, 0);
+ gl->glGetShaderInfoLog(shader, infoLogLength, nullptr, &infoLog[0]);
+ E("fail to compile. infolog: %s", &infoLog[0]);
+ }
+
+ return shader;
+}
+
+GLint compileAndLinkShaderProgram(const char* vshaderSrc, const char* fshaderSrc) {
+ auto gl = LazyLoadedGLESv2Dispatch::get();
+
+ GLuint vshader = compileShader(GL_VERTEX_SHADER, vshaderSrc);
+ GLuint fshader = compileShader(GL_FRAGMENT_SHADER, fshaderSrc);
+
+ GLuint program = gl->glCreateProgram();
+ gl->glAttachShader(program, vshader);
+ gl->glAttachShader(program, fshader);
+ gl->glLinkProgram(program);
+
+ GLint linkStatus;
+ gl->glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+
+ gl->glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
+
+ if (linkStatus != GL_TRUE) {
+ GLsizei infoLogLength = 0;
+ gl->glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
+ std::vector<char> infoLog(infoLogLength + 1, 0);
+ gl->glGetProgramInfoLog(program, infoLogLength, nullptr, &infoLog[0]);
+
+ E("failed to link. infolog: %s", &infoLog[0]);
+ }
+
+ return program;
+}
+
+// static Optional<std::string> getSpirvCompilerPath() {
+// #ifdef _WIN32
+// std::string programName = "glslangValidator.exe";
+// #else
+// std::string programName = "glslangValidator";
+// #endif
+//
+// auto programDirRelativePath =
+// pj(android::base::getProgramDirectory(),
+// "lib64", "vulkan", "tools", programName);
+//
+// if (path_exists(programDirRelativePath.c_str())) {
+// return programDirRelativePath;
+// }
+//
+// auto launcherDirRelativePath =
+// pj(android::base::getLauncherDirectory(),
+// "lib64", "vulkan", programName);
+//
+// if (path_exists(launcherDirRelativePath.c_str())) {
+// return launcherDirRelativePath;
+// }
+//
+// E("spirv compiler does not exist");
+// return {};
+// }
+
+// Optional<std::string> compileSpirvFromGLSL(const std::string& shaderType,
+// const std::string& src) {
+// auto spvCompilerPath = getSpirvCompilerPath();
+//
+// if (!spvCompilerPath) return {};
+//
+// const auto glslFile = android::base::makeCustomScopedPtr(
+// tempfile_create(), tempfile_unref_and_close_file);
+//
+// const auto spvFile = android::base::makeCustomScopedPtr(
+// tempfile_create(), tempfile_unref_and_close_file);
+//
+// auto glslPath = tempfile_path(glslFile.get());
+// auto spvPath = tempfile_path(spvFile.get());
+//
+// auto glslFd = android::base::ScopedFd(open(glslPath, O_RDWR));
+// if (!glslFd.valid()) { return {}; }
+//
+// android::writeStringToFile(glslFd.get(), src);
+// glslFd.close();
+//
+// std::vector<std::string> args =
+// { *spvCompilerPath, glslPath, "-V", "-S", shaderType, "-o", spvPath };
+//
+// auto runRes = System::get()->runCommandWithResult(args);
+//
+// if (!runRes) {
+// E("failed to compile SPIRV from GLSL. args: %s %s -V -S %s -o %s",
+// spvCompilerPath->c_str(), glslPath, shaderType.c_str(), spvPath);
+// return {};
+// }
+//
+// D("Result of compiling SPIRV from GLSL. res: %s args: %s %s -V -S %s -o %s",
+// runRes->c_str(), spvCompilerPath->c_str(), glslPath, shaderType.c_str(),
+// spvPath);
+//
+// auto res = android::readFileIntoString(spvPath);
+//
+// if (res) {
+// D("got %zu bytes:", res->size());
+// } else {
+// E("failed to read SPIRV file %s into string", spvPath);
+// }
+//
+// return res;
+// }
+//
+// Optional<std::vector<char> > readSpirv(const char* path) {
+// std::ifstream in(path, std::ios::ate | std::ios::binary);
+//
+// if (!in) return {};
+//
+// size_t fileSize = (size_t)in.tellg();
+// std::vector<char> buffer(fileSize);
+//
+// in.seekg(0);
+// in.read(buffer.data(), fileSize);
+//
+// return buffer;
+// }
+
+} // namespace emugl
diff --git a/stream-servers/tests/ShaderUtils.h b/stream-servers/tests/ShaderUtils.h
new file mode 100644
index 0000000..892718b
--- /dev/null
+++ b/stream-servers/tests/ShaderUtils.h
@@ -0,0 +1,34 @@
+// 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.
+#pragma once
+
+#include <GLES2/gl2.h>
+
+#include "base/Optional.h"
+
+#include <string>
+#include <vector>
+
+namespace emugl {
+
+GLuint compileShader(GLenum shaderType, const char* src);
+GLint compileAndLinkShaderProgram(const char* vshaderSrc, const char* fshaderSrc);
+
+android::base::Optional<std::string> compileSpirvFromGLSL(
+ const std::string& shaderType,
+ const std::string& src);
+
+android::base::Optional<std::vector<char>> readSpirv(const char* path);
+
+} // namespace emugl
diff --git a/stream-servers/tests/StalePtrRegistry_unittest.cpp b/stream-servers/tests/StalePtrRegistry_unittest.cpp
new file mode 100644
index 0000000..8ec9dfb
--- /dev/null
+++ b/stream-servers/tests/StalePtrRegistry_unittest.cpp
@@ -0,0 +1,201 @@
+// Copyright (C) 2017 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 "StalePtrRegistry.h"
+
+#include <gtest/gtest.h>
+
+TEST(StalePtrRegistry, Constructor) {
+ StalePtrRegistry<void> reg;
+}
+
+TEST(StalePtrRegistry, Add) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ reg.addPtr(nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+}
+
+TEST(StalePtrRegistry, AddRemove) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ reg.addPtr(nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ reg.removePtr(nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+}
+
+TEST(StalePtrRegistry, AddMakeStale) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ reg.addPtr(nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.numStaleEntries(), 0);
+ reg.makeCurrentPtrsStale();
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+}
+
+TEST(StalePtrRegistry, Get) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ reg.addPtr(nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.getPtr(0), nullptr);
+}
+
+TEST(StalePtrRegistry, GetDefaultVal) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.getPtr(0), nullptr);
+ EXPECT_EQ(reg.getPtr(0, (void*)0x1), (void*)0x1);
+}
+
+TEST(StalePtrRegistry, GetNonExisting) {
+ StalePtrRegistry<void> reg;
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.getPtr(0), nullptr);
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleGetStale) {
+ StalePtrRegistry<void> reg;
+ void* ptr = (void*)0xabcdef;
+ uint64_t handle = (uint64_t)(uintptr_t)ptr;
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+
+ reg.addPtr(ptr);
+
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.numStaleEntries(), 0);
+
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+
+ EXPECT_EQ(reg.getPtr(handle), ptr);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleGetStaleWithDelete) {
+ StalePtrRegistry<void> reg;
+ void* ptr = (void*)0xabcdef;
+ uint64_t handle = (uint64_t)(uintptr_t)ptr;
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+
+ reg.addPtr(ptr);
+
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.numStaleEntries(), 0);
+
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+
+ EXPECT_EQ(reg.getPtr(handle, nullptr, true), ptr);
+ EXPECT_EQ(reg.getPtr(handle, nullptr, true), nullptr);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleWithRemap) {
+ StalePtrRegistry<void> reg;
+ void* ptr = (void*)0xabcdef;
+ void* remapped = (void*)0xbcdefa;
+ uint64_t handle = (uint64_t)(uintptr_t)ptr;
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+
+ reg.addPtr(ptr);
+
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.numStaleEntries(), 0);
+
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+
+ reg.remapStalePtr(handle, remapped);
+
+ EXPECT_EQ(reg.getPtr(handle), remapped);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleWithRemapNameCollision) {
+ StalePtrRegistry<void> reg;
+ void* ptr = (void*)0xabcdef;
+ void* remapped = (void*)0xbcdefa;
+ uint64_t handle = (uint64_t)(uintptr_t)ptr;
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+
+ reg.addPtr(ptr);
+
+ EXPECT_EQ(reg.numCurrEntries(), 1);
+ EXPECT_EQ(reg.numStaleEntries(), 0);
+
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+
+ reg.remapStalePtr(handle, remapped);
+
+ EXPECT_EQ(reg.getPtr(handle), remapped);
+
+ reg.addPtr(ptr);
+
+ EXPECT_EQ(reg.getPtr(handle), ptr);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleTwice) {
+ StalePtrRegistry<void> reg;
+ void* ptr1 = (void*)0xabcdef;
+ uint64_t handle1 = (uint64_t)(uintptr_t)ptr1;
+ void* ptr2 = (void*)0xbcdefa;
+ uint64_t handle2 = (uint64_t)(uintptr_t)ptr2;
+
+ reg.addPtr(ptr1);
+ reg.makeCurrentPtrsStale();
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+ reg.addPtr(ptr2);
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 2);
+
+ EXPECT_EQ(reg.getPtr(handle1), ptr1);
+ EXPECT_EQ(reg.getPtr(handle2), ptr2);
+}
+
+TEST(StalePtrRegistry, AddMakeStaleTwiceWithCollision) {
+ StalePtrRegistry<void> reg;
+ void* ptr1 = (void*)0xabcdef;
+ uint64_t handle1 = (uint64_t)(uintptr_t)ptr1;
+ void* ptr2 = (void*)0xabcdef;
+ uint64_t handle2 = (uint64_t)(uintptr_t)ptr2;
+
+ reg.addPtr(ptr1);
+ reg.makeCurrentPtrsStale();
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+ reg.addPtr(ptr2);
+ reg.makeCurrentPtrsStale();
+
+ EXPECT_EQ(reg.numCurrEntries(), 0);
+ EXPECT_EQ(reg.numStaleEntries(), 1);
+
+ EXPECT_EQ(reg.getPtr(handle1), ptr1);
+ EXPECT_EQ(reg.getPtr(handle2), ptr2);
+}
diff --git a/stream-servers/tests/Standalone.h b/stream-servers/tests/Standalone.h
new file mode 100644
index 0000000..039d510
--- /dev/null
+++ b/stream-servers/tests/Standalone.h
@@ -0,0 +1,23 @@
+// 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.
+#pragma once
+
+#include "FrameBuffer.h"
+#include "OSWindow.h"
+#include "RenderThreadInfo.h"
+#include "ShaderUtils.h"
+#include "SampleApplication.h"
+
+#include "host-common/misc.h"
+#include "OpenGLESDispatch/OpenGLDispatchLoader.h"
diff --git a/stream-servers/tests/TextureDraw_unittest.cpp b/stream-servers/tests/TextureDraw_unittest.cpp
new file mode 100644
index 0000000..65a2b93
--- /dev/null
+++ b/stream-servers/tests/TextureDraw_unittest.cpp
@@ -0,0 +1,220 @@
+// 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 <gtest/gtest.h>
+
+#include "GLTestUtils.h"
+#include "OpenGLTestContext.h"
+#include "TextureDraw.h"
+
+namespace emugl {
+
+namespace {
+
+void TestTextureDrawBasic(const GLESv2Dispatch* gl, GLenum internalformat,
+ GLenum format, bool should_work) {
+ GLint viewport[4] = {};
+ gl->glGetIntegerv(GL_VIEWPORT, viewport);
+ EXPECT_EQ(0, viewport[0]);
+ EXPECT_EQ(0, viewport[1]);
+ const int width = viewport[2];
+ const int height = viewport[3];
+ const GLenum type = GL_UNSIGNED_BYTE;
+ const int bpp = 4;
+ const int bytes = width * height * bpp;
+
+ GLuint textureToDraw;
+
+ gl->glGenTextures(1, &textureToDraw);
+ gl->glActiveTexture(GL_TEXTURE0);
+ gl->glBindTexture(GL_TEXTURE_2D, textureToDraw);
+
+ gl->glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ gl->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ std::vector<unsigned char> pixels(bytes);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ pixels[i * width * bpp + j * bpp + 0] = (0xaa + i) % 0x100;
+ pixels[i * width * bpp + j * bpp + 1] = (0x00 + j) % 0x100;
+ pixels[i * width * bpp + j * bpp + 2] = (0x11 + i) % 0x100;
+ pixels[i * width * bpp + j * bpp + 3] = (0xff + j) % 0x100;
+ }
+ }
+ GLenum err = gl->glGetError();
+ EXPECT_EQ(GL_NO_ERROR, err);
+ gl->glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0,
+ format, type, pixels.data());
+ err = gl->glGetError();
+ if (should_work) {
+ EXPECT_EQ(GL_NO_ERROR, err);
+ } else {
+ EXPECT_NE(GL_NO_ERROR, err);
+ return;
+ }
+ gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ GLint fbStatus = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ EXPECT_EQ((GLint)GL_FRAMEBUFFER_COMPLETE, fbStatus);
+
+ TextureDraw textureDraw;
+
+ textureDraw.draw(textureToDraw, 0, 0, 0);
+
+ std::vector<unsigned char> pixelsOut(bytes, 0xff);
+
+ gl->glReadPixels(0, 0, width, height, format, type, pixelsOut.data());
+
+ // Check that the texture is drawn upside down (because that's what SurfaceFlinger wants)
+ for (int i = 0; i < height; i++) {
+ size_t rowBytes = width * bpp;
+ EXPECT_TRUE(RowMatches(i, width * bpp,
+ pixels.data() + i * rowBytes,
+ pixelsOut.data() + (height - i - 1) * rowBytes));
+ }
+}
+
+void TestTextureDrawLayer(const GLESv2Dispatch* gl) {
+ GLint viewport[4] = {};
+ gl->glGetIntegerv(GL_VIEWPORT, viewport);
+ EXPECT_EQ(0, viewport[0]);
+ EXPECT_EQ(0, viewport[1]);
+ const int width = viewport[2];
+ const int height = viewport[3];
+ const GLenum type = GL_UNSIGNED_BYTE;
+ const int bpp = 4;
+ const int bytes = width * height * bpp;
+
+ GLuint textureToDraw;
+
+ gl->glGenTextures(1, &textureToDraw);
+ gl->glActiveTexture(GL_TEXTURE0);
+ gl->glBindTexture(GL_TEXTURE_2D, textureToDraw);
+
+ gl->glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ gl->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ std::vector<unsigned char> pixels(bytes);
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ pixels[i * width * bpp + j * bpp + 0] = 0xff;
+ pixels[i * width * bpp + j * bpp + 1] = 0x0;
+ pixels[i * width * bpp + j * bpp + 2] = 0x0;
+ pixels[i * width * bpp + j * bpp + 3] = 0xff;
+ }
+ }
+ GLenum err = gl->glGetError();
+ EXPECT_EQ(GL_NO_ERROR, err);
+ gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, type, pixels.data());
+ err = gl->glGetError();
+ EXPECT_EQ(GL_NO_ERROR, err);
+ gl->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ GLint fbStatus = gl->glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ EXPECT_EQ((GLint)GL_FRAMEBUFFER_COMPLETE, fbStatus);
+
+ TextureDraw textureDraw;
+
+ // Test HWC2_COMPOSITION_SOLID_COLOR mode, red color
+ ComposeLayer l = {0,
+ HWC2_COMPOSITION_SOLID_COLOR,
+ {0, 0, width, height},
+ {0.0, 0.0, (float)width, (float)height},
+ HWC2_BLEND_MODE_NONE,
+ 1.0,
+ {255, 0, 0, 255},
+ (hwc_transform_t)0};
+ textureDraw.prepareForDrawLayer();
+ textureDraw.drawLayer(&l, width, height, width, height, textureToDraw);
+ std::vector<unsigned char> pixelsOut(bytes, 0xff);
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, type, pixelsOut.data());
+ EXPECT_TRUE(ImageMatches(width, height, bpp, width,
+ pixels.data(), pixelsOut.data()));
+
+
+ // Test HWC2_COMPOSITION_DEVICE mode, blue texture
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ pixels[i * width * bpp + j * bpp + 0] = 0x0;
+ pixels[i * width * bpp + j * bpp + 1] = 0x0;
+ pixels[i * width * bpp + j * bpp + 2] = 0xff;
+ pixels[i * width * bpp + j * bpp + 3] = 0xff;
+ }
+ }
+ err = gl->glGetError();
+ EXPECT_EQ(GL_NO_ERROR, err);
+ gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, type, pixels.data());
+ l.composeMode = HWC2_COMPOSITION_DEVICE;
+ textureDraw.drawLayer(&l, width, height, width, height, textureToDraw);
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, type, pixelsOut.data());
+ EXPECT_TRUE(ImageMatches(width, height, bpp, width,
+ pixels.data(), pixelsOut.data()));
+
+
+ // Test composing 2 layers, layer1 draws blue to the upper half frame;
+ // layer2 draws the bottom half of the texture to the bottom half frame
+ ComposeLayer l1 = {0,
+ HWC2_COMPOSITION_SOLID_COLOR,
+ {0, 0, width, height/2},
+ {0.0, 0.0, (float)width, (float)height/2},
+ HWC2_BLEND_MODE_NONE,
+ 1.0,
+ {0, 0, 255, 255},
+ (hwc_transform_t)0};
+ ComposeLayer l2 = {0,
+ HWC2_COMPOSITION_DEVICE,
+ {0, height/2, width, height},
+ {0.0, (float)height/2, (float)width, (float)height},
+ HWC2_BLEND_MODE_NONE,
+ 1.0,
+ {0, 0, 0, 0},
+ (hwc_transform_t)0};
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ // texture bottom half green
+ if (i >= height/2) {
+ pixels[i * width * bpp + j * bpp + 0] = 0x0;
+ pixels[i * width * bpp + j * bpp + 2] = 0xff;
+ }
+ }
+ }
+ gl->glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, type, pixels.data());
+ textureDraw.drawLayer(&l1, width, height, width, height, textureToDraw);
+ textureDraw.drawLayer(&l2, width, height, width, height, textureToDraw);
+ gl->glReadPixels(0, 0, width, height, GL_RGBA, type, pixelsOut.data());
+ EXPECT_TRUE(ImageMatches(width, height, bpp, width,
+ pixels.data(), pixelsOut.data()));
+
+}
+
+} // namespace
+
+#define GL_BGRA_EXT 0x80E1
+
+TEST_F(GLTest, TextureDrawBasic) {
+ TestTextureDrawBasic(gl, GL_RGBA, GL_RGBA, true);
+ // Assumes BGRA is supported
+ TestTextureDrawBasic(gl, GL_BGRA_EXT, GL_BGRA_EXT, true);
+ TestTextureDrawBasic(gl, GL_RGBA, GL_BGRA_EXT, false);
+ TestTextureDrawLayer(gl);
+}
+
+} // namespace emugl
diff --git a/stream-servers/tests/Vulkan_unittest.cpp b/stream-servers/tests/Vulkan_unittest.cpp
new file mode 100644
index 0000000..fa84c6c
--- /dev/null
+++ b/stream-servers/tests/Vulkan_unittest.cpp
@@ -0,0 +1,607 @@
+// 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 <gtest/gtest.h>
+
+#include "FrameBuffer.h"
+#include "VkCommonOperations.h"
+#include "VulkanDispatch.h"
+#include "emugl/common/feature_control.h"
+
+#include "android/base/ArraySize.h"
+#include "android/base/GLObjectCounter.h"
+#include "android/base/files/PathUtils.h"
+#include "android/base/system/System.h"
+#include "android/base/testing/TestSystem.h"
+#include "android/emulation/control/AndroidAgentFactory.h"
+
+#include "Standalone.h"
+
+#include <sstream>
+#include <string>
+#include <vulkan/vulkan.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#include "android/base/system/Win32UnicodeString.h"
+using android::base::Win32UnicodeString;
+#else
+#include <dlfcn.h>
+#endif
+
+using android::base::arraySize;
+using android::base::pj;
+using android::base::TestSystem;
+
+namespace emugl {
+
+static std::string libDir() {
+ return
+ pj(TestSystem::getProgramDirectoryFromPlatform(),
+#ifdef _WIN32
+ // Windows uses mock Vulkan ICD.
+ "testlib64"
+#else
+ "lib64", "vulkan"
+#endif
+ );
+}
+
+static std::string testIcdFilename() {
+ return pj(libDir(),
+#ifdef _WIN32
+ // Windows uses mock Vulkan ICD.
+ "VkICD_mock_icd.json"
+#else
+ "vk_swiftshader_icd.json"
+#endif
+ );
+}
+
+#ifdef _WIN32
+#define SKIP_TEST_IF_WIN32() GTEST_SKIP()
+#else
+#define SKIP_TEST_IF_WIN32()
+#endif
+
+static void* dlOpenFuncForTesting() {
+#ifdef _WIN32
+ const Win32UnicodeString name(
+ pj(libDir(), "vulkan-1.dll"));
+ return LoadLibraryW(name.c_str());
+#else
+
+#ifdef __APPLE__
+ constexpr char suffix[] = ".dylib";
+#else
+ constexpr char suffix[] = ".so";
+#endif
+
+ std::string libName =
+ std::string("libvulkan") + suffix;
+
+ auto name = pj(libDir(), libName);
+ return dlopen(name.c_str(), RTLD_NOW);
+#endif
+}
+
+static void* dlSymFuncForTesting(void* lib, const char* sym) {
+#ifdef _WIN32
+ return (void*)GetProcAddress((HMODULE)lib, sym);
+#else
+ return dlsym(lib, sym);
+#endif
+}
+
+static std::string deviceTypeToString(VkPhysicalDeviceType type) {
+#define DO_ENUM_RETURN_STRING(e) \
+ case e: \
+ return #e; \
+
+ switch (type) {
+ DO_ENUM_RETURN_STRING(VK_PHYSICAL_DEVICE_TYPE_OTHER)
+ DO_ENUM_RETURN_STRING(VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU)
+ DO_ENUM_RETURN_STRING(VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
+ DO_ENUM_RETURN_STRING(VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU)
+ DO_ENUM_RETURN_STRING(VK_PHYSICAL_DEVICE_TYPE_CPU)
+ default:
+ return "Unknown";
+ }
+}
+
+static std::string queueFlagsToString(VkQueueFlags queueFlags) {
+ std::stringstream ss;
+
+ if (queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ ss << "VK_QUEUE_GRAPHICS_BIT | ";
+ }
+ if (queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ ss << "VK_QUEUE_COMPUTE_BIT | ";
+ }
+ if (queueFlags & VK_QUEUE_TRANSFER_BIT) {
+ ss << "VK_QUEUE_TRANSFER_BIT | ";
+ }
+ if (queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) {
+ ss << "VK_QUEUE_SPARSE_BINDING_BIT | ";
+ }
+ if (queueFlags & VK_QUEUE_PROTECTED_BIT) {
+ ss << "VK_QUEUE_PROTECTED_BIT";
+ }
+
+ return ss.str();
+}
+
+static std::string getPhysicalDevicePropertiesString(
+ const goldfish_vk::VulkanDispatch* vk,
+ VkPhysicalDevice physicalDevice,
+ const VkPhysicalDeviceProperties& props) {
+
+ std::stringstream ss;
+
+ uint16_t apiMaj = (uint16_t)(props.apiVersion >> 22);
+ uint16_t apiMin = (uint16_t)(0x000003ff & (props.apiVersion >> 12));
+ uint16_t apiPatch = (uint16_t)(0x000007ff & (props.apiVersion));
+
+ ss << "API version: " << apiMaj << "." << apiMin << "." << apiPatch << "\n";
+ ss << "Driver version: " << std::hex << props.driverVersion << "\n";
+ ss << "Vendor ID: " << std::hex << props.vendorID << "\n";
+ ss << "Device ID: " << std::hex << props.deviceID << "\n";
+ ss << "Device type: " << deviceTypeToString(props.deviceType) << "\n";
+ ss << "Device name: " << props.deviceName << "\n";
+
+ uint32_t deviceExtensionCount;
+ std::vector<VkExtensionProperties> deviceExtensionProperties;
+ vk->vkEnumerateDeviceExtensionProperties(
+ physicalDevice,
+ nullptr,
+ &deviceExtensionCount,
+ nullptr);
+
+ deviceExtensionProperties.resize(deviceExtensionCount);
+ vk->vkEnumerateDeviceExtensionProperties(
+ physicalDevice,
+ nullptr,
+ &deviceExtensionCount,
+ deviceExtensionProperties.data());
+
+ for (uint32_t i = 0; i < deviceExtensionCount; ++i) {
+ ss << "Device extension: " <<
+ deviceExtensionProperties[i].extensionName << "\n";
+ }
+
+ return ss.str();
+}
+
+static void testInstanceCreation(const VulkanDispatch* vk,
+ VkInstance* instance_out) {
+
+ EXPECT_TRUE(vk->vkEnumerateInstanceExtensionProperties);
+ EXPECT_TRUE(vk->vkCreateInstance);
+
+ uint32_t count;
+ vk->vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
+
+ fprintf(stderr, "%s: exts: %u\n", __func__, count);
+
+ std::vector<VkExtensionProperties> props(count);
+ vk->vkEnumerateInstanceExtensionProperties(nullptr, &count, props.data());
+
+ for (uint32_t i = 0; i < count; i++) {
+ fprintf(stderr, "%s: ext: %s\n", __func__, props[i].extensionName);
+ }
+
+ VkInstanceCreateInfo instanceCreateInfo = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 0, 0,
+ nullptr,
+ 0, nullptr,
+ 0, nullptr,
+ };
+
+ VkInstance instance;
+
+ EXPECT_EQ(VK_SUCCESS,
+ vk->vkCreateInstance(
+ &instanceCreateInfo, nullptr, &instance));
+
+ *instance_out = instance;
+}
+
+static void testDeviceCreation(const VulkanDispatch* vk,
+ VkInstance instance,
+ VkPhysicalDevice* physDevice_out,
+ VkDevice* device_out) {
+
+ fprintf(stderr, "%s: call\n", __func__);
+
+ EXPECT_TRUE(vk->vkEnumeratePhysicalDevices);
+ EXPECT_TRUE(vk->vkGetPhysicalDeviceProperties);
+ EXPECT_TRUE(vk->vkGetPhysicalDeviceQueueFamilyProperties);
+
+ uint32_t physicalDeviceCount;
+ std::vector<VkPhysicalDevice> physicalDevices;
+
+ EXPECT_EQ(VK_SUCCESS,
+ vk->vkEnumeratePhysicalDevices(
+ instance, &physicalDeviceCount, nullptr));
+
+ physicalDevices.resize(physicalDeviceCount);
+
+ EXPECT_EQ(VK_SUCCESS,
+ vk->vkEnumeratePhysicalDevices(
+ instance, &physicalDeviceCount, physicalDevices.data()));
+
+ std::vector<VkPhysicalDeviceProperties> physicalDeviceProps(physicalDeviceCount);
+
+ // at the end of the day, we need to pick a physical device.
+ // Pick one that has graphics + compute if possible, otherwise settle for a device
+ // that has at least one queue family capable of graphics.
+ // TODO: Pick the device that has present capability for that queue if
+ // we are not running in no-window mode.
+
+ bool bestPhysicalDeviceFound = false;
+ uint32_t bestPhysicalDeviceIndex = 0;
+
+ std::vector<uint32_t> physDevsWithBothGraphicsAndCompute;
+ std::vector<uint32_t> physDevsWithGraphicsOnly;
+
+ for (uint32_t i = 0; i < physicalDeviceCount; i++) {
+ uint32_t deviceExtensionCount;
+ std::vector<VkExtensionProperties> deviceExtensionProperties;
+ vk->vkEnumerateDeviceExtensionProperties(
+ physicalDevices[i],
+ nullptr,
+ &deviceExtensionCount,
+ nullptr);
+
+ deviceExtensionProperties.resize(deviceExtensionCount);
+ vk->vkEnumerateDeviceExtensionProperties(
+ physicalDevices[i],
+ nullptr,
+ &deviceExtensionCount,
+ deviceExtensionProperties.data());
+
+ bool hasSwapchainExtension = false;
+
+ fprintf(stderr, "%s: check swapchain ext\n", __func__);
+ for (uint32_t j = 0; j < deviceExtensionCount; j++) {
+ std::string ext = deviceExtensionProperties[j].extensionName;
+ if (ext == "VK_KHR_swapchain") {
+ hasSwapchainExtension = true;
+ }
+ }
+
+ if (!hasSwapchainExtension) continue;
+
+ vk->vkGetPhysicalDeviceProperties(
+ physicalDevices[i],
+ physicalDeviceProps.data() + i);
+
+ auto str = getPhysicalDevicePropertiesString(vk, physicalDevices[i], physicalDeviceProps[i]);
+ fprintf(stderr, "device %u: %s\n", i, str.c_str());
+
+ uint32_t queueFamilyCount;
+ vk->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyCount, nullptr);
+
+ std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+ vk->vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyCount, queueFamilies.data());
+
+ bool hasGraphicsQueue = false;
+ bool hasComputeQueue = false;
+
+ for (uint32_t j = 0; j < queueFamilyCount; j++) {
+ if (queueFamilies[j].queueCount > 0) {
+
+ auto flags = queueFamilies[j].queueFlags;
+ auto flagsAsString =
+ queueFlagsToString(flags);
+
+ fprintf(stderr, "%s: found %u @ family %u with caps: %s\n",
+ __func__,
+ queueFamilies[j].queueCount, j,
+ flagsAsString.c_str());
+
+ if ((flags & VK_QUEUE_GRAPHICS_BIT) &&
+ (flags & VK_QUEUE_COMPUTE_BIT)) {
+ hasGraphicsQueue = true;
+ hasComputeQueue = true;
+ bestPhysicalDeviceFound = true;
+ break;
+ }
+
+ if (flags & VK_QUEUE_GRAPHICS_BIT) {
+ hasGraphicsQueue = true;
+ bestPhysicalDeviceFound = true;
+ }
+
+ if (flags & VK_QUEUE_COMPUTE_BIT) {
+ hasComputeQueue = true;
+ bestPhysicalDeviceFound = true;
+ }
+ }
+ }
+
+ if (hasGraphicsQueue && hasComputeQueue) {
+ physDevsWithBothGraphicsAndCompute.push_back(i);
+ break;
+ }
+
+ if (hasGraphicsQueue) {
+ physDevsWithGraphicsOnly.push_back(i);
+ }
+ }
+
+ EXPECT_TRUE(bestPhysicalDeviceFound);
+
+ if (physDevsWithBothGraphicsAndCompute.size() > 0) {
+ bestPhysicalDeviceIndex = physDevsWithBothGraphicsAndCompute[0];
+ } else if (physDevsWithGraphicsOnly.size() > 0) {
+ bestPhysicalDeviceIndex = physDevsWithGraphicsOnly[0];
+ } else {
+ EXPECT_TRUE(false);
+ return;
+ }
+
+ // Now we got our device; select it
+ VkPhysicalDevice bestPhysicalDevice = physicalDevices[bestPhysicalDeviceIndex];
+
+ uint32_t queueFamilyCount;
+ vk->vkGetPhysicalDeviceQueueFamilyProperties(
+ bestPhysicalDevice, &queueFamilyCount, nullptr);
+
+ std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
+ vk->vkGetPhysicalDeviceQueueFamilyProperties(
+ bestPhysicalDevice, &queueFamilyCount, queueFamilies.data());
+
+ std::vector<uint32_t> wantedQueueFamilies;
+ std::vector<uint32_t> wantedQueueFamilyCounts;
+
+ for (uint32_t i = 0; i < queueFamilyCount; i++) {
+ if (queueFamilies[i].queueCount > 0) {
+ auto flags = queueFamilies[i].queueFlags;
+ if ((flags & VK_QUEUE_GRAPHICS_BIT) &&
+ (flags & VK_QUEUE_COMPUTE_BIT)) {
+ wantedQueueFamilies.push_back(i);
+ wantedQueueFamilyCounts.push_back(queueFamilies[i].queueCount);
+ break;
+ }
+
+ if ((flags & VK_QUEUE_GRAPHICS_BIT) ||
+ (flags & VK_QUEUE_COMPUTE_BIT)) {
+ wantedQueueFamilies.push_back(i);
+ wantedQueueFamilyCounts.push_back(queueFamilies[i].queueCount);
+ }
+ }
+ }
+
+ std::vector<VkDeviceQueueCreateInfo> queueCis;
+
+ for (uint32_t i = 0; i < wantedQueueFamilies.size(); ++i) {
+ auto familyIndex = wantedQueueFamilies[i];
+ auto queueCount = wantedQueueFamilyCounts[i];
+
+ std::vector<float> priorities;
+
+ for (uint32_t j = 0; j < queueCount; ++j) {
+ priorities.push_back(1.0f);
+ }
+
+ VkDeviceQueueCreateInfo dqci = {
+ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 0, 0,
+ familyIndex,
+ queueCount,
+ priorities.data(),
+ };
+
+ queueCis.push_back(dqci);
+ }
+
+ const char* exts[] = {
+ "VK_KHR_swapchain",
+ };
+
+ VkDeviceCreateInfo ci = {
+ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 0, 0,
+ (uint32_t)queueCis.size(),
+ queueCis.data(),
+ 0, nullptr,
+ arraySize(exts), exts,
+ nullptr,
+ };
+
+ VkDevice device;
+ EXPECT_EQ(VK_SUCCESS,
+ vk->vkCreateDevice(bestPhysicalDevice, &ci, nullptr, &device));
+
+ *physDevice_out = bestPhysicalDevice;
+ *device_out = device;
+}
+
+static void teardownVulkanTest(const VulkanDispatch* vk,
+ VkDevice dev,
+ VkInstance instance) {
+ vk->vkDestroyDevice(dev, nullptr);
+ vk->vkDestroyInstance(instance, nullptr);
+}
+
+class VulkanTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ TestSystem::setEnvironmentVariable(
+ "VK_ICD_FILENAMES",
+ testIcdFilename());
+
+ goldfish_vk::init_vulkan_dispatch_from_system_loader(
+ dlOpenFuncForTesting,
+ dlSymFuncForTesting,
+ &mVk);
+
+ testInstanceCreation(&mVk, &mInstance);
+ testDeviceCreation(
+ &mVk, mInstance, &mPhysicalDevice, &mDevice);
+ }
+
+ void TearDown() override {
+ teardownVulkanTest(&mVk, mDevice, mInstance);
+ TestSystem::setEnvironmentVariable(
+ "VK_ICD_FILENAMES", "");
+ }
+
+ VulkanDispatch mVk;
+ VkInstance mInstance;
+ VkPhysicalDevice mPhysicalDevice;
+ VkDevice mDevice;
+};
+
+// Basic Vulkan instance/device setup.
+TEST_F(VulkanTest, Basic) { }
+
+// Checks that staging memory query is successful.
+TEST_F(VulkanTest, StagingMemoryQuery) {
+ VkPhysicalDeviceMemoryProperties memProps;
+ uint32_t typeIndex;
+
+ mVk.vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memProps);
+
+ EXPECT_TRUE(goldfish_vk::getStagingMemoryTypeIndex(
+ &mVk, mDevice, &memProps, &typeIndex));
+}
+
+#ifndef _WIN32 // TODO: Get this working w/ Swiftshader vk on Windows
+class VulkanFrameBufferTest : public VulkanTest {
+protected:
+ void SetUp() override {
+ // SwiftShader Vulkan doesn't work on Windows, so we skip all
+ // the rendering tests on Windows for now.
+ SKIP_TEST_IF_WIN32();
+
+ VulkanTest::SetUp();
+
+ emugl::set_emugl_feature_is_enabled(
+ [](android::featurecontrol::Feature feature) {
+ return feature == android::featurecontrol::Vulkan;
+ });
+
+ setupStandaloneLibrarySearchPaths();
+ emugl::setGLObjectCounter(android::base::GLObjectCounter::get());
+ emugl::set_emugl_window_operations(*getConsoleAgents()->emu);
+ emugl::set_emugl_multi_display_operations(*getConsoleAgents()->multi_display);
+ const EGLDispatch* egl = LazyLoadedEGLDispatch::get();
+ ASSERT_NE(nullptr, egl);
+ ASSERT_NE(nullptr, LazyLoadedGLESv2Dispatch::get());
+
+ mRenderThreadInfo = std::make_unique<RenderThreadInfo>();
+
+ bool useHostGpu = shouldUseHostGpu();
+ EXPECT_TRUE(FrameBuffer::initialize(mWidth, mHeight, false,
+ !useHostGpu /* egl2egl */));
+ mFb = FrameBuffer::getFB();
+ ASSERT_NE(nullptr, mFb);
+ }
+
+ void TearDown() override { VulkanTest::TearDown(); }
+
+ FrameBuffer* mFb = nullptr;
+ std::unique_ptr<RenderThreadInfo> mRenderThreadInfo;
+
+ constexpr static uint32_t mWidth = 640u;
+ constexpr static uint32_t mHeight = 480u;
+};
+
+TEST_F(VulkanFrameBufferTest, VkColorBufferWithoutMemoryProperties) {
+ // Create a color buffer without any memory properties restriction.
+ HandleType colorBuffer = mFb->createColorBuffer(
+ mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ ASSERT_NE(colorBuffer, 0u);
+ EXPECT_TRUE(goldfish_vk::setupVkColorBuffer(colorBuffer,
+ true, /* vulkanOnly */
+ 0 /* memoryProperty */
+ ));
+ EXPECT_TRUE(goldfish_vk::teardownVkColorBuffer(colorBuffer));
+ mFb->closeColorBuffer(colorBuffer);
+}
+
+TEST_F(VulkanFrameBufferTest, VkColorBufferWithMemoryPropertyFlags) {
+ auto* vkEmulation = goldfish_vk::getGlobalVkEmulation();
+ VkMemoryPropertyFlags kTargetMemoryPropertyFlags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+
+ // Create a Vulkan image with the same dimension and usage as
+ // the color buffer, to get a possible memory type index.
+ VkImageCreateInfo testImageCi = {
+ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ nullptr,
+ 0,
+ VK_IMAGE_TYPE_2D,
+ VK_FORMAT_R8G8B8A8_UNORM,
+ {
+ mWidth,
+ mHeight,
+ 1,
+ },
+ 1,
+ 1,
+ VK_SAMPLE_COUNT_1_BIT,
+ VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
+ VK_SHARING_MODE_EXCLUSIVE,
+ 0,
+ nullptr /* shared queue families */,
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ };
+ VkImage image;
+ mVk.vkCreateImage(mDevice, &testImageCi, nullptr, &image);
+
+ VkMemoryRequirements memReq;
+ mVk.vkGetImageMemoryRequirements(mDevice, image, &memReq);
+
+ mVk.vkDestroyImage(mDevice, image, nullptr);
+
+ if (!memReq.memoryTypeBits) {
+ GTEST_SKIP();
+ }
+
+ int32_t memoryTypeIndex = 31;
+ do {
+ if (((1 << memoryTypeIndex) & memReq.memoryTypeBits) &&
+ (vkEmulation->deviceInfo.memProps.memoryTypes[memoryTypeIndex]
+ .propertyFlags &
+ kTargetMemoryPropertyFlags)) {
+ break;
+ }
+ } while (--memoryTypeIndex >= 0);
+
+ if (memoryTypeIndex < 0) {
+ fprintf(stderr,
+ "No memory type supported for HOST_VISBILE memory "
+ "properties. Test skipped.\n");
+ GTEST_SKIP();
+ }
+
+ // Create a color buffer with the target memory property flags.
+ uint32_t allocatedTypeIndex = 0u;
+ HandleType colorBuffer = mFb->createColorBuffer(
+ mWidth, mHeight, GL_RGBA, FRAMEWORK_FORMAT_GL_COMPATIBLE);
+ ASSERT_NE(colorBuffer, 0u);
+ EXPECT_TRUE(goldfish_vk::setupVkColorBuffer(
+ colorBuffer, true, /* vulkanOnly */
+ static_cast<uint32_t>(kTargetMemoryPropertyFlags), nullptr, nullptr,
+ &allocatedTypeIndex));
+ EXPECT_TRUE(vkEmulation->deviceInfo.memProps.memoryTypes[allocatedTypeIndex]
+ .propertyFlags &
+ kTargetMemoryPropertyFlags);
+ EXPECT_TRUE(goldfish_vk::teardownVkColorBuffer(colorBuffer));
+ mFb->closeColorBuffer(colorBuffer);
+}
+#endif // !_WIN32
+} // namespace emugl