Binding di grafica

Per poter usare Rendering remoto di Azure in un'applicazione personalizzata, è necessario integrare questo servizio nella pipeline di rendering dell'applicazione. Questa integrazione è compito del binding di grafica.

Dopo che è stato configurato, il binding di grafica consente l'accesso a varie funzioni che interessano l'immagine di cui è stato eseguito il rendering. È possibile dividere queste funzioni in due categorie: funzioni generali sempre disponibili e funzioni specifiche pertinenti solo per l'API Microsoft.Azure.RemoteRendering.GraphicsApiType selezionata.

Binding di grafica in Unity

In Unity il binding viene interamente gestito dallo struct RemoteUnityClientInit passato in RemoteManagerUnity.InitializeManager. Per impostare la modalità di grafica, è necessario impostare il campo GraphicsApiType sul binding scelto. Il campo verrà popolato automaticamente a seconda della presenza di un XRDevice. È possibile eseguire l'override del comportamento manualmente con i comportamenti seguenti:

L'unica altra parte pertinente per Unity è l'accesso al binding di base. Tutte le altre sezioni riportate di seguito possono essere ignorate.

Configurazione del binding di grafica nelle applicazioni personalizzate

Per selezionare un binding grafico, eseguire i due passaggi seguenti: Innanzitutto, l'associazione grafica deve essere inizializzata in modo statico quando il programma viene inizializzato:

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

La chiamata precedente deve essere chiamata prima di accedere a qualsiasi altra API Rendering remoto. Analogamente, la funzione RemoteManagerStatic.ShutdownRemoteRendering(); de-init corrispondente deve essere chiamata dopo che tutti gli altri oggetti Rendering remoto sono già stati eliminati definitivamente. Per WMR StartupRemoteRendering è necessario chiamare anche prima di chiamare qualsiasi API olografica. Per OpenXR lo stesso vale per qualsiasi API correlata a OpenXR.

Accesso al binding di grafica

Dopo la configurazione di un client, è possibile accedere al binding di grafica di base con il getter RenderingSession.GraphicsBinding. È ad esempio possibile recuperare le statistiche dell'ultimo fotogramma come segue:

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)
    {
        ...
    }
}

API di grafica

Attualmente sono disponibili tre API grafiche che possono essere selezionate, OpenXrD3D11e WmrD3D11 SimD3D11. Esiste un quarto Headless ma non è ancora supportato sul lato client.

OpenXR

GraphicsApiType.OpenXrD3D11 è il binding predefinito da eseguire in HoloLens 2 e crea il binding GraphicsBindingOpenXrD3d11. In questa modalità Azure Rendering remoto crea un livello API OpenXR per integrarsi nel runtime OpenXR.

Per accedere ai binding di grafica derivati, è necessario eseguire il cast di GraphicsBinding di base. È necessario eseguire tre operazioni per usare l'associazione OpenXR:

Package custom OpenXR layer json

Per usare Rendering remoto con OpenXR, è necessario attivare il livello API OpenXR personalizzato. Questa operazione viene eseguita chiamando StartupRemoteRendering menzionato nella sezione precedente. Tuttavia, come prerequisito, è XrApiLayer_msft_holographic_remoting.json necessario creare un pacchetto con l'applicazione in modo che possa essere caricato. Questa operazione viene eseguita automaticamente se il pacchetto NuGet "Microsoft.Azure.RemoteRendering.Cpp" viene aggiunto a un progetto.

Informare Rendering remoto dello spazio XR usato

Questa operazione è necessaria per allineare il contenuto remoto e sottoposto a rendering locale.

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
{
    ...
}

Dove il precedente XrSpace è quello usato dall'applicazione che definisce il sistema di coordinate dello spazio globale in cui sono espresse le coordinate nell'API.

Eseguire il rendering dell'immagine remota (OpenXR)

All'inizio di ogni frame, il rendering del frame remoto deve essere eseguito nel buffer nascosto. Questa operazione viene eseguita chiamando BlitRemoteFrame, che riempie sia le informazioni di colore che di profondità per entrambi gli occhi nella destinazione di rendering attualmente associata. È quindi importante eseguire questa operazione dopo l'associazione del buffer back completo come destinazione di rendering.

Avviso

