Kurz: Integrace vzdáleného vykreslování do holografické aplikace HoloLens
V tomto kurzu se dozvíte:
- Vytvoření holografické aplikace, která se dá nasadit do HoloLensu pomocí sady Visual Studio
- Přidejte potřebné fragmenty kódu a nastavení projektu pro kombinování místního vykreslování s vzdáleně vykresleným obsahem.
Tento kurz se zaměřuje na přidání potřebných bitů do nativní Holographic App
ukázky pro kombinování místního vykreslování se službou Azure Remote Rendering. Jediný typ zpětné vazby stavu v této aplikaci je prostřednictvím výstupního panelu ladění v sadě Visual Studio, takže doporučujeme spustit ukázku ze sady Visual Studio. Přidání správné zpětné vazby v aplikaci je nad rámec této ukázky, protože vytvoření dynamického textového panelu od začátku zahrnuje spoustu kódování. Dobrým výchozím bodem je třída StatusDisplay
, která je součástí ukázkového projektu Remoting Player na GitHubu. Ve skutečnosti používá předem naskenovaná verze tohoto kurzu místní kopii této třídy.
Tip
Úložiště ukázek ARR obsahuje výsledek tohoto kurzu jako projekt sady Visual Studio, který je připravený k použití. Je také rozšířen o správné hlášení chyb a stavu prostřednictvím třídy StatusDisplay
uživatelského rozhraní . V tomto kurzu jsou všechny přidání specifické pro ARR vymezeny #ifdef USE_REMOTE_RENDERING
/ #endif
podle , takže je snadné identifikovat doplňky vzdáleného vykreslování.
Požadavky
Pro účely tohoto kurzu potřebujete:
- Informace o účtu (ID účtu, klíč účtu, doména účtu, ID předplatného). Pokud účet nemáte, vytvořte si ho.
- Windows SDK 10.0.18362.0 (stáhnout)
- Nejnovější verze sady Visual Studio 2022 (stáhnout)
- Nástroje sady Visual Studio pro hybridní realitu Konkrétně jsou povinné následující instalace úloh :
- Vývoj desktopových aplikací pomocí jazyka C++
- vývoj pro Univerzální platforma Windows (UPW)
- Šablony aplikací pro Windows Mixed Reality pro Visual Studio (stáhnout)
Vytvoření nové ukázky holografické aplikace
Jako první krok vytvoříme ukázku akcií, která je základem integrace vzdáleného vykreslování. Otevřete Visual Studio a vyberte Vytvořit nový projekt a vyhledejte Holographic DirectX 11 App (Universal Windows) (C++/WinRT).
Zadejte název projektu podle svého výběru, zvolte cestu a vyberte tlačítko Vytvořit. V novém projektu přepněte konfiguraci na Ladění / ARM64. Teď byste měli být schopni ho zkompilovat a nasadit na připojené zařízení HoloLens 2. Pokud ji spustíte na HoloLensu, měla by se před vámi zobrazit rotující datová krychle.
Přidání závislostí vzdáleného vykreslování prostřednictvím NuGetu
Prvním krokem k přidání funkcí vzdáleného vykreslování je přidání závislostí na straně klienta. Relevantní závislosti jsou k dispozici jako balíček NuGet. V Průzkumník řešení klikněte pravým tlačítkem na projekt a v místní nabídce vyberte Spravovat balíčky NuGet.
V dialogovém okně s výzvou vyhledejte balíček NuGet s názvem Microsoft.Azure.RemoteRendering.Cpp:
a přidejte ho do projektu tak, že vyberete balíček a stisknete tlačítko Nainstalovat.
Balíček NuGet přidá do projektu závislosti vzdáleného vykreslování. Konkrétně:
- Propojení s klientskou knihovnou (RemoteRenderingClient.lib)
- Nastavte .dll závislostí.
- Nastavte správnou cestu k adresáři include.
Příprava projektu
Potřebujeme udělat malé změny stávajícího projektu. Tyto změny jsou drobné, ale bez nich by vzdálené vykreslování nefungovalo.
Povolení ochrany před více vlákny na zařízení s DirectX
Zařízení DirectX11
musí mít povolenou ochranu před více vlákny. Pokud to chcete změnit, otevřete soubor DeviceResources.cpp ve složce Common a na konec funkce DeviceResources::CreateDeviceResources()
vložte následující kód:
// 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);
}
Povolení síťových funkcí v manifestu aplikace
Síťové funkce musí být pro nasazenou aplikaci explicitně povolené. Bez konfigurace budou dotazy na připojení nakonec mít za následek vypršení časových limitů. Pokud chcete tuto možnost povolit, poklikejte na package.appxmanifest
položku v Průzkumníku řešení. V dalším uživatelském rozhraní přejděte na kartu Schopnosti a vyberte:
- Internet (klient a server)
- Internet (klient)
Integrace vzdáleného vykreslování
Teď, když je projekt připravený, můžeme začít kódem. Dobrým vstupním bodem aplikace je třída HolographicAppMain
(soubor HolographicAppMain.h/cpp), protože má všechny potřebné háky pro inicializaci, deicializaci a vykreslování.
Zahrnuje
Začneme přidáním potřebných součástí. Do souboru HolographicAppMain.h přidejte následující:
#include <AzureRemoteRendering.h>
... a tyto další include
direktivy pro soubor HolographicAppMain.cpp:
#include <AzureRemoteRendering.inl>
#include <RemoteRenderingExtensions.h>
#include <windows.perception.spatial.h>
Pro zjednodušení kódu definujeme následující zástupce oboru názvů v horní části souboru HolographicAppMain.h za direktivy include
:
namespace RR = Microsoft::Azure::RemoteRendering;
Tato zkratka je užitečná, takže nemusíme zapisovat celý obor názvů všude, ale stále dokáže rozpoznat datové struktury specifické pro ARR. Samozřejmě bychom mohli tuto směrnici using namespace...
použít.
Inicializace vzdáleného vykreslování
Během životnosti aplikace potřebujeme uchovávat několik objektů pro relaci atd. Životnost se shoduje s životností objektu aplikace HolographicAppMain
, takže objekty přidáme jako členy do třídy HolographicAppMain
. Dalším krokem je přidání následujících členů třídy do souboru 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
}
Dobrým místem k provedení skutečné implementace je konstruktor třídy HolographicAppMain
. Musíme tam udělat tři typy inicializace:
- Jednorázová inicializace systému vzdáleného vykreslování
- Vytvoření klienta (ověřování)
- Vytvoření relace
To vše děláme postupně v konstruktoru. V reálných případech použití však může být vhodné provést tyto kroky samostatně.
Na začátek těla konstruktoru v souboru HolographicAppMain.cpp přidejte následující kód:
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:
...
}
Kód volá členské funkce SetNewSession
, SetNewState
které budeme implementovat v dalším odstavci spolu se zbytkem kódu stavového počítače.
Všimněte si, že přihlašovací údaje jsou pevně zakódované v ukázce a je potřeba je vyplnit (ID účtu, klíč účtu, doména účtu a vzdálená doména vykreslování).
Deicializaci provádíme symetricky a v obráceném pořadí na konci těla destruktoru:
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();
}
Stavový počítač
Ve vzdáleném vykreslování jsou klíčové funkce pro vytvoření relace a načtení modelu asynchronní funkce. Abychom to mohli zohlednit, potřebujeme jednoduchý stavový počítač, který v podstatě přechází mezi následující stavy automaticky:
Inicializace – Vytvoření relace –>> Spuštění relace –> Načítání modelu (s průběhem)
Proto jako další krok přidáme do třídy trochu zpracování stavových počítačů. Deklarujeme vlastní výčet AppConnectionStatus
pro různé stavy, ve které může být naše aplikace. Je to podobné RR::ConnectionStatus
, ale má další stav pro neúspěšné připojení.
Do deklarace třídy přidejte následující členy a funkce:
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;
}
Na straně implementace v souboru .cpp přidejte tyto těla funkcí:
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;
}
}
Aktualizace jednotlivých snímků
Musíme aktualizovat klienta jednou na jedno tlačítko simulace a provést některé další aktualizace stavu. Funkce HolographicAppMain::Update
poskytuje dobrý háček pro aktualizace jednotlivých snímků.
Aktualizace stavových počítačů
Musíme se dotazovat na stav relace a zjistit, jestli se přešel do Ready
stavu. Pokud jsme se úspěšně připojili, konečně zahájíme načítání modelu přes StartModelLoading
.
Do těla funkce HolographicAppMain::Update
přidejte následující kód:
// 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:
...
}
Aktualizace souřadnicového systému
Musíme souhlasit s vykreslovací službou v souřadnicovém systému, který se má použít. Pro přístup ke souřadnicovém systému, který chceme použít, potřebujeme m_stationaryReferenceFrame
, aby byl vytvořen na konci funkce HolographicAppMain::OnHolographicDisplayIsAvailableChanged
.
Tento souřadnicový systém se obvykle nemění, takže se jedná o jednorázovou inicializaci. Pokud aplikace změní souřadnicový systém, musí být znovu volána.
Výše uvedený kód nastaví souřadnicový systém jednou v rámci Update
funkce, jakmile oba máme referenční souřadnicový systém a připojenou relaci.
aktualizace Kamera
Musíme aktualizovat roviny klipartů kamery tak, aby serverová kamera byla synchronizovaná s místní kamerou. Můžeme to udělat na samém konci Update
funkce:
...
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;
}
Vykreslování
Poslední věcí, kterou je potřeba udělat, je vyvolání vykreslování vzdáleného obsahu. Toto volání musíme provést přesně na správné pozici v kanálu vykreslování po vymazání cíle vykreslení a nastavení oblasti zobrazení. Vložte následující fragment kódu do UseHolographicCameraResources
zámku uvnitř funkce 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();
}
...
Spuštění ukázky
Ukázka by teď měla být ve stavu, ve kterém se kompiluje a spouští.
Při správném spuštění ukázky se před vámi zobrazí rotující datová krychle a po vytvoření relace a načtení modelu se vykreslí model motoru umístěný na aktuální pozici hlavy. Vytvoření relace a načítání modelu může trvat až několik minut. Aktuální stav se zapíše jenom na výstupní panel sady Visual Studio. Proto doporučujeme spustit ukázku ze sady Visual Studio.
Upozornění
Klient se od serveru odpojí, když se funkce zaškrtnutí po dobu několika sekund nevolá. Proto aktivace zarážek může velmi snadno způsobit odpojení aplikace.
Správné zobrazení stavu pomocí textového panelu najdete v předem naskenované verzi tohoto kurzu na GitHubu.
Další kroky
V tomto kurzu jste se seznámili se všemi kroky potřebnými k přidání vzdáleného vykreslování do ukázky Holographic App C++/DirectX11. Pokud chcete převést vlastní model, projděte si následující rychlý start: