aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbors-servo <servo-ops@mozilla.com>2020-08-27 13:12:27 -0400
committerGitHub <noreply@github.com>2020-08-27 13:12:27 -0400
commit9e6da58d7793a4576fef38446457e1073a19cd5e (patch)
tree9413f55965530db94a2c82d68bbb33a4f3d765a9
parent84185eb1daf1420597f38a77e40ed3baedb5d521 (diff)
parent85b6bbb33ace8b433ed279d2cb73ed8d96bb9690 (diff)
downloadservo-9e6da58d7793a4576fef38446457e1073a19cd5e.tar.gz
servo-9e6da58d7793a4576fef38446457e1073a19cd5e.zip
Auto merge of #27614 - kunalmohan:webgpu-cts, r=kvark
Minor fixes and update cts <!-- Please describe your changes on the following line: --> - Prevent redundant buffer and texture destroy calls. - More subtests for B2B copy pass now. - All tests under `setViewport()` and `setScissorRect()` pass now. - Tests for `createTexture()` do not crash. More than 50% of them pass now. r?@kvark --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #___ (GitHub issue number if applicable) <!-- Either: --> - [X] There are tests for these changes OR - [ ] These changes do not require tests because ___ <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
-rw-r--r--Cargo.lock4
-rw-r--r--components/script/dom/gpubuffer.rs1
-rw-r--r--components/script/dom/gputexture.rs7
-rw-r--r--components/webgpu/lib.rs28
-rw-r--r--tests/wpt/webgpu/meta/MANIFEST.json32
-rw-r--r--tests/wpt/webgpu/meta/webgpu/cts.html.ini111
-rw-r--r--tests/wpt/webgpu/tests/webgpu/common/framework/params_utils.js3
-rw-r--r--tests/wpt/webgpu/tests/webgpu/common/framework/query/encode_selectively.js2
-rw-r--r--tests/wpt/webgpu/tests/webgpu/common/framework/query/parseQuery.js26
-rw-r--r--tests/wpt/webgpu/tests/webgpu/common/framework/version.js2
-rw-r--r--tests/wpt/webgpu/tests/webgpu/cts.html3
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/api/operation/copyBetweenLinearDataAndTexture.spec.js1026
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js184
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/setScissorRect.spec.js2
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js25
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/listing.js10
-rw-r--r--tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js5
17 files changed, 1390 insertions, 81 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b4bc8fe7f39..bebc8f83d36 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7028,7 +7028,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.6.0"
-source = "git+https://github.com/gfx-rs/wgpu#1d0e0ce37ede5ec53000ab252c27b8cf856865b2"
+source = "git+https://github.com/gfx-rs/wgpu#59f0996eabd43e882d4bfc73ee5b4ed912617abf"
dependencies = [
"arrayvec 0.5.1",
"bitflags",
@@ -7055,7 +7055,7 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "0.6.0"
-source = "git+https://github.com/gfx-rs/wgpu#1d0e0ce37ede5ec53000ab252c27b8cf856865b2"
+source = "git+https://github.com/gfx-rs/wgpu#59f0996eabd43e882d4bfc73ee5b4ed912617abf"
dependencies = [
"bitflags",
"serde",
diff --git a/components/script/dom/gpubuffer.rs b/components/script/dom/gpubuffer.rs
index 2d721465e68..60a6075cb7f 100644
--- a/components/script/dom/gpubuffer.rs
+++ b/components/script/dom/gpubuffer.rs
@@ -184,6 +184,7 @@ impl GPUBufferMethods for GPUBuffer {
GPUBufferState::Mapped | GPUBufferState::MappedAtCreation => {
self.Unmap();
},
+ GPUBufferState::Destroyed => return,
_ => {},
};
if let Err(e) = self
diff --git a/components/script/dom/gputexture.rs b/components/script/dom/gputexture.rs
index 175ec7c90cc..b473e52eaae 100644
--- a/components/script/dom/gputexture.rs
+++ b/components/script/dom/gputexture.rs
@@ -18,6 +18,7 @@ use crate::dom::gpudevice::{
};
use crate::dom::gputextureview::GPUTextureView;
use dom_struct::dom_struct;
+use std::cell::Cell;
use std::num::NonZeroU32;
use std::string::String;
use webgpu::{
@@ -40,6 +41,7 @@ pub struct GPUTexture {
dimension: GPUTextureDimension,
format: GPUTextureFormat,
texture_usage: u32,
+ destroyed: Cell<bool>,
}
impl GPUTexture {
@@ -67,6 +69,7 @@ impl GPUTexture {
dimension,
format,
texture_usage,
+ destroyed: Cell::new(false),
}
}
@@ -197,6 +200,9 @@ impl GPUTextureMethods for GPUTexture {
/// https://gpuweb.github.io/gpuweb/#dom-gputexture-destroy
fn Destroy(&self) {
+ if self.destroyed.get() {
+ return;
+ }
if let Err(e) = self
.channel
.0
@@ -207,5 +213,6 @@ impl GPUTextureMethods for GPUTexture {
self.texture.0, e
);
};
+ self.destroyed.set(true);
}
}
diff --git a/components/webgpu/lib.rs b/components/webgpu/lib.rs
index 7d4dbd3e808..0d6ece51273 100644
--- a/components/webgpu/lib.rs
+++ b/components/webgpu/lib.rs
@@ -21,7 +21,6 @@ use servo_config::pref;
use smallvec::SmallVec;
use std::borrow::Cow;
use std::cell::RefCell;
-use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::num::NonZeroU64;
use std::rc::Rc;
@@ -475,9 +474,7 @@ impl<'a> WGPU<'a> {
))
.map_err(|e| format!("{:?}", e))
};
- if result.is_err() {
- self.encoder_record_error(command_encoder_id, result.clone());
- }
+ self.encoder_record_error(command_encoder_id, &result);
self.send_result(device_id, scope_id, result);
},
WebGPURequest::CopyBufferToBuffer {
@@ -497,7 +494,7 @@ impl<'a> WGPU<'a> {
destination_offset,
size
));
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::CopyBufferToTexture {
command_encoder_id,
@@ -512,7 +509,7 @@ impl<'a> WGPU<'a> {
&destination,
&copy_size
));
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::CopyTextureToBuffer {
command_encoder_id,
@@ -527,7 +524,7 @@ impl<'a> WGPU<'a> {
&destination,
&copy_size
));
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::CopyTextureToTexture {
command_encoder_id,
@@ -542,7 +539,7 @@ impl<'a> WGPU<'a> {
&destination,
&copy_size
));
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::CreateBindGroup {
device_id,
@@ -985,7 +982,7 @@ impl<'a> WGPU<'a> {
} else {
Err(String::from("Invalid ComputePass"))
};
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::RunRenderPass {
command_encoder_id,
@@ -1000,7 +997,7 @@ impl<'a> WGPU<'a> {
} else {
Err(String::from("Invalid RenderPass"))
};
- self.encoder_record_error(command_encoder_id, result);
+ self.encoder_record_error(command_encoder_id, &result);
},
WebGPURequest::Submit {
queue_id,
@@ -1279,12 +1276,13 @@ impl<'a> WGPU<'a> {
fn encoder_record_error<U, T: std::fmt::Debug>(
&self,
encoder_id: id::CommandEncoderId,
- result: Result<U, T>,
+ result: &Result<U, T>,
) {
- if let Err(e) = result {
- if let Entry::Vacant(v) = self.error_command_encoders.borrow_mut().entry(encoder_id) {
- v.insert(format!("{:?}", e));
- }
+ if let Err(ref e) = result {
+ self.error_command_encoders
+ .borrow_mut()
+ .entry(encoder_id)
+ .or_insert_with(|| format!("{:?}", e));
}
}
}
diff --git a/tests/wpt/webgpu/meta/MANIFEST.json b/tests/wpt/webgpu/meta/MANIFEST.json
index 28666d5d9c1..f45fcb38480 100644
--- a/tests/wpt/webgpu/meta/MANIFEST.json
+++ b/tests/wpt/webgpu/meta/MANIFEST.json
@@ -89,7 +89,7 @@
[]
],
"params_utils.js": [
- "a95d01b9c8058076af5cf49d4ab82b5c74367a5b",
+ "d4ffd25372d2d3e975c683ca8a17c2c82d5c8687",
[]
],
"query": {
@@ -98,7 +98,7 @@
[]
],
"encode_selectively.js": [
- "62cb55ee039b7cc04dfdf751ce2241ba5864cac6",
+ "cb365deb4bb688e8ae5eebdef24a2ffcb3d857ef",
[]
],
"json_param_value.js": [
@@ -106,7 +106,7 @@
[]
],
"parseQuery.js": [
- "6c10baab0b2f97dc09e633201dae6618842fbd69",
+ "8bfd88bc9b66a41e08b3283b13b65bb3da0c10fe",
[]
],
"query.js": [
@@ -161,7 +161,7 @@
]
},
"version.js": [
- "794e3cdef44214801f15234a61aaf5af338f97fc",
+ "74eef63b8a1fa6042d1d8352b25eaf95d4b25985",
[]
]
},
@@ -223,6 +223,10 @@
]
}
},
+ "copyBetweenLinearDataAndTexture.spec.js": [
+ "d2b89189e2c65d50f4f6f0c20f32a7f6512c5b35",
+ []
+ ],
"fences.spec.js": [
"98f913008b8af33e1dc866f5714388d4ec9e050d",
[]
@@ -311,7 +315,7 @@
],
"resource_usages": {
"textureUsageInRender.spec.js": [
- "12efa65ce2d9e9ed67cb2be398a0466424d38015",
+ "b036245663df8d684067eff7c79861253205dbca",
[]
]
},
@@ -324,7 +328,7 @@
[]
],
"setScissorRect.spec.js": [
- "7934c007286c8c4e9702eaaadb14b288a78e8fb1",
+ "c049e92e70e3ad9a5f71f88a863e5a86cc74fc4e",
[]
],
"setStencilReference.spec.js": [
@@ -350,7 +354,7 @@
[]
],
"gpu_test.js": [
- "f350131af3babe9729dec35261f7c445e2bf41d2",
+ "21cb10f1429e495b2f32b1d51a09b8584f63707d",
[]
],
"idl": {
@@ -366,7 +370,7 @@
]
},
"listing.js": [
- "3134cf0dd5693705ed82f0828f4dbd93fedf358e",
+ "cbb23b30ec894c103a957f944b18be1e019ed571",
[]
],
"util": {
@@ -380,7 +384,7 @@
],
"texture": {
"layout.js": [
- "c3c610bf0d93ebe66abf81d8f2017deba62641a3",
+ "927798985fc772a4c9e9ea7a62335ab693b43991",
[]
],
"subresource.js": [
@@ -435,7 +439,7 @@
"testharness": {
"webgpu": {
"cts.html": [
- "28d69b38d20367b4c61e72e834f518a11e8de411",
+ "63357f7e996ecadde32f6816dc131b94e9ab976c",
[
"webgpu/cts.html?q=webgpu:api,operation,buffers,map_detach:*",
{}
@@ -457,6 +461,10 @@
{}
],
[
+ "webgpu/cts.html?q=webgpu:api,operation,copyBetweenLinearDataAndTexture:*",
+ {}
+ ],
+ [
"webgpu/cts.html?q=webgpu:api,operation,fences:*",
{}
],
@@ -485,10 +493,6 @@
{}
],
[
- "webgpu/cts.html?q=webgpu:api,validation,createTexture:*",
- {}
- ],
- [
"webgpu/cts.html?q=webgpu:api,validation,error_scope:*",
{}
],
diff --git a/tests/wpt/webgpu/meta/webgpu/cts.html.ini b/tests/wpt/webgpu/meta/webgpu/cts.html.ini
index 4f1bc0ef2b3..10c16d8a7f5 100644
--- a/tests/wpt/webgpu/meta/webgpu/cts.html.ini
+++ b/tests/wpt/webgpu/meta/webgpu/cts.html.ini
@@ -30,36 +30,95 @@
[cts.html?q=webgpu:api,validation,createTexture:*]
- expected: CRASH
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc2-rgba-unorm"]
+ expected: FAIL
-[cts.html?q=webgpu:api,validation,setViewport:*]
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=-1;minDepth=0;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc3-rgba-unorm"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_mipLevelCount:width=32;height=31;mipLevelCount=7]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="r8snorm"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode:destroyBeforeEncode=false;destroyAfterEncode=true]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="rg11b10float"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_submit_a_destroyed_texture_before_and_after_encode:destroyBeforeEncode=true;destroyAfterEncode=false]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_sampleCount:sampleCount=2]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc4-r-unorm"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_sampleCount:sampleCount=8]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_sampleCount:sampleCount=16]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_sampleCount:sampleCount=4;arrayLayerCount=2]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_mipLevelCount:width=32;height=32;mipLevelCount=0]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc1-rgba-unorm-srgb"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc6h-rgb-ufloat"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc7-rgba-unorm-srgb"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=1;minDepth=10;maxDepth=1]
+ [webgpu:api,validation,createTexture:validation_of_mipLevelCount:width=32;height=32;mipLevelCount=100]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=0;minDepth=0;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc5-rg-unorm"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=1;minDepth=-1;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc6h-rgb-sfloat"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=0;height=1;minDepth=0;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc7-rgba-unorm"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=1;minDepth=0;maxDepth=-1]
+ [webgpu:api,validation,createTexture:validation_of_sampleCount:sampleCount=4;mipLevelCount=2]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=0;height=0;minDepth=0;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="rg8snorm"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=-1;height=1;minDepth=0;maxDepth=1]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc1-rgba-unorm"]
expected: FAIL
- [webgpu:api,validation,setViewport:use_of_setViewport:x=0;y=0;width=1;height=1;minDepth=0;maxDepth=10]
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc4-r-snorm"]
expected: FAIL
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc3-rgba-unorm-srgb"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="rgba8snorm"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc5-rg-snorm"]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:validation_of_mipLevelCount:width=31;height=32;mipLevelCount=7]
+ expected: FAIL
+
+ [webgpu:api,validation,createTexture:it_is_invalid_to_have_an_output_attachment_texture_with_non_renderable_format:format="bc2-rgba-unorm-srgb"]
+ expected: FAIL
+
+
+[cts.html?q=webgpu:api,validation,setViewport:*]
[cts.html?q=webgpu:web-platform,copyImageBitmapToTexture:*]
expected: TIMEOUT
@@ -183,15 +242,6 @@
[cts.html?q=webgpu:api,validation,setScissorRect:*]
- [webgpu:api,validation,setScissorRect:use_of_setScissorRect:x=0;y=0;width=0;height=1]
- expected: FAIL
-
- [webgpu:api,validation,setScissorRect:use_of_setScissorRect:x=0;y=0;width=0;height=0]
- expected: FAIL
-
- [webgpu:api,validation,setScissorRect:use_of_setScissorRect:x=0;y=0;width=1;height=0]
- expected: FAIL
-
[cts.html?q=webgpu:web-platform,canvas,context_creation:*]
@@ -222,15 +272,9 @@
[cts.html?q=webgpu:api,operation,command_buffer,render,basic:*]
[cts.html?q=webgpu:api,validation,copyBufferToBuffer:*]
- [webgpu:api,validation,copyBufferToBuffer:copy_within_same_buffer:srcOffset=4;dstOffset=0;copySize=8]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=64;dstUsage=512]
expected: FAIL
- [webgpu:api,validation,copyBufferToBuffer:copy_out_of_bounds:srcOffset=0;dstOffset=36;copySize=0]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=512;dstUsage=8]
expected: FAIL
@@ -243,9 +287,6 @@
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=512;dstUsage=1]
expected: FAIL
- [webgpu:api,validation,copyBufferToBuffer:copy_out_of_bounds:srcOffset=36;dstOffset=0;copySize=0]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=8;dstUsage=512]
expected: FAIL
@@ -255,15 +296,9 @@
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=1;dstUsage=512]
expected: FAIL
- [webgpu:api,validation,copyBufferToBuffer:copy_within_same_buffer:srcOffset=0;dstOffset=4;copySize=8]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=2;dstUsage=512]
expected: FAIL
- [webgpu:api,validation,copyBufferToBuffer:copy_within_same_buffer:srcOffset=0;dstOffset=8;copySize=4]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=4;dstUsage=512]
expected: FAIL
@@ -279,9 +314,6 @@
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=16;dstUsage=512]
expected: FAIL
- [webgpu:api,validation,copyBufferToBuffer:copy_within_same_buffer:srcOffset=8;dstOffset=0;copySize=4]
- expected: FAIL
-
[webgpu:api,validation,copyBufferToBuffer:buffer_usage:srcUsage=512;dstUsage=64]
expected: FAIL
@@ -303,3 +335,6 @@
[cts.html?q=webgpu:api,validation,copy_between_linear_data_and_texture,copyBetweenLinearDataAndTexture_dataRelated:*]
expected: CRASH
+
+[cts.html?q=webgpu:api,operation,copyBetweenLinearDataAndTexture:*]
+ expected: TIMEOUT
diff --git a/tests/wpt/webgpu/tests/webgpu/common/framework/params_utils.js b/tests/wpt/webgpu/tests/webgpu/common/framework/params_utils.js
index a95d01b9c80..d4ffd25372d 100644
--- a/tests/wpt/webgpu/tests/webgpu/common/framework/params_utils.js
+++ b/tests/wpt/webgpu/tests/webgpu/common/framework/params_utils.js
@@ -3,6 +3,9 @@
**/ import { comparePublicParamsPaths, Ordering } from './query/compare.js';
import { kWildcard, kParamSeparator, kParamKVSeparator } from './query/separators.js';
// Consider adding more types here if needed
+//
+// TODO: This type isn't actually used to constrain what you're allowed to do in `.params()`, so
+// it's not really serving its purpose. Figure out how to fix that?
export function paramKeyIsPublic(key) {
return !key.startsWith('_');
diff --git a/tests/wpt/webgpu/tests/webgpu/common/framework/query/encode_selectively.js b/tests/wpt/webgpu/tests/webgpu/common/framework/query/encode_selectively.js
index 62cb55ee039..cb365deb4bb 100644
--- a/tests/wpt/webgpu/tests/webgpu/common/framework/query/encode_selectively.js
+++ b/tests/wpt/webgpu/tests/webgpu/common/framework/query/encode_selectively.js
@@ -17,6 +17,8 @@
ret = ret.replace(/%3D/g, '='); // for params (k=v)
ret = ret.replace(/%5B/g, '['); // for JSON arrays
ret = ret.replace(/%5D/g, ']'); // for JSON arrays
+ ret = ret.replace(/%7B/g, '{'); // for JSON objects
+ ret = ret.replace(/%7D/g, '}'); // for JSON objects
ret = ret.replace(/%E2%9C%97/g, '✗'); // for jsUndefinedMagicValue
return ret;
}
diff --git a/tests/wpt/webgpu/tests/webgpu/common/framework/query/parseQuery.js b/tests/wpt/webgpu/tests/webgpu/common/framework/query/parseQuery.js
index 6c10baab0b2..8bfd88bc9b6 100644
--- a/tests/wpt/webgpu/tests/webgpu/common/framework/query/parseQuery.js
+++ b/tests/wpt/webgpu/tests/webgpu/common/framework/query/parseQuery.js
@@ -26,9 +26,29 @@ function parseQueryImpl(s) {
// Undo encodeURIComponentSelectively
s = decodeURIComponent(s);
- // bigParts are: suite, group, test, params (note kBigSeparator could appear in params)
- const [suite, fileString, testString, paramsString] = s.split(kBigSeparator, 4);
- assert(fileString !== undefined, `filter string must have at least one ${kBigSeparator}`);
+ // bigParts are: suite, file, test, params (note kBigSeparator could appear in params)
+ let suite;
+ let fileString;
+ let testString;
+ let paramsString;
+ {
+ const i1 = s.indexOf(kBigSeparator);
+ assert(i1 !== -1, `query string must have at least one ${kBigSeparator}`);
+ suite = s.substring(0, i1);
+ const i2 = s.indexOf(kBigSeparator, i1 + 1);
+ if (i2 === -1) {
+ fileString = s.substring(i1 + 1);
+ } else {
+ fileString = s.substring(i1 + 1, i2);
+ const i3 = s.indexOf(kBigSeparator, i2 + 1);
+ if (i3 === -1) {
+ testString = s.substring(i2 + 1);
+ } else {
+ testString = s.substring(i2 + 1, i3);
+ paramsString = s.substring(i3 + 1);
+ }
+ }
+ }
const { parts: file, wildcard: filePathHasWildcard } = parseBigPart(fileString, kPathSeparator);
diff --git a/tests/wpt/webgpu/tests/webgpu/common/framework/version.js b/tests/wpt/webgpu/tests/webgpu/common/framework/version.js
index 794e3cdef44..74eef63b8a1 100644
--- a/tests/wpt/webgpu/tests/webgpu/common/framework/version.js
+++ b/tests/wpt/webgpu/tests/webgpu/common/framework/version.js
@@ -1,3 +1,3 @@
// AUTO-GENERATED - DO NOT EDIT. See tools/gen_version.
-export const version = 'fa4873f0a303566ca6f34744a253d96f5e462d3d';
+export const version = 'c1df7f4ff1adcde985384633e7cffa52d53e3535';
diff --git a/tests/wpt/webgpu/tests/webgpu/cts.html b/tests/wpt/webgpu/tests/webgpu/cts.html
index 28d69b38d20..63357f7e996 100644
--- a/tests/wpt/webgpu/tests/webgpu/cts.html
+++ b/tests/wpt/webgpu/tests/webgpu/cts.html
@@ -33,6 +33,7 @@
<meta name=variant content='?q=webgpu:api,operation,command_buffer,basic:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,copies:*'>
<meta name=variant content='?q=webgpu:api,operation,command_buffer,render,basic:*'>
+<meta name=variant content='?q=webgpu:api,operation,copyBetweenLinearDataAndTexture:*'>
<meta name=variant content='?q=webgpu:api,operation,fences:*'>
<meta name=variant content='?q=webgpu:api,operation,render_pass,storeOp:*'>
<!--<meta name=variant content='?q=webgpu:api,operation,resource_init,copied_texture_clear:*'>-->
@@ -42,7 +43,7 @@
<meta name=variant content='?q=webgpu:api,validation,createBindGroup:*'>
<!--<meta name=variant content='?q=webgpu:api,validation,createBindGroupLayout:*'>-->
<meta name=variant content='?q=webgpu:api,validation,createPipelineLayout:*'>
-<meta name=variant content='?q=webgpu:api,validation,createTexture:*'>
+<!--<meta name=variant content='?q=webgpu:api,validation,createTexture:*'>-->
<!--<meta name=variant content='?q=webgpu:api,validation,createView:*'>-->
<meta name=variant content='?q=webgpu:api,validation,error_scope:*'>
<meta name=variant content='?q=webgpu:api,validation,fences:*'>
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/api/operation/copyBetweenLinearDataAndTexture.spec.js b/tests/wpt/webgpu/tests/webgpu/webgpu/api/operation/copyBetweenLinearDataAndTexture.spec.js
new file mode 100644
index 00000000000..d2b89189e2c
--- /dev/null
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/api/operation/copyBetweenLinearDataAndTexture.spec.js
@@ -0,0 +1,1026 @@
+/**
+ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+ **/ export const description = `writeTexture + copyBufferToTexture + copyTextureToBuffer operation tests.
+
+* copy_with_various_rows_per_image_and_bytes_per_row: test that copying data with various bytesPerRow (including { ==, > } bytesInACompleteRow) and\
+ rowsPerImage (including { ==, > } copyExtent.height) values and minimum required bytes in copy works for every format. Also covers special code paths:
+ - bufferSize - offset < bytesPerImage * copyExtent.depth
+ - when bytesPerRow is not a multiple of 512 and copyExtent.depth > 1: copyExtent.depth % 2 == { 0, 1 }
+ - bytesPerRow == bytesInACompleteCopyImage
+
+* copy_with_various_offsets_and_data_sizes: test that copying data with various offset (including { ==, > } 0 and is/isn't power of 2) values and additional\
+ data paddings works for every format with 2d and 2d-array textures. Also covers special code paths:
+ - offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
+ - offset > bytesInACompleteCopyImage
+
+* copy_with_various_origins_and_copy_extents: test that copying slices of a texture works with various origin (including { origin.x, origin.y, origin.z }\
+ { ==, > } 0 and is/isn't power of 2) and copyExtent (including { copyExtent.x, copyExtent.y, copyExtent.z } { ==, > } 0 and is/isn't power of 2) values\
+ (also including {origin._ + copyExtent._ { ==, < } the subresource size of textureCopyView) works for all formats. origin and copyExtent values are passed\
+ as [number, number, number] instead of GPUExtent3DDict.
+
+* copy_various_mip_levels: test that copying various mip levels works for all formats. Also covers special code paths:
+ - the physical size of the subresouce is not equal to the logical size
+ - bufferSize - offset < bytesPerImage * copyExtent.depth and copyExtent needs to be clamped
+
+* copy_with_no_image_or_slice_padding_and_undefined_values: test that when copying a single row we can set any bytesPerRow value and when copying a single\
+ slice we can set rowsPerImage to 0. Also test setting offset, rowsPerImage, mipLevel, origin, origin.{x,y,z} to undefined.
+
+* TODO:
+ - add another initMethod which renders the texture
+ - because of expectContests 4-bytes alignment we don't test CopyT2B with buffer size not divisible by 4
+ - add tests for 1d / 3d textures
+`;
+import { params, poptions } from '../../../common/framework/params_builder.js';
+import { makeTestGroup } from '../../../common/framework/test_group.js';
+import { assert, unreachable } from '../../../common/framework/util/util.js';
+import { kSizedTextureFormatInfo, kSizedTextureFormats } from '../../capability_info.js';
+import { GPUTest } from '../../gpu_test.js';
+import { align } from '../../util/math.js';
+import { getTextureCopyLayout } from '../../util/texture/layout.js';
+
+/** Each combination of methods assume that the ones before it were tested and work correctly. */
+const kMethodsToTest = [
+ // We make sure that CopyT2B works when copying the whole texture for renderable formats:
+ // TODO
+ // Then we make sure that WriteTexture works for all formats:
+ { initMethod: 'WriteTexture', checkMethod: 'FullCopyT2B' },
+ // Then we make sure that CopyB2T works for all formats:
+ { initMethod: 'CopyB2T', checkMethod: 'FullCopyT2B' },
+ // Then we make sure that CopyT2B works for all formats:
+ { initMethod: 'WriteTexture', checkMethod: 'PartialCopyT2B' },
+];
+
+class CopyBetweenLinearDataAndTextureTest extends GPUTest {
+ bytesInACompleteRow(copyWidth, format) {
+ const blockWidth = kSizedTextureFormatInfo[format].blockWidth;
+ assert(copyWidth % blockWidth === 0);
+ const copyWidthInBlocks = copyWidth / blockWidth;
+ return kSizedTextureFormatInfo[format].bytesPerBlock * copyWidthInBlocks;
+ }
+
+ requiredBytesInCopy(layout, format, copyExtent) {
+ assert(layout.rowsPerImage % kSizedTextureFormatInfo[format].blockHeight === 0);
+ assert(copyExtent.height % kSizedTextureFormatInfo[format].blockHeight === 0);
+ assert(copyExtent.width % kSizedTextureFormatInfo[format].blockWidth === 0);
+ if (copyExtent.width === 0 || copyExtent.height === 0 || copyExtent.depth === 0) {
+ return 0;
+ } else {
+ const texelBlockRowsPerImage =
+ layout.rowsPerImage / kSizedTextureFormatInfo[format].blockHeight;
+ const bytesPerImage = layout.bytesPerRow * texelBlockRowsPerImage;
+ const bytesInLastSlice =
+ layout.bytesPerRow * (copyExtent.height / kSizedTextureFormatInfo[format].blockHeight - 1) +
+ (copyExtent.width / kSizedTextureFormatInfo[format].blockWidth) *
+ kSizedTextureFormatInfo[format].bytesPerBlock;
+ return bytesPerImage * (copyExtent.depth - 1) + bytesInLastSlice;
+ }
+ }
+
+ /** Offset for a particular texel in the linear texture data */
+ getTexelOffsetInBytes(textureDataLayout, format, texel, origin = { x: 0, y: 0, z: 0 }) {
+ const { offset, bytesPerRow, rowsPerImage } = textureDataLayout;
+ const info = kSizedTextureFormatInfo[format];
+
+ assert(texel.x >= origin.x && texel.y >= origin.y && texel.z >= origin.z);
+ assert(rowsPerImage % info.blockHeight === 0);
+ assert(texel.x % info.blockWidth === 0);
+ assert(texel.y % info.blockHeight === 0);
+ assert(origin.x % info.blockWidth === 0);
+ assert(origin.y % info.blockHeight === 0);
+
+ const bytesPerImage = (rowsPerImage / info.blockHeight) * bytesPerRow;
+
+ return (
+ offset +
+ (texel.z - origin.z) * bytesPerImage +
+ ((texel.y - origin.y) / info.blockHeight) * bytesPerRow +
+ ((texel.x - origin.x) / info.blockWidth) * info.bytesPerBlock
+ );
+ }
+
+ *iterateBlockRows(size, origin, format) {
+ if (size.width === 0 || size.height === 0 || size.depth === 0) {
+ // do not iterate anything for an empty region
+ return;
+ }
+ const info = kSizedTextureFormatInfo[format];
+ assert(size.height % info.blockHeight === 0);
+ for (let y = 0; y < size.height / info.blockHeight; ++y) {
+ for (let z = 0; z < size.depth; ++z) {
+ yield {
+ x: origin.x,
+ y: origin.y + y * info.blockHeight,
+ z: origin.z + z,
+ };
+ }
+ }
+ }
+
+ generateData(byteSize, start = 0) {
+ const arr = new Uint8Array(byteSize);
+ for (let i = 0; i < byteSize; ++i) {
+ arr[i] = (i ** 3 + i + start) % 251;
+ }
+ return arr;
+ }
+
+ /**
+ * This is used for testing passing undefined members of `GPUTextureDataLayout` instead of actual
+ * values where possible. Passing arguments as values and not as objects so that they are passed
+ * by copy and not by reference.
+ */
+ undefDataLayoutIfNeeded(offset, rowsPerImage, bytesPerRow, changeBeforePass) {
+ if (changeBeforePass === 'undefined') {
+ if (offset === 0) {
+ offset = undefined;
+ }
+ if (rowsPerImage === 0) {
+ rowsPerImage = undefined;
+ }
+ }
+ return { offset, bytesPerRow, rowsPerImage };
+ }
+
+ /**
+ * This is used for testing passing undefined members of `GPUTextureCopyView` instead of actual
+ * values where possible and also for testing passing the origin as `[number, number, number]`.
+ * Passing arguments as values and not as objects so that they are passed by copy and not by
+ * reference.
+ */
+ undefOrArrayCopyViewIfNeeded(texture, origin_x, origin_y, origin_z, mipLevel, changeBeforePass) {
+ let origin = { x: origin_x, y: origin_y, z: origin_z };
+
+ if (changeBeforePass === 'undefined') {
+ if (origin_x === 0 && origin_y === 0 && origin_z === 0) {
+ origin = undefined;
+ } else {
+ if (origin_x === 0) {
+ origin_x = undefined;
+ }
+ if (origin_y === 0) {
+ origin_y = undefined;
+ }
+ if (origin_z === 0) {
+ origin_z = undefined;
+ }
+ origin = { x: origin_x, y: origin_y, z: origin_z };
+ }
+
+ if (mipLevel === 0) {
+ mipLevel = undefined;
+ }
+ }
+
+ if (changeBeforePass === 'arrays') {
+ origin = [origin_x, origin_y, origin_z];
+ }
+
+ return { texture, origin, mipLevel };
+ }
+
+ /**
+ * This is used for testing passing `GPUExtent3D` as `[number, number, number]` instead of
+ * `GPUExtent3DDict`. Passing arguments as values and not as objects so that they are passed by
+ * copy and not by reference.
+ */
+ arrayCopySizeIfNeeded(width, height, depth, changeBeforePass) {
+ if (changeBeforePass === 'arrays') {
+ return [width, height, depth];
+ } else {
+ return { width, height, depth };
+ }
+ }
+
+ /** Run a CopyT2B command with appropriate arguments corresponding to `ChangeBeforePass` */
+ copyTextureToBufferWithAppliedArguments(
+ buffer,
+ { offset, rowsPerImage, bytesPerRow },
+ { width, height, depth },
+ { texture, mipLevel, origin },
+ changeBeforePass
+ ) {
+ const { x, y, z } = origin;
+
+ const appliedCopyView = this.undefOrArrayCopyViewIfNeeded(
+ texture,
+ x,
+ y,
+ z,
+ mipLevel,
+ changeBeforePass
+ );
+
+ const appliedDataLayout = this.undefDataLayoutIfNeeded(
+ offset,
+ rowsPerImage,
+ bytesPerRow,
+ changeBeforePass
+ );
+
+ const appliedCheckSize = this.arrayCopySizeIfNeeded(width, height, depth, changeBeforePass);
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ appliedCopyView,
+ { buffer, ...appliedDataLayout },
+ appliedCheckSize
+ );
+
+ this.device.defaultQueue.submit([encoder.finish()]);
+ }
+
+ /** Put data into a part of the texture with an appropriate method. */
+ uploadLinearTextureDataToTextureSubBox(
+ textureCopyView,
+ textureDataLayout,
+ copySize,
+ partialData,
+ method,
+ changeBeforePass
+ ) {
+ const { texture, mipLevel, origin } = textureCopyView;
+ const { offset, rowsPerImage, bytesPerRow } = textureDataLayout;
+ const { x, y, z } = origin;
+ const { width, height, depth } = copySize;
+
+ const appliedCopyView = this.undefOrArrayCopyViewIfNeeded(
+ texture,
+ x,
+ y,
+ z,
+ mipLevel,
+ changeBeforePass
+ );
+
+ const appliedDataLayout = this.undefDataLayoutIfNeeded(
+ offset,
+ rowsPerImage,
+ bytesPerRow,
+ changeBeforePass
+ );
+
+ const appliedCopySize = this.arrayCopySizeIfNeeded(width, height, depth, changeBeforePass);
+
+ switch (method) {
+ case 'WriteTexture': {
+ this.device.defaultQueue.writeTexture(
+ appliedCopyView,
+ partialData,
+ appliedDataLayout,
+ appliedCopySize
+ );
+
+ break;
+ }
+ case 'CopyB2T': {
+ const buffer = this.device.createBuffer({
+ mappedAtCreation: true,
+ size: align(partialData.byteLength, 4),
+ usage: GPUBufferUsage.COPY_SRC,
+ });
+
+ new Uint8Array(buffer.getMappedRange()).set(partialData);
+ buffer.unmap();
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyBufferToTexture(
+ { buffer, ...appliedDataLayout },
+ appliedCopyView,
+ appliedCopySize
+ );
+
+ this.device.defaultQueue.submit([encoder.finish()]);
+
+ break;
+ }
+ default:
+ unreachable();
+ }
+ }
+
+ /**
+ * We check an appropriate part of the texture against the given data.
+ * Used directly with PartialCopyT2B check method (for a subpart of the texture)
+ * and by `copyWholeTextureToBufferAndCheckContentsWithUpdatedData` with FullCopyT2B check method
+ * (for the whole texture). We also ensure that CopyT2B doesn't overwrite bytes it's not supposed
+ * to if validateOtherBytesInBuffer is set to true.
+ */
+ copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin },
+ checkSize,
+ format,
+ expected,
+ expectedDataLayout,
+ changeBeforePass = 'none'
+ ) {
+ // The alignment is necessary because we need to copy and map data from this buffer.
+ const bufferSize = align(expected.byteLength, 4);
+ // The start value ensures generated data here doesn't match the expected data.
+ const bufferData = this.generateData(bufferSize, 17);
+
+ const buffer = this.device.createBuffer({
+ mappedAtCreation: true,
+ size: bufferSize,
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ new Uint8Array(buffer.getMappedRange()).set(bufferData);
+ buffer.unmap();
+
+ this.copyTextureToBufferWithAppliedArguments(
+ buffer,
+ expectedDataLayout,
+ checkSize,
+ { texture, mipLevel, origin },
+ changeBeforePass
+ );
+
+ this.updateLinearTextureDataSubBox(
+ expectedDataLayout,
+ expectedDataLayout,
+ checkSize,
+ origin,
+ format,
+ bufferData,
+ expected
+ );
+
+ this.expectContents(buffer, bufferData);
+ }
+
+ /**
+ * Copies the whole texture into linear data stored in a buffer for further checks.
+ *
+ * Used for `copyWholeTextureToBufferAndCheckContentsWithUpdatedData`.
+ */
+ copyWholeTextureToNewBuffer({ texture, mipLevel }, resultDataLayout) {
+ const { mipSize, byteLength, bytesPerRow, rowsPerImage } = resultDataLayout;
+ const buffer = this.device.createBuffer({
+ size: align(byteLength, 4), // this is necessary because we need to copy and map data from this buffer
+ usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
+ });
+
+ const encoder = this.device.createCommandEncoder();
+ encoder.copyTextureToBuffer(
+ { texture, mipLevel },
+ { buffer, bytesPerRow, rowsPerImage },
+ mipSize
+ );
+
+ this.device.defaultQueue.submit([encoder.finish()]);
+
+ return buffer;
+ }
+
+ copyFromArrayToArray(src, srcOffset, dst, dstOffset, size) {
+ dst.set(src.subarray(srcOffset, srcOffset + size), dstOffset);
+ }
+
+ /**
+ * Takes the data returned by `copyWholeTextureToNewBuffer` and updates it after a copy operation
+ * on the texture by emulating the copy behaviour here directly.
+ */
+ updateLinearTextureDataSubBox(
+ { bytesPerRow, rowsPerImage },
+ sourceDataLayout,
+ copySize,
+ origin,
+ format,
+ destination,
+ source
+ ) {
+ for (const texel of this.iterateBlockRows(copySize, origin, format)) {
+ const sourceOffset = this.getTexelOffsetInBytes(sourceDataLayout, format, texel, origin);
+ const destinationOffset = this.getTexelOffsetInBytes(
+ { bytesPerRow, rowsPerImage, offset: 0 },
+ format,
+ texel
+ );
+
+ const rowLength = this.bytesInACompleteRow(copySize.width, format);
+ this.copyFromArrayToArray(source, sourceOffset, destination, destinationOffset, rowLength);
+ }
+ }
+
+ /**
+ * Used for checking whether the whole texture was updated correctly by
+ * `uploadLinearTextureDataToTextureSubpart`. Takes fullData returned by
+ * `copyWholeTextureToNewBuffer` before the copy operation which is the original texture data,
+ * then updates it with `updateLinearTextureDataSubpart` and checks the texture against the
+ * updated data after the copy operation.
+ */
+ copyWholeTextureToBufferAndCheckContentsWithUpdatedData(
+ { texture, mipLevel, origin },
+ fullTextureCopyLayout,
+ texturePartialDataLayout,
+ copySize,
+ format,
+ fullData,
+ partialData
+ ) {
+ const { mipSize, bytesPerRow, rowsPerImage, byteLength } = fullTextureCopyLayout;
+ const { dst, begin, end } = this.createAlignedCopyForMapRead(fullData, byteLength, 0);
+
+ // We add an eventual async expectation which will update the full data and then add
+ // other eventual async expectations to ensure it will be correct.
+ this.eventualAsyncExpectation(async () => {
+ await dst.mapAsync(GPUMapMode.READ);
+ const actual = new Uint8Array(dst.getMappedRange()).subarray(begin, end);
+ this.updateLinearTextureDataSubBox(
+ fullTextureCopyLayout,
+ texturePartialDataLayout,
+ copySize,
+ origin,
+ format,
+ actual,
+ partialData
+ );
+
+ this.copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin: { x: 0, y: 0, z: 0 } },
+ { width: mipSize[0], height: mipSize[1], depth: mipSize[2] },
+ format,
+ actual,
+ { bytesPerRow, rowsPerImage, offset: 0 }
+ );
+
+ dst.destroy();
+ });
+ }
+
+ /**
+ * Tests copy between linear data and texture by creating a texture, putting some data into it
+ * with WriteTexture/CopyB2T, then getting data for the whole texture/for a part of it back and
+ * comparing it with the expectation.
+ */
+ uploadTextureAndVerifyCopy({
+ textureDataLayout,
+ copySize,
+ dataSize,
+ mipLevel = 0,
+ origin = { x: 0, y: 0, z: 0 },
+ textureSize,
+ format,
+ dimension = '2d',
+ initMethod,
+ checkMethod,
+ changeBeforePass = 'none',
+ }) {
+ const texture = this.device.createTexture({
+ size: textureSize,
+ format,
+ dimension,
+ mipLevelCount: mipLevel + 1,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const data = this.generateData(dataSize);
+
+ switch (checkMethod) {
+ case 'PartialCopyT2B': {
+ this.uploadLinearTextureDataToTextureSubBox(
+ { texture, mipLevel, origin },
+ textureDataLayout,
+ copySize,
+ data,
+ initMethod,
+ changeBeforePass
+ );
+
+ this.copyPartialTextureToBufferAndCheckContents(
+ { texture, mipLevel, origin },
+ copySize,
+ format,
+ data,
+ textureDataLayout,
+ changeBeforePass
+ );
+
+ break;
+ }
+ case 'FullCopyT2B': {
+ const fullTextureCopyLayout = getTextureCopyLayout(format, dimension, textureSize, {
+ mipLevel,
+ });
+
+ const fullData = this.copyWholeTextureToNewBuffer(
+ { texture, mipLevel },
+ fullTextureCopyLayout
+ );
+
+ this.uploadLinearTextureDataToTextureSubBox(
+ { texture, mipLevel, origin },
+ textureDataLayout,
+ copySize,
+ data,
+ initMethod,
+ changeBeforePass
+ );
+
+ this.copyWholeTextureToBufferAndCheckContentsWithUpdatedData(
+ { texture, mipLevel, origin },
+ fullTextureCopyLayout,
+ textureDataLayout,
+ copySize,
+ format,
+ fullData,
+ data
+ );
+
+ break;
+ }
+ default:
+ unreachable();
+ }
+ }
+}
+
+/**
+ * This is a helper function used for filtering test parameters
+ *
+ * TODO: Modify this after introducing tests with rendering.
+ */
+function formatCanBeTested({ format }) {
+ return kSizedTextureFormatInfo[format].copyDst && kSizedTextureFormatInfo[format].copySrc;
+}
+
+export const g = makeTestGroup(CopyBetweenLinearDataAndTextureTest);
+
+// Test that copying data with various bytesPerRow and rowsPerImage values and minimum required
+// bytes in copy works for every format.
+// Covers a special code path for Metal:
+// bufferSize - offset < bytesPerImage * copyExtent.depth
+// Covers a special code path for D3D12:
+// when bytesPerRow is not a multiple of 512 and copyExtent.depth > 1: copyExtent.depth % 2 == { 0, 1 }
+// bytesPerRow == bytesInACompleteCopyImage */
+g.test('copy_with_various_rows_per_image_and_bytes_per_row')
+ .params(
+ params()
+ .combine(kMethodsToTest)
+ .combine([
+ { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding
+ { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 6 }, // rowsPerImage padding
+ { bytesPerRowPadding: 6, rowsPerImagePaddingInBlocks: 0 }, // bytesPerRow padding
+ { bytesPerRowPadding: 15, rowsPerImagePaddingInBlocks: 17 }, // both paddings
+ ])
+ .combine([
+ // In the two cases below, for (WriteTexture, PartialCopyB2T) and (CopyB2T, FullCopyT2B)
+ // sets of methods we will have bytesPerRow = 256 and copyDepth % 2 == { 0, 1 }
+ // respectively. This covers a special code path for D3D12.
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5 }, // standard copy
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 2 }, // standard copy
+
+ { copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2 }, // copyWidth is 256-aligned
+ { copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5 }, // empty copy because of width
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5 }, // empty copy because of height
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0 }, // empty copy because of depth
+ { copyWidthInBlocks: 1, copyHeightInBlocks: 3, copyDepth: 5 }, // copyWidth = 1
+
+ // The two cases below cover another special code path for D3D12.
+ // - For (WriteTexture, FullCopyT2B) with r8unorm:
+ // bytesPerRow = 15 = 3 * 5 = bytesInACompleteCopyImage.
+ { copyWidthInBlocks: 32, copyHeightInBlocks: 1, copyDepth: 8 }, // copyHeight = 1
+ // - For (CopyB2T, FullCopyT2B) and (WriteTexture, PartialCopyT2B) with r8unorm:
+ // bytesPerRow = 256 = 8 * 32 = bytesInACompleteCopyImage.
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1 }, // copyDepth = 1
+
+ { copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1 }, // copyHeight = 1 and copyDepth = 1
+ ])
+ .combine(poptions('format', kSizedTextureFormats))
+ .filter(formatCanBeTested)
+ )
+ .fn(async t => {
+ const {
+ bytesPerRowPadding,
+ rowsPerImagePaddingInBlocks,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ format,
+ initMethod,
+ checkMethod,
+ } = t.params;
+
+ const info = kSizedTextureFormatInfo[format];
+
+ // For CopyB2T and CopyT2B we need to have bytesPerRow 256-aligned,
+ // to make this happen we align the bytesInACompleteRow value and multiply
+ // bytesPerRowPadding by 256.
+ const bytesPerRowAlignment =
+ initMethod === 'WriteTexture' && checkMethod === 'FullCopyT2B' ? 1 : 256;
+
+ const copyWidth = copyWidthInBlocks * info.blockWidth;
+ const copyHeight = copyHeightInBlocks * info.blockHeight;
+ const rowsPerImage = copyHeight + rowsPerImagePaddingInBlocks * info.blockHeight;
+ const bytesPerRow =
+ align(t.bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
+ bytesPerRowPadding * bytesPerRowAlignment;
+ const copySize = { width: copyWidth, height: copyHeight, depth: copyDepth };
+
+ const minDataSize = t.requiredBytesInCopy(
+ { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize
+ );
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize: minDataSize,
+ textureSize: [
+ Math.max(copyWidth, info.blockWidth),
+ Math.max(copyHeight, info.blockHeight),
+ Math.max(copyDepth, 1),
+ ],
+ /* making sure the texture is non-empty */ format,
+ initMethod,
+ checkMethod,
+ });
+ });
+
+// Test that copying data with various offset values and additional data paddings
+// works for every format with 2d and 2d-array textures.
+// Covers two special code paths for D3D12:
+// offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow
+// offset > bytesInACompleteCopyImage
+g.test('copy_with_various_offsets_and_data_sizes')
+ .params(
+ params()
+ .combine(kMethodsToTest)
+ .combine([
+ { offsetInBlocks: 0, dataPaddingInBytes: 0 }, // no offset and no padding
+ { offsetInBlocks: 1, dataPaddingInBytes: 0 }, // offset = 1
+ { offsetInBlocks: 2, dataPaddingInBytes: 0 }, // offset = 2
+ { offsetInBlocks: 15, dataPaddingInBytes: 0 }, // offset = 15
+ { offsetInBlocks: 16, dataPaddingInBytes: 0 }, // offset = 16
+ { offsetInBlocks: 242, dataPaddingInBytes: 0 }, // for rgba8unorm format: offset + bytesInCopyExtentPerRow = 242 + 12 = 256 = bytesPerRow
+ { offsetInBlocks: 243, dataPaddingInBytes: 0 }, // for rgba8unorm format: offset + bytesInCopyExtentPerRow = 243 + 12 > 256 = bytesPerRow
+ { offsetInBlocks: 768, dataPaddingInBytes: 0 }, // for copyDepth = 1, blockWidth = 1 and bytesPerBlock = 1: offset = 768 = 3 * 256 = bytesInACompleteCopyImage
+ { offsetInBlocks: 769, dataPaddingInBytes: 0 }, // for copyDepth = 1, blockWidth = 1 and bytesPerBlock = 1: offset = 769 > 768 = bytesInACompleteCopyImage
+ { offsetInBlocks: 0, dataPaddingInBytes: 1 }, // dataPaddingInBytes > 0
+ { offsetInBlocks: 1, dataPaddingInBytes: 8 }, // offset > 0 and dataPaddingInBytes > 0
+ ])
+ .combine(poptions('copyDepth', [1, 2])) // 2d and 2d-array textures
+ .combine(poptions('format', kSizedTextureFormats))
+ .filter(formatCanBeTested)
+ )
+ .fn(async t => {
+ const {
+ offsetInBlocks,
+ dataPaddingInBytes,
+ copyDepth,
+ format,
+ initMethod,
+ checkMethod,
+ } = t.params;
+
+ const info = kSizedTextureFormatInfo[format];
+
+ const offset = offsetInBlocks * info.bytesPerBlock;
+ const copySize = {
+ width: 3 * info.blockWidth,
+ height: 3 * info.blockHeight,
+ depth: copyDepth,
+ };
+
+ const rowsPerImage = copySize.height;
+ const bytesPerRow = 256;
+
+ const dataSize =
+ offset +
+ t.requiredBytesInCopy({ offset, bytesPerRow, rowsPerImage }, format, copySize) +
+ dataPaddingInBytes;
+
+ // We're copying a (3 x 3 x copyDepth) (in texel blocks) part of a (4 x 4 x copyDepth)
+ // (in texel blocks) texture with no origin.
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ textureSize: [4 * info.blockWidth, 4 * info.blockHeight, copyDepth],
+ format,
+ initMethod,
+ checkMethod,
+ });
+ });
+
+// Test that copying slices of a texture works with various origin and copyExtent values
+// for all formats. We pass origin and copyExtent as [number, number, number].
+g.test('copy_with_various_origins_and_copy_extents')
+ .params(
+ params()
+ .combine(kMethodsToTest)
+ .combine(poptions('originValueInBlocks', [0, 7, 8]))
+ .combine(poptions('copySizeValueInBlocks', [0, 7, 8]))
+ .combine(poptions('textureSizePaddingValueInBlocks', [0, 7, 8]))
+ .unless(
+ p =>
+ // we can't create an empty texture
+ p.copySizeValueInBlocks + p.originValueInBlocks + p.textureSizePaddingValueInBlocks === 0
+ )
+ .combine(poptions('coordinateToTest', ['width', 'height', 'depth']))
+ .combine(poptions('format', kSizedTextureFormats))
+ .filter(formatCanBeTested)
+ )
+ .fn(async t => {
+ const {
+ coordinateToTest,
+ originValueInBlocks,
+ copySizeValueInBlocks,
+ textureSizePaddingValueInBlocks,
+ format,
+ initMethod,
+ checkMethod,
+ } = t.params;
+
+ const info = kSizedTextureFormatInfo[format];
+
+ const origin = { x: info.blockWidth, y: info.blockHeight, z: 1 };
+ const copySize = { width: 2 * info.blockWidth, height: 2 * info.blockHeight, depth: 2 };
+ const textureSize = [3 * info.blockWidth, 3 * info.blockHeight, 3];
+
+ switch (coordinateToTest) {
+ case 'width': {
+ origin.x = originValueInBlocks * info.blockWidth;
+ copySize.width = copySizeValueInBlocks * info.blockWidth;
+ textureSize[0] =
+ origin.x + copySize.width + textureSizePaddingValueInBlocks * info.blockWidth;
+ break;
+ }
+ case 'height': {
+ origin.y = originValueInBlocks * info.blockHeight;
+ copySize.height = copySizeValueInBlocks * info.blockHeight;
+ textureSize[1] =
+ origin.y + copySize.height + textureSizePaddingValueInBlocks * info.blockHeight;
+ break;
+ }
+ case 'depth': {
+ origin.z = originValueInBlocks;
+ copySize.depth = copySizeValueInBlocks;
+ textureSize[2] = origin.z + copySize.depth + textureSizePaddingValueInBlocks;
+ break;
+ }
+ }
+
+ const rowsPerImage = copySize.height;
+ const bytesPerRow = align(copySize.width, 256);
+ const dataSize = t.requiredBytesInCopy(
+ { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize
+ );
+
+ // For testing width: we copy a (_ x 2 x 2) (in texel blocks) part of a (_ x 3 x 3)
+ // (in texel blocks) texture with origin (_, 1, 1) (in texel blocks).
+ // Similarly for other coordinates.
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ origin,
+ textureSize,
+ format,
+ initMethod,
+ checkMethod,
+ changeBeforePass: 'arrays',
+ });
+ });
+
+/**
+ * Generates textureSizes which correspond to the same physicalSizeAtMipLevel including virtual
+ * sizes at mip level different from the physical ones.
+ */
+function* textureSizeExpander({ format, mipLevel, _texturePhysicalSizeAtMipLevelInBlocks }) {
+ const info = kSizedTextureFormatInfo[format];
+
+ const widthAtThisLevel = _texturePhysicalSizeAtMipLevelInBlocks.width * info.blockWidth;
+ const heightAtThisLevel = _texturePhysicalSizeAtMipLevelInBlocks.height * info.blockHeight;
+ const textureSize = [
+ widthAtThisLevel << mipLevel,
+ heightAtThisLevel << mipLevel,
+ _texturePhysicalSizeAtMipLevelInBlocks.depth,
+ ];
+
+ yield {
+ textureSize,
+ };
+
+ // We choose width and height of the texture so that the values are divisible by blockWidth and
+ // blockHeight respectively and so that the virtual size at mip level corresponds to the same
+ // physical size.
+ // Virtual size at mip level with modified width has width = (physical size width) - (blockWidth / 2).
+ // Virtual size at mip level with modified height has height = (physical size height) - (blockHeight / 2).
+ const widthAtPrevLevel = widthAtThisLevel << 1;
+ const heightAtPrevLevel = heightAtThisLevel << 1;
+ assert(mipLevel > 0);
+ assert(widthAtPrevLevel >= info.blockWidth && heightAtPrevLevel >= info.blockHeight);
+ const modifiedWidth = (widthAtPrevLevel - info.blockWidth) << (mipLevel - 1);
+ const modifiedHeight = (heightAtPrevLevel - info.blockHeight) << (mipLevel - 1);
+
+ const modifyWidth = info.blockWidth > 1 && modifiedWidth !== textureSize[0];
+ const modifyHeight = info.blockHeight > 1 && modifiedHeight !== textureSize[1];
+
+ if (modifyWidth) {
+ yield {
+ textureSize: [modifiedWidth, textureSize[1], textureSize[2]],
+ };
+ }
+ if (modifyHeight) {
+ yield {
+ textureSize: [textureSize[0], modifiedHeight, textureSize[2]],
+ };
+ }
+ if (modifyWidth && modifyHeight) {
+ yield {
+ textureSize: [modifiedWidth, modifiedHeight, textureSize[2]],
+ };
+ }
+}
+
+// Test that copying various mip levels works.
+// Covers two special code paths:
+// - the physical size of the subresouce is not equal to the logical size
+// - bufferSize - offset < bytesPerImage * copyExtent.depth and copyExtent needs to be clamped for all block formats */
+g.test('copy_various_mip_levels')
+ .params(
+ params()
+ .combine(kMethodsToTest)
+ .combine([
+ // origin + copySize = texturePhysicalSizeAtMipLevel for all coordinates, 2d texture */
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 1 },
+ originInBlocks: { x: 3, y: 2, z: 0 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 8, height: 6, depth: 1 },
+ mipLevel: 1,
+ },
+
+ // origin + copySize = texturePhysicalSizeAtMipLevel for all coordinates, 2d-array texture
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 8, height: 6, depth: 3 },
+ mipLevel: 2,
+ },
+
+ // origin.x + copySize.width = texturePhysicalSizeAtMipLevel.width
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 8, height: 7, depth: 4 },
+ mipLevel: 3,
+ },
+
+ // origin.y + copySize.height = texturePhysicalSizeAtMipLevel.height
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 9, height: 6, depth: 4 },
+ mipLevel: 4,
+ },
+
+ // origin.z + copySize.depth = texturePhysicalSizeAtMipLevel.depth
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 9, height: 7, depth: 3 },
+ mipLevel: 5,
+ },
+
+ // origin + copySize < texturePhysicalSizeAtMipLevel for all coordinates
+ {
+ copySizeInBlocks: { width: 5, height: 4, depth: 2 },
+ originInBlocks: { x: 3, y: 2, z: 1 },
+ _texturePhysicalSizeAtMipLevelInBlocks: { width: 9, height: 7, depth: 4 },
+ mipLevel: 6,
+ },
+ ])
+ .combine(poptions('format', kSizedTextureFormats))
+ .filter(formatCanBeTested)
+ .expand(textureSizeExpander)
+ )
+ .fn(async t => {
+ const {
+ copySizeInBlocks,
+ originInBlocks,
+ textureSize,
+ mipLevel,
+ format,
+ initMethod,
+ checkMethod,
+ } = t.params;
+
+ const info = kSizedTextureFormatInfo[format];
+
+ const origin = {
+ x: originInBlocks.x * info.blockWidth,
+ y: originInBlocks.y * info.blockHeight,
+ z: originInBlocks.z,
+ };
+
+ const copySize = {
+ width: copySizeInBlocks.width * info.blockWidth,
+ height: copySizeInBlocks.height * info.blockHeight,
+ depth: copySizeInBlocks.depth,
+ };
+
+ const rowsPerImage = copySize.height + info.blockHeight;
+ const bytesPerRow = align(copySize.width, 256);
+ const dataSize = t.requiredBytesInCopy(
+ { offset: 0, bytesPerRow, rowsPerImage },
+ format,
+ copySize
+ );
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize,
+ origin,
+ mipLevel,
+ textureSize,
+ format,
+ initMethod,
+ checkMethod,
+ });
+ });
+
+// Test that when copying a single row we can set any bytesPerRow value and when copying a single
+// slice we can set rowsPerImage to 0. Origin, offset, mipLevel and rowsPerImage values will be set
+// to undefined when appropriate.
+g.test('copy_with_no_image_or_slice_padding_and_undefined_values')
+ .params(
+ params()
+ .combine(kMethodsToTest)
+ .combine([
+ // copying one row: bytesPerRow and rowsPerImage can be set to 0
+ {
+ bytesPerRow: 0,
+ rowsPerImage: 0,
+ copySize: { width: 3, height: 1, depth: 1 },
+ origin: { x: 0, y: 0, z: 0 },
+ },
+
+ // copying one row: bytesPerRow can be < bytesInACompleteRow = 400
+ {
+ bytesPerRow: 256,
+ rowsPerImage: 0,
+ copySize: { width: 100, height: 1, depth: 1 },
+ origin: { x: 0, y: 0, z: 0 },
+ },
+
+ // copying one slice: rowsPerImage = 0 will be set to undefined
+ {
+ bytesPerRow: 256,
+ rowsPerImage: 0,
+ copySize: { width: 3, height: 3, depth: 1 },
+ origin: { x: 0, y: 0, z: 0 },
+ },
+
+ // copying one slice: rowsPerImage = 2 is valid
+ {
+ bytesPerRow: 256,
+ rowsPerImage: 2,
+ copySize: { width: 3, height: 3, depth: 1 },
+ origin: { x: 0, y: 0, z: 0 },
+ },
+
+ // origin.x = 0 will be set to undefined
+ {
+ bytesPerRow: 0,
+ rowsPerImage: 0,
+ copySize: { width: 1, height: 1, depth: 1 },
+ origin: { x: 0, y: 1, z: 1 },
+ },
+
+ // origin.y = 0 will be set to undefined
+ {
+ bytesPerRow: 0,
+ rowsPerImage: 0,
+ copySize: { width: 1, height: 1, depth: 1 },
+ origin: { x: 1, y: 0, z: 1 },
+ },
+
+ // origin.z = 0 will be set to undefined
+ {
+ bytesPerRow: 0,
+ rowsPerImage: 0,
+ copySize: { width: 1, height: 1, depth: 1 },
+ origin: { x: 1, y: 1, z: 0 },
+ },
+ ])
+ )
+ .fn(async t => {
+ const { bytesPerRow, rowsPerImage, copySize, origin, initMethod, checkMethod } = t.params;
+
+ t.uploadTextureAndVerifyCopy({
+ textureDataLayout: { offset: 0, bytesPerRow, rowsPerImage },
+ copySize,
+ dataSize: 100 * 3 * 4,
+ textureSize: [100, 3, 2],
+ origin,
+ format: 'rgba8unorm',
+ initMethod,
+ checkMethod,
+ changeBeforePass: 'undefined',
+ });
+ });
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js b/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js
index 12efa65ce2d..b036245663d 100644
--- a/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/resource_usages/textureUsageInRender.spec.js
@@ -21,13 +21,22 @@ Test Coverage:
- Test combinations of two shader stages:
- Texture usages in bindings with invisible shader stages should be tracked. Invisible shader
stages include shader stage with visibility none and compute shader stage in render pass.
+
+ - Tests replaced bindings:
+ - Texture usages via bindings replaced by another setBindGroup() upon the same bindGroup index
+ in current scope should be tracked.
+
+ - Test texture usages in bundle:
+ - Texture usages in bundle should be tracked if that bundle is executed in the current scope.
`;
-import { poptions, params } from '../../../../common/framework/params_builder.js';
+import { pbool, poptions, params } from '../../../../common/framework/params_builder.js';
import { makeTestGroup } from '../../../../common/framework/test_group.js';
import {
- kShaderStages,
kDepthStencilFormats,
kDepthStencilFormatInfo,
+ kTextureBindingTypes,
+ kTextureBindingTypeInfo,
+ kShaderStages,
} from '../../../capability_info.js';
import { ValidationTest } from '../validation_test.js';
@@ -52,6 +61,22 @@ class TextureUsageTracking extends ValidationTest {
usage,
});
}
+
+ createBindGroup(index, view, bindingType, bindingTexFormat) {
+ return this.device.createBindGroup({
+ entries: [{ binding: index, resource: view }],
+ layout: this.device.createBindGroupLayout({
+ entries: [
+ {
+ binding: index,
+ visibility: GPUShaderStage.FRAGMENT,
+ type: bindingType,
+ storageTextureFormat: bindingTexFormat,
+ },
+ ],
+ }),
+ });
+ }
}
export const g = makeTestGroup(TextureUsageTracking);
@@ -400,3 +425,158 @@ g.test('shader_stages_and_visibility')
encoder.finish();
});
});
+
+// We should track the texture usages in bindings which are replaced by another setBindGroup()
+// call site upon the same index in the same render pass.
+g.test('replaced_binding')
+ .params(poptions('bindingType', kTextureBindingTypes))
+ .fn(async t => {
+ const { bindingType } = t.params;
+ const info = kTextureBindingTypeInfo[bindingType];
+ const bindingTexFormat = info.resource === 'storageTex' ? 'rgba8unorm' : undefined;
+
+ const sampledView = t.createTexture().createView();
+ const sampledStorageView = t
+ .createTexture({ usage: GPUTextureUsage.STORAGE | GPUTextureUsage.SAMPLED })
+ .createView();
+
+ // Create bindGroup0. It has two bindings. These two bindings use different views/subresources.
+ const bglEntries0 = [
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' },
+ {
+ binding: 1,
+ visibility: GPUShaderStage.FRAGMENT,
+ type: bindingType,
+ storageTextureFormat: bindingTexFormat,
+ },
+ ];
+
+ const bgEntries0 = [
+ { binding: 0, resource: sampledView },
+ { binding: 1, resource: sampledStorageView },
+ ];
+
+ const bindGroup0 = t.device.createBindGroup({
+ entries: bgEntries0,
+ layout: t.device.createBindGroupLayout({ entries: bglEntries0 }),
+ });
+
+ // Create bindGroup1. It has one binding, which use the same view/subresoure of a binding in
+ // bindGroup0. So it may or may not conflicts with that binding in bindGroup0.
+ const bindGroup1 = t.createBindGroup(0, sampledStorageView, 'sampled-texture', undefined);
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ attachment: t.createTexture().createView(),
+ loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+
+ // Set bindGroup0 and bindGroup1. bindGroup0 is replaced by bindGroup1 in the current pass.
+ // But bindings in bindGroup0 should be tracked too.
+ pass.setBindGroup(0, bindGroup0);
+ pass.setBindGroup(0, bindGroup1);
+ pass.endPass();
+
+ const success = bindingType === 'writeonly-storage-texture' ? false : true;
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ });
+
+g.test('bindings_in_bundle')
+ .params(
+ params()
+ .combine(pbool('binding0InBundle'))
+ .combine(pbool('binding1InBundle'))
+ .combine(poptions('type0', ['render-target', ...kTextureBindingTypes]))
+ .combine(poptions('type1', ['render-target', ...kTextureBindingTypes]))
+ .unless(
+ ({ binding0InBundle, binding1InBundle, type0, type1 }) =>
+ // We can't set 'render-target' in bundle, so we need to exclude it from bundle.
+ // In addition, if both bindings are non-bundle, there is no need to test it because
+ // we have far more comprehensive test cases for that situation in this file.
+ (binding0InBundle && type0 === 'render-target') ||
+ (binding1InBundle && type1 === 'render-target') ||
+ (!binding0InBundle && !binding1InBundle)
+ )
+ )
+ .fn(async t => {
+ const { binding0InBundle, binding1InBundle, type0, type1 } = t.params;
+
+ // Two bindings are attached to the same texture view.
+ const view = t
+ .createTexture({
+ usage:
+ GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.STORAGE | GPUTextureUsage.SAMPLED,
+ })
+ .createView();
+
+ const bindGroups = [];
+ if (type0 !== 'render-target') {
+ const binding0TexFormat = type0 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ bindGroups[0] = t.createBindGroup(0, view, type0, binding0TexFormat);
+ }
+ if (type1 !== 'render-target') {
+ const binding1TexFormat = type1 === 'sampled-texture' ? undefined : 'rgba8unorm';
+ bindGroups[1] = t.createBindGroup(1, view, type1, binding1TexFormat);
+ }
+
+ const encoder = t.device.createCommandEncoder();
+ const pass = encoder.beginRenderPass({
+ colorAttachments: [
+ {
+ attachment:
+ // At least one binding is in bundle, which means that its type is not 'render-target'.
+ // As a result, only one binding's type is 'render-target' at most.
+ type0 === 'render-target' || type1 === 'render-target'
+ ? view
+ : t.createTexture().createView(),
+ loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 },
+ storeOp: 'store',
+ },
+ ],
+ });
+
+ const bindingsInBundle = [binding0InBundle, binding1InBundle];
+ for (let i = 0; i < 2; i++) {
+ // Create a bundle for each bind group if its bindings is required to be in bundle on purpose.
+ // Otherwise, call setBindGroup directly in pass if needed (when its binding is not
+ // 'render-target').
+ if (bindingsInBundle[i]) {
+ const bundleEncoder = t.device.createRenderBundleEncoder({
+ colorFormats: ['rgba8unorm'],
+ });
+
+ bundleEncoder.setBindGroup(i, bindGroups[i]);
+ const bundleInPass = bundleEncoder.finish();
+ pass.executeBundles([bundleInPass]);
+ } else if (bindGroups[i] !== undefined) {
+ pass.setBindGroup(i, bindGroups[i]);
+ }
+ }
+
+ pass.endPass();
+
+ let success = false;
+ if (
+ (type0 === 'sampled-texture' || type0 === 'readonly-storage-texture') &&
+ (type1 === 'sampled-texture' || type1 === 'readonly-storage-texture')
+ ) {
+ success = true;
+ }
+
+ if (type0 === 'writeonly-storage-texture' && type1 === 'writeonly-storage-texture') {
+ success = true;
+ }
+
+ // Resource usages in bundle should be tracked. And validation error should be reported
+ // if needed.
+ t.expectValidationError(() => {
+ encoder.finish();
+ }, !success);
+ });
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/setScissorRect.spec.js b/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/setScissorRect.spec.js
index 7934c007286..c049e92e70e 100644
--- a/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/setScissorRect.spec.js
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/api/validation/setScissorRect.spec.js
@@ -38,7 +38,7 @@ g.test('use_of_setScissorRect')
{ x: 0, y: 0, width: 0, height: 1, _success: false }, // Width of zero is not allowed
{ x: 0, y: 0, width: 1, height: 0, _success: false }, // Height of zero is not allowed
{ x: 0, y: 0, width: 0, height: 0, _success: false }, // Both width and height of zero are not allowed
- { x: 0, y: 0, width: TEXTURE_WIDTH + 1, height: TEXTURE_HEIGHT + 1, _success: true }, // Scissor larger than the framebuffer is allowed
+ { x: 0, y: 0, width: TEXTURE_WIDTH + 1, height: TEXTURE_HEIGHT + 1, _success: false }, // Scissor larger than the framebuffer is not allowed
])
.fn(async t => {
const { x, y, width, height, _success } = t.params;
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js b/tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js
index f350131af3b..21cb10f1429 100644
--- a/tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/gpu_test.js
@@ -18,6 +18,7 @@ import { DevicePool, TestOOMedShouldAttemptGC } from '../common/framework/gpu/de
import { attemptGarbageCollection } from '../common/framework/util/collect_garbage.js';
import { assert } from '../common/framework/util/util.js';
+import { align } from './util/math.js';
import { fillTextureDataWithTexelValue, getTextureCopyLayout } from './util/texture/layout.js';
import { getTexelDataRepresentation } from './util/texture/texelData.js';
@@ -76,6 +77,9 @@ export class GPUTest extends Fixture {
}
createCopyForMapRead(src, srcOffset, size) {
+ assert(srcOffset % 4 === 0);
+ assert(size % 4 === 0);
+
const dst = this.device.createBuffer({
size,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
@@ -91,14 +95,31 @@ export class GPUTest extends Fixture {
// TODO: add an expectContents for textures, which logs data: uris on failure
+ // Offset and size passed to createCopyForMapRead must be divisible by 4. For that
+ // we might need to copy more bytes from the buffer than we want to map.
+ // begin and end values represent the part of the copied buffer that stores the contents
+ // we initially wanted to map.
+ // The copy will not cause an OOB error because the buffer size must be 4-aligned.
+ createAlignedCopyForMapRead(src, size, offset) {
+ const alignedOffset = Math.floor(offset / 4) * 4;
+ const offsetDifference = offset - alignedOffset;
+ const alignedSize = align(size + offsetDifference, 4);
+ const dst = this.createCopyForMapRead(src, alignedOffset, alignedSize);
+ return { dst, begin: offsetDifference, end: offsetDifference + size };
+ }
+
expectContents(src, expected, srcOffset = 0) {
- const dst = this.createCopyForMapRead(src, srcOffset, expected.buffer.byteLength);
+ const { dst, begin, end } = this.createAlignedCopyForMapRead(
+ src,
+ expected.byteLength,
+ srcOffset
+ );
this.eventualAsyncExpectation(async niceStack => {
const constructor = expected.constructor;
await dst.mapAsync(GPUMapMode.READ);
const actual = new constructor(dst.getMappedRange());
- const check = this.checkBuffer(actual, expected);
+ const check = this.checkBuffer(actual.subarray(begin, end), expected);
if (check !== undefined) {
niceStack.message = check;
this.rec.expectationFailed(niceStack);
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/listing.js b/tests/wpt/webgpu/tests/webgpu/webgpu/listing.js
index 3134cf0dd56..cbb23b30ec8 100644
--- a/tests/wpt/webgpu/tests/webgpu/webgpu/listing.js
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/listing.js
@@ -85,6 +85,14 @@ export const listing = [
"file": [
"api",
"operation",
+ "copyBetweenLinearDataAndTexture"
+ ],
+ "description": "writeTexture + copyBufferToTexture + copyTextureToBuffer operation tests.\n\n* copy_with_various_rows_per_image_and_bytes_per_row: test that copying data with various bytesPerRow (including { ==, > } bytesInACompleteRow) and rowsPerImage (including { ==, > } copyExtent.height) values and minimum required bytes in copy works for every format. Also covers special code paths:\n - bufferSize - offset < bytesPerImage * copyExtent.depth\n - when bytesPerRow is not a multiple of 512 and copyExtent.depth > 1: copyExtent.depth % 2 == { 0, 1 }\n - bytesPerRow == bytesInACompleteCopyImage\n\n* copy_with_various_offsets_and_data_sizes: test that copying data with various offset (including { ==, > } 0 and is/isn't power of 2) values and additional data paddings works for every format with 2d and 2d-array textures. Also covers special code paths:\n - offset + bytesInCopyExtentPerRow { ==, > } bytesPerRow\n - offset > bytesInACompleteCopyImage\n\n* copy_with_various_origins_and_copy_extents: test that copying slices of a texture works with various origin (including { origin.x, origin.y, origin.z } { ==, > } 0 and is/isn't power of 2) and copyExtent (including { copyExtent.x, copyExtent.y, copyExtent.z } { ==, > } 0 and is/isn't power of 2) values (also including {origin._ + copyExtent._ { ==, < } the subresource size of textureCopyView) works for all formats. origin and copyExtent values are passed as [number, number, number] instead of GPUExtent3DDict.\n\n* copy_various_mip_levels: test that copying various mip levels works for all formats. Also covers special code paths:\n - the physical size of the subresouce is not equal to the logical size\n - bufferSize - offset < bytesPerImage * copyExtent.depth and copyExtent needs to be clamped\n\n* copy_with_no_image_or_slice_padding_and_undefined_values: test that when copying a single row we can set any bytesPerRow value and when copying a single slice we can set rowsPerImage to 0. Also test setting offset, rowsPerImage, mipLevel, origin, origin.{x,y,z} to undefined.\n\n* TODO:\n - add another initMethod which renders the texture\n - because of expectContests 4-bytes alignment we don't test CopyT2B with buffer size not divisible by 4\n - add tests for 1d / 3d textures"
+ },
+ {
+ "file": [
+ "api",
+ "operation",
"fences"
],
"description": ""
@@ -252,7 +260,7 @@ export const listing = [
"resource_usages",
"textureUsageInRender"
],
- "description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n\n - For each combination of two texture usages:\n - For various subresource ranges (different mip levels or array layers) that overlap a given\n subresources or not for color formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same texture subresource. Otherwise, no error should be generated. One exception is race\n condition upon two writeonly-storage-texture usages, which is valid.\n\n - For each combination of two texture usages:\n - For various aspects (all, depth-only, stencil-only) that overlap a given subresources or not\n for depth/stencil formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same aspect. Otherwise, no error should be generated.\n\n - Test combinations of two shader stages:\n - Texture usages in bindings with invisible shader stages should be tracked. Invisible shader\n stages include shader stage with visibility none and compute shader stage in render pass."
+ "description": "Texture Usages Validation Tests in Render Pass.\n\nTest Coverage:\n\n - For each combination of two texture usages:\n - For various subresource ranges (different mip levels or array layers) that overlap a given\n subresources or not for color formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same texture subresource. Otherwise, no error should be generated. One exception is race\n condition upon two writeonly-storage-texture usages, which is valid.\n\n - For each combination of two texture usages:\n - For various aspects (all, depth-only, stencil-only) that overlap a given subresources or not\n for depth/stencil formats:\n - Check that an error is generated when read-write or write-write usages are binding to the\n same aspect. Otherwise, no error should be generated.\n\n - Test combinations of two shader stages:\n - Texture usages in bindings with invisible shader stages should be tracked. Invisible shader\n stages include shader stage with visibility none and compute shader stage in render pass.\n\n - Tests replaced bindings:\n - Texture usages via bindings replaced by another setBindGroup() upon the same bindGroup index\n in current scope should be tracked.\n\n - Test texture usages in bundle:\n - Texture usages in bundle should be tracked if that bundle is executed in the current scope."
},
{
"file": [
diff --git a/tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js b/tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js
index c3c610bf0d9..927798985fc 100644
--- a/tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js
+++ b/tests/wpt/webgpu/tests/webgpu/webgpu/util/texture/layout.js
@@ -32,7 +32,10 @@ export function getTextureCopyLayout(format, dimension, size, options = kDefault
const { blockWidth, blockHeight, bytesPerBlock } = kSizedTextureFormatInfo[format];
- assert(isAligned(mipSize[0], blockWidth));
+ // We align mipSize to be the physical size of the texture subresource.
+ mipSize[0] = align(mipSize[0], blockWidth);
+ mipSize[1] = align(mipSize[1], blockHeight);
+
const minBytesPerRow = (mipSize[0] / blockWidth) * bytesPerBlock;
const alignedMinBytesPerRow = align(minBytesPerRow, kBytesPerRowAlignment);
if (bytesPerRow !== undefined) {