Associações de gráficos
Para usar o Azure Remote Rendering em um aplicativo personalizado, ele precisa ser integrado ao pipeline de renderização de aplicativo. O responsável por essa integração é a associação de gráficos.
Uma vez configurada, a associação de gráficos fornece acesso a várias funções que afetam a imagem renderizada. Essas funções podem ser separadas em duas categorias: funções gerais que estão sempre disponíveis e funções específicas que só são relevantes para o Microsoft.Azure.RemoteRendering.GraphicsApiType
selecionado.
Associação de gráficos no Unity
No Unity, toda a associação é manipulada pelo struct RemoteUnityClientInit
passado para RemoteManagerUnity.InitializeManager
. Para definir o modo de gráfico, o campo GraphicsApiType
deve ser definido como a associação escolhida. O campo será preenchido automaticamente dependendo da presença de um XRDevice. O comportamento pode ser substituído manualmente pelos seguintes comportamentos:
- HoloLens 2: a associação de gráficos OpenXR ou Realidade Misturada do Windows é usada dependendo do plug-in ativo do Unity XR.
- Aplicativo da área de trabalho simples da UWP: a simulação é sempre usada.
- Editor do Unity: a simulação é sempre usada, a menos que um headset de VR do WMR esteja conectado. Nesse caso, o ARR será desabilitado para permitir a depuração das partes do aplicativo não relacionadas ao ARR. Consulte também comunicação remota holográfica.
A única outra parte relevante para o Unity é o acesso à associação básica, e todas as outras seções abaixo podem ser ignoradas.
Configuração de associação de gráficos em aplicativos personalizados
Para selecionar uma associação de gráficos, execute as duas etapas a seguir: primeiro, a associação de gráficos precisa ser inicializada estaticamente quando o programa for 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 do Azure Remote Rendering seja acessada.
Da mesma maneira, a função RemoteManagerStatic.ShutdownRemoteRendering();
de desinicialização correspondente deverá ser chamada depois que todos os outros objetos do Azure Remote Rendering já estiverem destruídos.
Em WMR, StartupRemoteRendering
também deverá ser chamado antes de qualquer API Holographic ser chamada. Em OpenXR, o mesmo se aplica a quaisquer APIs relacionadas ao OpenXR.
Acessar associação de gráficos
Depois que um cliente é configurado, a associação básica de gráficos pode ser acessada com o getter RenderingSession.GraphicsBinding
. Por 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: OpenXrD3D11
, WmrD3D11
e SimD3D11
. Há uma quarta Headless
, mas ela ainda não tem suporte no lado do cliente.
OpenXR
GraphicsApiType.OpenXrD3D11
é a associação padrão para executar no HoloLens 2. Ela criará a associação GraphicsBindingOpenXrD3d11
. Nesse modo, o Azure Remote Rendering cria uma camada de API do OpenXR para se integrar ao runtime do OpenXR.
Para acessar as associações de gráficos derivadas, o GraphicsBinding
de base precisa ser convertido.
Há três ações que precisam ser executadas para usar a associação OpenXR:
JSON de camada de OpenXR personalizada do pacote
Para usar o Azure Remote Rendering com OpenXR, a camada de API OpenXR personalizada deve ser ativada. Isso é feito chamando-se StartupRemoteRendering
mencionado na seção anterior. No entanto, como um pré-requisito, o XrApiLayer_msft_holographic_remoting.json
deve ser empacotado com o aplicativo para que ele seja carregado. Isso será feito automaticamente se o pacote NuGet "Microsoft.Azure.RemoteRendering.Cpp" for adicionado a um projeto.
Informar o Azure Remote Rendering do XR Space usado
Isso é necessário para alinhar conteúdo renderizado remoto e local.
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 XrSpace
acima é usado pelo aplicativo que define o sistema de coordenadas de 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 de fundo. Para isso, é feita a chamada de BlitRemoteFrame
, que preencherá as informações de cor e de profundidade para ambos os olhos no destino de renderização atualmente associado. Portanto, é importante fazer isso após associar o buffer de fundo completo como um destino de renderização.
Aviso
Depois que a imagem remota foi transferida no buffer de fundo, o conteúdo local deve ser renderizado usando uma técnica de renderização estéreo de passagem única, 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 na degradação do desempenho ou artefatos gráficos importantes, 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 associação de gráficos usada anteriormente em execução no HoloLens 2. Ela criará a associação GraphicsBindingWmrD3d11
. Nesse modo, o Azure Remote Rendering se conecta diretamente às APIs holográficas.
Para acessar as associações de gráficos derivadas, o GraphicsBinding
de base precisa ser convertido.
Há duas ações que precisam ser efetuadas pra usar a associação WRM:
Informar o Remote Rendering do sistema de coordenadas usado
Isso é necessário para alinhar conteúdo renderizado remoto e local.
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 ptr
acima deve ser um ponteiro para um objeto ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem
nativo que define o sistema de coordenadas de espaço mundial no qual as coordenadas na API são expressas.
Renderizar imagem remota (WMR)
As mesmas considerações como no caso de OpenXR acima se aplicam aqui. As chamadas à API têm a seguinte 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 associação de simulação. Se ela for selecionada, criará a associação de gráficos GraphicsBindingSimD3d11
. Essa interface é usada para simular o movimento da cabeça, por exemplo, em um aplicativo de área de trabalho e renderiza uma imagem monoscópica.
Para implementar a associaçã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.
Duas câmeras são necessárias:
- Câmera local: essa câmera representa a posição atual da câmera que é controlada pela lógica de aplicativo.
- Câmera proxy: essa câmera corresponde ao Quadro Remoto atual que foi enviado pelo servidor. Como há um atraso de tempo entre a solicitação pelo cliente de um quadro e sua chegada, o Quadro Remoto está sempre um pouco atrasado na movimentação da câmera local.
A abordagem básica aqui é que a imagem remota e o conteúdo local são renderizados em um destino fora da tela usando a câmera proxy. Em seguida, a imagem de proxy é reprojetada no espaço da câmera local, o que é explicado mais adiante na reprodução de estágio tardio.
GraphicsApiType.SimD3D11
também dá suporte à renderização estereotipada, que precisa ser habilitada durante a chamada de instalação InitSimulation
abaixo. A configuração é um pouco mais envolvente e funciona da seguinte maneira:
Criar destino de renderização de proxy
O conteúdo local e remoto precisa ser renderizado para um destino de renderização de cor/profundidade fora da tela, chamado de “proxy” e que usa dados de câmera de proxy fornecidos pela função GraphicsBindingSimD3d11.Update
.
O proxy deve corresponder à resolução do buffer de fundo e deve estar no formato DXGI_FORMAT_R8G8B8A8_UNORM ou DXGI_FORMAT_B8G8R8A8_UNORM. No caso da renderização estereotipada, a textura do proxy de cor e, se a profundidade for usada, a textura do proxy de profundidade, precisarão ter duas camadas de matriz em vez de uma. Quando uma sessão estiver pronta, GraphicsBindingSimD3d11.InitSimulation
precisará ser chamado 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 d3d-device nativo, bem como para a textura de cores e de profundidade do destino de renderização de proxy. Depois de inicializados, RenderingSession.ConnectAsync
e Disconnect
podem ser chamados várias vezes, mas, ao alternar para uma sessão diferente, GraphicsBindingSimD3d11.DeinitSimulation
precisa ser chamado primeiro na sessão antiga antes de chamar GraphicsBindingSimD3d11.InitSimulation
na outra sessão.
Atualização do loop de renderização
A atualização do loop de renderização consiste em várias etapas:
- Cada quadro, antes de qualquer renderização,
GraphicsBindingSimD3d11.Update
é chamado com a transformação da 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 ser renderizada no destino de renderização de proxy. Se a atualização de proxy retornadaSimulationUpdate.frameId
for nula, ainda não haverá dados remotos. Nesse caso, todo conteúdo local deve ser renderizado no buffer de fundo, em vez de no destino de renderização de proxy, usando diretamente os dados atuais da câmera, e as duas próximas etapas são ignoradas. - O aplicativo agora deve associar o destino de renderização de proxy e chamar
GraphicsBindingSimD3d11.BlitRemoteFrameToProxy
. Isso preencherá as informações de cor e profundidade remotas no destino de renderização de proxy. Todo conteúdo local agora pode ser renderizado no proxy usando a transformação de câmera de proxy. - Em seguida, o buffer de fundo precisa ser associado como um destino de renderização e
GraphicsBindingSimD3d11.ReprojectProxy
ser chamado no ponto em que o buffer de fundo 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
Em cada quadro, a Atualização do loop de renderização da seção anterior, exige que você institua um intervalo de parâmetros de câmera correspondentes à câmera local, e retorne um conjunto de parâmetros de câmera que corresponda à câmera do próximo quadro disponível. Esses dois conjuntos são capturados nas estruturas SimulationUpdateParameters
e SimulationUpdateResult
, 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 | Descrição |
---|---|
FrameId | Identificador de quadro contínuo. Necessário para a entrada SimulationUpdateParameters e precisa ser incrementado continuamente para cada quadro novo. Será 0 em SimulationUpdateResult se nenhum dado de quadro ainda estiver disponível. |
ViewTransform | Par esquerdo-direito-estéreo das matrizes de transformação da exibição de câmera do quadro. Para a renderização monoscópica, somente o membro Left é válido. |
FieldOfView | Par esquerdo-direito-estéreo dos campos de exibição da câmera do quadro no Campo OpenXR da convenção de exibição. Para a renderização monoscópica, somente o membro Left é válido. |
NearPlaneDistance | distância do plano próximo usada para a matriz de projeção do quadro remoto atual. |
FarPlaneDistance | distância do plano distante usada para a matriz de projeção do quadro remoto atual. |
Os pares estéreo ViewTransform
e FieldOfView
permitem a configuração dos valores de olho-câmera, caso a renderização estereoscópica esteja habilitada. Caso contrário, os membros Right
serão ignorados. Como você pode ver, somente a transformação da câmera é aprovada como matrizes de transformação 4x4 simples, enquanto nenhuma matriz de projeção é especificada. As matrizes reais são calculadas pelo Azure Remote Rendering internamente usando os campos de exibição especificados e o conjunto atual de próximos e distantes na API de CameraSettings.
Como você pode alterar os planos próximo e distante no CameraSettings durante o runtime conforme desejado e o serviço aplica essas configurações de forma assíncrona, cada SimulationUpdateResult também carrega os planos próximo e distante 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 a fim de corresponder à renderização de quadro remoto.
Finalmente, embora a chamada de Atualização de Simulação exija o campo de exibição na convenção OpenXR, por motivos de padronização e segurança de algoritmo, você pode usar as funções de conversão ilustradas nos exemplos de população de estrutura a seguir:
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 a troca rápida entre a especificação do campo de exibição e uma matriz de projeção de perspectiva 4x4 simples, dependendo das 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 os campos de exibição de entrada sejam inválidos.
Documentação da API
- C# RemoteManagerStatic.StartupRemoteRendering()
- Classe C# GraphicsBinding
- Classe C# GraphicsBindingWmrD3d11
- Classe C# GraphicsBindingSimD3d11
- Struct C++ RemoteRenderingInitialization
- Classe C++ GraphicsBinding
- Classe C++ GraphicsBindingWmrD3d11
- Classe C++ GraphicsBindingSimD3d11