summaryrefslogtreecommitdiff
path: root/shared-core/radeon_drm.h
AgeCommit message (Collapse)Author
2006-05-24Add support for r200 vertex programs (R200_EMIT_VAP_PVS_CNTL, and newRoland Scheidegger
packet type for making it possible to address whole tcl vector space and have a larger count)
2006-03-06Add general-purpose packet for manipulating scratch registers (r300)Aapo Tahkola
2006-02-18add benh's memory management patchDave Airlie
2006-02-18major realigment of DRM CVS with kernel code, makes integration much easierDave Airlie
2005-12-29add radeon card type get param so userspace can avoid walking PCIDave Airlie
2005-09-30fix pci overriding from userspaceDave Airlie
2005-09-11Add GART in FB support for ati pcigart, and PCIE support for r300Dave Airlie
2005-09-09Add support for GL_ATI_fragment_shader, new packets R200_EMIT_PP_AFS_0/1,Roland Scheidegger
R200_EMIT_PP_TXCTLALL_0-5 (replaces R200_EMIT_PP_TXFILTER_0-5, 2 more regs) and R200_EMIT_ATF_TFACTOR (replaces R200_EMIT_TFACTOR_0 (8 consts instead of 6)
2005-08-04Mark some radeon init variables deprecated. These used to be passed in butJon Smirl
the driver already knew their correct value. For example the physical address of the framebuffer and registers.
2005-07-20Add latest r300 support from r300.sf.net CVS. Patch submitted by volodya,Eric Anholt
with BSD fix from jkim and the r300_reg.h license from Nicolai Haehnle. Big thanks to everyone involved!
2005-03-15add R200_EMIT_PP_TRI_PERF_CNTL packet to support brilinear filtering onRoland Scheidegger
r200
2005-02-10add support for texture micro tiling on radeon/r200. Add support for r100Roland Scheidegger
cube maps (since it also requires a version bump) at the same time.
2005-01-26(Stephane Marchesin,me) Add radeon framebuffer tiling support to radeonRoland Scheidegger
drm. Add new ioctls to manage surfaces which cover the tiled areas
2004-12-08(Stephane Marchesin, me) add hyperz support to radeon drm. Only fast zRoland Scheidegger
clear and z buffer compression are working correctly, hierarchical-z is not.
2004-10-10Vladimir requested support so we can at least load r300 microcode forDave Airlie
helping 2D operations. Ups radeon to version 1.12.0, Vladimir, you might want to add any extra pciids... Approved-by: Dave Airlie <airlied@linux.ie>
2004-09-30Lindent of core build. Drivers checked for no binary diffs. A few filesJon Smirl
weren't Lindent's because their comments didn't convert very well. A bunch of other minor clean up with no code implact included.
2004-08-17preparation patch for radeon permanent mapping registers/framebuffer makesDave Airlie
dev_priv live always, and add AGP detection in kernel patch: radeon-pre-2.patch From: Jon Smirl
2004-07-25sync up with current 2.6 kernel bk tree - mostly __user annotationsDave Airlie
2004-05-18add R200_EMIT_RB3D_BLENDCOLOR state packet to support GL_EXT_blend_color,Roland Scheidegger
GL_EXT_blend_func_separate and GL_EXT_blend_equation_separate on r200
2004-04-10white space changes to align with kernelDave Airlie
2004-03-12Fixes need to clean up the mess I made with the mesa merge. This codeJon Smirl
allows the mesa drivers to use a single definition of the DRM sarea/IOCTLS located in the drm driver directory. Adjustments were made to the 2D drivers to not include these changes. Changes to the mesa copy of DRM were copied to the DRI copy. XFree86 bug: Reported by: Submitted by: Reviewed by: Obtained from:
2003-11-04Memory layout transition:Michel Daenzer
the 2D driver initializes MC_FB_LOCATION and related registers sanely the DRM deduces the layout from these registers clients use the new SETPARAM ioctl to tell the DRM where they think the framebuffer is located in the card's address space the DRM uses all this information to check client state and fix it up if necessary This is a prerequisite for things like direct rendering with IGP chips and video capturing.
2003-08-26Remove artificial PCI GART limitations, rename AGP to GART whereMichel Daenzer
appropriate
2003-08-08Added some information as to when (which DRM version) various queries wereIan Romanick
added.
2003-06-10Texture rectangle support for r100Keith Whitwell
2003-05-20DRM part of Radeon DRI suspend/resume support (Charl Botha).David Dawes
2003-04-30Merged texmem-0-0-1Ian Romanick
2003-04-22add more get_param queries for embedded projectKeith Whitwell
2003-02-21Merge from bsd-4-0-0-branch.Eric Anholt
2003-02-03Fix size of VERTEX2 ioctl struct (Egbert Eich)Keith Whitwell
2002-10-29updated e-mail addresses for Keith, Alan and JensJens Owen
2002-10-29preserve CRTC{,2}_OFFSET_CNTL in 2D driver to avoid bad effects whenMichel Daenzer
pageflipping after a mode switch take current page into account in AdjustFrame(); writing the CRTC offset via the CP was probably a bad idea as this can happen asynchronously, reverted take frame offset into account when flipping pages handle CRTC2 as well for pageflipping (untested) preserve GEN_INT_CNTL on mode switches to prevent interrupts from getting disabled
2002-10-28merge from mesa-4-1-branch to get cube-map registers. bumped version to 1.7Brian Paul
2002-09-25change RADEON_PARAM_IRQ_ACTIVE to RADEON_PARAM_IRQ_NRMichel Daenzer
2002-09-23merged r200-0-2-branch to trunkKeith Whitwell
2002-08-26merged r200-0-1-branchKeith Whitwell
2002-07-11Don't read scratch registers directly, obtain the values via the GET_PARAMMichel Daenzer
ioctl. The DRM reads them from memory addresses the chip writes to on updates. Fall back to reading the registers directly with an old DRM. (Tim Smith, cleanups by myself)
2002-07-05merged bsd-3-0-0-branchAlan Hourihane
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <regex>
#include <set>
#include <chrono>

#include <sys/select.h>

#include <kms++/kms++.h>
#include <kms++/modedb.h>
#include <kms++/mode_cvt.h>

#include <kms++util/kms++util.h>

using namespace std;
using namespace kms;

struct PlaneInfo
{
	Plane* plane;

	unsigned x;
	unsigned y;
	unsigned w;
	unsigned h;

	unsigned view_x;
	unsigned view_y;
	unsigned view_w;
	unsigned view_h;

	vector<MappedFramebuffer*> fbs;
};

struct OutputInfo
{
	Connector* connector;

	Crtc* crtc;
	Plane* primary_plane;
	Videomode mode;
	bool user_set_crtc;
	vector<MappedFramebuffer*> fbs;

	vector<PlaneInfo> planes;
};

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 bool s_cvt;
static bool s_cvt_v2;
static bool s_cvt_vid_opt;

static set<Crtc*> s_used_crtcs;
static set<Plane*> s_used_planes;

__attribute__ ((unused))
static void print_regex_match(smatch sm)
{
	for (unsigned i = 0; i < sm.size(); ++i) {
		string str = sm[i].str();
		printf("%u: %s\n", i, str.c_str());
	}
}

static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
{
	Connector* conn = resman.reserve_connector(str);

	if (!conn)
		EXIT("No connector '%s'", str.c_str());

	if (!conn->connected())
		EXIT("Connector '%s' not connected", conn->fullname().c_str());

	output.connector = conn;
	output.mode = output.connector->get_default_mode();
}

static void get_default_crtc(Card& card, OutputInfo& output)
{
	Crtc* crtc = output.connector->get_current_crtc();

	if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
		s_used_crtcs.insert(crtc);
		output.crtc = crtc;
		return;
	}

	for (const auto& possible : output.connector->get_possible_crtcs()) {
		if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
			s_used_crtcs.insert(possible);
			output.crtc = possible;
			return;
		}
	}

	EXIT("Could not find available crtc");
}

