Oktatóanyag: Távoli renderelés integrálása HoloLens Holographic-alkalmazásba
Ebben az oktatóanyagban az alábbiakkal fog megismerkedni:
- HoloLensben üzembe helyezhető Holographic-alkalmazás létrehozása a Visual Studióval
- Adja hozzá a szükséges kódrészleteket és projektbeállításokat a helyi renderelés és a távolról renderelt tartalom kombinálásához
Ez az oktatóanyag a szükséges bitek natív Holographic App
mintához való hozzáadására összpontosít a helyi renderelés és az Azure Remote Rendering kombinálásához. Az alkalmazás egyetlen állapotvisszajelzése a Visual Studióban található hibakeresési kimeneti panelen keresztül történik, ezért javasoljuk, hogy a mintát a Visual Studióból indítsa el. A megfelelő alkalmazáson belüli visszajelzések hozzáadása meghaladja a minta hatókörét, mivel a dinamikus szövegpanel létrehozása az alapoktól sok kódolást igényel. Jó kiindulópont az osztály StatusDisplay
, amely része a Remoting Player mintaprojektnek a GitHubon. Az oktatóanyag előre beolvasott verziója valójában az osztály helyi példányát használja.
Tipp.
Az ARR-minták adattára az oktatóanyag eredményét egy használatra kész Visual Studio-projektként tartalmazza. Emellett a felhasználói felületi osztályon StatusDisplay
keresztüli megfelelő hiba- és állapotjelentésekkel is gazdagodik. Az oktatóanyagban az összes ARR-specifikus kiegészítés hatókörrel #ifdef USE_REMOTE_RENDERING
/ #endif
rendelkezik, így könnyen azonosíthatók a távoli renderelési kiegészítések.
Előfeltételek
Ehhez az oktatóanyaghoz a következőkre van szüksége:
- A fiók adatai (fiókazonosító, fiókkulcs, fióktartomány, előfizetés-azonosító). Ha nincs fiókja, hozzon létre egy fiókot.
- Windows SDK 10.0.18362.0 (letöltés).
- A Visual Studio 2022 legújabb verziója (letöltés).
- Visual Studio-eszközök vegyes valósághoz. Konkrétan a következő számítási feladatok telepítése kötelező:
- Asztali fejlesztés a C++ használatával
- Univerzális Windows-platform (UWP) fejlesztése
- A Visual Studióhoz készült Windows Mixed Reality alkalmazássablonok (letöltés).
Új holografikus alkalmazásminta létrehozása
Első lépésként létrehozunk egy készletmintát, amely a távoli renderelés integrációjának alapja. Nyissa meg a Visual Studiót, és válassza az "Új projekt létrehozása" lehetőséget, és keressen rá a "Holographic DirectX 11 App (Universal Windows) (C++/WinRT)" kifejezésre.
Írja be a kívánt projektnevet, válasszon egy útvonalat, és válassza a "Létrehozás" gombot. Az új projektben állítsa a konfigurációt "Hibakeresés / ARM64" értékre. Most már képesnek kell lennie arra, hogy lefordítsa és üzembe helyezze egy csatlakoztatott HoloLens 2-eszközön. Ha a HoloLensen futtatja, egy forgó kockát kell látnia előtted.
Távoli renderelési függőségek hozzáadása a NuGeten keresztül
A távoli renderelési képességek hozzáadásának első lépése az ügyféloldali függőségek hozzáadása. A releváns függőségek NuGet-csomagként érhetők el. A Megoldáskezelő kattintson a jobb gombbal a projektre, és válassza a helyi menü "NuGet-csomagok kezelése..." elemét.
A megjelenő párbeszédpanelen keresse meg a "Microsoft.Azure.RemoteRendering.Cpp" nevű NuGet-csomagot:
és adja hozzá a projekthez a csomag kiválasztásával, majd a "Telepítés" gomb lenyomásával.
A NuGet-csomag hozzáadja a távoli renderelési függőségeket a projekthez. Ezek konkrétan a következők:
- Csatolás az ügyfélkódtárhoz (RemoteRenderingClient.lib).
- Állítsa be a .dll-függőségeket.
- Adja meg a megfelelő elérési utat a belefoglalási könyvtárhoz.
Projekt előkészítése
Kis módosításokat kell hajtanunk végre a meglévő projekten. Ezek a módosítások finomak, de nélkülük a távoli renderelés nem működne.
Többszálas védelem engedélyezése DirectX-eszközön
Az DirectX11
eszköznek engedélyezve kell lennie a többszálú védelemnek. Ennek módosításához nyissa meg a DeviceResources.cpp fájlt a "Common" mappában, és szúrja be a következő kódot a függvény DeviceResources::CreateDeviceResources()
végére:
// 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);
}
Hálózati képességek engedélyezése az alkalmazásjegyzékben
A hálózati képességeket explicit módon engedélyezni kell az üzembe helyezett alkalmazáshoz. A konfigurálás nélkül a kapcsolati lekérdezések idővel időtúllépést eredményeznek. Az engedélyezéshez kattintson duplán az elemre a package.appxmanifest
megoldáskezelőben. A következő felhasználói felületen lépjen a Képességek lapra, és válassza a következő lehetőséget:
- Internet (ügyfél és kiszolgáló)
- Internet (Ügyfél)
Távoli renderelés integrálása
Most, hogy elkészült a projekt, elkezdhetjük a kódot. Az alkalmazás jó belépési pontja az osztály HolographicAppMain
(HolographicAppMain.h/cpp fájl), mert az inicializáláshoz, az inicializáláshoz és a rendereléshez szükséges összes kampóval rendelkezik.
Tartalmazza
Először hozzáadjuk a szükséges csomagokat. Adja hozzá a következőt a HolographicAppMain.h fájlhoz:
#include <AzureRemoteRendering.h>
... és ezek a további include
irányelvek a HolographicAppMain.cpp fájlhoz:
#include <AzureRemoteRendering.inl>
#include <RemoteRenderingExtensions.h>
#include <windows.perception.spatial.h>
A kód egyszerűsége érdekében a holographicAppMain.h fájl tetején az alábbi névtér-parancsikont határozzuk meg az include
irányelvek után:
namespace RR = Microsoft::Azure::RemoteRendering;
Ez a parancsikon hasznos, így nem kell mindenhol kiírnunk a teljes névteret, de az ARR-specifikus adatstruktúrák továbbra is felismerhetők. Természetesen az irányelvet is felhasználhatjuk using namespace...
.
Távoli renderelés inicializálása
Az alkalmazás élettartama alatt néhány objektumot kell tárolnunk a munkamenethez stb. Az élettartam egybeesik az alkalmazás HolographicAppMain
objektumának élettartamával, ezért tagként hozzáadjuk az objektumokat az osztályhoz HolographicAppMain
. A következő lépés a következő osztálytagok hozzáadása a HolographicAppMain.h fájlban:
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
}
A tényleges megvalósításhoz jó hely az osztály HolographicAppMain
konstruktora. Három inicializálási típust kell elvégeznünk:
- A távoli renderelési rendszer egyszeri inicializálása
- Ügyfél létrehozása (hitelesítés)
- Munkamenet létrehozása
Mindet szekvenciálisan csináljuk a konstruktorban. Valós használati esetekben azonban célszerű lehet ezeket a lépéseket külön elvégezni.
Adja hozzá a következő kódot a konstruktor törzsének elejéhez a HolographicAppMain.cpp fájlban:
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:
...
}
A kód meghívja a tagfüggvényeket SetNewSession
, és SetNewState
a következő bekezdésben implementáljuk a többi állapotgép-kóddal együtt.
Vegye figyelembe, hogy a hitelesítő adatok kódoltak a mintában, és a helyén kell kitölteni (fiókazonosító, fiókkulcs, fióktartomány és távoli renderelési tartomány).
A destruktor törzsének végén szimmetrikusan és fordított sorrendben hajtjuk végre az inicializálást:
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();
}
Állapotgép
A távoli renderelésben a munkamenet létrehozásához és a modell betöltéséhez szükséges legfontosabb függvények aszinkron függvények. Ennek figyelembe vételével egy egyszerű állapotgépre van szükségünk, amely lényegében automatikusan áttér az alábbi állapotokra:
Inicializálás –> Munkamenet létrehozása –> Munkamenet indítása –> Modell betöltése (folyamatban)
Ennek megfelelően a következő lépésként hozzáadunk egy kis állapotgép-kezelést az osztályhoz. Deklaráljuk a saját enumerálásunkat AppConnectionStatus
a különböző állapotokhoz, amelyekben az alkalmazás szerepelhet. Hasonló, RR::ConnectionStatus
de a sikertelen kapcsolathoz további állapottal rendelkezik.
Adja hozzá a következő tagokat és függvényeket az osztálydeklarációhoz:
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;
}
A .cpp fájl implementálási oldalán adja hozzá a következő függvénytesteket:
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;
}
}
Keretenkénti frissítés
Szimulációnként egyszer frissíteni kell az ügyfelet, és további állapotfrissítéseket kell végrehajtanunk. A függvény HolographicAppMain::Update
jó horogot biztosít a keretenkénti frissítésekhez.
Állapotgép-frissítés
Le kell kérdeznünk a munkamenet állapotát, és meg kell néznünk, hogy az állapotra Ready
vált-e. Ha sikeresen csatlakoztunk, végül elindítjuk a modell betöltését.StartModelLoading
Adja hozzá a következő kódot a függvény HolographicAppMain::Update
törzséhez:
// 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:
...
}
Rendszerfrissítés koordinálása
Meg kell egyeznünk a renderelési szolgáltatással egy használni kívánt koordinátarendszeren. A használni kívánt koordinátarendszer eléréséhez szükségünk van a m_stationaryReferenceFrame
függvény HolographicAppMain::OnHolographicDisplayIsAvailableChanged
végén létrehozott rendszerre.
Ez a koordinátarendszer általában nem változik, ezért egyszeri inicializálásról van szó. Újra meg kell hívni, ha az alkalmazás módosítja a koordináta-rendszert.
A fenti kód egyszer állítja be a koordinátarendszert a Update
függvényen belül, amint rendelkezünk egy referenciakoordináta-rendszerrel és egy csatlakoztatott munkamenettel.
Kamera frissítés
Frissíteni kell a kamera vágósíkjait, hogy a kiszolgáló kamerája szinkronban legyen a helyi kamerával. Ezt a függvény végén Update
végezhetjük el:
...
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
Az utolsó teendő a távoli tartalom renderelésének meghívása. Ezt a hívást pontosan a megfelelő pozícióban kell végrehajtani a renderelési folyamaton belül a renderelési cél egyértelművé tétele és a nézetport beállítása után. Szúrja be a következő kódrészletet a UseHolographicCameraResources
lock inside függvénybe 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();
}
...
Minta futtatása
A mintának most olyan állapotban kell lennie, amelyben lefordítja és futtatja.
Ha a minta megfelelően fut, a forgó kockát közvetlenül ön előtt jeleníti meg, és a munkamenet létrehozása és a modell betöltése után megjeleníti a motormodellt az aktuális fejpozícióban. A munkamenet létrehozása és a modell betöltése akár néhány percet is igénybe vehet. Az aktuális állapot csak a Visual Studio kimeneti paneljén van megírva. Ezért javasoljuk, hogy indítsa el a mintát a Visual Studióból.
Figyelem
Az ügyfél megszakítja a kapcsolatot a kiszolgálóval, ha a rendszer néhány másodpercig nem hívja meg az osztásfüggvényt. Így a töréspontok aktiválása nagyon könnyen okozhatja az alkalmazás leválasztását.
Ha megfelelő állapotot szeretne megjeleníteni egy szövegpanellel, tekintse meg az oktatóanyag előre beolvasott verzióját a GitHubon.
További lépések
Ebben az oktatóanyagban megismerte a távoli renderelés készlethez a C++/DirectX11 mintához való hozzáadásához szükséges összes lépést. Saját modell konvertálásához tekintse meg az alábbi rövid útmutatót: