diff --git a/utils/testpat.cpp b/utils/testpat.cpp
index 5f1095cb62cf7da2dccbcb284c35d599df2a5d89..31029ad27819f257d79b87206ef3efa0aabbc97a 100644 (file)
--- a/utils/testpat.cpp
+++ b/utils/testpat.cpp
#include <algorithm>
#include <regex>
#include <set>
+#include <chrono>
#include <kms++.h>
#include <modedb.h>
Connector* connector;
Crtc* crtc;
+ Plane* primary_plane;
Videomode mode;
bool user_set_crtc;
vector<DumbFramebuffer*> fbs;
static bool s_use_dmt;
static bool s_use_cea;
static unsigned s_num_buffers = 1;
+static bool s_flip_mode;
+static bool s_flip_sync;
static set<Crtc*> s_used_crtcs;
static set<Plane*> s_used_planes;
" -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
" --dmt Search for the given mode from DMT tables\n"
" --cea Search for the given mode from CEA tables\n"
+ " --flip Do page flipping for each output\n"
+ " --sync Synchronize page flipping\n"
"\n"
"<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
"<connector> can also be given by name.\n"
{
s_use_cea = true;
}),
+ Option("|flip", []()
+ {
+ s_flip_mode = true;
+ s_num_buffers = 2;
+ }),
+ Option("|sync", []()
+ {
+ s_flip_sync = true;
+ }),
Option("h|help", [&]()
{
usage();
printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
o.connector->fullname().c_str());
- printf(" Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
+ printf(" Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
+ if (o.primary_plane)
+ printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
+ printf(": %ux%u-%u (%s)\n",
o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
videomode_to_string(o.mode).c_str());
if (!o.fbs.empty()) {
auto fb = o.fbs[0];
- printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
+ printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
PixelFormatToFourCC(fb->format()).c_str());
}
auto fb = p.fbs[0];
printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
p.x, p.y, p.w, p.h);
- printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
+ printf(" Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
PixelFormatToFourCC(fb->format()).c_str());
}
}
}
}
-static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
+static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
{
for (const OutputInfo& o : outputs) {
auto conn = o.connector;
@@ -610,21 +628,294 @@ static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
}
}
+static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
+{
+ // Keep blobs here so that we keep ref to them until we have committed the req
+ vector<unique_ptr<Blob>> blobs;
+
+ AtomicReq req(card);
+
+ for (const OutputInfo& o : outputs) {
+ auto conn = o.connector;
+ auto crtc = o.crtc;
+
+ if (!o.fbs.empty()) {
+ auto fb = o.fbs[0];
+
+ blobs.emplace_back(o.mode.to_blob(card));
+ Blob* mode_blob = blobs.back().get();
+
+ req.add(conn, {
+ { "CRTC_ID", crtc->id() },
+ });
+
+ req.add(crtc, {
+ { "ACTIVE", 1 },
+ { "MODE_ID", mode_blob->id() },
+ });
+
+ req.add(o.primary_plane, {
+ { "FB_ID", fb->id() },
+ { "CRTC_ID", crtc->id() },
+ { "SRC_X", 0 << 16 },
+ { "SRC_Y", 0 << 16 },
+ { "SRC_W", fb->width() << 16 },
+ { "SRC_H", fb->height() << 16 },
+ { "CRTC_X", 0 },
+ { "CRTC_Y", 0 },
+ { "CRTC_W", fb->width() },
+ { "CRTC_H", fb->height() },
+ });
+ }
+
+ for (const PlaneInfo& p : o.planes) {
+ auto fb = p.fbs[0];
+
+ req.add(p.plane, {
+ { "FB_ID", fb->id() },
+ { "CRTC_ID", crtc->id() },
+ { "SRC_X", 0 << 16 },
+ { "SRC_Y", 0 << 16 },
+ { "SRC_W", fb->width() << 16 },
+ { "SRC_H", fb->height() << 16 },
+ { "CRTC_X", p.x },
+ { "CRTC_Y", p.y },
+ { "CRTC_W", p.w },
+ { "CRTC_H", p.h },
+ });
+ }
+ }
+
+ int r;
+
+ r = req.test(true);
+ if (r)
+ EXIT("Atomic test failed: %d\n", r);
+
+ r = req.commit_sync(true);
+ if (r)
+ EXIT("Atomic commit failed: %d\n", r);
+}
+
+class FlipState : private PageFlipHandlerBase
+{
+public:
+ FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
+ : m_card(card), m_name(name), m_outputs(outputs)
+ {
+ }
+
+ void start_flipping()
+ {
+ m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
+ m_slowest_frame = std::chrono::duration<float>::min();
+ m_frame_num = 0;
+ queue_next();
+ }
+
+private:
+ void handle_page_flip(uint32_t frame, double time)
+ {
+ m_frame_num++;
+
+ auto now = std::chrono::steady_clock::now();
+
+ std::chrono::duration<float> diff = now - m_prev_frame;
+ if (diff > m_slowest_frame)
+ m_slowest_frame = diff;
+
+ if (m_frame_num % 100 == 0) {
+ std::chrono::duration<float> fsec = now - m_prev_print;
+ printf("Connector %s: fps %f, slowest %.2f ms\n",
+ m_name.c_str(),
+ 100.0 / fsec.count(),
+ m_slowest_frame.count() * 1000);
+ m_prev_print = now;
+ m_slowest_frame = std::chrono::duration<float>::min();
+ }
+
+ m_prev_frame = now;
+
+ queue_next();
+ }
+
+ static unsigned get_bar_pos(DumbFramebuffer* fb, unsigned frame_num)
+ {
+ return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
+ }
+
+ static void draw_bar(DumbFramebuffer* fb, unsigned frame_num)
+ {
+ int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
+ int new_xpos = get_bar_pos(fb, frame_num);
+
+ draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
+ }
+
+ static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
+ {
+ unsigned cur = frame_num % s_num_buffers;
+
+ if (!o.fbs.empty()) {
+ auto fb = o.fbs[cur];
+
+ draw_bar(fb, frame_num);
+
+ req.add(o.primary_plane, {
+ { "FB_ID", fb->id() },
+ });
+ }
+
+ for (const PlaneInfo& p : o.planes) {
+ auto fb = p.fbs[cur];
+
+ draw_bar(fb, frame_num);
+
+ req.add(p.plane, {
+ { "FB_ID", fb->id() },
+ });
+ }
+ }
+
+ void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
+ {
+ unsigned cur = frame_num % s_num_buffers;
+
+ if (!o.fbs.empty()) {
+ auto fb = o.fbs[cur];
+
+ draw_bar(fb, frame_num);
+
+ int r = o.crtc->page_flip(*fb, this);
+ ASSERT(r == 0);
+ }
+
+ for (const PlaneInfo& p : o.planes) {
+ auto fb = p.fbs[cur];
+
+ draw_bar(fb, frame_num);
+
+ int r = o.crtc->set_plane(p.plane, *fb,
+ p.x, p.y, p.w, p.h,
+ 0, 0, fb->width(), fb->height());
+ ASSERT(r == 0);
+ }
+ }
+
+ void queue_next()
+ {
+ if (m_card.has_atomic()) {
+ AtomicReq req(m_card);
+
+ for (auto o : m_outputs)
+ do_flip_output(req, m_frame_num, *o);
+
+ int r = req.commit(this);
+ if (r)
+ EXIT("Flip commit failed: %d\n", r);
+ } else {
+ ASSERT(m_outputs.size() == 1);
+ do_flip_output_legacy(m_frame_num, *m_outputs[0]);
+ }
+ }
+
+ Card& m_card;
+ string m_name;
+ vector<const OutputInfo*> m_outputs;
+ unsigned m_frame_num;
+
+ chrono::steady_clock::time_point m_prev_print;
+ chrono::steady_clock::time_point m_prev_frame;
+ chrono::duration<float> m_slowest_frame;
+
+ static const unsigned bar_width = 20;
+ static const unsigned bar_speed = 8;
+};
+
+static void main_flip(Card& card, const vector<OutputInfo>& outputs)
+{
+ fd_set fds;
+
+ FD_ZERO(&fds);
+
+ int fd = card.fd();
+
+ vector<unique_ptr<FlipState>> flipstates;
+
+ if (!s_flip_sync) {
+ for (const OutputInfo& o : outputs) {
+ auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
+ flipstates.push_back(move(fs));
+ }
+ } else {
+ vector<const OutputInfo*> ois;
+
+ string name;
+ for (const OutputInfo& o : outputs) {
+ name += to_string(o.connector->idx()) + ",";
+ ois.push_back(&o);
+ }
+
+ auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
+ flipstates.push_back(move(fs));
+ }
+
+ for (unique_ptr<FlipState>& fs : flipstates)
+ fs->start_flipping();
+
+ while (true) {
+ int r;
+
+ FD_SET(0, &fds);
+ FD_SET(fd, &fds);
+
+ r = select(fd + 1, &fds, NULL, NULL, NULL);
+ if (r < 0) {
+ fprintf(stderr, "select() failed with %d: %m\n", errno);
+ break;
+ } else if (FD_ISSET(0, &fds)) {
+ fprintf(stderr, "Exit due to user-input\n");
+ break;
+ } else if (FD_ISSET(fd, &fds)) {
+ card.call_page_flip_handlers();
+ }
+ }
+}
+
int main(int argc, char **argv)
{
vector<Arg> output_args = parse_cmdline(argc, argv);
Card card(s_device_path);
+ if (!card.has_atomic() && s_flip_sync)
+ EXIT("Synchronized flipping requires atomic modesetting");
+
vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
- draw_test_patterns(outputs);
+ if (card.has_atomic()) {
+ for (OutputInfo& o : outputs) {
+ o.primary_plane = o.crtc->get_primary_plane();
+
+ if (!o.fbs.empty() && !o.primary_plane)
+ EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
+ }
+ }
+
+ if (!s_flip_mode)
+ draw_test_patterns(outputs);
print_outputs(outputs);
- set_crtcs_n_planes(card, outputs);
+ if (card.has_atomic())
+ set_crtcs_n_planes(card, outputs);
+ else
+ set_crtcs_n_planes_legacy(card, outputs);
printf("press enter to exit\n");
- getchar();
+ if (s_flip_mode)
+ main_flip(card, outputs);
+ else
+ getchar();
}