mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 12:18:30 +09:00
The captured traces contain per-frame patterns the bench did not exercise, and first measurements show two of them are now the worst remaining multipliers - which is exactly what the missing cases were hiding. mc_pass_switch: the 26.2 snapshot switches render targets 132 times a frame and re-declares draw buffers 198 times. Render-target churn is where a Vulkan backend pays for render-pass breaks and where a tiler pays most on device, and no case measured it. mc_state_toggle: Blaze3D brackets batches with blend toggles - 46 enable/disable pairs and 28 blend-func changes per vanilla frame. mc_tex_param: 26.2 re-sets texture parameters 612 times a frame, almost always to the value already in place, so this measures redundant-parameter filtering. mc_use_program: Sodium switches programs 62 times a frame with a mat4 upload on each, roughly one switch per multi-draw. All four live in the shared case file at the measured per-frame rates, so the desktop harness, the on-device harness and the POST screen's Run Bench report comparable numbers. First desktop measurements (ns/op, native / Espryt / Magma): pass_switch 8877 / 18502 / 13896, state_toggle 1182 / 8526 / 8305, tex_param 42 / 102 / 197, use_program 2182 / 10648 / 5096. The state-toggle multiplier - 7x on both backends - is the largest newly exposed gap and the next optimization target. Unit tests 421/421; the Android JNI translation unit compiles against the extended case set.
502 lines
23 KiB
C
502 lines
23 KiB
C
/* MobileGL - MobileGL/MG_Benchmark/Driver/DriverBench.c
|
|
* Copyright (c) 2025-2026 MobileGL-Dev
|
|
* Licensed under the GNU Lesser General Public License v3.0:
|
|
* https://www.gnu.org/licenses/gpl-3.0.txt
|
|
* https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
* SPDX-License-Identifier: LGPL-3.0-only
|
|
* End of Source File Header
|
|
*
|
|
* Headless, EGL-based driver benchmark shaped like Minecraft's GL usage.
|
|
* Unlike the MobileGL_s microbenches next door this exercises a full GL
|
|
* stack: it dlopens ONE EGL provider ($DRIVERBENCH_EGL_LIB - the system
|
|
* libEGL.so.1 for the native driver, or a libMobileGL.so path for either
|
|
* MobileGL backend selected with MOBILEGL_BACKEND_TYPE), creates a desktop-GL
|
|
* context on a small pbuffer, renders into its own FBO and paces frames with
|
|
* glFinish. No window system is required: the default display is tried first
|
|
* so a desktop run reaches the real driver, and a headless box (CI, a build
|
|
* server) falls back to EGL_MESA_platform_surfaceless - see
|
|
* run_driver_bench.sh.
|
|
*
|
|
* Every case models one hot pattern from captured Minecraft traces:
|
|
* draw_tiny back-to-back glDrawElements, shared state (chunk batch)
|
|
* draw_uniform per-draw vec3 offset uniform + draw (chunk sections)
|
|
* draw_multi_vao per-draw VAO/VBO switch + draw (per-section buffers)
|
|
* tex_pingpong per-draw texture bind churn on one unit
|
|
* program_pingpong alternate two programs + mat4 upload (chunk<->entity)
|
|
* chunk_upload glBufferData(NULL) orphan + glBufferSubData + draw
|
|
* atlas_sprite N 16x16 glTexSubImage2D into a 1024x512 atlas + draw
|
|
* lightmap full 16x16 lightmap respecify per frame + draw
|
|
* scene_mix composite frame built from the knobs below
|
|
*
|
|
* Output: one CSV line per case:
|
|
* case,frames,ops_per_frame,median_frame_ms,ns_per_op,fps
|
|
*/
|
|
#include <dlfcn.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
/* ---- EGL constants ---- */
|
|
typedef void* EGLDisplay;
|
|
typedef void* EGLConfig;
|
|
typedef void* EGLContext;
|
|
typedef void* EGLSurface;
|
|
typedef int EGLint;
|
|
typedef unsigned int EGLBoolean;
|
|
typedef unsigned int EGLenum;
|
|
#define EGL_DEFAULT_DISPLAY ((void*)0)
|
|
#define EGL_NO_CONTEXT ((EGLContext)0)
|
|
#define EGL_NO_SURFACE ((EGLSurface)0)
|
|
#define EGL_FALSE 0
|
|
#define EGL_SURFACE_TYPE 0x3033
|
|
#define EGL_PBUFFER_BIT 0x0001
|
|
#define EGL_RENDERABLE_TYPE 0x3040
|
|
#define EGL_OPENGL_BIT 0x0008
|
|
#define EGL_RED_SIZE 0x3024
|
|
#define EGL_GREEN_SIZE 0x3023
|
|
#define EGL_BLUE_SIZE 0x3022
|
|
#define EGL_DEPTH_SIZE 0x3025
|
|
#define EGL_WIDTH 0x3057
|
|
#define EGL_HEIGHT 0x3056
|
|
#define EGL_NONE 0x3038
|
|
#define EGL_OPENGL_API 0x30A2
|
|
#define EGL_OPENGL_ES_API 0x30A0
|
|
#define EGL_OPENGL_ES3_BIT 0x0040
|
|
#define EGL_CONTEXT_CLIENT_VERSION 0x3098
|
|
#define EGL_CONTEXT_MAJOR_VERSION 0x3098
|
|
#define EGL_CONTEXT_MINOR_VERSION 0x30FB
|
|
#define EGL_CONTEXT_OPENGL_PROFILE_MASK 0x30FD
|
|
#define EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT 0x00000001
|
|
#define EGL_PLATFORM_SURFACELESS_MESA 0x31DD
|
|
|
|
/* ---- GL constants ---- */
|
|
#define GL_COLOR_BUFFER_BIT 0x00004000
|
|
#define GL_DEPTH_BUFFER_BIT 0x00000100
|
|
#define GL_TRIANGLES 0x0004
|
|
#define GL_UNSIGNED_INT 0x1405
|
|
#define GL_SHORT 0x1402
|
|
#define GL_FLOAT 0x1406
|
|
#define GL_UNSIGNED_BYTE 0x1401
|
|
#define GL_ARRAY_BUFFER 0x8892
|
|
#define GL_ELEMENT_ARRAY_BUFFER 0x8893
|
|
#define GL_STATIC_DRAW 0x88E4
|
|
#define GL_TEXTURE_2D 0x0DE1
|
|
#define GL_TEXTURE0 0x84C0
|
|
#define GL_RGBA 0x1908
|
|
#define GL_RGBA8 0x8058
|
|
#define GL_DEPTH_COMPONENT24 0x81A6
|
|
#define GL_TEXTURE_MIN_FILTER 0x2801
|
|
#define GL_TEXTURE_MAG_FILTER 0x2800
|
|
#define GL_NEAREST 0x2600
|
|
#define GL_NEAREST_MIPMAP_LINEAR 0x2702
|
|
#define GL_DEPTH_TEST 0x0B71
|
|
#define GL_BLEND 0x0BE2
|
|
#define GL_SRC_ALPHA 0x0302
|
|
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
|
|
#define GL_ONE 1
|
|
#define GL_ZERO 0
|
|
#define GL_VERTEX_SHADER 0x8B31
|
|
#define GL_FRAGMENT_SHADER 0x8B30
|
|
#define GL_COMPILE_STATUS 0x8B81
|
|
#define GL_LINK_STATUS 0x8B82
|
|
#define GL_VERSION 0x1F02
|
|
#define GL_RENDERER 0x1F01
|
|
#define GL_NO_ERROR 0
|
|
#define GL_FRAMEBUFFER 0x8D40
|
|
#define GL_RENDERBUFFER 0x8D41
|
|
#define GL_COLOR_ATTACHMENT0 0x8CE0
|
|
#define GL_DEPTH_ATTACHMENT 0x8D00
|
|
#define GL_FRAMEBUFFER_COMPLETE 0x8CD5
|
|
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
|
#define GL_SYNC_FLUSH_COMMANDS_BIT 0x00000001
|
|
#define GL_UNIFORM_BUFFER 0x8A11
|
|
#define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34
|
|
#define GL_DYNAMIC_DRAW 0x88E8
|
|
#define GL_STREAM_DRAW 0x88E0
|
|
#define GL_UNPACK_ALIGNMENT 0x0CF5
|
|
#define GL_UNPACK_ROW_LENGTH 0x0CF2
|
|
#define GL_UNPACK_SKIP_ROWS 0x0CF3
|
|
#define GL_UNPACK_SKIP_PIXELS 0x0CF4
|
|
#define GL_TEXTURE_WRAP_S 0x2802
|
|
#define GL_TEXTURE_WRAP_T 0x2803
|
|
#define GL_CLAMP_TO_EDGE 0x812F
|
|
#define GL_REPEAT 0x2901
|
|
|
|
typedef unsigned int GLuint;
|
|
typedef int GLint;
|
|
typedef int GLsizei;
|
|
typedef unsigned int GLenum;
|
|
typedef char GLchar;
|
|
typedef unsigned char GLboolean;
|
|
typedef long GLsizeiptr;
|
|
typedef long GLintptr;
|
|
|
|
/* ---- resolved entry points ---- */
|
|
static void* (*g_eglGetProcAddress)(const char*);
|
|
static void* g_provider;
|
|
|
|
#define GLF(ret, name, args) static ret(*name) args;
|
|
GLF(void, glClear, (unsigned))
|
|
GLF(void, glClearColor, (float, float, float, float))
|
|
GLF(void, glEnable, (GLenum))
|
|
GLF(void, glDisable, (GLenum))
|
|
GLF(void, glBlendFuncSeparate, (GLenum, GLenum, GLenum, GLenum))
|
|
GLF(void, glDrawBuffers, (GLsizei, const GLenum*))
|
|
GLF(void, glViewport, (GLint, GLint, GLsizei, GLsizei))
|
|
GLF(const unsigned char*, glGetString, (GLenum))
|
|
GLF(GLenum, glGetError, (void))
|
|
GLF(void, glFinish, (void))
|
|
GLF(void, glFlush, (void))
|
|
GLF(void, glGenBuffers, (GLsizei, GLuint*))
|
|
GLF(void, glBindBuffer, (GLenum, GLuint))
|
|
GLF(void, glBufferData, (GLenum, GLsizeiptr, const void*, GLenum))
|
|
GLF(void, glBufferSubData, (GLenum, GLintptr, GLsizeiptr, const void*))
|
|
GLF(void, glGenVertexArrays, (GLsizei, GLuint*))
|
|
GLF(void, glBindVertexArray, (GLuint))
|
|
GLF(void, glEnableVertexAttribArray, (GLuint))
|
|
GLF(void, glVertexAttribPointer, (GLuint, GLint, GLenum, GLboolean, GLsizei, const void*))
|
|
GLF(void, glGenTextures, (GLsizei, GLuint*))
|
|
GLF(void, glBindTexture, (GLenum, GLuint))
|
|
GLF(void, glActiveTexture, (GLenum))
|
|
GLF(void, glTexImage2D, (GLenum, GLint, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum, const void*))
|
|
GLF(void, glTexSubImage2D, (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void*))
|
|
GLF(void, glTexParameteri, (GLenum, GLenum, GLint))
|
|
GLF(void, glPixelStorei, (GLenum, GLint))
|
|
GLF(void, glGetIntegerv, (GLenum, GLint*))
|
|
GLF(void, glGenerateMipmap, (GLenum))
|
|
GLF(GLuint, glCreateShader, (GLenum))
|
|
GLF(void, glShaderSource, (GLuint, GLsizei, const GLchar* const*, const GLint*))
|
|
GLF(void, glCompileShader, (GLuint))
|
|
GLF(void, glGetShaderiv, (GLuint, GLenum, GLint*))
|
|
GLF(void, glGetShaderInfoLog, (GLuint, GLsizei, GLsizei*, GLchar*))
|
|
GLF(GLuint, glCreateProgram, (void))
|
|
GLF(void, glAttachShader, (GLuint, GLuint))
|
|
GLF(void, glLinkProgram, (GLuint))
|
|
GLF(void, glGetProgramiv, (GLuint, GLenum, GLint*))
|
|
GLF(void, glUseProgram, (GLuint))
|
|
GLF(GLint, glGetUniformLocation, (GLuint, const GLchar*))
|
|
GLF(void, glUniform1i, (GLint, GLint))
|
|
GLF(void, glUniform3f, (GLint, float, float, float))
|
|
GLF(void, glUniformMatrix4fv, (GLint, GLsizei, GLboolean, const float*))
|
|
GLF(void, glDrawElements, (GLenum, GLsizei, GLenum, const void*))
|
|
GLF(void, glBindAttribLocation, (GLuint, GLuint, const GLchar*))
|
|
GLF(void, glUniform3fv, (GLint, GLsizei, const float*))
|
|
GLF(void, glDrawArrays, (GLenum, GLint, GLsizei))
|
|
GLF(void, glDrawElementsBaseVertex, (GLenum, GLsizei, GLenum, const void*, GLint))
|
|
GLF(void, glMultiDrawElementsBaseVertex,
|
|
(GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*))
|
|
GLF(void, glBindBufferRange, (GLenum, GLuint, GLuint, GLintptr, GLsizeiptr))
|
|
GLF(void, glBindBufferBase, (GLenum, GLuint, GLuint))
|
|
GLF(GLuint, glGetUniformBlockIndex, (GLuint, const GLchar*))
|
|
GLF(void, glUniformBlockBinding, (GLuint, GLuint, GLuint))
|
|
GLF(void, glGenSamplers, (GLsizei, GLuint*))
|
|
GLF(void, glBindSampler, (GLuint, GLuint))
|
|
GLF(void, glSamplerParameteri, (GLuint, GLenum, GLint))
|
|
GLF(void, glGenFramebuffers, (GLsizei, GLuint*))
|
|
GLF(void, glBindFramebuffer, (GLenum, GLuint))
|
|
GLF(void, glGenRenderbuffers, (GLsizei, GLuint*))
|
|
GLF(void, glBindRenderbuffer, (GLenum, GLuint))
|
|
GLF(void, glRenderbufferStorage, (GLenum, GLenum, GLsizei, GLsizei))
|
|
GLF(void, glFramebufferRenderbuffer, (GLenum, GLenum, GLenum, GLuint))
|
|
GLF(GLenum, glCheckFramebufferStatus, (GLenum))
|
|
GLF(void*, glFenceSync, (GLenum, unsigned))
|
|
GLF(GLenum, glClientWaitSync, (void*, unsigned, unsigned long long))
|
|
GLF(void, glDeleteSync, (void*))
|
|
|
|
static uint64_t now_ns(void) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
|
}
|
|
|
|
static int cmp_u64(const void* a, const void* b) {
|
|
uint64_t x = *(const uint64_t*)a, y = *(const uint64_t*)b;
|
|
return x < y ? -1 : x > y;
|
|
}
|
|
|
|
|
|
/* Scene, cases and the case table live next door so the Android plugin's
|
|
* in-process benchmark runs byte-identical bodies. */
|
|
static void bench_gl_failed(const char* what, const char* detail) {
|
|
fprintf(stderr, "FAIL: %s %s\n", what, detail ? detail : "");
|
|
exit(1);
|
|
}
|
|
|
|
/* GLES has glDrawElementsBaseVertex (3.2 core) but no multi-draw form of it, so
|
|
* against a native mobile driver the multi-draw case issues the same sub-draws
|
|
* one at a time - which is what the extension folds up, and what an application
|
|
* without it would have to write. Desktop GL and MobileGL take the real call. */
|
|
static void bench_multi_draw_elements_base_vertex(GLenum mode, const GLsizei* counts, GLenum type,
|
|
const void* const* offsets, GLsizei drawCount,
|
|
const GLint* baseVertices) {
|
|
if (glMultiDrawElementsBaseVertex) {
|
|
glMultiDrawElementsBaseVertex(mode, counts, type, offsets, drawCount, baseVertices);
|
|
return;
|
|
}
|
|
for (GLsizei i = 0; i < drawCount; ++i) {
|
|
glDrawElementsBaseVertex(mode, counts[i], type, offsets[i], baseVertices[i]);
|
|
}
|
|
}
|
|
|
|
#include "DriverBenchCases.inc"
|
|
|
|
/* ---- bench driver: fence-paced frames on the offscreen FBO ----------------
|
|
* Frames are closed with a real fence wait, not glFinish: MobileGL implements
|
|
* glFinish and glFlush as no-ops (MG_Impl/GLImpl/Exporting/Definitions.cpp),
|
|
* so a glFinish-paced loop would time only the CPU-side submit on a MobileGL
|
|
* backend while timing submit-plus-GPU on the native driver - the two numbers
|
|
* would not describe the same work. A sync object is honoured by every stack
|
|
* measured here.
|
|
*/
|
|
typedef void (*case_fn)(int frame, long a, long b);
|
|
static int g_warmup = 30, g_frames = 120;
|
|
|
|
static void end_frame_wait(void) {
|
|
if (glFenceSync && glClientWaitSync && glDeleteSync) {
|
|
void* sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
|
if (sync) {
|
|
glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000ull);
|
|
glDeleteSync(sync);
|
|
return;
|
|
}
|
|
}
|
|
glFinish();
|
|
}
|
|
|
|
static void run_case(const char* name, case_fn body, long a, long b, long opsPerFrame) {
|
|
static uint64_t samples[4096];
|
|
if (g_frames > 4096) g_frames = 4096;
|
|
end_frame_wait();
|
|
for (int i = 0; i < g_warmup; ++i) {
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
body(i, a, b);
|
|
end_frame_wait();
|
|
}
|
|
for (int i = 0; i < g_frames; ++i) {
|
|
uint64_t t0 = now_ns();
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
body(i, a, b);
|
|
end_frame_wait();
|
|
samples[i] = now_ns() - t0;
|
|
}
|
|
qsort(samples, g_frames, sizeof(uint64_t), cmp_u64);
|
|
uint64_t med = samples[g_frames / 2];
|
|
double frameMs = med / 1e6;
|
|
double nsPerOp = opsPerFrame > 0 ? (double)med / (double)opsPerFrame : 0.0;
|
|
printf("%s,%d,%ld,%.3f,%.1f,%.1f\n", name, g_frames, opsPerFrame, frameMs, nsPerOp,
|
|
1e9 / (double)med);
|
|
fflush(stdout);
|
|
if (glGetError() != GL_NO_ERROR) fprintf(stderr, "WARN: GL error after %s\n", name);
|
|
}
|
|
|
|
/* A display that needs no window system. eglGetPlatformDisplay is EGL 1.5
|
|
* core and eglGetPlatformDisplayEXT is the EGL_EXT_platform_base spelling
|
|
* older loaders ship; both are client entry points, so they resolve before
|
|
* any display exists. Only the attribute-list types differ between the two
|
|
* and this passes none, so one cast covers both. */
|
|
static EGLDisplay surfaceless_display(void) {
|
|
void* fn = dlsym(g_provider, "eglGetPlatformDisplay");
|
|
if (!fn) fn = g_eglGetProcAddress("eglGetPlatformDisplay");
|
|
if (!fn) fn = dlsym(g_provider, "eglGetPlatformDisplayEXT");
|
|
if (!fn) fn = g_eglGetProcAddress("eglGetPlatformDisplayEXT");
|
|
if (!fn) return NULL;
|
|
return ((EGLDisplay(*)(EGLenum, void*, const void*))fn)(EGL_PLATFORM_SURFACELESS_MESA,
|
|
EGL_DEFAULT_DISPLAY, NULL);
|
|
}
|
|
|
|
/* ---- EGL bootstrap: one provider library, pbuffer, desktop-GL context ---- */
|
|
static int boot_egl(void) {
|
|
const char* libpath = getenv("DRIVERBENCH_EGL_LIB");
|
|
if (!libpath) libpath = "libEGL.so.1";
|
|
g_provider = dlopen(libpath, RTLD_LAZY | RTLD_LOCAL);
|
|
if (!g_provider) {
|
|
fprintf(stderr, "FAIL: dlopen %s: %s\n", libpath, dlerror());
|
|
return 1;
|
|
}
|
|
#define ESYM(name) \
|
|
void* p_##name = dlsym(g_provider, #name); \
|
|
if (!p_##name) { fprintf(stderr, "FAIL: dlsym %s\n", #name); return 1; }
|
|
ESYM(eglGetDisplay)
|
|
ESYM(eglInitialize)
|
|
ESYM(eglChooseConfig)
|
|
ESYM(eglBindAPI)
|
|
ESYM(eglCreateContext)
|
|
ESYM(eglCreatePbufferSurface)
|
|
ESYM(eglMakeCurrent)
|
|
ESYM(eglGetProcAddress)
|
|
ESYM(eglGetError)
|
|
g_eglGetProcAddress = (void* (*)(const char*))p_eglGetProcAddress;
|
|
|
|
EGLint (*getError)(void) = (EGLint(*)(void))p_eglGetError;
|
|
EGLBoolean (*initialize)(EGLDisplay, EGLint*, EGLint*) =
|
|
(EGLBoolean(*)(EGLDisplay, EGLint*, EGLint*))p_eglInitialize;
|
|
|
|
/* The default display first: it is the one a windowed app would get, and
|
|
* on a desktop it is the one that reaches the real GPU - which is the
|
|
* driver this bench exists to measure. It does need a window system,
|
|
* though; Mesa's default platform is X11, so with no $DISPLAY (CI, a
|
|
* build server, ssh without forwarding) eglInitialize fails. Fall back to
|
|
* EGL_MESA_platform_surfaceless rather than give up: every case draws into
|
|
* the FBO built by build_resources(), so no window is needed for any of
|
|
* the work being timed. */
|
|
EGLint maj = 0, min = 0;
|
|
const char* how = "default display";
|
|
EGLDisplay dpy = ((EGLDisplay(*)(void*))p_eglGetDisplay)(EGL_DEFAULT_DISPLAY);
|
|
if (!dpy || !initialize(dpy, &maj, &min)) {
|
|
dpy = surfaceless_display();
|
|
how = "surfaceless display";
|
|
if (!dpy || !initialize(dpy, &maj, &min)) {
|
|
fprintf(stderr, "FAIL: eglInitialize (0x%x)\n", getError());
|
|
return 1;
|
|
}
|
|
}
|
|
fprintf(stderr, "EGL %d.%d via %s (%s)\n", maj, min, libpath, how);
|
|
|
|
// Desktop GL first (that is what MobileGL exposes and what the cases are
|
|
// written against), GLES 3 second so the same binary can measure a device's
|
|
// native driver as the baseline. The .inc picks ESSL shader sources when the
|
|
// context turns out to be ES.
|
|
EGLBoolean (*chooseConfig)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*) =
|
|
(EGLBoolean(*)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*))p_eglChooseConfig;
|
|
EGLContext (*createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint*) =
|
|
(EGLContext(*)(EGLDisplay, EGLConfig, EGLContext, const EGLint*))p_eglCreateContext;
|
|
EGLBoolean (*bindApi)(EGLenum) = (EGLBoolean(*)(EGLenum))p_eglBindAPI;
|
|
|
|
EGLConfig cfg = NULL;
|
|
EGLint ncfg = 0;
|
|
EGLContext ctx = EGL_NO_CONTEXT;
|
|
|
|
if (bindApi(EGL_OPENGL_API)) {
|
|
const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8,
|
|
EGL_DEPTH_SIZE, 24, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE};
|
|
if (chooseConfig(dpy, cfgAttribs, &cfg, 1, &ncfg) && ncfg >= 1) {
|
|
const EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 2,
|
|
EGL_CONTEXT_OPENGL_PROFILE_MASK,
|
|
EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE};
|
|
ctx = createContext(dpy, cfg, EGL_NO_CONTEXT, ctxAttribs);
|
|
if (ctx == EGL_NO_CONTEXT) ctx = createContext(dpy, cfg, EGL_NO_CONTEXT, NULL);
|
|
}
|
|
}
|
|
if (ctx == EGL_NO_CONTEXT) {
|
|
if (!bindApi(EGL_OPENGL_ES_API)) {
|
|
fprintf(stderr, "FAIL: neither OpenGL nor OpenGL ES is bindable on this provider\n");
|
|
return 1;
|
|
}
|
|
const EGLint esCfgAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24,
|
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE};
|
|
ncfg = 0;
|
|
if (!chooseConfig(dpy, esCfgAttribs, &cfg, 1, &ncfg) || ncfg < 1) {
|
|
// EGL_SURFACE_TYPE 0 matches any config: a stack that offers no
|
|
// pbuffer at all is still usable through the surfaceless context
|
|
// path below.
|
|
const EGLint relaxed[] = {EGL_SURFACE_TYPE, 0, EGL_RED_SIZE, 8, EGL_NONE};
|
|
if (!chooseConfig(dpy, relaxed, &cfg, 1, &ncfg) || ncfg < 1) {
|
|
fprintf(stderr, "FAIL: eglChooseConfig\n");
|
|
return 1;
|
|
}
|
|
}
|
|
const EGLint esCtxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
|
|
ctx = createContext(dpy, cfg, EGL_NO_CONTEXT, esCtxAttribs);
|
|
}
|
|
if (ctx == EGL_NO_CONTEXT) {
|
|
fprintf(stderr, "FAIL: eglCreateContext (0x%x)\n", getError());
|
|
return 1;
|
|
}
|
|
|
|
/* The pbuffer only exists to have something to make current - nothing is
|
|
* ever drawn to it. Where there is no pbuffer config, EGL_NO_SURFACE is
|
|
* exactly what EGL_KHR_surfaceless_context takes, so the same call covers
|
|
* both. */
|
|
const EGLint pbAttribs[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE};
|
|
EGLSurface surf = ((EGLSurface(*)(EGLDisplay, EGLConfig, const EGLint*))p_eglCreatePbufferSurface)(
|
|
dpy, cfg, pbAttribs);
|
|
if (surf == EGL_NO_SURFACE)
|
|
fprintf(stderr, "no pbuffer (0x%x), using a surfaceless context\n", getError());
|
|
if (!((EGLBoolean(*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))p_eglMakeCurrent)(dpy, surf,
|
|
surf, ctx)) {
|
|
fprintf(stderr, "FAIL: eglMakeCurrent (0x%x)\n", getError());
|
|
return 1;
|
|
}
|
|
|
|
/* Core GL entry points: eglGetProcAddress first (EGL 1.5 serves core
|
|
* functions), provider dlsym as fallback (both glvnd and MobileGL export
|
|
* the gl* symbols directly). */
|
|
#define RESOLVE(name) \
|
|
do { \
|
|
*(void**)&name = g_eglGetProcAddress(#name); \
|
|
if (!name) *(void**)&name = dlsym(g_provider, #name); \
|
|
if (!name) { fprintf(stderr, "FAIL: resolve %s\n", #name); return 1; } \
|
|
} while (0)
|
|
RESOLVE(glClear); RESOLVE(glClearColor); RESOLVE(glEnable); RESOLVE(glViewport);
|
|
RESOLVE(glDisable); RESOLVE(glBlendFuncSeparate); RESOLVE(glDrawBuffers);
|
|
RESOLVE(glGetString); RESOLVE(glGetError); RESOLVE(glFinish); RESOLVE(glFlush);
|
|
RESOLVE(glGenBuffers); RESOLVE(glBindBuffer); RESOLVE(glBufferData); RESOLVE(glBufferSubData);
|
|
RESOLVE(glGenVertexArrays); RESOLVE(glBindVertexArray); RESOLVE(glEnableVertexAttribArray);
|
|
RESOLVE(glVertexAttribPointer); RESOLVE(glGenTextures); RESOLVE(glBindTexture);
|
|
RESOLVE(glActiveTexture); RESOLVE(glTexImage2D); RESOLVE(glTexSubImage2D);
|
|
RESOLVE(glTexParameteri); RESOLVE(glGenerateMipmap); RESOLVE(glCreateShader);
|
|
RESOLVE(glPixelStorei); RESOLVE(glGetIntegerv);
|
|
RESOLVE(glShaderSource); RESOLVE(glCompileShader); RESOLVE(glGetShaderiv);
|
|
RESOLVE(glGetShaderInfoLog); RESOLVE(glCreateProgram); RESOLVE(glAttachShader);
|
|
RESOLVE(glLinkProgram); RESOLVE(glGetProgramiv); RESOLVE(glUseProgram);
|
|
RESOLVE(glGetUniformLocation); RESOLVE(glUniform1i); RESOLVE(glUniform3f);
|
|
RESOLVE(glUniformMatrix4fv); RESOLVE(glDrawElements); RESOLVE(glBindAttribLocation);
|
|
RESOLVE(glUniform3fv); RESOLVE(glDrawArrays); RESOLVE(glDrawElementsBaseVertex);
|
|
RESOLVE(glBindBufferRange); RESOLVE(glBindBufferBase);
|
|
RESOLVE(glGetUniformBlockIndex); RESOLVE(glUniformBlockBinding);
|
|
RESOLVE(glGenSamplers); RESOLVE(glBindSampler); RESOLVE(glSamplerParameteri);
|
|
RESOLVE(glGenFramebuffers); RESOLVE(glBindFramebuffer); RESOLVE(glGenRenderbuffers);
|
|
RESOLVE(glBindRenderbuffer); RESOLVE(glRenderbufferStorage); RESOLVE(glFramebufferRenderbuffer);
|
|
RESOLVE(glCheckFramebufferStatus);
|
|
// Optional: end_frame_wait() falls back to glFinish when a stack has no
|
|
// sync objects, so resolve without failing the run.
|
|
*(void**)&glFenceSync = g_eglGetProcAddress("glFenceSync");
|
|
if (!glFenceSync) *(void**)&glFenceSync = dlsym(g_provider, "glFenceSync");
|
|
*(void**)&glClientWaitSync = g_eglGetProcAddress("glClientWaitSync");
|
|
if (!glClientWaitSync) *(void**)&glClientWaitSync = dlsym(g_provider, "glClientWaitSync");
|
|
*(void**)&glDeleteSync = g_eglGetProcAddress("glDeleteSync");
|
|
if (!glDeleteSync) *(void**)&glDeleteSync = dlsym(g_provider, "glDeleteSync");
|
|
// Desktop-only: GLES 3.2 has DrawElementsBaseVertex but no multi-draw form,
|
|
// so bench_multi_draw_elements_base_vertex() emulates it when this is null.
|
|
*(void**)&glMultiDrawElementsBaseVertex = g_eglGetProcAddress("glMultiDrawElementsBaseVertex");
|
|
if (!glMultiDrawElementsBaseVertex)
|
|
*(void**)&glMultiDrawElementsBaseVertex = dlsym(g_provider, "glMultiDrawElementsBaseVertex");
|
|
|
|
fprintf(stderr, "renderer: %s\n", glGetString(GL_RENDERER));
|
|
fprintf(stderr, "version: %s\n", glGetString(GL_VERSION));
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
long draws = 2048;
|
|
if (getenv("DRIVERBENCH_DRAWS")) draws = atol(getenv("DRIVERBENCH_DRAWS"));
|
|
if (getenv("DRIVERBENCH_FRAMES")) g_frames = atoi(getenv("DRIVERBENCH_FRAMES"));
|
|
if (getenv("DRIVERBENCH_SPRITES")) g_mixSprites = atol(getenv("DRIVERBENCH_SPRITES"));
|
|
|
|
if (boot_egl()) return 1;
|
|
build_resources();
|
|
|
|
printf("case,frames,ops_per_frame,median_frame_ms,ns_per_op,fps\n");
|
|
for (int i = 0; i < kBenchCaseCount; ++i) {
|
|
const BenchCaseDesc* c = &kBenchCases[i];
|
|
if (argc > 1) {
|
|
int wanted = 0;
|
|
for (int j = 1; j < argc; ++j)
|
|
if (strcmp(argv[j], c->name) == 0) wanted = 1;
|
|
if (!wanted) continue;
|
|
}
|
|
// The generic cases scale with DRIVERBENCH_DRAWS; the mc_* rates are
|
|
// measured and must not move, or the numbers stop being comparable.
|
|
long a = c->a, ops = c->opsPerFrame;
|
|
if (strncmp(c->name, "mc_", 3) != 0 && a > 100) {
|
|
a = draws * a / 2048;
|
|
ops = c->opsPerFrame * draws / 2048;
|
|
}
|
|
run_case(c->name, c->fn, a, c->b, ops);
|
|
}
|
|
return 0;
|
|
}
|