xvideo.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. #include <X11/extensions/XShm.h>
  4. #include <X11/extensions/Xv.h>
  5. #include <X11/extensions/Xvlib.h>
  6. extern "C" auto XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*) -> XvImage*;
  7. struct VideoXVideo : VideoDriver {
  8. VideoXVideo& self = *this;
  9. VideoXVideo(Video& super) : VideoDriver(super) {}
  10. ~VideoXVideo() { terminate(); }
  11. auto create() -> bool override {
  12. VideoDriver::exclusive = true;
  13. VideoDriver::shader = "Blur";
  14. return initialize();
  15. }
  16. auto driver() -> string override { return "XVideo"; }
  17. auto ready() -> bool override { return _ready; }
  18. auto hasFullScreen() -> bool override { return true; }
  19. auto hasMonitor() -> bool override { return true; }
  20. auto hasContext() -> bool override { return true; }
  21. auto hasBlocking() -> bool override { return true; }
  22. auto hasFormats() -> vector<string> override {
  23. return _formatNames;
  24. }
  25. auto setFullScreen(bool fullScreen) -> bool override {
  26. return initialize();
  27. }
  28. auto setMonitor(string monitor) -> bool override {
  29. return initialize();
  30. }
  31. auto setContext(uintptr context) -> bool override {
  32. return initialize();
  33. }
  34. auto setBlocking(bool blocking) -> bool override {
  35. bool result = false;
  36. Display* display = XOpenDisplay(nullptr);
  37. Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", false);
  38. if(_port >= 0) {
  39. XvSetPortAttribute(display, _port, atom, self.blocking);
  40. result = true;
  41. }
  42. XCloseDisplay(display);
  43. return result;
  44. }
  45. auto setFormat(string format) -> bool override {
  46. return initialize();
  47. }
  48. auto focused() -> bool override {
  49. return true;
  50. }
  51. auto clear() -> void override {
  52. memory::fill<uint32_t>(_buffer, _bufferWidth * _bufferHeight);
  53. //clear twice in case video is double buffered ...
  54. output();
  55. output();
  56. }
  57. auto size(uint& width, uint& height) -> void override {
  58. if(self.fullScreen) {
  59. width = _monitorWidth;
  60. height = _monitorHeight;
  61. } else {
  62. XWindowAttributes parent;
  63. XGetWindowAttributes(_display, _parent, &parent);
  64. width = parent.width;
  65. height = parent.height;
  66. }
  67. }
  68. auto acquire(uint32_t*& data, uint& pitch, uint width, uint height) -> bool override {
  69. if(width != _width || height != _height) resize(_width = width, _height = height);
  70. pitch = _bufferWidth * 4;
  71. return data = _buffer;
  72. }
  73. auto release() -> void override {
  74. }
  75. auto output(uint width = 0, uint height = 0) -> void override {
  76. XWindowAttributes window;
  77. XGetWindowAttributes(_display, _window, &window);
  78. XWindowAttributes parent;
  79. XGetWindowAttributes(_display, _parent, &parent);
  80. if(window.width != parent.width || window.height != parent.height) {
  81. XResizeWindow(_display, _window, parent.width, parent.height);
  82. }
  83. uint viewportX = 0;
  84. uint viewportY = 0;
  85. uint viewportWidth = parent.width;
  86. uint viewportHeight = parent.height;
  87. if(self.fullScreen) {
  88. viewportX = _monitorX;
  89. viewportY = _monitorY;
  90. viewportWidth = _monitorWidth;
  91. viewportHeight = _monitorHeight;
  92. }
  93. auto& name = _formatName;
  94. if(name == "RGB24" ) renderRGB24 (_width, _height);
  95. if(name == "RGB24P") renderRGB24P(_width, _height);
  96. if(name == "RGB16" ) renderRGB16 (_width, _height);
  97. if(name == "RGB15" ) renderRGB15 (_width, _height);
  98. if(name == "UYVY" ) renderUYVY (_width, _height);
  99. if(name == "YUY2" ) renderYUY2 (_width, _height);
  100. if(name == "YV12" ) renderYV12 (_width, _height);
  101. if(name == "I420" ) renderI420 (_width, _height);
  102. if(!width) width = viewportWidth;
  103. if(!height) height = viewportHeight;
  104. int x = viewportX + ((int)viewportWidth - (int)width) / 2;
  105. int y = viewportY + ((int)viewportHeight - (int)height) / 2;
  106. XvShmPutImage(_display, _port, _window, _gc, _image,
  107. 0, 0, _width, _height,
  108. x, y, width, height,
  109. true);
  110. }
  111. auto poll() -> void override {
  112. while(XPending(_display)) {
  113. XEvent event;
  114. XNextEvent(_display, &event);
  115. if(event.type == Expose) {
  116. XWindowAttributes attributes;
  117. XGetWindowAttributes(_display, _window, &attributes);
  118. super.doUpdate(attributes.width, attributes.height);
  119. }
  120. }
  121. }
  122. private:
  123. auto initialize() -> bool {
  124. terminate();
  125. if(!self.fullScreen && !self.context) return false;
  126. _display = XOpenDisplay(nullptr);
  127. _screen = DefaultScreen(_display);
  128. if(!XShmQueryExtension(_display)) {
  129. print("XVideo: XShm extension not found.\n");
  130. return false;
  131. }
  132. //find an appropriate Xv port
  133. _port = -1;
  134. int depth = 0;
  135. int visualID = 0;
  136. XvAdaptorInfo* adaptorInfo = nullptr;
  137. uint adaptorCount = 0;
  138. XvQueryAdaptors(_display, DefaultRootWindow(_display), &adaptorCount, &adaptorInfo);
  139. for(uint n : range(adaptorCount)) {
  140. //find adaptor that supports both input (memory->drawable) and image (drawable->screen) masks
  141. if(adaptorInfo[n].num_formats < 1) continue;
  142. if(!(adaptorInfo[n].type & XvInputMask)) continue;
  143. if(!(adaptorInfo[n].type & XvImageMask)) continue;
  144. _port = adaptorInfo[n].base_id;
  145. depth = adaptorInfo[n].formats->depth;
  146. visualID = adaptorInfo[n].formats->visual_id;
  147. break;
  148. }
  149. XvFreeAdaptorInfo(adaptorInfo);
  150. if(_port < 0) {
  151. print("XVideo: failed to find valid XvPort.\n");
  152. return false;
  153. }
  154. XVisualInfo visualTemplate;
  155. visualTemplate.visualid = visualID;
  156. visualTemplate.screen = _screen;
  157. visualTemplate.depth = depth;
  158. visualTemplate.visual = 0;
  159. int visualMatches = 0;
  160. auto visualInfo = XGetVisualInfo(_display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualTemplate, &visualMatches);
  161. if(visualMatches < 1 || !visualInfo->visual) {
  162. if(visualInfo) XFree(visualInfo);
  163. print("XVideo: unable to find Xv-compatible visual.\n");
  164. return false;
  165. }
  166. _parent = self.fullScreen ? RootWindow(_display, _screen) : (Window)self.context;
  167. //create child window to attach to parent window.
  168. //this is so that even if parent window visual depth doesn't match Xv visual
  169. //(common with composited windows), Xv can still render to child window.
  170. XWindowAttributes windowAttributes{};
  171. XGetWindowAttributes(_display, _parent, &windowAttributes);
  172. auto monitor = Video::monitor(self.monitor);
  173. _monitorX = monitor.x;
  174. _monitorY = monitor.y;
  175. _monitorWidth = monitor.width;
  176. _monitorHeight = monitor.height;
  177. _colormap = XCreateColormap(_display, _parent, visualInfo->visual, AllocNone);
  178. XSetWindowAttributes attributes{};
  179. attributes.border_pixel = 0;
  180. attributes.colormap = _colormap;
  181. attributes.override_redirect = self.fullScreen;
  182. _window = XCreateWindow(_display, _parent,
  183. 0, 0, windowAttributes.width, windowAttributes.height,
  184. 0, depth, InputOutput, visualInfo->visual,
  185. CWBorderPixel | CWColormap | CWOverrideRedirect, &attributes);
  186. XSelectInput(_display, _window, ExposureMask);
  187. XFree(visualInfo);
  188. XSetWindowBackground(_display, _window, 0);
  189. XMapWindow(_display, _window);
  190. _gc = XCreateGC(_display, _window, 0, 0);
  191. int attributeCount = 0;
  192. auto attributeList = XvQueryPortAttributes(_display, _port, &attributeCount);
  193. for(auto n : range(attributeCount)) {
  194. if(string{attributeList[n].name} == "XV_AUTOPAINT_COLORKEY") {
  195. //set colorkey to auto paint, so that Xv video output is always visible
  196. Atom atom = XInternAtom(_display, "XV_AUTOPAINT_COLORKEY", false);
  197. XvSetPortAttribute(_display, _port, atom, 1);
  198. }
  199. }
  200. XFree(attributeList);
  201. queryAvailableFormats();
  202. if(!_formatNames) {
  203. print("XVideo: unable to find a supported image format.\n");
  204. return false;
  205. }
  206. if(auto match = _formatNames.find(self.format)) {
  207. _formatID = _formatIDs[match()];
  208. _formatName = _formatNames[match()];
  209. } else {
  210. _formatID = _formatIDs[0];
  211. _formatName = _formatNames[0];
  212. self.format = _formatName;
  213. }
  214. _ready = true;
  215. initializeTables();
  216. resize(_width = 256, _height = 256);
  217. clear();
  218. return true;
  219. }
  220. auto terminate() -> void {
  221. _ready = false;
  222. if(_image) {
  223. XShmDetach(_display, &_shmInfo);
  224. shmdt(_shmInfo.shmaddr);
  225. shmctl(_shmInfo.shmid, IPC_RMID, nullptr);
  226. XFree(_image);
  227. _image = nullptr;
  228. }
  229. if(_gc) {
  230. XFreeGC(_display, _gc);
  231. _gc = 0;
  232. }
  233. if(_window) {
  234. XUnmapWindow(_display, _window);
  235. _window = 0;
  236. }
  237. if(_colormap) {
  238. XFreeColormap(_display, _colormap);
  239. _colormap = 0;
  240. }
  241. if(_display) {
  242. XCloseDisplay(_display);
  243. _display = nullptr;
  244. }
  245. delete[] _buffer, _buffer = nullptr, _bufferWidth = 0, _bufferHeight = 0;
  246. delete[] _ytable, _ytable = nullptr;
  247. delete[] _utable, _utable = nullptr;
  248. delete[] _vtable, _vtable = nullptr;
  249. }
  250. auto queryAvailableFormats() -> void {
  251. auto& ids = _formatIDs;
  252. auto& names = _formatNames;
  253. ids.reset();
  254. names.reset();
  255. int count = 0;
  256. auto array = XvListImageFormats(_display, _port, &count);
  257. for(uint sort : range(8)) {
  258. for(uint n : range(count)) {
  259. auto id = array[n].id;
  260. auto type = array[n].type;
  261. auto format = array[n].format;
  262. auto depth = array[n].bits_per_pixel;
  263. auto redMask = array[n].red_mask;
  264. auto order = array[n].component_order;
  265. string components;
  266. for(uint n : range(4)) if(char c = order[n]) components.append(c);
  267. if(type == XvRGB) {
  268. if(sort == 0 && depth == 32) ids.append(id), names.append("RGB24");
  269. if(sort == 1 && depth == 24) ids.append(id), names.append("RGB24P");
  270. if(sort == 2 && depth <= 16 && redMask == 0xf800) ids.append(id), names.append("RGB16");
  271. if(sort == 3 && depth <= 16 && redMask == 0x7c00) ids.append(id), names.append("RGB15");
  272. }
  273. if(type == XvYUV && format == XvPacked) {
  274. if(sort == 4 && depth == 16 && components == "UYVY") ids.append(id), names.append("UYVY");
  275. if(sort == 5 && depth == 16 && components == "YUYV") ids.append(id), names.append("YUY2");
  276. }
  277. if(type == XvYUV && format == XvPlanar) {
  278. if(sort == 6 && depth == 12 && components == "YVU" ) ids.append(id), names.append("YV12");
  279. if(sort == 7 && depth == 12 && components == "YUV" ) ids.append(id), names.append("I420");
  280. }
  281. }
  282. }
  283. free(array);
  284. }
  285. auto resize(uint width, uint height) -> void {
  286. if(_bufferWidth >= width && _bufferHeight >= height) return;
  287. _bufferWidth = max(width, _bufferWidth);
  288. _bufferHeight = max(height, _bufferHeight);
  289. //must round to be evenly divisible by 4
  290. if(uint round = _bufferWidth & 3) _bufferWidth += 4 - round;
  291. if(uint round = _bufferHeight & 3) _bufferHeight += 4 - round;
  292. _bufferWidth = bit::round(_bufferWidth);
  293. _bufferHeight = bit::round(_bufferHeight);
  294. if(_image) {
  295. XShmDetach(_display, &_shmInfo);
  296. shmdt(_shmInfo.shmaddr);
  297. shmctl(_shmInfo.shmid, IPC_RMID, nullptr);
  298. XFree(_image);
  299. }
  300. _image = XvShmCreateImage(_display, _port, _formatID, 0, _bufferWidth, _bufferHeight, &_shmInfo);
  301. _shmInfo.shmid = shmget(IPC_PRIVATE, _image->data_size, IPC_CREAT | 0777);
  302. _shmInfo.shmaddr = _image->data = (char*)shmat(_shmInfo.shmid, 0, 0);
  303. _shmInfo.readOnly = false;
  304. XShmAttach(_display, &_shmInfo);
  305. delete[] _buffer;
  306. _buffer = new uint32_t[_bufferWidth * _bufferHeight];
  307. }
  308. auto renderRGB24(uint width, uint height) -> void {
  309. for(uint y : range(height)) {
  310. auto input = (const uint32_t*)_buffer + y * width;
  311. auto output = (uint32_t*)_image->data + y * (_image->pitches[0] >> 2);
  312. for(uint x : range(width)) {
  313. uint32_t p = *input++;
  314. *output++ = p;
  315. }
  316. }
  317. }
  318. auto renderRGB24P(uint width, uint height) -> void {
  319. for(uint y : range(height)) {
  320. auto input = (const uint32_t*)_buffer + y * width;
  321. auto output = (uint8_t*)_image->data + y * _image->pitches[0];
  322. for(uint x : range(width)) {
  323. uint32_t p = *input++;
  324. *output++ = p >> 0;
  325. *output++ = p >> 8;
  326. *output++ = p >> 16;
  327. }
  328. }
  329. }
  330. auto renderRGB16(uint width, uint height) -> void {
  331. for(uint y : range(height)) {
  332. auto input = (const uint32_t*)_buffer + y * width;
  333. auto output = (uint16_t*)_image->data + y * (_image->pitches[0] >> 1);
  334. for(uint x : range(width)) {
  335. uint32_t p = toRGB16(*input++);
  336. *output++ = p;
  337. }
  338. input += _bufferWidth - width;
  339. output += _bufferWidth - width;
  340. }
  341. }
  342. auto renderRGB15(uint width, uint height) -> void {
  343. for(uint y : range(height)) {
  344. auto input = (const uint32_t*)_buffer + y * width;
  345. auto output = (uint16_t*)_image->data + y * (_image->pitches[0] >> 1);
  346. for(uint x : range(width)) {
  347. uint32_t p = toRGB15(*input++);
  348. *output++ = p;
  349. }
  350. }
  351. }
  352. auto renderUYVY(uint width, uint height) -> void {
  353. for(uint y : range(height)) {
  354. auto input = (const uint32_t*)_buffer + y * width;
  355. auto output = (uint16_t*)_image->data + y * (_image->pitches[0] >> 1);
  356. for(uint x : range(width >> 1)) {
  357. uint32_t p0 = toRGB16(*input++);
  358. uint32_t p1 = toRGB16(*input++);
  359. *output++ = _ytable[p0] << 8 | ((_utable[p0] + _utable[p1]) >> 1) << 0;
  360. *output++ = _ytable[p1] << 8 | ((_vtable[p0] + _vtable[p1]) >> 1) << 0;
  361. }
  362. }
  363. }
  364. auto renderYUY2(uint width, uint height) -> void {
  365. for(uint y : range(height)) {
  366. auto input = (const uint32_t*)_buffer + y * width;
  367. auto output = (uint16_t*)_image->data + y * (_image->pitches[0] >> 1);
  368. for(uint x : range(width >> 1)) {
  369. uint32_t p0 = toRGB16(*input++);
  370. uint32_t p1 = toRGB16(*input++);
  371. *output++ = ((_utable[p0] + _utable[p1]) >> 1) << 8 | _ytable[p0] << 0;
  372. *output++ = ((_vtable[p0] + _vtable[p1]) >> 1) << 8 | _ytable[p1] << 0;
  373. }
  374. }
  375. }
  376. auto renderYV12(uint width, uint height) -> void {
  377. for(uint y : range(height >> 1)) {
  378. auto input0 = (const uint32_t*)_buffer + (2 * y + 0) * width;
  379. auto input1 = (const uint32_t*)_buffer + (2 * y + 1) * width;
  380. auto youtput0 = (uint16_t*)_image->data + (_image->offsets[0] >> 1) + (2 * y + 0) * (_image->pitches[0] >> 1);
  381. auto youtput1 = (uint16_t*)_image->data + (_image->offsets[0] >> 1) + (2 * y + 1) * (_image->pitches[0] >> 1);
  382. auto voutput = (uint8_t*)_image->data + _image->offsets[1] + y * _image->pitches[1];
  383. auto uoutput = (uint8_t*)_image->data + _image->offsets[2] + y * _image->pitches[2];
  384. for(uint x : range(width >> 1)) {
  385. uint16_t p0 = toRGB16(*input0++);
  386. uint16_t p1 = toRGB16(*input0++);
  387. uint16_t p2 = toRGB16(*input1++);
  388. uint16_t p3 = toRGB16(*input1++);
  389. *youtput0++ = _ytable[p0] << 0 | _ytable[p1] << 8;
  390. *youtput1++ = _ytable[p2] << 0 | _ytable[p3] << 8;
  391. *voutput++ = (_vtable[p0] + _vtable[p1] + _vtable[p2] + _vtable[p3]) >> 2;
  392. *uoutput++ = (_utable[p0] + _utable[p1] + _utable[p2] + _utable[p3]) >> 2;
  393. }
  394. }
  395. }
  396. auto renderI420(uint width, uint height) -> void {
  397. for(uint y : range(height >> 1)) {
  398. auto input0 = (const uint32_t*)_buffer + (2 * y + 0) * width;
  399. auto input1 = (const uint32_t*)_buffer + (2 * y + 1) * width;
  400. auto youtput0 = (uint16_t*)_image->data + (_image->offsets[0] >> 1) + (2 * y + 0) * (_image->pitches[0] >> 1);
  401. auto youtput1 = (uint16_t*)_image->data + (_image->offsets[0] >> 1) + (2 * y + 1) * (_image->pitches[0] >> 1);
  402. auto uoutput = (uint8_t*)_image->data + _image->offsets[1] + y * _image->pitches[1];
  403. auto voutput = (uint8_t*)_image->data + _image->offsets[2] + y * _image->pitches[2];
  404. for(uint x : range(width >> 1)) {
  405. uint16_t p0 = toRGB16(*input0++);
  406. uint16_t p1 = toRGB16(*input0++);
  407. uint16_t p2 = toRGB16(*input1++);
  408. uint16_t p3 = toRGB16(*input1++);
  409. *youtput0++ = _ytable[p0] << 0 | _ytable[p1] << 8;
  410. *youtput1++ = _ytable[p2] << 0 | _ytable[p3] << 8;
  411. *uoutput++ = (_utable[p0] + _utable[p1] + _utable[p2] + _utable[p3]) >> 2;
  412. *voutput++ = (_vtable[p0] + _vtable[p1] + _vtable[p2] + _vtable[p3]) >> 2;
  413. }
  414. }
  415. }
  416. inline auto toRGB15(uint32_t rgb32) const -> uint16_t {
  417. return ((rgb32 >> 9) & 0x7c00) + ((rgb32 >> 6) & 0x03e0) + ((rgb32 >> 3) & 0x001f);
  418. }
  419. inline auto toRGB16(uint32_t rgb32) const -> uint16_t {
  420. return ((rgb32 >> 8) & 0xf800) + ((rgb32 >> 5) & 0x07e0) + ((rgb32 >> 3) & 0x001f);
  421. }
  422. auto initializeTables() -> void {
  423. _ytable = new uint8_t[65536];
  424. _utable = new uint8_t[65536];
  425. _vtable = new uint8_t[65536];
  426. for(uint n : range(65536)) {
  427. //extract RGB565 color data from i
  428. uint8_t r = (n >> 11) & 31, g = (n >> 5) & 63, b = (n) & 31;
  429. r = (r << 3) | (r >> 2); //R5->R8
  430. g = (g << 2) | (g >> 4); //G6->G8
  431. b = (b << 3) | (b >> 2); //B5->B8
  432. //ITU-R Recommendation BT.601
  433. //double lr = 0.299, lg = 0.587, lb = 0.114;
  434. int y = int( +(double(r) * 0.257) + (double(g) * 0.504) + (double(b) * 0.098) + 16.0 );
  435. int u = int( -(double(r) * 0.148) - (double(g) * 0.291) + (double(b) * 0.439) + 128.0 );
  436. int v = int( +(double(r) * 0.439) - (double(g) * 0.368) - (double(b) * 0.071) + 128.0 );
  437. //ITU-R Recommendation BT.709
  438. //double lr = 0.2126, lg = 0.7152, lb = 0.0722;
  439. //int y = int( double(r) * lr + double(g) * lg + double(b) * lb );
  440. //int u = int( (double(b) - y) / (2.0 - 2.0 * lb) + 128.0 );
  441. //int v = int( (double(r) - y) / (2.0 - 2.0 * lr) + 128.0 );
  442. _ytable[n] = y < 0 ? 0 : y > 255 ? 255 : y;
  443. _utable[n] = u < 0 ? 0 : u > 255 ? 255 : u;
  444. _vtable[n] = v < 0 ? 0 : v > 255 ? 255 : v;
  445. }
  446. }
  447. bool _ready = false;
  448. uint _width = 0;
  449. uint _height = 0;
  450. uint32_t* _buffer = nullptr;
  451. uint _bufferWidth = 0;
  452. uint _bufferHeight = 0;
  453. uint8_t* _ytable = nullptr;
  454. uint8_t* _utable = nullptr;
  455. uint8_t* _vtable = nullptr;
  456. Display* _display = nullptr;
  457. uint _monitorX = 0;
  458. uint _monitorY = 0;
  459. uint _monitorWidth = 0;
  460. uint _monitorHeight = 0;
  461. uint _screen = 0;
  462. GC _gc = 0;
  463. Window _parent = 0;
  464. Window _window = 0;
  465. Colormap _colormap = 0;
  466. XShmSegmentInfo _shmInfo;
  467. int _port = -1;
  468. XvImage* _image = nullptr;
  469. vector<int> _formatIDs;
  470. vector<string> _formatNames;
  471. int _formatID = 0;
  472. string _formatName;
  473. };