static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
{
	// @12:1920x1200i@60
	// @12:33000000,800/210/30/16/-,480/22/13/10/-,i

	const regex modename_re("(?:(@?)(\\d+):)?"	// @12:
				"(?:(\\d+)x(\\d+)(i)?)"	// 1920x1200i
				"(?:@([\\d\\.]+))?");	// @60

	const regex modeline_re("(?:(@?)(\\d+):)?"			// @12:
				"(\\d+),"				// 33000000,
				"(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-]),"	// 800/210/30/16/-,
				"(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])"	// 480/22/13/10/-
				"(?:,([i]+))?"				// ,i
				);

	smatch sm;
	if (regex_match(crtc_str, sm, modename_re)) {
		if (sm[2].matched) {
			bool use_id = sm[1].length() == 1;
			unsigned num = stoul(sm[2].str());

			if (use_id) {
				Crtc* c = card.get_crtc(num);
				if (!c)
					EXIT("Bad crtc id '%u'", num);

				output.crtc = c;
			} else {
				auto crtcs = card.get_crtcs();

				if (num >= crtcs.size())
					EXIT("Bad crtc number '%u'", num);

				output.crtc = crtcs[num];
			}
		} else {
			output.crtc = output.connector->get_current_crtc();
		}

		unsigned w = stoul(sm[3]);
		unsigned h = stoul(sm[4]);
		bool ilace = sm[5].matched ? true : false;
		float refresh = sm[6].matched ? stof(sm[6]) : 0;

		if (s_cvt) {
			output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
		} else if (s_use_dmt) {
			try {
				output.mode = find_dmt(w, h, refresh, ilace);
			} catch (exception& e) {
				EXIT("Mode not found from DMT tables\n");
			}
		} else if (s_use_cea) {
			try {
				output.mode = find_cea(w, h, refresh, ilace);
			} catch (exception& e) {
				EXIT("Mode not found from CEA tables\n");
			}
		} else {
			try {
				output.mode = output.connector->get_mode(w, h, refresh, ilace);
			} catch (exception& e) {
				EXIT("Mode not found from the connector\n");
			}
		}
	} else if (regex_match(crtc_str, sm, modeline_re)) {
		if (sm[2].matched) {
			bool use_id = sm[1].length() == 1;
			unsigned num = stoul(sm[2].str());

			if (use_id) {
				Crtc* c = card.get_crtc(num);
				if (!c)
					EXIT("Bad crtc id '%u'", num);

				output.crtc = c;
			} else {
				auto crtcs = card.get_crtcs();

				if (num >= crtcs.size())
					EXIT("Bad crtc number '%u'", num);

				output.crtc = crtcs[num];
			}
		} else {
			output.crtc = output.connector->get_current_crtc();
		}

		unsigned clock = stoul(sm[3]);

		unsigned hact = stoul(sm[4]);
		unsigned hfp = stoul(sm[5]);
		unsigned hsw = stoul(sm[6]);
		unsigned hbp = stoul(sm[7]);
		bool h_pos_sync = sm[8] == "+" ? true : false;

		unsigned vact = stoul(sm[9]);
		unsigned vfp = stoul(sm[10]);
		unsigned vsw = stoul(sm[11]);
		unsigned vbp = stoul(sm[12]);
		bool v_pos_sync = sm[13] == "+" ? true : false;

		output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
		output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
		output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);

		if (sm[14].matched) {
			for (int i = 0; i < sm[14].length(); ++i) {
				char f = string(sm[14])[i];

				switch (f) {
				case 'i':
					output.mode.set_interlace(true);
					break;
				default:
					EXIT("Bad mode flag %c", f);
				}
			}
		}
	} else {
		EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
	}
}

