Udostępnij za pośrednictwem


Samouczek: integrowanie usługi Remote Rendering z aplikacją HoloLens Holographic

Ten samouczek zawiera następujące informacje:

  • Tworzenie aplikacji holograficznej, którą można wdrożyć na urządzeniu HoloLens przy użyciu programu Visual Studio
  • Dodawanie niezbędnych fragmentów kodu i ustawień projektu w celu połączenia renderowania lokalnego z zdalnie renderowaną zawartością

Ten samouczek koncentruje się na dodawaniu niezbędnych bitów do natywnego Holographic App przykładu w celu połączenia renderowania lokalnego z usługą Azure Remote Rendering. Jedynym typem opinii o stanie w tej aplikacji jest panel danych wyjściowych debugowania w programie Visual Studio, dlatego zaleca się uruchomienie przykładu z poziomu programu Visual Studio. Dodanie odpowiednich opinii w aplikacji wykracza poza zakres tego przykładu, ponieważ tworzenie dynamicznego panelu tekstowego od podstaw wymaga dużo kodowania. Dobrym punktem wyjścia jest klasa StatusDisplay, która jest częścią przykładowego projektu remoting Player w usłudze GitHub. W rzeczywistości wstępnie konserwowana wersja tego samouczka używa lokalnej kopii tej klasy.

Napiwek

Repozytorium przykładów ARR zawiera wynik tego samouczka jako projekt programu Visual Studio, który jest gotowy do użycia. Jest on również wzbogacony o odpowiednie raportowanie błędów i stanu za pośrednictwem klasy StatusDisplayinterfejsu użytkownika . W ramach samouczka wszystkie dodatki specyficzne dla usługi ARR są ograniczone przez #ifdef USE_REMOTE_RENDERING / #endifusługę , więc łatwo jest zidentyfikować dodatki do renderowania zdalnego.

Wymagania wstępne

Na potrzeby tego samouczka potrzebne są następujące elementy:

  • Informacje o koncie (identyfikator konta, klucz konta, domena konta, identyfikator subskrypcji). Jeśli nie masz konta, utwórz konto.
  • Windows SDK 10.0.18362.0 (pobierz).
  • Najnowsza wersja programu Visual Studio 2022 (pobierz).
  • Narzędzia programu Visual Studio dla rzeczywistości mieszanej. W szczególności następujące instalacje obciążeń są obowiązkowe:
    • Programowanie aplikacji klasycznych za pomocą języka C++
    • Tworzenie aplikacji platforma uniwersalna systemu Windows (UWP)
  • Szablony aplikacji windows Mixed Reality dla programu Visual Studio (pobierz).

Tworzenie nowego przykładu aplikacji holograficznej

W pierwszym kroku utworzymy przykład giełdowy, który jest podstawą integracji z usługą Remote Rendering. Otwórz program Visual Studio i wybierz pozycję "Utwórz nowy projekt" i wyszukaj frazę "Holographic DirectX 11 App (Universal Windows) (C++/WinRT)"

Tworzenie nowego projektu

Wpisz wybraną nazwę projektu, wybierz ścieżkę i wybierz przycisk "Utwórz". W nowym projekcie przełącz konfigurację na "Debugowanie /ARM64". Teraz powinno być możliwe skompilowanie i wdrożenie go na połączonym urządzeniu HoloLens 2. Jeśli uruchomisz go na urządzeniu HoloLens, przed tobą powinien zostać wyświetlony obrotowy moduł.

Dodawanie zależności renderowania zdalnego za pomocą narzędzia NuGet

Pierwszym krokiem do dodawania funkcji remote rendering jest dodanie zależności po stronie klienta. Odpowiednie zależności są dostępne jako pakiet NuGet. W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt i wybierz pozycję "Zarządzaj pakietami NuGet..." z menu kontekstowego.

W wyświetlonym oknie dialogowym wyszukaj pakiet NuGet o nazwie "Microsoft.Azure.RemoteRendering.Cpp":

Przeglądaj pod kątem pakietu NuGet

dodaj go do projektu, wybierając pakiet, a następnie naciskając przycisk "Zainstaluj".

Pakiet NuGet dodaje do projektu zależności renderowania zdalnego. Szczególnie:

  • Połącz z biblioteką klienta (RemoteRenderingClient.lib).
  • Skonfiguruj zależności .dll.
  • Ustaw poprawną ścieżkę do katalogu include.

Przygotowywanie projektu

Musimy wprowadzić niewielkie zmiany w istniejącym projekcie. Te zmiany są subtelne, ale bez nich zdalne renderowanie nie będzie działać.

Włączanie ochrony wielowątku na urządzeniu DirectX

Urządzenie DirectX11 musi mieć włączoną ochronę wielowątkową. Aby to zmienić, otwórz plik DeviceResources.cpp w folderze "Common" i wstaw następujący kod na końcu funkcji 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);
}

Włączanie możliwości sieci w manifeście aplikacji

Funkcje sieci muszą być jawnie włączone dla wdrożonej aplikacji. Bez tej konfiguracji zapytania połączeń w końcu spowodują przekroczenie limitu czasu. Aby włączyć, kliknij package.appxmanifest dwukrotnie element w Eksploratorze rozwiązań. W następnym interfejsie użytkownika przejdź do karty Możliwości i wybierz pozycję:

  • Internet (klient i serwer)
  • Internet (klient)

Możliwości sieci

Integrowanie zdalnego renderowania

Teraz, gdy projekt jest przygotowany, możemy rozpocząć od kodu. Dobrym punktem wejścia do aplikacji jest klasa HolographicAppMain(plik HolographicAppMain.h/cpp), ponieważ zawiera wszystkie niezbędne punkty zaczepienia do inicjowania, deicjalizacji i renderowania.

Zawiera

Zaczynamy od dodania niezbędnych dołączań. Dodaj następujące elementy dołączane do pliku HolographicAppMain.h:

#include <AzureRemoteRendering.h>

... i te dodatkowe include dyrektywy do złożenia HolographicAppMain.cpp:

#include <AzureRemoteRendering.inl>
#include <RemoteRenderingExtensions.h>
#include <windows.perception.spatial.h>

Dla uproszczenia kodu definiujemy następujący skrót przestrzeni nazw w górnej części pliku HolographicAppMain.h po dyrektywach include :

namespace RR = Microsoft::Azure::RemoteRendering;

Ten skrót jest przydatny, więc nie musimy zapisywać pełnej przestrzeni nazw wszędzie, ale nadal można rozpoznać struktury danych specyficzne dla usługi ARR. Oczywiście moglibyśmy również użyć using namespace... dyrektywy .

Inicjowanie renderowania zdalnego

Musimy przechowywać kilka obiektów dla sesji itp. w okresie istnienia aplikacji. Okres istnienia pokrywa się z okresem istnienia obiektu aplikacji HolographicAppMain , dlatego dodamy obiekty jako elementy członkowskie do klasy HolographicAppMain. Następnym krokiem jest dodanie następujących składowych klasy w pliku 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
}

Dobrym miejscem do wykonania rzeczywistej implementacji jest konstruktor klasy HolographicAppMain. Musimy wykonać w tym miejscu trzy typy inicjalizacji:

  1. Jednorazowe inicjowanie systemu zdalnego renderowania
  2. Tworzenie klienta (uwierzytelnianie)
  3. Tworzenie sesji

Robimy to wszystko sekwencyjnie w konstruktorze. Jednak w rzeczywistych przypadkach użycia może być konieczne oddzielne wykonanie tych kroków.

Dodaj następujący kod na początku treści konstruktora w pliku 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:
    ...
}

Kod wywołuje funkcje SetNewSession członkowskie i SetNewState, które zaimplementujemy w następnym akapicie wraz z resztą kodu maszyny stanu.

Należy pamiętać, że poświadczenia są zakodowane w przykładzie i muszą zostać wypełnione (identyfikator konta, klucz konta, domena konta i domena renderowania zdalnego).

Przeprowadzamy deicjalizacji symetrycznie i w odwrotnej kolejności na końcu treści destruktora:

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

Maszyna stanu

W usłudze Remote Rendering kluczowe funkcje służące do tworzenia sesji i ładowania modelu są funkcjami asynchronicznymi. Aby to uwzględnić, potrzebujemy prostej maszyny stanu, która zasadniczo przechodzi przez następujące stany automatycznie:

Inicjowanie —> tworzenie sesji —> rozpoczęcie sesji —> ładowanie modelu (z postępem)

W związku z tym w następnym kroku do klasy dodamy nieco obsługi maszyny stanu. Deklarujemy własne wyliczenie AppConnectionStatus dla różnych stanów, w których może znajdować się nasza aplikacja. Jest on podobny do RR::ConnectionStatus, ale ma dodatkowy stan dla nieudanego połączenia.

Dodaj następujące elementy członkowskie i funkcje do deklaracji klasy:

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

Po stronie implementacji w pliku .cpp dodaj następujące jednostki funkcji:

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

Aktualizacja na ramkę

Musimy zaktualizować klienta raz na znacznik symulacji i wykonać dodatkowe aktualizacje stanu. Funkcja HolographicAppMain::Update zapewnia dobry punkt zaczepienia dla aktualizacji na ramkę.

Aktualizacja maszyny stanu

Musimy sondować stan sesji i sprawdzić, czy została ona przeniesiona do Ready stanu. Jeśli pomyślnie nawiązaliśmy połączenie, na koniec uruchomimy ładowanie modelu za pośrednictwem polecenia StartModelLoading.

Dodaj następujący kod do treści funkcji 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:
    ...
}

Współrzędna aktualizacji systemu

Musimy zgodzić się z usługą renderowania w systemie współrzędnych do użycia. Aby uzyskać dostęp do systemu współrzędnych, którego chcemy użyć, potrzebujemy m_stationaryReferenceFrame elementu utworzonego na końcu funkcji HolographicAppMain::OnHolographicDisplayIsAvailableChanged.

Ten układ współrzędnych zwykle nie zmienia się, więc jest to jednorazowa inicjalizacja. Należy wywołać go ponownie, jeśli aplikacja zmieni układ współrzędnych.

Powyższy kod ustawia układ współrzędnych raz w Update funkcji zaraz po utworzeniu układu współrzędnych odwołania i połączonej sesji.

aktualizacja Aparat

Musimy zaktualizować płaszczyzny klipów kamer, aby aparat serwera był zsynchronizowany z aparatem lokalnym. Możemy to zrobić na samym końcu Update funkcji:

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

Renderowanie

Ostatnią rzeczą do zrobienia jest wywołanie renderowania zawartości zdalnej. Musimy wykonać to wywołanie w dokładnej właściwej pozycji w potoku renderowania, po wyczyszce celu renderowania i ustawieniu widoku. Wstaw następujący fragment kodu do blokady UseHolographicCameraResources wewnątrz funkcji 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();
        }

        ...

Uruchamianie aplikacji przykładowej

Przykład powinien teraz znajdować się w stanie, w którym kompiluje i uruchamia.

Gdy próbka działa prawidłowo, pokazuje obracający się moduł bezpośrednio przed Tobą, a po utworzeniu sesji i załadowaniu modelu renderuje model silnika znajdujący się w bieżącej pozycji głównej. Tworzenie sesji i ładowanie modelu może potrwać do kilku minut. Bieżący stan jest zapisywany tylko w panelu danych wyjściowych programu Visual Studio. Dlatego zaleca się uruchomienie przykładu z poziomu programu Visual Studio.

Uwaga

Klient rozłącza się z serwerem, gdy funkcja znacznika nie jest wywoływana przez kilka sekund. Dlatego wyzwalanie punktów przerwania może bardzo łatwo spowodować rozłączenie aplikacji.

Aby uzyskać prawidłowy stan wyświetlany w panelu tekstowym, zapoznaj się ze wstępnie obsługiwaną wersją tego samouczka w usłudze GitHub.

Następne kroki

W tym samouczku przedstawiono wszystkie kroki niezbędne do dodania usługi Remote Rendering do stockowego przykładu Holographic App C++/DirectX11. Aby przekonwertować własny model, zapoznaj się z następującym przewodnikiem Szybki start: