Powiązanie grafiki

Aby móc korzystać z usługi Azure Remote Rendering w aplikacji niestandardowej, należy ją zintegrować z potokiem renderowania aplikacji. Ta integracja jest obowiązkiem powiązania graficznego.

Po skonfigurowaniu powiązanie grafiki zapewnia dostęp do różnych funkcji, które mają wpływ na renderowany obraz. Te funkcje można podzielić na dwie kategorie: funkcje ogólne, które są zawsze dostępne i specyficzne funkcje, które są istotne tylko dla wybranego Microsoft.Azure.RemoteRendering.GraphicsApiTypeelementu .

Powiązanie grafiki w aucie Unity

W środowisku Unity całe powiązanie jest obsługiwane przez strukturę przekazaną RemoteUnityClientInit do RemoteManagerUnity.InitializeManagerklasy . Aby ustawić tryb grafiki, GraphicsApiType należy ustawić pole na wybrane powiązanie. Pole zostanie wypełnione automatycznie w zależności od tego, czy jest obecne urządzenie XRDevice. Zachowanie można ręcznie zastąpić następującymi zachowaniami:

Jedyną drugą istotną częścią aparatu Unity jest uzyskanie dostępu do powiązania podstawowego. Wszystkie pozostałe sekcje poniżej można pominąć.

Konfiguracja powiązań graficznych w aplikacjach niestandardowych

Aby wybrać powiązanie grafiki, wykonaj następujące dwa kroki: Najpierw powiązanie grafiki musi zostać statycznie zainicjowane po zainicjowaniu programu:

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

Powyższe wywołanie musi być wywoływane przed uzyskaniem dostępu do innych interfejsów API zdalnego renderowania. Podobnie odpowiednia funkcja RemoteManagerStatic.ShutdownRemoteRendering(); de-init powinna być wywoływana po tym, jak wszystkie inne obiekty renderowania zdalnego zostały już zniszczone. W przypadku usługi WMR StartupRemoteRendering należy również wywołać wywołanie przed wywołaniam dowolnego interfejsu API holograficznego. W przypadku biblioteki OpenXR to samo dotyczy dowolnych interfejsów API powiązanych z biblioteką OpenXR.

Uzyskiwanie dostępu do powiązania grafiki

Po skonfigurowaniu klienta można uzyskać dostęp do podstawowego powiązania graficznego za RenderingSession.GraphicsBinding pomocą metody getter. Na przykład można pobrać statystyki ostatniej ramki w następujący sposób:

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

Graficzne interfejsy API

Obecnie istnieją trzy interfejsy API grafiki, które można wybrać, OpenXrD3D11i WmrD3D11SimD3D11. Czwarty Headless istnieje, ale nie jest jeszcze obsługiwany po stronie klienta.

OpenXR

GraphicsApiType.OpenXrD3D11 to domyślne powiązanie do uruchomienia na urządzeniu HoloLens 2. Spowoduje to utworzenie GraphicsBindingOpenXrD3d11 powiązania. W tym trybie usługa Azure Remote Rendering tworzy warstwę interfejsu API OpenXR, aby zintegrować się ze środowiskiem uruchomieniowym OpenXR.

Aby uzyskać dostęp do pochodnych powiązań graficznych, należy rzutować bazę GraphicsBinding . Istnieją trzy czynności, które należy wykonać, aby użyć powiązania OpenXR:

Tworzenie niestandardowego pliku json warstwy OpenXR

Aby korzystać z funkcji Remote Rendering z funkcją OpenXR, należy aktywować niestandardową warstwę interfejsu API OpenXR. Jest to wykonywane przez wywołanie StartupRemoteRendering wymienione w poprzedniej sekcji. Jednak jako wymaganie wstępne XrApiLayer_msft_holographic_remoting.json należy spakować aplikację, aby można było ją załadować. Odbywa się to automatycznie, jeśli do projektu zostanie dodany pakiet NuGet "Microsoft.Azure.RemoteRendering.Cpp ".

Inform Remote Rendering of the used XR Space

Jest to potrzebne do wyrównania zawartości zdalnej i lokalnie renderowanej.

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

Gdzie powyższe XrSpace jest używane przez aplikację, która definiuje światowy system współrzędnych przestrzeni, w którym są wyrażane współrzędne w interfejsie API.

Renderowanie obrazu zdalnego (OpenXR)

Na początku każdej ramki należy renderować zdalną ramkę w buforze wstecznym. Odbywa się to przez wywołanie BlitRemoteFramemetody , która wypełni zarówno informacje o kolorze, jak i głębi dla obu oczu w aktualnie powiązanym obiekcie docelowym renderowania. Dlatego ważne jest, aby to zrobić po powiązaniu pełnego buforu wstecznego jako obiektu docelowego renderowania.

Ostrzeżenie

Gdy obraz zdalny został rozcięty do zaplecza, zawartość lokalna powinna być renderowana przy użyciu techniki renderowania stereo jednoprzepustowego, np. przy użyciu SV_RenderTargetArrayIndex. Korzystanie z innych technik renderowania stereo, takich jak renderowanie każdego oka w osobnym przebiegu, może spowodować znaczne obniżenie wydajności lub artefakty graficzne i należy unikać.

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 jest wcześniej używanym powiązaniem graficznym do uruchamiania na urządzeniu HoloLens 2. Spowoduje to utworzenie GraphicsBindingWmrD3d11 powiązania. W tym trybie podłączanie usługi Azure Remote Rendering bezpośrednio do interfejsów API holograficznego.

Aby uzyskać dostęp do pochodnych powiązań graficznych, należy rzutować bazę GraphicsBinding . Istnieją dwie rzeczy, które należy wykonać, aby użyć powiązania usługi WMR:

Informowanie zdalnego renderowania używanego układu współrzędnych

Jest to potrzebne do wyrównania zawartości zdalnej i lokalnie renderowanej.

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

Gdzie powyższe ptr musi być wskaźnikiem do obiektu natywnego ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem , który definiuje system współrzędnych przestrzeni świata, w którym współrzędne w interfejsie API są wyrażane.

Renderowanie obrazu zdalnego (WMR)

Te same zagadnienia, co w powyższym przypadku OpenXR, mają zastosowanie tutaj. Wywołania interfejsu API wyglądają następująco:

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

Symulacja

GraphicsApiType.SimD3D11 to powiązanie symulacji, a wybranie go powoduje utworzenie powiązania graficznego GraphicsBindingSimD3d11 . Ten interfejs służy do symulowania ruchu głowy, na przykład w aplikacji klasycznej i renderuje obraz monoskopowy.

Aby zaimplementować powiązanie symulacji, ważne jest, aby zrozumieć różnicę między aparatem lokalnym a ramką zdalną zgodnie z opisem na stronie aparatu .

Potrzebne są dwie kamery:

  • Aparat lokalny: ten aparat reprezentuje bieżącą pozycję aparatu opartą na logice aplikacji.
  • Aparat proxy: ten aparat jest zgodny z bieżącą ramką zdalną, która została wysłana przez serwer. Ponieważ występuje opóźnienie między klientem żądającym ramki a jego przybyciem, ramka zdalna jest zawsze nieco za ruchem aparatu lokalnego.

Podstawowe podejście polega na tym, że zarówno obraz zdalny, jak i zawartość lokalna są renderowane w miejscu docelowym poza ekranem przy użyciu aparatu proxy. Obraz serwera proxy jest następnie ponownie projektowany w lokalnej przestrzeni kamery, co zostało dokładniej wyjaśnione w późnym etapie ponownego projektu.

GraphicsApiType.SimD3D11 Obsługuje również renderowanie stereoskopowe, które należy włączyć podczas poniższego InitSimulation wywołania konfiguracji. Konfiguracja jest nieco bardziej zaangażowana i działa w następujący sposób:

Tworzenie obiektu docelowego renderowania serwera proxy

Zawartość zdalna i lokalna musi być renderowana do obiektu docelowego renderowania koloru/głębokości poza ekranem o nazwie "proxy" przy użyciu danych aparatu proxy dostarczonych przez GraphicsBindingSimD3d11.Update funkcję.

Serwer proxy musi odpowiadać rozdzielczości buforu wstecznego i powinien być w formacie DXGI_FORMAT_R8G8B8A8_UNORM lub DXGI_FORMAT_B8G8R8A8_UNORM . W przypadku renderowania stereoskopowego zarówno tekstura serwera proxy kolorów, jak i, jeśli jest używana głębokość, tekstura serwera proxy głębokości musi mieć dwie warstwy tablicy zamiast jednej. Gdy sesja będzie gotowa, GraphicsBindingSimD3d11.InitSimulation należy wywołać ją przed nawiązaniem z nim połączenia:

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

Funkcja init musi być dostarczana ze wskaźnikami do natywnego urządzenia d3d, a także do tekstury koloru i głębokości obiektu docelowego renderowania serwera proxy. Po zainicjowaniu i Disconnect może być wywoływany wiele razy, RenderingSession.ConnectAsync ale podczas przełączania do innej sesji należy wywołać najpierw w starej sesji, GraphicsBindingSimD3d11.DeinitSimulation zanim GraphicsBindingSimD3d11.InitSimulation będzie można wywołać w innej sesji.

Aktualizacja pętli renderowania

Aktualizacja pętli renderowania składa się z wielu kroków:

  1. Każda ramka, przed rozpoczęciem renderowania, GraphicsBindingSimD3d11.Update jest wywoływana z bieżącą transformacją aparatu, która jest wysyłana do serwera do renderowania. Jednocześnie zwrócona transformacja serwera proxy powinna zostać zastosowana do aparatu proxy w celu renderowania do elementu docelowego renderowania serwera proxy. Jeśli zwrócona aktualizacja SimulationUpdate.frameId serwera proxy ma wartość null, nie ma jeszcze danych zdalnych. W takim przypadku zamiast renderowania do obiektu docelowego renderowania serwera proxy każda zawartość lokalna powinna być renderowana bezpośrednio w buforze wstecznym przy użyciu bieżących danych aparatu, a kolejne dwa kroki zostaną pominięte.
  2. Aplikacja powinna teraz powiązać element docelowy renderowania serwera proxy i wywołać metodę GraphicsBindingSimD3d11.BlitRemoteFrameToProxy. Spowoduje to wypełnienie informacji o zdalnym kolorze i głębi do elementu docelowego renderowania serwera proxy. Każda lokalna zawartość może być teraz renderowana na serwerze proxy przy użyciu przekształcenia aparatu proxy.
  3. Następnie bufor wsteczny musi być powiązany jako element docelowy renderowania i GraphicsBindingSimD3d11.ReprojectProxy wywoływany w którym momencie można przedstawić bufor wsteczny.
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
    ...
}

Struktury aktualizacji symulacji

Każda ramka, aktualizacja pętli renderowania z poprzedniej sekcji wymaga wprowadzenia zakresu parametrów aparatu odpowiadającego aparatowi lokalnemu i zwraca zestaw parametrów aparatu, który odpowiada następnej dostępnej ramki aparatu. Te dwa zestawy są przechwytywane odpowiednio w SimulationUpdateParameters strukturach i 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;
};

Składowe struktury mają następujące znaczenie:

Element członkowski opis
Identyfikator ramki Identyfikator ramki ciągłej. Niezbędne dla danych wejściowych SimulationUpdateParameters i muszą być stale zwiększane dla każdej nowej ramki. Wartość 0 w obszarze SimulationUpdateResult będzie mieć wartość 0, jeśli żadne dane ramki nie są jeszcze dostępne.
WidokPrzekształć Lewa-prawa-stereo para macierzy transformacji widoku kamery ramki. W przypadku renderowania monoskopowego tylko Left element członkowski jest prawidłowy.
FieldOfView Lewa-prawa-stereo para pól kamery ramowej w polu OpenXR konwencji widoku. W przypadku renderowania monoskopowego tylko Left element członkowski jest prawidłowy.
NearPlaneDistance odległość zbliżona do płaszczyzny używanej dla macierzy projekcyjnej bieżącej ramki zdalnej.
FarPlaneDistance odległość płaszczyzny dalekiej używana dla macierzy projekcyjnej bieżącej ramki zdalnej.

Pary stereo i ViewTransformFieldOfView umożliwiają ustawienie obu wartości aparatu wzrokowego w przypadku włączenia renderowania stereoskopowego. Right W przeciwnym razie członkowie zostaną zignorowani. Jak widać, tylko transformacja aparatu jest przekazywana jako zwykłe macierze transformacji 4x4, podczas gdy nie określono macierzy projekcji. Rzeczywiste macierze są obliczane wewnętrznie przez usługę Azure Remote Rendering przy użyciu określonych pól widoku oraz bieżącego zestawu płaszczyzny bliskiej i dalekiej płaszczyzny w interfejsie API Aparat Ustawienia.

Ponieważ można zmienić płaszczyznę zbliżoną i daleko-płaszczyznę na Aparat Ustawienia w czasie wykonywania zgodnie z potrzebami, a usługa stosuje te ustawienia asynchronicznie, każda symulacjaUpdateResult przenosi również określone płaszczyzny bliskiej i dalekiej płaszczyzny używane podczas renderowania odpowiedniej ramki. Te wartości płaszczyzny umożliwiają dostosowanie macierzy projekcji do renderowania obiektów lokalnych w celu dopasowania ich do renderowania ramek zdalnych.

Na koniec, podczas gdy wywołanie aktualizacji symulacji wymaga pola widoku w konwencji OpenXR, ze względów standaryzacji i bezpieczeństwa algorytmicznego, można użyć funkcji konwersji przedstawionych w następujących przykładach populacji struktury:

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

Te funkcje konwersji umożliwiają szybkie przełączanie się między specyfikacją pola widoku a zwykłą macierzą projekcji perspektywy 4x4 w zależności od potrzeb lokalnego renderowania. Te funkcje konwersji zawierają logikę weryfikacji i będą zwracać błędy bez ustawiania prawidłowego wyniku, jeśli macierze projekcji wejściowej lub pola wejściowe widoku są nieprawidłowe.

Dokumentacja interfejsu API

Następne kroki