Condividi tramite


Uso di Direct2D per il rendering di Server-Side

Direct2D è ideale per le applicazioni grafiche che richiedono il rendering lato server in Windows Server. Questa panoramica descrive le nozioni di base sull'uso di Direct2D per il rendering lato server. Contiene le sezioni seguenti:

Requisiti per il rendering di Server-Side

Di seguito è riportato uno scenario tipico per un server di grafica: viene eseguito il rendering di grafici e immagini su un server e consegnati come bitmap in risposta alle richieste Web. Il server potrebbe essere dotato di una scheda grafica di fascia bassa o di nessuna scheda grafica.

Questo scenario rivela tre requisiti dell'applicazione. In primo luogo, l'applicazione deve gestire più richieste simultanee in modo efficiente, soprattutto nei server multicore. In secondo luogo, l'applicazione deve usare il rendering software durante l'esecuzione su server con una scheda grafica di fascia bassa o nessuna scheda grafica. Infine, l'applicazione deve essere eseguita come servizio nella sessione 0 in modo che non richieda l'accesso di un utente. Per altre informazioni sulla sessione 0, vedere Application Compatibility - Session 0 Isolation e Session Zero Guidelines for UMDF Drivers.

Opzioni per le API disponibili

Sono disponibili tre opzioni per il rendering lato server: GDI, GDI+ e Direct2D. Come GDI e GDI+, Direct2D è un'API di rendering 2D nativa che offre alle applicazioni un maggiore controllo sull'uso dei dispositivi grafici. Inoltre, Direct2D supporta in modo unico sia una factory a thread singolo sia una a multi-thread. Le sezioni seguenti confrontano ogni API in termini di qualità grafica e rendering multithread lato server.

GDI

A differenza di Direct2D e GDI+, GDI non supporta caratteristiche di disegno di alta qualità. Ad esempio, GDI non supporta l'antialiasing per la creazione di linee uniformi e ha solo un supporto limitato per la trasparenza. In base ai risultati dei test delle prestazioni grafiche in Windows 7 e Windows Server 2008 R2, Direct2D viene ridimensionato in modo più efficiente rispetto a GDI, nonostante la riprogettazione dei blocchi in GDI. Per altre informazioni su questi risultati dei test, vedere Engineering Windows 7 Graphics Performance.

Inoltre, le applicazioni che usano GDI sono limitate a 10.240 handle GDI per ogni processo e 65.536 handle GDI per ogni sessione. Il motivo è che internamente Windows usa word a 16 bit per archiviare l'indice degli handle per ogni sessione.

GDI+

Sebbene GDI+ supporti la fusione antialiasing e alfa per il disegno di alta qualità, il problema principale con GDI+ per gli scenari server è che non supporta l'esecuzione nella sessione 0. Poiché la sessione 0 supporta solo funzionalità non interattive, le funzioni che interagiscono direttamente o indirettamente con i dispositivi di visualizzazione riceveranno quindi errori. Esempi specifici di funzioni includono non solo quelli che gestiscono i dispositivi di visualizzazione, ma anche quelli indirettamente che gestiscono i driver di dispositivo.

Analogamente a GDI, GDI+ è limitato dal meccanismo di blocco. I meccanismi di blocco in GDI+ sono gli stessi in Windows 7 e Windows Server 2008 R2 come nelle versioni precedenti.

Direct2D

Direct2D è un'API grafica 2D accelerata dall'hardware che offre prestazioni elevate e rendering di alta qualità. Offre una fabbrica a thread singolo e una fabbrica multithread e la scalabilità lineare del rendering software a grana grossa.

A tale scopo, Direct2D definisce un'interfaccia della fabbrica radice. Come regola, un oggetto creato in una factory può essere usato solo con altri oggetti creati dalla stessa factory. Al momento della creazione, il chiamante può richiedere una factory a thread singolo o multithread. Se viene richiesta una factory a thread singolo, non viene eseguito alcun blocco dei thread. Se il chiamante richiede una factory multithreading, viene acquisito un blocco thread a livello di factory ogni volta che viene effettuata una chiamata in Direct2D.

Inoltre, il blocco dei thread in Direct2D è più granulare rispetto a GDI e GDI+, in modo che l'aumento del numero di thread abbia un impatto minimo sulle prestazioni.

Come usare Direct2D per il rendering di Server-Side

Le sezioni seguenti descrivono come usare il rendering software, come usare in modo ottimale una factory a thread singolo e una factory multithreading e come disegnare e salvare un disegno complesso in un file.

Software Rendering

Le applicazioni lato server usano il rendering software creando IWICBitmap destinazione di rendering, con il tipo di destinazione di rendering impostato su D2D1_RENDER_TARGET_TYPE_SOFTWARE o D2D1_RENDER_TARGET_TYPE_DEFAULT. Per altre informazioni sulle destinazioni di rendering di IWICBitmap, vedere il metodo ID2D1Factory::CreateWicBitmapRenderTarget; per altre informazioni sui tipi di destinazione di rendering, vedere D2D1_RENDER_TARGET_TYPE.

