Partilhar via


Vinculação de gráficos

Para poder usar a Renderização Remota do Azure em um aplicativo personalizado, ela precisa ser integrada ao pipeline de renderização do aplicativo. Esta integração é da responsabilidade da ligação gráfica.

Uma vez configurada, a ligação gráfica dá acesso a várias funções que afetam a imagem renderizada. Estas funções podem ser separadas em duas categorias: funções gerais que estão sempre disponíveis e funções específicas que são relevantes apenas para o selecionado Microsoft.Azure.RemoteRendering.GraphicsApiType.

Vinculação de gráficos no Unity

No Unity, toda a RemoteUnityClientInit ligação é tratada pela struct passada para RemoteManagerUnity.InitializeManager. Para definir o modo gráfico, o GraphicsApiType campo tem de ser definido para a ligação escolhida. O campo será preenchido automaticamente dependendo da presença de um XRDevice. O comportamento pode ser substituído manualmente com os seguintes comportamentos:

A única outra parte relevante para Unity é acessar a vinculação básica, todas as outras seções abaixo podem ser ignoradas.

Configuração de vinculação de gráficos em aplicativos personalizados

Para selecionar uma associação gráfica, execute as duas etapas a seguir: Primeiro, a ligação gráfica deve ser inicializada estaticamente quando o programa é inicializado:

RemoteRenderingInitialization managerInit = new RemoteRenderingInitialization();
managerInit.GraphicsApi = GraphicsApiType.OpenXrD3D11;
managerInit.ConnectionType = ConnectionType.General;
managerInit.Right = ///...
RemoteManagerStatic.StartupRemoteRendering(managerInit);
RemoteRenderingInitialization managerInit;
managerInit.GraphicsApi = GraphicsApiType::OpenXrD3D11;
managerInit.ConnectionType = ConnectionType::General;
managerInit.Right = ///...
StartupRemoteRendering(managerInit); // static function in namespace Microsoft::Azure::RemoteRendering

A chamada acima deve ser chamada antes que qualquer outra API de renderização remota seja acessada. Da mesma forma, a função RemoteManagerStatic.ShutdownRemoteRendering(); de-init correspondente deve ser chamada depois que todos os outros objetos de renderização remota já estiverem destruídos. Para WMR StartupRemoteRendering também precisa ser chamado antes de qualquer API holográfica é chamada. Para OpenXR o mesmo se aplica a quaisquer APIs relacionadas ao OpenXR.

Acessando a vinculação de gráficos

Uma vez que um cliente é configurado, a ligação gráfica básica pode ser acessada com o RenderingSession.GraphicsBinding getter. Como exemplo, as estatísticas do último quadro podem ser recuperadas assim:

RenderingSession currentSession = ...;
if (currentSession.GraphicsBinding != null)
{
    FrameStatistics frameStatistics;
    if (currentSession.GraphicsBinding.GetLastFrameStatistics(out frameStatistics) == Result.Success)
    {
        ...
    }
}
ApiHandle<RenderingSession> currentSession = ...;
if (ApiHandle<GraphicsBinding> binding = currentSession->GetGraphicsBinding())
{
    FrameStatistics frameStatistics;
    if (binding->GetLastFrameStatistics(&frameStatistics) == Result::Success)
    {
        ...
    }
}

APIs gráficas

Atualmente, há três APIs gráficas que podem ser selecionadas, OpenXrD3D11WmrD3D11 e SimD3D11. Um quarto Headless existe, mas ainda não é suportado no lado do cliente.

OpenXR

GraphicsApiType.OpenXrD3D11 é a ligação padrão para ser executada no HoloLens 2. Criará a GraphicsBindingOpenXrD3d11 vinculação. Neste modo, a Renderização Remota do Azure cria uma camada de API OpenXR para se integrar ao tempo de execução do OpenXR.

Para acessar as ligações gráficas derivadas, a base GraphicsBinding deve ser lançada. Há três coisas que precisam ser feitas para usar a ligação OpenXR:

Pacote personalizado OpenXR layer json

Para usar a renderização remota com OpenXR, a camada de API OpenXR personalizada precisa ser ativada. Isso é feito por meio de chamadas StartupRemoteRendering mencionadas na seção anterior. No entanto, como pré-requisito, o XrApiLayer_msft_holographic_remoting.json aplicativo precisa ser empacotado com o aplicativo para que ele possa ser carregado. Isso é feito automaticamente se o pacote NuGet "Microsoft.Azure.RemoteRendering.Cpp" for adicionado a um projeto.

Informar a renderização remota do espaço XR usado

Isso é necessário para alinhar o conteúdo remoto e renderizado localmente.

RenderingSession currentSession = ...;
ulong space = ...; // XrSpace cast to ulong
GraphicsBindingOpenXrD3d11 openXrBinding = (currentSession.GraphicsBinding as GraphicsBindingOpenXrD3d11);
if (openXrBinding.UpdateAppSpace(space) == Result.Success)
{
    ...
}
ApiHandle<RenderingSession> currentSession = ...;
XrSpace space = ...;
ApiHandle<GraphicsBindingOpenXrD3d11> openXrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingOpenXrD3d11>();
#ifdef _M_ARM64
    if (openXrBinding->UpdateAppSpace(reinterpret_cast<uint64_t>(space)) == Result::Success)
#else
    if (openXrBinding->UpdateAppSpace(space) == Result::Success)
#endif
{
    ...
}

Onde o acima XrSpace é o usado pelo aplicativo que define o sistema de coordenadas do espaço mundial no qual as coordenadas na API são expressas.

Renderizar imagem remota (OpenXR)

No início de cada quadro, o quadro remoto precisa ser renderizado no buffer traseiro. Isso é feito chamando BlitRemoteFrame, que preencherá as informações de cor e profundidade para ambos os olhos no destino de renderização vinculado no momento. Assim, é importante fazê-lo depois de vincular o buffer de retorno completo como um destino de renderização.

Aviso

Depois que a imagem remota foi gravada no backbuffer, o conteúdo local deve ser renderizado usando uma técnica de renderização estéreo de uma única passagem, por exemplo, usando SV_RenderTargetArrayIndex. O uso de outras técnicas de renderização estéreo, como renderizar cada olho em uma passagem separada, pode resultar em grande degradação de desempenho ou artefatos gráficos e deve ser evitado.

RenderingSession currentSession = ...;
GraphicsBindingOpenXrD3d11 openXrBinding = (currentSession.GraphicsBinding as GraphicsBindingOpenXrD3d11);
openXrBinding.BlitRemoteFrame();
ApiHandle<RenderingSession> currentSession = ...;
ApiHandle<GraphicsBindingOpenXrD3d11> openXrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingOpenXrD3d11>();
openXrBinding->BlitRemoteFrame();

Windows Mixed Reality

GraphicsApiType.WmrD3D11 é a ligação gráfica usada anteriormente para ser executada no HoloLens 2. Criará a GraphicsBindingWmrD3d11 vinculação. Neste modo, a Renderização Remota do Azure se conecta diretamente às APIs holográficas.

Para acessar as ligações gráficas derivadas, a base GraphicsBinding deve ser lançada. Há duas coisas que precisam ser feitas para usar a ligação WMR:

Informar a renderização remota do sistema de coordenadas usado

Isso é necessário para alinhar o conteúdo remoto e renderizado localmente.

RenderingSession currentSession = ...;
IntPtr ptr = ...; // native pointer to ISpatialCoordinateSystem
GraphicsBindingWmrD3d11 wmrBinding = (currentSession.GraphicsBinding as GraphicsBindingWmrD3d11);
if (wmrBinding.UpdateUserCoordinateSystem(ptr) == Result.Success)
{
    ...
}
ApiHandle<RenderingSession> currentSession = ...;
void* ptr = ...; // native pointer to ISpatialCoordinateSystem
ApiHandle<GraphicsBindingWmrD3d11> wmrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingWmrD3d11>();
if (wmrBinding->UpdateUserCoordinateSystem(ptr) == Result::Success)
{
    ...
}

Onde o acima ptr deve ser um ponteiro para um objeto nativo ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem que define o sistema de coordenadas do espaço mundial no qual as coordenadas na API são expressas.

Renderizar imagem remota (WMR)

As mesmas considerações que no caso OpenXR acima se aplicam aqui. As chamadas de API têm esta aparência:

RenderingSession currentSession = ...;
GraphicsBindingWmrD3d11 wmrBinding = (currentSession.GraphicsBinding as GraphicsBindingWmrD3d11);
wmrBinding.BlitRemoteFrame();
ApiHandle<RenderingSession> currentSession = ...;
ApiHandle<GraphicsBindingWmrD3d11> wmrBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingWmrD3d11>();
wmrBinding->BlitRemoteFrame();