Dopo che l'immagine remota è stata inserita nel backbuffer, il rendering del contenuto locale deve essere eseguito usando una tecnica di rendering stereo a passaggio singolo, ad esempio usando SV_RenderTargetArrayIndex. L'uso di altre tecniche di rendering stereo, ad esempio il rendering di ogni occhio in un passaggio separato, può comportare una riduzione significativa delle prestazioni o artefatti grafici ed essere evitati.

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 è l'associazione grafica usata in precedenza per l'esecuzione in HoloLens 2. e crea il binding GraphicsBindingWmrD3d11. In questa modalità, Rendering remoto di Azure si collega direttamente alle API olografiche.

Per accedere ai binding di grafica derivati, è necessario eseguire il cast di GraphicsBinding di base. Per usare il binding Windows Mixed Reality è necessario eseguire due operazioni:

Informare Rendering remoto del sistema di coordinate usato

Questa operazione è necessaria per allineare il contenuto remoto e sottoposto a rendering locale.

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)
{
    ...
}

Dove ptr qui sopra deve essere un puntatore a un oggetto ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem nativo che definisce il sistema di coordinate dello spazio globale in cui sono espresse le coordinate nell'API.

Eseguire il rendering dell'immagine remota (WMR)

Le stesse considerazioni del caso OpenXR precedente si applicano qui. Le chiamate API hanno un aspetto simile al seguente:

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

Simulazione

GraphicsApiType.SimD3D11 è il binding della simulazione e se selezionato crea il binding di grafica GraphicsBindingSimD3d11. Questa interfaccia viene usata per simulare il movimento della testa, ad esempio in un'applicazione desktop, ed esegue il rendering di un'immagine monoscopica.

Per implementare l'associazione di simulazione, è importante comprendere la differenza tra la fotocamera locale e il fotogramma remoto, come descritto nella pagina della fotocamera .

Sono necessarie due fotocamere:

  • Fotocamera locale: questa fotocamera rappresenta la posizione corrente della fotocamera guidata dalla logica dell'applicazione.
  • Fotocamera proxy: questa fotocamera corrisponde al frame remoto corrente inviato dal server. Poiché c'è un ritardo di tempo tra il client che richiede un fotogramma e il suo arrivo, il frame remoto è sempre un po 'dietro il movimento della fotocamera locale.

L'approccio di base è che sia l'immagine remota che il contenuto locale vengono sottoposti a rendering in una destinazione fuori schermo usando la fotocamera proxy. L'immagine proxy viene quindi riprogettazione nello spazio della fotocamera locale, che viene ulteriormente spiegato nella riprogettazione in fase tardiva.

GraphicsApiType.SimD3D11 supporta anche il rendering stereoscopico, che deve essere abilitato durante la chiamata di InitSimulation installazione seguente. La configurazione è un po' più complessa e funziona nel modo seguente:

Creare la destinazione di rendering proxy

È necessario eseguire il rendering del contenuto remoto e locale in una destinazione di rendering di colore/profondità fuori schermo denominata 'proxy' usando i dati della camera proxy forniti dalla funzione GraphicsBindingSimD3d11.Update.

Il proxy deve corrispondere alla risoluzione del buffer nascosto e deve essere int il formato DXGI_FORMAT_R8G8B8A8_UNORM o DXGI_FORMAT_B8G8R8A8_UNORM . Nel caso del rendering stereoscopico, sia la trama proxy di colore che, se viene usata la profondità, la trama proxy di profondità deve avere due livelli di matrice anziché uno. Quando la sessione è pronta, è necessario chiamare GraphicsBindingSimD3d11.InitSimulation prima della connessione:

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);

La funzione init deve essere dotata di puntatori al dispositivo D3D nativo, nonché alla texture di colore e profondità della destinazione di rendering proxy. Dopo l'inizializzazione, RenderingSession.ConnectAsync e Disconnect possono essere chiamate più volte, ma quando si passa a una sessione diversa è necessario chiamare prima GraphicsBindingSimD3d11.DeinitSimulation nella sessione precedente per poter chiamare GraphicsBindingSimD3d11.InitSimulation in un'altra sessione.

Aggiornamento del ciclo di rendering

