blob: 0c7196982829e433bbf8becab5b80a51a198bac8 [file] [log] [blame]
// 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 "aemu/base/GLObjectCounter.h"
#include "aemu/base/files/PathUtils.h"
#include "aemu/base/perflogger/BenchmarkLibrary.h"
#include "android/base/system/System.h"
#include "aemu/base/threads/FunctorThread.h"
#include "aemu/base/Tracing.h"
#include "host-common/opengles.h"
#include "android/emulation/testing/TestDmaMap.h"
#include "android/console.h"
#include "AndroidBufferQueue.h"
#include "AndroidWindow.h"
#include "AndroidWindowBuffer.h"
#include "ClientComposer.h"
#include "Display.h"
#include "GoldfishOpenglTestEnv.h"
#include "GrallocDispatch.h"
#include <android/hardware_buffer.h>
#include <cutils/properties.h>
#include <hardware/gralloc.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>
#include <sstream>
#include <string>
#include <stdio.h>
#include <sys/time.h>
using aemu::AndroidBufferQueue;
using aemu::AndroidWindow;
using aemu::AndroidWindowBuffer;
using aemu::ClientComposer;
using aemu::Display;
using android::base::FunctorThread;
using android::base::pj;
using android::base::System;
using android::TestDmaMap;
static constexpr int kWindowSize = 256;
class CombinedGoldfishOpenglTest :
public ::testing::Test,
public ::testing::WithParamInterface<const char*> {
protected:
static GoldfishOpenglTestEnv* testEnv;
static void SetUpTestCase() {
testEnv = new GoldfishOpenglTestEnv;
}
static void TearDownTestCase() {
delete testEnv;
testEnv = nullptr;
}
bool usingAddressSpaceGraphics() {
char value[PROPERTY_VALUE_MAX];
if (property_get(
"ro.kernel.qemu.gltransport", value, "pipe") > 0) {
return !strcmp("asg", value);
}
return false;
}
struct EGLState {
EGLDisplay display = EGL_NO_DISPLAY;
EGLConfig config;
EGLSurface surface = EGL_NO_SURFACE;
EGLContext context = EGL_NO_CONTEXT;
};
void SetUp() override {
property_set("ro.kernel.qemu.gltransport", GetParam());
printf("%s: using transport: %s\n", __func__, GetParam());
setupGralloc();
setupEGLAndMakeCurrent();
mBeforeTest = android::base::GLObjectCounter::get()->getCounts();
}
void TearDown() override {
EXPECT_EQ(EGL_TRUE, eglDestroyContext(mEGL.display, mEGLgles1.context));
if (eglGetCurrentContext() != EGL_NO_CONTEXT) {
glFinish(); // sync the pipe
}
// Only to force a sweep of color buffers
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT));
teardownEGL();
if (!mDisableLeakCheck) {
mAfterTest = android::base::GLObjectCounter::get()->getCounts();
for (int i = 0; i < mBeforeTest.size(); i++) {
EXPECT_TRUE(mBeforeTest[i] >= mAfterTest[i]) <<
"Leaked objects of type " << i << "; before: " << mBeforeTest[i] << " after: " << mAfterTest[i];
}
}
teardownGralloc();
teardownHostThreads();
mDisableLeakCheck = false;
testEnv->clear();
}
void setupGralloc() {
auto grallocPath = pj({System::get()->getProgramDirectory(), "lib64",
"gralloc.ranchu" LIBSUFFIX});
load_gralloc_module(grallocPath.c_str(), &mGralloc);
set_global_gralloc_module(&mGralloc);
EXPECT_NE(nullptr, mGralloc.alloc_dev);
EXPECT_NE(nullptr, mGralloc.alloc_module);
}
void teardownGralloc() { unload_gralloc_module(&mGralloc); }
void setupEGLAndMakeCurrent() {
int maj, min;
mEGL.display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EXPECT_EQ(EGL_TRUE, eglInitialize(mEGL.display, &maj, &min));
eglBindAPI(EGL_OPENGL_ES_API);
static const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES2_BIT, EGL_NONE,
};
int numConfigs;
EXPECT_EQ(EGL_TRUE, eglChooseConfig(mEGL.display, configAttribs,
&mEGL.config, 1, &numConfigs))
<< "Could not find GLES2 config!";
EXPECT_GT(numConfigs, 0);
static const EGLint pbufAttribs[] = {EGL_WIDTH, kWindowSize, EGL_HEIGHT,
kWindowSize, EGL_NONE};
mEGL.surface =
eglCreatePbufferSurface(mEGL.display, mEGL.config, pbufAttribs);
EXPECT_NE(EGL_NO_SURFACE, mEGL.surface)
<< "Could not create pbuffer surface!";
static const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION,
2,
EGL_NONE,
};
mEGL.context = eglCreateContext(mEGL.display, mEGL.config,
EGL_NO_CONTEXT, contextAttribs);
EXPECT_NE(EGL_NO_CONTEXT, mEGL.context) << "Could not create context!";
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, mEGL.surface,
mEGL.surface, mEGL.context))
<< "eglMakeCurrent failed!";
mEGLgles1 = mEGL;
static const EGLint contextAttribsGles1[] = {
EGL_CONTEXT_CLIENT_VERSION,
1,
EGL_NONE,
};
mEGLgles1.context = eglCreateContext(mEGL.display, mEGL.config, EGL_NO_CONTEXT, contextAttribsGles1);
EXPECT_NE(EGL_NO_CONTEXT, mEGLgles1.context) << "Could not create GLES1 context!";
}
void teardownEGL() {
eglRelease();
}
void teardownHostThreads() {
android_finishOpenglesRenderer();
}
void eglRelease() {
if (mEGL.display != EGL_NO_DISPLAY) {
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EQ(EGL_TRUE, eglDestroyContext(mEGL.display, mEGL.context));
EXPECT_EQ(EGL_TRUE, eglDestroySurface(mEGL.display, mEGL.surface));
EXPECT_EQ(EGL_TRUE, eglTerminate(mEGL.display));
EXPECT_EQ(EGL_TRUE, eglReleaseThread());
}
mEGL.display = EGL_NO_DISPLAY;
mEGL.context = EGL_NO_CONTEXT;
mEGL.surface = EGL_NO_SURFACE;
}
#define CHECK_GL_ERROR() do { GLint err = glGetError(); EXPECT_EQ(0, err) << "GL error " << err; } while(0)
buffer_handle_t createTestGrallocBuffer(
int usage = GRALLOC_USAGE_HW_RENDER,
int format = HAL_PIXEL_FORMAT_RGBA_8888,
int width = kWindowSize,
int height = kWindowSize) {
buffer_handle_t buffer;
int stride;
int res;
res = mGralloc.alloc(width, height, format, usage, &buffer, &stride);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
res = mGralloc.registerBuffer(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
(void)stride;
return buffer;
}
void destroyTestGrallocBuffer(buffer_handle_t buffer) {
int res;
res = mGralloc.unregisterBuffer(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
res = mGralloc.free(buffer);
if (res) {
fprintf(stderr, "%s:%d res=%d buffer=%p\n", __func__, __LINE__, res, buffer);
::abort();
}
}
std::vector<AndroidWindowBuffer> primeWindowBufferQueue(AndroidWindow& window) {
EXPECT_NE(nullptr, window.fromProducer);
EXPECT_NE(nullptr, window.toProducer);
std::vector<AndroidWindowBuffer> buffers;
for (int i = 0; i < AndroidBufferQueue::kCapacity; i++) {
buffers.push_back(AndroidWindowBuffer(
&window, createTestGrallocBuffer()));
}
for (int i = 0; i < AndroidBufferQueue::kCapacity; i++) {
window.fromProducer->queueBuffer(AndroidBufferQueue::Item(
(ANativeWindowBuffer*)(buffers.data() + i)));
}
return buffers;
}
class WindowWithBacking {
public:
WindowWithBacking(CombinedGoldfishOpenglTest* _env)
: env(_env), window(kWindowSize, kWindowSize) {
window.setProducer(&toWindow, &fromWindow);
buffers = env->primeWindowBufferQueue(window);
}
~WindowWithBacking() {
for (auto& buffer : buffers) {
env->destroyTestGrallocBuffer(buffer.handle);
}
}
CombinedGoldfishOpenglTest* env;
AndroidWindow window;
AndroidBufferQueue toWindow;
AndroidBufferQueue fromWindow;
std::vector<AndroidWindowBuffer> buffers;
};
TestDmaMap dmaMap;
struct gralloc_implementation mGralloc;
EGLState mEGL;
EGLState mEGLgles1;
std::vector<size_t> mBeforeTest;
std::vector<size_t> mAfterTest;
bool mDisableLeakCheck = false;
};
// static
GoldfishOpenglTestEnv* CombinedGoldfishOpenglTest::testEnv = nullptr;
// Tests that the pipe connection can be made and that
// gralloc/EGL can be set up.
TEST_P(CombinedGoldfishOpenglTest, Basic) { }
// Tests allocation and deletion of a gralloc buffer.
TEST_P(CombinedGoldfishOpenglTest, GrallocBuffer) {
buffer_handle_t buffer = createTestGrallocBuffer();
destroyTestGrallocBuffer(buffer);
}
// Tests basic GLES usage.
TEST_P(CombinedGoldfishOpenglTest, ViewportQuery) {
GLint viewport[4] = {};
glGetIntegerv(GL_VIEWPORT, viewport);
EXPECT_EQ(0, viewport[0]);
EXPECT_EQ(0, viewport[1]);
EXPECT_EQ(kWindowSize, viewport[2]);
EXPECT_EQ(kWindowSize, viewport[3]);
}
// Tests eglCreateWindowSurface.
TEST_P(CombinedGoldfishOpenglTest, CreateWindowSurface) {
AndroidWindow testWindow(kWindowSize, kWindowSize);
AndroidBufferQueue toWindow;
AndroidBufferQueue fromWindow;
testWindow.setProducer(&toWindow, &fromWindow);
AndroidWindowBuffer testBuffer(&testWindow, createTestGrallocBuffer());
toWindow.queueBuffer(AndroidBufferQueue::Item(&testBuffer));
static const EGLint attribs[] = {EGL_WIDTH, kWindowSize, EGL_HEIGHT,
kWindowSize, EGL_NONE};
EGLSurface testEGLWindow =
eglCreateWindowSurface(mEGL.display, mEGL.config,
(EGLNativeWindowType)(&testWindow), attribs);
EXPECT_NE(EGL_NO_SURFACE, testEGLWindow);
EXPECT_EQ(EGL_TRUE, eglDestroySurface(mEGL.display, testEGLWindow));
destroyTestGrallocBuffer(testBuffer.handle);
}
// Starts a client composer, but doesn't do anything with it.
TEST_P(CombinedGoldfishOpenglTest, ClientCompositionSetup) {
WindowWithBacking composeWindow(this);
WindowWithBacking appWindow(this);
ClientComposer composer(&composeWindow.window, &appWindow.fromWindow,
&appWindow.toWindow);
}
// Client composition, but also advances a frame.
TEST_P(CombinedGoldfishOpenglTest, ClientCompositionAdvanceFrame) {
WindowWithBacking composeWindow(this);
WindowWithBacking appWindow(this);
AndroidBufferQueue::Item item;
appWindow.toWindow.dequeueBuffer(&item);
appWindow.fromWindow.queueBuffer(item);
ClientComposer composer(&composeWindow.window, &appWindow.fromWindow,
&appWindow.toWindow);
composer.advanceFrame();
composer.join();
}
TEST_P(CombinedGoldfishOpenglTest, ShowWindow) {
bool useWindow =
System::get()->envGet("ANDROID_EMU_TEST_WITH_WINDOW") == "1";
aemu::Display disp(useWindow, kWindowSize, kWindowSize);
if (useWindow) {
EXPECT_NE(nullptr, disp.getNative());
android_showOpenglesWindow(disp.getNative(), 0, 0, kWindowSize,
kWindowSize, kWindowSize, kWindowSize, 1.0,
0, false, false);
}
for (int i = 0; i < 10; i++) {
disp.loop();
System::get()->sleepMs(1);
}
if (useWindow) android_hideOpenglesWindow();
}
TEST_P(CombinedGoldfishOpenglTest, DISABLED_ThreadCleanup) {
// initial clean up.
eglRelease();
mBeforeTest = android::base::GLObjectCounter::get()->getCounts();
for (int i = 0; i < 100; i++) {
std::unique_ptr<FunctorThread> th(new FunctorThread([this]() {
setupEGLAndMakeCurrent();
eglRelease();
}));
;
th->start();
th->wait();
}
}
// Test case adapted from https://buganizer.corp.google.com/issues/111228391
TEST_P(CombinedGoldfishOpenglTest, SwitchContext) {
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT));
for (int i = 0; i < 100; i++) {
std::unique_ptr<FunctorThread> th(new FunctorThread([this]() {
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, mEGL.surface,
mEGL.surface, mEGL.context));
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT));
}));
th->start();
th->wait();
}
}
// Test that direct mapped memory works.
TEST_P(CombinedGoldfishOpenglTest, MappedMemory) {
constexpr GLsizei kBufferSize = 64;
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, kBufferSize, 0, GL_DYNAMIC_DRAW);
std::vector<uint8_t> data(kBufferSize);
for (uint8_t i = 0; i < kBufferSize; ++i) {
data[i] = i;
}
uint8_t* mapped =
(uint8_t*)
glMapBufferRange(
GL_ARRAY_BUFFER, 0, kBufferSize, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
for (uint8_t i = 0; i < kBufferSize; ++i) {
mapped[i] = data[i];
}
glFlushMappedBufferRange(GL_ARRAY_BUFFER, 0, kBufferSize);
glUnmapBuffer(GL_ARRAY_BUFFER);
mapped =
(uint8_t*)
glMapBufferRange(
GL_ARRAY_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
for (uint8_t i = 0; i < kBufferSize; ++i) {
EXPECT_EQ(data[i], mapped[i]);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
mapped =
(uint8_t*)
glMapBufferRange(
GL_ARRAY_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
for (uint8_t i = 0; i < kBufferSize; ++i) {
EXPECT_EQ(data[i], mapped[i]);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
}
// Test big buffer transfer
TEST_P(CombinedGoldfishOpenglTest, BigBuffer) {
constexpr GLsizei kBufferSize = 4096;
std::vector<uint8_t> buf(kBufferSize);
GLuint buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glGetError();
glBufferData(GL_ARRAY_BUFFER, kBufferSize, buf.data(), GL_DYNAMIC_DRAW);
glGetError();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
}
static GLuint compileShader(GLenum shaderType, const char* src) {
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, (const GLchar* const*)&src, nullptr);
glCompileShader(shader);
GLint compileStatus;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus != GL_TRUE) {
GLsizei infoLogLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
std::vector<char> infoLog(infoLogLength + 1, 0);
glGetShaderInfoLog(shader, infoLogLength, nullptr, infoLog.data());
fprintf(stderr, "Failed to compile shader. Info log: [%s]\n", infoLog.data());
}
return shader;
}
static GLint compileAndLinkShaderProgram(const char* vshaderSrc,
const char* fshaderSrc) {
GLuint vshader = compileShader(GL_VERTEX_SHADER, vshaderSrc);
GLuint fshader = compileShader(GL_FRAGMENT_SHADER, fshaderSrc);
GLuint program = glCreateProgram();
glAttachShader(program, vshader);
glAttachShader(program, fshader);
glLinkProgram(program);
glDeleteShader(vshader);
glDeleteShader(fshader);
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
if (linkStatus != GL_TRUE) {
GLsizei infoLogLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
std::vector<char> infoLog(infoLogLength + 1, 0);
glGetProgramInfoLog(program, infoLogLength, nullptr,
infoLog.data());
fprintf(stderr, "Failed to link program. Info log: [%s]\n", infoLog.data());
}
return program;
}
// Test draw call rate. This is a perfgate benchmark
TEST_P(CombinedGoldfishOpenglTest, DrawCallRate) {
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 = glGetUniformLocation(program, "transform");
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
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;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes),
(GLvoid*)offsetof(VertexAttributes, color));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glUseProgram(program);
glClearColor(0.2f, 0.2f, 0.3f, 0.0f);
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,
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
uint32_t drawCount = 0;
static constexpr uint32_t kDrawCallLimit = 50000;
auto cpuTimeStart = System::cpuTime();
while (drawCount < kDrawCallLimit) {
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, matrix);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glDrawArrays(GL_TRIANGLES, 0, 3);
++drawCount;
}
// Need a round trip at the end to get an accurate measurement
glFinish();
auto cpuTime = System::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 = kDrawCallLimit / sec;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
glUseProgram(0);
glDeleteProgram(program);
printf("Drew %u times in %f ms. Rate: %f Hz\n", kDrawCallLimit, ms,
drawCallHz);
android::perflogger::logDrawCallOverheadTest(
(const char*)glGetString(GL_VENDOR),
(const char*)glGetString(GL_RENDERER),
(const char*)glGetString(GL_VERSION),
"drawArrays", "fullStack",
kDrawCallLimit,
(long)drawCallHz,
duration_us,
duration_cpu_us);
}
extern "C" void glDrawArraysNullAEMU(GLenum mode, GLint first, GLsizei count);
// Test draw call rate without drawing on the host driver, which gives numbers
// closer to pure emulator overhead. This is a perfgate benchmark
TEST_P(CombinedGoldfishOpenglTest, DrawCallRateOverheadOnly) {
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 = glGetUniformLocation(program, "transform");
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
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;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes),
(GLvoid*)offsetof(VertexAttributes, color));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glUseProgram(program);
glClearColor(0.2f, 0.2f, 0.3f, 0.0f);
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,
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
uint32_t drawCount = 0;
static constexpr uint32_t kDrawCallLimit = 2000000;
auto cpuTimeStart = System::cpuTime();
while (drawCount < kDrawCallLimit) {
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, matrix);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glDrawArraysNullAEMU(GL_TRIANGLES, 0, 3);
++drawCount;
}
// Need a round trip at the end to get an accurate measurement
glFinish();
auto cpuTime = System::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 = kDrawCallLimit / sec;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
glUseProgram(0);
glDeleteProgram(program);
printf("Drew %u times in %f ms. Rate: %f Hz\n", kDrawCallLimit, ms,
drawCallHz);
android::perflogger::logDrawCallOverheadTest(
(const char*)glGetString(GL_VENDOR),
(const char*)glGetString(GL_RENDERER),
(const char*)glGetString(GL_VERSION),
"drawArrays", "fullStackOverheadOnly",
kDrawCallLimit,
(long)drawCallHz,
duration_us,
duration_cpu_us);
}
// Test uniform upload rate. This is a perfgate benchmark
TEST_P(CombinedGoldfishOpenglTest, UniformUploadRate) {
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 = glGetUniformLocation(program, "transform");
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
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;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes),
(GLvoid*)offsetof(VertexAttributes, color));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glUseProgram(program);
glClearColor(0.2f, 0.2f, 0.3f, 0.0f);
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,
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
uint32_t uploadCount = 0;
static constexpr uint32_t kUploadLimit = 8000000;
auto cpuTimeStart = System::cpuTime();
while (uploadCount < kUploadLimit) {
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, matrix);
++uploadCount;
}
// Need a round trip at the end to get an accurate measurement
glFinish();
auto cpuTime = System::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 = kUploadLimit / sec;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
glUseProgram(0);
glDeleteProgram(program);
printf("glUniformMatrix4fv %u times in %f ms. Rate: %f Hz\n", kUploadLimit, ms,
drawCallHz);
android::perflogger::logDrawCallOverheadTest(
(const char*)glGetString(GL_VENDOR),
(const char*)glGetString(GL_RENDERER),
(const char*)glGetString(GL_VERSION),
"uniformUpload", "fullStack",
kUploadLimit,
(long)drawCallHz,
duration_us,
duration_cpu_us);
}
TEST_P(CombinedGoldfishOpenglTest, AndroidHardwareBuffer) {
uint64_t usage =
AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
uint64_t cpuUsage =
AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN;
AHardwareBuffer_Desc desc = {
kWindowSize, kWindowSize, 1,
AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
usage,
4, // stride ignored for allocate; don't check this
};
AHardwareBuffer* buf;
AHardwareBuffer_allocate(&desc, &buf);
AHardwareBuffer_Desc outDesc;
AHardwareBuffer_describe(buf, &outDesc);
EXPECT_EQ(desc.width, outDesc.width);
EXPECT_EQ(desc.height, outDesc.height);
EXPECT_EQ(desc.layers, outDesc.layers);
EXPECT_EQ(desc.format, outDesc.format);
EXPECT_EQ(desc.usage, outDesc.usage);
void* outVirtualAddress;
ARect rect = { 0, 0, kWindowSize, kWindowSize, };
EXPECT_EQ(0, AHardwareBuffer_lock(
buf, cpuUsage, -1, &rect, &outVirtualAddress));
EXPECT_EQ(0, AHardwareBuffer_unlock(buf, nullptr));
AHardwareBuffer_release(buf);
}
TEST_P(CombinedGoldfishOpenglTest, GenericSnapshot) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
// TODO: Find out why we leak some textures when loading snapshot
mDisableLeakCheck = true;
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
}
TEST_P(CombinedGoldfishOpenglTest, QuickbootSnapshot) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
// TODO: Find out why we leak some textures when loading snapshot
mDisableLeakCheck = true;
androidSnapshot_quickbootSave("test_snapshot");
androidSnapshot_quickbootLoad("test_snapshot");
}
TEST_P(CombinedGoldfishOpenglTest, GenericSnapshotLoadMulti) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
// TODO: Find out why we leak some textures when loading snapshot
mDisableLeakCheck = true;
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
androidSnapshot_load("test_snapshot");
androidSnapshot_load("test_snapshot");
}
// TODO: Test wtih hostmem device and goldfish address space device snapshot.
TEST_P(CombinedGoldfishOpenglTest, GenericSnapshotGralloc) {
// TODO: Skip if using address space graphics
if (usingAddressSpaceGraphics()) {
printf("%s: skipping, ASG does not yet support snapshots\n", __func__);
return;
}
// TODO: Find out why we leak some textures when loading snapshot
mDisableLeakCheck = true;
buffer_handle_t buffer = createTestGrallocBuffer();
androidSnapshot_save("test_snapshot");
androidSnapshot_load("test_snapshot");
destroyTestGrallocBuffer(buffer);
}
TEST_P(CombinedGoldfishOpenglTest, DISABLED_FboBlitTextureLayer) {
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 256, 256, 1);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, tex, 0, 0);
CHECK_GL_ERROR();
glBlitFramebuffer(0, 0, 256, 256, 0, 0, 256, 256, GL_COLOR_BUFFER_BIT, GL_NEAREST);
CHECK_GL_ERROR();
glDeleteFramebuffers(1, &fbo);
glDeleteTextures(1, &tex);
}
// TODO: Enable this test once we have a host-side sync device impl (also, causes compile error currently)
// TEST_P(CombinedGoldfishOpenglTest, GetSyncAttrib) {
// EGLSyncKHR sync = eglCreateSyncKHR(mEGL.display, EGL_SYNC_FENCE_KHR, nullptr);
// EXPECT_NE(nullptr, sync);
//
// EGLint value;
//
// EXPECT_EQ(
// EGL_TRUE,
// eglGetSyncAttribKHR(
// mEGL.display, sync, EGL_SYNC_STATUS_KHR, &value));
//
// EXPECT_TRUE(
// EGL_SIGNALED_KHR == value ||
// EGL_UNSIGNALED_KHR == value
// );
//
// if (EGL_UNSIGNALED_KHR == value) {
// printf("%s: Note: Nontrivial case; not signaled in the beginning\n", __func__);
// }
//
// glFinish();
//
// EXPECT_EQ(
// EGL_TRUE,
// eglGetSyncAttribKHR(
// mEGL.display, sync, EGL_SYNC_STATUS_KHR, &value));
//
// EXPECT_TRUE(
// EGL_SIGNALED_KHR == value);
//
// eglDestroySyncKHR(mEGL.display, sync);
// }
// Test uniform upload rate. This is a perfgate benchmark
TEST_P(CombinedGoldfishOpenglTest, FramebufferFetchShader) {
const char* extStr = (const char*)glGetString(GL_EXTENSIONS);
const char* rendererStr = (const char*)glGetString(GL_RENDERER);
EXPECT_NE(nullptr, extStr);
EXPECT_NE(nullptr, rendererStr);
bool supportsFramebufferFetch =
strstr(extStr, "GL_EXT_shader_framebuffer_fetch") != nullptr;
printf("Renderer: [%s] supports GL_EXT_shader_framebuffer_fetch? %d\n",
rendererStr,
supportsFramebufferFetch);
const char* fshaderSrc[] = {
R"(#version 300 es
#extension GL_EXT_shader_framebuffer_fetch : require
precision highp float;
in vec3 color_varying;
out vec4 fragColor;
void main() { fragColor = vec4(color_varying, 1.0); }
)",
};
GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);
EXPECT_NE(0, shader);
glShaderSource(shader, 1, (const GLchar* const*)fshaderSrc, nullptr);
glCompileShader(shader);
GLint compileStatus;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
if (supportsFramebufferFetch) {
EXPECT_EQ(GL_TRUE, compileStatus);
} else {
EXPECT_NE(GL_TRUE, compileStatus);
}
glDeleteShader(shader);
}
TEST_P(CombinedGoldfishOpenglTest, ConstantMatrixSource) {
const char* vshaderSrc[] = {
R"(#version 300 es
precision mediump float;
in highp vec4 dEQP_Position;
out vec2 out0;
void main()
{
const mat4x2 matA = mat4x2(2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0);
const mat4x2 matB = mat4x2(1.0/2.0, 1.0/4.0, 1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0, 1.0/128.0, 1.0/256.0);
mat4x2 result = matrixCompMult(matA, matB);
out0 = result * vec4(1.0, 1.0, 1.0, 1.0);
gl_Position = dEQP_Position;
}
)",
};
GLuint shader = glCreateShader(GL_VERTEX_SHADER);
EXPECT_NE(0, shader);
glShaderSource(shader, 1, (const GLchar* const*)vshaderSrc, nullptr);
glCompileShader(shader);
GLint compileStatus;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
EXPECT_EQ(GL_TRUE, compileStatus);
glDeleteShader(shader);
}
TEST_P(CombinedGoldfishOpenglTest, GetStringTwiceCurrent) {
// current in non-gles1 in SetUp
{
const char* versionString = (const char*)glGetString(GL_VERSION);
const char* extensionString = (const char*)glGetString(GL_EXTENSIONS);
printf("%s: GL version string (non-gles1): %s\n", __func__, versionString);
printf("%s: GL extension string (non-gles1): %s\n", __func__, extensionString);
EXPECT_NE(nullptr, strstr(versionString, "ES 3"));
EXPECT_EQ(nullptr, strstr(extensionString, "OES_draw_texture"));
}
{
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, mEGL.surface,
mEGL.surface, mEGLgles1.context));
const char* versionStringGles1 = (const char*)glGetString(GL_VERSION);
const char* extensionStringGles1 = (const char*)glGetString(GL_EXTENSIONS);
printf("%s: GL version string (gles1 mode): %s\n", __func__, versionStringGles1);
EXPECT_NE(nullptr, strstr(versionStringGles1, "ES-CM"));
EXPECT_NE(nullptr, strstr(extensionStringGles1, "OES_draw_texture"));
}
{
EXPECT_EQ(EGL_TRUE, eglMakeCurrent(mEGL.display, mEGL.surface,
mEGL.surface, mEGL.context));
const char* versionStringAfter = (const char*)glGetString(GL_VERSION);
const char* extensionStringAfter = (const char*)glGetString(GL_EXTENSIONS);
printf("%s: GL version string: (after, back to non-gles1): %s\n", __func__, versionStringAfter);
printf("%s: GL extension string (after, back to non-gles1): %s\n", __func__, extensionStringAfter);
EXPECT_NE(nullptr, strstr(versionStringAfter, "ES 3"));
EXPECT_EQ(nullptr, strstr(extensionStringAfter, "OES_draw_texture"));
}
glFinish();
}
TEST_P(CombinedGoldfishOpenglTest, CombinedGuestHostTracing) {
android::base::enableTracing();
const char* versionString = (const char*)glGetString(GL_VERSION);
const char* extensionString = (const char*)glGetString(GL_EXTENSIONS);
printf("%s: GL version string (non-gles1): %s\n", __func__, versionString);
printf("%s: GL extension string (non-gles1): %s\n", __func__, extensionString);
EXPECT_NE(nullptr, strstr(versionString, "ES 3"));
EXPECT_EQ(nullptr, strstr(extensionString, "OES_draw_texture"));
// Do some random stuff
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 = glGetUniformLocation(program, "transform");
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
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;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexAttrs), vertexAttrs,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes), 0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
sizeof(VertexAttributes),
(GLvoid*)offsetof(VertexAttributes, color));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glUseProgram(program);
glClearColor(0.2f, 0.2f, 0.3f, 0.0f);
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,
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
uint32_t drawCount = 0;
static constexpr uint32_t kDrawCallLimit = 200;
while (drawCount < kDrawCallLimit) {
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, matrix);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glDrawArraysNullAEMU(GL_TRIANGLES, 0, 3);
++drawCount;
}
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &buffer);
glUseProgram(0);
glDeleteProgram(program);
glFinish();
android::base::disableTracing();
}
INSTANTIATE_TEST_SUITE_P(
MultipleTransports,
CombinedGoldfishOpenglTest,
testing::ValuesIn(GoldfishOpenglTestEnv::getTransportsToTest()));