Skip to content

Commit

Permalink
Initial implementation of GPUBindGroupLayout for WebGPU
Browse files Browse the repository at this point in the history
Added WebIDL bindings for `GPUBindGroupLayout`, `GPUBindGroupLayoutDescriptor`, `GPUBindingType`, `GPUShaderStage`
and `GPUBindGroupLayoutBinding` (Note: The servo's codegen doesn't like the name, because its already occupied).
Implemented the `createBindGroupLayout` function of `GPUDevice`.
  • Loading branch information
Istvan Miklos committed Jan 16, 2020
1 parent 95614f5 commit 9cf0074
Show file tree
Hide file tree
Showing 13 changed files with 397 additions and 11 deletions.
25 changes: 22 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion components/script/dom/bindings/trace.rs
Expand Up @@ -151,7 +151,7 @@ use tendril::stream::LossyDecoder;
use tendril::{StrTendril, TendrilSink};
use time::{Duration, Timespec, Tm};
use uuid::Uuid;
use webgpu::{WebGPU, WebGPUAdapter, WebGPUBuffer, WebGPUDevice};
use webgpu::{WebGPU, WebGPUAdapter, WebGPUBindGroupLayout, WebGPUBuffer, WebGPUDevice};
use webrender_api::{DocumentId, ImageKey};
use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
use webxr_api::SwapChainId as WebXRSwapChainId;
Expand Down Expand Up @@ -530,6 +530,7 @@ unsafe_no_jsmanaged_fields!(WebGPU);
unsafe_no_jsmanaged_fields!(WebGPUAdapter);
unsafe_no_jsmanaged_fields!(WebGPUDevice);
unsafe_no_jsmanaged_fields!(WebGPUBuffer);
unsafe_no_jsmanaged_fields!(WebGPUBindGroupLayout);
unsafe_no_jsmanaged_fields!(GPUBufferState);
unsafe_no_jsmanaged_fields!(WebXRSwapChainId);
unsafe_no_jsmanaged_fields!(MediaList);
Expand Down
75 changes: 75 additions & 0 deletions components/script/dom/gpubindgrouplayout.rs
@@ -0,0 +1,75 @@
/* 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::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::GPUBindGroupLayoutBinding::{
self, GPUBindGroupLayoutBindings, GPUBindGroupLayoutMethods,
};
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use std::cell::Cell;
use webgpu::{WebGPU, WebGPUBindGroupLayout};

#[dom_struct]
pub struct GPUBindGroupLayout {
reflector_: Reflector,
label: DomRefCell<Option<DOMString>>,
bind_group_layout: WebGPUBindGroupLayout,
#[ignore_malloc_size_of = "defined in webgpu"]
bindings: Vec<GPUBindGroupLayoutBindings>,
#[ignore_malloc_size_of = "defined in webgpu"]
channel: WebGPU,
valid: Cell<bool>,
}

impl GPUBindGroupLayout {
fn new_inherited(
channel: WebGPU,
bind_group_layout: WebGPUBindGroupLayout,
bindings: Vec<GPUBindGroupLayoutBindings>,
valid: bool,
) -> GPUBindGroupLayout {
Self {
reflector_: Reflector::new(),
channel,
label: DomRefCell::new(None),
bind_group_layout,
bindings,
valid: Cell::new(valid),
}
}
pub fn new(
global: &GlobalScope,
channel: WebGPU,
bind_group_layout: WebGPUBindGroupLayout,
bindings: Vec<GPUBindGroupLayoutBindings>,
valid: bool,
) -> DomRoot<GPUBindGroupLayout> {
reflect_dom_object(
Box::new(GPUBindGroupLayout::new_inherited(
channel,
bind_group_layout,
bindings,
valid,
)),
global,
GPUBindGroupLayoutBinding::Wrap,
)
}
}

impl GPUBindGroupLayoutMethods for GPUBindGroupLayout {
/// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label
fn GetLabel(&self) -> Option<DOMString> {
self.label.borrow().clone()
}

/// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label
fn SetLabel(&self, value: Option<DOMString>) {
*self.label.borrow_mut() = value;
}
}
180 changes: 180 additions & 0 deletions components/script/dom/gpudevice.rs
Expand Up @@ -5,6 +5,10 @@
#![allow(unsafe_code)]

use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::GPUAdapterBinding::GPULimits;
use crate::dom::bindings::codegen::Bindings::GPUBindGroupLayoutBinding::{
GPUBindGroupLayoutBindings, GPUBindGroupLayoutDescriptor, GPUBindingType,
};
use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::GPUBufferDescriptor;
use crate::dom::bindings::codegen::Bindings::GPUDeviceBinding::{self, GPUDeviceMethods};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
Expand All @@ -15,6 +19,7 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::gpuadapter::GPUAdapter;
use crate::dom::gpubindgrouplayout::GPUBindGroupLayout;
use crate::dom::gpubuffer::{GPUBuffer, GPUBufferState};
use crate::dom::window::Window;
use crate::script_runtime::JSContext as SafeJSContext;
Expand All @@ -23,7 +28,9 @@ use ipc_channel::ipc;
use js::jsapi::{Heap, JSObject};
use js::jsval::{JSVal, ObjectValue, UndefinedValue};
use js::typedarray::{ArrayBuffer, CreateWith};
use std::collections::{HashMap, HashSet};
use std::ptr::{self, NonNull};
use webgpu::wgpu::binding_model::{BindGroupLayoutBinding, BindingType, ShaderStage};
use webgpu::wgpu::resource::{BufferDescriptor, BufferUsage};
use webgpu::{WebGPU, WebGPUBuffer, WebGPUDevice, WebGPURequest};

Expand Down Expand Up @@ -229,4 +236,177 @@ impl GPUDeviceMethods for GPUDevice {
self.resolve_create_buffer_mapped(cx, buffer, array_buffer, wgpu_descriptor, valid)
}
}

/// https://gpuweb.github.io/gpuweb/#GPUDevice-createBindGroupLayout
fn CreateBindGroupLayout(
&self,
descriptor: &GPUBindGroupLayoutDescriptor,
) -> DomRoot<GPUBindGroupLayout> {
#[derive(Clone)]
struct MaxLimits {
max_uniform_buffers_per_shader_stage: i32,
max_storage_buffers_per_shader_stage: i32,
max_sampled_textures_per_shader_stage: i32,
max_storage_textures_per_shader_stage: i32,
max_samplers_per_shader_stage: i32,
}
let mut storeBindings = HashSet::new();
// TODO: We should have these limits on device creation
let limits = GPULimits::empty();

let mut validation_map = HashMap::new();
let maxLimits = MaxLimits {
max_uniform_buffers_per_shader_stage: limits.maxUniformBuffersPerShaderStage as i32,
max_storage_buffers_per_shader_stage: limits.maxStorageBuffersPerShaderStage as i32,
max_sampled_textures_per_shader_stage: limits.maxSampledTexturesPerShaderStage as i32,
max_storage_textures_per_shader_stage: limits.maxStorageTexturesPerShaderStage as i32,
max_samplers_per_shader_stage: limits.maxSamplersPerShaderStage as i32,
};
validation_map.insert(
webgpu::wgpu::binding_model::ShaderStage::VERTEX,
maxLimits.clone(),
);
validation_map.insert(
webgpu::wgpu::binding_model::ShaderStage::FRAGMENT,
maxLimits.clone(),
);
validation_map.insert(
webgpu::wgpu::binding_model::ShaderStage::COMPUTE,
maxLimits.clone(),
);
let mut max_dynamic_uniform_buffers_per_pipeline_layout =
limits.maxDynamicUniformBuffersPerPipelineLayout as i32;
let mut max_dynamic_storage_buffers_per_pipeline_layout =
limits.maxDynamicStorageBuffersPerPipelineLayout as i32;
let mut valid = true;

let bindings = descriptor
.bindings
.iter()
.map(|bind| {
// TODO: binding must be >= 0
storeBindings.insert(bind.binding);
let visibility = match ShaderStage::from_bits(bind.visibility) {
Some(visibility) => visibility,
None => {
valid = false;
ShaderStage::from_bits(0).unwrap()
},
};
let ty = match bind.type_ {
GPUBindingType::Uniform_buffer => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_uniform_buffers_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
max_dynamic_uniform_buffers_per_pipeline_layout -= 1;
};
BindingType::UniformBuffer
},
GPUBindingType::Storage_buffer => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_storage_buffers_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
max_dynamic_storage_buffers_per_pipeline_layout -= 1;
};
BindingType::StorageBuffer
},
GPUBindingType::Readonly_storage_buffer => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_storage_buffers_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
max_dynamic_storage_buffers_per_pipeline_layout -= 1;
};
BindingType::ReadonlyStorageBuffer
},
GPUBindingType::Sampled_texture => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_sampled_textures_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
valid = false
};
BindingType::SampledTexture
},
GPUBindingType::Storage_texture => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_storage_textures_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
valid = false
};
BindingType::StorageTexture
},
GPUBindingType::Sampler => {
if let Some(limit) = validation_map.get_mut(&visibility) {
limit.max_samplers_per_shader_stage -= 1;
}
if bind.hasDynamicOffset {
valid = false
};
BindingType::Sampler
},
};

BindGroupLayoutBinding {
binding: bind.binding,
visibility,
ty,
dynamic: bind.hasDynamicOffset,
multisampled: bind.multisampled,
texture_dimension: webgpu::wgpu::resource::TextureViewDimension::D2, // Use as default for now
}
})
.collect::<Vec<BindGroupLayoutBinding>>();

// bindings are unique
valid &= storeBindings.len() == bindings.len();

// Ensure that values do not exceed the max limit for each ShaderStage.
valid &= validation_map.values().all(|stage| {
stage.max_uniform_buffers_per_shader_stage >= 0 &&
stage.max_storage_buffers_per_shader_stage >= 0 &&
stage.max_sampled_textures_per_shader_stage >= 0 &&
stage.max_storage_textures_per_shader_stage >= 0 &&
stage.max_samplers_per_shader_stage >= 0
});

// DynamicValues does not exceed the max limit for the pipeline
valid &= max_dynamic_uniform_buffers_per_pipeline_layout >= 0 &&
max_dynamic_storage_buffers_per_pipeline_layout >= 0;

let (sender, receiver) = ipc::channel().unwrap();
if let Some(window) = self.global().downcast::<Window>() {
let id = window
.Navigator()
.create_bind_group_layout_id(self.device.0.backend());
self.channel
.0
.send(WebGPURequest::CreateBindGroupLayout(
sender,
self.device,
id,
bindings.clone(),
))
.expect("Failed to create WebGPU BindGroupLayout");
}
let bgl = receiver.recv().unwrap();

let binds = descriptor
.bindings
.iter()
.map(|bind| GPUBindGroupLayoutBindings {
binding: bind.binding,
hasDynamicOffset: bind.hasDynamicOffset,
multisampled: bind.multisampled,
type_: bind.type_,
visibility: bind.visibility,
//texture_dimension: bind.texture_dimension
})
.collect::<Vec<_>>();

GPUBindGroupLayout::new(&self.global(), self.channel.clone(), bgl, binds, valid)
}
}
11 changes: 11 additions & 0 deletions components/script/dom/gpushaderstage.rs
@@ -0,0 +1,11 @@
/* 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::dom::bindings::reflector::Reflector;
use dom_struct::dom_struct;

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

0 comments on commit 9cf0074

Please sign in to comment.