Reland "Apply auto expiry mechanism to GPUExternalTexture"
This reland commit ad2a6bfd37ef8a2d8c6eeea447a8ace302fb99cd.
Fix the test case issues.
Original change's description:
> Revert "Apply auto expiry mechanism to GPUExternalTexture"
>
> This reverts commit ad2a6bfd37ef8a2d8c6eeea447a8ace302fb99cd.
> Reason for revert: breaks WebCodecs_GPUExternalTexture_*
> https://luci-milo.appspot.com/ui/p/chromium/builders/ci/Win10%20FYI%20x64%20Release%20(Intel)/956/overview
>> Original change's description:
>> Apply auto expiry mechanism to GPUExternalTexture
>>
>> WebGPU spec updated GPUExternalTexture expire mechanism. It removes
>> GPUExternalTexture.expire and uses auto expiry mechanism.
>>
>> The basic rules are:
>> - ImportExternalTexture should return same object if underly
>> resource is the same.
>> - GPUExternalTexture should be expire if current task scope finished.
>> And could be refreshed by importExternalTexture in new task scope.
>>
>> This CL adds auto expiry mechanism and a cache to address these requirements.
>>
>> Bug: chromium:1412338
>> Change-Id: I6ca72d2ea9a013d7729072cb76cb412ecf3a1e86
>> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4258147
>> Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
>> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
>> Cr-Commit-Position: refs/heads/main@{#1112071}
>
> Bug: chromium:1412338
> Change-Id: I1a9c0b6a2903c6b1e11e2532e4f3ad81081603c8
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4303861
> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
> Commit-Queue: Yuly Novikov <ynovikov@chromium.org>
> Reviewed-by: Corentin Wallez <cwallez@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1112220}
Bug:chromium:1412338
Change-Id: Ifb8ee07894880b249559999599dc386170760c98
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4310302
Commit-Queue: Shaobo Yan <shaobo.yan@intel.com>
Reviewed-by: Brandon Jones <bajones@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1114424}
diff --git a/content/test/content_unittests_bundle_data.filelist b/content/test/content_unittests_bundle_data.filelist
index 1ce2926..06ffe59 100644
--- a/content/test/content_unittests_bundle_data.filelist
+++ b/content/test/content_unittests_bundle_data.filelist
@@ -792,8 +792,6 @@
data/gpu/webcodecs/encode-decode.html
data/gpu/webcodecs/encode.html
data/gpu/webcodecs/encoding-modes.html
-data/gpu/webcodecs/gpu-device-destroy-expire-active-external-texture.html
-data/gpu/webcodecs/gpu-external-texture-expired.html
data/gpu/webcodecs/svc.html
data/gpu/webcodecs/tex-image-2d.html
data/gpu/webcodecs/webcodecs_common.js
diff --git a/content/test/data/gpu/webcodecs/gpu-device-destroy-expire-active-external-texture.html b/content/test/data/gpu/webcodecs/gpu-device-destroy-expire-active-external-texture.html
deleted file mode 100644
index da6d810..0000000
--- a/content/test/data/gpu/webcodecs/gpu-device-destroy-expire-active-external-texture.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE html>
-<!--
-Take frames coming from various sources and render them to a canvas with
-WebGLRenderingContext.texImage2D().
--->
-<html>
-
-<head>
- <title>GPUDevice.destroy() expires GPUExternalTexture test</title>
- <script src="webcodecs_common.js"></script>
- <script type="text/javascript">
- 'use strict';
- async function main(arg) {
- const device_destroyed_before_import = arg.device_destroyed_before_import;
- let source_type = arg.source_type;
- let canvas = document.getElementById('display');
- let source =
- await createFrameSource(source_type, canvas.width, canvas.height);
- if (!source) {
- TEST.skip('Unsupported source: ' + source_type);
- return;
- }
-
- const adapter = navigator.gpu && await navigator.gpu.requestAdapter();
- if (!adapter) {
- TEST.skip('navigator.gpu && navigator.gpu.requestAdapter failed');
- return;
- }
-
- const device = await adapter.requestDevice();
- if (!device) {
- TEST.skip('adapter.requestDevice() failed');
- return;
- }
-
- let frame = await source.getNextFrame();
- if (device_destroyed_before_import) {
- device.destroy();
- const gpu_external_texture = device.importExternalTexture({source: frame});
- TEST.assert(gpu_external_texture.expired == true, "GPUExternalTexture should be expired");
- } else {
- const gpu_external_texture = device.importExternalTexture({source: frame});
- TEST.assert(gpu_external_texture.expired == false, "GPUExternalTexture should be active");
- device.destroy();
- TEST.assert(gpu_external_texture.expired == true, "GPUExternalTexture should be expired");
- }
-
- frame.close();
- source.close();
- }
- </script>
-</head>
-
-<body>
- <div>
- <canvas id='display' width="640" height="480"></canvas>
- </div>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/content/test/data/gpu/webcodecs/gpu-external-texture-expired.html b/content/test/data/gpu/webcodecs/gpu-external-texture-expired.html
deleted file mode 100644
index c7e39a2..0000000
--- a/content/test/data/gpu/webcodecs/gpu-external-texture-expired.html
+++ /dev/null
@@ -1,72 +0,0 @@
-<!DOCTYPE html>
-<!--
-Take frames coming from various sources and render them to a canvas with
-WebGLRenderingContext.texImage2D().
--->
-<html>
-
-<head>
- <title>GPUExternalTexture.expired test</title>
- <script src="webcodecs_common.js"></script>
- <script id="myWorker" type="text/worker">
- self.onmessage = function(e) {
- self.postMessage("");
- }
- </script>
- <script type="text/javascript">
- 'use strict';
- function makeWorker(script) {
- var blob = new Blob([script]);
- return new Worker(URL.createObjectURL(blob));
- }
-
- async function main(arg) {
- const use_worker = arg.use_worker;
- let source_type = arg.source_type;
- let canvas = document.getElementById('display');
- let source =
- await createFrameSource(source_type, canvas.width, canvas.height);
- if (!source) {
- TEST.skip('Unsupported source: ' + source_type);
- return;
- }
-
- const adapter = navigator.gpu && await navigator.gpu.requestAdapter();
- if (!adapter) {
- TEST.skip('navigator.gpu && navigator.gpu.requestAdapter failed');
- return;
- }
-
- const device = await adapter.requestDevice();
- if (!device) {
- TEST.skip('adapter.requestDevice() failed');
- return;
- }
-
- let frame = await source.getNextFrame();
- const gpu_external_texture = device.importExternalTexture({ source: frame });
- TEST.assert(gpu_external_texture.expired == false, "GPUExternalTexture should be active");
-
- if (use_worker) {
- let worker = makeWorker(document.getElementById("myWorker").textContent);
- worker.onmessage = function (e) {
- TEST.assert(gpu_external_texture.expired == true, "GPUExternalTexture should be expired");
- }
- // GPUExternalTexture should be expired when the frame in the same thread has been transferred.
- worker.postMessage({ videoFrame: frame }, [frame]);
- } else {
- frame.close();
- TEST.assert(gpu_external_texture.expired == true, "GPUExternalTexture should be expired");
- }
- source.close();
- }
- </script>
-</head>
-
-<body>
- <div>
- <canvas id='display' width="640" height="480"></canvas>
- </div>
-</body>
-
-</html>
\ No newline at end of file
diff --git a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
index e27ef26..0de74e0 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webcodecs_expectations.txt
@@ -110,12 +110,8 @@
# Camera tests fail with NotFoundError on Sherlock devices.
crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_DrawImage_camera [ Failure ]
crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_Encode_camera_* [ Failure ]
-crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_GPUExternalTexture_expired_camera [ Failure ]
-crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_GPUExternalTexture_expired_worker_camera [ Failure ]
crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_TexImage2d_camera [ Failure ]
crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_copyTo_camera [ Failure ]
-crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_device_destroy_expired_texture_camera [ Failure ]
-crbug.com/1400512 [ fuchsia fuchsia-board-sherlock ] WebCodecs_texture_expired_from_destroyed_device_camera [ Failure ]
# finder:enable-unused
diff --git a/content/test/gpu/gpu_tests/webcodecs_integration_test.py b/content/test/gpu/gpu_tests/webcodecs_integration_test.py
index 7692484..48510a3 100644
--- a/content/test/gpu/gpu_tests/webcodecs_integration_test.py
+++ b/content/test/gpu/gpu_tests/webcodecs_integration_test.py
@@ -56,30 +56,6 @@
'source_type':
source_type
}])
- yield ('WebCodecs_GPUExternalTexture_expired_' + source_type,
- 'gpu-external-texture-expired.html', [{
- 'source_type': source_type,
- 'use_worker': False
- }])
- yield ('WebCodecs_GPUExternalTexture_expired_worker_' + source_type,
- 'gpu-external-texture-expired.html', [{
- 'source_type': source_type,
- 'use_worker': True
- }])
- yield ('WebCodecs_device_destroy_expired_texture_' + source_type,
- 'gpu-device-destroy-expire-active-external-texture.html', [{
- 'source_type':
- source_type,
- 'device_destroyed_before_import':
- False
- }])
- yield ('WebCodecs_texture_expired_from_destroyed_device_' + source_type,
- 'gpu-device-destroy-expire-active-external-texture.html', [{
- 'source_type':
- source_type,
- 'device_destroyed_before_import':
- True
- }])
@classmethod
def GenerateAudioTests(cls) -> ct.TestGenerator:
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index 43e81a9..9d38756 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -10,13 +10,11 @@
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_compute_pipeline_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_device_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_error_filter.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_external_texture_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_feature_name.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_query_set_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_queue_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_render_pipeline_descriptor.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_gpu_uncaptured_error_event_init.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_union_htmlvideoelement_videoframe.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
@@ -169,6 +167,8 @@
if (descriptor->defaultQueue()->hasLabel())
queue_->setLabel(descriptor->defaultQueue()->label());
+
+ external_texture_cache_ = MakeGarbageCollected<ExternalTextureCache>(this);
}
GPUDevice::~GPUDevice() {
@@ -418,9 +418,13 @@
return queue_;
}
+bool GPUDevice::destroyed() const {
+ return destroyed_;
+}
+
void GPUDevice::destroy(v8::Isolate* isolate) {
destroyed_ = true;
- DestroyAllExternalTextures();
+ external_texture_cache_->Destroy();
// Dissociate mailboxes before destroying the device. This ensures that
// mailbox operations which run during dissociation can succeed.
DissociateMailboxes();
@@ -456,24 +460,8 @@
ScriptState* script_state,
const GPUExternalTextureDescriptor* descriptor,
ExceptionState& exception_state) {
- // Gate VideoFrame importExternalTexture on the WebGPUWebCodecs OT.
- ExecutionContext* execution_context = ExecutionContext::From(script_state);
- if (descriptor->source()->GetContentType() ==
- V8UnionHTMLVideoElementOrVideoFrame::ContentType::kVideoFrame &&
- !RuntimeEnabledFeatures::WebGPUWebCodecsEnabled(execution_context)) {
- exception_state.ThrowTypeError(
- "VideoFrame isn't supported for importExternalTexture. This feature "
- "requires the WebGPUWebCodecs origin trial or "
- "--enable-webgpu-developer-features");
- return nullptr;
- }
-
- // Ensure the GPUExternalTexture created from a destroyed GPUDevice will be
- // expired immediately.
- if (destroyed_)
- return GPUExternalTexture::CreateExpired(this, descriptor, exception_state);
-
- return GPUExternalTexture::Create(this, descriptor, exception_state);
+ return external_texture_cache_->Import(ExecutionContext::From(script_state),
+ descriptor, exception_state);
}
GPUBindGroup* GPUDevice::createBindGroup(
@@ -665,7 +653,7 @@
visitor->Trace(limits_);
visitor->Trace(queue_);
visitor->Trace(lost_property_);
- visitor->Trace(active_external_textures_);
+ visitor->Trace(external_texture_cache_);
visitor->Trace(textures_with_mailbox_);
visitor->Trace(mappable_buffers_);
ExecutionContextClient::Trace(visitor);
@@ -675,14 +663,7 @@
void GPUDevice::Dispose() {
// This call accesses other GC objects, so it cannot be called inside GC
// objects destructors. Instead call it in the pre-finalizer.
- DestroyAllExternalTextures();
-}
-
-void GPUDevice::DestroyAllExternalTextures() {
- for (auto& external_texture : active_external_textures_) {
- external_texture->Destroy();
- }
- active_external_textures_.clear();
+ external_texture_cache_->Destroy();
}
void GPUDevice::DissociateMailboxes() {
@@ -706,17 +687,6 @@
mappable_buffers_.erase(buffer);
}
-void GPUDevice::AddActiveExternalTexture(GPUExternalTexture* external_texture) {
- DCHECK(external_texture);
- active_external_textures_.insert(external_texture);
-}
-
-void GPUDevice::RemoveActiveExternalTexture(
- GPUExternalTexture* external_texture) {
- DCHECK(external_texture);
- active_external_textures_.erase(external_texture);
-}
-
void GPUDevice::TrackTextureWithMailbox(GPUTexture* texture) {
DCHECK(texture);
textures_with_mailbox_.insert(texture);
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.h b/third_party/blink/renderer/modules/webgpu/gpu_device.h
index 584ff28..4ce3c99 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.h
@@ -18,6 +18,7 @@
namespace blink {
class ExecutionContext;
+class ExternalTextureCache;
class HTMLCanvasElement;
class GPUAdapter;
class GPUBuffer;
@@ -81,6 +82,7 @@
ScriptPromise lost(ScriptState* script_state);
GPUQueue* queue();
+ bool destroyed() const;
void destroy(v8::Isolate* isolate);
@@ -92,6 +94,7 @@
unsigned int usage_flags,
ExceptionState& exception_state);
GPUSampler* createSampler(const GPUSamplerDescriptor* descriptor);
+
GPUExternalTexture* importExternalTexture(
ScriptState* script_state,
const GPUExternalTextureDescriptor* descriptor,
@@ -142,9 +145,6 @@
void InjectError(WGPUErrorType type, const char* message);
void AddConsoleWarning(const char* message);
- void AddActiveExternalTexture(GPUExternalTexture* external_texture);
- void RemoveActiveExternalTexture(GPUExternalTexture* external_texture);
-
void TrackTextureWithMailbox(GPUTexture* texture);
void UntrackTextureWithMailbox(GPUTexture* texture);
@@ -165,8 +165,6 @@
// Used by USING_PRE_FINALIZER.
void Dispose();
-
- void DestroyAllExternalTextures();
void DissociateMailboxes();
void UnmapAllMappableBuffers(v8::Isolate* isolate);
@@ -215,15 +213,13 @@
static constexpr int kMaxAllowedConsoleWarnings = 500;
int allowed_console_warnings_remaining_ = kMaxAllowedConsoleWarnings;
- // Keep a list of all active GPUExternalTexture. Eagerly destroy them
- // when the device is destroyed (via .destroy) to free the memory.
- HeapHashSet<WeakMember<GPUExternalTexture>> active_external_textures_;
-
// Textures with mailboxes that should be dissociated before device.destroy().
HeapHashSet<WeakMember<GPUTexture>> textures_with_mailbox_;
HeapHashSet<WeakMember<GPUBuffer>> mappable_buffers_;
+ Member<ExternalTextureCache> external_texture_cache_;
+
// This attribute records that whether GPUDevice is destroyed (via destroy()).
bool destroyed_ = false;
};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
index 026a917..f8616b2 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.cc
@@ -14,16 +14,153 @@
#include "third_party/blink/renderer/modules/webcodecs/video_frame.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/external_texture_helper.h"
-#include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_mailbox_texture.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
namespace blink {
+ExternalTextureCache::ExternalTextureCache(GPUDevice* device)
+ : device_(device) {}
+
+GPUExternalTexture* ExternalTextureCache::Import(
+ ExecutionContext* execution_context,
+ const GPUExternalTextureDescriptor* descriptor,
+ ExceptionState& exception_state) {
+ // Gate VideoFrame importExternalTexture on the WebGPUWebCodecs OT.
+ if (descriptor->source()->GetContentType() ==
+ V8UnionHTMLVideoElementOrVideoFrame::ContentType::kVideoFrame &&
+ !RuntimeEnabledFeatures::WebGPUWebCodecsEnabled(execution_context)) {
+ exception_state.ThrowTypeError(
+ "VideoFrame isn't supported for importExternalTexture. This feature "
+ "requires the WebGPUWebCodecs origin trial or "
+ "--enable-webgpu-developer-features");
+ return nullptr;
+ }
+
+ // Ensure the GPUExternalTexture created from a destroyed GPUDevice will be
+ // expired immediately.
+ if (device()->destroyed()) {
+ return GPUExternalTexture::CreateExpired(this, descriptor, exception_state);
+ }
+
+ GPUExternalTexture* external_texture = nullptr;
+ switch (descriptor->source()->GetContentType()) {
+ case V8UnionHTMLVideoElementOrVideoFrame::ContentType::kHTMLVideoElement: {
+ HTMLVideoElement* video = descriptor->source()->GetAsHTMLVideoElement();
+
+ auto cache = from_html_video_element_.find(video);
+ if (cache != from_html_video_element_.end()) {
+ external_texture = cache->value;
+ } else {
+ external_texture = GPUExternalTexture::FromHTMLVideoElement(
+ this, video, descriptor, exception_state);
+ }
+ break;
+ }
+ case V8UnionHTMLVideoElementOrVideoFrame::ContentType::kVideoFrame: {
+ VideoFrame* frame = descriptor->source()->GetAsVideoFrame();
+
+ auto cache = from_video_frame_.find(frame);
+ if (cache != from_video_frame_.end()) {
+ external_texture = cache->value;
+ } else {
+ external_texture = GPUExternalTexture::FromVideoFrame(
+ this, frame, descriptor, exception_state);
+ }
+ break;
+ }
+ default:
+ NOTREACHED();
+ }
+
+ if (external_texture) {
+ external_texture->Refresh();
+ ExpireAtEndOfTask(external_texture);
+ }
+
+ return external_texture;
+}
+
+void ExternalTextureCache::Destroy() {
+ for (auto& cache : from_html_video_element_) {
+ cache.value->Destroy();
+ }
+ from_html_video_element_.clear();
+
+ for (auto& cache : from_video_frame_) {
+ cache.value->Destroy();
+ }
+ from_video_frame_.clear();
+
+ // GPUExternalTexture in expire list should be in from_html_video_element_ and
+ // from_video_frame_. It has been destroyed when clean up the cache. Clear
+ // list here is enough.
+ expire_list_.clear();
+}
+
+void ExternalTextureCache::Add(HTMLVideoElement* video,
+ GPUExternalTexture* external_texture) {
+ from_html_video_element_.insert(video, external_texture);
+}
+
+void ExternalTextureCache::Remove(HTMLVideoElement* video) {
+ from_html_video_element_.erase(video);
+}
+
+void ExternalTextureCache::Add(VideoFrame* frame,
+ GPUExternalTexture* external_texture) {
+ from_video_frame_.insert(frame, external_texture);
+}
+
+void ExternalTextureCache::Remove(VideoFrame* frame) {
+ from_video_frame_.erase(frame);
+}
+
+void ExternalTextureCache::Trace(Visitor* visitor) const {
+ visitor->Trace(from_html_video_element_);
+ visitor->Trace(from_video_frame_);
+ visitor->Trace(expire_list_);
+ visitor->Trace(device_);
+}
+
+GPUDevice* ExternalTextureCache::device() const {
+ return device_.Get();
+}
+
+void ExternalTextureCache::ExpireAtEndOfTask(
+ GPUExternalTexture* external_texture) {
+ DCHECK(external_texture);
+ expire_list_.push_back(external_texture);
+
+ if (expire_task_scheduled_) {
+ return;
+ }
+
+ device()
+ ->GetExecutionContext()
+ ->GetTaskRunner(TaskType::kWebGPU)
+ ->PostTask(FROM_HERE, WTF::BindOnce(&ExternalTextureCache::ExpireTask,
+ WrapWeakPersistent(this)));
+ expire_task_scheduled_ = true;
+}
+
+void ExternalTextureCache::ExpireTask() {
+ // GPUDevice.destroy() call has destroyed all pending external textures.
+ if (!expire_task_scheduled_) {
+ return;
+ }
+
+ expire_task_scheduled_ = false;
+
+ auto external_textures = std::move(expire_list_);
+ for (auto& external_texture : external_textures) {
+ external_texture->Expire();
+ }
+}
// static
GPUExternalTexture* GPUExternalTexture::CreateImpl(
- GPUDevice* device,
+ ExternalTextureCache* cache,
const GPUExternalTextureDescriptor* webgpu_desc,
scoped_refptr<media::VideoFrame> media_video_frame,
media::PaintCanvasVideoRenderer* video_renderer,
@@ -64,7 +201,7 @@
PredefinedColorSpaceToGfxColorSpace(dst_predefined_color_space);
ExternalTexture external_texture =
- CreateExternalTexture(device, src_color_space, dst_color_space,
+ CreateExternalTexture(cache->device(), src_color_space, dst_color_space,
media_video_frame, video_renderer);
if (external_texture.wgpu_external_texture == nullptr ||
@@ -76,7 +213,7 @@
GPUExternalTexture* gpu_external_texture =
MakeGarbageCollected<GPUExternalTexture>(
- device, external_texture.wgpu_external_texture,
+ cache, external_texture.wgpu_external_texture,
external_texture.mailbox_texture, external_texture.is_zero_copy,
media_video_frame_unique_id);
@@ -85,7 +222,7 @@
// static
GPUExternalTexture* GPUExternalTexture::CreateExpired(
- GPUDevice* device,
+ ExternalTextureCache* cache,
const GPUExternalTextureDescriptor* webgpu_desc,
ExceptionState& exception_state) {
// Validate GPUExternalTextureDescriptor.
@@ -108,9 +245,9 @@
// Bypass importing video frame into Dawn.
GPUExternalTexture* external_texture =
MakeGarbageCollected<GPUExternalTexture>(
- device,
- device->GetProcs().deviceCreateErrorExternalTexture(
- device->GetHandle()),
+ cache,
+ cache->device()->GetProcs().deviceCreateErrorExternalTexture(
+ cache->device()->GetHandle()),
nullptr /*mailbox_texture*/, false /*is_zero_copy*/,
absl::nullopt /*media_video_frame_unique_id*/);
@@ -119,7 +256,7 @@
// static
GPUExternalTexture* GPUExternalTexture::FromHTMLVideoElement(
- GPUDevice* device,
+ ExternalTextureCache* cache,
HTMLVideoElement* video,
const GPUExternalTextureDescriptor* webgpu_desc,
ExceptionState& exception_state) {
@@ -129,7 +266,7 @@
return nullptr;
GPUExternalTexture* external_texture = GPUExternalTexture::CreateImpl(
- device, webgpu_desc, source.media_video_frame, source.video_renderer,
+ cache, webgpu_desc, source.media_video_frame, source.video_renderer,
source.media_video_frame_unique_id, exception_state);
// WebGPU Spec requires that If the latest presented frame of video is not
@@ -139,7 +276,7 @@
// list for management.
if (external_texture) {
external_texture->ListenToHTMLVideoElement(video);
- device->AddActiveExternalTexture(external_texture);
+ cache->Add(video, external_texture);
}
return external_texture;
@@ -147,7 +284,7 @@
// static
GPUExternalTexture* GPUExternalTexture::FromVideoFrame(
- GPUDevice* device,
+ ExternalTextureCache* cache,
VideoFrame* frame,
const GPUExternalTextureDescriptor* webgpu_desc,
ExceptionState& exception_state) {
@@ -157,7 +294,7 @@
return nullptr;
GPUExternalTexture* external_texture = GPUExternalTexture::CreateImpl(
- device, webgpu_desc, source.media_video_frame, source.video_renderer,
+ cache, webgpu_desc, source.media_video_frame, source.video_renderer,
absl::nullopt, exception_state);
// If the webcodec video frame has been closed or destroyed, set expired to
@@ -168,52 +305,49 @@
external_texture->ListenToVideoFrame(frame);
// VideoFrame maybe closed when GPUExternalTexture trying to listen to.
- // In that case GPUExternalTexture should be expired and GPUDevice
+ // In that case GPUExternalTexture is not active and GPUDevice
// doesn't need to manage it.
- if (!external_texture->expired())
- device->AddActiveExternalTexture(external_texture);
+ if (external_texture->active()) {
+ cache->Add(frame, external_texture);
+ }
}
return external_texture;
}
-// static
-GPUExternalTexture* GPUExternalTexture::Create(
- GPUDevice* device,
- const GPUExternalTextureDescriptor* webgpu_desc,
- ExceptionState& exception_state) {
- switch (webgpu_desc->source()->GetContentType()) {
- case V8UnionHTMLVideoElementOrVideoFrame::ContentType::kHTMLVideoElement: {
- HTMLVideoElement* video = webgpu_desc->source()->GetAsHTMLVideoElement();
- return GPUExternalTexture::FromHTMLVideoElement(
- device, video, webgpu_desc, exception_state);
- }
- case V8UnionHTMLVideoElementOrVideoFrame::ContentType::kVideoFrame: {
- VideoFrame* frame = webgpu_desc->source()->GetAsVideoFrame();
- return GPUExternalTexture::FromVideoFrame(device, frame, webgpu_desc,
- exception_state);
- }
- }
-
- NOTREACHED();
-}
-
GPUExternalTexture::GPUExternalTexture(
- GPUDevice* device,
+ ExternalTextureCache* cache,
WGPUExternalTexture external_texture,
scoped_refptr<WebGPUMailboxTexture> mailbox_texture,
bool is_zero_copy,
absl::optional<media::VideoFrame::ID> media_video_frame_unique_id)
- : DawnObject<WGPUExternalTexture>(device, external_texture),
+ : DawnObject<WGPUExternalTexture>(cache->device(), external_texture),
mailbox_texture_(mailbox_texture),
is_zero_copy_(is_zero_copy),
- media_video_frame_unique_id_(media_video_frame_unique_id) {
+ media_video_frame_unique_id_(media_video_frame_unique_id),
+ cache_(cache) {
// Mark GPUExternalTexture without back resources as destroyed because no need
// to do real resource releasing.
if (!mailbox_texture_)
status_ = Status::Destroyed;
}
+void GPUExternalTexture::Refresh() {
+ DCHECK(status_ != Status::Destroyed);
+
+ GetProcs().externalTextureRefresh(GetHandle());
+ status_ = Status::Active;
+}
+
+void GPUExternalTexture::Expire() {
+ if (expired() || destroyed()) {
+ return;
+ }
+
+ GetProcs().externalTextureExpire(GetHandle());
+ status_ = Status::Expired;
+}
+
void GPUExternalTexture::Destroy() {
DCHECK(!destroyed());
DCHECK(mailbox_texture_);
@@ -224,15 +358,17 @@
void GPUExternalTexture::ListenToHTMLVideoElement(HTMLVideoElement* video) {
DCHECK(video);
-
- video_ = video;
video->GetDocument()
.GetScriptedAnimationController()
.WebGPURegisterVideoFrameStateCallback(WTF::BindRepeating(
&GPUExternalTexture::ContinueCheckingCurrentVideoFrame,
WrapPersistent(this)));
- status_ = Status::ListenToHTMLVideoElement;
+ video_ = video;
+ task_runner_ =
+ device()->GetExecutionContext()->GetTaskRunner(TaskType::kWebGPU);
+
+ status_ = Status::Active;
}
bool GPUExternalTexture::ContinueCheckingCurrentVideoFrame() {
@@ -246,7 +382,7 @@
// HTMLVideoElement transition from having a WMP to not having one.
if (!media_player) {
- ExpireExternalTextureFromHTMLVideoElement();
+ OnSourceInvalidated();
return false;
}
@@ -254,7 +390,7 @@
// with current video frame from compositor to detect a new presented
// video frame and expire the GPUExternalTexture.
if (media_video_frame_unique_id_ != media_player->CurrentFrameId()) {
- ExpireExternalTextureFromHTMLVideoElement();
+ OnSourceInvalidated();
return false;
}
@@ -262,22 +398,45 @@
}
void GPUExternalTexture::Trace(Visitor* visitor) const {
+ visitor->Trace(frame_);
visitor->Trace(video_);
+ visitor->Trace(cache_);
DawnObject<WGPUExternalTexture>::Trace(visitor);
}
-void GPUExternalTexture::ExpireExternalTextureFromHTMLVideoElement() {
- DCHECK(status_ != Status::ListenToVideoFrame);
- ExpireExternalTexture();
+void GPUExternalTexture::OnSourceInvalidated() {
+ DCHECK(task_runner_);
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ // OnSourceInvalidated is called for both VideoFrame and HTMLVE.
+ // VideoFrames are invalidated with and explicit close() call that
+ // should mark the ExternalTexture destroyed immediately.
+ // However HTMLVE could decide to advance in the middle of the task
+ // that imported the ExternalTexture. In that case defer the invalidation
+ // until the end of the task to preserve the semantic of ExternalTexture.
+ if (status_ == Status::Active && video_) {
+ if (task_runner_->BelongsToCurrentThread()) {
+ task_runner_->PostTask(FROM_HERE,
+ WTF::BindOnce(&GPUExternalTexture::RemoveFromCache,
+ WrapWeakPersistent(this)));
+ } else {
+ task_runner_->PostTask(FROM_HERE,
+ ConvertToBaseOnceCallback(CrossThreadBindOnce(
+ &GPUExternalTexture::RemoveFromCache,
+ WrapCrossThreadWeakPersistent(this))));
+ }
+ } else {
+ RemoveFromCache();
+ }
}
-void GPUExternalTexture::ExpireExternalTextureFromVideoFrame() {
- DCHECK(status_ != Status::ListenToHTMLVideoElement);
- ExpireExternalTexture();
-}
+void GPUExternalTexture::RemoveFromCache() {
+ if (video_) {
+ cache_->Remove(video_);
+ } else if (frame_) {
+ cache_->Remove(frame_);
+ }
-void GPUExternalTexture::ExpireExternalTexture() {
- device()->RemoveActiveExternalTexture(this);
Destroy();
}
@@ -290,10 +449,11 @@
return;
}
+ frame_ = frame;
task_runner_ =
device()->GetExecutionContext()->GetTaskRunner(TaskType::kWebGPU);
- status_ = Status::ListenToVideoFrame;
+ status_ = Status::Active;
}
void GPUExternalTexture::OnVideoFrameClosed() {
@@ -309,7 +469,7 @@
status_ = Status::Expired;
if (task_runner_->BelongsToCurrentThread()) {
- ExpireExternalTextureFromVideoFrame();
+ OnSourceInvalidated();
return;
}
@@ -321,8 +481,12 @@
WrapCrossThreadWeakPersistent(this))));
}
+bool GPUExternalTexture::active() const {
+ return status_ == Status::Active;
+}
+
bool GPUExternalTexture::expired() const {
- return status_ == Status::Expired || status_ == Status::Destroyed;
+ return status_ == Status::Expired;
}
bool GPUExternalTexture::isZeroCopy() const {
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
index 1714dde..232b2d6 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.h
@@ -10,30 +10,98 @@
#include "base/task/single_thread_task_runner.h"
#include "media/base/video_frame.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/wtf/ref_counted.h"
namespace blink {
class ExceptionState;
+class GPUExternalTexture;
class GPUExternalTextureDescriptor;
+class WebGPUMailboxTexture;
class HTMLVideoElement;
class VideoFrame;
-class WebGPUMailboxTexture;
+
+// GPUExternalTexture uses auto expiry mechanism
+// (https://www.w3.org/TR/webgpu/#-automatic-expiry-task-source). The
+// mechanism requires webgpu to expire GPUExternalTexture when current task
+// scope finished by posting expiration task. The expired GPUExternalTexture
+// is invalid to submit and needs to call importExternalTexture() to get the
+// refreshed GPUExternalTexture object. In implementation side,
+// importExternalTexture() also wraps GPUExternalTexture with underly video
+// frames. It is possible that multiple importExternalTexture() call with the
+// same source tries to wrap the same underly video frames. So a cache system
+// has been integrated to avoid re-creating the GPUExternalTexture again and
+// again with the same video frame. So importExternalTexture() tries to do:
+// - Search cache to see any hit. if not, create a new GPUExternalTexture and
+// insert it into cache.
+// - Refresh the external texture to un-expire it.
+// - Post a task to expire this external texture after finishing current task.
+// More details refers to
+// https://www.w3.org/TR/webgpu/#external-texture-creation
+class ExternalTextureCache : public GarbageCollected<ExternalTextureCache> {
+ public:
+ explicit ExternalTextureCache(GPUDevice* device);
+ ExternalTextureCache(const ExternalTextureCache&) = delete;
+ ExternalTextureCache& operator=(const ExternalTextureCache&) = delete;
+
+ // Implement importExternalTexture() auto expiry mechanism.
+ GPUExternalTexture* Import(ExecutionContext* execution_context,
+ const GPUExternalTextureDescriptor* descriptor,
+ ExceptionState& exception_state);
+
+ // Destroy all cached GPUExternalTexture and clear all lists.
+ void Destroy();
+
+ void Add(HTMLVideoElement* video, GPUExternalTexture* external_texture);
+ void Remove(HTMLVideoElement* video);
+
+ void Add(VideoFrame* frame, GPUExternalTexture* external_texture);
+ void Remove(VideoFrame* frame);
+
+ void Trace(Visitor* visitor) const;
+ GPUDevice* device() const;
+
+ private:
+ void ExpireAtEndOfTask(GPUExternalTexture* external_texture);
+ void ExpireTask();
+
+ // Keep a list of all active GPUExternalTexture. Eagerly destroy them
+ // when the device is destroyed (via .destroy) to free the memory.
+ HeapHashMap<WeakMember<HTMLVideoElement>, WeakMember<GPUExternalTexture>>
+ from_html_video_element_;
+ HeapHashMap<WeakMember<VideoFrame>, WeakMember<GPUExternalTexture>>
+ from_video_frame_;
+
+ bool expire_task_scheduled_ = false;
+ HeapVector<Member<GPUExternalTexture>> expire_list_;
+
+ Member<GPUDevice> device_;
+};
class GPUExternalTexture : public DawnObject<WGPUExternalTexture> {
DEFINE_WRAPPERTYPEINFO();
public:
- static GPUExternalTexture* Create(
- GPUDevice* device,
+ static GPUExternalTexture* CreateExpired(
+ ExternalTextureCache* cache,
const GPUExternalTextureDescriptor* webgpu_desc,
ExceptionState& exception_state);
- static GPUExternalTexture* CreateExpired(
- GPUDevice* device,
+ static GPUExternalTexture* FromHTMLVideoElement(
+ ExternalTextureCache* cache,
+ HTMLVideoElement* video,
+ const GPUExternalTextureDescriptor* webgpu_desc,
+ ExceptionState& exception_state);
+ static GPUExternalTexture* FromVideoFrame(
+ ExternalTextureCache* cache,
+ VideoFrame* frame,
const GPUExternalTextureDescriptor* webgpu_desc,
ExceptionState& exception_state);
explicit GPUExternalTexture(
- GPUDevice* device,
+ ExternalTextureCache* cache,
WGPUExternalTexture external_texture,
scoped_refptr<WebGPUMailboxTexture> mailbox_texture,
bool is_zero_copy,
@@ -42,11 +110,12 @@
GPUExternalTexture(const GPUExternalTexture&) = delete;
GPUExternalTexture& operator=(const GPUExternalTexture&) = delete;
- void Destroy();
-
- bool expired() const;
bool isZeroCopy() const;
+ void Destroy();
+ void Expire();
+ void Refresh();
+
void ListenToHTMLVideoElement(HTMLVideoElement* video);
void ListenToVideoFrame(VideoFrame* frame);
@@ -68,28 +137,19 @@
void Trace(Visitor* visitor) const override;
private:
- // The initial state of GPUExternalTexture is Expired. After listening to the
- // imported HTMLVE/VideoFrame, the state should be set to
- // ListenToHTMLVideoElement/VideoFrame, and then should only be changed in the
- // order: ListenToHTMLVideoElement/VideoFrame(->Expired)->Destroyed.
- enum class Status {
- ListenToHTMLVideoElement,
- ListenToVideoFrame,
- Expired,
- Destroyed
- };
- static GPUExternalTexture* FromHTMLVideoElement(
- GPUDevice* device,
- HTMLVideoElement* video,
- const GPUExternalTextureDescriptor* webgpu_desc,
- ExceptionState& exception_state);
- static GPUExternalTexture* FromVideoFrame(
- GPUDevice* device,
- VideoFrame* frame,
- const GPUExternalTextureDescriptor* webgpu_desc,
- ExceptionState& exception_state);
+ // [1] [2]
+ // Creation -> [Active] --> [Expired] --> [Destroyed]
+ // ^ |
+ // |-------------
+ // [3]
+ //
+ // [1] Happens when the current task finishes: the GPUExternalTexture cannot
+ // be used util it is refreshed [2] Happens when the source changes frames,
+ // the texture can no longer be refreshed. [3] Happens when the texture is
+ // refreshed by being re-imported.
+ enum class Status { Active, Expired, Destroyed };
static GPUExternalTexture* CreateImpl(
- GPUDevice* device,
+ ExternalTextureCache* cache,
const GPUExternalTextureDescriptor* webgpu_desc,
scoped_refptr<media::VideoFrame> media_video_frame,
media::PaintCanvasVideoRenderer* video_renderer,
@@ -101,18 +161,18 @@
GetProcs().externalTextureSetLabel(GetHandle(), utf8_label.c_str());
}
- // This is the function to expire the external texture when the imported
- // Blink::VideoFrame has been closed. The function is used as callback
- // function and be registered to the imported Blink::VideoFrame.
- void ExpireExternalTextureFromVideoFrame();
+ // This is the function to push a task to destroy the external texture when
+ // the imported video frame in GPUDevice cache is outdated. The function is
+ // used as callback function and be registered to the imported
+ // Blink::VideoFrame or HTMLVideoElement.
+ void OnSourceInvalidated();
- // This is the function to expire the external texture when the imported
- // HTMLVideoElement it imported from has been closed. The function is used
- // as callback function and be registered to the imported HTMLVideoElement.
- void ExpireExternalTextureFromHTMLVideoElement();
+ // GPUDevice holds cache for GPUExternalTextures to handling import same
+ // frame multiple time cases.
+ void RemoveFromCache();
- void ExpireExternalTexture();
-
+ bool active() const;
+ bool expired() const;
bool destroyed() const;
scoped_refptr<WebGPUMailboxTexture> mailbox_texture_;
@@ -120,6 +180,8 @@
absl::optional<media::VideoFrame::ID> media_video_frame_unique_id_;
WeakMember<HTMLVideoElement> video_;
+ WeakMember<VideoFrame> frame_;
+ WeakMember<ExternalTextureCache> cache_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
std::atomic<Status> status_ = Status::Expired;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.idl b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.idl
index 88b76039..1b9ea67 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_external_texture.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_external_texture.idl
@@ -8,7 +8,6 @@
Exposed(Window WebGPU, DedicatedWorker WebGPU),
SecureContext
] interface GPUExternalTexture {
- readonly attribute boolean expired;
[RuntimeEnabled=WebGPUDeveloperFeatures] readonly attribute boolean isZeroCopy;
};
GPUExternalTexture includes GPUObjectBase;