#include "NativeRendererSupport.h" #include #include namespace XCEngine::UI::Editor::Host { using namespace NativeRendererSupport; bool NativeRenderer::RenderToTarget( ID2D1RenderTarget& renderTarget, ID2D1SolidColorBrush& solidBrush, const ::XCEngine::UI::UIDrawData& drawData) { renderTarget.SetDpi(kBaseDpi, kBaseDpi); renderTarget.SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); renderTarget.BeginDraw(); renderTarget.Clear(D2D1::ColorF(0.04f, 0.05f, 0.06f, 1.0f)); std::vector clipStack = {}; for (const ::XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) { for (const ::XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) { RenderCommand(renderTarget, solidBrush, command, clipStack); } } while (!clipStack.empty()) { renderTarget.PopAxisAlignedClip(); clipStack.pop_back(); } return true; } void NativeRenderer::RenderCommand( ID2D1RenderTarget& renderTarget, ID2D1SolidColorBrush& solidBrush, const ::XCEngine::UI::UIDrawCommand& command, std::vector& clipStack) { solidBrush.SetColor(ToD2DColor(command.color)); const float dpiScale = ClampDpiScale(m_dpiScale); switch (command.type) { case ::XCEngine::UI::UIDrawCommandType::FilledRect: { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; if (command.rounding > 0.0f) { renderTarget.FillRoundedRectangle( D2D1::RoundedRect(rect, rounding, rounding), &solidBrush); } else { renderTarget.FillRectangle(rect, &solidBrush); } break; } case ::XCEngine::UI::UIDrawCommandType::FilledRectLinearGradient: { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; const D2D1_GRADIENT_STOP stops[2] = { D2D1::GradientStop(0.0f, ToD2DColor(command.color)), D2D1::GradientStop(1.0f, ToD2DColor(command.secondaryColor)) }; Microsoft::WRL::ComPtr stopCollection; HRESULT hr = renderTarget.CreateGradientStopCollection( stops, 2u, stopCollection.ReleaseAndGetAddressOf()); if (FAILED(hr) || !stopCollection) { break; } const D2D1_POINT_2F startPoint = command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.top) : D2D1::Point2F(rect.left, (rect.top + rect.bottom) * 0.5f); const D2D1_POINT_2F endPoint = command.gradientDirection == ::XCEngine::UI::UILinearGradientDirection::Vertical ? D2D1::Point2F((rect.left + rect.right) * 0.5f, rect.bottom) : D2D1::Point2F(rect.right, (rect.top + rect.bottom) * 0.5f); Microsoft::WRL::ComPtr gradientBrush; hr = renderTarget.CreateLinearGradientBrush( D2D1::LinearGradientBrushProperties(startPoint, endPoint), stopCollection.Get(), gradientBrush.ReleaseAndGetAddressOf()); if (FAILED(hr) || !gradientBrush) { break; } if (command.rounding > 0.0f) { renderTarget.FillRoundedRectangle( D2D1::RoundedRect(rect, rounding, rounding), gradientBrush.Get()); } else { renderTarget.FillRectangle(rect, gradientBrush.Get()); } break; } case ::XCEngine::UI::UIDrawCommandType::RectOutline: { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; const float rounding = command.rounding > 0.0f ? command.rounding * dpiScale : 0.0f; if (command.rounding > 0.0f) { renderTarget.DrawRoundedRectangle( D2D1::RoundedRect(rect, rounding, rounding), &solidBrush, thickness); } else { renderTarget.DrawRectangle(rect, &solidBrush, thickness); } break; } case ::XCEngine::UI::UIDrawCommandType::Line: { const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; const float pixelOffset = ResolveStrokePixelOffset(thickness); const D2D1_POINT_2F start = ToD2DPoint(command.position, dpiScale, pixelOffset); const D2D1_POINT_2F end = ToD2DPoint(command.uvMin, dpiScale, pixelOffset); renderTarget.DrawLine(start, end, &solidBrush, thickness); break; } case ::XCEngine::UI::UIDrawCommandType::FilledTriangle: { Microsoft::WRL::ComPtr geometry; HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf()); if (FAILED(hr) || !geometry) { break; } Microsoft::WRL::ComPtr sink; hr = geometry->Open(sink.ReleaseAndGetAddressOf()); if (FAILED(hr) || !sink) { break; } const D2D1_POINT_2F a = ToD2DPoint(command.position, dpiScale); const D2D1_POINT_2F b = ToD2DPoint(command.uvMin, dpiScale); const D2D1_POINT_2F c = ToD2DPoint(command.uvMax, dpiScale); const D2D1_POINT_2F points[2] = { b, c }; sink->BeginFigure(a, D2D1_FIGURE_BEGIN_FILLED); sink->AddLines(points, 2u); sink->EndFigure(D2D1_FIGURE_END_CLOSED); hr = sink->Close(); if (FAILED(hr)) { break; } renderTarget.FillGeometry(geometry.Get(), &solidBrush); break; } case ::XCEngine::UI::UIDrawCommandType::FilledCircle: { const float radius = command.radius * dpiScale; renderTarget.FillEllipse( D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), &solidBrush); break; } case ::XCEngine::UI::UIDrawCommandType::CircleOutline: { const float radius = command.radius * dpiScale; const float thickness = (command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale; renderTarget.DrawEllipse( D2D1::Ellipse(ToD2DPoint(command.position, dpiScale), radius, radius), &solidBrush, thickness); break; } case ::XCEngine::UI::UIDrawCommandType::Text: { if (command.text.empty()) { break; } const float fontSize = ResolveFontSize(command.fontSize); const float scaledFontSize = fontSize * dpiScale; IDWriteTextFormat* textFormat = GetTextFormat(scaledFontSize); if (textFormat == nullptr) { break; } const std::wstring text = Utf8ToWide(command.text); if (text.empty()) { break; } const D2D1_SIZE_F targetSize = renderTarget.GetSize(); const float originX = SnapToPixel(command.position.x, dpiScale); const float originY = SnapToPixel(command.position.y, dpiScale); const float lineHeight = std::ceil(scaledFontSize * 1.6f); const D2D1_RECT_F layoutRect = D2D1::RectF( originX, originY, targetSize.width, originY + lineHeight); renderTarget.DrawTextW( text.c_str(), static_cast(text.size()), textFormat, layoutRect, &solidBrush, D2D1_DRAW_TEXT_OPTIONS_CLIP, DWRITE_MEASURING_MODE_GDI_NATURAL); break; } case ::XCEngine::UI::UIDrawCommandType::Image: { if (!command.texture.IsValid()) { break; } Microsoft::WRL::ComPtr bitmap; if (command.texture.kind == ::XCEngine::UI::UITextureHandleKind::ShaderResourceView) { if (!ResolveInteropBitmap(command.texture, bitmap) || !bitmap) { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); break; } } else { auto* texture = reinterpret_cast(command.texture.nativeHandle); if (texture == nullptr || m_liveTextures.find(texture) == m_liveTextures.end()) { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); renderTarget.DrawRectangle(rect, &solidBrush, 1.0f); break; } if (!ResolveTextureBitmap(renderTarget, *texture, bitmap) || !bitmap) { break; } } const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); const float sourceWidth = static_cast(command.texture.width); const float sourceHeight = static_cast(command.texture.height); const float sourceLeft = sourceWidth * std::clamp(command.uvMin.x, 0.0f, 1.0f); const float sourceTop = sourceHeight * std::clamp(command.uvMin.y, 0.0f, 1.0f); const float sourceRight = sourceWidth * std::clamp(command.uvMax.x, 0.0f, 1.0f); const float sourceBottom = sourceHeight * std::clamp(command.uvMax.y, 0.0f, 1.0f); renderTarget.DrawBitmap( bitmap.Get(), rect, std::clamp(command.color.a, 0.0f, 1.0f), D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, D2D1::RectF(sourceLeft, sourceTop, sourceRight, sourceBottom)); break; } case ::XCEngine::UI::UIDrawCommandType::PushClipRect: { const D2D1_RECT_F rect = ToD2DRect(command.rect, dpiScale); renderTarget.PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); clipStack.push_back(rect); break; } case ::XCEngine::UI::UIDrawCommandType::PopClipRect: { if (!clipStack.empty()) { renderTarget.PopAxisAlignedClip(); clipStack.pop_back(); } break; } default: break; } } } // namespace XCEngine::UI::Editor::Host