diff --git a/Cargo.lock b/Cargo.lock index 5664212ba6..6085049221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,7 @@ dependencies = [ "data_model 0.1.0", "devices 0.1.0", "gpu_buffer 0.1.0", + "gpu_display 0.1.0", "io_jail 0.1.0", "kernel_cmdline 0.1.0", "kernel_loader 0.1.0", @@ -131,6 +132,16 @@ dependencies = [ "sys_util 0.1.0", ] +[[package]] +name = "gpu_display" +version = "0.1.0" +dependencies = [ + "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "data_model 0.1.0", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "sys_util 0.1.0", +] + [[package]] name = "io_jail" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b669011dfd..9ec3e75dd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer"] arch = { path = "arch" } devices = { path = "devices" } gpu_buffer = { path = "gpu_buffer", optional = true } +gpu_display = { path = "gpu_display", optional = true } io_jail = { path = "io_jail" } kvm = { path = "kvm" } kvm_sys = { path = "kvm_sys" } diff --git a/gpu_display/Cargo.toml b/gpu_display/Cargo.toml new file mode 100644 index 0000000000..fbd16ff7de --- /dev/null +++ b/gpu_display/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gpu_display" +version = "0.1.0" +authors = ["The Chromium OS Authors"] + +[dependencies] +data_model = { path = "../data_model" } +libc = "*" +sys_util = { path = "../sys_util" } + +[build-dependencies] +cc = "=1.0.15" diff --git a/gpu_display/build.rs b/gpu_display/build.rs new file mode 100644 index 0000000000..a1ce4f77b7 --- /dev/null +++ b/gpu_display/build.rs @@ -0,0 +1,96 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +extern crate cc; + +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; + +// Performs a recursive search for a file with name under path and returns the full path if such a +// file is found. +fn scan_path, O: AsRef>(path: P, name: O) -> Option { + for entry in fs::read_dir(path).ok()? { + if let Ok(entry) = entry { + let file_type = match entry.file_type() { + Ok(t) => t, + Err(_) => continue, + }; + + if file_type.is_file() && entry.file_name() == name.as_ref() { + return Some(entry.path()); + } else if file_type.is_dir() { + if let Some(found) = scan_path(entry.path(), name.as_ref()) { + return Some(found); + } + } + } + } + None +} + +// Searches for the given protocol in both the system wide and bundles protocols path. +fn find_protocol(name: &str) -> PathBuf { + let protocols_path = + env::var("WAYLAND_PROTOCOLS_PATH").unwrap_or("/usr/share/wayland-protocols".to_owned()); + let protocol_file_name = PathBuf::from(format!("{}.xml", name)); + + // Prioritize the systems wayland protocols before using the bundled ones. + if let Some(found) = scan_path(protocols_path, &protocol_file_name) { + return found; + } + + // Use bundled protocols as a fallback. + let protocol_path = Path::new("protocol").join(protocol_file_name); + assert!(protocol_path.is_file(), + "unable to locate wayland protocol specification for `{}`", + name); + protocol_path +} + +fn compile_protocol>(name: &str, out: P) -> PathBuf { + let in_protocol = find_protocol(name); + println!("cargo:rerun-if-changed={}", in_protocol.display()); + let out_code = out.as_ref().join(format!("{}.c", name)); + let out_header = out.as_ref().join(format!("{}.h", name)); + eprintln!("building protocol: {}", name); + Command::new("wayland-scanner") + .arg("code") + .arg(&in_protocol) + .arg(&out_code) + .output() + .unwrap(); + Command::new("wayland-scanner") + .arg("client-header") + .arg(&in_protocol) + .arg(&out_header) + .output() + .unwrap(); + out_code +} + +fn main() { + println!("cargo:rerun-if-env-changed=WAYLAND_PROTOCOLS_PATH"); + let out_dir = env::var("OUT_DIR").unwrap(); + + let mut build = cc::Build::new(); + build.warnings(true); + build.warnings_into_errors(true); + build.include(&out_dir); + build.flag("-std=gnu11"); + build.file("src/display_wl.c"); + println!("cargo:rerun-if-changed=src/display_wl.c"); + + for protocol in &["aura-shell", + "linux-dmabuf-unstable-v1", + "xdg-shell-unstable-v6", + "viewporter"] { + build.file(compile_protocol(protocol, &out_dir)); + } + build.compile("display_wl"); + + println!("cargo:rustc-link-lib=dylib=wayland-client"); +} diff --git a/gpu_display/examples/simple.rs b/gpu_display/examples/simple.rs new file mode 100644 index 0000000000..2ca9ab7b05 --- /dev/null +++ b/gpu_display/examples/simple.rs @@ -0,0 +1,11 @@ +extern crate gpu_display; + +use gpu_display::*; + +fn main() { + let mut disp = GpuDisplay::new().unwrap(); + let surface_id = disp.create_surface(None, 1280, 1024).unwrap(); + while !disp.close_requested(surface_id) { + disp.dispatch_events(); + } +} diff --git a/gpu_display/protocol/aura-shell.xml b/gpu_display/protocol/aura-shell.xml new file mode 100644 index 0000000000..74189f02eb --- /dev/null +++ b/gpu_display/protocol/aura-shell.xml @@ -0,0 +1,202 @@ + + + + Copyright 2017 The Chromium Authors. + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + The global interface exposing aura shell capabilities is used to + instantiate an interface extension for a wl_surface object. + This extended interface will then allow the client to use aura shell + specific functionality. + + + + + + + + Instantiate an interface extension for the given wl_surface to + provide aura shell functionality. If the given wl_surface is not + associated with a shell surface, the shell_surface_missing protocol + error is raised. + + + + + + + + Instantiate an interface extension for the given wl_output to + provide aura shell functionality. + + + + + + + + An additional interface to a wl_surface object, which allows the + client to access aura shell specific functionality for surface. + + + + Frame types that can be used to decorate a surface. + + + + + + + + Suggests a surface should use a specific frame. + + + + + + + Set the "parent" of this surface. "x" and "y" arguments specify the + initial position for surface relative to parent. + + + + + + + + + Set the frame colors. + + + + + + + + Set the startup ID. + + + + + + + Set the application ID. + + + + + + + An additional interface to a wl_output object, which allows the + client to access aura shell specific functionality for output. + + + + + These flags describe properties of an output scale. + They are used in the flags bitfield of the scale event. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The scale event describes an available scale for the output. + The event is sent when binding to the output object and there + will always be one scale, the current scale. The event is sent + again if an output changes scale, for the scale that is now + current. In other words, the current scale is always the last + scale that was received with the current flag set. + + + + + + + + + + + + The connection event describes how the output is connected. + The event is sent when binding to the output object. + + + + + + This event describes the device specific scale factor for the output. + The device specific scale factor is not expected the change during + the lifetime of the output. And it is not limited to an integer value + like the scale factor provided by wl_output interface. The exact + contents scale used by the compositor can be determined by combining + this device scale factor with the current output scale. + The event is sent when binding to the output object. + + + + + diff --git a/gpu_display/protocol/linux-dmabuf-unstable-v1.xml b/gpu_display/protocol/linux-dmabuf-unstable-v1.xml new file mode 100644 index 0000000000..154afe23e1 --- /dev/null +++ b/gpu_display/protocol/linux-dmabuf-unstable-v1.xml @@ -0,0 +1,348 @@ + + + + + Copyright © 2014, 2015 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + Following the interfaces from: + https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt + and the Linux DRM sub-system's AddFb2 ioctl. + + This interface offers ways to create generic dmabuf-based + wl_buffers. Immediately after a client binds to this interface, + the set of supported formats and format modifiers is sent with + 'format' and 'modifier' events. + + The following are required from clients: + + - Clients must ensure that either all data in the dma-buf is + coherent for all subsequent read access or that coherency is + correctly handled by the underlying kernel-side dma-buf + implementation. + + - Don't make any more attachments after sending the buffer to the + compositor. Making more attachments later increases the risk of + the compositor not being able to use (re-import) an existing + dmabuf-based wl_buffer. + + The underlying graphics stack must ensure the following: + + - The dmabuf file descriptors relayed to the server will stay valid + for the whole lifetime of the wl_buffer. This means the server may + at any time use those fds to import the dmabuf into any kernel + sub-system that might accept it. + + To create a wl_buffer from one or more dmabufs, a client creates a + zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params + request. All planes required by the intended format are added with + the 'add' request. Finally, a 'create' or 'create_immed' request is + issued, which has the following outcome depending on the import success. + + The 'create' request, + - on success, triggers a 'created' event which provides the final + wl_buffer to the client. + - on failure, triggers a 'failed' event to convey that the server + cannot use the dmabufs received from the client. + + For the 'create_immed' request, + - on success, the server immediately imports the added dmabufs to + create a wl_buffer. No event is sent from the server in this case. + - on failure, the server can choose to either: + - terminate the client by raising a fatal error. + - mark the wl_buffer as failed, and send a 'failed' event to the + client. If the client uses a failed wl_buffer as an argument to any + request, the behaviour is compositor implementation-defined. + + Warning! The protocol described in this file is experimental and + backward incompatible changes may be made. Backward compatible changes + may be added together with the corresponding interface version bump. + Backward incompatible changes are done by bumping the version number in + the protocol and interface names and resetting the interface version. + Once the protocol is to be declared stable, the 'z' prefix and the + version number in the protocol and interface names are removed and the + interface version number is reset. + + + + + Objects created through this interface, especially wl_buffers, will + remain valid. + + + + + + This temporary object is used to collect multiple dmabuf handles into + a single batch to create a wl_buffer. It can only be used once and + should be destroyed after a 'created' or 'failed' event has been + received. + + + + + + + This event advertises one buffer format that the server supports. + All the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees + that the client has received all supported formats. + + For the definition of the format codes, see the + zwp_linux_buffer_params_v1::create request. + + Warning: the 'format' event is likely to be deprecated and replaced + with the 'modifier' event introduced in zwp_linux_dmabuf_v1 + version 3, described below. Please refrain from using the information + received from this event. + + + + + + + This event advertises the formats that the server supports, along with + the modifiers supported for each format. All the supported modifiers + for all the supported formats are advertised once when the client + binds to this interface. A roundtrip after binding guarantees that + the client has received all supported format-modifier pairs. + + For the definition of the format and modifier codes, see the + zwp_linux_buffer_params_v1::create request. + + + + + + + + + + This temporary object is a collection of dmabufs and other + parameters that together form a single logical buffer. The temporary + object may eventually create one wl_buffer unless cancelled by + destroying it before requesting 'create'. + + Single-planar formats only require one dmabuf, however + multi-planar formats may require more than one dmabuf. For all + formats, an 'add' request must be called once per plane (even if the + underlying dmabuf fd is identical). + + You must use consecutive plane indices ('plane_idx' argument for 'add') + from zero to the number of planes used by the drm_fourcc format code. + All planes required by the format must be given exactly once, but can + be given in any order. Each plane index can be set only once. + + + + + + + + + + + + + + + + Cleans up the temporary data sent to the server for dmabuf-based + wl_buffer creation. + + + + + + This request adds one dmabuf to the set in this + zwp_linux_buffer_params_v1. + + The 64-bit unsigned value combined from modifier_hi and modifier_lo + is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the + fb modifier, which is defined in drm_mode.h of Linux UAPI. + This is an opaque token. Drivers use this token to express tiling, + compression, etc. driver-specific modifications to the base format + defined by the DRM fourcc code. + + This request raises the PLANE_IDX error if plane_idx is too large. + The error PLANE_SET is raised if attempting to set a plane that + was already set. + + + + + + + + + + + + + + + + + + This asks for creation of a wl_buffer from the added dmabuf + buffers. The wl_buffer is not created immediately but returned via + the 'created' event if the dmabuf sharing succeeds. The sharing + may fail at runtime for reasons a client cannot predict, in + which case the 'failed' event is triggered. + + The 'format' argument is a DRM_FORMAT code, as defined by the + libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the + authoritative source on how the format codes should work. + + The 'flags' is a bitfield of the flags defined in enum "flags". + 'y_invert' means the that the image needs to be y-flipped. + + Flag 'interlaced' means that the frame in the buffer is not + progressive as usual, but interlaced. An interlaced buffer as + supported here must always contain both top and bottom fields. + The top field always begins on the first pixel row. The temporal + ordering between the two fields is top field first, unless + 'bottom_first' is specified. It is undefined whether 'bottom_first' + is ignored if 'interlaced' is not set. + + This protocol does not convey any information about field rate, + duration, or timing, other than the relative ordering between the + two fields in one buffer. A compositor may have to estimate the + intended field rate from the incoming buffer rate. It is undefined + whether the time of receiving wl_surface.commit with a new buffer + attached, applying the wl_surface state, wl_surface.frame callback + trigger, presentation, or any other point in the compositor cycle + is used to measure the frame or field times. There is no support + for detecting missed or late frames/fields/buffers either, and + there is no support whatsoever for cooperating with interlaced + compositor output. + + The composited image quality resulting from the use of interlaced + buffers is explicitly undefined. A compositor may use elaborate + hardware features or software to deinterlace and create progressive + output frames from a sequence of interlaced input buffers, or it + may produce substandard image quality. However, compositors that + cannot guarantee reasonable image quality in all cases are recommended + to just reject all interlaced buffers. + + Any argument errors, including non-positive width or height, + mismatch between the number of planes and the format, bad + format, bad offset or stride, may be indicated by fatal protocol + errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, + OUT_OF_BOUNDS. + + Dmabuf import errors in the server that are not obvious client + bugs are returned via the 'failed' event as non-fatal. This + allows attempting dmabuf sharing and falling back in the client + if it fails. + + This request can be sent only once in the object's lifetime, after + which the only legal request is destroy. This object should be + destroyed after issuing a 'create' request. Attempting to use this + object after issuing 'create' raises ALREADY_USED protocol error. + + It is not mandatory to issue 'create'. If a client wants to + cancel the buffer creation, it can just destroy this object. + + + + + + + + + + This event indicates that the attempted buffer creation was + successful. It provides the new wl_buffer referencing the dmabuf(s). + + Upon receiving this event, the client should destroy the + zlinux_dmabuf_params object. + + + + + + + This event indicates that the attempted buffer creation has + failed. It usually means that one of the dmabuf constraints + has not been fulfilled. + + Upon receiving this event, the client should destroy the + zlinux_buffer_params object. + + + + + + This asks for immediate creation of a wl_buffer by importing the + added dmabufs. + + In case of import success, no event is sent from the server, and the + wl_buffer is ready to be used by the client. + + Upon import failure, either of the following may happen, as seen fit + by the implementation: + - the client is terminated with one of the following fatal protocol + errors: + - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS, + in case of argument errors such as mismatch between the number + of planes and the format, bad format, non-positive width or + height, or bad offset or stride. + - INVALID_WL_BUFFER, in case the cause for failure is unknown or + plaform specific. + - the server creates an invalid wl_buffer, marks it as failed and + sends a 'failed' event to the client. The result of using this + invalid wl_buffer as an argument in any request by the client is + defined by the compositor implementation. + + This takes the same arguments as a 'create' request, and obeys the + same restrictions. + + + + + + + + + + + diff --git a/gpu_display/protocol/viewporter.xml b/gpu_display/protocol/viewporter.xml new file mode 100644 index 0000000000..c732d8c35b --- /dev/null +++ b/gpu_display/protocol/viewporter.xml @@ -0,0 +1,186 @@ + + + + + Copyright © 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + + + + + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + + + + + + + + + + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + + + + + + + + + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + The x, y arguments of wl_surface.attach are applied as normal to + the surface. They indicate how many pixels to remove from the + surface size from the left and the top. In other words, they are + still in the surface-local coordinate system, just like dst_width + and dst_height are. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + + + + + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + + + + + + + + + + + + + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + + + + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + + + + + + + diff --git a/gpu_display/protocol/xdg-shell-unstable-v6.xml b/gpu_display/protocol/xdg-shell-unstable-v6.xml new file mode 100644 index 0000000000..1c0f92452b --- /dev/null +++ b/gpu_display/protocol/xdg-shell-unstable-v6.xml @@ -0,0 +1,1044 @@ + + + + + Copyright © 2008-2013 Kristian Høgsberg + Copyright © 2013 Rafael Antognolli + Copyright © 2013 Jasper St. Pierre + Copyright © 2010-2013 Intel Corporation + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + xdg_shell allows clients to turn a wl_surface into a "real window" + which can be dragged, resized, stacked, and moved around by the + user. Everything about this interface is suited towards traditional + desktop environments. + + + + + + + + + + + + + + Destroy this xdg_shell object. + + Destroying a bound xdg_shell object while there are surfaces + still alive created by this xdg_shell object instance is illegal + and will result in a protocol error. + + + + + + Create a positioner object. A positioner object is used to position + surfaces relative to some parent surface. See the interface description + and xdg_surface.get_popup for details. + + + + + + + This creates an xdg_surface for the given surface. While xdg_surface + itself is not a role, the corresponding surface may only be assigned + a role extending xdg_surface, such as xdg_toplevel or xdg_popup. + + This creates an xdg_surface for the given surface. An xdg_surface is + used as basis to define a role to a given surface, such as xdg_toplevel + or xdg_popup. It also manages functionality shared between xdg_surface + based surface roles. + + See the documentation of xdg_surface for more details about what an + xdg_surface is and how it is used. + + + + + + + + A client must respond to a ping event with a pong request or + the client may be deemed unresponsive. See xdg_shell.ping. + + + + + + + The ping event asks the client if it's still alive. Pass the + serial specified in the event back to the compositor by sending + a "pong" request back with the specified serial. See xdg_shell.ping. + + Compositors can use this to determine if the client is still + alive. It's unspecified what will happen if the client doesn't + respond to the ping request, or in what timeframe. Clients should + try to respond in a reasonable amount of time. + + A compositor is free to ping in any way it wants, but a client must + always respond to any xdg_shell object it created. + + + + + + + + The xdg_positioner provides a collection of rules for the placement of a + child surface relative to a parent surface. Rules can be defined to ensure + the child surface remains within the visible area's borders, and to + specify how the child surface changes its position, such as sliding along + an axis, or flipping around a rectangle. These positioner-created rules are + constrained by the requirement that a child surface must intersect with or + be at least partially adjacent to its parent surface. + + See the various requests for details about possible rules. + + At the time of the request, the compositor makes a copy of the rules + specified by the xdg_positioner. Thus, after the request is complete the + xdg_positioner object can be destroyed or reused; further changes to the + object will have no effect on previous usages. + + For an xdg_positioner object to be considered complete, it must have a + non-zero size set by set_size, and a non-zero anchor rectangle set by + set_anchor_rect. Passing an incomplete xdg_positioner object when + positioning a surface raises an error. + + + + + + + + + Notify the compositor that the xdg_positioner will no longer be used. + + + + + + Set the size of the surface that is to be positioned with the positioner + object. The size is in surface-local coordinates and corresponds to the + window geometry. See xdg_surface.set_window_geometry. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + Specify the anchor rectangle within the parent surface that the child + surface will be placed relative to. The rectangle is relative to the + window geometry as defined by xdg_surface.set_window_geometry of the + parent surface. The rectangle must be at least 1x1 large. + + When the xdg_positioner object is used to position a child surface, the + anchor rectangle may not extend outside the window geometry of the + positioned child's parent surface. + + If a zero or negative size is set the invalid_input error is raised. + + + + + + + + + + + + + + + + + + Defines a set of edges for the anchor rectangle. These are used to + derive an anchor point that the child surface will be positioned + relative to. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left position of the rectangle); otherwise, the derived + anchor point will be centered on the specified edge, or in the center of + the anchor rectangle if no edge is specified. + + If two parallel anchor edges are specified (e.g. 'left' and 'right'), + the invalid_input error is raised. + + + + + + + + + + + + + + + Defines in what direction a surface should be positioned, relative to + the anchor point of the parent surface. If two orthogonal gravities are + specified (e.g. 'bottom' and 'right'), then the child surface will be + placed in the specified direction; otherwise, the child surface will be + centered over the anchor point on any axis that had no gravity + specified. + + If two parallel gravities are specified (e.g. 'left' and 'right'), the + invalid_input error is raised. + + + + + + + The constraint adjustment value define ways the compositor will adjust + the position of the surface, if the unadjusted position would result + in the surface being partly constrained. + + Whether a surface is considered 'constrained' is left to the compositor + to determine. For example, the surface may be partly outside the + compositor's defined 'work area', thus necessitating the child surface's + position be adjusted until it is entirely inside the work area. + + The adjustments can be combined, according to a defined precedence: 1) + Flip, 2) Slide, 3) Resize. + + + + Don't alter the surface position even if it is constrained on some + axis, for example partially outside the edge of a monitor. + + + + + Slide the surface along the x axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the x axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + x axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Slide the surface along the y axis until it is no longer constrained. + + First try to slide towards the direction of the gravity on the y axis + until either the edge in the opposite direction of the gravity is + unconstrained or the edge in the direction of the gravity is + constrained. + + Then try to slide towards the opposite direction of the gravity on the + y axis until either the edge in the direction of the gravity is + unconstrained or the edge in the opposite direction of the gravity is + constrained. + + + + + Invert the anchor and gravity on the x axis if the surface is + constrained on the x axis. For example, if the left edge of the + surface is constrained, the gravity is 'left' and the anchor is + 'left', change the gravity to 'right' and the anchor to 'right'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_x adjustment will be the one before the + adjustment. + + + + + Invert the anchor and gravity on the y axis if the surface is + constrained on the y axis. For example, if the bottom edge of the + surface is constrained, the gravity is 'bottom' and the anchor is + 'bottom', change the gravity to 'top' and the anchor to 'top'. + + If the adjusted position also ends up being constrained, the resulting + position of the flip_y adjustment will be the one before the + adjustment. + + + + + Resize the surface horizontally so that it is completely + unconstrained. + + + + + Resize the surface vertically so that it is completely unconstrained. + + + + + + + Specify how the window should be positioned if the originally intended + position caused the surface to be constrained, meaning at least + partially outside positioning boundaries set by the compositor. The + adjustment is set by constructing a bitmask describing the adjustment to + be made when the surface is constrained on that axis. + + If no bit for one axis is set, the compositor will assume that the child + surface should not change its position on that axis when constrained. + + If more than one bit for one axis is set, the order of how adjustments + are applied is specified in the corresponding adjustment descriptions. + + The default adjustment is none. + + + + + + + Specify the surface position offset relative to the position of the + anchor on the anchor rectangle and the anchor on the surface. For + example if the anchor of the anchor rectangle is at (x, y), the surface + has the gravity bottom|right, and the offset is (ox, oy), the calculated + surface position will be (x + ox, y + oy). The offset position of the + surface is the one used for constraint testing. See + set_constraint_adjustment. + + An example use case is placing a popup menu on top of a user interface + element, while aligning the user interface element of the parent surface + with some user interface element placed somewhere in the popup surface. + + + + + + + + + An interface that may be implemented by a wl_surface, for + implementations that provide a desktop-style user interface. + + It provides a base set of functionality required to construct user + interface elements requiring management by the compositor, such as + toplevel windows, menus, etc. The types of functionality are split into + xdg_surface roles. + + Creating an xdg_surface does not set the role for a wl_surface. In order + to map an xdg_surface, the client must create a role-specific object + using, e.g., get_toplevel, get_popup. The wl_surface for any given + xdg_surface can have at most one role, and may not be assigned any role + not based on xdg_surface. + + A role must be assigned before any other requests are made to the + xdg_surface object. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_surface state to take effect. + + Creating an xdg_surface from a wl_surface which has a buffer attached or + committed is a client error, and any attempts by a client to attach or + manipulate a buffer prior to the first xdg_surface.configure call must + also be treated as errors. + + For a surface to be mapped by the compositor, the following conditions + must be met: (1) the client has assigned a xdg_surface based role to the + surface, (2) the client has set and committed the xdg_surface state and + the role dependent state to the surface and (3) the client has committed a + buffer to the surface. + + + + + + + + + + + Destroy the xdg_surface object. An xdg_surface must only be destroyed + after its role object has been destroyed. + + + + + + This creates an xdg_toplevel object for the given xdg_surface and gives + the associated wl_surface the xdg_toplevel role. + + See the documentation of xdg_toplevel for more details about what an + xdg_toplevel is and how it is used. + + + + + + + This creates an xdg_popup object for the given xdg_surface and gives the + associated wl_surface the xdg_popup role. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + + + The window geometry of a surface is its "visible bounds" from the + user's perspective. Client-side decorations often have invisible + portions like drop-shadows which should be ignored for the + purposes of aligning, placing and constraining windows. + + The window geometry is double buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Once the window geometry of the surface is set, it is not possible to + unset it, and it will remain the same until set_window_geometry is + called again, even if a new subsurface or buffer is attached. + + If never set, the value is the full bounds of the surface, + including any subsurfaces. This updates dynamically on every + commit. This unset is meant for extremely simple clients. + + The arguments are given in the surface-local coordinate space of + the wl_surface associated with this xdg_surface. + + The width and height must be greater than zero. Setting an invalid size + will raise an error. When applied, the effective window geometry will be + the set window geometry clamped to the bounding rectangle of the + combined geometry of the surface of the xdg_surface and the associated + subsurfaces. + + + + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + For instance, for toplevel surfaces the compositor might use this + information to move a surface to the top left only when the client has + drawn itself for the maximized or fullscreen state. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + The configure event marks the end of a configure sequence. A configure + sequence is a set of one or more events configuring the state of the + xdg_surface, including the final xdg_surface.configure event. + + Where applicable, xdg_surface surface roles will during a configure + sequence extend this event as a latched state sent as events before the + xdg_surface.configure event. Such events should be considered to make up + a set of atomically applied configuration states, where the + xdg_surface.configure commits the accumulated state. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + If the client receives multiple configure events before it can respond + to one, it is free to discard all but the last event it received. + + + + + + + + This interface defines an xdg_surface role which allows a surface to, + among other things, set window-like properties such as maximize, + fullscreen, and minimize, set application-specific metadata like title and + id, and well as trigger user interactive operations such as interactive + resize and move. + + + + + Unmap and destroy the window. The window will be effectively + hidden from the user's point of view, and all state like + maximization, fullscreen, and so on, will be lost. + + + + + + Set the "parent" of this surface. This window should be stacked + above a parent. The parent surface must be mapped as long as this + surface is mapped. + + Parent windows should be set on dialogs, toolboxes, or other + "auxiliary" surfaces, so that the parent is raised when the dialog + is raised. + + + + + + + Set a short title for the surface. + + This string may be used to identify the surface in a task bar, + window list, or other user interface elements provided by the + compositor. + + The string must be encoded in UTF-8. + + + + + + + Set an application identifier for the surface. + + The app ID identifies the general class of applications to which + the surface belongs. The compositor can use this to group multiple + surfaces together, or to determine how to launch a new application. + + For D-Bus activatable applications, the app ID is used as the D-Bus + service name. + + The compositor shell will try to group application surfaces together + by their app ID. As a best practice, it is suggested to select app + ID's that match the basename of the application's .desktop file. + For example, "org.freedesktop.FooViewer" where the .desktop file is + "org.freedesktop.FooViewer.desktop". + + See the desktop-entry specification [0] for more details on + application identifiers and how they relate to well-known D-Bus + names and .desktop files. + + [0] http://standards.freedesktop.org/desktop-entry-spec/ + + + + + + + Clients implementing client-side decorations might want to show + a context menu when right-clicking on the decorations, giving the + user a menu that they can use to maximize or minimize the window. + + This request asks the compositor to pop up such a window menu at + the given position, relative to the local surface coordinates of + the parent surface. There are no guarantees as to what menu items + the window menu contains. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. + + + + + + + + + + Start an interactive, user-driven move of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive move (touch, + pointer, etc). + + The server may ignore move requests depending on the state of + the surface (e.g. fullscreen or maximized), or if the passed serial + is no longer valid. + + If triggered, the surface will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the move. It is up to the + compositor to visually indicate that the move is taking place, such as + updating a pointer cursor, during the move. There is no guarantee + that the device focus will return when the move is completed. + + + + + + + + These values are used to indicate which edge of a surface + is being dragged in a resize operation. + + + + + + + + + + + + + + + Start a user-driven, interactive resize of the surface. + + This request must be used in response to some sort of user action + like a button press, key press, or touch down event. The passed + serial is used to determine the type of interactive resize (touch, + pointer, etc). + + The server may ignore resize requests depending on the state of + the surface (e.g. fullscreen or maximized). + + If triggered, the client will receive configure events with the + "resize" state enum value and the expected sizes. See the "resize" + enum value for more details about what is required. The client + must also acknowledge configure events using "ack_configure". After + the resize is completed, the client will receive another "configure" + event without the resize state. + + If triggered, the surface also will lose the focus of the device + (wl_pointer, wl_touch, etc) used for the resize. It is up to the + compositor to visually indicate that the resize is taking place, + such as updating a pointer cursor, during the resize. There is no + guarantee that the device focus will return when the resize is + completed. + + The edges parameter specifies how the surface should be resized, + and is one of the values of the resize_edge enum. The compositor + may use this information to update the surface position for + example when dragging the top left corner. The compositor may also + use this information to adapt its behavior, e.g. choose an + appropriate cursor image. + + + + + + + + + The different state values used on the surface. This is designed for + state values like maximized, fullscreen. It is paired with the + configure event to ensure that both the client and the compositor + setting the state can be synchronized. + + States set in this way are double-buffered. They will get applied on + the next commit. + + + + The surface is maximized. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is fullscreen. The window geometry specified in the configure + event must be obeyed by the client. + + + + + The surface is being resized. The window geometry specified in the + configure event is a maximum; the client cannot resize beyond it. + Clients that have aspect ratio or cell sizing configuration can use + a smaller size, however. + + + + + Client window decorations should be painted as if the window is + active. Do not assume this means that the window actually has + keyboard or pointer focus. + + + + + + + Set a maximum size for the window. + + The client can specify a maximum size so that the compositor does + not try to configure the window beyond this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the maximum + size. The compositor may decide to ignore the values set by the + client and request a larger size. + + If never set, or a value of zero in the request, means that the + client has no expected maximum size in the given dimension. + As a result, a client wishing to reset the maximum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a maximum size to be smaller than the minimum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Set a minimum size for the window. + + The client can specify a minimum size so that the compositor does + not try to configure the window below this size. + + The width and height arguments are in window geometry coordinates. + See xdg_surface.set_window_geometry. + + Values set in this way are double-buffered. They will get applied + on the next commit. + + The compositor can use this information to allow or disallow + different states like maximize or fullscreen and draw accurate + animations. + + Similarly, a tiling window manager may use this information to + place and resize client windows in a more effective way. + + The client should not rely on the compositor to obey the minimum + size. The compositor may decide to ignore the values set by the + client and request a smaller size. + + If never set, or a value of zero in the request, means that the + client has no expected minimum size in the given dimension. + As a result, a client wishing to reset the minimum size + to an unspecified state can use zero for width and height in the + request. + + Requesting a minimum size to be larger than the maximum size of + a surface is illegal and will result in a protocol error. + + The width and height must be greater than or equal to zero. Using + strictly negative values for width and height will result in a + protocol error. + + + + + + + + Maximize the surface. + + After requesting that the surface should be maximized, the compositor + will respond by emitting a configure event with the "maximized" state + and the required window geometry. The client should then update its + content, drawing it in a maximized state, i.e. without shadow or other + decoration outside of the window geometry. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to decide how and where to maximize the + surface, for example which output and what region of the screen should + be used. + + If the surface was already maximized, the compositor will still emit + a configure event with the "maximized" state. + + + + + + Unmaximize the surface. + + After requesting that the surface should be unmaximized, the compositor + will respond by emitting a configure event without the "maximized" + state. If available, the compositor will include the window geometry + dimensions the window had prior to being maximized in the configure + request. The client must then update its content, drawing it in a + regular state, i.e. potentially with shadow, etc. The client must also + acknowledge the configure when committing the new content (see + ack_configure). + + It is up to the compositor to position the surface after it was + unmaximized; usually the position the surface had before maximizing, if + applicable. + + If the surface was already not maximized, the compositor will still + emit a configure event without the "maximized" state. + + + + + + Make the surface fullscreen. + + You can specify an output that you would prefer to be fullscreen. + If this value is NULL, it's up to the compositor to choose which + display will be used to map this surface. + + If the surface doesn't cover the whole output, the compositor will + position the surface in the center of the output and compensate with + black borders filling the rest of the output. + + + + + + + + Request that the compositor minimize your surface. There is no + way to know if the surface is currently minimized, nor is there + any way to unset minimization on this surface. + + If you are looking to throttle redrawing when minimized, please + instead use the wl_surface.frame event for this, as this will + also work with live previews on windows in Alt-Tab, Expose or + similar compositor features. + + + + + + This configure event asks the client to resize its toplevel surface or + to change its state. The configured state should not be applied + immediately. See xdg_surface.configure for details. + + The width and height arguments specify a hint to the window + about how its surface should be resized in window geometry + coordinates. See set_window_geometry. + + If the width or height arguments are zero, it means the client + should decide its own window dimension. This may happen when the + compositor needs to configure the state of the surface but doesn't + have any information about any previous or expected dimension. + + The states listed in the event specify how the width/height + arguments should be interpreted, and possibly how it should be + drawn. + + Clients must send an ack_configure in response to this event. See + xdg_surface.configure and xdg_surface.ack_configure for details. + + + + + + + + + The close event is sent by the compositor when the user + wants the surface to be closed. This should be equivalent to + the user clicking the close button in client-side decorations, + if your application has any. + + This is only a request that the user intends to close the + window. The client may choose to ignore this request, or show + a dialog to ask the user to save their data, etc. + + + + + + + A popup surface is a short-lived, temporary surface. It can be used to + implement for example menus, popovers, tooltips and other similar user + interface concepts. + + A popup can be made to take an explicit grab. See xdg_popup.grab for + details. + + When the popup is dismissed, a popup_done event will be sent out, and at + the same time the surface will be unmapped. See the xdg_popup.popup_done + event for details. + + Explicitly destroying the xdg_popup object will also dismiss the popup and + unmap the surface. Clients that want to dismiss the popup when another + surface of their own is clicked should dismiss the popup using the destroy + request. + + The parent surface must have either the xdg_toplevel or xdg_popup surface + role. + + A newly created xdg_popup will be stacked on top of all previously created + xdg_popup surfaces associated with the same xdg_toplevel. + + The parent of an xdg_popup must be mapped (see the xdg_surface + description) before the xdg_popup itself. + + The x and y arguments passed when creating the popup object specify + where the top left of the popup should be placed, relative to the + local surface coordinates of the parent surface. See + xdg_surface.get_popup. An xdg_popup must intersect with or be at least + partially adjacent to its parent surface. + + The client must call wl_surface.commit on the corresponding wl_surface + for the xdg_popup state to take effect. + + + + + + + + + This destroys the popup. Explicitly destroying the xdg_popup + object will also dismiss the popup, and unmap the surface. + + If this xdg_popup is not the "topmost" popup, a protocol error + will be sent. + + + + + + This request makes the created popup take an explicit grab. An explicit + grab will be dismissed when the user dismisses the popup, or when the + client destroys the xdg_popup. This can be done by the user clicking + outside the surface, using the keyboard, or even locking the screen + through closing the lid or a timeout. + + If the compositor denies the grab, the popup will be immediately + dismissed. + + This request must be used in response to some sort of user action like a + button press, key press, or touch down event. The serial number of the + event should be passed as 'serial'. + + The parent of a grabbing popup must either be an xdg_toplevel surface or + another xdg_popup with an explicit grab. If the parent is another + xdg_popup it means that the popups are nested, with this popup now being + the topmost popup. + + Nested popups must be destroyed in the reverse order they were created + in, e.g. the only popup you are allowed to destroy at all times is the + topmost one. + + When compositors choose to dismiss a popup, they may dismiss every + nested grabbing popup as well. When a compositor dismisses popups, it + will follow the same dismissing order as required from the client. + + The parent of a grabbing popup must either be another xdg_popup with an + active explicit grab, or an xdg_popup or xdg_toplevel, if there are no + explicit grabs already taken. + + If the topmost grabbing popup is destroyed, the grab will be returned to + the parent of the popup, if that parent previously had an explicit grab. + + If the parent is a grabbing popup which has already been dismissed, this + popup will be immediately dismissed. If the parent is a popup that did + not take an explicit grab, an error will be raised. + + During a popup grab, the client owning the grab will receive pointer + and touch events for all their surfaces as normal (similar to an + "owner-events" grab in X11 parlance), while the top most grabbing popup + will always have keyboard focus. + + + + + + + + This event asks the popup surface to configure itself given the + configuration. The configured state should not be applied immediately. + See xdg_surface.configure for details. + + The x and y arguments represent the position the popup was placed at + given the xdg_positioner rule, relative to the upper left corner of the + window geometry of the parent surface. + + + + + + + + + + The popup_done event is sent out when a popup is dismissed by the + compositor. The client should destroy the xdg_popup object at this + point. + + + + + diff --git a/gpu_display/src/display_wl.c b/gpu_display/src/display_wl.c new file mode 100644 index 0000000000..e0c4e24ed6 --- /dev/null +++ b/gpu_display/src/display_wl.c @@ -0,0 +1,778 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aura-shell.h" +#include "linux-dmabuf-unstable-v1.h" +#include "viewporter.h" +#include "xdg-shell-unstable-v6.h" +#include +#include +#include + +#define DEFAULT_SCALE 2 +#define MAX_BUFFER_COUNT 64 + +struct dwl_context; + +struct interfaces { + struct dwl_context *context; + struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; + struct wl_shm *shm; + struct wl_shell *shell; + struct wl_seat *seat; + struct zaura_shell *aura; // optional + struct zwp_linux_dmabuf_v1 *linux_dmabuf; + struct zxdg_shell_v6 *xdg_shell; + struct wp_viewporter *viewporter; // optional +}; + +struct output { + struct wl_output *output; + struct zaura_output *aura_output; + struct dwl_context *context; + uint32_t id; + uint32_t current_scale; + uint32_t device_scale_factor; + bool internal; +}; + +struct dwl_context { + struct wl_display *display; + struct interfaces ifaces; + bool output_added; + struct output outputs[8]; +}; + +#define outputs_for_each(context, pos, output) \ + for (pos = 0, output = &context->outputs[pos]; \ + pos < (sizeof(context->outputs) / sizeof(context->outputs[0])); \ + pos++, output = &context->outputs[pos]) + +struct dwl_dmabuf { + uint32_t width; + uint32_t height; + bool in_use; + struct wl_buffer *buffer; +}; + +struct dwl_surface { + struct dwl_context *context; + struct wl_surface *surface; + struct zaura_surface *aura; + struct zxdg_surface_v6 *xdg; + struct zxdg_toplevel_v6 *toplevel; + struct wp_viewport *viewport; + struct wl_subsurface *subsurface; + uint32_t width; + uint32_t height; + double scale; + bool close_requested; + size_t buffer_count; + uint64_t buffer_use_bit_mask; + struct wl_buffer *buffers[0]; +}; + +static_assert(sizeof(((struct dwl_surface *)0)->buffer_use_bit_mask) * 8 >= + MAX_BUFFER_COUNT, + "not enough bits in buffer_use_bit_mask"); + +static void output_geometry(void *data, struct wl_output *output, int x, int y, + int physical_width, int physical_height, + int subpixel, const char *make, const char *model, + int transform) +{ + (void)data; + (void)output; + (void)x; + (void)y; + (void)physical_width; + (void)physical_height; + (void)subpixel; + (void)make; + (void)model; + (void)transform; +} + +static void output_mode(void *data, struct wl_output *output, uint32_t flags, + int width, int height, int refresh) +{ + (void)data; + (void)output; + (void)flags; + (void)width; + (void)height; + (void)refresh; +} + +static void output_done(void *data, struct wl_output *output) +{ + (void)data; + (void)output; +} + +static void output_scale(void *data, struct wl_output *wl_output, + int32_t scale_factor) +{ + (void)wl_output; + struct output *output = (struct output *)data; + struct dwl_context *context = output->context; + + // If the aura interface is available, we prefer the scale factor + // reported by that. + if (context->ifaces.aura) + return; + + output->current_scale = 1000 * scale_factor; +} + +static const struct wl_output_listener output_listener = { + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale}; + +static void aura_output_scale(void *data, struct zaura_output *aura_output, + uint32_t flags, uint32_t scale) +{ + (void)aura_output; + struct output *output = (struct output *)data; + if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT) { + output->current_scale = scale; + } +} + +static void aura_output_connection(void *data, struct zaura_output *aura_output, + uint32_t connection) +{ + (void)aura_output; + struct output *output = (struct output *)data; + output->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL; +} + +static void aura_output_device_scale_factor(void *data, + struct zaura_output *aura_output, + uint32_t device_scale_factor) +{ + (void)aura_output; + struct output *output = (struct output *)data; + output->device_scale_factor = device_scale_factor; +} + +static const struct zaura_output_listener aura_output_listener = { + .scale = aura_output_scale, + .connection = aura_output_connection, + .device_scale_factor = aura_output_device_scale_factor}; + +static void dwl_context_output_add(struct dwl_context *context, + struct wl_output *wl_output, uint32_t id) +{ + size_t i; + struct output *output; + outputs_for_each(context, i, output) + { + if (output->output == NULL) { + context->output_added = true; + output->id = id; + output->output = wl_output; + output->context = context; + output->current_scale = 1000; + output->device_scale_factor = 1000; + // This is a fun little hack from reveman. The idea is + // that the first display will be internal and never get + // removed. + output->internal = i == 0; + wl_output_add_listener(output->output, &output_listener, + output); + return; + } + } +} + +static void dwl_context_output_remove_destroy(struct dwl_context *context, + uint32_t id) +{ + size_t i; + struct output *output; + outputs_for_each(context, i, output) + { + if (output->id == id) { + if (output->aura_output) + zaura_output_destroy(output->aura_output); + wl_output_destroy(output->output); + memset(output, 0, sizeof(struct output)); + return; + } + } +} + +static void dwl_context_output_get_aura(struct dwl_context *context) +{ + if (!context->ifaces.aura) + return; + + size_t i; + struct output *output; + outputs_for_each(context, i, output) + { + if (output->output != NULL && output->aura_output == NULL) { + output->aura_output = zaura_shell_get_aura_output( + context->ifaces.aura, output->output); + zaura_output_add_listener( + output->aura_output, &aura_output_listener, output); + } + } +} + +static void registry_global(void *data, struct wl_registry *registry, + uint32_t id, const char *interface, + uint32_t version) +{ + (void)version; + struct interfaces *ifaces = (struct interfaces *)data; + if (strcmp(interface, "wl_compositor") == 0) { + ifaces->compositor = (struct wl_compositor *)wl_registry_bind( + registry, id, &wl_compositor_interface, 3); + } else if (strcmp(interface, "wl_subcompositor") == 0) { + ifaces->subcompositor = + (struct wl_subcompositor *)wl_registry_bind( + registry, id, &wl_subcompositor_interface, 1); + } else if (strcmp(interface, "wl_shm") == 0) { + ifaces->shm = (struct wl_shm *)wl_registry_bind( + registry, id, &wl_shm_interface, 1); + } else if (strcmp(interface, "wl_seat") == 0) { + ifaces->seat = (struct wl_seat *)wl_registry_bind( + registry, id, &wl_seat_interface, 5); + } else if (strcmp(interface, "wl_output") == 0) { + struct wl_output *output = (struct wl_output *)wl_registry_bind( + registry, id, &wl_output_interface, 2); + dwl_context_output_add(ifaces->context, output, id); + } else if (strcmp(interface, "zaura_shell") == 0 && version >= 6) { + ifaces->aura = (struct zaura_shell *)wl_registry_bind( + registry, id, &zaura_shell_interface, 6); + } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + ifaces->linux_dmabuf = + (struct zwp_linux_dmabuf_v1 *)wl_registry_bind( + registry, id, &zwp_linux_dmabuf_v1_interface, 1); + } else if (strcmp(interface, "zxdg_shell_v6") == 0) { + ifaces->xdg_shell = (struct zxdg_shell_v6 *)wl_registry_bind( + registry, id, &zxdg_shell_v6_interface, 1); + } else if (strcmp(interface, "wp_viewporter") == 0) { + ifaces->viewporter = (struct wp_viewporter *)wl_registry_bind( + registry, id, &wp_viewporter_interface, 1); + } +} + +static void global_remove(void *data, struct wl_registry *registry, uint32_t id) +{ + (void)registry; + + struct interfaces *ifaces = (struct interfaces *)data; + // If the ID matches any output, this will remove it. Otherwise, this is + // a no-op. + dwl_context_output_remove_destroy(ifaces->context, id); + + if (ifaces->aura && + wl_proxy_get_id((struct wl_proxy *)ifaces->aura) == id) { + zaura_shell_destroy(ifaces->aura); + ifaces->aura = NULL; + } + + // TODO(zachr): deal with the removal of some of the required + // interfaces. +} + +static const struct wl_registry_listener registry_listener = { + .global = registry_global, .global_remove = global_remove}; + +static void toplevel_configure(void *data, + struct zxdg_toplevel_v6 *zxdg_toplevel_v6, + int32_t width, int32_t height, + struct wl_array *states) +{ + (void)data; + (void)zxdg_toplevel_v6; + (void)width; + (void)height; + (void)states; +} + +static void toplevel_close(void *data, + struct zxdg_toplevel_v6 *zxdg_toplevel_v6) +{ + (void)zxdg_toplevel_v6; + struct dwl_surface *surface = (struct dwl_surface *)data; + surface->close_requested = true; +} + +static const struct zxdg_toplevel_v6_listener toplevel_listener = { + .configure = toplevel_configure, .close = toplevel_close}; + +static void surface_enter(void *data, struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct dwl_surface *surface = (struct dwl_surface *)data; + + struct output *output = + (struct output *)wl_output_get_user_data(wl_output); + + surface->scale = (output->device_scale_factor / 1000.0) * + (output->current_scale / 1000.0); + + if (surface->viewport) { + wp_viewport_set_destination( + surface->viewport, ceil(surface->width / surface->scale), + ceil(surface->height / surface->scale)); + } else { + wl_surface_set_buffer_scale(wl_surface, surface->scale); + } + + wl_surface_commit(wl_surface); +} + +static void surface_leave(void *data, struct wl_surface *wl_surface, + struct wl_output *output) +{ + (void)data; + (void)wl_surface; + (void)output; +} + +static const struct wl_surface_listener surface_listener = { + .enter = surface_enter, .leave = surface_leave}; + +struct dwl_context *dwl_context_new() +{ + struct dwl_context *ctx = calloc(1, sizeof(struct dwl_context)); + ctx->ifaces.context = ctx; + return ctx; +} + +void dwl_context_destroy(struct dwl_context **self) +{ + if ((*self)->display) + wl_display_disconnect((*self)->display); + free(*self); + *self = NULL; +} + +bool dwl_context_setup(struct dwl_context *self) +{ + struct wl_display *display = wl_display_connect(NULL); + if (!display) { + printf("failed to connect to display\n"); + return false; + } + self->display = display; + wl_display_set_user_data(display, self); + + struct wl_registry *registry = wl_display_get_registry(display); + if (!registry) { + printf("failed to get registry\n"); + goto fail; + } + + struct interfaces *ifaces = &self->ifaces; + wl_registry_add_listener(registry, ®istry_listener, ifaces); + wl_display_roundtrip(display); + dwl_context_output_get_aura(self); + + if (!ifaces->shm) { + printf("missing interface shm\n"); + goto fail; + } + if (!ifaces->compositor) { + printf("missing interface compositor\n"); + goto fail; + } + if (!ifaces->subcompositor) { + printf("missing interface subcompositor\n"); + goto fail; + } + if (!ifaces->seat) { + printf("missing interface seat\n"); + goto fail; + } + if (!ifaces->linux_dmabuf) { + printf("missing interface linux_dmabuf\n"); + goto fail; + } + if (!ifaces->xdg_shell) { + printf("missing interface xdg_shell\n"); + goto fail; + } + + return true; + +fail: + wl_display_disconnect(display); + return false; +} + +int dwl_context_fd(struct dwl_context *self) +{ + return wl_display_get_fd(self->display); +} + +void dwl_context_dispatch(struct dwl_context *self) +{ + wl_display_dispatch(self->display); + if (self->output_added) { + self->output_added = false; + dwl_context_output_get_aura(self); + wl_display_roundtrip(self->display); + } +} + +static void linux_buffer_created( + void *data, struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, + struct wl_buffer *buffer) +{ + (void)zwp_linux_buffer_params_v1; + struct dwl_dmabuf *dmabuf = (struct dwl_dmabuf *)data; + dmabuf->buffer = buffer; +} + +static void linux_buffer_failed( + void *data, struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) +{ + (void)data; + (void)zwp_linux_buffer_params_v1; +} + +static const struct zwp_linux_buffer_params_v1_listener linux_buffer_listener = + {.created = linux_buffer_created, .failed = linux_buffer_failed}; + +static void dmabuf_buffer_release(void *data, struct wl_buffer *buffer) +{ + struct dwl_dmabuf *dmabuf = (struct dwl_dmabuf *)data; + (void)buffer; + + dmabuf->in_use = false; +} + +static const struct wl_buffer_listener dmabuf_buffer_listener = { + .release = dmabuf_buffer_release}; + +struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self, int fd, + uint32_t offset, uint32_t stride, + uint64_t modifiers, uint32_t width, + uint32_t height, uint32_t fourcc) +{ + struct dwl_dmabuf *dmabuf = calloc(1, sizeof(struct dwl_dmabuf)); + if (!dmabuf) { + printf("failed to allocate dwl_dmabuf\n"); + return NULL; + } + dmabuf->width = width; + dmabuf->height = height; + + struct zwp_linux_buffer_params_v1 *params = + zwp_linux_dmabuf_v1_create_params(self->ifaces.linux_dmabuf); + if (!params) { + printf("failed to allocate zwp_linux_buffer_params_v1\n"); + free(dmabuf); + return NULL; + } + + zwp_linux_buffer_params_v1_add_listener(params, &linux_buffer_listener, + dmabuf); + zwp_linux_buffer_params_v1_add(params, fd, 0 /* plane_idx */, offset, + stride, modifiers >> 32, + (uint32_t)modifiers); + zwp_linux_buffer_params_v1_create(params, width, height, fourcc, 0); + wl_display_roundtrip(self->display); + zwp_linux_buffer_params_v1_destroy(params); + + if (!dmabuf->buffer) { + printf("failed to get wl_buffer for dmabuf\n"); + free(dmabuf); + return NULL; + } + + wl_buffer_add_listener(dmabuf->buffer, &dmabuf_buffer_listener, dmabuf); + + return dmabuf; +} + +void dwl_dmabuf_destroy(struct dwl_dmabuf **self) +{ + wl_buffer_destroy((*self)->buffer); + free(*self); + *self = NULL; +} + +static void surface_buffer_release(void *data, struct wl_buffer *buffer) +{ + struct dwl_surface *surface = (struct dwl_surface *)data; + (void)buffer; + + size_t i; + for (i = 0; i < surface->buffer_count; i++) { + if (buffer == surface->buffers[i]) { + surface->buffer_use_bit_mask &= ~(1 << i); + break; + } + } +} + +static const struct wl_buffer_listener surface_buffer_listener = { + .release = surface_buffer_release}; + +struct dwl_surface *dwl_context_surface_new(struct dwl_context *self, + struct dwl_surface *parent, + int shm_fd, size_t shm_size, + size_t buffer_size, uint32_t width, + uint32_t height, uint32_t stride) +{ + if (buffer_size == 0) + return NULL; + size_t buffer_count = shm_size / buffer_size; + if (buffer_count == 0) + return NULL; + if (buffer_count > MAX_BUFFER_COUNT) + return NULL; + + struct dwl_surface *disp_surface = + calloc(1, sizeof(struct dwl_surface) + + sizeof(struct wl_buffer *) * buffer_count); + if (!disp_surface) + return NULL; + disp_surface->context = self; + disp_surface->width = width; + disp_surface->height = height; + disp_surface->scale = DEFAULT_SCALE; + disp_surface->buffer_count = buffer_count; + + struct wl_region *region = NULL; + struct wl_shm_pool *shm_pool = + wl_shm_create_pool(self->ifaces.shm, shm_fd, shm_size); + if (!shm_pool) { + printf("failed to make shm pool\n"); + goto fail; + } + + size_t i; + for (i = 0; i < buffer_count; i++) { + struct wl_buffer *buffer = wl_shm_pool_create_buffer( + shm_pool, buffer_size * i, width, height, stride, + WL_SHM_FORMAT_ARGB8888); + if (!buffer) { + printf("failed to create buffer\n"); + goto fail; + } + disp_surface->buffers[i] = buffer; + } + + for (i = 0; i < buffer_count; i++) + wl_buffer_add_listener(disp_surface->buffers[i], + &surface_buffer_listener, disp_surface); + + disp_surface->surface = + wl_compositor_create_surface(self->ifaces.compositor); + if (!disp_surface->surface) { + printf("failed to make surface\n"); + goto fail; + } + + wl_surface_add_listener(disp_surface->surface, &surface_listener, + disp_surface); + + region = wl_compositor_create_region(self->ifaces.compositor); + if (!region) { + printf("failed to make region\n"); + goto fail; + } + wl_region_add(region, 0, 0, width, height); + wl_surface_set_opaque_region(disp_surface->surface, region); + + if (!parent) { + disp_surface->xdg = zxdg_shell_v6_get_xdg_surface( + self->ifaces.xdg_shell, disp_surface->surface); + if (!disp_surface->xdg) { + printf("failed to make xdg shell surface\n"); + goto fail; + } + + disp_surface->toplevel = + zxdg_surface_v6_get_toplevel(disp_surface->xdg); + if (!disp_surface->toplevel) { + printf("failed to make toplevel xdg shell surface\n"); + goto fail; + } + zxdg_toplevel_v6_set_title(disp_surface->toplevel, "crosvm"); + zxdg_toplevel_v6_add_listener(disp_surface->toplevel, + &toplevel_listener, disp_surface); + + if (self->ifaces.aura) { + disp_surface->aura = zaura_shell_get_aura_surface( + self->ifaces.aura, disp_surface->surface); + if (!disp_surface->aura) { + printf("failed to make aura surface\n"); + goto fail; + } + zaura_surface_set_frame( + disp_surface->aura, + ZAURA_SURFACE_FRAME_TYPE_NORMAL); + } + } else { + disp_surface->subsurface = wl_subcompositor_get_subsurface( + self->ifaces.subcompositor, disp_surface->surface, + parent->surface); + if (!disp_surface->subsurface) { + printf("failed to make subsurface\n"); + goto fail; + } + wl_subsurface_set_desync(disp_surface->subsurface); + } + + if (self->ifaces.viewporter) { + disp_surface->viewport = wp_viewporter_get_viewport( + self->ifaces.viewporter, disp_surface->surface); + if (!disp_surface->viewport) { + printf("faled to make surface viewport\n"); + goto fail; + } + } + + wl_surface_attach(disp_surface->surface, disp_surface->buffers[0], 0, + 0); + wl_surface_damage(disp_surface->surface, 0, 0, width, height); + wl_region_destroy(region); + wl_shm_pool_destroy(shm_pool); + + // Needed to get outputs before iterating them. + wl_display_roundtrip(self->display); + + // Assuming that this surface will enter the internal output initially, + // trigger a surface enter for that output before doing the first + // surface commit. THis is to avoid unpleasant artifacts when the + // surface first appears. + struct output *output; + outputs_for_each(self, i, output) + { + if (output->internal) { + surface_enter(disp_surface, disp_surface->surface, + output->output); + } + } + + wl_surface_commit(disp_surface->surface); + wl_display_flush(self->display); + + return disp_surface; +fail: + if (disp_surface->viewport) + wp_viewport_destroy(disp_surface->viewport); + if (disp_surface->subsurface) + wl_subsurface_destroy(disp_surface->subsurface); + if (disp_surface->toplevel) + zxdg_toplevel_v6_destroy(disp_surface->toplevel); + if (disp_surface->xdg) + zxdg_surface_v6_destroy(disp_surface->xdg); + if (disp_surface->aura) + zaura_surface_destroy(disp_surface->aura); + if (region) + wl_region_destroy(region); + if (disp_surface->surface) + wl_surface_destroy(disp_surface->surface); + for (i = 0; i < buffer_count; i++) + if (disp_surface->buffers[i]) + wl_buffer_destroy(disp_surface->buffers[i]); + if (shm_pool) + wl_shm_pool_destroy(shm_pool); + free(disp_surface); + return NULL; +} + +void dwl_surface_destroy(struct dwl_surface **self) +{ + size_t i; + if ((*self)->viewport) + wp_viewport_destroy((*self)->viewport); + if ((*self)->subsurface) + wl_subsurface_destroy((*self)->subsurface); + if ((*self)->toplevel) + zxdg_toplevel_v6_destroy((*self)->toplevel); + if ((*self)->xdg) + zxdg_surface_v6_destroy((*self)->xdg); + if ((*self)->aura) + zaura_surface_destroy((*self)->aura); + if ((*self)->surface) + wl_surface_destroy((*self)->surface); + for (i = 0; i < (*self)->buffer_count; i++) + wl_buffer_destroy((*self)->buffers[i]); + wl_display_flush((*self)->context->display); + free(*self); + *self = NULL; +} + +void dwl_surface_commit(struct dwl_surface *self) +{ + // It is possible that we are committing frames faster than the + // compositor can put them on the screen. This may result in dropped + // frames, but this is acceptable considering there is no good way to + // apply back pressure to the guest gpu driver right now. The intention + // of this module is to help bootstrap gpu support, so it does not have + // to have artifact free rendering. + wl_surface_commit(self->surface); + wl_display_flush(self->context->display); +} + +bool dwl_surface_buffer_in_use(struct dwl_surface *self, size_t buffer_index) +{ + return (self->buffer_use_bit_mask & (1 << buffer_index)) != 0; +} + +void dwl_surface_flip(struct dwl_surface *self, size_t buffer_index) +{ + if (buffer_index >= self->buffer_count) + return; + wl_surface_attach(self->surface, self->buffers[buffer_index], 0, 0); + wl_surface_damage(self->surface, 0, 0, self->width, self->height); + dwl_surface_commit(self); + self->buffer_use_bit_mask |= 1 << buffer_index; +} + +void dwl_surface_flip_to(struct dwl_surface *self, struct dwl_dmabuf *dmabuf) +{ + if (self->width != dmabuf->width || self->height != dmabuf->height) + return; + wl_surface_attach(self->surface, dmabuf->buffer, 0, 0); + wl_surface_damage(self->surface, 0, 0, self->width, self->height); + dwl_surface_commit(self); + dmabuf->in_use = true; +} + +bool dwl_surface_close_requested(const struct dwl_surface *self) +{ + return self->close_requested; +} + +void dwl_surface_set_position(struct dwl_surface *self, uint32_t x, uint32_t y) +{ + if (self->subsurface) { + wl_subsurface_set_position(self->subsurface, x / self->scale, + y / self->scale); + wl_surface_commit(self->surface); + wl_display_flush(self->context->display); + } +} diff --git a/gpu_display/src/dwl.rs b/gpu_display/src/dwl.rs new file mode 100644 index 0000000000..9101c82f64 --- /dev/null +++ b/gpu_display/src/dwl.rs @@ -0,0 +1,81 @@ +/* automatically generated by rust-bindgen */ + +# [ repr ( C ) ] +# [ derive ( Debug , Copy , Clone ) ] +pub struct dwl_context { + _unused: [u8; 0], +} +# [ repr ( C ) ] +# [ derive ( Debug , Copy , Clone ) ] +pub struct dwl_dmabuf { + _unused: [u8; 0], +} +# [ repr ( C ) ] +# [ derive ( Debug , Copy , Clone ) ] +pub struct dwl_surface { + _unused: [u8; 0], +} +extern "C" { + pub fn dwl_context_new() -> *mut dwl_context; +} +extern "C" { + pub fn dwl_context_destroy(self_: *mut *mut dwl_context); +} +extern "C" { + pub fn dwl_context_setup(self_: *mut dwl_context) -> bool; +} +extern "C" { + pub fn dwl_context_fd(self_: *mut dwl_context) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn dwl_context_dispatch(self_: *mut dwl_context); +} +extern "C" { + pub fn dwl_context_dmabuf_new(self_: *mut dwl_context, + fd: ::std::os::raw::c_int, + offset: u32, + stride: u32, + modifiers: u64, + width: u32, + height: u32, + fourcc: u32) + -> *mut dwl_dmabuf; +} +extern "C" { + pub fn dwl_dmabuf_in_use(self_: *mut dwl_dmabuf) -> bool; +} +extern "C" { + pub fn dwl_dmabuf_destroy(self_: *mut *mut dwl_dmabuf); +} +extern "C" { + pub fn dwl_context_surface_new(self_: *mut dwl_context, + parent: *mut dwl_surface, + shm_fd: ::std::os::raw::c_int, + shm_size: usize, + buffer_size: usize, + width: u32, + height: u32, + stride: u32) + -> *mut dwl_surface; +} +extern "C" { + pub fn dwl_surface_destroy(self_: *mut *mut dwl_surface); +} +extern "C" { + pub fn dwl_surface_commit(self_: *mut dwl_surface); +} +extern "C" { + pub fn dwl_surface_buffer_in_use(self_: *mut dwl_surface, buffer_index: usize) -> bool; +} +extern "C" { + pub fn dwl_surface_flip(self_: *mut dwl_surface, buffer_index: usize); +} +extern "C" { + pub fn dwl_surface_flip_to(self_: *mut dwl_surface, dmabuf: *mut dwl_dmabuf); +} +extern "C" { + pub fn dwl_surface_close_requested(self_: *const dwl_surface) -> bool; +} +extern "C" { + pub fn dwl_surface_set_position(self_: *mut dwl_surface, x: u32, y: u32); +} \ No newline at end of file diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs new file mode 100644 index 0000000000..7f2b775dab --- /dev/null +++ b/gpu_display/src/lib.rs @@ -0,0 +1,378 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +//! Crate for displaying simple surfaces and GPU buffers over wayland. + +extern crate data_model; +extern crate sys_util; + +mod dwl; + +use std::cell::Cell; +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::ptr::null_mut; + +use data_model::{VolatileSlice, VolatileMemory}; +use sys_util::{Error as SysError, SharedMemory, MemoryMapping, round_up_to_page_size}; + +use dwl::*; + +const BUFFER_COUNT: usize = 2; +const BYTES_PER_PIXEL: u32 = 4; + +/// An error generated by `GpuDisplay`. +#[derive(Debug)] +pub enum GpuDisplayError { + /// An internal allocation failed. + Allocate, + /// Connecting to the compositor failed. + Connect, + /// Creating shared memory failed. + CreateShm(SysError), + /// Setting the size of shared memory failed. + SetSize(SysError), + /// Failed to create a surface on the compositor. + CreateSurface, + /// Failed to import a buffer to the compositor. + FailedImport, + /// The surface ID is invalid. + InvalidSurfaceId, +} + +struct DwlContext(*mut dwl_context); +impl Drop for DwlContext { + fn drop(&mut self) { + if !self.0.is_null() { + // Safe given that we checked the pointer for non-null and it should always be of the + // correct type. + unsafe { + dwl_context_destroy(&mut self.0); + } + } + } +} + +struct DwlDmabuf(*mut dwl_dmabuf); +impl Drop for DwlDmabuf { + fn drop(&mut self) { + if !self.0.is_null() { + // Safe given that we checked the pointer for non-null and it should always be of the + // correct type. + unsafe { + dwl_dmabuf_destroy(&mut self.0); + } + } + } +} + +struct DwlSurface(*mut dwl_surface); +impl Drop for DwlSurface { + fn drop(&mut self) { + if !self.0.is_null() { + // Safe given that we checked the pointer for non-null and it should always be of the + // correct type. + unsafe { + dwl_surface_destroy(&mut self.0); + } + } + } +} + +struct GpuDisplaySurface { + surface: DwlSurface, + buffer_size: usize, + buffer_index: Cell, + buffer_mem: MemoryMapping, +} + +impl GpuDisplaySurface { + fn surface(&self) -> *mut dwl_surface { + self.surface.0 + } +} + +/// A connection to the compositor and associated collection of state. +/// +/// The user of `GpuDisplay` can use `AsRawFd` to poll on the compositor connection's file +/// descriptor. When the connection is readable, `dispatch_events` can be called to process it. +pub struct GpuDisplay { + ctx: DwlContext, + dmabufs: HashMap, + dmabuf_next_id: u32, + surfaces: HashMap, + surface_next_id: u32, +} + +impl GpuDisplay { + /// Opens a fresh connection to the compositor. + pub fn new() -> Result { + // The dwl_context_new call should always be safe to call, and we check its result. + let ctx = DwlContext(unsafe { dwl_context_new() }); + if ctx.0.is_null() { + return Err(GpuDisplayError::Allocate); + } + // The dwl_context_setup call is always safe to call given that the supplied context is + // valid. and we check its result. + let setup_success = unsafe { dwl_context_setup(ctx.0) }; + if !setup_success { + return Err(GpuDisplayError::Connect); + } + + Ok(GpuDisplay { + ctx, + dmabufs: Default::default(), + dmabuf_next_id: 0, + surfaces: Default::default(), + surface_next_id: 0, + }) + } + + fn ctx(&self) -> *mut dwl_context { + self.ctx.0 + } + + fn get_surface(&self, surface_id: u32) -> Option<&GpuDisplaySurface> { + self.surfaces.get(&surface_id) + } + + /// Imports a dmabuf to the compositor for use as a surface buffer and returns a handle to it. + pub fn import_dmabuf(&mut self, + fd: RawFd, + offset: u32, + stride: u32, + modifiers: u64, + width: u32, + height: u32, + fourcc: u32) + -> Result { + // Safe given that the context pointer is valid. Any other invalid parameters would be + // rejected by dwl_context_dmabuf_new safely. We check that the resulting dmabuf is valid + // before filing it away. + let dmabuf = DwlDmabuf(unsafe { + dwl_context_dmabuf_new(self.ctx(), + fd, + offset, + stride, + modifiers, + width, + height, + fourcc) + }); + if dmabuf.0.is_null() { + return Err(GpuDisplayError::FailedImport); + } + + let next_id = self.dmabuf_next_id; + self.dmabufs.insert(next_id, dmabuf); + self.dmabuf_next_id += 1; + Ok(next_id) + } + + pub fn import_in_use(&mut self, import_id: u32) -> bool { + match self.dmabufs.get(&import_id) { + // Safe because only a valid dmabuf is used. + Some(dmabuf) => unsafe { dwl_dmabuf_in_use(dmabuf.0) }, + None => { + debug_assert!(false, "invalid import_id {}", import_id); + false + } + } + } + + /// Releases a previously imported dmabuf identified by the given handle. + pub fn release_import(&mut self, import_id: u32) { + self.dmabufs.remove(&import_id); + } + + /// Dispatches internal events that were received from the compositor since the last call to + /// `dispatch_events`. + pub fn dispatch_events(&mut self) { + // Safe given that the context pointer is valid. + unsafe { + dwl_context_dispatch(self.ctx()); + } + } + + /// Creates a surface on the the compositor as either a top level window, or child of another + /// surface, returning a handle to the new surface. + pub fn create_surface(&mut self, + parent_surface_id: Option, + width: u32, + height: u32) + -> Result { + let parent_ptr = match parent_surface_id { + Some(id) => { + match self.get_surface(id).map(|p| p.surface()) { + Some(ptr) => ptr, + None => return Err(GpuDisplayError::InvalidSurfaceId), + } + } + None => null_mut(), + }; + let row_size = width * BYTES_PER_PIXEL; + let fb_size = row_size * height; + let buffer_size = round_up_to_page_size(fb_size as usize * BUFFER_COUNT); + let mut buffer_shm = + SharedMemory::new(Some(CStr::from_bytes_with_nul(b"GpuDisplaySurface\0").unwrap())) + .map_err(GpuDisplayError::CreateShm)?; + buffer_shm + .set_size(buffer_size as u64) + .map_err(GpuDisplayError::SetSize)?; + let buffer_mem = MemoryMapping::from_fd(&buffer_shm, buffer_size).unwrap(); + + // Safe because only a valid context, parent pointer (if not None), and buffer FD are used. + // The returned surface is checked for validity before being filed away. + let surface = DwlSurface(unsafe { + dwl_context_surface_new(self.ctx(), + parent_ptr, + buffer_shm.as_raw_fd(), + buffer_size, + fb_size as usize, + width, + height, + row_size) + }); + + if surface.0.is_null() { + return Err(GpuDisplayError::CreateSurface); + } + + let next_id = self.surface_next_id; + self.surfaces + .insert(next_id, + GpuDisplaySurface { + surface, + buffer_size: fb_size as usize, + buffer_index: Cell::new(0), + buffer_mem, + }); + + self.surface_next_id += 1; + Ok(next_id) + } + + /// Releases a previously created surface identified by the given handle. + pub fn release_surface(&mut self, surface_id: u32) { + self.surfaces.remove(&surface_id); + } + + /// Gets a reference to an unused framebuffer for the identified surface. + pub fn framebuffer_memory(&self, surface_id: u32) -> Option { + let surface = self.get_surface(surface_id)?; + let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; + surface + .buffer_mem + .get_slice((buffer_index * surface.buffer_size) as u64, + surface.buffer_size as u64) + .ok() + } + + /// Commits any pending state for the identified surface. + pub fn commit(&self, surface_id: u32) { + match self.get_surface(surface_id) { + Some(surface) => { + // Safe because only a valid surface is used. + unsafe { + dwl_surface_commit(surface.surface()); + } + } + None => debug_assert!(false, "invalid surface_id {}", surface_id), + } + } + + /// Returns true if the next buffer in the buffer queue for the given surface is currently in + /// use. + /// + /// If the next buffer is in use, the memory returned from `framebuffer_memory` should not be + /// written to. + pub fn next_buffer_in_use(&self, surface_id: u32) -> bool { + match self.get_surface(surface_id) { + Some(surface) => { + let next_buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT; + // Safe because only a valid surface and buffer index is used. + unsafe { dwl_surface_buffer_in_use(surface.surface(), next_buffer_index) } + } + None => { + debug_assert!(false, "invalid surface_id {}", surface_id); + false + } + } + } + + /// Changes the visible contents of the identified surface to the contents of the framebuffer + /// last returned by `framebuffer_memory` for this surface. + pub fn flip(&self, surface_id: u32) { + match self.get_surface(surface_id) { + Some(surface) => { + surface + .buffer_index + .set((surface.buffer_index.get() + 1) % BUFFER_COUNT); + // Safe because only a valid surface and buffer index is used. + unsafe { + dwl_surface_flip(surface.surface(), surface.buffer_index.get()); + } + } + None => debug_assert!(false, "invalid surface_id {}", surface_id), + } + } + + /// Changes the visible contents of the identified surface to that of the identified imported + /// buffer. + pub fn flip_to(&self, surface_id: u32, import_id: u32) { + match self.get_surface(surface_id) { + Some(surface) => { + match self.dmabufs.get(&import_id) { + // Safe because only a valid surface and dmabuf is used. + Some(dmabuf) => unsafe { dwl_surface_flip_to(surface.surface(), dmabuf.0) }, + None => debug_assert!(false, "invalid import_id {}", import_id), + } + } + None => debug_assert!(false, "invalid surface_id {}", surface_id), + } + } + + /// Returns true if the identified top level surface has been told to close by the compositor, + /// and by extension the user. + pub fn close_requested(&self, surface_id: u32) -> bool { + match self.get_surface(surface_id) { + Some(surface) => + // Safe because only a valid surface is used. + unsafe { + dwl_surface_close_requested(surface.surface()) + }, + None => false + } + } + + /// Sets the position of the identified subsurface relative to its parent. + /// + /// The change in position will not be visible until `commit` is called for the parent surface. + pub fn set_position(&self, surface_id: u32, x: u32, y: u32) { + match self.get_surface(surface_id) { + Some(surface) => { + // Safe because only a valid surface is used. + unsafe { + dwl_surface_set_position(surface.surface(), x, y); + } + } + None => debug_assert!(false, "invalid surface_id {}", surface_id), + } + } +} + +impl Drop for GpuDisplay { + fn drop(&mut self) { + // Safe given that the context pointer is valid. + unsafe { dwl_context_destroy(&mut self.ctx.0) } + } +} + +impl AsRawFd for GpuDisplay { + fn as_raw_fd(&self) -> RawFd { + // Safe given that the context pointer is valid. + unsafe { dwl_context_fd(self.ctx.0) } + } +}