MetaCore/Source/MetaCoreRender/Private/MetaCoreRuntimeUiRenderer.cpp

789 lines
27 KiB
C++

#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
#include <RmlUi/Core.h>
#include <algorithm>
#include <cmath>
#include <chrono>
#include <cstring>
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
namespace MetaCore {
namespace {
[[nodiscard]] float MetaCoreRuntimeUiEdge(
const MetaCoreRuntimeUiDrawVertex& a,
const MetaCoreRuntimeUiDrawVertex& b,
float x,
float y
) {
return (x - a.X) * (b.Y - a.Y) - (y - a.Y) * (b.X - a.X);
}
[[nodiscard]] std::uint8_t MetaCoreRuntimeUiByte(float value) {
return static_cast<std::uint8_t>(std::clamp(value, 0.0F, 255.0F));
}
[[nodiscard]] std::string MetaCoreEscapeRuntimeUiRmlText(std::string_view value) {
std::string escaped;
escaped.reserve(value.size());
for (char c : value) {
switch (c) {
case '&': escaped += "&amp;"; break;
case '<': escaped += "&lt;"; break;
case '>': escaped += "&gt;"; break;
case '"': escaped += "&quot;"; break;
case '\'': escaped += "&apos;"; break;
default: escaped.push_back(c); break;
}
}
return escaped;
}
void MetaCoreRuntimeUiBlendPixel(
MetaCoreRuntimeUiRasterFrame& rasterFrame,
std::int32_t x,
std::int32_t y,
float red,
float green,
float blue,
float alpha
) {
if (x < 0 || y < 0 || x >= rasterFrame.Width || y >= rasterFrame.Height) {
return;
}
const std::size_t pixelOffset =
(static_cast<std::size_t>(y) * static_cast<std::size_t>(rasterFrame.Width) + static_cast<std::size_t>(x)) * 4U;
if (pixelOffset + 3U >= rasterFrame.Rgba.size()) {
return;
}
const float sourceAlpha = std::clamp(alpha / 255.0F, 0.0F, 1.0F);
const float inverseAlpha = 1.0F - sourceAlpha;
rasterFrame.Rgba[pixelOffset + 0U] = MetaCoreRuntimeUiByte(red + static_cast<float>(rasterFrame.Rgba[pixelOffset + 0U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 1U] = MetaCoreRuntimeUiByte(green + static_cast<float>(rasterFrame.Rgba[pixelOffset + 1U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 2U] = MetaCoreRuntimeUiByte(blue + static_cast<float>(rasterFrame.Rgba[pixelOffset + 2U]) * inverseAlpha);
rasterFrame.Rgba[pixelOffset + 3U] = MetaCoreRuntimeUiByte(alpha + static_cast<float>(rasterFrame.Rgba[pixelOffset + 3U]) * inverseAlpha);
}
void MetaCoreRuntimeUiRasterizeTriangle(
MetaCoreRuntimeUiRasterFrame& rasterFrame,
const MetaCoreRuntimeUiDrawCommand& command,
const MetaCoreRuntimeUiDrawVertex& v0,
const MetaCoreRuntimeUiDrawVertex& v1,
const MetaCoreRuntimeUiDrawVertex& v2
) {
const float area = MetaCoreRuntimeUiEdge(v0, v1, v2.X, v2.Y);
if (std::abs(area) <= 0.0001F) {
return;
}
std::int32_t minX = static_cast<std::int32_t>(std::floor(std::min({v0.X, v1.X, v2.X})));
std::int32_t minY = static_cast<std::int32_t>(std::floor(std::min({v0.Y, v1.Y, v2.Y})));
std::int32_t maxX = static_cast<std::int32_t>(std::ceil(std::max({v0.X, v1.X, v2.X})));
std::int32_t maxY = static_cast<std::int32_t>(std::ceil(std::max({v0.Y, v1.Y, v2.Y})));
minX = std::clamp(minX, 0, std::max(0, rasterFrame.Width - 1));
minY = std::clamp(minY, 0, std::max(0, rasterFrame.Height - 1));
maxX = std::clamp(maxX, 0, std::max(0, rasterFrame.Width - 1));
maxY = std::clamp(maxY, 0, std::max(0, rasterFrame.Height - 1));
if (command.ScissorEnabled) {
minX = std::max(minX, command.ScissorLeft);
minY = std::max(minY, command.ScissorTop);
maxX = std::min(maxX, command.ScissorRight);
maxY = std::min(maxY, command.ScissorBottom);
}
if (minX > maxX || minY > maxY) {
return;
}
const bool positiveArea = area > 0.0F;
for (std::int32_t y = minY; y <= maxY; ++y) {
for (std::int32_t x = minX; x <= maxX; ++x) {
const float sampleX = static_cast<float>(x) + 0.5F;
const float sampleY = static_cast<float>(y) + 0.5F;
const float w0 = MetaCoreRuntimeUiEdge(v1, v2, sampleX, sampleY);
const float w1 = MetaCoreRuntimeUiEdge(v2, v0, sampleX, sampleY);
const float w2 = MetaCoreRuntimeUiEdge(v0, v1, sampleX, sampleY);
const bool inside = positiveArea
? (w0 >= 0.0F && w1 >= 0.0F && w2 >= 0.0F)
: (w0 <= 0.0F && w1 <= 0.0F && w2 <= 0.0F);
if (!inside) {
continue;
}
const float invArea = 1.0F / area;
const float b0 = w0 * invArea;
const float b1 = w1 * invArea;
const float b2 = w2 * invArea;
MetaCoreRuntimeUiBlendPixel(
rasterFrame,
x,
y,
b0 * static_cast<float>(v0.R) + b1 * static_cast<float>(v1.R) + b2 * static_cast<float>(v2.R),
b0 * static_cast<float>(v0.G) + b1 * static_cast<float>(v1.G) + b2 * static_cast<float>(v2.G),
b0 * static_cast<float>(v0.B) + b1 * static_cast<float>(v1.B) + b2 * static_cast<float>(v2.B),
b0 * static_cast<float>(v0.A) + b1 * static_cast<float>(v1.A) + b2 * static_cast<float>(v2.A)
);
}
}
}
void MetaCoreRuntimeUiRasterizeFrame(
const MetaCoreRuntimeUiFrame& frame,
MetaCoreRuntimeUiRasterFrame& rasterFrame,
MetaCoreRuntimeUiRenderStats& stats,
std::int32_t width,
std::int32_t height
) {
rasterFrame.Width = std::max<std::int32_t>(0, width);
rasterFrame.Height = std::max<std::int32_t>(0, height);
const std::size_t pixelCount =
static_cast<std::size_t>(rasterFrame.Width) * static_cast<std::size_t>(rasterFrame.Height);
rasterFrame.Rgba.assign(pixelCount * 4U, 0);
for (const MetaCoreRuntimeUiDrawCommand& command : frame.Commands) {
const std::size_t triangleCount = command.IndexCount / 3U;
for (std::size_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) {
const std::size_t i0 = command.IndexOffset + triangleIndex * 3U + 0U;
const std::size_t i1 = command.IndexOffset + triangleIndex * 3U + 1U;
const std::size_t i2 = command.IndexOffset + triangleIndex * 3U + 2U;
if (i2 >= frame.Indices.size()) {
continue;
}
const std::uint32_t v0Index = frame.Indices[i0];
const std::uint32_t v1Index = frame.Indices[i1];
const std::uint32_t v2Index = frame.Indices[i2];
if (v0Index >= frame.Vertices.size() ||
v1Index >= frame.Vertices.size() ||
v2Index >= frame.Vertices.size()) {
continue;
}
MetaCoreRuntimeUiRasterizeTriangle(
rasterFrame,
command,
frame.Vertices[v0Index],
frame.Vertices[v1Index],
frame.Vertices[v2Index]
);
}
}
std::size_t touchedPixelCount = 0;
for (std::size_t pixelIndex = 0; pixelIndex < pixelCount; ++pixelIndex) {
if (rasterFrame.Rgba[pixelIndex * 4U + 3U] > 0) {
++touchedPixelCount;
}
}
stats.RasterPixelCount = pixelCount;
stats.RasterTouchedPixelCount = touchedPixelCount;
}
class MetaCoreRmlSystemInterface final : public Rml::SystemInterface {
public:
double GetElapsedTime() override {
return ElapsedSeconds_;
}
void Advance(double deltaSeconds) {
ElapsedSeconds_ += std::max(0.0, deltaSeconds);
}
bool LogMessage(Rml::Log::Type type, const Rml::String& message) override {
LastLogType_ = type;
LastMessage_ = message;
return true;
}
[[nodiscard]] const std::string& GetLastMessage() const {
return LastMessage_;
}
private:
double ElapsedSeconds_ = 0.0;
Rml::Log::Type LastLogType_ = Rml::Log::LT_INFO;
std::string LastMessage_{};
};
struct MetaCoreRmlMemoryFile {
std::string Data{};
std::size_t Cursor = 0;
};
[[nodiscard]] std::string MetaCoreNormalizeRmlPath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
while (path.find("//") != std::string::npos) {
path.erase(path.find("//"), 1);
}
return path;
}
[[nodiscard]] std::string MetaCoreRmlBasename(const std::string& path) {
const std::string normalized = MetaCoreNormalizeRmlPath(path);
const auto slash = normalized.find_last_of('/');
return slash == std::string::npos ? normalized : normalized.substr(slash + 1);
}
class MetaCoreRmlMemoryFileInterface final : public Rml::FileInterface {
public:
void SetVirtualFile(std::string path, std::string data) {
if (path.empty()) {
path = "MetaCoreRuntimeUi.rcss";
}
const std::string normalized = MetaCoreNormalizeRmlPath(std::move(path));
VirtualFiles_[normalized] = data;
VirtualFiles_[MetaCoreRmlBasename(normalized)] = std::move(data);
}
void Clear() {
VirtualFiles_.clear();
OpenFiles_.clear();
}
Rml::FileHandle Open(const Rml::String& path) override {
const std::string normalized = MetaCoreNormalizeRmlPath(path);
auto iterator = VirtualFiles_.find(normalized);
if (iterator == VirtualFiles_.end()) {
iterator = VirtualFiles_.find(MetaCoreRmlBasename(normalized));
}
if (iterator == VirtualFiles_.end()) {
return 0;
}
const Rml::FileHandle handle = NextHandle_++;
OpenFiles_.emplace(handle, MetaCoreRmlMemoryFile{iterator->second, 0});
return handle;
}
void Close(Rml::FileHandle file) override {
OpenFiles_.erase(file);
}
size_t Read(void* buffer, size_t size, Rml::FileHandle file) override {
auto iterator = OpenFiles_.find(file);
if (iterator == OpenFiles_.end() || buffer == nullptr || size == 0) {
return 0;
}
MetaCoreRmlMemoryFile& memoryFile = iterator->second;
const std::size_t remaining = memoryFile.Cursor < memoryFile.Data.size()
? memoryFile.Data.size() - memoryFile.Cursor
: 0;
const std::size_t bytesToRead = std::min(size, remaining);
if (bytesToRead > 0) {
std::memcpy(buffer, memoryFile.Data.data() + memoryFile.Cursor, bytesToRead);
memoryFile.Cursor += bytesToRead;
}
return bytesToRead;
}
bool Seek(Rml::FileHandle file, long offset, int origin) override {
auto iterator = OpenFiles_.find(file);
if (iterator == OpenFiles_.end()) {
return false;
}
MetaCoreRmlMemoryFile& memoryFile = iterator->second;
long base = 0;
if (origin == SEEK_CUR) {
base = static_cast<long>(memoryFile.Cursor);
} else if (origin == SEEK_END) {
base = static_cast<long>(memoryFile.Data.size());
}
const long next = base + offset;
if (next < 0) {
return false;
}
memoryFile.Cursor = std::min<std::size_t>(
static_cast<std::size_t>(next),
memoryFile.Data.size()
);
return true;
}
size_t Tell(Rml::FileHandle file) override {
auto iterator = OpenFiles_.find(file);
return iterator == OpenFiles_.end() ? 0 : iterator->second.Cursor;
}
private:
Rml::FileHandle NextHandle_ = 1;
std::unordered_map<std::string, std::string> VirtualFiles_{};
std::unordered_map<Rml::FileHandle, MetaCoreRmlMemoryFile> OpenFiles_{};
};
struct MetaCoreRmlSharedRuntime {
[[nodiscard]] bool Acquire(MetaCoreRuntimeUiRenderStats& stats) {
if (ReferenceCount == 0) {
FileInterface.Clear();
Rml::SetSystemInterface(&SystemInterface);
Rml::SetFileInterface(&FileInterface);
Rml::SetFontEngineInterface(&FontInterface);
if (!Rml::Initialise()) {
stats.LastError = SystemInterface.GetLastMessage().empty()
? "RmlUi core initialization failed"
: "RmlUi core initialization failed: " + SystemInterface.GetLastMessage();
return false;
}
Initialized = true;
}
++ReferenceCount;
stats.RmlInitialized = Initialized;
return Initialized;
}
void Release() {
if (ReferenceCount == 0) {
return;
}
--ReferenceCount;
if (ReferenceCount == 0 && Initialized) {
Rml::Shutdown();
FileInterface.Clear();
Initialized = false;
}
}
MetaCoreRmlSystemInterface SystemInterface{};
MetaCoreRmlMemoryFileInterface FileInterface{};
Rml::FontEngineInterface FontInterface{};
std::size_t ReferenceCount = 0;
bool Initialized = false;
};
[[nodiscard]] MetaCoreRmlSharedRuntime& MetaCoreGetRmlSharedRuntime() {
static MetaCoreRmlSharedRuntime runtime;
return runtime;
}
class MetaCoreRmlDiagnosticRenderInterface final : public Rml::RenderInterface {
public:
MetaCoreRmlDiagnosticRenderInterface(
MetaCoreRuntimeUiRenderStats& stats,
MetaCoreRuntimeUiFrame& frame
)
: Stats_(stats),
Frame_(frame) {}
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override {
const Rml::CompiledGeometryHandle handle = NextGeometryHandle_++;
MetaCoreRmlGeometryRecord record;
record.Vertices.reserve(vertices.size());
for (const Rml::Vertex& vertex : vertices) {
record.Vertices.push_back(MetaCoreRuntimeUiDrawVertex{
vertex.position.x,
vertex.position.y,
vertex.tex_coord.x,
vertex.tex_coord.y,
vertex.colour.red,
vertex.colour.green,
vertex.colour.blue,
vertex.colour.alpha
});
}
record.Indices.reserve(indices.size());
for (const int index : indices) {
if (index >= 0) {
record.Indices.push_back(static_cast<std::uint32_t>(index));
}
}
Geometries_.emplace(handle, std::move(record));
Stats_.CompiledGeometryCount = Geometries_.size();
return handle;
}
void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override {
++Stats_.RenderGeometryCalls;
const auto geometryIterator = Geometries_.find(geometry);
if (geometryIterator == Geometries_.end()) {
return;
}
const MetaCoreRmlGeometryRecord& record = geometryIterator->second;
const std::size_t vertexOffset = Frame_.Vertices.size();
const std::size_t indexOffset = Frame_.Indices.size();
Frame_.Vertices.reserve(Frame_.Vertices.size() + record.Vertices.size());
for (MetaCoreRuntimeUiDrawVertex vertex : record.Vertices) {
vertex.X += translation.x;
vertex.Y += translation.y;
Frame_.Vertices.push_back(vertex);
}
Frame_.Indices.reserve(Frame_.Indices.size() + record.Indices.size());
for (const std::uint32_t index : record.Indices) {
Frame_.Indices.push_back(static_cast<std::uint32_t>(vertexOffset) + index);
}
Frame_.Commands.push_back(MetaCoreRuntimeUiDrawCommand{
vertexOffset,
record.Vertices.size(),
indexOffset,
record.Indices.size(),
static_cast<std::uint64_t>(texture),
ScissorEnabled_,
ScissorRegion_.Left(),
ScissorRegion_.Top(),
ScissorRegion_.Right(),
ScissorRegion_.Bottom()
});
SyncFrameStats();
}
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override {
Geometries_.erase(geometry);
Stats_.CompiledGeometryCount = Geometries_.size();
}
Rml::TextureHandle LoadTexture(Rml::Vector2i& textureDimensions, const Rml::String& source) override {
(void)source;
++Stats_.TextureLoadRequests;
textureDimensions = Rml::Vector2i(0, 0);
return 0;
}
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i sourceDimensions) override {
++Stats_.TextureGenerateRequests;
if (sourceDimensions.x <= 0 || sourceDimensions.y <= 0) {
return 0;
}
const std::size_t width = static_cast<std::size_t>(sourceDimensions.x);
const std::size_t height = static_cast<std::size_t>(sourceDimensions.y);
const std::size_t expectedByteCount = width * height * 4U;
if (expectedByteCount == 0 || source.empty()) {
return 0;
}
const Rml::TextureHandle handle = NextTextureHandle_++;
MetaCoreRmlTextureRecord record;
record.Handle = static_cast<std::uint64_t>(handle);
record.Width = sourceDimensions.x;
record.Height = sourceDimensions.y;
record.Revision = NextTextureRevision_++;
record.Rgba.resize(expectedByteCount, 0);
const auto* sourceBytes = reinterpret_cast<const std::uint8_t*>(source.data());
const std::size_t copyByteCount = std::min(expectedByteCount, source.size());
std::copy(sourceBytes, sourceBytes + copyByteCount, record.Rgba.begin());
Textures_.emplace(handle, std::move(record));
return handle;
}
void ReleaseTexture(Rml::TextureHandle texture) override {
Textures_.erase(texture);
}
void EnableScissorRegion(bool enable) override {
ScissorEnabled_ = enable;
}
void SetScissorRegion(Rml::Rectanglei region) override {
ScissorRegion_ = region;
}
void BeginFrame() {
Frame_ = MetaCoreRuntimeUiFrame{};
SyncFrameStats();
SyncTextureStats();
}
void SyncFrameStats() {
Stats_.DrawCommandCount = Frame_.Commands.size();
Stats_.DrawVertexCount = Frame_.Vertices.size();
Stats_.DrawIndexCount = Frame_.Indices.size();
}
void SyncTexturesToFrame() {
Frame_.Textures.clear();
Frame_.Textures.reserve(Textures_.size());
for (const auto& [handle, record] : Textures_) {
(void)handle;
if (record.Handle == 0 || record.Width <= 0 || record.Height <= 0 || record.Rgba.empty()) {
continue;
}
Frame_.Textures.push_back(MetaCoreRuntimeUiTexture{
record.Handle,
record.Width,
record.Height,
record.Revision,
record.Rgba
});
}
SyncTextureStats();
}
void SyncTextureStats() {
Stats_.TextureCount = Frame_.Textures.size();
Stats_.TexturePixelCount = 0;
for (const MetaCoreRuntimeUiTexture& texture : Frame_.Textures) {
if (texture.Width > 0 && texture.Height > 0) {
Stats_.TexturePixelCount += static_cast<std::size_t>(texture.Width) * static_cast<std::size_t>(texture.Height);
}
}
}
private:
struct MetaCoreRmlGeometryRecord {
std::vector<MetaCoreRuntimeUiDrawVertex> Vertices{};
std::vector<std::uint32_t> Indices{};
};
struct MetaCoreRmlTextureRecord {
std::uint64_t Handle = 0;
std::int32_t Width = 0;
std::int32_t Height = 0;
std::uint64_t Revision = 0;
std::vector<std::uint8_t> Rgba{};
};
MetaCoreRuntimeUiRenderStats& Stats_;
MetaCoreRuntimeUiFrame& Frame_;
Rml::CompiledGeometryHandle NextGeometryHandle_ = 1;
Rml::TextureHandle NextTextureHandle_ = 1;
std::uint64_t NextTextureRevision_ = 1;
std::unordered_map<Rml::CompiledGeometryHandle, MetaCoreRmlGeometryRecord> Geometries_{};
std::unordered_map<Rml::TextureHandle, MetaCoreRmlTextureRecord> Textures_{};
bool ScissorEnabled_ = false;
Rml::Rectanglei ScissorRegion_{};
};
std::uint64_t MetaCoreNextRuntimeUiContextId() {
static std::uint64_t nextContextId = 1;
return nextContextId++;
}
} // namespace
struct MetaCoreRuntimeUiRenderer::MetaCoreRuntimeUiRendererImpl {
explicit MetaCoreRuntimeUiRendererImpl(MetaCoreRuntimeUiRenderStats& stats)
: RenderInterface(stats, Frame) {}
MetaCoreCompiledRmlUiDocument Document{};
MetaCoreRuntimeUiFrame Frame{};
MetaCoreRuntimeUiRasterFrame RasterFrame{};
MetaCoreRmlDiagnosticRenderInterface RenderInterface;
Rml::Context* Context = nullptr;
Rml::ElementDocument* LoadedDocument = nullptr;
std::string ContextName{};
bool SharedRuntimeAcquired = false;
};
MetaCoreRuntimeUiRenderer::MetaCoreRuntimeUiRenderer() = default;
MetaCoreRuntimeUiRenderer::~MetaCoreRuntimeUiRenderer() {
Shutdown();
}
bool MetaCoreRuntimeUiRenderer::Initialize() {
Shutdown();
Impl_ = std::make_unique<MetaCoreRuntimeUiRendererImpl>(Stats_);
Impl_->SharedRuntimeAcquired = MetaCoreGetRmlSharedRuntime().Acquire(Stats_);
if (!Impl_->SharedRuntimeAcquired) {
Impl_.reset();
return false;
}
Impl_->ContextName = "MetaCoreRuntimeUi_" + std::to_string(MetaCoreNextRuntimeUiContextId());
Impl_->Context = Rml::CreateContext(
Impl_->ContextName,
Rml::Vector2i(1, 1),
&Impl_->RenderInterface
);
if (Impl_->Context == nullptr) {
Stats_.LastError = "RmlUi context creation failed";
MetaCoreGetRmlSharedRuntime().Release();
Impl_.reset();
return false;
}
Stats_.Initialized = true;
Stats_.RmlInitialized = true;
Stats_.RmlContextCreated = true;
Stats_.RmlDocumentLoaded = false;
Stats_.LastError.clear();
return true;
}
void MetaCoreRuntimeUiRenderer::Shutdown() {
if (Impl_ != nullptr) {
if (Impl_->Context != nullptr && Impl_->LoadedDocument != nullptr) {
Impl_->Context->UnloadDocument(Impl_->LoadedDocument);
Impl_->Context->Update();
Impl_->LoadedDocument = nullptr;
}
if (!Impl_->ContextName.empty()) {
(void)Rml::RemoveContext(Impl_->ContextName);
Impl_->Context = nullptr;
}
if (Impl_->SharedRuntimeAcquired) {
Rml::ReleaseCompiledGeometry(&Impl_->RenderInterface);
Rml::ReleaseTextures(&Impl_->RenderInterface);
Rml::ReleaseRenderManagers();
MetaCoreGetRmlSharedRuntime().Release();
Impl_->SharedRuntimeAcquired = false;
}
}
Impl_.reset();
Stats_ = MetaCoreRuntimeUiRenderStats{};
}
bool MetaCoreRuntimeUiRenderer::LoadCompiledDocument(const MetaCoreCompiledRmlUiDocument& document) {
if (!Stats_.Initialized || Impl_ == nullptr || Impl_->Context == nullptr) {
Stats_.LastError = "Runtime UI renderer is not initialized";
return false;
}
if (document.Rml.empty()) {
Stats_.Loaded = false;
Stats_.RmlDocumentLoaded = false;
Stats_.RmlBytes = 0;
Stats_.RcssBytes = 0;
Stats_.LastError = "Compiled runtime UI RML is empty";
return false;
}
if (Impl_->LoadedDocument != nullptr) {
Impl_->Context->UnloadDocument(Impl_->LoadedDocument);
Impl_->Context->Update();
Impl_->LoadedDocument = nullptr;
}
Impl_->Document = document;
const std::string stylesheetHref = Impl_->Document.StylesheetHref.empty()
? "MetaCoreRuntimeUi.rcss"
: Impl_->Document.StylesheetHref;
MetaCoreGetRmlSharedRuntime().FileInterface.SetVirtualFile(stylesheetHref, Impl_->Document.Rcss);
Impl_->LoadedDocument = Impl_->Context->LoadDocumentFromMemory(
Impl_->Document.Rml,
"MetaCoreRuntimeUi.rml"
);
if (Impl_->LoadedDocument == nullptr) {
Stats_.Loaded = false;
Stats_.RmlDocumentLoaded = false;
Stats_.LastError = "RmlUi failed to load compiled runtime UI document";
return false;
}
Impl_->LoadedDocument->Show(Rml::ModalFlag::None, Rml::FocusFlag::None);
Impl_->Context->Update();
Stats_.Loaded = true;
Stats_.RmlDocumentLoaded = true;
Stats_.RmlBytes = Impl_->Document.Rml.size();
Stats_.RcssBytes = Impl_->Document.Rcss.size();
Stats_.LastError.clear();
return true;
}
bool MetaCoreRuntimeUiRenderer::HasNode(std::string_view nodeId) const {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->LoadedDocument == nullptr) {
return false;
}
const std::string elementId = MetaCoreBuildRuntimeUiNodeElementId(nodeId);
return Impl_->LoadedDocument->GetElementById(elementId) != nullptr;
}
bool MetaCoreRuntimeUiRenderer::SetNodeText(std::string_view nodeId, std::string_view text) {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->LoadedDocument == nullptr) {
Stats_.LastError = "Runtime UI document is not loaded";
return false;
}
const std::string elementId = MetaCoreBuildRuntimeUiNodeElementId(nodeId);
Rml::Element* element = Impl_->LoadedDocument->GetElementById(elementId);
if (element == nullptr) {
Stats_.LastError = "Runtime UI node was not found: " + std::string(nodeId);
return false;
}
element->SetInnerRML(MetaCoreEscapeRuntimeUiRmlText(text));
if (Impl_->Context != nullptr) {
Impl_->Context->Update();
}
Stats_.LastError.clear();
return true;
}
void MetaCoreRuntimeUiRenderer::Resize(std::int32_t width, std::int32_t height) {
Stats_.Width = std::max<std::int32_t>(0, width);
Stats_.Height = std::max<std::int32_t>(0, height);
if (Impl_ != nullptr && Impl_->Context != nullptr) {
Impl_->Context->SetDimensions(Rml::Vector2i(
std::max<std::int32_t>(1, width),
std::max<std::int32_t>(1, height)
));
}
}
void MetaCoreRuntimeUiRenderer::BeginFrame(float deltaSeconds) {
if (!Stats_.Initialized) {
return;
}
Stats_.LastDeltaSeconds = deltaSeconds;
++Stats_.FrameCount;
MetaCoreGetRmlSharedRuntime().SystemInterface.Advance(deltaSeconds);
if (Impl_ != nullptr && Impl_->Context != nullptr) {
Impl_->Context->Update();
}
}
void MetaCoreRuntimeUiRenderer::Render() {
if (!Stats_.Initialized || !Stats_.Loaded || Impl_ == nullptr || Impl_->Context == nullptr) {
return;
}
Impl_->RenderInterface.BeginFrame();
if (!Impl_->Context->Render()) {
Stats_.LastError = "RmlUi context render failed";
}
Impl_->RenderInterface.SyncTexturesToFrame();
MetaCoreRuntimeUiRasterizeFrame(
Impl_->Frame,
Impl_->RasterFrame,
Stats_,
Stats_.Width,
Stats_.Height
);
}
const MetaCoreRuntimeUiRenderStats& MetaCoreRuntimeUiRenderer::GetStats() const {
return Stats_;
}
const MetaCoreRuntimeUiFrame& MetaCoreRuntimeUiRenderer::GetLastFrame() const {
static const MetaCoreRuntimeUiFrame emptyFrame{};
return Impl_ == nullptr ? emptyFrame : Impl_->Frame;
}
const MetaCoreRuntimeUiRasterFrame& MetaCoreRuntimeUiRenderer::GetLastRasterFrame() const {
static const MetaCoreRuntimeUiRasterFrame emptyRasterFrame{};
return Impl_ == nullptr ? emptyRasterFrame : Impl_->RasterFrame;
}
} // namespace MetaCore