Navigacija
Lista poslednjih: 16, 32, 64, 128 poruka.

Direct3D resize window & swap chain

[es] :: 3D programiranje :: Direct3D resize window & swap chain

[ Pregleda: 3581 | Odgovora: 7 ] > FB > Twit

Postavi temu Odgovori

Autor

Pretraga teme: Traži
Markiranje Štampanje RSS

dontoo

Član broj: 249815
Poruke: 40
*.adsl.net.t-com.hr.



+1 Profil

icon Direct3D resize window & swap chain05.03.2010. u 12:33 - pre 171 meseci
Čitam knjigu Intro to 3D Game Programming with DirectX 10 i tamo u prvom primjeru( gdje incijaliziramo device) piše da svaki put kad resajzamo window moramo ponovno kreirati swap chain, set viewport... itd. Ali također proučavam i Directx SDK tutorijale i tamo to ne spominju niti rade ( bar ne u početnim primjerima koje čitam ). Ovo je primjer iz SDK tutorijala gdje samo jednom funkcijom InitDevice() inicijaliziraju device. Ispod se nalazi primjer iz knjige gdje na case WM_SIZE: u msgProc() procedruri za hvatanje događaje autor knjge poziva funkciju OnResize() koja radi isto što i InitDevice() funkcija is SDK tutorijala samo što se ova funkcija poziva svaki put kad resajzamo prozor. Dali mi možete to malo pojasnit, dali trebam rekreirat device svaki put kad resajzam prozor (kao u knjizi ) ili da radim kao u SDK tutorijalima. Ili sam ja sve krivo skopčao.
Code:
//--------------------------------------------------------------------------------------
// File: Tutorial01.cpp
//
// This application demonstrates creating a Direct3D 10 device
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//--------------------------------------------------------------------------------------
#include <windows.h>
#include <d3d10.h>
#include <d3dx10.h>
#include "resource.h"


//--------------------------------------------------------------------------------------
// Global Variables
//--------------------------------------------------------------------------------------
HINSTANCE               g_hInst = NULL;
HWND                    g_hWnd = NULL;
D3D10_DRIVER_TYPE       g_driverType = D3D10_DRIVER_TYPE_NULL;
ID3D10Device*           g_pd3dDevice = NULL;
IDXGISwapChain*         g_pSwapChain = NULL;
ID3D10RenderTargetView* g_pRenderTargetView = NULL;


//--------------------------------------------------------------------------------------
// Forward declarations
//--------------------------------------------------------------------------------------
HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow );
HRESULT InitDevice();
void CleanupDevice();
LRESULT CALLBACK    WndProc( HWND, UINT, WPARAM, LPARAM );
void Render();


//--------------------------------------------------------------------------------------
// Entry point to the program. Initializes everything and goes into a message processing 
// loop. Idle time is used to render the scene.
//--------------------------------------------------------------------------------------
int WINAPI wWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow )
{
    if( FAILED( InitWindow( hInstance, nCmdShow ) ) )
        return 0;

 
    // Main message loop
    MSG msg = {0};
    while( WM_QUIT != msg.message )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            Render();
        }
    }

    CleanupDevice();

    return ( int )msg.wParam;
}


//--------------------------------------------------------------------------------------
// Register class and create window
//--------------------------------------------------------------------------------------
HRESULT InitWindow( HINSTANCE hInstance, int nCmdShow )
{
    // Register class
    WNDCLASSEX wcex;
    wcex.cbSize = sizeof( WNDCLASSEX );
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon( hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
    wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"TutorialWindowClass";
    wcex.hIconSm = LoadIcon( wcex.hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
    if( !RegisterClassEx( &wcex ) )
        return E_FAIL;

    // Create window
    g_hInst = hInstance;
    RECT rc = { 0, 0, 640, 480 };
    AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
    g_hWnd = CreateWindow( L"TutorialWindowClass", L"Direct3D 10 Tutorial 1: Direct3D 10 Basics", WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance,
                           NULL );
    if( !g_hWnd )
        return E_FAIL;

    ShowWindow( g_hWnd, nCmdShow );

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Called every time the application receives a message
//--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    PAINTSTRUCT ps;
    HDC hdc;

    switch( message )
    {
        case WM_PAINT:
            hdc = BeginPaint( hWnd, &ps );
            EndPaint( hWnd, &ps );
            break;

        case WM_DESTROY:
            PostQuitMessage( 0 );
            break;

        default:
            return DefWindowProc( hWnd, message, wParam, lParam );
    }

    return 0;
}


//--------------------------------------------------------------------------------------
// Create Direct3D device and swap chain
//--------------------------------------------------------------------------------------
HRESULT InitDevice()
{
    HRESULT hr = S_OK;;

    RECT rc;
    GetClientRect( g_hWnd, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

    D3D10_DRIVER_TYPE driverTypes[] =
    {
        D3D10_DRIVER_TYPE_HARDWARE,
        D3D10_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = sizeof( driverTypes ) / sizeof( driverTypes[0] );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = g_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
    {
        g_driverType = driverTypes[driverTypeIndex];
        hr = D3D10CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags,
                                            D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice );
        if( SUCCEEDED( hr ) )
            break;
    }
    if( FAILED( hr ) )
        return hr;

    // Create a render target view
    ID3D10Texture2D* pBackBuffer;
    hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), ( LPVOID* )&pBackBuffer );
    if( FAILED( hr ) )
        return hr;

    hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
    pBackBuffer->Release();
    if( FAILED( hr ) )
        return hr;

    g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );

    // Setup the viewport
    D3D10_VIEWPORT vp;
    vp.Width = width;
    vp.Height = height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    g_pd3dDevice->RSSetViewports( 1, &vp );

    return S_OK;
}


//--------------------------------------------------------------------------------------
// Render the frame
//--------------------------------------------------------------------------------------
void Render()
{
    // Just clear the backbuffer
    float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
    g_pd3dDevice->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
    g_pSwapChain->Present( 0, 0 );
}


//--------------------------------------------------------------------------------------
// Clean up the objects we've created
//--------------------------------------------------------------------------------------
void CleanupDevice()
{
    if( g_pd3dDevice ) g_pd3dDevice->ClearState();

    if( g_pRenderTargetView ) g_pRenderTargetView->Release();
    if( g_pSwapChain ) g_pSwapChain->Release();
    if( g_pd3dDevice ) g_pd3dDevice->Release();
}

Primjer iz knjige
Code:
    // WM_SIZE is sent when the user resizes the window.  
    case WM_SIZE:
        // Save the new client area dimensions.
        mClientWidth  = LOWORD(lParam);
        mClientHeight = HIWORD(lParam);
        if( md3dDevice )
        {
            if( wParam == SIZE_MINIMIZED )
            {
                mAppPaused = true;
                mMinimized = true;
                mMaximized = false;
            }
            else if( wParam == SIZE_MAXIMIZED )
            {
                mAppPaused = false;
                mMinimized = false;
                mMaximized = true;
                onResize();
            }
            else if( wParam == SIZE_RESTORED )
            {
                
                // Restoring from minimized state?
                if( mMinimized )
                {
                    mAppPaused = false;
                    mMinimized = false;
                    onResize();
                }

                // Restoring from maximized state?
                else if( mMaximized )
                {
                    mAppPaused = false;
                    mMaximized = false;
                    onResize();
                }
                else if( mResizing )
                {
                    // If user is dragging the resize bars, we do not resize 
                    // the buffers here because as the user continuously 
                    // drags the resize bars, a stream of WM_SIZE messages are
                    // sent to the window, and it would be pointless (and slow)
                    // to resize for each WM_SIZE message received from dragging
                    // the resize bars.  So instead, we reset after the user is 
                    // done resizing the window and releases the resize bars, which 
                    // sends a WM_EXITSIZEMOVE message.
                }
                else // API call such as SetWindowPos or mSwapChain->SetFullscreenState.
                {
                    onResize();
                }
            }
        }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
void D3DApp::onResize()
{
    // Release the old views, as they hold references to the buffers we
    // will be destroying.  Also release the old depth/stencil buffer.

    ReleaseCOM(mRenderTargetView);
    ReleaseCOM(mDepthStencilView);
    ReleaseCOM(mDepthStencilBuffer);


    // Resize the swap chain and recreate the render target view.

    HR(mSwapChain->ResizeBuffers(1, mClientWidth, mClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
    ID3D10Texture2D* backBuffer;
    HR(mSwapChain->GetBuffer(0, __uuidof(ID3D10Texture2D), reinterpret_cast<void**>(&backBuffer)));
    HR(md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView));
    ReleaseCOM(backBuffer);


    // Create the depth/stencil buffer and view.

    D3D10_TEXTURE2D_DESC depthStencilDesc;
    
    depthStencilDesc.Width     = mClientWidth;
    depthStencilDesc.Height    = mClientHeight;
    depthStencilDesc.MipLevels = 1;
    depthStencilDesc.ArraySize = 1;
    depthStencilDesc.Format    = DXGI_FORMAT_D24_UNORM_S8_UINT;
    depthStencilDesc.SampleDesc.Count   = 1; // multisampling must match
    depthStencilDesc.SampleDesc.Quality = 0; // swap chain values.
    depthStencilDesc.Usage          = D3D10_USAGE_DEFAULT;
    depthStencilDesc.BindFlags      = D3D10_BIND_DEPTH_STENCIL;
    depthStencilDesc.CPUAccessFlags = 0; 
    depthStencilDesc.MiscFlags      = 0;

    HR(md3dDevice->CreateTexture2D(&depthStencilDesc, 0, &mDepthStencilBuffer));
    HR(md3dDevice->CreateDepthStencilView(mDepthStencilBuffer, 0, &mDepthStencilView));


    // Bind the render target view and depth/stencil view to the pipeline.

    md3dDevice->OMSetRenderTargets(1, &mRenderTargetView, mDepthStencilView);
    

    // Set the viewport transform.

    D3D10_VIEWPORT vp;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    vp.Width    = mClientWidth;
    vp.Height   = mClientHeight;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;

    md3dDevice->RSSetViewports(1, &vp);
}
 
Odgovor na temu

Filip Strugar
Filip Strugar
UK

Član broj: 9871
Poruke: 383
91.143.178.*



+1 Profil

icon Re: Direct3D resize window & swap chain05.03.2010. u 13:30 - pre 171 meseci
Malo je nejasno sta pitas - u pocetku kazes da knjiga kaze da nakon resize-a treba re-kreirati swap chain, viewport itd, a posle pitas 'da li treba rekreirat device'?

Ne, ne treba re-kreirati device, samo swap chain i viewport, kako knjiga kaze - i to je sve. To moras da uradis, jer stari swap chain vise nije korektne velicine novog prozora.

Dobra stvar je sto za razliku od DirectX9 ne moras da re-kreiras nista drugo tako da je relativno jednostavno (u DirectX9-u se to zove Device Reset i onda moras da re-kreiras sve _DEFAULT memory pool resurse itd).

DirectX10/11 primeri takodje re-kreiraju swap chain, podese novi viewport itd - ne znam tacno gde, ali to se definitivno desava (inace resize prozora ne bi korektno radio, a radi!).
 
Odgovor na temu

dontoo

Član broj: 249815
Poruke: 40
*.adsl.net.t-com.hr.



+1 Profil

icon Re: Direct3D resize window & swap chain05.03.2010. u 14:43 - pre 171 meseci
Evo ovaj prvi kod je primjer iz DirectX SDK tutorijala i kao što se vidi swap chain i viewport je kreiran samo jedanput. Kad resajzam prozor primjer radi, a tako je i sa ostalim primjerima. Gdje je to u prvom primjeru rekreiran swap chain i viewport?
 
Odgovor na temu

dontoo

Član broj: 249815
Poruke: 40
*.adsl.net.t-com.hr.



+1 Profil

icon Re: Direct3D resize window & swap chain05.03.2010. u 14:51 - pre 171 meseci
Differences between Direct3D 9 and Direct3D 10:
Direct3D 10 is the first graphics component to use DXGI. DXGI has some different swap chain behaviors.

In DXGI, a swap chain is tied to a window when the swap chain is created. This change improves performance and saves memory. Previous versions of Direct3D allowed the swap chain to change the window that the swap chain is tied to.
In DXGI, a swap chain is tied to a rendering device on creation. A change to the rendering device requires the swap chain to be recreated.
In DXGI, the swap effects available are DXGI_SWAP_EFFECT_DISCARD and DXGI_SWAP_EFFECT_SEQUENTIAL. The following table shows a mapping of D3D9 to DXGI swap effect defines.
D3D9 Swap EffectDXGI Swap EffectD3DSWAPEFFECT_DISCARDDXGI_SWAP_EFFECT_DISCARDD3DSWAPEFFECT_COPYDXGI_SWAP_EFFECT_SEQUENTIAL with 1 bufferD3DSWAPEFFECT_FLIPDXGI_SWAP_EFFECT_SEQUENTIAL with 2 or more buffers

A swap chain's buffers are created at a particular size and in a particular format. The application specifies these values (or you can inherit the size from the target window) at startup, and can then optionally modify them as the window size changes in response to user input or program events.
http://msdn.microsoft.com/en-us/library/ee415671(VS.85).aspx
Dakle kod D3D10 rekreacija swap chain je opcionalna kod resajza prozora?
 
Odgovor na temu

Filip Strugar
Filip Strugar
UK

Član broj: 9871
Poruke: 383
*.zone7.bethere.co.uk.



+1 Profil

icon Re: Direct3D resize window & swap chain05.03.2010. u 21:07 - pre 171 meseci
Ah mislio sam da pricas o DirectX primerima - sorry, tako mi i treba kad povrsno citam poruke.

Da, u pravu si, re-kreacija je opciona ali imaj na umu da ako ne re-kreiras (zapravo re-sizeujes), rendering ce da se radi u staru velicinu i da se na kraju skalira na prozor prave velicine sto u vecini slucajeva ne zelis.
Probaj sa tim primerom - kreiraj pocetni prozor da bude tipa 300x200, pa ga povecaj preko celog ekrana - trebalo bi da sve bude mutno!

Tako da moras da uradis resize.

Ovde ima jos par detalja na tu temu: http://msdn.microsoft.com/en-us/library/ee415671%28VS.85%29.aspx ("Care and Feeding of the Swap Chain").
 
Odgovor na temu

dontoo

Član broj: 249815
Poruke: 40
*.adsl.net.t-com.hr.



+1 Profil

icon Re: Direct3D resize window & swap chain06.03.2010. u 10:21 - pre 171 meseci
ok, kužim, thx
 
Odgovor na temu

dontoo

Član broj: 249815
Poruke: 40
*.adsl.net.t-com.hr.



+1 Profil

icon Re: Direct3D resize window & swap chain06.03.2010. u 10:51 - pre 171 meseci
Čekaj, još jedno pitanje. Ako je ovo kod za kreiranje svega:
Code:
HRESULT InitDevice()
{
    HRESULT hr = S_OK;;

    RECT rc;
    GetClientRect( g_hWnd, &rc );
    UINT width = rc.right - rc.left;
    UINT height = rc.bottom - rc.top;

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D10_CREATE_DEVICE_DEBUG;
#endif

    D3D10_DRIVER_TYPE driverTypes[] =
    {
        D3D10_DRIVER_TYPE_HARDWARE,
        D3D10_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = sizeof( driverTypes ) / sizeof( driverTypes[0] );

    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof( sd ) );
    sd.BufferCount = 1;
    sd.BufferDesc.Width = width;
    sd.BufferDesc.Height = height;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.OutputWindow = g_hWnd;
    sd.SampleDesc.Count = 1;
    sd.SampleDesc.Quality = 0;
    sd.Windowed = TRUE;

    for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ )
    {
        g_driverType = driverTypes[driverTypeIndex];
        hr = D3D10CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags,
                                            D3D10_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice );
        if( SUCCEEDED( hr ) )
            break;
    }
    if( FAILED( hr ) )
        return hr;

    // Create a render target view
    ID3D10Texture2D* pBackBuffer;
    hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D10Texture2D ), ( LPVOID* )&pBackBuffer );
    if( FAILED( hr ) )
        return hr;

    hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
    pBackBuffer->Release();
    if( FAILED( hr ) )
        return hr;

    g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );

    // Setup the viewport
    D3D10_VIEWPORT vp;
    vp.Width = width;
    vp.Height = height;
    vp.MinDepth = 0.0f;
    vp.MaxDepth = 1.0f;
    vp.TopLeftX = 0;
    vp.TopLeftY = 0;
    g_pd3dDevice->RSSetViewports( 1, &vp );

    return S_OK;
}

A ovo kod za otpuštanje COM objekata.
Code:
void CleanupDevice()
{
    if( g_pd3dDevice ) g_pd3dDevice->ClearState();

    if( g_pRenderTargetView ) g_pRenderTargetView->Release();
    if( g_pSwapChain ) g_pSwapChain->Release();
    if( g_pd3dDevice ) g_pd3dDevice->Release();
}

Ako napravim resize funkciju OnResize() koja kreira swap chain i viewport koje objekte moram otpustit od starog swap chaina?

OnResize ()
{
g_pRenderTargetView->Release();
g_pSwapChain->Release();
//Što još treba otpustit? Ne treba otpustit cijeli device.
//Sad kreiramo novi swap chain i viewport
}

 
Odgovor na temu

Filip Strugar
Filip Strugar
UK

Član broj: 9871
Poruke: 383
*.zone7.bethere.co.uk.



+1 Profil

icon Re: Direct3D resize window & swap chain08.03.2010. u 22:09 - pre 171 meseci
Dokumentacija kaze da za resize ne treba da re-kreiras SwapChain - samo da releasujes sve reference na buffere koje drzis i da onda pozoves ResizeBuffers(). Tako isto rade DirectX10/DirectX11 primeri iz DirectX SDK-a - pogledaj DXUT.cpp, funkcija DXUTResizeDXGIBuffers().
Ti DirectX samples su najbolji primer za ovakve stvari, samo malo su prekomplikovani jer pokrivaju sve moguce opcije i imaju raznoraznu drugu funkcionalnost.

Ukratko:

Mislim da treba samo da Release-ujes g_pRenderTargetView jer je to jedino sto si ti napravio (gledajuci taj kod) a taj view drzi referencu na back buffer.

Cini mi se, mada nisam 100% siguran, da sam back buffer i depth stencil buffer ne treba da releasujes jer si koristio D3D10CreateDeviceAndSwapChain() ali ako to nije tacno ->ResizeBuffers ce ti prijaviti gresku.

Dakle sam swap chain objekat ne treba da releaseujes, nego samo pozoves g_pSwapChain->ResizeBuffers( ... ) sa novim parametrima i on bi trebalo da interno releaseuje i to je cini mi se to.
Ako nesto ne radi, g_pSwapChain->ResizeBuffers ce ti prijaviti gresku (za slucaj da moras da Release-ujes jos nesto)

Jako je korisno da kada kreiras DEVICE u debug build konfiguracijama koristis D3D10_CREATE_DEVICE_DEBUG flag da bi dobio upozorenja ako nesto ne valja u debug output-u. Ja takodje imam ukljucen Break on Error, Warning, etc, da mi ne promakne neka greska ako ne gledam debug output - to mozes da uradis tako sto odes u DirectX control panel (32bit ako ti je tvoj program 32bit) koji se nalazi u DirectX SDK-u, odes u Direct3D 10.x/11 Tab, izaberes/dodas putanju gde se nalazi tvoj program u listu na vrhu, i ukljucis u 'Break Settings' 'Enable break on functionality' i izaberes 'Error', 'Corruption' i 'Warning'.

(A, da, viewport nije COM objekat, samo struktura koju postavljas, tako da o tome ne moras da brines.)


To je valjda to :)
 
Odgovor na temu

[es] :: 3D programiranje :: Direct3D resize window & swap chain

[ Pregleda: 3581 | Odgovora: 7 ] > FB > Twit

Postavi temu Odgovori

Navigacija
Lista poslednjih: 16, 32, 64, 128 poruka.