在 DirectX 中呈现

注意

本文与旧版 WinRT 本机 API 相关。 对于新的本机应用项目,建议使用 OpenXR API

Windows Mixed Reality基于 DirectX 构建,可为用户提供丰富的 3D 图形体验。 呈现抽象位于 DirectX 的正上方,使应用能够对系统预测的全息场景观察器的位置和方向进行推理。 然后,开发人员可以根据每个相机定位其全息影像,让应用在用户移动时以各种空间坐标系呈现这些全息影像。

注意:本演练介绍 Direct3D 11 中的全息渲染。 Direct3D 12 Windows Mixed Reality 应用模板也随 混合现实 应用模板扩展一起提供。

当前帧的更新

若要更新全息影像的应用程序状态,每帧一次,应用将:

  • 从显示管理系统获取 全息帧
  • 使用渲染完成后相机视图所在位置的当前预测更新场景。 请注意,全息场景可以有多个相机。

若要呈现到全息相机视图,每帧一次,应用将:

  • 对于每个相机,使用来自系统的相机视图和投影矩阵呈现当前帧的场景。

创建新的全息帧并获取其预测

HolographicFrame 包含应用更新和呈现当前帧所需的信息。 应用通过调用 CreateNextFrame 方法开始每个新帧。 调用此方法时,将使用可用的最新传感器数据进行预测,并封装在 CurrentPrediction 对象中。

新帧对象必须用于每个呈现的帧,因为它仅在一瞬间有效。 CurrentPrediction 属性包含相机位置等信息。 该信息推断为预期帧对用户可见的确切时间点。

以下代码摘自 AppMain::Update

// The HolographicFrame has information that the app needs in order
// to update and render the current frame. The app begins each new
// frame by calling CreateNextFrame.
HolographicFrame holographicFrame = m_holographicSpace.CreateNextFrame();

// Get a prediction of where holographic cameras will be when this frame
// is presented.
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

处理相机更新

后退缓冲区可以逐帧更改。 你的应用需要验证每个相机的后台缓冲区,并根据需要释放和重新创建资源视图和深度缓冲区。 请注意,预测中的姿势集是当前帧中使用的相机的权威列表。 通常,使用此列表来循环访问相机集。

AppMain::Update

m_deviceResources->EnsureCameraResources(holographicFrame, prediction);

DeviceResources::EnsureCameraResources

for (HolographicCameraPose const& cameraPose : prediction.CameraPoses())
{
    HolographicCameraRenderingParameters renderingParameters = frame.GetRenderingParameters(cameraPose);
    CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();
    pCameraResources->CreateResourcesForBackBuffer(this, renderingParameters);
}

获取要用作呈现基础的坐标系

Windows Mixed Reality可让你的应用创建各种坐标系,例如用于跟踪物理世界中位置的附加和固定参考帧。 然后,你的应用可以使用这些坐标系来推断在何处呈现每个帧的全息影像。 从 API 请求坐标时,将始终传入希望在其中表示这些坐标的 SpatialCoordinateSystem

AppMain::Update

pose = SpatialPointerPose::TryGetAtTimestamp(
    m_stationaryReferenceFrame.CoordinateSystem(), prediction.Timestamp());

然后,这些坐标系可用于在场景中呈现内容时生成立体视图矩阵。

From CameraResources::UpdateViewProjectionBuffer

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

处理凝视和手势输入

凝视 部输入不是基于时间的,也无需在 StepTimer 函数中更新。 但是,此输入是应用需要查看每个帧的内容。

处理基于时间的更新

任何实时呈现应用都需要某种方法来处理基于时间的更新 - Windows 全息应用模板使用 StepTimer 实现,类似于 DirectX 11 UWP 应用模板中提供的 StepTimer。 此 StepTimer 示例帮助程序类可以提供固定时间步长更新和可变时间步长更新,默认模式是可变时间步长。

对于全息渲染,我们已选择不要将太多内容放入计时器函数,因为你可以将其配置为固定时间步长。 对于某些帧,它可能会被调用多次(或者根本不调用),并且我们的全息数据更新应该每帧进行一次。

AppMain::Update

m_timer.Tick([this]()
{
    m_spinningCubeRenderer->Update(m_timer);
});

在坐标系中定位和旋转全息影像

