Självstudie: Integrera Remote Rendering i en HoloLens Holographic-app
I den här självstudien lär du dig:
- Använda Visual Studio för att skapa en Holographic-app som kan distribueras till HoloLens
- Lägg till nödvändiga kodfragment och projektinställningar för att kombinera lokal rendering med fjärrre renderat innehåll
Den här självstudien fokuserar på att lägga till nödvändiga bitar i ett inbyggt Holographic App exempel för att kombinera lokal rendering med Azure Remote Rendering. Den enda typen av statusfeedback i den här appen är via utdatapanelen för felsökning i Visual Studio, så vi rekommenderar att du startar exemplet inifrån Visual Studio. Att lägga till rätt feedback i appen ligger utanför omfånget för det här exemplet, eftersom det krävs mycket kodning för att skapa en dynamisk textpanel från grunden. En bra startpunkt är klassen StatusDisplay , som är en del av exempelprojektet Fjärrkommunikationsspelare på GitHub. I själva verket använder den förkonterade versionen av den här självstudien en lokal kopia av den klassen.
Tips
Lagringsplatsen arr-exempel innehåller resultatet av den här självstudien som ett Visual Studio projekt som är redo att användas. Den är också berikad med korrekt fel- och statusrapportering via UI-klassen StatusDisplay . I självstudien är alla ARR-specifika tillägg begränsade till , så det är enkelt att #ifdef USE_REMOTE_RENDERING / #endif identifiera de Remote Rendering tilläggen.
Förutsättningar
För den här självstudien behöver du:
- Din kontoinformation (konto-ID, kontonyckel, kontodomän, prenumerations-ID). Om du inte har något konto skapar du ett konto.
- Windows SDK 10.0.18362.0 (ladda ned).
- Den senaste versionen av Visual Studio 2019 (ladda ned).
- Visual Studio verktyg för Mixed Reality. Mer specifikt är följande arbetsbelastningsinstallationer obligatoriska:
- Skrivbordsutveckling med C++
- UWP-utveckling (Universal Windows Platform)
- Den Windows Mixed Reality appmallar för Visual Studio (ladda ned).
Skapa ett nytt Holographic App-exempel
Som ett första steg skapar vi ett aktieprov som utgör grunden för Remote Rendering integrering. Öppna Visual Studio och välj "Skapa ett nytt projekt" och sök efter "Holographic DirectX 11 App (Universal Windows) (C++/WinRT)"

Ange ett projektnamn, välj en sökväg och välj knappen "Skapa". I det nya projektet växlar du konfigurationen till "Debug/ARM64". Du bör nu kunna kompilera och distribuera den till en ansluten HoloLens 2-enhet. Om du kör den HoloLens bör du se en roterande kub framför dig.
Lägga Remote Rendering beroenden via NuGet
Det första steget i Remote Rendering att lägga till beroenden på klientsidan. Relevanta beroenden är tillgängliga som ett NuGet-paket. I Solution Explorer högerklickar du på projektet och väljer "Hantera NuGet-paket..." på snabbmenyn.
I den efterfrågade dialogrutan bläddrar du efter NuGet-paketet med namnet "Microsoft.Azure.RemoteRendering.Cpp":

och lägg till det i projektet genom att välja paketet och sedan trycka på knappen "Installera".
NuGet-paketet lägger Remote Rendering till beroenden i projektet. Specifikt:
- Länka till klientbiblioteket (RemoteRenderingClient.lib).
- Konfigurera .dll beroenden.
- Ange rätt sökväg till katalogen include.
Project förberedelse
Vi behöver göra små ändringar i det befintliga projektet. De här ändringarna är diskreta, men utan dem Remote Rendering de inte fungera.
Aktivera flertrådsskydd på DirectX-enhet
DirectX11Flertrådsskydd måste vara aktiverat på enheten. Om du vill ändra det öppnar du filen DeviceResources.cpp i mappen "Common" och infogar följande kod i slutet av funktionen DeviceResources::CreateDeviceResources() :
// Enable multi thread protection as now multiple threads use the immediate context.
Microsoft::WRL::ComPtr<ID3D11Multithread> contextMultithread;
if (context.As(&contextMultithread) == S_OK)
{
contextMultithread->SetMultithreadProtected(true);
}
Aktivera nätverksfunktioner i appmanifestet
Nätverksfunktioner måste uttryckligen aktiveras för den distribuerade appen. Utan att detta konfigureras resulterar anslutningsfrågorna i tidsgränser så småningom. Om du vill aktivera dubbelklickar du på package.appxmanifest objektet i Solution Explorer. I nästa användargränssnitt går du till fliken Funktioner och väljer:
- Internet (Client & Server)
- Internet (Klient)

