다음을 통해 공유


서버 쪽 렌더링에 Direct2D 사용

Direct2D는 Windows Server에서 서버 쪽 렌더링이 필요한 그래픽 애플리케이션에 적합합니다. 이 개요에서는 서버 쪽 렌더링에 Direct2D를 사용하는 기본 사항에 대해 설명합니다. 여기에는 다음 단원이 포함되어 있습니다.

서버 쪽 렌더링에 대한 요구 사항

다음은 차트 서버의 일반적인 시나리오입니다. 차트 및 그래픽은 서버에서 렌더링되고 웹 요청에 대한 응답으로 비트맵으로 전달됩니다. 서버에는 카드 그래픽이 없거나 그래픽이 전혀 카드 않을 수 있습니다.

이 시나리오에서는 세 가지 애플리케이션 요구 사항을 표시합니다. 먼저 애플리케이션은 특히 다중 코어 서버에서 여러 동시 요청을 효율적으로 처리해야 합니다. 둘째, 애플리케이션은 그래픽 카드 카드 또는 그래픽이 없는 서버에서 실행할 때 소프트웨어 렌더링을 사용해야 합니다. 마지막으로 애플리케이션은 사용자를 로그인할 필요가 없도록 세션 0에서 서비스로 실행해야 합니다. 세션 0 에 대한 자세한 내용은 UMDF 드라이버에 대한 애플리케이션 호환성 - 세션 0 격리세션 제로 지침을 참조하세요.

사용 가능한 API에 대한 옵션

서버 쪽 렌더링에는 GDI, GDI+ 및 Direct2D의 세 가지 옵션이 있습니다. GDI 및 GDI+와 마찬가지로 Direct2D는 네이티브 2D 렌더링 API로, 애플리케이션에서 그래픽 디바이스의 사용을 보다 세세하게 제어할 수 있습니다. 또한 Direct2D는 단일 스레드 및 다중 스레드 팩터리를 고유하게 지원합니다. 다음 섹션에서는 그리기 품질 및 다중 스레드 서버 쪽 렌더링 측면에서 각 API를 비교합니다.

GDI

Direct2D 및 GDI+와 달리 GDI는 고품질 그리기 기능을 지원하지 않습니다. 예를 들어 GDI는 부드러운 선 만들기를 위한 앤티앨리어싱을 지원하지 않으며 투명성에 대한 지원만 제한합니다. Windows 7 및 Windows Server 2008 R2의 그래픽 성능 테스트 결과를 기반으로 Direct2D는 GDI의 잠금 재설계에도 불구하고 GDI보다 더 효율적으로 확장됩니다. 이러한 테스트 결과에 대한 자세한 내용은 Windows 7 그래픽 성능 엔지니어링을 참조 하세요.

또한 GDI를 사용하는 애플리케이션은 프로세스당 10240GDI 핸들 및 세션당 65536 GDI 핸들로 제한됩니다. 그 이유는 내부적으로 Windows에서 16비트 WORD를 사용하여 각 세션에 대한 핸들 인덱스를 저장하기 때문입니다.

GDI+

GDI+는 고품질 그리기를 위해 앤티앨리어싱 및 알파 혼합을 지원하지만 서버 시나리오의 경우 GDI+의 기본 문제는 세션 0에서 실행을 지원하지 않는다는 것입니다. 세션 0은 비대화형 기능만 지원하므로 디스플레이 디바이스와 직접 또는 간접적으로 상호 작용하는 함수는 오류를 받습니다. 함수의 특정 예로는 디스플레이 디바이스를 다루는 기능뿐만 아니라 디바이스 드라이버를 간접적으로 다루는 기능도 포함됩니다.

GDI와 마찬가지로 GDI+는 잠금 메커니즘에 의해 제한됩니다. GDI+의 잠금 메커니즘은 Windows 7 및 Windows Server 2008 R2에서 이전 버전과 동일합니다.

Direct2D

Direct2D는 고성능 및 고품질 렌더링을 제공하는 하드웨어 가속 즉시 모드 2차원 그래픽 API입니다. 단일 스레드 및 다중 스레드 팩터리 및 거친 세분화된 소프트웨어 렌더링의 선형 크기 조정을 제공합니다.

이를 위해 Direct2D는 루트 팩터리 인터페이스를 정의합니다. 일반적으로 팩터리에서 만든 개체는 동일한 팩터리에서 만든 다른 개체와만 사용할 수 있습니다. 호출자는 만들 때 단일 스레드 또는 다중 스레드 팩터리를 요청할 수 있습니다. 단일 스레드 팩터리를 요청하면 스레드 잠금이 수행되지 않습니다. 호출자가 다중 스레드 팩터리를 요청하는 경우 Direct2D로 호출할 때마다 팩터리 전체 스레드 잠금이 획득됩니다.

