mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-12-24 11:58:41 +00:00
gpu_display: provides wayland based output for virtio-gpu
This provides virtual display style output, useful for debugging virtio-gpu. Although using virtio-gpu for display purposes clashes with the more integreated virtio-wayland support, it is nonetheless helpful for debugging virtio-gpu, and is technically required to fully implement that device. TEST=cargo build -p gpu_display BUG=chromium:837073 CQ-DEPEND=CL:1096300 Change-Id: I59f895e951ef593d4119e7558168dd34223519ee Reviewed-on: https://chromium-review.googlesource.com/1043446 Commit-Ready: Zach Reizner <zachr@chromium.org> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org>
This commit is contained in:
parent
ac5a8dbe50
commit
20d71f8928
12 changed files with 3148 additions and 0 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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" }
|
||||
|
|
12
gpu_display/Cargo.toml
Normal file
12
gpu_display/Cargo.toml
Normal file
|
@ -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"
|
96
gpu_display/build.rs
Normal file
96
gpu_display/build.rs
Normal file
|
@ -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<P: AsRef<Path>, O: AsRef<OsStr>>(path: P, name: O) -> Option<PathBuf> {
|
||||
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<P: AsRef<Path>>(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");
|
||||
}
|
11
gpu_display/examples/simple.rs
Normal file
11
gpu_display/examples/simple.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
202
gpu_display/protocol/aura-shell.xml
Normal file
202
gpu_display/protocol/aura-shell.xml
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="aura_shell">
|
||||
<copyright>
|
||||
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.
|
||||
</copyright>
|
||||
<interface name="zaura_shell" version="6">
|
||||
<description summary="aura_shell">
|
||||
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.
|
||||
</description>
|
||||
<enum name="error">
|
||||
<entry name="aura_surface_exists" value="0"
|
||||
summary="the surface already has an aura surface object associated"/>
|
||||
<entry name="aura_output_exists" value="1"
|
||||
summary="the output already has an aura output object associated"/>
|
||||
</enum>
|
||||
<request name="get_aura_surface">
|
||||
<description summary="extend surface interface for aura shell">
|
||||
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.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zaura_surface"
|
||||
summary="the new aura surface interface id"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"
|
||||
summary="the surface"/>
|
||||
</request>
|
||||
<!-- Version 2 additions -->
|
||||
<request name="get_aura_output" since="2">
|
||||
<description summary="extend output interface for aura shell">
|
||||
Instantiate an interface extension for the given wl_output to
|
||||
provide aura shell functionality.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zaura_output"
|
||||
summary="the new aura output interface id"/>
|
||||
<arg name="output" type="object" interface="wl_output"
|
||||
summary="the output"/>
|
||||
</request>
|
||||
</interface>
|
||||
<interface name="zaura_surface" version="5">
|
||||
<description summary="aura shell interface to a wl_surface">
|
||||
An additional interface to a wl_surface object, which allows the
|
||||
client to access aura shell specific functionality for surface.
|
||||
</description>
|
||||
<enum name="frame_type">
|
||||
<description summary="different frame types">
|
||||
Frame types that can be used to decorate a surface.
|
||||
</description>
|
||||
<entry name="none" value="0" summary="no frame"/>
|
||||
<entry name="normal" value="1" summary="caption with shadow" />
|
||||
<entry name="shadow" value="2" summary="shadow only"/>
|
||||
</enum>
|
||||
<request name="set_frame">
|
||||
<description summary="request a frame for surface">
|
||||
Suggests a surface should use a specific frame.
|
||||
</description>
|
||||
<arg name="type" type="uint" summary="the new frame type"/>
|
||||
</request>
|
||||
<!-- Version 2 additions -->
|
||||
<request name="set_parent" since="2">
|
||||
<description summary="set the parent of this surface">
|
||||
Set the "parent" of this surface. "x" and "y" arguments specify the
|
||||
initial position for surface relative to parent.
|
||||
</description>
|
||||
<arg name="parent" type="object" interface="zaura_surface" allow-null="true"/>
|
||||
<arg name="x" type="int"/>
|
||||
<arg name="y" type="int"/>
|
||||
</request>
|
||||
<!-- Version 3 additions -->
|
||||
<request name="set_frame_colors" since="3">
|
||||
<description summary="set the frame colors of this surface">
|
||||
Set the frame colors.
|
||||
</description>
|
||||
<arg name="active_color" type="uint" summary="32 bit ARGB color value, not premultiplied"/>
|
||||
<arg name="inactive_color" type="uint" summary="32 bit ARGB color value, not premultiplied"/>
|
||||
</request>
|
||||
<!-- Version 4 additions -->
|
||||
<request name="set_startup_id" since="4">
|
||||
<description summary="set the startup ID of this surface">
|
||||
Set the startup ID.
|
||||
</description>
|
||||
<arg name="startup_id" type="string" allow-null="true"/>
|
||||
</request>
|
||||
<!-- Version 5 additions -->
|
||||
<request name="set_application_id" since="5">
|
||||
<description summary="set the application ID of this surface">
|
||||
Set the application ID.
|
||||
</description>
|
||||
<arg name="application_id" type="string" allow-null="true"/>
|
||||
</request>
|
||||
</interface>
|
||||
<interface name="zaura_output" version="6">
|
||||
<description summary="aura shell interface to a wl_output">
|
||||
An additional interface to a wl_output object, which allows the
|
||||
client to access aura shell specific functionality for output.
|
||||
</description>
|
||||
<!-- Version 2 additions -->
|
||||
<enum name="scale_property" bitfield="true">
|
||||
<description summary="scale information">
|
||||
These flags describe properties of an output scale.
|
||||
They are used in the flags bitfield of the scale event.
|
||||
</description>
|
||||
<entry name="current" value="0x1"
|
||||
summary="indicates this is the current scale"/>
|
||||
<entry name="preferred" value="0x2"
|
||||
summary="indicates this is the preferred scale"/>
|
||||
</enum>
|
||||
<enum name="scale_factor">
|
||||
<entry name="0400" value="400"/>
|
||||
<entry name="0500" value="500"/>
|
||||
<entry name="0550" value="550"/>
|
||||
<entry name="0600" value="600"/>
|
||||
<entry name="0625" value="625"/>
|
||||
<entry name="0650" value="650"/>
|
||||
<entry name="0700" value="700"/>
|
||||
<entry name="0750" value="750"/>
|
||||
<entry name="0800" value="800"/>
|
||||
<entry name="0850" value="850"/>
|
||||
<entry name="0900" value="900"/>
|
||||
<entry name="0950" value="950"/>
|
||||
<entry name="1000" value="1000"/>
|
||||
<entry name="1050" value="1050"/>
|
||||
<entry name="1100" value="1100"/>
|
||||
<entry name="1150" value="1150"/>
|
||||
<entry name="1125" value="1125"/>
|
||||
<entry name="1200" value="1200"/>
|
||||
<entry name="1250" value="1250"/>
|
||||
<entry name="1300" value="1300"/>
|
||||
<entry name="1400" value="1400"/>
|
||||
<entry name="1450" value="1450"/>
|
||||
<entry name="1500" value="1500"/>
|
||||
<entry name="1600" value="1600"/>
|
||||
<entry name="1750" value="1750"/>
|
||||
<entry name="1800" value="1800"/>
|
||||
<entry name="2000" value="2000"/>
|
||||
<entry name="2200" value="2200"/>
|
||||
<entry name="2250" value="2250"/>
|
||||
<entry name="2500" value="2500"/>
|
||||
<entry name="2750" value="2750"/>
|
||||
<entry name="3000" value="3000"/>
|
||||
<entry name="3500" value="3500"/>
|
||||
<entry name="4000" value="4000"/>
|
||||
<entry name="4500" value="4500"/>
|
||||
<entry name="5000" value="5000"/>
|
||||
</enum>
|
||||
<event name="scale" since="2">
|
||||
<description summary="advertise available scales for the output">
|
||||
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.
|
||||
</description>
|
||||
<arg name="flags" type="uint" enum="scale_property" summary="bitfield of scale flags"/>
|
||||
<arg name="scale" type="uint" enum="scale_factor" summary="output scale"/>
|
||||
</event>
|
||||
<!-- Version 5 additions -->
|
||||
<enum name="connection_type">
|
||||
<entry name="unknown" value="0"/>
|
||||
<entry name="internal" value="1"/>
|
||||
</enum>
|
||||
<event name="connection" since="5">
|
||||
<description summary="advertise connection for the output">
|
||||
The connection event describes how the output is connected.
|
||||
The event is sent when binding to the output object.
|
||||
</description>
|
||||
<arg name="connection" type="uint" enum="connection_type" summary="output connection"/>
|
||||
</event>
|
||||
<event name="device_scale_factor" since="5">
|
||||
<description summary="advertise device scale factor for the output">
|
||||
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.
|
||||
</description>
|
||||
<arg name="scale" type="uint" enum="scale_factor" summary="output device scale factor"/>
|
||||
</event>
|
||||
</interface>
|
||||
</protocol>
|
348
gpu_display/protocol/linux-dmabuf-unstable-v1.xml
Normal file
348
gpu_display/protocol/linux-dmabuf-unstable-v1.xml
Normal file
|
@ -0,0 +1,348 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="linux_dmabuf_unstable_v1">
|
||||
|
||||
<copyright>
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwp_linux_dmabuf_v1" version="3">
|
||||
<description summary="factory for creating dmabuf-based wl_buffers">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="unbind the factory">
|
||||
Objects created through this interface, especially wl_buffers, will
|
||||
remain valid.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="create_params">
|
||||
<description summary="create a temporary object for buffer parameters">
|
||||
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.
|
||||
</description>
|
||||
<arg name="params_id" type="new_id" interface="zwp_linux_buffer_params_v1"
|
||||
summary="the new temporary"/>
|
||||
</request>
|
||||
|
||||
<event name="format">
|
||||
<description summary="supported buffer format">
|
||||
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.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
</event>
|
||||
|
||||
<event name="modifier" since="3">
|
||||
<description summary="supported buffer format modifier">
|
||||
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.
|
||||
</description>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="modifier_hi" type="uint"
|
||||
summary="high 32 bits of layout modifier"/>
|
||||
<arg name="modifier_lo" type="uint"
|
||||
summary="low 32 bits of layout modifier"/>
|
||||
</event>
|
||||
</interface>
|
||||
|
||||
<interface name="zwp_linux_buffer_params_v1" version="3">
|
||||
<description summary="parameters for creating a dmabuf-based wl_buffer">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="already_used" value="0"
|
||||
summary="the dmabuf_batch object has already been used to create a wl_buffer"/>
|
||||
<entry name="plane_idx" value="1"
|
||||
summary="plane index out of bounds"/>
|
||||
<entry name="plane_set" value="2"
|
||||
summary="the plane index was already set"/>
|
||||
<entry name="incomplete" value="3"
|
||||
summary="missing or too many planes to create a buffer"/>
|
||||
<entry name="invalid_format" value="4"
|
||||
summary="format not supported"/>
|
||||
<entry name="invalid_dimensions" value="5"
|
||||
summary="invalid width or height"/>
|
||||
<entry name="out_of_bounds" value="6"
|
||||
summary="offset + stride * height goes out of dmabuf bounds"/>
|
||||
<entry name="invalid_wl_buffer" value="7"
|
||||
summary="invalid wl_buffer resulted from importing dmabufs via
|
||||
the create_immed request on given buffer_params"/>
|
||||
</enum>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="delete this object, used or not">
|
||||
Cleans up the temporary data sent to the server for dmabuf-based
|
||||
wl_buffer creation.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<request name="add">
|
||||
<description summary="add a dmabuf to the temporary set">
|
||||
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.
|
||||
</description>
|
||||
<arg name="fd" type="fd" summary="dmabuf fd"/>
|
||||
<arg name="plane_idx" type="uint" summary="plane index"/>
|
||||
<arg name="offset" type="uint" summary="offset in bytes"/>
|
||||
<arg name="stride" type="uint" summary="stride in bytes"/>
|
||||
<arg name="modifier_hi" type="uint"
|
||||
summary="high 32 bits of layout modifier"/>
|
||||
<arg name="modifier_lo" type="uint"
|
||||
summary="low 32 bits of layout modifier"/>
|
||||
</request>
|
||||
|
||||
<enum name="flags">
|
||||
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
|
||||
<entry name="interlaced" value="2" summary="content is interlaced"/>
|
||||
<entry name="bottom_first" value="4" summary="bottom field first"/>
|
||||
</enum>
|
||||
|
||||
<request name="create">
|
||||
<description summary="create a wl_buffer from the given dmabufs">
|
||||
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.
|
||||
</description>
|
||||
<arg name="width" type="int" summary="base plane width in pixels"/>
|
||||
<arg name="height" type="int" summary="base plane height in pixels"/>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="flags" type="uint" summary="see enum flags"/>
|
||||
</request>
|
||||
|
||||
<event name="created">
|
||||
<description summary="buffer creation succeeded">
|
||||
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.
|
||||
</description>
|
||||
<arg name="buffer" type="new_id" interface="wl_buffer"
|
||||
summary="the newly created wl_buffer"/>
|
||||
</event>
|
||||
|
||||
<event name="failed">
|
||||
<description summary="buffer creation failed">
|
||||
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.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<request name="create_immed" since="2">
|
||||
<description summary="immediately create a wl_buffer from the given
|
||||
dmabufs">
|
||||
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.
|
||||
</description>
|
||||
<arg name="buffer_id" type="new_id" interface="wl_buffer"
|
||||
summary="id for the newly created wl_buffer"/>
|
||||
<arg name="width" type="int" summary="base plane width in pixels"/>
|
||||
<arg name="height" type="int" summary="base plane height in pixels"/>
|
||||
<arg name="format" type="uint" summary="DRM_FORMAT code"/>
|
||||
<arg name="flags" type="uint" summary="see enum flags"/>
|
||||
</request>
|
||||
|
||||
</interface>
|
||||
|
||||
</protocol>
|
186
gpu_display/protocol/viewporter.xml
Normal file
186
gpu_display/protocol/viewporter.xml
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="viewporter">
|
||||
|
||||
<copyright>
|
||||
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.
|
||||
</copyright>
|
||||
|
||||
<interface name="wp_viewporter" version="1">
|
||||
<description summary="surface cropping and scaling">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="unbind from the cropping and scaling interface">
|
||||
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.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="viewport_exists" value="0"
|
||||
summary="the surface already has a viewport object associated"/>
|
||||
</enum>
|
||||
|
||||
<request name="get_viewport">
|
||||
<description summary="extend surface interface for crop and scale">
|
||||
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.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="wp_viewport"
|
||||
summary="the new viewport interface id"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"
|
||||
summary="the surface"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
<interface name="wp_viewport" version="1">
|
||||
<description summary="crop and scale interface to a wl_surface">
|
||||
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.
|
||||
</description>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="remove scaling and cropping from the surface">
|
||||
The associated wl_surface's crop and scale state is removed.
|
||||
The change is applied on the next wl_surface.commit.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="bad_value" value="0"
|
||||
summary="negative or zero values in width or height"/>
|
||||
<entry name="bad_size" value="1"
|
||||
summary="destination size is not integer"/>
|
||||
<entry name="out_of_buffer" value="2"
|
||||
summary="source rectangle extends outside of the content area"/>
|
||||
<entry name="no_surface" value="3"
|
||||
summary="the wl_surface was destroyed"/>
|
||||
</enum>
|
||||
|
||||
<request name="set_source">
|
||||
<description summary="set the source rectangle for cropping">
|
||||
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.
|
||||
</description>
|
||||
<arg name="x" type="fixed" summary="source rectangle x"/>
|
||||
<arg name="y" type="fixed" summary="source rectangle y"/>
|
||||
<arg name="width" type="fixed" summary="source rectangle width"/>
|
||||
<arg name="height" type="fixed" summary="source rectangle height"/>
|
||||
</request>
|
||||
|
||||
<request name="set_destination">
|
||||
<description summary="set the surface size for scaling">
|
||||
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.
|
||||
</description>
|
||||
<arg name="width" type="int" summary="surface width"/>
|
||||
<arg name="height" type="int" summary="surface height"/>
|
||||
</request>
|
||||
</interface>
|
||||
|
||||
</protocol>
|
1044
gpu_display/protocol/xdg-shell-unstable-v6.xml
Normal file
1044
gpu_display/protocol/xdg-shell-unstable-v6.xml
Normal file
File diff suppressed because it is too large
Load diff
778
gpu_display/src/display_wl.c
Normal file
778
gpu_display/src/display_wl.c
Normal file
|
@ -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 <assert.h>
|
||||
#include <memory.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "aura-shell.h"
|
||||
#include "linux-dmabuf-unstable-v1.h"
|
||||
#include "viewporter.h"
|
||||
#include "xdg-shell-unstable-v6.h"
|
||||
#include <wayland-client-core.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
81
gpu_display/src/dwl.rs
Normal file
81
gpu_display/src/dwl.rs
Normal file
|
@ -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);
|
||||
}
|
378
gpu_display/src/lib.rs
Normal file
378
gpu_display/src/lib.rs
Normal file
|
@ -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<usize>,
|
||||
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<u32, DwlDmabuf>,
|
||||
dmabuf_next_id: u32,
|
||||
surfaces: HashMap<u32, GpuDisplaySurface>,
|
||||
surface_next_id: u32,
|
||||
}
|
||||
|
||||
impl GpuDisplay {
|
||||
/// Opens a fresh connection to the compositor.
|
||||
pub fn new() -> Result<GpuDisplay, GpuDisplayError> {
|
||||
// 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<u32, GpuDisplayError> {
|
||||
// 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<u32>,
|
||||
width: u32,
|
||||
height: u32)
|
||||
-> Result<u32, GpuDisplayError> {
|
||||
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<VolatileSlice> {
|
||||
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) }
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue