Rml initialization and rendering
This commit is contained in:
parent
68ec759c36
commit
481512689b
4
assets-src/materials/rmlui.mat.json
Normal file
4
assets-src/materials/rmlui.mat.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"vertexShaderAssetID": "assets/shaders/rml-ui/rmlui.vert",
|
||||
"fragmentShaderAssetID": "assets/shaders/rml-ui/rmlui.frag"
|
||||
}
|
||||
15
assets-src/shaders/rml-ui/rmlui.frag
Normal file
15
assets-src/shaders/rml-ui/rmlui.frag
Normal file
@ -0,0 +1,15 @@
|
||||
#version 330
|
||||
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shading_language_420pack : enable
|
||||
|
||||
layout(set = 0, binding = 1) uniform sampler2D Texture;
|
||||
layout (location = 0) in vec2 vUV;
|
||||
layout (location = 1) in vec4 vColor;
|
||||
|
||||
layout (location = 0) out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
vec4 texColor = texture(Texture, vUV);
|
||||
outColor = vColor * texColor;
|
||||
}
|
||||
25
assets-src/shaders/rml-ui/rmlui.vert
Normal file
25
assets-src/shaders/rml-ui/rmlui.vert
Normal file
@ -0,0 +1,25 @@
|
||||
#version 330
|
||||
|
||||
#extension GL_ARB_separate_shader_objects : enable
|
||||
#extension GL_ARB_shading_language_420pack : enable
|
||||
|
||||
layout(push_constant) uniform Transform
|
||||
{
|
||||
mat4 mvp;
|
||||
vec2 translate;
|
||||
} transform;
|
||||
|
||||
layout (location = 0) in vec2 inPosition;
|
||||
layout (location = 1) in vec4 inColor;
|
||||
layout (location = 2) in vec2 inUV;
|
||||
|
||||
layout (location = 0) out vec2 vUV;
|
||||
layout (location = 1) out vec4 vColor;
|
||||
|
||||
void main() {
|
||||
vUV = inUV;
|
||||
vColor = inColor;
|
||||
vec2 translatedPos = inPosition + transform.translate.xy;
|
||||
vec4 outPos = transform.mvp * vec4(translatedPos, 0, 1);
|
||||
gl_Position = outPos;
|
||||
}
|
||||
@ -17,7 +17,7 @@ body
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: 0;
|
||||
background-color: rgb(255, 255, 255);
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
7
assets/layout/test/test.rcss
Normal file
7
assets/layout/test/test.rcss
Normal file
@ -0,0 +1,7 @@
|
||||
.root {
|
||||
background-color: #C0C0C0;
|
||||
color: red;
|
||||
font-size: 14px;
|
||||
height: 50%;
|
||||
width: 50%;
|
||||
}
|
||||
@ -2,11 +2,12 @@
|
||||
|
||||
<head>
|
||||
<link type="text/css" href="../base.rcss" />
|
||||
<link type="text/css" href="test.rcss" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<p>Hello World!</p>
|
||||
<div class="root">
|
||||
HELLO WORLD
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
BIN
assets/materials/rmlui.mat
Normal file
BIN
assets/materials/rmlui.mat
Normal file
Binary file not shown.
BIN
assets/shaders/rml-ui/rmlui.frag
Normal file
BIN
assets/shaders/rml-ui/rmlui.frag
Normal file
Binary file not shown.
BIN
assets/shaders/rml-ui/rmlui.vert
Normal file
BIN
assets/shaders/rml-ui/rmlui.vert
Normal file
Binary file not shown.
@ -7,12 +7,16 @@ export module Render;
|
||||
import utils;
|
||||
import views;
|
||||
import AssetData;
|
||||
import rml;
|
||||
import std;
|
||||
|
||||
export namespace ee_engine {
|
||||
struct ShaderRefs {
|
||||
ee_assets::AssetShader *vertProvinces;
|
||||
ee_assets::AssetShader *fragProvinces;
|
||||
|
||||
ee_assets::AssetShader *vertRml;
|
||||
ee_assets::AssetShader *fragRml;
|
||||
};
|
||||
|
||||
struct CameraTransform {
|
||||
@ -54,6 +58,7 @@ export namespace ee_engine {
|
||||
void RegisterMaterials(const ShaderRefs& shaders);
|
||||
void RegisterMapMesh(MapMesh *mapMesh);
|
||||
|
||||
void SetUIContext(Rml::Context *rmlContext);
|
||||
virtual void Init() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
void Draw();
|
||||
@ -61,12 +66,15 @@ export namespace ee_engine {
|
||||
virtual void ResizeWindow(int width, int height);
|
||||
void SetCamera(const CameraTransform& camera);
|
||||
|
||||
virtual constexpr Rml::RenderInterface* getRenderInterface() noexcept = 0;
|
||||
|
||||
constexpr CameraTransform getCamera() const noexcept { return camera; };
|
||||
glm::vec2 ScreenToWorld(float screenX, float screenY) const;
|
||||
glm::vec2 ScreenToWorld(const glm::vec2& screenPos) const;
|
||||
|
||||
protected:
|
||||
bool _isReady;
|
||||
bool _isReady = false;
|
||||
bool isDrawing = false;
|
||||
int screenWidth, screenHeight;
|
||||
float screenAspect;
|
||||
CameraTransform camera;
|
||||
@ -77,6 +85,8 @@ export namespace ee_engine {
|
||||
MapMesh *mapMesh;
|
||||
ShaderRefs shaders;
|
||||
|
||||
Rml::Context *rmlContext;
|
||||
|
||||
struct RegionRanges {
|
||||
std::size_t offset;
|
||||
std::size_t provincesCount;
|
||||
@ -86,9 +96,10 @@ export namespace ee_engine {
|
||||
|
||||
explicit IRenderer(GLFWwindow *window);
|
||||
|
||||
virtual void Clear() = 0;
|
||||
virtual void DrawBegin() = 0;
|
||||
virtual void DrawMap() = 0;
|
||||
virtual void DrawUI() = 0;
|
||||
virtual void DrawEnd() = 0;
|
||||
|
||||
virtual void ApplyCamera() = 0;
|
||||
|
||||
|
||||
@ -25,10 +25,17 @@ void ee_engine::IRenderer::RegisterMapMesh(MapMesh *mapMesh) {
|
||||
this->mapMesh = mapMesh;
|
||||
}
|
||||
|
||||
void ee_engine::IRenderer::SetUIContext(Rml::Context *rmlContext) {
|
||||
this->rmlContext = rmlContext;
|
||||
}
|
||||
|
||||
void ee_engine::IRenderer::Draw() {
|
||||
Clear();
|
||||
if (!isDrawing) return;
|
||||
|
||||
DrawBegin();
|
||||
DrawMap();
|
||||
DrawUI();
|
||||
DrawEnd();
|
||||
}
|
||||
|
||||
void ee_engine::IRenderer::ResizeWindow(const int width, const int height) {
|
||||
|
||||
@ -5,6 +5,7 @@ module;
|
||||
export module Engine;
|
||||
export import Render;
|
||||
export import Input;
|
||||
export import UI;
|
||||
export import AssetManager;
|
||||
export import Client;
|
||||
|
||||
@ -18,14 +19,22 @@ export
|
||||
inline static ee_engine::IInput *input;
|
||||
inline static AssetManager *assetManager;
|
||||
inline static ee_engine::IRenderer *renderer;
|
||||
inline static UI *ui;
|
||||
inline static Client *client;
|
||||
|
||||
static void Shutdown() {
|
||||
renderer->Shutdown();
|
||||
|
||||
if (input != nullptr)
|
||||
Deallocate(input);
|
||||
input = nullptr;
|
||||
|
||||
if (ui != nullptr) {
|
||||
Deallocate(ui);
|
||||
}
|
||||
ui = nullptr;
|
||||
|
||||
if (renderer != nullptr) {
|
||||
renderer->Shutdown();
|
||||
Deallocate(renderer);
|
||||
}
|
||||
renderer = nullptr;
|
||||
|
||||
21
engine/modules/UI.cppm
Normal file
21
engine/modules/UI.cppm
Normal file
@ -0,0 +1,21 @@
|
||||
export module UI;
|
||||
import rml;
|
||||
import std;
|
||||
|
||||
export {
|
||||
class UI {
|
||||
public:
|
||||
UI(Rml::RenderInterface &renderer, std::uint32_t width, std::uint32_t height);
|
||||
~UI();
|
||||
|
||||
void Update() const;
|
||||
|
||||
constexpr Rml::Context* getContext() const noexcept {return context;}
|
||||
|
||||
private:
|
||||
Rml::RenderInterface &renderInterface;
|
||||
Rml::Context *context;
|
||||
|
||||
Rml::ElementDocument *currentDocument;
|
||||
};
|
||||
}
|
||||
22
engine/src/UI.cpp
Normal file
22
engine/src/UI.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
module UI;
|
||||
|
||||
UI::UI(Rml::RenderInterface &renderer, const std::uint32_t width, const std::uint32_t height) :
|
||||
renderInterface(renderer) {
|
||||
Rml::SetRenderInterface(&renderInterface);
|
||||
|
||||
Rml::Initialise();
|
||||
context = Rml::CreateContext("main", Rml::Vector2i(width, height));
|
||||
|
||||
Rml::LoadFontFace("assets/fonts/ARIAL.TTF");
|
||||
|
||||
currentDocument = context->LoadDocument("assets/layout/test/test.rml");
|
||||
currentDocument->Show();
|
||||
}
|
||||
|
||||
UI::~UI() {
|
||||
Rml::Shutdown();
|
||||
}
|
||||
|
||||
void UI::Update() const {
|
||||
context->Update();
|
||||
}
|
||||
@ -9,6 +9,9 @@ import VulkanRender;
|
||||
import GLFWInput;
|
||||
import std;
|
||||
|
||||
constexpr std::uint32_t SCREEN_WIDTH = 800;
|
||||
constexpr std::uint32_t SCREEN_HEIGHT = 600;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
// Setup P7 logger
|
||||
Logger::Init(P7_SINK P7_LOGS_PATH);
|
||||
@ -22,7 +25,7 @@ int main(int argc, char **argv) {
|
||||
// Vulkan only
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
|
||||
GLFWwindow *window = glfwCreateWindow(800, 600, "Etredia Empires", nullptr, nullptr);
|
||||
GLFWwindow *window = glfwCreateWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Etredia Empires", nullptr, nullptr);
|
||||
|
||||
if (!window)
|
||||
LOG(CRITICAL, "GLFW Window failed");
|
||||
@ -57,9 +60,17 @@ int main(int argc, char **argv) {
|
||||
provincesMaterial.vertexShaderAssetID);
|
||||
ee_assets::AssetShader& fragProv = Engine::assetManager->FindOnce<ee_assets::AssetShader>(
|
||||
provincesMaterial.fragmentShaderAssetID);
|
||||
const ee_assets::AssetMaterial& rmlMaterial =
|
||||
Engine::assetManager->FindOnce<ee_assets::AssetMaterial>("assets/materials/rmlui.mat");
|
||||
ee_assets::AssetShader& vertUI = Engine::assetManager->FindOnce<ee_assets::AssetShader>(
|
||||
rmlMaterial.vertexShaderAssetID);
|
||||
ee_assets::AssetShader& fragUI = Engine::assetManager->FindOnce<ee_assets::AssetShader>(
|
||||
rmlMaterial.fragmentShaderAssetID);
|
||||
Engine::renderer->RegisterMaterials({
|
||||
.vertProvinces = &vertProv,
|
||||
.fragProvinces = &fragProv
|
||||
.fragProvinces = &fragProv,
|
||||
.vertRml = &vertUI,
|
||||
.fragRml = &fragUI
|
||||
});
|
||||
|
||||
Engine::renderer->Init();
|
||||
@ -80,9 +91,15 @@ int main(int argc, char **argv) {
|
||||
Engine::client->Init();
|
||||
Engine::client->Start();
|
||||
|
||||
//Setup UI
|
||||
Engine::ui = Allocate<UI>(*Engine::renderer->getRenderInterface(), SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
Engine::renderer->SetUIContext(Engine::ui->getContext());
|
||||
|
||||
// Game loop
|
||||
ee_engine::RendererCommand renderCmdBuffer;
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
Engine::ui->Update();
|
||||
|
||||
Engine::renderer->ProcessQueue(renderCmdBuffer);
|
||||
Engine::renderer->Draw();
|
||||
|
||||
@ -101,3 +118,4 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
// TODO: Leaks detection
|
||||
// TODO: Detach engine and engine-api from Rml
|
||||
|
||||
@ -6,7 +6,6 @@ set(LIB vulkan-renderer)
|
||||
|
||||
find_package(Vulkan REQUIRED)
|
||||
find_package(glm CONFIG REQUIRED)
|
||||
find_package(RmlUi CONFIG REQUIRED)
|
||||
|
||||
# Link library
|
||||
|
||||
@ -28,4 +27,3 @@ target_link_libraries(${LIB} PUBLIC utils)
|
||||
target_link_libraries(${LIB} PUBLIC p7)
|
||||
target_link_libraries(${LIB} PRIVATE glm::glm)
|
||||
target_link_libraries(${LIB} PRIVATE Vulkan::Vulkan)
|
||||
target_link_libraries(${LIB} PRIVATE RmlUi::RmlUi)
|
||||
@ -7,6 +7,8 @@ export module VulkanRender;
|
||||
import Render;
|
||||
import views;
|
||||
import vulkan_render;
|
||||
import rmlui_vulkan;
|
||||
import rml;
|
||||
import std;
|
||||
|
||||
namespace ee_vulkan {
|
||||
@ -52,10 +54,13 @@ export namespace ee_vulkan {
|
||||
void Init() override;
|
||||
void Shutdown() override;
|
||||
|
||||
constexpr Rml::RenderInterface* getRenderInterface() noexcept override {return &*rmlRenderInterface;}
|
||||
|
||||
protected:
|
||||
void Clear() override;
|
||||
void DrawBegin() override;
|
||||
void DrawMap() override;
|
||||
void DrawUI() override;
|
||||
void DrawEnd() override;
|
||||
void ResizeWindow(int width, int height) override;
|
||||
|
||||
void ApplyCamera() override;
|
||||
@ -65,24 +70,28 @@ export namespace ee_vulkan {
|
||||
private:
|
||||
const std::uint32_t MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
bool isDrawing = false;
|
||||
|
||||
lazy<vulkan_render::Context> context;
|
||||
vulkan_render::Surface surface;
|
||||
lazy<vulkan_render::Swapchain> swapchain;
|
||||
lazy<vulkan_render::RenderCommands> renderCommands;
|
||||
lazy<vulkan_render::RenderPass> renderPass;
|
||||
lazy<vulkan_render::Pipeline> provincesPipeline;
|
||||
lazy<vulkan_render::Pipeline> rmlPipeline;
|
||||
lazy<vulkan_render::SyncManager> syncManager;
|
||||
|
||||
lazy<vulkan_render::Texture<vulkan_render::TextureMode::DEPTH>> depth;
|
||||
lazy<vulkan_render::Uniform<glm::mat4>> viewProjectionUniform;
|
||||
ProvinceMesh provinces;
|
||||
|
||||
lazy<ee_rml::RmlVulkan> rmlRenderInterface;
|
||||
|
||||
std::size_t currentFrame = 0;
|
||||
lazy<vulkan_render::FrameContext> frameContext;
|
||||
|
||||
void RecreateSwapchain();
|
||||
|
||||
void LoadProvincesMaterial();
|
||||
void LoadRmlMaterial();
|
||||
void LoadProvinces();
|
||||
void UpdateData(std::uint32_t frame);
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
export module vulkan_render:control;
|
||||
import :hardware;
|
||||
import :workflow;
|
||||
import :resources;
|
||||
import :data;
|
||||
import std;
|
||||
@ -66,7 +67,6 @@ export namespace vulkan_render {
|
||||
);
|
||||
};
|
||||
|
||||
//TODO: sync2 with timeline-semaphore(s)
|
||||
class SyncManager {
|
||||
public:
|
||||
const Context &context;
|
||||
@ -86,4 +86,21 @@ export namespace vulkan_render {
|
||||
std::uint32_t &imageIndex
|
||||
) const;
|
||||
};
|
||||
|
||||
class FrameContext {
|
||||
public:
|
||||
const Command *renderCommand;
|
||||
std::uint32_t imageIndex;
|
||||
std::uint32_t currentFrame;
|
||||
|
||||
FrameContext(
|
||||
const RenderPass &renderPass,
|
||||
const Surface &surface,
|
||||
const RenderCommands &renderCommands,
|
||||
std::uint32_t imageIndex,
|
||||
std::uint32_t currentFrame,
|
||||
std::span<vk::ClearValue> clearValues
|
||||
);
|
||||
void Complete() const;
|
||||
};
|
||||
}
|
||||
@ -69,7 +69,9 @@ export namespace vulkan_render {
|
||||
NO_UNIQUE_ADDRESS MappedMemory<T, mode != BufferMode::STATIC> content;
|
||||
static constexpr std::size_t elementSize = sizeof(T);
|
||||
|
||||
Buffer(const Context &context, const std::span<T> data) requires (mode == BufferMode::STATIC)
|
||||
template <typename U>
|
||||
Buffer(const Context &context, const std::span<U> data)
|
||||
requires (mode == BufferMode::STATIC && std::same_as<std::remove_const_t<U>, T>)
|
||||
: BaseBuffer<mode>(context, data.size() * elementSize) {
|
||||
|
||||
MakeInstance(
|
||||
@ -205,7 +207,10 @@ export namespace vulkan_render {
|
||||
context(from.context),
|
||||
width(from.width),
|
||||
height(from.height),
|
||||
format(from.format) {
|
||||
format(from.format),
|
||||
handle(std::move(from.handle)),
|
||||
view(std::move(from.view)),
|
||||
memory(std::move(from.memory)) {
|
||||
if (*from.view == nullptr) {
|
||||
LOG(ERROR, "Corrupted render texture created, source data is not complete.");
|
||||
}
|
||||
@ -267,11 +272,15 @@ export namespace vulkan_render {
|
||||
}
|
||||
};
|
||||
|
||||
using PartialTexture = Texture<TextureMode::PARTIAL>;
|
||||
using DepthTexture = Texture<TextureMode::DEPTH>;
|
||||
using RenderTexture = Texture<TextureMode::RENDER>;
|
||||
|
||||
template <typename T>
|
||||
class Uniform {
|
||||
public:
|
||||
std::vector<Buffer<T, vk::BufferUsageFlagBits::eUniformBuffer, BufferMode::CPU_ACCESS>> buffers;
|
||||
explicit Uniform(const Context &context, std::uint32_t maxFramesInFlight) {
|
||||
Uniform(const Context &context, std::uint32_t maxFramesInFlight) {
|
||||
buffers.reserve(maxFramesInFlight);
|
||||
for (std::uint32_t i = 0; i < maxFramesInFlight; i++) {
|
||||
buffers.push_back(Buffer<T, vk::BufferUsageFlagBits::eUniformBuffer, BufferMode::CPU_ACCESS>(context, 1));
|
||||
@ -279,4 +288,32 @@ export namespace vulkan_render {
|
||||
}
|
||||
};
|
||||
|
||||
class Sampler {
|
||||
public:
|
||||
vk::raii::Sampler handle;
|
||||
|
||||
explicit Sampler(const Context &context) : handle(MakeInstance(context)) {}
|
||||
|
||||
private:
|
||||
static vk::raii::Sampler MakeInstance(const Context &context) {
|
||||
const vk::SamplerCreateInfo samplerInfo {
|
||||
.magFilter = vk::Filter::eNearest,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eLinear,
|
||||
.addressModeU = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeV = vk::SamplerAddressMode::eRepeat,
|
||||
.addressModeW = vk::SamplerAddressMode::eRepeat,
|
||||
.mipLodBias = 0.0f,
|
||||
.anisotropyEnable = vk::False,
|
||||
.compareEnable = vk::False,
|
||||
.compareOp = vk::CompareOp::eAlways,
|
||||
.minLod = 0.0f,
|
||||
.maxLod = 0.0f,
|
||||
.borderColor = vk::BorderColor::eIntOpaqueBlack,
|
||||
.unnormalizedCoordinates = vk::False
|
||||
};
|
||||
return context.device.handle.createSampler(samplerInfo);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@ -31,11 +31,16 @@ export namespace vulkan_render {
|
||||
|
||||
class DataFactory {
|
||||
public:
|
||||
static Texture<TextureMode::RENDER> CreateRenderTexture(
|
||||
static RenderTexture CreateRenderTexture(
|
||||
const Context &context,
|
||||
const RenderCommands &commands,
|
||||
std::uint32_t width, std::uint32_t height,
|
||||
std::span<std::byte> textureData
|
||||
std::span<const std::byte> textureData
|
||||
);
|
||||
|
||||
static RenderTexture CreateRenderTexture(
|
||||
const Context &context,
|
||||
const RenderCommands &commands
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,15 +1,11 @@
|
||||
/**
|
||||
* Do not include <RmlUi/Core.h> in module .cpp implementation
|
||||
* to prevent MSVC internal compiler error
|
||||
*/
|
||||
|
||||
module;
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
export module rmlui_vulkan;
|
||||
import vulkan_render;
|
||||
import utils;
|
||||
import rml;
|
||||
import std;
|
||||
|
||||
namespace ee_rml {
|
||||
@ -30,60 +26,64 @@ namespace ee_rml {
|
||||
}
|
||||
|
||||
export namespace ee_rml {
|
||||
struct RmlVertexLayout {
|
||||
constexpr static std::size_t stride = sizeof(Rml::Vertex);
|
||||
constexpr static std::size_t offsetPos = offsetof(Rml::Vertex, position);
|
||||
constexpr static std::size_t offsetColor = offsetof(Rml::Vertex, colour);
|
||||
constexpr static std::size_t offsetUV = offsetof(Rml::Vertex, tex_coord);
|
||||
};
|
||||
|
||||
struct RmlTransformGeometry {
|
||||
glm::mat4 mvp;
|
||||
glm::vec2 translate;
|
||||
};
|
||||
|
||||
struct RenderContext {
|
||||
vulkan_render::Context &context;
|
||||
vulkan_render::Surface &surface;
|
||||
vulkan_render::RenderCommands &commands;
|
||||
vulkan_render::Pipeline &pipeline;
|
||||
};
|
||||
|
||||
class RmlVulkan final : public Rml::RenderInterface {
|
||||
public:
|
||||
explicit RmlVulkan(RenderContext &renderContext) : renderer(renderContext) {}
|
||||
explicit RmlVulkan(RenderContext renderContext);
|
||||
|
||||
Rml::CompiledGeometryHandle CompileGeometry(
|
||||
const Rml::Span<const Rml::Vertex> vertices,
|
||||
const Rml::Span<const std::int32_t> indices
|
||||
) override {
|
||||
GeometryMesh *mesh = Allocate<GeometryMesh>();
|
||||
mesh->vertices.emplace(renderer.context, vertices.size());
|
||||
mesh->indices.emplace(renderer.context, indices.size());
|
||||
|
||||
std::ranges::copy(vertices, mesh->vertices->content.data());
|
||||
std::ranges::copy(indices, mesh->indices->content.data());
|
||||
|
||||
renderer.commands.SubmitStagingBuffer(*mesh->vertices);
|
||||
renderer.commands.SubmitStagingBuffer(*mesh->indices);
|
||||
|
||||
return reinterpret_cast<Rml::CompiledGeometryHandle>(mesh);
|
||||
}
|
||||
Rml::Span<const Rml::Vertex> vertices,
|
||||
Rml::Span<const std::int32_t> indices
|
||||
) override;
|
||||
|
||||
void RenderGeometry(
|
||||
Rml::CompiledGeometryHandle geometry,
|
||||
Rml::Vector2f translation,
|
||||
Rml::TextureHandle texture
|
||||
) override {
|
||||
) override;
|
||||
|
||||
}
|
||||
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
|
||||
|
||||
void ReleaseGeometry(const Rml::CompiledGeometryHandle geometry) override {
|
||||
Deallocate(reinterpret_cast<GeometryMesh*>(geometry));
|
||||
};
|
||||
|
||||
Rml::TextureHandle LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source) override {return 0;}
|
||||
Rml::TextureHandle LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source) override;
|
||||
|
||||
Rml::TextureHandle GenerateTexture(
|
||||
Rml::Span<const std::uint8_t> source,
|
||||
Rml::Vector2i source_dimensions
|
||||
) override {return 0;}
|
||||
) override;
|
||||
|
||||
void ReleaseTexture(Rml::TextureHandle texture) override {}
|
||||
void ReleaseTexture(Rml::TextureHandle texture) override;
|
||||
|
||||
void EnableScissorRegion(bool enable) override {}
|
||||
void EnableScissorRegion(bool enable) override;
|
||||
|
||||
void SetScissorRegion(Rml::Rectanglei region) override {}
|
||||
void SetScissorRegion(Rml::Rectanglei region) override;
|
||||
|
||||
void SetFrameContext(vulkan_render::FrameContext *frameContext);
|
||||
|
||||
private:
|
||||
RenderContext &renderer;
|
||||
RenderContext renderer;
|
||||
vulkan_render::FrameContext *frameContext = nullptr;
|
||||
bool scissorEnabled = false;
|
||||
vulkan_render::Sampler sampler;
|
||||
std::unordered_map<Rml::TextureHandle, vk::raii::DescriptorSets> textures;
|
||||
vulkan_render::RenderTexture defaultTexture;
|
||||
};
|
||||
|
||||
}
|
||||
@ -36,6 +36,7 @@ export namespace vulkan_render {
|
||||
static constexpr vk::Format VEC2 = vk::Format::eR32G32Sfloat;
|
||||
static constexpr vk::Format VEC3 = vk::Format::eR32G32B32Sfloat;
|
||||
static constexpr vk::Format VEC4 = vk::Format::eR32G32B32A32Sfloat;
|
||||
static constexpr vk::Format UVEC4 = vk::Format::eR8G8B8A8Unorm;
|
||||
|
||||
ShaderAttributeFormat() = delete;
|
||||
};
|
||||
@ -46,7 +47,7 @@ export namespace vulkan_render {
|
||||
struct ShaderBindings {
|
||||
vk::VertexInputBindingDescription vertexBinding;
|
||||
std::uint32_t vertexAttributesCount;
|
||||
vk::VertexInputAttributeDescription vertexAttributes;
|
||||
std::vector<vk::VertexInputAttributeDescription> vertexAttributes;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -67,6 +68,7 @@ export namespace vulkan_render {
|
||||
std::vector<vk::PipelineColorBlendAttachmentState> colorBlendAttachmentStates;
|
||||
std::vector<vk::DynamicState> dynamicStates;
|
||||
std::vector<vk::DescriptorSetLayoutBinding> descriptors;
|
||||
std::uint32_t maxExtraDescriptorSets = 0;
|
||||
|
||||
vk::Viewport defaultViewport;
|
||||
vk::Rect2D defaultScissors;
|
||||
@ -84,6 +86,26 @@ export namespace vulkan_render {
|
||||
}
|
||||
|
||||
export namespace vulkan_render {
|
||||
class RenderPass {
|
||||
public:
|
||||
const Context &context;
|
||||
const Swapchain &swapchain;
|
||||
const Surface &surface;
|
||||
|
||||
vk::raii::RenderPass handle;
|
||||
std::vector<vk::raii::Framebuffer> framebuffers;
|
||||
|
||||
RenderPass(
|
||||
const Context &context,
|
||||
const Swapchain &swapchain,
|
||||
const Surface &surface,
|
||||
const Texture<TextureMode::DEPTH> &depth
|
||||
);
|
||||
private:
|
||||
vk::raii::RenderPass MakeInstance() const;
|
||||
void MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth);
|
||||
};
|
||||
|
||||
/**
|
||||
* Vulkan pipeline constructed on context, shaders and descriptors.
|
||||
*/
|
||||
@ -92,11 +114,11 @@ export namespace vulkan_render {
|
||||
const Context &context;
|
||||
const Surface &surface;
|
||||
const Swapchain &swapchain;
|
||||
const RenderPass &renderPass;
|
||||
const std::uint32_t maxFramesInFlight;
|
||||
DescriptorCounts descriptorCounts;
|
||||
|
||||
vk::raii::DescriptorSetLayout descriptorsLayout;
|
||||
vk::raii::RenderPass renderPass;
|
||||
vk::raii::PipelineLayout layout;
|
||||
vk::raii::Pipeline handle;
|
||||
|
||||
@ -106,8 +128,6 @@ export namespace vulkan_render {
|
||||
vk::raii::DescriptorPool descriptorPool;
|
||||
vk::raii::DescriptorSets descriptorSets;
|
||||
|
||||
std::vector<vk::raii::Framebuffer> framebuffers;
|
||||
|
||||
/**
|
||||
* Initialize Pipeline on shaders and their input.
|
||||
* @param context Vulkan context with ready device.
|
||||
@ -121,13 +141,15 @@ export namespace vulkan_render {
|
||||
const Context &context,
|
||||
const Surface &surface,
|
||||
const Swapchain &swapchain,
|
||||
const Texture<TextureMode::DEPTH> &depth,
|
||||
const PipelineConfig &config,
|
||||
const RenderPass &renderPass,
|
||||
const ShaderProgram &shaderProgram,
|
||||
std::uint32_t maxFramesInFlight
|
||||
);
|
||||
|
||||
void UpdateDescriptors(const std::vector<DescriptorInfo> &descriptors, vk::DescriptorSet set) const;
|
||||
void UpdateDescriptors(const std::vector<DescriptorInfo> &descriptors, std::uint32_t frame);
|
||||
vk::raii::DescriptorSets AllocateDescriptorSet() const;
|
||||
|
||||
/**
|
||||
* Construct default pipeline configuration for given shaders input layout.
|
||||
@ -148,10 +170,11 @@ export namespace vulkan_render {
|
||||
vk::raii::DescriptorSetLayout MakeDescriptorsLayout(const PipelineConfig &config) const;
|
||||
vk::raii::PipelineLayout MakeLayout(const PipelineConfig &config) const;
|
||||
vk::raii::Pipeline MakeInstance(const PipelineConfig &config, const ShaderProgram &shaderProgram) const;
|
||||
vk::raii::DescriptorPool MakeDescriptorPool(const DescriptorBindings &descriptors);
|
||||
vk::raii::DescriptorPool MakeDescriptorPool(
|
||||
const DescriptorBindings &descriptors,
|
||||
std::uint32_t maxExtraDescriptorSet
|
||||
);
|
||||
vk::raii::DescriptorSets MakeDescriptorSets() const;
|
||||
vk::raii::RenderPass MakerRenderPass() const;
|
||||
void MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth);
|
||||
};
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@ module;
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <RmlUi/Core/Core.h>
|
||||
|
||||
module VulkanRender;
|
||||
import utils;
|
||||
@ -42,13 +43,24 @@ ee_vulkan::VulkanRenderer::~VulkanRenderer() {}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::Init() {
|
||||
depth.emplace(*context, surface);
|
||||
renderPass.emplace(*context, *swapchain, surface, *depth);
|
||||
|
||||
LoadProvincesMaterial();
|
||||
LoadRmlMaterial();
|
||||
|
||||
ee_rml::RenderContext rmlContext {
|
||||
.context = *context,
|
||||
.surface = surface,
|
||||
.commands = *renderCommands,
|
||||
.pipeline = *rmlPipeline
|
||||
};
|
||||
rmlRenderInterface.emplace(rmlContext);
|
||||
|
||||
viewProjectionUniform.emplace(*context, MAX_FRAMES_IN_FLIGHT);
|
||||
|
||||
LoadProvinces();
|
||||
|
||||
_isReady = true;
|
||||
isDrawing = true;
|
||||
}
|
||||
|
||||
@ -56,11 +68,7 @@ void ee_vulkan::VulkanRenderer::Shutdown() {
|
||||
context->device.handle.waitIdle();
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::Clear() {}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
if (!isDrawing) return;
|
||||
|
||||
void ee_vulkan::VulkanRenderer::DrawBegin() {
|
||||
syncManager->WaitInFlightFence(currentFrame);
|
||||
|
||||
std::uint32_t imageIndex = 0;
|
||||
@ -78,43 +86,32 @@ void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
LOG(ERROR, "Failed to get next image index. Code: %d", result);
|
||||
break;
|
||||
}
|
||||
syncManager->WaitImageInFlight(currentFrame, imageIndex);
|
||||
|
||||
UpdateData(currentFrame);
|
||||
|
||||
syncManager->WaitImageInFlight(currentFrame, imageIndex);
|
||||
|
||||
const vulkan_render::Command &renderCommand = renderCommands->graphicsBuffers[currentFrame];
|
||||
renderCommand.handle.reset();
|
||||
renderCommand.Begin();
|
||||
|
||||
std::array clears = {DrawConfig::CLEAR_COLOR, DrawConfig::CLEAR_DEPTH};
|
||||
renderCommand.handle.beginRenderPass({
|
||||
.renderPass = provincesPipeline->renderPass,
|
||||
.framebuffer = provincesPipeline->framebuffers[imageIndex],
|
||||
.renderArea = {
|
||||
.offset = {.x = 0, .y = 0},
|
||||
.extent = surface.extent
|
||||
},
|
||||
.clearValueCount = static_cast<std::uint32_t>(clears.size()),
|
||||
.pClearValues = clears.data()
|
||||
}, vk::SubpassContents::eInline);
|
||||
frameContext.emplace(vulkan_render::FrameContext(*renderPass, surface, *renderCommands, imageIndex, currentFrame, clears));
|
||||
rmlRenderInterface->SetFrameContext(&*frameContext);
|
||||
}
|
||||
|
||||
renderCommand.handle.bindPipeline(vk::PipelineBindPoint::eGraphics, provincesPipeline->handle);
|
||||
renderCommand.handle.setViewport(0, {provincesPipeline->viewport});
|
||||
renderCommand.handle.setScissor(0, {provincesPipeline->scissors});
|
||||
void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
frameContext->renderCommand->handle.bindPipeline(vk::PipelineBindPoint::eGraphics, provincesPipeline->handle);
|
||||
frameContext->renderCommand->handle.setViewport(0, {provincesPipeline->viewport});
|
||||
frameContext->renderCommand->handle.setScissor(0, {provincesPipeline->scissors});
|
||||
|
||||
renderCommand.handle.bindVertexBuffers(
|
||||
frameContext->renderCommand->handle.bindVertexBuffers(
|
||||
0,
|
||||
{provinces.vertices->handle},
|
||||
{0}
|
||||
);
|
||||
renderCommand.handle.bindIndexBuffer(
|
||||
frameContext->renderCommand->handle.bindIndexBuffer(
|
||||
provinces.indices->handle,
|
||||
0,
|
||||
vk::IndexType::eUint32
|
||||
);
|
||||
|
||||
renderCommand.handle.bindDescriptorSets(
|
||||
frameContext->renderCommand->handle.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
provincesPipeline->layout,
|
||||
0,
|
||||
@ -122,20 +119,26 @@ void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
{}
|
||||
);
|
||||
|
||||
renderCommand.handle.drawIndexedIndirect(
|
||||
frameContext->renderCommand->handle.drawIndexedIndirect(
|
||||
provinces.indirectCommands->handle,
|
||||
0,
|
||||
provinces.indirectCommands->content.size(),
|
||||
provinces.indirectCommands->elementSize
|
||||
);
|
||||
}
|
||||
|
||||
renderCommand.handle.endRenderPass();
|
||||
renderCommand.handle.end();
|
||||
void ee_vulkan::VulkanRenderer::DrawUI() {
|
||||
frameContext->renderCommand->handle.bindPipeline(vk::PipelineBindPoint::eGraphics, rmlPipeline->handle);
|
||||
rmlContext->Render();
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::DrawEnd() {
|
||||
frameContext->Complete();
|
||||
|
||||
context->device.handle.resetFences({syncManager->inFlightFences[currentFrame]});
|
||||
|
||||
vk::CommandBufferSubmitInfo cmdSubmitInfo {
|
||||
.commandBuffer = renderCommand.handle
|
||||
.commandBuffer = frameContext->renderCommand->handle
|
||||
};
|
||||
vk::SemaphoreSubmitInfo waitSemaphoreInfo {
|
||||
.semaphore = syncManager->imageAvailable[currentFrame],
|
||||
@ -162,7 +165,7 @@ void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
.pWaitSemaphores = &signalSemaphoreInfo.semaphore,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = swapchains,
|
||||
.pImageIndices = &imageIndex
|
||||
.pImageIndices = &frameContext->imageIndex
|
||||
})
|
||||
) {
|
||||
case vk::Result::eSuccess:
|
||||
@ -182,8 +185,6 @@ void ee_vulkan::VulkanRenderer::DrawMap() {
|
||||
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::DrawUI() {}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::ResizeWindow(const int width, const int height) {
|
||||
IRenderer::ResizeWindow(width, height);
|
||||
surface.extent = {
|
||||
@ -216,10 +217,11 @@ void ee_vulkan::VulkanRenderer::RecreateSwapchain() {
|
||||
swapchain = vulkan_render::ResourcesFactory::CreateSwapchain(*context, surface);
|
||||
depth.emplace(*context, surface);
|
||||
LoadProvincesMaterial();
|
||||
LoadRmlMaterial();
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::LoadProvincesMaterial() {
|
||||
constexpr vulkan_render::ShaderBindings shaderBindings {
|
||||
const vulkan_render::ShaderBindings shaderBindings {
|
||||
.vertexBinding = {
|
||||
.binding = 0,
|
||||
.stride = sizeof(glm::vec2),
|
||||
@ -227,11 +229,13 @@ void ee_vulkan::VulkanRenderer::LoadProvincesMaterial() {
|
||||
},
|
||||
.vertexAttributesCount = 1,
|
||||
.vertexAttributes = {
|
||||
{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = vulkan_render::ShaderAttributeFormat::VEC2,
|
||||
.offset = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vk::DescriptorSetLayoutBinding viewProjection {
|
||||
@ -270,13 +274,80 @@ void ee_vulkan::VulkanRenderer::LoadProvincesMaterial() {
|
||||
*context,
|
||||
surface,
|
||||
*swapchain,
|
||||
*depth,
|
||||
provincesMaterial,
|
||||
*renderPass,
|
||||
provincesShader,
|
||||
MAX_FRAMES_IN_FLIGHT
|
||||
);
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::LoadRmlMaterial() {
|
||||
const vulkan_render::ShaderBindings shaderBindings {
|
||||
.vertexBinding = {
|
||||
.binding = 0,
|
||||
.stride = ee_rml::RmlVertexLayout::stride,
|
||||
.inputRate = vk::VertexInputRate::eVertex
|
||||
},
|
||||
.vertexAttributesCount = 3,
|
||||
.vertexAttributes ={
|
||||
{
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = vulkan_render::ShaderAttributeFormat::VEC2,
|
||||
.offset = ee_rml::RmlVertexLayout::offsetPos
|
||||
},
|
||||
{
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = vulkan_render::ShaderAttributeFormat::UVEC4,
|
||||
.offset = ee_rml::RmlVertexLayout::offsetColor
|
||||
},
|
||||
{
|
||||
.location = 2,
|
||||
.binding = 0,
|
||||
.format = vulkan_render::ShaderAttributeFormat::VEC2,
|
||||
.offset = ee_rml::RmlVertexLayout::offsetUV
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vk::DescriptorSetLayoutBinding texture {
|
||||
.binding = 1,
|
||||
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eFragment,
|
||||
.pImmutableSamplers = nullptr
|
||||
};
|
||||
|
||||
const vk::PushConstantRange model {
|
||||
.stageFlags = vk::ShaderStageFlagBits::eVertex,
|
||||
.offset = 0,
|
||||
.size = sizeof(ee_rml::RmlTransformGeometry)
|
||||
};
|
||||
|
||||
vulkan_render::PipelineConfig rmlMaterial = vulkan_render::Pipeline::MakeConfig(
|
||||
surface,
|
||||
shaderBindings,
|
||||
{texture},
|
||||
model
|
||||
);
|
||||
rmlMaterial.maxExtraDescriptorSets = 100; //TODO: remove magic const
|
||||
vulkan_render::ShaderProgram rmlShader {
|
||||
.vertex = {reinterpret_cast<std::byte*>(shaders.vertRml->content), shaders.vertRml->size},
|
||||
.fragment = {reinterpret_cast<std::byte*>(shaders.fragRml->content), shaders.fragRml->size}
|
||||
};
|
||||
|
||||
rmlPipeline.emplace(
|
||||
*context,
|
||||
surface,
|
||||
*swapchain,
|
||||
rmlMaterial,
|
||||
*renderPass,
|
||||
rmlShader,
|
||||
MAX_FRAMES_IN_FLIGHT
|
||||
);
|
||||
}
|
||||
|
||||
void ee_vulkan::VulkanRenderer::LoadProvinces() {
|
||||
std::vector<ee_engine::ProvinceSSBO> provincesData;
|
||||
std::vector<glm::vec2> verticesData;
|
||||
|
||||
@ -79,6 +79,7 @@ void vulkan_render::RenderCommands::SubmitTexture(
|
||||
const Texture<TextureMode::PARTIAL> &texture
|
||||
) const {
|
||||
const Command cmd(buffer.context, graphicsPool);
|
||||
cmd.Begin();
|
||||
|
||||
const vk::BufferImageCopy copyRegion {
|
||||
.bufferOffset = 0,
|
||||
@ -105,6 +106,7 @@ void vulkan_render::RenderCommands::TranslateTextureLayout(
|
||||
const vk::ImageLayout &to
|
||||
) const {
|
||||
const Command cmd(texture.context, graphicsPool);
|
||||
cmd.Begin();
|
||||
|
||||
vk::ImageMemoryBarrier barrier {
|
||||
.oldLayout = from,
|
||||
@ -230,3 +232,35 @@ vk::Result vulkan_render::SyncManager::AcquireNextImage(
|
||||
imageIndex = newImageIndex;
|
||||
return result;
|
||||
}
|
||||
|
||||
vulkan_render::FrameContext::FrameContext(
|
||||
const RenderPass &renderPass,
|
||||
const Surface &surface,
|
||||
const RenderCommands &renderCommands,
|
||||
const std::uint32_t imageIndex,
|
||||
const std::uint32_t currentFrame,
|
||||
const std::span<vk::ClearValue> clearValues
|
||||
) {
|
||||
this->imageIndex = imageIndex;
|
||||
this->currentFrame = currentFrame;
|
||||
|
||||
renderCommand = &renderCommands.graphicsBuffers[currentFrame];
|
||||
renderCommand->handle.reset();
|
||||
renderCommand->Begin();
|
||||
|
||||
renderCommand->handle.beginRenderPass({
|
||||
.renderPass = renderPass.handle,
|
||||
.framebuffer = renderPass.framebuffers[imageIndex],
|
||||
.renderArea = {
|
||||
.offset = {.x = 0, .y = 0},
|
||||
.extent = surface.extent
|
||||
},
|
||||
.clearValueCount = static_cast<std::uint32_t>(clearValues.size()),
|
||||
.pClearValues = clearValues.data()
|
||||
}, vk::SubpassContents::eInline);
|
||||
}
|
||||
|
||||
void vulkan_render::FrameContext::Complete() const {
|
||||
renderCommand->handle.endRenderPass();
|
||||
renderCommand->handle.end();
|
||||
}
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
module;
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
module vulkan_render;
|
||||
import utils;
|
||||
|
||||
@ -86,13 +90,13 @@ void vulkan_render::ResourcesFactory::SetupFrames(const Context &context, Swapch
|
||||
LOG(INFO, "Frame views are ready.");
|
||||
}
|
||||
|
||||
vulkan_render::Texture<vulkan_render::TextureMode::RENDER> vulkan_render::DataFactory::CreateRenderTexture(
|
||||
vulkan_render::RenderTexture vulkan_render::DataFactory::CreateRenderTexture(
|
||||
const Context &context,
|
||||
const RenderCommands &commands,
|
||||
const std::uint32_t width, const std::uint32_t height,
|
||||
const std::span<std::byte> textureData
|
||||
const std::span<const std::byte> textureData
|
||||
) {
|
||||
Texture<TextureMode::PARTIAL> texture(context, width, height, vk::Format::eR8G8B8A8Srgb);
|
||||
PartialTexture texture(context, width, height, vk::Format::eR8G8B8A8Srgb);
|
||||
const Buffer<
|
||||
std::byte,
|
||||
vk::BufferUsageFlagBits::eTransferSrc,
|
||||
@ -115,3 +119,16 @@ vulkan_render::Texture<vulkan_render::TextureMode::RENDER> vulkan_render::DataFa
|
||||
|
||||
return std::move(texture);
|
||||
}
|
||||
|
||||
vulkan_render::RenderTexture vulkan_render::DataFactory::CreateRenderTexture(
|
||||
const Context &context,
|
||||
const RenderCommands &commands
|
||||
) {
|
||||
glm::u8vec4 pixel = {255, 255, 255, 255};
|
||||
return CreateRenderTexture(
|
||||
context,
|
||||
commands,
|
||||
1, 1,
|
||||
std::span(reinterpret_cast<std::byte*>(&pixel), 4)
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,171 @@
|
||||
// module;
|
||||
//
|
||||
// #include <RmlUi/Core.h>
|
||||
//
|
||||
// module rmlui_vulkan;
|
||||
//
|
||||
module;
|
||||
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
module rmlui_vulkan;
|
||||
|
||||
// ReSharper disable once CppPassValueParameterByConstReference
|
||||
ee_rml::RmlVulkan::RmlVulkan(RenderContext renderContext) :
|
||||
renderer(renderContext),
|
||||
sampler(vulkan_render::Sampler(renderContext.context)),
|
||||
defaultTexture(
|
||||
//std::move(
|
||||
vulkan_render::DataFactory::CreateRenderTexture(
|
||||
renderContext.context,
|
||||
renderContext.commands
|
||||
)
|
||||
//)
|
||||
) {}
|
||||
|
||||
Rml::CompiledGeometryHandle ee_rml::RmlVulkan::CompileGeometry(
|
||||
const Rml::Span<const Rml::Vertex> vertices,
|
||||
const Rml::Span<const std::int32_t> indices
|
||||
) {
|
||||
GeometryMesh *mesh = Allocate<GeometryMesh>();
|
||||
mesh->vertices.emplace(renderer.context, vertices.size());
|
||||
mesh->indices.emplace(renderer.context, indices.size());
|
||||
|
||||
std::ranges::copy(vertices, mesh->vertices->content.data());
|
||||
std::ranges::copy(indices, mesh->indices->content.data());
|
||||
|
||||
renderer.commands.SubmitStagingBuffer(*mesh->vertices);
|
||||
renderer.commands.SubmitStagingBuffer(*mesh->indices);
|
||||
|
||||
return reinterpret_cast<Rml::CompiledGeometryHandle>(mesh);
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::RenderGeometry(
|
||||
const Rml::CompiledGeometryHandle geometry,
|
||||
const Rml::Vector2f translation,
|
||||
const Rml::TextureHandle texture
|
||||
) {
|
||||
GeometryMesh *geometryMesh = reinterpret_cast<GeometryMesh*>(geometry);
|
||||
|
||||
const Rml::TextureHandle cachedHandle = texture ? texture : static_cast<Rml::TextureHandle>(0);
|
||||
|
||||
auto textureDescriptor = textures.find(cachedHandle);
|
||||
if (textureDescriptor == textures.end()) {
|
||||
textureDescriptor = textures.emplace(cachedHandle, renderer.pipeline.AllocateDescriptorSet()).first;
|
||||
|
||||
const vulkan_render::RenderTexture *textureObject =
|
||||
texture ? reinterpret_cast<vulkan_render::RenderTexture*>(texture) : &defaultTexture;
|
||||
|
||||
renderer.pipeline.UpdateDescriptors({
|
||||
vulkan_render::DescriptorInfo{
|
||||
.info = vk::DescriptorImageInfo{
|
||||
.sampler = sampler.handle,
|
||||
.imageView = textureObject->view,
|
||||
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal
|
||||
},
|
||||
.bindingPosition = 1,
|
||||
.type = vk::DescriptorType::eCombinedImageSampler
|
||||
}
|
||||
}, textureDescriptor->second[0]);
|
||||
}
|
||||
|
||||
frameContext->renderCommand->handle.bindDescriptorSets(
|
||||
vk::PipelineBindPoint::eGraphics,
|
||||
renderer.pipeline.layout,
|
||||
0,
|
||||
{textureDescriptor->second[0]},
|
||||
{}
|
||||
);
|
||||
|
||||
frameContext->renderCommand->handle.setViewport(0, {renderer.pipeline.viewport});
|
||||
frameContext->renderCommand->handle.setScissor(0, {renderer.pipeline.scissors});
|
||||
|
||||
frameContext->renderCommand->handle.bindVertexBuffers(
|
||||
0,
|
||||
{geometryMesh->vertices->handle},
|
||||
{0}
|
||||
);
|
||||
frameContext->renderCommand->handle.bindIndexBuffer(
|
||||
geometryMesh->indices->handle,
|
||||
0,
|
||||
vk::IndexType::eUint32
|
||||
);
|
||||
|
||||
RmlTransformGeometry transform {
|
||||
.mvp = glm::ortho(
|
||||
0.0f, static_cast<float>(renderer.surface.extent.width),
|
||||
0.0f, static_cast<float>(renderer.surface.extent.height)
|
||||
),
|
||||
.translate = glm::vec2(translation.x, translation.y)
|
||||
};
|
||||
|
||||
frameContext->renderCommand->handle.pushConstants<RmlTransformGeometry>(
|
||||
renderer.pipeline.layout,
|
||||
vk::ShaderStageFlagBits::eVertex,
|
||||
0,
|
||||
{transform}
|
||||
);
|
||||
|
||||
frameContext->renderCommand->handle.drawIndexed(
|
||||
geometryMesh->indices->content.size(),
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::ReleaseGeometry(const Rml::CompiledGeometryHandle geometry) {
|
||||
Deallocate(reinterpret_cast<GeometryMesh*>(geometry));
|
||||
}
|
||||
|
||||
Rml::TextureHandle ee_rml::RmlVulkan::LoadTexture(Rml::Vector2i &texture_dimensions, const Rml::String &source) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
Rml::TextureHandle ee_rml::RmlVulkan::GenerateTexture(
|
||||
const Rml::Span<const std::uint8_t> source,
|
||||
const Rml::Vector2i source_dimensions
|
||||
) {
|
||||
vulkan_render::RenderTexture *texture =
|
||||
Allocate<vulkan_render::RenderTexture>(
|
||||
std::move(
|
||||
vulkan_render::DataFactory::CreateRenderTexture(
|
||||
renderer.context,
|
||||
renderer.commands,
|
||||
static_cast<std::uint32_t>(source_dimensions.x),
|
||||
static_cast<std::uint32_t>(source_dimensions.y),
|
||||
std::as_bytes(std::span{source.data(), source.size()}
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return reinterpret_cast<Rml::TextureHandle>(texture);
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::ReleaseTexture(const Rml::TextureHandle texture) {
|
||||
textures.erase(texture);
|
||||
Deallocate(reinterpret_cast<vulkan_render::RenderTexture*>(texture));
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::EnableScissorRegion(const bool enable) {
|
||||
scissorEnabled = enable;
|
||||
if (enable) return;
|
||||
renderer.pipeline.scissors.extent = renderer.surface.extent;
|
||||
renderer.pipeline.scissors.offset = {.x = 0, .y = 0};
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::SetScissorRegion(const Rml::Rectanglei region) {
|
||||
if (!scissorEnabled) return;
|
||||
|
||||
const std::int32_t x = std::max(0, region.Position().x);
|
||||
const std::int32_t y = std::max(0, region.Position().y);
|
||||
std::uint32_t width = std::max(0, region.Size().x);
|
||||
std::uint32_t height = std::max(0, region.Size().y);
|
||||
|
||||
if (x + width > renderer.surface.extent.width)width = renderer.surface.extent.width - x;
|
||||
if (y + height > renderer.surface.extent.height) height = renderer.surface.extent.height - y;
|
||||
|
||||
renderer.pipeline.scissors.offset = {x, y};
|
||||
renderer.pipeline.scissors.extent = {width, height};
|
||||
}
|
||||
|
||||
void ee_rml::RmlVulkan::SetFrameContext(vulkan_render::FrameContext *frameContext) {
|
||||
this->frameContext = frameContext;
|
||||
}
|
||||
|
||||
@ -1,40 +1,114 @@
|
||||
module vulkan_render;
|
||||
import utils;
|
||||
|
||||
vulkan_render::RenderPass::RenderPass(
|
||||
const Context &context,
|
||||
const Swapchain &swapchain,
|
||||
const Surface &surface,
|
||||
const Texture<TextureMode::DEPTH> &depth
|
||||
) :
|
||||
context(context),
|
||||
swapchain(swapchain),
|
||||
surface(surface),
|
||||
handle(MakeInstance()) {
|
||||
MakeFramebuffers(depth);
|
||||
}
|
||||
|
||||
vk::raii::RenderPass vulkan_render::RenderPass::MakeInstance() const {
|
||||
const vk::AttachmentDescription colorAttachmentDescription {
|
||||
.format = surface.format.format,
|
||||
.samples = vk::SampleCountFlagBits::e1, // No multisampling
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::ePresentSrcKHR
|
||||
};
|
||||
|
||||
vk::AttachmentReference colorAttachmentRef {
|
||||
.attachment = 0,
|
||||
.layout = vk::ImageLayout::eColorAttachmentOptimal
|
||||
};
|
||||
|
||||
const vk::AttachmentDescription depthAttachmentDescription {
|
||||
.format = vk::Format::eD32SfloatS8Uint,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare ,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
};
|
||||
|
||||
vk::AttachmentReference depthAttachmentRef {
|
||||
.attachment = 1,
|
||||
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
|
||||
};
|
||||
|
||||
vk::SubpassDescription subpass {
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &colorAttachmentRef,
|
||||
.pDepthStencilAttachment = &depthAttachmentRef
|
||||
};
|
||||
|
||||
vk::SubpassDependency dependency {
|
||||
.srcSubpass = vk::SubpassExternal,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
.dstStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
||||
.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
.dstAccessMask = vk::Flags(vk::AccessFlagBits::eColorAttachmentWrite) | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
};
|
||||
|
||||
std::array attachments = {colorAttachmentDescription, depthAttachmentDescription};
|
||||
const vk::RenderPassCreateInfo baseRenderPassInfo {
|
||||
.attachmentCount = static_cast<std::uint32_t>(attachments.size()),
|
||||
.pAttachments = attachments.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
.dependencyCount = 1,
|
||||
.pDependencies = &dependency
|
||||
};
|
||||
|
||||
return context.device.handle.createRenderPass(baseRenderPassInfo);
|
||||
}
|
||||
|
||||
vulkan_render::Pipeline::Pipeline(
|
||||
const Context &context,
|
||||
const Surface &surface,
|
||||
const Swapchain &swapchain,
|
||||
const Texture<TextureMode::DEPTH> &depth,
|
||||
const PipelineConfig &config,
|
||||
const RenderPass &renderPass,
|
||||
const ShaderProgram &shaderProgram,
|
||||
const std::uint32_t maxFramesInFlight
|
||||
) : context(context),
|
||||
surface(surface),
|
||||
swapchain(swapchain),
|
||||
renderPass(renderPass),
|
||||
maxFramesInFlight(maxFramesInFlight),
|
||||
descriptorsLayout(MakeDescriptorsLayout(config)),
|
||||
layout(MakeLayout(config)),
|
||||
renderPass(MakerRenderPass()),
|
||||
handle(MakeInstance(config, shaderProgram)),
|
||||
viewport(config.defaultViewport),
|
||||
scissors(config.defaultScissors),
|
||||
descriptorPool(MakeDescriptorPool(config.descriptors)),
|
||||
descriptorPool(MakeDescriptorPool(config.descriptors, config.maxExtraDescriptorSets)),
|
||||
descriptorSets(MakeDescriptorSets()) {
|
||||
MakeFramebuffers(depth);
|
||||
LOG(INFO, "Pipeline created");
|
||||
}
|
||||
|
||||
void vulkan_render::Pipeline::UpdateDescriptors(
|
||||
const std::vector<DescriptorInfo> &descriptors,
|
||||
const std::uint32_t frame
|
||||
) {
|
||||
const vk::DescriptorSet set
|
||||
) const {
|
||||
std::vector<vk::WriteDescriptorSet> writes;
|
||||
writes.reserve(descriptors.size());
|
||||
|
||||
for (const auto &[info, bindingPosition, type] : descriptors) {
|
||||
vk::WriteDescriptorSet write {
|
||||
.dstSet = descriptorSets[frame],
|
||||
.dstSet = set,
|
||||
.dstBinding = bindingPosition,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
@ -60,6 +134,21 @@ void vulkan_render::Pipeline::UpdateDescriptors(
|
||||
context.device.handle.updateDescriptorSets(writes, 0);
|
||||
}
|
||||
|
||||
void vulkan_render::Pipeline::UpdateDescriptors(
|
||||
const std::vector<DescriptorInfo> &descriptors,
|
||||
const std::uint32_t frame
|
||||
) {
|
||||
UpdateDescriptors(descriptors, descriptorSets[frame]);
|
||||
}
|
||||
|
||||
vk::raii::DescriptorSets vulkan_render::Pipeline::AllocateDescriptorSet() const {
|
||||
return vk::raii::DescriptorSets(context.device.handle, {
|
||||
.descriptorPool = descriptorPool,
|
||||
.descriptorSetCount = 1,
|
||||
.pSetLayouts = &*descriptorsLayout
|
||||
});
|
||||
}
|
||||
|
||||
vulkan_render::PipelineConfig vulkan_render::Pipeline::MakeConfig(
|
||||
const Surface &surface,
|
||||
const ShaderBindings &bindings,
|
||||
@ -88,7 +177,7 @@ vulkan_render::PipelineConfig vulkan_render::Pipeline::MakeConfig(
|
||||
.vertexBindingDescriptionCount = 1,
|
||||
.pVertexBindingDescriptions = &bindings.vertexBinding,
|
||||
.vertexAttributeDescriptionCount = bindings.vertexAttributesCount,
|
||||
.pVertexAttributeDescriptions = &bindings.vertexAttributes
|
||||
.pVertexAttributeDescriptions = bindings.vertexAttributes.data()
|
||||
};
|
||||
|
||||
config.inputAssemblyInfo = {
|
||||
@ -229,7 +318,7 @@ vk::raii::Pipeline vulkan_render::Pipeline::MakeInstance(
|
||||
.pColorBlendState = &config.colorBlendInfo,
|
||||
.pDynamicState = &config.dynamicStateInfo,
|
||||
.layout = layout,
|
||||
.renderPass = renderPass,
|
||||
.renderPass = renderPass.handle,
|
||||
.basePipelineHandle = nullptr,
|
||||
.basePipelineIndex = -1,
|
||||
},
|
||||
@ -243,7 +332,10 @@ vk::raii::Pipeline vulkan_render::Pipeline::MakeInstance(
|
||||
return context.device.handle.createGraphicsPipeline(nullptr, pipelineInfo.get());
|
||||
}
|
||||
|
||||
vk::raii::DescriptorPool vulkan_render::Pipeline::MakeDescriptorPool(const DescriptorBindings &descriptors) {
|
||||
vk::raii::DescriptorPool vulkan_render::Pipeline::MakeDescriptorPool(
|
||||
const DescriptorBindings &descriptors,
|
||||
const std::uint32_t maxExtraDescriptorSet
|
||||
) {
|
||||
descriptorCounts.clear();
|
||||
for (const vk::DescriptorSetLayoutBinding &binding : descriptors) {
|
||||
descriptorCounts[binding.descriptorType] += binding.descriptorCount;
|
||||
@ -254,13 +346,13 @@ vk::raii::DescriptorPool vulkan_render::Pipeline::MakeDescriptorPool(const Descr
|
||||
for (const auto &[type, count] : descriptorCounts) {
|
||||
poolSizes.push_back({
|
||||
.type = type,
|
||||
.descriptorCount = count * maxFramesInFlight
|
||||
.descriptorCount = count * (maxFramesInFlight + maxExtraDescriptorSet)
|
||||
});
|
||||
}
|
||||
|
||||
return context.device.handle.createDescriptorPool({
|
||||
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
|
||||
.maxSets = static_cast<std::uint32_t>(maxFramesInFlight),
|
||||
.maxSets = (maxFramesInFlight + maxExtraDescriptorSet),
|
||||
.poolSizeCount = static_cast<std::uint32_t>(poolSizes.size()),
|
||||
.pPoolSizes = poolSizes.data()
|
||||
});
|
||||
@ -279,76 +371,14 @@ vk::raii::DescriptorSets vulkan_render::Pipeline::MakeDescriptorSets() const {
|
||||
);
|
||||
}
|
||||
|
||||
vk::raii::RenderPass vulkan_render::Pipeline::MakerRenderPass() const {
|
||||
const vk::AttachmentDescription colorAttachmentDescription {
|
||||
.format = surface.format.format,
|
||||
.samples = vk::SampleCountFlagBits::e1, // No multisampling
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eStore,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::ePresentSrcKHR
|
||||
};
|
||||
|
||||
vk::AttachmentReference colorAttachmentRef {
|
||||
.attachment = 0,
|
||||
.layout = vk::ImageLayout::eColorAttachmentOptimal
|
||||
};
|
||||
|
||||
const vk::AttachmentDescription depthAttachmentDescription {
|
||||
.format = vk::Format::eD32SfloatS8Uint,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
.loadOp = vk::AttachmentLoadOp::eClear,
|
||||
.storeOp = vk::AttachmentStoreOp::eDontCare,
|
||||
.stencilLoadOp = vk::AttachmentLoadOp::eDontCare,
|
||||
.stencilStoreOp = vk::AttachmentStoreOp::eDontCare ,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
.finalLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
||||
};
|
||||
|
||||
vk::AttachmentReference depthAttachmentRef {
|
||||
.attachment = 1,
|
||||
.layout = vk::ImageLayout::eDepthStencilAttachmentOptimal
|
||||
};
|
||||
|
||||
vk::SubpassDescription subpass {
|
||||
.pipelineBindPoint = vk::PipelineBindPoint::eGraphics,
|
||||
.colorAttachmentCount = 1,
|
||||
.pColorAttachments = &colorAttachmentRef,
|
||||
.pDepthStencilAttachment = &depthAttachmentRef
|
||||
};
|
||||
|
||||
vk::SubpassDependency dependency {
|
||||
.srcSubpass = vk::SubpassExternal,
|
||||
.dstSubpass = 0,
|
||||
.srcStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eLateFragmentTests,
|
||||
.dstStageMask = vk::Flags(vk::PipelineStageFlagBits::eColorAttachmentOutput) | vk::PipelineStageFlagBits::eEarlyFragmentTests,
|
||||
.srcAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
.dstAccessMask = vk::Flags(vk::AccessFlagBits::eColorAttachmentWrite) | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
|
||||
};
|
||||
|
||||
std::array attachments = {colorAttachmentDescription, depthAttachmentDescription};
|
||||
const vk::RenderPassCreateInfo baseRenderPassInfo {
|
||||
.attachmentCount = static_cast<std::uint32_t>(attachments.size()),
|
||||
.pAttachments = attachments.data(),
|
||||
.subpassCount = 1,
|
||||
.pSubpasses = &subpass,
|
||||
.dependencyCount = 1,
|
||||
.pDependencies = &dependency
|
||||
};
|
||||
|
||||
return context.device.handle.createRenderPass(baseRenderPassInfo);
|
||||
}
|
||||
|
||||
void vulkan_render::Pipeline::MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth) {
|
||||
void vulkan_render::RenderPass::MakeFramebuffers(const Texture<TextureMode::DEPTH> &depth) {
|
||||
framebuffers.reserve(swapchain.frameViews.size());
|
||||
for (const vk::raii::ImageView &frameView : swapchain.frameViews)
|
||||
{
|
||||
std::array attachments = {*frameView, *depth.view};
|
||||
|
||||
vk::FramebufferCreateInfo frameBufferInfo {
|
||||
.renderPass = renderPass,
|
||||
.renderPass = handle,
|
||||
.attachmentCount = static_cast<std::uint32_t>(attachments.size()),
|
||||
.pAttachments = attachments.data(),
|
||||
.width = surface.extent.width,
|
||||
|
||||
@ -7,6 +7,7 @@ set(LIB utils)
|
||||
find_package(Boost REQUIRED COMPONENTS lockfree)
|
||||
find_package(reflectcpp CONFIG REQUIRED)
|
||||
find_package(glm CONFIG REQUIRED)
|
||||
find_package(RmlUi CONFIG REQUIRED)
|
||||
|
||||
# Link library
|
||||
|
||||
@ -16,3 +17,4 @@ target_link_libraries(${LIB} PUBLIC p7)
|
||||
target_link_libraries(${LIB} PRIVATE Boost::lockfree)
|
||||
target_link_libraries(${LIB} PRIVATE reflectcpp::reflectcpp)
|
||||
target_link_libraries(${LIB} PRIVATE glm::glm)
|
||||
target_link_libraries(${LIB} PRIVATE RmlUi::RmlUi)
|
||||
26
utils/modules/rml.cppm
Normal file
26
utils/modules/rml.cppm
Normal file
@ -0,0 +1,26 @@
|
||||
module;
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
export module rml;
|
||||
|
||||
export namespace Rml {
|
||||
using Rml::SetRenderInterface;
|
||||
using Rml::Initialise;
|
||||
using Rml::Shutdown;
|
||||
using Rml::Context;
|
||||
using Rml::CreateContext;
|
||||
using Rml::LoadFontFace;
|
||||
using Rml::ElementDocument;
|
||||
|
||||
using Rml::RenderInterface;
|
||||
using Rml::Vertex;
|
||||
using Rml::Span;
|
||||
using Rml::CompiledGeometryHandle;
|
||||
using Rml::Vector2f;
|
||||
using Rml::Vector2i;
|
||||
using Rml::ColourbPremultiplied;
|
||||
using Rml::String;
|
||||
using Rml::Rectanglei;
|
||||
using Rml::TextureHandle;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user