2026-04-21 20:49:18 +08:00
|
|
|
#include "D3D12UiRenderer.h"
|
|
|
|
|
|
|
|
|
|
#include <XCEngine/Rendering/RenderSurfacePipelineUtils.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12CommandList.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12DescriptorHeap.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12DescriptorSet.h>
|
|
|
|
|
#include <XCEngine/RHI/D3D12/D3D12PipelineLayout.h>
|
|
|
|
|
#include <XCEngine/RHI/RHICommandList.h>
|
|
|
|
|
#include <XCEngine/RHI/RHIEnums.h>
|
|
|
|
|
#include <XCEngine/RHI/RHITypes.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::Host {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
using ::XCEngine::RHI::BlendFactor;
|
|
|
|
|
using ::XCEngine::RHI::BlendOp;
|
|
|
|
|
using ::XCEngine::RHI::BufferDesc;
|
|
|
|
|
using ::XCEngine::RHI::BufferType;
|
|
|
|
|
using ::XCEngine::RHI::ColorWriteMask;
|
|
|
|
|
using ::XCEngine::RHI::ComparisonFunc;
|
|
|
|
|
using ::XCEngine::RHI::CullMode;
|
|
|
|
|
using ::XCEngine::RHI::DescriptorHeapType;
|
|
|
|
|
using ::XCEngine::RHI::DescriptorPoolDesc;
|
|
|
|
|
using ::XCEngine::RHI::DescriptorSetLayoutBinding;
|
|
|
|
|
using ::XCEngine::RHI::DescriptorSetLayoutDesc;
|
|
|
|
|
using ::XCEngine::RHI::DescriptorType;
|
|
|
|
|
using ::XCEngine::RHI::D3D12CommandList;
|
|
|
|
|
using ::XCEngine::RHI::D3D12DescriptorSet;
|
|
|
|
|
using ::XCEngine::RHI::D3D12DescriptorHeap;
|
|
|
|
|
using ::XCEngine::RHI::FilterMode;
|
|
|
|
|
using ::XCEngine::RHI::FillMode;
|
|
|
|
|
using ::XCEngine::RHI::Format;
|
|
|
|
|
using ::XCEngine::RHI::FrontFace;
|
|
|
|
|
using ::XCEngine::RHI::GraphicsPipelineDesc;
|
|
|
|
|
using ::XCEngine::RHI::PrimitiveTopology;
|
|
|
|
|
using ::XCEngine::RHI::PrimitiveTopologyType;
|
|
|
|
|
using ::XCEngine::RHI::RHICommandList;
|
|
|
|
|
using ::XCEngine::RHI::Rect;
|
|
|
|
|
using ::XCEngine::RHI::ResourceViewDesc;
|
|
|
|
|
using ::XCEngine::RHI::ResourceViewDimension;
|
|
|
|
|
using ::XCEngine::RHI::RHIPipelineLayoutDesc;
|
|
|
|
|
using ::XCEngine::RHI::SamplerDesc;
|
|
|
|
|
using ::XCEngine::RHI::ShaderVisibility;
|
|
|
|
|
using ::XCEngine::RHI::TextureAddressMode;
|
|
|
|
|
using ::XCEngine::RHI::Viewport;
|
|
|
|
|
using ::XCEngine::Rendering::RenderContext;
|
|
|
|
|
using ::XCEngine::Rendering::RenderSurface;
|
|
|
|
|
using ::XCEngine::UI::UIColor;
|
|
|
|
|
using ::XCEngine::UI::UIDrawCommand;
|
|
|
|
|
using ::XCEngine::UI::UIDrawCommandType;
|
|
|
|
|
using ::XCEngine::UI::UIDrawData;
|
|
|
|
|
using ::XCEngine::UI::UIDrawList;
|
|
|
|
|
using ::XCEngine::UI::UILinearGradientDirection;
|
|
|
|
|
using ::XCEngine::UI::UIPoint;
|
|
|
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
|
using ::XCEngine::UI::UITextureHandle;
|
|
|
|
|
|
|
|
|
|
constexpr std::uint64_t kMinDynamicVertexBufferBytes = 16u * 1024u;
|
|
|
|
|
constexpr std::uint64_t kMinDynamicInstanceBufferBytes = 16u * 1024u;
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
enum class UiPrimitiveKind : int {
|
2026-04-21 20:49:18 +08:00
|
|
|
Textured = 0,
|
|
|
|
|
RectFill = 1,
|
|
|
|
|
RectOutline = 2,
|
|
|
|
|
CircleFill = 3,
|
2026-04-22 02:47:27 +08:00
|
|
|
CircleOutline = 4,
|
|
|
|
|
TriangleFill = 5
|
2026-04-21 20:49:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct UiQuadVertex {
|
|
|
|
|
float corner[2] = {};
|
|
|
|
|
float uv[2] = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const UiQuadVertex kUnitQuadVertices[] = {
|
|
|
|
|
{ { 0.0f, 0.0f }, { 0.0f, 0.0f } },
|
|
|
|
|
{ { 1.0f, 0.0f }, { 1.0f, 0.0f } },
|
|
|
|
|
{ { 1.0f, 1.0f }, { 1.0f, 1.0f } },
|
|
|
|
|
{ { 0.0f, 1.0f }, { 0.0f, 1.0f } }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const std::uint32_t kUnitQuadIndices[] = { 0u, 1u, 2u, 0u, 2u, 3u };
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
const char kPrimitiveUiPassHlsl[] = R"(
|
2026-04-21 20:49:18 +08:00
|
|
|
cbuffer UiConstants : register(b0) {
|
|
|
|
|
float2 gInvViewportSize;
|
|
|
|
|
float2 gPadding;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct VSInput {
|
|
|
|
|
float2 corner : POSITION0;
|
|
|
|
|
float2 unitUv : TEXCOORD0;
|
|
|
|
|
float2 origin : POSITION1;
|
|
|
|
|
float2 axisX : TEXCOORD1;
|
|
|
|
|
float2 axisY : TEXCOORD2;
|
|
|
|
|
float2 uvMin : TEXCOORD3;
|
|
|
|
|
float2 uvMax : TEXCOORD4;
|
|
|
|
|
float4 color : COLOR0;
|
|
|
|
|
float4 secondaryColor : COLOR1;
|
|
|
|
|
float4 params : TEXCOORD5;
|
2026-04-22 02:47:27 +08:00
|
|
|
float4 aux0 : TEXCOORD6;
|
|
|
|
|
float4 aux1 : TEXCOORD7;
|
2026-04-21 20:49:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct VSOutput {
|
|
|
|
|
float4 position : SV_POSITION;
|
|
|
|
|
float2 unitUv : TEXCOORD0;
|
|
|
|
|
float2 localPos : TEXCOORD1;
|
|
|
|
|
float2 size : TEXCOORD2;
|
|
|
|
|
float2 sampleUv : TEXCOORD3;
|
|
|
|
|
float4 color : COLOR0;
|
|
|
|
|
float4 secondaryColor : COLOR1;
|
|
|
|
|
float4 params : TEXCOORD4;
|
2026-04-22 02:47:27 +08:00
|
|
|
float4 aux0 : TEXCOORD5;
|
|
|
|
|
float4 aux1 : TEXCOORD6;
|
2026-04-21 20:49:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Texture2D gTexture : register(t0);
|
|
|
|
|
SamplerState gSampler : register(s0);
|
|
|
|
|
|
|
|
|
|
float2 ToNdc(float2 pixelPos) {
|
|
|
|
|
float2 ndc;
|
|
|
|
|
ndc.x = pixelPos.x * gInvViewportSize.x * 2.0f - 1.0f;
|
|
|
|
|
ndc.y = 1.0f - pixelPos.y * gInvViewportSize.y * 2.0f;
|
|
|
|
|
return ndc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VSOutput MainVS(VSInput input) {
|
|
|
|
|
VSOutput output;
|
|
|
|
|
const float2 pixelPos =
|
|
|
|
|
input.origin +
|
|
|
|
|
input.axisX * input.corner.x +
|
|
|
|
|
input.axisY * input.corner.y;
|
|
|
|
|
const float2 axisLength = float2(length(input.axisX), length(input.axisY));
|
|
|
|
|
output.position = float4(ToNdc(pixelPos), 0.0f, 1.0f);
|
|
|
|
|
output.unitUv = input.corner;
|
|
|
|
|
output.localPos = axisLength * input.corner;
|
|
|
|
|
output.size = axisLength;
|
|
|
|
|
output.sampleUv = lerp(input.uvMin, input.uvMax, input.corner);
|
|
|
|
|
output.color = input.color;
|
|
|
|
|
output.secondaryColor = input.secondaryColor;
|
|
|
|
|
output.params = input.params;
|
2026-04-22 02:47:27 +08:00
|
|
|
output.aux0 = input.aux0;
|
|
|
|
|
output.aux1 = input.aux1;
|
2026-04-21 20:49:18 +08:00
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CoverageFromSignedDistance(float distance) {
|
|
|
|
|
const float aa = max(fwidth(distance), 1.0f);
|
|
|
|
|
return saturate(0.5f - distance / aa);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float RoundedRectSignedDistance(float2 localPos, float2 size, float radius) {
|
|
|
|
|
const float2 halfSize = max(size * 0.5f, float2(0.0f, 0.0f));
|
|
|
|
|
const float resolvedRadius = min(radius, min(halfSize.x, halfSize.y));
|
|
|
|
|
const float2 centered = localPos - halfSize;
|
|
|
|
|
const float2 radiusVector = float2(resolvedRadius, resolvedRadius);
|
|
|
|
|
const float2 q = abs(centered) - (halfSize - radiusVector);
|
|
|
|
|
return length(max(q, float2(0.0f, 0.0f))) + min(max(q.x, q.y), 0.0f) - resolvedRadius;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float CircleSignedDistance(float2 localPos, float2 center, float radius) {
|
|
|
|
|
return length(localPos - center) - radius;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
float Cross2(float2 lhs, float2 rhs) {
|
|
|
|
|
return lhs.x * rhs.y - lhs.y * rhs.x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float TriangleSignedDistance(float2 localPos, float2 p0, float2 p1, float2 p2) {
|
|
|
|
|
const float orientation = Cross2(p1 - p0, p2 - p0);
|
|
|
|
|
if (abs(orientation) <= 1.0e-5f) {
|
|
|
|
|
return 1.0e6f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float sign = orientation >= 0.0f ? 1.0f : -1.0f;
|
|
|
|
|
const float2 edge0 = p1 - p0;
|
|
|
|
|
const float2 edge1 = p2 - p1;
|
|
|
|
|
const float2 edge2 = p0 - p2;
|
|
|
|
|
const float distance0 =
|
|
|
|
|
sign * Cross2(edge0, localPos - p0) / max(length(edge0), 1.0e-5f);
|
|
|
|
|
const float distance1 =
|
|
|
|
|
sign * Cross2(edge1, localPos - p1) / max(length(edge1), 1.0e-5f);
|
|
|
|
|
const float distance2 =
|
|
|
|
|
sign * Cross2(edge2, localPos - p2) / max(length(edge2), 1.0e-5f);
|
|
|
|
|
return -min(distance0, min(distance1, distance2));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
float4 ResolveGradientColor(VSOutput input) {
|
|
|
|
|
const bool verticalGradient = input.params.w > 0.5f;
|
|
|
|
|
const float span = verticalGradient ? input.size.y : input.size.x;
|
|
|
|
|
const float axisValue = verticalGradient ? input.localPos.y : input.localPos.x;
|
|
|
|
|
const float t = span > 0.0f ? saturate(axisValue / span) : 0.0f;
|
|
|
|
|
return lerp(input.color, input.secondaryColor, t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 MainPS(VSOutput input) : SV_TARGET0 {
|
|
|
|
|
const int quadKind = (int)round(input.params.x);
|
|
|
|
|
if (quadKind == 0) {
|
|
|
|
|
return gTexture.Sample(gSampler, input.sampleUv) * input.color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float4 baseColor = ResolveGradientColor(input);
|
|
|
|
|
float coverage = 1.0f;
|
|
|
|
|
|
|
|
|
|
if (quadKind == 1) {
|
|
|
|
|
const float distance = RoundedRectSignedDistance(input.localPos, input.size, input.params.y);
|
|
|
|
|
coverage = CoverageFromSignedDistance(distance);
|
|
|
|
|
} else if (quadKind == 2) {
|
|
|
|
|
const float outerDistance =
|
|
|
|
|
RoundedRectSignedDistance(input.localPos, input.size, input.params.y);
|
|
|
|
|
const float innerRadius = max(0.0f, input.params.y - input.params.z);
|
|
|
|
|
const float2 insetVector = float2(input.params.z, input.params.z);
|
|
|
|
|
const float2 innerSize = max(input.size - insetVector * 2.0f, float2(0.0f, 0.0f));
|
|
|
|
|
const float2 innerLocalPos = input.localPos - insetVector;
|
|
|
|
|
const float innerDistance =
|
|
|
|
|
RoundedRectSignedDistance(innerLocalPos, innerSize, innerRadius);
|
|
|
|
|
coverage =
|
|
|
|
|
CoverageFromSignedDistance(outerDistance) *
|
|
|
|
|
(1.0f - CoverageFromSignedDistance(innerDistance));
|
|
|
|
|
} else if (quadKind == 3) {
|
|
|
|
|
const float2 center = input.size * 0.5f;
|
|
|
|
|
const float distance = CircleSignedDistance(input.localPos, center, input.params.y);
|
|
|
|
|
coverage = CoverageFromSignedDistance(distance);
|
|
|
|
|
} else if (quadKind == 4) {
|
|
|
|
|
const float2 center = input.size * 0.5f;
|
|
|
|
|
const float distance =
|
|
|
|
|
abs(length(input.localPos - center) - input.params.y) -
|
|
|
|
|
input.params.z * 0.5f;
|
|
|
|
|
coverage = CoverageFromSignedDistance(distance);
|
2026-04-22 02:47:27 +08:00
|
|
|
} else if (quadKind == 5) {
|
|
|
|
|
const float2 p0 = input.aux0.xy;
|
|
|
|
|
const float2 p1 = input.aux0.zw;
|
|
|
|
|
const float2 p2 = input.aux1.xy;
|
|
|
|
|
const float distance = TriangleSignedDistance(input.localPos, p0, p1, p2);
|
|
|
|
|
coverage = CoverageFromSignedDistance(distance);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return float4(baseColor.rgb, baseColor.a * coverage);
|
|
|
|
|
}
|
|
|
|
|
)";
|
|
|
|
|
|
|
|
|
|
struct FloatPoint {
|
|
|
|
|
float x = 0.0f;
|
|
|
|
|
float y = 0.0f;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FloatRect {
|
|
|
|
|
float left = 0.0f;
|
|
|
|
|
float top = 0.0f;
|
|
|
|
|
float right = 0.0f;
|
|
|
|
|
float bottom = 0.0f;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ClipState {
|
|
|
|
|
Rect scissor = {};
|
|
|
|
|
FloatRect rect = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct UiConstants {
|
|
|
|
|
float invViewportSize[2] = {};
|
|
|
|
|
float padding[2] = {};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::string HrToString(const char* operation, HRESULT hr) {
|
|
|
|
|
char buffer[128] = {};
|
|
|
|
|
sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast<unsigned int>(hr));
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float ClampDpiScale(float dpiScale) {
|
|
|
|
|
return dpiScale > 0.0f ? dpiScale : 1.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float ResolveFontSize(float fontSize) {
|
|
|
|
|
return fontSize > 0.0f ? fontSize : 16.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float SnapToPixel(float value, float dpiScale) {
|
|
|
|
|
return std::round(value * ClampDpiScale(dpiScale));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FloatRect ToPixelRect(const UIRect& rect, float dpiScale) {
|
|
|
|
|
return FloatRect{
|
|
|
|
|
SnapToPixel(rect.x, dpiScale),
|
|
|
|
|
SnapToPixel(rect.y, dpiScale),
|
|
|
|
|
SnapToPixel(rect.x + rect.width, dpiScale),
|
|
|
|
|
SnapToPixel(rect.y + rect.height, dpiScale)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FloatPoint ToPixelPoint(const UIPoint& point, float dpiScale) {
|
|
|
|
|
return FloatPoint{
|
|
|
|
|
SnapToPixel(point.x, dpiScale),
|
|
|
|
|
SnapToPixel(point.y, dpiScale)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FloatRect IntersectFloatRect(const FloatRect& lhs, const FloatRect& rhs) {
|
|
|
|
|
return FloatRect{
|
|
|
|
|
(std::max)(lhs.left, rhs.left),
|
|
|
|
|
(std::max)(lhs.top, rhs.top),
|
|
|
|
|
(std::min)(lhs.right, rhs.right),
|
|
|
|
|
(std::min)(lhs.bottom, rhs.bottom)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsEmptyRect(const FloatRect& rect) {
|
|
|
|
|
return rect.right <= rect.left || rect.bottom <= rect.top;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Rect ClampScissor(const FloatRect& rect, const Rect& renderArea) {
|
|
|
|
|
Rect clamped = {};
|
|
|
|
|
clamped.left = (std::max)(renderArea.left, static_cast<int>(std::floor(rect.left)));
|
|
|
|
|
clamped.top = (std::max)(renderArea.top, static_cast<int>(std::floor(rect.top)));
|
|
|
|
|
clamped.right = (std::min)(renderArea.right, static_cast<int>(std::ceil(rect.right)));
|
|
|
|
|
clamped.bottom = (std::min)(renderArea.bottom, static_cast<int>(std::ceil(rect.bottom)));
|
|
|
|
|
if (clamped.right < clamped.left) {
|
|
|
|
|
clamped.right = clamped.left;
|
|
|
|
|
}
|
|
|
|
|
if (clamped.bottom < clamped.top) {
|
|
|
|
|
clamped.bottom = clamped.top;
|
|
|
|
|
}
|
|
|
|
|
return clamped;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsEmptyScissor(const Rect& rect) {
|
|
|
|
|
return rect.right <= rect.left || rect.bottom <= rect.top;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr std::uint64_t kFnvOffsetBasis = 14695981039346656037ull;
|
|
|
|
|
constexpr std::uint64_t kFnvPrime = 1099511628211ull;
|
|
|
|
|
|
|
|
|
|
void HashBytes(
|
|
|
|
|
std::uint64_t& hash,
|
|
|
|
|
const void* data,
|
|
|
|
|
std::size_t size) {
|
|
|
|
|
const auto* bytes = static_cast<const unsigned char*>(data);
|
|
|
|
|
for (std::size_t index = 0u; index < size; ++index) {
|
|
|
|
|
hash ^= static_cast<std::uint64_t>(bytes[index]);
|
|
|
|
|
hash *= kFnvPrime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashFloat(std::uint64_t& hash, float value) {
|
|
|
|
|
std::uint32_t bits = 0u;
|
|
|
|
|
static_assert(sizeof(bits) == sizeof(value));
|
|
|
|
|
std::memcpy(&bits, &value, sizeof(bits));
|
|
|
|
|
HashBytes(hash, &bits, sizeof(bits));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashBool(std::uint64_t& hash, bool value) {
|
|
|
|
|
const std::uint8_t encoded = value ? 1u : 0u;
|
|
|
|
|
HashBytes(hash, &encoded, sizeof(encoded));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashString(std::uint64_t& hash, std::string_view value) {
|
|
|
|
|
const std::size_t size = value.size();
|
|
|
|
|
HashBytes(hash, &size, sizeof(size));
|
|
|
|
|
if (!value.empty()) {
|
|
|
|
|
HashBytes(hash, value.data(), value.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashTextureHandle(std::uint64_t& hash, const UITextureHandle& texture) {
|
|
|
|
|
HashBytes(hash, &texture.nativeHandle, sizeof(texture.nativeHandle));
|
|
|
|
|
HashBytes(hash, &texture.width, sizeof(texture.width));
|
|
|
|
|
HashBytes(hash, &texture.height, sizeof(texture.height));
|
|
|
|
|
HashBytes(hash, &texture.kind, sizeof(texture.kind));
|
|
|
|
|
HashBytes(hash, &texture.resourceHandle, sizeof(texture.resourceHandle));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashPoint(std::uint64_t& hash, const UIPoint& point) {
|
|
|
|
|
HashFloat(hash, point.x);
|
|
|
|
|
HashFloat(hash, point.y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HashRect(std::uint64_t& hash, const UIRect& rect) {
|
|
|
|
|
HashFloat(hash, rect.x);
|
|
|
|
|
HashFloat(hash, rect.y);
|
|
|
|
|
HashFloat(hash, rect.width);
|
|
|
|
|
HashFloat(hash, rect.height);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::uint64_t BuildDrawListContentHash(const UIDrawList& drawList) {
|
|
|
|
|
std::uint64_t hash = kFnvOffsetBasis;
|
|
|
|
|
HashString(hash, drawList.GetDebugName());
|
|
|
|
|
|
|
|
|
|
const std::size_t commandCount = drawList.GetCommandCount();
|
|
|
|
|
HashBytes(hash, &commandCount, sizeof(commandCount));
|
|
|
|
|
|
|
|
|
|
for (const UIDrawCommand& command : drawList.GetCommands()) {
|
|
|
|
|
HashBytes(hash, &command.type, sizeof(command.type));
|
|
|
|
|
HashRect(hash, command.rect);
|
|
|
|
|
HashPoint(hash, command.position);
|
|
|
|
|
HashPoint(hash, command.uvMin);
|
|
|
|
|
HashPoint(hash, command.uvMax);
|
|
|
|
|
HashFloat(hash, command.color.r);
|
|
|
|
|
HashFloat(hash, command.color.g);
|
|
|
|
|
HashFloat(hash, command.color.b);
|
|
|
|
|
HashFloat(hash, command.color.a);
|
|
|
|
|
HashFloat(hash, command.secondaryColor.r);
|
|
|
|
|
HashFloat(hash, command.secondaryColor.g);
|
|
|
|
|
HashFloat(hash, command.secondaryColor.b);
|
|
|
|
|
HashFloat(hash, command.secondaryColor.a);
|
|
|
|
|
HashFloat(hash, command.thickness);
|
|
|
|
|
HashFloat(hash, command.rounding);
|
|
|
|
|
HashFloat(hash, command.radius);
|
|
|
|
|
HashFloat(hash, command.fontSize);
|
|
|
|
|
HashBool(hash, command.intersectWithCurrentClip);
|
|
|
|
|
HashBytes(hash, &command.gradientDirection, sizeof(command.gradientDirection));
|
|
|
|
|
HashTextureHandle(hash, command.texture);
|
|
|
|
|
HashString(hash, command.text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WriteColor(float outColor[4], const UIColor& color) {
|
|
|
|
|
outColor[0] = color.r;
|
|
|
|
|
outColor[1] = color.g;
|
|
|
|
|
outColor[2] = color.b;
|
|
|
|
|
outColor[3] = color.a;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
void AppendBatch(
|
2026-04-21 20:49:18 +08:00
|
|
|
std::vector<D3D12UiRenderer::UiBatch>& batches,
|
2026-04-22 02:47:27 +08:00
|
|
|
std::uint32_t firstInstance,
|
|
|
|
|
std::uint32_t instanceCount,
|
2026-04-21 20:49:18 +08:00
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle,
|
|
|
|
|
const Rect& scissor) {
|
2026-04-22 02:47:27 +08:00
|
|
|
if (instanceCount == 0u || textureHandle.ptr == 0u || IsEmptyScissor(scissor)) {
|
2026-04-21 20:49:18 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!batches.empty()) {
|
|
|
|
|
D3D12UiRenderer::UiBatch& lastBatch = batches.back();
|
2026-04-22 02:47:27 +08:00
|
|
|
if (lastBatch.textureHandle.ptr == textureHandle.ptr &&
|
2026-04-21 20:49:18 +08:00
|
|
|
lastBatch.scissorRect.left == scissor.left &&
|
|
|
|
|
lastBatch.scissorRect.top == scissor.top &&
|
|
|
|
|
lastBatch.scissorRect.right == scissor.right &&
|
|
|
|
|
lastBatch.scissorRect.bottom == scissor.bottom &&
|
2026-04-22 02:47:27 +08:00
|
|
|
lastBatch.firstInstance + lastBatch.instanceCount == firstInstance) {
|
|
|
|
|
lastBatch.instanceCount += instanceCount;
|
2026-04-21 20:49:18 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
D3D12UiRenderer::UiBatch batch = {};
|
2026-04-22 02:47:27 +08:00
|
|
|
batch.firstInstance = firstInstance;
|
|
|
|
|
batch.instanceCount = instanceCount;
|
2026-04-21 20:49:18 +08:00
|
|
|
batch.textureHandle = textureHandle;
|
|
|
|
|
batch.scissorRect = scissor;
|
|
|
|
|
batches.push_back(batch);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
void AppendPrimitiveInstance(
|
|
|
|
|
std::vector<D3D12UiRenderer::UiPrimitiveInstance>& instances,
|
2026-04-21 20:49:18 +08:00
|
|
|
std::vector<D3D12UiRenderer::UiBatch>& batches,
|
|
|
|
|
const FloatPoint& origin,
|
|
|
|
|
const FloatPoint& axisX,
|
|
|
|
|
const FloatPoint& axisY,
|
|
|
|
|
const UIPoint& uvMin,
|
|
|
|
|
const UIPoint& uvMax,
|
|
|
|
|
const UIColor& color,
|
|
|
|
|
const UIColor& secondaryColor,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind primitiveKind,
|
2026-04-21 20:49:18 +08:00
|
|
|
float radius,
|
|
|
|
|
float thickness,
|
|
|
|
|
float gradientDirection,
|
2026-04-22 02:47:27 +08:00
|
|
|
const float aux0[4],
|
|
|
|
|
const float aux1[4],
|
2026-04-21 20:49:18 +08:00
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle,
|
|
|
|
|
const Rect& scissor) {
|
|
|
|
|
if (textureHandle.ptr == 0u || IsEmptyScissor(scissor)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
D3D12UiRenderer::UiPrimitiveInstance instance = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
instance.origin[0] = origin.x;
|
|
|
|
|
instance.origin[1] = origin.y;
|
|
|
|
|
instance.axisX[0] = axisX.x;
|
|
|
|
|
instance.axisX[1] = axisX.y;
|
|
|
|
|
instance.axisY[0] = axisY.x;
|
|
|
|
|
instance.axisY[1] = axisY.y;
|
|
|
|
|
instance.uvMin[0] = uvMin.x;
|
|
|
|
|
instance.uvMin[1] = uvMin.y;
|
|
|
|
|
instance.uvMax[0] = uvMax.x;
|
|
|
|
|
instance.uvMax[1] = uvMax.y;
|
|
|
|
|
WriteColor(instance.color, color);
|
|
|
|
|
WriteColor(instance.secondaryColor, secondaryColor);
|
2026-04-22 02:47:27 +08:00
|
|
|
instance.params[0] = static_cast<float>(static_cast<int>(primitiveKind));
|
2026-04-21 20:49:18 +08:00
|
|
|
instance.params[1] = radius;
|
|
|
|
|
instance.params[2] = thickness;
|
|
|
|
|
instance.params[3] = gradientDirection;
|
2026-04-22 02:47:27 +08:00
|
|
|
std::memcpy(instance.aux0, aux0, sizeof(instance.aux0));
|
|
|
|
|
std::memcpy(instance.aux1, aux1, sizeof(instance.aux1));
|
2026-04-21 20:49:18 +08:00
|
|
|
const std::uint32_t firstInstance = static_cast<std::uint32_t>(instances.size());
|
|
|
|
|
instances.push_back(instance);
|
2026-04-22 02:47:27 +08:00
|
|
|
AppendBatch(batches, firstInstance, 1u, textureHandle, scissor);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GraphicsPipelineDesc BuildBaseUiPipelineDesc(
|
|
|
|
|
::XCEngine::RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
GraphicsPipelineDesc pipelineDesc = {};
|
|
|
|
|
pipelineDesc.pipelineLayout = pipelineLayout;
|
|
|
|
|
pipelineDesc.topologyType =
|
|
|
|
|
static_cast<std::uint32_t>(PrimitiveTopologyType::Triangle);
|
|
|
|
|
::XCEngine::Rendering::ApplySingleColorAttachmentPropertiesToGraphicsPipelineDesc(
|
|
|
|
|
surface,
|
|
|
|
|
pipelineDesc);
|
|
|
|
|
|
|
|
|
|
pipelineDesc.inputLayout.elements = {
|
|
|
|
|
{ "POSITION", 0, static_cast<std::uint32_t>(Format::R32G32_Float), 0, 0, 0, 0 },
|
|
|
|
|
{ "TEXCOORD", 0, static_cast<std::uint32_t>(Format::R32G32_Float), 0, 8, 0, 0 },
|
|
|
|
|
{ "COLOR", 0, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 0, 16, 0, 0 }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pipelineDesc.rasterizerState.fillMode =
|
|
|
|
|
static_cast<std::uint32_t>(FillMode::Solid);
|
|
|
|
|
pipelineDesc.rasterizerState.cullMode =
|
|
|
|
|
static_cast<std::uint32_t>(CullMode::None);
|
|
|
|
|
pipelineDesc.rasterizerState.frontFace =
|
|
|
|
|
static_cast<std::uint32_t>(FrontFace::CounterClockwise);
|
|
|
|
|
pipelineDesc.rasterizerState.depthClipEnable = true;
|
|
|
|
|
pipelineDesc.rasterizerState.scissorTestEnable = true;
|
|
|
|
|
|
|
|
|
|
pipelineDesc.blendState.blendEnable = true;
|
|
|
|
|
pipelineDesc.blendState.srcBlend =
|
|
|
|
|
static_cast<std::uint32_t>(BlendFactor::SrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.dstBlend =
|
|
|
|
|
static_cast<std::uint32_t>(BlendFactor::InvSrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.srcBlendAlpha =
|
|
|
|
|
static_cast<std::uint32_t>(BlendFactor::One);
|
|
|
|
|
pipelineDesc.blendState.dstBlendAlpha =
|
|
|
|
|
static_cast<std::uint32_t>(BlendFactor::InvSrcAlpha);
|
|
|
|
|
pipelineDesc.blendState.blendOp =
|
|
|
|
|
static_cast<std::uint32_t>(BlendOp::Add);
|
|
|
|
|
pipelineDesc.blendState.blendOpAlpha =
|
|
|
|
|
static_cast<std::uint32_t>(BlendOp::Add);
|
|
|
|
|
pipelineDesc.blendState.colorWriteMask =
|
|
|
|
|
static_cast<std::uint8_t>(ColorWriteMask::All);
|
|
|
|
|
|
|
|
|
|
pipelineDesc.depthStencilState.depthTestEnable = false;
|
|
|
|
|
pipelineDesc.depthStencilState.depthWriteEnable = false;
|
|
|
|
|
pipelineDesc.depthStencilState.depthFunc =
|
|
|
|
|
static_cast<std::uint32_t>(ComparisonFunc::Always);
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
GraphicsPipelineDesc BuildPrimitiveUiPipelineDesc(
|
2026-04-21 20:49:18 +08:00
|
|
|
::XCEngine::RHI::RHIPipelineLayout* pipelineLayout,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
GraphicsPipelineDesc pipelineDesc = BuildBaseUiPipelineDesc(pipelineLayout, surface);
|
|
|
|
|
pipelineDesc.inputLayout.elements = {
|
|
|
|
|
{ "POSITION", 0, static_cast<std::uint32_t>(Format::R32G32_Float), 0, 0, 0, 0 },
|
|
|
|
|
{ "TEXCOORD", 0, static_cast<std::uint32_t>(Format::R32G32_Float), 0, 8, 0, 0 },
|
|
|
|
|
{ "POSITION", 1, static_cast<std::uint32_t>(Format::R32G32_Float), 1, 0, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 1, static_cast<std::uint32_t>(Format::R32G32_Float), 1, 8, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 2, static_cast<std::uint32_t>(Format::R32G32_Float), 1, 16, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 3, static_cast<std::uint32_t>(Format::R32G32_Float), 1, 24, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 4, static_cast<std::uint32_t>(Format::R32G32_Float), 1, 32, 1, 1 },
|
|
|
|
|
{ "COLOR", 0, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 1, 40, 1, 1 },
|
|
|
|
|
{ "COLOR", 1, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 1, 56, 1, 1 },
|
2026-04-22 02:47:27 +08:00
|
|
|
{ "TEXCOORD", 5, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 1, 72, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 6, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 1, 88, 1, 1 },
|
|
|
|
|
{ "TEXCOORD", 7, static_cast<std::uint32_t>(Format::R32G32B32A32_Float), 1, 104, 1, 1 }
|
2026-04-21 20:49:18 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pipelineDesc.vertexShader.source.assign(
|
2026-04-22 02:47:27 +08:00
|
|
|
kPrimitiveUiPassHlsl,
|
|
|
|
|
kPrimitiveUiPassHlsl + std::strlen(kPrimitiveUiPassHlsl));
|
2026-04-21 20:49:18 +08:00
|
|
|
pipelineDesc.vertexShader.sourceLanguage = ::XCEngine::RHI::ShaderLanguage::HLSL;
|
|
|
|
|
pipelineDesc.vertexShader.entryPoint = L"MainVS";
|
|
|
|
|
pipelineDesc.vertexShader.profile = L"vs_5_0";
|
|
|
|
|
|
|
|
|
|
pipelineDesc.fragmentShader.source.assign(
|
2026-04-22 02:47:27 +08:00
|
|
|
kPrimitiveUiPassHlsl,
|
|
|
|
|
kPrimitiveUiPassHlsl + std::strlen(kPrimitiveUiPassHlsl));
|
2026-04-21 20:49:18 +08:00
|
|
|
pipelineDesc.fragmentShader.sourceLanguage = ::XCEngine::RHI::ShaderLanguage::HLSL;
|
|
|
|
|
pipelineDesc.fragmentShader.entryPoint = L"MainPS";
|
|
|
|
|
pipelineDesc.fragmentShader.profile = L"ps_5_0";
|
|
|
|
|
return pipelineDesc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::TextRunCacheKey::operator==(const TextRunCacheKey& other) const {
|
|
|
|
|
return text == other.text &&
|
|
|
|
|
fontSizeTenths == other.fontSizeTenths &&
|
|
|
|
|
dpiScaleMilli == other.dpiScaleMilli;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::size_t D3D12UiRenderer::TextRunCacheKeyHash::operator()(
|
|
|
|
|
const TextRunCacheKey& key) const {
|
|
|
|
|
const std::size_t textHash = std::hash<std::string>{}(key.text);
|
|
|
|
|
const std::size_t fontHash = std::hash<int>{}(key.fontSizeTenths);
|
|
|
|
|
const std::size_t dpiHash = std::hash<int>{}(key.dpiScaleMilli);
|
|
|
|
|
return textHash ^ (fontHash << 1u) ^ (dpiHash << 2u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::CompiledDrawListKey::operator==(
|
|
|
|
|
const CompiledDrawListKey& other) const {
|
|
|
|
|
return contentHash == other.contentHash &&
|
|
|
|
|
renderWidth == other.renderWidth &&
|
|
|
|
|
renderHeight == other.renderHeight &&
|
|
|
|
|
dpiScaleMilli == other.dpiScaleMilli;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::size_t D3D12UiRenderer::CompiledDrawListKeyHash::operator()(
|
|
|
|
|
const CompiledDrawListKey& key) const {
|
|
|
|
|
const std::size_t contentHashValue = std::hash<std::uint64_t>{}(key.contentHash);
|
|
|
|
|
const std::size_t widthHash = std::hash<std::uint32_t>{}(key.renderWidth);
|
|
|
|
|
const std::size_t heightHash = std::hash<std::uint32_t>{}(key.renderHeight);
|
|
|
|
|
const std::size_t dpiHash = std::hash<int>{}(key.dpiScaleMilli);
|
|
|
|
|
return contentHashValue ^
|
|
|
|
|
(widthHash << 1u) ^
|
|
|
|
|
(heightHash << 2u) ^
|
|
|
|
|
(dpiHash << 3u);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::Initialize(
|
|
|
|
|
D3D12WindowRenderer& windowRenderer,
|
|
|
|
|
D3D12UiTextureHost& textureHost,
|
|
|
|
|
D3D12UiTextSystem& textSystem) {
|
|
|
|
|
const float dpiScale = m_dpiScale;
|
|
|
|
|
Shutdown();
|
|
|
|
|
|
|
|
|
|
m_windowRenderer = &windowRenderer;
|
|
|
|
|
m_textureHost = &textureHost;
|
|
|
|
|
m_textSystem = &textSystem;
|
|
|
|
|
m_dpiScale = ClampDpiScale(dpiScale);
|
|
|
|
|
m_lastError.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiRenderer::Shutdown() {
|
|
|
|
|
ReleaseTextRunCache();
|
|
|
|
|
ReleaseCompiledDrawListCache();
|
|
|
|
|
if (m_textureHost != nullptr) {
|
|
|
|
|
m_textureHost->ReleaseTexture(m_whiteTexture);
|
|
|
|
|
} else {
|
|
|
|
|
m_whiteTexture = {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyResources();
|
|
|
|
|
m_windowRenderer = nullptr;
|
|
|
|
|
m_textureHost = nullptr;
|
|
|
|
|
m_textSystem = nullptr;
|
|
|
|
|
m_dpiScale = 1.0f;
|
2026-04-21 21:07:06 +08:00
|
|
|
m_compiledDrawListFrameCounter = 0u;
|
2026-04-21 20:49:18 +08:00
|
|
|
m_lastError.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiRenderer::SetDpiScale(float dpiScale) {
|
|
|
|
|
const float resolvedScale = ClampDpiScale(dpiScale);
|
|
|
|
|
if (std::abs(m_dpiScale - resolvedScale) > 0.0001f) {
|
|
|
|
|
ReleaseTextRunCache();
|
|
|
|
|
ReleaseCompiledDrawListCache();
|
|
|
|
|
}
|
|
|
|
|
m_dpiScale = resolvedScale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float D3D12UiRenderer::GetDpiScale() const {
|
|
|
|
|
return m_dpiScale;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::string& D3D12UiRenderer::GetLastError() const {
|
|
|
|
|
return m_lastError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::EnsureWhiteTexture() {
|
|
|
|
|
if (m_whiteTexture.IsValid()) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_textureHost == nullptr) {
|
|
|
|
|
m_lastError = "EnsureWhiteTexture requires an initialized texture host.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint8_t whitePixel[4] = { 255u, 255u, 255u, 255u };
|
|
|
|
|
std::string error = {};
|
|
|
|
|
if (!m_textureHost->LoadTextureFromRgba(
|
|
|
|
|
whitePixel,
|
|
|
|
|
1u,
|
|
|
|
|
1u,
|
|
|
|
|
m_whiteTexture,
|
|
|
|
|
error)) {
|
|
|
|
|
m_lastError = error.empty()
|
|
|
|
|
? "Failed to create the UI white texture."
|
|
|
|
|
: error;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const D3D12UiRenderer::CachedTextRun* D3D12UiRenderer::ResolveTextRun(
|
|
|
|
|
std::string_view text,
|
2026-04-21 21:07:06 +08:00
|
|
|
float fontSize,
|
|
|
|
|
std::uint64_t currentFrameId) {
|
2026-04-21 20:49:18 +08:00
|
|
|
if (m_textureHost == nullptr || m_textSystem == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextRunCacheKey cacheKey = {};
|
|
|
|
|
cacheKey.text.assign(text);
|
|
|
|
|
cacheKey.fontSizeTenths =
|
|
|
|
|
static_cast<int>(std::lround(ResolveFontSize(fontSize) * 10.0f));
|
|
|
|
|
cacheKey.dpiScaleMilli =
|
|
|
|
|
static_cast<int>(std::lround(ClampDpiScale(m_dpiScale) * 1000.0f));
|
|
|
|
|
|
|
|
|
|
const auto found = m_textRunCache.find(cacheKey);
|
|
|
|
|
if (found != m_textRunCache.end()) {
|
2026-04-21 21:07:06 +08:00
|
|
|
found->second.lastUsedFrame = currentFrameId;
|
2026-04-21 20:49:18 +08:00
|
|
|
return &found->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string error = {};
|
2026-04-21 21:07:06 +08:00
|
|
|
D3D12UiTextSystem::RasterizedTextRun rasterizedText = {};
|
|
|
|
|
if (!m_textSystem->RasterizeTextMask(text, fontSize, rasterizedText, error)) {
|
2026-04-21 20:49:18 +08:00
|
|
|
m_lastError = error.empty()
|
2026-04-21 21:07:06 +08:00
|
|
|
? "Failed to rasterize the UI text run."
|
2026-04-21 20:49:18 +08:00
|
|
|
: error;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CachedTextRun cachedText = {};
|
2026-04-21 21:07:06 +08:00
|
|
|
cachedText.offsetX = rasterizedText.offsetX;
|
|
|
|
|
cachedText.offsetY = rasterizedText.offsetY;
|
|
|
|
|
cachedText.width = static_cast<float>(rasterizedText.width);
|
|
|
|
|
cachedText.height = static_cast<float>(rasterizedText.height);
|
|
|
|
|
cachedText.lastUsedFrame = currentFrameId;
|
|
|
|
|
cachedText.hasPixels =
|
|
|
|
|
rasterizedText.width > 0u &&
|
|
|
|
|
rasterizedText.height > 0u &&
|
|
|
|
|
!rasterizedText.rgbaPixels.empty();
|
|
|
|
|
|
|
|
|
|
if (cachedText.hasPixels) {
|
|
|
|
|
if (!m_textureHost->LoadTextureFromRgba(
|
|
|
|
|
rasterizedText.rgbaPixels.data(),
|
|
|
|
|
rasterizedText.width,
|
|
|
|
|
rasterizedText.height,
|
|
|
|
|
cachedText.texture,
|
|
|
|
|
error)) {
|
|
|
|
|
m_lastError = error.empty()
|
|
|
|
|
? "Failed to upload the UI text run texture."
|
|
|
|
|
: error;
|
2026-04-21 20:49:18 +08:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
2026-04-21 21:07:06 +08:00
|
|
|
cachedText.textureHandle.ptr = static_cast<UINT64>(cachedText.texture.nativeHandle);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto [insertedIt, inserted] =
|
|
|
|
|
m_textRunCache.emplace(std::move(cacheKey), std::move(cachedText));
|
|
|
|
|
(void)inserted;
|
|
|
|
|
return &insertedIt->second;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:07:06 +08:00
|
|
|
void D3D12UiRenderer::ReleaseTextRunCache() {
|
2026-04-21 20:49:18 +08:00
|
|
|
if (m_textureHost != nullptr) {
|
2026-04-21 21:07:06 +08:00
|
|
|
for (auto& [key, cachedText] : m_textRunCache) {
|
|
|
|
|
m_textureHost->ReleaseTexture(cachedText.texture);
|
|
|
|
|
cachedText.textureHandle = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-21 21:07:06 +08:00
|
|
|
m_textRunCache.clear();
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:07:06 +08:00
|
|
|
void D3D12UiRenderer::PruneTextRunCache(std::uint64_t currentFrameId) {
|
|
|
|
|
constexpr std::size_t kTextRunCacheMaxEntries = 512u;
|
|
|
|
|
constexpr std::uint64_t kTextRunCacheRetentionFrames = 240u;
|
|
|
|
|
if (m_textRunCache.size() <= kTextRunCacheMaxEntries) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint64_t pruneBeforeFrame =
|
|
|
|
|
currentFrameId > kTextRunCacheRetentionFrames
|
|
|
|
|
? currentFrameId - kTextRunCacheRetentionFrames
|
|
|
|
|
: 0u;
|
|
|
|
|
|
|
|
|
|
bool evictedAny = false;
|
|
|
|
|
for (auto it = m_textRunCache.begin(); it != m_textRunCache.end();) {
|
|
|
|
|
if (it->second.lastUsedFrame <= pruneBeforeFrame) {
|
|
|
|
|
if (m_textureHost != nullptr) {
|
|
|
|
|
m_textureHost->ReleaseTexture(it->second.texture);
|
|
|
|
|
}
|
|
|
|
|
it = m_textRunCache.erase(it);
|
|
|
|
|
evictedAny = true;
|
|
|
|
|
} else {
|
|
|
|
|
++it;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (evictedAny) {
|
|
|
|
|
ReleaseCompiledDrawListCache();
|
|
|
|
|
}
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiRenderer::ReleaseCompiledDrawListCache() {
|
|
|
|
|
m_compiledDrawListCache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::EnsureInitialized(
|
|
|
|
|
const RenderContext& renderContext,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
const Format renderTargetFormat =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceColorFormat(surface, 0u);
|
|
|
|
|
const std::uint32_t sampleCount =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceSampleCount(surface);
|
|
|
|
|
const std::uint32_t sampleQuality =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceSampleQuality(surface);
|
|
|
|
|
|
|
|
|
|
if (m_device == renderContext.device &&
|
|
|
|
|
m_backendType == renderContext.backendType &&
|
|
|
|
|
m_pipelineLayout != nullptr &&
|
2026-04-22 02:47:27 +08:00
|
|
|
m_primitivePipelineState != nullptr &&
|
2026-04-21 20:49:18 +08:00
|
|
|
m_constantPool != nullptr &&
|
|
|
|
|
m_constantSet != nullptr &&
|
|
|
|
|
m_samplerPool != nullptr &&
|
|
|
|
|
m_samplerSet != nullptr &&
|
|
|
|
|
m_sampler != nullptr &&
|
|
|
|
|
m_quadVertexBuffer != nullptr &&
|
|
|
|
|
m_quadVertexBufferView != nullptr &&
|
|
|
|
|
m_quadIndexBuffer != nullptr &&
|
|
|
|
|
m_quadIndexBufferView != nullptr &&
|
|
|
|
|
m_renderTargetFormat == renderTargetFormat &&
|
|
|
|
|
m_renderTargetSampleCount == sampleCount &&
|
|
|
|
|
m_renderTargetSampleQuality == sampleQuality) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return CreateResources(renderContext, surface);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::CreateResources(
|
|
|
|
|
const RenderContext& renderContext,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
if (!renderContext.IsValid() ||
|
|
|
|
|
renderContext.device == nullptr ||
|
|
|
|
|
!::XCEngine::Rendering::HasSingleColorAttachment(surface) ||
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceColorFormat(surface, 0u) == Format::Unknown) {
|
|
|
|
|
m_lastError = "CreateResources requires a valid single-color render surface.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_device = renderContext.device;
|
|
|
|
|
m_backendType = renderContext.backendType;
|
|
|
|
|
m_renderTargetFormat =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceColorFormat(surface, 0u);
|
|
|
|
|
m_renderTargetSampleCount =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceSampleCount(surface);
|
|
|
|
|
m_renderTargetSampleQuality =
|
|
|
|
|
::XCEngine::Rendering::ResolveSurfaceSampleQuality(surface);
|
|
|
|
|
|
|
|
|
|
DescriptorSetLayoutBinding constantBinding = {};
|
|
|
|
|
constantBinding.binding = 0u;
|
|
|
|
|
constantBinding.type = static_cast<std::uint32_t>(DescriptorType::CBV);
|
|
|
|
|
constantBinding.count = 1u;
|
|
|
|
|
constantBinding.visibility = static_cast<std::uint32_t>(ShaderVisibility::All);
|
|
|
|
|
|
|
|
|
|
DescriptorSetLayoutBinding textureBinding = {};
|
|
|
|
|
textureBinding.binding = 0u;
|
|
|
|
|
textureBinding.type = static_cast<std::uint32_t>(DescriptorType::SRV);
|
|
|
|
|
textureBinding.count = 1u;
|
|
|
|
|
textureBinding.visibility = static_cast<std::uint32_t>(ShaderVisibility::All);
|
|
|
|
|
|
|
|
|
|
DescriptorSetLayoutBinding samplerBinding = {};
|
|
|
|
|
samplerBinding.binding = 0u;
|
|
|
|
|
samplerBinding.type = static_cast<std::uint32_t>(DescriptorType::Sampler);
|
|
|
|
|
samplerBinding.count = 1u;
|
|
|
|
|
samplerBinding.visibility = static_cast<std::uint32_t>(ShaderVisibility::All);
|
|
|
|
|
|
|
|
|
|
DescriptorSetLayoutDesc setLayouts[3] = {};
|
|
|
|
|
setLayouts[0].bindings = &constantBinding;
|
|
|
|
|
setLayouts[0].bindingCount = 1u;
|
|
|
|
|
setLayouts[1].bindings = &textureBinding;
|
|
|
|
|
setLayouts[1].bindingCount = 1u;
|
|
|
|
|
setLayouts[2].bindings = &samplerBinding;
|
|
|
|
|
setLayouts[2].bindingCount = 1u;
|
|
|
|
|
|
|
|
|
|
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
|
|
|
|
pipelineLayoutDesc.setLayouts = setLayouts;
|
|
|
|
|
pipelineLayoutDesc.setLayoutCount = 3u;
|
|
|
|
|
m_pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
|
|
|
|
if (m_pipelineLayout == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI pipeline layout.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DescriptorPoolDesc constantPoolDesc = {};
|
|
|
|
|
constantPoolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
|
|
|
|
constantPoolDesc.descriptorCount = 1u;
|
|
|
|
|
constantPoolDesc.shaderVisible = false;
|
|
|
|
|
m_constantPool = m_device->CreateDescriptorPool(constantPoolDesc);
|
|
|
|
|
if (m_constantPool == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI constant descriptor pool.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_constantSet = m_constantPool->AllocateSet(setLayouts[0]);
|
|
|
|
|
if (m_constantSet == nullptr) {
|
|
|
|
|
m_lastError = "Failed to allocate the D3D12 UI constant descriptor set.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SamplerDesc samplerDesc = {};
|
|
|
|
|
samplerDesc.filter = static_cast<std::uint32_t>(FilterMode::Linear);
|
|
|
|
|
samplerDesc.addressU = static_cast<std::uint32_t>(TextureAddressMode::Clamp);
|
|
|
|
|
samplerDesc.addressV = static_cast<std::uint32_t>(TextureAddressMode::Clamp);
|
|
|
|
|
samplerDesc.addressW = static_cast<std::uint32_t>(TextureAddressMode::Clamp);
|
|
|
|
|
samplerDesc.maxAnisotropy = 1u;
|
|
|
|
|
samplerDesc.comparisonFunc = static_cast<std::uint32_t>(ComparisonFunc::Always);
|
|
|
|
|
samplerDesc.minLod = 0.0f;
|
|
|
|
|
samplerDesc.maxLod = 16.0f;
|
|
|
|
|
m_sampler = m_device->CreateSampler(samplerDesc);
|
|
|
|
|
if (m_sampler == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI sampler.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DescriptorPoolDesc samplerPoolDesc = {};
|
|
|
|
|
samplerPoolDesc.type = DescriptorHeapType::Sampler;
|
|
|
|
|
samplerPoolDesc.descriptorCount = 1u;
|
|
|
|
|
samplerPoolDesc.shaderVisible = true;
|
|
|
|
|
m_samplerPool = m_device->CreateDescriptorPool(samplerPoolDesc);
|
|
|
|
|
if (m_samplerPool == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI sampler descriptor pool.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_samplerSet = m_samplerPool->AllocateSet(setLayouts[2]);
|
|
|
|
|
if (m_samplerSet == nullptr) {
|
|
|
|
|
m_lastError = "Failed to allocate the D3D12 UI sampler descriptor set.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_samplerSet->UpdateSampler(0u, m_sampler);
|
|
|
|
|
auto* samplerDescriptorSet = static_cast<D3D12DescriptorSet*>(m_samplerSet);
|
|
|
|
|
m_samplerGpuHandle = samplerDescriptorSet->GetGPUHandleForBinding(0u);
|
|
|
|
|
if (m_samplerGpuHandle.ptr == 0u) {
|
|
|
|
|
m_lastError = "Failed to resolve the D3D12 UI sampler GPU descriptor handle.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
m_primitivePipelineState = m_device->CreatePipelineState(
|
|
|
|
|
BuildPrimitiveUiPipelineDesc(m_pipelineLayout, surface));
|
|
|
|
|
if (m_primitivePipelineState == nullptr || !m_primitivePipelineState->IsValid()) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI primitive pipeline state.";
|
2026-04-21 20:49:18 +08:00
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BufferDesc quadVertexBufferDesc = {};
|
|
|
|
|
quadVertexBufferDesc.size = sizeof(kUnitQuadVertices);
|
|
|
|
|
quadVertexBufferDesc.stride = static_cast<std::uint32_t>(sizeof(UiQuadVertex));
|
|
|
|
|
quadVertexBufferDesc.bufferType = static_cast<std::uint32_t>(BufferType::Vertex);
|
|
|
|
|
m_quadVertexBuffer =
|
|
|
|
|
m_device->CreateBuffer(quadVertexBufferDesc, kUnitQuadVertices, sizeof(kUnitQuadVertices));
|
|
|
|
|
if (m_quadVertexBuffer == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI unit-quad vertex buffer.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_quadVertexBuffer->SetStride(quadVertexBufferDesc.stride);
|
|
|
|
|
m_quadVertexBuffer->SetBufferType(BufferType::Vertex);
|
|
|
|
|
|
|
|
|
|
ResourceViewDesc quadVertexViewDesc = {};
|
|
|
|
|
quadVertexViewDesc.dimension = ResourceViewDimension::Buffer;
|
|
|
|
|
quadVertexViewDesc.structureByteStride = quadVertexBufferDesc.stride;
|
|
|
|
|
m_quadVertexBufferView =
|
|
|
|
|
m_device->CreateVertexBufferView(m_quadVertexBuffer, quadVertexViewDesc);
|
|
|
|
|
if (m_quadVertexBufferView == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI unit-quad vertex view.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BufferDesc quadIndexBufferDesc = {};
|
|
|
|
|
quadIndexBufferDesc.size = sizeof(kUnitQuadIndices);
|
|
|
|
|
quadIndexBufferDesc.stride = static_cast<std::uint32_t>(sizeof(std::uint32_t));
|
|
|
|
|
quadIndexBufferDesc.bufferType = static_cast<std::uint32_t>(BufferType::Index);
|
|
|
|
|
m_quadIndexBuffer =
|
|
|
|
|
m_device->CreateBuffer(quadIndexBufferDesc, kUnitQuadIndices, sizeof(kUnitQuadIndices));
|
|
|
|
|
if (m_quadIndexBuffer == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI unit-quad index buffer.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_quadIndexBuffer->SetStride(quadIndexBufferDesc.stride);
|
|
|
|
|
m_quadIndexBuffer->SetBufferType(BufferType::Index);
|
|
|
|
|
|
|
|
|
|
ResourceViewDesc quadIndexViewDesc = {};
|
|
|
|
|
quadIndexViewDesc.dimension = ResourceViewDimension::Buffer;
|
|
|
|
|
quadIndexViewDesc.format = static_cast<std::uint32_t>(Format::R32_UInt);
|
|
|
|
|
m_quadIndexBufferView =
|
|
|
|
|
m_device->CreateIndexBufferView(m_quadIndexBuffer, quadIndexViewDesc);
|
|
|
|
|
if (m_quadIndexBufferView == nullptr) {
|
|
|
|
|
m_lastError = "Failed to create the D3D12 UI unit-quad index view.";
|
|
|
|
|
DestroyResources();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiRenderer::DestroyFrameResources(FrameResources& frameResources) {
|
2026-04-22 02:47:27 +08:00
|
|
|
if (frameResources.primitiveInstanceBufferView != nullptr) {
|
|
|
|
|
frameResources.primitiveInstanceBufferView->Shutdown();
|
|
|
|
|
delete frameResources.primitiveInstanceBufferView;
|
|
|
|
|
frameResources.primitiveInstanceBufferView = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (frameResources.primitiveInstanceBuffer != nullptr) {
|
|
|
|
|
frameResources.primitiveInstanceBuffer->Shutdown();
|
|
|
|
|
delete frameResources.primitiveInstanceBuffer;
|
|
|
|
|
frameResources.primitiveInstanceBuffer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
frameResources.primitiveInstanceCapacityBytes = 0u;
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void D3D12UiRenderer::DestroyResources() {
|
|
|
|
|
for (FrameResources& frameResources : m_frameResources) {
|
|
|
|
|
DestroyFrameResources(frameResources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_quadIndexBufferView != nullptr) {
|
|
|
|
|
m_quadIndexBufferView->Shutdown();
|
|
|
|
|
delete m_quadIndexBufferView;
|
|
|
|
|
m_quadIndexBufferView = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_quadIndexBuffer != nullptr) {
|
|
|
|
|
m_quadIndexBuffer->Shutdown();
|
|
|
|
|
delete m_quadIndexBuffer;
|
|
|
|
|
m_quadIndexBuffer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_quadVertexBufferView != nullptr) {
|
|
|
|
|
m_quadVertexBufferView->Shutdown();
|
|
|
|
|
delete m_quadVertexBufferView;
|
|
|
|
|
m_quadVertexBufferView = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_quadVertexBuffer != nullptr) {
|
|
|
|
|
m_quadVertexBuffer->Shutdown();
|
|
|
|
|
delete m_quadVertexBuffer;
|
|
|
|
|
m_quadVertexBuffer = nullptr;
|
|
|
|
|
}
|
2026-04-22 02:47:27 +08:00
|
|
|
if (m_primitivePipelineState != nullptr) {
|
|
|
|
|
m_primitivePipelineState->Shutdown();
|
|
|
|
|
delete m_primitivePipelineState;
|
|
|
|
|
m_primitivePipelineState = nullptr;
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
if (m_samplerSet != nullptr) {
|
|
|
|
|
m_samplerSet->Shutdown();
|
|
|
|
|
delete m_samplerSet;
|
|
|
|
|
m_samplerSet = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_samplerPool != nullptr) {
|
|
|
|
|
m_samplerPool->Shutdown();
|
|
|
|
|
delete m_samplerPool;
|
|
|
|
|
m_samplerPool = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_sampler != nullptr) {
|
|
|
|
|
m_sampler->Shutdown();
|
|
|
|
|
delete m_sampler;
|
|
|
|
|
m_sampler = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_constantSet != nullptr) {
|
|
|
|
|
m_constantSet->Shutdown();
|
|
|
|
|
delete m_constantSet;
|
|
|
|
|
m_constantSet = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_constantPool != nullptr) {
|
|
|
|
|
m_constantPool->Shutdown();
|
|
|
|
|
delete m_constantPool;
|
|
|
|
|
m_constantPool = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (m_pipelineLayout != nullptr) {
|
|
|
|
|
m_pipelineLayout->Shutdown();
|
|
|
|
|
delete m_pipelineLayout;
|
|
|
|
|
m_pipelineLayout = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_samplerGpuHandle = {};
|
|
|
|
|
m_device = nullptr;
|
|
|
|
|
m_backendType = ::XCEngine::RHI::RHIType::D3D12;
|
|
|
|
|
m_renderTargetFormat = Format::Unknown;
|
|
|
|
|
m_renderTargetSampleCount = 1u;
|
|
|
|
|
m_renderTargetSampleQuality = 0u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::EnsureFrameBufferCapacity(
|
|
|
|
|
std::uint32_t frameSlot,
|
2026-04-22 02:47:27 +08:00
|
|
|
std::size_t primitiveInstanceBytes) {
|
2026-04-21 20:49:18 +08:00
|
|
|
if (m_device == nullptr || frameSlot >= m_frameResources.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FrameResources& frameResources = m_frameResources[frameSlot];
|
|
|
|
|
auto ensureVertexBuffer =
|
|
|
|
|
[&](std::size_t requiredBytes,
|
|
|
|
|
std::uint64_t minimumBytes,
|
|
|
|
|
std::uint32_t stride,
|
|
|
|
|
::XCEngine::RHI::RHIBuffer*& buffer,
|
|
|
|
|
::XCEngine::RHI::RHIResourceView*& bufferView,
|
|
|
|
|
std::uint64_t& capacityBytes) -> bool {
|
|
|
|
|
if (requiredBytes == 0u) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint64_t resolvedRequiredBytes =
|
|
|
|
|
(std::max<std::uint64_t>)(static_cast<std::uint64_t>(requiredBytes), minimumBytes);
|
|
|
|
|
if (buffer != nullptr && capacityBytes >= resolvedRequiredBytes) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bufferView != nullptr) {
|
|
|
|
|
bufferView->Shutdown();
|
|
|
|
|
delete bufferView;
|
|
|
|
|
bufferView = nullptr;
|
|
|
|
|
}
|
|
|
|
|
if (buffer != nullptr) {
|
|
|
|
|
buffer->Shutdown();
|
|
|
|
|
delete buffer;
|
|
|
|
|
buffer = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
capacityBytes = resolvedRequiredBytes;
|
|
|
|
|
BufferDesc bufferDesc = {};
|
|
|
|
|
bufferDesc.size = capacityBytes;
|
|
|
|
|
bufferDesc.stride = stride;
|
|
|
|
|
bufferDesc.bufferType = static_cast<std::uint32_t>(BufferType::Vertex);
|
|
|
|
|
buffer = m_device->CreateBuffer(bufferDesc);
|
|
|
|
|
if (buffer == nullptr) {
|
|
|
|
|
capacityBytes = 0u;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
buffer->SetStride(bufferDesc.stride);
|
|
|
|
|
buffer->SetBufferType(BufferType::Vertex);
|
|
|
|
|
|
|
|
|
|
ResourceViewDesc viewDesc = {};
|
|
|
|
|
viewDesc.dimension = ResourceViewDimension::Buffer;
|
|
|
|
|
viewDesc.structureByteStride = bufferDesc.stride;
|
|
|
|
|
bufferView = m_device->CreateVertexBufferView(buffer, viewDesc);
|
|
|
|
|
if (bufferView == nullptr) {
|
|
|
|
|
buffer->Shutdown();
|
|
|
|
|
delete buffer;
|
|
|
|
|
buffer = nullptr;
|
|
|
|
|
capacityBytes = 0u;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return ensureVertexBuffer(
|
2026-04-22 02:47:27 +08:00
|
|
|
primitiveInstanceBytes,
|
2026-04-21 20:49:18 +08:00
|
|
|
kMinDynamicInstanceBufferBytes,
|
2026-04-22 02:47:27 +08:00
|
|
|
static_cast<std::uint32_t>(sizeof(UiPrimitiveInstance)),
|
|
|
|
|
frameResources.primitiveInstanceBuffer,
|
|
|
|
|
frameResources.primitiveInstanceBufferView,
|
|
|
|
|
frameResources.primitiveInstanceCapacityBytes);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::BuildDrawBatches(
|
|
|
|
|
const UIDrawData& drawData,
|
|
|
|
|
const RenderSurface& surface,
|
2026-04-22 02:47:27 +08:00
|
|
|
std::vector<UiPrimitiveInstance>& outPrimitiveInstances,
|
2026-04-21 20:49:18 +08:00
|
|
|
std::vector<UiBatch>& outBatches) {
|
2026-04-22 02:47:27 +08:00
|
|
|
outPrimitiveInstances.clear();
|
2026-04-21 20:49:18 +08:00
|
|
|
outBatches.clear();
|
|
|
|
|
|
|
|
|
|
if (!EnsureWhiteTexture()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto renderArea = surface.GetRenderArea();
|
|
|
|
|
const Rect fullScissor = {
|
|
|
|
|
renderArea.x,
|
|
|
|
|
renderArea.y,
|
|
|
|
|
renderArea.x + renderArea.width,
|
|
|
|
|
renderArea.y + renderArea.height
|
|
|
|
|
};
|
|
|
|
|
const FloatRect fullRect = {
|
|
|
|
|
static_cast<float>(fullScissor.left),
|
|
|
|
|
static_cast<float>(fullScissor.top),
|
|
|
|
|
static_cast<float>(fullScissor.right),
|
|
|
|
|
static_cast<float>(fullScissor.bottom)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const float dpiScale = ClampDpiScale(m_dpiScale);
|
2026-04-21 21:07:06 +08:00
|
|
|
const std::uint64_t currentFrameId = ++m_compiledDrawListFrameCounter;
|
2026-04-21 20:49:18 +08:00
|
|
|
const D3D12_GPU_DESCRIPTOR_HANDLE whiteTextureHandle = {
|
|
|
|
|
static_cast<UINT64>(m_whiteTexture.nativeHandle)
|
|
|
|
|
};
|
2026-04-22 02:47:27 +08:00
|
|
|
const float zeroAux[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
auto buildCompiledDrawList =
|
|
|
|
|
[&](const UIDrawList& drawList,
|
2026-04-22 02:47:27 +08:00
|
|
|
std::vector<UiPrimitiveInstance>& compiledInstances,
|
2026-04-21 20:49:18 +08:00
|
|
|
std::vector<UiBatch>& compiledBatches) {
|
2026-04-22 02:47:27 +08:00
|
|
|
compiledInstances.clear();
|
2026-04-21 20:49:18 +08:00
|
|
|
compiledBatches.clear();
|
|
|
|
|
|
|
|
|
|
std::vector<ClipState> localClipStack = {};
|
|
|
|
|
localClipStack.push_back({ fullScissor, fullRect });
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
auto appendAxisAlignedPrimitive =
|
2026-04-21 20:49:18 +08:00
|
|
|
[&](const FloatRect& rect,
|
|
|
|
|
const UIPoint& uvMin,
|
|
|
|
|
const UIPoint& uvMax,
|
|
|
|
|
const UIColor& color,
|
|
|
|
|
const UIColor& secondaryColor,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind primitiveKind,
|
2026-04-21 20:49:18 +08:00
|
|
|
float radius,
|
|
|
|
|
float thickness,
|
|
|
|
|
float gradientDirection,
|
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle,
|
|
|
|
|
const Rect& scissor) {
|
|
|
|
|
if (IsEmptyRect(rect) || textureHandle.ptr == 0u || IsEmptyScissor(scissor)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
AppendPrimitiveInstance(
|
|
|
|
|
compiledInstances,
|
2026-04-21 20:49:18 +08:00
|
|
|
compiledBatches,
|
|
|
|
|
FloatPoint{ rect.left, rect.top },
|
|
|
|
|
FloatPoint{ rect.right - rect.left, 0.0f },
|
|
|
|
|
FloatPoint{ 0.0f, rect.bottom - rect.top },
|
|
|
|
|
uvMin,
|
|
|
|
|
uvMax,
|
|
|
|
|
color,
|
|
|
|
|
secondaryColor,
|
2026-04-22 02:47:27 +08:00
|
|
|
primitiveKind,
|
2026-04-21 20:49:18 +08:00
|
|
|
radius,
|
|
|
|
|
thickness,
|
|
|
|
|
gradientDirection,
|
2026-04-22 02:47:27 +08:00
|
|
|
zeroAux,
|
|
|
|
|
zeroAux,
|
2026-04-21 20:49:18 +08:00
|
|
|
textureHandle,
|
|
|
|
|
scissor);
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
auto appendOrientedPrimitive =
|
2026-04-21 20:49:18 +08:00
|
|
|
[&](const FloatPoint& origin,
|
|
|
|
|
const FloatPoint& axisX,
|
|
|
|
|
const FloatPoint& axisY,
|
|
|
|
|
const UIColor& color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind primitiveKind,
|
2026-04-21 20:49:18 +08:00
|
|
|
float radius,
|
|
|
|
|
float thickness,
|
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle,
|
|
|
|
|
const Rect& scissor) {
|
|
|
|
|
if (textureHandle.ptr == 0u || IsEmptyScissor(scissor)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
AppendPrimitiveInstance(
|
|
|
|
|
compiledInstances,
|
2026-04-21 20:49:18 +08:00
|
|
|
compiledBatches,
|
|
|
|
|
origin,
|
|
|
|
|
axisX,
|
|
|
|
|
axisY,
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
color,
|
|
|
|
|
color,
|
2026-04-22 02:47:27 +08:00
|
|
|
primitiveKind,
|
2026-04-21 20:49:18 +08:00
|
|
|
radius,
|
|
|
|
|
thickness,
|
|
|
|
|
0.0f,
|
2026-04-22 02:47:27 +08:00
|
|
|
zeroAux,
|
|
|
|
|
zeroAux,
|
|
|
|
|
textureHandle,
|
|
|
|
|
scissor);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto appendTrianglePrimitive =
|
|
|
|
|
[&](const FloatPoint& p0,
|
|
|
|
|
const FloatPoint& p1,
|
|
|
|
|
const FloatPoint& p2,
|
|
|
|
|
const UIColor& color,
|
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE textureHandle,
|
|
|
|
|
const Rect& scissor) {
|
|
|
|
|
if (textureHandle.ptr == 0u || IsEmptyScissor(scissor)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float minX = (std::min)({ p0.x, p1.x, p2.x });
|
|
|
|
|
const float minY = (std::min)({ p0.y, p1.y, p2.y });
|
|
|
|
|
const float maxX = (std::max)({ p0.x, p1.x, p2.x });
|
|
|
|
|
const float maxY = (std::max)({ p0.y, p1.y, p2.y });
|
|
|
|
|
if (maxX <= minX || maxY <= minY) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FloatPoint origin = { minX, minY };
|
|
|
|
|
const FloatPoint axisX = { maxX - minX, 0.0f };
|
|
|
|
|
const FloatPoint axisY = { 0.0f, maxY - minY };
|
|
|
|
|
const float triangleAux0[4] = {
|
|
|
|
|
p0.x - origin.x,
|
|
|
|
|
p0.y - origin.y,
|
|
|
|
|
p1.x - origin.x,
|
|
|
|
|
p1.y - origin.y
|
|
|
|
|
};
|
|
|
|
|
const float triangleAux1[4] = {
|
|
|
|
|
p2.x - origin.x,
|
|
|
|
|
p2.y - origin.y,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
AppendPrimitiveInstance(
|
|
|
|
|
compiledInstances,
|
|
|
|
|
compiledBatches,
|
|
|
|
|
origin,
|
|
|
|
|
axisX,
|
|
|
|
|
axisY,
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
color,
|
|
|
|
|
color,
|
|
|
|
|
UiPrimitiveKind::TriangleFill,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
triangleAux0,
|
|
|
|
|
triangleAux1,
|
2026-04-21 20:49:18 +08:00
|
|
|
textureHandle,
|
|
|
|
|
scissor);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const UIDrawCommand& command : drawList.GetCommands()) {
|
|
|
|
|
if (localClipStack.empty()) {
|
|
|
|
|
localClipStack.push_back({ fullScissor, fullRect });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.type == UIDrawCommandType::PushClipRect) {
|
|
|
|
|
FloatRect clipRect = ToPixelRect(command.rect, dpiScale);
|
|
|
|
|
clipRect = command.intersectWithCurrentClip
|
|
|
|
|
? IntersectFloatRect(localClipStack.back().rect, clipRect)
|
|
|
|
|
: IntersectFloatRect(fullRect, clipRect);
|
|
|
|
|
ClipState clipState = {};
|
|
|
|
|
clipState.rect = clipRect;
|
|
|
|
|
clipState.scissor = ClampScissor(clipRect, fullScissor);
|
|
|
|
|
localClipStack.push_back(clipState);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (command.type == UIDrawCommandType::PopClipRect) {
|
|
|
|
|
if (localClipStack.size() > 1u) {
|
|
|
|
|
localClipStack.pop_back();
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ClipState currentClip = localClipStack.back();
|
|
|
|
|
if (IsEmptyScissor(currentClip.scissor)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (command.type) {
|
|
|
|
|
case UIDrawCommandType::FilledRect: {
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
ToPixelRect(command.rect, dpiScale),
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::RectFill,
|
2026-04-21 20:49:18 +08:00
|
|
|
command.rounding * dpiScale,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::FilledRectLinearGradient: {
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
ToPixelRect(command.rect, dpiScale),
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.secondaryColor,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::RectFill,
|
2026-04-21 20:49:18 +08:00
|
|
|
command.rounding * dpiScale,
|
|
|
|
|
0.0f,
|
|
|
|
|
command.gradientDirection == UILinearGradientDirection::Vertical
|
|
|
|
|
? 1.0f
|
|
|
|
|
: 0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::RectOutline: {
|
|
|
|
|
const FloatRect rect = ToPixelRect(command.rect, dpiScale);
|
|
|
|
|
const float thickness =
|
|
|
|
|
(command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
|
|
|
|
|
const float halfThickness = thickness * 0.5f;
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
FloatRect{
|
|
|
|
|
rect.left - halfThickness,
|
|
|
|
|
rect.top - halfThickness,
|
|
|
|
|
rect.right + halfThickness,
|
|
|
|
|
rect.bottom + halfThickness
|
|
|
|
|
},
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::RectOutline,
|
2026-04-21 20:49:18 +08:00
|
|
|
command.rounding * dpiScale + halfThickness,
|
|
|
|
|
thickness,
|
|
|
|
|
0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::Line: {
|
|
|
|
|
const FloatPoint start = ToPixelPoint(command.position, dpiScale);
|
|
|
|
|
const FloatPoint end = ToPixelPoint(command.uvMin, dpiScale);
|
|
|
|
|
const float dx = end.x - start.x;
|
|
|
|
|
const float dy = end.y - start.y;
|
|
|
|
|
const float length = std::sqrt(dx * dx + dy * dy);
|
|
|
|
|
if (length <= 0.0f) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float thickness =
|
|
|
|
|
(command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
|
|
|
|
|
const float halfThickness = thickness * 0.5f;
|
|
|
|
|
const float nx = -dy / length;
|
|
|
|
|
const float ny = dx / length;
|
2026-04-22 02:47:27 +08:00
|
|
|
appendOrientedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
FloatPoint{ start.x + nx * halfThickness, start.y + ny * halfThickness },
|
|
|
|
|
FloatPoint{ dx, dy },
|
|
|
|
|
FloatPoint{ -nx * thickness, -ny * thickness },
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::RectFill,
|
2026-04-21 20:49:18 +08:00
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::FilledTriangle: {
|
2026-04-22 02:47:27 +08:00
|
|
|
appendTrianglePrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
ToPixelPoint(command.position, dpiScale),
|
|
|
|
|
ToPixelPoint(command.uvMin, dpiScale),
|
|
|
|
|
ToPixelPoint(command.uvMax, dpiScale),
|
2026-04-22 02:47:27 +08:00
|
|
|
command.color,
|
2026-04-21 20:49:18 +08:00
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::FilledCircle: {
|
|
|
|
|
const FloatPoint center = ToPixelPoint(command.position, dpiScale);
|
|
|
|
|
const float radius = command.radius * dpiScale;
|
|
|
|
|
if (radius <= 0.0f) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
FloatRect{
|
|
|
|
|
center.x - radius,
|
|
|
|
|
center.y - radius,
|
|
|
|
|
center.x + radius,
|
|
|
|
|
center.y + radius
|
|
|
|
|
},
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::CircleFill,
|
2026-04-21 20:49:18 +08:00
|
|
|
radius,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::CircleOutline: {
|
|
|
|
|
const FloatPoint center = ToPixelPoint(command.position, dpiScale);
|
|
|
|
|
const float thickness =
|
|
|
|
|
(command.thickness > 0.0f ? command.thickness : 1.0f) * dpiScale;
|
|
|
|
|
const float radius = command.radius * dpiScale;
|
|
|
|
|
const float outerRadius = radius + thickness * 0.5f;
|
|
|
|
|
if (outerRadius <= 0.0f) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
FloatRect{
|
|
|
|
|
center.x - outerRadius,
|
|
|
|
|
center.y - outerRadius,
|
|
|
|
|
center.x + outerRadius,
|
|
|
|
|
center.y + outerRadius
|
|
|
|
|
},
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::CircleOutline,
|
2026-04-21 20:49:18 +08:00
|
|
|
radius,
|
|
|
|
|
thickness,
|
|
|
|
|
0.0f,
|
|
|
|
|
whiteTextureHandle,
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::Text: {
|
|
|
|
|
if (command.text.empty()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CachedTextRun* textRun =
|
2026-04-21 21:07:06 +08:00
|
|
|
ResolveTextRun(command.text, command.fontSize, currentFrameId);
|
|
|
|
|
if (textRun == nullptr ||
|
|
|
|
|
!textRun->hasPixels ||
|
|
|
|
|
textRun->textureHandle.ptr == 0u ||
|
|
|
|
|
textRun->width <= 0.0f ||
|
|
|
|
|
textRun->height <= 0.0f) {
|
2026-04-21 20:49:18 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const FloatPoint position = ToPixelPoint(command.position, dpiScale);
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 21:07:06 +08:00
|
|
|
FloatRect{
|
|
|
|
|
position.x + textRun->offsetX,
|
|
|
|
|
position.y + textRun->offsetY,
|
|
|
|
|
position.x + textRun->offsetX + textRun->width,
|
|
|
|
|
position.y + textRun->offsetY + textRun->height
|
|
|
|
|
},
|
|
|
|
|
UIPoint(0.0f, 0.0f),
|
|
|
|
|
UIPoint(1.0f, 1.0f),
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::Textured,
|
2026-04-21 21:07:06 +08:00
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
textRun->textureHandle,
|
|
|
|
|
currentClip.scissor);
|
2026-04-21 20:49:18 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case UIDrawCommandType::Image: {
|
|
|
|
|
if (!command.texture.IsValid()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
appendAxisAlignedPrimitive(
|
2026-04-21 20:49:18 +08:00
|
|
|
ToPixelRect(command.rect, dpiScale),
|
|
|
|
|
command.uvMin,
|
|
|
|
|
command.uvMax,
|
|
|
|
|
command.color,
|
|
|
|
|
command.color,
|
2026-04-22 02:47:27 +08:00
|
|
|
UiPrimitiveKind::Textured,
|
2026-04-21 20:49:18 +08:00
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
0.0f,
|
|
|
|
|
D3D12_GPU_DESCRIPTOR_HANDLE{
|
|
|
|
|
static_cast<UINT64>(command.texture.nativeHandle)
|
|
|
|
|
},
|
|
|
|
|
currentClip.scissor);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto appendCompiledDrawList =
|
|
|
|
|
[&](const CompiledDrawList& compiled) {
|
|
|
|
|
if (compiled.batches.empty()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t baseInstance =
|
2026-04-22 02:47:27 +08:00
|
|
|
static_cast<std::uint32_t>(outPrimitiveInstances.size());
|
2026-04-21 20:49:18 +08:00
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
if (!compiled.instances.empty()) {
|
|
|
|
|
outPrimitiveInstances.insert(
|
|
|
|
|
outPrimitiveInstances.end(),
|
|
|
|
|
compiled.instances.begin(),
|
|
|
|
|
compiled.instances.end());
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const UiBatch& batch : compiled.batches) {
|
2026-04-22 02:47:27 +08:00
|
|
|
AppendBatch(
|
|
|
|
|
outBatches,
|
|
|
|
|
baseInstance + batch.firstInstance,
|
|
|
|
|
batch.instanceCount,
|
|
|
|
|
batch.textureHandle,
|
|
|
|
|
batch.scissorRect);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const std::uint32_t renderWidth = renderArea.width > 0 ? renderArea.width : 0u;
|
|
|
|
|
const std::uint32_t renderHeight = renderArea.height > 0 ? renderArea.height : 0u;
|
|
|
|
|
const int dpiScaleMilli =
|
|
|
|
|
static_cast<int>(std::lround(dpiScale * 1000.0f));
|
|
|
|
|
|
|
|
|
|
for (const UIDrawList& drawList : drawData.GetDrawLists()) {
|
|
|
|
|
if (drawList.Empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CompiledDrawListKey key = {};
|
|
|
|
|
key.contentHash = BuildDrawListContentHash(drawList);
|
|
|
|
|
key.renderWidth = renderWidth;
|
|
|
|
|
key.renderHeight = renderHeight;
|
|
|
|
|
key.dpiScaleMilli = dpiScaleMilli;
|
|
|
|
|
|
|
|
|
|
auto found = m_compiledDrawListCache.find(key);
|
|
|
|
|
if (found == m_compiledDrawListCache.end()) {
|
|
|
|
|
CompiledDrawList compiled = {};
|
|
|
|
|
if (!buildCompiledDrawList(
|
|
|
|
|
drawList,
|
2026-04-22 02:47:27 +08:00
|
|
|
compiled.instances,
|
2026-04-21 20:49:18 +08:00
|
|
|
compiled.batches)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compiled.lastUsedFrame = currentFrameId;
|
|
|
|
|
const auto [insertedIt, inserted] =
|
|
|
|
|
m_compiledDrawListCache.emplace(key, std::move(compiled));
|
|
|
|
|
(void)inserted;
|
|
|
|
|
found = insertedIt;
|
|
|
|
|
} else {
|
|
|
|
|
found->second.lastUsedFrame = currentFrameId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
appendCompiledDrawList(found->second);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr std::size_t kCompiledDrawListCacheMaxEntries = 256u;
|
|
|
|
|
constexpr std::uint64_t kCompiledDrawListCacheRetentionFrames = 240u;
|
|
|
|
|
if (m_compiledDrawListCache.size() > kCompiledDrawListCacheMaxEntries) {
|
|
|
|
|
const std::uint64_t pruneBeforeFrame =
|
|
|
|
|
currentFrameId > kCompiledDrawListCacheRetentionFrames
|
|
|
|
|
? currentFrameId - kCompiledDrawListCacheRetentionFrames
|
|
|
|
|
: 0u;
|
|
|
|
|
for (auto it = m_compiledDrawListCache.begin();
|
|
|
|
|
it != m_compiledDrawListCache.end();) {
|
|
|
|
|
if (it->second.lastUsedFrame <= pruneBeforeFrame) {
|
|
|
|
|
it = m_compiledDrawListCache.erase(it);
|
|
|
|
|
} else {
|
|
|
|
|
++it;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:07:06 +08:00
|
|
|
PruneTextRunCache(currentFrameId);
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool D3D12UiRenderer::Render(
|
|
|
|
|
const UIDrawData& drawData,
|
|
|
|
|
const RenderContext& renderContext,
|
|
|
|
|
const RenderSurface& surface) {
|
|
|
|
|
if (m_windowRenderer == nullptr ||
|
|
|
|
|
m_textureHost == nullptr ||
|
|
|
|
|
m_textSystem == nullptr) {
|
|
|
|
|
m_lastError = "Render requires an initialized D3D12 UI renderer.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!renderContext.IsValid() || renderContext.commandList == nullptr) {
|
|
|
|
|
m_lastError = "Render requires a valid D3D12 render context.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!::XCEngine::Rendering::HasSingleColorAttachment(surface) ||
|
|
|
|
|
surface.GetColorAttachments().empty() ||
|
|
|
|
|
surface.GetColorAttachments()[0] == nullptr) {
|
|
|
|
|
m_lastError = "Render requires a valid single-color render surface.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EnsureInitialized(renderContext, surface)) {
|
|
|
|
|
if (m_lastError.empty()) {
|
|
|
|
|
m_lastError = "Failed to initialize the D3D12 UI renderer resources.";
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_textureHost->BeginFrame(m_windowRenderer->GetActiveFrameSlot());
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
std::vector<UiPrimitiveInstance> primitiveInstances = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
std::vector<UiBatch> batches = {};
|
2026-04-22 02:47:27 +08:00
|
|
|
if (!BuildDrawBatches(drawData, surface, primitiveInstances, batches)) {
|
2026-04-21 20:49:18 +08:00
|
|
|
if (m_lastError.empty()) {
|
|
|
|
|
m_lastError = "Failed to build the D3D12 UI draw batches.";
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (batches.empty()) {
|
|
|
|
|
m_lastError.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const std::uint32_t frameSlot = m_windowRenderer->GetActiveFrameSlot();
|
|
|
|
|
if (!EnsureFrameBufferCapacity(
|
|
|
|
|
frameSlot,
|
2026-04-22 02:47:27 +08:00
|
|
|
primitiveInstances.size() * sizeof(UiPrimitiveInstance))) {
|
2026-04-21 20:49:18 +08:00
|
|
|
m_lastError = "Failed to allocate the D3D12 UI dynamic buffers.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FrameResources& frameResources = m_frameResources[frameSlot];
|
2026-04-22 02:47:27 +08:00
|
|
|
if (!primitiveInstances.empty() && frameResources.primitiveInstanceBuffer != nullptr) {
|
|
|
|
|
frameResources.primitiveInstanceBuffer->SetData(
|
|
|
|
|
primitiveInstances.data(),
|
|
|
|
|
primitiveInstances.size() * sizeof(UiPrimitiveInstance));
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto renderArea = surface.GetRenderArea();
|
|
|
|
|
UiConstants constants = {};
|
|
|
|
|
constants.invViewportSize[0] =
|
|
|
|
|
renderArea.width > 0 ? 1.0f / static_cast<float>(renderArea.width) : 0.0f;
|
|
|
|
|
constants.invViewportSize[1] =
|
|
|
|
|
renderArea.height > 0 ? 1.0f / static_cast<float>(renderArea.height) : 0.0f;
|
|
|
|
|
m_constantSet->WriteConstant(0u, &constants, sizeof(constants));
|
|
|
|
|
|
|
|
|
|
auto* d3d12CommandList =
|
|
|
|
|
static_cast<D3D12CommandList*>(renderContext.commandList);
|
|
|
|
|
auto* renderTarget = surface.GetColorAttachments()[0];
|
|
|
|
|
RHICommandList* commandList = renderContext.commandList;
|
|
|
|
|
commandList->SetRenderTargets(1u, &renderTarget, nullptr);
|
|
|
|
|
commandList->SetViewport(Viewport{
|
|
|
|
|
static_cast<float>(renderArea.x),
|
|
|
|
|
static_cast<float>(renderArea.y),
|
|
|
|
|
static_cast<float>(renderArea.width),
|
|
|
|
|
static_cast<float>(renderArea.height),
|
|
|
|
|
0.0f,
|
|
|
|
|
1.0f
|
|
|
|
|
});
|
|
|
|
|
commandList->SetScissorRect(Rect{
|
|
|
|
|
renderArea.x,
|
|
|
|
|
renderArea.y,
|
|
|
|
|
renderArea.x + renderArea.width,
|
|
|
|
|
renderArea.y + renderArea.height
|
|
|
|
|
});
|
|
|
|
|
commandList->SetPrimitiveTopology(PrimitiveTopology::TriangleList);
|
|
|
|
|
|
|
|
|
|
::XCEngine::RHI::RHIDescriptorSet* constantSets[] = { m_constantSet };
|
|
|
|
|
commandList->SetGraphicsDescriptorSets(0u, 1u, constantSets, m_pipelineLayout);
|
|
|
|
|
|
|
|
|
|
auto* textureHeap = m_windowRenderer->GetTextureDescriptorAllocator().GetDescriptorHeap();
|
|
|
|
|
auto* samplerHeap = static_cast<D3D12DescriptorHeap*>(m_samplerPool)->GetDescriptorHeap();
|
|
|
|
|
ID3D12DescriptorHeap* descriptorHeaps[] = { textureHeap, samplerHeap };
|
|
|
|
|
d3d12CommandList->SetDescriptorHeaps(2u, descriptorHeaps);
|
|
|
|
|
|
|
|
|
|
const auto* pipelineLayout =
|
|
|
|
|
static_cast<const ::XCEngine::RHI::D3D12PipelineLayout*>(m_pipelineLayout);
|
|
|
|
|
const std::uint32_t textureRootIndex =
|
|
|
|
|
pipelineLayout->GetShaderResourceTableRootParameterIndex(1u);
|
|
|
|
|
const std::uint32_t samplerRootIndex =
|
|
|
|
|
pipelineLayout->GetSamplerTableRootParameterIndex(2u);
|
|
|
|
|
|
2026-04-22 02:47:27 +08:00
|
|
|
if (m_primitivePipelineState == nullptr ||
|
|
|
|
|
m_quadVertexBufferView == nullptr ||
|
|
|
|
|
m_quadIndexBufferView == nullptr ||
|
|
|
|
|
frameResources.primitiveInstanceBufferView == nullptr) {
|
|
|
|
|
m_lastError = "D3D12 UI primitive rendering resources are unavailable.";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commandList->SetPipelineState(m_primitivePipelineState);
|
|
|
|
|
const std::uint64_t primitiveVertexOffsets[] = { 0u, 0u };
|
|
|
|
|
const std::uint32_t primitiveVertexStrides[] = {
|
|
|
|
|
static_cast<std::uint32_t>(sizeof(UiQuadVertex)),
|
|
|
|
|
static_cast<std::uint32_t>(sizeof(UiPrimitiveInstance))
|
|
|
|
|
};
|
|
|
|
|
::XCEngine::RHI::RHIResourceView* primitiveVertexViews[] = {
|
|
|
|
|
m_quadVertexBufferView,
|
|
|
|
|
frameResources.primitiveInstanceBufferView
|
2026-04-21 20:49:18 +08:00
|
|
|
};
|
2026-04-22 02:47:27 +08:00
|
|
|
commandList->SetVertexBuffers(
|
|
|
|
|
0u,
|
|
|
|
|
2u,
|
|
|
|
|
primitiveVertexViews,
|
|
|
|
|
primitiveVertexOffsets,
|
|
|
|
|
primitiveVertexStrides);
|
|
|
|
|
commandList->SetIndexBuffer(m_quadIndexBufferView, 0u);
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
for (const UiBatch& batch : batches) {
|
|
|
|
|
commandList->SetScissorRect(batch.scissorRect);
|
|
|
|
|
d3d12CommandList->SetGraphicsRootDescriptorTable(
|
|
|
|
|
textureRootIndex,
|
|
|
|
|
batch.textureHandle);
|
|
|
|
|
d3d12CommandList->SetGraphicsRootDescriptorTable(
|
|
|
|
|
samplerRootIndex,
|
|
|
|
|
m_samplerGpuHandle);
|
2026-04-22 02:47:27 +08:00
|
|
|
commandList->DrawIndexed(
|
|
|
|
|
static_cast<std::uint32_t>(sizeof(kUnitQuadIndices) / sizeof(kUnitQuadIndices[0])),
|
|
|
|
|
batch.instanceCount,
|
|
|
|
|
0u,
|
|
|
|
|
0,
|
|
|
|
|
batch.firstInstance);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastError.clear();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace XCEngine::UI::Editor::Host
|