KasmVNC/tests/fbperf.cxx
2020-09-20 12:16:44 +00:00

400 lines
8.6 KiB
C++

/* Copyright 2016 Pierre Ossman <ossman@cendio.se> for Cendio AB
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <math.h>
#include <sys/time.h>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/fl_draw.H>
#include <rdr/Exception.h>
#include <rfb/util.h>
#include "../vncviewer/PlatformPixelBuffer.h"
#include "util.h"
class TestWindow: public Fl_Window {
public:
TestWindow();
~TestWindow();
virtual void start(int width, int height);
virtual void stop();
virtual void draw();
protected:
virtual void flush();
void update();
virtual void changefb();
static void timer(void* data);
public:
unsigned long long pixels, frames;
double time;
protected:
PlatformPixelBuffer* fb;
};
class PartialTestWindow: public TestWindow {
protected:
virtual void changefb();
};
class OverlayTestWindow: public PartialTestWindow {
public:
OverlayTestWindow();
virtual void start(int width, int height);
virtual void stop();
virtual void draw();
protected:
Surface* overlay;
Surface* offscreen;
};
TestWindow::TestWindow() :
Fl_Window(0, 0, "Framebuffer Performance Test"),
fb(NULL)
{
}
TestWindow::~TestWindow()
{
stop();
}
void TestWindow::start(int width, int height)
{
rdr::U32 pixel;
stop();
resize(x(), y(), width, height);
pixels = 0;
frames = 0;
time = 0;
fb = new PlatformPixelBuffer(w(), h());
pixel = 0;
fb->fillRect(fb->getRect(), &pixel);
show();
}
void TestWindow::stop()
{
hide();
delete fb;
fb = NULL;
Fl::remove_idle(timer, this);
}
void TestWindow::draw()
{
int X, Y, W, H;
// We cannot update the damage region from inside the draw function,
// so delegate this to an idle function
Fl::add_idle(timer, this);
// Check what actually needs updating
fl_clip_box(0, 0, w(), h(), X, Y, W, H);
if ((W == 0) || (H == 0))
return;
fb->draw(X, Y, X, Y, W, H);
pixels += W*H;
frames++;
}
void TestWindow::flush()
{
startTimeCounter();
Fl_Window::flush();
#if !defined(WIN32) && !defined(__APPLE__)
// Make sure we measure any work we queue up
XSync(fl_display, False);
#endif
endTimeCounter();
time += getTimeCounter();
}
void TestWindow::update()
{
rfb::Rect r;
startTimeCounter();
changefb();
r = fb->getDamage();
damage(FL_DAMAGE_USER1, r.tl.x, r.tl.y, r.width(), r.height());
#if !defined(WIN32) && !defined(__APPLE__)
// Make sure we measure any work we queue up
XSync(fl_display, False);
#endif
endTimeCounter();
time += getTimeCounter();
}
void TestWindow::changefb()
{
rdr::U32 pixel;
pixel = rand();
fb->fillRect(fb->getRect(), &pixel);
}
void TestWindow::timer(void* data)
{
TestWindow* self;
Fl::remove_idle(timer, data);
self = (TestWindow*)data;
self->update();
}
void PartialTestWindow::changefb()
{
rfb::Rect r;
rdr::U32 pixel;
r = fb->getRect();
r.tl.x += w() / 4;
r.tl.y += h() / 4;
r.br.x -= w() / 4;
r.br.y -= h() / 4;
pixel = rand();
fb->fillRect(r, &pixel);
}
OverlayTestWindow::OverlayTestWindow() :
overlay(NULL), offscreen(NULL)
{
}
void OverlayTestWindow::start(int width, int height)
{
PartialTestWindow::start(width, height);
overlay = new Surface(400, 200);
overlay->clear(0xff, 0x80, 0x00, 0xcc);
// X11 needs an off screen buffer for compositing to avoid flicker,
// and alpha blending doesn't work for windows on Win32
#if !defined(__APPLE__)
offscreen = new Surface(w(), h());
#else
offscreen = NULL;
#endif
}
void OverlayTestWindow::stop()
{
PartialTestWindow::stop();
delete offscreen;
offscreen = NULL;
delete overlay;
overlay = NULL;
}
void OverlayTestWindow::draw()
{
int ox, oy, ow, oh;
int X, Y, W, H;
// We cannot update the damage region from inside the draw function,
// so delegate this to an idle function
Fl::add_idle(timer, this);
// Check what actually needs updating
fl_clip_box(0, 0, w(), h(), X, Y, W, H);
if ((W == 0) || (H == 0))
return;
// We might get a redraw before we are fully ready
if (!overlay)
return;
// Simplify the clip region to a simple rectangle in order to
// properly draw all the layers even if they only partially overlap
fl_push_no_clip();
fl_push_clip(X, Y, W, H);
if (offscreen)
fb->draw(offscreen, X, Y, X, Y, W, H);
else
fb->draw(X, Y, X, Y, W, H);
pixels += W*H;
frames++;
ox = (w() - overlay->width()) / 2;
oy = h() / 4 - overlay->height() / 2;
ow = overlay->width();
oh = overlay->height();
fl_clip_box(ox, oy, ow, oh, X, Y, W, H);
if ((W != 0) && (H != 0)) {
if (offscreen)
overlay->blend(offscreen, X - ox, Y - oy, X, Y, W, H);
else
overlay->blend(X - ox, Y - oy, X, Y, W, H);
}
fl_pop_clip();
fl_pop_clip();
if (offscreen) {
fl_clip_box(0, 0, w(), h(), X, Y, W, H);
offscreen->draw(X, Y, X, Y, W, H);
}
}
static void dosubtest(TestWindow* win, int width, int height,
unsigned long long* pixels,
unsigned long long* frames,
double* time)
{
struct timeval start;
win->start(width, height);
gettimeofday(&start, NULL);
while (rfb::msSince(&start) < 3000)
Fl::wait();
win->stop();
*pixels = win->pixels;
*frames = win->frames;
*time = win->time;
}
static bool is_constant(double a, double b)
{
return (fabs(a - b) / a) < 0.1;
}
static void dotest(TestWindow* win)
{
unsigned long long pixels[3];
unsigned long long frames[3];
double time[3];
double delay, rate;
char s[1024];
// Run the test several times at different resolutions...
dosubtest(win, 800, 600, &pixels[0], &frames[0], &time[0]);
dosubtest(win, 1024, 768, &pixels[1], &frames[1], &time[1]);
dosubtest(win, 1280, 960, &pixels[2], &frames[2], &time[2]);
// ...in order to compute how much of the rendering time is static,
// and how much depends on the number of pixels
// (i.e. solve: time = delay * frames + rate * pixels)
delay = (((time[0] - (double)pixels[0] / pixels[1] * time[1]) /
(frames[0] - (double)pixels[0] / pixels[1] * frames[1])) +
((time[1] - (double)pixels[1] / pixels[2] * time[2]) /
(frames[1] - (double)pixels[1] / pixels[2] * frames[2]))) / 2.0;
rate = (((time[0] - (double)frames[0] / frames[1] * time[1]) /
(pixels[0] - (double)frames[0] / frames[1] * pixels[1])) +
((time[1] - (double)frames[1] / frames[2] * time[2]) /
(pixels[1] - (double)frames[1] / frames[2] * pixels[2]))) / 2.0;
// However, we have some corner cases:
// We are restricted by some delay, e.g. refresh rate
if (is_constant(frames[0]/time[0], frames[2]/time[2])) {
fprintf(stderr, "WARNING: Fixed delay dominating updates.\n\n");
delay = time[2]/frames[2];
rate = 0.0;
}
// There isn't any fixed delay, we are only restricted by pixel
// throughput
if (fabs(delay) < 0.001) {
delay = 0.0;
rate = time[2]/pixels[2];
}
// We can hit cache limits that causes performance to drop
// with increasing update size, screwing up our calculations
if ((pixels[2] / time[2]) < (pixels[0] / time[0] * 0.9)) {
fprintf(stderr, "WARNING: Unexpected behaviour. Measurement unreliable.\n\n");
// We can't determine the proportions between these, so divide the
// time spent evenly
delay = time[2] / 2.0 / frames[2];
rate = time[2] / 2.0 / pixels[2];
}
fprintf(stderr, "Rendering delay: %g ms/frame\n", delay * 1000.0);
if (rate == 0.0)
strcpy(s, "N/A pixels/s");
else
rfb::siPrefix(1.0 / rate, "pixels/s", s, sizeof(s));
fprintf(stderr, "Rendering rate: %s\n", s);
fprintf(stderr, "Maximum FPS: %g fps @ 1920x1080\n",
1.0 / (delay + rate * 1920 * 1080));
}
int main(int argc, char** argv)
{
TestWindow* win;
fprintf(stderr, "Full window update:\n\n");
win = new TestWindow();
dotest(win);
delete win;
fprintf(stderr, "\n");
fprintf(stderr, "Partial window update:\n\n");
win = new PartialTestWindow();
dotest(win);
delete win;
fprintf(stderr, "\n");
fprintf(stderr, "Partial window update with overlay:\n\n");
win = new OverlayTestWindow();
dotest(win);
delete win;
fprintf(stderr, "\n");
return 0;
}