L'aggiornamento del ciclo di rendering è costituito da più passaggi:

  1. Per ogni fotogramma, prima che venga eseguito il rendering, viene chiamata la funzione GraphicsBindingSimD3d11.Update con la trasformazione della camera corrente inviata al server di cui eseguire il rendering. Allo stesso tempo, la trasformazione proxy restituita deve essere applicata alla camera proxy per eseguire il rendering nella destinazione di rendering proxy. Se SimulationUpdate.frameId dell'aggiornamento proxy restituito è Null, non sono ancora presenti dati remoti. In questo caso, anziché eseguire il rendering nella destinazione di rendering proxy, è necessario eseguire il rendering del contenuto locale nel buffer nascosto usando direttamente i dati della camera correnti. I due passaggi successivi verranno ignorati.
  2. L'applicazione dovrebbe ora eseguire il binding della destinazione di rendering proxy e chiamare GraphicsBindingSimD3d11.BlitRemoteFrameToProxy. In questo modo le informazioni remote relative a colore e profondità verranno inserite nella destinazione di rendering proxy. È ora possibile eseguire il rendering di qualsiasi contenuto locale nel proxy usando la trasformazione della camera proxy.
  3. È quindi necessario eseguire il binding del buffer nascosto come destinazione di rendering e chiamare GraphicsBindingSimD3d11.ReprojectProxy. A questo punto è possibile presentare il buffer nascosto.
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
    ...
}

Strutture di aggiornamento simulazione

Ogni fotogramma, l'aggiornamento del ciclo render della sezione precedente richiede di immettere un intervallo di parametri della fotocamera corrispondente alla fotocamera locale e restituisce un set di parametri della fotocamera che corrispondono alla fotocamera successiva disponibile. Questi due set vengono acquisiti rispettivamente nelle SimulationUpdateParameters strutture e SimulationUpdateResult :

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;
};

I membri della struttura hanno il significato seguente:

Membro Descrizione
FrameId Identificatore di frame continuo. Necessario per l'input SimulationUpdateParameters e deve essere incrementato continuamente per ogni nuovo frame. Sarà 0 in SimulationUpdateResult se non sono ancora disponibili dati frame.
ViewTransform Coppia stereo a sinistra delle matrici di trasformazione della visualizzazione fotocamera del fotogramma. Per il rendering monoscopico, solo il Left membro è valido.
FieldOfView Coppia stereo a sinistra dei campi della fotocamera del fotogramma nel campo OpenXR della convenzione di visualizzazione. Per il rendering monoscopico, solo il Left membro è valido.
NearPlaneDistance distanza quasi piano utilizzata per la matrice di proiezione del fotogramma remoto corrente.
Farplanedistance distanza del piano lontano utilizzata per la matrice di proiezione del frame remoto corrente.

Le coppie ViewTransform FieldOfView stereo e consentono di impostare entrambi i valori della fotocamera oculare nel caso in cui il rendering stereoscopico sia abilitato. In caso contrario, i Right membri verranno ignorati. Come si può notare, solo la trasformazione della fotocamera viene passata come matrici di trasformazione 4x4 semplici, mentre non vengono specificate matrici di proiezione. Le matrici effettive vengono calcolate da Azure Rendering remoto internamente usando i campi di visualizzazione specificati e il piano vicino e lontano corrente impostati nell'API Fotocamera Impostazioni.

Poiché è possibile modificare il piano vicino e il piano lontano sul Fotocamera Impostazioni durante il runtime in base alle esigenze e il servizio applica queste impostazioni in modo asincrono, ogni SimulationUpdateResult include anche lo specifico piano near-plane e far-plane usato durante il rendering del frame corrispondente. È possibile usare questi valori del piano per adattare le matrici di proiezione per il rendering degli oggetti locali in modo che corrispondano al rendering dei fotogrammi remoti.

Infine, mentre la chiamata di Simulation Update richiede il campo di visualizzazione nella convenzione OpenXR, per motivi di standardizzazione e sicurezza algoritmica, è possibile usare le funzioni di conversione illustrate negli esempi di popolamento della struttura seguenti:

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;
}

Queste funzioni di conversione consentono di passare rapidamente tra la specifica field-of-view e una semplice matrice di proiezione prospettica 4x4, a seconda delle esigenze per il rendering locale. Queste funzioni di conversione contengono la logica di verifica e restituiscono errori, senza impostare un risultato valido, nel caso in cui le matrici di proiezione di input o i campi di input della visualizzazione non siano validi.

Documentazione sull'API

Passaggi successivi