如果你在单个坐标系中作,就像模板使用 SpatialStationaryReferenceFrame 一样,此过程与 3D 图形中过去所用的过程没有不同。 在这里,我们将旋转立方体并根据静止坐标系中的位置设置模型矩阵。

From SpinningCubeRenderer::Update

// Rotate the cube.
// Convert degrees to radians, then convert seconds to rotation angle.
const float    radiansPerSecond = XMConvertToRadians(m_degreesPerSecond);
const double   totalRotation = timer.GetTotalSeconds() * radiansPerSecond;
const float    radians = static_cast<float>(fmod(totalRotation, XM_2PI));
const XMMATRIX modelRotation = XMMatrixRotationY(-radians);

// Position the cube.
const XMMATRIX modelTranslation = XMMatrixTranslationFromVector(XMLoadFloat3(&m_position));

// Multiply to get the transform matrix.
// Note that this transform does not enforce a particular coordinate system. The calling
// class is responsible for rendering this content in a consistent manner.
const XMMATRIX modelTransform = XMMatrixMultiply(modelRotation, modelTranslation);

// The view and projection matrices are provided by the system; they are associated
// with holographic cameras, and updated on a per-camera basis.
// Here, we provide the model transform for the sample hologram. The model transform
// matrix is transposed to prepare it for the shader.
XMStoreFloat4x4(&m_modelConstantBufferData.model, XMMatrixTranspose(modelTransform));

有关高级方案的注意事项: 旋转立方体是如何在单个参考帧中定位全息影像的一个简单示例。 还可以在同一呈现帧中同时使用 多个 SpatialCoordinateSystems

更新常量缓冲区数据

内容的模型转换将照常更新。 现在,你将计算要呈现的坐标系的有效转换。

From SpinningCubeRenderer::Update

// Update the model transform buffer for the hologram.
context->UpdateSubresource(
    m_modelConstantBuffer.Get(),
    0,
    nullptr,
    &m_modelConstantBufferData,
    0,
    0
);

视图和投影转换呢? 为了获得最佳结果,我们希望等到我们几乎准备好进行抽签调用后再得到这些。

呈现当前帧

在 Windows Mixed Reality 上呈现与在 2D 单声道显示器上呈现没有太大区别,但有一些区别:

  • 全息帧预测非常重要。 预测越接近显示帧的时间,全息影像的外观就越好。
  • Windows Mixed Reality控制相机视图。 呈现到每个帧,因为全息帧稍后将呈现它们。
  • 建议使用实例化绘图对呈现目标数组进行立体渲染。 全息应用模板使用建议的实例化绘制方法,以呈现目标数组,该数组使用 纹理 2DArray 上的呈现目标视图。
  • 如果要在不使用立体声实例化的情况下进行渲染,则需要创建两个非数组 RenderTargetViews,每个眼睛一个。 每个 RenderTargetViews 引用从系统提供给应用的 Texture2DArray 中的两个切片之一。 不建议这样做,因为它通常比使用实例化慢。

获取更新的全息帧预测

更新帧预测可增强图像稳定效果。 由于预测与帧对用户可见的时间较短,因此可以更准确地定位全息影像。 理想情况下,请在渲染前更新帧预测。

holographicFrame.UpdateCurrentPrediction();
HolographicFramePrediction prediction = holographicFrame.CurrentPrediction();

呈现到每个相机

Loop预测中的相机姿势集,并呈现到此集中的每个相机。

设置呈现阶段

Windows Mixed Reality使用立体渲染来增强深度的错觉和立体渲染,因此左右显示都处于活动状态。 使用立体渲染时,两个显示器之间存在偏移量,大脑可以协调为实际深度。 本部分介绍使用实例化、使用 Windows 全息应用模板中的代码进行立体渲染。

每个相机都有自己的渲染目标, (全息空间中的后台缓冲区) 以及视图和投影矩阵。 你的应用需要基于每个相机创建任何其他基于相机的资源(例如深度缓冲区)。 在 Windows 全息应用模板中,我们提供了一个帮助程序类,用于将这些资源捆绑在 DX::CameraResources 中。 首先设置呈现器目标视图:

AppMain::Render

// This represents the device-based resources for a HolographicCamera.
DX::CameraResources* pCameraResources = cameraResourceMap[cameraPose.HolographicCamera().Id()].get();

// Get the device context.
const auto context = m_deviceResources->GetD3DDeviceContext();
const auto depthStencilView = pCameraResources->GetDepthStencilView();

// Set render targets to the current holographic camera.
ID3D11RenderTargetView *const targets[1] =
    { pCameraResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, depthStencilView);

// Clear the back buffer and depth stencil view.
if (m_canGetHolographicDisplayForCamera &&
    cameraPose.HolographicCamera().Display().IsOpaque())
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::CornflowerBlue);
}
else
{
    context->ClearRenderTargetView(targets[0], DirectX::Colors::Transparent);
}
context->ClearDepthStencilView(
    depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

使用预测获取相机的视图和投影矩阵

每个全息相机的视图和投影矩阵将随每个帧而变化。 刷新每个全息相机的常量缓冲区中的数据。 在更新预测之后,并在为该相机进行任何绘制调用之前执行此作。

AppMain::Render

// The view and projection matrices for each holographic camera will change
// every frame. This function refreshes the data in the constant buffer for
// the holographic camera indicated by cameraPose.
if (m_stationaryReferenceFrame)
{
    pCameraResources->UpdateViewProjectionBuffer(
        m_deviceResources, cameraPose, m_stationaryReferenceFrame.CoordinateSystem());
}

// Attach the view/projection constant buffer for this camera to the graphics pipeline.
bool cameraActive = pCameraResources->AttachViewProjectionBuffer(m_deviceResources);

在这里,我们展示了如何从相机姿势获取矩阵。 在此过程中,我们还获取相机的当前视区。 请注意我们如何提供坐标系:这是用于理解凝视的相同坐标系,也是用于定位旋转立方体的坐标系。

From CameraResources::UpdateViewProjectionBuffer

// The system changes the viewport on a per-frame basis for system optimizations.
auto viewport = cameraPose.Viewport();
m_d3dViewport = CD3D11_VIEWPORT(
    viewport.X,
    viewport.Y,
    viewport.Width,
    viewport.Height
);

// The projection transform for each frame is provided by the HolographicCameraPose.
HolographicStereoTransform cameraProjectionTransform = cameraPose.ProjectionTransform();

// Get a container object with the view and projection matrices for the given
// pose in the given coordinate system.
auto viewTransformContainer = cameraPose.TryGetViewTransform(coordinateSystem);

// If TryGetViewTransform returns a null pointer, that means the pose and coordinate
// system cannot be understood relative to one another; content cannot be rendered
// in this coordinate system for the duration of the current frame.
// This usually means that positional tracking is not active for the current frame, in
// which case it is possible to use a SpatialLocatorAttachedFrameOfReference to render
// content that is not world-locked instead.
DX::ViewProjectionConstantBuffer viewProjectionConstantBufferData;
bool viewTransformAcquired = viewTransformContainer != nullptr;
if (viewTransformAcquired)
{
    // Otherwise, the set of view transforms can be retrieved.
    HolographicStereoTransform viewCoordinateSystemTransform = viewTransformContainer.Value();

    // Update the view matrices. Holographic cameras (such as Microsoft HoloLens) are
    // constantly moving relative to the world. The view matrices need to be updated
    // every frame.
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[0],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Left) *
            XMLoadFloat4x4(&cameraProjectionTransform.Left))
    );
    XMStoreFloat4x4(
        &viewProjectionConstantBufferData.viewProjection[1],
        XMMatrixTranspose(XMLoadFloat4x4(&viewCoordinateSystemTransform.Right) *
            XMLoadFloat4x4(&cameraProjectionTransform.Right))
    );
}

应设置每个帧的视区。 顶点着色器 (至少) 通常需要访问视图/投影数据。

From CameraResources::AttachViewProjectionBuffer

// Set the viewport for this camera.
context->RSSetViewports(1, &m_d3dViewport);

// Send the constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    1,
    1,
    m_viewProjectionConstantBuffer.GetAddressOf()
);

呈现到相机后台缓冲区并提交深度缓冲区

最好检查 TryGetViewTransform 在尝试使用视图/投影数据之前成功,因为例如,如果坐标系无法定位 (,则跟踪将中断,) 你的应用无法使用它为该帧呈现。 仅当 CameraResources 类指示更新成功时,模板才对旋转立方体调用 Render

Windows Mixed Reality包括图像防抖动功能,使全息影像位于开发人员或用户放置于世界的位置。 图像稳定有助于隐藏呈现管道中固有的延迟,以确保为用户提供最佳的全息体验。 可以指定焦点以进一步增强图像稳定性,或者提供深度缓冲区来实时计算优化的图像稳定。

为了获得最佳结果,应用应使用 CommitDirect3D11DepthBuffer API 提供深度缓冲区。 然后,Windows Mixed Reality可以使用深度缓冲区中的几何信息来实时优化图像稳定。 默认情况下,Windows 全息应用模板会提交应用的深度缓冲区,帮助优化全息影像稳定性。

AppMain::Render

// Only render world-locked content when positional tracking is active.
if (cameraActive)
{
    // Draw the sample hologram.
    m_spinningCubeRenderer->Render();
    if (m_canCommitDirect3D11DepthBuffer)
    {
        // On versions of the platform that support the CommitDirect3D11DepthBuffer API, we can 
        // provide the depth buffer to the system, and it will use depth information to stabilize 
        // the image at a per-pixel level.
        HolographicCameraRenderingParameters renderingParameters =
            holographicFrame.GetRenderingParameters(cameraPose);
        
        IDirect3DSurface interopSurface =
            DX::CreateDepthTextureInteropObject(pCameraResources->GetDepthStencilTexture2D());

        // Calling CommitDirect3D11DepthBuffer causes the system to queue Direct3D commands to 
        // read the depth buffer. It will then use that information to stabilize the image as
        // the HolographicFrame is presented.
        renderingParameters.CommitDirect3D11DepthBuffer(interopSurface);
    }
}

注意

Windows 将在 GPU 上处理深度纹理,因此必须使用深度缓冲区作为着色器资源。 创建的 ID3D11Texture2D 应采用无类型格式,并且应绑定为着色器资源视图。 下面是一个示例,说明如何创建可为图像稳定而提交的深度纹理。

用于为 CommitDirect3D11DepthBuffer 创建深度缓冲区资源的代码

// Create a depth stencil view for use with 3D rendering if needed.
CD3D11_TEXTURE2D_DESC depthStencilDesc(
    DXGI_FORMAT_R16_TYPELESS,
    static_cast<UINT>(m_d3dRenderTargetSize.Width),
    static_cast<UINT>(m_d3dRenderTargetSize.Height),
    m_isStereo ? 2 : 1, // Create two textures when rendering in stereo.
    1, // Use a single mipmap level.
    D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE
);

winrt::check_hresult(
    device->CreateTexture2D(
        &depthStencilDesc,
        nullptr,
        &m_d3dDepthStencil
    ));

CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(
    m_isStereo ? D3D11_DSV_DIMENSION_TEXTURE2DARRAY : D3D11_DSV_DIMENSION_TEXTURE2D,
    DXGI_FORMAT_D16_UNORM
);
winrt::check_hresult(
    device->CreateDepthStencilView(
        m_d3dDepthStencil.Get(),
        &depthStencilViewDesc,
        &m_d3dDepthStencilView
    ));

绘制全息内容

Windows 全息应用模板使用将实例化几何图形绘制到大小为 2 的 Texture2DArray 的建议技术,以立体声呈现内容。 让我们看一下此实例的实例部分,以及它在Windows Mixed Reality上的工作原理。

From SpinningCubeRenderer::Render

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

每个实例从常量缓冲区访问不同的视图/投影矩阵。 下面是常量缓冲区结构,它只是一个由两个矩阵构成的数组。

VertexShaderShared.hlsl,由 VPRTVertexShader.hlsl 包含:

// A constant buffer that stores each set of view and projection matrices in column-major format.
cbuffer ViewProjectionConstantBuffer : register(b1)
{
    float4x4 viewProjection[2];
};

必须为每个像素设置呈现器目标数组索引。 在以下代码片段中,output.viewId 映射到 SV_RenderTargetArrayIndex 语义。 这需要支持可选的 Direct3D 11.3 功能,该功能允许从任何着色器阶段设置呈现目标数组索引语义。

VPRTVertexShader.hlsl

// Per-vertex data passed to the geometry shader.
struct VertexShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;

    // The render target array index is set here in the vertex shader.
    uint        viewId  : SV_RenderTargetArrayIndex;
};

VertexShaderShared.hlsl,由 VPRTVertexShader.hlsl 包含:

// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput
{
    min16float3 pos     : POSITION;
    min16float3 color   : COLOR0;
    uint        instId  : SV_InstanceID;
};

// Simple shader to do vertex processing on the GPU.
VertexShaderOutput main(VertexShaderInput input)
{
    VertexShaderOutput output;
    float4 pos = float4(input.pos, 1.0f);

    // Note which view this vertex has been sent to. Used for matrix lookup.
    // Taking the modulo of the instance ID allows geometry instancing to be used
    // along with stereo instanced drawing; in that case, two copies of each 
    // instance would be drawn, one for left and one for right.
    int idx = input.instId % 2;

    // Transform the vertex position into world space.
    pos = mul(pos, model);

    // Correct for perspective and project the vertex position onto the screen.
    pos = mul(pos, viewProjection[idx]);
    output.pos = (min16float4)pos;

    // Pass the color through without modification.
    output.color = input.color;

    // Set the render target array index.
    output.viewId = idx;

    return output;
}

如果要将现有的实例化绘图技术与这种绘制到立体呈现目标数组的方法结合使用,请绘制通常具有的实例数的两倍。 在着色器中,将 input.instId 除以 2 以获取原始实例 ID,该 ID 可以索引到 (例如) 每个对象数据的缓冲区: int actualIdx = input.instId / 2;

有关在 HoloLens 上呈现立体声内容的重要说明

Windows Mixed Reality支持从任何着色器阶段设置呈现器目标数组索引的功能。 通常,这是一项只能在几何着色器阶段完成的任务,因为为 Direct3D 11 定义语义的方式。 在这里,我们演示了如何设置仅设置顶点和像素着色器阶段的呈现管道的完整示例。 着色器代码如上所述。

From SpinningCubeRenderer::Render

const auto context = m_deviceResources->GetD3DDeviceContext();

// Each vertex is one instance of the VertexPositionColor struct.
const UINT stride = sizeof(VertexPositionColor);
const UINT offset = 0;
context->IASetVertexBuffers(
    0,
    1,
    m_vertexBuffer.GetAddressOf(),
    &stride,
    &offset
);
context->IASetIndexBuffer(
    m_indexBuffer.Get(),
    DXGI_FORMAT_R16_UINT, // Each index is one 16-bit unsigned integer (short).
    0
);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->IASetInputLayout(m_inputLayout.Get());

// Attach the vertex shader.
context->VSSetShader(
    m_vertexShader.Get(),
    nullptr,
    0
);
// Apply the model constant buffer to the vertex shader.
context->VSSetConstantBuffers(
    0,
    1,
    m_modelConstantBuffer.GetAddressOf()
);

// Attach the pixel shader.
context->PSSetShader(
    m_pixelShader.Get(),
    nullptr,
    0
);

// Draw the objects.
context->DrawIndexedInstanced(
    m_indexCount,   // Index count per instance.
    2,              // Instance count.
    0,              // Start index location.
    0,              // Base vertex location.
    0               // Start instance location.
);

有关在非 HoloLens 设备上呈现的重要说明

在顶点着色器中设置呈现器目标数组索引需要图形驱动程序支持可选的 Direct3D 11.3 功能,HoloLens 支持此功能。 你的应用可以安全地实现这种呈现技术,并且满足在Microsoft HoloLens上运行的所有要求。

你可能还希望使用 HoloLens 仿真器,它可以是全息应用的强大开发工具,并支持连接到Windows 10电脑的Windows Mixed Reality沉浸式头戴显示设备。 Windows Holographic 应用模板中还内置了对非 HoloLens 呈现路径的支持(适用于所有Windows Mixed Reality)。 在模板代码中,你将找到使全息应用能够在开发电脑的 GPU 上运行的代码。 以下是 DeviceResources 类检查此可选功能支持的方式。

DeviceResources::CreateDeviceResources

// Check for device support for the optional feature that allows setting the render target array index from the vertex shader stage.
D3D11_FEATURE_DATA_D3D11_OPTIONS3 options;
m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS3, &options, sizeof(options));
if (options.VPAndRTArrayIndexFromAnyShaderFeedingRasterizer)
{
    m_supportsVprt = true;
}

若要支持在没有此可选功能的情况下进行呈现,你的应用必须使用几何着色器来设置呈现目标数组索引。 此代码片段将添加到 VSSetConstantBuffers之后PSSetShader 之前,如上一部分所示,该代码示例介绍如何在 HoloLens 上呈现立体声。

From SpinningCubeRenderer::Render

