mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-12-01 04:23:22 +01:00
400 lines
8.6 KiB
C++
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;
|
|
}
|