Integrera Remote Rendering
Nu när projektet är förberett kan vi börja med koden. En bra startpunkt i programmet är klassen HolographicAppMain (filen HolographicAppMain.h/cpp) eftersom den har alla nödvändiga hookar för initiering, avinitiering och rendering.
Inkluderar
Vi börjar med att lägga till nödvändiga in includes. Lägg till följande include i filen HolographicAppMain.h:
#include <AzureRemoteRendering.h>
... och dessa ytterligare include direktiv till filen HolographicAppMain.cpp:
#include <AzureRemoteRendering.inl>
#include <RemoteRenderingExtensions.h>
#include <windows.perception.spatial.h>
För enkelhetens skull definierar vi följande genväg för namnområdet överst i filen HolographicAppMain.h, efter include -direktivet:
namespace RR = Microsoft::Azure::RemoteRendering;
Den här genvägen är användbar så att vi inte behöver skriva ut det fullständiga namnområdet överallt, men ändå kan identifiera ARR-specifika datastrukturer. Naturligtvis kan vi också använda using namespace... -direktivet.
Remote Rendering initiering
Vi behöver hålla några objekt för sessionen osv. under programmets livslängd. Livslängden sammanträffar med livslängden för programmets HolographicAppMain -objekt, så vi lägger till våra objekt som medlemmar i klassen HolographicAppMain . Nästa steg är att lägga till följande klassmedlemmar i filen HolographicAppMain.h:
class HolographicAppMain
{
...
// members:
std::string m_sessionOverride; // if we have a valid session ID, we specify it here. Otherwise a new one is created
RR::ApiHandle<RR::RemoteRenderingClient> m_client; // the client instance
RR::ApiHandle<RR::RenderingSession> m_session; // the current remote rendering session
RR::ApiHandle<RR::RenderingConnection> m_api; // the API instance, that is used to perform all the actions. This is just a shortcut to m_session->Connection()
RR::ApiHandle<RR::GraphicsBindingWmrD3d11> m_graphicsBinding; // the graphics binding instance
}
En bra plats för den faktiska implementeringen är konstruktorn för klassen HolographicAppMain . Vi måste göra tre typer av initiering där:
- Initieringen en gång för Remote Rendering system
- Skapa klient (autentisering)
- Skapa session
Vi gör allt detta sekventiellt i konstruktorn. I verkliga användningsfall kan det dock vara lämpligt att göra dessa steg separat.
Lägg till följande kod i början av konstruktortexten i filen HolographicAppMain.cpp:
HolographicAppMain::HolographicAppMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) :
m_deviceResources(deviceResources)
{
// 1. One time initialization
{
RR::RemoteRenderingInitialization clientInit;
clientInit.ConnectionType = RR::ConnectionType::General;
clientInit.GraphicsApi = RR::GraphicsApiType::WmrD3D11;
clientInit.ToolId = "<sample name goes here>"; // <put your sample name here>
clientInit.UnitsPerMeter = 1.0f;
clientInit.Forward = RR::Axis::NegativeZ;
clientInit.Right = RR::Axis::X;
clientInit.Up = RR::Axis::Y;
if (RR::StartupRemoteRendering(clientInit) != RR::Result::Success)
{
// something fundamental went wrong with the initialization
throw std::exception("Failed to start remote rendering. Invalid client init data.");
}
}
// 2. Create Client
{
// Users need to fill out the following with their account data and model
RR::SessionConfiguration init;
init.AccountId = "00000000-0000-0000-0000-000000000000";
init.AccountKey = "<account key>";
init.RemoteRenderingDomain = "westus2.mixedreality.azure.com"; // <change to the region that the rendering session should be created in>
init.AccountDomain = "westus2.mixedreality.azure.com"; // <change to the region the account was created in>
m_modelURI = "builtin://Engine";
m_sessionOverride = ""; // If there is a valid session ID to re-use, put it here. Otherwise a new one is created
m_client = RR::ApiHandle(RR::RemoteRenderingClient(init));
}
// 3. Open/create rendering session
{
auto SessionHandler = [&](RR::Status status, RR::ApiHandle<RR::CreateRenderingSessionResult> result)
{
if (status == RR::Status::OK)
{
auto ctx = result->GetContext();
if (ctx.Result == RR::Result::Success)
{
SetNewSession(result->GetSession());
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, ctx.ErrorMessage.c_str());
}
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, "failed");
}
};
// If we had an old (valid) session that we can recycle, we call async function m_client->OpenRenderingSessionAsync
if (!m_sessionOverride.empty())
{
m_client->OpenRenderingSessionAsync(m_sessionOverride, SessionHandler);
SetNewState(AppConnectionStatus::CreatingSession, nullptr);
}
else
{
// create a new session
RR::RenderingSessionCreationOptions init;
init.MaxLeaseInMinutes = 10; // session is leased for 10 minutes
init.Size = RR::RenderingSessionVmSize::Standard;
m_client->CreateNewRenderingSessionAsync(init, SessionHandler);
SetNewState(AppConnectionStatus::CreatingSession, nullptr);
}
}
// Rest of constructor code:
...
}
Koden anropar SetNewSession medlemsfunktionerna SetNewState och , som vi implementerar i nästa stycke tillsammans med resten av koden för tillståndsdatorn.
Observera att autentiseringsuppgifterna är hårdkodade i exemplet och måste fyllas i på plats (konto-ID, kontonyckel,kontodomän och domän för fjärrrendering).
Vi gör avinitieringen symmetriskt och i omvänd ordning i slutet av destructor-brödtexten:
HolographicAppMain::~HolographicAppMain()
{
// Existing destructor code:
...
// Destroy session:
if (m_session != nullptr)
{
m_session->Disconnect();
m_session = nullptr;
}
// Destroy front end:
m_client = nullptr;
// One-time de-initialization:
RR::ShutdownRemoteRendering();
}
Tillståndsdator
I Remote Rendering är viktiga funktioner för att skapa en session och läsa in en modell asynkrona funktioner. För att kunna ta hänsyn till detta behöver vi en enkel tillståndsdator som i stort sett går igenom följande tillstånd automatiskt:
Initiering – skapa > -> session med början -> modell inläsning (pågår)
Därför lägger vi till lite tillståndsdatorhantering i klassen som ett nästa steg. Vi deklarerar vår egen AppConnectionStatus uppräkning för de olika tillstånd som programmet kan finnas i. Det liknar , RR::ConnectionStatus men har ytterligare ett tillstånd för misslyckad anslutning.
Lägg till följande medlemmar och funktioner i klassdeklarationen:
namespace HolographicApp
{
// Our application's possible states:
enum class AppConnectionStatus
{
Disconnected,
CreatingSession,
StartingSession,
Connecting,
Connected,
// error state:
ConnectionFailed,
};
class HolographicAppMain
{
...
// Member functions for state transition handling
void OnConnectionStatusChanged(RR::ConnectionStatus status, RR::Result error);
void SetNewState(AppConnectionStatus state, const char* statusMsg);
void SetNewSession(RR::ApiHandle<RR::RenderingSession> newSession);
void StartModelLoading();
// Members for state handling:
// Model loading:
std::string m_modelURI;
RR::ApiHandle<RR::LoadModelAsync> m_loadModelAsync;
// Connection state machine:
AppConnectionStatus m_currentStatus = AppConnectionStatus::Disconnected;
std::string m_statusMsg;
RR::Result m_connectionResult = RR::Result::Success;
RR::Result m_modelLoadResult = RR::Result::Success;
bool m_isConnected = false;
bool m_sessionStarted = false;
RR::ApiHandle<RR::SessionPropertiesAsync> m_sessionPropertiesAsync;
bool m_modelLoadTriggered = false;
float m_modelLoadingProgress = 0.f;
bool m_modelLoadFinished = false;
double m_timeAtLastRESTCall = 0;
bool m_needsCoordinateSystemUpdate = true;
}
Lägg till följande funktionskroppar på implementeringssidan i .cpp-filen:
void HolographicAppMain::StartModelLoading()
{
m_modelLoadingProgress = 0.f;
RR::LoadModelFromSasOptions options;
options.ModelUri = m_modelURI.c_str();
options.Parent = nullptr;
// start the async model loading
m_api->LoadModelFromSasAsync(options,
// completed callback
[this](RR::Status status, RR::ApiHandle<RR::LoadModelResult> result)
{
m_modelLoadResult = RR::StatusToResult(status);
m_modelLoadFinished = true;
if (m_modelLoadResult == RR::Result::Success)
{
RR::Double3 pos = { 0.0, 0.0, -2.0 };
result->GetRoot()->SetPosition(pos);
}
},
// progress update callback
[this](float progress)
{
// progress callback
m_modelLoadingProgress = progress;
m_needsStatusUpdate = true;
});
}
void HolographicAppMain::SetNewState(AppConnectionStatus state, const char* statusMsg)
{
m_currentStatus = state;
m_statusMsg = statusMsg ? statusMsg : "";
// Some log for the VS output panel:
const char* appStatus = nullptr;
switch (state)
{
case AppConnectionStatus::Disconnected: appStatus = "Disconnected"; break;
case AppConnectionStatus::CreatingSession: appStatus = "CreatingSession"; break;
case AppConnectionStatus::StartingSession: appStatus = "StartingSession"; break;
case AppConnectionStatus::Connecting: appStatus = "Connecting"; break;
case AppConnectionStatus::Connected: appStatus = "Connected"; break;
case AppConnectionStatus::ConnectionFailed: appStatus = "ConnectionFailed"; break;
}
char buffer[1024];
sprintf_s(buffer, "Remote Rendering: New status: %s, result: %s\n", appStatus, m_statusMsg.c_str());
OutputDebugStringA(buffer);
}
void HolographicAppMain::SetNewSession(RR::ApiHandle<RR::RenderingSession> newSession)
{
SetNewState(AppConnectionStatus::StartingSession, nullptr);
m_sessionStartingTime = m_timeAtLastRESTCall = m_timer.GetTotalSeconds();
m_session = newSession;
m_api = m_session->Connection();
m_graphicsBinding = m_session->GetGraphicsBinding().as<RR::GraphicsBindingWmrD3d11>();
m_session->ConnectionStatusChanged([this](auto status, auto error)
{
OnConnectionStatusChanged(status, error);
});
};
void HolographicAppMain::OnConnectionStatusChanged(RR::ConnectionStatus status, RR::Result error)
{
const char* asString = RR::ResultToString(error);
m_connectionResult = error;
switch (status)
{
case RR::ConnectionStatus::Connecting:
SetNewState(AppConnectionStatus::Connecting, asString);
break;
case RR::ConnectionStatus::Connected:
if (error == RR::Result::Success)
{
SetNewState(AppConnectionStatus::Connected, asString);
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, asString);
}
m_modelLoadTriggered = m_modelLoadFinished = false;
m_isConnected = error == RR::Result::Success;
break;
case RR::ConnectionStatus::Disconnected:
if (error == RR::Result::Success)
{
SetNewState(AppConnectionStatus::Disconnected, asString);
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, asString);
}
m_modelLoadTriggered = m_modelLoadFinished = false;
m_isConnected = false;
break;
default:
break;
}
}
Uppdatering per bildruta
Vi måste uppdatera klienten en gång per simulerings tick och göra några ytterligare tillståndsuppdateringar. Funktionen HolographicAppMain::Update ger en bra hook för uppdateringar per bildruta.
Uppdatera tillståndsdator
Vi måste avse sessionens status och se om den har gått över till Ready tillstånd. Om vi har anslutit startar vi slutligen modellläsningen via StartModelLoading .
Lägg till följande kod i brödtexten i funktionen HolographicAppMain::Update :
// Updates the application state once per frame.
HolographicFrame HolographicAppMain::Update()
{
if (m_session != nullptr)
{
// Tick the client to receive messages
m_api->Update();
if (!m_sessionStarted)
{
// Important: To avoid server-side throttling of the requests, we should call GetPropertiesAsync very infrequently:
const double delayBetweenRESTCalls = 10.0;
// query session status periodically until we reach 'session started'
if (m_sessionPropertiesAsync == nullptr && m_timer.GetTotalSeconds() - m_timeAtLastRESTCall > delayBetweenRESTCalls)
{
m_timeAtLastRESTCall = m_timer.GetTotalSeconds();
m_session->GetPropertiesAsync([this](RR::Status status, RR::ApiHandle<RR::RenderingSessionPropertiesResult> propertiesResult)
{
if (status == RR::Status::OK)
{
auto ctx = propertiesResult->GetContext();
if (ctx.Result == RR::Result::Success)
{
auto res = propertiesResult->GetSessionProperties();
switch (res.Status)
{
case RR::RenderingSessionStatus::Ready:
{
// The following ConnectAsync is async, but we'll get notifications via OnConnectionStatusChanged
m_sessionStarted = true;
SetNewState(AppConnectionStatus::Connecting, nullptr);
RR::RendererInitOptions init;
init.IgnoreCertificateValidation = false;
init.RenderMode = RR::ServiceRenderMode::Default;
m_session->ConnectAsync(init, [](RR::Status, RR::ConnectionStatus) {});
}
break;
case RR::RenderingSessionStatus::Error:
SetNewState(AppConnectionStatus::ConnectionFailed, "Session error");
break;
case RR::RenderingSessionStatus::Stopped:
SetNewState(AppConnectionStatus::ConnectionFailed, "Session stopped");
break;
case RR::RenderingSessionStatus::Expired:
SetNewState(AppConnectionStatus::ConnectionFailed, "Session expired");
break;
}
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, ctx.ErrorMessage.c_str());
}
}
else
{
SetNewState(AppConnectionStatus::ConnectionFailed, "Failed to retrieve session status");
}
m_sessionPropertiesQueryInProgress = false; // next try
}); }
}
}
if (m_isConnected && !m_modelLoadTriggered)
{
m_modelLoadTriggered = true;
StartModelLoading();
}
}
if (m_needsCoordinateSystemUpdate && m_stationaryReferenceFrame && m_graphicsBinding)
{
// Set the coordinate system once. This must be called again whenever the coordinate system changes.
winrt::com_ptr<ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem> ptr{ m_stationaryReferenceFrame.CoordinateSystem().as<ABI::Windows::Perception::Spatial::ISpatialCoordinateSystem>() };
m_graphicsBinding->UpdateUserCoordinateSystem(ptr.get());
m_needsCoordinateSystemUpdate = false;
}
// Rest of the body:
...
}
Systemuppdatering för koordinat
Vi måste komma överens med renderingstjänsten om ett koordinatsystem som ska användas. För att komma åt det koordinatsystem som vi vill använda behöver m_stationaryReferenceFrame vi det som skapas i slutet av funktionen HolographicAppMain::OnHolographicDisplayIsAvailableChanged .
Det här koordinatsystemet ändras vanligtvis inte, så det här är en initiering en gång. Det måste anropas igen om programmet ändrar koordinatsystemet.
Koden ovan anger koordinatsystemet en gång i funktionen Update så snart vi båda har ett referenskoordinatsystem och en ansluten session.
Kamerauppdatering
Vi måste uppdatera kameraklippsplanet så att serverkameran hålls synkroniserad med den lokala kameran. Det kan vi göra i slutet av Update funktionen:
...
if (m_isConnected)
{
// Any near/far plane values of your choosing.
constexpr float fNear = 0.1f;
constexpr float fFar = 10.0f;
for (HolographicCameraPose const& cameraPose : prediction.CameraPoses())
{
// Set near and far to the holographic camera as normal
cameraPose.HolographicCamera().SetNearPlaneDistance(fNear);
cameraPose.HolographicCamera().SetFarPlaneDistance(fFar);
}
// The API to inform the server always requires near < far. Depth buffer data will be converted locally to match what is set on the HolographicCamera.
auto settings = m_api->GetCameraSettings();
settings->SetNearAndFarPlane(std::min(fNear, fFar), std::max(fNear, fFar));
settings->SetEnableDepth(true);
}
// The holographic frame will be used to get up-to-date view and projection matrices and
// to present the swap chain.
return holographicFrame;
}
Rendering
Det sista du ska göra är att göra en återgivning av fjärrinnehållet. Vi måste göra det här anropet på exakt rätt plats i återgivningspipelinen, efter att renderingsmålet har rensat och ställer in visningsområdet. Infoga följande kodfragment i UseHolographicCameraResources låsfunktionen HolographicAppMain::Render :
...
// Existing clear function:
context->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
// ...
// Existing check to test for valid camera:
bool cameraActive = pCameraResources->AttachViewProjectionBuffer(m_deviceResources);
// Inject remote rendering: as soon as we are connected, start blitting the remote frame.
// We do the blitting after the Clear, and before cube rendering.
if (m_isConnected && cameraActive)
{
m_graphicsBinding->BlitRemoteFrame();
}
...
Kör exemplet
Exemplet bör nu vara i ett tillstånd där det kompileras och körs.
När exemplet körs korrekt visas den roterande kuben framför dig, och efter att en session har skapats och modellen har lästs in återges motormodellen som finns vid den aktuella huvudpositionen. Det kan ta upp till några minuter att skapa sessioner och läsa in modeller. Den aktuella statusen skrivs endast Visual Studio på utdatapanelen. Vi rekommenderar därför att du startar exemplet inifrån Visual Studio.
Varning
Klienten kopplar från servern när tick-funktionen inte anropas på några sekunder. Det är därför enkelt att utlösa brytpunkter som gör att programmet kopplas från.
Korrekt statusvisning med en textpanel finns i den förkonterade versionen av den här självstudien på GitHub.
Nästa steg
I den här självstudien har du lärt dig alla steg som krävs för att Remote Rendering till ett lagerexempel för Holographic App C++/DirectX11. Om du vill konvertera din egen modell kan du gå till följande snabbstart: