// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/vr/openxr/windows/openxr_graphics_binding_d3d11.h"
#include <d3d11_4.h>
#include <wrl.h>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "device/vr/openxr/openxr_api_wrapper.h"
#include "device/vr/openxr/openxr_platform.h"
#include "device/vr/openxr/openxr_util.h"
#include "device/vr/openxr/openxr_view_configuration.h"
#include "device/vr/openxr/windows/openxr_platform_helper_windows.h"
#include "device/vr/public/cpp/features.h"
#include "device/vr/windows/d3d11_texture_helper.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl_dxgi.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
#include "ui/gfx/gpu_fence.h"
namespace device {
// static
void OpenXrGraphicsBinding::GetRequiredExtensions(
std::vector<const char*>& extensions) {
extensions.push_back(XR_KHR_D3D11_ENABLE_EXTENSION_NAME);
}
OpenXrGraphicsBindingD3D11::OpenXrGraphicsBindingD3D11(
base::WeakPtr<OpenXrPlatformHelperWindows> weak_platform_helper)
: texture_helper_(std::make_unique<D3D11TextureHelper>()),
weak_platform_helper_(weak_platform_helper) {}
OpenXrGraphicsBindingD3D11::~OpenXrGraphicsBindingD3D11() = default;
bool OpenXrGraphicsBindingD3D11::Initialize(XrInstance instance,
XrSystemId system) {
if (initialized_) {
return true;
}
if (!texture_helper_) {
DVLOG(1) << __func__ << " No TextureHelper";
return false;
}
if (!weak_platform_helper_) {
DVLOG(1) << __func__ << " WeakPtr failed to resolve";
return false;
}
LUID luid;
if (!weak_platform_helper_->TryGetLuid(&luid, system)) {
DVLOG(1) << __func__ << " Did not get a luid";
return false;
}
texture_helper_->SetUseBGRA(true);
if (!texture_helper_->SetAdapterLUID(luid) ||
!texture_helper_->EnsureInitialized()) {
DVLOG(1) << __func__ << " Texture helper initialization failed";
return false;
}
binding_.device = texture_helper_->GetDevice().Get();
initialized_ = true;
return true;
}
const void* OpenXrGraphicsBindingD3D11::GetSessionCreateInfo() const {
CHECK(initialized_);
return &binding_;
}
int64_t OpenXrGraphicsBindingD3D11::GetSwapchainFormat(
XrSession session) const {
// OpenXR's swapchain format expects to describe the texture content.
// The result of a swapchain image created from OpenXR API always contains a
// typeless texture. On the other hand, WebGL API uses CSS color convention
// that's sRGB. The RGBA typelss texture from OpenXR swapchain image leads to
// a linear format render target view (reference to function
// D3D11TextureHelper::EnsureRenderTargetView in d3d11_texture_helper.cc).
// Therefore, the content in this openxr swapchain image is in sRGB format.
return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
}
XrResult OpenXrGraphicsBindingD3D11::EnumerateSwapchainImages(
const XrSwapchain& color_swapchain) {
CHECK(color_swapchain != XR_NULL_HANDLE);
CHECK(color_swapchain_images_.empty());
uint32_t chain_length;
RETURN_IF_XR_FAILED(
xrEnumerateSwapchainImages(color_swapchain, 0, &chain_length, nullptr));
std::vector<XrSwapchainImageD3D11KHR> xr_color_swapchain_images(
chain_length, {XR_TYPE_SWAPCHAIN_IMAGE_D3D11_KHR});
RETURN_IF_XR_FAILED(xrEnumerateSwapchainImages(
color_swapchain, xr_color_swapchain_images.size(), &chain_length,
reinterpret_cast<XrSwapchainImageBaseHeader*>(
xr_color_swapchain_images.data())));
color_swapchain_images_.reserve(xr_color_swapchain_images.size());
for (const auto& swapchain_image : xr_color_swapchain_images) {
color_swapchain_images_.emplace_back(swapchain_image.texture);
}
return XR_SUCCESS;
}
void OpenXrGraphicsBindingD3D11::ClearSwapchainImages() {
color_swapchain_images_.clear();
}
base::span<SwapChainInfo> OpenXrGraphicsBindingD3D11::GetSwapChainImages() {
return color_swapchain_images_;
}
bool OpenXrGraphicsBindingD3D11::CanUseSharedImages() const {
// Put shared image feature behind a flag until remaining issues with overlays
// are resolved.
return !base::FeatureList::IsEnabled(device::features::kOpenXRSharedImages);
}
void OpenXrGraphicsBindingD3D11::CreateSharedImages(
gpu::SharedImageInterface* sii) {
CHECK(sii);
for (auto& swap_chain_info : color_swapchain_images_) {
Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
HRESULT hr = swap_chain_info.d3d11_texture->QueryInterface(
IID_PPV_ARGS(&dxgi_resource));
if (FAILED(hr)) {
DLOG(ERROR) << "QueryInterface for IDXGIResource failed with error "
<< std::hex << hr;
return;
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
hr = dxgi_resource.As(&d3d11_texture);
if (FAILED(hr)) {
DLOG(ERROR) << "QueryInterface for ID3D11Texture2D failed with error "
<< std::hex << hr;
return;
}
D3D11_TEXTURE2D_DESC texture2d_desc;
d3d11_texture->GetDesc(&texture2d_desc);
// Shared handle creation can fail on platforms where the texture, for
// whatever reason, cannot be shared. We need to fallback gracefully to
// texture copies.
HANDLE shared_handle;
hr = dxgi_resource->CreateSharedHandle(
nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE,
nullptr, &shared_handle);
if (FAILED(hr)) {
DLOG(ERROR) << "Unable to create shared handle for DXGIResource "
<< std::hex << hr;
return;
}
gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle;
gpu_memory_buffer_handle.dxgi_handle.Set(shared_handle);
gpu_memory_buffer_handle.dxgi_token = gfx::DXGIHandleToken();
gpu_memory_buffer_handle.type = gfx::DXGI_SHARED_HANDLE;
// TODO(crbug.com/40918787): This size is the size of the texture
// from the OpenXr runtime, which is fine but does not work properly if the
// page requests any kind of framebuffer scaling, because then the image
// size that the page uses would be different than this size, which can
// cause errors in rendering.
gfx::Size buffer_size =
gfx::Size(texture2d_desc.Width, texture2d_desc.Height);
// The SharedImages created here will eventually be transferred to other
// processes to have their contents written by WebGL and read via GL by
// OpenXR.
const gpu::SharedImageUsageSet shared_image_usage =
gpu::SHARED_IMAGE_USAGE_SCANOUT | gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
gpu::SHARED_IMAGE_USAGE_GLES2_READ |
gpu::SHARED_IMAGE_USAGE_GLES2_WRITE;
swap_chain_info.shared_image = sii->CreateSharedImage(
{viz::SinglePlaneFormat::kRGBA_8888, buffer_size,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709,
gfx::ColorSpace::TransferID::LINEAR),
shared_image_usage, "OpenXrSwapChain"},
std::move(gpu_memory_buffer_handle));
CHECK(swap_chain_info.shared_image);
swap_chain_info.sync_token = sii->GenVerifiedSyncToken();
}
}
const SwapChainInfo& OpenXrGraphicsBindingD3D11::GetActiveSwapchainImage() {
CHECK(has_active_swapchain_image());
CHECK(active_swapchain_index() < color_swapchain_images_.size());
// We don't do any index translation on the images returned from the system;
// so whatever the system says is the active swapchain image, it is in the
// same spot in our vector.
return color_swapchain_images_[active_swapchain_index()];
}
bool OpenXrGraphicsBindingD3D11::WaitOnFence(gfx::GpuFence& gpu_fence) {
if (!has_active_swapchain_image() ||
active_swapchain_index() >= color_swapchain_images_.size()) {
return false;
}
// We don't do any index translation on the images returned from the system;
// so whatever the system says is the active swapchain image, it is in the
// same spot in our vector.
auto& swap_chain_info = color_swapchain_images_[active_swapchain_index()];
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device =
texture_helper_->GetDevice();
Microsoft::WRL::ComPtr<ID3D11Device5> d3d11_device5;
HRESULT hr = d3d11_device.As(&d3d11_device5);
if (FAILED(hr)) {
DLOG(ERROR) << "Unable to retrieve ID3D11Device5 interface " << std::hex
<< hr;
return false;
}
Microsoft::WRL::ComPtr<ID3D11Fence> d3d11_fence;
hr = d3d11_device5->OpenSharedFence(gpu_fence.GetGpuFenceHandle().Peek(),
IID_PPV_ARGS(&d3d11_fence));
if (FAILED(hr)) {
DLOG(ERROR) << "Unable to open a shared fence " << std::hex << hr;
return false;
}
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d11_device_context;
d3d11_device5->GetImmediateContext(&d3d11_device_context);
Microsoft::WRL::ComPtr<ID3D11DeviceContext4> d3d11_device_context4;
hr = d3d11_device_context.As(&d3d11_device_context4);
if (FAILED(hr)) {
DLOG(ERROR) << "Unable to retrieve ID3D11DeviceContext4 interface "
<< std::hex << hr;
return false;
}
hr = d3d11_device_context4->Wait(d3d11_fence.Get(), 1);
if (FAILED(hr)) {
DLOG(ERROR) << "Unable to Wait on D3D11 fence " << std::hex << hr;
return false;
}
// In order for the fence to be respected by the system, it needs to stick
// around until the next time the texture comes up for use.
swap_chain_info.d3d11_fence = std::move(d3d11_fence);
return true;
}
bool OpenXrGraphicsBindingD3D11::Render(
const scoped_refptr<viz::ContextProvider>& context_provider) {
return texture_helper_->UpdateBackbufferSizes() &&
texture_helper_->CompositeToBackBuffer(context_provider);
}
void OpenXrGraphicsBindingD3D11::CleanupWithoutSubmit() {
texture_helper_->CleanupNoSubmit();
}
bool OpenXrGraphicsBindingD3D11::ShouldFlipSubmittedImage() {
return IsUsingSharedImages();
}
void OpenXrGraphicsBindingD3D11::OnSwapchainImageSizeChanged() {
texture_helper_->SetDefaultSize(GetSwapchainImageSize());
}
void OpenXrGraphicsBindingD3D11::OnSwapchainImageActivated(
gpu::SharedImageInterface* sii) {
CHECK(has_active_swapchain_image());
CHECK(active_swapchain_index() < color_swapchain_images_.size());
texture_helper_->SetBackbuffer(
color_swapchain_images_[active_swapchain_index()].d3d11_texture.get());
}
void OpenXrGraphicsBindingD3D11::SetOverlayAndWebXrVisibility(
bool overlay_visible,
bool webxr_visible) {
texture_helper_->SetSourceAndOverlayVisible(webxr_visible, overlay_visible);
}
void OpenXrGraphicsBindingD3D11::SetWebXrTexture(
mojo::PlatformHandle texture_handle,
const gpu::SyncToken& sync_token,
const gfx::RectF& left,
const gfx::RectF& right) {
base::win::ScopedHandle scoped_handle = texture_handle.is_valid()
? texture_handle.TakeHandle()
: base::win::ScopedHandle();
texture_helper_->SetSourceTexture(std::move(scoped_handle), sync_token, left,
right);
}
bool OpenXrGraphicsBindingD3D11::SetOverlayTexture(
gfx::GpuMemoryBufferHandle texture,
const gpu::SyncToken& sync_token,
const gfx::RectF& left,
const gfx::RectF& right) {
if (texture.is_null()) {
return false;
}
CHECK(texture.type == gfx::DXGI_SHARED_HANDLE);
return texture_helper_->SetOverlayTexture(std::move(texture.dxgi_handle),
sync_token, left, right);
}
} // namespace device