webgpu: renovate gpucanvascontext and webgpu presentation to match the spec (#33521)

* Reimpl gpucanvascontext

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* ValidateTextureDescriptorAndCreateSwapChain

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* reconfigure

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* resize

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* work around deadlocks in wgpu core

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* match spec even more by moving all swapchain operations into one updatecontext

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* error handling

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* enable one test

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* label dummy texture

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* update expect

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* clean some expectation (they are not flaky anymore)

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* one more

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* change for configuration change in update_wr_image

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* DEFAULT_IMAGE_FORMAT

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* fixup

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

* introduce WebGPUImageDescriptor

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>

---------

Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Samson 2024-10-01 12:03:11 +02:00 committed by GitHub
parent 0b2549f4cb
commit 05ecb8eddb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 795 additions and 483 deletions

View file

@ -2951,7 +2951,7 @@ impl Document {
self.dirty_webgpu_contexts
.borrow_mut()
.drain()
.for_each(|(_, context)| context.send_swap_chain_present());
.for_each(|(_, context)| context.update_rendering_of_webgpu_canvas());
}
pub fn id_map(&self) -> Ref<HashMapTracedValues<Atom, Vec<Dom<Element>>>> {

View file

@ -2,6 +2,9 @@
* 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 std::borrow::Cow;
use std::cell::RefCell;
use arrayvec::ArrayVec;
use dom_struct::dom_struct;
use euclid::default::Size2D;
@ -9,20 +12,25 @@ use ipc_channel::ipc;
use script_layout_interface::HTMLCanvasDataSource;
use webgpu::swapchain::WebGPUContextId;
use webgpu::wgc::id;
use webgpu::{WebGPU, WebGPURequest, WebGPUTexture, PRESENTATION_BUFFER_COUNT};
use webrender_api::{units, ImageFormat, ImageKey};
use webgpu::{
ContextConfiguration, WebGPU, WebGPURequest, WebGPUTexture, PRESENTATION_BUFFER_COUNT,
};
use webrender_api::units::DeviceIntSize;
use webrender_api::ImageKey;
use super::bindings::codegen::Bindings::WebGPUBinding::GPUTextureUsageConstants;
use super::bindings::codegen::Bindings::WebGPUBinding::{
GPUCanvasAlphaMode, GPUTextureUsageConstants,
};
use super::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use super::bindings::error::{Error, Fallible};
use super::bindings::root::MutNullableDom;
use super::bindings::str::USVString;
use super::gpuconvert::convert_texture_descriptor;
use super::gputexture::GPUTexture;
use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::HTMLCanvasElement_Binding::HTMLCanvasElementMethods;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
GPUCanvasConfiguration, GPUCanvasContextMethods, GPUDeviceMethods, GPUExtent3D,
GPUExtent3DDict, GPUObjectDescriptorBase, GPUTextureDescriptor, GPUTextureDimension,
GPUTextureFormat,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
@ -85,6 +93,29 @@ impl malloc_size_of::MallocSizeOf for HTMLCanvasElementOrOffscreenCanvas {
}
}
impl HTMLCanvasElementOrOffscreenCanvas {
fn size(&self) -> Size2D<u64> {
match self {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
canvas.get_size().cast()
},
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
}
}
}
#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)]
/// Helps observe changes on swapchain
struct DrawingBuffer {
#[no_trace]
size: DeviceIntSize,
/// image is transparent black
cleared: bool,
#[ignore_malloc_size_of = "Defined in wgpu"]
#[no_trace]
config: Option<ContextConfiguration>,
}
#[dom_struct]
pub struct GPUCanvasContext {
reflector_: Reflector,
@ -99,14 +130,34 @@ pub struct GPUCanvasContext {
webrender_image: ImageKey,
#[no_trace]
context_id: WebGPUContextId,
#[ignore_malloc_size_of = "manual writing is hard"]
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configuration-slot>
configuration: RefCell<Option<GPUCanvasConfiguration>>,
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-texturedescriptor-slot>
texture_descriptor: RefCell<Option<GPUTextureDescriptor>>,
/// Conceptually <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-drawingbuffer-slot>
drawing_buffer: RefCell<DrawingBuffer>,
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-currenttexture-slot>
texture: MutNullableDom<GPUTexture>,
current_texture: MutNullableDom<GPUTexture>,
}
impl GPUCanvasContext {
fn new_inherited(canvas: HTMLCanvasElementOrOffscreenCanvas, channel: WebGPU) -> Self {
fn new_inherited(
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
channel: WebGPU,
) -> Self {
let (sender, receiver) = ipc::channel().unwrap();
if let Err(e) = channel.0.send(WebGPURequest::CreateContext(sender)) {
let size = canvas.size().cast().cast_unit();
let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
for _ in 0..PRESENTATION_BUFFER_COUNT {
buffer_ids.push(global.wgpu_id_hub().create_buffer_id());
}
if let Err(e) = channel.0.send(WebGPURequest::CreateContext {
buffer_ids,
size,
sender,
}) {
warn!("Failed to send CreateContext ({:?})", e);
}
let (external_id, webrender_image) = receiver.recv().unwrap();
@ -116,13 +167,21 @@ impl GPUCanvasContext {
canvas,
webrender_image,
context_id: WebGPUContextId(external_id.0),
texture: MutNullableDom::default(),
drawing_buffer: RefCell::new(DrawingBuffer {
size,
cleared: true,
..Default::default()
}),
configuration: RefCell::new(None),
texture_descriptor: RefCell::new(None),
current_texture: MutNullableDom::default(),
}
}
pub fn new(global: &GlobalScope, canvas: &HTMLCanvasElement, channel: WebGPU) -> DomRoot<Self> {
reflect_dom_object(
Box::new(GPUCanvasContext::new_inherited(
global,
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(canvas)),
channel,
)),
@ -131,17 +190,93 @@ impl GPUCanvasContext {
}
}
// Abstract ops from spec
impl GPUCanvasContext {
fn layout_handle(&self) -> HTMLCanvasDataSource {
HTMLCanvasDataSource::WebGPU(self.webrender_image)
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-gputexturedescriptor-for-the-canvas-and-configuration>
fn texture_descriptor_for_canvas(
&self,
configuration: &GPUCanvasConfiguration,
) -> GPUTextureDescriptor {
let size = self.size();
GPUTextureDescriptor {
format: configuration.format,
// We need to add `COPY_SRC` so we can copy texture to presentation buffer
// causes FAIL on webgpu:web_platform,canvas,configure:usage:*
usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
width: size.width as u32,
height: size.height as u32,
depthOrArrayLayers: 1,
}),
viewFormats: configuration.viewFormats.clone(),
// other members to default
mipLevelCount: 1,
sampleCount: 1,
parent: GPUObjectDescriptorBase {
label: USVString::default(),
},
dimension: GPUTextureDimension::_2d,
}
}
pub fn send_swap_chain_present(&self) {
let texture_id = self.texture_id().unwrap().0;
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture>
fn expire_current_texture(&self) {
if let Some(current_texture) = self.current_texture.take() {
// Make copy of texture content
self.send_swap_chain_present(current_texture.id());
// Step 1
current_texture.Destroy()
}
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-replace-the-drawing-buffer>
fn replace_drawing_buffer(&self) {
// Step 1
self.expire_current_texture();
// Step 2
let configuration = self.configuration.borrow();
// Step 3
let mut drawing_buffer = self.drawing_buffer.borrow_mut();
drawing_buffer.size = self.size().cast().cast_unit();
drawing_buffer.cleared = true;
if let Some(configuration) = configuration.as_ref() {
drawing_buffer.config = Some(ContextConfiguration {
device_id: configuration.device.id().0,
queue_id: configuration.device.queue_id().0,
format: configuration.format.into(),
is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque),
});
} else {
drawing_buffer.config.take();
};
// TODO: send less
self.channel
.0
.send(WebGPURequest::UpdateContext {
context_id: self.context_id,
size: drawing_buffer.size,
configuration: drawing_buffer.config.clone(),
})
.expect("Failed to update webgpu context");
}
}
// Internal helper methods
impl GPUCanvasContext {
fn layout_handle(&self) -> HTMLCanvasDataSource {
if self.drawing_buffer.borrow().cleared {
HTMLCanvasDataSource::Empty
} else {
HTMLCanvasDataSource::WebGPU(self.webrender_image)
}
}
fn send_swap_chain_present(&self, texture_id: WebGPUTexture) {
self.drawing_buffer.borrow_mut().cleared = false;
let encoder_id = self.global().wgpu_id_hub().create_command_encoder_id();
if let Err(e) = self.channel.0.send(WebGPURequest::SwapChainPresent {
context_id: self.context_id,
texture_id,
texture_id: texture_id.0,
encoder_id,
}) {
warn!(
@ -151,29 +286,42 @@ impl GPUCanvasContext {
}
}
pub fn context_id(&self) -> WebGPUContextId {
fn size(&self) -> Size2D<u64> {
self.canvas.size()
}
}
// public methods for canvas handling
// these methods should probably be behind trait for all canvases
impl GPUCanvasContext {
pub(crate) fn context_id(&self) -> WebGPUContextId {
self.context_id
}
pub fn texture_id(&self) -> Option<WebGPUTexture> {
self.texture.get().map(|t| t.id())
}
pub fn mark_as_dirty(&self) {
pub(crate) fn mark_as_dirty(&self) {
if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = &self.canvas {
canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
let document = document_from_node(&**canvas);
document.add_dirty_webgpu_canvas(self);
}
// TODO(sagudev): offscreen canvas also dirty?
}
fn size(&self) -> Size2D<u64> {
match &self.canvas {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => {
Size2D::new(canvas.Width() as u64, canvas.Height() as u64)
},
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(),
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas>
pub(crate) fn update_rendering_of_webgpu_canvas(&self) {
// Step 1
self.expire_current_texture();
}
/// <https://gpuweb.github.io/gpuweb/#abstract-opdef-update-the-canvas-size>
pub(crate) fn resize(&self) {
// Step 1
self.replace_drawing_buffer();
// Step 2
let configuration = self.configuration.borrow();
// Step 3
if let Some(configuration) = configuration.as_ref() {
self.texture_descriptor
.replace(Some(self.texture_descriptor_for_canvas(configuration)));
}
}
}
@ -192,110 +340,84 @@ impl GPUCanvasContextMethods for GPUCanvasContext {
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configure>
fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> {
// Step 1 is let
// Step 2
configuration
.device
.validate_texture_format_required_features(&configuration.format)?;
// Mapping between wr and wgpu can be determined by inspecting
// https://github.com/gfx-rs/wgpu/blob/9b36a3e129da04b018257564d5129caff240cb75/wgpu-hal/src/gles/conv.rs#L10
// https://github.com/servo/webrender/blob/1e73f384a7a86e413f91e9e430436a9bd83381cd/webrender/src/device/gl.rs#L4064
let format = match configuration.format {
GPUTextureFormat::R8unorm => ImageFormat::R8,
GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Bgra8unorm_srgb => ImageFormat::BGRA8,
GPUTextureFormat::Rgba8unorm | GPUTextureFormat::Rgba8unorm_srgb => ImageFormat::RGBA8,
GPUTextureFormat::Rgba32float => ImageFormat::RGBAF32,
GPUTextureFormat::Rgba32sint => ImageFormat::RGBAI32,
GPUTextureFormat::Rg8unorm => ImageFormat::RG8,
_ => {
return Err(Error::Type(format!(
"SwapChain format({:?}) not supported",
configuration.format
)))
},
};
// Step 3
for view_format in &configuration.viewFormats {
configuration
.device
.validate_texture_format_required_features(view_format)?;
}
// Step 4
let size = self.size();
let text_desc = GPUTextureDescriptor {
format: configuration.format,
mipLevelCount: 1,
sampleCount: 1,
// We need to add `COPY_SRC` so we can copy texture to presentation buffer
usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC,
size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict {
width: size.width as u32,
height: size.height as u32,
depthOrArrayLayers: 1,
}),
viewFormats: configuration.viewFormats.clone(),
// other members to default
parent: GPUObjectDescriptorBase {
label: USVString::default(),
},
dimension: GPUTextureDimension::_2d,
};
let descriptor = self.texture_descriptor_for_canvas(configuration);
// Step 8
let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new();
for _ in 0..PRESENTATION_BUFFER_COUNT {
buffer_ids.push(self.global().wgpu_id_hub().create_buffer_id());
}
// Step 2&3
let (mut desc, _) = convert_texture_descriptor(&descriptor, &configuration.device)?;
desc.label = Some(Cow::Borrowed(
"dummy texture for texture descriptor validation",
));
// Step 5
self.configuration.replace(Some(configuration.clone()));
// Step 6
self.texture_descriptor.replace(Some(descriptor));
// Step 7
self.replace_drawing_buffer();
// Step 8: Validate texture descriptor
let texture_id = self.global().wgpu_id_hub().create_texture_id();
self.channel
.0
.send(WebGPURequest::CreateSwapChain {
.send(WebGPURequest::ValidateTextureDescriptor {
device_id: configuration.device.id().0,
queue_id: configuration.device.GetQueue().id().0,
buffer_ids,
context_id: self.context_id,
image_key: self.webrender_image,
format,
size: units::DeviceIntSize::new(size.width as i32, size.height as i32),
texture_id,
descriptor: desc,
})
.expect("Failed to create WebGPU SwapChain");
self.texture.set(Some(
&configuration.device.CreateTexture(&text_desc).unwrap(),
));
Ok(())
}
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-unconfigure>
fn Unconfigure(&self) {
if let Some(texture) = self.texture.take() {
if let Err(e) = self.channel.0.send(WebGPURequest::DestroySwapChain {
// Step 1
self.configuration.take();
// Step 2
self.current_texture.take();
// Step 3
self.replace_drawing_buffer();
}
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-getcurrenttexture>
fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
// Step 1
let configuration = self.configuration.borrow();
let Some(configuration) = configuration.as_ref() else {
return Err(Error::InvalidState);
};
// Step 2
let texture_descriptor = self.texture_descriptor.borrow();
let texture_descriptor = texture_descriptor.as_ref().unwrap();
// Step 6
let current_texture = if let Some(current_texture) = self.current_texture.get() {
current_texture
} else {
// Step 3&4
self.replace_drawing_buffer();
let current_texture = configuration.device.CreateTexture(&texture_descriptor)?;
self.current_texture.set(Some(&current_texture));
current_texture
};
// Step 5
self.mark_as_dirty();
// Step 6
Ok(current_texture)
}
}
impl Drop for GPUCanvasContext {
fn drop(&mut self) {
if let Err(e) = self.channel.0.send(WebGPURequest::DestroyContext {
context_id: self.context_id,
image_key: self.webrender_image,
}) {
warn!(
"Failed to send DestroySwapChain-ImageKey({:?}) ({})",
self.webrender_image, e
);
}
drop(texture);
}
}
/// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-getcurrenttexture>
fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> {
// Step 5.
self.mark_as_dirty();
// Step 6.
self.texture.get().ok_or(Error::InvalidState)
}
}
impl Drop for GPUCanvasContext {
fn drop(&mut self) {
self.Unconfigure()
}
}

View file

@ -8,10 +8,11 @@ use std::num::NonZeroU64;
use webgpu::wgc::binding_model::{BindGroupEntry, BindingResource, BufferBinding};
use webgpu::wgc::command as wgpu_com;
use webgpu::wgc::pipeline::ProgrammableStageDescriptor;
use webgpu::wgc::resource::TextureDescriptor;
use webgpu::wgt::{self, AstcBlock, AstcChannel};
use super::bindings::codegen::Bindings::WebGPUBinding::{
GPUProgrammableStage, GPUTextureDimension,
GPUProgrammableStage, GPUTextureDescriptor, GPUTextureDimension,
};
use super::bindings::error::Error;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
@ -506,6 +507,7 @@ impl<'a> From<&GPUObjectDescriptorBase> for Option<Cow<'a, str>> {
}
}
}
pub fn convert_bind_group_layout_entry(
bgle: &GPUBindGroupLayoutEntry,
device: &GPUDevice,
@ -581,6 +583,28 @@ pub fn convert_bind_group_layout_entry(
}))
}
pub fn convert_texture_descriptor(
descriptor: &GPUTextureDescriptor,
device: &GPUDevice,
) -> Fallible<(TextureDescriptor<'static>, wgt::Extent3d)> {
let size = (&descriptor.size).try_into()?;
let desc = TextureDescriptor {
label: (&descriptor.parent).into(),
size,
mip_level_count: descriptor.mipLevelCount,
sample_count: descriptor.sampleCount,
dimension: descriptor.dimension.into(),
format: device.validate_texture_format_required_features(&descriptor.format)?,
usage: wgt::TextureUsages::from_bits_retain(descriptor.usage),
view_formats: descriptor
.viewFormats
.iter()
.map(|tf| device.validate_texture_format_required_features(tf))
.collect::<Fallible<_>>()?,
};
Ok((desc, size))
}
impl TryFrom<&GPUColor> for wgt::Color {
type Error = Error;

View file

@ -153,6 +153,10 @@ impl GPUDevice {
self.device
}
pub fn queue_id(&self) -> webgpu::WebGPUQueue {
self.default_queue.id()
}
pub fn channel(&self) -> WebGPU {
self.channel.clone()
}

View file

@ -9,6 +9,7 @@ use webgpu::wgc::resource;
use webgpu::{wgt, WebGPU, WebGPURequest, WebGPUTexture, WebGPUTextureView};
use super::bindings::error::Fallible;
use super::gpuconvert::convert_texture_descriptor;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
GPUTextureAspect, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat,
@ -127,21 +128,7 @@ impl GPUTexture {
device: &GPUDevice,
descriptor: &GPUTextureDescriptor,
) -> Fallible<DomRoot<GPUTexture>> {
let size = (&descriptor.size).try_into()?;
let desc = wgt::TextureDescriptor {
label: (&descriptor.parent).into(),
size,
mip_level_count: descriptor.mipLevelCount,
sample_count: descriptor.sampleCount,
dimension: descriptor.dimension.into(),
format: device.validate_texture_format_required_features(&descriptor.format)?,
usage: wgt::TextureUsages::from_bits_retain(descriptor.usage),
view_formats: descriptor
.viewFormats
.iter()
.map(|tf| device.validate_texture_format_required_features(tf))
.collect::<Fallible<_>>()?,
};
let (desc, size) = convert_texture_descriptor(descriptor, device)?;
let texture_id = device.global().wgpu_id_hub().create_texture_id();

View file

@ -105,7 +105,7 @@ impl HTMLCanvasElement {
},
CanvasContext::WebGL(ref context) => context.recreate(size),
CanvasContext::WebGL2(ref context) => context.recreate(size),
CanvasContext::WebGPU(_) => unimplemented!(),
CanvasContext::WebGPU(ref context) => context.resize(),
}
}
}

View file

@ -10,7 +10,7 @@ use base::id::PipelineId;
use ipc_channel::ipc::{IpcSender, IpcSharedMemory};
use serde::{Deserialize, Serialize};
use webrender_api::units::DeviceIntSize;
use webrender_api::{ImageFormat, ImageKey};
use webrender_api::ImageKey;
use wgc::binding_model::{
BindGroupDescriptor, BindGroupLayoutDescriptor, PipelineLayoutDescriptor,
};
@ -34,6 +34,14 @@ use crate::render_commands::RenderCommand;
use crate::swapchain::WebGPUContextId;
use crate::{Error, ErrorFilter, WebGPUResponse, PRESENTATION_BUFFER_COUNT};
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct ContextConfiguration {
pub device_id: id::DeviceId,
pub queue_id: id::QueueId,
pub format: wgt::TextureFormat,
pub is_opaque: bool,
}
#[derive(Debug, Deserialize, Serialize)]
pub enum WebGPURequest {
BufferMapAsync {
@ -103,7 +111,6 @@ pub enum WebGPURequest {
/// present only on ASYNC versions
async_sender: Option<IpcSender<WebGPUResponse>>,
},
CreateContext(IpcSender<(WebGPUContextId, ImageKey)>),
CreatePipelineLayout {
device_id: id::DeviceId,
pipeline_layout_id: id::PipelineLayoutId,
@ -129,14 +136,31 @@ pub enum WebGPURequest {
label: Option<String>,
sender: IpcSender<WebGPUResponse>,
},
CreateSwapChain {
device_id: id::DeviceId,
queue_id: id::QueueId,
/// Creates context
CreateContext {
buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
context_id: WebGPUContextId,
image_key: ImageKey,
format: ImageFormat,
size: DeviceIntSize,
sender: IpcSender<(WebGPUContextId, ImageKey)>,
},
/// Recreates swapchain (if needed)
UpdateContext {
context_id: WebGPUContextId,
size: DeviceIntSize,
configuration: Option<ContextConfiguration>,
},
/// Reads texture to swapchains buffer and maps it
SwapChainPresent {
context_id: WebGPUContextId,
texture_id: id::TextureId,
encoder_id: id::CommandEncoderId,
},
ValidateTextureDescriptor {
device_id: id::DeviceId,
texture_id: id::TextureId,
descriptor: TextureDescriptor<'static>,
},
DestroyContext {
context_id: WebGPUContextId,
},
CreateTexture {
device_id: id::DeviceId,
@ -152,10 +176,6 @@ pub enum WebGPURequest {
DestroyBuffer(id::BufferId),
DestroyDevice(id::DeviceId),
DestroyTexture(id::TextureId),
DestroySwapChain {
context_id: WebGPUContextId,
image_key: ImageKey,
},
DropTexture(id::TextureId),
DropAdapter(id::AdapterId),
DropDevice(id::DeviceId),
@ -254,11 +274,6 @@ pub enum WebGPURequest {
queue_id: id::QueueId,
command_buffers: Vec<id::CommandBufferId>,
},
SwapChainPresent {
context_id: WebGPUContextId,
texture_id: id::TextureId,
encoder_id: id::CommandEncoderId,
},
UnmapBuffer {
buffer_id: id::BufferId,
array_buffer: IpcSharedMemory,

View file

@ -4,7 +4,7 @@
use log::warn;
use swapchain::WGPUImageMap;
pub use swapchain::{PresentationData, WGPUExternalImages};
pub use swapchain::{ContextData, WGPUExternalImages};
use webrender::RenderApiSender;
use wgpu_thread::WGPU;
pub use {wgpu_core as wgc, wgpu_types as wgt};

View file

@ -3,21 +3,21 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap;
use std::ops::ControlFlow;
use std::ptr::NonNull;
use std::slice;
use std::sync::{Arc, Mutex};
use arrayvec::ArrayVec;
use euclid::default::Size2D;
use ipc_channel::ipc::IpcSender;
use log::{error, warn};
use malloc_size_of::MallocSizeOf;
use serde::{Deserialize, Serialize};
use webrender::{RenderApi, Transaction};
use webrender_api::units::DeviceIntSize;
use webrender_api::{
DirtyRect, ExternalImageData, ExternalImageId, ExternalImageType, ImageData, ImageDescriptor,
ImageDescriptorFlags, ImageFormat, ImageKey,
DirtyRect, DocumentId, ExternalImageData, ExternalImageId, ExternalImageType, ImageData,
ImageDescriptor, ImageDescriptorFlags, ImageFormat, ImageKey,
};
use webrender_traits::{WebrenderExternalImageApi, WebrenderImageSource};
use wgpu_core::device::HostMap;
@ -25,9 +25,10 @@ use wgpu_core::global::Global;
use wgpu_core::id;
use wgpu_core::resource::{BufferAccessError, BufferMapCallback, BufferMapOperation};
use crate::{wgt, WebGPUMsg};
use crate::{wgt, ContextConfiguration, Error, WebGPUMsg};
pub const PRESENTATION_BUFFER_COUNT: usize = 10;
const DEFAULT_IMAGE_FORMAT: ImageFormat = ImageFormat::RGBA8;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct WebGPUContextId(pub u64);
@ -38,7 +39,7 @@ impl MallocSizeOf for WebGPUContextId {
}
}
pub type WGPUImageMap = Arc<Mutex<HashMap<WebGPUContextId, PresentationData>>>;
pub type WGPUImageMap = Arc<Mutex<HashMap<WebGPUContextId, ContextData>>>;
struct GPUPresentationBuffer {
global: Arc<Global>,
@ -84,20 +85,20 @@ pub struct WGPUExternalImages {
impl WebrenderExternalImageApi for WGPUExternalImages {
fn lock(&mut self, id: u64) -> (WebrenderImageSource, Size2D<i32>) {
let id = WebGPUContextId(id);
let size;
let data;
if let Some(present_data) = self.images.lock().unwrap().get(&id) {
size = present_data.image_desc.size.cast_unit();
data = if let Some(present_data) = &present_data.data {
present_data.slice().to_vec()
let webgpu_contexts = self.images.lock().unwrap();
let context_data = webgpu_contexts.get(&id).unwrap();
let size = context_data.image_desc.size().cast_unit();
let data = if let Some(present_buffer) = context_data
.swap_chain
.as_ref()
.map(|swap_chain| swap_chain.data.as_ref())
.flatten()
{
present_buffer.slice().to_vec()
} else {
present_data.dummy_data()
context_data.dummy_data()
};
} else {
size = Size2D::new(0, 0);
data = Vec::new();
}
let _ = self.locked_ids.insert(id, data);
self.locked_ids.insert(id, data);
(
WebrenderImageSource::Raw(self.locked_ids.get(&id).unwrap().as_slice()),
size,
@ -106,13 +107,13 @@ impl WebrenderExternalImageApi for WGPUExternalImages {
fn unlock(&mut self, id: u64) {
let id = WebGPUContextId(id);
let _ = self.locked_ids.remove(&id);
self.locked_ids.remove(&id);
}
}
/// States of presentation buffer
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub enum PresentationBufferState {
enum PresentationBufferState {
/// Initial state, buffer has yet to be created,
/// only its id is reserved
#[default]
@ -125,61 +126,105 @@ pub enum PresentationBufferState {
Mapped,
}
pub struct PresentationData {
struct SwapChain {
device_id: id::DeviceId,
queue_id: id::QueueId,
data: Option<GPUPresentationBuffer>,
buffer_ids: ArrayVec<(id::BufferId, PresentationBufferState), PRESENTATION_BUFFER_COUNT>,
image_key: ImageKey,
image_desc: ImageDescriptor,
image_data: ImageData,
}
impl PresentationData {
pub fn new(
device_id: id::DeviceId,
queue_id: id::QueueId,
buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WebGPUImageDescriptor(pub ImageDescriptor);
impl WebGPUImageDescriptor {
fn new(format: ImageFormat, size: DeviceIntSize, is_opaque: bool) -> Self {
let stride = (((size.width * format.bytes_per_pixel()) |
(wgt::COPY_BYTES_PER_ROW_ALIGNMENT as i32 - 1)) +
1) as i32;
Self(ImageDescriptor {
format,
size,
stride: Some(stride),
offset: 0,
flags: if is_opaque {
ImageDescriptorFlags::IS_OPAQUE
} else {
ImageDescriptorFlags::empty()
},
})
}
fn default(size: DeviceIntSize) -> Self {
Self::new(DEFAULT_IMAGE_FORMAT, size, false)
}
/// Returns true if needs image update (if it's changed)
fn update(&mut self, new: Self) -> bool {
if self.0 != new.0 {
self.0 = new.0;
true
} else {
false
}
}
fn buffer_stride(&self) -> i32 {
self.0
.stride
.expect("Stride should be set by WebGPUImageDescriptor")
}
fn buffer_size(&self) -> wgt::BufferAddress {
(self.buffer_stride() * self.0.size.height) as wgt::BufferAddress
}
fn size(&self) -> DeviceIntSize {
self.0.size
}
}
pub struct ContextData {
image_key: ImageKey,
image_desc: ImageDescriptor,
image_desc: WebGPUImageDescriptor,
image_data: ImageData,
buffer_ids: ArrayVec<(id::BufferId, PresentationBufferState), PRESENTATION_BUFFER_COUNT>,
/// If there is no associated swapchain the context is dummy (transparent black)
swap_chain: Option<SwapChain>,
}
impl ContextData {
/// Init ContextData as dummy (transparent black)
fn new(
context_id: WebGPUContextId,
image_key: ImageKey,
size: DeviceIntSize,
buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
) -> Self {
let image_data = ImageData::External(ExternalImageData {
id: ExternalImageId(context_id.0),
channel_index: 0,
image_type: ExternalImageType::Buffer,
});
Self {
device_id,
queue_id,
data: None,
image_key,
image_desc: WebGPUImageDescriptor::default(size),
image_data,
swap_chain: None,
buffer_ids: buffer_ids
.iter()
.map(|&id| (id, PresentationBufferState::Unassigned))
.map(|&buffer_id| (buffer_id, PresentationBufferState::Unassigned))
.collect(),
image_key,
image_desc,
image_data,
}
}
fn dummy_data(&self) -> Vec<u8> {
let size = (self
.image_desc
.stride
.expect("Stride should be set when creating swapchain") *
self.image_desc.size.height) as usize;
vec![0; size]
}
fn buffer_stride(&self) -> i32 {
self.image_desc
.stride
.expect("Stride should be set when creating swapchain")
}
fn buffer_size(&self) -> wgt::BufferAddress {
(self.buffer_stride() * self.image_desc.size.height) as wgt::BufferAddress
vec![0; self.image_desc.buffer_size() as usize]
}
/// Returns id of available buffer
/// and sets state to PresentationBufferState::Mapping
fn get_available_buffer(&'_ mut self, global: &Arc<Global>) -> Option<id::BufferId> {
assert!(self.swap_chain.is_some());
if let Some((buffer_id, buffer_state)) = self
.buffer_ids
.iter_mut()
@ -196,11 +241,15 @@ impl PresentationData {
let buffer_id = *buffer_id;
let buffer_desc = wgt::BufferDescriptor {
label: None,
size: self.buffer_size(),
size: self.image_desc.buffer_size(),
usage: wgt::BufferUsages::MAP_READ | wgt::BufferUsages::COPY_DST,
mapped_at_creation: false,
};
let _ = global.device_create_buffer(self.device_id, &buffer_desc, Some(buffer_id));
let _ = global.device_create_buffer(
self.swap_chain.as_ref().unwrap().device_id,
&buffer_desc,
Some(buffer_id),
);
Some(buffer_id)
} else {
None
@ -217,57 +266,135 @@ impl PresentationData {
}
fn unmap_old_buffer(&mut self, presentation_buffer: GPUPresentationBuffer) {
assert!(self.swap_chain.is_some());
let buffer_state = self.get_buffer_state(presentation_buffer.buffer_id);
assert_eq!(*buffer_state, PresentationBufferState::Mapped);
*buffer_state = PresentationBufferState::Available;
drop(presentation_buffer);
}
fn destroy_swapchain(&mut self, global: &Arc<Global>) {
drop(self.swap_chain.take());
// free all buffers
for (buffer_id, buffer_state) in &mut self.buffer_ids {
match buffer_state {
PresentationBufferState::Unassigned => {
/* These buffer were not yet created in wgpu */
},
_ => {
global.buffer_drop(*buffer_id);
},
}
*buffer_state = PresentationBufferState::Unassigned;
}
}
fn destroy(
mut self,
global: &Arc<Global>,
script_sender: &IpcSender<WebGPUMsg>,
webrender_api: &Arc<Mutex<RenderApi>>,
webrender_document: DocumentId,
) {
self.destroy_swapchain(global);
for (buffer_id, _) in self.buffer_ids {
if let Err(e) = script_sender.send(WebGPUMsg::FreeBuffer(buffer_id)) {
warn!("Unable to send FreeBuffer({:?}) ({:?})", buffer_id, e);
};
}
let mut txn = Transaction::new();
txn.delete_image(self.image_key);
webrender_api
.lock()
.unwrap()
.send_transaction(webrender_document, txn);
}
}
impl crate::WGPU {
pub(crate) fn create_swapchain(
pub(crate) fn create_context(
&self,
device_id: id::DeviceId,
queue_id: id::QueueId,
buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
context_id: WebGPUContextId,
format: ImageFormat,
size: DeviceIntSize,
image_key: ImageKey,
size: DeviceIntSize,
buffer_ids: ArrayVec<id::BufferId, PRESENTATION_BUFFER_COUNT>,
) {
let image_desc = ImageDescriptor {
format,
size,
stride: Some(
(((size.width as u32 * 4) | (wgt::COPY_BYTES_PER_ROW_ALIGNMENT - 1)) + 1) as i32,
),
offset: 0,
flags: ImageDescriptorFlags::IS_OPAQUE,
};
let image_data = ImageData::External(ExternalImageData {
id: ExternalImageId(context_id.0),
channel_index: 0,
image_type: ExternalImageType::Buffer,
});
let _ = self.wgpu_image_map.lock().unwrap().insert(
context_id,
PresentationData::new(
device_id,
queue_id,
buffer_ids,
image_key,
image_desc,
image_data.clone(),
),
);
let context_data = ContextData::new(context_id, image_key, size, buffer_ids);
let mut txn = Transaction::new();
txn.add_image(image_key, image_desc, image_data, None);
txn.add_image(
image_key,
context_data.image_desc.0,
context_data.image_data.clone(),
None,
);
self.webrender_api
.lock()
.unwrap()
.send_transaction(self.webrender_document, txn);
assert!(
self.wgpu_image_map
.lock()
.unwrap()
.insert(context_id, context_data)
.is_none(),
"Context should be created only once!"
);
}
pub(crate) fn update_context(
&self,
context_id: WebGPUContextId,
size: DeviceIntSize,
config: Option<ContextConfiguration>,
) {
let mut webgpu_contexts = self.wgpu_image_map.lock().unwrap();
let context_data = webgpu_contexts.get_mut(&context_id).unwrap();
// If configuration is not provided or presentation format is not valid
// the context will be dummy until recreation
let format = config
.as_ref()
.map(|config| match config.format {
wgt::TextureFormat::Rgba8Unorm => Some(ImageFormat::RGBA8),
wgt::TextureFormat::Bgra8Unorm => Some(ImageFormat::BGRA8),
_ => None,
})
.flatten();
let needs_image_update = if let Some(format) = format {
let config = config.expect("Config should exist when valid format is available");
let new_image_desc = WebGPUImageDescriptor::new(format, size, config.is_opaque);
let needs_swapchain_rebuild = context_data.swap_chain.is_none() ||
new_image_desc.buffer_size() != context_data.image_desc.buffer_size();
if needs_swapchain_rebuild {
context_data.destroy_swapchain(&self.global);
context_data.swap_chain = Some(SwapChain {
device_id: config.device_id,
queue_id: config.queue_id,
data: None,
});
}
context_data.image_desc.update(new_image_desc)
} else {
context_data.destroy_swapchain(&self.global);
context_data
.image_desc
.update(WebGPUImageDescriptor::default(size))
};
if needs_image_update {
let mut txn = Transaction::new();
txn.update_image(
context_data.image_key,
context_data.image_desc.0,
context_data.image_data.clone(),
&DirtyRect::All,
);
self.webrender_api
.lock()
.unwrap()
.send_transaction(self.webrender_document, txn);
}
}
/// Copies data async from provided texture using encoder_id to available staging presentation buffer
@ -276,39 +403,42 @@ impl crate::WGPU {
context_id: WebGPUContextId,
encoder_id: id::Id<id::markers::CommandEncoder>,
texture_id: id::Id<id::markers::Texture>,
) -> ControlFlow<()> {
) -> Result<(), Box<dyn std::error::Error>> {
fn err<T: std::error::Error + 'static>(e: Option<T>) -> Result<(), T> {
if let Some(error) = e {
Err(error)
} else {
Ok(())
}
}
let global = &self.global;
let device_id;
let queue_id;
let size;
let buffer_id;
let buffer_stride;
let buffer_size;
let image_desc;
{
if let Some(present_data) = self.wgpu_image_map.lock().unwrap().get_mut(&context_id) {
size = present_data.image_desc.size;
device_id = present_data.device_id;
queue_id = present_data.queue_id;
buffer_stride = present_data.buffer_stride();
buffer_size = present_data.buffer_size();
buffer_id = if let Some(buffer_id) = present_data.get_available_buffer(global) {
buffer_id
} else {
error!("No staging buffer available for {:?}", context_id);
return ControlFlow::Break(());
if let Some(context_data) = self.wgpu_image_map.lock().unwrap().get_mut(&context_id) {
let Some(swap_chain) = context_data.swap_chain.as_ref() else {
return Ok(());
};
device_id = swap_chain.device_id;
queue_id = swap_chain.queue_id;
buffer_id = context_data.get_available_buffer(global).unwrap();
image_desc = context_data.image_desc;
} else {
error!("Data not found for {:?}", context_id);
return ControlFlow::Break(());
return Ok(());
}
}
let comm_desc = wgt::CommandEncoderDescriptor { label: None };
let _ = global.device_create_command_encoder(device_id, &comm_desc, Some(encoder_id));
let (encoder_id, error) =
global.device_create_command_encoder(device_id, &comm_desc, Some(encoder_id));
err(error)?;
let buffer_cv = wgt::ImageCopyBuffer {
buffer: buffer_id,
layout: wgt::ImageDataLayout {
offset: 0,
bytes_per_row: Some(buffer_stride as u32),
bytes_per_row: Some(image_desc.buffer_stride() as u32),
rows_per_image: None,
},
};
@ -319,18 +449,25 @@ impl crate::WGPU {
aspect: wgt::TextureAspect::All,
};
let copy_size = wgt::Extent3d {
width: size.width as u32,
height: size.height as u32,
width: image_desc.size().width as u32,
height: image_desc.size().height as u32,
depth_or_array_layers: 1,
};
let _ = global.command_encoder_copy_texture_to_buffer(
global.command_encoder_copy_texture_to_buffer(
encoder_id,
&texture_cv,
&buffer_cv,
&copy_size,
);
let _ = global.command_encoder_finish(encoder_id, &wgt::CommandBufferDescriptor::default());
let _ = global.queue_submit(queue_id, &[encoder_id.into_command_buffer_id()]);
)?;
let (command_buffer_id, error) =
global.command_encoder_finish(encoder_id, &wgt::CommandBufferDescriptor::default());
err(error)?;
{
let _guard = self.poller.lock();
global
.queue_submit(queue_id, &[command_buffer_id])
.map_err(Error::from_error)?;
}
let callback = {
let global = Arc::clone(&self.global);
let wgpu_image_map = Arc::clone(&self.wgpu_image_map);
@ -343,11 +480,11 @@ impl crate::WGPU {
result,
global,
buffer_id,
buffer_size,
wgpu_image_map,
context_id,
webrender_api,
webrender_document,
image_desc,
);
}))
};
@ -355,42 +492,23 @@ impl crate::WGPU {
host: HostMap::Read,
callback: Some(callback),
};
let _ = global.buffer_map_async(buffer_id, 0, Some(buffer_size), map_op);
global.buffer_map_async(buffer_id, 0, Some(image_desc.buffer_size()), map_op)?;
self.poller.wake();
ControlFlow::Continue(())
Ok(())
}
pub(crate) fn destroy_swapchain(
&mut self,
context_id: WebGPUContextId,
image_key: webrender_api::ImageKey,
) {
let present_data = self
.wgpu_image_map
pub(crate) fn destroy_context(&mut self, context_id: WebGPUContextId) {
self.wgpu_image_map
.lock()
.unwrap()
.remove(&context_id)
.unwrap();
for (buffer_id, buffer_state) in present_data.buffer_ids {
match buffer_state {
PresentationBufferState::Unassigned => {
/* These buffer were not yet created in wgpu */
},
_ => {
self.global.buffer_drop(buffer_id);
},
}
if let Err(e) = self.script_sender.send(WebGPUMsg::FreeBuffer(buffer_id)) {
warn!("Unable to send FreeBuffer({:?}) ({:?})", buffer_id, e);
};
}
let mut txn = Transaction::new();
txn.delete_image(image_key);
self.webrender_api
.lock()
.unwrap()
.send_transaction(self.webrender_document, txn);
.destroy(
&self.global,
&self.script_sender,
&self.webrender_api,
self.webrender_document,
);
}
}
@ -398,26 +516,60 @@ fn update_wr_image(
result: Result<(), BufferAccessError>,
global: Arc<Global>,
buffer_id: id::BufferId,
buffer_size: u64,
wgpu_image_map: WGPUImageMap,
context_id: WebGPUContextId,
webrender_api: Arc<Mutex<RenderApi>>,
webrender_document: webrender_api::DocumentId,
image_desc: WebGPUImageDescriptor,
) {
match result {
Ok(()) => {
if let Some(present_data) = wgpu_image_map.lock().unwrap().get_mut(&context_id) {
let buffer_state = present_data.get_buffer_state(buffer_id);
assert_eq!(*buffer_state, PresentationBufferState::Mapping);
if let Some(context_data) = wgpu_image_map.lock().unwrap().get_mut(&context_id) {
let config_changed = image_desc != context_data.image_desc;
let buffer_state = context_data.get_buffer_state(buffer_id);
match buffer_state {
PresentationBufferState::Unassigned => {
// throw away all work, because we are from old swapchain
return;
},
PresentationBufferState::Mapping => {},
_ => panic!("Unexpected presentation buffer state"),
}
if config_changed {
/*
This means that while mapasync was running, context got recreated
so we need to throw all out work away.
It is also possible that we got recreated with same config,
so canvas should be cleared, but we handle such case in gpucanvascontext
with drawing_buffer.cleared
One more case is that we already have newer map async done,
so we can replace new image with old image but that should happen very rarely
One possible solution to all problems is blocking device timeline
(wgpu thread or introduce new timeline/thread for presentation)
something like this is also mentioned in spec:
2. Ensure that all submitted work items (e.g. queue submissions) have completed writing to the image
https://gpuweb.github.io/gpuweb/#abstract-opdef-get-a-copy-of-the-image-contents-of-a-context
*/
let _ = global.buffer_unmap(buffer_id);
*buffer_state = PresentationBufferState::Available;
return;
}
*buffer_state = PresentationBufferState::Mapped;
let presentation_buffer =
GPUPresentationBuffer::new(global, buffer_id, buffer_size);
let old_presentation_buffer = present_data.data.replace(presentation_buffer);
GPUPresentationBuffer::new(global, buffer_id, image_desc.buffer_size());
let Some(swap_chain) = context_data.swap_chain.as_mut() else {
return;
};
let old_presentation_buffer = swap_chain.data.replace(presentation_buffer);
let mut txn = Transaction::new();
txn.update_image(
present_data.image_key,
present_data.image_desc,
present_data.image_data.clone(),
context_data.image_key,
context_data.image_desc.0,
context_data.image_data.clone(),
&DirtyRect::All,
);
webrender_api
@ -425,10 +577,10 @@ fn update_wr_image(
.unwrap()
.send_transaction(webrender_document, txn);
if let Some(old_presentation_buffer) = old_presentation_buffer {
present_data.unmap_old_buffer(old_presentation_buffer)
context_data.unmap_old_buffer(old_presentation_buffer)
}
} else {
error!("Data not found for {:?}", context_id);
error!("WebGPU Context {:?} is destroyed", context_id);
}
},
_ => error!("Could not map buffer({:?})", buffer_id),

View file

@ -6,7 +6,6 @@
use std::borrow::Cow;
use std::collections::HashMap;
use std::ops::ControlFlow;
use std::slice;
use std::sync::{Arc, Mutex};
@ -15,7 +14,7 @@ use ipc_channel::ipc::{IpcReceiver, IpcSender, IpcSharedMemory};
use log::{info, warn};
use servo_config::pref;
use webrender::{RenderApi, RenderApiSender};
use webrender_api::DocumentId;
use webrender_api::{DocumentId, ExternalImageId};
use webrender_traits::{WebrenderExternalImageRegistry, WebrenderImageHandlerType};
use wgc::command::{ComputePass, ComputePassDescriptor, RenderPass};
use wgc::device::queue::SubmittedWorkDoneClosure;
@ -404,17 +403,6 @@ impl WGPU {
self.maybe_dispatch_wgpu_error(device_id, error);
}
},
WebGPURequest::CreateContext(sender) => {
let id = self
.external_images
.lock()
.expect("Lock poisoned?")
.next_id(WebrenderImageHandlerType::WebGPU);
let image_key = self.webrender_api.lock().unwrap().generate_image_key();
if let Err(e) = sender.send((WebGPUContextId(id.0), image_key)) {
warn!("Failed to send ExternalImageId to new context ({})", e);
};
},
WebGPURequest::CreatePipelineLayout {
device_id,
pipeline_layout_id,
@ -513,17 +501,79 @@ impl WGPU {
}
self.maybe_dispatch_wgpu_error(device_id, error);
},
WebGPURequest::CreateSwapChain {
device_id,
queue_id,
WebGPURequest::CreateContext {
buffer_ids,
context_id,
image_key,
size,
format,
} => self.create_swapchain(
device_id, queue_id, buffer_ids, context_id, format, size, image_key,
),
sender,
} => {
let id = self
.external_images
.lock()
.expect("Lock poisoned?")
.next_id(WebrenderImageHandlerType::WebGPU);
let image_key = self.webrender_api.lock().unwrap().generate_image_key();
let context_id = WebGPUContextId(id.0);
if let Err(e) = sender.send((context_id, image_key)) {
warn!("Failed to send ExternalImageId to new context ({})", e);
};
self.create_context(context_id, image_key, size, buffer_ids);
},
WebGPURequest::UpdateContext {
context_id,
size,
configuration,
} => {
self.update_context(context_id, size, configuration);
},
WebGPURequest::SwapChainPresent {
context_id,
texture_id,
encoder_id,
} => {
let result = self.swapchain_present(context_id, encoder_id, texture_id);
if let Err(e) = result {
log::error!("Error occured in SwapChainPresent: {e:?}");
}
},
WebGPURequest::ValidateTextureDescriptor {
device_id,
texture_id,
descriptor,
} => {
// https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configure
// validating TextureDescriptor by creating dummy texture
let global = &self.global;
let (_, error) =
global.device_create_texture(device_id, &descriptor, Some(texture_id));
global.texture_drop(texture_id);
self.poller.wake();
if let Err(e) = self.script_sender.send(WebGPUMsg::FreeTexture(texture_id))
{
warn!("Unable to send FreeTexture({:?}) ({:?})", texture_id, e);
};
if let Some(error) = error {
self.dispatch_error(device_id, Error::from_error(error));
continue;
}
// Supported context formats
// TODO: wgt::TextureFormat::Rgba16Float, when wr supports HDR
if !matches!(
descriptor.format,
wgt::TextureFormat::Bgra8Unorm | wgt::TextureFormat::Rgba8Unorm
) {
self.dispatch_error(
device_id,
Error::Validation("Unsupported context format".to_string()),
);
}
},
WebGPURequest::DestroyContext { context_id } => {
self.destroy_context(context_id);
self.external_images
.lock()
.expect("Lock poisoned?")
.remove(&ExternalImageId(context_id.0));
},
WebGPURequest::CreateTexture {
device_id,
texture_id,
@ -561,12 +611,6 @@ impl WGPU {
// Wake poller thread to trigger DeviceLostClosure
self.poller.wake();
},
WebGPURequest::DestroySwapChain {
context_id,
image_key,
} => {
self.destroy_swapchain(context_id, image_key);
},
WebGPURequest::DestroyTexture(texture_id) => {
let global = &self.global;
let _ = global.texture_destroy(texture_id);
@ -965,17 +1009,6 @@ impl WGPU {
};
self.maybe_dispatch_error(device_id, result.err());
},
WebGPURequest::SwapChainPresent {
context_id,
texture_id,
encoder_id,
} => {
if let ControlFlow::Break(_) =
self.swapchain_present(context_id, encoder_id, texture_id)
{
continue;
}
},
WebGPURequest::UnmapBuffer {
buffer_id,
array_buffer,

View file

@ -9741,8 +9741,6 @@
[:format="bc1-rgba-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc1-rgba-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc1-rgba-unorm-srgb";canvasType="offscreen";enable_required_feature=false]
@ -9751,8 +9749,6 @@
[:format="bc1-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=false]
[:format="bc1-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc2-rgba-unorm";canvasType="offscreen";enable_required_feature=false]
@ -9761,8 +9757,6 @@
[:format="bc2-rgba-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc2-rgba-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc2-rgba-unorm-srgb";canvasType="offscreen";enable_required_feature=false]
@ -9771,8 +9765,6 @@
[:format="bc2-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=false]
[:format="bc2-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc3-rgba-unorm";canvasType="offscreen";enable_required_feature=false]
@ -9781,8 +9773,6 @@
[:format="bc3-rgba-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc3-rgba-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc3-rgba-unorm-srgb";canvasType="offscreen";enable_required_feature=false]
@ -9791,8 +9781,6 @@
[:format="bc3-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=false]
[:format="bc3-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc4-r-snorm";canvasType="offscreen";enable_required_feature=false]
@ -9801,8 +9789,6 @@
[:format="bc4-r-snorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc4-r-snorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc4-r-unorm";canvasType="offscreen";enable_required_feature=false]
@ -9811,8 +9797,6 @@
[:format="bc4-r-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc4-r-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc5-rg-snorm";canvasType="offscreen";enable_required_feature=false]
@ -9821,8 +9805,6 @@
[:format="bc5-rg-snorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc5-rg-snorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc5-rg-unorm";canvasType="offscreen";enable_required_feature=false]
@ -9831,8 +9813,6 @@
[:format="bc5-rg-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc5-rg-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc6h-rgb-float";canvasType="offscreen";enable_required_feature=false]
@ -9841,8 +9821,6 @@
[:format="bc6h-rgb-float";canvasType="onscreen";enable_required_feature=false]
[:format="bc6h-rgb-float";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc6h-rgb-ufloat";canvasType="offscreen";enable_required_feature=false]
@ -9851,8 +9829,6 @@
[:format="bc6h-rgb-ufloat";canvasType="onscreen";enable_required_feature=false]
[:format="bc6h-rgb-ufloat";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc7-rgba-unorm";canvasType="offscreen";enable_required_feature=false]
@ -9861,8 +9837,6 @@
[:format="bc7-rgba-unorm";canvasType="onscreen";enable_required_feature=false]
[:format="bc7-rgba-unorm";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="bc7-rgba-unorm-srgb";canvasType="offscreen";enable_required_feature=false]
@ -9871,8 +9845,6 @@
[:format="bc7-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=false]
[:format="bc7-rgba-unorm-srgb";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="depth32float-stencil8";canvasType="offscreen";enable_required_feature=false]
@ -9881,8 +9853,6 @@
[:format="depth32float-stencil8";canvasType="onscreen";enable_required_feature=false]
[:format="depth32float-stencil8";canvasType="onscreen";enable_required_feature=true]
expected:
if os == "linux" and not debug: FAIL
[:format="eac-r11snorm";canvasType="offscreen";enable_required_feature=false]
@ -16934,9 +16904,45 @@
[cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:configure,at_over:*]
disabled: true
expected:
if os == "linux" and not debug: SKIP
[:limitTest="atDefault";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="atDefault";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="atDefault";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="atDefault";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="atMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="atMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="atMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="atMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="overMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="overMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="overMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="overMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="underDefault";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="underDefault";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="underDefault";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="underDefault";testValueName="overLimit";canvasType="onscreen"]
[cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:createTexture,at_over:*]
@ -16962,8 +16968,45 @@
[cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension2D:getCurrentTexture,at_over:*]
expected:
if os == "linux" and not debug: CRASH
[:limitTest="atDefault";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="atDefault";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="atDefault";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="atDefault";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="atMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="atMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="atMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="atMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="betweenDefaultAndMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="overMaximum";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="overMaximum";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="overMaximum";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="overMaximum";testValueName="overLimit";canvasType="onscreen"]
[:limitTest="underDefault";testValueName="atLimit";canvasType="offscreen"]
[:limitTest="underDefault";testValueName="atLimit";canvasType="onscreen"]
[:limitTest="underDefault";testValueName="overLimit";canvasType="offscreen"]
[:limitTest="underDefault";testValueName="overLimit";canvasType="onscreen"]
[cts.https.html?q=webgpu:api,validation,capability_checks,limits,maxTextureDimension3D:createTexture,at_over:*]
@ -185733,60 +185776,32 @@
[:canvasType="onscreen";format="astc-8x8-unorm-srgb"]
[:canvasType="onscreen";format="bc1-rgba-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc1-rgba-unorm-srgb"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc2-rgba-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc2-rgba-unorm-srgb"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc3-rgba-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc3-rgba-unorm-srgb"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc4-r-snorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc4-r-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc5-rg-snorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc5-rg-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc6h-rgb-float"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc6h-rgb-ufloat"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc7-rgba-unorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bc7-rgba-unorm-srgb"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bgra8unorm"]
@ -185863,8 +185878,6 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="r8snorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="r8uint"]
expected:
@ -185875,8 +185888,6 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rg11b10ufloat"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rg16float"]
expected:
@ -185907,8 +185918,6 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rg8snorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rg8uint"]
expected:
@ -185927,8 +185936,6 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgb9e5ufloat"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba16float"]
expected:
@ -185959,8 +185966,6 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba8snorm"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba8uint"]
expected:
@ -185978,13 +185983,23 @@
[cts.https.html?q=webgpu:web_platform,canvas,configure:size_zero_after_configure:*]
expected:
if os == "linux" and not debug: CRASH
[:canvasType="offscreen";zeroDimension="height"]
[:canvasType="offscreen";zeroDimension="width"]
[:canvasType="onscreen";zeroDimension="height"]
[:canvasType="onscreen";zeroDimension="width"]
[cts.https.html?q=webgpu:web_platform,canvas,configure:size_zero_before_configure:*]
expected:
if os == "linux" and not debug: CRASH
[:canvasType="offscreen";zeroDimension="height"]
[:canvasType="offscreen";zeroDimension="width"]
[:canvasType="onscreen";zeroDimension="height"]
[:canvasType="onscreen";zeroDimension="width"]
[cts.https.html?q=webgpu:web_platform,canvas,configure:usage:*]
@ -186027,18 +186042,12 @@
[:canvasType="offscreen";format="rgba8unorm";viewFormatFeature="texture-compression-etc2"]
[:canvasType="onscreen";format="bgra8unorm";viewFormatFeature="_undef_"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bgra8unorm";viewFormatFeature="depth32float-stencil8"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bgra8unorm";viewFormatFeature="texture-compression-astc"]
[:canvasType="onscreen";format="bgra8unorm";viewFormatFeature="texture-compression-bc"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="bgra8unorm";viewFormatFeature="texture-compression-etc2"]
@ -186047,30 +186056,20 @@
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba16float";viewFormatFeature="depth32float-stencil8"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba16float";viewFormatFeature="texture-compression-astc"]
[:canvasType="onscreen";format="rgba16float";viewFormatFeature="texture-compression-bc"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba16float";viewFormatFeature="texture-compression-etc2"]
[:canvasType="onscreen";format="rgba8unorm";viewFormatFeature="_undef_"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba8unorm";viewFormatFeature="depth32float-stencil8"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba8unorm";viewFormatFeature="texture-compression-astc"]
[:canvasType="onscreen";format="rgba8unorm";viewFormatFeature="texture-compression-bc"]
expected:
if os == "linux" and not debug: FAIL
[:canvasType="onscreen";format="rgba8unorm";viewFormatFeature="texture-compression-etc2"]
@ -186082,8 +186081,9 @@
[cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:configured:*]
expected:
if os == "linux" and not debug: CRASH
[:canvasType="offscreen"]
[:canvasType="onscreen"]
[cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:expiry:*]
@ -186119,8 +186119,9 @@
[cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:resize:*]
expected:
if os == "linux" and not debug: CRASH
[:canvasType="offscreen"]
[:canvasType="onscreen"]
[cts.https.html?q=webgpu:web_platform,canvas,getCurrentTexture:single_frames:*]

View file

@ -1,6 +1,3 @@
[canvas_clear.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,6 +1,3 @@
[canvas_colorspace_rgba16float.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH

View file

@ -1,6 +1,3 @@
[canvas_complex_rgba16float_copy.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH

View file

@ -1,6 +1,3 @@
[canvas_complex_rgba16float_draw.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH

View file

@ -1,6 +1,3 @@
[canvas_complex_rgba16float_store.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,3 +1,3 @@
[canvas_complex_rgba8unorm_store.https.html]
expected:
if os == "linux" and not debug: [PASS, FAIL]
if os == "linux" and not debug: PASS

View file

@ -1,3 +1,3 @@
[canvas_composite_alpha_bgra8unorm_opaque_copy.https.html]
expected:
if os == "linux" and not debug: [CRASH, PASS, FAIL]
if os == "linux" and not debug: PASS

View file

@ -1,2 +1,2 @@
[canvas_composite_alpha_bgra8unorm_opaque_draw.https.html]
expected: [CRASH, PASS, FAIL]
expected: PASS

View file

@ -1,2 +1,3 @@
[canvas_composite_alpha_bgra8unorm_premultiplied_copy.https.html]
expected: FAIL
expected:
if os == "linux" and not debug: PASS

View file

@ -1,2 +1,3 @@
[canvas_composite_alpha_bgra8unorm_premultiplied_draw.https.html]
expected: FAIL
expected:
if os == "linux" and not debug: PASS

View file

@ -1,6 +1,3 @@
[canvas_composite_alpha_rgba16float_opaque_copy.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,6 +1,3 @@
[canvas_composite_alpha_rgba16float_opaque_draw.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,6 +1,3 @@
[canvas_composite_alpha_rgba16float_premultiplied_copy.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,6 +1,3 @@
[canvas_composite_alpha_rgba16float_premultiplied_draw.https.html]
expected:
if os == "win": CRASH
if os == "linux" and debug: CRASH
if os == "linux" and not debug: TIMEOUT
if os == "mac": CRASH
if os == "linux" and not debug: FAIL

View file

@ -1,3 +1,3 @@
[canvas_composite_alpha_rgba8unorm_opaque_copy.https.html]
expected:
if os == "linux" and not debug: [CRASH, PASS, FAIL]
if os == "linux" and not debug: PASS

View file

@ -1,3 +1,3 @@
[canvas_composite_alpha_rgba8unorm_opaque_draw.https.html]
expected:
if os == "linux" and not debug: [PASS, FAIL]
if os == "linux" and not debug: PASS

View file

@ -1,2 +1,3 @@
[canvas_composite_alpha_rgba8unorm_premultiplied_copy.https.html]
expected: FAIL
expected:
if os == "linux" and not debug: PASS

View file

@ -1,2 +1,3 @@
[canvas_composite_alpha_rgba8unorm_premultiplied_draw.https.html]
expected: [CRASH, FAIL]
expected:
if os == "linux" and not debug: PASS

View file

@ -1,6 +1,3 @@
[delay_get_texture.https.html]
expected:
if os == "win": FAIL
if os == "linux" and debug: FAIL
if os == "linux" and not debug: [PASS, FAIL]
if os == "mac": FAIL
if os == "linux" and not debug: PASS