if (!m_usingVprtShaders)
{
    // On devices that do not support the D3D11_FEATURE_D3D11_OPTIONS3::
    // VPAndRTArrayIndexFromAnyShaderFeedingRasterizer optional feature,
    // a pass-through geometry shader is used to set the render target 
    // array index.
    context->GSSetShader(
        m_geometryShader.Get(),
        nullptr,
        0
    );
}

HLSL 注意:在这种情况下,还必须加载稍作修改的顶点着色器,该着色器使用始终允许的着色器语义(例如TEXCOORD0)将呈现目标数组索引传递到几何着色器。 几何着色器无需执行任何工作;模板几何图形着色器会传递所有数据,但呈现目标数组索引除外,该索引用于设置SV_RenderTargetArrayIndex语义。

GeometryShader.hlsl 的应用模板代码:

// Per-vertex data from the vertex shader.
struct GeometryShaderInput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint instId         : TEXCOORD0;
};

// Per-vertex data passed to the rasterizer.
struct GeometryShaderOutput
{
    min16float4 pos     : SV_POSITION;
    min16float3 color   : COLOR0;
    uint rtvId          : SV_RenderTargetArrayIndex;
};

// This geometry shader is a pass-through that leaves the geometry unmodified 
// and sets the render target array index.
[maxvertexcount(3)]
void main(triangle GeometryShaderInput input[3], inout TriangleStream<GeometryShaderOutput> outStream)
{
    GeometryShaderOutput output;
    [unroll(3)]
    for (int i = 0; i < 3; ++i)
    {
        output.pos   = input[i].pos;
        output.color = input[i].color;
        output.rtvId = input[i].instId;
        outStream.Append(output);
    }
}

目前

启用全息帧来显示交换链

使用Windows Mixed Reality,系统将控制交换链。 然后,系统管理向每个全息相机呈现帧,以确保高质量的用户体验。 它还为每个相机提供视区更新每个帧,以优化系统的各个方面,例如图像防抖动或混合现实捕获。 因此,使用 DirectX 的全息应用不会在 DXGI 交换链上调用 Present 。 相反,在绘制完框架后,可以使用 HolographicFrame 类来显示框架的所有交换链。

From DeviceResources::P resent

HolographicFramePresentResult presentResult = frame.PresentUsingCurrentPrediction();

默认情况下,此 API 等待帧完成,然后再返回。 全息应用应在开始处理新帧之前等待上一帧完成,因为这样可以降低延迟,并允许从全息帧预测获得更好的结果。 这不是硬性规则,如果帧需要超过一个屏幕刷新才能呈现,可以通过将 HolographicFramePresentWaitBehavior 参数传递给 PresentUsingCurrentPrediction 来禁用此等待。 在这种情况下,你可能会使用异步呈现线程来维护 GPU 上的连续负载。 HoloLens 设备的刷新率为 60 hz,其中一帧的持续时间约为 16 毫秒。 沉浸式头戴显示设备的范围可以从 60 hz 到 90 hz;以 90 hz 刷新屏幕时,每帧的持续时间约为 11 毫秒。

与全息帧合作处理 DeviceLost 方案

DirectX 11 应用通常需要检查 DXGI 交换链的 Present 函数返回的 HRESULT,以确定是否存在 DeviceLost 错误。 HolographicFrame 类会为你处理此问题。 检查返回的 HolographicFramePresentResult ,了解是否需要释放和重新创建 Direct3D 设备和基于设备的资源。

// The PresentUsingCurrentPrediction API will detect when the graphics device
// changes or becomes invalid. When this happens, it is considered a Direct3D
// device lost scenario.
if (presentResult == HolographicFramePresentResult::DeviceRemoved)
{
    // The Direct3D device, context, and resources should be recreated.
    HandleDeviceLost();
}

如果 Direct3D 设备丢失,并且你确实重新创建了它,则必须告知 HolographicSpace 开始使用新设备。 将为此设备重新创建交换链。

DeviceResources::InitializeUsingHolographicSpace

m_holographicSpace.SetDirect3D11Device(m_d3dInteropDevice);

显示帧后,可以返回到main程序循环,并允许它继续下一帧。

混合图形电脑和混合现实应用程序

Windows 10 创意者更新电脑可以同时配置离散 GPU 和集成 GPU。 对于这些类型的计算机,Windows 将选择头戴显示设备连接到的适配器。 应用程序必须确保它创建的 DirectX 设备使用相同的适配器。

