Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of WebGPU API #24708

Merged
merged 3 commits into from Nov 25, 2019
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

Large diffs are not rendered by default.

@@ -29,3 +29,5 @@ opt-level = 3
mio = { git = "https://github.com/servo/mio.git", branch = "servo" }
# https://github.com/retep998/winapi-rs/pull/816
winapi = { git = "https://github.com/servo/winapi-rs", branch = "patch-1" }
spirv_cross = { git = "https://github.com/kvark/spirv_cross", branch = "wgpu" }
wgpu-native = { git = "https://github.com/zakorgy/wgpu", branch = "v0.4" }
@@ -713,6 +713,12 @@ pub fn from_cmdline_args(mut opts: Options, args: &[String]) -> ArgumentParsingR
"A preference to set to enable",
"dom.bluetooth.enabled",
);
opts.optmulti(
"",
"pref",
"A preference to set to enable",
"dom.webgpu.enabled",
);
opts.optflag("b", "no-native-titlebar", "Do not use native titlebar");
opts.optflag("w", "webrender", "Use webrender backend");
opts.optopt("G", "graphics", "Select graphics backend (gl or es2)", "gl");
@@ -159,6 +159,9 @@ mod gen {
},
},
dom: {
webgpu: {
enabled: bool,
},
bluetooth: {
enabled: bool,
testing: {
@@ -47,6 +47,7 @@ servo_geometry = {path = "../geometry"}
servo_rand = {path = "../rand"}
servo_remutex = {path = "../remutex"}
servo_url = {path = "../url"}
webgpu = {path = "../webgpu"}
webvr_traits = {path = "../webvr_traits"}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
webxr-api = {git = "https://github.com/servo/webxr", features = ["ipc"]}
@@ -172,6 +172,7 @@ use std::sync::Arc;
use std::thread;
use style_traits::viewport::ViewportConstraints;
use style_traits::CSSPixel;
use webgpu::WebGPU;
use webvr_traits::{WebVREvent, WebVRMsg};

type PendingApprovalNavigations = HashMap<PipelineId, (LoadData, HistoryEntryReplacement)>;
@@ -440,6 +441,10 @@ pub struct Constellation<Message, LTF, STF> {
/// Entry point to create and get channels to a WebGLThread.
webgl_threads: Option<WebGLThreads>,

/// An IPC channel for the constellation to send messages to the
/// WebGPU threads.
webgpu: Option<WebGPU>,

/// A channel through which messages can be sent to the webvr thread.
webvr_chan: Option<IpcSender<WebVRMsg>>,

@@ -521,6 +526,9 @@ pub struct InitialConstellationState {
/// Entry point to create and get channels to a WebGLThread.
pub webgl_threads: Option<WebGLThreads>,

/// A channel to the WebGPU threads.
pub webgpu: Option<WebGPU>,

/// A channel to the webgl thread.
pub webvr_chan: Option<IpcSender<WebVRMsg>>,

@@ -836,6 +844,7 @@ where
(rng, prob)
}),
webgl_threads: state.webgl_threads,
webgpu: state.webgpu,
webvr_chan: state.webvr_chan,
webxr_registry: state.webxr_registry,
canvas_chan: CanvasPaintThread::start(),
@@ -1090,6 +1099,7 @@ where
.webgl_threads
.as_ref()
.map(|threads| threads.pipeline()),
webgpu: self.webgpu.clone(),
webvr_chan: self.webvr_chan.clone(),
webxr_registry: self.webxr_registry.clone(),
player_context: self.player_context.clone(),
@@ -2355,6 +2365,17 @@ where
}
}

if let Some(webgpu) = self.webgpu.as_ref() {
debug!("Exiting WebGPU thread.");
let (sender, receiver) = ipc::channel().expect("Failed to create IPC channel!");
if let Err(e) = webgpu.exit(sender) {
warn!("Exit WebGPU Thread failed ({})", e);
}
if let Err(e) = receiver.recv() {
warn!("Failed to receive exit response from WebGPU ({})", e);
}
}

if let Some(chan) = self.webvr_chan.as_ref() {
debug!("Exiting WebVR thread.");
if let Err(e) = chan.send(WebVRMsg::Exit) {
@@ -46,6 +46,7 @@ use std::process;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use webgpu::WebGPU;
use webvr_traits::WebVRMsg;

/// A `Pipeline` is the constellation's view of a `Document`. Each pipeline has an
@@ -188,6 +189,9 @@ pub struct InitialPipelineState {
/// A channel to the WebGL thread.
pub webgl_chan: Option<WebGLPipeline>,

/// A channel to the WebGPU threads.
pub webgpu: Option<WebGPU>,

/// A channel to the webvr thread.
pub webvr_chan: Option<IpcSender<WebVRMsg>>,

@@ -299,6 +303,7 @@ impl Pipeline {
webrender_document: state.webrender_document,
webgl_chan: state.webgl_chan,
webvr_chan: state.webvr_chan,
webgpu: state.webgpu,
webxr_registry: state.webxr_registry,
player_context: state.player_context,
};
@@ -504,6 +509,7 @@ pub struct UnprivilegedPipelineContent {
webrender_api_sender: webrender_api::RenderApiSender,
webrender_document: webrender_api::DocumentId,
webgl_chan: Option<WebGLPipeline>,
webgpu: Option<WebGPU>,
webvr_chan: Option<IpcSender<WebVRMsg>>,
webxr_registry: webxr_api::Registry,
player_context: WindowGLContext,
@@ -556,6 +562,7 @@ impl UnprivilegedPipelineContent {
pipeline_namespace_id: self.pipeline_namespace_id,
content_process_shutdown_chan: content_process_shutdown_chan,
webgl_chan: self.webgl_chan,
webgpu: self.webgpu,
webvr_chan: self.webvr_chan,
webxr_registry: self.webxr_registry,
webrender_document: self.webrender_document,
@@ -113,6 +113,7 @@ utf-8 = "0.7"
uuid = {version = "0.8", features = ["v4"]}
xml5ever = "0.16"
webdriver = "0.40"
webgpu = {path = "../webgpu"}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
webvr_traits = {path = "../webvr_traits"}
webxr-api = {git = "https://github.com/servo/webxr", features = ["ipc"]}
@@ -134,7 +134,10 @@ DOMInterfaces = {

'XR': {
'inCompartments': ['SupportsSessionMode', 'RequestSession'],
}
},

'GPU': {
'inCompartments': ['RequestAdapter'],
}

}
@@ -145,6 +145,7 @@ use tendril::stream::LossyDecoder;
use tendril::{StrTendril, TendrilSink};
use time::{Duration, Timespec};
use uuid::Uuid;
use webgpu::{WebGPU, WebGPUAdapter};
use webrender_api::{DocumentId, ImageKey, RenderApiSender};
use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
use webxr_api::SwapChainId as WebXRSwapChainId;
@@ -502,6 +503,8 @@ unsafe_no_jsmanaged_fields!(WebGLTextureId);
unsafe_no_jsmanaged_fields!(WebGLVertexArrayId);
unsafe_no_jsmanaged_fields!(WebGLVersion);
unsafe_no_jsmanaged_fields!(WebGLSLVersion);
unsafe_no_jsmanaged_fields!(WebGPU);
unsafe_no_jsmanaged_fields!(WebGPUAdapter);
unsafe_no_jsmanaged_fields!(WebXRSwapChainId);
unsafe_no_jsmanaged_fields!(MediaList);
unsafe_no_jsmanaged_fields!(WebVRGamepadData, WebVRGamepadState, WebVRGamepadHand);
@@ -0,0 +1,158 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use crate::compartments::InCompartment;
use crate::dom::bindings::codegen::Bindings::GPUBinding::GPURequestAdapterOptions;
use crate::dom::bindings::codegen::Bindings::GPUBinding::{self, GPUMethods, GPUPowerPreference};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::gpuadapter::GPUAdapter;
use crate::dom::promise::Promise;
use crate::task_source::TaskSource;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self, IpcSender};
use ipc_channel::router::ROUTER;
use js::jsapi::Heap;
use std::rc::Rc;
use webgpu::wgpu;
use webgpu::{WebGPU, WebGPURequest, WebGPUResponse, WebGPUResponseResult};

#[dom_struct]
pub struct GPU {
reflector_: Reflector,
}

impl GPU {
pub fn new_inherited() -> GPU {
GPU {
reflector_: Reflector::new(),
}
}

pub fn new(global: &GlobalScope) -> DomRoot<GPU> {
reflect_dom_object(Box::new(GPU::new_inherited()), global, GPUBinding::Wrap)
}

fn wgpu_channel(&self) -> Option<WebGPU> {
self.global().as_window().webgpu_channel()
}
}

pub trait AsyncWGPUListener {
fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>);
}

struct WGPUResponse<T: AsyncWGPUListener + DomObject> {
trusted: TrustedPromise,
receiver: Trusted<T>,
}

impl<T: AsyncWGPUListener + DomObject> WGPUResponse<T> {
#[allow(unrooted_must_root)]
fn response(self, response: WebGPUResponseResult) {
let promise = self.trusted.root();
match response {
Ok(response) => self.receiver.root().handle_response(response, &promise),
Err(error) => promise.reject_error(Error::Type(format!(
"Received error from WebGPU thread: {}",
error
))),
}
}
}

pub fn response_async<T: AsyncWGPUListener + DomObject + 'static>(

This comment has been minimized.

Copy link
@gterzian

gterzian Nov 15, 2019

Member

I'm wondering if the AsyncWGPUListener trait is necessary. Unless things other than the GPU can be passed to the those methods, I would say you can simply pass it as is and just define a "handle_adapter_response" method on it.

From reading the spec it seems different adapters can be returned(software or GPU), however requesting one seems to go through a single GPU object.

This comment has been minimized.

Copy link
@zakorgy

zakorgy Nov 15, 2019

Author Contributor

Our intention would be using AsyncWGPUListener trait for other WebGPU interfaces in the future, since there are a handful of promise calls in the spec.

This comment has been minimized.

Copy link
@gterzian

gterzian Nov 15, 2019

Member

Ah ok.

Would you have to update the state of those objects at some point based on what's happening in the WebGPU backend(what is currently named "WebGPUThread")?

If so, I think you're going to have to keep track of the various objects passed to the promise, either as Dom<_> or WeakRef<_>, and store those somewhere that is accessible from the object that is handling the incoming IPC message in the route. See for example how MessagePort are tracked with

pub enum MessagePortState {
and how there is only a single route used to manage various incoming messages, at
// Setup a route for IPC, for messages from the constellation to our ports.

I guess for now it's fine to keep it as it is, however I'm not sure that you can scale the response_async pattern to handle all those interfaces. We'll see.

promise: &Rc<Promise>,
receiver: &T,
) -> IpcSender<WebGPUResponseResult> {
let (action_sender, action_receiver) = ipc::channel().unwrap();
let (task_source, canceller) = receiver
.global()
.as_window()
.task_manager()
.dom_manipulation_task_source_with_canceller();
let mut trusted = Some(TrustedPromise::new(promise.clone()));
let trusted_receiver = Trusted::new(receiver);
ROUTER.add_route(
action_receiver.to_opaque(),
Box::new(move |message| {
let trusted = if let Some(trusted) = trusted.take() {
trusted
} else {
error!("WebGPU callback called twice!");
return;
};

let context = WGPUResponse {
trusted,
receiver: trusted_receiver.clone(),
};
let result = task_source.queue_with_canceller(
task!(process_webgpu_task: move|| {
context.response(message.to().unwrap());
}),
&canceller,
);
if let Err(err) = result {
error!("Failed to queue GPU listener-task: {:?}", err);
}
}),
);
action_sender
}

impl GPUMethods for GPU {
// https://gpuweb.github.io/gpuweb/#dom-gpu-requestadapter
fn RequestAdapter(
&self,
options: &GPURequestAdapterOptions,
comp: InCompartment,
) -> Rc<Promise> {
let promise = Promise::new_in_current_compartment(&self.global(), comp);
let sender = response_async(&promise, self);
let power_preference = match options.powerPreference {
Some(GPUPowerPreference::Low_power) => wgpu::PowerPreference::LowPower,
Some(GPUPowerPreference::High_performance) => wgpu::PowerPreference::HighPerformance,
None => wgpu::PowerPreference::Default,
};

match self.wgpu_channel() {
Some(channel) => {
channel
.0
.send(WebGPURequest::RequestAdapter(
sender,
wgpu::RequestAdapterOptions { power_preference },
))
.unwrap();
},
None => promise.reject_error(Error::Type("No WebGPU thread...".to_owned())),
};
promise
}
}

impl AsyncWGPUListener for GPU {
fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) {
match response {
WebGPUResponse::RequestAdapter(name, adapter) => {
let adapter = GPUAdapter::new(
&self.global(),
DOMString::from(name),
Heap::default(),
adapter,
);
promise.resolve_native(&adapter);
},
response => promise.reject_error(Error::Type(format!(
"Wrong response received for GPU from WebGPU thread {:?}",
response,
))),
}
}
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.