blob: 0cdf703b8e38f7b0d8734633e4ee3ed403e26dd9 [file] [log] [blame] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#define WAFFLE_API_VERSION 0x0106
#include <chrono>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <waffle.h>
#include "platform.h"
#include "workload.h"
#if THIS_IS(PLATFORM_GLX)
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "waffle_glx.h"
#define PLATFORM_TYPE WAFFLE_PLATFORM_GLX
#elif THIS_IS(PLATFORM_NULL)
#include "waffle_null.h"
#define PLATFORM_TYPE WAFFLE_PLATFORM_NULL
#elif THIS_IS(PLATFORM_X11_EGL)
#include "waffle_x11_egl.h"
#define PLATFORM_TYPE WAFFLE_PLATFORM_X11_EGL
#endif
using namespace std::chrono;
#if defined(USE_OPENGLES)
#define PLATFORM_API WAFFLE_CONTEXT_OPENGL_ES2
const std::string kVertexHeader = R"STRING(
#version 320 es
)STRING";
const std::string kFragmentHeader = R"STRING(
#version 320 es
precision mediump float;
)STRING";
#else
#define _NET_WM_STATE_REMOVE 0
#define _NET_WM_STATE_ADD 1
#define _NET_WM_STATE_TOGGLE 2
#define PLATFORM_API WAFFLE_CONTEXT_OPENGL
const std::string kVertexHeader = R"STRING(
#version 430 core
)STRING";
const std::string kFragmentHeader = R"STRING(
#version 430 core
)STRING";
#endif
const std::string kVertexShader = R"STRING(
layout (location = 0) in vec2 pos;
layout (location = 1) uniform float mod_size;
layout (location = 2) uniform float line_idx;
layout (location = 3) uniform float start_size;
out vec2 instance_color;
out float blue_intensity;
void main()
{
float gli = float(gl_InstanceID);
float h = gli / mod_size;
float l = mod(gli, mod_size);
float vx = 0.5f - l / mod_size;
float vy = 0.5f - h / mod_size;
float a = mod(line_idx, mod_size);
blue_intensity = mod_size / start_size;
if (blue_intensity >= 1.0f) {
blue_intensity = 1.0f;
}
if (abs(a - l) < mod_size * 0.05) {
instance_color = vec2(1, 1);
} else {
instance_color = vec2(vx + 0.5f, vy + 0.5f);
blue_intensity = 0.4f;
}
gl_Position =
vec4(pos.x - 2.0f * vx, pos.y + 2.0f * vy, 0, 1.0f);
}
)STRING";
const std::string kFragmentShader = R"STRING(
in vec2 instance_color;
in float blue_intensity;
out vec4 frag_color;
void main()
{
frag_color = vec4(instance_color.x, instance_color.y, blue_intensity, 1.0f);
}
)STRING";
unsigned int CreateShader(int* error) {
// Build and compile our shader programs.
// Vertex shader.
unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
auto temp = kVertexHeader + kVertexShader;
const char *full_vertex = temp.data();
glShaderSource(vertex_shader, 1, &full_vertex, NULL);
glCompileShader(vertex_shader);
// check for shader compile errors
int success;
char info_log[512];
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertex_shader, 512, NULL, info_log);
std::cout << "Error: Vertex shader compilation failed." << info_log
<< std::endl;
}
// Fragment shader.
unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
temp = kFragmentHeader + kFragmentShader;
const char *full_fragment = temp.data();
glShaderSource(fragment_shader, 1, &full_fragment, NULL);
glCompileShader(fragment_shader);
// check for shader compile errors
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragment_shader, 512, NULL, info_log);
std::cout << "Error: Fragment shader compilation failed." << info_log
<< std::endl;
}
// link shaders
unsigned int shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
// check for linking errors
glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
if (!success) {
glGetProgramInfoLog(shader_program, 512, NULL, info_log);
std::cout << "Error: Shader linking failed." << info_log << std::endl;
*error = -1;
}
return shader_program;
}
void CreateContext(
struct waffle_display **display,
struct waffle_config **config,
struct waffle_window **window,
struct waffle_context **ctx,
int swap_interval,
int window_width,
int window_height,
bool fullscreen) {
const int32_t init_attrs[] = {
WAFFLE_PLATFORM, PLATFORM_TYPE,
0,
};
const int32_t config_attrs[] = {
WAFFLE_CONTEXT_API, PLATFORM_API,
WAFFLE_RED_SIZE, 8,
WAFFLE_GREEN_SIZE, 8,
WAFFLE_BLUE_SIZE, 8,
WAFFLE_ALPHA_SIZE, 8,
WAFFLE_DEPTH_SIZE, 8,
WAFFLE_STENCIL_SIZE, 8,
WAFFLE_DOUBLE_BUFFERED, true,
0};
waffle_init(init_attrs);
*display = waffle_display_connect(NULL);
*config = waffle_config_choose(*display, config_attrs);
if (fullscreen) {
const intptr_t attrib[] = {WAFFLE_WINDOW_FULLSCREEN, 1, 0};
*window = waffle_window_create2(*config, attrib);
#if THIS_IS(PLATFORM_GLX)
struct waffle_glx_window *window_glx =
waffle_window_get_native(*window)->glx;
Display *display_x11 = window_glx->xlib_display;
Window window_x11 = (Window) window_glx->xlib_window;
XEvent ev;
Atom atom;
ev.type = ClientMessage;
ev.xclient.window = window_x11;
ev.xclient.send_event = True;
ev.xclient.message_type = XInternAtom(display_x11, "_NET_WM_STATE", False);
ev.xclient.format = 32;
ev.xclient.data.l[0] = _NET_WM_STATE_ADD;
atom = XInternAtom(display_x11, "_NET_WM_STATE_FULLSCREEN", False);
ev.xclient.data.l[1] = atom;
ev.xclient.data.l[2] = 0;
ev.xclient.data.l[3] = 1;
XSendEvent(display_x11, DefaultRootWindow(display_x11), False,
SubstructureNotifyMask | SubstructureRedirectMask, &ev);
#endif
} else {
*window = waffle_window_create(*config, window_width, window_height);
}
waffle_window_show(*window);
*ctx = waffle_context_create(*config, NULL);
waffle_make_current(*display, *window, *ctx);
union waffle_native_display *native_display =
waffle_display_get_native(*display);
#if THIS_IS(PLATFORM_GLX)
glXSwapIntervalEXT(native_display->glx->xlib_display,
glXGetCurrentDrawable(), swap_interval);
#elif THIS_IS(PLATFORM_NULL)
eglSwapInterval(native_display->null->egl_display, swap_interval);
#elif THIS_IS(PLATFORM_X11_EGL)
eglSwapInterval(native_display->x11_egl->egl_display, swap_interval);
#endif
}
int main(int argc, char* argv[]) {
// TODO(mrfemi): parse workload scale from command line. At least as a
// parameter. (0.99f)
bool vsync = true;
bool fullscreen = false;
bool target_refresh_rate = true;
int total_frames = 240;
int sleep_cpu_milliseconds = 0;
int width = 1280;
int height = 720;
double display_scale = 1.0;
double gpu_workload_ms = 1000 / 60.0;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--vsync") {
vsync = true;
} else if (arg == "--no-vsync") {
vsync = false;
} else if (arg == "--fullscreen") {
fullscreen = true;
} else if (arg == "--no-fullscreen") {
fullscreen = false;
} else if (arg == "--target-refresh-rate") {
target_refresh_rate = true;
} else if (arg == "--no-target-refresh-rate") {
target_refresh_rate = false;
} else if (arg == "--sleep-cpu-milliseconds" && (i + 1) < argc) {
sleep_cpu_milliseconds = std::stod(argv[i + 1]);
++i;
} else if (arg == "--total-frames" && (i + 1) < argc) {
total_frames = std::stod(argv[i + 1]);
++i;
} else if (arg == "--width" && (i + 1) < argc) {
width = std::stoi(argv[i + 1]);
++i;
} else if (arg == "--height" && (i + 1) < argc) {
height = std::stoi(argv[i + 1]);
++i;
} else if (arg == "--display-scale" && (i + 1) < argc) {
display_scale = std::stod(argv[i + 1]);
++i;
} else if (arg == "--gpu-workload-ms" && (i + 1) < argc) {
gpu_workload_ms = std::stod(argv[i + 1]);
++i;
} else {
std::cout << "Error: Parsing command line - check args: "
<< arg << std::endl;
return -1;
}
}
std::cout << "Settings: "
<< "\nvsync=" << (vsync ? "True" : "False")
<< "\nwidth=" << width
<< "\nheight=" << height
<< "\ntotal_frames=" << total_frames
<< "\nfullscreen=" << (fullscreen ? "True" : "False")
<< "\ntarget_refresh_rate=" << (target_refresh_rate ?
"True" : "False")
<< "\nsleep_cpu_milliseconds=" << sleep_cpu_milliseconds
<< "\ndisplay_scale=" << display_scale
<< "\ngpu_workload_ms=" << gpu_workload_ms
<< std::endl;
// Context creation.
struct waffle_display *display;
struct waffle_config *config;
struct waffle_window *window;
struct waffle_context *ctx;
// Enable or disable vsync
int swap_interval = vsync ? 1 : 0;
width = static_cast<int>(floor(width * display_scale));
height = static_cast<int>(floor(height * display_scale));
CreateContext(&display, &config, &window, &ctx, swap_interval, width, height,
fullscreen);
int error = 0;
auto shader_program = CreateShader(&error);
std::cout << "Platform OpenGL version: "
<< glGetString(GL_VERSION) << std::endl;
if (error < 0)
return -1;
glUseProgram(shader_program);
// String format floating points to 4 decimal places.
std::cout << std::fixed << std::setprecision(4);
std::vector<double> cpu_times(total_frames);
// Set up workload class.
auto workload = new Workload();
if(!workload->Initialize(total_frames))
return -1;
// Check if we should target the display refresh rate.
if (target_refresh_rate) {
waffle_window_swap_buffers(window);
auto start_time = steady_clock::now();
int num_swaps = 0;
while (num_swaps < 60) {
++num_swaps;
workload->Draw();
waffle_window_swap_buffers(window);
}
auto current_time = steady_clock::now();
auto total_time = duration<double, std::milli>(
current_time - start_time).count();
gpu_workload_ms = total_time / num_swaps;
std::cout << "Detecting refresh rate of " << gpu_workload_ms << " ms.\n"
<< std::endl;
}
// Adjust the workload so the gpu spends gpu_workload_ms rendering a frame.
workload->CalibrateWorkload(gpu_workload_ms);
waffle_window_swap_buffers(window);
waffle_window_swap_buffers(window);
waffle_window_swap_buffers(window);
// Display loop.
auto first_time = steady_clock::now();
auto prev_time = first_time;
waffle_window_swap_buffers(window);
for (int frame = 0; frame < total_frames; ++frame) {
auto current_time = steady_clock::now();
cpu_times[frame] =
duration<double, std::milli>(current_time - prev_time).count();
prev_time = current_time;
if (cpu_times[frame] > gpu_workload_ms * 1.5) {
// TODO(mrfemi): consider this a dropped frame and mark somehow
// But this can also be processed from the logs.
workload->DecreaseWorkload(/*record_quads=*/true);
}
// Do not bother drawing if sleep_cpu_milliseconds has been set.
if (sleep_cpu_milliseconds == 0) {
workload->StartTimer(frame);
workload->Draw();
workload->EndTimer();
} else {
std::this_thread::sleep_for(milliseconds(sleep_cpu_milliseconds));
}
waffle_window_swap_buffers(window);
}
auto current_time = steady_clock::now();
auto total_time = duration<double>(
current_time - first_time).count();
// Print performance numbers to stdout.
workload->PrintQuadHistory();
for (int i = 0; i < total_frames; ++i) {
std::cout << "frame " << i << ": "
<< "cpu_elapsed_time: " << cpu_times[i] << " ms" << std::endl;
if (sleep_cpu_milliseconds > 0) {
std::cout << "frame sleep: "
<< sleep_cpu_milliseconds << " ms" << std::endl;
continue;
}
auto gpu_elapsed_time = workload->GetFrameGpuTime(i);
std::cout << "frame " << i << ": "
<< "gpu_elapsed_time: " << gpu_elapsed_time << " ms" << std::endl;
}
workload->TakeDown();
std::cout << "Avg: " << total_frames / total_time << " fps for "
<< total_time << " secs. "
<< total_frames << " frames rendered." << std::endl;
return 0;
}