261 lines
10 KiB
C++
261 lines
10 KiB
C++
|
|
#include "NativeRendererSupport.h"
|
||
|
|
|
||
|
|
#include <algorithm>
|
||
|
|
#include <cmath>
|
||
|
|
|
||
|
|
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<D2D1_RECT_F> 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<D2D1_RECT_F>& 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<ID2D1GradientStopCollection> 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<ID2D1LinearGradientBrush> 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<ID2D1PathGeometry> geometry;
|
||
|
|
HRESULT hr = m_d2dFactory->CreatePathGeometry(geometry.ReleaseAndGetAddressOf());
|
||
|
|
if (FAILED(hr) || !geometry) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
Microsoft::WRL::ComPtr<ID2D1GeometrySink> 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<UINT32>(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<ID2D1Bitmap> 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<NativeTextureResource*>(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<float>(command.texture.width);
|
||
|
|
const float sourceHeight = static_cast<float>(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
|