static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
{
	// 3:400,400-400x400
	const regex plane_re("(?:(@?)(\\d+):)?"		// 3:
			     "(?:(\\d+),(\\d+)-)?"	// 400,400-
			     "(\\d+)x(\\d+)");		// 400x400

	smatch sm;
	if (!regex_match(plane_str, sm, plane_re))
		EXIT("Failed to parse plane option '%s'", plane_str.c_str());

	if (sm[2].matched) {
		bool use_id = sm[1].length() == 1;
		unsigned num = stoul(sm[2].str());

		if (use_id) {
			Plane* p = card.get_plane(num);
			if (!p)
				EXIT("Bad plane id '%u'", num);

			pinfo.plane = p;
		} else {
			auto planes = card.get_planes();

			if (num >= planes.size())
				EXIT("Bad plane number '%u'", num);

			pinfo.plane = planes[num];
		}
	} else {
		for (Plane* p : output.crtc->get_possible_planes()) {
			if (s_used_planes.find(p) != s_used_planes.end())
				continue;

			if (p->plane_type() != PlaneType::Overlay)
				continue;

			pinfo.plane = p;
		}

		if (!pinfo.plane)
			EXIT("Failed to find available plane");
	}

	s_used_planes.insert(pinfo.plane);

	pinfo.w = stoul(sm[5]);
	pinfo.h = stoul(sm[6]);

	if (sm[3].matched)
		pinfo.x = stoul(sm[3]);
	else
		pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;

	if (sm[4].matched)
		pinfo.y = stoul(sm[4]);
	else
		pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
}

static vector<MappedFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
{
	vector<MappedFramebuffer*> v;

	for (unsigned i = 0; i < s_num_buffers; ++i)
		v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));

	return v;
}

static vector<MappedFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
{
	unsigned w = def_w;
	unsigned h = def_h;
	PixelFormat format = PixelFormat::XRGB8888;

	if (!fb_str.empty()) {
		// XXX the regexp is not quite correct
		// 400x400-NV12
		const regex fb_re("(?:(\\d+)x(\\d+))?"		// 400x400
				  "(?:-)?"			// -
				  "(\\w\\w\\w\\w)?");		// NV12

		smatch sm;
		if (!regex_match(fb_str, sm, fb_re))
			EXIT("Failed to parse fb option '%s'", fb_str.c_str());

		if (sm[1].matched)
			w = stoul(sm[1]);
		if (sm[2].matched)
			h = stoul(sm[2]);
		if (sm[3].matched)
			format = FourCCToPixelFormat(sm[3]);
	}

	vector<MappedFramebuffer*> v;

	for (unsigned i = 0; i < s_num_buffers; ++i)
		v.push_back(new DumbFramebuffer(card, w, h, format));

	return v;
}

static void parse_view(const string& view_str, PlaneInfo& pinfo)
{
	const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)");		// 400,400-400x400

	smatch sm;
	if (!regex_match(view_str, sm, view_re))
		EXIT("Failed to parse view option '%s'", view_str.c_str());

	pinfo.view_x = stoul(sm[1]);
	pinfo.view_y = stoul(sm[2]);
	pinfo.view_w = stoul(sm[3]);
	pinfo.view_h = stoul(sm[4]);
}

static const char* usage_str =
		"Usage: kmstest [OPTION]...\n\n"
		"Show a test pattern on a display or plane\n\n"
		"Options:\n"
		"      --device=DEVICE       DEVICE is the path to DRM card to open\n"
		"  -c, --connector=CONN      CONN is <connector>\n"
		"  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
		"                            or\n"
		"                            [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
		"  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
		"  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
		"  -v, --view=VIEW           VIEW is <x>,<y>-<w>x<h>\n"
		"      --dmt                 Search for the given mode from DMT tables\n"
		"      --cea                 Search for the given mode from CEA tables\n"
		"      --cvt=CVT             Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
		"      --flip                Do page flipping for each output\n"
		"      --sync                Synchronize page flipping\n"
		"\n"
		"<connector>, <crtc> and <plane> can be given by index (<idx>) or id (<id>).\n"
		"<connector> can also be given by name.\n"
		"\n"
		"Options can be given multiple times to set up multiple displays or planes.\n"
		"Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
		"an earlier option.\n"
		"If you omit parameters, kmstest tries to guess what you mean\n"
		"\n"
		"Examples:\n"
		"\n"
		"Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
		"    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
		"XR24 framebuffer on first connected connector in the default mode:\n"
		"    kmstest -f XR24\n\n"
		"XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
		"    kmstest -p 400x400 -f XR24\n\n"
		"Test pattern on the second connector with default mode:\n"
		"    kmstest -c 1\n"
		;

static void usage()
{
	puts(usage_str);
}

enum class ObjectType
{
	Connector,
	Crtc,
	Plane,
	Framebuffer,
	View,
};

struct Arg
{
	ObjectType type;
	string arg;
};

static string s_device_path = "/dev/dri/card0";

static vector<Arg> parse_cmdline(int argc, char **argv)
{
	vector<Arg> args;

	OptionSet optionset = {
		Option("|device=",
		[&](string s)
		{
			s_device_path = s;
		}),
		Option("c|connector=",
		[&](string s)
		{
			args.push_back(Arg { ObjectType::Connector, s });
		}),
		Option("r|crtc=", [&](string s)
		{
			args.push_back(Arg { ObjectType::Crtc, s });
		}),
		Option("p|plane=", [&](string s)
		{
			args.push_back(Arg { ObjectType::Plane, s });
		}),
		Option("f|fb=", [&](string s)
		{
			args.push_back(Arg { ObjectType::Framebuffer, s });
		}),
		Option("v|view=", [&](string s)
		{
			args.push_back(Arg { ObjectType::View, s });
		}),
		Option("|dmt", []()
		{
			s_use_dmt = true;
		}),
		Option("|cea", []()
		{
			s_use_cea = true;
		}),
		Option("|flip", []()
		{
			s_flip_mode = true;
			s_num_buffers = 2;
		}),
		Option("|sync", []()
		{
			s_flip_sync = true;
		}),
		Option("|cvt=", [&](string s)
		{
			if (s == "v1")
				s_cvt = true;
			else if (s == "v2")
				s_cvt = s_cvt_v2 = true;
			else if (s == "v2o")
				s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
			else {
				usage();
				exit(-1);
			}
		}),
		Option("h|help", [&]()
		{
			usage();
			exit(-1);
		}),
	};

	optionset.parse(argc, argv);

	if (optionset.params().size() > 0) {
		usage();
		exit(-1);
	}

	return args;
}