또한 Direct2D의 스레드 잠금은 GDI 및 GDI+보다 세분화되어 스레드 수를 늘리면 성능에 미치는 영향이 최소화됩니다.

서버 쪽 렌더링에 Direct2D를 사용하는 방법

다음 섹션에서는 소프트웨어 렌더링을 사용하는 방법, 단일 스레드 및 다중 스레드 팩터리를 최적으로 사용하는 방법 및 복잡한 드로잉을 그리고 파일에 저장하는 방법을 설명합니다.

소프트웨어 렌더링

서버 쪽 애플리케이션은 렌더링 대상 유형이 D2D1_RENDER_TARGET_TYPE_SOFTWARE 또는 D2D1_RENDER_TARGET_TYPE_DEFAULT 설정된 IWICBitmap 렌더링 대상을 만들어 소프트웨어 렌더링을 사용합니다. IWICBitmap 렌더링 대상에 대한 자세한 내용은 ID2D1Factory::CreateWicBitmapRenderTarget 메서드를 참조하세요. 렌더링 대상 형식에 대한 자세한 내용은 D2D1_RENDER_TARGET_TYPE 참조하세요.

멀티스레딩

팩터리를 만들고 공유하고 스레드 간에 대상을 렌더링하는 방법을 알면 애플리케이션의 성능에 큰 영향을 미칠 수 있습니다. 다음 세 가지 수치는 세 가지 다양한 접근 방식을 보여줍니다. 최적 방법은 그림 3에 나와 있습니다.

direct2d multithreading diagram with a single render target.

그림 1에서는 서로 다른 스레드가 동일한 팩터리와 동일한 렌더링 대상을 공유합니다. 이 방법은 여러 스레드가 동시에 변환 매트릭스를 설정하는 등 공유 렌더링 대상의 상태를 변경하는 경우 예측할 수 없는 결과를 초래할 수 있습니다. Direct2D의 내부 잠금이 렌더링 대상과 같은 공유 리소스를 동기화하지 않으므로 이 접근 방식으로 인해 스레드 2에서 BeginDraw 호출이 이미 공유 렌더링 대상을 사용하고 있기 때문에 BeginDraw 호출이 스레드 1에서 실패할 수 있습니다.

direct2d multithreading diagram with multiple render targets.

그림 1에서 예측할 수 없는 결과를 방지하기 위해 그림 2는 각 스레드에 자체 렌더링 대상이 있는 다중 스레드 팩터리를 보여 줍니다. 이 방법은 작동하지만 효과적으로 단일 스레드 애플리케이션으로 작동합니다. 그 이유는 팩터리 전체 잠금이 드로잉 작업 수준에만 적용되고 결과적으로 동일한 팩터리의 모든 그리기 호출이 직렬화되기 때문입니다. 결과적으로 스레드 1은 그리기 호출을 입력하려고 할 때 차단되고 스레드 2는 다른 그리기 호출을 실행하는 중간에 있습니다.

direct2d multithreading diagram with multiple factories and multiple render targets.

그림 3에서는 단일 스레드 팩터리 및 단일 스레드 렌더링 대상이 사용되는 최적의 방법을 보여 줍니다. 단일 스레드 팩터리를 사용할 때 잠금이 수행되지 않으므로 각 스레드의 그리기 작업을 동시에 실행하여 최적의 성능을 얻을 수 있습니다.

비트맵 파일 생성

소프트웨어 렌더링을 사용하여 비트맵 파일을 생성하려면 IWICBitmap 렌더링 대상을 사용합니다. IWICStream사용하여 파일에 비트맵을 작성합니다. IWICBitmapFrameEncode를 사용하여 비트맵을 지정된 이미지 형식으로 인코딩합니다. 다음 코드 예제에서는 다음 이미지를 그리고 파일에 저장하는 방법을 보여 있습니다.

example output image.

이 코드 예제에서는 먼저 IWICBitmap 및 IWICBitmap 렌더링 대상을 만듭니다. 그런 다음 일부 텍스트가 있는 드로잉, 시간 유리를 나타내는 경로 기하 도형, 변환된 시간 유리를 WIC 비트맵으로 렌더링합니다. 그런 다음 IWICStream::InitializeFromFilename을 사용하여 비트맵을 파일에 저장합니다. 애플리케이션이 비트맵을 메모리에 저장해야 하는 경우 IWICStream::InitializeFromMemory를 대신 사용합니다. 마지막으로 IWICBitmapFrameEncode를 사용하여 비트맵을 인코딩합니다.

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

결론

위에서 본 것처럼 서버 쪽 렌더링에 Direct2D를 사용하는 것은 간단하고 간단합니다. 또한 서버의 낮은 권한 환경에서 실행할 수 있는 고품질의 병렬 처리 가능한 렌더링을 제공합니다.

Direct2D 참조