Multithreading

Sapere come creare e condividere factory e destinazioni di rendering tra thread può influire significativamente sulle prestazioni di un'applicazione. Le tre figure seguenti mostrano tre approcci diversi. L'approccio ottimale è illustrato nella figura 3.

diagramma multithreading Direct2D con un singolo target di rendering.

Nella figura 1, thread diversi condividono la stessa factory e la stessa destinazione di rendering. Questo approccio può causare risultati imprevedibili nei casi in cui più thread modificano simultaneamente lo stato della destinazione di rendering condivisa, ad esempio l'impostazione simultanea della matrice di trasformazione. Poiché il blocco interno in Direct2D non sincronizza una risorsa condivisa, ad esempio le destinazioni di rendering, questo approccio può causare l'esito negativo della chiamata BeginDraw nel thread 1, perché nel thread 2 la chiamata BeginDraw sta già usando la destinazione di rendering condivisa.

diagramma Direct2D multithreading con più target di rendering.

Per evitare i risultati imprevedibili rilevati nella figura 1, la figura 2 mostra una factory multithreaded con ciascun thread dotato di una propria destinazione di rendering. Questo approccio è valido, ma di fatto funziona come un'applicazione a thread singolo. Il motivo è che il blocco a livello di fabbrica si applica solo al livello di operazione di disegno e tutte le chiamate di disegno nella stessa factory vengono serializzate di conseguenza. Di conseguenza, il thread 1 viene bloccato quando si tenta di immettere una chiamata di disegno, mentre il thread 2 è al centro dell'esecuzione di un'altra chiamata di disegno.

diagramma multithreading Direct2D con più fabbriche e più target di rendering.

La figura 3 mostra l'approccio ottimale, in cui vengono usati un'istanza a thread singolo e un target di rendering a thread singolo. Poiché non viene eseguito alcun blocco quando si usa una factory a thread singolo, le operazioni di disegno in ogni thread possono essere eseguite simultaneamente per ottenere prestazioni ottimali.

Generazione di un file bitmap

Per generare un file bitmap usando il rendering software, usare un IWICBitmap destinazione di rendering. Utilizzare un IWICStream per scrivere la bitmap in un file. Usare IWICBitmapFrameEncode per codificare la bitmap in un formato di immagine specificato. Nell'esempio di codice seguente viene illustrato come disegnare e salvare l'immagine seguente in un file.

immagine di output di esempio.

Questo esempio di codice crea innanzitutto un IWICBitmap e un IWICBitmap come destinazione di rendering. Esegue quindi il rendering di un disegno con del testo, una geometria di percorso che rappresenta una clessidra, e una clessidra trasformata in una bitmap WIC. Usa quindi IWICStream::InitializeFromFilename per salvare la bitmap in un file. Se l'applicazione deve salvare la bitmap in memoria, usare IWICStream::InitializeFromMemory. Infine, usa IWICBitmapFrameEncode per codificare la bitmap.

// Create an IWICBitmap and RT
static const UINT sc_bitmapWidth = 640;
static const UINT sc_bitmapHeight = 480;
if (SUCCEEDED(hr))
{
    hr = pWICFactory->CreateBitmap(
        sc_bitmapWidth,
        sc_bitmapHeight,
        GUID_WICPixelFormat32bppBGR,
        WICBitmapCacheOnLoad,
        &pWICBitmap
        );
}

// Set the render target type to D2D1_RENDER_TARGET_TYPE_DEFAULT to use software rendering.
if (SUCCEEDED(hr))
{
    hr = pD2DFactory->CreateWicBitmapRenderTarget(
        pWICBitmap,
        D2D1::RenderTargetProperties(),
        &pRT
        );
}

// Create text format and a path geometry representing an hour glass. 
if (SUCCEEDED(hr))
{
    static const WCHAR sc_fontName[] = L"Calibri";
    static const FLOAT sc_fontSize = 50;

    hr = pDWriteFactory->CreateTextFormat(
        sc_fontName,
        NULL,
        DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        sc_fontSize,
        L"", //locale
        &pTextFormat
        );
}
if (SUCCEEDED(hr))
{
    pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
    pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
    hr = pD2DFactory->CreatePathGeometry(&pPathGeometry);
}
if (SUCCEEDED(hr))
{
    hr = pPathGeometry->Open(&pSink);
}
if (SUCCEEDED(hr))
{
    pSink->SetFillMode(D2D1_FILL_MODE_ALTERNATE);

    pSink->BeginFigure(
        D2D1::Point2F(0, 0),
        D2D1_FIGURE_BEGIN_FILLED
        );

    pSink->AddLine(D2D1::Point2F(200, 0));

    pSink->AddBezier(
        D2D1::BezierSegment(
            D2D1::Point2F(150, 50),
            D2D1::Point2F(150, 150),
            D2D1::Point2F(200, 200))
        );

    pSink->AddLine(D2D1::Point2F(0, 200));

    pSink->AddBezier(
        D2D1::BezierSegment(
            D2D1::Point2F(50, 150),
            D2D1::Point2F(50, 50),
            D2D1::Point2F(0, 0))
        );

    pSink->EndFigure(D2D1_FIGURE_END_CLOSED);

    hr = pSink->Close();
}
if (SUCCEEDED(hr))
{
    static const D2D1_GRADIENT_STOP stops[] =
    {
        {   0.f,  { 0.f, 1.f, 1.f, 1.f }  },
        {   1.f,  { 0.f, 0.f, 1.f, 1.f }  },
    };

    hr = pRT->CreateGradientStopCollection(
        stops,
        ARRAYSIZE(stops),
        &pGradientStops
        );
}
if (SUCCEEDED(hr))
{
    hr = pRT->CreateLinearGradientBrush(
        D2D1::LinearGradientBrushProperties(
            D2D1::Point2F(100, 0),
            D2D1::Point2F(100, 200)),
        D2D1::BrushProperties(),
        pGradientStops,
        &pLGBrush
        );
}
if (SUCCEEDED(hr))
{
    hr = pRT->CreateSolidColorBrush(
        D2D1::ColorF(D2D1::ColorF::Black),
        &pBlackBrush
        );
}
if (SUCCEEDED(hr))
{
    // Render into the bitmap.
    pRT->BeginDraw();
    pRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
    D2D1_SIZE_F rtSize = pRT->GetSize();

    // Set the world transform to a 45 degree rotation at the center of the render target
    // and write "Hello, World".
    pRT->SetTransform(
        D2D1::Matrix3x2F::Rotation(
            45,
            D2D1::Point2F(
                rtSize.width / 2,
                rtSize.height / 2))
            );

    static const WCHAR sc_helloWorld[] = L"Hello, World!";
    pRT->DrawText(
        sc_helloWorld,
        ARRAYSIZE(sc_helloWorld) - 1,
        pTextFormat,
        D2D1::RectF(0, 0, rtSize.width, rtSize.height),
        pBlackBrush);

    // Reset back to the identity transform.
    pRT->SetTransform(D2D1::Matrix3x2F::Translation(0, rtSize.height - 200));
    pRT->FillGeometry(pPathGeometry, pLGBrush);
    pRT->SetTransform(D2D1::Matrix3x2F::Translation(rtSize.width - 200, 0));
    pRT->FillGeometry(pPathGeometry, pLGBrush);
    hr = pRT->EndDraw();
}

if (SUCCEEDED(hr))
{
    // Save the image to a file.
    hr = pWICFactory->CreateStream(&pStream);
}

WICPixelFormatGUID format = GUID_WICPixelFormatDontCare;

// Use InitializeFromFilename to write to a file. If there is need to write inside the memory, use InitializeFromMemory. 
if (SUCCEEDED(hr))
{
    static const WCHAR filename[] = L"output.png";
    hr = pStream->InitializeFromFilename(filename, GENERIC_WRITE);
}
if (SUCCEEDED(hr))
{
    hr = pWICFactory->CreateEncoder(GUID_ContainerFormatPng, NULL, &pEncoder);
}
if (SUCCEEDED(hr))
{
    hr = pEncoder->Initialize(pStream, WICBitmapEncoderNoCache);
}
if (SUCCEEDED(hr))
{
    hr = pEncoder->CreateNewFrame(&pFrameEncode, NULL);
}
// Use IWICBitmapFrameEncode to encode the bitmap into the picture format you want.
if (SUCCEEDED(hr))
{
    hr = pFrameEncode->Initialize(NULL);
}
if (SUCCEEDED(hr))
{
    hr = pFrameEncode->SetSize(sc_bitmapWidth, sc_bitmapHeight);
}
if (SUCCEEDED(hr))
{
    hr = pFrameEncode->SetPixelFormat(&format);
}
if (SUCCEEDED(hr))
{
    hr = pFrameEncode->WriteSource(pWICBitmap, NULL);
}
if (SUCCEEDED(hr))
{
    hr = pFrameEncode->Commit();
}
if (SUCCEEDED(hr))
{
    hr = pEncoder->Commit();
}

Conclusione

Come illustrato in precedenza, l'uso di Direct2D per il rendering lato server è semplice e semplice. Offre inoltre un rendering altamente parallelizzabile e di alta qualità che può essere eseguito in ambienti con privilegi limitati del server.

Riferimento Direct2D