static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
{
	vector<OutputInfo> outputs;

	if (output_args.size() == 0) {
		// no output args, show a pattern on all screens
		for (Connector* conn : card.get_connectors()) {
			if (!conn->connected())
				continue;

			OutputInfo output = { };
			output.connector = resman.reserve_connector(conn);
			output.crtc = resman.reserve_crtc(conn);
			output.mode = output.connector->get_default_mode();

			output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);

			outputs.push_back(output);
		}

		return outputs;
	}

	OutputInfo* current_output = 0;
	PlaneInfo* current_plane = 0;

	for (auto& arg : output_args) {
		switch (arg.type) {
		case ObjectType::Connector:
		{
			outputs.push_back(OutputInfo { });
			current_output = &outputs.back();

			get_connector(resman, *current_output, arg.arg);
			current_plane = 0;

			break;
		}

		case ObjectType::Crtc:
		{
			if (!current_output) {
				outputs.push_back(OutputInfo { });
				current_output = &outputs.back();
			}

			if (!current_output->connector)
				get_connector(resman, *current_output);

			parse_crtc(card, arg.arg, *current_output);

			current_output->user_set_crtc = true;

			current_plane = 0;

			break;
		}

		case ObjectType::Plane:
		{
			if (!current_output) {
				outputs.push_back(OutputInfo { });
				current_output = &outputs.back();
			}

			if (!current_output->connector)
				get_connector(resman, *current_output);

			if (!current_output->crtc)
				get_default_crtc(card, *current_output);

			current_output->planes.push_back(PlaneInfo { });
			current_plane = &current_output->planes.back();

			parse_plane(card, arg.arg, *current_output, *current_plane);

			break;
		}

		case ObjectType::Framebuffer:
		{
			if (!current_output) {
				outputs.push_back(OutputInfo { });
				current_output = &outputs.back();
			}

			if (!current_output->connector)
				get_connector(resman, *current_output);

			if (!current_output->crtc)
				get_default_crtc(card, *current_output);

			int def_w, def_h;

			if (current_plane) {
				def_w = current_plane->w;
				def_h = current_plane->h;
			} else {
				def_w = current_output->mode.hdisplay;
				def_h = current_output->mode.vdisplay;
			}

			auto fbs = parse_fb(card, arg.arg, def_w, def_h);

			if (current_plane)
				current_plane->fbs = fbs;
			else
				current_output->fbs = fbs;

			break;
		}

		case ObjectType::View:
		{
			if (!current_plane || current_plane->fbs.empty())
				EXIT("'view' parameter requires a plane and a fb");

			parse_view(arg.arg, *current_plane);
			break;
		}
		}
	}

	// create default framebuffers if needed
	for (OutputInfo& o : outputs) {
		if (!o.crtc) {
			get_default_crtc(card, o);
			o.user_set_crtc = true;
		}

		if (o.fbs.empty() && o.user_set_crtc)
			o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);

		for (PlaneInfo &p : o.planes) {
			if (p.fbs.empty())
				p.fbs = get_default_fb(card, p.w, p.h);
		}
	}

	return outputs;
}

static std::string videomode_to_string(const Videomode& m)
{
	string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
	string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());

	return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
		       m.name.c_str(),
		       m.clock / 1000.0,
		       h.c_str(), v.c_str(),
		       m.vrefresh, m.calculated_vrefresh(),
		       m.flags,
		       m.type);
}

