789 lines
27 KiB
C++
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 += "&"; 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<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
|