大多数常规 Direct3D 示例代码演示如何使用默认硬件适配器创建 DirectX 设备,该适配器在混合系统上可能与头戴显示设备所用的硬件适配器不同。

若要解决任何问题,请使用 HolographicSpace 中的 HolographicAdapterID。PrimaryAdapterId () 或 HolographicDisplay。AdapterId () 。 然后,可以使用此 adapterId 使用 IDXGIFactory4.EnumAdapterByLuid 选择合适的 DXGIAdapter。

DeviceResources::InitializeUsingHolographicSpace

// The holographic space might need to determine which adapter supports
// holograms, in which case it will specify a non-zero PrimaryAdapterId.
LUID id =
{
    m_holographicSpace.PrimaryAdapterId().LowPart,
    m_holographicSpace.PrimaryAdapterId().HighPart
};

// When a primary adapter ID is given to the app, the app should find
// the corresponding DXGI adapter and use it to create Direct3D devices
// and device contexts. Otherwise, there is no restriction on the DXGI
// adapter the app can use.
if ((id.HighPart != 0) || (id.LowPart != 0))
{
    UINT createFlags = 0;

    // Create the DXGI factory.
    ComPtr<IDXGIFactory1> dxgiFactory;
    winrt::check_hresult(
        CreateDXGIFactory2(
            createFlags,
            IID_PPV_ARGS(&dxgiFactory)
        ));
    ComPtr<IDXGIFactory4> dxgiFactory4;
    winrt::check_hresult(dxgiFactory.As(&dxgiFactory4));

    // Retrieve the adapter specified by the holographic space.
    winrt::check_hresult(
        dxgiFactory4->EnumAdapterByLuid(
            id,
            IID_PPV_ARGS(&m_dxgiAdapter)
        ));
}
else
{
    m_dxgiAdapter.Reset();
}

DeviceResources::CreateDeviceResources 更新为使用 IDXGIAdapter 的代码

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

const D3D_DRIVER_TYPE driverType = m_dxgiAdapter == nullptr ? D3D_DRIVER_TYPE_HARDWARE : D3D_DRIVER_TYPE_UNKNOWN;
const HRESULT hr = D3D11CreateDevice(
    m_dxgiAdapter.Get(),        // Either nullptr, or the primary adapter determined by Windows Holographic.
    driverType,                 // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Windows Runtime apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
);

混合图形和媒体基础

在混合系统上使用 Media Foundation 可能会导致视频无法呈现或视频纹理损坏的问题,因为 Media Foundation 默认为系统行为。 在某些情况下,需要创建单独的 ID3D11Device 才能支持多线程处理,并设置正确的创建标志。

初始化 ID3D11Device 时,必须将D3D11_CREATE_DEVICE_VIDEO_SUPPORT标志定义为D3D11_CREATE_DEVICE_FLAG的一部分。 创建设备和上下文后,调用 SetMultithreadProtected 以启用多线程处理。 若要将设备与 IMFDXGIDeviceManager 相关联,请使用 IMFDXGIDeviceManager::ResetDevice 函数。

ID3D11Device 与 IMFDXGIDeviceManager 关联的代码:

// create dx device for media pipeline
winrt::com_ptr<ID3D11Device> spMediaDevice;

// See above. Also make sure to enable the following flags on the D3D11 device:
//   * D3D11_CREATE_DEVICE_VIDEO_SUPPORT
//   * D3D11_CREATE_DEVICE_BGRA_SUPPORT
if (FAILED(CreateMediaDevice(spAdapter.get(), &spMediaDevice)))
    return;                                                     

// Turn multithreading on 
winrt::com_ptr<ID3D10Multithread> spMultithread;
if (spContext.try_as(spMultithread))
{
    spMultithread->SetMultithreadProtected(TRUE);
}

// lock the shared dxgi device manager
// call MFUnlockDXGIDeviceManager when no longer needed
UINT uiResetToken;
winrt::com_ptr<IMFDXGIDeviceManager> spDeviceManager;
hr = MFLockDXGIDeviceManager(&uiResetToken, spDeviceManager.put());
if (FAILED(hr))
    return hr;
    
// associate the device with the manager
hr = spDeviceManager->ResetDevice(spMediaDevice.get(), uiResetToken);
if (FAILED(hr))
    return hr;

另请参阅