Commit d71ad353 authored by Natasha Lee's avatar Natasha Lee Committed by Commit Bot

WebGPU set and handle DeviceLostCallback

Before Dawn implemented DeviceLostCallback, we were using
UncapturedErrorCallback to handle device lost errors.

Bug: dawn:68
Change-Id: I6d603f2d54f34d7e25c6f12fe4ee1098c5da53b0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2363387
Commit-Queue: Natasha Lee <natlee@microsoft.com>
Reviewed-by: default avatarKai Ninomiya <kainino@chromium.org>
Reviewed-by: default avatarAustin Eng <enga@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806279}
parent efbdaef4
......@@ -155,9 +155,9 @@ function onLoad() {
if (!testHorizontalBands())
return;
var query = /query=(.*)/.exec(window.location.href);
if (query)
contextLostTest(query[1]);
const query = new URLSearchParams(window.location.search).get('query');
if (query)
contextLostTest(query);
}
</script>
</head>
......
<html>
<head>
<script type='module'>
let lostJustHappened = false;
const initTextureColor = [1.0, 0.0, 0.0];
const reinitTextureColor = [0.0, 1.0, 0.0];
const reinitTextureColorArray = new Uint8Array([0x00, 0xff, 0x00, 0xff]);
function contextLostTest(kind) {
switch (kind) {
case 'kill_after_notification':
// The browser test waits for notification from the page that it
// has been loaded before navigating to about:gpucrash.
window.domAutomationController.send('LOADED');
break;
}
}
async function init(canvas, color) {
let adapter = null;
// TODO: do this loop internally in the browser
while (!adapter) {
try {
adapter = await navigator.gpu.requestAdapter();
} catch (err) {
// TODO: remove try and catch here once we no longer expect rejections here.
// According to the spec, rejection should only happen when invalid stuff is passed in.
console.warn("request adapter failed:", err);
}
}
const device = await adapter.requestDevice();
device.lost.then(async value => {
// After device lost, re-initialize for new adapter and device.
// We choose a different texture color from when we first
// init to be sure that the texture is coming from here
lostJustHappened = true;
await init(canvas, reinitTextureColor);
});
const context = canvas.getContext('gpupresent');
if (!context)
return;
const swapChainFormat = 'bgra8unorm';
const swapChain = context.configureSwapChain({
device,
format: swapChainFormat,
usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT
});
async function checkTextureColor(
src,
format,
{ x, y },
exp
) {
const byteLength = 4;
const bytesPerRow = 256;
const rowsPerImage = 1;
const mipSize = [1, 1, 1];
const buffer = device.createBuffer({
size: byteLength,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
const commandEncoder = device.createCommandEncoder();
commandEncoder.copyTextureToBuffer(
{ texture: src, mipLevel: 0, arrayLayer: 0, origin: { x, y, z: 0 } },
{ buffer, bytesPerRow, rowsPerImage },
mipSize
);
device.defaultQueue.submit([commandEncoder.finish()]);
await buffer.mapAsync(GPUMapMode.READ);
const actual = new Uint8Array(buffer.getMappedRange());
// check that the actual and expected buffers are the same
if (actual.byteLength !== exp.byteLength) {
window.domAutomationController.send('FAILED');
}
for (var i = 0; i !== actual.byteLength; i++) {
if (actual[i] != exp[i]) {
window.domAutomationController.send('FAILED');
return;
}
}
// if we did not fail in the previous checks, the context successfully restored
window.domAutomationController.send('SUCCESS');
}
function drawFrame() {
const commandEncoder = device.createCommandEncoder();
const texture = swapChain ? swapChain.getCurrentTexture() : null;
const textureView = texture ? texture.createView() : null;
const renderPassDescriptor = {
colorAttachments: [{
attachment: textureView,
loadValue: { r: color[0], g: color[1], b: color[2], a: 1.0 },
storeOp: 'store'
}],
};
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
passEncoder.endPass();
device.defaultQueue.submit([commandEncoder.finish()]);
if (lostJustHappened) {
// We want to check if it's been restored to the lost texture color.
checkTextureColor(texture, swapChainFormat, { x: 0, y: 0 }, reinitTextureColorArray );
lostJustHappened = false;
}
}
function doFrame(timestamp) {
drawFrame(timestamp);
requestAnimationFrame(doFrame);
}
requestAnimationFrame(doFrame);
}
export async function onLoad() {
const container = document.getElementById('container');
const canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
container.appendChild(canvas);
if (!navigator.gpu) {
console.warn('webgpu not supported');
return;
}
await init(canvas, initTextureColor);
const query = new URLSearchParams(window.location.search).get('query');
if (query)
contextLostTest(query);
}
document.addEventListener('DOMContentLoaded', onLoad);
</script>
</head>
<body>
<div id='container'></div>
</body>
</html>
\ No newline at end of file
......@@ -94,6 +94,8 @@ class ContextLostIntegrationTest(gpu_integration_test.GpuIntegrationTest):
cls._is_asan = options.is_asan
tests = (('GpuCrash_GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash',
'gpu_process_crash.html'),
('ContextLost_WebGPUContextLostFromGPUProcessExit',
'webgpu-context-lost.html?query=kill_after_notification'),
('ContextLost_WebGLContextLostFromGPUProcessExit',
'webgl.html?query=kill_after_notification'),
('ContextLost_WebGLContextLostFromLoseContextExtension',
......@@ -249,6 +251,14 @@ class ContextLostIntegrationTest(gpu_integration_test.GpuIntegrationTest):
self._KillGPUProcess(1, False)
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGPUContextLostFromGPUProcessExit(self, test_path):
self.RestartBrowserIfNecessaryWithArgs([
'--enable-unsafe-webgpu',
])
self._NavigateAndWaitForLoad(test_path)
self._KillGPUProcess(1, False)
self._RestartBrowser('must restart after tests that kill the GPU process')
def _ContextLost_WebGLContextLostFromLoseContextExtension(self, test_path):
self.RestartBrowserIfNecessaryWithArgs(
[cba.DISABLE_DOMAIN_BLOCKING_FOR_3D_APIS])
......
......@@ -73,3 +73,8 @@ crbug.com/1105308 [ fuchsia ] ContextLost_WebGLContextLostFromGPUProcessExit [ S
crbug.com/1122373 [ fuchsia ] ContextLost_WebGLContextLostFromQuantity [ Skip ]
crbug.com/1105308 [ fuchsia ] ContextLost_WebGLBlockedAfterJSNavigation [ Skip ]
crbug.com/1105308 [ fuchsia ] GpuNormalTermination_NewWebGLNotBlocked [ Skip ]
# Webgpu not fully supported on Linux, Android, and Fuchsia
[ linux ] ContextLost_WebGPUContextLostFromGPUProcessExit [ Skip ]
[ fuchsia ] ContextLost_WebGPUContextLostFromGPUProcessExit [ Skip ]
[ android ] ContextLost_WebGPUContextLostFromGPUProcessExit [ Skip ]
\ No newline at end of file
......@@ -53,12 +53,9 @@ std::unique_ptr<WebGraphicsContext3DProvider> CreateContextProviderOnMainThread(
return created_context_provider;
}
} // anonymous namespace
// static
GPU* GPU::Create(ExecutionContext& execution_context) {
std::unique_ptr<WebGraphicsContext3DProvider> CreateContextProvider(
ExecutionContext& execution_context) {
const KURL& url = execution_context.Url();
std::unique_ptr<WebGraphicsContext3DProvider> context_provider;
if (IsMainThread()) {
context_provider =
......@@ -74,22 +71,18 @@ GPU* GPU::Create(ExecutionContext& execution_context) {
// error.
return nullptr;
}
return context_provider;
}
if (!context_provider) {
// TODO(crbug.com/973017): Collect GPU info and surface context creation
// error.
return nullptr;
}
} // anonymous namespace
return MakeGarbageCollected<GPU>(execution_context,
std::move(context_provider));
// static
GPU* GPU::Create(ExecutionContext& execution_context) {
return MakeGarbageCollected<GPU>(execution_context);
}
GPU::GPU(ExecutionContext& execution_context,
std::unique_ptr<WebGraphicsContext3DProvider> context_provider)
: ExecutionContextLifecycleObserver(&execution_context),
dawn_control_client_(base::MakeRefCounted<DawnControlClientHolder>(
std::move(context_provider))) {}
GPU::GPU(ExecutionContext& execution_context)
: ExecutionContextLifecycleObserver(&execution_context) {}
GPU::~GPU() = default;
......@@ -99,6 +92,9 @@ void GPU::Trace(Visitor* visitor) const {
}
void GPU::ContextDestroyed() {
if (!dawn_control_client_) {
return;
}
dawn_control_client_->Destroy();
}
......@@ -118,6 +114,29 @@ ScriptPromise GPU::requestAdapter(ScriptState* script_state,
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise promise = resolver->Promise();
if (!dawn_control_client_ || dawn_control_client_->IsContextLost()) {
ExecutionContext* execution_context = ExecutionContext::From(script_state);
// TODO(natlee@microsoft.com): if GPU process is lost, wait for the GPU
// process to come back instead of rejecting right away
std::unique_ptr<WebGraphicsContext3DProvider> context_provider =
CreateContextProvider(*execution_context);
if (!context_provider) {
// Failed to create context provider, won't be able to request adapter
// TODO(crbug.com/973017): Collect GPU info and surface context creation
// error.
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kOperationError, "Fail to request GPUAdapter"));
return promise;
} else {
// Make a new DawnControlClientHolder with the context provider we just
// made and set the lost context callback
dawn_control_client_ = base::MakeRefCounted<DawnControlClientHolder>(
std::move(context_provider));
dawn_control_client_->SetLostContextCallback();
}
}
// For now we choose kHighPerformance by default.
gpu::webgpu::PowerPreference power_preference =
gpu::webgpu::PowerPreference::kHighPerformance;
......
......@@ -18,7 +18,6 @@ namespace blink {
class GPURequestAdapterOptions;
class ScriptPromiseResolver;
class ScriptState;
class WebGraphicsContext3DProvider;
class DawnControlClientHolder;
class GPU final : public ScriptWrappable,
......@@ -27,8 +26,7 @@ class GPU final : public ScriptWrappable,
public:
static GPU* Create(ExecutionContext& execution_context);
explicit GPU(ExecutionContext& execution_context,
std::unique_ptr<WebGraphicsContext3DProvider> context_provider);
explicit GPU(ExecutionContext& execution_context);
~GPU() override;
// ScriptWrappable overrides
......
......@@ -68,11 +68,16 @@ GPUDevice::GPUDevice(ExecutionContext* execution_context,
GetProcs().deviceGetDefaultQueue(GetHandle()))),
lost_property_(MakeGarbageCollected<LostProperty>(execution_context)),
error_callback_(BindRepeatingDawnCallback(&GPUDevice::OnUncapturedError,
WrapWeakPersistent(this))) {
WrapWeakPersistent(this))),
lost_callback_(BindDawnCallback(&GPUDevice::OnDeviceLostError,
WrapWeakPersistent(this))) {
DCHECK(dawn_control_client->GetInterface()->GetDevice(client_id));
GetProcs().deviceSetUncapturedErrorCallback(
GetHandle(), error_callback_->UnboundRepeatingCallback(),
error_callback_->AsUserdata());
GetProcs().deviceSetDeviceLostCallback(GetHandle(),
lost_callback_->UnboundCallback(),
lost_callback_->AsUserdata());
if (extension_name_list_.Contains("textureCompressionBC")) {
AddConsoleWarning(
......@@ -112,16 +117,10 @@ void GPUDevice::AddConsoleWarning(const char* message) {
void GPUDevice::OnUncapturedError(WGPUErrorType errorType,
const char* message) {
DCHECK_NE(errorType, WGPUErrorType_NoError);
DCHECK_NE(errorType, WGPUErrorType_DeviceLost);
LOG(ERROR) << "GPUDevice: " << message;
AddConsoleWarning(message);
// TODO: Use device lost callback instead of uncaptured error callback.
if (errorType == WGPUErrorType_DeviceLost &&
lost_property_->GetState() == LostProperty::kPending) {
auto* device_lost_info = MakeGarbageCollected<GPUDeviceLostInfo>(message);
lost_property_->Resolve(device_lost_info);
}
GPUUncapturedErrorEventInit* init = GPUUncapturedErrorEventInit::Create();
if (errorType == WGPUErrorType_Validation) {
auto* error = MakeGarbageCollected<GPUValidationError>(message);
......@@ -139,6 +138,15 @@ void GPUDevice::OnUncapturedError(WGPUErrorType errorType,
event_type_names::kUncapturederror, init));
}
void GPUDevice::OnDeviceLostError(const char* message) {
AddConsoleWarning(message);
if (lost_property_->GetState() == LostProperty::kPending) {
auto* device_lost_info = MakeGarbageCollected<GPUDeviceLostInfo>(message);
lost_property_->Resolve(device_lost_info);
}
}
GPUAdapter* GPUDevice::adapter() const {
return adapter_;
}
......
......@@ -112,6 +112,7 @@ class GPUDevice final : public EventTargetWithInlineData,
ScriptPromiseProperty<Member<GPUDeviceLostInfo>, ToV8UndefinedGenerator>;
void OnUncapturedError(WGPUErrorType errorType, const char* message);
void OnDeviceLostError(const char* message);
void OnPopErrorScopeCallback(ScriptPromiseResolver* resolver,
WGPUErrorType type,
......@@ -124,6 +125,8 @@ class GPUDevice final : public EventTargetWithInlineData,
std::unique_ptr<
DawnCallback<base::RepeatingCallback<void(WGPUErrorType, const char*)>>>
error_callback_;
std::unique_ptr<DawnCallback<base::OnceCallback<void(const char*)>>>
lost_callback_;
static constexpr int kMaxAllowedConsoleWarnings = 500;
int allowed_console_warnings_remaining_ = kMaxAllowedConsoleWarnings;
......
......@@ -6,6 +6,7 @@
#include "base/check.h"
#include "gpu/command_buffer/client/webgpu_interface.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
......@@ -14,6 +15,11 @@ DawnControlClientHolder::DawnControlClientHolder(
: context_provider_(std::move(context_provider)),
interface_(context_provider_->WebGPUInterface()) {}
void DawnControlClientHolder::SetLostContextCallback() {
context_provider_->SetLostContextCallback(WTF::BindRepeating(
&DawnControlClientHolder::SetContextLost, base::WrapRefCounted(this)));
}
void DawnControlClientHolder::Destroy() {
interface_ = nullptr;
context_provider_.reset();
......@@ -38,4 +44,12 @@ const DawnProcTable& DawnControlClientHolder::GetProcs() const {
return interface_->GetProcs();
}
void DawnControlClientHolder::SetContextLost() {
lost_ = true;
}
bool DawnControlClientHolder::IsContextLost() const {
return lost_;
}
} // namespace blink
......@@ -37,6 +37,9 @@ class PLATFORM_EXPORT DawnControlClientHolder
WebGraphicsContext3DProvider* GetContextProvider() const;
gpu::webgpu::WebGPUInterface* GetInterface() const;
const DawnProcTable& GetProcs() const;
void SetContextLost();
bool IsContextLost() const;
void SetLostContextCallback();
private:
friend class RefCounted<DawnControlClientHolder>;
......@@ -44,6 +47,7 @@ class PLATFORM_EXPORT DawnControlClientHolder
std::unique_ptr<WebGraphicsContext3DProvider> context_provider_;
gpu::webgpu::WebGPUInterface* interface_;
bool lost_ = false;
};
} // namespace blink
......
......@@ -10,29 +10,32 @@ async_test((test) => {
test.done();
return;
}
navigator.gpu.requestAdapter().then(adapter => {
if (!adapter) {
test.done();
return;
}
return adapter.requestDevice();
}).then(device => {
if (!device) {
}).catch(err => {
test.done();
return;
}
device.createBuffer({
size: 24,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
device.createBuffer({
size: 0,
usage: GPUBufferUsage.VERTEX | 2261634.5098,
mappedAtCreation: true
});
test.done();
});
}).then(device => {
if (!device) {
test.done();
return;
}
device.createBuffer({
size: 24,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
mappedAtCreation: true
});
device.createBuffer({
size: 0,
usage: GPUBufferUsage.VERTEX | 2261634.5098,
mappedAtCreation: true
});
test.done();
});
}, 'Regression test for crbug.com/1104580');
</script>
</script>
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment