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 StatusDisplaykeresztü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 / #endifrendelkezik, í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:

Ú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.

Create new project

Í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:

Browse for NuGet package

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

Network capabilities

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 HolographicAppMainkonstruktora. Három inicializálási típust kell elvégeznünk:

  1. A távoli renderelési rendszer egyszeri inicializálása
  2. Ügyfél létrehozása (hitelesítés)
  3. 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 SetNewStatea 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::ConnectionStatusde 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::Updatetö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::OnHolographicDisplayIsAvailableChangedvé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: