#include "pch.h" #include "D2DCanvas.h" #include #include #include #include "D2DPolyline.h" #include "D2DShapeStyle.h" using namespace Windows::Graphics::Display; namespace Telerik { namespace UI { namespace Drawing { D2DCanvas::D2DCanvas(void) { this->SizeChanged += ref new SizeChangedEventHandler(this, &D2DCanvas::OnSizeChanged); this->Loaded += ref new RoutedEventHandler(this, &D2DCanvas::OnLoaded); this->Unloaded += ref new RoutedEventHandler(this, &D2DCanvas::OnUnloaded); this->zoomFactor = 1; this->pixelZoomFactor = 1; this->dpi = DefaultDPI; this->resources = ref new D3DResources(); this->updatingShapes = false; } D2DCanvas::~D2DCanvas(void) { this->CleanUp(); } bool D2DCanvas::HasShapesForLayer(int layerId) { return this->FindLayerIndexById(layerId) != -1; } void D2DCanvas::BeginShapeUpdate() { this->updatingShapes = true; } void D2DCanvas::EndShapeUpdate() { this->updatingShapes = false; this->ResetViewportBuffer(); } D2DShape^ D2DCanvas::HitTest(Point location, int layerZIndex) { auto pixelLocation = Extensions::ConvertPointToPixels(location, this->dpi); pixelLocation.X -= this->renderOffset.x; pixelLocation.Y -= this->renderOffset.y; for (auto layerPtr = this->shapeLayers.rbegin(); layerPtr != this->shapeLayers.rend(); ++layerPtr) { if (layerZIndex != -1 && (*layerPtr)->parameters.ZIndex != layerZIndex) { continue; } for (auto shapePtr = (*layerPtr)->shapes.rbegin(); shapePtr != (*layerPtr)->shapes.rend(); ++shapePtr) { if ((*shapePtr)->HitTest(pixelLocation)) { return (*shapePtr); } } } return nullptr; } void D2DCanvas::CleanUpOnSuspend(void) { // Starting in Windows 8.1, apps that render with Direct2D and/or Direct3D must call Trim in response to the PLM suspend callback. // More information: http://msdn.microsoft.com/en-us/library/windows/desktop/dn280346.aspx. if (this->resources != nullptr && this->resources->IsReady) { this->resources->TrimDXGIDevice3(); } } void D2DCanvas::SetShapesForLayer(IIterable^ shapes, ShapeLayerParameters parameters) { this->ResetDrawing(true); auto layerIndex = this->FindLayerIndexById(parameters.Id); if (layerIndex != -1) { this->ClearLayer(this->shapeLayers.at(layerIndex)); } if (shapes == nullptr) { if (layerIndex != -1) { this->RemoveLayerAtIndex(layerIndex); } return; } D2DShapeLayer^ layer; if (layerIndex == -1) { layer = ref new D2DShapeLayer(); layer->parameters = parameters; this->shapeLayers.push_back(layer); // sort the layer by z-index (each layer implements the "<" operator, which is used to the vector) std::sort(this->shapeLayers.begin(), this->shapeLayers.end()); } else { layer = this->shapeLayers.at(layerIndex); } IIterator^ iterator = shapes->First(); while (iterator->HasCurrent) { layer->shapes.push_back(iterator->Current); iterator->Current->SetLayerId(parameters.Id); iterator->Current->SetOwner(this); iterator->MoveNext(); } } void D2DCanvas::ClearLayer(D2DShapeLayer^ layer) { for (auto shape = layer->shapes.begin(); shape != layer->shapes.end(); ++shape) { (*shape)->SetOwner(nullptr); } layer->shapes.clear(); } int D2DCanvas::FindLayerIndexById(int layerId) { int index = 0; for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr, ++index) { if ((*layerPtr)->parameters.Id == layerId) { return index; } } return -1; } void D2DCanvas::RemoveLayerAtIndex(int index) { auto it = this->shapeLayers.begin(); it += index; this->shapeLayers.erase(it); } void D2DCanvas::SetViewportOrigin(DoublePoint origin) { auto pixelOrigin = Extensions::ConvertPointToPixels(origin, this->dpi); if (this->pixelViewportOrigin.X == pixelOrigin.X && this->pixelViewportOrigin.Y == pixelOrigin.Y) { return; } if (this->hasBuffer) { if (abs(pixelOrigin.X - this->pixelViewportOrigin.X) >= this->currentPixelSize.Width || abs(pixelOrigin.Y - this->pixelViewportOrigin.Y) >= this->currentPixelSize.Height) { this->ResetViewportBuffer(); } else { Rect hInvalidRect = Rect(0, 0, 0, 0); Rect vInvalidRect = Rect(0, 0, 0, 0); // build invalid rects if (pixelOrigin.X != this->pixelViewportOrigin.X) { vInvalidRect.Y = -this->renderOffset.y; vInvalidRect.Height = this->currentPixelSize.Height; if (origin.X > this->viewportOrigin.X) { // we are moving right vInvalidRect.Width = static_cast(pixelOrigin.X - this->pixelViewportOrigin.X); vInvalidRect.X = -(this->renderOffset.x + vInvalidRect.Width); } else { // we are moving left vInvalidRect.X = this->currentPixelSize.Width - this->renderOffset.x; vInvalidRect.Width = static_cast(this->pixelViewportOrigin.X - pixelOrigin.X); } this->invalidRects.push_back(vInvalidRect); } if (pixelOrigin.Y != this->pixelViewportOrigin.Y) { hInvalidRect.X = -this->renderOffset.x; hInvalidRect.Width = this->currentPixelSize.Width; if (origin.X > this->viewportOrigin.X) { hInvalidRect.X -= vInvalidRect.Width; } else { hInvalidRect.Width += vInvalidRect.Width; } if (origin.Y > this->viewportOrigin.Y) { // we are moving bottom hInvalidRect.Height = static_cast((pixelOrigin.Y - this->pixelViewportOrigin.Y)); hInvalidRect.Y = -(this->renderOffset.y + hInvalidRect.Height); } else { // we are moving top hInvalidRect.Y = this->currentPixelSize.Height - this->renderOffset.y; hInvalidRect.Height = static_cast(this->pixelViewportOrigin.Y - pixelOrigin.Y); } this->invalidRects.push_back(hInvalidRect); } } } if (!this->renderOffsetReset) { this->renderOffset.x += static_cast(pixelOrigin.X - this->pixelViewportOrigin.X); this->renderOffset.y += static_cast(pixelOrigin.Y - this->pixelViewportOrigin.Y); } this->viewportOrigin = origin; this->pixelViewportOrigin = pixelOrigin; this->InvalidateArrange(); } Size D2DCanvas::ArrangeOverride(Size finalSize) { if (this->wasUnloaded && this->mainRenderContext == nullptr) { if (!this->resources->IsReady) { this->resources->Initialize(); } this->InitRenderContext(this->currentSize); } this->Render(); return Panel::ArrangeOverride(finalSize); } Size D2DCanvas::MeasureOverride(Size availableSize) { auto size = Panel::MeasureOverride(availableSize); if (!this->resources->IsReady) { this->resources->Initialize(); } return size; } void D2DCanvas::ResetDrawing(bool displayChanged) { this->InvalidateShapes(displayChanged); this->ResetViewportBuffer(); } void D2DCanvas::Render() { if (this->updatingShapes) { return; } if (this->resources == nullptr || !this->resources->IsReady || this->mainRenderContext == nullptr) { return; } if (this->renderOffsetReset) { this->renderOffsetReset = false; this->renderOffset = D2D1::Point2F(0, 0); } this->BeginDraw(); this->DoRender(); this->EndDraw(); /*this->waitingThreadCount = 2; this->hasPendingEndDraw = true; int shapeCount = this->shapes.size(); int chunk = shapeCount / this->waitingThreadCount; std::future f1 = std::async(std::launch::async, [this, chunk](){ this->DoRender(this->mainRenderContext, 0, chunk); }); std::future f2 = std::async(std::launch::async, [this, chunk, shapeCount](){ this->DoRender(this->bufferRenderContext, chunk, shapeCount); });*/ } void D2DCanvas::DoRender() { this->RenderWithViewportCaching(); this->invalidRects.clear(); } void D2DCanvas::RenderWithViewportCaching() { if (this->hasBuffer) { auto rect = this->GetViewportBufferRenderBounds(); // do not anti-alias the viewport buffer this->mainRenderContext->DeviceContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED); this->mainRenderContext->DeviceContext->DrawBitmap( this->viewportBuffer.Get(), rect, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR ); this->mainRenderContext->DeviceContext->PopAxisAlignedClip(); } D2D1::Matrix3x2F transform = D2D1::Matrix3x2F::Translation(this->renderOffset.x, this->renderOffset.y); this->mainRenderContext->PushTransform(transform); for (auto rect = this->invalidRects.begin(); rect != this->invalidRects.end(); ++rect) { this->RenderShapes((*rect)); } this->mainRenderContext->PopTransform(); } void D2DCanvas::RenderShapes(Rect invalidRect) { bool clearRect = true; if (invalidRect.Width == 0 || invalidRect.Height == 0) { // entire viewport is the clip rect invalidRect = Rect(-this->renderOffset.x, -this->renderOffset.y, this->currentPixelSize.Width, this->currentPixelSize.Height); clearRect = false; } D2D1_RECT_F clip = Extensions::ToRect(invalidRect); this->mainRenderContext->DeviceContext->PushAxisAlignedClip(&clip, D2D1_ANTIALIAS_MODE_ALIASED); if (clearRect) { this->mainRenderContext->Clear(); } for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { (*layerPtr)->Render(this->mainRenderContext, invalidRect, this->pixelViewportOrigin); } // render text on second pass for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { for (auto shapePtr = (*layerPtr)->shapes.begin(); shapePtr != (*layerPtr)->shapes.end(); ++shapePtr) { (*shapePtr)->RenderLabel(this->mainRenderContext, invalidRect); } } this->mainRenderContext->DeviceContext->PopAxisAlignedClip(); } void D2DCanvas::ResetViewportBuffer() { this->hasBuffer = false; this->invalidRects.clear(); this->invalidRects.push_back(Rect(0, 0, 0, 0)); this->viewportBuffer.Reset(); this->InvalidateArrange(); } D2D1_RECT_F D2DCanvas::GetViewportBufferRenderBounds() { float offsetX = this->renderOffset.x - this->pixelBufferOrigin.x; float offsetY = this->renderOffset.y - this->pixelBufferOrigin.y; return D2D1::RectF(offsetX, offsetY, offsetX + this->currentPixelSize.Width, offsetY + this->currentPixelSize.Height); } void D2DCanvas::OnSizeChanged(Object^ sender, SizeChangedEventArgs^ args) { this->Resize(args->NewSize); } void D2DCanvas::Resize(Size newSize) { this->currentSize = newSize; this->ResetDrawing(true); this->ClearRenderContext(); this->InitRenderContext(this->currentSize); } void D2DCanvas::InitRenderContext(Size size) { if (size.Width <= 0 || size.Height <= 0 || Windows::ApplicationModel::DesignMode::DesignModeEnabled) { return; } this->displayInfo = Windows::Graphics::Display::DisplayInformation::GetForCurrentView(); this->dpi = this->displayInfo->LogicalDpi; this->currentPixelSize.Width = ceilf(size.Width * this->dpi / DefaultDPI); this->currentPixelSize.Height = ceilf(size.Height * this->dpi / DefaultDPI); this->pixelViewportOrigin = Extensions::ConvertPointToPixels(this->viewportOrigin, this->dpi); this->pixelZoomFactor = this->zoomFactor * this->dpi / DefaultDPI; this->imageSource = ref new SurfaceImageSource(static_cast(this->currentPixelSize.Width), static_cast(this->currentPixelSize.Height)); IInspectable* inspectable = (IInspectable*) reinterpret_cast(this->imageSource); if (!inspectable) { throw; } HRESULT result = inspectable->QueryInterface(__uuidof(ISurfaceImageSourceNative), (void **)&this->nativeImageSource); if (!SUCCEEDED(result)) { throw; } result = this->nativeImageSource->SetDevice(this->resources->DXGIDevice.Get()); if (!SUCCEEDED(result)) { throw; } this->mainRenderContext = ref new D2DRenderContext(); // we will render in 96 dpi and then scale the drawing appropriately this->mainRenderContext->Initialize(this->resources, size, static_cast(this->currentPixelSize.Width), static_cast(this->currentPixelSize.Height), DefaultDPI); this->background = ref new ImageBrush(); this->background->ImageSource = this->imageSource; this->Background = this->background; this->surfaceRect.left = 0; this->surfaceRect.top = 0; this->surfaceRect.right = static_cast(this->currentPixelSize.Width); this->surfaceRect.bottom = static_cast(this->currentPixelSize.Height); this->InvalidateArrange(); } void D2DCanvas::ClearRenderContext() { for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { for (auto shapePtr = (*layerPtr)->shapes.begin(); shapePtr != (*layerPtr)->shapes.end(); ++shapePtr) { (*shapePtr)->OnDisplayInvalidated(); } } if (this->mainRenderContext != nullptr) { this->mainRenderContext->Uninitialize(); delete this->mainRenderContext; this->mainRenderContext = nullptr; delete this->imageSource; this->imageSource = nullptr; delete this->background; this->background = nullptr; } this->viewportBuffer.Reset(); this->nativeImageSource.Reset(); } void D2DCanvas::InvalidateShapes(bool displayChanged) { for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { for (auto shapePtr = (*layerPtr)->shapes.begin(); shapePtr != (*layerPtr)->shapes.end(); ++shapePtr) { (*shapePtr)->Invalidate(true); if (displayChanged) { (*shapePtr)->OnDisplayInvalidated(); } } } if (displayChanged) { this->renderOffsetReset = true; } } void D2DCanvas::BeginDraw() { HRESULT result; // Copy the already rendered content to the SurfaceImageSource ComPtr surface; result = this->nativeImageSource->BeginDraw(this->surfaceRect, &surface, &this->surfaceOffset); if (result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET) { // recreate device resources this->resources->Initialize(); this->ClearRenderContext(); return; } ComPtr surfaceBitmap; this->mainRenderContext->DeviceContext->CreateBitmapFromDxgiSurface(surface.Get(), NULL, &surfaceBitmap); this->mainRenderContext->DeviceContext->SetTarget(surfaceBitmap.Get()); this->mainRenderContext->BeginDraw(); if (this->surfaceOffset.x > 0 || this->surfaceOffset.y > 0) { this->mainRenderContext->PushTransform(D2D1::Matrix3x2F::Translation(static_cast(this->surfaceOffset.x), static_cast(this->surfaceOffset.y))); } } void D2DCanvas::EndDraw() { HRESULT result; this->mainRenderContext->PopTransform(); this->mainRenderContext->EndDraw(); ComPtr target; this->mainRenderContext->DeviceContext->GetTarget(&target); ComPtr bitmap; target.As(&bitmap); this->CaptureViewport(bitmap); this->mainRenderContext->DeviceContext->SetTarget(nullptr); result = this->nativeImageSource->EndDraw(); if (!SUCCEEDED(result)) { throw; } } void D2DCanvas::CaptureViewport(ComPtr surfaceBitmap) { this->pixelBufferOrigin = this->renderOffset; if (this->viewportBuffer == nullptr) { D2D1_BITMAP_PROPERTIES1 bitmapProperties = D2D1::BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), this->mainRenderContext->DPI, this->mainRenderContext->DPI ); this->mainRenderContext->DeviceContext->CreateBitmap( D2D1::SizeU(this->surfaceRect.right - this->surfaceRect.left, this->surfaceRect.bottom - this->surfaceRect.top), nullptr, 0, &bitmapProperties, &this->viewportBuffer ); } HRESULT hr = this->viewportBuffer->CopyFromBitmap( &D2D1::Point2U(0, 0), surfaceBitmap.Get(), &D2D1::RectU( this->surfaceOffset.x, this->surfaceOffset.y, (UINT)this->currentPixelSize.Width + this->surfaceOffset.x, (UINT)this->currentPixelSize.Height + this->surfaceOffset.y) ); if (!SUCCEEDED(hr)) { throw; } this->hasBuffer = true; } void D2DCanvas::OnZoomFactorChanged(double oldZoom) { for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { for (auto shapePtr = (*layerPtr)->shapes.begin(); shapePtr != (*layerPtr)->shapes.end(); ++shapePtr) { (*shapePtr)->OnZoomFactorChanged(); } } this->renderOffsetReset = true; this->renderOffset = D2D1::Point2F(0, 0); this->ResetViewportBuffer(); } void D2DCanvas::PrepareZoomIn() { } void D2DCanvas::PrepareZoomOut() { } void D2DCanvas::OnLoaded(Object^ sender, RoutedEventArgs^ args) { if (this->displayInfo == nullptr) { this->displayInfo = DisplayInformation::GetForCurrentView(); } this->displayInvalidatedToken = this->displayInfo->DisplayContentsInvalidated += ref new TypedEventHandler(this, &D2DCanvas::OnDisplayInvalidated); this->wasUnloaded = false; } void D2DCanvas::OnUnloaded(Object^ sender, RoutedEventArgs^ args) { if (this->displayInfo == nullptr) { return; } this->displayInfo->DisplayContentsInvalidated -= this->displayInvalidatedToken; this->wasUnloaded = true; this->CleanUp(); } void D2DCanvas::CleanUp() { this->ResetDrawing(true); this->ClearRenderContext(); this->Background = nullptr; this->resources->Reset(); for (auto layerPtr = this->shapeLayers.begin(); layerPtr != this->shapeLayers.end(); ++layerPtr) { this->ClearLayer((*layerPtr)); } this->shapeLayers.clear(); } void D2DCanvas::OnDisplayInvalidated(DisplayInformation^ info, Object^ sender) { this->ResetDrawing(true); this->ClearRenderContext(); } void D2DCanvas::InvalidateShape(D2DShape^ shape) { if (this->updatingShapes) { return; } float strokeThickness = shape->CurrentStyle->StrokeThicknessAsFloat; Rect bounds = shape->GetBounds(); bounds.X = floorf(bounds.X - strokeThickness / 2); bounds.Y = floorf(bounds.Y - strokeThickness / 2); bounds.Width = ceilf(bounds.Width + strokeThickness); bounds.Height = ceilf(bounds.Height + strokeThickness); this->invalidRects.push_back(bounds); this->InvalidateArrange(); } } } }