blob: d226c764f58da9f600fe6c226a3c93604c151c65 [file] [log] [blame]
//
// Copyright 2020 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// TracePerf:
// Performance test for ANGLE replaying traces.
//
#include "tests/perf_tests/TracePerfTest.h"
#include <gtest/gtest.h>
#include "common/PackedEnums.h"
#include "common/string_utils.h"
#include "common/system_utils.h"
#include "tests/perf_tests/ANGLEPerfTest.h"
#include "tests/perf_tests/ANGLEPerfTestArgs.h"
#include "tests/perf_tests/DrawCallPerfParams.h"
#include "util/capture/frame_capture_test_utils.h"
#include "util/capture/traces_export.h"
#include "util/egl_loader_autogen.h"
#include "util/png_utils.h"
#include "util/test_utils.h"
#if defined(ANGLE_PLATFORM_ANDROID)
# include "util/android/AndroidWindow.h"
#endif
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
#include <cassert>
#include <fstream>
#include <functional>
#include <sstream>
// When --minimize-gpu-work is specified, we want to reduce GPU work to minimum and lift up the CPU
// overhead to surface so that we can see how much CPU overhead each driver has for each app trace.
// On some driver(s) the bufferSubData/texSubImage calls end up dominating the frame time when the
// actual GPU work is minimized. Even reducing the texSubImage calls to only update 1x1 area is not
// enough. The driver may be implementing copy on write by cloning the entire texture to another
// memory storage for texSubImage call. While this information is also important for performance,
// they should be evaluated separately in real app usage scenario, or write stand alone tests for
// these. For the purpose of CPU overhead and avoid data copy to dominate the trace, I am using this
// flag to noop the texSubImage and bufferSubData call when --minimize-gpu-work is specified. Feel
// free to disable this when you have other needs. Or it can be turned to another run time option
// when desired.
#define NOOP_SUBDATA_SUBIMAGE_FOR_MINIMIZE_GPU_WORK
using namespace angle;
using namespace egl_platform;
namespace
{
class TracePerfTest : public ANGLERenderTest
{
public:
TracePerfTest(std::unique_ptr<const TracePerfParams> params);
void startTest() override;
void initializeBenchmark() override;
void destroyBenchmark() override;
void drawBenchmark() override;
// TODO(http://anglebug.com/42264418): Add support for creating EGLSurface:
// - eglCreatePbufferSurface()
// - eglCreateWindowSurface()
EGLContext onEglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share_context,
EGLint const *attrib_list);
void onEglMakeCurrent(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext context);
EGLContext onEglGetCurrentContext();
EGLImage onEglCreateImage(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib *attrib_list);
EGLImageKHR onEglCreateImageKHR(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLint *attrib_list);
EGLBoolean onEglDestroyImage(EGLDisplay display, EGLImage image);
EGLBoolean onEglDestroyImageKHR(EGLDisplay display, EGLImage image);
EGLSync onEglCreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list);
EGLSync onEglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list);
EGLBoolean onEglDestroySync(EGLDisplay dpy, EGLSync sync);
EGLBoolean onEglDestroySyncKHR(EGLDisplay dpy, EGLSync sync);
EGLint onEglClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTimeKHR timeout);
EGLint onEglClientWaitSyncKHR(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTimeKHR timeout);
EGLint onEglGetError();
EGLDisplay onEglGetCurrentDisplay();
void onReplayFramebufferChange(GLenum target, GLuint framebuffer);
void onReplayInvalidateFramebuffer(GLenum target,
GLsizei numAttachments,
const GLenum *attachments);
void onReplayInvalidateSubFramebuffer(GLenum target,
GLsizei numAttachments,
const GLenum *attachments,
GLint x,
GLint y,
GLsizei width,
GLsizei height);
void onReplayDrawBuffers(GLsizei n, const GLenum *bufs);
void onReplayReadBuffer(GLenum src);
void onReplayDiscardFramebufferEXT(GLenum target,
GLsizei numAttachments,
const GLenum *attachments);
void validateSerializedState(const char *serializedState, const char *fileName, uint32_t line);
bool isDefaultFramebuffer(GLenum target) const;
double getHostTimeFromGLTime(GLint64 glTime);
uint32_t frameCount() const
{
const TraceInfo &traceInfo = mParams->traceInfo;
return traceInfo.frameEnd - traceInfo.frameStart + 1;
}
int getStepAlignment() const override
{
// Align step counts to the number of frames in a trace.
return static_cast<int>(frameCount());
}
void TestBody() override { run(); }
bool traceNameIs(const char *name) const
{
return strncmp(name, mParams->traceInfo.name, kTraceInfoMaxNameLen) == 0;
}
bool loadTestExpectationsFromFileWithConfig(const GPUTestConfig &config,
const std::string &fileName);
private:
struct QueryInfo
{
GLuint beginTimestampQuery;
GLuint endTimestampQuery;
GLuint framebuffer;
};
struct TimeSample
{
GLint64 glTime;
double hostTime;
};
void sampleTime();
enum class ScreenshotType
{
kFrame,
kGrid, // Grid of frames (framebuffer 0) in offscreen mode
};
void saveScreenshotIfEnabled(ScreenshotType screenshotType);
void saveScreenshot(const std::string &screenshotName);
std::unique_ptr<const TracePerfParams> mParams;
uint32_t mStartFrame;
uint32_t mEndFrame;
// For tracking RenderPass/FBO change timing.
QueryInfo mCurrentQuery = {};
std::vector<QueryInfo> mRunningQueries;
std::vector<TimeSample> mTimeline;
bool mUseTimestampQueries = false;
// Note: more than 2 offscreen buffers can cause races, surface is double buffered so real-world
// apps can rely on (now broken) assumptions about GPU completion of a previous frame
static constexpr int mMaxOffscreenBufferCount = 2;
std::array<GLuint, mMaxOffscreenBufferCount> mOffscreenFramebuffers = {0, 0};
std::array<GLuint, mMaxOffscreenBufferCount> mOffscreenTextures = {0, 0};
std::array<GLsync, mMaxOffscreenBufferCount> mOffscreenSyncs = {0, 0};
GLuint mOffscreenDepthStencil = 0;
int mWindowWidth = 0;
int mWindowHeight = 0;
GLuint mDrawFramebufferBinding = 0;
GLuint mReadFramebufferBinding = 0;
EGLContext mEglContext = 0;
uint32_t mCurrentFrame = 0;
uint32_t mCurrentIteration = 0;
uint32_t mCurrentOffscreenGridIteration = 0;
uint32_t mOffscreenFrameCount = 0;
uint32_t mTotalFrameCount = 0;
bool mScreenshotSaved = false;
int32_t mScreenshotFrame = gScreenshotFrame;
std::unique_ptr<TraceLibrary> mTraceReplay;
GPUTestExpectationsParser mTestExpectationsParser;
};
TracePerfTest *gCurrentTracePerfTest = nullptr;
// Don't forget to include KHRONOS_APIENTRY in override methods. Necessary on Win/x86.
EGLContext KHRONOS_APIENTRY EglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share_context,
EGLint const *attrib_list)
{
return gCurrentTracePerfTest->onEglCreateContext(display, config, share_context, attrib_list);
}
void KHRONOS_APIENTRY EglMakeCurrent(EGLDisplay display,
EGLSurface draw,
EGLSurface read,
EGLContext context)
{
gCurrentTracePerfTest->onEglMakeCurrent(display, draw, read, context);
}
EGLContext KHRONOS_APIENTRY EglGetCurrentContext()
{
return gCurrentTracePerfTest->onEglGetCurrentContext();
}
EGLImage KHRONOS_APIENTRY EglCreateImage(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib *attrib_list)
{
return gCurrentTracePerfTest->onEglCreateImage(display, context, target, buffer, attrib_list);
}
EGLImageKHR KHRONOS_APIENTRY EglCreateImageKHR(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLint *attrib_list)
{
return gCurrentTracePerfTest->onEglCreateImageKHR(display, context, target, buffer,
attrib_list);
}
EGLBoolean KHRONOS_APIENTRY EglDestroyImage(EGLDisplay display, EGLImage image)
{
return gCurrentTracePerfTest->onEglDestroyImage(display, image);
}
EGLBoolean KHRONOS_APIENTRY EglDestroyImageKHR(EGLDisplay display, EGLImage image)
{
return gCurrentTracePerfTest->onEglDestroyImageKHR(display, image);
}
EGLSync KHRONOS_APIENTRY EglCreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list)
{
return gCurrentTracePerfTest->onEglCreateSync(dpy, type, attrib_list);
}
EGLSync KHRONOS_APIENTRY EglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
{
return gCurrentTracePerfTest->onEglCreateSyncKHR(dpy, type, attrib_list);
}
EGLBoolean KHRONOS_APIENTRY EglDestroySync(EGLDisplay dpy, EGLSync sync)
{
return gCurrentTracePerfTest->onEglDestroySync(dpy, sync);
}
EGLBoolean KHRONOS_APIENTRY EglDestroySyncKHR(EGLDisplay dpy, EGLSync sync)
{
return gCurrentTracePerfTest->onEglDestroySyncKHR(dpy, sync);
}
EGLint KHRONOS_APIENTRY EglClientWaitSync(EGLDisplay dpy,
EGLSync sync,
EGLint flags,
EGLTimeKHR timeout)
{
return gCurrentTracePerfTest->onEglClientWaitSync(dpy, sync, flags, timeout);
}
EGLint KHRONOS_APIENTRY EglClientWaitSyncKHR(EGLDisplay dpy,
EGLSync sync,
EGLint flags,
EGLTimeKHR timeout)
{
return gCurrentTracePerfTest->onEglClientWaitSyncKHR(dpy, sync, flags, timeout);
}
EGLint KHRONOS_APIENTRY EglGetError()
{
return gCurrentTracePerfTest->onEglGetError();
}
EGLDisplay KHRONOS_APIENTRY EglGetCurrentDisplay()
{
return gCurrentTracePerfTest->onEglGetCurrentDisplay();
}
void KHRONOS_APIENTRY BindFramebufferProc(GLenum target, GLuint framebuffer)
{
gCurrentTracePerfTest->onReplayFramebufferChange(target, framebuffer);
}
void KHRONOS_APIENTRY InvalidateFramebufferProc(GLenum target,
GLsizei numAttachments,
const GLenum *attachments)
{
gCurrentTracePerfTest->onReplayInvalidateFramebuffer(target, numAttachments, attachments);
}
void KHRONOS_APIENTRY InvalidateSubFramebufferProc(GLenum target,
GLsizei numAttachments,
const GLenum *attachments,
GLint x,
GLint y,
GLsizei width,
GLsizei height)
{
gCurrentTracePerfTest->onReplayInvalidateSubFramebuffer(target, numAttachments, attachments, x,
y, width, height);
}
void KHRONOS_APIENTRY DrawBuffersProc(GLsizei n, const GLenum *bufs)
{
gCurrentTracePerfTest->onReplayDrawBuffers(n, bufs);
}
void KHRONOS_APIENTRY ReadBufferProc(GLenum src)
{
gCurrentTracePerfTest->onReplayReadBuffer(src);
}
void KHRONOS_APIENTRY DiscardFramebufferEXTProc(GLenum target,
GLsizei numAttachments,
const GLenum *attachments)
{
gCurrentTracePerfTest->onReplayDiscardFramebufferEXT(target, numAttachments, attachments);
}
void KHRONOS_APIENTRY ViewportMinimizedProc(GLint x, GLint y, GLsizei width, GLsizei height)
{
glViewport(x, y, 1, 1);
}
void KHRONOS_APIENTRY ScissorMinimizedProc(GLint x, GLint y, GLsizei width, GLsizei height)
{
glScissor(x, y, 1, 1);
}
// Interpose the calls that generate actual GPU work
void KHRONOS_APIENTRY DrawElementsMinimizedProc(GLenum mode,
GLsizei count,
GLenum type,
const void *indices)
{
glDrawElements(GL_POINTS, 1, type, indices);
}
void KHRONOS_APIENTRY DrawElementsIndirectMinimizedProc(GLenum mode,
GLenum type,
const void *indirect)
{
glDrawElementsInstancedBaseVertex(GL_POINTS, 1, type, 0, 1, 0);
}
void KHRONOS_APIENTRY DrawElementsInstancedMinimizedProc(GLenum mode,
GLsizei count,
GLenum type,
const void *indices,
GLsizei instancecount)
{
glDrawElementsInstanced(GL_POINTS, 1, type, indices, 1);
}
void KHRONOS_APIENTRY DrawElementsBaseVertexMinimizedProc(GLenum mode,
GLsizei count,
GLenum type,
const void *indices,
GLint basevertex)
{
glDrawElementsBaseVertex(GL_POINTS, 1, type, indices, basevertex);
}
void KHRONOS_APIENTRY DrawElementsInstancedBaseVertexMinimizedProc(GLenum mode,
GLsizei count,
GLenum type,
const void *indices,
GLsizei instancecount,
GLint basevertex)
{
glDrawElementsInstancedBaseVertex(GL_POINTS, 1, type, indices, 1, basevertex);
}
void KHRONOS_APIENTRY DrawRangeElementsMinimizedProc(GLenum mode,
GLuint start,
GLuint end,
GLsizei count,
GLenum type,
const void *indices)
{
glDrawRangeElements(GL_POINTS, start, end, 1, type, indices);
}
void KHRONOS_APIENTRY DrawArraysMinimizedProc(GLenum mode, GLint first, GLsizei count)
{
glDrawArrays(GL_POINTS, first, 1);
}
void KHRONOS_APIENTRY DrawArraysInstancedMinimizedProc(GLenum mode,
GLint first,
GLsizei count,
GLsizei instancecount)
{
glDrawArraysInstanced(GL_POINTS, first, 1, 1);
}
void KHRONOS_APIENTRY DrawArraysIndirectMinimizedProc(GLenum mode, const void *indirect)
{
glDrawArraysInstanced(GL_POINTS, 0, 1, 1);
}
void KHRONOS_APIENTRY DispatchComputeMinimizedProc(GLuint num_groups_x,
GLuint num_groups_y,
GLuint num_groups_z)
{
glDispatchCompute(1, 1, 1);
}
void KHRONOS_APIENTRY DispatchComputeIndirectMinimizedProc(GLintptr indirect)
{
glDispatchCompute(1, 1, 1);
}
// Interpose the calls that generate data copying work
void KHRONOS_APIENTRY BufferDataMinimizedProc(GLenum target,
GLsizeiptr size,
const void *data,
GLenum usage)
{
glBufferData(target, size, nullptr, usage);
}
void KHRONOS_APIENTRY BufferSubDataMinimizedProc(GLenum target,
GLintptr offset,
GLsizeiptr size,
const void *data)
{
#if !defined(NOOP_SUBDATA_SUBIMAGE_FOR_MINIMIZE_GPU_WORK)
glBufferSubData(target, offset, 1, data);
#endif
}
void *KHRONOS_APIENTRY MapBufferRangeMinimizedProc(GLenum target,
GLintptr offset,
GLsizeiptr length,
GLbitfield access)
{
access |= GL_MAP_UNSYNCHRONIZED_BIT;
return glMapBufferRange(target, offset, length, access);
}
void KHRONOS_APIENTRY TexImage2DMinimizedProc(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLint border,
GLenum format,
GLenum type,
const void *pixels)
{
GLint unpackBuffer = 0;
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &unpackBuffer);
if (unpackBuffer)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
glTexImage2D(target, level, internalformat, width, height, border, format, type, nullptr);
if (unpackBuffer)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
}
}
void KHRONOS_APIENTRY TexSubImage2DMinimizedProc(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void *pixels)
{
#if !defined(NOOP_SUBDATA_SUBIMAGE_FOR_MINIMIZE_GPU_WORK)
glTexSubImage2D(target, level, xoffset, yoffset, 1, 1, format, type, pixels);
#endif
}
void KHRONOS_APIENTRY TexImage3DMinimizedProc(GLenum target,
GLint level,
GLint internalformat,
GLsizei width,
GLsizei height,
GLsizei depth,
GLint border,
GLenum format,
GLenum type,
const void *pixels)
{
GLint unpackBuffer = 0;
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &unpackBuffer);
if (unpackBuffer)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
glTexImage3D(target, level, internalformat, width, height, depth, border, format, type,
nullptr);
if (unpackBuffer)
{
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
}
}
void KHRONOS_APIENTRY TexSubImage3DMinimizedProc(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLint zoffset,
GLsizei width,
GLsizei height,
GLsizei depth,
GLenum format,
GLenum type,
const void *pixels)
{
#if !defined(NOOP_SUBDATA_SUBIMAGE_FOR_MINIMIZE_GPU_WORK)
glTexSubImage3D(target, level, xoffset, yoffset, zoffset, 1, 1, 1, format, type, pixels);
#endif
}
void KHRONOS_APIENTRY GenerateMipmapMinimizedProc(GLenum target)
{
// Noop it for now. There is a risk that this will leave an incomplete mipmap chain and cause
// other issues. If this turns out to be a real issue with app traces, we can turn this into a
// glTexImage2D call for each generated level.
}
void KHRONOS_APIENTRY BlitFramebufferMinimizedProc(GLint srcX0,
GLint srcY0,
GLint srcX1,
GLint srcY1,
GLint dstX0,
GLint dstY0,
GLint dstX1,
GLint dstY1,
GLbitfield mask,
GLenum filter)
{
glBlitFramebuffer(srcX0, srcY0, srcX0 + 1, srcY0 + 1, dstX0, dstY0, dstX0 + 1, dstY0 + 1, mask,
filter);
}
void KHRONOS_APIENTRY ReadPixelsMinimizedProc(GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
void *pixels)
{
glReadPixels(x, y, 1, 1, format, type, pixels);
}
void KHRONOS_APIENTRY BeginTransformFeedbackMinimizedProc(GLenum primitiveMode)
{
glBeginTransformFeedback(GL_POINTS);
}
angle::GenericProc KHRONOS_APIENTRY TraceLoadProc(const char *procName)
{
// EGL
if (strcmp(procName, "eglCreateContext") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglCreateContext);
}
if (strcmp(procName, "eglMakeCurrent") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglMakeCurrent);
}
if (strcmp(procName, "eglGetCurrentContext") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglGetCurrentContext);
}
if (strcmp(procName, "eglCreateImage") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglCreateImage);
}
if (strcmp(procName, "eglCreateImageKHR") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglCreateImageKHR);
}
if (strcmp(procName, "eglDestroyImage") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglDestroyImage);
}
if (strcmp(procName, "eglDestroyImageKHR") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglDestroyImageKHR);
}
if (strcmp(procName, "eglCreateSync") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglCreateSync);
}
if (strcmp(procName, "eglCreateSyncKHR") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglCreateSyncKHR);
}
if (strcmp(procName, "eglDestroySync") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglDestroySync);
}
if (strcmp(procName, "eglDestroySyncKHR") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglDestroySyncKHR);
}
if (strcmp(procName, "eglClientWaitSync") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglClientWaitSync);
}
if (strcmp(procName, "eglClientWaitSyncKHR") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglClientWaitSyncKHR);
}
if (strcmp(procName, "eglGetError") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglGetError);
}
if (strcmp(procName, "eglGetCurrentDisplay") == 0)
{
return reinterpret_cast<angle::GenericProc>(EglGetCurrentDisplay);
}
// GLES
if (strcmp(procName, "glBindFramebuffer") == 0 || strcmp(procName, "glBindFramebufferOES") == 0)
{
return reinterpret_cast<angle::GenericProc>(BindFramebufferProc);
}
if (strcmp(procName, "glInvalidateFramebuffer") == 0)
{
return reinterpret_cast<angle::GenericProc>(InvalidateFramebufferProc);
}
if (strcmp(procName, "glInvalidateSubFramebuffer") == 0)
{
return reinterpret_cast<angle::GenericProc>(InvalidateSubFramebufferProc);
}
if (strcmp(procName, "glDrawBuffers") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawBuffersProc);
}
if (strcmp(procName, "glReadBuffer") == 0)
{
return reinterpret_cast<angle::GenericProc>(ReadBufferProc);
}
if (strcmp(procName, "glDiscardFramebufferEXT") == 0)
{
return reinterpret_cast<angle::GenericProc>(DiscardFramebufferEXTProc);
}
if (gMinimizeGPUWork)
{
if (strcmp(procName, "glViewport") == 0)
{
return reinterpret_cast<angle::GenericProc>(ViewportMinimizedProc);
}
if (strcmp(procName, "glScissor") == 0)
{
return reinterpret_cast<angle::GenericProc>(ScissorMinimizedProc);
}
// Interpose the calls that generate actual GPU work
if (strcmp(procName, "glDrawElements") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawElementsMinimizedProc);
}
if (strcmp(procName, "glDrawElementsIndirect") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawElementsIndirectMinimizedProc);
}
if (strcmp(procName, "glDrawElementsInstanced") == 0 ||
strcmp(procName, "glDrawElementsInstancedEXT") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawElementsInstancedMinimizedProc);
}
if (strcmp(procName, "glDrawElementsBaseVertex") == 0 ||
strcmp(procName, "glDrawElementsBaseVertexEXT") == 0 ||
strcmp(procName, "glDrawElementsBaseVertexOES") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawElementsBaseVertexMinimizedProc);
}
if (strcmp(procName, "glDrawElementsInstancedBaseVertex") == 0 ||
strcmp(procName, "glDrawElementsInstancedBaseVertexEXT") == 0 ||
strcmp(procName, "glDrawElementsInstancedBaseVertexOES") == 0)
{
return reinterpret_cast<angle::GenericProc>(
DrawElementsInstancedBaseVertexMinimizedProc);
}
if (strcmp(procName, "glDrawRangeElements") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawRangeElementsMinimizedProc);
}
if (strcmp(procName, "glDrawArrays") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawArraysMinimizedProc);
}
if (strcmp(procName, "glDrawArraysInstanced") == 0 ||
strcmp(procName, "glDrawArraysInstancedEXT") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawArraysInstancedMinimizedProc);
}
if (strcmp(procName, "glDrawArraysIndirect") == 0)
{
return reinterpret_cast<angle::GenericProc>(DrawArraysIndirectMinimizedProc);
}
if (strcmp(procName, "glDispatchCompute") == 0)
{
return reinterpret_cast<angle::GenericProc>(DispatchComputeMinimizedProc);
}
if (strcmp(procName, "glDispatchComputeIndirect") == 0)
{
return reinterpret_cast<angle::GenericProc>(DispatchComputeIndirectMinimizedProc);
}
// Interpose the calls that generate data copying work
if (strcmp(procName, "glBufferData") == 0)
{
return reinterpret_cast<angle::GenericProc>(BufferDataMinimizedProc);
}
if (strcmp(procName, "glBufferSubData") == 0)
{
return reinterpret_cast<angle::GenericProc>(BufferSubDataMinimizedProc);
}
if (strcmp(procName, "glMapBufferRange") == 0 ||
strcmp(procName, "glMapBufferRangeEXT") == 0)
{
return reinterpret_cast<angle::GenericProc>(MapBufferRangeMinimizedProc);
}
if (strcmp(procName, "glTexImage2D") == 0)
{
return reinterpret_cast<angle::GenericProc>(TexImage2DMinimizedProc);
}
if (strcmp(procName, "glTexImage3D") == 0)
{
return reinterpret_cast<angle::GenericProc>(TexImage3DMinimizedProc);
}
if (strcmp(procName, "glTexSubImage2D") == 0)
{
return reinterpret_cast<angle::GenericProc>(TexSubImage2DMinimizedProc);
}
if (strcmp(procName, "glTexSubImage3D") == 0)
{
return reinterpret_cast<angle::GenericProc>(TexSubImage3DMinimizedProc);
}
if (strcmp(procName, "glGenerateMipmap") == 0 ||
strcmp(procName, "glGenerateMipmapOES") == 0)
{
return reinterpret_cast<angle::GenericProc>(GenerateMipmapMinimizedProc);
}
if (strcmp(procName, "glBlitFramebuffer") == 0)
{
return reinterpret_cast<angle::GenericProc>(BlitFramebufferMinimizedProc);
}
if (strcmp(procName, "glReadPixels") == 0)
{
return reinterpret_cast<angle::GenericProc>(ReadPixelsMinimizedProc);
}
if (strcmp(procName, "glBeginTransformFeedback") == 0)
{
return reinterpret_cast<angle::GenericProc>(BeginTransformFeedbackMinimizedProc);
}
}
return gCurrentTracePerfTest->getGLWindow()->getProcAddress(procName);
}
void ValidateSerializedState(const char *serializedState, const char *fileName, uint32_t line)
{
gCurrentTracePerfTest->validateSerializedState(serializedState, fileName, line);
}
bool FindTraceTestDataPath(const char *traceName, char *testDataDirOut, size_t maxDataDirLen)
{
char relativeTestDataDir[kMaxPath] = {};
snprintf(relativeTestDataDir, kMaxPath, "%s%c%s", kTraceTestFolder, GetPathSeparator(),
traceName);
return angle::FindTestDataPath(relativeTestDataDir, testDataDirOut, maxDataDirLen);
}
bool FindRootTraceTestDataPath(char *testDataDirOut, size_t maxDataDirLen)
{
return angle::FindTestDataPath(kTraceTestFolder, testDataDirOut, maxDataDirLen);
}
GPUTestConfig::API getTestConfigAPIFromRenderer(angle::GLESDriverType driverType,
EGLenum renderer,
EGLenum deviceType)
{
if (driverType == angle::GLESDriverType::SystemEGL ||
driverType == angle::GLESDriverType::SystemWGL)
{
return GPUTestConfig::kAPINative;
}
if (driverType != angle::GLESDriverType::AngleEGL &&
driverType != angle::GLESDriverType::AngleVulkanSecondariesEGL)
{
return GPUTestConfig::kAPIUnknown;
}
switch (renderer)
{
case EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE:
return GPUTestConfig::kAPID3D11;
case EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE:
return GPUTestConfig::kAPID3D9;
case EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE:
return GPUTestConfig::kAPIGLDesktop;
case EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE:
return GPUTestConfig::kAPIGLES;
case EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE:
if (deviceType == EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE)
{
return GPUTestConfig::kAPISwiftShader;
}
else
{
return GPUTestConfig::kAPIVulkan;
}
case EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE:
return GPUTestConfig::kAPIMetal;
case EGL_PLATFORM_ANGLE_TYPE_WEBGPU_ANGLE:
return GPUTestConfig::kAPIWgpu;
default:
std::cerr << "Unknown Renderer enum: 0x" << std::hex << renderer << "\n";
return GPUTestConfig::kAPIUnknown;
}
}
bool TracePerfTest::loadTestExpectationsFromFileWithConfig(const GPUTestConfig &config,
const std::string &fileName)
{
if (!mTestExpectationsParser.loadTestExpectationsFromFile(config, fileName))
{
std::stringstream errorMsgStream;
for (const auto &message : mTestExpectationsParser.getErrorMessages())
{
errorMsgStream << std::endl << " " << message;
}
std::cerr << "Failed to load test expectations." << errorMsgStream.str() << std::endl;
return false;
}
return true;
}
TracePerfTest::TracePerfTest(std::unique_ptr<const TracePerfParams> params)
: ANGLERenderTest("TracePerf", *params.get(), "ms"),
mParams(std::move(params)),
mStartFrame(0),
mEndFrame(0)
{
constexpr char kTestExpectationsPath[] =
"src/tests/perf_tests/angle_trace_tests_expectations.txt";
constexpr size_t kMaxPath = 512;
std::array<char, kMaxPath> foundDataPath;
if (!angle::FindTestDataPath(kTestExpectationsPath, foundDataPath.data(), foundDataPath.size()))
{
failTest(std::string("Unable to find ANGLE trace tests expectations path: ") +
std::string(kTestExpectationsPath));
return;
}
angle::GPUTestConfig::API api = getTestConfigAPIFromRenderer(
mParams->driver, mParams->eglParameters.renderer, mParams->eglParameters.deviceType);
GPUTestConfig testConfig = GPUTestConfig(api, 0);
if (!loadTestExpectationsFromFileWithConfig(testConfig, std::string(foundDataPath.data())))
{
failTest(std::string("Unable to load ANGLE trace tests expectations file: ") +
std::string(foundDataPath.data()));
return;
}
else
{
int32_t testExpectation =
mTestExpectationsParser.getTestExpectation(mParams->traceInfo.name);
if (testExpectation == GPUTestExpectationsParser::kGpuTestSkip)
{
skipTest("Test skipped on this config");
}
}
if (!mParams->traceInfo.initialized)
{
failTest("Failed to load trace json.");
return;
}
for (std::string extension : mParams->traceInfo.requiredExtensions)
{
addExtensionPrerequisite(extension);
}
if (!mParams->traceInfo.keyFrames.empty())
{
// Only support one keyFrame for now
if (mParams->traceInfo.keyFrames.size() != 1)
{
WARN() << "Multiple keyframes detected, only using the first";
}
// Only use keyFrame if the user didn't specify a value.
if (gScreenshotFrame == kDefaultScreenshotFrame)
{
mScreenshotFrame = mParams->traceInfo.keyFrames[0];
INFO() << "Trace contains keyframe, using frame " << mScreenshotFrame
<< " for screenshot";
}
else
{
WARN() << "Ignoring keyframe, user requested frame " << mScreenshotFrame
<< " for screenshot";
if (mScreenshotFrame == kAllFrames)
{
WARN() << "Capturing screenshots of all frames since requested frame was "
<< kAllFrames;
}
}
}
// Configuration-specific test exceptions. Only include exceptions that are outside the scope
// of the trace tests expectations file, "angle_trace_tests_expectations.txt".
if (traceNameIs("modern_combat_5"))
{
if (IsPixel6() && !IsAndroid14OrNewer())
{
skipTest(
"https://issuetracker.google.com/42267261 Causing thermal failures on Pixel 6 with "
"Android 13");
}
}
if (traceNameIs("genshin_impact"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
if (!Is64Bit())
{
skipTest("Genshin is too large to handle in 32-bit mode");
}
}
// Legacy trace-specific extension dependencies. For new traces this information will be
// included in the trace's json file.
if (traceNameIs("brawl_stars"))
{
addExtensionPrerequisite("GL_EXT_shadow_samplers");
}
if (traceNameIs("free_fire"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("marvel_contest_of_champions"))
{
addExtensionPrerequisite("GL_EXT_color_buffer_half_float");
}
if (traceNameIs("world_of_tanks_blitz"))
{
addExtensionPrerequisite("GL_EXT_disjoint_timer_query");
}
if (traceNameIs("dragon_ball_legends"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("lego_legacy"))
{
addExtensionPrerequisite("GL_EXT_shadow_samplers");
}
if (traceNameIs("world_war_doh"))
{
// Linux+NVIDIA doesn't support GL_KHR_texture_compression_astc_ldr (possibly others also)
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("saint_seiya_awakening"))
{
addExtensionPrerequisite("GL_EXT_shadow_samplers");
}
if (traceNameIs("magic_tiles_3"))
{
// Linux+NVIDIA doesn't support GL_KHR_texture_compression_astc_ldr (possibly others also)
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("real_gangster_crime"))
{
// Linux+NVIDIA doesn't support GL_KHR_texture_compression_astc_ldr (possibly others also)
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
// Intel doesn't support external images.
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("asphalt_8"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("hearthstone"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("shadow_fight_2"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("rise_of_kingdoms"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("among_us"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("extreme_car_driving_simulator"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("junes_journey"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("ragnarok_m_eternal_love"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("league_of_legends_wild_rift"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("aztec_ruins"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("dragon_raja"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("avakin_life"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("ludo_king"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("pokemon_go"))
{
addExtensionPrerequisite("GL_EXT_texture_cube_map_array");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("cookie_run_kingdom"))
{
addExtensionPrerequisite("GL_EXT_texture_cube_map_array");
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("pubg_mobile_skydive") || traceNameIs("pubg_mobile_battle_royale"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
}
if (traceNameIs("scrabble_go"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("world_of_kings"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("nier_reincarnation"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("pokemon_unite"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("world_cricket_championship_2"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("township"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("asphalt_9"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("dead_by_daylight"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
}
if (traceNameIs("war_planet_online"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("lords_mobile"))
{
// http://anglebug.com/42265475 - glTexStorage2DEXT is not exposed on Pixel 4 native
addExtensionPrerequisite("GL_EXT_texture_storage");
}
if (traceNameIs("real_racing3"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
}
if (traceNameIs("blade_and_soul_revolution"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("scary_teacher_3d"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("car_chase"))
{
addExtensionPrerequisite("GL_EXT_geometry_shader");
addExtensionPrerequisite("GL_EXT_primitive_bounding_box");
addExtensionPrerequisite("GL_EXT_tessellation_shader");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
addExtensionPrerequisite("GL_EXT_texture_cube_map_array");
}
if (traceNameIs("aztec_ruins_high"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("special_forces_group_2"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
}
if (traceNameIs("tessellation"))
{
addExtensionPrerequisite("GL_EXT_geometry_shader");
addExtensionPrerequisite("GL_EXT_primitive_bounding_box");
addExtensionPrerequisite("GL_EXT_tessellation_shader");
addExtensionPrerequisite("GL_EXT_texture_cube_map_array");
}
if (traceNameIs("basemark_gpu"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("mortal_kombat"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
}
if (traceNameIs("ni_no_kuni"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("octopath_traveler"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("antutu_refinery"))
{
addExtensionPrerequisite("GL_ANDROID_extension_pack_es31a");
}
if (traceNameIs("botworld_adventure"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("life_is_strange"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
addExtensionPrerequisite("GL_EXT_texture_cube_map_array");
}
if (traceNameIs("minetest"))
{
addExtensionPrerequisite("GL_EXT_texture_format_BGRA8888");
addIntegerPrerequisite(GL_MAX_TEXTURE_UNITS, 4);
}
if (traceNameIs("diablo_immortal"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
}
if (traceNameIs("mu_origin_3"))
{
addExtensionPrerequisite("GL_EXT_texture_buffer");
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("catalyst_black"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
}
if (traceNameIs("limbo"))
{
addExtensionPrerequisite("GL_EXT_shader_framebuffer_fetch");
// For LUMINANCE8_ALPHA8_EXT
addExtensionPrerequisite("GL_EXT_texture_storage");
}
if (traceNameIs("arknights"))
{
// Intel doesn't support external images.
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("honkai_star_rail"))
{
addExtensionPrerequisite("GL_KHR_texture_compression_astc_ldr");
}
if (traceNameIs("toca_life_world"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
}
if (traceNameIs("poppy_playtime"))
{
addExtensionPrerequisite("GL_OES_EGL_image_external");
addIntegerPrerequisite(GL_MAX_TEXTURE_SIZE, 16383);
}
if (traceNameIs("grand_mountain_adventure"))
{
addIntegerPrerequisite(GL_MAX_TEXTURE_SIZE, 11016);
}
if (traceNameIs("passmark_simple"))
{
addExtensionPrerequisite("GL_OES_framebuffer_object");
}
// glDebugMessageControlKHR and glDebugMessageCallbackKHR crash on ARM GLES1.
if (IsARM() && mParams->traceInfo.contextClientMajorVersion == 1)
{
mEnableDebugCallback = false;
}
// We already swap in TracePerfTest::drawBenchmark, no need to swap again in the harness.
disableTestHarnessSwap();
gCurrentTracePerfTest = this;
if (gTraceTestValidation)
{
mStepsToRun = frameCount();
}
if (gRunToKeyFrame)
{
if (mParams->traceInfo.keyFrames.empty())
{
// If we don't have a keyFrame, run one step
INFO() << "No keyframe available for trace, running to frame 1";
mStepsToRun = 1;
}
else
{
int keyFrame = mParams->traceInfo.keyFrames[0];
INFO() << "Running to keyframe: " << keyFrame;
mStepsToRun = keyFrame;
}
}
}
void TracePerfTest::startTest()
{
// runTrial() must align to frameCount()
ASSERT(mCurrentFrame == mStartFrame);
ANGLERenderTest::startTest();
}
std::string FindTraceGzPath(const std::string &traceName)
{
std::stringstream pathStream;
char genDir[kMaxPath] = {};
if (!angle::FindTestDataPath("gen", genDir, kMaxPath))
{
return "";
}
pathStream << genDir << angle::GetPathSeparator() << "tracegz_" << traceName << ".gz";
return pathStream.str();
}
void TracePerfTest::initializeBenchmark()
{
const TraceInfo &traceInfo = mParams->traceInfo;
char testDataDir[kMaxPath] = {};
if (!FindTraceTestDataPath(traceInfo.name, testDataDir, kMaxPath))
{
failTest("Could not find test data folder.");
return;
}
std::string baseDir = "";
#if defined(ANGLE_TRACE_EXTERNAL_BINARIES)
baseDir += AndroidWindow::GetApplicationDirectory() + "/angle_traces/";
#endif
if (gTraceInterpreter)
{
mTraceReplay.reset(new TraceLibrary("angle_trace_interpreter", traceInfo, baseDir));
if (strcmp(gTraceInterpreter, "gz") == 0)
{
std::string traceGzPath = FindTraceGzPath(traceInfo.name);
if (traceGzPath.empty())
{
failTest("Could not find trace gz.");
return;
}
mTraceReplay->setTraceGzPath(traceGzPath);
}
}
else
{
std::stringstream traceNameStr;
traceNameStr << "angle_restricted_traces_" << traceInfo.name;
std::string traceName = traceNameStr.str();
mTraceReplay.reset(new TraceLibrary(traceNameStr.str(), traceInfo, baseDir));
}
LoadTraceEGL(TraceLoadProc);
LoadTraceGLES(TraceLoadProc);
if (!mTraceReplay->valid())
{
failTest("Could not load trace.");
return;
}
mStartFrame = traceInfo.frameStart;
mEndFrame = traceInfo.frameEnd;
mTraceReplay->setValidateSerializedStateCallback(ValidateSerializedState);
mTraceReplay->setBinaryDataDir(testDataDir);
mTraceReplay->setReplayResourceMode(gIncludeInactiveResources);
if (gScreenshotDir)
{
mTraceReplay->setDebugOutputDir(gScreenshotDir);
}
if (gMinimizeGPUWork)
{
// Shrink the offscreen window to 1x1.
mWindowWidth = 1;
mWindowHeight = 1;
}
else
{
mWindowWidth = mTestParams.windowWidth;
mWindowHeight = mTestParams.windowHeight;
}
mCurrentFrame = mStartFrame;
mCurrentIteration = mStartFrame;
mCurrentOffscreenGridIteration = 0;
if (IsAndroid())
{
// On Android, set the orientation used by the app, based on width/height
getWindow()->setOrientation(mTestParams.windowWidth, mTestParams.windowHeight);
}
// If we're rendering offscreen we set up a default back buffer.
if (mParams->surfaceType == SurfaceType::Offscreen)
{
bool gles1 = mParams->traceInfo.contextClientMajorVersion == 1;
if (gles1 &&
!CheckExtensionExists(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS)),
"GL_OES_framebuffer_object"))
{
failTest("GLES1 --offscreen requires GL_OES_framebuffer_object");
return;
}
auto genRenderbuffers = gles1 ? glGenRenderbuffersOES : glGenRenderbuffers;
auto bindRenderbuffer = gles1 ? glBindRenderbufferOES : glBindRenderbuffer;
auto renderbufferStorage = gles1 ? glRenderbufferStorageOES : glRenderbufferStorage;
auto genFramebuffers = gles1 ? glGenFramebuffersOES : glGenFramebuffers;
auto bindFramebuffer = gles1 ? glBindFramebufferOES : glBindFramebuffer;
auto framebufferTexture2D = gles1 ? glFramebufferTexture2DOES : glFramebufferTexture2D;
auto framebufferRenderbuffer =
gles1 ? glFramebufferRenderbufferOES : glFramebufferRenderbuffer;
genRenderbuffers(1, &mOffscreenDepthStencil);
bindRenderbuffer(GL_RENDERBUFFER, mOffscreenDepthStencil);
renderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, mWindowWidth, mWindowHeight);
bindRenderbuffer(GL_RENDERBUFFER, 0);
mEglContext = eglGetCurrentContext();
genFramebuffers(mMaxOffscreenBufferCount, mOffscreenFramebuffers.data());
glGenTextures(mMaxOffscreenBufferCount, mOffscreenTextures.data());
for (int i = 0; i < mMaxOffscreenBufferCount; i++)
{
bindFramebuffer(GL_FRAMEBUFFER, mOffscreenFramebuffers[i]);
// Hard-code RGBA8/D24S8. This should be specified in the trace info.
glBindTexture(GL_TEXTURE_2D, mOffscreenTextures[i]);
glTexImage2D(GL_TEXTURE_2D, 0,
mParams->colorSpace == EGL_GL_COLORSPACE_SRGB ? GL_SRGB8_ALPHA8 : GL_RGBA,
mWindowWidth, mWindowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
mOffscreenTextures[i], 0);
framebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
mOffscreenDepthStencil);
framebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
mOffscreenDepthStencil);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
// Potentially slow. Can load a lot of resources.
mTraceReplay->setupReplay();
glFinish();
ASSERT_GE(mEndFrame, mStartFrame);
getWindow()->ignoreSizeEvents();
getWindow()->setVisible(true);
// If we're re-tracing, trigger capture start after setup. This ensures the Setup function gets
// recaptured into another Setup function and not merged with the first frame.
if (gRetraceMode)
{
getGLWindow()->swap();
}
}
#undef TRACE_TEST_CASE
void TracePerfTest::destroyBenchmark()
{
if (mParams->surfaceType == SurfaceType::Offscreen)
{
glDeleteTextures(mMaxOffscreenBufferCount, mOffscreenTextures.data());
mOffscreenTextures.fill(0);
bool gles1 = mParams->traceInfo.contextClientMajorVersion == 1;
auto deleteRenderbuffers = gles1 ? glDeleteRenderbuffersOES : glDeleteRenderbuffers;
auto deleteFramebuffers = gles1 ? glDeleteFramebuffersOES : glDeleteFramebuffers;
deleteRenderbuffers(1, &mOffscreenDepthStencil);
mOffscreenDepthStencil = 0;
deleteFramebuffers(mMaxOffscreenBufferCount, mOffscreenFramebuffers.data());
mOffscreenFramebuffers.fill(0);
}
mTraceReplay->finishReplay();
mTraceReplay.reset(nullptr);
}
void TracePerfTest::sampleTime()
{
if (mUseTimestampQueries)
{
GLint64 glTime;
// glGetInteger64vEXT is exported by newer versions of the timer query extensions.
// Unfortunately only the core EP is exposed by some desktop drivers (e.g. NVIDIA).
if (glGetInteger64vEXT)
{
glGetInteger64vEXT(GL_TIMESTAMP_EXT, &glTime);
}
else
{
glGetInteger64v(GL_TIMESTAMP_EXT, &glTime);
}
mTimeline.push_back({glTime, angle::GetHostTimeSeconds()});
}
}
void TracePerfTest::drawBenchmark()
{
constexpr uint32_t kFramesPerX = 6;
constexpr uint32_t kFramesPerY = 4;
constexpr uint32_t kFramesPerSwap = kFramesPerY * kFramesPerX;
const uint32_t kOffscreenOffsetX = 0;
const uint32_t kOffscreenOffsetY = 0;
const uint32_t kOffscreenWidth = mTestParams.windowWidth;
const uint32_t kOffscreenHeight = mTestParams.windowHeight;
const uint32_t kOffscreenFrameWidth = static_cast<uint32_t>(
static_cast<double>(kOffscreenWidth / static_cast<double>(kFramesPerX)));
const uint32_t kOffscreenFrameHeight = static_cast<uint32_t>(
static_cast<double>(kOffscreenHeight / static_cast<double>(kFramesPerY)));
// Add a time sample from GL and the host.
if (mCurrentFrame == mStartFrame)
{
sampleTime();
}
bool gles1 = mParams->traceInfo.contextClientMajorVersion == 1;
auto bindFramebuffer = gles1 ? glBindFramebufferOES : glBindFramebuffer;
int offscreenBufferIndex = mTotalFrameCount % mMaxOffscreenBufferCount;
if (mParams->surfaceType == SurfaceType::Offscreen)
{
// Some driver (ARM and ANGLE) try to nop or defer the glFlush if it is called within the
// renderpass to avoid breaking renderpass (performance reason). For app traces that does
// not use any FBO, when we run in the offscreen mode, there is no frame boundary and
// glFlush call we issued at end of frame will get skipped. To overcome this (and also
// matches what onscreen double buffering behavior as well), we use two offscreen FBOs and
// ping pong between them for each frame.
GLuint buffer = mOffscreenFramebuffers[offscreenBufferIndex];
if (gles1 && mOffscreenFrameCount == kFramesPerSwap - 1)
{
buffer = 0; // gles1: a single frame is rendered to buffer 0
}
bindFramebuffer(GL_FRAMEBUFFER, buffer);
GLsync sync = mOffscreenSyncs[offscreenBufferIndex];
if (sync)
{
constexpr GLuint64 kTimeout = 2'000'000'000; // 2 seconds
GLenum result = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, kTimeout);
if (result != GL_CONDITION_SATISFIED && result != GL_ALREADY_SIGNALED)
{
failTest(std::string("glClientWaitSync unexpected result: ") +
std::to_string(result));
}
glDeleteSync(sync);
}
}
char frameName[32];
snprintf(frameName, sizeof(frameName), "Frame %u", mCurrentFrame);
beginInternalTraceEvent(frameName);
startGpuTimer();
atraceCounter("TraceFrameIndex", mCurrentFrame);
mTraceReplay->replayFrame(mCurrentFrame);
stopGpuTimer();
updatePerfCounters();
if (mParams->surfaceType == SurfaceType::Offscreen)
{
if (gMinimizeGPUWork)
{
// To keep GPU work minimum, we skip the blit.
glFlush();
mOffscreenFrameCount++;
}
else
{
GLuint offscreenBuffer = mOffscreenFramebuffers[offscreenBufferIndex];
EGLContext currentEglContext = eglGetCurrentContext();
if (currentEglContext != mEglContext)
{
eglMakeCurrent(eglGetCurrentDisplay(), eglGetCurrentSurface(EGL_DRAW),
eglGetCurrentSurface(EGL_READ), mEglContext);
}
GLint currentDrawFBO, currentReadFBO;
if (gles1)
{
// OES_framebuffer_object doesn't define a separate "read" binding
glGetIntegerv(GL_FRAMEBUFFER_BINDING_OES, &currentDrawFBO);
bindFramebuffer(GL_FRAMEBUFFER, offscreenBuffer);
}
else
{
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &currentDrawFBO);
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &currentReadFBO);
bindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
bindFramebuffer(GL_READ_FRAMEBUFFER, offscreenBuffer);
}
uint32_t frameX = (mOffscreenFrameCount % kFramesPerSwap) % kFramesPerX;
uint32_t frameY = (mOffscreenFrameCount % kFramesPerSwap) / kFramesPerX;
uint32_t windowX = kOffscreenOffsetX + frameX * kOffscreenFrameWidth;
uint32_t windowY = kOffscreenOffsetY + frameY * kOffscreenFrameHeight;
GLboolean scissorTest = GL_FALSE;
glGetBooleanv(GL_SCISSOR_TEST, &scissorTest);
if (scissorTest)
{
glDisable(GL_SCISSOR_TEST);
}
if (!gles1) // gles1: no glBlitFramebuffer, a single frame is rendered to buffer 0
{
mOffscreenSyncs[offscreenBufferIndex] =
glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
glBlitFramebuffer(0, 0, mWindowWidth, mWindowHeight, windowX, windowY,
windowX + kOffscreenFrameWidth, windowY + kOffscreenFrameHeight,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
// GL_READ_FRAMEBUFFER is already set correctly for glReadPixels
saveScreenshotIfEnabled(ScreenshotType::kFrame);
if (frameX == kFramesPerX - 1 && frameY == kFramesPerY - 1)
{
bindFramebuffer(GL_FRAMEBUFFER, 0);
if (!gles1) // gles1: no grid, a single frame is rendered to buffer 0
{
mCurrentOffscreenGridIteration++;
saveScreenshotIfEnabled(ScreenshotType::kGrid);
}
getGLWindow()->swap();
glClear(GL_COLOR_BUFFER_BIT);
mOffscreenFrameCount = 0;
}
else
{
glFlush();
mOffscreenFrameCount++;
}
if (scissorTest)
{
glEnable(GL_SCISSOR_TEST);
}
if (gles1)
{
bindFramebuffer(GL_FRAMEBUFFER, currentDrawFBO);
}
else
{
bindFramebuffer(GL_DRAW_FRAMEBUFFER, currentDrawFBO);
bindFramebuffer(GL_READ_FRAMEBUFFER, currentReadFBO);
}
if (currentEglContext != mEglContext)
{
eglMakeCurrent(eglGetCurrentDisplay(), eglGetCurrentSurface(EGL_DRAW),
eglGetCurrentSurface(EGL_READ), currentEglContext);
}
}
}
else
{
bindFramebuffer(GL_FRAMEBUFFER, 0);
saveScreenshotIfEnabled(ScreenshotType::kFrame);
getGLWindow()->swap();
}
endInternalTraceEvent(frameName);
mTotalFrameCount++;
if (mCurrentFrame == mEndFrame)
{
mTraceReplay->resetReplay();
mCurrentFrame = mStartFrame;
}
else
{
mCurrentFrame++;
}
// Always iterated for saving screenshots after reset
mCurrentIteration++;
// Process any running queries once per iteration.
for (size_t queryIndex = 0; queryIndex < mRunningQueries.size();)
{
const QueryInfo &query = mRunningQueries[queryIndex];
GLuint endResultAvailable = 0;
glGetQueryObjectuivEXT(query.endTimestampQuery, GL_QUERY_RESULT_AVAILABLE,
&endResultAvailable);
if (endResultAvailable == GL_TRUE)
{
char fboName[32];
snprintf(fboName, sizeof(fboName), "FBO %u", query.framebuffer);
GLint64 beginTimestamp = 0;
glGetQueryObjecti64vEXT(query.beginTimestampQuery, GL_QUERY_RESULT, &beginTimestamp);
glDeleteQueriesEXT(1, &query.beginTimestampQuery);
double beginHostTime = getHostTimeFromGLTime(beginTimestamp);
beginGLTraceEvent(fboName, beginHostTime);
GLint64 endTimestamp = 0;
glGetQueryObjecti64vEXT(query.endTimestampQuery, GL_QUERY_RESULT, &endTimestamp);
glDeleteQueriesEXT(1, &query.endTimestampQuery);
double endHostTime = getHostTimeFromGLTime(endTimestamp);
endGLTraceEvent(fboName, endHostTime);
mRunningQueries.erase(mRunningQueries.begin() + queryIndex);
}
else
{
queryIndex++;
}
}
}
// Converts a GL timestamp into a host-side CPU time aligned with "GetHostTimeSeconds".
// This check is necessary to line up sampled trace events in a consistent timeline.
// Uses a linear interpolation from a series of samples. We do a blocking call to sample
// both host and GL time once per swap. We then find the two closest GL timestamps and
// interpolate the host times between them to compute our result. If we are past the last
// GL timestamp we sample a new data point pair.
double TracePerfTest::getHostTimeFromGLTime(GLint64 glTime)
{
// Find two samples to do a lerp.
size_t firstSampleIndex = mTimeline.size() - 1;
while (firstSampleIndex > 0)
{
if (mTimeline[firstSampleIndex].glTime < glTime)
{
break;
}
firstSampleIndex--;
}
// Add an extra sample if we're missing an ending sample.
if (firstSampleIndex == mTimeline.size() - 1)
{
sampleTime();
}
const TimeSample &start = mTimeline[firstSampleIndex];
const TimeSample &end = mTimeline[firstSampleIndex + 1];
// Note: we have observed in some odd cases later timestamps producing values that are
// smaller than preceding timestamps. This bears further investigation.
// Compute the scaling factor for the lerp.
double glDelta = static_cast<double>(glTime - start.glTime);
double glRange = static_cast<double>(end.glTime - start.glTime);
double t = glDelta / glRange;
// Lerp(t1, t2, t)
double hostRange = end.hostTime - start.hostTime;
return mTimeline[firstSampleIndex].hostTime + hostRange * t;
}
EGLContext TracePerfTest::onEglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext share_context,
EGLint const *attrib_list)
{
GLWindowContext newContext =
getGLWindow()->createContextGeneric(reinterpret_cast<GLWindowContext>(share_context));
return reinterpret_cast<EGLContext>(newContext);
}
void TracePerfTest::onEglMakeCurrent(EGLDisplay display,
EGLSurface draw,
EGLSurface read,
EGLContext context)
{
getGLWindow()->makeCurrentGeneric(reinterpret_cast<GLWindowContext>(context));
}
EGLContext TracePerfTest::onEglGetCurrentContext()
{
return getGLWindow()->getCurrentContextGeneric();
}
EGLImage TracePerfTest::onEglCreateImage(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLAttrib *attrib_list)
{
GLWindowBase::Image image = getGLWindow()->createImage(
reinterpret_cast<GLWindowContext>(context), target, buffer, attrib_list);
return reinterpret_cast<EGLImage>(image);
}
EGLImageKHR TracePerfTest::onEglCreateImageKHR(EGLDisplay display,
EGLContext context,
EGLenum target,
EGLClientBuffer buffer,
const EGLint *attrib_list)
{
GLWindowBase::Image image = getGLWindow()->createImageKHR(
reinterpret_cast<GLWindowContext>(context), target, buffer, attrib_list);
return reinterpret_cast<EGLImage>(image);
}
EGLBoolean TracePerfTest::onEglDestroyImage(EGLDisplay display, EGLImage image)
{
return getGLWindow()->destroyImage(image);
}
EGLBoolean TracePerfTest::onEglDestroyImageKHR(EGLDisplay display, EGLImage image)
{
return getGLWindow()->destroyImageKHR(image);
}
EGLSync TracePerfTest::onEglCreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list)
{
return getGLWindow()->createSync(dpy, type, attrib_list);
}
EGLSync TracePerfTest::onEglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
{
return getGLWindow()->createSyncKHR(dpy, type, attrib_list);
}
EGLBoolean TracePerfTest::onEglDestroySync(EGLDisplay dpy, EGLSync sync)
{
return getGLWindow()->destroySync(dpy, sync);
}
EGLBoolean TracePerfTest::onEglDestroySyncKHR(EGLDisplay dpy, EGLSync sync)
{
return getGLWindow()->destroySyncKHR(dpy, sync);
}
EGLint TracePerfTest::onEglClientWaitSync(EGLDisplay dpy,
EGLSync sync,
EGLint flags,
EGLTimeKHR timeout)
{
return getGLWindow()->clientWaitSync(dpy, sync, flags, timeout);
}
EGLint TracePerfTest::onEglClientWaitSyncKHR(EGLDisplay dpy,
EGLSync sync,
EGLint flags,
EGLTimeKHR timeout)
{
return getGLWindow()->clientWaitSyncKHR(dpy, sync, flags, timeout);
}
EGLint TracePerfTest::onEglGetError()
{
return getGLWindow()->getEGLError();
}
EGLDisplay TracePerfTest::onEglGetCurrentDisplay()
{
return getGLWindow()->getCurrentDisplay();
}
// Triggered when the replay calls glBindFramebuffer.
void TracePerfTest::onReplayFramebufferChange(GLenum target, GLuint framebuffer)
{
bool gles1 = mParams->traceInfo.contextClientMajorVersion == 1;
auto bindFramebuffer = gles1 ? glBindFramebufferOES : glBindFramebuffer;
if (framebuffer == 0 && mParams->surfaceType == SurfaceType::Offscreen)
{
bindFramebuffer(target,
mOffscreenFramebuffers[mTotalFrameCount % mMaxOffscreenBufferCount]);
}
else
{
bindFramebuffer(target, framebuffer);
}
switch (target)
{
case GL_FRAMEBUFFER:
mDrawFramebufferBinding = framebuffer;
mReadFramebufferBinding = framebuffer;
break;
case GL_DRAW_FRAMEBUFFER:
mDrawFramebufferBinding = framebuffer;
break;
case GL_READ_FRAMEBUFFER:
mReadFramebufferBinding = framebuffer;
return;
default:
UNREACHABLE();
break;
}
if (!mUseTimestampQueries)
return;
// We have at most one active timestamp query at a time. This code will end the current
// query and immediately start a new one.
if (mCurrentQuery.beginTimestampQuery != 0)
{
glGenQueriesEXT(1, &mCurrentQuery.endTimestampQuery);
glQueryCounterEXT(mCurrentQuery.endTimestampQuery, GL_TIMESTAMP_EXT);
mRunningQueries.push_back(mCurrentQuery);
mCurrentQuery = {};
}
ASSERT(mCurrentQuery.beginTimestampQuery == 0);
glGenQueriesEXT(1, &mCurrentQuery.beginTimestampQuery);
glQueryCounterEXT(mCurrentQuery.beginTimestampQuery, GL_TIMESTAMP_EXT);
mCurrentQuery.framebuffer = framebuffer;
}
std::string GetDiffPath()
{
#if defined(ANGLE_PLATFORM_WINDOWS)
std::array<char, MAX_PATH> filenameBuffer = {};
char *filenamePtr = nullptr;
if (SearchPathA(NULL, "diff", ".exe", MAX_PATH, filenameBuffer.data(), &filenamePtr) == 0)
{
return "";
}
return std::string(filenameBuffer.data());
#else
return "/usr/bin/diff";
#endif // defined(ANGLE_PLATFORM_WINDOWS)
}
void PrintFileDiff(const char *aFilePath, const char *bFilePath)
{
std::string pathToDiff = GetDiffPath();
if (pathToDiff.empty())
{
printf("Could not find diff in the path.\n");
return;
}
std::vector<const char *> args;
args.push_back(pathToDiff.c_str());
args.push_back(aFilePath);
args.push_back(bFilePath);
args.push_back("-u3");
printf("Calling");
for (const char *arg : args)
{
printf(" %s", arg);
}
printf("\n");
ProcessHandle proc(LaunchProcess(args, ProcessOutputCapture::StdoutOnly));
if (proc && proc->finish())
{
printf("\n%s\n", proc->getStdout().c_str());
}
}
void TracePerfTest::validateSerializedState(const char *expectedCapturedSerializedState,
const char *fileName,
uint32_t line)
{
if (!gTraceTestValidation)
{
return;
}
printf("Serialization checkpoint %s:%u...\n", fileName, line);
const GLubyte *bytes = glGetString(GL_SERIALIZED_CONTEXT_STRING_ANGLE);
const char *actualReplayedSerializedState = reinterpret_cast<const char *>(bytes);
if (strcmp(expectedCapturedSerializedState, actualReplayedSerializedState) == 0)
{
printf("Serialization match.\n");
return;
}
GTEST_NONFATAL_FAILURE_("Serialization mismatch!");
const Optional<std::string> aFilePath = CreateTemporaryFile();
const char *aFilePathCStr = aFilePath.value().c_str();
if (aFilePath.valid())
{
printf("Saving \"expected\" capture serialization to \"%s\".\n", aFilePathCStr);
FILE *fpA = fopen(aFilePathCStr, "wt");
ASSERT(fpA);
fprintf(fpA, "%s", expectedCapturedSerializedState);
fclose(fpA);
}
const Optional<std::string> bFilePath = CreateTemporaryFile();
const char *bFilePathCStr = bFilePath.value().c_str();
if (bFilePath.valid())
{
printf("Saving \"actual\" replay serialization to \"%s\".\n", bFilePathCStr);
FILE *fpB = fopen(bFilePathCStr, "wt");
ASSERT(fpB);
fprintf(fpB, "%s", actualReplayedSerializedState);
fclose(fpB);
}
PrintFileDiff(aFilePathCStr, bFilePathCStr);
}
bool TracePerfTest::isDefaultFramebuffer(GLenum target) const
{
switch (target)
{
case GL_FRAMEBUFFER:
case GL_DRAW_FRAMEBUFFER:
return (mDrawFramebufferBinding == 0);
case GL_READ_FRAMEBUFFER:
return (mReadFramebufferBinding == 0);
default:
UNREACHABLE();
return false;
}
}
GLenum ConvertDefaultFramebufferEnum(GLenum value)
{
switch (value)
{
case GL_NONE:
return GL_NONE;
case GL_BACK:
case GL_COLOR:
return GL_COLOR_ATTACHMENT0;
case GL_DEPTH:
return GL_DEPTH_ATTACHMENT;
case GL_STENCIL:
return GL_STENCIL_ATTACHMENT;
case GL_DEPTH_STENCIL:
return GL_DEPTH_STENCIL_ATTACHMENT;
default:
UNREACHABLE();
return GL_NONE;
}
}
std::vector<GLenum> ConvertDefaultFramebufferEnums(GLsizei numAttachments,
const GLenum *attachments)
{
std::vector<GLenum> translatedAttachments;
for (GLsizei attachmentIndex = 0; attachmentIndex < numAttachments; ++attachmentIndex)
{
GLenum converted = ConvertDefaultFramebufferEnum(attachments[attachmentIndex]);
translatedAttachments.push_back(converted);
}
return translatedAttachments;
}
// Needs special handling to treat the 0 framebuffer in offscreen mode.
void TracePerfTest::onReplayInvalidateFramebuffer(GLenum target,
GLsizei numAttachments,
const GLenum *attachments)
{
if (mParams->surfaceType != SurfaceType::Offscreen || !isDefaultFramebuffer(target))
{
glInvalidateFramebuffer(target, numAttachments, attachments);
}
else
{
std::vector<GLenum> translatedAttachments =
ConvertDefaultFramebufferEnums(numAttachments, attachments);
glInvalidateFramebuffer(target, numAttachments, translatedAttachments.data());
}
}
void TracePerfTest::onReplayInvalidateSubFramebuffer(GLenum target,
GLsizei numAttachments,
const GLenum *attachments,
GLint x,
GLint y,
GLsizei width,
GLsizei height)
{
if (mParams->surfaceType != SurfaceType::Offscreen || !isDefaultFramebuffer(target))
{
glInvalidateSubFramebuffer(target, numAttachments, attachments, x, y, width, height);
}
else
{
std::vector<GLenum> translatedAttachments =
ConvertDefaultFramebufferEnums(numAttachments, attachments);
glInvalidateSubFramebuffer(target, numAttachments, translatedAttachments.data(), x, y,
width, height);
}
}
void TracePerfTest::onReplayDrawBuffers(GLsizei n, const GLenum *bufs)
{
if (mParams->surfaceType != SurfaceType::Offscreen ||
!isDefaultFramebuffer(GL_DRAW_FRAMEBUFFER))
{
glDrawBuffers(n, bufs);
}
else
{
std::vector<GLenum> translatedBufs = ConvertDefaultFramebufferEnums(n, bufs);
glDrawBuffers(n, translatedBufs.data());
}
}
void TracePerfTest::onReplayReadBuffer(GLenum src)
{
if (mParams->surfaceType != SurfaceType::Offscreen ||
!isDefaultFramebuffer(GL_READ_FRAMEBUFFER))
{
glReadBuffer(src);
}
else
{
GLenum translated = ConvertDefaultFramebufferEnum(src);
glReadBuffer(translated);
}
}
void TracePerfTest::onReplayDiscardFramebufferEXT(GLenum target,
GLsizei numAttachments,
const GLenum *attachments)
{
if (mParams->surfaceType != SurfaceType::Offscreen || !isDefaultFramebuffer(target))
{
glDiscardFramebufferEXT(target, numAttachments, attachments);
}
else
{
std::vector<GLenum> translatedAttachments =
ConvertDefaultFramebufferEnums(numAttachments, attachments);
glDiscardFramebufferEXT(target, numAttachments, translatedAttachments.data());
}
}
void TracePerfTest::saveScreenshotIfEnabled(ScreenshotType screenshotType)
{
if (gScreenshotDir != nullptr && gSaveScreenshots && !mScreenshotSaved &&
(static_cast<uint32_t>(mScreenshotFrame) == mCurrentIteration ||
mScreenshotFrame == kAllFrames))
{
std::stringstream screenshotNameStr;
screenshotNameStr << gScreenshotDir << GetPathSeparator() << "angle" << mBackend << "_"
<< mStory;
// Add a marker to the name for any screenshot that isn't start frame
if (mStartFrame != static_cast<uint32_t>(mScreenshotFrame))
{
if (screenshotType == ScreenshotType::kFrame)
{
screenshotNameStr << "_frame" << mCurrentIteration;
}
else
{
screenshotNameStr << "_grid" << mCurrentOffscreenGridIteration;
}
}
screenshotNameStr << ".png";
std::string screenshotName = screenshotNameStr.str();
saveScreenshot(screenshotName);
// Only set this value if we're capturing a single frame
mScreenshotSaved = mScreenshotFrame != kAllFrames;
}
}
void TracePerfTest::saveScreenshot(const std::string &screenshotName)
{
// The frame is already rendered and is waiting in the default framebuffer.
// RGBA 4-byte data.
uint32_t pixelCount = mTestParams.windowWidth * mTestParams.windowHeight;
std::vector<uint8_t> pixelData(pixelCount * 4);
glFinish();
// Backup the current pixel pack state
GLint originalPackRowLength;
GLint originalPackSkipRows;
GLint originalPackSkipPixels;
GLint originalPackAlignment;
glGetIntegerv(GL_PACK_ROW_LENGTH, &originalPackRowLength);
glGetIntegerv(GL_PACK_SKIP_ROWS, &originalPackSkipRows);
glGetIntegerv(GL_PACK_SKIP_PIXELS, &originalPackSkipPixels);
glGetIntegerv(GL_PACK_ALIGNMENT, &originalPackAlignment);
// Set default pixel pack parameters (per ES 3.2 Table 16.1)
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glPixelStorei(GL_PACK_SKIP_ROWS, 0);
glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
glReadPixels(0, 0, mTestParams.windowWidth, mTestParams.windowHeight, GL_RGBA, GL_UNSIGNED_BYTE,
pixelData.data());
// Restore the original pixel pack state
glPixelStorei(GL_PACK_ROW_LENGTH, originalPackRowLength);
glPixelStorei(GL_PACK_SKIP_ROWS, originalPackSkipRows);
glPixelStorei(GL_PACK_SKIP_PIXELS, originalPackSkipPixels);
glPixelStorei(GL_PACK_ALIGNMENT, originalPackAlignment);
// Convert to RGB and flip y.
std::vector<uint8_t> rgbData(pixelCount * 3);
for (EGLint y = 0; y < mTestParams.windowHeight; ++y)
{
for (EGLint x = 0; x < mTestParams.windowWidth; ++x)
{
EGLint srcPixel = x + y * mTestParams.windowWidth;
EGLint dstPixel = x + (mTestParams.windowHeight - y - 1) * mTestParams.windowWidth;
memcpy(&rgbData[dstPixel * 3], &pixelData[srcPixel * 4], 3);
}
}
if (!angle::SavePNGRGB(screenshotName.c_str(), "ANGLE Screenshot", mTestParams.windowWidth,
mTestParams.windowHeight, rgbData))
{
failTest(std::string("Error saving screenshot: ") + screenshotName);
return;
}
else
{
printf("Saved screenshot: '%s'\n", screenshotName.c_str());
}
}
} // anonymous namespace
using namespace params;
void RegisterTraceTests()
{
GLESDriverType driverType = GetDriverTypeFromString(gUseGL, GLESDriverType::AngleEGL);
GLenum platformType = EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE;
GLenum deviceType = EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE;
if (IsANGLE(driverType))
{
platformType = GetPlatformANGLETypeFromArg(gUseANGLE, EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE);
deviceType =
GetANGLEDeviceTypeFromArg(gUseANGLE, EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE);
}
char rootTracePath[kMaxPath] = {};
if (!FindRootTraceTestDataPath(rootTracePath, kMaxPath))
{
ERR() << "Unable to find trace folder " << rootTracePath;
return;
}
// Load JSON data.
std::vector<std::string> traces;
{
char traceListPath[kMaxPath] = {};
if (!angle::FindTestDataPath("gen/trace_list.json", traceListPath, kMaxPath))
{
ERR() << "Cannot find gen/trace_list.json";
return;
}
if (!LoadTraceNamesFromJSON(traceListPath, &traces))
{
ERR() << "Unable to load traces from JSON file: " << traceListPath;
return;
}
}
std::vector<TraceInfo> traceInfos;
for (const std::string &trace : traces)
{
std::stringstream traceJsonStream;
traceJsonStream << rootTracePath << GetPathSeparator() << trace << GetPathSeparator()
<< trace << ".json";
std::string traceJsonPath = traceJsonStream.str();
TraceInfo traceInfo = {};
strncpy(traceInfo.name, trace.c_str(), kTraceInfoMaxNameLen);
traceInfo.initialized = LoadTraceInfoFromJSON(trace, traceJsonPath, &traceInfo);
traceInfos.push_back(traceInfo);
}
for (const TraceInfo &traceInfo : traceInfos)
{
const TracePerfParams params(traceInfo, driverType, platformType, deviceType);
if (!IsPlatformAvailable(params))
{
continue;
}
std::function<ANGLEPerfTest *()> factory = [params]() -> ANGLEPerfTest * {
if (params.isCL)
{
return CreateTracePerfTestCL(std::make_unique<TracePerfParams>(params));
}
return new TracePerfTest(std::make_unique<TracePerfParams>(params));
};
testing::RegisterTest("TraceTest", traceInfo.name, nullptr, nullptr, __FILE__, __LINE__,
factory);
}
}