Simulação

GraphicsApiType.SimD3D11 é a ligação de simulação e, se selecionada, cria a GraphicsBindingSimD3d11 ligação gráfica. Esta interface é usada para simular o movimento da cabeça, por exemplo, em um aplicativo de desktop e renderiza uma imagem monoscópica.

Para implementar a ligação de simulação, é importante entender a diferença entre a câmera local e o quadro remoto, conforme descrito na página da câmera .

São necessárias duas câmaras:

  • Câmera local: esta câmera representa a posição atual da câmera que é impulsionada pela lógica do aplicativo.
  • Câmera proxy: Esta câmera corresponde ao quadro remoto atual que foi enviado pelo servidor. Como há um atraso de tempo entre o cliente solicitando um quadro e sua chegada, o quadro remoto está sempre um pouco atrás do movimento da câmera local.

A abordagem básica aqui é que tanto a imagem remota quanto o conteúdo local são renderizados em um alvo fora da tela usando a câmera proxy. A imagem proxy é então reprojetada no espaço da câmera local, o que é explicado na reprojeção de estágio tardio.

GraphicsApiType.SimD3D11 também suporta renderização estereoscópica, que precisa ser ativada durante a chamada de InitSimulation configuração abaixo. A configuração é um pouco mais envolvida e funciona da seguinte forma:

Criar destino de renderização de proxy

O conteúdo remoto e local precisa ser renderizado para um alvo de renderização de cor / profundidade fora da tela chamado 'proxy' usando os dados da GraphicsBindingSimD3d11.Update câmera proxy fornecidos pela função.

O proxy deve corresponder à resolução do buffer traseiro e deve estar no formato DXGI_FORMAT_R8G8B8A8_UNORM ou DXGI_FORMAT_B8G8R8A8_UNORM . No caso da renderização estereoscópica, tanto a textura do proxy de cor e, se a profundidade for usada, a textura do proxy de profundidade precisam ter duas camadas de matriz em vez de uma. Quando uma sessão estiver pronta, GraphicsBindingSimD3d11.InitSimulation precisa ser chamada antes de se conectar a ela:

RenderingSession currentSession = ...;
IntPtr d3dDevice = ...; // native pointer to ID3D11Device
IntPtr color = ...; // native pointer to ID3D11Texture2D
IntPtr depth = ...; // native pointer to ID3D11Texture2D
float refreshRate = 60.0f; // Monitor refresh rate up to 60hz.
bool flipBlitRemoteFrameTextureVertically = false;
bool flipReprojectTextureVertically = false;
bool stereoscopicRendering = false;
GraphicsBindingSimD3d11 simBinding = (currentSession.GraphicsBinding as GraphicsBindingSimD3d11);
simBinding.InitSimulation(d3dDevice, depth, color, refreshRate, flipBlitRemoteFrameTextureVertically, flipReprojectTextureVertically, stereoscopicRendering);
ApiHandle<RenderingSession> currentSession = ...;
void* d3dDevice = ...; // native pointer to ID3D11Device
void* color = ...; // native pointer to ID3D11Texture2D
void* depth = ...; // native pointer to ID3D11Texture2D
float refreshRate = 60.0f; // Monitor refresh rate up to 60hz.
bool flipBlitRemoteFrameTextureVertically = false;
bool flipReprojectTextureVertically = false;
bool stereoscopicRendering = false;
ApiHandle<GraphicsBindingSimD3d11> simBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingSimD3d11>();
simBinding->InitSimulation(d3dDevice, depth, color, refreshRate, flipBlitRemoteFrameTextureVertically, flipReprojectTextureVertically, stereoscopicRendering);

A função init precisa ser fornecida com ponteiros para o dispositivo d3d nativo, bem como para a textura de cor e profundidade do destino de renderização do proxy. Uma vez inicializado, e Disconnect pode ser chamado várias vezes, RenderingSession.ConnectAsync mas ao mudar para uma sessão diferente, GraphicsBindingSimD3d11.DeinitSimulation precisa ser chamado primeiro na sessão antiga antes GraphicsBindingSimD3d11.InitSimulation de poder ser chamado em outra sessão.

Atualização de loop de renderização

A atualização do loop de renderização consiste em várias etapas:

  1. Cada quadro, antes de qualquer renderização ocorrer, GraphicsBindingSimD3d11.Update é chamado com a transformação de câmera atual que é enviada para o servidor a ser renderizado. Ao mesmo tempo, a transformação de proxy retornada deve ser aplicada à câmera de proxy para renderizar no destino de renderização de proxy. Se a atualização SimulationUpdate.frameId de proxy retornada for nula, ainda não há dados remotos. Nesse caso, em vez de renderizar no destino de renderização do proxy, qualquer conteúdo local deve ser renderizado para o buffer traseiro diretamente usando os dados atuais da câmera e as próximas duas etapas são ignoradas.
  2. O aplicativo agora deve vincular o destino de renderização de proxy e chamar GraphicsBindingSimD3d11.BlitRemoteFrameToProxy. Isso preencherá as informações remotas de cor e profundidade no destino de renderização do proxy. Qualquer conteúdo local agora pode ser renderizado no proxy usando a transformação da câmera proxy.
  3. Em seguida, o buffer de retorno precisa ser vinculado como um destino de renderização e GraphicsBindingSimD3d11.ReprojectProxy chamado em que ponto o buffer de retorno pode ser apresentado.
RenderingSession currentSession = ...;
GraphicsBindingSimD3d11 simBinding = (currentSession.GraphicsBinding as GraphicsBindingSimD3d11);
SimulationUpdateParameters updateParameters = new SimulationUpdateParameters();
// Fill out camera data with current camera data
// (see "Simulation Update structures" section below)
...
SimulationUpdateResult updateResult = new SimulationUpdateResult();
simBinding.Update(updateParameters, out updateResult);
// Is the frame data valid?
if (updateResult.FrameId != 0)
{
    // Bind proxy render target
    simBinding.BlitRemoteFrameToProxy();
    // Use proxy camera data to render local content
    ...
    // Bind back buffer
    simBinding.ReprojectProxy();
}
else
{
    // Bind back buffer
    // Use current camera data to render local content
    ...
}
ApiHandle<RenderingSession> currentSession;
ApiHandle<GraphicsBindingSimD3d11> simBinding = currentSession->GetGraphicsBinding().as<GraphicsBindingSimD3d11>();

SimulationUpdateParameters updateParameters;
// Fill out camera data with current camera data
// (see "Simulation Update structures" section below)
...
SimulationUpdateResult updateResult;
simBinding->Update(updateParameters, &updateResult);
// Is the frame data valid?
if (updateResult.FrameId != 0)
{
    // Bind proxy render target
    simBinding->BlitRemoteFrameToProxy();
    // Use proxy camera data to render local content
    ...
    // Bind back buffer
    simBinding->ReprojectProxy();
}
else
{
    // Bind back buffer
    // Use current camera data to render local content
    ...
}

Estruturas de atualização de simulação

Cada quadro, a atualização do loop de renderização da seção anterior requer que você insira uma gama de parâmetros da câmera correspondentes à câmera local e retorna um conjunto de parâmetros da câmera que correspondem à próxima câmera do quadro disponível. Estes dois conjuntos são capturados SimulationUpdateParameters no e nas SimulationUpdateResult estruturas, respectivamente:

public struct SimulationUpdateParameters
{
    public int FrameId;
    public StereoMatrix4x4 ViewTransform;
    public StereoCameraFov FieldOfView;
};

public struct SimulationUpdateResult
{
    public int FrameId;
    public float NearPlaneDistance;
    public float FarPlaneDistance;
    public StereoMatrix4x4 ViewTransform;
    public StereoCameraFov FieldOfView;
};

Os membros da estrutura têm o seguinte significado:

Membro Description
FrameId Identificador de quadro contínuo. Necessário para a entrada SimulationUpdateParameters e precisa ser continuamente incrementado para cada novo quadro. Será 0 em SimulationUpdateResult se ainda não houver dados de quadro disponíveis.
ViewTransform Par estéreo esquerda-direita-estéreo das matrizes de transformação da visão da câmara do quadro. Para renderização monoscópica, apenas o Left membro é válido.
Campo de visão Par estéreo esquerda-direita-de-campos de visão da câmara de fotogramas na convenção de campo de visão OpenXR. Para renderização monoscópica, apenas o Left membro é válido.
NearPlaneDistance distância plana próxima usada para a matriz de projeção do quadro remoto atual.
FarPlaneDistance distância de plano distante usada para a matriz de projeção do quadro remoto atual.

Os pares estéreo e FieldOfView permitem definir ambos os valores da câmera ocular no caso de ViewTransform a renderização estereoscópica estar ativada. Caso contrário, os Right membros serão ignorados. Como você pode ver, apenas a transformação da câmera é passada como matrizes de transformação 4x4 simples, enquanto nenhuma matriz de projeção é especificada. As matrizes reais são calculadas pela Renderização Remota do Azure internamente usando os campos de exibição especificados e o conjunto atual de plano próximo e plano distante na API CameraSettings.

Como você pode alterar o plano próximo e o plano distante nas CameraSettings durante o tempo de execução, conforme desejado, e o serviço aplica essas configurações de forma assíncrona, cada SimulationUpdateResult também carrega o plano próximo e o plano distante específicos usados durante a renderização do quadro correspondente. Você pode usar esses valores de plano para adaptar suas matrizes de projeção para renderizar objetos locais para corresponder à renderização remota de quadros.

Finalmente, embora a chamada Simulation Update exija o campo de visão na convenção OpenXR, por motivos de padronização e segurança algorítmica, você pode usar as funções de conversão ilustradas nos seguintes exemplos de população de estrutura:

public SimulationUpdateParameters CreateSimulationUpdateParameters(int frameId, Matrix4x4 viewTransform, Matrix4x4 projectionMatrix)
{
    SimulationUpdateParameters parameters = default;
    parameters.FrameId = frameId;
    parameters.ViewTransform.Left = viewTransform;
    if (parameters.FieldOfView.Left.FromProjectionMatrix(projectionMatrix) != Result.Success)
    {
        // Invalid projection matrix
        throw new ArgumentException("Invalid projection settings");
    }
    return parameters;
}

public void GetCameraSettingsFromSimulationUpdateResult(SimulationUpdateResult result, out Matrix4x4 projectionMatrix, out Matrix4x4 viewTransform, out int frameId)
{
    projectionMatrix = default;
    viewTransform = default;
    frameId = 0;

    if (result.FrameId == 0)
    {
        // Invalid frame data
        return;
    }

    // Use the screenspace depth convention you expect for your projection matrix locally
    if (result.FieldOfView.Left.ToProjectionMatrix(result.NearPlaneDistance, result.FarPlaneDistance, DepthConvention.ZeroToOne, out projectionMatrix) != Result.Success)
    {
        // Invalid field-of-view
        return;
    }
    viewTransform = result.ViewTransform.Left;
    frameId = result.FrameId;
}
SimulationUpdateParameters CreateSimulationUpdateParameters(uint32_t frameId, Matrix4x4 viewTransform, Matrix4x4 projectionMatrix)
{
    SimulationUpdateParameters parameters;
    parameters.FrameId = frameId;
    parameters.ViewTransform.Left = viewTransform;
    if (FovFromProjectionMatrix(projectionMatrix, parameters.FieldOfView.Left) != Result::Success)
    {
        // Invalid projection matrix
        return {};
    }
    return parameters;
}

void GetCameraSettingsFromSimulationUpdateResult(const SimulationUpdateResult& result, Matrix4x4& projectionMatrix, Matrix4x4& viewTransform, uint32_t& frameId)
{
    if (result.FrameId == 0)
    {
        // Invalid frame data
        return;
    }

    // Use the screenspace depth convention you expect for your projection matrix locally
    if (FovToProjectionMatrix(result.FieldOfView.Left, result.NearPlaneDistance, result.FarPlaneDistance, DepthConvention::ZeroToOne, projectionMatrix) != Result::Success)
    {
        // Invalid field-of-view
        return;
    }
    viewTransform = result.ViewTransform.Left;
    frameId = result.FrameId;
}

Essas funções de conversão permitem alternar rapidamente entre a especificação do campo de visão e uma matriz de projeção de perspetiva 4x4 simples, dependendo de suas necessidades de renderização local. Essas funções de conversão contêm lógica de verificação e retornarão erros, sem definir um resultado válido, caso as matrizes de projeção de entrada ou campos de visão de entrada sejam inválidos.

Documentação da API

Próximos passos