| // Copyright 2015 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "starboard/shared/x11/window_internal.h" |
| |
| #include <X11/Xatom.h> |
| #include <X11/XKBlib.h> |
| #include <X11/Xlib.h> |
| #include <X11/Xutil.h> |
| |
| #include <algorithm> |
| |
| #include "starboard/configuration.h" |
| #include "starboard/double.h" |
| #include "starboard/log.h" |
| |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| #include <X11/extensions/Xcomposite.h> |
| #include <X11/extensions/Xrender.h> |
| #endif // SB_IS(PLAYER_PUNCHED_OUT) |
| |
| using starboard::shared::starboard::player::VideoFrame; |
| |
| namespace { |
| |
| const int kWindowWidth = 1920; |
| const int kWindowHeight = 1080; |
| |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| bool HasNeededExtensionsForPunchedOutVideo(Display* display) { |
| int dummy; |
| return (XRenderQueryExtension(display, &dummy, &dummy) && |
| XCompositeQueryExtension(display, &dummy, &dummy)); |
| } |
| #endif // SB_IS(PLAYER_PUNCHED_OUT) |
| |
| } // namespace |
| |
| SbWindowPrivate::SbWindowPrivate(Display* display, |
| const SbWindowOptions* options) |
| : window(None) |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| , window_picture(None) |
| , composition_pixmap(None) |
| , composition_picture(None) |
| , video_pixmap(None) |
| , video_pixmap_width(0) |
| , video_pixmap_height(0) |
| , video_pixmap_gc(None) |
| , video_picture(None) |
| , gl_window(None) |
| , gl_picture(None) |
| #endif |
| , display(display) { |
| // Request a 32-bit depth visual for our Window. |
| XVisualInfo x_visual_info = {0}; |
| XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, |
| &x_visual_info); |
| |
| Window root_window = RootWindow(display, x_visual_info.screen); |
| |
| width = kWindowWidth; |
| height = kWindowHeight; |
| if (options && options->size.width > 0 && options->size.height > 0) { |
| width = options->size.width; |
| height = options->size.height; |
| } |
| |
| XSetWindowAttributes swa = {0}; |
| swa.event_mask = |
| KeyPressMask | KeyReleaseMask | StructureNotifyMask | FocusChangeMask; |
| swa.colormap = |
| XCreateColormap(display, root_window, x_visual_info.visual, AllocNone); |
| // Setting border_pixel to 0 is required if the requested window depth (e.g. |
| // 32) is not equal to the parent window's depth (e.g. 24). |
| swa.border_pixel = 0; |
| int attribute_flags = CWBorderPixel | CWEventMask | CWColormap; |
| |
| window = XCreateWindow(display, root_window, 0, 0, width, height, 0, |
| x_visual_info.depth, InputOutput, x_visual_info.visual, |
| attribute_flags, &swa); |
| SB_CHECK(window != None) << "Failed to create the X window."; |
| |
| const char* name = "Cobalt"; |
| if (options && options->name) { |
| name = options->name; |
| } |
| XStoreName(display, window, name); |
| |
| Atom wm_delete = XInternAtom(display, "WM_DELETE_WINDOW", True); |
| XSetWMProtocols(display, window, &wm_delete, 1); |
| |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| SB_CHECK(HasNeededExtensionsForPunchedOutVideo(display)); |
| gl_window = XCreateSimpleWindow(display, window, 0, 0, width, height, 0, |
| WhitePixel(display, DefaultScreen(display)), |
| BlackPixel(display, DefaultScreen(display))); |
| SB_CHECK(gl_window != None); |
| XMapWindow(display, gl_window); |
| // Manual redirection means that this window will only draw to its pixmap, and |
| // won't be automatically rendered onscreen. This is important, because the GL |
| // graphics will punch through any layers of windows up to, but not including, |
| // the root window. We must composite manually, in Composite(), to avoid that. |
| XCompositeRedirectWindow(display, gl_window, CompositeRedirectManual); |
| |
| gl_picture = XRenderCreatePicture( |
| display, gl_window, |
| XRenderFindStandardFormat(display, PictStandardARGB32), 0, NULL); |
| SB_CHECK(gl_picture != None); |
| |
| // Create the picture for compositing onto. This can persist across frames. |
| XRenderPictFormat* pict_format = |
| XRenderFindVisualFormat(display, x_visual_info.visual); |
| window_picture = XRenderCreatePicture(display, window, pict_format, 0, NULL); |
| SB_CHECK(window_picture != None); |
| #endif // SB_IS(PLAYER_PUNCHED_OUT) |
| |
| XMapWindow(display, window); |
| } |
| |
| SbWindowPrivate::~SbWindowPrivate() { |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| if (composition_pixmap != None) { |
| XFreePixmap(display, composition_pixmap); |
| XRenderFreePicture(display, composition_picture); |
| } |
| if (video_pixmap != None) { |
| XRenderFreePicture(display, video_picture); |
| XFreeGC(display, video_pixmap_gc); |
| XFreePixmap(display, video_pixmap); |
| } |
| XRenderFreePicture(display, gl_picture); |
| XDestroyWindow(display, gl_window); |
| XRenderFreePicture(display, window_picture); |
| #endif // SB_IS(PLAYER_PUNCHED_OUT) |
| XDestroyWindow(display, window); |
| } |
| |
| #if SB_IS(PLAYER_PUNCHED_OUT) |
| void SbWindowPrivate::Composite(int bounds_x, |
| int bounds_y, |
| int bounds_width, |
| int bounds_height, |
| VideoFrame* frame) { |
| XSynchronize(display, True); |
| XWindowAttributes window_attributes; |
| XGetWindowAttributes(display, window, &window_attributes); |
| if (window_attributes.width != width || |
| window_attributes.height != height) { |
| width = window_attributes.width; |
| height = window_attributes.height; |
| if (composition_pixmap != None) { |
| XFreePixmap(display, composition_pixmap); |
| composition_pixmap = None; |
| XRenderFreePicture(display, composition_picture); |
| composition_picture = None; |
| } |
| } |
| |
| if (composition_pixmap == None) { |
| composition_pixmap = XCreatePixmap(display, window, width, height, 32); |
| SB_DCHECK(composition_pixmap != None); |
| |
| composition_picture = XRenderCreatePicture( |
| display, composition_pixmap, |
| XRenderFindStandardFormat(display, PictStandardARGB32), 0, NULL); |
| } |
| SB_CHECK(composition_picture != None); |
| |
| XRenderColor black = {0x0000, 0x0000, 0x0000, 0xFFFF}; |
| XRenderFillRectangle(display, PictOpSrc, composition_picture, &black, 0, 0, |
| width, height); |
| |
| if (frame != NULL && frame->format() == VideoFrame::kBGRA32 && |
| frame->GetPlaneCount() > 0 && frame->width() > 0 && frame->height() > 0) { |
| if (frame->width() != video_pixmap_width || |
| frame->height() != video_pixmap_height) { |
| if (video_pixmap != None) { |
| XRenderFreePicture(display, video_picture); |
| video_picture = None; |
| XFreeGC(display, video_pixmap_gc); |
| video_pixmap_gc = None; |
| XFreePixmap(display, video_pixmap); |
| video_pixmap = None; |
| } |
| } |
| |
| if (video_pixmap == None) { |
| video_pixmap_width = frame->width(); |
| video_pixmap_height = frame->height(); |
| video_pixmap = XCreatePixmap(display, window, video_pixmap_width, |
| video_pixmap_height, 32); |
| SB_DCHECK(video_pixmap != None); |
| |
| video_pixmap_gc = XCreateGC(display, video_pixmap, 0, NULL); |
| SB_DCHECK(video_pixmap_gc != None); |
| |
| video_picture = XRenderCreatePicture( |
| display, video_pixmap, |
| XRenderFindStandardFormat(display, PictStandardARGB32), 0, NULL); |
| } |
| SB_CHECK(video_picture != None); |
| |
| XImage image = {0}; |
| image.width = frame->width(); |
| image.height = frame->height(); |
| image.format = ZPixmap; |
| image.data = const_cast<char*>( |
| reinterpret_cast<const char*>(frame->GetPlane(0).data)); |
| image.depth = 32; |
| image.bits_per_pixel = 32; |
| image.bitmap_pad = 32; |
| image.bytes_per_line = frame->GetPlane(0).pitch_in_bytes; |
| Status status = XInitImage(&image); |
| SB_DCHECK(status); |
| |
| // Upload the video frame to the X server. |
| XPutImage(display, video_pixmap, video_pixmap_gc, &image, 0, 0, |
| 0, 0, image.width, image.height); |
| |
| // Initially assume we don't have to center or scale. |
| int video_width = frame->width(); |
| int video_height = frame->height(); |
| if (bounds_width != width || bounds_height != height || |
| frame->width() != width || frame->height() != height) { |
| // Scale to fit the smallest dimension of the frame into the window. |
| double scale = |
| std::min(bounds_width / static_cast<double>(frame->width()), |
| bounds_height / static_cast<double>(frame->height())); |
| // Center the scaled frame within the window. |
| video_width = scale * frame->width(); |
| video_height = scale * frame->height(); |
| XTransform transform = {{ |
| { XDoubleToFixed(1), XDoubleToFixed(0), XDoubleToFixed(0) }, |
| { XDoubleToFixed(0), XDoubleToFixed(1), XDoubleToFixed(0) }, |
| { XDoubleToFixed(0), XDoubleToFixed(0), XDoubleToFixed(scale) } |
| }}; |
| XRenderSetPictureTransform(display, video_picture, &transform); |
| } |
| |
| int dest_x = bounds_x + (bounds_width - video_width) / 2; |
| int dest_y = bounds_y + (bounds_height - video_height) / 2; |
| XRenderComposite(display, PictOpSrc, video_picture, NULL, |
| composition_picture, 0, 0, 0, 0, dest_x, dest_y, |
| video_width, video_height); |
| } |
| |
| // Composite (with blending) the GL output on top of the composition pixmap |
| // that already has the current video frame if video is playing. |
| XRenderComposite(display, PictOpOver, gl_picture, NULL, composition_picture, |
| 0, 0, 0, 0, 0, 0, width, height); |
| |
| // Now that we have a fully-composited frame in composition_pixmap, render it |
| // to the window, which acts as our front buffer. |
| XRenderComposite(display, PictOpSrc, composition_picture, NULL, |
| window_picture, 0, 0, 0, 0, 0, 0, width, height); |
| XSynchronize(display, False); |
| } |
| #endif // SB_IS(PLAYER_PUNCHED_OUT) |