static void print_outputs(const vector<OutputInfo>& outputs)
{
	for (unsigned i = 0; i < outputs.size(); ++i) {
		const OutputInfo& o = outputs[i];

		printf("Connector %u/@%u: %s\n", o.connector->idx(), o.connector->id(),
		       o.connector->fullname().c_str());
		printf("  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
		if (o.primary_plane)
			printf(" (plane %u/@%u)", o.primary_plane->idx(), o.primary_plane->id());
		printf(": %s\n", videomode_to_string(o.mode).c_str());
		if (!o.fbs.empty()) {
			auto fb = o.fbs[0];
			printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
			       PixelFormatToFourCC(fb->format()).c_str());
		}

		for (unsigned j = 0; j < o.planes.size(); ++j) {
			const PlaneInfo& p = o.planes[j];
			auto fb = p.fbs[0];
			printf("  Plane %u/@%u: %u,%u-%ux%u\n", p.plane->idx(), p.plane->id(),
			       p.x, p.y, p.w, p.h);
			printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
			       PixelFormatToFourCC(fb->format()).c_str());
		}
	}
}

static void draw_test_patterns(const vector<OutputInfo>& outputs)
{
	for (const OutputInfo& o : outputs) {
		for (auto fb : o.fbs)
			draw_test_pattern(*fb);

		for (const PlaneInfo& p : o.planes)
			for (auto fb : p.fbs)
				draw_test_pattern(*fb);
	}
}

static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
{
	// Disable unused crtcs
	for (Crtc* crtc : card.get_crtcs()) {
		if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
			continue;

		crtc->disable_mode();
	}

	for (const OutputInfo& o : outputs) {
		auto conn = o.connector;
		auto crtc = o.crtc;

		if (!o.fbs.empty()) {
			auto fb = o.fbs[0];
			int r = crtc->set_mode(conn, *fb, o.mode);
			if (r)
				printf("crtc->set_mode() failed for crtc %u: %s\n",
				       crtc->id(), strerror(-r));
		}

		for (const PlaneInfo& p : o.planes) {
			auto fb = p.fbs[0];
			int r = crtc->set_plane(p.plane, *fb,
						p.x, p.y, p.w, p.h,
						0, 0, fb->width(), fb->height());
			if (r)
				printf("crtc->set_plane() failed for plane %u: %s\n",
				       p.plane->id(), strerror(-r));
		}
	}
}

static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
{
	int r;

	// XXX DRM framework doesn't allow moving an active plane from one crtc to another.
	// See drm_atomic.c::plane_switching_crtc().
	// For the time being, disable all crtcs and planes here.

	AtomicReq disable_req(card);

	// Disable unused crtcs
	for (Crtc* crtc : card.get_crtcs()) {
		//if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
		//	continue;

		disable_req.add(crtc, {
				{ "ACTIVE", 0 },
			});
	}

	// Disable unused planes
	for (Plane* plane : card.get_planes()) {
		//if (find_if(outputs.begin(), outputs.end(), [plane](const OutputInfo& o) { return o.primary_plane == plane; }) != outputs.end())
		//	continue;

		disable_req.add(plane, {
				{ "FB_ID", 0 },
				{ "CRTC_ID", 0 },
			});
	}

	r = disable_req.commit_sync(true);
	if (r)
		EXIT("Atomic commit failed when disabling: %d\n", r);


	// 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;

		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() },
			});

		if (!o.fbs.empty()) {
			auto fb = o.fbs[0];

			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", (p.view_x ?: 0) << 16 },
					{ "SRC_Y", (p.view_y ?: 0) << 16 },
					{ "SRC_W", (p.view_w ?: fb->width()) << 16 },
					{ "SRC_H", (p.view_h ?: fb->height()) << 16 },
					{ "CRTC_X", p.x },
					{ "CRTC_Y", p.y },
					{ "CRTC_W", p.w },
					{ "CRTC_H", p.h },
				});
		}
	}

	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);
}

static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
{
	if (card.has_atomic())
		set_crtcs_n_planes_atomic(card, outputs);
	else
		set_crtcs_n_planes_legacy(card, outputs);
}

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(MappedFramebuffer* fb, unsigned frame_num)
	{
		return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
	}

	static void draw_bar(MappedFramebuffer* 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);
		draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
	}

	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");

	ResourceManager resman(card);

	vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);

	if (card.has_atomic()) {
		for (OutputInfo& o : outputs) {
			if (o.fbs.empty())
				continue;

			o.primary_plane = resman.reserve_primary_plane(o.crtc);

			if (!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);

	printf("press enter to exit\n");

	if (s_flip_mode)
		main_flip(card, outputs);
	else
		getchar();
}