#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h" #include #include #include #include #include #include #include #include #include #include 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::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 += "&"; break; case '<': escaped += "<"; break; case '>': escaped += ">"; break; case '"': escaped += """; break; case '\'': escaped += "'"; 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(y) * static_cast(rasterFrame.Width) + static_cast(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(rasterFrame.Rgba[pixelOffset + 0U]) * inverseAlpha); rasterFrame.Rgba[pixelOffset + 1U] = MetaCoreRuntimeUiByte(green + static_cast(rasterFrame.Rgba[pixelOffset + 1U]) * inverseAlpha); rasterFrame.Rgba[pixelOffset + 2U] = MetaCoreRuntimeUiByte(blue + static_cast(rasterFrame.Rgba[pixelOffset + 2U]) * inverseAlpha); rasterFrame.Rgba[pixelOffset + 3U] = MetaCoreRuntimeUiByte(alpha + static_cast(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::floor(std::min({v0.X, v1.X, v2.X}))); std::int32_t minY = static_cast(std::floor(std::min({v0.Y, v1.Y, v2.Y}))); std::int32_t maxX = static_cast(std::ceil(std::max({v0.X, v1.X, v2.X}))); std::int32_t maxY = static_cast(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(x) + 0.5F; const float sampleY = static_cast(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(v0.R) + b1 * static_cast(v1.R) + b2 * static_cast(v2.R), b0 * static_cast(v0.G) + b1 * static_cast(v1.G) + b2 * static_cast(v2.G), b0 * static_cast(v0.B) + b1 * static_cast(v1.B) + b2 * static_cast(v2.B), b0 * static_cast(v0.A) + b1 * static_cast(v1.A) + b2 * static_cast(v2.A) ); } } } void MetaCoreRuntimeUiRasterizeFrame( const MetaCoreRuntimeUiFrame& frame, MetaCoreRuntimeUiRasterFrame& rasterFrame, MetaCoreRuntimeUiRenderStats& stats, std::int32_t width, std::int32_t height ) { rasterFrame.Width = std::max(0, width); rasterFrame.Height = std::max(0, height); const std::size_t pixelCount = static_cast(rasterFrame.Width) * static_cast(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(memoryFile.Cursor); } else if (origin == SEEK_END) { base = static_cast(memoryFile.Data.size()); } const long next = base + offset; if (next < 0) { return false; } memoryFile.Cursor = std::min( static_cast(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 VirtualFiles_{}; std::unordered_map 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 vertices, Rml::Span 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(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(vertexOffset) + index); } Frame_.Commands.push_back(MetaCoreRuntimeUiDrawCommand{ vertexOffset, record.Vertices.size(), indexOffset, record.Indices.size(), static_cast(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 source, Rml::Vector2i sourceDimensions) override { ++Stats_.TextureGenerateRequests; if (sourceDimensions.x <= 0 || sourceDimensions.y <= 0) { return 0; } const std::size_t width = static_cast(sourceDimensions.x); const std::size_t height = static_cast(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(handle); record.Width = sourceDimensions.x; record.Height = sourceDimensions.y; record.Revision = NextTextureRevision_++; record.Rgba.resize(expectedByteCount, 0); const auto* sourceBytes = reinterpret_cast(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(texture.Width) * static_cast(texture.Height); } } } private: struct MetaCoreRmlGeometryRecord { std::vector Vertices{}; std::vector 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 Rgba{}; }; MetaCoreRuntimeUiRenderStats& Stats_; MetaCoreRuntimeUiFrame& Frame_; Rml::CompiledGeometryHandle NextGeometryHandle_ = 1; Rml::TextureHandle NextTextureHandle_ = 1; std::uint64_t NextTextureRevision_ = 1; std::unordered_map Geometries_{}; std::unordered_map 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(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(0, width); Stats_.Height = std::max(0, height); if (Impl_ != nullptr && Impl_->Context != nullptr) { Impl_->Context->SetDimensions(Rml::Vector2i( std::max(1, width), std::max(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