Files
XCEngine/new_editor/src/XCUIBackend/XCUIRHICommandCompiler.cpp

591 lines
21 KiB
C++

#include "XCUIBackend/XCUIRHICommandCompiler.h"
#include <algorithm>
#include <iterator>
namespace XCEngine {
namespace Editor {
namespace XCUIBackend {
namespace {
using Compiler = XCUIRHICommandCompiler;
UI::UIRect NormalizeRect(const UI::UIRect& rect) {
UI::UIRect normalized = rect;
if (normalized.width < 0.0f) {
normalized.x += normalized.width;
normalized.width = -normalized.width;
}
if (normalized.height < 0.0f) {
normalized.y += normalized.height;
normalized.height = -normalized.height;
}
return normalized;
}
bool IsRectEmpty(const UI::UIRect& rect) {
return rect.width <= 0.0f || rect.height <= 0.0f;
}
bool IntersectRects(
const UI::UIRect& lhs,
const UI::UIRect& rhs,
UI::UIRect& outIntersection) {
const UI::UIRect left = NormalizeRect(lhs);
const UI::UIRect right = NormalizeRect(rhs);
const float minX = (std::max)(left.x, right.x);
const float minY = (std::max)(left.y, right.y);
const float maxX = (std::min)(left.x + left.width, right.x + right.width);
const float maxY = (std::min)(left.y + left.height, right.y + right.height);
if (maxX <= minX || maxY <= minY) {
outIntersection = {};
return false;
}
outIntersection = UI::UIRect(minX, minY, maxX - minX, maxY - minY);
return true;
}
bool RectEquals(const UI::UIRect& lhs, const UI::UIRect& rhs) {
return lhs.x == rhs.x &&
lhs.y == rhs.y &&
lhs.width == rhs.width &&
lhs.height == rhs.height;
}
bool TextureEquals(const UI::UITextureHandle& lhs, const UI::UITextureHandle& rhs) {
return lhs.nativeHandle == rhs.nativeHandle &&
lhs.width == rhs.width &&
lhs.height == rhs.height &&
lhs.kind == rhs.kind;
}
void AppendFilledRectVertices(
std::vector<Compiler::ColorVertex>& outVertices,
const UI::UIRect& rect,
const UI::UIColor& color) {
const Compiler::ColorVertex vertices[6] = {
{ { rect.x, rect.y }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y + rect.height }, { color.r, color.g, color.b, color.a } },
{ { rect.x, rect.y }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y + rect.height }, { color.r, color.g, color.b, color.a } },
{ { rect.x, rect.y + rect.height }, { color.r, color.g, color.b, color.a } }
};
outVertices.insert(outVertices.end(), std::begin(vertices), std::end(vertices));
}
bool AppendClippedFilledRect(
std::vector<Compiler::ColorVertex>& outVertices,
const UI::UIRect& rect,
const UI::UIColor& color,
const UI::UIRect& clipRect) {
UI::UIRect clipped = {};
if (!IntersectRects(rect, clipRect, clipped)) {
return false;
}
AppendFilledRectVertices(outVertices, clipped, color);
return true;
}
bool AppendClippedRectOutline(
std::vector<Compiler::ColorVertex>& outVertices,
const UI::UIRect& rect,
const UI::UIColor& color,
float thickness,
const UI::UIRect& clipRect) {
const UI::UIRect normalized = NormalizeRect(rect);
if (IsRectEmpty(normalized)) {
return false;
}
const float resolvedThickness = (std::max)(1.0f, thickness);
const float halfWidth = normalized.width * 0.5f;
const float halfHeight = normalized.height * 0.5f;
const float edgeThickness = (std::min)(resolvedThickness, (std::min)(halfWidth, halfHeight));
if (edgeThickness <= 0.0f) {
return false;
}
bool rendered = false;
rendered |= AppendClippedFilledRect(
outVertices,
UI::UIRect(normalized.x, normalized.y, normalized.width, edgeThickness),
color,
clipRect);
rendered |= AppendClippedFilledRect(
outVertices,
UI::UIRect(
normalized.x,
normalized.y + normalized.height - edgeThickness,
normalized.width,
edgeThickness),
color,
clipRect);
rendered |= AppendClippedFilledRect(
outVertices,
UI::UIRect(
normalized.x,
normalized.y + edgeThickness,
edgeThickness,
normalized.height - edgeThickness * 2.0f),
color,
clipRect);
rendered |= AppendClippedFilledRect(
outVertices,
UI::UIRect(
normalized.x + normalized.width - edgeThickness,
normalized.y + edgeThickness,
edgeThickness,
normalized.height - edgeThickness * 2.0f),
color,
clipRect);
return rendered;
}
void AppendTexturedRectVertices(
std::vector<Compiler::TexturedVertex>& outVertices,
const UI::UIRect& rect,
float u0,
float v0,
float u1,
float v1,
const UI::UIColor& color) {
const Compiler::TexturedVertex vertices[6] = {
{ { rect.x, rect.y }, { u0, v0 }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y }, { u1, v0 }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y + rect.height }, { u1, v1 }, { color.r, color.g, color.b, color.a } },
{ { rect.x, rect.y }, { u0, v0 }, { color.r, color.g, color.b, color.a } },
{ { rect.x + rect.width, rect.y + rect.height }, { u1, v1 }, { color.r, color.g, color.b, color.a } },
{ { rect.x, rect.y + rect.height }, { u0, v1 }, { color.r, color.g, color.b, color.a } }
};
outVertices.insert(outVertices.end(), std::begin(vertices), std::end(vertices));
}
bool AppendClippedTexturedRect(
std::vector<Compiler::TexturedVertex>& outVertices,
const UI::UIRect& rect,
float u0,
float v0,
float u1,
float v1,
const UI::UIColor& color,
const UI::UIRect& clipRect) {
const bool flipU = rect.width < 0.0f;
const bool flipV = rect.height < 0.0f;
const UI::UIRect normalized = NormalizeRect(rect);
if (IsRectEmpty(normalized)) {
return false;
}
UI::UIRect clipped = {};
if (!IntersectRects(normalized, clipRect, clipped)) {
return false;
}
const float leftT = (clipped.x - normalized.x) / normalized.width;
const float rightT = (clipped.x + clipped.width - normalized.x) / normalized.width;
const float topT = (clipped.y - normalized.y) / normalized.height;
const float bottomT = (clipped.y + clipped.height - normalized.y) / normalized.height;
const float resolvedU0 = flipU ? u1 : u0;
const float resolvedU1 = flipU ? u0 : u1;
const float resolvedV0 = flipV ? v1 : v0;
const float resolvedV1 = flipV ? v0 : v1;
const float clippedU0 = resolvedU0 + (resolvedU1 - resolvedU0) * leftT;
const float clippedU1 = resolvedU0 + (resolvedU1 - resolvedU0) * rightT;
const float clippedV0 = resolvedV0 + (resolvedV1 - resolvedV0) * topT;
const float clippedV1 = resolvedV0 + (resolvedV1 - resolvedV0) * bottomT;
AppendTexturedRectVertices(
outVertices,
clipped,
clippedU0,
clippedV0,
clippedU1,
clippedV1,
color);
return true;
}
bool DecodeNextUtf8(const char*& cursor, const char* end, std::uint32_t& outCodepoint) {
if (cursor >= end) {
return false;
}
const unsigned char lead = static_cast<unsigned char>(*cursor++);
if (lead < 0x80u) {
outCodepoint = static_cast<std::uint32_t>(lead);
return true;
}
const int continuationCount =
(lead & 0xE0u) == 0xC0u ? 1 :
(lead & 0xF0u) == 0xE0u ? 2 :
(lead & 0xF8u) == 0xF0u ? 3 :
-1;
if (continuationCount < 0 || cursor + continuationCount > end) {
outCodepoint = 0xFFFDu;
cursor = end;
return true;
}
std::uint32_t codepoint =
continuationCount == 1 ? static_cast<std::uint32_t>(lead & 0x1Fu) :
continuationCount == 2 ? static_cast<std::uint32_t>(lead & 0x0Fu) :
static_cast<std::uint32_t>(lead & 0x07u);
for (int index = 0; index < continuationCount; ++index) {
const unsigned char continuation = static_cast<unsigned char>(*cursor);
if ((continuation & 0xC0u) != 0x80u) {
outCodepoint = 0xFFFDu;
return true;
}
++cursor;
codepoint = (codepoint << 6u) | static_cast<std::uint32_t>(continuation & 0x3Fu);
}
outCodepoint = codepoint;
return true;
}
void AppendOrMergeBatch(
std::vector<Compiler::Batch>& outBatches,
Compiler::BatchKind kind,
std::size_t drawListIndex,
std::size_t commandIndex,
std::size_t firstVertex,
std::size_t vertexCount,
const UI::UIRect& clipRect,
const UI::UITextureHandle& texture,
bool allowMerge) {
if (vertexCount == 0u) {
return;
}
if (allowMerge && !outBatches.empty()) {
Compiler::Batch& lastBatch = outBatches.back();
if (lastBatch.kind == kind &&
lastBatch.drawListIndex == drawListIndex &&
lastBatch.firstCommandIndex + lastBatch.commandCount == commandIndex &&
RectEquals(lastBatch.clipRect, clipRect) &&
TextureEquals(lastBatch.texture, texture) &&
static_cast<std::size_t>(lastBatch.firstVertex + lastBatch.vertexCount) == firstVertex) {
lastBatch.vertexCount += static_cast<std::uint32_t>(vertexCount);
lastBatch.commandCount += 1u;
return;
}
}
Compiler::Batch batch = {};
batch.kind = kind;
batch.drawListIndex = static_cast<std::uint32_t>(drawListIndex);
batch.firstCommandIndex = static_cast<std::uint32_t>(commandIndex);
batch.commandCount = 1u;
batch.firstVertex = static_cast<std::uint32_t>(firstVertex);
batch.vertexCount = static_cast<std::uint32_t>(vertexCount);
batch.clipRect = clipRect;
batch.texture = texture;
outBatches.push_back(batch);
}
bool CompileTextCommand(
const UI::UIDrawCommand& command,
const UI::UIRect& clipRect,
const Compiler::TextGlyphProvider& glyphProvider,
std::vector<Compiler::TexturedVertex>& outVertices,
Compiler::TextRunContext& outContext,
std::size_t& outAddedVertexCount) {
outContext = {};
if (!glyphProvider.BeginText(command.fontSize, outContext)) {
outAddedVertexCount = 0u;
return false;
}
if (!outContext.texture.IsValid() || outContext.lineHeight <= 0.0f) {
outAddedVertexCount = 0u;
return false;
}
const std::size_t startVertex = outVertices.size();
const float startX = command.position.x;
float cursorX = startX;
float cursorY = command.position.y;
const char* cursor = command.text.c_str();
const char* const end = cursor + command.text.size();
while (cursor < end) {
std::uint32_t codepoint = 0u;
if (!DecodeNextUtf8(cursor, end, codepoint)) {
break;
}
if (codepoint == '\r') {
continue;
}
if (codepoint == '\n') {
cursorX = startX;
cursorY += outContext.lineHeight;
continue;
}
Compiler::TextGlyph glyph = {};
if (!glyphProvider.ResolveGlyph(outContext, codepoint, glyph)) {
continue;
}
if (glyph.visible) {
const UI::UIRect glyphRect(
cursorX + glyph.x0,
cursorY + glyph.y0,
glyph.x1 - glyph.x0,
glyph.y1 - glyph.y0);
AppendClippedTexturedRect(
outVertices,
glyphRect,
glyph.u0,
glyph.v0,
glyph.u1,
glyph.v1,
command.color,
clipRect);
}
cursorX += glyph.advanceX;
}
outAddedVertexCount = outVertices.size() - startVertex;
return true;
}
} // namespace
void XCUIRHICommandCompiler::CompiledDrawData::Clear() {
colorVertices.clear();
texturedVertices.clear();
batches.clear();
stats = {};
}
bool XCUIRHICommandCompiler::CompiledDrawData::Empty() const {
return colorVertices.empty() && texturedVertices.empty() && batches.empty();
}
void XCUIRHICommandCompiler::Compile(
const UI::UIDrawData& drawData,
const CompileConfig& config,
CompiledDrawData& outCompiledDrawData) const {
outCompiledDrawData.Clear();
outCompiledDrawData.stats.drawListCount = drawData.GetDrawListCount();
outCompiledDrawData.stats.commandCount = drawData.GetTotalCommandCount();
if (drawData.Empty()) {
return;
}
UI::UIRect surfaceClipRect = NormalizeRect(config.surfaceClipRect);
if (IsRectEmpty(surfaceClipRect)) {
surfaceClipRect = {};
}
outCompiledDrawData.colorVertices.reserve(drawData.GetTotalCommandCount() * 24u);
outCompiledDrawData.texturedVertices.reserve(drawData.GetTotalCommandCount() * 36u);
outCompiledDrawData.batches.reserve(drawData.GetTotalCommandCount());
std::vector<UI::UIRect> clipStack = {};
UI::UIRect currentClipRect = surfaceClipRect;
std::size_t drawListIndex = 0u;
for (const UI::UIDrawList& drawList : drawData.GetDrawLists()) {
std::size_t commandIndex = 0u;
for (const UI::UIDrawCommand& command : drawList.GetCommands()) {
bool compiled = false;
bool culled = false;
bool unsupported = false;
switch (command.type) {
case UI::UIDrawCommandType::FilledRect: {
++outCompiledDrawData.stats.filledRectCommandCount;
const std::size_t firstVertex = outCompiledDrawData.colorVertices.size();
compiled = AppendClippedFilledRect(
outCompiledDrawData.colorVertices,
command.rect,
command.color,
currentClipRect);
if (compiled) {
AppendOrMergeBatch(
outCompiledDrawData.batches,
BatchKind::Color,
drawListIndex,
commandIndex,
firstVertex,
outCompiledDrawData.colorVertices.size() - firstVertex,
currentClipRect,
{},
config.mergeBatchesWithinDrawList);
} else {
culled = true;
}
break;
}
case UI::UIDrawCommandType::RectOutline: {
++outCompiledDrawData.stats.rectOutlineCommandCount;
const std::size_t firstVertex = outCompiledDrawData.colorVertices.size();
compiled = AppendClippedRectOutline(
outCompiledDrawData.colorVertices,
command.rect,
command.color,
command.thickness,
currentClipRect);
if (compiled) {
AppendOrMergeBatch(
outCompiledDrawData.batches,
BatchKind::Color,
drawListIndex,
commandIndex,
firstVertex,
outCompiledDrawData.colorVertices.size() - firstVertex,
currentClipRect,
{},
config.mergeBatchesWithinDrawList);
} else {
culled = true;
}
break;
}
case UI::UIDrawCommandType::Text: {
++outCompiledDrawData.stats.textCommandCount;
if (command.text.empty()) {
culled = true;
break;
}
if (config.textGlyphProvider == nullptr) {
unsupported = true;
break;
}
const std::size_t firstVertex = outCompiledDrawData.texturedVertices.size();
std::size_t addedVertexCount = 0u;
TextRunContext textRunContext = {};
compiled = CompileTextCommand(
command,
currentClipRect,
*config.textGlyphProvider,
outCompiledDrawData.texturedVertices,
textRunContext,
addedVertexCount);
if (compiled && addedVertexCount > 0u) {
AppendOrMergeBatch(
outCompiledDrawData.batches,
BatchKind::Textured,
drawListIndex,
commandIndex,
firstVertex,
addedVertexCount,
currentClipRect,
textRunContext.texture,
config.mergeBatchesWithinDrawList);
} else if (!compiled) {
unsupported = true;
} else {
compiled = false;
culled = true;
}
break;
}
case UI::UIDrawCommandType::Image: {
++outCompiledDrawData.stats.imageCommandCount;
if (!command.texture.IsValid()) {
unsupported = true;
break;
}
const std::size_t firstVertex = outCompiledDrawData.texturedVertices.size();
compiled = AppendClippedTexturedRect(
outCompiledDrawData.texturedVertices,
command.rect,
command.uvMin.x,
command.uvMin.y,
command.uvMax.x,
command.uvMax.y,
command.color,
currentClipRect);
if (compiled) {
AppendOrMergeBatch(
outCompiledDrawData.batches,
BatchKind::Textured,
drawListIndex,
commandIndex,
firstVertex,
outCompiledDrawData.texturedVertices.size() - firstVertex,
currentClipRect,
command.texture,
config.mergeBatchesWithinDrawList);
} else {
culled = true;
}
break;
}
case UI::UIDrawCommandType::PushClipRect: {
++outCompiledDrawData.stats.clipPushCommandCount;
UI::UIRect nextClipRect = NormalizeRect(command.rect);
UI::UIRect intersection = {};
if (command.intersectWithCurrentClip) {
if (!IntersectRects(currentClipRect, nextClipRect, intersection)) {
intersection = {};
}
} else if (!IntersectRects(surfaceClipRect, nextClipRect, intersection)) {
intersection = {};
}
clipStack.push_back(currentClipRect);
currentClipRect = intersection;
compiled = true;
outCompiledDrawData.stats.maxClipDepth =
(std::max)(outCompiledDrawData.stats.maxClipDepth, clipStack.size());
break;
}
case UI::UIDrawCommandType::PopClipRect:
++outCompiledDrawData.stats.clipPopCommandCount;
if (!clipStack.empty()) {
currentClipRect = clipStack.back();
clipStack.pop_back();
} else {
currentClipRect = surfaceClipRect;
++outCompiledDrawData.stats.clipStackUnderflowCount;
}
compiled = true;
break;
default:
unsupported = true;
break;
}
if (compiled) {
++outCompiledDrawData.stats.compiledCommandCount;
} else {
++outCompiledDrawData.stats.skippedCommandCount;
if (culled) {
++outCompiledDrawData.stats.culledCommandCount;
}
if (unsupported) {
++outCompiledDrawData.stats.unsupportedCommandCount;
}
}
++commandIndex;
}
++drawListIndex;
}
outCompiledDrawData.stats.colorVertexCount = outCompiledDrawData.colorVertices.size();
outCompiledDrawData.stats.texturedVertexCount = outCompiledDrawData.texturedVertices.size();
outCompiledDrawData.stats.triangleCount =
(outCompiledDrawData.stats.colorVertexCount +
outCompiledDrawData.stats.texturedVertexCount) /
3u;
outCompiledDrawData.stats.batchCount = outCompiledDrawData.batches.size();
outCompiledDrawData.stats.danglingClipDepth = clipStack.size();
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine