blob: 55eee90084f9448069aa300879d332d7e2a4a0b6 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/xr/xr_gpu_binding.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_xr_gpu_projection_layer_init.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_enum_conversions.h"
#include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
#include "third_party/blink/renderer/modules/webgpu/gpu.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/modules/webgpu/gpu_supported_limits.h"
#include "third_party/blink/renderer/modules/webgpu/gpu_texture.h"
#include "third_party/blink/renderer/modules/xr/xr_gpu_projection_layer.h"
#include "third_party/blink/renderer/modules/xr/xr_gpu_sub_image.h"
#include "third_party/blink/renderer/modules/xr/xr_session.h"
#include "third_party/blink/renderer/modules/xr/xr_view.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/size_f.h"
namespace blink {
namespace {
const double kMinScaleFactor = 0.2;
// A texture swap chain that is not communicated back to the compositor, used
// for things like depth/stencil attachments that don't assist reprojection.
class XRGPUStaticTextureLayerSwapChain : public XRGPULayerTextureSwapChain {
public:
XRGPUStaticTextureLayerSwapChain(GPUDevice* device,
const wgpu::TextureDescriptor* desc) {
texture_ = GPUTexture::Create(device, desc);
}
GPUTexture* GetCurrentTexture() override { return texture_; }
void OnFrameEnd() override {
// TODO(crbug.com/5818595): Prior to shipping the spec needs to determine
// if texture re-use is appropriate or not. If re-use is not specified then
// it should at the very least be detached from the JavaScript wrapper and
// reattached to a new one here. In both cases the texture should be
// cleared.
}
void Trace(Visitor* visitor) const override {
visitor->Trace(texture_);
XRGPULayerTextureSwapChain::Trace(visitor);
}
private:
Member<GPUTexture> texture_;
};
} // namespace
void XRGPULayerTextureSwapChain::OnFrameStart() {}
void XRGPULayerTextureSwapChain::OnFrameEnd() {}
void XRGPULayerTextureSwapChain::Trace(Visitor* visitor) const {}
XRGPUBinding* XRGPUBinding::Create(XRSession* session,
GPUDevice* device,
ExceptionState& exception_state) {
DCHECK(session);
DCHECK(device);
if (session->ended()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot create an XRGPUBinding for an "
"XRSession which has already ended.");
return nullptr;
}
if (!session->immersive()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot create an XRGPUBinding for an "
"inline XRSession.");
return nullptr;
}
if (device->destroyed()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot create an XRGPUBinding with a "
"destroyed WebGPU device.");
return nullptr;
}
if (!device->adapter()->isXRCompatible()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"WebGPU device must be created by an XR compatible adapter in order to "
"use with an immersive XRSession");
return nullptr;
}
return MakeGarbageCollected<XRGPUBinding>(session, device);
}
XRGPUBinding::XRGPUBinding(XRSession* session, GPUDevice* device)
: XRGraphicsBinding(session), device_(device) {}
XRProjectionLayer* XRGPUBinding::createProjectionLayer(
const XRGPUProjectionLayerInit* init,
ExceptionState& exception_state) {
// TODO(crbug.com/5818595): Validate the colorFormat and depthStencilFormat.
// The max size will be either the native resolution or the default
// if that happens to be larger than the native res. (That can happen on
// desktop systems.)
double max_scale = std::max(session()->NativeFramebufferScale(), 1.0);
// Clamp the developer-requested framebuffer scale to ensure it's not too
// small to see or unreasonably large.
double scale_factor =
std::clamp(init->scaleFactor(), kMinScaleFactor, max_scale);
gfx::SizeF scaled_size =
gfx::ScaleSize(session()->RecommendedArrayTextureSize(), scale_factor);
// If the scaled texture dimensions are larger than the max texture dimension
// for the device scale it down till it fits.
unsigned max_texture_size = device_->limits()->maxTextureDimension2D();
if (scaled_size.width() > max_texture_size ||
scaled_size.height() > max_texture_size) {
double max_dimension = std::max(scaled_size.width(), scaled_size.height());
scaled_size = gfx::ScaleSize(scaled_size, max_texture_size / max_dimension);
}
gfx::Size texture_size = gfx::ToFlooredSize(scaled_size);
// Create the color swap chain
wgpu::TextureDescriptor color_desc = {};
color_desc.label = "XRProjectionLayer Color";
color_desc.format = AsDawnEnum(init->colorFormat());
color_desc.usage = static_cast<wgpu::TextureUsage>(init->textureUsage());
color_desc.size = {static_cast<uint32_t>(texture_size.width()),
static_cast<uint32_t>(texture_size.height()),
static_cast<uint32_t>(session()->array_texture_layers())};
color_desc.dimension = wgpu::TextureDimension::e2D;
XRGPUStaticTextureLayerSwapChain* color_swap_chain =
MakeGarbageCollected<XRGPUStaticTextureLayerSwapChain>(device_,
&color_desc);
// Create the depth/stencil swap chain
XRGPUStaticTextureLayerSwapChain* depth_stencil_swap_chain = nullptr;
if (init->hasDepthStencilFormat()) {
wgpu::TextureDescriptor depth_stencil_desc = {};
depth_stencil_desc.label = "XRProjectionLayer Depth/Stencil";
depth_stencil_desc.format = AsDawnEnum(*init->depthStencilFormat());
depth_stencil_desc.usage =
static_cast<wgpu::TextureUsage>(init->textureUsage());
depth_stencil_desc.size = {
static_cast<uint32_t>(texture_size.width()),
static_cast<uint32_t>(texture_size.height()),
static_cast<uint32_t>(session()->array_texture_layers())};
depth_stencil_desc.dimension = wgpu::TextureDimension::e2D;
depth_stencil_swap_chain =
MakeGarbageCollected<XRGPUStaticTextureLayerSwapChain>(
device_, &depth_stencil_desc);
}
return MakeGarbageCollected<XRGPUProjectionLayer>(this, color_swap_chain,
depth_stencil_swap_chain);
}
XRGPUSubImage* XRGPUBinding::getViewSubImage(XRProjectionLayer* layer,
XRView* view,
ExceptionState& exception_state) {
if (!OwnsLayer(layer)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Layer was not created with this binding.");
return nullptr;
}
XRGPUProjectionLayer* gpu_layer = static_cast<XRGPUProjectionLayer*>(layer);
GPUTexture* color_texture =
gpu_layer->color_swap_chain()->GetCurrentTexture();
GPUTexture* depth_stencil_texture = nullptr;
XRGPULayerTextureSwapChain* depth_stencil_swap_chain =
gpu_layer->depth_stencil_swap_chain();
if (depth_stencil_swap_chain) {
depth_stencil_texture = depth_stencil_swap_chain->GetCurrentTexture();
}
gfx::Rect viewport =
gfx::Rect(0, 0, color_texture->width(), color_texture->height());
return MakeGarbageCollected<XRGPUSubImage>(
viewport, view->ViewData()->index(), color_texture,
depth_stencil_texture);
}
String XRGPUBinding::getPreferredColorFormat() {
return FromDawnEnum(GPU::preferred_canvas_format());
}
void XRGPUBinding::Trace(Visitor* visitor) const {
visitor->Trace(device_);
XRGraphicsBinding::Trace(visitor);
ScriptWrappable::Trace(visitor);
}
} // namespace blink