/*-------------------------------------------------------------------------
* drawElements Quality Program EGL Module
* ---------------------------------------
*
* Copyright 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.
*
*//*!
* \file
* \brief Test the EGL_ANDROID_get_frame_timestamps extension.
*//*--------------------------------------------------------------------*/
#include "teglGetFrameTimestampsTests.hpp"
#include "teglSimpleConfigCase.hpp"
#include "egluNativeWindow.hpp"
#include "egluUtil.hpp"
#include "egluUnique.hpp"
#include "eglwLibrary.hpp"
#include "eglwEnums.hpp"
#include "gluDefs.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "tcuResultCollector.hpp"
#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuVectorUtil.hpp"
#include "deClock.h"
#include "deMath.h"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "deThread.hpp"
#include <algorithm>
#include <string>
#include <vector>
#include <sstream>
// Tentative EGL header definitions for EGL_ANDROID_get_Frame_timestamps.
// \todo [2017-01-25 brianderson] Remove once defined in the official headers.
#define EGL_TIMESTAMPS_ANDROID 0x3430
#define EGL_COMPOSITE_DEADLINE_ANDROID 0x3431
#define EGL_COMPOSITE_INTERVAL_ANDROID 0x3432
#define EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID 0x3433
#define EGL_REQUESTED_PRESENT_TIME_ANDROID 0x3434
#define EGL_RENDERING_COMPLETE_TIME_ANDROID 0x3435
#define EGL_COMPOSITION_LATCH_TIME_ANDROID 0x3436
#define EGL_FIRST_COMPOSITION_START_TIME_ANDROID 0x3437
#define EGL_LAST_COMPOSITION_START_TIME_ANDROID 0x3438
#define EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID 0x3439
#define EGL_DISPLAY_PRESENT_TIME_ANDROID 0x343A
#define EGL_DEQUEUE_READY_TIME_ANDROID 0x343B
#define EGL_READS_DONE_TIME_ANDROID 0x343C
typedef deInt64 EGLnsecsANDROID;
typedef deUint64 EGLuint64KHR;
#define EGL_TIMESTAMP_PENDING_ANDROID (-2)
#define EGL_TIMESTAMP_INVALID_ANDROID (-1)
typedef EGLW_APICALL eglw::EGLBoolean (EGLW_APIENTRY* eglGetNextFrameIdANDROIDFunc) (eglw::EGLDisplay dpy, eglw::EGLSurface surface, EGLuint64KHR *frameId);
typedef EGLW_APICALL eglw::EGLBoolean (EGLW_APIENTRY* eglGetCompositorTimingANDROIDFunc) (eglw::EGLDisplay dpy, eglw::EGLSurface surface, eglw::EGLint numTimestamps, const eglw::EGLint *names, EGLnsecsANDROID *values);
typedef EGLW_APICALL eglw::EGLBoolean (EGLW_APIENTRY* eglGetCompositorTimingSupportedANDROIDFunc) (eglw::EGLDisplay dpy, eglw::EGLSurface surface, eglw::EGLint name);
typedef EGLW_APICALL eglw::EGLBoolean (EGLW_APIENTRY* eglGetFrameTimestampsANDROIDFunc) (eglw::EGLDisplay dpy, eglw::EGLSurface surface, EGLuint64KHR frameId, eglw::EGLint numTimestamps, const eglw::EGLint *timestamps, EGLnsecsANDROID *values);
typedef EGLW_APICALL eglw::EGLBoolean (EGLW_APIENTRY* eglGetFrameTimestampSupportedANDROIDFunc) (eglw::EGLDisplay dpy, eglw::EGLSurface surface, eglw::EGLint timestamp);
#define CHECK_NAKED_EGL_CALL(EGLW, CALL) do { CALL; eglu::checkError((EGLW).getError(), #CALL, __FILE__, __LINE__); } while (deGetFalse())
namespace deqp
{
namespace egl
{
using tcu::TestLog;
using std::string;
using std::vector;
using namespace eglw;
namespace
{
// Careful: This has microsecond precision, which can cause timestamps to
// appear non monotonic when compared to the nanosecond precision timestamps
// we get from the eglGetFrameTimestamps extension.
// Current test expectations only make sure microsecond precision timestamps
// are less than the nanosecond precision timestamps, so this is okay.
EGLnsecsANDROID getNanoseconds (void)
{
return deGetMicroseconds() * 1000;
}
struct FrameTimes
{
FrameTimes (void)
: frameId (-1)
, swapBufferBeginNs (-1)
, compositeDeadline (-1)
, compositeInterval (-1)
, compositeToPresentLatency (-1)
, requestedPresent (-1)
, latch (-1)
, firstCompositionStart (-1)
, lastCompositionStart (-1)
, dequeueReady (-1)
, renderingComplete (-1)
, firstCompositionGpuFinished (-1)
, displayPresent (-1)
, readsDone (-1)
{
}
EGLuint64KHR frameId;
// Timestamps sampled by the test.
EGLnsecsANDROID swapBufferBeginNs;
// Compositor info.
EGLnsecsANDROID compositeDeadline;
EGLnsecsANDROID compositeInterval;
EGLnsecsANDROID compositeToPresentLatency;
// CPU Timeline.
EGLnsecsANDROID requestedPresent;
EGLnsecsANDROID latch;
EGLnsecsANDROID firstCompositionStart;
EGLnsecsANDROID lastCompositionStart;
EGLnsecsANDROID dequeueReady;
// GPU Timeline.
EGLnsecsANDROID renderingComplete;
EGLnsecsANDROID firstCompositionGpuFinished;
EGLnsecsANDROID displayPresent;
EGLnsecsANDROID readsDone;
};
struct TimestampInfo
{
TimestampInfo()
: required(false)
, supported(false)
, supportedIndex(0)
{
}
TimestampInfo(bool required_, bool supported_, size_t supportedIndex_)
: required(required_)
, supported(supported_)
, supportedIndex(supportedIndex_)
{
}
bool required;
bool supported;
size_t supportedIndex;
};
typedef std::map<eglw::EGLint, TimestampInfo> TimestampInfoMap;
EGLnsecsANDROID getTimestamp(eglw::EGLint name, TimestampInfoMap& map, const std::vector<EGLnsecsANDROID>& supportedValues)
{
TimestampInfo& info = map[name];
return info.supported ? supportedValues[info.supportedIndex] : EGL_TIMESTAMP_INVALID_ANDROID;
}
void populateFrameTimes(FrameTimes* frameTimes, TimestampInfoMap& map, const std::vector<EGLnsecsANDROID>& supportedValues)
{
frameTimes->requestedPresent = getTimestamp(EGL_REQUESTED_PRESENT_TIME_ANDROID, map, supportedValues);
frameTimes->renderingComplete = getTimestamp(EGL_RENDERING_COMPLETE_TIME_ANDROID, map, supportedValues);
frameTimes->latch = getTimestamp(EGL_COMPOSITION_LATCH_TIME_ANDROID, map, supportedValues);
frameTimes->firstCompositionStart = getTimestamp(EGL_FIRST_COMPOSITION_START_TIME_ANDROID, map, supportedValues);
frameTimes->lastCompositionStart = getTimestamp(EGL_LAST_COMPOSITION_START_TIME_ANDROID, map, supportedValues);
frameTimes->firstCompositionGpuFinished = getTimestamp(EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID, map, supportedValues);
frameTimes->displayPresent = getTimestamp(EGL_DISPLAY_PRESENT_TIME_ANDROID, map, supportedValues);
frameTimes->dequeueReady = getTimestamp(EGL_DEQUEUE_READY_TIME_ANDROID, map, supportedValues);
frameTimes->readsDone = getTimestamp(EGL_READS_DONE_TIME_ANDROID, map, supportedValues);
}
bool timestampValid (EGLnsecsANDROID timestamp)
{
// \todo [2017-10-19 brianderson] Don't consider 0 invalid once kernel fix is in.
return (timestamp > 0) || (timestamp == EGL_TIMESTAMP_PENDING_ANDROID);
}
bool timestampPending (EGLnsecsANDROID timestamp)
{
return timestamp == EGL_TIMESTAMP_PENDING_ANDROID;
}
template<typename T>
void check_lt(tcu::ResultCollector& result, const T& a, const T& b, const std::string& msg) {
if (a < b)
return;
std::string m = msg + "!(" + de::toString(a) + " < " + de::toString(b) + ")";
result.fail(m);
}
template<typename T>
void check_le(tcu::ResultCollector& result, const T& a, const T& b, const std::string& msg) {
if (a <= b)
return;
std::string m = msg + "!(" + de::toString(a) + " <= " + de::toString(b) + ")";
result.fail(m);
}
void verifySingleFrame (const FrameTimes& frameTimes, tcu::ResultCollector& result, bool verifyReadsDone)
{
// Verify CPU timeline is monotonic.
check_lt(result, frameTimes.swapBufferBeginNs, frameTimes.latch, "Buffer latched before it was swapped.");
check_lt(result, frameTimes.latch, frameTimes.firstCompositionStart, "Buffer composited before it was latched.");
check_le(result, frameTimes.firstCompositionStart, frameTimes.lastCompositionStart, "First composition start after last composition start.");
check_lt(result, frameTimes.lastCompositionStart, frameTimes.dequeueReady, "Buffer composited after it was ready to be dequeued.");
// Verify GPU timeline is monotonic.
if (timestampValid(frameTimes.firstCompositionGpuFinished))
check_lt(result, frameTimes.renderingComplete, frameTimes.firstCompositionGpuFinished, "Buffer rendering completed after compositor GPU work finished.");
if (timestampValid(frameTimes.displayPresent))
check_lt(result, frameTimes.renderingComplete, frameTimes.displayPresent, "Buffer displayed before rendering completed.");
if (timestampValid(frameTimes.firstCompositionGpuFinished) && timestampValid(frameTimes.displayPresent))
check_lt(result, frameTimes.firstCompositionGpuFinished, frameTimes.displayPresent, "Buffer displayed before compositor GPU work completed");
// Drivers may maintain shadow copies of the buffer, so the readsDone time
// of the real buffer may be earlier than apparent dependencies. We can only
// be sure that the readsDone time must be after the renderingComplete time.
// It may also be equal to the renderingComplete time if no reads were
// peformed.
if (verifyReadsDone && timestampValid(frameTimes.readsDone))
check_le(result, frameTimes.renderingComplete, frameTimes.readsDone, "Buffer rendering completed after reads completed.");
// Verify CPU/GPU dependencies
if (timestampValid(frameTimes.firstCompositionGpuFinished))
check_lt(result, frameTimes.firstCompositionStart, frameTimes.firstCompositionGpuFinished, "Composition CPU work started after GPU work finished.");
if (timestampValid(frameTimes.displayPresent))
check_lt(result, frameTimes.firstCompositionStart, frameTimes.displayPresent, "Buffer displayed before it was composited.");
}
void verifyNeighboringFrames (const FrameTimes& frame1, const FrameTimes& frame2, tcu::ResultCollector& result)
{
// CPU timeline.
check_lt(result, frame1.swapBufferBeginNs, frame2.swapBufferBeginNs, "Swap begin times not monotonic.");
check_lt(result, frame1.latch, frame2.latch, "Latch times not monotonic.");
check_lt(result, frame1.lastCompositionStart, frame2.latch, "Old buffer composited after new buffer latched.");
check_lt(result, frame1.lastCompositionStart, frame2.firstCompositionStart, "Composition times overlap.");
check_lt(result, frame1.dequeueReady, frame2.dequeueReady, "Dequeue ready times not monotonic.");
// GPU timeline.
check_lt(result, frame1.renderingComplete, frame2.renderingComplete, "Rendering complete times not monotonic.");
if (timestampValid(frame1.firstCompositionGpuFinished) && timestampValid(frame2.firstCompositionGpuFinished))
check_lt(result, frame1.firstCompositionGpuFinished, frame2.firstCompositionGpuFinished, "Composition GPU work complete times not monotonic.");
if (timestampValid(frame1.displayPresent) && timestampValid(frame2.displayPresent))
check_lt(result, frame1.displayPresent, frame2.displayPresent, "Display present times not monotonic.");
}
EGLContext createGLES2Context (const Library& egl, EGLDisplay display, EGLConfig config)
{
EGLContext context = EGL_NO_CONTEXT;
const EGLint attribList[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLU_CHECK_CALL(egl, bindAPI(EGL_OPENGL_ES_API));
context = egl.createContext(display, config, EGL_NO_CONTEXT, attribList);
EGLU_CHECK_MSG(egl, "eglCreateContext() failed");
TCU_CHECK(context);
return context;
}
class GetFrameTimestampTest : public SimpleConfigCase
{
public:
GetFrameTimestampTest (EglTestContext& eglTestCtx, const NamedFilterList& filters);
~GetFrameTimestampTest (void);
private:
void executeForConfig (EGLDisplay display, EGLConfig config);
void initializeExtension (const Library& egl);
// Not allowed
GetFrameTimestampTest (const GetFrameTimestampTest&);
GetFrameTimestampTest& operator= (const GetFrameTimestampTest&);
// TODO: Move these to eglw::Library.
eglGetNextFrameIdANDROIDFunc m_eglGetNextFrameIdANDROID;
eglGetCompositorTimingANDROIDFunc m_eglGetCompositorTimingANDROID;
eglGetCompositorTimingSupportedANDROIDFunc m_eglGetCompositorTimingSupportedANDROID;
eglGetFrameTimestampsANDROIDFunc m_eglGetFrameTimestampsANDROID;
eglGetFrameTimestampSupportedANDROIDFunc m_eglGetFrameTimestampSupportedANDROID;
tcu::ResultCollector m_result;
};
GetFrameTimestampTest::GetFrameTimestampTest (EglTestContext& eglTestCtx, const NamedFilterList& filters)
: SimpleConfigCase (eglTestCtx, filters.getName(), filters.getDescription(), filters)
, m_eglGetNextFrameIdANDROID (DE_NULL)
, m_eglGetCompositorTimingANDROID (DE_NULL)
, m_eglGetCompositorTimingSupportedANDROID (DE_NULL)
, m_eglGetFrameTimestampsANDROID (DE_NULL)
, m_eglGetFrameTimestampSupportedANDROID (DE_NULL)
, m_result (m_testCtx.getLog())
{
}
GetFrameTimestampTest::~GetFrameTimestampTest (void)
{
}
void GetFrameTimestampTest::initializeExtension (const Library& egl)
{
m_eglGetNextFrameIdANDROID = reinterpret_cast<eglGetNextFrameIdANDROIDFunc>(egl.getProcAddress("eglGetNextFrameIdANDROID"));
EGLU_CHECK_MSG(egl, "getProcAddress of eglGetNextFrameIdANDROID failed.");
m_eglGetCompositorTimingANDROID = reinterpret_cast<eglGetCompositorTimingANDROIDFunc>(egl.getProcAddress("eglGetCompositorTimingANDROID"));
EGLU_CHECK_MSG(egl, "getProcAddress of eglGetCompositorTimingANDROID failed.");
m_eglGetCompositorTimingSupportedANDROID = reinterpret_cast<eglGetCompositorTimingSupportedANDROIDFunc>(egl.getProcAddress("eglGetCompositorTimingSupportedANDROID"));
EGLU_CHECK_MSG(egl, "getProcAddress of eglGetCompositorTimingSupportedANDROID failed.");
m_eglGetFrameTimestampsANDROID = reinterpret_cast<eglGetFrameTimestampsANDROIDFunc>(egl.getProcAddress("eglGetFrameTimestampsANDROID"));
EGLU_CHECK_MSG(egl, "getProcAddress of eglGetFrameTimestampsANDROID failed.");
m_eglGetFrameTimestampSupportedANDROID = reinterpret_cast<eglGetFrameTimestampSupportedANDROIDFunc>(egl.getProcAddress("eglGetFrameTimestampSupportedANDROID"));
EGLU_CHECK_MSG(egl, "getProcAddress of eglGetFrameTimestampSupportedANDROID failed.");
}
string getConfigIdString (const Library& egl, EGLDisplay display, EGLConfig config)
{
std::ostringstream stream;
EGLint id;
EGLU_CHECK_CALL(egl, getConfigAttrib(display, config , EGL_CONFIG_ID, &id));
stream << id;
return stream.str();
}
deUint32 createGLES2Program (const glw::Functions& gl, TestLog& log)
{
const char* const vertexShaderSource =
"attribute highp vec2 a_pos;\n"
"void main (void)\n"
"{\n"
"\tgl_Position = vec4(a_pos, 0.0, 1.0);\n"
"}";
const char* const fragmentShaderSource =
"void main (void)\n"
"{\n"
"\tgl_FragColor = vec4(0.9, 0.1, 0.4, 1.0);\n"
"}";
deUint32 program = 0;
deUint32 vertexShader = 0;
deUint32 fragmentShader = 0;
deInt32 vertexCompileStatus;
string vertexInfoLog;
deInt32 fragmentCompileStatus;
string fragmentInfoLog;
deInt32 linkStatus;
string programInfoLog;
try
{
program = gl.createProgram();
vertexShader = gl.createShader(GL_VERTEX_SHADER);
fragmentShader = gl.createShader(GL_FRAGMENT_SHADER);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create shaders and program");
gl.shaderSource(vertexShader, 1, &vertexShaderSource, DE_NULL);
gl.compileShader(vertexShader);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup vertex shader");
gl.shaderSource(fragmentShader, 1, &fragmentShaderSource, DE_NULL);
gl.compileShader(fragmentShader);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup fragment shader");
{
deInt32 infoLogLength = 0;
gl.getShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexCompileStatus);
gl.getShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &infoLogLength);
vertexInfoLog.resize(infoLogLength, '\0');
gl.getShaderInfoLog(vertexShader, (glw::GLsizei)vertexInfoLog.length(), &infoLogLength, &(vertexInfoLog[0]));
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get vertex shader compile info");
vertexInfoLog.resize(infoLogLength);
}
{
deInt32 infoLogLength = 0;
gl.getShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentCompileStatus);
gl.getShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &infoLogLength);
fragmentInfoLog.resize(infoLogLength, '\0');
gl.getShaderInfoLog(fragmentShader, (glw::GLsizei)fragmentInfoLog.length(), &infoLogLength, &(fragmentInfoLog[0]));
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get fragment shader compile info");
fragmentInfoLog.resize(infoLogLength);
}
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup program");
{
deInt32 infoLogLength = 0;
gl.getProgramiv(program, GL_LINK_STATUS, &linkStatus);
gl.getProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
programInfoLog.resize(infoLogLength, '\0');
gl.getProgramInfoLog(program, (glw::GLsizei)programInfoLog.length(), &infoLogLength, &(programInfoLog[0]));
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to get program link info");
programInfoLog.resize(infoLogLength);
}
if (linkStatus == 0 || vertexCompileStatus == 0 || fragmentCompileStatus == 0)
{
log.startShaderProgram(linkStatus != 0, programInfoLog.c_str());
log << TestLog::Shader(QP_SHADER_TYPE_VERTEX, vertexShaderSource, vertexCompileStatus != 0, vertexInfoLog);
log << TestLog::Shader(QP_SHADER_TYPE_FRAGMENT, fragmentShaderSource, fragmentCompileStatus != 0, fragmentInfoLog);
log.endShaderProgram();
}
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to delete shaders");
TCU_CHECK(linkStatus != 0 && vertexCompileStatus != 0 && fragmentCompileStatus != 0);
}
catch (...)
{
if (program)
gl.deleteProgram(program);
if (vertexShader)
gl.deleteShader(vertexShader);
if (fragmentShader)
gl.deleteShader(fragmentShader);
throw;
}
return program;
}
void GetFrameTimestampTest::executeForConfig (EGLDisplay display, EGLConfig config)
{
const Library& egl = m_eglTestCtx.getLibrary();
if (!eglu::hasExtension(egl, display, "EGL_ANDROID_get_frame_timestamps"))
TCU_THROW(NotSupportedError, "EGL_ANDROID_get_frame_timestamps is not supported");
initializeExtension(egl);
const string configIdStr (getConfigIdString(egl, display, config));
tcu::ScopedLogSection logSection (m_testCtx.getLog(), ("Config ID " + configIdStr).c_str(), ("Config ID " + configIdStr).c_str());
const eglu::NativeWindowFactory& factory = eglu::selectNativeWindowFactory(m_eglTestCtx.getNativeDisplayFactory(), m_testCtx.getCommandLine());
{
TestLog& log = m_testCtx.getLog();
log << TestLog::Message << "EGL_RED_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_RED_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_GREEN_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_GREEN_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_BLUE_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_BLUE_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_ALPHA_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_ALPHA_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_DEPTH_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_DEPTH_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_STENCIL_SIZE: " << eglu::getConfigAttribInt(egl, display, config, EGL_STENCIL_SIZE) << TestLog::EndMessage;
log << TestLog::Message << "EGL_SAMPLES: " << eglu::getConfigAttribInt(egl, display, config, EGL_SAMPLES) << TestLog::EndMessage;
}
de::UniquePtr<eglu::NativeWindow> window (factory.createWindow(&m_eglTestCtx.getNativeDisplay(), display, config, DE_NULL, eglu::WindowParams(128, 128, eglu::WindowParams::VISIBILITY_VISIBLE)));
eglu::UniqueSurface surface (egl, display, eglu::createWindowSurface(m_eglTestCtx.getNativeDisplay(), *window, display, config, DE_NULL));
eglu::UniqueContext context (egl, display, createGLES2Context(egl, display, config));
glw::Functions gl;
deUint32 program = 0;
EGLU_CHECK_CALL(egl, surfaceAttrib(display, *surface, EGL_TIMESTAMPS_ANDROID, EGL_TRUE));
m_eglTestCtx.initGLFunctions(&gl, glu::ApiType::es(2,0));
EGLU_CHECK_CALL(egl, makeCurrent(display, *surface, *surface, *context));
try
{
// EGL_DISPLAY_PRESENT_TIME_ANDROID support is currently optional
// but should be required once HWC1 is no longer supported.
// All HWC2 devices should support EGL_DISPLAY_PRESENT_TIME_ANDROID.
TimestampInfoMap timestamps;
timestamps[EGL_REQUESTED_PRESENT_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_RENDERING_COMPLETE_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_COMPOSITION_LATCH_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_FIRST_COMPOSITION_START_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_LAST_COMPOSITION_START_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_FIRST_COMPOSITION_GPU_FINISHED_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_DISPLAY_PRESENT_TIME_ANDROID] = TimestampInfo(false, false, 0);
timestamps[EGL_DEQUEUE_READY_TIME_ANDROID] = TimestampInfo(true, false, 0);
timestamps[EGL_READS_DONE_TIME_ANDROID] = TimestampInfo(true, false, 0);
const eglw::EGLint invalidTimestampName = EGL_READS_DONE_TIME_ANDROID + 1;
// Verify required timestamps are supported and populate supportedNames.
std::vector<eglw::EGLint> supportedNames;
for (TimestampInfoMap::iterator i = timestamps.begin(); i != timestamps.end(); i++)
{
TimestampInfo& info = i->second;
info.supported = m_eglGetFrameTimestampSupportedANDROID(display, *surface, i->first) != EGL_FALSE;
EGLU_CHECK_MSG(egl, "eglGetFrameTimestampSupportedANDROID failed.");
if (info.supported)
{
info.supportedIndex = supportedNames.size();
supportedNames.push_back(i->first);
}
else
TCU_CHECK_MSG(!info.required, "Required timestamp not supported.");
}
// Verify unsupported timestamps are reported properly.
const bool invalidSupported = m_eglGetFrameTimestampSupportedANDROID(display, *surface, invalidTimestampName) != EGL_FALSE;
EGLU_CHECK_MSG(egl, "eglGetFrameTimestampSupportedANDROID failed.");
TCU_CHECK_MSG(!invalidSupported, "Non existant timestamp reports that it is supported.");
// Verify compositor timings are supported.
const bool deadlineSupported = m_eglGetCompositorTimingSupportedANDROID(display, *surface, EGL_COMPOSITE_DEADLINE_ANDROID) != EGL_FALSE;
EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
TCU_CHECK_MSG(deadlineSupported, "EGL_COMPOSITE_DEADLINE_ANDROID not supported.");
const bool intervalSupported = m_eglGetCompositorTimingSupportedANDROID(display, *surface, EGL_COMPOSITE_INTERVAL_ANDROID) != EGL_FALSE;
EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
TCU_CHECK_MSG(intervalSupported, "EGL_COMPOSITE_INTERVAL_ANDROID not supported.");
const bool latencySupported = m_eglGetCompositorTimingSupportedANDROID(display, *surface, EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID) != EGL_FALSE;
EGLU_CHECK_MSG(egl, "eglGetCompositorTimingSupportedANDROID failed.");
TCU_CHECK_MSG(latencySupported, "EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID not supported.");
const float positions1[] =
{
0.00f, 0.00f,
0.75f, 0.00f,
0.75f, 0.75f,
0.75f, 0.75f,
0.00f, 0.75f,
0.00f, 0.00f
};
const float positions2[] =
{
-0.75f, -0.75f,
0.00f, -0.75f,
0.00f, 0.00f,
0.00f, 0.00f,
-0.75f, 0.00f,
-0.75f, -0.75f
};
deUint32 posLocation;
program = createGLES2Program(gl, m_testCtx.getLog());
gl.useProgram(program);
posLocation = gl.getAttribLocation(program, "a_pos");
gl.enableVertexAttribArray(posLocation);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to setup shader program for rendering");
const size_t frameCount = 120;
std::vector<FrameTimes> frameTimes(frameCount);
for (size_t i = 0; i < frameCount; i++)
{
FrameTimes& frame = frameTimes[i];
const eglw::EGLint compositorTimingNames[] =
{
EGL_COMPOSITE_DEADLINE_ANDROID,
EGL_COMPOSITE_INTERVAL_ANDROID,
EGL_COMPOSITE_TO_PRESENT_LATENCY_ANDROID,
};
const EGLint compositorTimingCount = DE_LENGTH_OF_ARRAY(compositorTimingNames);
EGLnsecsANDROID compositorTimingValues[compositorTimingCount] = { -2 };
// Get the current time before making any API calls in case "now"
// just happens to get sampled near one of the composite deadlines.
EGLnsecsANDROID now = getNanoseconds();
// Get the frame id.
EGLuint64KHR nextFrameId = 0;
CHECK_NAKED_EGL_CALL(egl, m_eglGetNextFrameIdANDROID(display, *surface, &nextFrameId));
frame.frameId = nextFrameId;
// Get the compositor timing.
CHECK_NAKED_EGL_CALL(egl, m_eglGetCompositorTimingANDROID(
display, *surface, compositorTimingCount,
compositorTimingNames, compositorTimingValues));
frame.compositeDeadline = compositorTimingValues[0];
frame.compositeInterval = compositorTimingValues[1];
frame.compositeToPresentLatency = compositorTimingValues[2];
// Verify compositor timing is sane.
check_lt<EGLnsecsANDROID>(m_result, 1000000, frame.compositeInterval, "Reported refresh rate greater than 1kHz.");
check_lt<EGLnsecsANDROID>(m_result, frame.compositeInterval, 1000000000, "Reported refresh rate less than 1Hz.");
check_lt<EGLnsecsANDROID>(m_result, 0, frame.compositeToPresentLatency, "Composite to present latency must be greater than 0.");
check_lt(m_result, frame.compositeToPresentLatency, frame.compositeInterval * 3, "Composite to present latency is more than 3 vsyncs.");
const EGLnsecsANDROID minDeadline = now;
check_lt(m_result, minDeadline, frame.compositeDeadline, "Next composite deadline is in the past.");
const EGLnsecsANDROID maxDeadline = now + frame.compositeInterval * 2;
check_lt(m_result, frame.compositeDeadline, maxDeadline, "Next composite deadline over two intervals away.");
const float colorAngle = (static_cast<float>(i) / static_cast<float>(frameCount)) * 6.28318f;
gl.clearColor((1.0f + deFloatSin(colorAngle)) / 2.0f, 0.7f, (1.0f + deFloatCos(colorAngle)) / 2.0f, 1.0f);
gl.clear(GL_COLOR_BUFFER_BIT);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to clear surface");
const bool posSelect = ((i % 2) == 0);
gl.vertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 0, posSelect ? positions1 : positions2);
gl.drawArrays(GL_TRIANGLES, 0, 6);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to render");
frame.swapBufferBeginNs = getNanoseconds();
EGLU_CHECK_CALL(egl, swapBuffers(display, *surface));
// All timestamps from 5 frames ago should definitely be available.
const size_t frameDelay = 5;
if (i >= frameDelay)
{
// \todo [2017-01-25 brianderson] Remove this work around once reads done is fixed.
const bool verifyReadsDone = i > (frameDelay + 3);
FrameTimes& frame5ago = frameTimes[i-frameDelay];
std::vector<EGLnsecsANDROID> supportedValues(supportedNames.size(), 0);
CHECK_NAKED_EGL_CALL(egl, m_eglGetFrameTimestampsANDROID(
display, *surface, frame5ago.frameId, static_cast<eglw::EGLint>(supportedNames.size()),
&supportedNames[0], &supportedValues[0]));
populateFrameTimes(&frame5ago, timestamps, supportedValues);
verifySingleFrame(frame5ago, m_result, verifyReadsDone);
if (i >= frameDelay + 1)
{
FrameTimes& frame6ago = frameTimes[i-frameDelay-1];
verifyNeighboringFrames(frame6ago, frame5ago, m_result);
}
}
}
// All timestamps for the most recently swapped frame should
// become available by only polling eglGetFrametimestamps.
// No additional swaps should be necessary.
FrameTimes& lastFrame = frameTimes.back();
const EGLnsecsANDROID pollingDeadline = lastFrame.swapBufferBeginNs + 1000000000;
bool finalTimestampAvailable = false;
do
{
std::vector<EGLnsecsANDROID> supportedValues(supportedNames.size(), 0);
CHECK_NAKED_EGL_CALL(egl, m_eglGetFrameTimestampsANDROID(
display, *surface, lastFrame.frameId, static_cast<eglw::EGLint>(supportedNames.size()),
&supportedNames[0], &supportedValues[0]));
populateFrameTimes(&lastFrame, timestamps, supportedValues);
// Poll for present if it's supported.
// Otherwise, poll for firstCompositionStart.
if (timestamps[EGL_DISPLAY_PRESENT_TIME_ANDROID].supported)
finalTimestampAvailable = !timestampPending(lastFrame.displayPresent);
else
finalTimestampAvailable = !timestampPending(lastFrame.firstCompositionStart);
} while (!finalTimestampAvailable && (getNanoseconds() < pollingDeadline));
m_result.check(finalTimestampAvailable, "Timed out polling for timestamps of last swap.");
m_result.check((lastFrame.requestedPresent >= 0), "Requested present of last swap not avaiable.");
m_result.check((lastFrame.renderingComplete >= 0), "Rendering complete of last swap not avaiable.");
m_result.check((lastFrame.latch >= 0), "Latch of last swap not avaiable.");
m_result.check((lastFrame.firstCompositionStart >= 0), "First composite time of last swap not avaiable.");
m_result.check((lastFrame.lastCompositionStart >= 0), "Last composite time of last swap not avaiable.");
window->processEvents();
gl.disableVertexAttribArray(posLocation);
gl.useProgram(0);
GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to release program state");
gl.deleteProgram(program);
program = 0;
GLU_EXPECT_NO_ERROR(gl.getError(), "glDeleteProgram()");
m_result.setTestContextResult(m_testCtx);
}
catch (...)
{
if (program != 0)
gl.deleteProgram(program);
EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
throw;
}
EGLU_CHECK_CALL(egl, makeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
}
class GetFrameTimestampsTests : public TestCaseGroup
{
public:
GetFrameTimestampsTests (EglTestContext& eglTestCtx);
void init (void);
private:
GetFrameTimestampsTests (const GetFrameTimestampsTests&);
GetFrameTimestampsTests& operator= (const GetFrameTimestampsTests&);
};
GetFrameTimestampsTests::GetFrameTimestampsTests (EglTestContext& eglTestCtx)
: TestCaseGroup(eglTestCtx, "get_frame_timestamps", "Get frame timestamp tests")
{
}
bool isWindow (const eglu::CandidateConfig& c)
{
return (c.surfaceType() & EGL_WINDOW_BIT) != 0;
}
void GetFrameTimestampsTests::init (void)
{
eglu::FilterList baseFilters;
baseFilters << isWindow;
vector<NamedFilterList> filterLists;
getDefaultFilterLists(filterLists, baseFilters);
for (vector<NamedFilterList>::iterator i = filterLists.begin(); i != filterLists.end(); i++)
addChild(new GetFrameTimestampTest(m_eglTestCtx, *i));
}
} // anonymous
TestCaseGroup* createGetFrameTimestampsTests (EglTestContext& eglTestCtx)
{
return new GetFrameTimestampsTests(eglTestCtx);
}
} // egl
} // deqp