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*)&currentArrayBuffer);
+        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, &currentBind);
+        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*)&current.type);
+            EXPECT_EQ(GL_NO_ERROR, gl->glGetError());
+
+            if (current.type != GL_NONE) {
+                gl->glGetFramebufferAttachmentParameteriv(
+                        GL_FRAMEBUFFER, attachment,
+                        GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME,
+                        (GLint*)&current.name);
+                if (current.type == GL_TEXTURE) {
+                    gl->glGetFramebufferAttachmentParameteriv(
+                            GL_FRAMEBUFFER, attachment,
+                            GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL,
+                            (GLint*)&current.textureLevel);
+                    gl->glGetFramebufferAttachmentParameteriv(
+                            GL_FRAMEBUFFER, attachment,
+                            GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE,
+                            (GLint*)&current.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*)&currentArrayBuffer);
+    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, &current);
+    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, &current);
+    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, &current);
+    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, &current[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, &current[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, &current[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