Adding template project and correct name

This commit is contained in:
2026-07-19 22:15:13 -05:00
commit 718156d516
90 changed files with 71513 additions and 0 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.direnv/
result
build/
.vscode/
.cache/

62
CMakeLists.txt Normal file
View File

@@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.16)
project(init-tracker LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(PkgConfig REQUIRED)
find_package(Freetype REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(Lua REQUIRED)
pkg_check_modules(LUNASVG REQUIRED IMPORTED_TARGET lunasvg)
add_library(lunasvg::lunasvg ALIAS PkgConfig::LUNASVG)
find_package(RmlUi CONFIG REQUIRED)
add_library(rmlui_backend STATIC
vendor/rmlui_backend/RmlUi_Backend_GLFW_GL3.cpp
vendor/rmlui_backend/RmlUi_Platform_GLFW.cpp
vendor/rmlui_backend/RmlUi_Renderer_GL3.cpp
)
file(GLOB_RECURSE PAGE_SOURCES CONFIGURE_DEPENDS "src/pages/*.cpp")
file(GLOB_RECURSE ELEMENT_SOURCES CONFIGURE_DEPENDS "src/elements/*.cpp")
add_executable(${PROJECT_NAME}
src/main.cpp
src/app.cpp
src/lua_app_api.cpp
src/page.cpp
${PAGE_SOURCES}
${ELEMENT_SOURCES}
)
target_include_directories(${PROJECT_NAME} PRIVATE
src
src/elements
vendor/rmlui_backend
${FREETYPE_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
rmlui_backend
RmlUi::RmlUi
RmlUi::Debugger
RmlUi::Lua
${LUA_LIBRARIES}
Freetype::Freetype
lunasvg::lunasvg
glfw
)
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
install(DIRECTORY assets DESTINATION bin)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_DIR="${CMAKE_SOURCE_DIR}/bin/assets"
)

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Init tracker

15
assets/main.rml Normal file
View File

@@ -0,0 +1,15 @@
<rml>
<head>
<title>RmlUi Temaplate</title>
<link type="text/rcss" href="styles/main.rcss" />
</head>
<body>
<div id="navbar">
<h1>RmlUi App Template</h1>
<div class="nav-buttons">
<!-- <button id="nav-lua-test" onclick="NavigateTo('lua-test')">Lua Test</button> -->
</div>
</div>
<div id="page-content"></div>
</body>
</rml>

3
assets/pages/home.rml Normal file
View File

@@ -0,0 +1,3 @@
<div id="page-root" class="page">
</div>

314
assets/styles/main.rcss Normal file
View File

@@ -0,0 +1,314 @@
/* ==========================================================================
RCSS User Agent Defaults (Plain HTML Style)
========================================================================== */
/* Core Application Window Foundation */
body {
display: block;
width: 100vw;
height: 100vh;
font-family: "rmlui-debugger-font";
font-size: 16px;
background-color: #ffffff;
color: #000000;
margin: 0px;
padding: 0px;
}
/* Generic Structural Layout Defaults */
* {
box-sizing: border-box;
}
div, section, article, header, footer, main, nav, aside {
display: block;
}
span, em, strong, a {
display: inline;
}
/* Typographic Defaults */
p {
display: block;
margin: 1em 0px;
}
h1, h2, h3, h4, h5, h6 {
display: block;
font-weight: bold;
margin: 0.83em 0px;
}
h1 { font-size: 2em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.17em; }
h4 { font-size: 1em; }
h5 { font-size: 0.83em; }
h6 { font-size: 0.67em; }
em {
font-style: italic;
}
strong {
font-weight: bold;
}
a {
color: #0000ee;
text-decoration: underline;
}
a:hover {
color: #551a8b;
}
blockquote {
display: block;
margin: 1em 40px;
padding-left: 10px;
border-left: 2px #cccccc;
color: #555555;
}
code, pre {
font-family: "rmlui-debugger-font";
background-color: #606060;
color: #000000;
padding: 3px;
font-style: italic;
}
pre {
display: block;
white-space: pre;
margin: 1em 0px;
padding: 8px;
}
/* Lists */
ul, ol {
display: block;
margin: 1em 0px;
padding-left: 40px;
}
li {
display: block;
}
/* ==========================================================================
Tables
========================================================================== */
table {
display: table;
}
tr {
display: table-row;
}
td, th {
display: table-cell;
padding: 4px 8px;
text-align: left;
}
th {
font-weight: bold;
background-color: #eaeaea;
}
col {
display: table-column;
}
colgroup {
display: table-column-group;
}
thead, tbody, tfoot {
display: table-row-group;
}
/* ==========================================================================
Form Inputs & Interactive UI Components
========================================================================== */
button, input, select, textarea {
display: inline-block;
font-family: "rmlui-debugger-font";
font-size: 14px;
margin: 2px;
vertical-align: middle;
}
/* Traditional gray bevel button style */
button, input[type="submit"], input[type="button"] {
padding: 4px 12px;
min-width: 40px;
background-color: #e1e1e1;
color: #000000;
border: 1px #767676;
text-align: center;
}
button:hover, input[type="submit"]:hover, input[type="button"]:hover {
background-color: #d0d0d0;
border: 1px #4f4f4f;
}
button:active, input[type="submit"]:active, input[type="button"]:active {
background-color: #b5b5b5;
border: 1px #000000;
}
/* Standard text field styling */
input[type="text"], input[type="password"], textarea {
padding: 4px;
background-color: #ffffff;
color: #000000;
border: 1px #767676;
}
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
border: 1px #005aff;
}
textarea {
display: block;
white-space: pre-wrap;
width: 300px;
height: 100px;
}
/* Select Box & Dropdowns */
select {
padding: 4px;
background-color: #ffffff;
color: #000000;
border: 1px #767676;
text-align: left;
}
/* ==========================================================================
RmlUi Specific Elements (Tabsets, Progress Bars, etc.)
========================================================================== */
tabset {
display: block;
}
tabset tabs {
display: block;
border-bottom: 1px #cccccc;
}
/* Simple default layout style for built-in tabs */
tab {
display: inline-block;
padding: 6px 12px;
background-color: #f0f0f0;
border: 1px #cccccc;
margin-right: 2px;
}
tab:selected {
background-color: #ffffff;
border-bottom: 1px #ffffff; /* Overlap the bottom border line */
font-weight: bold;
}
panel {
display: block;
padding: 10px;
background-color: #ffffff;
}
/* Standard HTML5 / RmlUi Progress Bar Fallback */
progress {
display: inline-block;
width: 160px;
height: 20px;
background-color: #e6e6e6;
border: 1px #b0b0b0;
}
progress-value {
display: block;
height: 100%;
background-color: #0078d7; /* Classic Windows Blue fill */
}
/* ==========================================================================
Utility & Modern Layout Helpers
========================================================================== */
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.justify-center {
justify-content: center;
}
.align-center {
align-items: center;
}
.hidden {
display: none;
}
#navbar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
height: 60px;
padding: 0px 20px;
background-color: #2b2baa;
}
#navbar h1 {
color: #ffffff;
font-size: 24px;
margin: 0px;
}
#navbar .nav-buttons {
display: flex;
flex-direction: row;
gap: 12px;
}
#navbar button {
padding: 8px 16px;
background-color: #3c3c3c;
color: #e0e0e0;
border: 1px #666666;
}
#navbar button:hover {
background-color: #55555a;
}
/* Page Content Base */
#page-content {
display: block;
padding: 20px;
background-color: #1a1a1a;
color: #f0f0f0;
}

1
compile_commands.json Symbolic link
View File

@@ -0,0 +1 @@
build/compile_commands.json

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1783224372,
"narHash": "sha256-8i/87eeoqiGE4yOTjwSA3Eh/ziJRQEmd/unYU+K27sk=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d407951447dcd00442e97087bf374aad70c04cea",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

106
flake.nix Normal file
View File

@@ -0,0 +1,106 @@
{
description = "C++ RmlUI + Raylib development environemt";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, utils }:
utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
rmlui = pkgs.stdenv.mkDerivation rec {
pname = "rmlui";
version = "master";
src = pkgs.fetchFromGitHub {
owner = "mikke89";
repo = "RmlUi";
rev = "9c74c4fadc2ad7f1496239e560bab19ca2fe5072";
hash = "sha256-YZz41ViE3+a0VPypnbJ1Orlf5Hyxd65se5yzyHIY7ZI=";
};
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
];
buildInputs = [
pkgs.freetype
pkgs.lunasvg
pkgs.lua
];
cmakeFlags = [
"-DRMLUI_LUA_BINDINGS=ON"
"-DRMLUI_LUA_BINDINGS_LIBRARY=lua"
"-DRMLUI_SVG_PLUGIN=ON"
"-DBUILD_SHARED_LIBS=ON"
"-DBUILD_SAMPLES=OFF"
"-DBUILD_TESTING=OFF"
"-DNO_FONT_INTERFACE_DEFAULT=OFF"
];
};
buildDeps = with pkgs; [
cmake
pkg-config
gcc
clang-tools
lua-language-server
];
runtimeDeps = with pkgs; [
raylib
glfw
libGL
clang-tools
libX11
libXcursor
libXrandr
libXinerama
libXi
wayland
libxkbcommon
rmlui
freetype
lunasvg
lua
lua-language-server
];
in
{
devShells.default = pkgs.mkShell {
nativeBuildInputs = buildDeps;
buildInputs = runtimeDeps;
shellHook = ''
export CP_PATH="${pkgs.raylib}/include:${pkgs.glfw}/include:${rmlui}/include"
export LIBRARY_PATH="${pkgs.raylib}/lib:${pkgs.glfw}/lib:${pkgs.libGL}/lib:${rmlui}/lib"
export LD_LIBRARY_PATH="$LIBRARY_PATH:$LD_LIBRARY_PATH"
echo "Env ready"
'';
};
packages.default = pkgs.stdenv.mkDerivation {
pname = "init-tracker";
version = "0.1.0";
src = ./.;
nativeBuildInputs = buildDeps ++ [ pkgs.makeWrapper ];
buildInputs = runtimeDeps;
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
];
postInstall = ''
wrapProgram $out/bin/''$pname \
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath runtimeDeps}"
'';
};
}
);
}

View File

@@ -0,0 +1,18 @@
#include <RmlUi/Core/Event.h>
#include <RmlUi/Core/EventListener.h>
class ButtonClickListener : public Rml::EventListener {
public:
using ClickCallback = std::function<void(Rml::Event &event)>;
ButtonClickListener(ClickCallback callback) : callback(callback) {}
void ProcessEvent(Rml::Event &event) override {
if (event.GetId() == Rml::EventId::Click) {
if (callback) callback(event);
}
}
private:
ClickCallback callback;
};

217
src/app.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include "app.hpp"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Factory.h>
#include <RmlUi/Lua/Interpreter.h>
#include <RmlUi/Core/StyleSheetContainer.h>
#include <RmlUi/Lua.h>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <lauxlib.h>
#include <lua.h>
#include <sstream>
App::App(Rml::Context *ctx, ApplicationData *appData)
: ctx(ctx), appData(appData), doc(nullptr) {}
App::~App() {
Shutdown();
}
void App::SetupListeners(std::function<void(Rml::ElementDocument*)> bindCallback) {
onBindListeners = bindCallback;
}
void App::RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener) {
if (element && listener) {
element->AddEventListener(Rml::EventId::Click, listener.get());
listeners.push_back(std::move(listener));
}
}
PageBase* App::GetOrCreatePage(const std::string& pageName) {
if (auto it = pages.find(pageName); it != pages.end()) {
return it->second.get();
}
std::string path = GetAssetsPath() + "/pages/" + pageName + ".rml";
if (!std::filesystem::exists(path)) {
return nullptr;
}
auto page = std::make_unique<PageBase>(pageName, this);
PageBase* raw = page.get();
pages.emplace(pageName, std::move(page));
return raw;
}
bool App::ReadFile(const std::string& path, std::string& outContent) {
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file) return false;
std::ostringstream ss;
ss << file.rdbuf();
outContent = ss.str();
return true;
}
bool App::NavigateTo(const std::string &pageName) {
if (!pageOutlet) {
std::cout << "[ERROR] no page outlet - is main.rml missing id=\"page-content\"?\n";
return false;
}
PageBase* page = GetOrCreatePage(pageName);
if (!page) {
std::cout << "[ERROR] no page registered or found on disk for '" << pageName << "'\n";
return false;
}
TeardownCurrentPage();
std::string luaPath = GetAssetsPath() + "/pages/" + pageName + ".lua";
if (std::filesystem::exists(luaPath)) {
std::string luaContent;
if (ReadFile(luaPath, luaContent)) {
lua_State *L = Rml::Lua::Interpreter::GetLuaState();
if (luaL_dostring(L, luaContent.c_str()) != LUA_OK) {
std::cout << "[LUA ERROR] Failed to run page script " << pageName << ".lua: "
<< lua_tostring(L, -1) << "\n";
lua_pop(L, 1);
}
}
}
std::string path = GetAssetsPath() + "/pages/" + pageName + ".rml";
std::string content;
if (!ReadFile(path, content)) {
std::cout << "[ERROR] could not read page file: " << path << "\n";
return false;
}
if (Rml::DataModelConstructor constructor = ctx->CreateDataModel(pageName)) {
page->SetupDataModel(constructor);
page->model = constructor.GetModelHandle();
page->hasModel = true;
} else {
std::cout << "[WARN] failed to create data model for page '" << pageName << "'\n";
}
std::string rcssPath = GetAssetsPath() + "/pages/" + pageName + ".rcss";
if (std::filesystem::exists(rcssPath)) {
auto pageStyleSheet = Rml::Factory::InstanceStyleSheetFile(rcssPath);
if (pageStyleSheet && baseStyleSheet) {
auto combined = baseStyleSheet->CombineStyleSheetContainer(*pageStyleSheet);
doc->SetStyleSheetContainer(combined);
}
} else if (doc && baseStyleSheet) {
doc->SetStyleSheetContainer(baseStyleSheet);
}
pageOutlet->SetInnerRML(content);
page->root = pageOutlet->GetElementById("page-root");
if (!page->root) {
std::cout << "[WARN] page '" << pageName << "' has no element with id=\"page-root\"\n";
}
currentPage = page;
page->OnEnter(page->root);
return true;
}
bool App::LoadUi() {
if (!ctx) return false;
doc = ctx->LoadDocument(GetAssetsPath() + "/main.rml");
if (!doc) return false;
doc->Show();
pageOutlet = doc->GetElementById("page-content");
if (!pageOutlet) {
std::cout << "[WARN] main.rml has no element with id=\"page-content\"\n";
}
baseStyleSheet = Rml::Factory::InstanceStyleSheetFile(GetAssetsPath() + "/styles/main.rcss");
listeners.clear();
if (onBindListeners) {
onBindListeners(doc);
}
return true;
}
void App::Reload() {
Rml::Factory::ClearStyleSheetCache();
Rml::Factory::ClearTemplateCache();
std::string previousPage = currentPage ? currentPage->GetName() : "";
TeardownCurrentPage();
if (doc) {
doc->Close();
doc = nullptr;
pageOutlet = nullptr;
}
LoadUi();
if (!previousPage.empty()) {
NavigateTo(previousPage);
}
}
std::string App::GetAssetsPath() {
std::string install_path = std::string(ASSETS_DIR) + "/main.rml";
if (std::filesystem::exists(install_path)) {
return std::string(ASSETS_DIR);
}
if (std::filesystem::exists("./assets/main.rml")) {
return "./assets";
}
return ".";
}
void App::TeardownCurrentPage() {
if (!currentPage) return;
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
lua_pushnil(L);
lua_setglobal(L, currentPage->GetName().c_str());
currentPage->OnExit();
if (currentPage->hasModel) {
ctx->RemoveDataModel(currentPage->GetName());
currentPage->hasModel = false;
}
currentPage->root = nullptr;
if (pageOutlet) {
pageOutlet->SetInnerRML("");
}
currentPage->pageListeners.clear();
currentPage = nullptr;
}
void App::Shutdown() {
if (hasShutdown) return;
hasShutdown = true;
TeardownCurrentPage(); // uses ctx->RemoveDataModel - must run while ctx is still valid
if (doc) {
doc->Close(); // deferred destruction, but marks it for cleanup on next Update/Shutdown
doc = nullptr;
pageOutlet = nullptr;
}
}

58
src/app.hpp Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/EventListener.h>
#include <string>
#include <type_traits>
#include <vector>
#include <memory>
#include <functional>
#include <unordered_map>
#include "page.hpp"
struct ApplicationData {};
class App {
public:
App(Rml::Context* ctx, ApplicationData* appData);
~App();
void SetupListeners(std::function<void(Rml::ElementDocument*)> bindCallback);
void RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener);
template <typename T = PageBase>
void RegisterPage(const std::string &pageName) {
static_assert(std::is_base_of<PageBase, T>::value, "T must derive from PageBase");
pages[pageName] = std::make_unique<T>(pageName, this);
}
bool NavigateTo(const std::string &pageName);
bool LoadUi();
void Reload();
static std::string GetAssetsPath();
void Shutdown();
private:
ApplicationData *appData;
Rml::Context* ctx;
Rml::ElementDocument* doc;
Rml::Element* pageOutlet = nullptr;
Rml::SharedPtr<Rml::StyleSheetContainer> baseStyleSheet;
std::vector<std::unique_ptr<Rml::EventListener>> listeners;
std::function<void(Rml::ElementDocument*)> onBindListeners;
std::unordered_map<std::string, std::unique_ptr<PageBase>> pages;
PageBase* currentPage = nullptr;
PageBase* GetOrCreatePage(const std::string& pageName);
static bool ReadFile(const std::string& path, std::string& outContent);
void TeardownCurrentPage();
bool hasShutdown = false;
};

22
src/element_registry.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include <RmlUi/Core/ElementInstancer.h>
#include <RmlUi/Core/Factory.h>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
class ElementRegistry {
public:
template <typename T>
void Register(const std::string& tag) {
static_assert(std::is_base_of<Rml::Element, T>::value, "T must derive from Rml::Element");
auto instancer = std::make_unique<Rml::ElementInstancerGeneric<T>>();
Rml::Factory::RegisterElementInstancer(tag, instancer.get());
instancers.push_back(std::move(instancer));
}
private:
std::vector<std::unique_ptr<Rml::ElementInstancer>> instancers;
};

35
src/lua_app_api.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "lui_app_api.hpp"
#include <RmlUi/Lua/Interpreter.h>
#include <RmlUi/Lua/Lua.h>
#include <iostream>
extern "C" {
#include "lua.h"
}
int AppLuaApi::Lua_NavigateTo(lua_State *L) {
AppLuaApi* apiInstance = static_cast<AppLuaApi*>(lua_touserdata(L, lua_upvalueindex(1)));
if (!apiInstance || !apiInstance->app) {
std::cout << "[LUA ERROR] API context or App pointer is invalid.\n";
return 0;
}
if (lua_isstring(L, 1)) {
std::string pageName = lua_tostring(L, 1);
apiInstance->app->NavigateTo(pageName);
} else {
std::cout << "[LUA ERROR] NavigateTo expects a string argument for pageName.\n";
}
return 0;
}
void AppLuaApi::RegisterLuaBindings() {
if (!L) return;
lua_pushlightuserdata(L, this);
lua_pushcclosure(L, Lua_NavigateTo, 1);
lua_setglobal(L, "NavigateTo");
}

16
src/lui_app_api.hpp Normal file
View File

@@ -0,0 +1,16 @@
#include <RmlUi/Lua/Lua.h>
#include "app.hpp"
class AppLuaApi {
public:
AppLuaApi(App *app, lua_State *L) : app(app), L(L) {}
void RegisterLuaBindings();
static int Lua_NavigateTo(lua_State *L);
private:
App *app;
lua_State *L;
};

130
src/main.cpp Normal file
View File

@@ -0,0 +1,130 @@
#include <GLFW/glfw3.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/Event.h>
#include <RmlUi/Debugger/Debugger.h>
#include <RmlUi/Lua/Interpreter.h>
#include <iostream>
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Math.h>
#include <RmlUi/Core.h>
#include <RmlUi_Backend.h>
#include <RmlUi/Debugger.h>
#include <RmlUi/Lua/Lua.h>
#include "app.hpp"
#include "element_registry.hpp"
#include "ButtonClickListener.hpp"
#include "lui_app_api.hpp"
static bool is_initalized = false;
static bool is_running = false;
static Rml::Context *ctx;
static App *app;
static ElementRegistry elementRegistry;
void RegisterElements() {
}
bool InitalizeApplication() {
if (!Backend::Initialize("RmlUi App Template", 800, 600, true)) {
std::cout << "[ERROR] backend was not initalized successfully.\n";
return false;
}
Rml::SetSystemInterface(Backend::GetSystemInterface());
Rml::SetRenderInterface(Backend::GetRenderInterface());
Rml::Initialise();
Rml::Lua::Initialise();
ctx = Rml::CreateContext("main", Rml::Vector2i(800,600));
if (!ctx) {
std::cout << "[ERROR] context was null.\n";
Rml::Shutdown();
Backend::Shutdown();
return false;
}
Rml::Debugger::Initialise(ctx);
app = new App(ctx, nullptr);
if (!app) {
std::cout << "[ERROR] app was null.\n";
Rml::Shutdown();
Backend::Shutdown();
return false;
}
lua_State* L = Rml::Lua::Interpreter::GetLuaState();
AppLuaApi *lua_app_api = new AppLuaApi(app, L);
lua_app_api->RegisterLuaBindings();
RegisterElements();
std::cout << "Application initalized...\n";
return true;
}
bool f5_was_pressed = false;
bool f8_was_pressed = false;
void handle_input() {
GLFWwindow *window = glfwGetCurrentContext();
if(window) {
// Reload ui
bool f5_state = glfwGetKey(window, GLFW_KEY_F5);
if (f5_state == GLFW_PRESS && !f5_was_pressed) {
f5_was_pressed = true;
app->Reload();
} else if (f5_state == GLFW_RELEASE) {
f5_was_pressed = false;
}
// Toggle debugger window
bool f8_state = glfwGetKey(window, GLFW_KEY_F8);
if (f8_state == GLFW_PRESS && !f8_was_pressed) {
f8_was_pressed = true;
bool is_visible = Rml::Debugger::IsVisible();
Rml::Debugger::SetVisible(!is_visible);
} else if (f8_state == GLFW_RELEASE) {
f8_was_pressed = false;
}
}
}
int main() {
std::cout << "Application starting...\n";
is_initalized = InitalizeApplication();
if (!is_initalized) return 1;
is_running = true;
std::cout << "Loading UI...\n";
app->SetupListeners([](Rml::ElementDocument *doc) {
});
app->LoadUi();
app->NavigateTo("home");
std::cout << "UI Loaded\n";
while (is_running) {
is_running = Backend::ProcessEvents(ctx);
handle_input();
ctx->Update();
Backend::BeginFrame();
ctx->Render();
Backend::PresentFrame();
}
app->Shutdown();
Rml::Shutdown();
delete app;
Backend::Shutdown();
return 0;
}

0
src/page.cpp Normal file
View File

49
src/page.hpp Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/DataModelHandle.h>
#include <RmlUi/Core/Element.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Core/Factory.h>
#include <string>
class App;
class PageBase
{
public:
PageBase(std::string pageName, App* app) : pageName(std::move(pageName)), app(app) {}
virtual ~PageBase() = default;
const std::string& GetName() const { return pageName; }
bool TryLoadRmlPage(std::string path = "");
virtual void SetupDataModel(Rml::DataModelConstructor constructor) {}
virtual void OnEnter(Rml::Element *root) {}
virtual void OnExit() {}
protected:
App* GetApp() const {return app;}
void RegisterListener(Rml::Element* element, std::unique_ptr<Rml::EventListener> listener) {
if (element && listener) {
element->AddEventListener(Rml::EventId::Click, listener.get());
pageListeners.push_back(std::move(listener));
}
}
void DirtyVariable(const std::string& name) {
if (hasModel) model.DirtyVariable(name);
}
private:
friend class App;
std::string pageName;
App *app;
std::vector<std::unique_ptr<Rml::EventListener>> pageListeners;
Rml::Element *root = nullptr;
Rml::DataModelHandle model;
bool hasModel = false;
};

253
vendor/rmlui_backend/CMakeLists.txt vendored Normal file
View File

@@ -0,0 +1,253 @@
#[[
Interface libraries for each backend, including their source files and linking requirements.
Every time a new backend gets added or its target name is modified, please update
the list of available backends found in OptionsLists.cmake
]]
add_library(rmlui_backend_common_headers INTERFACE)
target_include_directories(rmlui_backend_common_headers INTERFACE "${CMAKE_CURRENT_LIST_DIR}")
target_sources(rmlui_backend_common_headers INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend.h"
)
if(RMLUI_BACKEND_SIMULATE_TOUCH)
set(RMLUI_BACKEND_SIMULATE_TOUCH_SUPPORTED
"SDL_GL2"
"SDL_GL3"
"SDL_VK"
"SDL_SDLrenderer"
"SDL_GPU"
)
if(NOT RMLUI_BACKEND IN_LIST RMLUI_BACKEND_SIMULATE_TOUCH_SUPPORTED)
message(WARNING "RMLUI_BACKEND_SIMULATE_TOUCH enabled but not supported on selected backend ${RMLUI_BACKEND}")
else()
target_compile_definitions(rmlui_backend_common_headers INTERFACE "RMLUI_BACKEND_SIMULATE_TOUCH")
endif()
endif()
add_library(rmlui_backend_Win32_GL2 INTERFACE)
target_sources(rmlui_backend_Win32_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_Win32_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Windows.h"
)
target_link_libraries(rmlui_backend_Win32_GL2 INTERFACE rmlui_backend_common_headers OpenGL::GL)
add_library(rmlui_backend_Win32_VK INTERFACE)
target_sources(rmlui_backend_Win32_VK INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_Win32_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Vulkan.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Vulkan/ShadersCompiledSPV.h"
)
target_link_libraries(rmlui_backend_Win32_VK INTERFACE rmlui_backend_common_headers)
add_library(rmlui_backend_Win32_DX12 INTERFACE)
target_sources(rmlui_backend_Win32_DX12 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_Win32_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_DirectX_12.h"
)
target_link_libraries(rmlui_backend_Win32_DX12 INTERFACE rmlui_backend_common_headers)
add_library(rmlui_backend_X11_GL2 INTERFACE)
target_sources(rmlui_backend_X11_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_X11.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_X11_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_X11.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Xlib.h"
)
# RMLUI_CMAKE_MINIMUM_VERSION_RAISE_NOTICE:
# Once the minimum CMake version is CMake >= 3.14, "${X11_LIBRARIES}" should
# be substituted by "X11:X11" in addition to any of the other imported that might
# be required. More info:
# https://cmake.org/cmake/help/latest/module/FindX11.html
target_link_libraries(rmlui_backend_X11_GL2 INTERFACE rmlui_backend_common_headers OpenGL::GL ${X11_LIBRARIES})
add_library(rmlui_backend_SDL_GL2 INTERFACE)
target_sources(rmlui_backend_SDL_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.h"
)
target_link_libraries(rmlui_backend_SDL_GL2 INTERFACE rmlui_backend_common_headers OpenGL::GL SDL::SDL SDL_image::SDL_image)
add_library(rmlui_backend_SDL_GL3 INTERFACE)
target_sources(rmlui_backend_SDL_GL3 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_GL3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL3.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_GL3.h"
)
target_link_libraries(rmlui_backend_SDL_GL3 INTERFACE rmlui_backend_common_headers SDL::SDL SDL_image::SDL_image)
if(UNIX)
# The OpenGL 3 renderer implementation uses dlopen/dlclose
# This is required in some UNIX and UNIX-like operating systems to load shared object files at runtime
target_link_libraries(rmlui_backend_SDL_GL3 INTERFACE ${CMAKE_DL_LIBS})
endif()
if(EMSCRIPTEN)
# Only Emscripten requires linking to OpenGL::GL, for other platforms we use 'glad' as an OpenGL loader.
target_link_libraries(rmlui_backend_SDL_GL3 INTERFACE OpenGL::GL)
endif()
add_library(rmlui_backend_SDL_VK INTERFACE)
target_sources(rmlui_backend_SDL_VK INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Vulkan.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Vulkan/ShadersCompiledSPV.h"
)
target_link_libraries(rmlui_backend_SDL_VK INTERFACE rmlui_backend_common_headers SDL::SDL)
if(UNIX)
# The Vulkan renderer implementation uses dlopen/dlclose
# This is required in some UNIX and UNIX-like operating systems to load shared object files at runtime
target_link_libraries(rmlui_backend_SDL_VK INTERFACE ${CMAKE_DL_LIBS})
endif()
add_library(rmlui_backend_SDL_DX12 INTERFACE)
target_sources(rmlui_backend_SDL_DX12 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_DirectX_12.h"
)
target_link_libraries(rmlui_backend_SDL_DX12 INTERFACE rmlui_backend_common_headers SDL::SDL SDL_image::SDL_image)
add_library(rmlui_backend_SDL_SDLrenderer INTERFACE)
target_sources(rmlui_backend_SDL_SDLrenderer INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_SDLrenderer.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_SDL.h"
)
target_link_libraries(rmlui_backend_SDL_SDLrenderer INTERFACE rmlui_backend_common_headers SDL::SDL SDL_image::SDL_image)
add_library(rmlui_backend_SDL_GPU INTERFACE)
target_sources(rmlui_backend_SDL_GPU INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_SDL_GPU.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SDL_GPU.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SDL.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_SDL_GPU.h"
)
target_link_libraries(rmlui_backend_SDL_GPU INTERFACE rmlui_backend_common_headers SDL::SDL SDL_image::SDL_image)
add_library(rmlui_backend_SFML_GL2 INTERFACE)
target_sources(rmlui_backend_SFML_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SFML.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_SFML_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_SFML.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.h"
)
target_link_libraries(rmlui_backend_SFML_GL2 INTERFACE
rmlui_backend_common_headers OpenGL::GL SFML::Graphics SFML::Window SFML::System
)
add_library(rmlui_backend_GLFW_GL2 INTERFACE)
target_sources(rmlui_backend_GLFW_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_GLFW_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL2.h"
)
target_link_libraries(rmlui_backend_GLFW_GL2 INTERFACE rmlui_backend_common_headers OpenGL::GL glfw)
add_library(rmlui_backend_GLFW_GL3 INTERFACE)
target_sources(rmlui_backend_GLFW_GL3 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_GLFW_GL3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_GL3.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_GL3.h"
)
target_link_libraries(rmlui_backend_GLFW_GL3 INTERFACE rmlui_backend_common_headers glfw)
if(UNIX)
# The OpenGL 3 renderer implementation uses dlopen/dlclose
# This is required in some UNIX and UNIX-like operating systems to load shared object files at runtime
target_link_libraries(rmlui_backend_GLFW_GL3 INTERFACE ${CMAKE_DL_LIBS})
endif()
add_library(rmlui_backend_GLFW_VK INTERFACE)
target_sources(rmlui_backend_GLFW_VK INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_GLFW_VK.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_VK.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Vulkan.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Vulkan/ShadersCompiledSPV.h"
)
target_link_libraries(rmlui_backend_GLFW_VK INTERFACE rmlui_backend_common_headers glfw)
if(UNIX)
# The Vulkan renderer implementation uses dlopen/dlclose
# This is required in some UNIX and UNIX-like operating systems to load shared object files at runtime
target_link_libraries(rmlui_backend_GLFW_VK INTERFACE ${CMAKE_DL_LIBS})
endif()
add_library(rmlui_backend_GLFW_DX12 INTERFACE)
target_sources(rmlui_backend_GLFW_DX12 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_GLFW_DX12.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX12.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_DirectX_12.h"
)
target_link_libraries(rmlui_backend_GLFW_DX12 INTERFACE rmlui_backend_common_headers glfw)
add_library(rmlui_backend_BackwardCompatible_GLFW_GL2 INTERFACE)
target_sources(rmlui_backend_BackwardCompatible_GLFW_GL2 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp"
)
target_link_libraries(rmlui_backend_BackwardCompatible_GLFW_GL2 INTERFACE rmlui_backend_common_headers OpenGL::GL glfw)
add_library(rmlui_backend_BackwardCompatible_GLFW_GL3 INTERFACE)
target_sources(rmlui_backend_BackwardCompatible_GLFW_GL3 INTERFACE
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h"
"${CMAKE_CURRENT_LIST_DIR}/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp"
)
target_link_libraries(rmlui_backend_BackwardCompatible_GLFW_GL3 INTERFACE rmlui_backend_common_headers glfw)
if(UNIX)
# The OpenGL 3 renderer implementation uses dlopen/dlclose
# This is required in some UNIX and UNIX-like operating systems to load shared object files at runtime
target_link_libraries(rmlui_backend_BackwardCompatible_GLFW_GL3 INTERFACE ${CMAKE_DL_LIBS})
endif()
if(RMLUI_IS_ROOT_PROJECT)
install(DIRECTORY "./"
DESTINATION "${CMAKE_INSTALL_DATADIR}/Backends"
)
endif()

41
vendor/rmlui_backend/RmlUi_Backend.h vendored Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/RenderInterface.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/Types.h>
using KeyDownCallback = bool (*)(Rml::Context* context, Rml::Input::KeyIdentifier key, int key_modifier, float native_dp_ratio, bool priority);
/**
This interface serves as a basic abstraction over the various backends included with RmlUi. It is mainly intended as an example to get something
simple up and running, and provides just enough functionality for the included samples.
This interface may be used directly for simple applications and testing. However, for anything more advanced we recommend to use the backend as a
starting point and copy relevant parts into the main loop of your application. On the other hand, the underlying platform and renderer used by the
backend are intended to be re-usable as is.
*/
namespace Backend {
// Initializes the backend, including the custom system and render interfaces, and opens a window for rendering the RmlUi context.
bool Initialize(const char* window_name, int width, int height, bool allow_resize);
// Closes the window and release all resources owned by the backend, including the system and render interfaces.
void Shutdown();
// Returns a pointer to the custom system interface which should be provided to RmlUi.
Rml::SystemInterface* GetSystemInterface();
// Returns a pointer to the custom render interface which should be provided to RmlUi.
Rml::RenderInterface* GetRenderInterface();
// Polls and processes events from the current platform, and applies any relevant events to the provided RmlUi context and the key down callback.
// @return False to indicate that the application should be closed.
bool ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback = nullptr, bool power_save = false);
// Request application closure during the next event processing call.
void RequestExit();
// Prepares the render state to accept rendering commands from RmlUi, call before rendering the RmlUi context.
void BeginFrame();
// Presents the rendered frame to the screen, call after rendering the RmlUi context.
void PresentFrame();
} // namespace Backend

View File

@@ -0,0 +1,280 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_GLFW.h"
#include "RmlUi_Renderer_DX12.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Profiling.h>
#include <GLFW/glfw3.h>
#include <thread>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(GLFWwindow* window) : system_interface(window), window(window) {}
SystemInterface_GLFW system_interface;
Rml::UniquePtr<RenderInterface_DX12> render_interface;
GLFWwindow* window;
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
int glfw_active_modifiers = 0;
bool running = true;
bool context_dimensions_dirty = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
{
glfwTerminate();
return false;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, window_name, nullptr, nullptr);
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW failed to create window");
return false;
}
HWND window_handle = glfwGetWin32Window(window);
if (!window_handle)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not retrieve native window handle from GLFW");
return false;
}
data = Rml::MakeUnique<BackendData>(window);
RmlRendererSettings settings = {};
settings.vsync = true;
settings.msaa_sample_count = RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT;
data->render_interface = Rml::MakeUnique<RenderInterface_DX12>(window_handle, settings);
if (!data->render_interface || !(*data->render_interface))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize DirectX 12 render interface");
data.reset();
return false;
}
// The window size may have been scaled by DPI settings, get the actual pixel size.
glfwGetFramebufferSize(window, &width, &height);
data->render_interface->SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
data->render_interface.reset();
glfwDestroyWindow(data->window);
data.reset();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return data->render_interface.get();
}
static bool WaitForValidSwapchain()
{
bool result = true;
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any render
// calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, we keep the
// application inside this loop until we are able to recreate the swapchain and render again.
while (!data->render_interface->IsSwapchainValid())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (glfwWindowShouldClose(data->window))
{
glfwRestoreWindow(data->window);
result = false;
}
glfwPollEvents();
data->render_interface->RecreateSwapchain();
}
return result;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
bool result = data->running;
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
if (!WaitForValidSwapchain())
result = false;
data->context = nullptr;
data->key_down_callback = nullptr;
result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface->BeginFrame();
data->render_interface->Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface->EndFrame();
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface->SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,227 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_GLFW.h"
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Profiling.h>
#include <GLFW/glfw3.h>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(GLFWwindow* window) : system_interface(window) {}
SystemInterface_GLFW system_interface;
RenderInterface_GL2 render_interface;
GLFWwindow* window = nullptr;
int glfw_active_modifiers = 0;
bool context_dimensions_dirty = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
return false;
// Set window hints for OpenGL 2 context creation.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
glfwWindowHint(GLFW_STENCIL_BITS, 8);
// Enable MSAA for better-looking visuals, especially when transforms are applied.
glfwWindowHint(GLFW_SAMPLES, 2);
// Apply window properties and create it.
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
return false;
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
data = Rml::MakeUnique<BackendData>(window);
data->window = window;
// The window size may have been scaled by DPI settings, get the actual pixel size.
glfwGetFramebufferSize(window, &width, &height);
data->render_interface.SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
// Setup the input and window event callback functions.
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
glfwDestroyWindow(data->window);
data.reset();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
data->context = nullptr;
data->key_down_callback = nullptr;
const bool result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
data->render_interface.Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
glfwSwapBuffers(data->window);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface.SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,235 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_GLFW.h"
#include "RmlUi_Renderer_GL3.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Profiling.h>
#include <GLFW/glfw3.h>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(GLFWwindow* window) : system_interface(window) {}
SystemInterface_GLFW system_interface;
RenderInterface_GL3 render_interface;
GLFWwindow* window = nullptr;
int glfw_active_modifiers = 0;
bool context_dimensions_dirty = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
return false;
// Set window hints for OpenGL 3.3 Core context creation.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
// Apply window properties and create it.
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
return false;
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
// Load the OpenGL functions.
Rml::String renderer_message;
if (!RmlGL3::Initialize(&renderer_message))
return false;
// Construct the system and render interface, this includes compiling all the shaders. If this fails, it is likely an error in the shader code.
data = Rml::MakeUnique<BackendData>(window);
if (!data || !data->render_interface)
return false;
data->window = window;
data->system_interface.LogMessage(Rml::Log::LT_INFO, renderer_message);
// The window size may have been scaled by DPI settings, get the actual pixel size.
glfwGetFramebufferSize(window, &width, &height);
data->render_interface.SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
// Setup the input and window event callback functions.
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
glfwDestroyWindow(data->window);
data.reset();
RmlGL3::Shutdown();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
data->context = nullptr;
data->key_down_callback = nullptr;
const bool result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.Clear();
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
glfwSwapBuffers(data->window);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface.SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,268 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Renderer_VK.h"
// This space is intentional to prevent autoformat from reordering RmlUi_Renderer_VK behind RmlUi_Platform_GLFW
#include "RmlUi_Platform_GLFW.h"
#include <RmlUi/Config/Config.h>
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <GLFW/glfw3.h>
#include <stdint.h>
#include <thread>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(GLFWwindow* window) : system_interface(window) {}
SystemInterface_GLFW system_interface;
RenderInterface_VK render_interface;
GLFWwindow* window = nullptr;
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
int glfw_active_modifiers = 0;
bool running = true;
bool context_dimensions_dirty = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
{
glfwTerminate();
return false;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, window_name, nullptr, nullptr);
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW failed to create window");
return false;
}
data = Rml::MakeUnique<BackendData>(window);
data->window = window;
uint32_t count;
const char** extensions = glfwGetRequiredInstanceExtensions(&count);
RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query GLFW Vulkan extensions");
if (!data->render_interface.Initialize(Rml::Vector<const char*>(extensions, extensions + count),
[](VkInstance instance, VkSurfaceKHR* out_surface) {
return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS;
;
}))
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
return false;
}
data->render_interface.SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
data->render_interface.Shutdown();
glfwDestroyWindow(data->window);
data.reset();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
static bool WaitForValidSwapchain()
{
bool result = true;
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any render
// calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, we keep the
// application inside this loop until we are able to recreate the swapchain and render again.
while (!data->render_interface.IsSwapchainValid())
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (glfwWindowShouldClose(data->window))
{
glfwRestoreWindow(data->window);
result = false;
}
glfwPollEvents();
data->render_interface.RecreateSwapchain();
}
return result;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
bool result = data->running;
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
if (!WaitForValidSwapchain())
result = false;
data->context = nullptr;
data->key_down_callback = nullptr;
result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface.SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,394 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_DX12.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Profiling.h>
#if SDL_MAJOR_VERSION >= 3
#include <SDL3_image/SDL_image.h>
#else
#include <SDL_image.h>
#include <SDL_syswm.h>
#endif
/**
Custom render interface example for the SDL/DX12 backend.
Overloads the DirectX 12 render interface to load textures through SDL_image's built-in texture loading functionality.
*/
class RenderInterface_DX12_SDL : public RenderInterface_DX12 {
public:
RenderInterface_DX12_SDL(void* p_window_handle, const Backend::RmlRendererSettings& settings) : RenderInterface_DX12(p_window_handle, settings) {}
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
return {};
file_interface->Seek(file_handle, 0, SEEK_END);
const size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
const size_t i_ext = source.rfind('.');
Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
#if SDL_MAJOR_VERSION >= 3
auto CreateSurface = [&]() { return IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format; };
auto ConvertSurface = [](SDL_Surface* surface, SDL_PixelFormat format) { return SDL_ConvertSurface(surface, format); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_DestroySurface(surface); };
#else
auto CreateSurface = [&]() { return IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format->format; };
auto ConvertSurface = [](SDL_Surface* surface, Uint32 format) { return SDL_ConvertSurfaceFormat(surface, format, 0); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_FreeSurface(surface); };
#endif
SDL_Surface* surface = CreateSurface();
if (!surface)
return {};
texture_dimensions = {surface->w, surface->h};
if (GetSurfaceFormat(surface) != SDL_PIXELFORMAT_RGBA32)
{
// Ensure correct format for premultiplied alpha conversion and GenerateTexture below.
SDL_Surface* converted_surface = ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
DestroySurface(surface);
if (!converted_surface)
return {};
surface = converted_surface;
}
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
const size_t pixels_byte_size = surface->w * surface->h * 4;
byte* pixels = static_cast<byte*>(surface->pixels);
for (size_t i = 0; i < pixels_byte_size; i += 4)
{
const byte alpha = pixels[i + 3];
for (size_t j = 0; j < 3; ++j)
pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
}
Rml::TextureHandle texture_handle = RenderInterface_DX12::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions);
DestroySurface(surface);
return texture_handle;
}
};
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window) : system_interface(window), window(window) {}
SystemInterface_SDL system_interface;
Rml::UniquePtr<RenderInterface_DX12_SDL> render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
#if SDL_MAJOR_VERSION >= 3
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
return false;
#endif
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
#if SDL_MAJOR_VERSION >= 3
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
#else
const Uint32 window_flags = (allow_resize ? SDL_WINDOW_RESIZABLE : 0);
SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
// SDL2 implicitly activates text input on window creation. Turn it off for now, it will be activated again e.g. when focusing a text input field.
SDL_StopTextInput();
#endif
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
return false;
}
#if SDL_MAJOR_VERSION >= 3
HWND window_handle = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#else
SDL_SysWMinfo wm_info = {};
SDL_VERSION(&wm_info.version);
if (!SDL_GetWindowWMInfo(window, &wm_info))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error getting window info: %s", SDL_GetError());
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
HWND window_handle = wm_info.info.win.window;
#endif
if (!window_handle)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not retrieve native window handle from SDL");
SDL_DestroyWindow(window);
SDL_Quit();
return false;
}
// Get the actual window size (may differ due to DPI scaling).
#if SDL_MAJOR_VERSION >= 3
SDL_GetWindowSizeInPixels(window, &width, &height);
#else
SDL_GetWindowSize(window, &width, &height);
#endif
data = Rml::MakeUnique<BackendData>(window);
RmlRendererSettings settings = {};
settings.vsync = true;
settings.msaa_sample_count = RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT;
data->render_interface = Rml::MakeUnique<RenderInterface_DX12_SDL>(window_handle, settings);
if (!data->render_interface || !(*data->render_interface))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize DirectX 12 render interface");
SDL_DestroyWindow(window);
data.reset();
SDL_Quit();
return false;
}
data->render_interface->SetViewport(width, height);
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
data->render_interface.reset();
SDL_DestroyWindow(data->window);
data.reset();
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return data->render_interface.get();
}
static bool WaitForValidSwapchain()
{
#if SDL_MAJOR_VERSION >= 3
constexpr auto event_quit = SDL_EVENT_QUIT;
#else
constexpr auto event_quit = SDL_QUIT;
#endif
bool result = true;
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any render
// calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, we keep the
// application inside this loop until we are able to recreate the swapchain and render again.
while (!data->render_interface->IsSwapchainValid())
{
SDL_Event ev;
while (SDL_PollEvent(&ev))
{
if (ev.type == event_quit)
{
// Restore the window so that we can recreate the swapchain, and then properly release all resource and shutdown cleanly.
SDL_RestoreWindow(data->window);
result = false;
}
}
SDL_Delay(10);
data->render_interface->RecreateSwapchain();
}
return result;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
#if SDL_MAJOR_VERSION >= 3
#define RMLSDL_WINDOW_EVENTS_BEGIN
#define RMLSDL_WINDOW_EVENTS_END
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
bool has_event = false;
#else
#define RMLSDL_WINDOW_EVENTS_BEGIN \
case SDL_WINDOWEVENT: \
{ \
switch (ev.window.event) \
{
#define RMLSDL_WINDOW_EVENTS_END \
} \
} \
break;
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetDisplayScale = []() { return 1.f; };
constexpr auto event_quit = SDL_QUIT;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_text_editing = SDL_TEXTEDITING;
constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
int has_event = 0;
#endif
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
RMLSDL_WINDOW_EVENTS_BEGIN
case event_window_size_changed:
{
Rml::Vector2i dimensions = {ev.window.data1, ev.window.data2};
data->render_interface->SetViewport(dimensions.x, dimensions.y);
}
break;
RMLSDL_WINDOW_EVENTS_END
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
if (!WaitForValidSwapchain())
result = false;
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface->BeginFrame();
data->render_interface->Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface->EndFrame();
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}

View File

@@ -0,0 +1,353 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#if SDL_MAJOR_VERSION >= 3
#include <SDL3_image/SDL_image.h>
#else
#include <SDL_image.h>
#endif
#if SDL_MAJOR_VERSION == 2 && !(SDL_VIDEO_RENDER_OGL)
#error "Only the OpenGL SDL backend is supported."
#endif
/**
Custom render interface example for the SDL/GL2 backend.
Overloads the OpenGL2 render interface to load textures through SDL_image's built-in texture loading functionality.
*/
class RenderInterface_GL2_SDL : public RenderInterface_GL2 {
public:
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
return {};
file_interface->Seek(file_handle, 0, SEEK_END);
const size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
const size_t i_ext = source.rfind('.');
Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
#if SDL_MAJOR_VERSION >= 3
auto CreateSurface = [&]() { return IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format; };
auto ConvertSurface = [](SDL_Surface* surface, SDL_PixelFormat format) { return SDL_ConvertSurface(surface, format); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_DestroySurface(surface); };
#else
auto CreateSurface = [&]() { return IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format->format; };
auto ConvertSurface = [](SDL_Surface* surface, Uint32 format) { return SDL_ConvertSurfaceFormat(surface, format, 0); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_FreeSurface(surface); };
#endif
SDL_Surface* surface = CreateSurface();
if (!surface)
return {};
texture_dimensions = {surface->w, surface->h};
if (GetSurfaceFormat(surface) != SDL_PIXELFORMAT_RGBA32)
{
// Ensure correct format for premultiplied alpha conversion and GenerateTexture below.
SDL_Surface* converted_surface = ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
DestroySurface(surface);
if (!converted_surface)
return {};
surface = converted_surface;
}
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
const size_t pixels_byte_size = surface->w * surface->h * 4;
byte* pixels = static_cast<byte*>(surface->pixels);
for (size_t i = 0; i < pixels_byte_size; i += 4)
{
const byte alpha = pixels[i + 3];
for (size_t j = 0; j < 3; ++j)
pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
}
Rml::TextureHandle texture_handle = RenderInterface_GL2::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions);
DestroySurface(surface);
return texture_handle;
}
};
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window) : system_interface(window) {}
SystemInterface_SDL system_interface;
RenderInterface_GL2_SDL render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window = nullptr;
SDL_GLContext glcontext = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
#if SDL_MAJOR_VERSION >= 3
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
return false;
#endif
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// Enable MSAA for better-looking visuals, especially when transforms are applied.
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);
#if SDL_MAJOR_VERSION >= 3
auto CreateWindow = [&]() {
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
return window;
};
#else
auto CreateWindow = [&]() {
const Uint32 window_flags = (SDL_WINDOW_OPENGL | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
// SDL2 implicitly activates text input on window creation. Turn it off for now, it will be activated again e.g. when focusing a text input
// field.
SDL_StopTextInput();
return window;
};
// Enable linear filtering, SDL 3 already defaults to it.
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
#endif
SDL_Window* window = CreateWindow();
if (!window)
{
// Try again on low-quality settings.
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
window = CreateWindow();
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
return false;
}
}
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, glcontext);
SDL_GL_SetSwapInterval(1);
data = Rml::MakeUnique<BackendData>(window);
data->window = window;
data->glcontext = glcontext;
data->render_interface.SetViewport(width, height);
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
#if SDL_MAJOR_VERSION >= 3
SDL_GL_DestroyContext(data->glcontext);
#else
SDL_GL_DeleteContext(data->glcontext);
#endif
SDL_DestroyWindow(data->window);
data.reset();
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
#if SDL_MAJOR_VERSION >= 3
#define RMLSDL_WINDOW_EVENTS_BEGIN
#define RMLSDL_WINDOW_EVENTS_END
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
bool has_event = false;
#else
#define RMLSDL_WINDOW_EVENTS_BEGIN \
case SDL_WINDOWEVENT: \
{ \
switch (ev.window.event) \
{
#define RMLSDL_WINDOW_EVENTS_END \
} \
} \
break;
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetDisplayScale = []() { return 1.f; };
constexpr auto event_quit = SDL_QUIT;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_text_editing = SDL_TEXTEDITING;
constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
int has_event = 0;
#endif
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
RMLSDL_WINDOW_EVENTS_BEGIN
case event_window_size_changed:
{
Rml::Vector2i dimensions = {ev.window.data1, ev.window.data2};
data->render_interface.SetViewport(dimensions.x, dimensions.y);
}
break;
RMLSDL_WINDOW_EVENTS_END
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.Clear();
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
SDL_GL_SwapWindow(data->window);
}

View File

@@ -0,0 +1,388 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_GL3.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Profiling.h>
#if SDL_MAJOR_VERSION >= 3
#include <SDL3_image/SDL_image.h>
#else
#include <SDL_image.h>
#endif
#if defined RMLUI_PLATFORM_EMSCRIPTEN
#include <emscripten.h>
#elif SDL_MAJOR_VERSION == 2 && !(SDL_VIDEO_RENDER_OGL)
#error "Only the OpenGL SDL backend is supported."
#endif
/**
Custom render interface example for the SDL/GL3 backend.
Overloads the OpenGL3 render interface to load textures through SDL_image's built-in texture loading functionality.
*/
class RenderInterface_GL3_SDL : public RenderInterface_GL3 {
public:
RenderInterface_GL3_SDL() {}
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
return {};
file_interface->Seek(file_handle, 0, SEEK_END);
const size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
const size_t i_ext = source.rfind('.');
Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
#if SDL_MAJOR_VERSION >= 3
auto CreateSurface = [&]() { return IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format; };
auto ConvertSurface = [](SDL_Surface* surface, SDL_PixelFormat format) { return SDL_ConvertSurface(surface, format); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_DestroySurface(surface); };
#else
auto CreateSurface = [&]() { return IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format->format; };
auto ConvertSurface = [](SDL_Surface* surface, Uint32 format) { return SDL_ConvertSurfaceFormat(surface, format, 0); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_FreeSurface(surface); };
#endif
SDL_Surface* surface = CreateSurface();
if (!surface)
return {};
texture_dimensions = {surface->w, surface->h};
if (GetSurfaceFormat(surface) != SDL_PIXELFORMAT_RGBA32)
{
// Ensure correct format for premultiplied alpha conversion and GenerateTexture below.
SDL_Surface* converted_surface = ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
DestroySurface(surface);
if (!converted_surface)
return {};
surface = converted_surface;
}
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
const size_t pixels_byte_size = surface->w * surface->h * 4;
byte* pixels = static_cast<byte*>(surface->pixels);
for (size_t i = 0; i < pixels_byte_size; i += 4)
{
const byte alpha = pixels[i + 3];
for (size_t j = 0; j < 3; ++j)
pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
}
Rml::TextureHandle texture_handle = RenderInterface_GL3::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions);
DestroySurface(surface);
return texture_handle;
}
};
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window) : system_interface(window) {}
SystemInterface_SDL system_interface;
RenderInterface_GL3_SDL render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window = nullptr;
SDL_GLContext glcontext = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
#if SDL_MAJOR_VERSION >= 3
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
return false;
#endif
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
#if defined(RMLUI_PLATFORM_EMSCRIPTEN)
// GLES 3.0 (WebGL 2.0)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__ANDROID__)
// GLES 3.2 on Android
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
// GL 3.3 Core
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
#endif
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#if SDL_MAJOR_VERSION >= 3
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_OPENGL_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
#else
const Uint32 window_flags = (SDL_WINDOW_OPENGL | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
// SDL2 implicitly activates text input on window creation. Turn it off for now, it will be activated again e.g. when focusing a text input field.
SDL_StopTextInput();
#endif
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
return false;
}
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, glcontext);
SDL_GL_SetSwapInterval(1);
if (!RmlGL3::Initialize())
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL renderer");
return false;
}
data = Rml::MakeUnique<BackendData>(window);
if (!data->render_interface)
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL3 render interface");
return false;
}
data->window = window;
data->glcontext = glcontext;
data->render_interface.SetViewport(width, height);
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
SDL_Window* window = data->window;
SDL_GLContext glcontext = data->glcontext;
data.reset();
#if SDL_MAJOR_VERSION >= 3
SDL_GL_DestroyContext(glcontext);
#else
SDL_GL_DeleteContext(glcontext);
#endif
SDL_DestroyWindow(window);
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
#if defined RMLUI_PLATFORM_EMSCRIPTEN
// Ideally we would hand over control of the main loop to emscripten:
//
// // Hand over control of the main loop to the WebAssembly runtime.
// emscripten_set_main_loop_arg(EventLoopIteration, (void*)user_data_handle, 0, true);
//
// The above is the recommended approach. However, as we don't control the main loop here we have to make due with another approach. Instead, use
// Asyncify to yield by sleeping.
// Important: Must be linked with option -sASYNCIFY
emscripten_sleep(1);
#endif
#if SDL_MAJOR_VERSION >= 3
#define RMLSDL_WINDOW_EVENTS_BEGIN
#define RMLSDL_WINDOW_EVENTS_END
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
bool has_event = false;
#else
#define RMLSDL_WINDOW_EVENTS_BEGIN \
case SDL_WINDOWEVENT: \
{ \
switch (ev.window.event) \
{
#define RMLSDL_WINDOW_EVENTS_END \
} \
} \
break;
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetDisplayScale = []() { return 1.f; };
constexpr auto event_quit = SDL_QUIT;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_text_editing = SDL_TEXTEDITING;
constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
int has_event = 0;
#endif
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
RMLSDL_WINDOW_EVENTS_BEGIN
case event_window_size_changed:
{
Rml::Vector2i dimensions = {ev.window.data1, ev.window.data2};
data->render_interface.SetViewport(dimensions.x, dimensions.y);
}
break;
RMLSDL_WINDOW_EVENTS_END
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.Clear();
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
SDL_GL_SwapWindow(data->window);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}

View File

@@ -0,0 +1,246 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_SDL_GPU.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Log.h>
#ifndef RMLUI_BACKEND_SDL_GPU_DEBUG
#define RMLUI_BACKEND_SDL_GPU_DEBUG false
#endif
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window, SDL_GPUDevice* device) : system_interface(window), render_interface(device, window) {}
SystemInterface_SDL system_interface;
RenderInterface_SDL_GPU render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window = nullptr;
SDL_GPUDevice* device = nullptr;
SDL_GPUCommandBuffer* command_buffer = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO))
return false;
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
return false;
}
props = SDL_CreateProperties();
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, RMLUI_BACKEND_SDL_GPU_DEBUG);
SDL_GPUDevice* device = SDL_CreateGPUDeviceWithProperties(props);
SDL_DestroyProperties(props);
if (!device)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create GPU device: %s", SDL_GetError());
return false;
}
if (!SDL_ClaimWindowForGPUDevice(device, window))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on claiming window for GPU device: %s", SDL_GetError());
return false;
}
data = Rml::MakeUnique<BackendData>(window, device);
data->window = window;
data->device = device;
const char* renderer_name = SDL_GetGPUDeviceDriver(device);
data->system_interface.LogMessage(Rml::Log::LT_INFO, Rml::CreateString("Using SDL device driver: %s", renderer_name));
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
data->render_interface.Shutdown();
SDL_ReleaseWindowFromGPUDevice(data->device, data->window);
SDL_DestroyGPUDevice(data->device);
SDL_DestroyWindow(data->window);
data.reset();
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
bool has_event = false;
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->command_buffer = SDL_AcquireGPUCommandBuffer(data->device);
if (!data->command_buffer)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to acquire command buffer: %s", SDL_GetError());
return;
}
SDL_GPUTexture* swapchain_texture;
uint32_t width;
uint32_t height;
if (!SDL_WaitAndAcquireGPUSwapchainTexture(data->command_buffer, data->window, &swapchain_texture, &width, &height))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to acquire swapchain texture: %s", SDL_GetError());
return;
}
if (!swapchain_texture || !width || !height)
{
// Not an error. Happens on minimize
SDL_CancelGPUCommandBuffer(data->command_buffer);
return;
}
// Do your normal draw operations (make sure you clear the swapchain texture)
SDL_GPUColorTargetInfo color_info{};
color_info.texture = swapchain_texture;
color_info.load_op = SDL_GPU_LOADOP_CLEAR;
color_info.store_op = SDL_GPU_STOREOP_STORE;
SDL_GPURenderPass* render_pass = SDL_BeginGPURenderPass(data->command_buffer, &color_info, 1, nullptr);
SDL_EndGPURenderPass(render_pass);
data->render_interface.BeginFrame(data->command_buffer, swapchain_texture, width, height);
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
SDL_SubmitGPUCommandBuffer(data->command_buffer);
data->command_buffer = nullptr;
}

View File

@@ -0,0 +1,242 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_SDL.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Log.h>
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window, SDL_Renderer* renderer) : system_interface(window), render_interface(renderer) {}
SystemInterface_SDL system_interface;
RenderInterface_SDL render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
#if SDL_MAJOR_VERSION >= 3
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
return false;
#endif
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
#if SDL_MAJOR_VERSION >= 3
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
#else
const Uint32 window_flags = (allow_resize ? SDL_WINDOW_RESIZABLE : 0);
SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
// SDL2 implicitly activates text input on window creation. Turn it off for now, it will be activated again e.g. when focusing a text input field.
SDL_StopTextInput();
#endif
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s\n", SDL_GetError());
return false;
}
// Force a specific SDL renderer
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2");
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d");
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d12");
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
// SDL_SetHint(SDL_HINT_RENDER_DRIVER, "vulkan");
#if SDL_MAJOR_VERSION >= 3
SDL_Renderer* renderer = SDL_CreateRenderer(window, nullptr);
#else
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
#endif
if (!renderer)
return false;
data = Rml::MakeUnique<BackendData>(window, renderer);
data->window = window;
data->renderer = renderer;
const char* renderer_name = nullptr;
Rml::String available_renderers;
#if SDL_MAJOR_VERSION >= 3
SDL_SetRenderVSync(renderer, 1);
renderer_name = SDL_GetRendererName(renderer);
for (int i = 0; i < SDL_GetNumRenderDrivers(); i++)
{
if (i > 0)
available_renderers += ", ";
available_renderers += SDL_GetRenderDriver(i);
}
#else
SDL_RendererInfo renderer_info;
if (SDL_GetRendererInfo(renderer, &renderer_info) == 0)
renderer_name = renderer_info.name;
#endif
if (!available_renderers.empty())
data->system_interface.LogMessage(Rml::Log::LT_INFO, Rml::CreateString("Available SDL renderers: %s", available_renderers.c_str()));
if (renderer_name)
data->system_interface.LogMessage(Rml::Log::LT_INFO, Rml::CreateString("Using SDL renderer: %s", renderer_name));
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
SDL_DestroyRenderer(data->renderer);
SDL_DestroyWindow(data->window);
data.reset();
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
#if SDL_MAJOR_VERSION >= 3
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
bool has_event = false;
#else
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetDisplayScale = []() { return 1.f; };
constexpr auto event_quit = SDL_QUIT;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_text_editing = SDL_TEXTEDITING;
int has_event = 0;
#endif
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
SDL_RenderPresent(data->renderer);
}

View File

@@ -0,0 +1,319 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SDL.h"
#include "RmlUi_Renderer_VK.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#if SDL_MAJOR_VERSION == 2 && !(SDL_VIDEO_VULKAN)
#error "Only the Vulkan SDL backend is supported."
#endif
#if SDL_MAJOR_VERSION >= 3
#include <SDL3/SDL_vulkan.h>
#else
#include <SDL2/SDL_vulkan.h>
#endif
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(SDL_Window* window) : system_interface(window) {}
SystemInterface_SDL system_interface;
RenderInterface_VK render_interface;
TextInputMethodEditor_SDL text_input_method_editor;
SDL_Window* window = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
#if SDL_MAJOR_VERSION >= 3
SDL_SetHint(SDL_HINT_IME_IMPLEMENTED_UI, "composition");
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0)
return false;
#endif
// Submit click events when focusing the window.
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// Touch events are handled natively, no need to generate synthetic mouse events for touch devices.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
#if defined RMLUI_BACKEND_SIMULATE_TOUCH
// Simulate touch events from mouse events for testing touch behavior on a desktop machine.
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "1");
#endif
#if SDL_MAJOR_VERSION >= 3
const float window_size_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetStringProperty(props, SDL_PROP_WINDOW_CREATE_TITLE_STRING, window_name);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_Y_NUMBER, SDL_WINDOWPOS_CENTERED);
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_WIDTH_NUMBER, int(width * window_size_scale));
SDL_SetNumberProperty(props, SDL_PROP_WINDOW_CREATE_HEIGHT_NUMBER, int(height * window_size_scale));
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_VULKAN_BOOLEAN, true);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_RESIZABLE_BOOLEAN, allow_resize);
SDL_SetBooleanProperty(props, SDL_PROP_WINDOW_CREATE_HIGH_PIXEL_DENSITY_BOOLEAN, true);
SDL_Window* window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
auto CreateSurface = [](VkInstance instance, VkSurfaceKHR* out_surface) {
return SDL_Vulkan_CreateSurface(data->window, instance, nullptr, out_surface);
};
#else
const Uint32 window_flags = (SDL_WINDOW_VULKAN | (allow_resize ? SDL_WINDOW_RESIZABLE : 0));
SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags);
// SDL2 implicitly activates text input on window creation. Turn it off for now, it will be activated again e.g. when focusing a text input field.
SDL_StopTextInput();
auto CreateSurface = [](VkInstance instance, VkSurfaceKHR* out_surface) {
return (bool)SDL_Vulkan_CreateSurface(data->window, instance, out_surface);
};
#endif
if (!window)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError());
return false;
}
data = Rml::MakeUnique<BackendData>(window);
data->window = window;
Rml::Vector<const char*> extensions;
{
unsigned int count;
#if SDL_MAJOR_VERSION >= 3
const char* const* extensions_list = SDL_Vulkan_GetInstanceExtensions(&count);
if (!extensions_list)
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions");
return false;
}
extensions.resize(count);
for (unsigned int i = 0; i < count; i++)
extensions[i] = extensions_list[i];
#else
if (!SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr))
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions");
return false;
}
extensions.resize(count);
if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data()))
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions");
return false;
}
#endif
}
if (!data->render_interface.Initialize(std::move(extensions), CreateSurface))
{
data.reset();
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
return false;
}
data->render_interface.SetViewport(width, height);
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
data->render_interface.Shutdown();
SDL_DestroyWindow(data->window);
data.reset();
SDL_Quit();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
static bool WaitForValidSwapchain()
{
#if SDL_MAJOR_VERSION >= 3
constexpr auto event_quit = SDL_EVENT_QUIT;
#else
constexpr auto event_quit = SDL_QUIT;
#endif
bool result = true;
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any render
// calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, we keep the
// application inside this loop until we are able to recreate the swapchain and render again.
while (!data->render_interface.IsSwapchainValid())
{
SDL_Event ev;
while (SDL_PollEvent(&ev))
{
if (ev.type == event_quit)
{
// Restore the window so that we can recreate the swapchain, and then properly release all resource and shutdown cleanly.
SDL_RestoreWindow(data->window);
result = false;
}
}
SDL_Delay(10);
data->render_interface.RecreateSwapchain();
}
return result;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
#if SDL_MAJOR_VERSION >= 3
#define RMLSDL_WINDOW_EVENTS_BEGIN
#define RMLSDL_WINDOW_EVENTS_END
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetDisplayScale = []() { return SDL_GetWindowDisplayScale(data->window); };
constexpr auto event_quit = SDL_EVENT_QUIT;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_text_editing = SDL_EVENT_TEXT_EDITING;
constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
bool has_event = false;
#else
#define RMLSDL_WINDOW_EVENTS_BEGIN \
case SDL_WINDOWEVENT: \
{ \
switch (ev.window.event) \
{
#define RMLSDL_WINDOW_EVENTS_END \
} \
} \
break;
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetDisplayScale = []() { return 1.f; };
constexpr auto event_quit = SDL_QUIT;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_text_editing = SDL_TEXTEDITING;
constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
int has_event = 0;
#endif
bool result = data->running;
data->running = true;
SDL_Event ev;
if (power_save)
has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000));
else
has_event = SDL_PollEvent(&ev);
while (has_event)
{
bool propagate_event = true;
switch (ev.type)
{
case event_quit:
{
propagate_event = false;
result = false;
}
break;
case event_key_down:
{
propagate_event = false;
const Rml::Input::KeyIdentifier key = RmlSDL::ConvertKey(GetKey(ev));
const int key_modifier = RmlSDL::GetKeyModifierState();
const float native_dp_ratio = GetDisplayScale();
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSDL::InputEventHandler(context, data->window, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case event_text_editing:
{
propagate_event = false;
data->text_input_method_editor.HandleEdit(ev.edit);
}
break;
RMLSDL_WINDOW_EVENTS_BEGIN
case event_window_size_changed:
{
Rml::Vector2i dimensions = {ev.window.data1, ev.window.data2};
data->render_interface.SetViewport(dimensions.x, dimensions.y);
}
break;
RMLSDL_WINDOW_EVENTS_END
default: break;
}
if (propagate_event)
RmlSDL::InputEventHandler(context, data->window, ev);
has_event = SDL_PollEvent(&ev);
}
if (!WaitForValidSwapchain())
result = false;
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
}

View File

@@ -0,0 +1,296 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Platform_SFML.h"
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Profiling.h>
#include <RmlUi/Debugger/Debugger.h>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <cstdint>
#if SFML_VERSION_MAJOR >= 3
#define SFML_COORDINATE(x, y) {x, y}
#else
#define SFML_COORDINATE(x, y) x, y
#endif
/**
Custom render interface example for the SFML/GL2 backend.
Overloads the OpenGL2 render interface to load textures through SFML's built-in texture loading functionality.
*/
class RenderInterface_GL2_SFML : public RenderInterface_GL2 {
public:
// -- Inherited from Rml::RenderInterface --
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override
{
if (texture)
{
sf::Texture::bind((sf::Texture*)texture);
texture = RenderInterface_GL2::TextureEnableWithoutBinding;
}
RenderInterface_GL2::RenderGeometry(handle, translation, texture);
}
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
return false;
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
sf::Image image;
if (!image.loadFromMemory(buffer.get(), buffer_size))
return false;
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
for (unsigned int x = 0; x < image.getSize().x; x++)
{
for (unsigned int y = 0; y < image.getSize().y; y++)
{
sf::Color color = image.getPixel(SFML_COORDINATE(x, y));
color.r = static_cast<std::uint8_t>((color.r * color.a) / 255);
color.g = static_cast<std::uint8_t>((color.g * color.a) / 255);
color.b = static_cast<std::uint8_t>((color.b * color.a) / 255);
image.setPixel(SFML_COORDINATE(x, y), color);
}
}
sf::Texture* texture = new sf::Texture();
texture->setSmooth(true);
if (!texture->loadFromImage(image))
{
delete texture;
return false;
}
texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y);
return (Rml::TextureHandle)texture;
}
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions_i) override
{
const auto source_dimensions = Rml::Vector2<unsigned int>(source_dimensions_i);
#if SFML_VERSION_MAJOR >= 3
sf::Texture* texture = new sf::Texture(sf::Vector2u{source_dimensions.x, source_dimensions.y});
#else
sf::Texture* texture = new sf::Texture();
if (!texture->create(source_dimensions.x, source_dimensions.y))
{
delete texture;
return false;
}
#endif
texture->setSmooth(true);
texture->update(source.data(), SFML_COORDINATE(source_dimensions.x, source_dimensions.y), SFML_COORDINATE(0, 0));
return (Rml::TextureHandle)texture;
}
void ReleaseTexture(Rml::TextureHandle texture_handle) override { delete (sf::Texture*)texture_handle; }
};
// Updates the viewport and context dimensions, should be called whenever the window size changes.
static void UpdateWindowDimensions(sf::RenderWindow& window, RenderInterface_GL2_SFML& render_interface, Rml::Context* context)
{
const int width = (int)window.getSize().x;
const int height = (int)window.getSize().y;
if (context)
context->SetDimensions(Rml::Vector2i(width, height));
sf::View view(sf::FloatRect(SFML_COORDINATE(0.f, 0.f), SFML_COORDINATE((float)width, (float)height)));
window.setView(view);
render_interface.SetViewport(width, height);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_SFML system_interface;
RenderInterface_GL2_SFML render_interface;
sf::RenderWindow window;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
data = Rml::MakeUnique<BackendData>();
const std::uint32_t style = (allow_resize ? sf::Style::Default : (sf::Style::Titlebar | sf::Style::Close));
constexpr unsigned int anti_aliasing_level = 2;
// Create the window.
sf::RenderWindow out_window;
sf::ContextSettings context_settings;
context_settings.stencilBits = 8;
#if SFML_VERSION_MAJOR >= 3
context_settings.antiAliasingLevel = anti_aliasing_level;
data->window.create(sf::VideoMode({(unsigned int)width, (unsigned int)height}), window_name, style, sf::State::Windowed, context_settings);
#else
context_settings.antialiasingLevel = anti_aliasing_level;
data->window.create(sf::VideoMode(width, height), window_name, style, context_settings);
#endif
data->window.setVerticalSyncEnabled(true);
if (!data->window.isOpen())
{
data.reset();
return false;
}
// Optionally apply the SFML window to the system interface so that it can change its mouse cursor.
data->system_interface.SetWindow(&data->window);
UpdateWindowDimensions(data->window, data->render_interface, nullptr);
return true;
}
void Backend::Shutdown()
{
data.reset();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// SFML does not seem to provide a way to wait for events with a timeout.
(void)power_save;
// The contents of this function is intended to be copied directly into your main loop.
bool result = data->running;
data->running = true;
auto handle_key_pressed = [&](const sf::Event& ev, sf::Keyboard::Key key_pressed_code) {
const Rml::Input::KeyIdentifier key = RmlSFML::ConvertKey(key_pressed_code);
const int key_modifier = RmlSFML::GetKeyModifierState();
const float native_dp_ratio = 1.f;
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
return;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlSFML::InputHandler(context, ev))
return;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
return;
};
#if SFML_VERSION_MAJOR >= 3
while (const std::optional ev = data->window.pollEvent())
{
if (ev->is<sf::Event::Resized>())
{
UpdateWindowDimensions(data->window, data->render_interface, context);
}
else if (auto key_pressed = ev->getIf<sf::Event::KeyPressed>())
{
handle_key_pressed(*ev, key_pressed->code);
}
else if (ev->is<sf::Event::Closed>())
{
result = false;
}
else
{
RmlSFML::InputHandler(context, *ev);
}
}
#else
sf::Event ev;
while (data->window.pollEvent(ev))
{
switch (ev.type)
{
case sf::Event::Resized: UpdateWindowDimensions(data->window, data->render_interface, context); break;
case sf::Event::KeyPressed: handle_key_pressed(ev, ev.key.code); break;
case sf::Event::Closed: result = false; break;
default: RmlSFML::InputHandler(context, ev); break;
}
}
#endif
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
sf::RenderWindow& window = data->window;
window.resetGLStates();
window.clear();
data->render_interface.BeginFrame();
#if 0
// Draw a simple shape with SFML for demonstration purposes. Make sure to push and pop GL states as appropriate.
sf::Vector2f circle_position(100.f, 100.f);
window.pushGLStates();
sf::CircleShape circle(50.f);
circle.setPosition(circle_position);
circle.setFillColor(sf::Color::Blue);
circle.setOutlineColor(sf::Color::Red);
circle.setOutlineThickness(10.f);
window.draw(circle);
window.popGLStates();
#endif
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
data->window.display();
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}

View File

@@ -0,0 +1,393 @@
#include "RmlUi/Config/Config.h"
#include "RmlUi_Backend.h"
#include "RmlUi_Include_Windows.h"
#include "RmlUi_Platform_Win32.h"
#include "RmlUi_Renderer_DX12.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Profiling.h>
/**
High DPI support using Windows Per Monitor V2 DPI awareness.
Requires Windows 10, version 1703.
*/
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE) - 4)
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
#define FIX_WARNING_UNUSED(code) (void)(code);
// Declare pointers to the DPI aware Windows API functions.
using ProcSetProcessDpiAwarenessContext = BOOL(WINAPI*)(HANDLE value);
using ProcGetDpiForWindow = UINT(WINAPI*)(HWND hwnd);
using ProcAdjustWindowRectExForDpi = BOOL(WINAPI*)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
static bool has_dpi_support = false;
static ProcSetProcessDpiAwarenessContext procSetProcessDpiAwarenessContext = NULL;
static ProcGetDpiForWindow procGetDpiForWindow = NULL;
static ProcAdjustWindowRectExForDpi procAdjustWindowRectExForDpi = NULL;
// Make ourselves DPI aware on supported Windows versions.
static void InitializeDpiSupport()
{
// Cast function pointers to void* first for MinGW not to emit errors.
procSetProcessDpiAwarenessContext =
(ProcSetProcessDpiAwarenessContext)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "SetProcessDpiAwarenessContext");
procGetDpiForWindow = (ProcGetDpiForWindow)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "GetDpiForWindow");
procAdjustWindowRectExForDpi =
(ProcAdjustWindowRectExForDpi)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "AdjustWindowRectExForDpi");
if (!has_dpi_support && procSetProcessDpiAwarenessContext != NULL && procGetDpiForWindow != NULL && procAdjustWindowRectExForDpi != NULL)
{
// Activate Per Monitor V2.
if (procSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
has_dpi_support = true;
}
}
static UINT GetWindowDpi(HWND window_handle)
{
if (has_dpi_support)
{
UINT dpi = procGetDpiForWindow(window_handle);
if (dpi != 0)
return dpi;
}
return USER_DEFAULT_SCREEN_DPI;
}
static float GetDensityIndependentPixelRatio(HWND window_handle)
{
return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI);
}
// Create the window but don't show it yet. Returns the pixel size of the window, which may be different from the passed size due to DPI settings.
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize);
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_Win32 system_interface;
Rml::UniquePtr<RenderInterface_DX12> render_interface;
TextInputMethodEditor_Win32 text_input_method_editor;
HINSTANCE instance_handle = nullptr;
std::wstring instance_name;
HWND window_handle = nullptr;
bool context_dimensions_dirty = true;
Rml::Vector2i window_dimensions;
bool running = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
const std::wstring name = RmlWin32::ConvertToUTF16(Rml::String(window_name));
data = Rml::MakeUnique<BackendData>();
data->instance_handle = GetModuleHandle(nullptr);
data->instance_name = name;
InitializeDpiSupport();
// Initialize the window but don't show it yet.
HWND window_handle = InitializeWindow(data->instance_handle, name, width, height, allow_resize);
if (!window_handle)
return false;
data->window_handle = window_handle;
RmlRendererSettings settings = {};
settings.vsync = true;
settings.msaa_sample_count = RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT;
Rml::String initialization_message;
data->render_interface = Rml::MakeUnique<RenderInterface_DX12>(window_handle, settings);
if (!data->render_interface)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not initialize DirectX 12 render interface: %s", initialization_message.c_str());
::CloseWindow(window_handle);
data.reset();
return false;
}
data->system_interface.SetWindow(window_handle);
data->render_interface->SetViewport(width, height);
// Now we are ready to show the window.
::ShowWindow(window_handle, SW_SHOW);
::SetForegroundWindow(window_handle);
::SetFocus(window_handle);
// Provide a backend-specific text input handler to manage the IME.
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
// As we forcefully override the global text input handler, we must reset it before the data is destroyed to avoid any potential use-after-free.
if (Rml::GetTextInputHandler() == &data->text_input_method_editor)
Rml::SetTextInputHandler(nullptr);
if (data->render_interface)
{
data->render_interface.reset();
::DestroyWindow(data->window_handle);
::UnregisterClassW((LPCWSTR)data->instance_name.data(), data->instance_handle);
}
data.reset();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return data->render_interface.get();
}
static bool NextEvent(MSG& message, UINT timeout)
{
if (timeout != 0)
{
UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL);
BOOL res = GetMessage(&message, NULL, 0, 0);
KillTimer(NULL, timer_id);
if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
return res;
}
return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE);
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
const float dp_ratio = GetDensityIndependentPixelRatio(data->window_handle);
context->SetDimensions(data->window_dimensions);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
MSG message;
// Process events.
bool has_message = NextEvent(message, power_save ? static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000.0) : 0);
while (has_message || !data->render_interface->IsSwapchainValid())
{
if (has_message)
{
// Dispatch the message to our local event handler below.
TranslateMessage(&message);
DispatchMessage(&message);
}
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any
// render calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead,
// we trap the application inside this loop until we are able to recreate the swapchain and render again.
if (!data->render_interface->IsSwapchainValid())
data->render_interface->RecreateSwapchain();
has_message = NextEvent(message, 0);
}
data->context = nullptr;
data->key_down_callback = nullptr;
return data->running;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
RMLUI_ASSERT(data->render_interface);
if (data->render_interface)
{
data->render_interface->BeginFrame();
data->render_interface->Clear();
}
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
RMLUI_ASSERT(data->render_interface);
data->render_interface->EndFrame();
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
// Local event handler for window and input events.
static LRESULT CALLBACK WindowProcedureHandler(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
RMLUI_ASSERT(data);
switch (message)
{
case WM_CLOSE:
{
data->running = false;
return 0;
}
break;
case WM_SIZE:
{
const int width = LOWORD(l_param);
const int height = HIWORD(l_param);
data->window_dimensions.x = width;
data->window_dimensions.y = height;
if (data->context)
{
data->render_interface->SetViewport(width, height);
data->context->SetDimensions(data->window_dimensions);
}
return 0;
}
break;
case WM_DPICHANGED:
{
RECT* new_pos = (RECT*)l_param;
SetWindowPos(window_handle, NULL, new_pos->left, new_pos->top, new_pos->right - new_pos->left, new_pos->bottom - new_pos->top,
SWP_NOZORDER | SWP_NOACTIVATE);
if (data->context && has_dpi_support)
data->context->SetDensityIndependentPixelRatio(GetDensityIndependentPixelRatio(window_handle));
return 0;
}
break;
case WM_KEYDOWN:
{
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
const Rml::Input::KeyIdentifier rml_key = RmlWin32::ConvertKey((int)w_param);
const int rml_modifier = RmlWin32::GetKeyModifierState();
const float native_dp_ratio = GetDensityIndependentPixelRatio(window_handle);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, true))
return 0;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlWin32::WindowProcedure(context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, false))
return 0;
return 0;
}
break;
default:
{
// Submit it to the platform handler for default input handling.
if (!RmlWin32::WindowProcedure(data->context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
}
break;
}
// All unhandled messages go to DefWindowProc.
return DefWindowProc(window_handle, message, w_param, l_param);
}
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize)
{
WNDCLASSW window_class;
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = &WindowProcedureHandler; // Attach our local event handler.
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance_handle;
window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.hbrBackground = nullptr;
window_class.lpszMenuName = nullptr;
window_class.lpszClassName = name.data();
if (!RegisterClassW(&window_class))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class");
return nullptr;
}
HWND window_handle = CreateWindowExW(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
name.data(), // Window class name.
name.data(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 0, 0, // Window position.
0, 0, // Window size.
nullptr, nullptr, instance_handle, nullptr);
if (!window_handle)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window");
return nullptr;
}
UINT window_dpi = GetWindowDpi(window_handle);
inout_width = (inout_width * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
inout_height = (inout_height * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX));
DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Adjust the window size to take the edges into account.
RECT window_rect;
window_rect.top = 0;
window_rect.left = 0;
window_rect.right = inout_width;
window_rect.bottom = inout_height;
if (has_dpi_support)
procAdjustWindowRectExForDpi(&window_rect, style, FALSE, extended_style, window_dpi);
else
AdjustWindowRectEx(&window_rect, style, FALSE, extended_style);
SetWindowLong(window_handle, GWL_EXSTYLE, extended_style);
SetWindowLong(window_handle, GWL_STYLE, style);
// Resize the window and center it on the screen.
Rml::Vector2i screen_size = {GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
Rml::Vector2i window_size = {int(window_rect.right - window_rect.left), int(window_rect.bottom - window_rect.top)};
Rml::Vector2i window_pos = Rml::Math::Max((screen_size - window_size) / 2, Rml::Vector2i(0));
SetWindowPos(window_handle, HWND_TOP, window_pos.x, window_pos.y, window_size.x, window_size.y, SWP_NOACTIVATE);
return window_handle;
}

View File

@@ -0,0 +1,444 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Include_Windows.h"
#include "RmlUi_Platform_Win32.h"
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Profiling.h>
/**
High DPI support using Windows Per Monitor V2 DPI awareness.
Requires Windows 10, version 1703.
*/
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE)-4)
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
// Declare pointers to the DPI aware Windows API functions.
using ProcSetProcessDpiAwarenessContext = BOOL(WINAPI*)(HANDLE value);
using ProcGetDpiForWindow = UINT(WINAPI*)(HWND hwnd);
using ProcAdjustWindowRectExForDpi = BOOL(WINAPI*)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
static bool has_dpi_support = false;
static ProcSetProcessDpiAwarenessContext procSetProcessDpiAwarenessContext = NULL;
static ProcGetDpiForWindow procGetDpiForWindow = NULL;
static ProcAdjustWindowRectExForDpi procAdjustWindowRectExForDpi = NULL;
// Make ourselves DPI aware on supported Windows versions.
static void InitializeDpiSupport()
{
// Cast function pointers to void* first for MinGW not to emit errors.
procSetProcessDpiAwarenessContext =
(ProcSetProcessDpiAwarenessContext)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "SetProcessDpiAwarenessContext");
procGetDpiForWindow = (ProcGetDpiForWindow)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "GetDpiForWindow");
procAdjustWindowRectExForDpi =
(ProcAdjustWindowRectExForDpi)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "AdjustWindowRectExForDpi");
if (!has_dpi_support && procSetProcessDpiAwarenessContext != NULL && procGetDpiForWindow != NULL && procAdjustWindowRectExForDpi != NULL)
{
// Activate Per Monitor V2.
if (procSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
has_dpi_support = true;
}
}
static UINT GetWindowDpi(HWND window_handle)
{
if (has_dpi_support)
{
UINT dpi = procGetDpiForWindow(window_handle);
if (dpi != 0)
return dpi;
}
return USER_DEFAULT_SCREEN_DPI;
}
static float GetDensityIndependentPixelRatio(HWND window_handle)
{
return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI);
}
// Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings.
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize);
// Attach the OpenGL context.
static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& out_render_context);
// Detach the OpenGL context.
static void DetachFromNative(HWND window_handle, HDC device_context, HGLRC render_context);
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_Win32 system_interface;
RenderInterface_GL2 render_interface;
TextInputMethodEditor_Win32 text_input_method_editor;
HINSTANCE instance_handle = nullptr;
std::wstring instance_name;
HWND window_handle = nullptr;
HDC device_context = nullptr;
HGLRC render_context = nullptr;
bool context_dimensions_dirty = true;
Rml::Vector2i window_dimensions;
bool running = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
const std::wstring name = RmlWin32::ConvertToUTF16(Rml::String(window_name));
data = Rml::MakeUnique<BackendData>();
data->instance_handle = GetModuleHandle(nullptr);
data->instance_name = name;
InitializeDpiSupport();
// Initialize the window but don't show it yet.
HWND window_handle = InitializeWindow(data->instance_handle, name, width, height, allow_resize);
if (!window_handle)
return false;
// Attach the OpenGL context.
if (!AttachToNative(window_handle, data->device_context, data->render_context))
{
::CloseWindow(window_handle);
return false;
}
data->window_handle = window_handle;
data->system_interface.SetWindow(window_handle);
// Now we are ready to show the window.
::ShowWindow(window_handle, SW_SHOW);
::SetForegroundWindow(window_handle);
::SetFocus(window_handle);
// Provide a backend-specific text input handler to manage the IME.
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
// As we forcefully override the global text input handler, we must reset it before the data is destroyed to avoid any potential use-after-free.
if (Rml::GetTextInputHandler() == &data->text_input_method_editor)
Rml::SetTextInputHandler(nullptr);
DetachFromNative(data->window_handle, data->device_context, data->render_context);
::DestroyWindow(data->window_handle);
::UnregisterClassW((LPCWSTR)data->instance_name.data(), data->instance_handle);
data.reset();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
static bool NextEvent(MSG& message, UINT timeout)
{
if (timeout != 0)
{
UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL);
BOOL res = GetMessage(&message, NULL, 0, 0);
KillTimer(NULL, timer_id);
if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
return res;
}
return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE);
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
const float dp_ratio = GetDensityIndependentPixelRatio(data->window_handle);
context->SetDimensions(data->window_dimensions);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
MSG message;
bool has_message = NextEvent(message, power_save ? static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000.0) : 0);
while (has_message)
{
// Dispatch the message to our local event handler below.
TranslateMessage(&message);
DispatchMessage(&message);
has_message = NextEvent(message, 0);
}
data->context = nullptr;
data->key_down_callback = nullptr;
const bool result = data->running;
data->running = true;
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
data->render_interface.Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
// Flips the OpenGL buffers.
SwapBuffers(data->device_context);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
// Local event handler for window and input events.
static LRESULT CALLBACK WindowProcedureHandler(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
RMLUI_ASSERT(data);
switch (message)
{
case WM_CLOSE:
{
data->running = false;
return 0;
}
break;
case WM_SIZE:
{
const int width = LOWORD(l_param);
const int height = HIWORD(l_param);
data->window_dimensions.x = width;
data->window_dimensions.y = height;
data->render_interface.SetViewport(width, height);
if (data->context)
data->context->SetDimensions(data->window_dimensions);
return 0;
}
break;
case WM_DPICHANGED:
{
RECT* new_pos = (RECT*)l_param;
SetWindowPos(window_handle, NULL, new_pos->left, new_pos->top, new_pos->right - new_pos->left, new_pos->bottom - new_pos->top,
SWP_NOZORDER | SWP_NOACTIVATE);
if (data->context && has_dpi_support)
data->context->SetDensityIndependentPixelRatio(GetDensityIndependentPixelRatio(window_handle));
return 0;
}
break;
case WM_KEYDOWN:
{
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
const Rml::Input::KeyIdentifier rml_key = RmlWin32::ConvertKey((int)w_param);
const int rml_modifier = RmlWin32::GetKeyModifierState();
const float native_dp_ratio = GetDensityIndependentPixelRatio(window_handle);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, true))
return 0;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlWin32::WindowProcedure(context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, false))
return 0;
return 0;
}
break;
default:
{
// Submit it to the platform handler for default input handling.
if (!RmlWin32::WindowProcedure(data->context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
}
break;
}
// All unhandled messages go to DefWindowProc.
return DefWindowProc(window_handle, message, w_param, l_param);
}
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize)
{
// Fill out the window class struct.
WNDCLASSW window_class;
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = &WindowProcedureHandler; // Attach our local event handler.
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance_handle;
window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.hbrBackground = nullptr;
window_class.lpszMenuName = nullptr;
window_class.lpszClassName = name.data();
if (!RegisterClassW(&window_class))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class");
return nullptr;
}
HWND window_handle = CreateWindowExW(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
name.data(), // Window class name.
name.data(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 0, 0, // Window position.
0, 0, // Window size.
nullptr, nullptr, instance_handle, nullptr);
if (!window_handle)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window");
return nullptr;
}
UINT window_dpi = GetWindowDpi(window_handle);
inout_width = (inout_width * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
inout_height = (inout_height * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX));
DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Adjust the window size to take the edges into account.
RECT window_rect;
window_rect.top = 0;
window_rect.left = 0;
window_rect.right = inout_width;
window_rect.bottom = inout_height;
if (has_dpi_support)
procAdjustWindowRectExForDpi(&window_rect, style, FALSE, extended_style, window_dpi);
else
AdjustWindowRectEx(&window_rect, style, FALSE, extended_style);
SetWindowLong(window_handle, GWL_EXSTYLE, extended_style);
SetWindowLong(window_handle, GWL_STYLE, style);
// Resize the window and center it on the screen.
Rml::Vector2i screen_size = {GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
Rml::Vector2i window_size = {int(window_rect.right - window_rect.left), int(window_rect.bottom - window_rect.top)};
Rml::Vector2i window_pos = Rml::Math::Max((screen_size - window_size) / 2, Rml::Vector2i(0));
SetWindowPos(window_handle, HWND_TOP, window_pos.x, window_pos.y, window_size.x, window_size.y, SWP_NOACTIVATE);
return window_handle;
}
static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& out_render_context)
{
HDC device_context = GetDC(window_handle);
if (!device_context)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get device context");
return false;
}
PIXELFORMATDESCRIPTOR pixel_format_descriptor = {};
pixel_format_descriptor.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pixel_format_descriptor.nVersion = 1;
pixel_format_descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pixel_format_descriptor.iPixelType = PFD_TYPE_RGBA;
pixel_format_descriptor.cColorBits = 32;
pixel_format_descriptor.cRedBits = 8;
pixel_format_descriptor.cGreenBits = 8;
pixel_format_descriptor.cBlueBits = 8;
pixel_format_descriptor.cAlphaBits = 8;
pixel_format_descriptor.cDepthBits = 24;
pixel_format_descriptor.cStencilBits = 8;
int pixel_format = ChoosePixelFormat(device_context, &pixel_format_descriptor);
if (!pixel_format)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to choose 32-bit pixel format");
return false;
}
if (!SetPixelFormat(device_context, pixel_format, &pixel_format_descriptor))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to set pixel format");
return false;
}
HGLRC render_context = wglCreateContext(device_context);
if (!render_context)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create OpenGL rendering context");
return false;
}
// Activate the rendering context.
if (!wglMakeCurrent(device_context, render_context))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to make rendering context current");
return false;
}
out_device_context = device_context;
out_render_context = render_context;
return true;
}
static void DetachFromNative(HWND window_handle, HDC device_context, HGLRC render_context)
{
// Shutdown OpenGL
if (render_context)
{
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(render_context);
}
if (device_context)
{
ReleaseDC(window_handle, device_context);
}
}

View File

@@ -0,0 +1,395 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Include_Windows.h"
#include "RmlUi_Platform_Win32.h"
#include "RmlUi_Renderer_VK.h"
#include <RmlUi/Config/Config.h>
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Profiling.h>
/**
High DPI support using Windows Per Monitor V2 DPI awareness.
Requires Windows 10, version 1703.
*/
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE)-4)
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif
// Declare pointers to the DPI aware Windows API functions.
using ProcSetProcessDpiAwarenessContext = BOOL(WINAPI*)(HANDLE value);
using ProcGetDpiForWindow = UINT(WINAPI*)(HWND hwnd);
using ProcAdjustWindowRectExForDpi = BOOL(WINAPI*)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi);
static bool has_dpi_support = false;
static ProcSetProcessDpiAwarenessContext procSetProcessDpiAwarenessContext = NULL;
static ProcGetDpiForWindow procGetDpiForWindow = NULL;
static ProcAdjustWindowRectExForDpi procAdjustWindowRectExForDpi = NULL;
// Make ourselves DPI aware on supported Windows versions.
static void InitializeDpiSupport()
{
// Cast function pointers to void* first for MinGW not to emit errors.
procSetProcessDpiAwarenessContext =
(ProcSetProcessDpiAwarenessContext)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "SetProcessDpiAwarenessContext");
procGetDpiForWindow = (ProcGetDpiForWindow)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "GetDpiForWindow");
procAdjustWindowRectExForDpi =
(ProcAdjustWindowRectExForDpi)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "AdjustWindowRectExForDpi");
if (!has_dpi_support && procSetProcessDpiAwarenessContext != NULL && procGetDpiForWindow != NULL && procAdjustWindowRectExForDpi != NULL)
{
// Activate Per Monitor V2.
if (procSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
has_dpi_support = true;
}
}
static UINT GetWindowDpi(HWND window_handle)
{
if (has_dpi_support)
{
UINT dpi = procGetDpiForWindow(window_handle);
if (dpi != 0)
return dpi;
}
return USER_DEFAULT_SCREEN_DPI;
}
static float GetDensityIndependentPixelRatio(HWND window_handle)
{
return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI);
}
// Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings.
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize);
// Create the Win32 Vulkan surface.
static bool CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* out_surface);
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_Win32 system_interface;
RenderInterface_VK render_interface;
TextInputMethodEditor_Win32 text_input_method_editor;
HINSTANCE instance_handle = nullptr;
std::wstring instance_name;
HWND window_handle = nullptr;
bool context_dimensions_dirty = true;
Rml::Vector2i window_dimensions;
bool running = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
const std::wstring name = RmlWin32::ConvertToUTF16(Rml::String(window_name));
data = Rml::MakeUnique<BackendData>();
data->instance_handle = GetModuleHandle(nullptr);
data->instance_name = name;
InitializeDpiSupport();
// Initialize the window but don't show it yet.
HWND window_handle = InitializeWindow(data->instance_handle, name, width, height, allow_resize);
if (!window_handle)
return false;
data->window_handle = window_handle;
Rml::Vector<const char*> extensions;
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
if (!data->render_interface.Initialize(std::move(extensions), CreateVulkanSurface))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface");
::CloseWindow(window_handle);
data.reset();
return false;
}
data->system_interface.SetWindow(window_handle);
data->render_interface.SetViewport(width, height);
// Now we are ready to show the window.
::ShowWindow(window_handle, SW_SHOW);
::SetForegroundWindow(window_handle);
::SetFocus(window_handle);
// Provide a backend-specific text input handler to manage the IME.
Rml::SetTextInputHandler(&data->text_input_method_editor);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
// As we forcefully override the global text input handler, we must reset it before the data is destroyed to avoid any potential use-after-free.
if (Rml::GetTextInputHandler() == &data->text_input_method_editor)
Rml::SetTextInputHandler(nullptr);
data->render_interface.Shutdown();
::DestroyWindow(data->window_handle);
::UnregisterClassW((LPCWSTR)data->instance_name.data(), data->instance_handle);
data.reset();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
static bool NextEvent(MSG& message, UINT timeout)
{
if (timeout != 0)
{
UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL);
BOOL res = GetMessage(&message, NULL, 0, 0);
KillTimer(NULL, timer_id);
if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
return res;
}
return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE);
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
const float dp_ratio = GetDensityIndependentPixelRatio(data->window_handle);
context->SetDimensions(data->window_dimensions);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
MSG message;
// Process events.
bool has_message = NextEvent(message, power_save ? static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000.0) : 0);
while (has_message || !data->render_interface.IsSwapchainValid())
{
if (has_message)
{
// Dispatch the message to our local event handler below.
TranslateMessage(&message);
DispatchMessage(&message);
}
// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any
// render calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead,
// we trap the application inside this loop until we are able to recreate the swapchain and render again.
if (!data->render_interface.IsSwapchainValid())
data->render_interface.RecreateSwapchain();
has_message = NextEvent(message, 0);
}
data->context = nullptr;
data->key_down_callback = nullptr;
return data->running;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
// Local event handler for window and input events.
static LRESULT CALLBACK WindowProcedureHandler(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
RMLUI_ASSERT(data);
switch (message)
{
case WM_CLOSE:
{
data->running = false;
return 0;
}
break;
case WM_SIZE:
{
const int width = LOWORD(l_param);
const int height = HIWORD(l_param);
data->window_dimensions.x = width;
data->window_dimensions.y = height;
if (data->context)
{
data->render_interface.SetViewport(width, height);
data->context->SetDimensions(data->window_dimensions);
}
return 0;
}
break;
case WM_DPICHANGED:
{
RECT* new_pos = (RECT*)l_param;
SetWindowPos(window_handle, NULL, new_pos->left, new_pos->top, new_pos->right - new_pos->left, new_pos->bottom - new_pos->top,
SWP_NOZORDER | SWP_NOACTIVATE);
if (data->context && has_dpi_support)
data->context->SetDensityIndependentPixelRatio(GetDensityIndependentPixelRatio(window_handle));
return 0;
}
break;
case WM_KEYDOWN:
{
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
const Rml::Input::KeyIdentifier rml_key = RmlWin32::ConvertKey((int)w_param);
const int rml_modifier = RmlWin32::GetKeyModifierState();
const float native_dp_ratio = GetDensityIndependentPixelRatio(window_handle);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, true))
return 0;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlWin32::WindowProcedure(context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, false))
return 0;
return 0;
}
break;
default:
{
// Submit it to the platform handler for default input handling.
if (!RmlWin32::WindowProcedure(data->context, data->text_input_method_editor, window_handle, message, w_param, l_param))
return 0;
}
break;
}
// All unhandled messages go to DefWindowProc.
return DefWindowProc(window_handle, message, w_param, l_param);
}
static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize)
{
// Fill out the window class struct.
WNDCLASSW window_class;
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = &WindowProcedureHandler; // Attach our local event handler.
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance_handle;
window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.hbrBackground = nullptr;
window_class.lpszMenuName = nullptr;
window_class.lpszClassName = name.data();
if (!RegisterClassW(&window_class))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class");
return nullptr;
}
HWND window_handle = CreateWindowExW(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
name.data(), // Window class name.
name.data(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 0, 0, // Window position.
0, 0, // Window size.
nullptr, nullptr, instance_handle, nullptr);
if (!window_handle)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window");
return nullptr;
}
UINT window_dpi = GetWindowDpi(window_handle);
inout_width = (inout_width * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
inout_height = (inout_height * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI;
DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX));
DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Adjust the window size to take the edges into account.
RECT window_rect;
window_rect.top = 0;
window_rect.left = 0;
window_rect.right = inout_width;
window_rect.bottom = inout_height;
if (has_dpi_support)
procAdjustWindowRectExForDpi(&window_rect, style, FALSE, extended_style, window_dpi);
else
AdjustWindowRectEx(&window_rect, style, FALSE, extended_style);
SetWindowLong(window_handle, GWL_EXSTYLE, extended_style);
SetWindowLong(window_handle, GWL_STYLE, style);
// Resize the window and center it on the screen.
Rml::Vector2i screen_size = {GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
Rml::Vector2i window_size = {int(window_rect.right - window_rect.left), int(window_rect.bottom - window_rect.top)};
Rml::Vector2i window_pos = Rml::Math::Max((screen_size - window_size) / 2, Rml::Vector2i(0));
SetWindowPos(window_handle, HWND_TOP, window_pos.x, window_pos.y, window_size.x, window_size.y, SWP_NOACTIVATE);
return window_handle;
}
bool CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* out_surface)
{
VkWin32SurfaceCreateInfoKHR info = {};
info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
info.hinstance = GetModuleHandle(NULL);
info.hwnd = data->window_handle;
VkResult status = vkCreateWin32SurfaceKHR(instance, &info, nullptr, out_surface);
bool result = (status == VK_SUCCESS);
RMLUI_VK_ASSERTMSG(result, "Failed to create Win32 Vulkan surface");
return result;
}

View File

@@ -0,0 +1,292 @@
#include "RmlUi_Backend.h"
#include "RmlUi_Include_Xlib.h"
#include "RmlUi_Platform_X11.h"
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Debug.h>
#include <RmlUi/Core/Log.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/extensions/xf86vmode.h>
#include <cmath>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
// Attach the OpenGL context to the window.
static bool AttachToNative(GLXContext& out_gl_context, Display* display, Window window, XVisualInfo* visual_info)
{
GLXContext gl_context = glXCreateContext(display, visual_info, nullptr, GL_TRUE);
if (!gl_context)
return false;
if (!glXMakeCurrent(display, window, gl_context))
return false;
if (!glXIsDirect(display, gl_context))
Rml::Log::Message(Rml::Log::LT_INFO, "OpenGL context does not support direct rendering; performance is likely to be poor.");
out_gl_context = gl_context;
Window root_window;
int x, y;
unsigned int width, height;
unsigned int border_width, depth;
XGetGeometry(display, window, &root_window, &x, &y, &width, &height, &border_width, &depth);
return true;
}
// Shutdown the OpenGL context.
static void DetachFromNative(GLXContext gl_context, Display* display)
{
glXMakeCurrent(display, 0L, nullptr);
glXDestroyContext(display, gl_context);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
BackendData(Display* display) : system_interface(display) {}
SystemInterface_X11 system_interface;
RenderInterface_GL2 render_interface;
Display* display = nullptr;
Window window = 0;
GLXContext gl_context = nullptr;
bool running = true;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
Display* display = XOpenDisplay(0);
if (!display)
return false;
int screen = XDefaultScreen(display);
// Fetch an appropriate 32-bit visual interface.
int attribute_list[] = {GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 24, GLX_STENCIL_SIZE, 8,
0L};
XVisualInfo* visual_info = glXChooseVisual(display, screen, attribute_list);
if (!visual_info)
return false;
// Build up our window attributes.
XSetWindowAttributes window_attributes;
window_attributes.colormap = XCreateColormap(display, RootWindow(display, visual_info->screen), visual_info->visual, AllocNone);
window_attributes.border_pixel = 0;
window_attributes.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask;
// Create the window.
Window window = XCreateWindow(display, RootWindow(display, visual_info->screen), 0, 0, width, height, 0, visual_info->depth, InputOutput,
visual_info->visual, CWBorderPixel | CWColormap | CWEventMask, &window_attributes);
// Handle delete events in windowed mode.
Atom delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", True);
XSetWMProtocols(display, window, &delete_atom, 1);
// Capture the events we're interested in.
XSelectInput(display, window,
KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask);
if (!allow_resize)
{
// Force the window to remain at the fixed size by asking the window manager nicely, it may choose to ignore us
XSizeHints* win_size_hints = XAllocSizeHints(); // Allocate a size hint structure
if (!win_size_hints)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "XAllocSizeHints - out of memory");
}
else
{
// Initialize the structure and specify which hints will be providing
win_size_hints->flags = PSize | PMinSize | PMaxSize;
// Set the sizes we want the window manager to use
win_size_hints->base_width = width;
win_size_hints->base_height = height;
win_size_hints->min_width = width;
win_size_hints->min_height = height;
win_size_hints->max_width = width;
win_size_hints->max_height = height;
// Pass the size hints to the window manager.
XSetWMNormalHints(display, window, win_size_hints);
// Free the size buffer
XFree(win_size_hints);
}
}
// Set the window title and show the window.
XSetStandardProperties(display, window, window_name, "", 0L, nullptr, 0, nullptr);
XMapRaised(display, window);
GLXContext gl_context = {};
if (!AttachToNative(gl_context, display, window, visual_info))
return false;
data = Rml::MakeUnique<BackendData>(display);
data->display = display;
data->window = window;
data->gl_context = gl_context;
data->system_interface.SetWindow(window);
data->render_interface.SetViewport(width, height);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
DetachFromNative(data->gl_context, data->display);
XCloseDisplay(data->display);
data.reset();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return &data->render_interface;
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
Display* display = data->display;
bool result = data->running;
data->running = true;
if (power_save && XPending(display) == 0)
{
int display_fd = ConnectionNumber(display);
fd_set fds{};
FD_ZERO(&fds);
FD_SET(display_fd, &fds);
double timeout = Rml::Math::Min(context->GetNextUpdateDelay(), 10.0);
struct timeval tv {};
double seconds;
tv.tv_usec = std::modf(timeout, &seconds) * 1000000.0;
tv.tv_sec = seconds;
int ready_fd_count;
do
{
ready_fd_count = select(display_fd + 1, &fds, NULL, NULL, &tv);
// We don't care about the return value as long as select didn't error out
RMLUI_ASSERT(ready_fd_count >= 0);
} while (XPending(display) == 0 && ready_fd_count != 0);
}
while (XPending(display) > 0)
{
XEvent ev;
XNextEvent(display, &ev);
switch (ev.type)
{
case ClientMessage:
{
// The only message we register for is WM_DELETE_WINDOW, so if we receive a client message then the window has been closed.
char* event_type = XGetAtomName(display, ev.xclient.message_type);
if (strcmp(event_type, "WM_PROTOCOLS") == 0)
data->running = false;
XFree(event_type);
event_type = nullptr;
}
break;
case ConfigureNotify:
{
int x = ev.xconfigure.width;
int y = ev.xconfigure.height;
context->SetDimensions({x, y});
data->render_interface.SetViewport(x, y);
}
break;
case KeyPress:
{
Rml::Input::KeyIdentifier key = RmlX11::ConvertKey(display, ev.xkey.keycode);
const int key_modifier = RmlX11::GetKeyModifierState(ev.xkey.state);
const float native_dp_ratio = 1.f;
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlX11::HandleInputEvent(context, display, ev))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, native_dp_ratio, false))
break;
}
break;
case SelectionRequest:
{
data->system_interface.HandleSelectionRequest(ev);
}
break;
default:
{
// Pass unhandled events to the platform layer's input handler.
RmlX11::HandleInputEvent(context, display, ev);
}
break;
}
}
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
data->running = false;
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
data->render_interface.Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
// Flips the OpenGL buffers.
glXSwapBuffers(data->display, data->window);
}

View File

@@ -0,0 +1,226 @@
#include "../RmlUi_Backend.h"
#include "../RmlUi_Platform_GLFW.h"
#include "RmlUi_Renderer_BackwardCompatible_GL2.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Profiling.h>
#include <GLFW/glfw3.h>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_GLFW system_interface;
RenderInterface_BackwardCompatible_GL2 render_interface;
GLFWwindow* window = nullptr;
int glfw_active_modifiers = 0;
bool context_dimensions_dirty = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
return false;
// Set window hints for OpenGL 2 context creation.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
glfwWindowHint(GLFW_STENCIL_BITS, 8);
// Enable MSAA for better-looking visuals, especially when transforms are applied.
glfwWindowHint(GLFW_SAMPLES, 2);
// Apply window properties and create it.
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
return false;
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
data = Rml::MakeUnique<BackendData>();
data->window = window;
data->system_interface.SetWindow(window);
// The window size may have been scaled by DPI settings, get the actual pixel size.
glfwGetFramebufferSize(window, &width, &height);
data->render_interface.SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
// Setup the input and window event callback functions.
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
glfwDestroyWindow(data->window);
data.reset();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return data->render_interface.GetAdaptedInterface();
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
data->context = nullptr;
data->key_down_callback = nullptr;
const bool result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
data->render_interface.Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
glfwSwapBuffers(data->window);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface.SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,240 @@
#include "../RmlUi_Backend.h"
#include "../RmlUi_Platform_GLFW.h"
#include "RmlUi_Renderer_BackwardCompatible_GL3.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Profiling.h>
#include <GLFW/glfw3.h>
static void SetupCallbacks(GLFWwindow* window);
static void LogErrorFromGLFW(int error, const char* description)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description);
}
/**
Global data used by this backend.
Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown().
*/
struct BackendData {
SystemInterface_GLFW system_interface;
RenderInterface_BackwardCompatible_GL3 render_interface;
GLFWwindow* window = nullptr;
int glfw_active_modifiers = 0;
bool context_dimensions_dirty = true;
// Arguments set during event processing and nulled otherwise.
Rml::Context* context = nullptr;
KeyDownCallback key_down_callback = nullptr;
};
static Rml::UniquePtr<BackendData> data;
bool Backend::Initialize(const char* name, int width, int height, bool allow_resize)
{
RMLUI_ASSERT(!data);
glfwSetErrorCallback(LogErrorFromGLFW);
if (!glfwInit())
return false;
// Set window hints for OpenGL 3.3 Core context creation.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
// Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements.
glfwWindowHint(GLFW_STENCIL_BITS, 8);
// Enable MSAA for better-looking visuals, especially when transforms are applied.
glfwWindowHint(GLFW_SAMPLES, 2);
// Apply window properties and create it.
glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE);
glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr);
if (!window)
return false;
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
// Load the OpenGL functions.
Rml::String renderer_message;
if (!RmlGL3::Initialize(&renderer_message))
return false;
// Construct the system and render interface, this includes compiling all the shaders. If this fails, it is likely an error in the shader code.
data = Rml::MakeUnique<BackendData>();
if (!data || !data->render_interface)
return false;
data->window = window;
data->system_interface.SetWindow(window);
data->system_interface.LogMessage(Rml::Log::LT_INFO, renderer_message);
// The window size may have been scaled by DPI settings, get the actual pixel size.
glfwGetFramebufferSize(window, &width, &height);
data->render_interface.SetViewport(width, height);
// Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields.
glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE);
// Setup the input and window event callback functions.
SetupCallbacks(window);
return true;
}
void Backend::Shutdown()
{
RMLUI_ASSERT(data);
glfwDestroyWindow(data->window);
data.reset();
RmlGL3::Shutdown();
glfwTerminate();
}
Rml::SystemInterface* Backend::GetSystemInterface()
{
RMLUI_ASSERT(data);
return &data->system_interface;
}
Rml::RenderInterface* Backend::GetRenderInterface()
{
RMLUI_ASSERT(data);
return data->render_interface.GetAdaptedInterface();
}
bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
{
RMLUI_ASSERT(data && context);
// The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context.
if (data->context_dimensions_dirty)
{
data->context_dimensions_dirty = false;
Rml::Vector2i window_size;
float dp_ratio = 1.f;
glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y);
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
context->SetDimensions(window_size);
context->SetDensityIndependentPixelRatio(dp_ratio);
}
data->context = context;
data->key_down_callback = key_down_callback;
if (power_save)
glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
else
glfwPollEvents();
data->context = nullptr;
data->key_down_callback = nullptr;
const bool result = !glfwWindowShouldClose(data->window);
glfwSetWindowShouldClose(data->window, GLFW_FALSE);
return result;
}
void Backend::RequestExit()
{
RMLUI_ASSERT(data);
glfwSetWindowShouldClose(data->window, GLFW_TRUE);
}
void Backend::BeginFrame()
{
RMLUI_ASSERT(data);
data->render_interface.BeginFrame();
data->render_interface.Clear();
}
void Backend::PresentFrame()
{
RMLUI_ASSERT(data);
data->render_interface.EndFrame();
glfwSwapBuffers(data->window);
// Optional, used to mark frames during performance profiling.
RMLUI_FrameMark;
}
static void SetupCallbacks(GLFWwindow* window)
{
RMLUI_ASSERT(data);
// Key input
glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) {
if (!data->context)
return;
// Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events.
data->glfw_active_modifiers = glfw_mods;
// Override the default key event callback to add global shortcuts for the samples.
Rml::Context* context = data->context;
KeyDownCallback key_down_callback = data->key_down_callback;
switch (glfw_action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
{
const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key);
const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods);
float dp_ratio = 1.f;
glfwGetWindowContentScale(data->window, &dp_ratio, nullptr);
// See if we have any global shortcuts that take priority over the context.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true))
break;
// Otherwise, hand the event over to the context by calling the input handler as normal.
if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods))
break;
// The key was not consumed by the context either, try keyboard shortcuts of lower priority.
if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false))
break;
}
break;
case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break;
}
});
glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); });
glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); });
// Mouse input
glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) {
RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers);
});
glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) {
data->glfw_active_modifiers = mods;
RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods);
});
glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) {
RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers);
});
// Window events
glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) {
data->render_interface.SetViewport(width, height);
RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height);
});
glfwSetWindowContentScaleCallback(window,
[](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); });
}

View File

@@ -0,0 +1,300 @@
#include "RmlUi_Renderer_BackwardCompatible_GL2.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Platform.h>
#include <string.h>
#if defined RMLUI_PLATFORM_WIN32
#include "RmlUi_Include_Windows.h"
#include <gl/Gl.h>
#include <gl/Glu.h>
#elif defined RMLUI_PLATFORM_MACOSX
#include <AGL/agl.h>
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#include <OpenGL/glu.h>
#elif defined RMLUI_PLATFORM_UNIX
#include "RmlUi_Include_Xlib.h"
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glu.h>
#include <GL/glx.h>
#endif
#define GL_CLAMP_TO_EDGE 0x812F
RenderInterface_BackwardCompatible_GL2::RenderInterface_BackwardCompatible_GL2() {}
void RenderInterface_BackwardCompatible_GL2::SetViewport(int in_viewport_width, int in_viewport_height)
{
viewport_width = in_viewport_width;
viewport_height = in_viewport_height;
}
void RenderInterface_BackwardCompatible_GL2::BeginFrame()
{
RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
glViewport(0, 0, viewport_width, viewport_height);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projection.data());
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
transform_enabled = false;
}
void RenderInterface_BackwardCompatible_GL2::EndFrame() {}
void RenderInterface_BackwardCompatible_GL2::Clear()
{
glClearStencil(0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
void RenderInterface_BackwardCompatible_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertices*/, int* indices, int num_indices,
const Rml::TextureHandle texture, const Rml::Vector2f& translation)
{
glPushMatrix();
glTranslatef(translation.x, translation.y, 0);
glVertexPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].position);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Rml::Vertex), &vertices[0].colour);
if (!texture)
{
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
else
{
glEnable(GL_TEXTURE_2D);
if (texture != TextureEnableWithoutBinding)
glBindTexture(GL_TEXTURE_2D, (GLuint)texture);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].tex_coord);
}
glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, indices);
glPopMatrix();
}
void RenderInterface_BackwardCompatible_GL2::EnableScissorRegion(bool enable)
{
if (enable)
{
if (!transform_enabled)
{
glEnable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
}
else
{
glDisable(GL_SCISSOR_TEST);
glEnable(GL_STENCIL_TEST);
}
}
else
{
glDisable(GL_SCISSOR_TEST);
glDisable(GL_STENCIL_TEST);
}
}
void RenderInterface_BackwardCompatible_GL2::SetScissorRegion(int x, int y, int width, int height)
{
if (!transform_enabled)
{
glScissor(x, viewport_height - (y + height), width, height);
}
else
{
// clear the stencil buffer
glStencilMask(GLuint(-1));
glClear(GL_STENCIL_BUFFER_BIT);
// fill the stencil buffer
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_FALSE);
glStencilFunc(GL_NEVER, 1, GLuint(-1));
glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP);
float fx = (float)x;
float fy = (float)y;
float fwidth = (float)width;
float fheight = (float)height;
// draw transformed quad
GLfloat vertices[] = {fx, fy, 0, fx, fy + fheight, 0, fx + fwidth, fy + fheight, 0, fx + fwidth, fy, 0};
glDisableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, vertices);
GLushort indices[] = {1, 2, 0, 3};
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices);
glEnableClientState(GL_COLOR_ARRAY);
// prepare for drawing the real thing
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
glStencilMask(0);
glStencilFunc(GL_EQUAL, 1, GLuint(-1));
}
}
// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
#pragma pack(1)
struct TGAHeader {
char idLength;
char colourMapType;
char dataType;
short int colourMapOrigin;
short int colourMapLength;
char colourMapDepth;
short int xOrigin;
short int yOrigin;
short int width;
short int height;
char bitsPerPixel;
char imageDescriptor;
};
// Restore packing
#pragma pack()
bool RenderInterface_BackwardCompatible_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions,
const Rml::String& source)
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
{
return false;
}
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
if (buffer_size <= sizeof(TGAHeader))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
file_interface->Close(file_handle);
return false;
}
char* buffer = new char[buffer_size];
file_interface->Read(buffer, buffer_size, file_handle);
file_interface->Close(file_handle);
TGAHeader header;
memcpy(&header, buffer, sizeof(TGAHeader));
int color_mode = header.bitsPerPixel / 8;
int image_size = header.width * header.height * 4; // We always make 32bit textures
if (header.dataType != 2)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
delete[] buffer;
return false;
}
// Ensure we have at least 3 colors
if (color_mode < 3)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
delete[] buffer;
return false;
}
const char* image_src = buffer + sizeof(TGAHeader);
unsigned char* image_dest = new unsigned char[image_size];
// Targa is BGR, swap to RGB and flip Y axis
for (long y = 0; y < header.height; y++)
{
long read_index = y * header.width * color_mode;
long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * color_mode;
for (long x = 0; x < header.width; x++)
{
image_dest[write_index] = image_src[read_index + 2];
image_dest[write_index + 1] = image_src[read_index + 1];
image_dest[write_index + 2] = image_src[read_index];
if (color_mode == 4)
image_dest[write_index + 3] = image_src[read_index + 3];
else
image_dest[write_index + 3] = 255;
write_index += 4;
read_index += color_mode;
}
}
texture_dimensions.x = header.width;
texture_dimensions.y = header.height;
bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
delete[] image_dest;
delete[] buffer;
return success;
}
bool RenderInterface_BackwardCompatible_GL2::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source,
const Rml::Vector2i& source_dimensions)
{
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
if (texture_id == 0)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
return false;
}
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
texture_handle = (Rml::TextureHandle)texture_id;
return true;
}
void RenderInterface_BackwardCompatible_GL2::ReleaseTexture(Rml::TextureHandle texture_handle)
{
glDeleteTextures(1, (GLuint*)&texture_handle);
}
void RenderInterface_BackwardCompatible_GL2::SetTransform(const Rml::Matrix4f* transform)
{
transform_enabled = (transform != nullptr);
if (transform)
{
if (std::is_same<Rml::Matrix4f, Rml::ColumnMajorMatrix4f>::value)
glLoadMatrixf(transform->data());
else if (std::is_same<Rml::Matrix4f, Rml::RowMajorMatrix4f>::value)
glLoadMatrixf(transform->Transpose().data());
}
else
glLoadIdentity();
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <RmlUi/Core/RenderInterfaceCompatibility.h>
/*
The GL2 renderer from RmlUi 5, only modified to derive from the compatibility interface.
Implemented for testing and demonstration purposes, not recommended for production use.
*/
class RenderInterface_BackwardCompatible_GL2 : public Rml::RenderInterfaceCompatibility {
public:
RenderInterface_BackwardCompatible_GL2();
// The viewport should be updated whenever the window size changes.
void SetViewport(int viewport_width, int viewport_height);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
void EndFrame();
// Optional, can be used to clear the framebuffer.
void Clear();
// -- Inherited from Rml::RenderInterface --
void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
const Rml::Vector2f& translation) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(int x, int y, int width, int height) override;
bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void SetTransform(const Rml::Matrix4f* transform) override;
// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
private:
int viewport_width = 0;
int viewport_height = 0;
bool transform_enabled = false;
};

View File

@@ -0,0 +1,765 @@
#include "RmlUi_Renderer_BackwardCompatible_GL3.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Platform.h>
#include <string.h>
#if defined RMLUI_PLATFORM_WIN32_NATIVE
// function call missing argument list
#pragma warning(disable : 4551)
// unreferenced local function has been removed
#pragma warning(disable : 4505)
#endif
#if defined RMLUI_PLATFORM_EMSCRIPTEN
#define RMLUI_SHADER_HEADER "#version 300 es\nprecision highp float;\n"
#include <GLES3/gl3.h>
#elif defined RMLUI_GL3_CUSTOM_LOADER
#define RMLUI_SHADER_HEADER "#version 330\n"
#include RMLUI_GL3_CUSTOM_LOADER
#else
#define RMLUI_SHADER_HEADER "#version 330\n"
#define GLAD_GL_IMPLEMENTATION
#include "../RmlUi_Include_GL3.h"
#endif
static const char* shader_main_vertex = RMLUI_SHADER_HEADER R"(
uniform vec2 _translate;
uniform mat4 _transform;
in vec2 inPosition;
in vec4 inColor0;
in vec2 inTexCoord0;
out vec2 fragTexCoord;
out vec4 fragColor;
void main() {
fragTexCoord = inTexCoord0;
fragColor = inColor0;
vec2 translatedPos = inPosition + _translate.xy;
vec4 outPos = _transform * vec4(translatedPos, 0, 1);
gl_Position = outPos;
}
)";
static const char* shader_main_fragment_texture = RMLUI_SHADER_HEADER R"(
uniform sampler2D _tex;
in vec2 fragTexCoord;
in vec4 fragColor;
out vec4 finalColor;
void main() {
vec4 texColor = texture(_tex, fragTexCoord);
finalColor = fragColor * texColor;
}
)";
static const char* shader_main_fragment_color = RMLUI_SHADER_HEADER R"(
in vec2 fragTexCoord;
in vec4 fragColor;
out vec4 finalColor;
void main() {
finalColor = fragColor;
}
)";
namespace Gfx {
enum class ProgramUniform { Translate, Transform, Tex, Count };
static const char* const program_uniform_names[(size_t)ProgramUniform::Count] = {"_translate", "_transform", "_tex"};
enum class VertexAttribute { Position, Color0, TexCoord0, Count };
static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"};
struct CompiledGeometryData {
Rml::TextureHandle texture;
GLuint vao;
GLuint vbo;
GLuint ibo;
GLsizei draw_count;
};
struct ProgramData {
GLuint id;
GLint uniform_locations[(size_t)ProgramUniform::Count];
};
struct ShadersData {
ProgramData program_color;
ProgramData program_texture;
GLuint shader_main_vertex;
GLuint shader_main_fragment_color;
GLuint shader_main_fragment_texture;
};
static void CheckGLError(const char* operation_name)
{
#ifdef RMLUI_DEBUG
GLenum error_code = glGetError();
if (error_code != GL_NO_ERROR)
{
static const Rml::Pair<GLenum, const char*> error_names[] = {{GL_INVALID_ENUM, "GL_INVALID_ENUM"}, {GL_INVALID_VALUE, "GL_INVALID_VALUE"},
{GL_INVALID_OPERATION, "GL_INVALID_OPERATION"}, {GL_OUT_OF_MEMORY, "GL_OUT_OF_MEMORY"}};
const char* error_str = "''";
for (auto& err : error_names)
{
if (err.first == error_code)
{
error_str = err.second;
break;
}
}
Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL error during %s. Error code 0x%x (%s).", operation_name, error_code, error_str);
}
#endif
(void)operation_name;
}
// Create the shader, 'shader_type' is either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
static GLuint CreateShader(GLenum shader_type, const char* code_string)
{
GLuint id = glCreateShader(shader_type);
glShaderSource(id, 1, (const GLchar**)&code_string, NULL);
glCompileShader(id);
GLint status = 0;
glGetShaderiv(id, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE)
{
GLint info_log_length = 0;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
char* info_log_string = new char[info_log_length + 1];
glGetShaderInfoLog(id, info_log_length, NULL, info_log_string);
Rml::Log::Message(Rml::Log::LT_ERROR, "Compile failure in OpenGL shader: %s", info_log_string);
delete[] info_log_string;
glDeleteShader(id);
return 0;
}
CheckGLError("CreateShader");
return id;
}
static void BindAttribLocations(GLuint program)
{
for (GLuint i = 0; i < (GLuint)VertexAttribute::Count; i++)
{
glBindAttribLocation(program, i, vertex_attribute_names[i]);
}
CheckGLError("BindAttribLocations");
}
static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramData& out_program)
{
GLuint id = glCreateProgram();
RMLUI_ASSERT(id);
BindAttribLocations(id);
glAttachShader(id, vertex_shader);
glAttachShader(id, fragment_shader);
glLinkProgram(id);
glDetachShader(id, vertex_shader);
glDetachShader(id, fragment_shader);
GLint status = 0;
glGetProgramiv(id, GL_LINK_STATUS, &status);
if (status == GL_FALSE)
{
GLint info_log_length = 0;
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &info_log_length);
char* info_log_string = new char[info_log_length + 1];
glGetProgramInfoLog(id, info_log_length, NULL, info_log_string);
Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program linking failure: %s", info_log_string);
delete[] info_log_string;
glDeleteProgram(id);
return false;
}
out_program = {};
out_program.id = id;
// Make a lookup table for the uniform locations.
GLint num_active_uniforms = 0;
glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &num_active_uniforms);
constexpr size_t name_size = 64;
GLchar name_buf[name_size] = "";
for (int unif = 0; unif < num_active_uniforms; ++unif)
{
GLint array_size = 0;
GLenum type = 0;
GLsizei actual_length = 0;
glGetActiveUniform(id, unif, name_size, &actual_length, &array_size, &type, name_buf);
GLint location = glGetUniformLocation(id, name_buf);
// See if we have the name in our pre-defined name list.
ProgramUniform program_uniform = ProgramUniform::Count;
for (int i = 0; i < (int)ProgramUniform::Count; i++)
{
const char* uniform_name = program_uniform_names[i];
if (strcmp(name_buf, uniform_name) == 0)
{
program_uniform = (ProgramUniform)i;
break;
}
}
if ((size_t)program_uniform < (size_t)ProgramUniform::Count)
{
out_program.uniform_locations[(size_t)program_uniform] = location;
}
else
{
Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program uses unknown uniform '%s'.", name_buf);
return false;
}
}
CheckGLError("CreateProgram");
return true;
}
static bool CreateShaders(ShadersData& out_shaders)
{
out_shaders = {};
GLuint& main_vertex = out_shaders.shader_main_vertex;
GLuint& main_fragment_color = out_shaders.shader_main_fragment_color;
GLuint& main_fragment_texture = out_shaders.shader_main_fragment_texture;
main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex);
if (!main_vertex)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_vertex'.");
return false;
}
main_fragment_color = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color);
if (!main_fragment_color)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_color'.");
return false;
}
main_fragment_texture = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture);
if (!main_fragment_texture)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_texture'.");
return false;
}
if (!CreateProgram(main_vertex, main_fragment_color, out_shaders.program_color))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_color'.");
return false;
}
if (!CreateProgram(main_vertex, main_fragment_texture, out_shaders.program_texture))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_texture'.");
return false;
}
return true;
}
static void DestroyShaders(ShadersData& shaders)
{
glDeleteProgram(shaders.program_color.id);
glDeleteProgram(shaders.program_texture.id);
glDeleteShader(shaders.shader_main_vertex);
glDeleteShader(shaders.shader_main_fragment_color);
glDeleteShader(shaders.shader_main_fragment_texture);
shaders = {};
}
} // namespace Gfx
RenderInterface_BackwardCompatible_GL3::RenderInterface_BackwardCompatible_GL3()
{
shaders = Rml::MakeUnique<Gfx::ShadersData>();
if (!Gfx::CreateShaders(*shaders))
shaders.reset();
}
RenderInterface_BackwardCompatible_GL3::~RenderInterface_BackwardCompatible_GL3()
{
if (shaders)
Gfx::DestroyShaders(*shaders);
}
void RenderInterface_BackwardCompatible_GL3::SetViewport(int width, int height)
{
viewport_width = width;
viewport_height = height;
}
void RenderInterface_BackwardCompatible_GL3::BeginFrame()
{
RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
// Backup GL state.
glstate_backup.enable_cull_face = glIsEnabled(GL_CULL_FACE);
glstate_backup.enable_blend = glIsEnabled(GL_BLEND);
glstate_backup.enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
glstate_backup.enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
glGetIntegerv(GL_VIEWPORT, glstate_backup.viewport);
glGetIntegerv(GL_SCISSOR_BOX, glstate_backup.scissor);
glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &glstate_backup.stencil_clear_value);
glGetFloatv(GL_COLOR_CLEAR_VALUE, glstate_backup.color_clear_value);
glGetIntegerv(GL_BLEND_EQUATION_RGB, &glstate_backup.blend_equation_rgb);
glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &glstate_backup.blend_equation_alpha);
glGetIntegerv(GL_BLEND_SRC_RGB, &glstate_backup.blend_src_rgb);
glGetIntegerv(GL_BLEND_DST_RGB, &glstate_backup.blend_dst_rgb);
glGetIntegerv(GL_BLEND_SRC_ALPHA, &glstate_backup.blend_src_alpha);
glGetIntegerv(GL_BLEND_DST_ALPHA, &glstate_backup.blend_dst_alpha);
glGetIntegerv(GL_STENCIL_FUNC, &glstate_backup.stencil_front.func);
glGetIntegerv(GL_STENCIL_REF, &glstate_backup.stencil_front.ref);
glGetIntegerv(GL_STENCIL_VALUE_MASK, &glstate_backup.stencil_front.value_mask);
glGetIntegerv(GL_STENCIL_WRITEMASK, &glstate_backup.stencil_front.writemask);
glGetIntegerv(GL_STENCIL_FAIL, &glstate_backup.stencil_front.fail);
glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &glstate_backup.stencil_front.pass_depth_fail);
glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &glstate_backup.stencil_front.pass_depth_pass);
glGetIntegerv(GL_STENCIL_BACK_FUNC, &glstate_backup.stencil_back.func);
glGetIntegerv(GL_STENCIL_BACK_REF, &glstate_backup.stencil_back.ref);
glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &glstate_backup.stencil_back.value_mask);
glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &glstate_backup.stencil_back.writemask);
glGetIntegerv(GL_STENCIL_BACK_FAIL, &glstate_backup.stencil_back.fail);
glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &glstate_backup.stencil_back.pass_depth_fail);
glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &glstate_backup.stencil_back.pass_depth_pass);
// Setup expected GL state.
glViewport(0, 0, viewport_width, viewport_height);
glClearStencil(0);
glClearColor(0, 0, 0, 1);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
glStencilMask(GLuint(-1));
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
SetTransform(nullptr);
}
void RenderInterface_BackwardCompatible_GL3::EndFrame()
{
// Restore GL state.
if (glstate_backup.enable_cull_face)
glEnable(GL_CULL_FACE);
else
glDisable(GL_CULL_FACE);
if (glstate_backup.enable_blend)
glEnable(GL_BLEND);
else
glDisable(GL_BLEND);
if (glstate_backup.enable_stencil_test)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
if (glstate_backup.enable_scissor_test)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);
glViewport(glstate_backup.viewport[0], glstate_backup.viewport[1], glstate_backup.viewport[2], glstate_backup.viewport[3]);
glScissor(glstate_backup.scissor[0], glstate_backup.scissor[1], glstate_backup.scissor[2], glstate_backup.scissor[3]);
glClearStencil(glstate_backup.stencil_clear_value);
glClearColor(glstate_backup.color_clear_value[0], glstate_backup.color_clear_value[1], glstate_backup.color_clear_value[2],
glstate_backup.color_clear_value[3]);
glBlendEquationSeparate(glstate_backup.blend_equation_rgb, glstate_backup.blend_equation_alpha);
glBlendFuncSeparate(glstate_backup.blend_src_rgb, glstate_backup.blend_dst_rgb, glstate_backup.blend_src_alpha, glstate_backup.blend_dst_alpha);
glStencilFuncSeparate(GL_FRONT, glstate_backup.stencil_front.func, glstate_backup.stencil_front.ref, glstate_backup.stencil_front.value_mask);
glStencilMaskSeparate(GL_FRONT, glstate_backup.stencil_front.writemask);
glStencilOpSeparate(GL_FRONT, glstate_backup.stencil_front.fail, glstate_backup.stencil_front.pass_depth_fail,
glstate_backup.stencil_front.pass_depth_pass);
glStencilFuncSeparate(GL_BACK, glstate_backup.stencil_back.func, glstate_backup.stencil_back.ref, glstate_backup.stencil_back.value_mask);
glStencilMaskSeparate(GL_BACK, glstate_backup.stencil_back.writemask);
glStencilOpSeparate(GL_BACK, glstate_backup.stencil_back.fail, glstate_backup.stencil_back.pass_depth_fail,
glstate_backup.stencil_back.pass_depth_pass);
}
void RenderInterface_BackwardCompatible_GL3::Clear()
{
glClearStencil(0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
void RenderInterface_BackwardCompatible_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
const Rml::TextureHandle texture, const Rml::Vector2f& translation)
{
Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices, texture);
if (geometry)
{
RenderCompiledGeometry(geometry, translation);
ReleaseCompiledGeometry(geometry);
}
}
Rml::CompiledGeometryHandle RenderInterface_BackwardCompatible_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices,
int num_indices, Rml::TextureHandle texture)
{
constexpr GLenum draw_usage = GL_STATIC_DRAW;
GLuint vao = 0;
GLuint vbo = 0;
GLuint ibo = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(Rml::Vertex) * num_vertices, (const void*)vertices, draw_usage);
glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Position);
glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
(const GLvoid*)(offsetof(Rml::Vertex, position)));
glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Color0);
glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Color0, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Rml::Vertex),
(const GLvoid*)(offsetof(Rml::Vertex, colour)));
glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::TexCoord0);
glVertexAttribPointer((GLuint)Gfx::VertexAttribute::TexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex),
(const GLvoid*)(offsetof(Rml::Vertex, tex_coord)));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, (const void*)indices, draw_usage);
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
Gfx::CheckGLError("CompileGeometry");
Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData;
geometry->texture = texture;
geometry->vao = vao;
geometry->vbo = vbo;
geometry->ibo = ibo;
geometry->draw_count = num_indices;
return (Rml::CompiledGeometryHandle)geometry;
}
void RenderInterface_BackwardCompatible_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation)
{
Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle;
if (geometry->texture)
{
glUseProgram(shaders->program_texture.id);
if (geometry->texture != TextureEnableWithoutBinding)
glBindTexture(GL_TEXTURE_2D, (GLuint)geometry->texture);
SubmitTransformUniform(ProgramId::Texture, shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]);
glUniform2fv(shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x);
}
else
{
glUseProgram(shaders->program_color.id);
glBindTexture(GL_TEXTURE_2D, 0);
SubmitTransformUniform(ProgramId::Color, shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]);
glUniform2fv(shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x);
}
glBindVertexArray(geometry->vao);
glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0);
glBindVertexArray(0);
glUseProgram(0);
glBindTexture(GL_TEXTURE_2D, 0);
Gfx::CheckGLError("RenderCompiledGeometry");
}
void RenderInterface_BackwardCompatible_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle handle)
{
Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle;
glDeleteVertexArrays(1, &geometry->vao);
glDeleteBuffers(1, &geometry->vbo);
glDeleteBuffers(1, &geometry->ibo);
delete geometry;
}
void RenderInterface_BackwardCompatible_GL3::EnableScissorRegion(bool enable)
{
ScissoringState new_state = ScissoringState::Disable;
if (enable)
new_state = (transform_active ? ScissoringState::Stencil : ScissoringState::Scissor);
if (new_state != scissoring_state)
{
// Disable old
if (scissoring_state == ScissoringState::Scissor)
glDisable(GL_SCISSOR_TEST);
else if (scissoring_state == ScissoringState::Stencil)
glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
// Enable new
if (new_state == ScissoringState::Scissor)
glEnable(GL_SCISSOR_TEST);
else if (new_state == ScissoringState::Stencil)
glStencilFunc(GL_EQUAL, 1, GLuint(-1));
scissoring_state = new_state;
}
}
void RenderInterface_BackwardCompatible_GL3::SetScissorRegion(int x, int y, int width, int height)
{
if (transform_active)
{
const float left = float(x);
const float right = float(x + width);
const float top = float(y);
const float bottom = float(y + height);
Rml::Vertex vertices[4];
vertices[0].position = {left, top};
vertices[1].position = {right, top};
vertices[2].position = {right, bottom};
vertices[3].position = {left, bottom};
int indices[6] = {0, 2, 1, 0, 3, 2};
glClear(GL_STENCIL_BUFFER_BIT);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0));
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 1, GLuint(-1));
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
else
{
glScissor(x, viewport_height - (y + height), width, height);
}
}
// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
#pragma pack(1)
struct TGAHeader {
char idLength;
char colourMapType;
char dataType;
short int colourMapOrigin;
short int colourMapLength;
char colourMapDepth;
short int xOrigin;
short int yOrigin;
short int width;
short int height;
char bitsPerPixel;
char imageDescriptor;
};
// Restore packing
#pragma pack()
bool RenderInterface_BackwardCompatible_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions,
const Rml::String& source)
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
{
return false;
}
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
if (buffer_size <= sizeof(TGAHeader))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
file_interface->Close(file_handle);
return false;
}
using Rml::byte;
byte* buffer = new byte[buffer_size];
file_interface->Read(buffer, buffer_size, file_handle);
file_interface->Close(file_handle);
TGAHeader header;
memcpy(&header, buffer, sizeof(TGAHeader));
int color_mode = header.bitsPerPixel / 8;
int image_size = header.width * header.height * 4; // We always make 32bit textures
if (header.dataType != 2)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
delete[] buffer;
return false;
}
// Ensure we have at least 3 colors
if (color_mode < 3)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
delete[] buffer;
return false;
}
const byte* image_src = buffer + sizeof(TGAHeader);
byte* image_dest = new byte[image_size];
// Targa is BGR, swap to RGB and flip Y axis
for (long y = 0; y < header.height; y++)
{
long read_index = y * header.width * color_mode;
long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
for (long x = 0; x < header.width; x++)
{
image_dest[write_index] = image_src[read_index + 2];
image_dest[write_index + 1] = image_src[read_index + 1];
image_dest[write_index + 2] = image_src[read_index];
if (color_mode == 4)
{
const int alpha = image_src[read_index + 3];
#ifdef RMLUI_SRGB_PREMULTIPLIED_ALPHA
image_dest[write_index + 0] = (image_dest[write_index + 0] * alpha) / 255;
image_dest[write_index + 1] = (image_dest[write_index + 1] * alpha) / 255;
image_dest[write_index + 2] = (image_dest[write_index + 2] * alpha) / 255;
#endif
image_dest[write_index + 3] = (byte)alpha;
}
else
{
image_dest[write_index + 3] = 255;
}
write_index += 4;
read_index += color_mode;
}
}
texture_dimensions.x = header.width;
texture_dimensions.y = header.height;
bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
delete[] image_dest;
delete[] buffer;
return success;
}
bool RenderInterface_BackwardCompatible_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source,
const Rml::Vector2i& source_dimensions)
{
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
if (texture_id == 0)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
return false;
}
glBindTexture(GL_TEXTURE_2D, texture_id);
GLint internal_format = GL_RGBA8;
glTexImage2D(GL_TEXTURE_2D, 0, internal_format, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
texture_handle = (Rml::TextureHandle)texture_id;
glBindTexture(GL_TEXTURE_2D, 0);
return true;
}
void RenderInterface_BackwardCompatible_GL3::ReleaseTexture(Rml::TextureHandle texture_handle)
{
glDeleteTextures(1, (GLuint*)&texture_handle);
}
void RenderInterface_BackwardCompatible_GL3::SetTransform(const Rml::Matrix4f* new_transform)
{
transform_active = (new_transform != nullptr);
transform = projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity());
transform_dirty_state = ProgramId::All;
}
void RenderInterface_BackwardCompatible_GL3::SubmitTransformUniform(ProgramId program_id, int uniform_location)
{
if ((int)program_id & (int)transform_dirty_state)
{
glUniformMatrix4fv(uniform_location, 1, false, transform.data());
transform_dirty_state = ProgramId((int)transform_dirty_state & ~(int)program_id);
}
}
bool RmlGL3::Initialize(Rml::String* out_message)
{
#if defined RMLUI_PLATFORM_EMSCRIPTEN
if (out_message)
*out_message = "Started Emscripten WebGL renderer.";
#elif !defined RMLUI_GL3_CUSTOM_LOADER
const int gl_version = gladLoaderLoadGL();
if (gl_version == 0)
{
if (out_message)
*out_message = "Failed to initialize OpenGL context.";
return false;
}
if (out_message)
*out_message = Rml::CreateString("Loaded OpenGL %d.%d.", GLAD_VERSION_MAJOR(gl_version), GLAD_VERSION_MINOR(gl_version));
#endif
return true;
}
void RmlGL3::Shutdown()
{
#if !defined RMLUI_PLATFORM_EMSCRIPTEN && !defined RMLUI_GL3_CUSTOM_LOADER
gladLoaderUnloadGL();
#endif
}

View File

@@ -0,0 +1,117 @@
#pragma once
#include <RmlUi/Core/RenderInterfaceCompatibility.h>
#include <RmlUi/Core/Types.h>
namespace Gfx {
struct ShadersData;
}
/*
The GL3 renderer from RmlUi 5, only modified to derive from the compatibility interface.
Implemented for testing and demonstration purposes, not recommended for production use.
*/
class RenderInterface_BackwardCompatible_GL3 : public Rml::RenderInterfaceCompatibility {
public:
RenderInterface_BackwardCompatible_GL3();
~RenderInterface_BackwardCompatible_GL3();
// Returns true if the renderer was successfully constructed.
explicit operator bool() const { return static_cast<bool>(shaders); }
// The viewport should be updated whenever the window size changes.
void SetViewport(int viewport_width, int viewport_height);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
void EndFrame();
// Optional, can be used to clear the framebuffer.
void Clear();
// -- Inherited from Rml::RenderInterface --
void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture,
const Rml::Vector2f& translation) override;
Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices,
Rml::TextureHandle texture) override;
void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override;
void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(int x, int y, int width, int height) override;
bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void SetTransform(const Rml::Matrix4f* transform) override;
// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
private:
enum class ProgramId { None, Texture = 1, Color = 2, All = (Texture | Color) };
void SubmitTransformUniform(ProgramId program_id, int uniform_location);
Rml::Matrix4f transform, projection;
ProgramId transform_dirty_state = ProgramId::All;
bool transform_active = false;
enum class ScissoringState { Disable, Scissor, Stencil };
ScissoringState scissoring_state = ScissoringState::Disable;
int viewport_width = 0;
int viewport_height = 0;
Rml::UniquePtr<Gfx::ShadersData> shaders;
struct GLStateBackup {
bool enable_cull_face;
bool enable_blend;
bool enable_stencil_test;
bool enable_scissor_test;
int viewport[4];
int scissor[4];
int stencil_clear_value;
float color_clear_value[4];
int blend_equation_rgb;
int blend_equation_alpha;
int blend_src_rgb;
int blend_dst_rgb;
int blend_src_alpha;
int blend_dst_alpha;
struct Stencil {
int func;
int ref;
int value_mask;
int writemask;
int fail;
int pass_depth_fail;
int pass_depth_pass;
};
Stencil stencil_front;
Stencil stencil_back;
};
GLStateBackup glstate_backup = {};
};
/**
Helper functions for the OpenGL 3 renderer.
*/
namespace RmlGL3 {
// Loads OpenGL functions. Optionally, the out message describes the loaded GL version or an error message on failure.
bool Initialize(Rml::String* out_message = nullptr);
// Unloads OpenGL functions.
void Shutdown();
} // namespace RmlGL3

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
The RmlUi DirectX 12 renderer includes thirdparty code with the following licenses.
----- D3D12MemAlloc.cpp/.h -----
Copyright (c) 2019-2024 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
----- offsetAllocator.cpp/.hpp -----
MIT License
Copyright (c) 2023 Sebastian Aaltonen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
----- Snippets from d3dx12.h header (included directly in RmlUi_Renderer_DX12.cpp) -----
The MIT License (MIT)
Copyright (c) 2015 Microsoft
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,513 @@
// (C) Sebastian Aaltonen 2023
// MIT License (see file: LICENSE)
#include "offsetAllocator.hpp"
#ifdef DEBUG
#include <assert.h>
#define ASSERT(x) assert(x)
//#define DEBUG_VERBOSE
#else
#define ASSERT(x)
#endif
#ifdef DEBUG_VERBOSE
#include <stdio.h>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#endif
#include <cstring>
namespace OffsetAllocator
{
inline uint32 lzcnt_nonzero(uint32 v)
{
#ifdef _MSC_VER
unsigned long retVal;
_BitScanReverse(&retVal, v);
return 31 - retVal;
#else
return __builtin_clz(v);
#endif
}
inline uint32 tzcnt_nonzero(uint32 v)
{
#ifdef _MSC_VER
unsigned long retVal;
_BitScanForward(&retVal, v);
return retVal;
#else
return __builtin_ctz(v);
#endif
}
namespace SmallFloat
{
static constexpr uint32 MANTISSA_BITS = 3;
static constexpr uint32 MANTISSA_VALUE = 1 << MANTISSA_BITS;
static constexpr uint32 MANTISSA_MASK = MANTISSA_VALUE - 1;
// Bin sizes follow floating point (exponent + mantissa) distribution (piecewise linear log approx)
// This ensures that for each size class, the average overhead percentage stays the same
uint32 uintToFloatRoundUp(uint32 size)
{
uint32 exp = 0;
uint32 mantissa = 0;
if (size < MANTISSA_VALUE)
{
// Denorm: 0..(MANTISSA_VALUE-1)
mantissa = size;
}
else
{
// Normalized: Hidden high bit always 1. Not stored. Just like float.
uint32 leadingZeros = lzcnt_nonzero(size);
uint32 highestSetBit = 31 - leadingZeros;
uint32 mantissaStartBit = highestSetBit - MANTISSA_BITS;
exp = mantissaStartBit + 1;
mantissa = (size >> mantissaStartBit) & MANTISSA_MASK;
uint32 lowBitsMask = (1 << mantissaStartBit) - 1;
// Round up!
if ((size & lowBitsMask) != 0)
mantissa++;
}
return (exp << MANTISSA_BITS) + mantissa; // + allows mantissa->exp overflow for round up
}
uint32 uintToFloatRoundDown(uint32 size)
{
uint32 exp = 0;
uint32 mantissa = 0;
if (size < MANTISSA_VALUE)
{
// Denorm: 0..(MANTISSA_VALUE-1)
mantissa = size;
}
else
{
// Normalized: Hidden high bit always 1. Not stored. Just like float.
uint32 leadingZeros = lzcnt_nonzero(size);
uint32 highestSetBit = 31 - leadingZeros;
uint32 mantissaStartBit = highestSetBit - MANTISSA_BITS;
exp = mantissaStartBit + 1;
mantissa = (size >> mantissaStartBit) & MANTISSA_MASK;
}
return (exp << MANTISSA_BITS) | mantissa;
}
uint32 floatToUint(uint32 floatValue)
{
uint32 exponent = floatValue >> MANTISSA_BITS;
uint32 mantissa = floatValue & MANTISSA_MASK;
if (exponent == 0)
{
// Denorms
return mantissa;
}
else
{
return (mantissa | MANTISSA_VALUE) << (exponent - 1);
}
}
}
// Utility functions
uint32 findLowestSetBitAfter(uint32 bitMask, uint32 startBitIndex)
{
uint32 maskBeforeStartIndex = (1 << startBitIndex) - 1;
uint32 maskAfterStartIndex = ~maskBeforeStartIndex;
uint32 bitsAfter = bitMask & maskAfterStartIndex;
if (bitsAfter == 0) return Allocation::NO_SPACE;
return tzcnt_nonzero(bitsAfter);
}
// Allocator...
Allocator::Allocator(uint32 size, uint32 maxAllocs) :
m_size(size),
m_maxAllocs(maxAllocs),
m_nodes(nullptr),
m_freeNodes(nullptr)
{
if (sizeof(NodeIndex) == 2)
{
ASSERT(maxAllocs <= 65536);
}
reset();
}
Allocator::Allocator(Allocator &&other) :
m_size(other.m_size),
m_maxAllocs(other.m_maxAllocs),
m_freeStorage(other.m_freeStorage),
m_usedBinsTop(other.m_usedBinsTop),
m_nodes(other.m_nodes),
m_freeNodes(other.m_freeNodes),
m_freeOffset(other.m_freeOffset)
{
memcpy(m_usedBins, other.m_usedBins, sizeof(uint8) * NUM_TOP_BINS);
memcpy(m_binIndices, other.m_binIndices, sizeof(NodeIndex) * NUM_LEAF_BINS);
other.m_nodes = nullptr;
other.m_freeNodes = nullptr;
other.m_freeOffset = 0;
other.m_maxAllocs = 0;
other.m_usedBinsTop = 0;
}
void Allocator::reset()
{
m_freeStorage = 0;
m_usedBinsTop = 0;
m_freeOffset = m_maxAllocs - 1;
for (uint32 i = 0 ; i < NUM_TOP_BINS; i++)
m_usedBins[i] = 0;
for (uint32 i = 0 ; i < NUM_LEAF_BINS; i++)
m_binIndices[i] = Node::unused;
if (m_nodes) delete[] m_nodes;
if (m_freeNodes) delete[] m_freeNodes;
m_nodes = new Node[m_maxAllocs];
m_freeNodes = new NodeIndex[m_maxAllocs];
// Freelist is a stack. Nodes in inverse order so that [0] pops first.
for (uint32 i = 0; i < m_maxAllocs; i++)
{
m_freeNodes[i] = m_maxAllocs - i - 1;
}
// Start state: Whole storage as one big node
// Algorithm will split remainders and push them back as smaller nodes
insertNodeIntoBin(m_size, 0);
}
Allocator::~Allocator()
{
delete[] m_nodes;
delete[] m_freeNodes;
}
Allocation Allocator::allocate(uint32 size)
{
// Out of allocations?
if (m_freeOffset == 0)
{
Allocation result;
result.offset = Allocation::NO_SPACE;
result.metadata = Allocation::NO_SPACE;
//return {.offset = Allocation::NO_SPACE, .metadata = Allocation::NO_SPACE};
return result;
}
// Round up to bin index to ensure that alloc >= bin
// Gives us min bin index that fits the size
uint32 minBinIndex = SmallFloat::uintToFloatRoundUp(size);
uint32 minTopBinIndex = minBinIndex >> TOP_BINS_INDEX_SHIFT;
uint32 minLeafBinIndex = minBinIndex & LEAF_BINS_INDEX_MASK;
uint32 topBinIndex = minTopBinIndex;
uint32 leafBinIndex = Allocation::NO_SPACE;
// If top bin exists, scan its leaf bin. This can fail (NO_SPACE).
if (m_usedBinsTop & (1 << topBinIndex))
{
leafBinIndex = findLowestSetBitAfter(m_usedBins[topBinIndex], minLeafBinIndex);
}
// If we didn't find space in top bin, we search top bin from +1
if (leafBinIndex == Allocation::NO_SPACE)
{
topBinIndex = findLowestSetBitAfter(m_usedBinsTop, minTopBinIndex + 1);
// Out of space?
if (topBinIndex == Allocation::NO_SPACE)
{
Allocation result;
result.offset = Allocation::NO_SPACE;
result.metadata = Allocation::NO_SPACE;
//return {.offset = Allocation::NO_SPACE, .metadata = Allocation::NO_SPACE};
return result;
}
// All leaf bins here fit the alloc, since the top bin was rounded up. Start leaf search from bit 0.
// NOTE: This search can't fail since at least one leaf bit was set because the top bit was set.
leafBinIndex = tzcnt_nonzero(m_usedBins[topBinIndex]);
}
uint32 binIndex = (topBinIndex << TOP_BINS_INDEX_SHIFT) | leafBinIndex;
// Pop the top node of the bin. Bin top = node.next.
uint32 nodeIndex = m_binIndices[binIndex];
Node& node = m_nodes[nodeIndex];
uint32 nodeTotalSize = node.dataSize;
node.dataSize = size;
node.used = true;
m_binIndices[binIndex] = node.binListNext;
if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = Node::unused;
m_freeStorage -= nodeTotalSize;
#ifdef DEBUG_VERBOSE
printf("Free storage: %u (-%u) (allocate)\n", m_freeStorage, nodeTotalSize);
#endif
// Bin empty?
if (m_binIndices[binIndex] == Node::unused)
{
// Remove a leaf bin mask bit
m_usedBins[topBinIndex] &= ~(1 << leafBinIndex);
// All leaf bins empty?
if (m_usedBins[topBinIndex] == 0)
{
// Remove a top bin mask bit
m_usedBinsTop &= ~(1 << topBinIndex);
}
}
// Push back reminder N elements to a lower bin
uint32 reminderSize = nodeTotalSize - size;
if (reminderSize > 0)
{
uint32 newNodeIndex = insertNodeIntoBin(reminderSize, node.dataOffset + size);
// Link nodes next to each other so that we can merge them later if both are free
// And update the old next neighbor to point to the new node (in middle)
if (node.neighborNext != Node::unused) m_nodes[node.neighborNext].neighborPrev = newNodeIndex;
m_nodes[newNodeIndex].neighborPrev = nodeIndex;
m_nodes[newNodeIndex].neighborNext = node.neighborNext;
node.neighborNext = newNodeIndex;
}
// return {.offset = node.dataOffset, .metadata = nodeIndex};
Allocation result;
result.offset = node.dataOffset;
result.metadata = nodeIndex;
return result;
}
void Allocator::free(Allocation allocation)
{
ASSERT(allocation.metadata != Allocation::NO_SPACE);
if (!m_nodes) return;
uint32 nodeIndex = allocation.metadata;
Node& node = m_nodes[nodeIndex];
// Double delete check
ASSERT(node.used == true);
// Merge with neighbors...
uint32 offset = node.dataOffset;
uint32 size = node.dataSize;
if ((node.neighborPrev != Node::unused) && (m_nodes[node.neighborPrev].used == false))
{
// Previous (contiguous) free node: Change offset to previous node offset. Sum sizes
Node& prevNode = m_nodes[node.neighborPrev];
offset = prevNode.dataOffset;
size += prevNode.dataSize;
// Remove node from the bin linked list and put it in the freelist
removeNodeFromBin(node.neighborPrev);
ASSERT(prevNode.neighborNext == nodeIndex);
node.neighborPrev = prevNode.neighborPrev;
}
if ((node.neighborNext != Node::unused) && (m_nodes[node.neighborNext].used == false))
{
// Next (contiguous) free node: Offset remains the same. Sum sizes.
Node& nextNode = m_nodes[node.neighborNext];
size += nextNode.dataSize;
// Remove node from the bin linked list and put it in the freelist
removeNodeFromBin(node.neighborNext);
ASSERT(nextNode.neighborPrev == nodeIndex);
node.neighborNext = nextNode.neighborNext;
}
uint32 neighborNext = node.neighborNext;
uint32 neighborPrev = node.neighborPrev;
// Insert the removed node to freelist
#ifdef DEBUG_VERBOSE
printf("Putting node %u into freelist[%u] (free)\n", nodeIndex, m_freeOffset + 1);
#endif
m_freeNodes[++m_freeOffset] = nodeIndex;
// Insert the (combined) free node to bin
uint32 combinedNodeIndex = insertNodeIntoBin(size, offset);
// Connect neighbors with the new combined node
if (neighborNext != Node::unused)
{
m_nodes[combinedNodeIndex].neighborNext = neighborNext;
m_nodes[neighborNext].neighborPrev = combinedNodeIndex;
}
if (neighborPrev != Node::unused)
{
m_nodes[combinedNodeIndex].neighborPrev = neighborPrev;
m_nodes[neighborPrev].neighborNext = combinedNodeIndex;
}
}
uint32 Allocator::insertNodeIntoBin(uint32 size, uint32 dataOffset)
{
// Round down to bin index to ensure that bin >= alloc
uint32 binIndex = SmallFloat::uintToFloatRoundDown(size);
uint32 topBinIndex = binIndex >> TOP_BINS_INDEX_SHIFT;
uint32 leafBinIndex = binIndex & LEAF_BINS_INDEX_MASK;
// Bin was empty before?
if (m_binIndices[binIndex] == Node::unused)
{
// Set bin mask bits
m_usedBins[topBinIndex] |= 1 << leafBinIndex;
m_usedBinsTop |= 1 << topBinIndex;
}
// Take a freelist node and insert on top of the bin linked list (next = old top)
uint32 topNodeIndex = m_binIndices[binIndex];
uint32 nodeIndex = m_freeNodes[m_freeOffset--];
#ifdef DEBUG_VERBOSE
printf("Getting node %u from freelist[%u]\n", nodeIndex, m_freeOffset + 1);
#endif
// m_nodes[nodeIndex] = {.dataOffset = dataOffset, .dataSize = size, .binListNext = topNodeIndex};
Node node_instance;
node_instance.dataOffset = dataOffset;
node_instance.dataSize = size;
node_instance.binListNext = topNodeIndex;
m_nodes[nodeIndex] = node_instance;
if (topNodeIndex != Node::unused) m_nodes[topNodeIndex].binListPrev = nodeIndex;
m_binIndices[binIndex] = nodeIndex;
m_freeStorage += size;
#ifdef DEBUG_VERBOSE
printf("Free storage: %u (+%u) (insertNodeIntoBin)\n", m_freeStorage, size);
#endif
return nodeIndex;
}
void Allocator::removeNodeFromBin(uint32 nodeIndex)
{
Node &node = m_nodes[nodeIndex];
if (node.binListPrev != Node::unused)
{
// Easy case: We have previous node. Just remove this node from the middle of the list.
m_nodes[node.binListPrev].binListNext = node.binListNext;
if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = node.binListPrev;
}
else
{
// Hard case: We are the first node in a bin. Find the bin.
// Round down to bin index to ensure that bin >= alloc
uint32 binIndex = SmallFloat::uintToFloatRoundDown(node.dataSize);
uint32 topBinIndex = binIndex >> TOP_BINS_INDEX_SHIFT;
uint32 leafBinIndex = binIndex & LEAF_BINS_INDEX_MASK;
m_binIndices[binIndex] = node.binListNext;
if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = Node::unused;
// Bin empty?
if (m_binIndices[binIndex] == Node::unused)
{
// Remove a leaf bin mask bit
m_usedBins[topBinIndex] &= ~(1 << leafBinIndex);
// All leaf bins empty?
if (m_usedBins[topBinIndex] == 0)
{
// Remove a top bin mask bit
m_usedBinsTop &= ~(1 << topBinIndex);
}
}
}
// Insert the node to freelist
#ifdef DEBUG_VERBOSE
printf("Putting node %u into freelist[%u] (removeNodeFromBin)\n", nodeIndex, m_freeOffset + 1);
#endif
m_freeNodes[++m_freeOffset] = nodeIndex;
m_freeStorage -= node.dataSize;
#ifdef DEBUG_VERBOSE
printf("Free storage: %u (-%u) (removeNodeFromBin)\n", m_freeStorage, node.dataSize);
#endif
}
uint32 Allocator::allocationSize(Allocation allocation) const
{
if (allocation.metadata == Allocation::NO_SPACE) return 0;
if (!m_nodes) return 0;
return m_nodes[allocation.metadata].dataSize;
}
StorageReport Allocator::storageReport() const
{
uint32 largestFreeRegion = 0;
uint32 freeStorage = 0;
// Out of allocations? -> Zero free space
if (m_freeOffset > 0)
{
freeStorage = m_freeStorage;
if (m_usedBinsTop)
{
uint32 topBinIndex = 31 - lzcnt_nonzero(m_usedBinsTop);
uint32 leafBinIndex = 31 - lzcnt_nonzero(m_usedBins[topBinIndex]);
largestFreeRegion = SmallFloat::floatToUint((topBinIndex << TOP_BINS_INDEX_SHIFT) | leafBinIndex);
ASSERT(freeStorage >= largestFreeRegion);
}
}
// return {.totalFreeSpace = freeStorage, .largestFreeRegion = largestFreeRegion};
StorageReport result;
result.totalFreeSpace = freeStorage;
result.largestFreeRegion = largestFreeRegion;
return result;
}
StorageReportFull Allocator::storageReportFull() const
{
StorageReportFull report;
StorageReportFull::Region region;
for (uint32 i = 0; i < NUM_LEAF_BINS; i++)
{
uint32 count = 0;
uint32 nodeIndex = m_binIndices[i];
while (nodeIndex != Node::unused)
{
nodeIndex = m_nodes[nodeIndex].binListNext;
count++;
}
region.size = SmallFloat::floatToUint(i);
region.count = count;
// report.freeRegions[i] = { .size = SmallFloat::floatToUint(i), .count = count };
report.freeRegions[i] = region;
}
return report;
}
}

View File

@@ -0,0 +1,97 @@
// (C) Sebastian Aaltonen 2023
// MIT License (see file: LICENSE)
//#define USE_16_BIT_OFFSETS
#pragma once
namespace OffsetAllocator
{
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
// 16 bit offsets mode will halve the metadata storage cost
// But it only supports up to 65536 maximum allocation count
#ifdef USE_16_BIT_NODE_INDICES
typedef uint16 NodeIndex;
#else
typedef uint32 NodeIndex;
#endif
static constexpr uint32 NUM_TOP_BINS = 32;
static constexpr uint32 BINS_PER_LEAF = 8;
static constexpr uint32 TOP_BINS_INDEX_SHIFT = 3;
static constexpr uint32 LEAF_BINS_INDEX_MASK = 0x7;
static constexpr uint32 NUM_LEAF_BINS = NUM_TOP_BINS * BINS_PER_LEAF;
struct Allocation
{
static constexpr uint32 NO_SPACE = 0xffffffff;
uint32 offset = NO_SPACE;
NodeIndex metadata = NO_SPACE; // internal: node index
};
struct StorageReport
{
uint32 totalFreeSpace;
uint32 largestFreeRegion;
};
struct StorageReportFull
{
struct Region
{
uint32 size;
uint32 count;
};
Region freeRegions[NUM_LEAF_BINS];
};
class Allocator
{
public:
Allocator(uint32 size, uint32 maxAllocs = 128 * 1024);
Allocator(Allocator &&other);
~Allocator();
void reset();
Allocation allocate(uint32 size);
void free(Allocation allocation);
uint32 allocationSize(Allocation allocation) const;
StorageReport storageReport() const;
StorageReportFull storageReportFull() const;
private:
uint32 insertNodeIntoBin(uint32 size, uint32 dataOffset);
void removeNodeFromBin(uint32 nodeIndex);
struct Node
{
static constexpr NodeIndex unused = 0xffffffff;
uint32 dataOffset = 0;
uint32 dataSize = 0;
NodeIndex binListPrev = unused;
NodeIndex binListNext = unused;
NodeIndex neighborPrev = unused;
NodeIndex neighborNext = unused;
bool used = false; // TODO: Merge as bit flag
};
uint32 m_size;
uint32 m_maxAllocs;
uint32 m_freeStorage;
uint32 m_usedBinsTop;
uint8 m_usedBins[NUM_TOP_BINS];
NodeIndex m_binIndices[NUM_LEAF_BINS];
Node* m_nodes;
NodeIndex* m_freeNodes;
uint32 m_freeOffset;
};
}

View File

@@ -0,0 +1,284 @@
#pragma once
#if defined _MSC_VER
#pragma warning(push, 0)
#elif defined __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wall"
#pragma clang diagnostic ignored "-Wextra"
#pragma clang diagnostic ignored "-Wnullability-extension"
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#pragma clang diagnostic ignored "-Wnullability-completeness"
#elif defined __GNUC__
#pragma GCC system_header
#endif
#include "RmlUi_DirectX/D3D12MemAlloc.h"
#include "RmlUi_DirectX/offsetAllocator.hpp"
#include <bitset>
#include <chrono>
#include <d3d12.h>
#include <d3dcompiler.h>
#include <dxgi1_6.h>
namespace Rml {
inline constexpr const char* DXGIFormatToString(DXGI_FORMAT format)
{
switch (format)
{
case DXGI_FORMAT::DXGI_FORMAT_UNKNOWN: return "DXGI_FORMAT_UNKNOWN";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_TYPELESS: return "DXGI_FORMAT_R32G32B32A32_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_FLOAT: return "DXGI_FORMAT_R32G32B32A32_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_UINT: return "DXGI_FORMAT_R32G32B32A32_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32A32_SINT: return "DXGI_FORMAT_R32G32B32A32_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32_TYPELESS: return "DXGI_FORMAT_R32G32B32_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32_FLOAT: return "DXGI_FORMAT_R32G32B32_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32_UINT: return "DXGI_FORMAT_R32G32B32_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32B32_SINT: return "DXGI_FORMAT_R32G32B32_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_TYPELESS: return "DXGI_FORMAT_R16G16B16A16_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_FLOAT: return "DXGI_FORMAT_R16G16B16A16_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM: return "DXGI_FORMAT_R16G16B16A16_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UINT: return "DXGI_FORMAT_R16G16B16A16_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_SNORM: return "DXGI_FORMAT_R16G16B16A16_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_SINT: return "DXGI_FORMAT_R16G16B16A16_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32_TYPELESS: return "DXGI_FORMAT_R32G32_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R32G32_FLOAT: return "DXGI_FORMAT_R32G32_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32_UINT: return "DXGI_FORMAT_R32G32_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G32_SINT: return "DXGI_FORMAT_R32G32_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R32G8X24_TYPELESS: return "DXGI_FORMAT_R32G8X24_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_D32_FLOAT_S8X24_UINT: return "DXGI_FORMAT_D32_FLOAT_S8X24_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: return "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: return "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R10G10B10A2_TYPELESS: return "DXGI_FORMAT_R10G10B10A2_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R10G10B10A2_UNORM: return "DXGI_FORMAT_R10G10B10A2_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R10G10B10A2_UINT: return "DXGI_FORMAT_R10G10B10A2_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R11G11B10_FLOAT: return "DXGI_FORMAT_R11G11B10_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_TYPELESS: return "DXGI_FORMAT_R8G8B8A8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM: return "DXGI_FORMAT_R8G8B8A8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UINT: return "DXGI_FORMAT_R8G8B8A8_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_SNORM: return "DXGI_FORMAT_R8G8B8A8_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_SINT: return "DXGI_FORMAT_R8G8B8A8_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_TYPELESS: return "DXGI_FORMAT_R16G16_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_FLOAT: return "DXGI_FORMAT_R16G16_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_UNORM: return "DXGI_FORMAT_R16G16_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_UINT: return "DXGI_FORMAT_R16G16_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_SNORM: return "DXGI_FORMAT_R16G16_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16G16_SINT: return "DXGI_FORMAT_R16G16_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R32_TYPELESS: return "DXGI_FORMAT_R32_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_D32_FLOAT: return "DXGI_FORMAT_D32_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R32_FLOAT: return "DXGI_FORMAT_R32_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_R32_UINT: return "DXGI_FORMAT_R32_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R32_SINT: return "DXGI_FORMAT_R32_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R24G8_TYPELESS: return "DXGI_FORMAT_R24G8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_D24_UNORM_S8_UINT: return "DXGI_FORMAT_D24_UNORM_S8_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R24_UNORM_X8_TYPELESS: return "DXGI_FORMAT_R24_UNORM_X8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_X24_TYPELESS_G8_UINT: return "DXGI_FORMAT_X24_TYPELESS_G8_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_TYPELESS: return "DXGI_FORMAT_R8G8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_UNORM: return "DXGI_FORMAT_R8G8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_UINT: return "DXGI_FORMAT_R8G8_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_SNORM: return "DXGI_FORMAT_R8G8_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_SINT: return "DXGI_FORMAT_R8G8_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R16_TYPELESS: return "DXGI_FORMAT_R16_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R16_FLOAT: return "DXGI_FORMAT_R16_FLOAT";
case DXGI_FORMAT::DXGI_FORMAT_D16_UNORM: return "DXGI_FORMAT_D16_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16_UNORM: return "DXGI_FORMAT_R16_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16_UINT: return "DXGI_FORMAT_R16_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R16_SNORM: return "DXGI_FORMAT_R16_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R16_SINT: return "DXGI_FORMAT_R16_SINT";
case DXGI_FORMAT::DXGI_FORMAT_R8_TYPELESS: return "DXGI_FORMAT_R8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_R8_UNORM: return "DXGI_FORMAT_R8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8_UINT: return "DXGI_FORMAT_R8_UINT";
case DXGI_FORMAT::DXGI_FORMAT_R8_SNORM: return "DXGI_FORMAT_R8_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_R8_SINT: return "DXGI_FORMAT_R8_SINT";
case DXGI_FORMAT::DXGI_FORMAT_A8_UNORM: return "DXGI_FORMAT_A8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R1_UNORM: return "DXGI_FORMAT_R1_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R9G9B9E5_SHAREDEXP: return "DXGI_FORMAT_R9G9B9E5_SHAREDEXP";
case DXGI_FORMAT::DXGI_FORMAT_R8G8_B8G8_UNORM: return "DXGI_FORMAT_R8G8_B8G8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_G8R8_G8B8_UNORM: return "DXGI_FORMAT_G8R8_G8B8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC1_TYPELESS: return "DXGI_FORMAT_BC1_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC1_UNORM: return "DXGI_FORMAT_BC1_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC1_UNORM_SRGB: return "DXGI_FORMAT_BC1_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_BC2_TYPELESS: return "DXGI_FORMAT_BC2_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC2_UNORM: return "DXGI_FORMAT_BC2_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC2_UNORM_SRGB: return "DXGI_FORMAT_BC2_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_BC3_TYPELESS: return "DXGI_FORMAT_BC3_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC3_UNORM: return "DXGI_FORMAT_BC3_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC3_UNORM_SRGB: return "DXGI_FORMAT_BC3_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_BC4_TYPELESS: return "DXGI_FORMAT_BC4_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC4_UNORM: return "DXGI_FORMAT_BC4_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC4_SNORM: return "DXGI_FORMAT_BC4_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC5_TYPELESS: return "DXGI_FORMAT_BC5_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC5_UNORM: return "DXGI_FORMAT_BC5_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC5_SNORM: return "DXGI_FORMAT_BC5_SNORM";
case DXGI_FORMAT::DXGI_FORMAT_B5G6R5_UNORM: return "DXGI_FORMAT_B5G6R5_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_B5G5R5A1_UNORM: return "DXGI_FORMAT_B5G5R5A1_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM: return "DXGI_FORMAT_B8G8R8A8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8X8_UNORM: return "DXGI_FORMAT_B8G8R8X8_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: return "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_TYPELESS: return "DXGI_FORMAT_B8G8R8A8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8X8_TYPELESS: return "DXGI_FORMAT_B8G8R8X8_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: return "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_BC6H_TYPELESS: return "DXGI_FORMAT_BC6H_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC6H_UF16: return "DXGI_FORMAT_BC6H_UF16";
case DXGI_FORMAT::DXGI_FORMAT_BC6H_SF16: return "DXGI_FORMAT_BC6H_SF16";
case DXGI_FORMAT::DXGI_FORMAT_BC7_TYPELESS: return "DXGI_FORMAT_BC7_TYPELESS";
case DXGI_FORMAT::DXGI_FORMAT_BC7_UNORM: return "DXGI_FORMAT_BC7_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_BC7_UNORM_SRGB: return "DXGI_FORMAT_BC7_UNORM_SRGB";
case DXGI_FORMAT::DXGI_FORMAT_AYUV: return "DXGI_FORMAT_AYUV";
case DXGI_FORMAT::DXGI_FORMAT_Y410: return "DXGI_FORMAT_Y410";
case DXGI_FORMAT::DXGI_FORMAT_Y416: return "DXGI_FORMAT_Y416";
case DXGI_FORMAT::DXGI_FORMAT_NV12: return "DXGI_FORMAT_NV12";
case DXGI_FORMAT::DXGI_FORMAT_P010: return "DXGI_FORMAT_P010";
case DXGI_FORMAT::DXGI_FORMAT_P016: return "DXGI_FORMAT_P016";
case DXGI_FORMAT::DXGI_FORMAT_420_OPAQUE: return "DXGI_FORMAT_420_OPAQUE";
case DXGI_FORMAT::DXGI_FORMAT_YUY2: return "DXGI_FORMAT_YUY2";
case DXGI_FORMAT::DXGI_FORMAT_Y210: return "DXGI_FORMAT_Y210";
case DXGI_FORMAT::DXGI_FORMAT_Y216: return "DXGI_FORMAT_Y216";
case DXGI_FORMAT::DXGI_FORMAT_NV11: return "DXGI_FORMAT_NV11";
case DXGI_FORMAT::DXGI_FORMAT_AI44: return "DXGI_FORMAT_AI44";
case DXGI_FORMAT::DXGI_FORMAT_IA44: return "DXGI_FORMAT_IA44";
case DXGI_FORMAT::DXGI_FORMAT_P8: return "DXGI_FORMAT_P8";
case DXGI_FORMAT::DXGI_FORMAT_A8P8: return "DXGI_FORMAT_A8P8";
case DXGI_FORMAT::DXGI_FORMAT_B4G4R4A4_UNORM: return "DXGI_FORMAT_B4G4R4A4_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_P208: return "DXGI_FORMAT_P208";
case DXGI_FORMAT::DXGI_FORMAT_V208: return "DXGI_FORMAT_V208";
case DXGI_FORMAT::DXGI_FORMAT_V408: return "DXGI_FORMAT_V408";
case DXGI_FORMAT::DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE: return "DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE";
case DXGI_FORMAT::DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE: return "DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE";
case DXGI_FORMAT::DXGI_FORMAT_A4B4G4R4_UNORM: return "DXGI_FORMAT_A4B4G4R4_UNORM";
case DXGI_FORMAT::DXGI_FORMAT_FORCE_UINT: return "DXGI_FORMAT_FORCE_UINT";
default: return "UNKNOWN_DXGI_FORMAT";
}
}
} // namespace Rml
#ifdef RMLUI_DEBUG
#define RMLUI_DX_VERIFY_MSG(statement, msg) RMLUI_ASSERTMSG(SUCCEEDED(statement), msg)
// Uncomment the following line to enable additional DirectX debugging.
// #define RMLUI_DX_DEBUG
#else
#define RMLUI_DX_VERIFY_MSG(statement, msg) static_cast<void>(statement)
#endif
#if defined _MSC_VER
#pragma warning(pop)
#elif defined __clang__
#pragma clang diagnostic pop
#endif
// user's preprocessor overrides
// system field (it is not supposed to specify by initialization structure)
// on some render backend implementations (Vulkan/DirectX-12) we have to check memory leaks but
// if we don't reserve memory for a field that contains layers (it is vector)
// at runtime we will get a called dtor of move-to-copy object (because of reallocation)
// and for that matter we will get a false-positive trigger of assert and it is not right generally
#ifndef RMLUI_RENDER_BACKEND_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS
#define RMLUI_RENDER_BACKEND_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS 6
#endif
// this field specifies the default amount of swapchain buffer that will be created
// but it is used only when user provided invalid input in initialization structure of backend
#ifndef RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT
#define RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT 3
#endif
static_assert(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT > 0);
// this field specifies the default amount of videomemory that will be used on creation
// this field is used when user provided invalid input in initialization structure of backend
#ifndef RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION
#define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION (1024 * 1024 * 2)
#endif
static_assert(RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION > 0);
// this field specifies the default amount of videomemory that will be used for texture creation
// on some backends it determines the size of a temp buffer that might be used not trivial (just for uploading texture by copying data in it)
// like on DirectX-12 it has a placement resources feature and making this value lower (4 Mb) the placement resource feature becomes pointless
// and doesn't gain any performance related boosting
#ifndef RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION
#define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION (4 * 1024 * 1024)
#endif
static_assert(RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION > 0);
// system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always
// use it, not just because we handling invalid input from user)
#ifndef RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER
#define RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT
#endif
// system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always
// use it, not just because we handling invalid input from user)
#ifndef RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT
#define RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM
#endif
// system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always
// use it, not just because we handling invalid input from user)
#ifndef RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT
#define RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT DXGI_FORMAT::DXGI_FORMAT_D32_FLOAT_S8X24_UINT
#endif
// system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always
// use it, not just because we handling invalid input from user)
// notice: this field is shared for all srv and cbv and uav it doesn't mean that it specifically allocates for srv 128, cbv 128 and uav 128,
// no! it allocates only on descriptor for all of such types and total amount is 128 not 3 * 128 = 384 !!!
#ifndef RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV
#define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV 128
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_MAXNUMPROGRAMS
#define RMLUI_RENDER_BACKEND_FIELD_MAXNUMPROGRAMS 32
#endif
// for getting total size of constant buffers you should multiply kPreAllocatedConstantBuffers * kSwapchainBackBufferCount e.g. 512 *
// RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT = 1536 (if RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT=3) but keep in mind that
// allocation per constant buffer is 512 bytes (due to fact of the max presented CB in shaders) so 1536 * 512 will be 786'432 bytes and knowing
// that default buffer budget is just 2Mb (2'097'152) you will have 1'310'720 bytes (~37% of budget took by CBs) but some information will go to
// vertices and some information will go to indices and then you will have some free space (maybe small depends on your input) for handling other
// constant buffers that weren't sufficient for your current frame aka reallocation happens at some point it is fine just because the memory
// management of this backend handles a reallocation situations and you will just get a new allocated buffer and all stuff goes to that buffer but
// I give you this reasoning for being a developer that cares about optimizations and cares about pipeline development for end user and for that
// reason I gave you understanding that generally constant buffers even 512 bytes but uses so much in frame will take a big chunk from your VB/IB
// budget just because GAPI needs to handle many frames from swapchain, just because we expect indeed hard scene for rendering, but for average
// mid or low pages that's enough for sure (in terms of preallocated memory for VB,IB,CB)
#ifndef RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS
#define RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS 512
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT
#define RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT 2
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV
#define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV 12
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV
#define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV 8
#endif
// specifies general (for all depth stencil textures that might be allocated by backend) depth value on clear operation (::ClearDepthStencilView)
#ifndef RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_DEPTHSTENCIL_DEPTH_VALUE
#define RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_DEPTHSTENCIL_DEPTH_VALUE 1.0f
#endif
// specifies general (for all depth stencil textures that might be allocated by backend) stencil value on clear operation
// (::ClearDepthStencilView)
#ifndef RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_DEPTHSTENCIL_STENCIL_VALUE
#define RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_DEPTHSTENCIL_STENCIL_VALUE 0
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_RENDERTARGET_COLOR_VAlUE
#define RMLUI_RENDER_BACKEND_FIELD_CLEAR_VALUE_RENDERTARGET_COLOR_VAlUE 0.0f, 0.0f, 0.0f, 1.0f
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_STAGING_BUFFER_SIZE
#define RMLUI_RENDER_BACKEND_FIELD_STAGING_BUFFER_SIZE 1024 * 1024 * 8
#endif
#ifndef RMLUI_RENDER_BACKEND_FIELD_STAGING_BUFFER_CACHE_ENABLED
#define RMLUI_RENDER_BACKEND_FIELD_STAGING_BUFFER_CACHE_ENABLED 1
#endif

3582
vendor/rmlui_backend/RmlUi_Include_GL3.h vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
#ifndef RMLUI_BACKENDS_INCLUDE_VULKAN_H
#define RMLUI_BACKENDS_INCLUDE_VULKAN_H
#if defined RMLUI_PLATFORM_UNIX
#define VK_USE_PLATFORM_XCB_KHR 1
#endif
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#endif
#if defined _MSC_VER
#pragma warning(push, 0)
#elif defined __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wall"
#pragma clang diagnostic ignored "-Wextra"
#pragma clang diagnostic ignored "-Wnullability-extension"
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#pragma clang diagnostic ignored "-Wnullability-completeness"
#elif defined __GNUC__
#pragma GCC system_header
#endif
#include "RmlUi_Vulkan/vulkan.h"
// Always include "vulkan.h" first, this comment prevents clang-format from reordering the includes.
#include "RmlUi_Vulkan/vk_mem_alloc.h"
#if defined _MSC_VER
#pragma warning(pop)
#elif defined __clang__
#pragma clang diagnostic pop
#endif

View File

@@ -0,0 +1,20 @@
#pragma once
#ifndef RMLUI_DISABLE_INCLUDE_WINDOWS
#if !defined _WIN32_WINNT || _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
// Target Windows 7
#define _WIN32_WINNT 0x0601
#endif
#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif

View File

@@ -0,0 +1,18 @@
#pragma once
#ifndef RMLUI_DISABLE_INCLUDE_XLIB
#include <X11/Xlib.h>
// The None and Always defines from X.h conflicts with RmlUi code base,
// use their underlying constants where necessary.
#ifdef None
// Value 0L
#undef None
#endif
#ifdef Always
// Value 2
#undef Always
#endif
#endif

View File

@@ -0,0 +1,330 @@
#include "RmlUi_Platform_GLFW.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Math.h>
#include <RmlUi/Core/StringUtilities.h>
#include <GLFW/glfw3.h>
#define GLFW_HAS_EXTRA_CURSORS (GLFW_VERSION_MAJOR >= 3 && GLFW_VERSION_MINOR >= 4)
SystemInterface_GLFW::SystemInterface_GLFW(GLFWwindow* window) : window(window)
{
RMLUI_ASSERTMSG(window, "Please provide a valid SDL window to the SDL system interface");
cursor_pointer = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
cursor_cross = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
cursor_text = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
#if GLFW_HAS_EXTRA_CURSORS
cursor_move = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
cursor_resize = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
cursor_unavailable = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
#else
cursor_move = cursor_pointer;
cursor_resize = cursor_pointer;
cursor_unavailable = nullptr;
#endif
}
SystemInterface_GLFW::~SystemInterface_GLFW()
{
glfwDestroyCursor(cursor_pointer);
glfwDestroyCursor(cursor_cross);
glfwDestroyCursor(cursor_text);
#if GLFW_HAS_EXTRA_CURSORS
glfwDestroyCursor(cursor_move);
glfwDestroyCursor(cursor_resize);
glfwDestroyCursor(cursor_unavailable);
#endif
}
double SystemInterface_GLFW::GetElapsedTime()
{
return glfwGetTime();
}
void SystemInterface_GLFW::SetMouseCursor(const Rml::String& cursor_name)
{
GLFWcursor* cursor = nullptr;
if (cursor_name.empty() || cursor_name == "arrow")
cursor = nullptr;
else if (cursor_name == "move")
cursor = cursor_move;
else if (cursor_name == "pointer")
cursor = cursor_pointer;
else if (cursor_name == "resize")
cursor = cursor_resize;
else if (cursor_name == "cross")
cursor = cursor_cross;
else if (cursor_name == "text")
cursor = cursor_text;
else if (cursor_name == "unavailable")
cursor = cursor_unavailable;
else if (Rml::StringUtilities::StartsWith(cursor_name, "rmlui-scroll"))
cursor = cursor_move;
glfwSetCursor(window, cursor);
}
void SystemInterface_GLFW::SetClipboardText(const Rml::String& text_utf8)
{
glfwSetClipboardString(window, text_utf8.c_str());
}
void SystemInterface_GLFW::GetClipboardText(Rml::String& text)
{
if (const char* clipboard = glfwGetClipboardString(window))
{
text = Rml::String(clipboard);
}
else
{
text.clear();
}
}
bool RmlGLFW::ProcessKeyCallback(Rml::Context* context, int key, int action, int mods)
{
if (!context)
return true;
bool result = true;
switch (action)
{
case GLFW_PRESS:
case GLFW_REPEAT:
result = context->ProcessKeyDown(RmlGLFW::ConvertKey(key), RmlGLFW::ConvertKeyModifiers(mods));
if (key == GLFW_KEY_ENTER || key == GLFW_KEY_KP_ENTER)
result &= context->ProcessTextInput('\n');
break;
case GLFW_RELEASE: result = context->ProcessKeyUp(RmlGLFW::ConvertKey(key), RmlGLFW::ConvertKeyModifiers(mods)); break;
}
return result;
}
bool RmlGLFW::ProcessCharCallback(Rml::Context* context, unsigned int codepoint)
{
if (!context)
return true;
bool result = context->ProcessTextInput((Rml::Character)codepoint);
return result;
}
bool RmlGLFW::ProcessCursorEnterCallback(Rml::Context* context, int entered)
{
if (!context)
return true;
bool result = true;
if (!entered)
result = context->ProcessMouseLeave();
return result;
}
bool RmlGLFW::ProcessCursorPosCallback(Rml::Context* context, GLFWwindow* window, double xpos, double ypos, int mods)
{
if (!context)
return true;
using Rml::Vector2i;
using Vector2d = Rml::Vector2<double>;
Vector2i window_size, framebuffer_size;
glfwGetWindowSize(window, &window_size.x, &window_size.y);
glfwGetFramebufferSize(window, &framebuffer_size.x, &framebuffer_size.y);
// Convert from mouse position in GLFW screen coordinates to framebuffer coordinates (pixels) used by RmlUi.
const Vector2d mouse_pos = Vector2d(xpos, ypos) * (Vector2d(framebuffer_size) / Vector2d(window_size));
const Vector2i mouse_pos_round = {int(Rml::Math::Round(mouse_pos.x)), int(Rml::Math::Round(mouse_pos.y))};
bool result = context->ProcessMouseMove(mouse_pos_round.x, mouse_pos_round.y, RmlGLFW::ConvertKeyModifiers(mods));
return result;
}
bool RmlGLFW::ProcessMouseButtonCallback(Rml::Context* context, int button, int action, int mods)
{
if (!context)
return true;
bool result = true;
switch (action)
{
case GLFW_PRESS: result = context->ProcessMouseButtonDown(button, RmlGLFW::ConvertKeyModifiers(mods)); break;
case GLFW_RELEASE: result = context->ProcessMouseButtonUp(button, RmlGLFW::ConvertKeyModifiers(mods)); break;
}
return result;
}
bool RmlGLFW::ProcessScrollCallback(Rml::Context* context, double yoffset, int mods)
{
if (!context)
return true;
bool result = context->ProcessMouseWheel(-float(yoffset), RmlGLFW::ConvertKeyModifiers(mods));
return result;
}
void RmlGLFW::ProcessFramebufferSizeCallback(Rml::Context* context, int width, int height)
{
if (context)
context->SetDimensions(Rml::Vector2i(width, height));
}
void RmlGLFW::ProcessContentScaleCallback(Rml::Context* context, float xscale)
{
if (context)
context->SetDensityIndependentPixelRatio(xscale);
}
int RmlGLFW::ConvertKeyModifiers(int glfw_mods)
{
int key_modifier_state = 0;
if (GLFW_MOD_SHIFT & glfw_mods)
key_modifier_state |= Rml::Input::KM_SHIFT;
if (GLFW_MOD_CONTROL & glfw_mods)
key_modifier_state |= Rml::Input::KM_CTRL;
if (GLFW_MOD_ALT & glfw_mods)
key_modifier_state |= Rml::Input::KM_ALT;
if (GLFW_MOD_CAPS_LOCK & glfw_mods)
key_modifier_state |= Rml::Input::KM_SCROLLLOCK;
if (GLFW_MOD_NUM_LOCK & glfw_mods)
key_modifier_state |= Rml::Input::KM_NUMLOCK;
return key_modifier_state;
}
Rml::Input::KeyIdentifier RmlGLFW::ConvertKey(int glfw_key)
{
// clang-format off
switch (glfw_key)
{
case GLFW_KEY_A: return Rml::Input::KI_A;
case GLFW_KEY_B: return Rml::Input::KI_B;
case GLFW_KEY_C: return Rml::Input::KI_C;
case GLFW_KEY_D: return Rml::Input::KI_D;
case GLFW_KEY_E: return Rml::Input::KI_E;
case GLFW_KEY_F: return Rml::Input::KI_F;
case GLFW_KEY_G: return Rml::Input::KI_G;
case GLFW_KEY_H: return Rml::Input::KI_H;
case GLFW_KEY_I: return Rml::Input::KI_I;
case GLFW_KEY_J: return Rml::Input::KI_J;
case GLFW_KEY_K: return Rml::Input::KI_K;
case GLFW_KEY_L: return Rml::Input::KI_L;
case GLFW_KEY_M: return Rml::Input::KI_M;
case GLFW_KEY_N: return Rml::Input::KI_N;
case GLFW_KEY_O: return Rml::Input::KI_O;
case GLFW_KEY_P: return Rml::Input::KI_P;
case GLFW_KEY_Q: return Rml::Input::KI_Q;
case GLFW_KEY_R: return Rml::Input::KI_R;
case GLFW_KEY_S: return Rml::Input::KI_S;
case GLFW_KEY_T: return Rml::Input::KI_T;
case GLFW_KEY_U: return Rml::Input::KI_U;
case GLFW_KEY_V: return Rml::Input::KI_V;
case GLFW_KEY_W: return Rml::Input::KI_W;
case GLFW_KEY_X: return Rml::Input::KI_X;
case GLFW_KEY_Y: return Rml::Input::KI_Y;
case GLFW_KEY_Z: return Rml::Input::KI_Z;
case GLFW_KEY_0: return Rml::Input::KI_0;
case GLFW_KEY_1: return Rml::Input::KI_1;
case GLFW_KEY_2: return Rml::Input::KI_2;
case GLFW_KEY_3: return Rml::Input::KI_3;
case GLFW_KEY_4: return Rml::Input::KI_4;
case GLFW_KEY_5: return Rml::Input::KI_5;
case GLFW_KEY_6: return Rml::Input::KI_6;
case GLFW_KEY_7: return Rml::Input::KI_7;
case GLFW_KEY_8: return Rml::Input::KI_8;
case GLFW_KEY_9: return Rml::Input::KI_9;
case GLFW_KEY_BACKSPACE: return Rml::Input::KI_BACK;
case GLFW_KEY_TAB: return Rml::Input::KI_TAB;
case GLFW_KEY_ENTER: return Rml::Input::KI_RETURN;
case GLFW_KEY_PAUSE: return Rml::Input::KI_PAUSE;
case GLFW_KEY_CAPS_LOCK: return Rml::Input::KI_CAPITAL;
case GLFW_KEY_ESCAPE: return Rml::Input::KI_ESCAPE;
case GLFW_KEY_SPACE: return Rml::Input::KI_SPACE;
case GLFW_KEY_PAGE_UP: return Rml::Input::KI_PRIOR;
case GLFW_KEY_PAGE_DOWN: return Rml::Input::KI_NEXT;
case GLFW_KEY_END: return Rml::Input::KI_END;
case GLFW_KEY_HOME: return Rml::Input::KI_HOME;
case GLFW_KEY_LEFT: return Rml::Input::KI_LEFT;
case GLFW_KEY_UP: return Rml::Input::KI_UP;
case GLFW_KEY_RIGHT: return Rml::Input::KI_RIGHT;
case GLFW_KEY_DOWN: return Rml::Input::KI_DOWN;
case GLFW_KEY_PRINT_SCREEN: return Rml::Input::KI_SNAPSHOT;
case GLFW_KEY_INSERT: return Rml::Input::KI_INSERT;
case GLFW_KEY_DELETE: return Rml::Input::KI_DELETE;
case GLFW_KEY_LEFT_SUPER: return Rml::Input::KI_LWIN;
case GLFW_KEY_RIGHT_SUPER: return Rml::Input::KI_RWIN;
case GLFW_KEY_KP_0: return Rml::Input::KI_NUMPAD0;
case GLFW_KEY_KP_1: return Rml::Input::KI_NUMPAD1;
case GLFW_KEY_KP_2: return Rml::Input::KI_NUMPAD2;
case GLFW_KEY_KP_3: return Rml::Input::KI_NUMPAD3;
case GLFW_KEY_KP_4: return Rml::Input::KI_NUMPAD4;
case GLFW_KEY_KP_5: return Rml::Input::KI_NUMPAD5;
case GLFW_KEY_KP_6: return Rml::Input::KI_NUMPAD6;
case GLFW_KEY_KP_7: return Rml::Input::KI_NUMPAD7;
case GLFW_KEY_KP_8: return Rml::Input::KI_NUMPAD8;
case GLFW_KEY_KP_9: return Rml::Input::KI_NUMPAD9;
case GLFW_KEY_KP_ENTER: return Rml::Input::KI_NUMPADENTER;
case GLFW_KEY_KP_MULTIPLY: return Rml::Input::KI_MULTIPLY;
case GLFW_KEY_KP_ADD: return Rml::Input::KI_ADD;
case GLFW_KEY_KP_SUBTRACT: return Rml::Input::KI_SUBTRACT;
case GLFW_KEY_KP_DECIMAL: return Rml::Input::KI_DECIMAL;
case GLFW_KEY_KP_DIVIDE: return Rml::Input::KI_DIVIDE;
case GLFW_KEY_F1: return Rml::Input::KI_F1;
case GLFW_KEY_F2: return Rml::Input::KI_F2;
case GLFW_KEY_F3: return Rml::Input::KI_F3;
case GLFW_KEY_F4: return Rml::Input::KI_F4;
case GLFW_KEY_F5: return Rml::Input::KI_F5;
case GLFW_KEY_F6: return Rml::Input::KI_F6;
case GLFW_KEY_F7: return Rml::Input::KI_F7;
case GLFW_KEY_F8: return Rml::Input::KI_F8;
case GLFW_KEY_F9: return Rml::Input::KI_F9;
case GLFW_KEY_F10: return Rml::Input::KI_F10;
case GLFW_KEY_F11: return Rml::Input::KI_F11;
case GLFW_KEY_F12: return Rml::Input::KI_F12;
case GLFW_KEY_F13: return Rml::Input::KI_F13;
case GLFW_KEY_F14: return Rml::Input::KI_F14;
case GLFW_KEY_F15: return Rml::Input::KI_F15;
case GLFW_KEY_F16: return Rml::Input::KI_F16;
case GLFW_KEY_F17: return Rml::Input::KI_F17;
case GLFW_KEY_F18: return Rml::Input::KI_F18;
case GLFW_KEY_F19: return Rml::Input::KI_F19;
case GLFW_KEY_F20: return Rml::Input::KI_F20;
case GLFW_KEY_F21: return Rml::Input::KI_F21;
case GLFW_KEY_F22: return Rml::Input::KI_F22;
case GLFW_KEY_F23: return Rml::Input::KI_F23;
case GLFW_KEY_F24: return Rml::Input::KI_F24;
case GLFW_KEY_NUM_LOCK: return Rml::Input::KI_NUMLOCK;
case GLFW_KEY_SCROLL_LOCK: return Rml::Input::KI_SCROLL;
case GLFW_KEY_LEFT_SHIFT: return Rml::Input::KI_LSHIFT;
case GLFW_KEY_LEFT_CONTROL: return Rml::Input::KI_LCONTROL;
case GLFW_KEY_RIGHT_SHIFT: return Rml::Input::KI_RSHIFT;
case GLFW_KEY_RIGHT_CONTROL: return Rml::Input::KI_RCONTROL;
case GLFW_KEY_MENU: return Rml::Input::KI_LMENU;
case GLFW_KEY_KP_EQUAL: return Rml::Input::KI_OEM_NEC_EQUAL;
default: break;
}
// clang-format on
return Rml::Input::KI_UNKNOWN;
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/Types.h>
#include <GLFW/glfw3.h>
class SystemInterface_GLFW : public Rml::SystemInterface {
public:
SystemInterface_GLFW(GLFWwindow* window);
~SystemInterface_GLFW();
// -- Inherited from Rml::SystemInterface --
double GetElapsedTime() override;
void SetMouseCursor(const Rml::String& cursor_name) override;
void SetClipboardText(const Rml::String& text) override;
void GetClipboardText(Rml::String& text) override;
private:
GLFWwindow* window = nullptr;
GLFWcursor* cursor_pointer = nullptr;
GLFWcursor* cursor_cross = nullptr;
GLFWcursor* cursor_text = nullptr;
GLFWcursor* cursor_move = nullptr;
GLFWcursor* cursor_resize = nullptr;
GLFWcursor* cursor_unavailable = nullptr;
};
/**
Optional helper functions for the GLFW plaform.
*/
namespace RmlGLFW {
// The following optional functions are intended to be called from their respective GLFW callback functions. The functions expect arguments passed
// directly from GLFW, in addition to the RmlUi context to apply the input or sizing event on. The input callbacks return true if the event is
// propagating, i.e. was not handled by the context.
bool ProcessKeyCallback(Rml::Context* context, int key, int action, int mods);
bool ProcessCharCallback(Rml::Context* context, unsigned int codepoint);
bool ProcessCursorEnterCallback(Rml::Context* context, int entered);
bool ProcessCursorPosCallback(Rml::Context* context, GLFWwindow* window, double xpos, double ypos, int mods);
bool ProcessMouseButtonCallback(Rml::Context* context, int button, int action, int mods);
bool ProcessScrollCallback(Rml::Context* context, double yoffset, int mods);
void ProcessFramebufferSizeCallback(Rml::Context* context, int width, int height);
void ProcessContentScaleCallback(Rml::Context* context, float xscale);
// Converts the GLFW key to RmlUi key.
Rml::Input::KeyIdentifier ConvertKey(int glfw_key);
// Converts the GLFW key modifiers to RmlUi key modifiers.
int ConvertKeyModifiers(int glfw_mods);
} // namespace RmlGLFW

View File

@@ -0,0 +1,569 @@
#include "RmlUi_Platform_SDL.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/StringUtilities.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/TextInputContext.h>
static Rml::TouchList TouchEventToTouchList(SDL_Event& ev, Rml::Context* context, SDL_FingerID finger_id)
{
const Rml::Vector2f position = Rml::Vector2f{ev.tfinger.x, ev.tfinger.y} * Rml::Vector2f{context->GetDimensions()};
return {Rml::Touch{static_cast<Rml::TouchId>(finger_id), position}};
}
SystemInterface_SDL::SystemInterface_SDL(SDL_Window* in_window) : window(in_window)
{
RMLUI_ASSERTMSG(window, "Please provide a valid SDL window to the SDL system interface");
#if SDL_MAJOR_VERSION >= 3
cursor_default = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_DEFAULT);
cursor_move = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_MOVE);
cursor_pointer = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_POINTER);
cursor_resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NWSE_RESIZE);
cursor_cross = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
cursor_text = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_TEXT);
cursor_unavailable = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NOT_ALLOWED);
#else
cursor_default = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
cursor_move = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL);
cursor_pointer = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
cursor_resize = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE);
cursor_cross = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
cursor_text = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
cursor_unavailable = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO);
(void)window; // Window is unused on SDL 2.
#endif
}
SystemInterface_SDL::~SystemInterface_SDL()
{
#if SDL_MAJOR_VERSION >= 3
auto DestroyCursor = [](SDL_Cursor* cursor) { SDL_DestroyCursor(cursor); };
#else
auto DestroyCursor = [](SDL_Cursor* cursor) { SDL_FreeCursor(cursor); };
#endif
DestroyCursor(cursor_default);
DestroyCursor(cursor_move);
DestroyCursor(cursor_pointer);
DestroyCursor(cursor_resize);
DestroyCursor(cursor_cross);
DestroyCursor(cursor_text);
DestroyCursor(cursor_unavailable);
}
double SystemInterface_SDL::GetElapsedTime()
{
static const Uint64 start = SDL_GetPerformanceCounter();
static const double frequency = double(SDL_GetPerformanceFrequency());
return double(SDL_GetPerformanceCounter() - start) / frequency;
}
void SystemInterface_SDL::SetMouseCursor(const Rml::String& cursor_name)
{
SDL_Cursor* cursor = nullptr;
if (cursor_name.empty() || cursor_name == "arrow")
cursor = cursor_default;
else if (cursor_name == "move")
cursor = cursor_move;
else if (cursor_name == "pointer")
cursor = cursor_pointer;
else if (cursor_name == "resize")
cursor = cursor_resize;
else if (cursor_name == "cross")
cursor = cursor_cross;
else if (cursor_name == "text")
cursor = cursor_text;
else if (cursor_name == "unavailable")
cursor = cursor_unavailable;
else if (Rml::StringUtilities::StartsWith(cursor_name, "rmlui-scroll"))
cursor = cursor_move;
if (cursor)
SDL_SetCursor(cursor);
}
void SystemInterface_SDL::SetClipboardText(const Rml::String& text)
{
SDL_SetClipboardText(text.c_str());
}
void SystemInterface_SDL::GetClipboardText(Rml::String& text)
{
char* raw_text = SDL_GetClipboardText();
text = Rml::String(raw_text);
SDL_free(raw_text);
}
void SystemInterface_SDL::ActivateKeyboard(Rml::Vector2f caret_position, float line_height)
{
#if SDL_MAJOR_VERSION >= 3
const SDL_Rect rect = {int(caret_position.x), int(caret_position.y), 1, int(line_height)};
SDL_SetTextInputArea(window, &rect, 0);
SDL_StartTextInput(window);
#else
(void)caret_position;
(void)line_height;
SDL_StartTextInput();
#endif
}
void SystemInterface_SDL::DeactivateKeyboard()
{
#if SDL_MAJOR_VERSION >= 3
SDL_StopTextInput(window);
#else
SDL_StopTextInput();
#endif
}
bool RmlSDL::InputEventHandler(Rml::Context* context, SDL_Window* window, SDL_Event& ev)
{
#if SDL_MAJOR_VERSION >= 3
#define RMLSDL_WINDOW_EVENTS_BEGIN
#define RMLSDL_WINDOW_EVENTS_END
auto GetKey = [](const SDL_Event& event) { return event.key.key; };
auto GetPixelDensity = [](SDL_Window* target_window) { return SDL_GetWindowPixelDensity(target_window); };
auto GetFingerId = [](const SDL_Event& event) { return event.tfinger.fingerID; };
constexpr auto event_mouse_motion = SDL_EVENT_MOUSE_MOTION;
constexpr auto event_mouse_down = SDL_EVENT_MOUSE_BUTTON_DOWN;
constexpr auto event_mouse_up = SDL_EVENT_MOUSE_BUTTON_UP;
constexpr auto event_mouse_wheel = SDL_EVENT_MOUSE_WHEEL;
constexpr auto event_key_down = SDL_EVENT_KEY_DOWN;
constexpr auto event_key_up = SDL_EVENT_KEY_UP;
constexpr auto event_text_input = SDL_EVENT_TEXT_INPUT;
constexpr auto event_window_size_changed = SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED;
constexpr auto event_window_leave = SDL_EVENT_WINDOW_MOUSE_LEAVE;
constexpr auto event_finger_down = SDL_EVENT_FINGER_DOWN;
constexpr auto event_finger_up = SDL_EVENT_FINGER_UP;
constexpr auto event_finger_motion = SDL_EVENT_FINGER_MOTION;
constexpr auto rmlsdl_true = true;
constexpr auto rmlsdl_false = false;
#else
(void)window;
#define RMLSDL_WINDOW_EVENTS_BEGIN \
case SDL_WINDOWEVENT: \
{ \
switch (ev.window.event) \
{
#define RMLSDL_WINDOW_EVENTS_END \
} \
} \
break;
auto GetKey = [](const SDL_Event& event) { return event.key.keysym.sym; };
auto GetPixelDensity = [](SDL_Window* /*target_window*/) { return 1.f; };
auto GetFingerId = [](const SDL_Event& event) { return event.tfinger.fingerId; };
constexpr auto event_mouse_motion = SDL_MOUSEMOTION;
constexpr auto event_mouse_down = SDL_MOUSEBUTTONDOWN;
constexpr auto event_mouse_up = SDL_MOUSEBUTTONUP;
constexpr auto event_mouse_wheel = SDL_MOUSEWHEEL;
constexpr auto event_key_down = SDL_KEYDOWN;
constexpr auto event_key_up = SDL_KEYUP;
constexpr auto event_text_input = SDL_TEXTINPUT;
constexpr auto event_window_size_changed = SDL_WINDOWEVENT_SIZE_CHANGED;
constexpr auto event_window_leave = SDL_WINDOWEVENT_LEAVE;
constexpr auto event_finger_down = SDL_FINGERDOWN;
constexpr auto event_finger_up = SDL_FINGERUP;
constexpr auto event_finger_motion = SDL_FINGERMOTION;
constexpr auto rmlsdl_true = SDL_TRUE;
constexpr auto rmlsdl_false = SDL_FALSE;
#endif
bool result = true;
switch (ev.type)
{
#ifndef RMLUI_BACKEND_SIMULATE_TOUCH
case event_mouse_motion:
{
const float pixel_density = GetPixelDensity(window);
result = context->ProcessMouseMove(int(ev.motion.x * pixel_density), int(ev.motion.y * pixel_density), GetKeyModifierState());
}
break;
case event_mouse_down:
{
result = context->ProcessMouseButtonDown(ConvertMouseButton(ev.button.button), GetKeyModifierState());
SDL_CaptureMouse(rmlsdl_true);
}
break;
case event_mouse_up:
{
SDL_CaptureMouse(rmlsdl_false);
result = context->ProcessMouseButtonUp(ConvertMouseButton(ev.button.button), GetKeyModifierState());
}
break;
#endif
case event_mouse_wheel:
{
result = context->ProcessMouseWheel(float(-ev.wheel.y), GetKeyModifierState());
}
break;
case event_key_down:
{
result = context->ProcessKeyDown(ConvertKey(GetKey(ev)), GetKeyModifierState());
if (GetKey(ev) == SDLK_RETURN || GetKey(ev) == SDLK_KP_ENTER)
result &= context->ProcessTextInput('\n');
}
break;
case event_key_up:
{
result = context->ProcessKeyUp(ConvertKey(GetKey(ev)), GetKeyModifierState());
}
break;
case event_text_input:
{
result = context->ProcessTextInput(Rml::String(&ev.text.text[0]));
}
break;
case event_finger_down:
{
const Rml::TouchList touches = TouchEventToTouchList(ev, context, GetFingerId(ev));
result = context->ProcessTouchStart(touches, GetKeyModifierState());
}
break;
case event_finger_motion:
{
const Rml::TouchList touches = TouchEventToTouchList(ev, context, GetFingerId(ev));
result = context->ProcessTouchMove(touches, GetKeyModifierState());
}
break;
case event_finger_up:
{
const Rml::TouchList touches = TouchEventToTouchList(ev, context, GetFingerId(ev));
result = context->ProcessTouchEnd(touches, GetKeyModifierState());
}
break;
RMLSDL_WINDOW_EVENTS_BEGIN
case event_window_size_changed:
{
Rml::Vector2i dimensions(ev.window.data1, ev.window.data2);
#if SDL_MAJOR_VERSION >= 3
// SDL_Renderer backend (SDL3): if SDL_SetRenderLogicalPresentation() is enabled, the renderer uses a fixed logical
// output size (render coordinates) and scales it to the window; use that logical size for the RmlUi context.
// Input events should be converted to render coordinates first (e.g. SDL_ConvertEventToRenderCoordinates()).
SDL_Renderer* renderer = SDL_GetRenderer(window);
if (renderer)
{
int logical_w = 0;
int logical_h = 0;
SDL_RendererLogicalPresentation mode{};
if (SDL_GetRenderLogicalPresentation(renderer, &logical_w, &logical_h, &mode)
&& mode != SDL_LOGICAL_PRESENTATION_DISABLED
&& logical_w > 0 && logical_h > 0)
dimensions = Rml::Vector2i(logical_w, logical_h);
}
#endif
context->SetDimensions(dimensions);
}
break;
case event_window_leave:
{
context->ProcessMouseLeave();
}
break;
#if SDL_MAJOR_VERSION >= 3
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
{
const float display_scale = SDL_GetWindowDisplayScale(window);
context->SetDensityIndependentPixelRatio(display_scale);
}
break;
#endif
RMLSDL_WINDOW_EVENTS_END
default: break;
}
return result;
}
Rml::Input::KeyIdentifier RmlSDL::ConvertKey(int sdlkey)
{
#if SDL_MAJOR_VERSION >= 3
constexpr auto key_a = SDLK_A;
constexpr auto key_b = SDLK_B;
constexpr auto key_c = SDLK_C;
constexpr auto key_d = SDLK_D;
constexpr auto key_e = SDLK_E;
constexpr auto key_f = SDLK_F;
constexpr auto key_g = SDLK_G;
constexpr auto key_h = SDLK_H;
constexpr auto key_i = SDLK_I;
constexpr auto key_j = SDLK_J;
constexpr auto key_k = SDLK_K;
constexpr auto key_l = SDLK_L;
constexpr auto key_m = SDLK_M;
constexpr auto key_n = SDLK_N;
constexpr auto key_o = SDLK_O;
constexpr auto key_p = SDLK_P;
constexpr auto key_q = SDLK_Q;
constexpr auto key_r = SDLK_R;
constexpr auto key_s = SDLK_S;
constexpr auto key_t = SDLK_T;
constexpr auto key_u = SDLK_U;
constexpr auto key_v = SDLK_V;
constexpr auto key_w = SDLK_W;
constexpr auto key_x = SDLK_X;
constexpr auto key_y = SDLK_Y;
constexpr auto key_z = SDLK_Z;
constexpr auto key_grave = SDLK_GRAVE;
constexpr auto key_dblapostrophe = SDLK_DBLAPOSTROPHE;
#else
constexpr auto key_a = SDLK_a;
constexpr auto key_b = SDLK_b;
constexpr auto key_c = SDLK_c;
constexpr auto key_d = SDLK_d;
constexpr auto key_e = SDLK_e;
constexpr auto key_f = SDLK_f;
constexpr auto key_g = SDLK_g;
constexpr auto key_h = SDLK_h;
constexpr auto key_i = SDLK_i;
constexpr auto key_j = SDLK_j;
constexpr auto key_k = SDLK_k;
constexpr auto key_l = SDLK_l;
constexpr auto key_m = SDLK_m;
constexpr auto key_n = SDLK_n;
constexpr auto key_o = SDLK_o;
constexpr auto key_p = SDLK_p;
constexpr auto key_q = SDLK_q;
constexpr auto key_r = SDLK_r;
constexpr auto key_s = SDLK_s;
constexpr auto key_t = SDLK_t;
constexpr auto key_u = SDLK_u;
constexpr auto key_v = SDLK_v;
constexpr auto key_w = SDLK_w;
constexpr auto key_x = SDLK_x;
constexpr auto key_y = SDLK_y;
constexpr auto key_z = SDLK_z;
constexpr auto key_grave = SDLK_BACKQUOTE;
constexpr auto key_dblapostrophe = SDLK_QUOTEDBL;
#endif
// clang-format off
switch (sdlkey)
{
case SDLK_UNKNOWN: return Rml::Input::KI_UNKNOWN;
case SDLK_ESCAPE: return Rml::Input::KI_ESCAPE;
case SDLK_SPACE: return Rml::Input::KI_SPACE;
case SDLK_0: return Rml::Input::KI_0;
case SDLK_1: return Rml::Input::KI_1;
case SDLK_2: return Rml::Input::KI_2;
case SDLK_3: return Rml::Input::KI_3;
case SDLK_4: return Rml::Input::KI_4;
case SDLK_5: return Rml::Input::KI_5;
case SDLK_6: return Rml::Input::KI_6;
case SDLK_7: return Rml::Input::KI_7;
case SDLK_8: return Rml::Input::KI_8;
case SDLK_9: return Rml::Input::KI_9;
case key_a: return Rml::Input::KI_A;
case key_b: return Rml::Input::KI_B;
case key_c: return Rml::Input::KI_C;
case key_d: return Rml::Input::KI_D;
case key_e: return Rml::Input::KI_E;
case key_f: return Rml::Input::KI_F;
case key_g: return Rml::Input::KI_G;
case key_h: return Rml::Input::KI_H;
case key_i: return Rml::Input::KI_I;
case key_j: return Rml::Input::KI_J;
case key_k: return Rml::Input::KI_K;
case key_l: return Rml::Input::KI_L;
case key_m: return Rml::Input::KI_M;
case key_n: return Rml::Input::KI_N;
case key_o: return Rml::Input::KI_O;
case key_p: return Rml::Input::KI_P;
case key_q: return Rml::Input::KI_Q;
case key_r: return Rml::Input::KI_R;
case key_s: return Rml::Input::KI_S;
case key_t: return Rml::Input::KI_T;
case key_u: return Rml::Input::KI_U;
case key_v: return Rml::Input::KI_V;
case key_w: return Rml::Input::KI_W;
case key_x: return Rml::Input::KI_X;
case key_y: return Rml::Input::KI_Y;
case key_z: return Rml::Input::KI_Z;
case SDLK_SEMICOLON: return Rml::Input::KI_OEM_1;
case SDLK_PLUS: return Rml::Input::KI_OEM_PLUS;
case SDLK_COMMA: return Rml::Input::KI_OEM_COMMA;
case SDLK_MINUS: return Rml::Input::KI_OEM_MINUS;
case SDLK_PERIOD: return Rml::Input::KI_OEM_PERIOD;
case SDLK_SLASH: return Rml::Input::KI_OEM_2;
case key_grave: return Rml::Input::KI_OEM_3;
case SDLK_LEFTBRACKET: return Rml::Input::KI_OEM_4;
case SDLK_BACKSLASH: return Rml::Input::KI_OEM_5;
case SDLK_RIGHTBRACKET: return Rml::Input::KI_OEM_6;
case key_dblapostrophe: return Rml::Input::KI_OEM_7;
case SDLK_KP_0: return Rml::Input::KI_NUMPAD0;
case SDLK_KP_1: return Rml::Input::KI_NUMPAD1;
case SDLK_KP_2: return Rml::Input::KI_NUMPAD2;
case SDLK_KP_3: return Rml::Input::KI_NUMPAD3;
case SDLK_KP_4: return Rml::Input::KI_NUMPAD4;
case SDLK_KP_5: return Rml::Input::KI_NUMPAD5;
case SDLK_KP_6: return Rml::Input::KI_NUMPAD6;
case SDLK_KP_7: return Rml::Input::KI_NUMPAD7;
case SDLK_KP_8: return Rml::Input::KI_NUMPAD8;
case SDLK_KP_9: return Rml::Input::KI_NUMPAD9;
case SDLK_KP_ENTER: return Rml::Input::KI_NUMPADENTER;
case SDLK_KP_MULTIPLY: return Rml::Input::KI_MULTIPLY;
case SDLK_KP_PLUS: return Rml::Input::KI_ADD;
case SDLK_KP_MINUS: return Rml::Input::KI_SUBTRACT;
case SDLK_KP_PERIOD: return Rml::Input::KI_DECIMAL;
case SDLK_KP_DIVIDE: return Rml::Input::KI_DIVIDE;
case SDLK_KP_EQUALS: return Rml::Input::KI_OEM_NEC_EQUAL;
case SDLK_BACKSPACE: return Rml::Input::KI_BACK;
case SDLK_TAB: return Rml::Input::KI_TAB;
case SDLK_CLEAR: return Rml::Input::KI_CLEAR;
case SDLK_RETURN: return Rml::Input::KI_RETURN;
case SDLK_PAUSE: return Rml::Input::KI_PAUSE;
case SDLK_CAPSLOCK: return Rml::Input::KI_CAPITAL;
case SDLK_PAGEUP: return Rml::Input::KI_PRIOR;
case SDLK_PAGEDOWN: return Rml::Input::KI_NEXT;
case SDLK_END: return Rml::Input::KI_END;
case SDLK_HOME: return Rml::Input::KI_HOME;
case SDLK_LEFT: return Rml::Input::KI_LEFT;
case SDLK_UP: return Rml::Input::KI_UP;
case SDLK_RIGHT: return Rml::Input::KI_RIGHT;
case SDLK_DOWN: return Rml::Input::KI_DOWN;
case SDLK_INSERT: return Rml::Input::KI_INSERT;
case SDLK_DELETE: return Rml::Input::KI_DELETE;
case SDLK_HELP: return Rml::Input::KI_HELP;
case SDLK_F1: return Rml::Input::KI_F1;
case SDLK_F2: return Rml::Input::KI_F2;
case SDLK_F3: return Rml::Input::KI_F3;
case SDLK_F4: return Rml::Input::KI_F4;
case SDLK_F5: return Rml::Input::KI_F5;
case SDLK_F6: return Rml::Input::KI_F6;
case SDLK_F7: return Rml::Input::KI_F7;
case SDLK_F8: return Rml::Input::KI_F8;
case SDLK_F9: return Rml::Input::KI_F9;
case SDLK_F10: return Rml::Input::KI_F10;
case SDLK_F11: return Rml::Input::KI_F11;
case SDLK_F12: return Rml::Input::KI_F12;
case SDLK_F13: return Rml::Input::KI_F13;
case SDLK_F14: return Rml::Input::KI_F14;
case SDLK_F15: return Rml::Input::KI_F15;
case SDLK_NUMLOCKCLEAR: return Rml::Input::KI_NUMLOCK;
case SDLK_SCROLLLOCK: return Rml::Input::KI_SCROLL;
case SDLK_LSHIFT: return Rml::Input::KI_LSHIFT;
case SDLK_RSHIFT: return Rml::Input::KI_RSHIFT;
case SDLK_LCTRL: return Rml::Input::KI_LCONTROL;
case SDLK_RCTRL: return Rml::Input::KI_RCONTROL;
case SDLK_LALT: return Rml::Input::KI_LMENU;
case SDLK_RALT: return Rml::Input::KI_RMENU;
case SDLK_LGUI: return Rml::Input::KI_LMETA;
case SDLK_RGUI: return Rml::Input::KI_RMETA;
/*
case SDLK_LSUPER: return Rml::Input::KI_LWIN;
case SDLK_RSUPER: return Rml::Input::KI_RWIN;
*/
default: break;
}
// clang-format on
return Rml::Input::KI_UNKNOWN;
}
int RmlSDL::ConvertMouseButton(int button)
{
switch (button)
{
case SDL_BUTTON_LEFT: return 0;
case SDL_BUTTON_RIGHT: return 1;
case SDL_BUTTON_MIDDLE: return 2;
default: return 3;
}
}
int RmlSDL::GetKeyModifierState()
{
SDL_Keymod sdl_mods = SDL_GetModState();
#if SDL_MAJOR_VERSION >= 3
constexpr auto mod_ctrl = SDL_KMOD_CTRL;
constexpr auto mod_shift = SDL_KMOD_SHIFT;
constexpr auto mod_alt = SDL_KMOD_ALT;
constexpr auto mod_num = SDL_KMOD_NUM;
constexpr auto mod_caps = SDL_KMOD_CAPS;
#else
constexpr auto mod_ctrl = KMOD_CTRL;
constexpr auto mod_shift = KMOD_SHIFT;
constexpr auto mod_alt = KMOD_ALT;
constexpr auto mod_num = KMOD_NUM;
constexpr auto mod_caps = KMOD_CAPS;
#endif
int retval = 0;
if (sdl_mods & mod_ctrl)
retval |= Rml::Input::KM_CTRL;
if (sdl_mods & mod_shift)
retval |= Rml::Input::KM_SHIFT;
if (sdl_mods & mod_alt)
retval |= Rml::Input::KM_ALT;
if (sdl_mods & mod_num)
retval |= Rml::Input::KM_NUMLOCK;
if (sdl_mods & mod_caps)
retval |= Rml::Input::KM_CAPSLOCK;
return retval;
}
void TextInputMethodEditor_SDL::OnActivate(Rml::TextInputContext* input_context)
{
context = input_context;
}
void TextInputMethodEditor_SDL::OnDeactivate(Rml::TextInputContext* input_context)
{
if (context == input_context)
context = nullptr;
}
void TextInputMethodEditor_SDL::OnDestroy(Rml::TextInputContext* input_context)
{
if (context == input_context)
context = nullptr;
}
void TextInputMethodEditor_SDL::HandleEdit(const SDL_TextEditingEvent& ev)
{
if (context == nullptr)
return;
auto string = Rml::String(ev.text);
auto length = static_cast<int>(Rml::StringUtilities::LengthUTF8(string));
auto composing = start != end;
if (!composing)
context->GetSelectionRange(start, end);
if (composing || length > 0)
context->SetText(string, start, end);
end = start + length;
context->SetCompositionRange(start, end);
if (length > 0 && ev.start >= 0 && ev.length >= 0)
context->SetSelectionRange(start + ev.start, start + ev.start + ev.length);
else if (composing)
context->SetCursorPosition(end);
// When committing, SDL sends a text editing event with an empty string and a
// separate text input event with the committed text.
if (composing && length == 0)
context->CommitComposition(Rml::StringView());
}

View File

@@ -0,0 +1,78 @@
#pragma once
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/TextInputHandler.h>
#include <RmlUi/Core/Types.h>
#if RMLUI_SDL_VERSION_MAJOR == 3
#include <SDL3/SDL.h>
#elif RMLUI_SDL_VERSION_MAJOR == 2
#include <SDL.h>
#else
#error "Unspecified RMLUI_SDL_VERSION_MAJOR. Please set this definition to the major version of the SDL library being linked to."
#endif
class SystemInterface_SDL : public Rml::SystemInterface {
public:
SystemInterface_SDL(SDL_Window* window);
~SystemInterface_SDL();
// -- Inherited from Rml::SystemInterface --
double GetElapsedTime() override;
void SetMouseCursor(const Rml::String& cursor_name) override;
void SetClipboardText(const Rml::String& text) override;
void GetClipboardText(Rml::String& text) override;
void ActivateKeyboard(Rml::Vector2f caret_position, float line_height) override;
void DeactivateKeyboard() override;
private:
SDL_Window* window = nullptr;
SDL_Cursor* cursor_default = nullptr;
SDL_Cursor* cursor_move = nullptr;
SDL_Cursor* cursor_pointer = nullptr;
SDL_Cursor* cursor_resize = nullptr;
SDL_Cursor* cursor_cross = nullptr;
SDL_Cursor* cursor_text = nullptr;
SDL_Cursor* cursor_unavailable = nullptr;
};
namespace RmlSDL {
// Applies input on the context based on the given SDL event.
//
// Note (SDL3 + SDL_Renderer): When using SDL_SetRenderLogicalPresentation(), SDL_Renderer operates in render
// coordinates (logical coordinates). Therefore, before passing an SDL_Event to InputEventHandler, input event
// coordinates (mouse/touch/etc.) should be converted to render coordinates, e.g.
// SDL_ConvertEventToRenderCoordinates(renderer, &ev).
// @return True if the event is still propagating, false if it was handled by the context.
bool InputEventHandler(Rml::Context* context, SDL_Window* window, SDL_Event& ev);
// Converts the SDL key to RmlUi key.
Rml::Input::KeyIdentifier ConvertKey(int sdl_key);
// Converts the SDL mouse button to RmlUi mouse button.
int ConvertMouseButton(int sdl_mouse_button);
// Returns the active RmlUi key modifier state.
int GetKeyModifierState();
} // namespace RmlSDL
class TextInputMethodEditor_SDL final : public Rml::TextInputHandler {
public:
void OnActivate(Rml::TextInputContext* input_context) override;
void OnDeactivate(Rml::TextInputContext* input_context) override;
void OnDestroy(Rml::TextInputContext* input_context) override;
void HandleEdit(const SDL_TextEditingEvent& ev);
private:
Rml::TextInputContext* context = nullptr;
int start = 0, end = 0;
};

View File

@@ -0,0 +1,287 @@
#include "RmlUi_Platform_SFML.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/StringUtilities.h>
#include <RmlUi/Core/SystemInterface.h>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#if SFML_VERSION_MAJOR >= 3
SystemInterface_SFML::Cursors::Cursors() :
cursor_default(sf::Cursor::Type::Arrow), cursor_move(sf::Cursor::Type::SizeAll), cursor_pointer(sf::Cursor::Type::Hand),
cursor_resize(sf::Cursor::createFromSystem(sf::Cursor::Type::SizeTopLeftBottomRight).value_or(sf::Cursor(sf::Cursor::Type::SizeAll))),
cursor_cross(sf::Cursor::Type::Cross), cursor_text(sf::Cursor::Type::Text), cursor_unavailable(sf::Cursor::Type::NotAllowed)
{}
SystemInterface_SFML::SystemInterface_SFML()
{
try
{
cursors = std::make_unique<Cursors>();
} catch (const sf::Exception& /*exception*/)
{
cursors.reset();
}
}
#else
SystemInterface_SFML::Cursors::Cursors() {}
SystemInterface_SFML::SystemInterface_SFML()
{
cursors = std::make_unique<Cursors>();
bool cursors_valid = true;
cursors_valid &= cursors->cursor_default.loadFromSystem(sf::Cursor::Arrow);
cursors_valid &= cursors->cursor_move.loadFromSystem(sf::Cursor::SizeAll);
cursors_valid &= cursors->cursor_pointer.loadFromSystem(sf::Cursor::Hand);
cursors_valid &=
cursors->cursor_resize.loadFromSystem(sf::Cursor::SizeTopLeftBottomRight) || cursors->cursor_resize.loadFromSystem(sf::Cursor::SizeAll);
cursors_valid &= cursors->cursor_cross.loadFromSystem(sf::Cursor::Cross);
cursors_valid &= cursors->cursor_text.loadFromSystem(sf::Cursor::Text);
cursors_valid &= cursors->cursor_unavailable.loadFromSystem(sf::Cursor::NotAllowed);
if (!cursors_valid)
cursors.reset();
}
#endif
void SystemInterface_SFML::SetWindow(sf::RenderWindow* in_window)
{
window = in_window;
}
double SystemInterface_SFML::GetElapsedTime()
{
return static_cast<double>(timer.getElapsedTime().asMicroseconds()) / 1'000'000.0;
}
void SystemInterface_SFML::SetMouseCursor(const Rml::String& cursor_name)
{
if (cursors && window)
{
sf::Cursor* cursor = nullptr;
if (cursor_name.empty() || cursor_name == "arrow")
cursor = &cursors->cursor_default;
else if (cursor_name == "move")
cursor = &cursors->cursor_move;
else if (cursor_name == "pointer")
cursor = &cursors->cursor_pointer;
else if (cursor_name == "resize")
cursor = &cursors->cursor_resize;
else if (cursor_name == "cross")
cursor = &cursors->cursor_cross;
else if (cursor_name == "text")
cursor = &cursors->cursor_text;
else if (cursor_name == "unavailable")
cursor = &cursors->cursor_unavailable;
else if (Rml::StringUtilities::StartsWith(cursor_name, "rmlui-scroll"))
cursor = &cursors->cursor_move;
if (cursor)
window->setMouseCursor(*cursor);
}
}
void SystemInterface_SFML::SetClipboardText(const Rml::String& text_utf8)
{
sf::Clipboard::setString(text_utf8);
}
void SystemInterface_SFML::GetClipboardText(Rml::String& text)
{
text = sf::Clipboard::getString();
}
bool RmlSFML::InputHandler(Rml::Context* context, const sf::Event& ev)
{
bool result = true;
#if SFML_VERSION_MAJOR >= 3
if (auto mouse_moved = ev.getIf<sf::Event::MouseMoved>())
{
result = context->ProcessMouseMove(mouse_moved->position.x, mouse_moved->position.y, RmlSFML::GetKeyModifierState());
}
else if (auto mouse_pressed = ev.getIf<sf::Event::MouseButtonPressed>())
{
result = context->ProcessMouseButtonDown(int(mouse_pressed->button), RmlSFML::GetKeyModifierState());
}
else if (auto mouse_released = ev.getIf<sf::Event::MouseButtonReleased>())
{
result = context->ProcessMouseButtonUp(int(mouse_released->button), RmlSFML::GetKeyModifierState());
}
else if (auto wheel_scrolled = ev.getIf<sf::Event::MouseWheelScrolled>())
{
const Rml::Vector2f delta = {
wheel_scrolled->wheel == sf::Mouse::Wheel::Horizontal ? -wheel_scrolled->delta : 0.f,
wheel_scrolled->wheel == sf::Mouse::Wheel::Vertical ? -wheel_scrolled->delta : 0.f,
};
result = context->ProcessMouseWheel(delta, RmlSFML::GetKeyModifierState());
}
else if (ev.is<sf::Event::MouseLeft>())
{
result = context->ProcessMouseLeave();
}
else if (auto text_entered = ev.getIf<sf::Event::TextEntered>())
{
Rml::Character character = Rml::Character(text_entered->unicode);
if (character == Rml::Character('\r'))
character = Rml::Character('\n');
if (text_entered->unicode >= 32 || character == Rml::Character('\n'))
result = context->ProcessTextInput(character);
}
else if (auto key_pressed = ev.getIf<sf::Event::KeyPressed>())
{
result = context->ProcessKeyDown(RmlSFML::ConvertKey(key_pressed->code), RmlSFML::GetKeyModifierState());
}
else if (auto key_released = ev.getIf<sf::Event::KeyReleased>())
{
result = context->ProcessKeyUp(RmlSFML::ConvertKey(key_released->code), RmlSFML::GetKeyModifierState());
}
#else
switch (ev.type)
{
case sf::Event::MouseMoved: result = context->ProcessMouseMove(ev.mouseMove.x, ev.mouseMove.y, RmlSFML::GetKeyModifierState()); break;
case sf::Event::MouseButtonPressed: result = context->ProcessMouseButtonDown(ev.mouseButton.button, RmlSFML::GetKeyModifierState()); break;
case sf::Event::MouseButtonReleased: result = context->ProcessMouseButtonUp(ev.mouseButton.button, RmlSFML::GetKeyModifierState()); break;
case sf::Event::MouseWheelMoved: result = context->ProcessMouseWheel(float(-ev.mouseWheel.delta), RmlSFML::GetKeyModifierState()); break;
case sf::Event::MouseLeft: result = context->ProcessMouseLeave(); break;
case sf::Event::TextEntered:
{
Rml::Character character = Rml::Character(ev.text.unicode);
if (character == Rml::Character('\r'))
character = Rml::Character('\n');
if (ev.text.unicode >= 32 || character == Rml::Character('\n'))
result = context->ProcessTextInput(character);
}
break;
case sf::Event::KeyPressed: result = context->ProcessKeyDown(RmlSFML::ConvertKey(ev.key.code), RmlSFML::GetKeyModifierState()); break;
case sf::Event::KeyReleased: result = context->ProcessKeyUp(RmlSFML::ConvertKey(ev.key.code), RmlSFML::GetKeyModifierState()); break;
default: break;
}
#endif
return result;
}
Rml::Input::KeyIdentifier RmlSFML::ConvertKey(sf::Keyboard::Key sfml_key)
{
#if SFML_VERSION_MAJOR >= 3
constexpr auto sfBackspace = sf::Keyboard::Key::Backspace;
constexpr auto sfEnter = sf::Keyboard::Key::Enter;
#else
constexpr auto sfBackspace = sf::Keyboard::Key::BackSpace;
constexpr auto sfEnter = sf::Keyboard::Key::Return;
#endif
// clang-format off
switch (sfml_key)
{
case sf::Keyboard::Key::A: return Rml::Input::KI_A;
case sf::Keyboard::Key::B: return Rml::Input::KI_B;
case sf::Keyboard::Key::C: return Rml::Input::KI_C;
case sf::Keyboard::Key::D: return Rml::Input::KI_D;
case sf::Keyboard::Key::E: return Rml::Input::KI_E;
case sf::Keyboard::Key::F: return Rml::Input::KI_F;
case sf::Keyboard::Key::G: return Rml::Input::KI_G;
case sf::Keyboard::Key::H: return Rml::Input::KI_H;
case sf::Keyboard::Key::I: return Rml::Input::KI_I;
case sf::Keyboard::Key::J: return Rml::Input::KI_J;
case sf::Keyboard::Key::K: return Rml::Input::KI_K;
case sf::Keyboard::Key::L: return Rml::Input::KI_L;
case sf::Keyboard::Key::M: return Rml::Input::KI_M;
case sf::Keyboard::Key::N: return Rml::Input::KI_N;
case sf::Keyboard::Key::O: return Rml::Input::KI_O;
case sf::Keyboard::Key::P: return Rml::Input::KI_P;
case sf::Keyboard::Key::Q: return Rml::Input::KI_Q;
case sf::Keyboard::Key::R: return Rml::Input::KI_R;
case sf::Keyboard::Key::S: return Rml::Input::KI_S;
case sf::Keyboard::Key::T: return Rml::Input::KI_T;
case sf::Keyboard::Key::U: return Rml::Input::KI_U;
case sf::Keyboard::Key::V: return Rml::Input::KI_V;
case sf::Keyboard::Key::W: return Rml::Input::KI_W;
case sf::Keyboard::Key::X: return Rml::Input::KI_X;
case sf::Keyboard::Key::Y: return Rml::Input::KI_Y;
case sf::Keyboard::Key::Z: return Rml::Input::KI_Z;
case sf::Keyboard::Key::Num0: return Rml::Input::KI_0;
case sf::Keyboard::Key::Num1: return Rml::Input::KI_1;
case sf::Keyboard::Key::Num2: return Rml::Input::KI_2;
case sf::Keyboard::Key::Num3: return Rml::Input::KI_3;
case sf::Keyboard::Key::Num4: return Rml::Input::KI_4;
case sf::Keyboard::Key::Num5: return Rml::Input::KI_5;
case sf::Keyboard::Key::Num6: return Rml::Input::KI_6;
case sf::Keyboard::Key::Num7: return Rml::Input::KI_7;
case sf::Keyboard::Key::Num8: return Rml::Input::KI_8;
case sf::Keyboard::Key::Num9: return Rml::Input::KI_9;
case sf::Keyboard::Key::Numpad0: return Rml::Input::KI_NUMPAD0;
case sf::Keyboard::Key::Numpad1: return Rml::Input::KI_NUMPAD1;
case sf::Keyboard::Key::Numpad2: return Rml::Input::KI_NUMPAD2;
case sf::Keyboard::Key::Numpad3: return Rml::Input::KI_NUMPAD3;
case sf::Keyboard::Key::Numpad4: return Rml::Input::KI_NUMPAD4;
case sf::Keyboard::Key::Numpad5: return Rml::Input::KI_NUMPAD5;
case sf::Keyboard::Key::Numpad6: return Rml::Input::KI_NUMPAD6;
case sf::Keyboard::Key::Numpad7: return Rml::Input::KI_NUMPAD7;
case sf::Keyboard::Key::Numpad8: return Rml::Input::KI_NUMPAD8;
case sf::Keyboard::Key::Numpad9: return Rml::Input::KI_NUMPAD9;
case sf::Keyboard::Key::Left: return Rml::Input::KI_LEFT;
case sf::Keyboard::Key::Right: return Rml::Input::KI_RIGHT;
case sf::Keyboard::Key::Up: return Rml::Input::KI_UP;
case sf::Keyboard::Key::Down: return Rml::Input::KI_DOWN;
case sf::Keyboard::Key::Add: return Rml::Input::KI_ADD;
case sfBackspace: return Rml::Input::KI_BACK;
case sf::Keyboard::Key::Delete: return Rml::Input::KI_DELETE;
case sf::Keyboard::Key::Divide: return Rml::Input::KI_DIVIDE;
case sf::Keyboard::Key::End: return Rml::Input::KI_END;
case sf::Keyboard::Key::Escape: return Rml::Input::KI_ESCAPE;
case sf::Keyboard::Key::F1: return Rml::Input::KI_F1;
case sf::Keyboard::Key::F2: return Rml::Input::KI_F2;
case sf::Keyboard::Key::F3: return Rml::Input::KI_F3;
case sf::Keyboard::Key::F4: return Rml::Input::KI_F4;
case sf::Keyboard::Key::F5: return Rml::Input::KI_F5;
case sf::Keyboard::Key::F6: return Rml::Input::KI_F6;
case sf::Keyboard::Key::F7: return Rml::Input::KI_F7;
case sf::Keyboard::Key::F8: return Rml::Input::KI_F8;
case sf::Keyboard::Key::F9: return Rml::Input::KI_F9;
case sf::Keyboard::Key::F10: return Rml::Input::KI_F10;
case sf::Keyboard::Key::F11: return Rml::Input::KI_F11;
case sf::Keyboard::Key::F12: return Rml::Input::KI_F12;
case sf::Keyboard::Key::F13: return Rml::Input::KI_F13;
case sf::Keyboard::Key::F14: return Rml::Input::KI_F14;
case sf::Keyboard::Key::F15: return Rml::Input::KI_F15;
case sf::Keyboard::Key::Home: return Rml::Input::KI_HOME;
case sf::Keyboard::Key::Insert: return Rml::Input::KI_INSERT;
case sf::Keyboard::Key::LControl: return Rml::Input::KI_LCONTROL;
case sf::Keyboard::Key::LShift: return Rml::Input::KI_LSHIFT;
case sf::Keyboard::Key::Multiply: return Rml::Input::KI_MULTIPLY;
case sf::Keyboard::Key::Pause: return Rml::Input::KI_PAUSE;
case sf::Keyboard::Key::RControl: return Rml::Input::KI_RCONTROL;
case sfEnter: return Rml::Input::KI_RETURN;
case sf::Keyboard::Key::RShift: return Rml::Input::KI_RSHIFT;
case sf::Keyboard::Key::Space: return Rml::Input::KI_SPACE;
case sf::Keyboard::Key::Subtract: return Rml::Input::KI_SUBTRACT;
case sf::Keyboard::Key::Tab: return Rml::Input::KI_TAB;
default: break;
}
// clang-format on
return Rml::Input::KI_UNKNOWN;
}
int RmlSFML::GetKeyModifierState()
{
int modifiers = 0;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RShift))
modifiers |= Rml::Input::KM_SHIFT;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RControl))
modifiers |= Rml::Input::KM_CTRL;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::LAlt) || sf::Keyboard::isKeyPressed(sf::Keyboard::Key::RAlt))
modifiers |= Rml::Input::KM_ALT;
return modifiers;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/Types.h>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
class SystemInterface_SFML : public Rml::SystemInterface {
public:
SystemInterface_SFML();
// Optionally, provide or change the window to be used for setting the mouse cursors.
// @lifetime Any window provided here must be destroyed before the system interface.
// @lifetime The currently active window must stay alive until after the call to Rml::Shutdown.
void SetWindow(sf::RenderWindow* window);
// -- Inherited from Rml::SystemInterface --
double GetElapsedTime() override;
void SetMouseCursor(const Rml::String& cursor_name) override;
void SetClipboardText(const Rml::String& text) override;
void GetClipboardText(Rml::String& text) override;
private:
sf::Clock timer;
sf::RenderWindow* window = nullptr;
struct Cursors {
Cursors();
sf::Cursor cursor_default;
sf::Cursor cursor_move;
sf::Cursor cursor_pointer;
sf::Cursor cursor_resize;
sf::Cursor cursor_cross;
sf::Cursor cursor_text;
sf::Cursor cursor_unavailable;
};
Rml::UniquePtr<Cursors> cursors;
};
/**
Optional helper functions for the SFML platform.
*/
namespace RmlSFML {
// Applies input on the context based on the given SFML event.
// @return True if the event is still propagating, false if it was handled by the context.
bool InputHandler(Rml::Context* context, const sf::Event& ev);
// Converts the SFML key to RmlUi key.
Rml::Input::KeyIdentifier ConvertKey(sf::Keyboard::Key sfml_key);
// Returns the active RmlUi key modifier state.
int GetKeyModifierState();
} // namespace RmlSFML

View File

@@ -0,0 +1,730 @@
#include "RmlUi_Platform_Win32.h"
#include "RmlUi_Include_Windows.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/StringUtilities.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/TextInputContext.h>
#include <RmlUi/Core/TextInputHandler.h>
#include <string.h>
// Used to interact with the input method editor (IME). Users of MinGW should manually link to this.
#ifdef _MSC_VER
#pragma comment(lib, "imm32")
#endif
SystemInterface_Win32::SystemInterface_Win32()
{
LARGE_INTEGER time_ticks_per_second;
QueryPerformanceFrequency(&time_ticks_per_second);
QueryPerformanceCounter(&time_startup);
time_frequency = 1.0 / (double)time_ticks_per_second.QuadPart;
// Load cursors
cursor_default = LoadCursor(nullptr, IDC_ARROW);
cursor_move = LoadCursor(nullptr, IDC_SIZEALL);
cursor_pointer = LoadCursor(nullptr, IDC_HAND);
cursor_resize = LoadCursor(nullptr, IDC_SIZENWSE);
cursor_cross = LoadCursor(nullptr, IDC_CROSS);
cursor_text = LoadCursor(nullptr, IDC_IBEAM);
cursor_unavailable = LoadCursor(nullptr, IDC_NO);
}
SystemInterface_Win32::~SystemInterface_Win32() = default;
void SystemInterface_Win32::SetWindow(HWND in_window_handle)
{
window_handle = in_window_handle;
}
double SystemInterface_Win32::GetElapsedTime()
{
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return double(counter.QuadPart - time_startup.QuadPart) * time_frequency;
}
void SystemInterface_Win32::SetMouseCursor(const Rml::String& cursor_name)
{
if (window_handle)
{
HCURSOR cursor_handle = nullptr;
if (cursor_name.empty() || cursor_name == "arrow")
cursor_handle = cursor_default;
else if (cursor_name == "move")
cursor_handle = cursor_move;
else if (cursor_name == "pointer")
cursor_handle = cursor_pointer;
else if (cursor_name == "resize")
cursor_handle = cursor_resize;
else if (cursor_name == "cross")
cursor_handle = cursor_cross;
else if (cursor_name == "text")
cursor_handle = cursor_text;
else if (cursor_name == "unavailable")
cursor_handle = cursor_unavailable;
else if (Rml::StringUtilities::StartsWith(cursor_name, "rmlui-scroll"))
cursor_handle = cursor_move;
if (cursor_handle)
{
SetCursor(cursor_handle);
SetClassLongPtrA(window_handle, GCLP_HCURSOR, (LONG_PTR)cursor_handle);
}
}
}
void SystemInterface_Win32::SetClipboardText(const Rml::String& text_utf8)
{
if (window_handle)
{
if (!OpenClipboard(window_handle))
return;
EmptyClipboard();
const std::wstring text = RmlWin32::ConvertToUTF16(text_utf8);
const size_t size = sizeof(wchar_t) * (text.size() + 1);
HGLOBAL clipboard_data = GlobalAlloc(GMEM_FIXED, size);
memcpy(clipboard_data, text.data(), size);
if (SetClipboardData(CF_UNICODETEXT, clipboard_data) == nullptr)
{
CloseClipboard();
GlobalFree(clipboard_data);
}
else
CloseClipboard();
}
}
void SystemInterface_Win32::GetClipboardText(Rml::String& text)
{
if (window_handle)
{
if (!OpenClipboard(window_handle))
return;
HANDLE clipboard_data = GetClipboardData(CF_UNICODETEXT);
if (clipboard_data == nullptr)
{
CloseClipboard();
return;
}
const wchar_t* clipboard_text = (const wchar_t*)GlobalLock(clipboard_data);
if (clipboard_text)
text = RmlWin32::ConvertToUTF8(clipboard_text);
GlobalUnlock(clipboard_data);
CloseClipboard();
}
}
void SystemInterface_Win32::ActivateKeyboard(Rml::Vector2f caret_position, float line_height)
{
HIMC himc = ImmGetContext(window_handle);
if (himc == NULL)
return;
constexpr LONG BottomMargin = 2;
// Adjust the position of the input method editor (IME) to the caret.
const LONG x = static_cast<LONG>(caret_position.x);
const LONG y = static_cast<LONG>(caret_position.y);
const LONG w = 1;
const LONG h = static_cast<LONG>(line_height) + BottomMargin;
COMPOSITIONFORM comp = {};
comp.dwStyle = CFS_FORCE_POSITION;
comp.ptCurrentPos = {x, y};
ImmSetCompositionWindow(himc, &comp);
CANDIDATEFORM cand = {};
cand.dwStyle = CFS_EXCLUDE;
cand.ptCurrentPos = {x, y};
cand.rcArea = {x, y, x + w, y + h};
ImmSetCandidateWindow(himc, &cand);
ImmReleaseContext(window_handle, himc);
}
Rml::String RmlWin32::ConvertToUTF8(const std::wstring& wstr)
{
const int count = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
Rml::String str(count, 0);
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], count, NULL, NULL);
return str;
}
std::wstring RmlWin32::ConvertToUTF16(const Rml::String& str)
{
const int count = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), NULL, 0);
std::wstring wstr(count, 0);
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.length(), &wstr[0], count);
return wstr;
}
static int IMEGetCursorPosition(HIMC context)
{
return ImmGetCompositionString(context, GCS_CURSORPOS, nullptr, 0);
}
static std::wstring IMEGetCompositionString(HIMC context, bool finalize)
{
DWORD type = finalize ? GCS_RESULTSTR : GCS_COMPSTR;
int len_bytes = ImmGetCompositionString(context, type, nullptr, 0);
if (len_bytes <= 0)
return {};
int len_chars = len_bytes / sizeof(TCHAR);
Rml::UniquePtr<TCHAR[]> buffer(new TCHAR[len_chars + 1]);
ImmGetCompositionString(context, type, buffer.get(), len_bytes);
#ifdef UNICODE
return std::wstring(buffer.get(), len_chars);
#else
return RmlWin32::ConvertToUTF16(Rml::String(buffer.get(), len_chars));
#endif
}
static void IMECompleteComposition(HWND window_handle)
{
if (HIMC context = ImmGetContext(window_handle))
{
ImmNotifyIME(context, NI_COMPOSITIONSTR, CPS_COMPLETE, NULL);
ImmReleaseContext(window_handle, context);
}
}
bool RmlWin32::WindowProcedure(Rml::Context* context, TextInputMethodEditor_Win32& text_input_method_editor, HWND window_handle, UINT message,
WPARAM w_param, LPARAM l_param)
{
if (!context)
return true;
static bool tracking_mouse_leave = false;
// If the user tries to interact with the window by using the mouse in any way, end the
// composition by committing the current string. This behavior is identical to other
// browsers and is expected, yet, Windows does not send any IME messages in such a case.
if (text_input_method_editor.IsComposing() && message >= WM_LBUTTONDOWN && message <= WM_MBUTTONDBLCLK)
IMECompleteComposition(window_handle);
bool result = true;
switch (message)
{
case WM_LBUTTONDOWN:
result = context->ProcessMouseButtonDown(0, RmlWin32::GetKeyModifierState());
SetCapture(window_handle);
break;
case WM_LBUTTONUP:
ReleaseCapture();
result = context->ProcessMouseButtonUp(0, RmlWin32::GetKeyModifierState());
break;
case WM_RBUTTONDOWN: result = context->ProcessMouseButtonDown(1, RmlWin32::GetKeyModifierState()); break;
case WM_RBUTTONUP: result = context->ProcessMouseButtonUp(1, RmlWin32::GetKeyModifierState()); break;
case WM_MBUTTONDOWN: result = context->ProcessMouseButtonDown(2, RmlWin32::GetKeyModifierState()); break;
case WM_MBUTTONUP: result = context->ProcessMouseButtonUp(2, RmlWin32::GetKeyModifierState()); break;
case WM_MOUSEMOVE:
result = context->ProcessMouseMove(static_cast<int>((short)LOWORD(l_param)), static_cast<int>((short)HIWORD(l_param)),
RmlWin32::GetKeyModifierState());
if (!tracking_mouse_leave)
{
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = window_handle;
tracking_mouse_leave = TrackMouseEvent(&tme);
}
break;
case WM_MOUSEWHEEL:
result = context->ProcessMouseWheel(static_cast<float>((short)HIWORD(w_param)) / static_cast<float>(-WHEEL_DELTA),
RmlWin32::GetKeyModifierState());
break;
case WM_MOUSELEAVE:
result = context->ProcessMouseLeave();
tracking_mouse_leave = false;
break;
case WM_KEYDOWN: result = context->ProcessKeyDown(RmlWin32::ConvertKey((int)w_param), RmlWin32::GetKeyModifierState()); break;
case WM_KEYUP: result = context->ProcessKeyUp(RmlWin32::ConvertKey((int)w_param), RmlWin32::GetKeyModifierState()); break;
case WM_CHAR:
{
static wchar_t first_u16_code_unit = 0;
const wchar_t c = (wchar_t)w_param;
Rml::Character character = (Rml::Character)c;
// Windows sends two-wide characters as two messages.
if (c >= 0xD800 && c < 0xDC00)
{
// First 16-bit code unit of a two-wide character.
first_u16_code_unit = c;
}
else
{
if (c >= 0xDC00 && c < 0xE000 && first_u16_code_unit != 0)
{
// Second 16-bit code unit of a two-wide character.
Rml::String utf8 = ConvertToUTF8(std::wstring{first_u16_code_unit, c});
character = Rml::StringUtilities::ToCharacter(utf8.data(), utf8.data() + utf8.size());
}
else if (c == '\r')
{
// Windows sends new-lines as carriage returns, convert to endlines.
character = (Rml::Character)'\n';
}
first_u16_code_unit = 0;
// Only send through printable characters.
if (((char32_t)character >= 32 || character == (Rml::Character)'\n') && character != (Rml::Character)127)
result = context->ProcessTextInput(character);
}
}
break;
case WM_IME_STARTCOMPOSITION:
text_input_method_editor.StartComposition();
// Prevent the native composition window from appearing by capturing the message.
result = false;
break;
case WM_IME_ENDCOMPOSITION:
if (text_input_method_editor.IsComposing())
text_input_method_editor.ConfirmComposition(Rml::StringView());
break;
case WM_IME_COMPOSITION:
{
HIMC imm_context = ImmGetContext(window_handle);
// Not every IME starts a composition.
if (!text_input_method_editor.IsComposing())
text_input_method_editor.StartComposition();
if (!!(l_param & GCS_CURSORPOS))
{
// The cursor position is the wchar_t offset in the composition string. Because we
// work with UTF-8 and not UTF-16, we will have to convert the character offset.
int cursor_pos = IMEGetCursorPosition(imm_context);
std::wstring composition = IMEGetCompositionString(imm_context, false);
Rml::String converted = RmlWin32::ConvertToUTF8(composition.substr(0, cursor_pos));
cursor_pos = (int)Rml::StringUtilities::LengthUTF8(converted);
text_input_method_editor.SetCursorPosition(cursor_pos, true);
}
if (!!(l_param & CS_NOMOVECARET))
{
// Suppress the cursor position update. CS_NOMOVECARET is always a part of a more
// complex message which means that the cursor is updated from a different event.
text_input_method_editor.SetCursorPosition(-1, false);
}
if (!!(l_param & GCS_RESULTSTR))
{
std::wstring composition = IMEGetCompositionString(imm_context, true);
text_input_method_editor.ConfirmComposition(RmlWin32::ConvertToUTF8(composition));
}
if (!!(l_param & GCS_COMPSTR))
{
std::wstring composition = IMEGetCompositionString(imm_context, false);
text_input_method_editor.SetComposition(RmlWin32::ConvertToUTF8(composition));
}
// The composition has been canceled.
if (!l_param)
text_input_method_editor.CancelComposition();
ImmReleaseContext(window_handle, imm_context);
}
break;
case WM_IME_CHAR:
case WM_IME_REQUEST:
// Ignore WM_IME_CHAR and WM_IME_REQUEST to block the system from appending the composition string.
result = false;
break;
default: break;
}
return result;
}
int RmlWin32::GetKeyModifierState()
{
int key_modifier_state = 0;
if (GetKeyState(VK_CAPITAL) & 1)
key_modifier_state |= Rml::Input::KM_CAPSLOCK;
if (GetKeyState(VK_NUMLOCK) & 1)
key_modifier_state |= Rml::Input::KM_NUMLOCK;
if (HIWORD(GetKeyState(VK_SHIFT)) & 1)
key_modifier_state |= Rml::Input::KM_SHIFT;
if (HIWORD(GetKeyState(VK_CONTROL)) & 1)
key_modifier_state |= Rml::Input::KM_CTRL;
if (HIWORD(GetKeyState(VK_MENU)) & 1)
key_modifier_state |= Rml::Input::KM_ALT;
return key_modifier_state;
}
// These are defined in winuser.h of MinGW 64 but are missing from MinGW 32
// Visual Studio has them by default
#if defined(__MINGW32__) && !defined(__MINGW64__)
#define VK_OEM_NEC_EQUAL 0x92
#define VK_OEM_FJ_JISHO 0x92
#define VK_OEM_FJ_MASSHOU 0x93
#define VK_OEM_FJ_TOUROKU 0x94
#define VK_OEM_FJ_LOYA 0x95
#define VK_OEM_FJ_ROYA 0x96
#define VK_OEM_AX 0xE1
#define VK_ICO_HELP 0xE3
#define VK_ICO_00 0xE4
#define VK_ICO_CLEAR 0xE6
#endif // !defined(__MINGW32__) || defined(__MINGW64__)
Rml::Input::KeyIdentifier RmlWin32::ConvertKey(int win32_key_code)
{
// clang-format off
switch (win32_key_code)
{
case 'A': return Rml::Input::KI_A;
case 'B': return Rml::Input::KI_B;
case 'C': return Rml::Input::KI_C;
case 'D': return Rml::Input::KI_D;
case 'E': return Rml::Input::KI_E;
case 'F': return Rml::Input::KI_F;
case 'G': return Rml::Input::KI_G;
case 'H': return Rml::Input::KI_H;
case 'I': return Rml::Input::KI_I;
case 'J': return Rml::Input::KI_J;
case 'K': return Rml::Input::KI_K;
case 'L': return Rml::Input::KI_L;
case 'M': return Rml::Input::KI_M;
case 'N': return Rml::Input::KI_N;
case 'O': return Rml::Input::KI_O;
case 'P': return Rml::Input::KI_P;
case 'Q': return Rml::Input::KI_Q;
case 'R': return Rml::Input::KI_R;
case 'S': return Rml::Input::KI_S;
case 'T': return Rml::Input::KI_T;
case 'U': return Rml::Input::KI_U;
case 'V': return Rml::Input::KI_V;
case 'W': return Rml::Input::KI_W;
case 'X': return Rml::Input::KI_X;
case 'Y': return Rml::Input::KI_Y;
case 'Z': return Rml::Input::KI_Z;
case '0': return Rml::Input::KI_0;
case '1': return Rml::Input::KI_1;
case '2': return Rml::Input::KI_2;
case '3': return Rml::Input::KI_3;
case '4': return Rml::Input::KI_4;
case '5': return Rml::Input::KI_5;
case '6': return Rml::Input::KI_6;
case '7': return Rml::Input::KI_7;
case '8': return Rml::Input::KI_8;
case '9': return Rml::Input::KI_9;
case VK_BACK: return Rml::Input::KI_BACK;
case VK_TAB: return Rml::Input::KI_TAB;
case VK_CLEAR: return Rml::Input::KI_CLEAR;
case VK_RETURN: return Rml::Input::KI_RETURN;
case VK_PAUSE: return Rml::Input::KI_PAUSE;
case VK_CAPITAL: return Rml::Input::KI_CAPITAL;
case VK_KANA: return Rml::Input::KI_KANA;
//case VK_HANGUL: return Rml::Input::KI_HANGUL; /* overlaps with VK_KANA */
case VK_JUNJA: return Rml::Input::KI_JUNJA;
case VK_FINAL: return Rml::Input::KI_FINAL;
case VK_HANJA: return Rml::Input::KI_HANJA;
//case VK_KANJI: return Rml::Input::KI_KANJI; /* overlaps with VK_HANJA */
case VK_ESCAPE: return Rml::Input::KI_ESCAPE;
case VK_CONVERT: return Rml::Input::KI_CONVERT;
case VK_NONCONVERT: return Rml::Input::KI_NONCONVERT;
case VK_ACCEPT: return Rml::Input::KI_ACCEPT;
case VK_MODECHANGE: return Rml::Input::KI_MODECHANGE;
case VK_SPACE: return Rml::Input::KI_SPACE;
case VK_PRIOR: return Rml::Input::KI_PRIOR;
case VK_NEXT: return Rml::Input::KI_NEXT;
case VK_END: return Rml::Input::KI_END;
case VK_HOME: return Rml::Input::KI_HOME;
case VK_LEFT: return Rml::Input::KI_LEFT;
case VK_UP: return Rml::Input::KI_UP;
case VK_RIGHT: return Rml::Input::KI_RIGHT;
case VK_DOWN: return Rml::Input::KI_DOWN;
case VK_SELECT: return Rml::Input::KI_SELECT;
case VK_PRINT: return Rml::Input::KI_PRINT;
case VK_EXECUTE: return Rml::Input::KI_EXECUTE;
case VK_SNAPSHOT: return Rml::Input::KI_SNAPSHOT;
case VK_INSERT: return Rml::Input::KI_INSERT;
case VK_DELETE: return Rml::Input::KI_DELETE;
case VK_HELP: return Rml::Input::KI_HELP;
case VK_LWIN: return Rml::Input::KI_LWIN;
case VK_RWIN: return Rml::Input::KI_RWIN;
case VK_APPS: return Rml::Input::KI_APPS;
case VK_SLEEP: return Rml::Input::KI_SLEEP;
case VK_NUMPAD0: return Rml::Input::KI_NUMPAD0;
case VK_NUMPAD1: return Rml::Input::KI_NUMPAD1;
case VK_NUMPAD2: return Rml::Input::KI_NUMPAD2;
case VK_NUMPAD3: return Rml::Input::KI_NUMPAD3;
case VK_NUMPAD4: return Rml::Input::KI_NUMPAD4;
case VK_NUMPAD5: return Rml::Input::KI_NUMPAD5;
case VK_NUMPAD6: return Rml::Input::KI_NUMPAD6;
case VK_NUMPAD7: return Rml::Input::KI_NUMPAD7;
case VK_NUMPAD8: return Rml::Input::KI_NUMPAD8;
case VK_NUMPAD9: return Rml::Input::KI_NUMPAD9;
case VK_MULTIPLY: return Rml::Input::KI_MULTIPLY;
case VK_ADD: return Rml::Input::KI_ADD;
case VK_SEPARATOR: return Rml::Input::KI_SEPARATOR;
case VK_SUBTRACT: return Rml::Input::KI_SUBTRACT;
case VK_DECIMAL: return Rml::Input::KI_DECIMAL;
case VK_DIVIDE: return Rml::Input::KI_DIVIDE;
case VK_F1: return Rml::Input::KI_F1;
case VK_F2: return Rml::Input::KI_F2;
case VK_F3: return Rml::Input::KI_F3;
case VK_F4: return Rml::Input::KI_F4;
case VK_F5: return Rml::Input::KI_F5;
case VK_F6: return Rml::Input::KI_F6;
case VK_F7: return Rml::Input::KI_F7;
case VK_F8: return Rml::Input::KI_F8;
case VK_F9: return Rml::Input::KI_F9;
case VK_F10: return Rml::Input::KI_F10;
case VK_F11: return Rml::Input::KI_F11;
case VK_F12: return Rml::Input::KI_F12;
case VK_F13: return Rml::Input::KI_F13;
case VK_F14: return Rml::Input::KI_F14;
case VK_F15: return Rml::Input::KI_F15;
case VK_F16: return Rml::Input::KI_F16;
case VK_F17: return Rml::Input::KI_F17;
case VK_F18: return Rml::Input::KI_F18;
case VK_F19: return Rml::Input::KI_F19;
case VK_F20: return Rml::Input::KI_F20;
case VK_F21: return Rml::Input::KI_F21;
case VK_F22: return Rml::Input::KI_F22;
case VK_F23: return Rml::Input::KI_F23;
case VK_F24: return Rml::Input::KI_F24;
case VK_NUMLOCK: return Rml::Input::KI_NUMLOCK;
case VK_SCROLL: return Rml::Input::KI_SCROLL;
case VK_OEM_NEC_EQUAL: return Rml::Input::KI_OEM_NEC_EQUAL;
//case VK_OEM_FJ_JISHO: return Rml::Input::KI_OEM_FJ_JISHO; /* overlaps with VK_OEM_NEC_EQUAL */
case VK_OEM_FJ_MASSHOU: return Rml::Input::KI_OEM_FJ_MASSHOU;
case VK_OEM_FJ_TOUROKU: return Rml::Input::KI_OEM_FJ_TOUROKU;
case VK_OEM_FJ_LOYA: return Rml::Input::KI_OEM_FJ_LOYA;
case VK_OEM_FJ_ROYA: return Rml::Input::KI_OEM_FJ_ROYA;
case VK_SHIFT: return Rml::Input::KI_LSHIFT;
case VK_CONTROL: return Rml::Input::KI_LCONTROL;
case VK_MENU: return Rml::Input::KI_LMENU;
case VK_BROWSER_BACK: return Rml::Input::KI_BROWSER_BACK;
case VK_BROWSER_FORWARD: return Rml::Input::KI_BROWSER_FORWARD;
case VK_BROWSER_REFRESH: return Rml::Input::KI_BROWSER_REFRESH;
case VK_BROWSER_STOP: return Rml::Input::KI_BROWSER_STOP;
case VK_BROWSER_SEARCH: return Rml::Input::KI_BROWSER_SEARCH;
case VK_BROWSER_FAVORITES: return Rml::Input::KI_BROWSER_FAVORITES;
case VK_BROWSER_HOME: return Rml::Input::KI_BROWSER_HOME;
case VK_VOLUME_MUTE: return Rml::Input::KI_VOLUME_MUTE;
case VK_VOLUME_DOWN: return Rml::Input::KI_VOLUME_DOWN;
case VK_VOLUME_UP: return Rml::Input::KI_VOLUME_UP;
case VK_MEDIA_NEXT_TRACK: return Rml::Input::KI_MEDIA_NEXT_TRACK;
case VK_MEDIA_PREV_TRACK: return Rml::Input::KI_MEDIA_PREV_TRACK;
case VK_MEDIA_STOP: return Rml::Input::KI_MEDIA_STOP;
case VK_MEDIA_PLAY_PAUSE: return Rml::Input::KI_MEDIA_PLAY_PAUSE;
case VK_LAUNCH_MAIL: return Rml::Input::KI_LAUNCH_MAIL;
case VK_LAUNCH_MEDIA_SELECT: return Rml::Input::KI_LAUNCH_MEDIA_SELECT;
case VK_LAUNCH_APP1: return Rml::Input::KI_LAUNCH_APP1;
case VK_LAUNCH_APP2: return Rml::Input::KI_LAUNCH_APP2;
case VK_OEM_1: return Rml::Input::KI_OEM_1;
case VK_OEM_PLUS: return Rml::Input::KI_OEM_PLUS;
case VK_OEM_COMMA: return Rml::Input::KI_OEM_COMMA;
case VK_OEM_MINUS: return Rml::Input::KI_OEM_MINUS;
case VK_OEM_PERIOD: return Rml::Input::KI_OEM_PERIOD;
case VK_OEM_2: return Rml::Input::KI_OEM_2;
case VK_OEM_3: return Rml::Input::KI_OEM_3;
case VK_OEM_4: return Rml::Input::KI_OEM_4;
case VK_OEM_5: return Rml::Input::KI_OEM_5;
case VK_OEM_6: return Rml::Input::KI_OEM_6;
case VK_OEM_7: return Rml::Input::KI_OEM_7;
case VK_OEM_8: return Rml::Input::KI_OEM_8;
case VK_OEM_AX: return Rml::Input::KI_OEM_AX;
case VK_OEM_102: return Rml::Input::KI_OEM_102;
case VK_ICO_HELP: return Rml::Input::KI_ICO_HELP;
case VK_ICO_00: return Rml::Input::KI_ICO_00;
case VK_PROCESSKEY: return Rml::Input::KI_PROCESSKEY;
case VK_ICO_CLEAR: return Rml::Input::KI_ICO_CLEAR;
case VK_ATTN: return Rml::Input::KI_ATTN;
case VK_CRSEL: return Rml::Input::KI_CRSEL;
case VK_EXSEL: return Rml::Input::KI_EXSEL;
case VK_EREOF: return Rml::Input::KI_EREOF;
case VK_PLAY: return Rml::Input::KI_PLAY;
case VK_ZOOM: return Rml::Input::KI_ZOOM;
case VK_PA1: return Rml::Input::KI_PA1;
case VK_OEM_CLEAR: return Rml::Input::KI_OEM_CLEAR;
}
// clang-format on
return Rml::Input::KI_UNKNOWN;
}
TextInputMethodEditor_Win32::TextInputMethodEditor_Win32() :
input_context(nullptr), composing(false), cursor_pos(-1), composition_range_start(0), composition_range_end(0)
{}
void TextInputMethodEditor_Win32::OnActivate(Rml::TextInputContext* _input_context)
{
input_context = _input_context;
}
void TextInputMethodEditor_Win32::OnDeactivate(Rml::TextInputContext* _input_context)
{
if (input_context == _input_context)
input_context = nullptr;
}
void TextInputMethodEditor_Win32::OnDestroy(Rml::TextInputContext* _input_context)
{
if (input_context == _input_context)
input_context = nullptr;
}
bool TextInputMethodEditor_Win32::IsComposing() const
{
return composing;
}
void TextInputMethodEditor_Win32::StartComposition()
{
RMLUI_ASSERT(!composing);
composing = true;
}
void TextInputMethodEditor_Win32::EndComposition()
{
if (input_context != nullptr)
input_context->SetCompositionRange(0, 0);
RMLUI_ASSERT(composing);
composing = false;
composition_range_start = 0;
composition_range_end = 0;
}
void TextInputMethodEditor_Win32::CancelComposition()
{
RMLUI_ASSERT(IsComposing());
if (input_context != nullptr)
{
// Purge the current composition string.
input_context->SetText(Rml::StringView(), composition_range_start, composition_range_end);
// Move the cursor back to where the composition began.
input_context->SetCursorPosition(composition_range_start);
}
EndComposition();
}
void TextInputMethodEditor_Win32::SetComposition(Rml::StringView composition)
{
RMLUI_ASSERT(IsComposing());
SetCompositionString(composition);
UpdateCursorPosition();
// Update the composition range only if the cursor can be moved around. Editors working with a single
// character (e.g., Hangul IME) should have no visual feedback; they use a selection range instead.
if (cursor_pos != -1 && input_context != nullptr)
input_context->SetCompositionRange(composition_range_start, composition_range_end);
}
void TextInputMethodEditor_Win32::ConfirmComposition(Rml::StringView composition)
{
RMLUI_ASSERT(IsComposing());
SetCompositionString(composition);
if (input_context != nullptr)
{
input_context->SetCompositionRange(composition_range_start, composition_range_end);
input_context->CommitComposition(composition);
}
// Move the cursor to the end of the string.
SetCursorPosition(composition_range_end - composition_range_start, true);
EndComposition();
}
void TextInputMethodEditor_Win32::SetCursorPosition(int _cursor_pos, bool update)
{
RMLUI_ASSERT(IsComposing());
cursor_pos = _cursor_pos;
if (update)
UpdateCursorPosition();
}
void TextInputMethodEditor_Win32::SetCompositionString(Rml::StringView composition)
{
if (input_context == nullptr)
return;
// Retrieve the composition range if it is missing.
if (composition_range_start == 0 && composition_range_end == 0)
input_context->GetSelectionRange(composition_range_start, composition_range_end);
input_context->SetText(composition, composition_range_start, composition_range_end);
size_t length = Rml::StringUtilities::LengthUTF8(composition);
composition_range_end = composition_range_start + (int)length;
}
void TextInputMethodEditor_Win32::UpdateCursorPosition()
{
// Cursor position update happens before a composition is set; ignore this event.
if (input_context == nullptr || (composition_range_start == 0 && composition_range_end == 0))
return;
if (cursor_pos != -1)
{
int position = composition_range_start + cursor_pos;
input_context->SetCursorPosition(position);
}
else
{
// If the API reports no cursor position, select the entire composition string for a better UX.
input_context->SetSelectionRange(composition_range_start, composition_range_end);
}
}

View File

@@ -0,0 +1,119 @@
#pragma once
#include "RmlUi_Include_Windows.h"
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/StringUtilities.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/TextInputHandler.h>
#include <RmlUi/Core/Types.h>
#include <string>
class SystemInterface_Win32 : public Rml::SystemInterface {
public:
SystemInterface_Win32();
~SystemInterface_Win32();
// Optionally, provide or change the window to be used for setting the mouse cursor, clipboard text and IME position.
void SetWindow(HWND window_handle);
// -- Inherited from Rml::SystemInterface --
double GetElapsedTime() override;
void SetMouseCursor(const Rml::String& cursor_name) override;
void SetClipboardText(const Rml::String& text) override;
void GetClipboardText(Rml::String& text) override;
void ActivateKeyboard(Rml::Vector2f caret_position, float line_height) override;
private:
HWND window_handle = nullptr;
double time_frequency = {};
LARGE_INTEGER time_startup = {};
HCURSOR cursor_default = nullptr;
HCURSOR cursor_move = nullptr;
HCURSOR cursor_pointer = nullptr;
HCURSOR cursor_resize = nullptr;
HCURSOR cursor_cross = nullptr;
HCURSOR cursor_text = nullptr;
HCURSOR cursor_unavailable = nullptr;
};
class TextInputMethodEditor_Win32;
/**
Optional helper functions for the Win32 platform.
*/
namespace RmlWin32 {
// Convenience helpers for converting between RmlUi strings (UTF-8) and Windows strings (UTF-16).
Rml::String ConvertToUTF8(const std::wstring& wstr);
std::wstring ConvertToUTF16(const Rml::String& str);
// Window event handler to submit default input behavior to the context.
// @return True if the event is still propagating, false if it was handled by the context.
bool WindowProcedure(Rml::Context* context, TextInputMethodEditor_Win32& text_input_method_editor, HWND window_handle, UINT message, WPARAM w_param,
LPARAM l_param);
// Converts the key from Win32 key code to RmlUi key.
Rml::Input::KeyIdentifier ConvertKey(int win32_key_code);
// Returns the active RmlUi key modifier state.
int GetKeyModifierState();
} // namespace RmlWin32
/**
Custom backend implementation of TextInputHandler to handle the system's Input Method Editor (IME).
This version supports only one active text input context.
*/
class TextInputMethodEditor_Win32 final : public Rml::TextInputHandler {
public:
TextInputMethodEditor_Win32();
void OnActivate(Rml::TextInputContext* input_context) override;
void OnDeactivate(Rml::TextInputContext* input_context) override;
void OnDestroy(Rml::TextInputContext* input_context) override;
/// Check that a composition is currently active.
/// @return True if we are composing, false otherwise.
bool IsComposing() const;
void StartComposition();
void CancelComposition();
/// Set the composition string.
/// @param[in] composition A string to be set.
void SetComposition(Rml::StringView composition);
/// End the current composition by confirming the composition string.
/// @param[in] composition A string to confirm.
void ConfirmComposition(Rml::StringView composition);
/// Set the cursor position within the composition.
/// @param[in] cursor_pos A character position of the cursor within the composition string.
/// @param[in] update Update the cursor position within active input contexts.
void SetCursorPosition(int cursor_pos, bool update);
private:
void EndComposition();
void SetCompositionString(Rml::StringView composition);
void UpdateCursorPosition();
private:
// An actively used text input method context.
Rml::TextInputContext* input_context;
// A flag to mark a composition is currently active.
bool composing;
// Character position of the cursor in the composition string.
int cursor_pos;
// Composition range (character position) relative to the text input value.
int composition_range_start;
int composition_range_end;
};

View File

@@ -0,0 +1,610 @@
#include "RmlUi_Platform_X11.h"
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Types.h>
#include <X11/cursorfont.h>
#include <X11/extensions/xf86vmode.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#ifdef HAS_X11XKBLIB
#include <X11/XKBlib.h>
#endif // HAS_X11XKBLIB
#include <X11/keysym.h>
static Rml::Character GetCharacterCode(Rml::Input::KeyIdentifier key_identifier, int key_modifier_state);
SystemInterface_X11::SystemInterface_X11(Display* display) : display(display)
{
// Create cursors
cursor_default = XCreateFontCursor(display, XC_left_ptr);
cursor_move = XCreateFontCursor(display, XC_fleur);
cursor_pointer = XCreateFontCursor(display, XC_hand1);
cursor_resize = XCreateFontCursor(display, XC_sizing);
cursor_cross = XCreateFontCursor(display, XC_crosshair);
cursor_text = XCreateFontCursor(display, XC_xterm);
cursor_unavailable = XCreateFontCursor(display, XC_X_cursor);
// For copy & paste functions
UTF8_atom = XInternAtom(display, "UTF8_STRING", 1);
XSEL_DATA_atom = XInternAtom(display, "XSEL_DATA", 0);
CLIPBOARD_atom = XInternAtom(display, "CLIPBOARD", 0);
TARGETS_atom = XInternAtom(display, "TARGETS", 0);
TEXT_atom = XInternAtom(display, "TEXT", 0);
gettimeofday(&start_time, nullptr);
}
void SystemInterface_X11::SetWindow(Window in_window)
{
window = in_window;
}
bool SystemInterface_X11::HandleSelectionRequest(const XEvent& ev)
{
if (ev.type == SelectionRequest && XGetSelectionOwner(display, CLIPBOARD_atom) == window && ev.xselectionrequest.selection == CLIPBOARD_atom)
{
XCopy(clipboard_text, ev);
return false;
}
return true;
}
double SystemInterface_X11::GetElapsedTime()
{
struct timeval now;
gettimeofday(&now, nullptr);
double sec = now.tv_sec - start_time.tv_sec;
double usec = now.tv_usec - start_time.tv_usec;
double result = sec + (usec / 1000000.0);
return result;
}
void SystemInterface_X11::SetMouseCursor(const Rml::String& cursor_name)
{
if (display && window)
{
Cursor cursor_handle = 0;
if (cursor_name.empty() || cursor_name == "arrow")
cursor_handle = cursor_default;
else if (cursor_name == "move")
cursor_handle = cursor_move;
else if (cursor_name == "pointer")
cursor_handle = cursor_pointer;
else if (cursor_name == "resize")
cursor_handle = cursor_resize;
else if (cursor_name == "cross")
cursor_handle = cursor_cross;
else if (cursor_name == "text")
cursor_handle = cursor_text;
else if (cursor_name == "rmlui-scroll-idle")
cursor_handle = cursor_move;
else if (cursor_name == "rmlui-scroll-up")
cursor_handle = cursor_move;
else if (cursor_name == "rmlui-scroll-down")
cursor_handle = cursor_move;
else if (cursor_name == "unavailable")
cursor_handle = cursor_unavailable;
if (cursor_handle)
{
XDefineCursor(display, window, cursor_handle);
}
}
}
void SystemInterface_X11::SetClipboardText(const Rml::String& text)
{
clipboard_text = text;
XSetSelectionOwner(display, CLIPBOARD_atom, window, 0);
}
void SystemInterface_X11::GetClipboardText(Rml::String& text)
{
if (XGetSelectionOwner(display, CLIPBOARD_atom) != window)
{
if (!UTF8_atom || !XPaste(UTF8_atom, text))
{
// fallback
XPaste(XA_STRING_atom, text);
}
}
else
{
text = clipboard_text;
}
}
void SystemInterface_X11::XCopy(const Rml::String& clipboard_data, const XEvent& event)
{
Atom format = (UTF8_atom ? UTF8_atom : XA_STRING_atom);
XSelectionEvent ev = {
SelectionNotify, // the event type that will be sent to the requestor
0, // serial
0, // send_event
event.xselectionrequest.display, event.xselectionrequest.requestor, event.xselectionrequest.selection, event.xselectionrequest.target,
event.xselectionrequest.property,
0 // time
};
int retval = 0;
if (ev.target == TARGETS_atom)
{
retval = XChangeProperty(ev.display, ev.requestor, ev.property, XA_atom, 32, PropModeReplace, (unsigned char*)&format, 1);
}
else if (ev.target == XA_STRING_atom || ev.target == TEXT_atom)
{
retval = XChangeProperty(ev.display, ev.requestor, ev.property, XA_STRING_atom, 8, PropModeReplace, (unsigned char*)clipboard_data.c_str(),
clipboard_data.size());
}
else if (ev.target == UTF8_atom)
{
retval = XChangeProperty(ev.display, ev.requestor, ev.property, UTF8_atom, 8, PropModeReplace, (unsigned char*)clipboard_data.c_str(),
clipboard_data.size());
}
else
{
ev.property = 0;
}
if ((retval & 2) == 0)
{
// Notify the requestor that clipboard data is available
XSendEvent(display, ev.requestor, 0, 0, (XEvent*)&ev);
}
}
bool SystemInterface_X11::XPaste(Atom target_atom, Rml::String& clipboard_data)
{
XEvent ev;
// A SelectionRequest event will be sent to the clipboard owner, which should respond with SelectionNotify
XConvertSelection(display, CLIPBOARD_atom, target_atom, XSEL_DATA_atom, window, CurrentTime);
XSync(display, 0);
XNextEvent(display, &ev);
if (ev.type == SelectionNotify)
{
if (ev.xselection.property == 0)
{
// If no owner for the specified selection exists, the X server generates
// a SelectionNotify event with property None (0).
return false;
}
if (ev.xselection.selection == CLIPBOARD_atom)
{
int actual_format;
unsigned long bytes_after, nitems;
char* prop = nullptr;
Atom actual_type;
XGetWindowProperty(ev.xselection.display, ev.xselection.requestor, ev.xselection.property,
0L, // offset
(~0L), // length
0, // delete?
AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char**)&prop);
if (actual_type == UTF8_atom || actual_type == XA_STRING_atom)
{
clipboard_data = Rml::String(prop, prop + nitems);
XFree(prop);
}
XDeleteProperty(ev.xselection.display, ev.xselection.requestor, ev.xselection.property);
return true;
}
}
return false;
}
bool RmlX11::HandleInputEvent(Rml::Context* context, Display* display, const XEvent& ev)
{
switch (ev.type)
{
case ButtonPress:
{
switch (ev.xbutton.button)
{
case Button1:
case Button2:
case Button3: return context->ProcessMouseButtonDown(ConvertMouseButton(ev.xbutton.button), RmlX11::GetKeyModifierState(ev.xbutton.state));
case Button4: return context->ProcessMouseWheel(-1, RmlX11::GetKeyModifierState(ev.xbutton.state));
case Button5: return context->ProcessMouseWheel(1, RmlX11::GetKeyModifierState(ev.xbutton.state));
default: return true;
}
}
break;
case ButtonRelease:
{
switch (ev.xbutton.button)
{
case Button1:
case Button2:
case Button3: return context->ProcessMouseButtonUp(ConvertMouseButton(ev.xbutton.button), RmlX11::GetKeyModifierState(ev.xbutton.state));
default: return true;
}
}
break;
case MotionNotify:
{
return context->ProcessMouseMove(ev.xmotion.x, ev.xmotion.y, RmlX11::GetKeyModifierState(ev.xmotion.state));
}
break;
case LeaveNotify:
{
return context->ProcessMouseLeave();
}
break;
case KeyPress:
{
Rml::Input::KeyIdentifier key_identifier = RmlX11::ConvertKey(display, ev.xkey.keycode);
const int key_modifier_state = RmlX11::GetKeyModifierState(ev.xkey.state);
bool propagates = true;
if (key_identifier != Rml::Input::KI_UNKNOWN)
propagates = context->ProcessKeyDown(key_identifier, key_modifier_state);
Rml::Character character = GetCharacterCode(key_identifier, key_modifier_state);
if (character != Rml::Character::Null && !(key_modifier_state & Rml::Input::KM_CTRL))
propagates &= context->ProcessTextInput(character);
return propagates;
}
break;
case KeyRelease:
{
Rml::Input::KeyIdentifier key_identifier = RmlX11::ConvertKey(display, ev.xkey.keycode);
const int key_modifier_state = RmlX11::GetKeyModifierState(ev.xkey.state);
bool propagates = true;
if (key_identifier != Rml::Input::KI_UNKNOWN)
propagates = context->ProcessKeyUp(key_identifier, key_modifier_state);
return propagates;
}
break;
default: break;
}
return true;
}
int RmlX11::GetKeyModifierState(int x11_state)
{
int key_modifier_state = 0;
if (x11_state & ShiftMask)
key_modifier_state |= Rml::Input::KM_SHIFT;
if (x11_state & LockMask)
key_modifier_state |= Rml::Input::KM_CAPSLOCK;
if (x11_state & ControlMask)
key_modifier_state |= Rml::Input::KM_CTRL;
if (x11_state & Mod5Mask)
key_modifier_state |= Rml::Input::KM_ALT;
if (x11_state & Mod2Mask)
key_modifier_state |= Rml::Input::KM_NUMLOCK;
return key_modifier_state;
}
/**
X11 Key data used for key code conversion.
*/
struct XKeyData {
#ifdef HAS_X11XKBLIB
bool has_xkblib = false;
#endif // HAS_X11XKBLIB
bool initialized = false;
int min_keycode = 0, max_keycode = 0, keysyms_per_keycode = 0;
KeySym* x11_key_mapping = nullptr;
};
// Get the key data, which is initialized and filled on the first fetch.
static const XKeyData& GetXKeyData(Display* display)
{
RMLUI_ASSERT(display);
static XKeyData data;
if (!data.initialized)
{
data.initialized = true;
#ifdef HAS_X11XKBLIB
int opcode_rtrn = -1;
int event_rtrn = -1;
int error_rtrn = -1;
int major_in_out = -1;
int minor_in_out = -1;
// Xkb extension may not exist in the server. This checks for its existence and initializes the extension if available.
data.has_xkblib = XkbQueryExtension(display, &opcode_rtrn, &event_rtrn, &error_rtrn, &major_in_out, &minor_in_out);
// if Xkb isn't available, fall back to using XGetKeyboardMapping, which may occur if RmlUi is compiled with Xkb support but the server
// doesn't support it. This occurs with older X11 servers or virtual framebuffers such as x11vnc server.
if (!data.has_xkblib)
#endif // HAS_X11XKBLIB
{
XDisplayKeycodes(display, &data.min_keycode, &data.max_keycode);
data.x11_key_mapping = XGetKeyboardMapping(display, data.min_keycode, data.max_keycode + 1 - data.min_keycode, &data.keysyms_per_keycode);
}
}
return data;
}
Rml::Input::KeyIdentifier RmlX11::ConvertKey(Display* display, unsigned int x11_key_code)
{
RMLUI_ASSERT(display);
const XKeyData& key_data = GetXKeyData(display);
const int group_index = 0; // this is always 0 for our limited example
KeySym sym = {};
#ifdef HAS_X11XKBLIB
if (key_data.has_xkblib)
{
sym = XkbKeycodeToKeysym(display, x11_key_code, 0, group_index);
}
else
#endif // HAS_X11XKBLIB
{
KeySym sym_full = key_data.x11_key_mapping[(x11_key_code - key_data.min_keycode) * key_data.keysyms_per_keycode + group_index];
KeySym lower_sym, upper_sym;
XConvertCase(sym_full, &lower_sym, &upper_sym);
sym = lower_sym;
}
// clang-format off
switch (sym & 0xFF)
{
case XK_BackSpace & 0xFF: return Rml::Input::KI_BACK;
case XK_Tab & 0xFF: return Rml::Input::KI_TAB;
case XK_Clear & 0xFF: return Rml::Input::KI_CLEAR;
case XK_Return & 0xFF: return Rml::Input::KI_RETURN;
case XK_Pause & 0xFF: return Rml::Input::KI_PAUSE;
case XK_Scroll_Lock & 0xFF: return Rml::Input::KI_SCROLL;
case XK_Escape & 0xFF: return Rml::Input::KI_ESCAPE;
case XK_Delete & 0xFF: return Rml::Input::KI_DELETE;
case XK_Kanji & 0xFF: return Rml::Input::KI_KANJI;
// case XK_Muhenkan & 0xFF: return Rml::Input::; /* Cancel Conversion */
// case XK_Henkan_Mode & 0xFF: return Rml::Input::; /* Start/Stop Conversion */
// case XK_Henkan & 0xFF: return Rml::Input::; /* Alias for Henkan_Mode */
// case XK_Romaji & 0xFF: return Rml::Input::; /* to Romaji */
// case XK_Hiragana & 0xFF: return Rml::Input::; /* to Hiragana */
// case XK_Katakana & 0xFF: return Rml::Input::; /* to Katakana */
// case XK_Hiragana_Katakana & 0xFF: return Rml::Input::; /* Hiragana/Katakana toggle */
// case XK_Zenkaku & 0xFF: return Rml::Input::; /* to Zenkaku */
// case XK_Hankaku & 0xFF: return Rml::Input::; /* to Hankaku */
// case XK_Zenkaku_Hankaku & 0xFF: return Rml::Input::; /* Zenkaku/Hankaku toggle */
case XK_Touroku & 0xFF: return Rml::Input::KI_OEM_FJ_TOUROKU;
// case XK_Massyo & 0xFF: return Rml::Input::KI_OEM_FJ_MASSHOU;
// case XK_Kana_Lock & 0xFF: return Rml::Input::; /* Kana Lock */
// case XK_Kana_Shift & 0xFF: return Rml::Input::; /* Kana Shift */
// case XK_Eisu_Shift & 0xFF: return Rml::Input::; /* Alphanumeric Shift */
// case XK_Eisu_toggle & 0xFF: return Rml::Input::; /* Alphanumeric toggle */
case XK_Home & 0xFF: return Rml::Input::KI_HOME;
case XK_Left & 0xFF: return Rml::Input::KI_LEFT;
case XK_Up & 0xFF: return Rml::Input::KI_UP;
case XK_Right & 0xFF: return Rml::Input::KI_RIGHT;
case XK_Down & 0xFF: return Rml::Input::KI_DOWN;
case XK_Prior & 0xFF: return Rml::Input::KI_PRIOR;
case XK_Next & 0xFF: return Rml::Input::KI_NEXT;
case XK_End & 0xFF: return Rml::Input::KI_END;
case XK_Begin & 0xFF: return Rml::Input::KI_HOME;
// case XK_Print & 0xFF: return Rml::Input::KI_SNAPSHOT;
// case XK_Insert & 0xFF: return Rml::Input::KI_INSERT;
case XK_Num_Lock & 0xFF: return Rml::Input::KI_NUMLOCK;
case XK_KP_Space & 0xFF: return Rml::Input::KI_SPACE;
case XK_KP_Tab & 0xFF: return Rml::Input::KI_TAB;
case XK_KP_Enter & 0xFF: return Rml::Input::KI_NUMPADENTER;
case XK_KP_F1 & 0xFF: return Rml::Input::KI_F1;
case XK_KP_F2 & 0xFF: return Rml::Input::KI_F2;
case XK_KP_F3 & 0xFF: return Rml::Input::KI_F3;
case XK_KP_F4 & 0xFF: return Rml::Input::KI_F4;
case XK_KP_Home & 0xFF: return Rml::Input::KI_NUMPAD7;
case XK_KP_Left & 0xFF: return Rml::Input::KI_NUMPAD4;
case XK_KP_Up & 0xFF: return Rml::Input::KI_NUMPAD8;
case XK_KP_Right & 0xFF: return Rml::Input::KI_NUMPAD6;
case XK_KP_Down & 0xFF: return Rml::Input::KI_NUMPAD2;
case XK_KP_Prior & 0xFF: return Rml::Input::KI_NUMPAD9;
case XK_KP_Next & 0xFF: return Rml::Input::KI_NUMPAD3;
case XK_KP_End & 0xFF: return Rml::Input::KI_NUMPAD1;
case XK_KP_Begin & 0xFF: return Rml::Input::KI_NUMPAD5;
case XK_KP_Insert & 0xFF: return Rml::Input::KI_NUMPAD0;
case XK_KP_Delete & 0xFF: return Rml::Input::KI_DECIMAL;
case XK_KP_Equal & 0xFF: return Rml::Input::KI_OEM_NEC_EQUAL;
case XK_KP_Multiply & 0xFF: return Rml::Input::KI_MULTIPLY;
case XK_KP_Add & 0xFF: return Rml::Input::KI_ADD;
case XK_KP_Separator & 0xFF: return Rml::Input::KI_SEPARATOR;
case XK_KP_Subtract & 0xFF: return Rml::Input::KI_SUBTRACT;
case XK_KP_Decimal & 0xFF: return Rml::Input::KI_DECIMAL;
case XK_KP_Divide & 0xFF: return Rml::Input::KI_DIVIDE;
case XK_F1 & 0xFF: return Rml::Input::KI_F1;
case XK_F2 & 0xFF: return Rml::Input::KI_F2;
case XK_F3 & 0xFF: return Rml::Input::KI_F3;
case XK_F4 & 0xFF: return Rml::Input::KI_F4;
case XK_F5 & 0xFF: return Rml::Input::KI_F5;
case XK_F6 & 0xFF: return Rml::Input::KI_F6;
case XK_F7 & 0xFF: return Rml::Input::KI_F7;
case XK_F8 & 0xFF: return Rml::Input::KI_F8;
case XK_F9 & 0xFF: return Rml::Input::KI_F9;
case XK_F10 & 0xFF: return Rml::Input::KI_F10;
case XK_F11 & 0xFF: return Rml::Input::KI_F11;
case XK_F12 & 0xFF: return Rml::Input::KI_F12;
case XK_F13 & 0xFF: return Rml::Input::KI_F13;
case XK_F14 & 0xFF: return Rml::Input::KI_F14;
case XK_F15 & 0xFF: return Rml::Input::KI_F15;
case XK_F16 & 0xFF: return Rml::Input::KI_F16;
case XK_F17 & 0xFF: return Rml::Input::KI_F17;
case XK_F18 & 0xFF: return Rml::Input::KI_F18;
case XK_F19 & 0xFF: return Rml::Input::KI_F19;
case XK_F20 & 0xFF: return Rml::Input::KI_F20;
case XK_F21 & 0xFF: return Rml::Input::KI_F21;
case XK_F22 & 0xFF: return Rml::Input::KI_F22;
case XK_F23 & 0xFF: return Rml::Input::KI_F23;
case XK_F24 & 0xFF: return Rml::Input::KI_F24;
case XK_Shift_L & 0xFF: return Rml::Input::KI_LSHIFT;
case XK_Shift_R & 0xFF: return Rml::Input::KI_RSHIFT;
case XK_Control_L & 0xFF: return Rml::Input::KI_LCONTROL;
case XK_Control_R & 0xFF: return Rml::Input::KI_RCONTROL;
case XK_Caps_Lock & 0xFF: return Rml::Input::KI_CAPITAL;
case XK_Alt_L & 0xFF: return Rml::Input::KI_LMENU;
case XK_Alt_R & 0xFF: return Rml::Input::KI_RMENU;
case XK_space & 0xFF: return Rml::Input::KI_SPACE;
case XK_apostrophe & 0xFF: return Rml::Input::KI_OEM_7;
case XK_comma & 0xFF: return Rml::Input::KI_OEM_COMMA;
case XK_minus & 0xFF: return Rml::Input::KI_OEM_MINUS;
case XK_period & 0xFF: return Rml::Input::KI_OEM_PERIOD;
case XK_slash & 0xFF: return Rml::Input::KI_OEM_2;
case XK_0 & 0xFF: return Rml::Input::KI_0;
case XK_1 & 0xFF: return Rml::Input::KI_1;
case XK_2 & 0xFF: return Rml::Input::KI_2;
case XK_3 & 0xFF: return Rml::Input::KI_3;
case XK_4 & 0xFF: return Rml::Input::KI_4;
case XK_5 & 0xFF: return Rml::Input::KI_5;
case XK_6 & 0xFF: return Rml::Input::KI_6;
case XK_7 & 0xFF: return Rml::Input::KI_7;
case XK_8 & 0xFF: return Rml::Input::KI_8;
case XK_9 & 0xFF: return Rml::Input::KI_9;
case XK_semicolon & 0xFF: return Rml::Input::KI_OEM_1;
case XK_equal & 0xFF: return Rml::Input::KI_OEM_PLUS;
case XK_bracketleft & 0xFF: return Rml::Input::KI_OEM_4;
case XK_backslash & 0xFF: return Rml::Input::KI_OEM_5;
case XK_bracketright & 0xFF: return Rml::Input::KI_OEM_6;
case XK_grave & 0xFF: return Rml::Input::KI_OEM_3;
case XK_a & 0xFF: return Rml::Input::KI_A;
case XK_b & 0xFF: return Rml::Input::KI_B;
case XK_c & 0xFF: return Rml::Input::KI_C;
case XK_d & 0xFF: return Rml::Input::KI_D;
case XK_e & 0xFF: return Rml::Input::KI_E;
case XK_f & 0xFF: return Rml::Input::KI_F;
case XK_g & 0xFF: return Rml::Input::KI_G;
case XK_h & 0xFF: return Rml::Input::KI_H;
case XK_i & 0xFF: return Rml::Input::KI_I;
case XK_j & 0xFF: return Rml::Input::KI_J;
case XK_k & 0xFF: return Rml::Input::KI_K;
case XK_l & 0xFF: return Rml::Input::KI_L;
case XK_m & 0xFF: return Rml::Input::KI_M;
case XK_n & 0xFF: return Rml::Input::KI_N;
case XK_o & 0xFF: return Rml::Input::KI_O;
case XK_p & 0xFF: return Rml::Input::KI_P;
case XK_q & 0xFF: return Rml::Input::KI_Q;
case XK_r & 0xFF: return Rml::Input::KI_R;
case XK_s & 0xFF: return Rml::Input::KI_S;
case XK_t & 0xFF: return Rml::Input::KI_T;
case XK_u & 0xFF: return Rml::Input::KI_U;
case XK_v & 0xFF: return Rml::Input::KI_V;
case XK_w & 0xFF: return Rml::Input::KI_W;
case XK_x & 0xFF: return Rml::Input::KI_X;
case XK_y & 0xFF: return Rml::Input::KI_Y;
case XK_z & 0xFF: return Rml::Input::KI_Z;
default: break;
}
// clang-format on
return Rml::Input::KI_UNKNOWN;
}
int RmlX11::ConvertMouseButton(unsigned int x11_mouse_button)
{
switch (x11_mouse_button)
{
case Button1: return 0;
case Button2: return 2;
case Button3: return 1;
default: break;
}
return 0;
}
/**
This map contains 4 different mappings from key identifiers to character codes. Each entry represents a different
combination of shift and capslock state.
*/
static char ascii_map[4][51] = {
// shift off and capslock off
{0, ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ';', '=', ',', '-', '.', '/', '`', '[', '\\', ']', '\'', 0, 0},
// shift on and capslock off
{0, ' ', ')', '!', '@', '#', '$', '%', '^', '&', '*', '(', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ':', '+', '<', '_', '>', '?', '~', '{', '|', '}', '"', 0, 0},
// shift on and capslock on
{0, ' ', ')', '!', '@', '#', '$', '%', '^', '&', '*', '(', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ':', '+', '<', '_', '>', '?', '~', '{', '|', '}', '"', 0, 0},
// shift off and capslock on
{0, ' ', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', ';', '=', ',', '-', '.', '/', '`', '[', '\\', ']', '\'', 0, 0}};
static char keypad_map[2][18] = {{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '\n', '*', '+', 0, '-', '.', '/', '='},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\n', '*', '+', 0, '-', 0, '/', '='}};
// Returns the character code for a key identifer / key modifier combination.
static Rml::Character GetCharacterCode(Rml::Input::KeyIdentifier key_identifier, int key_modifier_state)
{
using Rml::Character;
// Check if we have a keycode capable of generating characters on the main keyboard (ie, not on the numeric
// keypad; that is dealt with below).
if (key_identifier <= Rml::Input::KI_OEM_102)
{
// Get modifier states
bool shift = (key_modifier_state & Rml::Input::KM_SHIFT) > 0;
bool capslock = (key_modifier_state & Rml::Input::KM_CAPSLOCK) > 0;
// Return character code based on identifier and modifiers
if (shift && !capslock)
return (Character)ascii_map[1][key_identifier];
if (shift && capslock)
return (Character)ascii_map[2][key_identifier];
if (!shift && capslock)
return (Character)ascii_map[3][key_identifier];
return (Character)ascii_map[0][key_identifier];
}
// Check if we have a keycode from the numeric keypad.
else if (key_identifier <= Rml::Input::KI_OEM_NEC_EQUAL)
{
if (key_modifier_state & Rml::Input::KM_NUMLOCK)
return (Character)keypad_map[0][key_identifier - Rml::Input::KI_NUMPAD0];
else
return (Character)keypad_map[1][key_identifier - Rml::Input::KI_NUMPAD0];
}
else if (key_identifier == Rml::Input::KI_RETURN)
return (Character)'\n';
return Character::Null;
}

View File

@@ -0,0 +1,75 @@
#pragma once
#include "RmlUi_Include_Xlib.h"
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/SystemInterface.h>
#include <RmlUi/Core/Types.h>
#include <X11/Xutil.h>
class SystemInterface_X11 : public Rml::SystemInterface {
public:
SystemInterface_X11(Display* display);
// Optionally, provide or change the window to be used for setting the mouse cursors and clipboard text.
void SetWindow(Window window);
// Handle selection request events.
// @return True if the event is still propagating, false if it was handled here.
bool HandleSelectionRequest(const XEvent& ev);
// -- Inherited from Rml::SystemInterface --
double GetElapsedTime() override;
void SetMouseCursor(const Rml::String& cursor_name) override;
void SetClipboardText(const Rml::String& text) override;
void GetClipboardText(Rml::String& text) override;
private:
void XCopy(const Rml::String& clipboard_data, const XEvent& event);
bool XPaste(Atom target_atom, Rml::String& clipboard_data);
Display* display = nullptr;
Window window = 0;
timeval start_time;
Cursor cursor_default = 0;
Cursor cursor_move = 0;
Cursor cursor_pointer = 0;
Cursor cursor_resize = 0;
Cursor cursor_cross = 0;
Cursor cursor_text = 0;
Cursor cursor_unavailable = 0;
// -- Clipboard
Rml::String clipboard_text;
Atom XA_atom = 4;
Atom XA_STRING_atom = 31;
Atom UTF8_atom;
Atom CLIPBOARD_atom;
Atom XSEL_DATA_atom;
Atom TARGETS_atom;
Atom TEXT_atom;
};
/**
Optional helper functions for the X11 plaform.
*/
namespace RmlX11 {
// Applies input on the context based on the given SFML event.
// @return True if the event is still propagating, false if it was handled by the context.
bool HandleInputEvent(Rml::Context* context, Display* display, const XEvent& ev);
// Converts the X11 key code to RmlUi key.
// @note The display must be passed here as it needs to query the connected X server display for information about its install keymap abilities.
Rml::Input::KeyIdentifier ConvertKey(Display* display, unsigned int x11_key_code);
// Converts the X11 mouse button to RmlUi mouse button.
int ConvertMouseButton(unsigned int x11_mouse_button);
// Converts the X11 key modifier to RmlUi key modifier.
int GetKeyModifierState(int x11_state);
} // namespace RmlX11

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,696 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
#ifndef RMLUI_PLATFORM_WIN32
#error "DirectX 12 renderer only supported on Windows"
#endif
// clang-format off
#include "RmlUi_Include_Windows.h"
#include "RmlUi_Include_DirectX_12.h"
// clang-format on
enum class ProgramId;
class RenderLayerStack;
namespace Gfx {
struct ProgramData;
struct FramebufferData;
} // namespace Gfx
namespace Backend {
struct RmlRenderInitInfo;
struct RmlRendererSettings;
} // namespace Backend
class RenderInterface_DX12 : public Rml::RenderInterface {
private:
static constexpr uint32_t InvalidConstantBuffer_RootParameterIndex = std::numeric_limits<uint32_t>::max();
static constexpr uint8_t MaxConstantBuffersPerShader = 3;
static constexpr size_t NumPrograms = 23;
public:
struct GraphicsAllocationInfo {
int buffer_index = -1;
size_t offset = {};
size_t size = {};
D3D12MA::VirtualAllocation alloc_info = {};
};
class TextureHandleType : Rml::NonCopyMoveable {
public:
TextureHandleType() = default;
~TextureHandleType()
{
#ifdef RMLUI_DX_DEBUG
RMLUI_ASSERTMSG(m_is_destroyed, "The texture was not destroyed");
#endif
}
const GraphicsAllocationInfo& Get_Info() const noexcept { return m_info; }
void Set_Info(const GraphicsAllocationInfo& info) { m_info = info; }
void Set_Resource(void* p_resource) { m_p_resource = p_resource; }
void* Get_Resource() const noexcept { return m_p_resource; }
#ifdef RMLUI_DX_DEBUG
/// @brief returns debug resource name. Implementation of this method exists only when DEBUG is enabled for this project
/// @return debug resource name when the source string is passed in LoadTexture method
const Rml::String& Get_ResourceName() const { return m_debug_resource_name; }
/// @brief sets m_debug_resource_name field with specified argument as resource_name
/// @param resource_name this string is getting from LoadTexture method from source argument
void Set_ResourceName(const Rml::String& resource_name) { m_debug_resource_name = resource_name; }
#endif
// this method calls texture manager! don't call it manually because you must ensure that you freed virtual block if it had
void Destroy()
{
#ifdef RMLUI_DX_DEBUG
RMLUI_ASSERTMSG(!m_is_destroyed, "The texture has already been destroyed");
m_is_destroyed = true;
#endif
if (m_p_resource)
{
if (m_info.buffer_index != -1)
{
static_cast<ID3D12Resource*>(m_p_resource)->Release();
}
else
{
D3D12MA::Allocation* p_committed_resource = static_cast<D3D12MA::Allocation*>(m_p_resource);
if (auto* underlying_resource = p_committed_resource->GetResource())
underlying_resource->Release();
p_committed_resource->Release();
}
m_info = GraphicsAllocationInfo();
m_p_resource = nullptr;
}
}
const OffsetAllocator::Allocation& Get_Allocation_DescriptorHeap() const noexcept { return m_allocation_descriptor_heap; }
void Set_Allocation_DescriptorHeap(const OffsetAllocator::Allocation& allocation) noexcept { m_allocation_descriptor_heap = allocation; }
private:
GraphicsAllocationInfo m_info;
OffsetAllocator::Allocation m_allocation_descriptor_heap;
void* m_p_resource = nullptr; // placed or committed (CreateResource from D3D12MA)
#ifdef RMLUI_DX_DEBUG
Rml::String m_debug_resource_name;
bool m_is_destroyed = false;
#endif
};
struct ConstantBufferType {
GraphicsAllocationInfo m_alloc_info;
void* m_p_gpu_start_memory_for_binding_data = nullptr;
};
class GeometryHandleType : Rml::NonCopyMoveable {
public:
GeometryHandleType() = default;
void Set_InfoVertex(const GraphicsAllocationInfo& info) { m_info_vertex = info; }
const GraphicsAllocationInfo& Get_InfoVertex() const noexcept { return m_info_vertex; }
void Set_InfoIndex(const GraphicsAllocationInfo& info) { m_info_index = info; }
const GraphicsAllocationInfo& Get_InfoIndex() const noexcept { return m_info_index; }
void Set_NumVertices(int num) { m_num_vertices = num; }
int Get_NumVertices() const { return m_num_vertices; }
void Set_NumIndices(int num) { m_num_indices = num; }
int Get_NumIndices() const { return m_num_indices; }
void Set_SizeOfOneVertex(size_t size) { m_one_element_vertex_size = size; }
size_t Get_SizeOfOneVertex() const { return m_one_element_vertex_size; }
void Set_SizeOfOneIndex(size_t size) { m_one_element_index_size = size; }
size_t Get_SizeOfOneIndex() const { return m_one_element_index_size; }
const OffsetAllocator::Allocation& Get_Allocation_DescriptorHeap() const noexcept { return m_allocation_descriptor_heap; }
void Set_Allocation_DescriptorHeap(const OffsetAllocator::Allocation& allocation) noexcept { m_allocation_descriptor_heap = allocation; }
int Get_HistoryBackBufferFrameIndex(void) const { return m_history_backbuffer_frame_index; }
void Set_HistoryBackBufferFrameIndex(int frame_index) { m_history_backbuffer_frame_index = frame_index; }
void Set_ConstantBuffer(ConstantBufferType* p_constant_buffer)
{
RMLUI_ASSERTMSG(p_constant_buffer, "must be valid constant buffer!");
m_p_constant_buffer_override = p_constant_buffer;
}
void Reset_ConstantBuffer()
{
m_p_constant_buffer_override = nullptr;
for (uint8_t i = 0; i < MaxConstantBuffersPerShader; ++i)
{
m_constant_buffer_root_parameter_indices[i] = InvalidConstantBuffer_RootParameterIndex;
}
}
ConstantBufferType* Get_ConstantBuffer() const { return m_p_constant_buffer_override; }
// use this only for shared CBV that will be used among all shaders otherwise we need to provide a new array that will hold a GPU addresses to
// different cbv and their indices but for now it is only for ONE CBV that can be used among vertex and pixel shaders
void Add_ConstantBufferRootParameterIndices(uint32_t index)
{
#ifdef RMLUI_DX_DEBUG
bool found_empty_slot = false;
#endif
for (unsigned char i = 0; i < MaxConstantBuffersPerShader; ++i)
{
if (m_constant_buffer_root_parameter_indices[i] == InvalidConstantBuffer_RootParameterIndex)
{
#ifdef RMLUI_DX_DEBUG
found_empty_slot = true;
#endif
m_constant_buffer_root_parameter_indices[i] = index;
break;
}
}
#ifdef RMLUI_DX_DEBUG
RMLUI_ASSERTMSG(found_empty_slot,
"failed to obtain empty slot in such case you have to set another limits for engine using "
"MaxConstantBuffersPerShader field");
#endif
}
const uint32_t* Get_ConstantBufferRootParameterIndices(uint8_t& amount_of_indices) const
{
amount_of_indices = 0;
// might be slow for big arrays otherwise provide a cache field for this class just like field that will show current size of
// m_constant_buffer_root_parameter_indices member in terms of current entries that are filled the array for now just to reduce the
// memory footprint for this class and I don't use this cache field as size so we determine in runtime
for (uint8_t i = 0; i < MaxConstantBuffersPerShader; ++i)
{
if (m_constant_buffer_root_parameter_indices[i] != InvalidConstantBuffer_RootParameterIndex)
{
amount_of_indices = i + 1;
}
}
return m_constant_buffer_root_parameter_indices;
}
private:
int m_num_vertices = {};
int m_num_indices = {};
int m_history_backbuffer_frame_index = -1;
ConstantBufferType* m_p_constant_buffer_override = {};
size_t m_one_element_vertex_size = {};
size_t m_one_element_index_size = {};
uint32_t m_constant_buffer_root_parameter_indices[MaxConstantBuffersPerShader] = {
InvalidConstantBuffer_RootParameterIndex,
InvalidConstantBuffer_RootParameterIndex,
InvalidConstantBuffer_RootParameterIndex,
};
GraphicsAllocationInfo m_info_vertex;
GraphicsAllocationInfo m_info_index;
OffsetAllocator::Allocation m_allocation_descriptor_heap;
};
class BufferMemoryManager : Rml::NonCopyMoveable {
public:
BufferMemoryManager();
~BufferMemoryManager();
void Initialize(ID3D12Device* m_p_device, D3D12MA::Allocator* p_allocator,
OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav, D3D12_CPU_DESCRIPTOR_HANDLE* p_handle,
uint32_t size_descriptor_element, size_t size_for_allocation = RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION,
size_t size_alignment = RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER);
void Shutdown();
void Alloc_Vertex(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, GeometryHandleType* p_handle);
void Alloc_Index(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, GeometryHandleType* p_handle);
GraphicsAllocationInfo Alloc_ConstantBuffer(ConstantBufferType* p_resource, size_t size);
void Free_ConstantBuffer(ConstantBufferType* p_constantbuffer);
void Free_Geometry(GeometryHandleType* p_geometryhandle);
void* Get_WritableMemoryFromBufferByOffset(const GraphicsAllocationInfo& info);
D3D12MA::Allocation* Get_BufferByIndex(int buffer_index);
bool Is_Initialized() const;
private:
void Alloc_Buffer(size_t size
#ifdef RMLUI_DX_DEBUG
,
const std::wstring& debug_name
#endif
);
// Searches for block that has enough memory for requested allocation size, otherwise returns nullptr that means no block!
D3D12MA::VirtualBlock* Get_AvailableBlock(size_t size_for_allocation, int* p_result_buffer_index);
D3D12MA::VirtualBlock* Get_NotOutOfMemoryAndAvailableBlock(size_t size_for_allocation, int* p_result_buffer_index);
int Alloc(GraphicsAllocationInfo& info, size_t size, size_t alignment = 0);
void TryToFreeAvailableBlock();
uint32_t m_descriptor_increment_size_srv_cbv_uav;
size_t m_size_for_allocation_in_bytes;
size_t m_size_alignment_in_bytes;
ID3D12Device* m_p_device;
D3D12MA::Allocator* m_p_allocator;
OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav;
D3D12_CPU_DESCRIPTOR_HANDLE* m_p_start_pointer_of_descriptor_heap_srv_cbv_uav;
/// @brief this is for sub allocating purposes using 'Virtual Allocation' from D3D12MA
Rml::Vector<D3D12MA::VirtualBlock*> m_virtual_buffers;
/// @brief this is physical representation of VRAM and uses from CPU side for binding data
Rml::Vector<Rml::Pair<D3D12MA::Allocation*, void*>> m_buffers;
};
/**
* The key feature of this manager is texture management and if texture size is less or equal to 1.0 MB it will
* allocate heap for placing resources and if resource is less (or equal) to 1 MB then it will be written to that
* heap Otherwise will be used heap per texture (committed resource)
*/
class TextureMemoryManager : Rml::NonCopyMoveable {
public:
TextureMemoryManager();
~TextureMemoryManager();
void Initialize(D3D12MA::Allocator* p_allocator, OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav,
ID3D12Device* p_device, ID3D12GraphicsCommandList* p_copy_command_list, ID3D12GraphicsCommandList* p_backend_command_list,
ID3D12CommandAllocator* p_copy_allocator_command, ID3D12DescriptorHeap* p_descriptor_heap_srv,
ID3D12DescriptorHeap* p_descriptor_heap_rtv, ID3D12DescriptorHeap* p_descriptor_heap_dsv, ID3D12CommandQueue* p_copy_queue,
D3D12_CPU_DESCRIPTOR_HANDLE* p_handle, RenderInterface_DX12* p_renderer,
size_t size_for_placed_heap = RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION);
void Shutdown();
ID3D12Resource* Alloc_Texture(D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, const Rml::byte* p_data
#ifdef RMLUI_DX_DEBUG
,
const Rml::String& debug_name
#endif
);
// if you want to create texture for rendering to it aka render target texture
ID3D12Resource* Alloc_Texture(D3D12_RESOURCE_DESC& desc, Gfx::FramebufferData* p_impl, D3D12_RESOURCE_FLAGS flags,
D3D12_RESOURCE_STATES initial_state
#ifdef RMLUI_DX_DEBUG
,
const Rml::String& debug_name
#endif
);
void Free_Texture(TextureHandleType* type);
void Free_Texture(Gfx::FramebufferData* p_texture);
void Free_Texture(TextureHandleType* p_allocated_texture_with_class, bool is_rt, D3D12MA::VirtualAllocation& allocation);
bool Is_Initialized() const;
private:
bool CanAllocate(size_t total_memory_for_allocation, D3D12MA::VirtualBlock* p_block);
bool CanBePlacedResource(size_t total_memory_for_allocation);
// don't use it for MSAA texture because we don't implement a such feature!
bool CanBeSmallResource(size_t base_memory);
// use index for obtaining heap from m_heaps_placed
D3D12MA::VirtualBlock* Get_AvailableBlock(size_t total_memory_for_allocation, int* result_index);
void Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl,
const Rml::byte* p_data);
// we don't upload to GPU because it is render target and needed to be written
void Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES initial_state,
TextureHandleType* p_texture, Gfx::FramebufferData* p_impl);
void Alloc_As_Placed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, const Rml::byte* p_data);
void Upload(bool is_committed, TextureHandleType* p_texture_handle, const D3D12_RESOURCE_DESC& desc, const Rml::byte* p_data,
ID3D12Resource* p_impl);
size_t BytesPerPixel(DXGI_FORMAT format);
size_t BitsPerPixel(DXGI_FORMAT format);
Rml::Pair<ID3D12Heap*, D3D12MA::VirtualBlock*> Create_HeapPlaced(size_t size_for_creation);
D3D12_CPU_DESCRIPTOR_HANDLE Alloc_RenderTargetResourceView(ID3D12Resource* p_resource, D3D12MA::VirtualAllocation* p_allocation);
D3D12_CPU_DESCRIPTOR_HANDLE Alloc_DepthStencilResourceView(ID3D12Resource* p_resource, D3D12MA::VirtualAllocation* p_allocation);
size_t m_size_for_placed_heap;
/// @brief limit size that we define as acceptable. On 4 Mb it is enough for placing 1 Mb but higher it is bad. 1 Mb occupies 25% from 4 Mb
/// and that means that m_size_limit_for_being_placed will be determined as 25% * m_size_for_placed_heap;
size_t m_size_limit_for_being_placed;
size_t m_size_srv_cbv_uav_descriptor;
size_t m_size_rtv_descriptor;
size_t m_size_dsv_descriptor;
size_t m_fence_value;
D3D12MA::Allocator* m_p_allocator;
OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav;
ID3D12Device* m_p_device;
ID3D12GraphicsCommandList* m_p_copy_command_list;
/// @brief command list from drawing
ID3D12GraphicsCommandList* m_p_backend_command_list;
ID3D12CommandAllocator* m_p_command_allocator;
ID3D12DescriptorHeap* m_p_descriptor_heap_srv;
ID3D12CommandQueue* m_p_copy_queue;
ID3D12Fence* m_p_fence;
HANDLE m_p_fence_event;
D3D12_CPU_DESCRIPTOR_HANDLE* m_p_handle;
RenderInterface_DX12* m_p_renderer;
D3D12MA::VirtualBlock* m_p_virtual_block_for_render_target_heap_allocations;
D3D12MA::VirtualBlock* m_p_virtual_block_for_depth_stencil_heap_allocations;
ID3D12DescriptorHeap* m_p_descriptor_heap_rtv;
ID3D12DescriptorHeap* m_p_descriptor_heap_dsv;
#if RMLUI_RENDER_BACKEND_FIELD_STAGING_BUFFER_CACHE_ENABLED == 1
D3D12MA::Allocation* m_p_upload_buffer;
#endif
Rml::Vector<D3D12MA::VirtualBlock*> m_blocks;
Rml::Vector<ID3D12Heap*> m_heaps_placed;
};
/*
Manages render targets, including the layer stack and postprocessing framebuffers.
Layers can be pushed and popped, creating new framebuffers as needed. Typically, geometry is rendered to the top
layer. The layer framebuffers may have MSAA enabled.
Postprocessing framebuffers are separate from the layers, and are commonly used to apply texture-wide effects
such as filters. They are used both as input and output during rendering, and do not use MSAA.
*/
class RenderLayerStack : Rml::NonCopyMoveable {
public:
RenderLayerStack();
~RenderLayerStack();
void Initialize(RenderInterface_DX12* p_owner);
void Shutdown();
// Push a new layer. All references to previously retrieved layers are invalidated.
Rml::LayerHandle PushLayer();
// Pop the top layer. All references to previously retrieved layers are invalidated.
void PopLayer();
const Gfx::FramebufferData& GetLayer(Rml::LayerHandle layer) const;
const Gfx::FramebufferData& GetTopLayer() const;
const Gfx::FramebufferData& Get_SharedDepthStencil_Layers();
// const Gfx::FramebufferData& Get_SharedDepthStencil_Postprocess();
Rml::LayerHandle GetTopLayerHandle() const;
const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); }
const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); }
const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); }
const Gfx::FramebufferData& GetBlendMask() { return EnsureFramebufferPostprocess(3); }
void SwapPostprocessPrimarySecondary();
void BeginFrame(int new_width, int new_height);
void EndFrame();
private:
void DestroyFramebuffers();
const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index);
void CreateFramebuffer(Gfx::FramebufferData* p_result, int width, int height, int sample_count, bool is_depth_stencil);
void DestroyFramebuffer(Gfx::FramebufferData* p_data);
unsigned char m_msaa_sample_count;
int m_width, m_height;
// The number of active layers is manually tracked since we re-use the framebuffers stored in the fb_layers stack.
int m_layers_size;
TextureMemoryManager* m_p_manager_texture;
BufferMemoryManager* m_p_manager_buffer;
ID3D12Device* m_p_device;
Rml::UniquePtr<Gfx::FramebufferData> m_p_depth_stencil_for_layers;
Rml::Vector<Gfx::FramebufferData> m_fb_layers;
Rml::Vector<Gfx::FramebufferData> m_fb_postprocess;
};
public:
RenderInterface_DX12(void* p_window_handle, const Backend::RmlRendererSettings& settings);
~RenderInterface_DX12();
// Returns true if the renderer was successfully constructed.
explicit operator bool() const;
// The viewport should be updated whenever the window size changes.
void SetViewport(int viewport_width, int viewport_height);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
// Draws the result to the backbuffer and restores OpenGL state.
void EndFrame();
// Optional, can be used to clear the active framebuffer.
void Clear();
// -- Inherited from Rml::RenderInterface --
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
void EnableClipMask(bool enable) override;
void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void SetTransform(const Rml::Matrix4f* transform) override;
Rml::LayerHandle PushLayer() override;
void CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blend_mode,
Rml::Span<const Rml::CompiledFilterHandle> filters) override;
void PopLayer() override;
Rml::TextureHandle SaveLayerAsTexture() override;
Rml::CompiledFilterHandle SaveLayerAsMaskImage() override;
Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override;
void ReleaseFilter(Rml::CompiledFilterHandle filter) override;
Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override;
void RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation,
Rml::TextureHandle texture) override;
void ReleaseShader(Rml::CompiledShaderHandle effect_handle) override;
// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
static constexpr Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
// Can be passed to RenderGeometry() to leave the bound texture and used program unchanged.
static constexpr Rml::TextureHandle TexturePostprocess = Rml::TextureHandle(-2);
bool IsSwapchainValid() noexcept;
void RecreateSwapchain() noexcept;
ID3D12Fence* Get_Fence();
HANDLE Get_FenceEvent();
Rml::Array<uint64_t, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT>& Get_FenceValues();
uint32_t Get_CurrentFrameIndex();
ID3D12Device* Get_Device() const;
TextureMemoryManager& Get_TextureManager();
BufferMemoryManager& Get_BufferManager();
unsigned char Get_MSAASampleCount() const;
void Set_UserFramebufferIndex(unsigned char framebuffer_index);
void Set_UserRenderTarget(void* rtv_where_we_render_to);
void Set_UserDepthStencil(void* dsv_where_we_render_to);
bool CaptureScreen(int& width, int& height, int& num_components, Rml::UniquePtr<Rml::byte[]>& data);
private:
void Initialize_Device() noexcept;
void Initialize_Adapter() noexcept;
void Initialize_DebugLayer() noexcept;
void Initialize_Swapchain(int width, int height) noexcept;
void Initialize_SyncPrimitives() noexcept;
void Initialize_SyncPrimitives_Screenshot() noexcept;
void Initialize_CommandAllocators();
void Initialize_Allocator() noexcept;
void Destroy_Swapchain() noexcept;
void Destroy_SyncPrimitives() noexcept;
void Destroy_CommandAllocators() noexcept;
void Destroy_CommandList() noexcept;
void Destroy_Allocator() noexcept;
void Destroy_SyncPrimitives_Screenshot() noexcept;
void Flush() noexcept;
uint64_t Signal(uint32_t frame_index) noexcept;
void WaitForFenceValue(uint32_t frame_index);
void Create_Resources_DependentOnSize() noexcept;
void Destroy_Resources_DependentOnSize() noexcept;
ID3D12DescriptorHeap* Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags,
uint32_t descriptor_count) noexcept;
ID3D12CommandAllocator* Create_CommandAllocator(D3D12_COMMAND_LIST_TYPE type);
ID3D12GraphicsCommandList* Create_CommandList(ID3D12CommandAllocator* p_allocator, D3D12_COMMAND_LIST_TYPE type) noexcept;
ID3D12CommandQueue* Create_CommandQueue(D3D12_COMMAND_LIST_TYPE type) noexcept;
void Create_Resource_RenderTargetViews();
void Destroy_Resource_RenderTagetViews();
void Destroy_Resource_For_Shaders();
void Create_Resource_Pipelines();
void Create_Resource_Pipeline_Color();
void Create_Resource_Pipeline_Texture();
void Create_Resource_Pipeline_Gradient();
void Create_Resource_Pipeline_Creation();
void Create_Resource_Pipeline_Passthrough();
void Create_Resource_Pipeline_Passthrough_NoBlend();
void Create_Resource_Pipeline_ColorMatrix();
void Create_Resource_Pipeline_BlendMask();
void Create_Resource_Pipeline_Blur();
void Create_Resource_Pipeline_DropShadow();
void Create_Resource_DepthStencil();
void Destroy_Resource_DepthStencil();
void Destroy_Resource_Pipelines();
bool CheckTearingSupport() noexcept;
IDXGIAdapter* Get_Adapter(bool is_use_warp) noexcept;
void PrintAdapterDesc(IDXGIAdapter* p_adapter);
void SetScissor(Rml::Rectanglei region, bool vertically_flip = false);
void SubmitTransformUniform(ConstantBufferType& constant_buffer, const Rml::Vector2f& translation);
void UseProgram(ProgramId pipeline_id);
ConstantBufferType* Get_ConstantBuffer(uint32_t current_back_buffer_index);
void Free_Geometry(GeometryHandleType* p_handle);
void Free_Texture(TextureHandleType* p_handle);
void Update_PendingForDeletion_Geometry();
void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_id);
void RenderFilters(Rml::Span<const Rml::CompiledFilterHandle> filter_handles);
void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp,
const Rml::Rectanglei window_flipped);
void DrawFullscreenQuad(ConstantBufferType* p_override_constant_buffer = nullptr);
void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f),
ConstantBufferType* p_override_constant_buffer = nullptr);
void BindTexture(TextureHandleType* p_texture, UINT root_parameter_index = 0);
void BindRenderTarget(const Gfx::FramebufferData& framebuffer, bool depth_included = true);
void OverrideConstantBufferOfGeometry(Rml::CompiledGeometryHandle geometry, ConstantBufferType* p_override_constant_buffer);
// 1 means not supported
// otherwise return max value of supported multisample count
unsigned char GetMSAASupportedSampleCount(unsigned char max_samples);
void BlitFramebuffer(const Gfx::FramebufferData& source, const Gfx::FramebufferData& dest, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0,
int dstY0, int dstX1, int dstY1);
// debug only
void ValidateTextureAllocationNotAsPlaced(const Gfx::FramebufferData& data);
ID3D12Resource* GetResourceFromFramebufferData(const Gfx::FramebufferData& data);
bool m_is_use_vsync;
bool m_is_use_tearing;
bool m_is_scissor_was_set;
bool m_is_stencil_enabled;
bool m_is_stencil_equal;
bool m_is_use_msaa;
bool m_is_execute_when_end_frame_issued;
bool m_is_command_list_user;
unsigned char m_msaa_sample_count;
unsigned char m_user_framebuffer_index;
// Current viewport's width
int m_width;
// Current viewport's height
int m_height;
int m_current_clip_operation;
ProgramId m_active_program_id;
Rml::Rectanglei m_scissor;
uint32_t m_size_descriptor_heap_render_target_view;
uint32_t m_size_descriptor_heap_shaders;
UINT m_current_back_buffer_index;
UINT m_stencil_ref_value;
// For debug builds: D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION, otherwise 0.
UINT m_default_shader_flags;
ID3D12Device* m_p_device;
ID3D12CommandQueue* m_p_command_queue;
ID3D12CommandQueue* m_p_copy_queue;
IDXGISwapChain4* m_p_swapchain;
ID3D12GraphicsCommandList* m_p_command_graphics_list;
ID3D12GraphicsCommandList* m_p_command_graphics_list_screenshot;
ID3D12CommandAllocator* m_p_command_allocator_screenshot;
ID3D12DescriptorHeap* m_p_descriptor_heap_render_target_view;
ID3D12DescriptorHeap* m_p_descriptor_heap_render_target_view_for_texture_manager;
ID3D12DescriptorHeap* m_p_descriptor_heap_depth_stencil_view_for_texture_manager;
// cbv; srv; uav; all in one
ID3D12DescriptorHeap* m_p_descriptor_heap_shaders;
ID3D12DescriptorHeap* m_p_descriptor_heap_depthstencil;
D3D12MA::Allocation* m_p_depthstencil_resource;
ID3D12Fence* m_p_backbuffer_fence;
ID3D12Fence* m_p_fence_screenshot;
IDXGIAdapter* m_p_adapter;
ID3D12PipelineState* m_pipelines[NumPrograms];
ID3D12RootSignature* m_root_signatures[NumPrograms];
ID3D12CommandAllocator* m_p_copy_allocator;
ID3D12GraphicsCommandList* m_p_copy_command_list;
D3D12MA::Allocator* m_p_allocator;
OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_shaders;
// where user wants to render rmlui final image
D3D12_CPU_DESCRIPTOR_HANDLE* m_p_user_rtv_present;
// as well as rtv just dsv
D3D12_CPU_DESCRIPTOR_HANDLE* m_p_user_dsv_present;
HWND m_p_window_handle;
HANDLE m_p_fence_event;
HANDLE m_p_fence_event_screenshot;
uint64_t m_fence_value;
uint64_t m_fence_screenshot_value;
Rml::CompiledGeometryHandle m_precompiled_fullscreen_quad_geometry;
Rml::Array<ID3D12Resource*, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_backbuffers_resources;
Rml::Array<ID3D12CommandAllocator*, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_backbuffers_allocators;
Rml::Array<uint64_t, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_backbuffers_fence_values;
Rml::Array<size_t, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_constant_buffer_count_per_frame;
Rml::Array<size_t, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_vertex_and_index_buffer_count_per_frame;
// per object (per draw)
Rml::Array<Rml::Vector<ConstantBufferType>, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT> m_constantbuffers;
Rml::Vector<GeometryHandleType*> m_pending_for_deletion_geometry;
DXGI_SAMPLE_DESC m_desc_sample;
D3D12_CPU_DESCRIPTOR_HANDLE m_handle_shaders;
BufferMemoryManager m_manager_buffer;
TextureMemoryManager m_manager_texture;
Rml::Matrix4f m_constant_buffer_data_transform;
Rml::Matrix4f m_constant_buffer_data_projection;
Rml::Matrix4f m_projection;
RenderLayerStack m_manager_render_layer;
};
namespace Backend {
struct RmlRendererSettings {
bool vsync;
unsigned char msaa_sample_count;
};
} // namespace Backend

View File

@@ -0,0 +1,325 @@
#include "RmlUi_Renderer_GL2.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Platform.h>
#include <string.h>
#if defined RMLUI_PLATFORM_WIN32
#include "RmlUi_Include_Windows.h"
#include <gl/Gl.h>
#elif defined RMLUI_PLATFORM_MACOSX
#include <AGL/agl.h>
#include <OpenGL/gl.h>
#include <OpenGL/glext.h>
#elif defined RMLUI_PLATFORM_UNIX
#include "RmlUi_Include_Xlib.h"
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#endif
#define GL_CLAMP_TO_EDGE 0x812F
RenderInterface_GL2::RenderInterface_GL2() {}
void RenderInterface_GL2::SetViewport(int in_viewport_width, int in_viewport_height)
{
viewport_width = in_viewport_width;
viewport_height = in_viewport_height;
}
void RenderInterface_GL2::BeginFrame()
{
RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0);
glViewport(0, 0, viewport_width, viewport_height);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, GLuint(-1));
glStencilMask(GLuint(-1));
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projection.data());
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
transform_enabled = false;
}
void RenderInterface_GL2::EndFrame() {}
void RenderInterface_GL2::Clear()
{
glClearStencil(0);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices)
{
GeometryView* data = new GeometryView{vertices, indices};
return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
}
void RenderInterface_GL2::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
{
delete reinterpret_cast<GeometryView*>(geometry);
}
void RenderInterface_GL2::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture)
{
const GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
const Rml::Vertex* vertices = geometry->vertices.data();
const int* indices = geometry->indices.data();
const int num_indices = (int)geometry->indices.size();
glPushMatrix();
glTranslatef(translation.x, translation.y, 0);
glVertexPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].position);
glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Rml::Vertex), &vertices[0].colour);
if (!texture)
{
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
else
{
glEnable(GL_TEXTURE_2D);
if (texture != TextureEnableWithoutBinding)
glBindTexture(GL_TEXTURE_2D, (GLuint)texture);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].tex_coord);
}
glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, indices);
glPopMatrix();
}
void RenderInterface_GL2::EnableScissorRegion(bool enable)
{
if (enable)
glEnable(GL_SCISSOR_TEST);
else
glDisable(GL_SCISSOR_TEST);
}
void RenderInterface_GL2::SetScissorRegion(Rml::Rectanglei region)
{
glScissor(region.Left(), viewport_height - region.Bottom(), region.Width(), region.Height());
}
void RenderInterface_GL2::EnableClipMask(bool enable)
{
if (enable)
glEnable(GL_STENCIL_TEST);
else
glDisable(GL_STENCIL_TEST);
}
void RenderInterface_GL2::RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation)
{
RMLUI_ASSERT(glIsEnabled(GL_STENCIL_TEST));
using Rml::ClipMaskOperation;
const bool clear_stencil = (operation == ClipMaskOperation::Set || operation == ClipMaskOperation::SetInverse);
if (clear_stencil)
{
// @performance Increment the reference value instead of clearing each time.
glClear(GL_STENCIL_BUFFER_BIT);
}
GLint stencil_test_value = 0;
glGetIntegerv(GL_STENCIL_REF, &stencil_test_value);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glStencilFunc(GL_ALWAYS, GLint(1), GLuint(-1));
switch (operation)
{
case ClipMaskOperation::Set:
{
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
stencil_test_value = 1;
}
break;
case ClipMaskOperation::SetInverse:
{
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
stencil_test_value = 0;
}
break;
case ClipMaskOperation::Intersect:
{
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
stencil_test_value += 1;
}
break;
}
RenderGeometry(geometry, translation, {});
// Restore state
// @performance Cache state so we don't toggle it unnecessarily.
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, stencil_test_value, GLuint(-1));
}
// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file
#pragma pack(1)
struct TGAHeader {
char idLength;
char colourMapType;
char dataType;
short int colourMapOrigin;
short int colourMapLength;
char colourMapDepth;
short int xOrigin;
short int yOrigin;
short int width;
short int height;
char bitsPerPixel;
char imageDescriptor;
};
// Restore packing
#pragma pack()
Rml::TextureHandle RenderInterface_GL2::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source)
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
{
return false;
}
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
if (buffer_size <= sizeof(TGAHeader))
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
file_interface->Close(file_handle);
return false;
}
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
TGAHeader header;
memcpy(&header, buffer.get(), sizeof(TGAHeader));
int color_mode = header.bitsPerPixel / 8;
const size_t image_size = header.width * header.height * 4; // We always make 32bit textures
if (header.dataType != 2)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
return false;
}
// Ensure we have at least 3 colors
if (color_mode < 3)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
return false;
}
const byte* image_src = buffer.get() + sizeof(TGAHeader);
Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
byte* image_dest = image_dest_buffer.get();
const bool top_to_bottom_order = ((header.imageDescriptor & 32) != 0);
// Targa is BGR, swap to RGB, flip Y axis as necessary, and convert to premultiplied alpha.
for (long y = 0; y < header.height; y++)
{
long read_index = y * header.width * color_mode;
long write_index = top_to_bottom_order ? (y * header.width * 4) : (header.height - y - 1) * header.width * 4;
for (long x = 0; x < header.width; x++)
{
image_dest[write_index] = image_src[read_index + 2];
image_dest[write_index + 1] = image_src[read_index + 1];
image_dest[write_index + 2] = image_src[read_index];
if (color_mode == 4)
{
const byte alpha = image_src[read_index + 3];
for (size_t j = 0; j < 3; j++)
image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
image_dest[write_index + 3] = alpha;
}
else
image_dest[write_index + 3] = 255;
write_index += 4;
read_index += color_mode;
}
}
texture_dimensions.x = header.width;
texture_dimensions.y = header.height;
return GenerateTexture({image_dest, image_size}, texture_dimensions);
}
Rml::TextureHandle RenderInterface_GL2::GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions)
{
RMLUI_ASSERT(source.data() && source.size() == size_t(source_dimensions.x * source_dimensions.y * 4));
GLuint texture_id = 0;
glGenTextures(1, &texture_id);
if (texture_id == 0)
{
Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture.");
return {};
}
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source.data());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
return (Rml::TextureHandle)texture_id;
}
void RenderInterface_GL2::ReleaseTexture(Rml::TextureHandle texture_handle)
{
glDeleteTextures(1, (GLuint*)&texture_handle);
}
void RenderInterface_GL2::SetTransform(const Rml::Matrix4f* transform)
{
transform_enabled = (transform != nullptr);
if (transform)
{
if (std::is_same_v<Rml::Matrix4f, Rml::ColumnMajorMatrix4f>)
glLoadMatrixf(transform->data());
else if (std::is_same_v<Rml::Matrix4f, Rml::RowMajorMatrix4f>)
glLoadMatrixf(transform->Transpose().data());
}
else
glLoadIdentity();
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
class RenderInterface_GL2 : public Rml::RenderInterface {
public:
RenderInterface_GL2();
// The viewport should be updated whenever the window size changes.
void SetViewport(int viewport_width, int viewport_height);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
void EndFrame();
// Optional, can be used to clear the framebuffer.
void Clear();
// -- Inherited from Rml::RenderInterface --
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
void EnableClipMask(bool enable) override;
void RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override;
void SetTransform(const Rml::Matrix4f* transform) override;
// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
private:
struct GeometryView {
Rml::Span<const Rml::Vertex> vertices;
Rml::Span<const int> indices;
};
int viewport_width = 0;
int viewport_height = 0;
bool transform_enabled = false;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
#include <RmlUi/Core/Types.h>
#include <bitset>
enum class ProgramId;
enum class UniformId;
class RenderLayerStack;
namespace Gfx {
struct ProgramData;
struct FramebufferData;
} // namespace Gfx
class RenderInterface_GL3 : public Rml::RenderInterface {
public:
RenderInterface_GL3();
~RenderInterface_GL3();
// Returns true if the renderer was successfully constructed.
explicit operator bool() const { return static_cast<bool>(program_data); }
// The viewport should be updated whenever the window size changes.
void SetViewport(int viewport_width, int viewport_height, int viewport_offset_x = 0, int viewport_offset_y = 0);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
// Draws the result to the backbuffer and restores OpenGL state.
void EndFrame();
// Optional, can be used to clear the active framebuffer.
void Clear();
// -- Inherited from Rml::RenderInterface --
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle handle) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
void EnableClipMask(bool enable) override;
void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override;
void SetTransform(const Rml::Matrix4f* transform) override;
Rml::LayerHandle PushLayer() override;
void CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blend_mode,
Rml::Span<const Rml::CompiledFilterHandle> filters) override;
void PopLayer() override;
Rml::TextureHandle SaveLayerAsTexture() override;
Rml::CompiledFilterHandle SaveLayerAsMaskImage() override;
Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override;
void ReleaseFilter(Rml::CompiledFilterHandle filter) override;
Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override;
void RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation,
Rml::TextureHandle texture) override;
void ReleaseShader(Rml::CompiledShaderHandle effect_handle) override;
// Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture.
static constexpr Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1);
// Can be passed to RenderGeometry() to leave the bound texture and used program unchanged.
static constexpr Rml::TextureHandle TexturePostprocess = Rml::TextureHandle(-2);
// -- Utility functions for clients --
const Rml::Matrix4f& GetTransform() const;
void ResetProgram();
private:
void UseProgram(ProgramId program_id);
int GetUniformLocation(UniformId uniform_id) const;
void SubmitTransformUniform(Rml::Vector2f translation);
void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle);
void RenderFilters(Rml::Span<const Rml::CompiledFilterHandle> filter_handles);
void SetScissor(Rml::Rectanglei region, bool vertically_flip = false);
void DrawFullscreenQuad();
void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f));
void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, Rml::Rectanglei window_flipped);
static constexpr size_t MaxNumPrograms = 32;
std::bitset<MaxNumPrograms> program_transform_dirty;
Rml::Matrix4f transform;
Rml::Matrix4f projection;
ProgramId active_program = {};
Rml::Rectanglei scissor_state;
int viewport_width = 0;
int viewport_height = 0;
int viewport_offset_x = 0;
int viewport_offset_y = 0;
Rml::CompiledGeometryHandle fullscreen_quad_geometry = {};
Rml::UniquePtr<const Gfx::ProgramData> program_data;
/*
Manages render targets, including the layer stack and postprocessing framebuffers.
Layers can be pushed and popped, creating new framebuffers as needed. Typically, geometry is rendered to the top
layer. The layer framebuffers may have MSAA enabled.
Postprocessing framebuffers are separate from the layers, and are commonly used to apply texture-wide effects
such as filters. They are used both as input and output during rendering, and do not use MSAA.
*/
class RenderLayerStack {
public:
RenderLayerStack();
~RenderLayerStack();
// Push a new layer. All references to previously retrieved layers are invalidated.
Rml::LayerHandle PushLayer();
// Pop the top layer. All references to previously retrieved layers are invalidated.
void PopLayer();
const Gfx::FramebufferData& GetLayer(Rml::LayerHandle layer) const;
const Gfx::FramebufferData& GetTopLayer() const;
Rml::LayerHandle GetTopLayerHandle() const;
const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); }
const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); }
const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); }
const Gfx::FramebufferData& GetBlendMask() { return EnsureFramebufferPostprocess(3); }
void SwapPostprocessPrimarySecondary();
void BeginFrame(int new_width, int new_height);
void EndFrame();
private:
void DestroyFramebuffers();
const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index);
int width = 0, height = 0;
// The number of active layers is manually tracked since we re-use the framebuffers stored in the fb_layers stack.
int layers_size = 0;
Rml::Vector<Gfx::FramebufferData> fb_layers;
Rml::Vector<Gfx::FramebufferData> fb_postprocess;
};
RenderLayerStack render_layers;
struct GLStateBackup {
bool enable_cull_face;
bool enable_blend;
bool enable_stencil_test;
bool enable_scissor_test;
bool enable_depth_test;
int viewport[4];
int scissor[4];
int active_texture;
int stencil_clear_value;
float color_clear_value[4];
unsigned char color_writemask[4];
int blend_equation_rgb;
int blend_equation_alpha;
int blend_src_rgb;
int blend_dst_rgb;
int blend_src_alpha;
int blend_dst_alpha;
struct Stencil {
int func;
int ref;
int value_mask;
int writemask;
int fail;
int pass_depth_fail;
int pass_depth_pass;
};
Stencil stencil_front;
Stencil stencil_back;
};
GLStateBackup glstate_backup = {};
};
/**
Helper functions for the OpenGL 3 renderer.
*/
namespace RmlGL3 {
// Loads OpenGL functions. Optionally, the out message describes the loaded GL version or an error message on failure.
bool Initialize(Rml::String* out_message = nullptr);
// Unloads OpenGL functions.
void Shutdown();
} // namespace RmlGL3

View File

@@ -0,0 +1,212 @@
#include "RmlUi_Renderer_SDL.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Types.h>
#if SDL_MAJOR_VERSION >= 3
#include <SDL3_image/SDL_image.h>
#else
#include <SDL_image.h>
#endif
#if SDL_MAJOR_VERSION == 2 && !(SDL_VIDEO_RENDER_OGL)
#error "Only the OpenGL SDL backend is supported."
#endif
static void SetRenderClipRect(SDL_Renderer* renderer, const SDL_Rect* rect)
{
#if SDL_MAJOR_VERSION >= 3
SDL_SetRenderClipRect(renderer, rect);
#else
SDL_RenderSetClipRect(renderer, rect);
#endif
}
static void SetRenderViewport(SDL_Renderer* renderer, const SDL_Rect* rect)
{
#if SDL_MAJOR_VERSION >= 3
SDL_SetRenderViewport(renderer, rect);
#else
SDL_RenderSetViewport(renderer, rect);
#endif
}
RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer)
{
// RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly.
// Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
blend_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE,
SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD);
}
void RenderInterface_SDL::BeginFrame()
{
SetRenderViewport(renderer, nullptr);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_SetRenderDrawBlendMode(renderer, blend_mode);
}
void RenderInterface_SDL::EndFrame() {}
Rml::CompiledGeometryHandle RenderInterface_SDL::CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices)
{
GeometryView* data = new GeometryView{vertices, indices};
return reinterpret_cast<Rml::CompiledGeometryHandle>(data);
}
void RenderInterface_SDL::ReleaseGeometry(Rml::CompiledGeometryHandle geometry)
{
delete reinterpret_cast<GeometryView*>(geometry);
}
void RenderInterface_SDL::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture)
{
const GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
const Rml::Vertex* vertices = geometry->vertices.data();
const size_t num_vertices = geometry->vertices.size();
const int* indices = geometry->indices.data();
const size_t num_indices = geometry->indices.size();
if (sdl_vertices_size < num_vertices)
{
sdl_vertices_size = num_vertices * 1.5;
sdl_vertices.reset(new SDL_Vertex[sdl_vertices_size]);
}
for (size_t i = 0; i < num_vertices; i++)
{
SDL_Vertex& sdl_vertex = sdl_vertices[i];
sdl_vertex.position = {vertices[i].position.x + translation.x, vertices[i].position.y + translation.y};
sdl_vertex.tex_coord = {vertices[i].tex_coord.x, vertices[i].tex_coord.y};
const auto& color = vertices[i].colour;
#if SDL_MAJOR_VERSION >= 3
sdl_vertex.color = {color.red / 255.f, color.green / 255.f, color.blue / 255.f, color.alpha / 255.f};
#else
sdl_vertex.color = {color.red, color.green, color.blue, color.alpha};
#endif
}
SDL_Texture* sdl_texture = (SDL_Texture*)texture;
SDL_RenderGeometry(renderer, sdl_texture, sdl_vertices.get(), (int)num_vertices, indices, (int)num_indices);
}
void RenderInterface_SDL::EnableScissorRegion(bool enable)
{
if (enable)
SetRenderClipRect(renderer, &rect_scissor);
else
SetRenderClipRect(renderer, nullptr);
scissor_region_enabled = enable;
}
void RenderInterface_SDL::SetScissorRegion(Rml::Rectanglei region)
{
rect_scissor.x = region.Left();
rect_scissor.y = region.Top();
rect_scissor.w = region.Width();
rect_scissor.h = region.Height();
if (scissor_region_enabled)
SetRenderClipRect(renderer, &rect_scissor);
}
Rml::TextureHandle RenderInterface_SDL::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source)
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
return {};
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
const size_t i_ext = source.rfind('.');
Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
#if SDL_MAJOR_VERSION >= 3
auto CreateSurface = [&]() { return IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format; };
auto ConvertSurface = [](SDL_Surface* surface, SDL_PixelFormat format) { return SDL_ConvertSurface(surface, format); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_DestroySurface(surface); };
#else
auto CreateSurface = [&]() { return IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); };
auto GetSurfaceFormat = [](SDL_Surface* surface) { return surface->format->format; };
auto ConvertSurface = [](SDL_Surface* surface, Uint32 format) { return SDL_ConvertSurfaceFormat(surface, format, 0); };
auto DestroySurface = [](SDL_Surface* surface) { SDL_FreeSurface(surface); };
#endif
SDL_Surface* surface = CreateSurface();
if (!surface)
return {};
texture_dimensions = {surface->w, surface->h};
if (GetSurfaceFormat(surface) != SDL_PIXELFORMAT_RGBA32 && GetSurfaceFormat(surface) != SDL_PIXELFORMAT_BGRA32)
{
SDL_Surface* converted_surface = ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
DestroySurface(surface);
if (!converted_surface)
return {};
surface = converted_surface;
}
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
const size_t pixels_byte_size = surface->w * surface->h * 4;
byte* pixels = static_cast<byte*>(surface->pixels);
for (size_t i = 0; i < pixels_byte_size; i += 4)
{
const byte alpha = pixels[i + 3];
for (size_t j = 0; j < 3; ++j)
pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
texture_dimensions = Rml::Vector2i(surface->w, surface->h);
DestroySurface(surface);
if (texture)
SDL_SetTextureBlendMode(texture, blend_mode);
return (Rml::TextureHandle)texture;
}
Rml::TextureHandle RenderInterface_SDL::GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions)
{
RMLUI_ASSERT(source.data() && source.size() == size_t(source_dimensions.x * source_dimensions.y * 4));
#if SDL_MAJOR_VERSION >= 3
auto CreateSurface = [&]() {
return SDL_CreateSurfaceFrom(source_dimensions.x, source_dimensions.y, SDL_PIXELFORMAT_RGBA32, (void*)source.data(), source_dimensions.x * 4);
};
auto DestroySurface = [](SDL_Surface* surface) { SDL_DestroySurface(surface); };
#else
auto CreateSurface = [&]() {
return SDL_CreateRGBSurfaceWithFormatFrom((void*)source.data(), source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4,
SDL_PIXELFORMAT_RGBA32);
};
auto DestroySurface = [](SDL_Surface* surface) { SDL_FreeSurface(surface); };
#endif
SDL_Surface* surface = CreateSurface();
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_SetTextureBlendMode(texture, blend_mode);
DestroySurface(surface);
return (Rml::TextureHandle)texture;
}
void RenderInterface_SDL::ReleaseTexture(Rml::TextureHandle texture_handle)
{
SDL_DestroyTexture((SDL_Texture*)texture_handle);
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
#if RMLUI_SDL_VERSION_MAJOR == 3
#include <SDL3/SDL.h>
#elif RMLUI_SDL_VERSION_MAJOR == 2
#include <SDL.h>
#else
#error "Unspecified RMLUI_SDL_VERSION_MAJOR. Please set this definition to the major version of the SDL library being linked to."
#endif
class RenderInterface_SDL : public Rml::RenderInterface {
public:
RenderInterface_SDL(SDL_Renderer* renderer);
// Sets up OpenGL states for taking rendering commands from RmlUi.
void BeginFrame();
void EndFrame();
// -- Inherited from Rml::RenderInterface --
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
private:
struct GeometryView {
Rml::Span<const Rml::Vertex> vertices;
Rml::Span<const int> indices;
};
SDL_Renderer* renderer;
SDL_BlendMode blend_mode = {};
SDL_Rect rect_scissor = {};
bool scissor_region_enabled = false;
Rml::UniquePtr<SDL_Vertex[]> sdl_vertices;
size_t sdl_vertices_size = 0;
};

View File

@@ -0,0 +1,630 @@
#include "RmlUi_Renderer_SDL_GPU.h"
#include "RmlUi_SDL_GPU/ShadersCompiledSPV.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/FileInterface.h>
#include <RmlUi/Core/Log.h>
#include <RmlUi/Core/Types.h>
#include <SDL3_image/SDL_image.h>
using namespace Rml;
enum ShaderType {
ShaderTypeColor,
ShaderTypeTexture,
ShaderTypeVert,
ShaderTypeCount,
};
enum ShaderFormat {
ShaderFormatSPIRV,
ShaderFormatMSL,
ShaderFormatDXIL,
ShaderFormatCount,
};
struct Shader {
Span<const byte> data[ShaderFormatCount];
int uniforms;
int samplers;
SDL_GPUShaderStage stage;
};
#undef X
#define X(name) \
Span<const byte> \
{ \
name, sizeof(name) \
}
static const Shader shaders[ShaderTypeCount] = {
{{X(shader_frag_color_spirv), X(shader_frag_color_msl), X(shader_frag_color_dxil)}, 0, 0, SDL_GPU_SHADERSTAGE_FRAGMENT},
{{X(shader_frag_texture_spirv), X(shader_frag_texture_msl), X(shader_frag_texture_dxil)}, 0, 1, SDL_GPU_SHADERSTAGE_FRAGMENT},
{{X(shader_vert_spirv), X(shader_vert_msl), X(shader_vert_dxil)}, 2, 0, SDL_GPU_SHADERSTAGE_VERTEX}};
#undef X
static SDL_GPUShader* CreateShaderFromMemory(SDL_GPUDevice* device, ShaderType type)
{
SDL_GPUShaderFormat sdl_shader_format = SDL_GetGPUShaderFormats(device);
ShaderFormat format = ShaderFormatCount;
const char* entrypoint = nullptr;
if (sdl_shader_format & SDL_GPU_SHADERFORMAT_SPIRV)
{
sdl_shader_format = SDL_GPU_SHADERFORMAT_SPIRV;
format = ShaderFormatSPIRV;
entrypoint = "main";
}
else if (sdl_shader_format & SDL_GPU_SHADERFORMAT_DXIL)
{
sdl_shader_format = SDL_GPU_SHADERFORMAT_DXIL;
format = ShaderFormatDXIL;
entrypoint = "main";
}
else if (sdl_shader_format & SDL_GPU_SHADERFORMAT_MSL)
{
sdl_shader_format = SDL_GPU_SHADERFORMAT_MSL;
format = ShaderFormatMSL;
entrypoint = "main0";
}
else
{
RMLUI_ERRORMSG("Invalid shader format");
return nullptr;
}
const Shader& shader = shaders[type];
SDL_GPUShaderCreateInfo info{};
info.code = static_cast<const Uint8*>(shader.data[format].data());
info.code_size = shader.data[format].size();
info.entrypoint = entrypoint;
info.format = sdl_shader_format;
info.stage = shader.stage;
info.num_samplers = shader.samplers;
info.num_uniform_buffers = shader.uniforms;
SDL_GPUShader* sdl_shader = SDL_CreateGPUShader(device, &info);
if (!sdl_shader)
{
Log::Message(Log::LT_ERROR, "Failed to create shader: %s", SDL_GetError());
RMLUI_ERROR;
}
return sdl_shader;
}
void RenderInterface_SDL_GPU::CreatePipelines()
{
SDL_GPUShader* color_shader = CreateShaderFromMemory(device, ShaderTypeColor);
SDL_GPUShader* texture_shader = CreateShaderFromMemory(device, ShaderTypeTexture);
SDL_GPUShader* vert_shader = CreateShaderFromMemory(device, ShaderTypeVert);
SDL_GPUColorTargetDescription target{};
target.format = SDL_GetGPUSwapchainTextureFormat(device, window);
target.blend_state.enable_blend = true;
target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
SDL_GPUVertexAttribute attrib[3]{};
attrib[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
attrib[0].location = 0;
attrib[0].offset = offsetof(Vertex, position);
attrib[1].format = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM;
attrib[1].location = 1;
attrib[1].offset = offsetof(Vertex, colour);
attrib[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
attrib[2].location = 2;
attrib[2].offset = offsetof(Vertex, tex_coord);
SDL_GPUVertexBufferDescription buffer{};
buffer.pitch = sizeof(Vertex);
SDL_GPUGraphicsPipelineCreateInfo info{};
info.vertex_shader = vert_shader;
info.target_info.num_color_targets = 1;
info.target_info.color_target_descriptions = &target;
info.vertex_input_state.num_vertex_attributes = 3;
info.vertex_input_state.num_vertex_buffers = 1;
info.vertex_input_state.vertex_attributes = attrib;
info.vertex_input_state.vertex_buffer_descriptions = &buffer;
info.fragment_shader = color_shader;
color_pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
if (!color_pipeline)
{
Log::Message(Log::LT_ERROR, "Failed to create color pipeline: %s", SDL_GetError());
RMLUI_ERROR;
}
info.fragment_shader = texture_shader;
texture_pipeline = SDL_CreateGPUGraphicsPipeline(device, &info);
if (!texture_pipeline)
{
Log::Message(Log::LT_ERROR, "Failed to create texture pipeline: %s", SDL_GetError());
RMLUI_ERROR;
}
SDL_ReleaseGPUShader(device, color_shader);
SDL_ReleaseGPUShader(device, texture_shader);
SDL_ReleaseGPUShader(device, vert_shader);
}
RenderInterface_SDL_GPU::RenderInterface_SDL_GPU(SDL_GPUDevice* device, SDL_Window* window) :
device(device), window(window), render_pass(nullptr), copy_pass(nullptr)
{
CreatePipelines();
SDL_GPUSamplerCreateInfo info{};
info.min_filter = SDL_GPU_FILTER_LINEAR;
info.mag_filter = SDL_GPU_FILTER_LINEAR;
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
linear_sampler = SDL_CreateGPUSampler(device, &info);
if (!linear_sampler)
{
Log::Message(Log::LT_ERROR, "Failed to acquire command buffer: %s", SDL_GetError());
return;
}
}
void RenderInterface_SDL_GPU::Shutdown()
{
for (Rml::UniquePtr<Command>& command : commands)
{
command->Update(*this);
}
for (Rml::UniquePtr<Buffer>& buffer : buffers)
{
SDL_ReleaseGPUTransferBuffer(device, buffer->transfer_buffer);
SDL_ReleaseGPUBuffer(device, buffer->buffer);
}
SDL_ReleaseGPUSampler(device, linear_sampler);
SDL_ReleaseGPUGraphicsPipeline(device, color_pipeline);
SDL_ReleaseGPUGraphicsPipeline(device, texture_pipeline);
}
void RenderInterface_SDL_GPU::BeginFrame(SDL_GPUCommandBuffer* command_buffer, SDL_GPUTexture* swapchain_texture, uint32_t width, uint32_t height)
{
this->command_buffer = command_buffer;
this->swapchain_texture = swapchain_texture;
swapchain_width = width;
swapchain_height = height;
proj = Matrix4f::ProjectOrtho(0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f, -10'000.f, 10'000.f);
SetTransform(nullptr);
EnableScissorRegion(false);
}
void RenderInterface_SDL_GPU::EndFrame()
{
for (Rml::UniquePtr<Command>& command : commands)
{
command->Update(*this);
}
commands.clear();
if (copy_pass)
{
SDL_EndGPUCopyPass(copy_pass);
copy_pass = nullptr;
}
if (render_pass)
{
SDL_EndGPURenderPass(render_pass);
render_pass = nullptr;
}
}
bool RenderInterface_SDL_GPU::BeginCopyPass()
{
if (copy_pass)
{
return true;
}
if (render_pass)
{
SDL_EndGPURenderPass(render_pass);
render_pass = nullptr;
}
copy_pass = SDL_BeginGPUCopyPass(command_buffer);
if (!copy_pass)
{
Log::Message(Log::LT_ERROR, "Failed to begin copy pass: %s", SDL_GetError());
return false;
}
return true;
}
bool RenderInterface_SDL_GPU::BeginRenderPass()
{
if (render_pass)
{
return true;
}
if (copy_pass)
{
SDL_EndGPUCopyPass(copy_pass);
copy_pass = nullptr;
}
SDL_GPUColorTargetInfo color_info{};
color_info.texture = swapchain_texture;
color_info.load_op = SDL_GPU_LOADOP_LOAD;
color_info.store_op = SDL_GPU_STOREOP_STORE;
render_pass = SDL_BeginGPURenderPass(command_buffer, &color_info, 1, nullptr);
if (!render_pass)
{
Log::Message(Log::LT_ERROR, "Failed to begin render pass: %s", SDL_GetError());
return false;
}
return true;
}
CompiledGeometryHandle RenderInterface_SDL_GPU::CompileGeometry(Span<const Vertex> vertices, Span<const int> indices)
{
if (!BeginCopyPass())
{
return 0;
}
uint32_t vertex_size = static_cast<int>(vertices.size() * sizeof(Vertex));
uint32_t index_size = static_cast<int>(indices.size() * sizeof(int));
GeometryView* geometry = new GeometryView();
geometry->vertex_buffer = RequestBuffer(vertex_size, SDL_GPU_BUFFERUSAGE_VERTEX);
geometry->index_buffer = RequestBuffer(index_size, SDL_GPU_BUFFERUSAGE_INDEX);
if (!geometry->vertex_buffer || !geometry->index_buffer)
{
Log::Message(Log::LT_ERROR, "Failed to request buffer(s)");
delete geometry;
return 0;
}
void* vertex_data = SDL_MapGPUTransferBuffer(device, geometry->vertex_buffer->transfer_buffer, true);
void* index_data = SDL_MapGPUTransferBuffer(device, geometry->index_buffer->transfer_buffer, true);
if (!vertex_data || !index_data)
{
Log::Message(Log::LT_ERROR, "Failed to map transfer buffer(s): %s", SDL_GetError());
delete geometry;
return 0;
}
std::memcpy(vertex_data, vertices.data(), vertex_size);
std::memcpy(index_data, indices.data(), index_size);
SDL_UnmapGPUTransferBuffer(device, geometry->vertex_buffer->transfer_buffer);
SDL_UnmapGPUTransferBuffer(device, geometry->index_buffer->transfer_buffer);
SDL_GPUTransferBufferLocation location{};
SDL_GPUBufferRegion region{};
location.transfer_buffer = geometry->vertex_buffer->transfer_buffer;
region.buffer = geometry->vertex_buffer->buffer;
region.size = vertex_size;
SDL_UploadToGPUBuffer(copy_pass, &location, &region, false);
location.transfer_buffer = geometry->index_buffer->transfer_buffer;
region.buffer = geometry->index_buffer->buffer;
region.size = index_size;
SDL_UploadToGPUBuffer(copy_pass, &location, &region, false);
geometry->num_indices = static_cast<int>(indices.size());
geometry->vertex_buffer->in_use = true;
geometry->index_buffer->in_use = true;
return reinterpret_cast<Rml::CompiledGeometryHandle>(geometry);
}
void RenderInterface_SDL_GPU::ReleaseGeometryCommand::Update(RenderInterface_SDL_GPU& interface)
{
(void)interface;
GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
geometry->vertex_buffer->in_use = false;
geometry->index_buffer->in_use = false;
delete geometry;
}
void RenderInterface_SDL_GPU::ReleaseGeometry(CompiledGeometryHandle handle)
{
commands.push_back(Rml::MakeUnique<ReleaseGeometryCommand>(handle));
}
void RenderInterface_SDL_GPU::RenderGeometryCommand::Update(RenderInterface_SDL_GPU& interface)
{
if (!interface.BeginRenderPass())
{
return;
}
GeometryView* geometry = reinterpret_cast<GeometryView*>(handle);
if (texture != 0)
{
SDL_BindGPUGraphicsPipeline(interface.render_pass, interface.texture_pipeline);
SDL_GPUTextureSamplerBinding texture_binding{};
texture_binding.texture = reinterpret_cast<SDL_GPUTexture*>(texture);
texture_binding.sampler = interface.linear_sampler;
SDL_BindGPUFragmentSamplers(interface.render_pass, 0, &texture_binding, 1);
}
else
{
SDL_BindGPUGraphicsPipeline(interface.render_pass, interface.color_pipeline);
}
SDL_GPUBufferBinding vertex_buffer_binding{};
SDL_GPUBufferBinding index_buffer_binding{};
vertex_buffer_binding.buffer = geometry->vertex_buffer->buffer;
index_buffer_binding.buffer = geometry->index_buffer->buffer;
SDL_BindGPUVertexBuffers(interface.render_pass, 0, &vertex_buffer_binding, 1);
SDL_BindGPUIndexBuffer(interface.render_pass, &index_buffer_binding, SDL_GPU_INDEXELEMENTSIZE_32BIT);
SDL_SetGPUScissor(interface.render_pass, &interface.scissor);
SDL_PushGPUVertexUniformData(interface.command_buffer, 0, &interface.transform, sizeof(interface.transform));
SDL_PushGPUVertexUniformData(interface.command_buffer, 1, &translation, sizeof(translation));
SDL_DrawGPUIndexedPrimitives(interface.render_pass, geometry->num_indices, 1, 0, 0, 0);
}
void RenderInterface_SDL_GPU::RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture)
{
commands.push_back(Rml::MakeUnique<RenderGeometryCommand>(handle, translation, texture));
}
void RenderInterface_SDL_GPU::EnableScissorRegionCommand::Update(RenderInterface_SDL_GPU& interface)
{
if (!enable)
{
interface.scissor.x = 0;
interface.scissor.y = 0;
interface.scissor.w = interface.swapchain_width;
interface.scissor.h = interface.swapchain_height;
}
}
void RenderInterface_SDL_GPU::EnableScissorRegion(bool enable)
{
commands.push_back(Rml::MakeUnique<EnableScissorRegionCommand>(enable));
}
void RenderInterface_SDL_GPU::SetScissorRegionCommand::Update(RenderInterface_SDL_GPU& interface)
{
interface.scissor.x = region.Left();
interface.scissor.w = region.Width();
interface.scissor.y = region.Top();
interface.scissor.h = region.Height();
}
void RenderInterface_SDL_GPU::SetScissorRegion(Rectanglei region)
{
commands.push_back(Rml::MakeUnique<SetScissorRegionCommand>(region));
}
TextureHandle RenderInterface_SDL_GPU::LoadTexture(Vector2i& texture_dimensions, const String& source)
{
Rml::FileInterface* file_interface = Rml::GetFileInterface();
Rml::FileHandle file_handle = file_interface->Open(source);
if (!file_handle)
{
return 0;
}
file_interface->Seek(file_handle, 0, SEEK_END);
size_t buffer_size = file_interface->Tell(file_handle);
file_interface->Seek(file_handle, 0, SEEK_SET);
using Rml::byte;
Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
file_interface->Read(buffer.get(), buffer_size, file_handle);
file_interface->Close(file_handle);
const size_t i_ext = source.rfind('.');
Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
SDL_Surface* surface = IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
if (!surface)
{
return 0;
}
texture_dimensions = {surface->w, surface->h};
if (surface->format != SDL_PIXELFORMAT_RGBA32)
{
SDL_Surface* converted_surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
SDL_DestroySurface(surface);
if (!converted_surface)
{
return 0;
}
surface = converted_surface;
}
// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
const size_t pixels_byte_size = surface->w * surface->h * 4;
byte* pixels = static_cast<byte*>(surface->pixels);
for (size_t i = 0; i < pixels_byte_size; i += 4)
{
const byte alpha = pixels[i + 3];
for (size_t j = 0; j < 3; ++j)
{
pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
}
}
Span<const byte> data{static_cast<const byte*>(surface->pixels), static_cast<size_t>(surface->pitch * surface->h)};
texture_dimensions = {surface->w, surface->h};
TextureHandle handle = GenerateTexture(data, texture_dimensions);
SDL_DestroySurface(surface);
return handle;
}
TextureHandle RenderInterface_SDL_GPU::GenerateTexture(Span<const byte> source, Vector2i source_dimensions)
{
SDL_GPUTransferBuffer* transfer_buffer;
{
SDL_GPUTransferBufferCreateInfo info{};
info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
info.size = source_dimensions.x * source_dimensions.y * 4;
transfer_buffer = SDL_CreateGPUTransferBuffer(device, &info);
if (!transfer_buffer)
{
Log::Message(Log::LT_ERROR, "Failed to create transfer buffer: %s", SDL_GetError());
return 0;
}
}
void* dst = SDL_MapGPUTransferBuffer(device, transfer_buffer, false);
if (!dst)
{
SDL_Log("Failed to map transfer buffer: %s", SDL_GetError());
return 0;
}
std::memcpy(dst, source.data(), source_dimensions.x * source_dimensions.y * 4);
SDL_UnmapGPUTransferBuffer(device, transfer_buffer);
SDL_GPUTexture* texture;
{
SDL_GPUTextureCreateInfo info{};
info.type = SDL_GPU_TEXTURETYPE_2D;
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
info.width = source_dimensions.x;
info.height = source_dimensions.y;
info.layer_count_or_depth = 1;
info.num_levels = 1;
texture = SDL_CreateGPUTexture(device, &info);
if (!texture)
{
Log::Message(Log::LT_ERROR, "Failed to create texture: %s", SDL_GetError());
return 0;
}
}
SDL_GPUTextureTransferInfo transfer_info{};
SDL_GPUTextureRegion region{};
transfer_info.transfer_buffer = transfer_buffer;
region.texture = texture;
region.w = source_dimensions.x;
region.h = source_dimensions.y;
region.d = 1;
// We can get calls out outside of Begin/End Frame so always acquire a command buffer here
SDL_GPUCommandBuffer* command_buffer = SDL_AcquireGPUCommandBuffer(device);
if (!command_buffer)
{
Log::Message(Log::LT_ERROR, "Failed to acquire command buffer: %s", SDL_GetError());
SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
SDL_ReleaseGPUTexture(device, texture);
return 0;
}
SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(command_buffer);
if (!copy_pass)
{
Log::Message(Log::LT_ERROR, "Failed to begin copy pass: %s", SDL_GetError());
SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
SDL_ReleaseGPUTexture(device, texture);
SDL_CancelGPUCommandBuffer(command_buffer);
return 0;
}
SDL_UploadToGPUTexture(copy_pass, &transfer_info, &region, false);
SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
SDL_EndGPUCopyPass(copy_pass);
SDL_SubmitGPUCommandBuffer(command_buffer);
return reinterpret_cast<TextureHandle>(texture);
}
void RenderInterface_SDL_GPU::ReleaseTextureCommand::Update(RenderInterface_SDL_GPU& interface)
{
SDL_GPUTexture* texture = reinterpret_cast<SDL_GPUTexture*>(handle);
SDL_ReleaseGPUTexture(interface.device, texture);
}
void RenderInterface_SDL_GPU::ReleaseTexture(TextureHandle texture_handle)
{
commands.push_back(Rml::MakeUnique<ReleaseTextureCommand>(texture_handle));
}
RenderInterface_SDL_GPU::SetTransformCommand::SetTransformCommand(const Rml::Matrix4f* new_transform)
{
if (new_transform)
{
has_transform = true;
transform = *new_transform;
}
else
{
has_transform = false;
}
}
void RenderInterface_SDL_GPU::SetTransformCommand::Update(RenderInterface_SDL_GPU& interface)
{
if (has_transform)
{
interface.transform = interface.proj * transform;
}
else
{
interface.transform = interface.proj;
}
}
void RenderInterface_SDL_GPU::SetTransform(const Rml::Matrix4f* new_transform)
{
commands.push_back(Rml::MakeUnique<SetTransformCommand>(new_transform));
}
RenderInterface_SDL_GPU::Buffer* RenderInterface_SDL_GPU::RequestBuffer(int capacity, SDL_GPUBufferUsageFlags usage)
{
auto it = std::lower_bound(buffers.begin(), buffers.end(), capacity,
[](const Rml::UniquePtr<Buffer>& lhs, int capacity) { return lhs->capacity < capacity; });
for (auto tmp_it = it; tmp_it != buffers.end(); ++tmp_it)
{
const auto& buffer = *tmp_it;
if (!buffer->in_use && buffer->usage == usage)
{
// set in_use as false and expect the caller to set it to true themselves
buffer->in_use = false;
return buffer.get();
}
}
Rml::UniquePtr<Buffer> buffer = Rml::MakeUnique<Buffer>();
{
SDL_GPUTransferBufferCreateInfo info{};
info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
info.size = capacity;
buffer->transfer_buffer = SDL_CreateGPUTransferBuffer(device, &info);
}
{
SDL_GPUBufferCreateInfo info{};
info.usage = usage;
info.size = capacity;
buffer->buffer = SDL_CreateGPUBuffer(device, &info);
}
if (!buffer->transfer_buffer || !buffer->buffer)
{
Log::Message(Log::LT_ERROR, "Failed to create buffer(s): %s", SDL_GetError());
SDL_ReleaseGPUTransferBuffer(device, buffer->transfer_buffer);
SDL_ReleaseGPUBuffer(device, buffer->buffer);
return {};
}
buffer->usage = usage;
buffer->in_use = false;
buffer->capacity = capacity;
auto inserted_it = buffers.insert(it, std::move(buffer));
return inserted_it->get();
}

View File

@@ -0,0 +1,122 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
#include <RmlUi/Core/Types.h>
#include <SDL3/SDL.h>
class RenderInterface_SDL_GPU : public Rml::RenderInterface {
public:
RenderInterface_SDL_GPU(SDL_GPUDevice* device, SDL_Window* window);
void Shutdown();
void BeginFrame(SDL_GPUCommandBuffer* command_buffer, SDL_GPUTexture* swapchain_texture, uint32_t width, uint32_t height);
void EndFrame();
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i source_dimensions) override;
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
void EnableScissorRegion(bool enable) override;
void SetScissorRegion(Rml::Rectanglei region) override;
void SetTransform(const Rml::Matrix4f* new_transform) override;
private:
SDL_GPUDevice* device;
SDL_Window* window;
SDL_GPUGraphicsPipeline* texture_pipeline;
SDL_GPUGraphicsPipeline* color_pipeline;
SDL_GPUSampler* linear_sampler;
SDL_GPUCommandBuffer* command_buffer;
SDL_GPUTexture* swapchain_texture;
uint32_t swapchain_width;
uint32_t swapchain_height;
SDL_GPURenderPass* render_pass;
SDL_GPUCopyPass* copy_pass;
SDL_Rect scissor;
Rml::Matrix4f transform;
Rml::Matrix4f proj;
struct Command {
virtual ~Command() = default;
virtual void Update(RenderInterface_SDL_GPU& interface) = 0;
};
struct EnableScissorRegionCommand : Command {
EnableScissorRegionCommand(bool enable) : enable(enable) {}
void Update(RenderInterface_SDL_GPU& interface) override;
bool enable;
};
struct SetScissorRegionCommand : Command {
SetScissorRegionCommand(Rml::Rectanglei region) : region(region) {}
void Update(RenderInterface_SDL_GPU& interface) override;
Rml::Rectanglei region;
};
struct RenderGeometryCommand : Command {
RenderGeometryCommand(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) :
handle(handle), translation(translation), texture(texture)
{}
void Update(RenderInterface_SDL_GPU& interface) override;
Rml::CompiledGeometryHandle handle;
Rml::Vector2f translation;
Rml::TextureHandle texture;
};
struct ReleaseGeometryCommand : Command {
ReleaseGeometryCommand(Rml::CompiledGeometryHandle handle) : handle(handle) {}
void Update(RenderInterface_SDL_GPU& interface) override;
Rml::CompiledGeometryHandle handle;
};
struct ReleaseTextureCommand : Command {
ReleaseTextureCommand(Rml::TextureHandle handle) : handle(handle) {}
void Update(RenderInterface_SDL_GPU& interface) override;
Rml::TextureHandle handle;
};
struct SetTransformCommand : Command {
SetTransformCommand(const Rml::Matrix4f* new_transform);
void Update(RenderInterface_SDL_GPU& interface) override;
Rml::Matrix4f transform;
bool has_transform;
};
friend struct EnableScissorRegionCommand;
friend struct SetScissorRegionCommand;
friend struct RenderGeometryCommand;
friend struct ReleaseGeometryCommand;
friend struct ReleaseTextureCommand;
friend struct SetTransformCommand;
struct Buffer {
SDL_GPUTransferBuffer* transfer_buffer;
SDL_GPUBuffer* buffer;
SDL_GPUBufferUsageFlags usage;
int capacity;
bool in_use;
};
struct GeometryView {
Buffer* vertex_buffer;
Buffer* index_buffer;
int num_indices;
};
// List of ordered render commands
Rml::Vector<Rml::UniquePtr<Command>> commands;
// Sorted vertex/index buffers by capacities
Rml::Vector<Rml::UniquePtr<Buffer>> buffers;
void CreatePipelines();
bool BeginCopyPass();
bool BeginRenderPass();
Buffer* RequestBuffer(int capacity, SDL_GPUBufferUsageFlags usage);
};

3017
vendor/rmlui_backend/RmlUi_Renderer_VK.cpp vendored Normal file

File diff suppressed because it is too large Load Diff

563
vendor/rmlui_backend/RmlUi_Renderer_VK.h vendored Normal file
View File

@@ -0,0 +1,563 @@
#pragma once
#include <RmlUi/Core/RenderInterface.h>
#ifdef RMLUI_PLATFORM_WIN32
#include "RmlUi_Include_Windows.h"
#define VK_USE_PLATFORM_WIN32_KHR
#endif
#include "RmlUi_Include_Vulkan.h"
#ifdef RMLUI_DEBUG
#define RMLUI_VK_ASSERTMSG(statement, msg) RMLUI_ASSERTMSG(statement, msg)
// Uncomment the following line to enable additional Vulkan debugging.
// #define RMLUI_VK_DEBUG
#else
#define RMLUI_VK_ASSERTMSG(statement, msg) static_cast<void>(statement)
#endif
// Your specified API version. Ideally, this will be dynamic in the future.
#define RMLUI_VK_API_VERSION VK_API_VERSION_1_0
class RenderInterface_VK : public Rml::RenderInterface {
public:
static constexpr uint32_t kSwapchainBackBufferCount = 3;
static constexpr VkDeviceSize kVideoMemoryForAllocation = 4 * 1024 * 1024; // [bytes]
RenderInterface_VK();
~RenderInterface_VK();
using CreateSurfaceCallback = bool (*)(VkInstance instance, VkSurfaceKHR* out_surface);
bool Initialize(Rml::Vector<const char*> required_extensions, CreateSurfaceCallback create_surface_callback);
void Shutdown();
void BeginFrame();
void EndFrame();
void SetViewport(int width, int height);
bool IsSwapchainValid();
void RecreateSwapchain();
// -- Inherited from Rml::RenderInterface --
/// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future.
Rml::CompiledGeometryHandle CompileGeometry(Rml::Span<const Rml::Vertex> vertices, Rml::Span<const int> indices) override;
/// Called by RmlUi when it wants to render application-compiled geometry.
void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override;
/// Called by RmlUi when it wants to release application-compiled geometry.
void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override;
/// Called by RmlUi when a texture is required by the library.
Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override;
/// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels.
Rml::TextureHandle GenerateTexture(Rml::Span<const Rml::byte> source_data, Rml::Vector2i source_dimensions) override;
/// Called by RmlUi when a loaded texture is no longer required.
void ReleaseTexture(Rml::TextureHandle texture_handle) override;
/// Called by RmlUi when it wants to enable or disable scissoring to clip content.
void EnableScissorRegion(bool enable) override;
/// Called by RmlUi when it wants to change the scissor region.
void SetScissorRegion(Rml::Rectanglei region) override;
/// Called by RmlUi when it wants to set the current transform matrix to a new matrix.
void SetTransform(const Rml::Matrix4f* transform) override;
private:
enum class shader_type_t : int { Vertex, Fragment, Unknown = -1 };
enum class shader_id_t : int { Vertex, Fragment_WithoutTextures, Fragment_WithTextures };
struct shader_vertex_user_data_t {
// Member objects are order-sensitive to match shader.
Rml::Matrix4f m_transform;
Rml::Vector2f m_translate;
};
struct texture_data_t {
VkImage m_p_vk_image;
VkImageView m_p_vk_image_view;
VkSampler m_p_vk_sampler;
VkDescriptorSet m_p_vk_descriptor_set;
VmaAllocation m_p_vma_allocation;
};
struct geometry_handle_t {
int m_num_indices;
VkDescriptorBufferInfo m_p_vertex;
VkDescriptorBufferInfo m_p_index;
VkDescriptorBufferInfo m_p_shader;
// @ this is for freeing our logical blocks for VMA
// see https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/virtual_allocator.html
VmaVirtualAllocation m_p_vertex_allocation;
VmaVirtualAllocation m_p_index_allocation;
VmaVirtualAllocation m_p_shader_allocation;
};
struct buffer_data_t {
VkBuffer m_p_vk_buffer;
VmaAllocation m_p_vma_allocation;
};
class UploadResourceManager {
public:
UploadResourceManager() : m_p_device{}, m_p_fence{}, m_p_command_buffer{}, m_p_command_pool{}, m_p_graphics_queue{} {}
~UploadResourceManager() {}
void Initialize(VkDevice p_device, VkQueue p_queue, uint32_t queue_family_index)
{
RMLUI_VK_ASSERTMSG(p_queue, "you have to pass a valid VkQueue");
RMLUI_VK_ASSERTMSG(p_device, "you have to pass a valid VkDevice for creation resources");
m_p_device = p_device;
m_p_graphics_queue = p_queue;
Create_All(queue_family_index);
}
void Shutdown()
{
vkDestroyFence(m_p_device, m_p_fence, nullptr);
vkDestroyCommandPool(m_p_device, m_p_command_pool, nullptr);
}
template <typename Func>
void UploadToGPU(Func&& p_user_commands) noexcept
{
RMLUI_VK_ASSERTMSG(m_p_command_buffer, "you didn't initialize VkCommandBuffer");
VkCommandBufferBeginInfo info_command = {};
info_command.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
info_command.pNext = nullptr;
info_command.pInheritanceInfo = nullptr;
info_command.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VkResult status = vkBeginCommandBuffer(m_p_command_buffer, &info_command);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkBeginCommandBuffer");
p_user_commands(m_p_command_buffer);
status = vkEndCommandBuffer(m_p_command_buffer);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "faield to vkEndCommandBuffer");
Submit();
Wait();
}
private:
void Create_Fence() noexcept
{
VkFenceCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
info.pNext = nullptr;
info.flags = 0;
VkResult status = vkCreateFence(m_p_device, &info, nullptr, &m_p_fence);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateFence");
}
void Create_CommandBuffer() noexcept
{
RMLUI_VK_ASSERTMSG(m_p_command_pool, "you have to initialize VkCommandPool before calling this method!");
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.pNext = nullptr;
info.commandPool = m_p_command_pool;
info.commandBufferCount = 1;
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
VkResult status = vkAllocateCommandBuffers(m_p_device, &info, &m_p_command_buffer);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkAllocateCommandBuffers");
}
void Create_CommandPool(uint32_t queue_family_index) noexcept
{
VkCommandPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
info.pNext = nullptr;
info.queueFamilyIndex = queue_family_index;
info.flags = 0;
VkResult status = vkCreateCommandPool(m_p_device, &info, nullptr, &m_p_command_pool);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateCommandPool");
}
void Create_All(uint32_t queue_family_index) noexcept
{
Create_Fence();
Create_CommandPool(queue_family_index);
Create_CommandBuffer();
}
void Wait() noexcept
{
RMLUI_VK_ASSERTMSG(m_p_fence, "you must initialize your VkFence");
vkWaitForFences(m_p_device, 1, &m_p_fence, VK_TRUE, UINT64_MAX);
vkResetFences(m_p_device, 1, &m_p_fence);
vkResetCommandPool(m_p_device, m_p_command_pool, 0);
}
void Submit() noexcept
{
VkSubmitInfo info = {};
info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
info.pNext = nullptr;
info.waitSemaphoreCount = 0;
info.signalSemaphoreCount = 0;
info.pSignalSemaphores = nullptr;
info.pWaitSemaphores = nullptr;
info.pWaitDstStageMask = nullptr;
info.pCommandBuffers = &m_p_command_buffer;
info.commandBufferCount = 1;
auto status = vkQueueSubmit(m_p_graphics_queue, 1, &info, m_p_fence);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkQueueSubmit");
}
private:
VkDevice m_p_device;
VkFence m_p_fence;
VkCommandBuffer m_p_command_buffer;
VkCommandPool m_p_command_pool;
VkQueue m_p_graphics_queue;
};
// @ main manager for "allocating" vertex, index, uniform stuff
class MemoryPool {
public:
MemoryPool();
~MemoryPool();
void Initialize(VkDeviceSize byte_size, VkDeviceSize device_min_uniform_alignment, VmaAllocator p_allocator, VkDevice p_device) noexcept;
void Shutdown() noexcept;
bool Alloc_GeneralBuffer(VkDeviceSize size, void** p_data, VkDescriptorBufferInfo* p_out, VmaVirtualAllocation* p_alloc) noexcept;
bool Alloc_VertexBuffer(uint32_t number_of_elements, uint32_t stride_in_bytes, void** p_data, VkDescriptorBufferInfo* p_out,
VmaVirtualAllocation* p_alloc) noexcept;
bool Alloc_IndexBuffer(uint32_t number_of_elements, uint32_t stride_in_bytes, void** p_data, VkDescriptorBufferInfo* p_out,
VmaVirtualAllocation* p_alloc) noexcept;
void SetDescriptorSet(uint32_t binding_index, uint32_t size, VkDescriptorType descriptor_type, VkDescriptorSet p_set) noexcept;
void SetDescriptorSet(uint32_t binding_index, VkDescriptorBufferInfo* p_info, VkDescriptorType descriptor_type,
VkDescriptorSet p_set) noexcept;
void SetDescriptorSet(uint32_t binding_index, VkSampler p_sampler, VkImageLayout layout, VkImageView p_view, VkDescriptorType descriptor_type,
VkDescriptorSet p_set) noexcept;
void Free_GeometryHandle(geometry_handle_t* p_valid_geometry_handle) noexcept;
void Free_GeometryHandle_ShaderDataOnly(geometry_handle_t* p_valid_geometry_handle) noexcept;
private:
VkDeviceSize m_memory_total_size;
VkDeviceSize m_device_min_uniform_alignment;
char* m_p_data;
VkBuffer m_p_buffer;
VmaAllocation m_p_buffer_alloc;
VkDevice m_p_device;
VmaAllocator m_p_vk_allocator;
VmaVirtualBlock m_p_block;
};
// If we need additional command buffers, we can add them to this list and retrieve them from the ring.
enum class CommandBufferName { Primary, Count };
// The command buffer ring stores a unique set of named command buffers for each bufferd frame.
// Explanation of how to use Vulkan efficiently: https://vkguide.dev/docs/chapter-4/double_buffering/
class CommandBufferRing {
public:
static constexpr uint32_t kNumFramesToBuffer = kSwapchainBackBufferCount;
static constexpr uint32_t kNumCommandBuffersPerFrame = static_cast<uint32_t>(CommandBufferName::Count);
CommandBufferRing();
void Initialize(VkDevice p_device, uint32_t queue_index_graphics) noexcept;
void Shutdown();
void OnBeginFrame();
VkCommandBuffer GetCommandBufferForActiveFrame(CommandBufferName named_command_buffer);
private:
struct CommandBuffersPerFrame {
Rml::Array<VkCommandPool, kNumCommandBuffersPerFrame> m_command_pools;
Rml::Array<VkCommandBuffer, kNumCommandBuffersPerFrame> m_command_buffers;
};
VkDevice m_p_device;
uint32_t m_frame_index;
CommandBuffersPerFrame* m_p_current_frame;
Rml::Array<CommandBuffersPerFrame, kNumFramesToBuffer> m_frames;
};
class DescriptorPoolManager {
public:
DescriptorPoolManager() : m_allocated_descriptor_count{}, m_p_descriptor_pool{} {}
~DescriptorPoolManager()
{
RMLUI_VK_ASSERTMSG(m_allocated_descriptor_count <= 0, "something is wrong. You didn't free some VkDescriptorSet");
}
void Initialize(VkDevice p_device, uint32_t count_uniform_buffer, uint32_t count_image_sampler, uint32_t count_sampler,
uint32_t count_storage_buffer) noexcept
{
RMLUI_VK_ASSERTMSG(p_device, "you can't pass an invalid VkDevice here");
Rml::Array<VkDescriptorPoolSize, 5> sizes;
sizes[0] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, count_uniform_buffer};
sizes[1] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, count_uniform_buffer};
sizes[2] = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, count_image_sampler};
sizes[3] = {VK_DESCRIPTOR_TYPE_SAMPLER, count_sampler};
sizes[4] = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, count_storage_buffer};
VkDescriptorPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
info.pNext = nullptr;
info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
info.maxSets = 1000;
info.poolSizeCount = static_cast<uint32_t>(sizes.size());
info.pPoolSizes = sizes.data();
auto status = vkCreateDescriptorPool(p_device, &info, nullptr, &m_p_descriptor_pool);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkCreateDescriptorPool");
}
void Shutdown(VkDevice p_device)
{
RMLUI_VK_ASSERTMSG(p_device, "you can't pass an invalid VkDevice here");
vkDestroyDescriptorPool(p_device, m_p_descriptor_pool, nullptr);
}
uint32_t Get_AllocatedDescriptorCount() const noexcept { return m_allocated_descriptor_count; }
bool Alloc_Descriptor(VkDevice p_device, VkDescriptorSetLayout* p_layouts, VkDescriptorSet* p_sets,
uint32_t descriptor_count_for_creation = 1) noexcept
{
RMLUI_VK_ASSERTMSG(p_layouts, "you have to pass a valid and initialized VkDescriptorSetLayout (probably you must create it)");
RMLUI_VK_ASSERTMSG(p_device, "you must pass a valid VkDevice here");
VkDescriptorSetAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
info.pNext = nullptr;
info.descriptorPool = m_p_descriptor_pool;
info.descriptorSetCount = descriptor_count_for_creation;
info.pSetLayouts = p_layouts;
auto status = vkAllocateDescriptorSets(p_device, &info, p_sets);
RMLUI_VK_ASSERTMSG(status == VkResult::VK_SUCCESS, "failed to vkAllocateDescriptorSets");
m_allocated_descriptor_count += descriptor_count_for_creation;
return status == VkResult::VK_SUCCESS;
}
void Free_Descriptors(VkDevice p_device, VkDescriptorSet* p_sets, uint32_t descriptor_count = 1) noexcept
{
RMLUI_VK_ASSERTMSG(p_device, "you must pass a valid VkDevice here");
if (p_sets)
{
m_allocated_descriptor_count -= descriptor_count;
vkFreeDescriptorSets(p_device, m_p_descriptor_pool, descriptor_count, p_sets);
}
}
private:
int m_allocated_descriptor_count;
VkDescriptorPool m_p_descriptor_pool;
};
struct PhysicalDeviceWrapper {
VkPhysicalDevice m_p_physical_device;
VkPhysicalDeviceProperties m_physical_device_properties;
};
using PhysicalDeviceWrapperList = Rml::Vector<PhysicalDeviceWrapper>;
using LayerPropertiesList = Rml::Vector<VkLayerProperties>;
using ExtensionPropertiesList = Rml::Vector<VkExtensionProperties>;
private:
Rml::TextureHandle CreateTexture(Rml::Span<const Rml::byte> source, Rml::Vector2i dimensions, const Rml::String& name);
void Initialize_Instance(Rml::Vector<const char*> required_extensions) noexcept;
void Initialize_Device() noexcept;
void Initialize_PhysicalDevice(VkPhysicalDeviceProperties& out_physical_device_properties) noexcept;
void Initialize_Swapchain(VkExtent2D window_extent) noexcept;
void Initialize_Surface(CreateSurfaceCallback create_surface_callback) noexcept;
void Initialize_QueueIndecies() noexcept;
void Initialize_Queues() noexcept;
void Initialize_SyncPrimitives() noexcept;
void Initialize_Resources(const VkPhysicalDeviceProperties& physical_device_properties) noexcept;
void Initialize_Allocator() noexcept;
void Destroy_Instance() noexcept;
void Destroy_Device() noexcept;
void Destroy_Swapchain() noexcept;
void Destroy_Surface() noexcept;
void Destroy_SyncPrimitives() noexcept;
void Destroy_Resources() noexcept;
void Destroy_Allocator() noexcept;
void QueryInstanceLayers(LayerPropertiesList& result) noexcept;
void QueryInstanceExtensions(ExtensionPropertiesList& result, const LayerPropertiesList& instance_layer_properties) noexcept;
bool AddLayerToInstance(Rml::Vector<const char*>& result, const LayerPropertiesList& instance_layer_properties,
const char* p_instance_layer_name) noexcept;
bool AddExtensionToInstance(Rml::Vector<const char*>& result, const ExtensionPropertiesList& instance_extension_properties,
const char* p_instance_extension_name) noexcept;
void CreatePropertiesFor_Instance(Rml::Vector<const char*>& instance_layer_names, Rml::Vector<const char*>& instance_extension_names) noexcept;
bool IsLayerPresent(const LayerPropertiesList& properties, const char* p_layer_name) noexcept;
bool IsExtensionPresent(const ExtensionPropertiesList& properties, const char* p_extension_name) noexcept;
bool AddExtensionToDevice(Rml::Vector<const char*>& result, const ExtensionPropertiesList& device_extension_properties,
const char* p_device_extension_name) noexcept;
void CreatePropertiesFor_Device(ExtensionPropertiesList& result) noexcept;
void CreateReportDebugCallback() noexcept;
void Destroy_ReportDebugCallback() noexcept;
uint32_t GetUserAPIVersion() const noexcept;
uint32_t GetRequiredVersionAndValidateMachine() noexcept;
void CollectPhysicalDevices(PhysicalDeviceWrapperList& out_physical_devices) noexcept;
const PhysicalDeviceWrapper* ChoosePhysicalDevice(const PhysicalDeviceWrapperList& physical_devices, VkPhysicalDeviceType device_type) noexcept;
VkSurfaceFormatKHR ChooseSwapchainFormat() noexcept;
VkSurfaceTransformFlagBitsKHR CreatePretransformSwapchain() noexcept;
VkCompositeAlphaFlagBitsKHR ChooseSwapchainCompositeAlpha() noexcept;
int Choose_SwapchainImageCount(uint32_t user_swapchain_count_for_creation = kSwapchainBackBufferCount, bool if_failed_choose_min = true) noexcept;
VkPresentModeKHR GetPresentMode(VkPresentModeKHR type = VkPresentModeKHR::VK_PRESENT_MODE_FIFO_KHR) noexcept;
VkSurfaceCapabilitiesKHR GetSurfaceCapabilities() noexcept;
VkExtent2D GetValidSurfaceExtent() noexcept;
void CreateShaders() noexcept;
void CreateDescriptorSetLayout() noexcept;
void CreatePipelineLayout() noexcept;
void CreateDescriptorSets() noexcept;
void CreateSamplers() noexcept;
void Create_Pipelines() noexcept;
void CreateRenderPass() noexcept;
void CreateSwapchainFrameBuffers(const VkExtent2D& real_render_image_size) noexcept;
// This method is called in Views, so don't call it manually
void CreateSwapchainImages() noexcept;
void CreateSwapchainImageViews() noexcept;
void Create_DepthStencilImage() noexcept;
void Create_DepthStencilImageViews() noexcept;
void CreateResourcesDependentOnSize(const VkExtent2D& real_render_image_size) noexcept;
buffer_data_t CreateResource_StagingBuffer(VkDeviceSize size, VkBufferUsageFlags flags) noexcept;
void DestroyResource_StagingBuffer(const buffer_data_t& data) noexcept;
void Destroy_Textures() noexcept;
void Destroy_Geometries() noexcept;
void Destroy_Texture(const texture_data_t& p_texture) noexcept;
void DestroyResourcesDependentOnSize() noexcept;
void DestroySwapchainImageViews() noexcept;
void DestroySwapchainFrameBuffers() noexcept;
void DestroyRenderPass() noexcept;
void Destroy_Pipelines() noexcept;
void DestroyDescriptorSets() noexcept;
void DestroyPipelineLayout() noexcept;
void DestroySamplers() noexcept;
void Wait() noexcept;
void Update_PendingForDeletion_Textures_By_Frames() noexcept;
void Update_PendingForDeletion_Geometries() noexcept;
void Submit() noexcept;
void Present() noexcept;
VkFormat Get_SupportedDepthFormat();
private:
bool m_is_transform_enabled;
bool m_is_apply_to_regular_geometry_stencil;
bool m_is_use_scissor_specified;
bool m_is_use_stencil_pipeline;
int m_width;
int m_height;
uint32_t m_queue_index_present;
uint32_t m_queue_index_graphics;
uint32_t m_queue_index_compute;
uint32_t m_semaphore_index;
uint32_t m_semaphore_index_previous;
uint32_t m_image_index;
VkInstance m_p_instance;
VkDevice m_p_device;
VkPhysicalDevice m_p_physical_device;
VkSurfaceKHR m_p_surface;
VkSwapchainKHR m_p_swapchain;
VmaAllocator m_p_allocator;
// @ obtained from command list see PrepareRenderBuffer method
VkCommandBuffer m_p_current_command_buffer;
VkDescriptorSetLayout m_p_descriptor_set_layout_vertex_transform;
VkDescriptorSetLayout m_p_descriptor_set_layout_texture;
VkPipelineLayout m_p_pipeline_layout;
VkPipeline m_p_pipeline_with_textures;
VkPipeline m_p_pipeline_without_textures;
VkPipeline m_p_pipeline_stencil_for_region_where_geometry_will_be_drawn;
VkPipeline m_p_pipeline_stencil_for_regular_geometry_that_applied_to_region_with_textures;
VkPipeline m_p_pipeline_stencil_for_regular_geometry_that_applied_to_region_without_textures;
VkDescriptorSet m_p_descriptor_set;
VkRenderPass m_p_render_pass;
VkSampler m_p_sampler_linear;
VkRect2D m_scissor;
// @ means it captures the window size full width and full height, offset equals both x and y to 0
VkRect2D m_scissor_original;
VkViewport m_viewport;
VkQueue m_p_queue_present;
VkQueue m_p_queue_graphics;
VkQueue m_p_queue_compute;
#ifdef RMLUI_VK_DEBUG
VkDebugUtilsMessengerEXT m_debug_messenger;
#endif
VkSurfaceFormatKHR m_swapchain_format;
shader_vertex_user_data_t m_user_data_for_vertex_shader;
texture_data_t m_texture_depthstencil;
Rml::Matrix4f m_projection;
Rml::Vector<VkFence> m_executed_fences;
Rml::Vector<VkSemaphore> m_semaphores_image_available;
Rml::Vector<VkSemaphore> m_semaphores_finished_render;
Rml::Vector<VkFramebuffer> m_swapchain_frame_buffers;
Rml::Vector<VkImage> m_swapchain_images;
Rml::Vector<VkImageView> m_swapchain_image_views;
Rml::Vector<VkShaderModule> m_shaders;
Rml::Array<Rml::Vector<texture_data_t*>, kSwapchainBackBufferCount> m_pending_for_deletion_textures_by_frames;
// vma handles that thing, so there's no need for frame splitting
Rml::Vector<geometry_handle_t*> m_pending_for_deletion_geometries;
CommandBufferRing m_command_buffer_ring;
MemoryPool m_memory_pool;
UploadResourceManager m_upload_manager;
DescriptorPoolManager m_manager_descriptors;
};

View File

@@ -0,0 +1,28 @@
The SDL GPU backend for RmlUi uses the following third-party tool to generate
the included shader binaries:
SDL_shadercross
https://github.com/libsdl-org/SDL_shadercross
This tool is licensed separately from Rmlui. The license of this dependency
is recited below.
----
Copyright (C) 2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

View File

@@ -0,0 +1,817 @@
// RmlUi SDL GPU shaders compiled using command: 'python compile_shaders.py'. Do not edit manually.
#include <stdint.h>
alignas(uint32_t) static const unsigned char shader_frag_color_spirv[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x0F,0x00,0x07,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x10,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x03,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x58,0x02,0x00,0x00,0x05,0x00,0x07,0x00,0x02,0x00,0x00,0x00,
0x69,0x6E,0x2E,0x76,0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x00,0x00,0x00,0x00,
0x05,0x00,0x07,0x00,0x03,0x00,0x00,0x00,0x6F,0x75,0x74,0x2E,0x76,0x61,0x72,0x2E,0x53,0x56,0x5F,0x54,
0x61,0x72,0x67,0x65,0x74,0x30,0x00,0x00,0x05,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x02,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x03,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x00,0x03,0x00,
0x04,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
0x04,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x13,0x00,0x02,0x00,
0x08,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x09,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x36,0x00,0x05,0x00,0x08,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0xF8,0x00,0x02,0x00,0x0A,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x05,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x03,0x00,0x00,0x00,
0x0B,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
};
alignas(uint32_t) static const unsigned char shader_frag_color_msl[] = {
0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x6D,0x65,0x74,0x61,0x6C,0x5F,0x73,0x74,0x64,0x6C,
0x69,0x62,0x3E,0x0A,0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x73,0x69,0x6D,0x64,0x2F,0x73,
0x69,0x6D,0x64,0x2E,0x68,0x3E,0x0A,0x0A,0x75,0x73,0x69,0x6E,0x67,0x20,0x6E,0x61,0x6D,0x65,0x73,0x70,
0x61,0x63,0x65,0x20,0x6D,0x65,0x74,0x61,0x6C,0x3B,0x0A,0x0A,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6D,
0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,
0x34,0x20,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x53,0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x30,
0x20,0x5B,0x5B,0x63,0x6F,0x6C,0x6F,0x72,0x28,0x30,0x29,0x5D,0x5D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x73,
0x74,0x72,0x75,0x63,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x69,0x6E,0x0A,0x7B,0x0A,0x20,0x20,0x20,
0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x20,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,
0x4F,0x52,0x44,0x30,0x20,0x5B,0x5B,0x75,0x73,0x65,0x72,0x28,0x6C,0x6F,0x63,0x6E,0x30,0x29,0x5D,0x5D,
0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x66,0x72,0x61,0x67,0x6D,0x65,0x6E,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,
0x5F,0x6F,0x75,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x28,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x69,0x6E,0x20,
0x69,0x6E,0x20,0x5B,0x5B,0x73,0x74,0x61,0x67,0x65,0x5F,0x69,0x6E,0x5D,0x5D,0x29,0x0A,0x7B,0x0A,0x20,
0x20,0x20,0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x20,0x6F,0x75,0x74,0x20,0x3D,0x20,0x7B,
0x7D,0x3B,0x0A,0x20,0x20,0x20,0x20,0x6F,0x75,0x74,0x2E,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x53,
0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x20,0x3D,0x20,0x69,0x6E,0x2E,0x69,0x6E,0x5F,0x76,0x61,
0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x3B,0x0A,0x20,0x20,0x20,0x20,0x72,0x65,0x74,
0x75,0x72,0x6E,0x20,0x6F,0x75,0x74,0x3B,0x0A,0x7D,0x0A,0x0A,
};
alignas(uint32_t) static const unsigned char shader_frag_color_dxil[] = {
0x44,0x58,0x42,0x43,0x2E,0x2E,0xA9,0x96,0x45,0xE1,0xC0,0xAA,0xE2,0x19,0x19,0xF0,0x13,0x89,0x7B,0xCC,
0x01,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x4C,0x00,0x00,0x00,
0x88,0x00,0x00,0x00,0xC4,0x00,0x00,0x00,0x58,0x01,0x00,0x00,0x08,0x06,0x00,0x00,0x24,0x06,0x00,0x00,
0x53,0x46,0x49,0x30,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x53,0x47,0x31,
0x34,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,
0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x00,0x00,0x00,0x4F,0x53,0x47,0x31,
0x34,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x53,0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x00,0x00,0x00,0x50,0x53,0x56,0x30,
0x8C,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x01,
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x6D,0x61,
0x69,0x6E,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x00,0x44,0x00,0x03,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x44,0x10,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
0x08,0x00,0x00,0x00,0x53,0x54,0x41,0x54,0xA8,0x04,0x00,0x00,0x60,0x00,0x00,0x00,0x2A,0x01,0x00,0x00,
0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,0x10,0x00,0x00,0x00,0x90,0x04,0x00,0x00,0x42,0x43,0xC0,0xDE,
0x21,0x0C,0x00,0x00,0x21,0x01,0x00,0x00,0x0B,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,
0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,
0x1E,0x04,0x8B,0x62,0x80,0x10,0x45,0x02,0x42,0x92,0x0B,0x42,0x84,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,
0x0A,0x32,0x42,0x88,0x48,0x90,0x14,0x20,0x43,0x46,0x88,0xA5,0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,
0x11,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,0xE1,0x83,0xE5,0x8A,0x04,0x21,0x46,0x06,0x51,0x18,0x00,0x00,
0x06,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,0xFF,0xFF,0xFF,0x07,0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,
0xFF,0xFF,0x03,0x20,0x01,0x00,0x00,0x00,0x49,0x18,0x00,0x00,0x02,0x00,0x00,0x00,0x13,0x82,0x60,0x42,
0x20,0x00,0x00,0x00,0x89,0x20,0x00,0x00,0x0F,0x00,0x00,0x00,0x32,0x22,0x08,0x09,0x20,0x64,0x85,0x04,
0x13,0x22,0xA4,0x84,0x04,0x13,0x22,0xE3,0x84,0xA1,0x90,0x14,0x12,0x4C,0x88,0x8C,0x0B,0x84,0x84,0x4C,
0x10,0x30,0x23,0x00,0x25,0x00,0x8A,0x19,0x80,0x39,0x02,0x30,0x98,0x23,0x40,0x8A,0x31,0x44,0x54,0x44,
0x56,0x0C,0x20,0xA2,0x1A,0xC2,0x81,0x80,0x34,0x20,0x00,0x00,0x13,0x14,0x72,0xC0,0x87,0x74,0x60,0x87,
0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,0x0E,0x6D,0xD0,0x0E,0x7A,0x50,0x0E,0x6D,
0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0xA0,0x07,0x73,0x20,0x07,
0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xD0,
0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x76,0x40,0x07,0x7A,0x60,0x07,0x74,
0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,0x60,0x0E,0x73,0x20,0x07,0x7A,0x30,0x07,
0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,0x6D,0xE0,0x0E,0x78,0xA0,0x07,0x71,0x60,
0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x79,0x10,0x20,
0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC8,0x02,0x01,0x0B,0x00,0x00,0x00,0x32,0x1E,0x98,0x14,
0x19,0x11,0x4C,0x90,0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0xA2,0x12,0x18,0x01,0x28,0x86,0x32,0x28,0x8F,
0x92,0x28,0x04,0xAA,0x92,0x18,0x01,0x28,0x82,0x42,0x28,0x10,0xDA,0xB1,0x0C,0x82,0x08,0x04,0x02,0x01,
0x79,0x18,0x00,0x00,0x57,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,0x46,0x02,0x13,0xC4,0x8E,0x0C,0x6F,0xEC,
0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,0x05,0x07,0x04,0x45,0x8C,0xE6,0x26,0x26,
0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x88,0x61,0x82,0x40,0x10,0x1B,0x84,0x81,0xD8,0x20,
0x10,0x04,0x05,0xB8,0xB9,0x09,0x02,0x51,0x6C,0x18,0x0E,0x84,0x98,0x20,0x08,0xC0,0x06,0x60,0xC3,0x40,
0x2C,0xCB,0x86,0x80,0xD9,0x30,0x0C,0x4A,0x33,0x41,0x58,0x9E,0x0D,0xC1,0x43,0xA2,0x2D,0x2C,0xCD,0x8D,
0x08,0x55,0x11,0xD6,0xD0,0xD3,0x93,0x14,0xD1,0x04,0xA1,0x50,0x26,0x08,0xC5,0xB2,0x21,0x20,0x26,0x08,
0x05,0x33,0x41,0x28,0x9A,0x09,0x02,0x61,0x4C,0x10,0x88,0x63,0x83,0x80,0x65,0x1B,0x16,0x42,0x9A,0xA8,
0xCA,0x1A,0x2E,0x82,0xD2,0x36,0x04,0x1B,0x93,0x29,0xAB,0x2F,0xAA,0x30,0xB9,0xB3,0x32,0xBA,0x09,0x42,
0xE1,0x6C,0x58,0x88,0x6E,0xF2,0x2A,0x6A,0xB8,0x08,0x4A,0xDB,0x10,0x7C,0x1B,0x06,0x0E,0x0C,0x80,0x0D,
0x85,0x12,0x85,0x01,0x00,0xB0,0x48,0x73,0x9B,0xA3,0x9B,0x9B,0x20,0x10,0x08,0x8D,0xB9,0xB4,0xB3,0x2F,
0x36,0xB2,0x09,0x02,0x91,0xD0,0x98,0x4B,0x3B,0xFB,0x9A,0xA3,0xDB,0x60,0x8C,0x01,0x19,0x94,0x81,0x19,
0x9C,0x81,0x19,0x54,0x61,0x63,0xB3,0x6B,0x73,0x49,0x23,0x2B,0x73,0xA3,0x9B,0x12,0x04,0x55,0xC8,0xF0,
0x5C,0xEC,0xCA,0xE4,0xE6,0xD2,0xDE,0xDC,0xA6,0x04,0x44,0x13,0x32,0x3C,0x17,0xBB,0x30,0x36,0xBB,0x32,
0xB9,0x29,0x41,0x51,0x87,0x0C,0xCF,0x65,0x0E,0x2D,0x8C,0xAC,0x4C,0xAE,0xE9,0x8D,0xAC,0x8C,0x6D,0x4A,
0x80,0x54,0x22,0xC3,0x73,0xA1,0xCB,0x83,0x2B,0x0B,0x72,0x73,0x7B,0xA3,0x0B,0xA3,0x4B,0x7B,0x73,0x9B,
0x9B,0x12,0x34,0x75,0xC8,0xF0,0x5C,0xEC,0xD2,0xCA,0xEE,0x92,0xC8,0xA6,0xE8,0xC2,0xE8,0xCA,0xA6,0x04,
0x4F,0x1D,0x32,0x3C,0x97,0x32,0x37,0x3A,0xB9,0x3C,0xA8,0xB7,0x34,0x37,0xBA,0xB9,0x29,0x41,0x18,0x74,
0x21,0xC3,0x73,0x19,0x7B,0xAB,0x73,0xA3,0x2B,0x93,0x9B,0x9B,0x12,0x9C,0x01,0x00,0x79,0x18,0x00,0x00,
0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,0x14,0x01,0x3D,0x88,0x43,0x38,0x84,0xC3,
0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,0x00,0x0F,0xED,0x10,0x0E,0xF4,0x80,0x0E,
0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,0x05,0x3D,0x88,0x43,0x38,0x84,0x83,0x1B,
0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,0x74,0x70,0x07,0x7B,0x08,0x07,0x79,0x48,
0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xCC,0x11,0x0E,0xEC,0x90,0x0E,
0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,0x33,0x10,0xC4,0x1D,0xDE,0x21,0x1C,0xD8,
0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,0xD0,0x43,0x39,0xB4,0x03,0x3C,0xBC,0x83,
0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,
0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xF8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,
0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2C,0xEE,0xF0,0x0E,0xEE,0xE0,0x0E,0xF5,
0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,0xCC,0xA1,0x1C,0xE4,0xA1,0x1C,0xDC,0x61,
0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,0x43,0x39,0xC8,0x43,0x39,0x98,0x43,0x39,
0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,0x94,0xC3,0x2F,0xBC,0x83,0x3C,0xFC,0x82,
0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,0x03,0x72,0x10,0x87,0x73,0x70,0x03,0x7B,
0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,0x98,0x81,0x3C,0xE4,0x80,0x0F,0x6E,0x40,
0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x0B,0x00,0x00,0x00,0x16,0x30,0x0D,0x97,
0xEF,0x3C,0xFE,0xE2,0x00,0x83,0xD8,0x3C,0xD4,0xE4,0x17,0xB7,0x6D,0x02,0xD5,0x70,0xF9,0xCE,0xE3,0x4B,
0x93,0x13,0x11,0x28,0x35,0x3D,0xD4,0xE4,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x48,0x41,0x53,0x48,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x65,0xE3,0x7F,0x22,
0xB6,0x5B,0x10,0xF4,0xA4,0x0F,0xBE,0x64,0x48,0x54,0x5A,0xC1,0x44,0x58,0x49,0x4C,0xD4,0x04,0x00,0x00,
0x60,0x00,0x00,0x00,0x35,0x01,0x00,0x00,0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,0x10,0x00,0x00,0x00,
0xBC,0x04,0x00,0x00,0x42,0x43,0xC0,0xDE,0x21,0x0C,0x00,0x00,0x2C,0x01,0x00,0x00,0x0B,0x82,0x20,0x00,
0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,0x06,0x10,0x32,0x39,
0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,0x1E,0x04,0x8B,0x62,0x80,0x10,0x45,0x02,0x42,0x92,0x0B,0x42,
0x84,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,0x0A,0x32,0x42,0x88,0x48,0x90,0x14,0x20,0x43,0x46,0x88,0xA5,
0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,0x11,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,0xE1,0x83,0xE5,0x8A,
0x04,0x21,0x46,0x06,0x51,0x18,0x00,0x00,0x06,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,0xFF,0xFF,0xFF,0x07,
0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,0xFF,0xFF,0x03,0x20,0x01,0x00,0x00,0x00,0x49,0x18,0x00,0x00,
0x02,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,0x00,0x00,0x00,0x89,0x20,0x00,0x00,0x0F,0x00,0x00,0x00,
0x32,0x22,0x08,0x09,0x20,0x64,0x85,0x04,0x13,0x22,0xA4,0x84,0x04,0x13,0x22,0xE3,0x84,0xA1,0x90,0x14,
0x12,0x4C,0x88,0x8C,0x0B,0x84,0x84,0x4C,0x10,0x30,0x23,0x00,0x25,0x00,0x8A,0x19,0x80,0x39,0x02,0x30,
0x98,0x23,0x40,0x8A,0x31,0x44,0x54,0x44,0x56,0x0C,0x20,0xA2,0x1A,0xC2,0x81,0x80,0x34,0x20,0x00,0x00,
0x13,0x14,0x72,0xC0,0x87,0x74,0x60,0x87,0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,
0x0E,0x6D,0xD0,0x0E,0x7A,0x50,0x0E,0x6D,0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x90,0x0E,0x71,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,
0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,
0x0E,0x76,0x40,0x07,0x7A,0x60,0x07,0x74,0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x60,0x0E,0x73,0x20,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,
0x6D,0xE0,0x0E,0x78,0xA0,0x07,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x0C,0x79,0x10,0x20,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC8,0x02,0x01,
0x0B,0x00,0x00,0x00,0x32,0x1E,0x98,0x10,0x19,0x11,0x4C,0x90,0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0xA2,
0x12,0x18,0x01,0x28,0x88,0x62,0x28,0x83,0xF2,0xA0,0x2A,0x89,0x11,0x80,0x22,0x28,0x84,0x02,0xA1,0x1D,
0xCB,0x20,0x88,0x40,0x20,0x10,0x00,0x00,0x79,0x18,0x00,0x00,0x42,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,
0x46,0x02,0x13,0xC4,0x8E,0x0C,0x6F,0xEC,0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,
0x05,0x07,0x04,0x45,0x8C,0xE6,0x26,0x26,0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x88,0x61,
0x82,0x40,0x10,0x1B,0x84,0x81,0x98,0x20,0x10,0xC5,0x06,0x61,0x30,0x28,0xC0,0xCD,0x4D,0x10,0x08,0x63,
0xC3,0x80,0x24,0xC4,0x04,0x61,0x71,0x36,0x04,0xCB,0x04,0x41,0x00,0x48,0xB4,0x85,0xA5,0xB9,0x11,0xA1,
0x2A,0xC2,0x1A,0x7A,0x7A,0x92,0x22,0x9A,0x20,0x14,0xC9,0x04,0xA1,0x50,0x36,0x04,0xC4,0x04,0xA1,0x58,
0x26,0x08,0x05,0x33,0x41,0x20,0x8E,0x09,0x02,0x81,0x6C,0x10,0x2A,0x6B,0xC3,0x42,0x3C,0x50,0x24,0x4D,
0x03,0x45,0x44,0xD7,0x86,0x00,0x63,0x32,0x65,0xF5,0x45,0x15,0x26,0x77,0x56,0x46,0x37,0x41,0x28,0x9A,
0x0D,0x0B,0xA1,0x41,0x9B,0x14,0x0D,0x14,0x11,0x5D,0x1B,0x02,0x6E,0xC3,0x90,0x75,0xC0,0x86,0xA2,0x71,
0x3C,0x00,0xA8,0xC2,0xC6,0x66,0xD7,0xE6,0x92,0x46,0x56,0xE6,0x46,0x37,0x25,0x08,0xAA,0x90,0xE1,0xB9,
0xD8,0x95,0xC9,0xCD,0xA5,0xBD,0xB9,0x4D,0x09,0x88,0x26,0x64,0x78,0x2E,0x76,0x61,0x6C,0x76,0x65,0x72,
0x53,0x02,0xA3,0x0E,0x19,0x9E,0xCB,0x1C,0x5A,0x18,0x59,0x99,0x5C,0xD3,0x1B,0x59,0x19,0xDB,0x94,0x20,
0xA9,0x43,0x86,0xE7,0x62,0x97,0x56,0x76,0x97,0x44,0x36,0x45,0x17,0x46,0x57,0x36,0x25,0x58,0xEA,0x90,
0xE1,0xB9,0x94,0xB9,0xD1,0xC9,0xE5,0x41,0xBD,0xA5,0xB9,0xD1,0xCD,0x4D,0x09,0x3C,0x00,0x00,0x00,0x00,
0x79,0x18,0x00,0x00,0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,0x14,0x01,0x3D,0x88,
0x43,0x38,0x84,0xC3,0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,0x00,0x0F,0xED,0x10,
0x0E,0xF4,0x80,0x0E,0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,0x05,0x3D,0x88,0x43,
0x38,0x84,0x83,0x1B,0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,0x74,0x70,0x07,0x7B,
0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xCC,0x11,
0x0E,0xEC,0x90,0x0E,0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,0x33,0x10,0xC4,0x1D,
0xDE,0x21,0x1C,0xD8,0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,0xD0,0x43,0x39,0xB4,
0x03,0x3C,0xBC,0x83,0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,0x07,0x37,0x68,0x87,
0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xF8,0x05,0x76,0x78,
0x87,0x77,0x80,0x87,0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2C,0xEE,0xF0,0x0E,
0xEE,0xE0,0x0E,0xF5,0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,0xCC,0xA1,0x1C,0xE4,
0xA1,0x1C,0xDC,0x61,0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,0x43,0x39,0xC8,0x43,
0x39,0x98,0x43,0x39,0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,0x94,0xC3,0x2F,0xBC,
0x83,0x3C,0xFC,0x82,0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,0x03,0x72,0x10,0x87,
0x73,0x70,0x03,0x7B,0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,0x98,0x81,0x3C,0xE4,
0x80,0x0F,0x6E,0x40,0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x0B,0x00,0x00,0x00,
0x16,0x30,0x0D,0x97,0xEF,0x3C,0xFE,0xE2,0x00,0x83,0xD8,0x3C,0xD4,0xE4,0x17,0xB7,0x6D,0x02,0xD5,0x70,
0xF9,0xCE,0xE3,0x4B,0x93,0x13,0x11,0x28,0x35,0x3D,0xD4,0xE4,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,
0x00,0x00,0x00,0x00,0x61,0x20,0x00,0x00,0x1E,0x00,0x00,0x00,0x13,0x04,0x41,0x2C,0x10,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x44,0x85,0x30,0x03,0x50,0x0A,0x54,0x25,0x50,0x06,0x00,0x00,0x23,0x06,0x09,0x00,
0x82,0x60,0x60,0x4C,0x05,0x04,0x29,0xC4,0x88,0x41,0x02,0x80,0x20,0x18,0x18,0x94,0x11,0x45,0x43,0x31,
0x62,0x90,0x00,0x20,0x08,0x06,0x46,0x75,0x48,0xD2,0x62,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x61,0x21,
0xD3,0x44,0x1C,0x23,0x06,0x09,0x00,0x82,0x60,0x80,0x58,0x07,0x45,0x39,0xC4,0x88,0x41,0x02,0x80,0x20,
0x18,0x20,0xD6,0x41,0x51,0xC6,0x30,0x62,0x90,0x00,0x20,0x08,0x06,0x88,0x75,0x50,0x54,0x23,0x8C,0x18,
0x24,0x00,0x08,0x82,0x01,0x62,0x1D,0x14,0x55,0x04,0x08,0x00,0x00,0x00,0x00,0x00,
};
alignas(uint32_t) static const unsigned char shader_frag_texture_spirv[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x0F,0x00,0x08,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x10,0x00,0x03,0x00,0x01,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x58,0x02,0x00,0x00,0x05,0x00,0x06,0x00,
0x05,0x00,0x00,0x00,0x74,0x79,0x70,0x65,0x2E,0x32,0x64,0x2E,0x69,0x6D,0x61,0x67,0x65,0x00,0x00,0x00,
0x05,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x00,0x05,0x00,0x06,0x00,
0x07,0x00,0x00,0x00,0x74,0x79,0x70,0x65,0x2E,0x73,0x61,0x6D,0x70,0x6C,0x65,0x72,0x00,0x00,0x00,0x00,
0x05,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x53,0x61,0x6D,0x70,0x6C,0x65,0x72,0x00,0x05,0x00,0x07,0x00,
0x02,0x00,0x00,0x00,0x69,0x6E,0x2E,0x76,0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,
0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,0x03,0x00,0x00,0x00,0x69,0x6E,0x2E,0x76,0x61,0x72,0x2E,0x54,
0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,0x04,0x00,0x00,0x00,
0x6F,0x75,0x74,0x2E,0x76,0x61,0x72,0x2E,0x53,0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x00,0x00,
0x05,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,
0x09,0x00,0x00,0x00,0x74,0x79,0x70,0x65,0x2E,0x73,0x61,0x6D,0x70,0x6C,0x65,0x64,0x2E,0x69,0x6D,0x61,
0x67,0x65,0x00,0x00,0x47,0x00,0x04,0x00,0x02,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x03,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x04,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x06,0x00,0x00,0x00,
0x22,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x21,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x00,0x03,0x00,
0x0A,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x19,0x00,0x09,0x00,0x05,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x00,0x00,
0x1A,0x00,0x02,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x17,0x00,0x04,0x00,
0x0F,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x10,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x0D,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x12,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x13,0x00,0x00,0x00,
0x12,0x00,0x00,0x00,0x1B,0x00,0x03,0x00,0x09,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x0B,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0C,0x00,0x00,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x3B,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x36,0x00,0x05,0x00,
0x12,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0xF8,0x00,0x02,0x00,
0x14,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x0F,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x05,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x18,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x56,0x00,0x05,0x00,0x09,0x00,0x00,0x00,0x19,0x00,0x00,0x00,
0x17,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x57,0x00,0x06,0x00,0x0D,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,
0x19,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x85,0x00,0x05,0x00,0x0D,0x00,0x00,0x00,
0x1B,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x04,0x00,0x00,0x00,
0x1B,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
};
alignas(uint32_t) static const unsigned char shader_frag_texture_msl[] = {
0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x6D,0x65,0x74,0x61,0x6C,0x5F,0x73,0x74,0x64,0x6C,
0x69,0x62,0x3E,0x0A,0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x73,0x69,0x6D,0x64,0x2F,0x73,
0x69,0x6D,0x64,0x2E,0x68,0x3E,0x0A,0x0A,0x75,0x73,0x69,0x6E,0x67,0x20,0x6E,0x61,0x6D,0x65,0x73,0x70,
0x61,0x63,0x65,0x20,0x6D,0x65,0x74,0x61,0x6C,0x3B,0x0A,0x0A,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6D,
0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,
0x34,0x20,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x53,0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x30,
0x20,0x5B,0x5B,0x63,0x6F,0x6C,0x6F,0x72,0x28,0x30,0x29,0x5D,0x5D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x73,
0x74,0x72,0x75,0x63,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x69,0x6E,0x0A,0x7B,0x0A,0x20,0x20,0x20,
0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x20,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,
0x4F,0x52,0x44,0x30,0x20,0x5B,0x5B,0x75,0x73,0x65,0x72,0x28,0x6C,0x6F,0x63,0x6E,0x30,0x29,0x5D,0x5D,
0x3B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x32,0x20,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,
0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x20,0x5B,0x5B,0x75,0x73,0x65,0x72,0x28,0x6C,0x6F,0x63,
0x6E,0x31,0x29,0x5D,0x5D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x66,0x72,0x61,0x67,0x6D,0x65,0x6E,0x74,0x20,
0x6D,0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x28,0x6D,0x61,0x69,0x6E,
0x30,0x5F,0x69,0x6E,0x20,0x69,0x6E,0x20,0x5B,0x5B,0x73,0x74,0x61,0x67,0x65,0x5F,0x69,0x6E,0x5D,0x5D,
0x2C,0x20,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x32,0x64,0x3C,0x66,0x6C,0x6F,0x61,0x74,0x3E,0x20,0x54,
0x65,0x78,0x74,0x75,0x72,0x65,0x20,0x5B,0x5B,0x74,0x65,0x78,0x74,0x75,0x72,0x65,0x28,0x30,0x29,0x5D,
0x5D,0x2C,0x20,0x73,0x61,0x6D,0x70,0x6C,0x65,0x72,0x20,0x53,0x61,0x6D,0x70,0x6C,0x65,0x72,0x20,0x5B,
0x5B,0x73,0x61,0x6D,0x70,0x6C,0x65,0x72,0x28,0x30,0x29,0x5D,0x5D,0x29,0x0A,0x7B,0x0A,0x20,0x20,0x20,
0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x20,0x6F,0x75,0x74,0x20,0x3D,0x20,0x7B,0x7D,0x3B,
0x0A,0x20,0x20,0x20,0x20,0x6F,0x75,0x74,0x2E,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x53,0x56,0x5F,
0x54,0x61,0x72,0x67,0x65,0x74,0x30,0x20,0x3D,0x20,0x69,0x6E,0x2E,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,
0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x20,0x2A,0x20,0x54,0x65,0x78,0x74,0x75,0x72,0x65,0x2E,
0x73,0x61,0x6D,0x70,0x6C,0x65,0x28,0x53,0x61,0x6D,0x70,0x6C,0x65,0x72,0x2C,0x20,0x69,0x6E,0x2E,0x69,
0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x29,0x3B,0x0A,0x20,0x20,
0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6E,0x20,0x6F,0x75,0x74,0x3B,0x0A,0x7D,0x0A,0x0A,
};
alignas(uint32_t) static const unsigned char shader_frag_texture_dxil[] = {
0x44,0x58,0x42,0x43,0xD6,0xEC,0x87,0x84,0xAB,0xDB,0x76,0x44,0x3D,0x9B,0x09,0x21,0x5C,0x7F,0x90,0x2E,
0x01,0x00,0x00,0x00,0x14,0x0F,0x00,0x00,0x07,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x4C,0x00,0x00,0x00,
0xA8,0x00,0x00,0x00,0xE4,0x00,0x00,0x00,0xD8,0x01,0x00,0x00,0x30,0x08,0x00,0x00,0x4C,0x08,0x00,0x00,
0x53,0x46,0x49,0x30,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x53,0x47,0x31,
0x54,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,
0x4F,0x4F,0x52,0x44,0x00,0x00,0x00,0x00,0x4F,0x53,0x47,0x31,0x34,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x56,0x5F,0x54,
0x61,0x72,0x67,0x65,0x74,0x00,0x00,0x00,0x50,0x53,0x56,0x30,0xEC,0x00,0x00,0x00,0x34,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x02,0x01,0x00,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,
0x44,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x6D,0x61,0x69,0x6E,0x00,0x02,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x44,0x00,0x03,0x02,0x00,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x01,0x42,0x00,
0x03,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x44,0x10,0x03,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,
0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x54,0x41,0x54,0x50,0x06,0x00,0x00,
0x60,0x00,0x00,0x00,0x94,0x01,0x00,0x00,0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,0x10,0x00,0x00,0x00,
0x38,0x06,0x00,0x00,0x42,0x43,0xC0,0xDE,0x21,0x0C,0x00,0x00,0x8B,0x01,0x00,0x00,0x0B,0x82,0x20,0x00,
0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,0x06,0x10,0x32,0x39,
0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,0x1E,0x04,0x8B,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0B,0x42,
0xA4,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,0x0A,0x32,0x52,0x88,0x48,0x90,0x14,0x20,0x43,0x46,0x88,0xA5,
0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,0x91,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,0xE1,0x83,0xE5,0x8A,
0x04,0x29,0x46,0x06,0x51,0x18,0x00,0x00,0x08,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,0xFF,0xFF,0xFF,0x07,
0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,0xFF,0xFF,0x03,0x20,0x6D,0x30,0x86,0xFF,0xFF,0xFF,0xFF,0x1F,
0x00,0x09,0xA8,0x00,0x49,0x18,0x00,0x00,0x03,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,0x4C,0x08,0x06,
0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00,0x43,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,
0x93,0x22,0xA4,0x84,0x04,0x93,0x22,0xE3,0x84,0xA1,0x90,0x14,0x12,0x4C,0x8A,0x8C,0x0B,0x84,0xA4,0x4C,
0x10,0x68,0x23,0x00,0x25,0x00,0x14,0x66,0x00,0xE6,0x08,0xC0,0x60,0x8E,0x00,0x29,0xC6,0x20,0x84,0x14,
0x42,0xA6,0x18,0x80,0x10,0x52,0x06,0xA1,0x9B,0x86,0xCB,0x9F,0xB0,0x87,0x90,0xFC,0x95,0x90,0x56,0x62,
0xF2,0x8B,0xDB,0x46,0xC5,0x18,0x63,0x10,0x2A,0xF7,0x0C,0x97,0x3F,0x61,0x0F,0x21,0xF9,0x21,0xD0,0x0C,
0x0B,0x81,0x82,0x55,0x18,0x45,0x18,0x1B,0x63,0x0C,0x42,0xC8,0xA0,0x36,0x47,0x10,0x14,0x83,0x91,0x42,
0xC8,0x23,0x38,0x10,0x30,0x8C,0x40,0x0C,0x33,0xB5,0xC1,0x38,0xB0,0x43,0x38,0xCC,0xC3,0x3C,0xB8,0x01,
0x2D,0x94,0x03,0x3E,0xD0,0x43,0x3D,0xC8,0x43,0x39,0xC8,0x01,0x29,0xF0,0x81,0x3D,0x94,0xC3,0x38,0xD0,
0xC3,0x3B,0xC8,0x03,0x1F,0x98,0x03,0x3B,0xBC,0x43,0x38,0xD0,0x03,0x1B,0x80,0x01,0x1D,0xF8,0x01,0x18,
0xF8,0x81,0x1E,0xE8,0x41,0x3B,0xA4,0x03,0x3C,0xCC,0xC3,0x2F,0xD0,0x43,0x3E,0xC0,0x43,0x39,0xA0,0x80,
0xCC,0x24,0x06,0xE3,0xC0,0x0E,0xE1,0x30,0x0F,0xF3,0xE0,0x06,0xB4,0x50,0x0E,0xF8,0x40,0x0F,0xF5,0x20,
0x0F,0xE5,0x20,0x07,0xA4,0xC0,0x07,0xF6,0x50,0x0E,0xE3,0x40,0x0F,0xEF,0x20,0x0F,0x7C,0x60,0x0E,0xEC,
0xF0,0x0E,0xE1,0x40,0x0F,0x6C,0x00,0x06,0x74,0xE0,0x07,0x60,0xE0,0x07,0x48,0x98,0x94,0xEA,0x4D,0xD2,
0x14,0x51,0xC2,0xE4,0xB3,0x00,0xF3,0x2C,0x44,0xC4,0x4E,0xC0,0x44,0xA0,0x80,0xD0,0x4D,0x04,0x02,0x00,
0x13,0x14,0x72,0xC0,0x87,0x74,0x60,0x87,0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,
0x0E,0x6D,0xD0,0x0E,0x7A,0x50,0x0E,0x6D,0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x90,0x0E,0x71,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,
0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,
0x0E,0x76,0x40,0x07,0x7A,0x60,0x07,0x74,0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x60,0x0E,0x73,0x20,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,
0x6D,0xE0,0x0E,0x78,0xA0,0x07,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,
0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x0C,0x79,0x10,0x20,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF2,0x34,
0x40,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0xE4,0x81,0x80,0x00,0x18,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x20,0x0B,0x04,0x00,0x00,0x0E,0x00,0x00,0x00,0x32,0x1E,0x98,0x14,0x19,0x11,0x4C,0x90,
0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0x22,0x25,0x30,0x02,0x50,0x0C,0x45,0x50,0x12,0x65,0x50,0x1E,0x85,
0x50,0x2C,0x54,0x4A,0x62,0x04,0xA0,0x08,0x0A,0xA1,0x40,0xC8,0xCE,0x00,0x10,0x9E,0x01,0xA0,0x3C,0x16,
0x62,0x10,0x81,0x40,0x20,0xCF,0x03,0x00,0x79,0x18,0x00,0x00,0x79,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,
0x46,0x02,0x13,0xC4,0x8E,0x0C,0x6F,0xEC,0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,
0x05,0x07,0x04,0x45,0x8C,0xE6,0x26,0x26,0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x88,0x62,
0x82,0x40,0x18,0x1B,0x84,0x81,0xD8,0x20,0x10,0x04,0x05,0xB8,0xB9,0x09,0x02,0x71,0x6C,0x18,0x0E,0x84,
0x98,0x20,0x58,0x13,0x0F,0xAA,0x32,0x3C,0xBA,0x3A,0xB9,0xB2,0x09,0x02,0x81,0x4C,0x10,0x88,0x64,0x83,
0x40,0x34,0x1B,0x12,0x42,0x59,0x18,0x62,0x60,0x08,0x67,0x43,0xF0,0x4C,0x10,0x30,0x8A,0xC7,0x54,0x58,
0x1B,0x1C,0x5B,0x99,0xDC,0x06,0x84,0x88,0x24,0x86,0x18,0x08,0x60,0x43,0x30,0x6D,0x20,0x20,0x00,0xA0,
0x26,0x08,0x02,0xB0,0x01,0xD8,0x30,0x10,0xD7,0xB5,0x21,0xC0,0x36,0x0C,0x83,0x95,0x4D,0x10,0xB2,0x6A,
0x43,0xB0,0x91,0x68,0x0B,0x4B,0x73,0x23,0x42,0x55,0x84,0x35,0xF4,0xF4,0x24,0x45,0x34,0x41,0x28,0x9C,
0x09,0x42,0xF1,0x6C,0x08,0x88,0x09,0x42,0x01,0x4D,0x10,0x8A,0x68,0x82,0x40,0x28,0x13,0x04,0x62,0xD9,
0x20,0x90,0x41,0x19,0x6C,0x58,0x08,0xEF,0x03,0x83,0x30,0x10,0x83,0x61,0x0C,0x08,0x30,0x30,0x83,0x0D,
0xC1,0xB0,0x41,0x20,0x03,0x32,0xD8,0xB0,0x0C,0xDE,0x07,0x06,0x68,0x20,0x06,0x83,0x18,0x0C,0x60,0x90,
0x06,0x1B,0x84,0x33,0x50,0x03,0x26,0x53,0x56,0x5F,0x54,0x61,0x72,0x67,0x65,0x74,0x13,0x84,0x42,0xDA,
0xB0,0x10,0x6C,0xF0,0xB5,0x41,0x18,0x80,0xC1,0x30,0x06,0x04,0x18,0x98,0xC1,0x86,0xC0,0x0D,0x36,0x0C,
0x6B,0xF0,0x06,0xC0,0x86,0xC2,0xEA,0xE0,0xA0,0x02,0x68,0x98,0xB1,0xBD,0x85,0xD1,0xCD,0x4D,0x10,0x08,
0x86,0x45,0x9A,0xDB,0x1C,0xDD,0xDC,0x04,0x81,0x68,0x68,0xCC,0xA5,0x9D,0x7D,0xB1,0x91,0xD1,0x98,0x4B,
0x3B,0xFB,0x9A,0xA3,0x23,0x42,0x57,0x86,0xF7,0xE5,0xF6,0x26,0xD7,0xB6,0x41,0x91,0x83,0x39,0xA0,0x83,
0x3A,0xB0,0x03,0xE4,0x0E,0xE6,0x00,0x0F,0x86,0x2A,0x6C,0x6C,0x76,0x6D,0x2E,0x69,0x64,0x65,0x6E,0x74,
0x53,0x82,0xA0,0x0A,0x19,0x9E,0x8B,0x5D,0x99,0xDC,0x5C,0xDA,0x9B,0xDB,0x94,0x80,0x68,0x42,0x86,0xE7,
0x62,0x17,0xC6,0x66,0x57,0x26,0x37,0x25,0x28,0xEA,0x90,0xE1,0xB9,0xCC,0xA1,0x85,0x91,0x95,0xC9,0x35,
0xBD,0x91,0x95,0xB1,0x4D,0x09,0x90,0x32,0x64,0x78,0x2E,0x72,0x65,0x73,0x6F,0x75,0x72,0x63,0x65,0x73,
0x53,0x02,0xAA,0x12,0x19,0x9E,0x0B,0x5D,0x1E,0x5C,0x59,0x90,0x9B,0xDB,0x1B,0x5D,0x18,0x5D,0xDA,0x9B,
0xDB,0xDC,0x94,0x20,0xAB,0x43,0x86,0xE7,0x62,0x97,0x56,0x76,0x97,0x44,0x36,0x45,0x17,0x46,0x57,0x36,
0x25,0xD8,0xEA,0x90,0xE1,0xB9,0x94,0xB9,0xD1,0xC9,0xE5,0x41,0xBD,0xA5,0xB9,0xD1,0xCD,0x4D,0x09,0xE0,
0xA0,0x0B,0x19,0x9E,0xCB,0xD8,0x5B,0x9D,0x1B,0x5D,0x99,0xDC,0xDC,0x94,0x00,0x0F,0x00,0x00,0x00,0x00,
0x79,0x18,0x00,0x00,0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,0x14,0x01,0x3D,0x88,
0x43,0x38,0x84,0xC3,0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,0x00,0x0F,0xED,0x10,
0x0E,0xF4,0x80,0x0E,0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,0x05,0x3D,0x88,0x43,
0x38,0x84,0x83,0x1B,0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,0x74,0x70,0x07,0x7B,
0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xCC,0x11,
0x0E,0xEC,0x90,0x0E,0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,0x33,0x10,0xC4,0x1D,
0xDE,0x21,0x1C,0xD8,0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,0xD0,0x43,0x39,0xB4,
0x03,0x3C,0xBC,0x83,0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,0x07,0x37,0x68,0x87,
0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xF8,0x05,0x76,0x78,
0x87,0x77,0x80,0x87,0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2C,0xEE,0xF0,0x0E,
0xEE,0xE0,0x0E,0xF5,0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,0xCC,0xA1,0x1C,0xE4,
0xA1,0x1C,0xDC,0x61,0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,0x43,0x39,0xC8,0x43,
0x39,0x98,0x43,0x39,0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,0x94,0xC3,0x2F,0xBC,
0x83,0x3C,0xFC,0x82,0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,0x03,0x72,0x10,0x87,
0x73,0x70,0x03,0x7B,0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,0x98,0x81,0x3C,0xE4,
0x80,0x0F,0x6E,0x40,0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x12,0x00,0x00,0x00,
0x46,0x20,0x0D,0x97,0xEF,0x3C,0xBE,0x10,0x11,0xC0,0x44,0x84,0x40,0x33,0x2C,0x84,0x05,0x4C,0xC3,0xE5,
0x3B,0x8F,0xBF,0x38,0xC0,0x20,0x36,0x0F,0x35,0xF9,0xC5,0x6D,0xDB,0x00,0x34,0x5C,0xBE,0xF3,0xF8,0x12,
0xC0,0x3C,0x0B,0xE1,0x17,0xB7,0x6D,0x02,0xD5,0x70,0xF9,0xCE,0xE3,0x4B,0x93,0x13,0x11,0x28,0x35,0x3D,
0xD4,0xE4,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x41,0x53,0x48,
0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x40,0x62,0x49,0x8C,0x9B,0x86,0xEE,0x5C,0xCE,0x55,0xED,
0xA7,0x1A,0xB9,0x7D,0x44,0x58,0x49,0x4C,0xC0,0x06,0x00,0x00,0x60,0x00,0x00,0x00,0xB0,0x01,0x00,0x00,
0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,0x10,0x00,0x00,0x00,0xA8,0x06,0x00,0x00,0x42,0x43,0xC0,0xDE,
0x21,0x0C,0x00,0x00,0xA7,0x01,0x00,0x00,0x0B,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,
0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,
0x1E,0x04,0x8B,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0B,0x42,0xA4,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,
0x0A,0x32,0x52,0x88,0x48,0x90,0x14,0x20,0x43,0x46,0x88,0xA5,0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,
0x91,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,0xE1,0x83,0xE5,0x8A,0x04,0x29,0x46,0x06,0x51,0x18,0x00,0x00,
0x08,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,0xFF,0xFF,0xFF,0x07,0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,
0xFF,0xFF,0x03,0x20,0x6D,0x30,0x86,0xFF,0xFF,0xFF,0xFF,0x1F,0x00,0x09,0xA8,0x00,0x49,0x18,0x00,0x00,
0x03,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,0x4C,0x08,0x06,0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00,
0x43,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,0x93,0x22,0xA4,0x84,0x04,0x93,0x22,0xE3,
0x84,0xA1,0x90,0x14,0x12,0x4C,0x8A,0x8C,0x0B,0x84,0xA4,0x4C,0x10,0x68,0x23,0x00,0x25,0x00,0x14,0x66,
0x00,0xE6,0x08,0xC0,0x60,0x8E,0x00,0x29,0xC6,0x20,0x84,0x14,0x42,0xA6,0x18,0x80,0x10,0x52,0x06,0xA1,
0x9B,0x86,0xCB,0x9F,0xB0,0x87,0x90,0xFC,0x95,0x90,0x56,0x62,0xF2,0x8B,0xDB,0x46,0xC5,0x18,0x63,0x10,
0x2A,0xF7,0x0C,0x97,0x3F,0x61,0x0F,0x21,0xF9,0x21,0xD0,0x0C,0x0B,0x81,0x82,0x55,0x18,0x45,0x18,0x1B,
0x63,0x0C,0x42,0xC8,0xA0,0x36,0x47,0x10,0x14,0x83,0x91,0x42,0xC8,0x23,0x38,0x10,0x30,0x8C,0x40,0x0C,
0x33,0xB5,0xC1,0x38,0xB0,0x43,0x38,0xCC,0xC3,0x3C,0xB8,0x01,0x2D,0x94,0x03,0x3E,0xD0,0x43,0x3D,0xC8,
0x43,0x39,0xC8,0x01,0x29,0xF0,0x81,0x3D,0x94,0xC3,0x38,0xD0,0xC3,0x3B,0xC8,0x03,0x1F,0x98,0x03,0x3B,
0xBC,0x43,0x38,0xD0,0x03,0x1B,0x80,0x01,0x1D,0xF8,0x01,0x18,0xF8,0x81,0x1E,0xE8,0x41,0x3B,0xA4,0x03,
0x3C,0xCC,0xC3,0x2F,0xD0,0x43,0x3E,0xC0,0x43,0x39,0xA0,0x80,0xCC,0x24,0x06,0xE3,0xC0,0x0E,0xE1,0x30,
0x0F,0xF3,0xE0,0x06,0xB4,0x50,0x0E,0xF8,0x40,0x0F,0xF5,0x20,0x0F,0xE5,0x20,0x07,0xA4,0xC0,0x07,0xF6,
0x50,0x0E,0xE3,0x40,0x0F,0xEF,0x20,0x0F,0x7C,0x60,0x0E,0xEC,0xF0,0x0E,0xE1,0x40,0x0F,0x6C,0x00,0x06,
0x74,0xE0,0x07,0x60,0xE0,0x07,0x48,0x98,0x94,0xEA,0x4D,0xD2,0x14,0x51,0xC2,0xE4,0xB3,0x00,0xF3,0x2C,
0x44,0xC4,0x4E,0xC0,0x44,0xA0,0x80,0xD0,0x4D,0x04,0x02,0x00,0x13,0x14,0x72,0xC0,0x87,0x74,0x60,0x87,
0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,0x0E,0x6D,0xD0,0x0E,0x7A,0x50,0x0E,0x6D,
0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0xA0,0x07,0x73,0x20,0x07,
0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xD0,
0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x76,0x40,0x07,0x7A,0x60,0x07,0x74,
0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,0x60,0x0E,0x73,0x20,0x07,0x7A,0x30,0x07,
0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,0x6D,0xE0,0x0E,0x78,0xA0,0x07,0x71,0x60,
0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x79,0x10,0x20,
0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF2,0x34,0x40,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x30,0xE4,0x81,0x80,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x0B,0x04,0x00,0x00,
0x0E,0x00,0x00,0x00,0x32,0x1E,0x98,0x14,0x19,0x11,0x4C,0x90,0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0x22,
0x25,0x30,0x02,0x50,0x10,0xC5,0x50,0x04,0x25,0x51,0x06,0xE5,0x41,0xA5,0x24,0x46,0x00,0x8A,0xA0,0x10,
0x0A,0x84,0xEC,0x0C,0x00,0xE1,0x19,0x00,0xCA,0x63,0x21,0x06,0x11,0x08,0x04,0xF2,0x3C,0x00,0x00,0x00,
0x79,0x18,0x00,0x00,0x58,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,0x46,0x02,0x13,0xC4,0x8E,0x0C,0x6F,0xEC,
0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,0x05,0x07,0x04,0x45,0x8C,0xE6,0x26,0x26,
0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x88,0x62,0x82,0x40,0x18,0x1B,0x84,0x81,0x98,0x20,
0x10,0xC7,0x06,0x61,0x30,0x28,0xC0,0xCD,0x4D,0x10,0x08,0x64,0xC3,0x80,0x24,0xC4,0x04,0xC1,0x92,0x08,
0x4C,0x10,0x88,0x64,0x82,0x40,0x28,0x1B,0x04,0xC2,0xD9,0x90,0x10,0x0B,0xD3,0x10,0x43,0x43,0x3C,0x1B,
0x02,0x68,0x82,0x80,0x4D,0x1B,0x10,0x42,0x62,0x1A,0x62,0x20,0x80,0x0D,0xC1,0xB4,0x81,0x88,0x00,0x80,
0x9A,0x20,0x64,0xD4,0x86,0xC0,0x9A,0x20,0x08,0x00,0x89,0xB6,0xB0,0x34,0x37,0x22,0x54,0x45,0x58,0x43,
0x4F,0x4F,0x52,0x44,0x13,0x84,0xA2,0x99,0x20,0x14,0xCE,0x86,0x80,0x98,0x20,0x14,0xCF,0x04,0xA1,0x80,
0x26,0x08,0xC4,0x32,0x41,0x20,0x98,0x0D,0x02,0x18,0x84,0xC1,0x86,0x85,0xD0,0x36,0xAE,0xF3,0x86,0x8F,
0xE0,0xC4,0x60,0x43,0x30,0x6C,0x10,0xC0,0x00,0x0C,0x36,0x2C,0x83,0xB6,0x71,0x64,0xE0,0x0D,0xDE,0xC0,
0x95,0xC1,0x06,0x61,0x0C,0xCC,0x80,0xC9,0x94,0xD5,0x17,0x55,0x98,0xDC,0x59,0x19,0xDD,0x04,0xA1,0x88,
0x36,0x2C,0x04,0x1A,0x6C,0x69,0xD0,0x71,0xC3,0x47,0x70,0x62,0xB0,0x21,0x50,0x83,0x0D,0xC3,0x19,0xAC,
0x01,0xB0,0xA1,0xC0,0x32,0x36,0xA8,0x80,0x2A,0x6C,0x6C,0x76,0x6D,0x2E,0x69,0x64,0x65,0x6E,0x74,0x53,
0x82,0xA0,0x0A,0x19,0x9E,0x8B,0x5D,0x99,0xDC,0x5C,0xDA,0x9B,0xDB,0x94,0x80,0x68,0x42,0x86,0xE7,0x62,
0x17,0xC6,0x66,0x57,0x26,0x37,0x25,0x30,0xEA,0x90,0xE1,0xB9,0xCC,0xA1,0x85,0x91,0x95,0xC9,0x35,0xBD,
0x91,0x95,0xB1,0x4D,0x09,0x92,0x32,0x64,0x78,0x2E,0x72,0x65,0x73,0x6F,0x75,0x72,0x63,0x65,0x73,0x53,
0x02,0xAA,0x0E,0x19,0x9E,0x8B,0x5D,0x5A,0xD9,0x5D,0x12,0xD9,0x14,0x5D,0x18,0x5D,0xD9,0x94,0xC0,0xAA,
0x43,0x86,0xE7,0x52,0xE6,0x46,0x27,0x97,0x07,0xF5,0x96,0xE6,0x46,0x37,0x37,0x25,0x60,0x03,0x00,0x00,
0x79,0x18,0x00,0x00,0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,0x14,0x01,0x3D,0x88,
0x43,0x38,0x84,0xC3,0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,0x00,0x0F,0xED,0x10,
0x0E,0xF4,0x80,0x0E,0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,0x05,0x3D,0x88,0x43,
0x38,0x84,0x83,0x1B,0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,0x74,0x70,0x07,0x7B,
0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xCC,0x11,
0x0E,0xEC,0x90,0x0E,0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,0x33,0x10,0xC4,0x1D,
0xDE,0x21,0x1C,0xD8,0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,0xD0,0x43,0x39,0xB4,
0x03,0x3C,0xBC,0x83,0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,0x07,0x37,0x68,0x87,
0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xF8,0x05,0x76,0x78,
0x87,0x77,0x80,0x87,0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2C,0xEE,0xF0,0x0E,
0xEE,0xE0,0x0E,0xF5,0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,0xCC,0xA1,0x1C,0xE4,
0xA1,0x1C,0xDC,0x61,0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,0x43,0x39,0xC8,0x43,
0x39,0x98,0x43,0x39,0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,0x94,0xC3,0x2F,0xBC,
0x83,0x3C,0xFC,0x82,0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,0x03,0x72,0x10,0x87,
0x73,0x70,0x03,0x7B,0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,0x98,0x81,0x3C,0xE4,
0x80,0x0F,0x6E,0x40,0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x12,0x00,0x00,0x00,
0x46,0x20,0x0D,0x97,0xEF,0x3C,0xBE,0x10,0x11,0xC0,0x44,0x84,0x40,0x33,0x2C,0x84,0x05,0x4C,0xC3,0xE5,
0x3B,0x8F,0xBF,0x38,0xC0,0x20,0x36,0x0F,0x35,0xF9,0xC5,0x6D,0xDB,0x00,0x34,0x5C,0xBE,0xF3,0xF8,0x12,
0xC0,0x3C,0x0B,0xE1,0x17,0xB7,0x6D,0x02,0xD5,0x70,0xF9,0xCE,0xE3,0x4B,0x93,0x13,0x11,0x28,0x35,0x3D,
0xD4,0xE4,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,0x00,0x00,0x61,0x20,0x00,0x00,0x3B,0x00,0x00,0x00,
0x13,0x04,0x41,0x2C,0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0xF4,0x46,0x00,0x88,0xCC,0x00,0x14,0x42,
0x29,0x94,0x5C,0xE1,0x51,0x29,0x83,0x12,0xA0,0x31,0x03,0x00,0x23,0x06,0x09,0x00,0x82,0x60,0x00,0x69,
0x05,0x84,0x61,0xC9,0x88,0x41,0x02,0x80,0x20,0x18,0x40,0x9B,0x41,0x64,0x99,0x32,0x62,0x90,0x00,0x20,
0x08,0x06,0xC6,0x97,0x6C,0x9A,0xA4,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x01,0x06,0x0A,0xB7,0x15,0xCB,
0x88,0x41,0x02,0x80,0x20,0x18,0x18,0x61,0xB0,0x70,0x1C,0xC5,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x21,
0x06,0x4C,0xD7,0x1D,0xCD,0x88,0x41,0x02,0x80,0x20,0x18,0x18,0x63,0xD0,0x78,0x5E,0xE5,0x8C,0x18,0x24,
0x00,0x08,0x82,0x81,0x41,0x06,0xCE,0xF7,0x29,0xCF,0x88,0xC1,0x03,0x80,0x20,0x18,0x34,0x63,0xC0,0x20,
0x87,0x51,0x24,0x09,0x18,0x80,0x01,0x94,0x8C,0x26,0x04,0xC0,0x68,0x82,0x10,0x8C,0x26,0x0C,0xC2,0x68,
0x02,0x31,0x18,0x91,0xC8,0xC7,0x88,0x44,0x3E,0x46,0x24,0xF2,0x31,0x22,0x91,0xCF,0x88,0x41,0x02,0x80,
0x20,0x18,0x20,0x6D,0x70,0xA5,0x41,0x1A,0x84,0x01,0x31,0x62,0x90,0x00,0x20,0x08,0x06,0x48,0x1B,0x5C,
0x69,0x90,0x06,0xD3,0x30,0x62,0x90,0x00,0x20,0x08,0x06,0x48,0x1B,0x5C,0x69,0x90,0x06,0x60,0x20,0x8C,
0x18,0x24,0x00,0x08,0x82,0x01,0xD2,0x06,0x57,0x1A,0xA4,0x01,0x15,0x20,0x00,0x00,0x00,0x00,0x00,0x00,
};
alignas(uint32_t) static const unsigned char shader_vert_spirv[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x2B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x0F,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x05,0x00,0x00,0x00,0x58,0x02,0x00,0x00,0x05,0x00,0x09,0x00,
0x08,0x00,0x00,0x00,0x74,0x79,0x70,0x65,0x2E,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,
0x6B,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x00,0x00,0x06,0x00,0x06,0x00,0x08,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x00,0x00,0x00,0x05,0x00,0x08,0x00,
0x09,0x00,0x00,0x00,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,
0x73,0x66,0x6F,0x72,0x6D,0x00,0x00,0x00,0x05,0x00,0x09,0x00,0x0A,0x00,0x00,0x00,0x74,0x79,0x70,0x65,
0x2E,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,
0x74,0x65,0x00,0x00,0x06,0x00,0x06,0x00,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x72,0x61,0x6E,
0x73,0x6C,0x61,0x74,0x65,0x00,0x00,0x00,0x05,0x00,0x08,0x00,0x0B,0x00,0x00,0x00,0x55,0x6E,0x69,0x66,
0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x00,0x00,0x00,
0x05,0x00,0x07,0x00,0x02,0x00,0x00,0x00,0x69,0x6E,0x2E,0x76,0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,
0x4F,0x52,0x44,0x30,0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,0x03,0x00,0x00,0x00,0x69,0x6E,0x2E,0x76,
0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,
0x04,0x00,0x00,0x00,0x69,0x6E,0x2E,0x76,0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x32,
0x00,0x00,0x00,0x00,0x05,0x00,0x07,0x00,0x05,0x00,0x00,0x00,0x6F,0x75,0x74,0x2E,0x76,0x61,0x72,0x2E,
0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x00,0x00,0x00,0x05,0x00,0x07,0x00,0x06,0x00,0x00,0x00,
0x6F,0x75,0x74,0x2E,0x76,0x61,0x72,0x2E,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x00,0x00,0x00,
0x05,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x07,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x02,0x00,0x00,0x00,
0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x03,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x05,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x06,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x09,0x00,0x00,0x00,
0x22,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x21,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x48,0x00,0x04,0x00,
0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x08,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x0A,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x15,0x00,0x04,0x00,
0x0C,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,0x0C,0x00,0x00,0x00,
0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x0E,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
0x2B,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,
0x0E,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x17,0x00,0x04,0x00,0x11,0x00,0x00,0x00,
0x0E,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x18,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x11,0x00,0x00,0x00,
0x04,0x00,0x00,0x00,0x1E,0x00,0x03,0x00,0x08,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x20,0x00,0x04,0x00,
0x13,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x14,0x00,0x00,0x00,
0x0E,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x1E,0x00,0x03,0x00,0x0A,0x00,0x00,0x00,0x14,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x15,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x20,0x00,0x04,0x00,
0x16,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x17,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x11,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x19,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x14,0x00,0x00,0x00,
0x13,0x00,0x02,0x00,0x1A,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x1B,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x1C,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x20,0x00,0x04,0x00,
0x1D,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x13,0x00,0x00,0x00,
0x09,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x15,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x16,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x3B,0x00,0x04,0x00,0x17,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x16,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x18,0x00,0x00,0x00,
0x05,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x19,0x00,0x00,0x00,0x06,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x36,0x00,0x05,0x00,0x1A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,
0xF8,0x00,0x02,0x00,0x1E,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x11,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x41,0x00,0x05,0x00,
0x1C,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x14,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x14,0x00,0x00,0x00,
0x24,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x0E,0x00,0x00,0x00,
0x25,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x0E,0x00,0x00,0x00,
0x26,0x00,0x00,0x00,0x24,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x07,0x00,0x11,0x00,0x00,0x00,
0x27,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x10,0x00,0x00,0x00,
0x41,0x00,0x05,0x00,0x1D,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x12,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x90,0x00,0x05,0x00,
0x11,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,0x27,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,
0x05,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x21,0x00,0x00,0x00,
0x3E,0x00,0x03,0x00,0x07,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
};
alignas(uint32_t) static const unsigned char shader_vert_msl[] = {
0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x6D,0x65,0x74,0x61,0x6C,0x5F,0x73,0x74,0x64,0x6C,
0x69,0x62,0x3E,0x0A,0x23,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x20,0x3C,0x73,0x69,0x6D,0x64,0x2F,0x73,
0x69,0x6D,0x64,0x2E,0x68,0x3E,0x0A,0x0A,0x75,0x73,0x69,0x6E,0x67,0x20,0x6E,0x61,0x6D,0x65,0x73,0x70,
0x61,0x63,0x65,0x20,0x6D,0x65,0x74,0x61,0x6C,0x3B,0x0A,0x0A,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x74,
0x79,0x70,0x65,0x5F,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,
0x73,0x66,0x6F,0x72,0x6D,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x78,0x34,
0x20,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x73,0x74,0x72,0x75,
0x63,0x74,0x20,0x74,0x79,0x70,0x65,0x5F,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,
0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,
0x74,0x32,0x20,0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x73,0x74,
0x72,0x75,0x63,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x6F,0x75,0x74,0x0A,0x7B,0x0A,0x20,0x20,0x20,
0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x20,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,
0x4F,0x4F,0x52,0x44,0x30,0x20,0x5B,0x5B,0x75,0x73,0x65,0x72,0x28,0x6C,0x6F,0x63,0x6E,0x30,0x29,0x5D,
0x5D,0x3B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x32,0x20,0x6F,0x75,0x74,0x5F,0x76,0x61,
0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x20,0x5B,0x5B,0x75,0x73,0x65,0x72,0x28,0x6C,
0x6F,0x63,0x6E,0x31,0x29,0x5D,0x5D,0x3B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x20,
0x67,0x6C,0x5F,0x50,0x6F,0x73,0x69,0x74,0x69,0x6F,0x6E,0x20,0x5B,0x5B,0x70,0x6F,0x73,0x69,0x74,0x69,
0x6F,0x6E,0x5D,0x5D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x73,0x74,0x72,0x75,0x63,0x74,0x20,0x6D,0x61,0x69,
0x6E,0x30,0x5F,0x69,0x6E,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x32,0x20,0x69,
0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x20,0x5B,0x5B,0x61,0x74,
0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x30,0x29,0x5D,0x5D,0x3B,0x0A,0x20,0x20,0x20,0x20,0x66,0x6C,
0x6F,0x61,0x74,0x34,0x20,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,
0x31,0x20,0x5B,0x5B,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,0x31,0x29,0x5D,0x5D,0x3B,0x0A,
0x20,0x20,0x20,0x20,0x66,0x6C,0x6F,0x61,0x74,0x32,0x20,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,
0x58,0x43,0x4F,0x4F,0x52,0x44,0x32,0x20,0x5B,0x5B,0x61,0x74,0x74,0x72,0x69,0x62,0x75,0x74,0x65,0x28,
0x32,0x29,0x5D,0x5D,0x3B,0x0A,0x7D,0x3B,0x0A,0x0A,0x76,0x65,0x72,0x74,0x65,0x78,0x20,0x6D,0x61,0x69,
0x6E,0x30,0x5F,0x6F,0x75,0x74,0x20,0x6D,0x61,0x69,0x6E,0x30,0x28,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x69,
0x6E,0x20,0x69,0x6E,0x20,0x5B,0x5B,0x73,0x74,0x61,0x67,0x65,0x5F,0x69,0x6E,0x5D,0x5D,0x2C,0x20,0x63,
0x6F,0x6E,0x73,0x74,0x61,0x6E,0x74,0x20,0x74,0x79,0x70,0x65,0x5F,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,
0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x26,0x20,0x55,0x6E,0x69,0x66,
0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x20,0x5B,0x5B,
0x62,0x75,0x66,0x66,0x65,0x72,0x28,0x30,0x29,0x5D,0x5D,0x2C,0x20,0x63,0x6F,0x6E,0x73,0x74,0x61,0x6E,
0x74,0x20,0x74,0x79,0x70,0x65,0x5F,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,
0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x26,0x20,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,0x42,0x6C,0x6F,
0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x20,0x5B,0x5B,0x62,0x75,0x66,0x66,0x65,0x72,
0x28,0x31,0x29,0x5D,0x5D,0x29,0x0A,0x7B,0x0A,0x20,0x20,0x20,0x20,0x6D,0x61,0x69,0x6E,0x30,0x5F,0x6F,
0x75,0x74,0x20,0x6F,0x75,0x74,0x20,0x3D,0x20,0x7B,0x7D,0x3B,0x0A,0x20,0x20,0x20,0x20,0x6F,0x75,0x74,
0x2E,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x20,0x3D,
0x20,0x69,0x6E,0x2E,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,
0x3B,0x0A,0x20,0x20,0x20,0x20,0x6F,0x75,0x74,0x2E,0x6F,0x75,0x74,0x5F,0x76,0x61,0x72,0x5F,0x54,0x45,
0x58,0x43,0x4F,0x4F,0x52,0x44,0x31,0x20,0x3D,0x20,0x69,0x6E,0x2E,0x69,0x6E,0x5F,0x76,0x61,0x72,0x5F,
0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x32,0x3B,0x0A,0x20,0x20,0x20,0x20,0x6F,0x75,0x74,0x2E,0x67,
0x6C,0x5F,0x50,0x6F,0x73,0x69,0x74,0x69,0x6F,0x6E,0x20,0x3D,0x20,0x55,0x6E,0x69,0x66,0x6F,0x72,0x6D,
0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x2E,0x54,0x72,0x61,0x6E,0x73,
0x66,0x6F,0x72,0x6D,0x20,0x2A,0x20,0x66,0x6C,0x6F,0x61,0x74,0x34,0x28,0x69,0x6E,0x2E,0x69,0x6E,0x5F,
0x76,0x61,0x72,0x5F,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x30,0x20,0x2B,0x20,0x55,0x6E,0x69,0x66,
0x6F,0x72,0x6D,0x42,0x6C,0x6F,0x63,0x6B,0x54,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x2E,0x54,0x72,
0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x2C,0x20,0x30,0x2E,0x30,0x2C,0x20,0x31,0x2E,0x30,0x29,0x3B,0x0A,
0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6E,0x20,0x6F,0x75,0x74,0x3B,0x0A,0x7D,0x0A,0x0A,
};
alignas(uint32_t) static const unsigned char shader_vert_dxil[] = {
0x44,0x58,0x42,0x43,0x41,0x89,0x95,0x46,0xEE,0xBB,0xC2,0x51,0x0B,0xA5,0x2F,0x16,0x3D,0x58,0x9E,0xDD,
0x01,0x00,0x00,0x00,0x9C,0x11,0x00,0x00,0x07,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x4C,0x00,0x00,0x00,
0xC8,0x00,0x00,0x00,0x50,0x01,0x00,0x00,0xA4,0x02,0x00,0x00,0xB4,0x09,0x00,0x00,0xD0,0x09,0x00,0x00,
0x53,0x46,0x49,0x30,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x49,0x53,0x47,0x31,
0x74,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0F,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x68,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x00,0x00,0x00,
0x4F,0x53,0x47,0x31,0x80,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x68,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x71,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,
0x00,0x53,0x56,0x5F,0x50,0x6F,0x73,0x69,0x74,0x69,0x6F,0x6E,0x00,0x00,0x00,0x00,0x50,0x53,0x56,0x30,
0x4C,0x01,0x00,0x00,0x34,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x03,0x03,0x00,0x03,
0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2E,0x00,0x00,0x00,
0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x34,0x00,0x00,0x00,
0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x54,
0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x54,0x45,0x58,0x43,0x4F,0x4F,0x52,0x44,0x00,0x54,0x45,0x58,
0x43,0x4F,0x4F,0x52,0x44,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x42,0x00,0x03,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x01,0x44,0x00,
0x03,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x02,0x42,0x00,0x03,0x00,0x00,0x00,
0x1C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x44,0x00,0x03,0x02,0x00,0x00,0x25,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x01,0x01,0x42,0x00,0x03,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x44,0x03,0x03,0x04,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x08,0x00,0x00,0x00,
0x10,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x54,0x41,0x54,
0x08,0x07,0x00,0x00,0x60,0x00,0x01,0x00,0xC2,0x01,0x00,0x00,0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,
0x10,0x00,0x00,0x00,0xF0,0x06,0x00,0x00,0x42,0x43,0xC0,0xDE,0x21,0x0C,0x00,0x00,0xB9,0x01,0x00,0x00,
0x0B,0x82,0x20,0x00,0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,
0x06,0x10,0x32,0x39,0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,0x1E,0x04,0x8B,0x62,0x80,0x14,0x45,0x02,
0x42,0x92,0x0B,0x42,0xA4,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,0x0A,0x32,0x52,0x88,0x48,0x90,0x14,0x20,
0x43,0x46,0x88,0xA5,0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,0x91,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,
0xE1,0x83,0xE5,0x8A,0x04,0x29,0x46,0x06,0x51,0x18,0x00,0x00,0x08,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,
0xFF,0xFF,0xFF,0x07,0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,0xFF,0xFF,0x03,0x20,0x6D,0x30,0x86,0xFF,
0xFF,0xFF,0xFF,0x1F,0x00,0x09,0xA8,0x00,0x49,0x18,0x00,0x00,0x03,0x00,0x00,0x00,0x13,0x82,0x60,0x42,
0x20,0x4C,0x08,0x06,0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00,0x2E,0x00,0x00,0x00,0x32,0x22,0x48,0x09,
0x20,0x64,0x85,0x04,0x93,0x22,0xA4,0x84,0x04,0x93,0x22,0xE3,0x84,0xA1,0x90,0x14,0x12,0x4C,0x8A,0x8C,
0x0B,0x84,0xA4,0x4C,0x10,0x74,0x23,0x00,0x25,0x00,0x14,0x66,0x00,0xE6,0x08,0xC0,0x60,0x8E,0x00,0x29,
0xC6,0x20,0x84,0x14,0x42,0xA6,0x18,0x80,0x10,0x52,0x06,0xA1,0xA3,0x86,0xCB,0x9F,0xB0,0x87,0x90,0x7C,
0x6E,0xA3,0x8A,0x95,0x98,0xFC,0xE2,0xB6,0x11,0x31,0xC6,0x18,0x54,0xEE,0x19,0x2E,0x7F,0xC2,0x1E,0x42,
0xF2,0x43,0xA0,0x19,0x16,0x02,0x05,0xAB,0x10,0x8A,0x30,0x42,0xAD,0x14,0x83,0x8C,0x31,0xE8,0xCD,0x11,
0x04,0xC5,0x60,0xA4,0x10,0x12,0x49,0x0E,0x04,0x0C,0x23,0x10,0x43,0x12,0xD4,0x2B,0x83,0xC3,0x91,0xA6,
0x05,0xC0,0x1C,0x6A,0xF2,0x27,0xEC,0x21,0x7E,0xB7,0x41,0x0A,0x27,0x62,0xB6,0xC5,0x11,0x94,0x36,0x02,
0x1A,0xA9,0x70,0x22,0x06,0x05,0x96,0xEE,0x30,0x82,0x30,0x9C,0x36,0x61,0x0F,0xF1,0xBB,0x0D,0x52,0x38,
0x11,0xB3,0x2D,0x8E,0xA0,0xB4,0x11,0xD0,0x48,0x0B,0x30,0x11,0x28,0xC8,0xA4,0x93,0x81,0x00,0x00,0x00,
0x13,0x14,0x72,0xC0,0x87,0x74,0x60,0x87,0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,
0x0E,0x6D,0xD0,0x0E,0x7A,0x50,0x0E,0x6D,0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x90,0x0E,0x71,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,
0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,
0x0E,0x76,0x40,0x07,0x7A,0x60,0x07,0x74,0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,
0x60,0x0E,0x73,0x20,0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,
0x6D,0xE0,0x0E,0x78,0xA0,0x07,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,
0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x0C,0x79,0x10,0x20,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF2,0x34,
0x40,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0xE4,0x79,0x80,0x00,0x08,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x60,0xC8,0x23,0x01,0x01,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,
0x14,0x00,0x00,0x00,0x32,0x1E,0x98,0x14,0x19,0x11,0x4C,0x90,0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0x22,
0x25,0x30,0x02,0x50,0x0C,0x05,0x18,0x50,0x10,0x65,0x50,0x0E,0x25,0x51,0x1A,0x45,0x50,0x08,0xE5,0x51,
0x14,0xA5,0x46,0xA5,0x24,0x46,0x00,0x8A,0xA0,0x10,0xCA,0x80,0xEE,0x0C,0x00,0xE1,0x19,0x00,0xD2,0x33,
0x00,0xB4,0x67,0x00,0x88,0x8F,0xC5,0x28,0x0C,0x88,0x0F,0x20,0x3E,0x00,0x40,0x20,0x10,0x08,0x04,0x06,
0x00,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0xAD,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,0x46,0x02,0x13,0xC4,
0x8E,0x0C,0x6F,0xEC,0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,0x05,0x07,0x04,0x45,
0x8C,0xE6,0x26,0x26,0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x08,0x63,0x82,0x40,0x1C,0x1B,
0x84,0x81,0xD8,0x20,0x10,0x04,0x05,0xBB,0xB9,0x09,0x02,0x81,0x6C,0x18,0x0E,0x84,0x98,0x20,0x60,0x1A,
0x35,0xBA,0x3C,0xB8,0xB2,0xAF,0x2A,0xB7,0x34,0xB3,0x37,0xB9,0x36,0x21,0xB6,0xB7,0xB1,0x35,0x2A,0xB9,
0x30,0xB7,0x39,0xB3,0x37,0xB9,0xB6,0x09,0x02,0x91,0x6C,0x40,0x08,0x65,0x19,0x88,0x81,0x01,0x26,0x08,
0x1B,0x47,0x8D,0x2E,0x0F,0xAE,0xEC,0xAB,0xCA,0x2D,0xCD,0xEC,0x4D,0xAE,0x4D,0x88,0xED,0x6D,0x6C,0x8D,
0x4A,0x2E,0xCC,0x6D,0x8E,0x2D,0x8C,0xAE,0x6C,0x82,0x40,0x28,0x1B,0x90,0xC1,0x79,0x86,0x61,0x80,0x80,
0x0D,0x42,0x13,0x6D,0x20,0x00,0x40,0x02,0x26,0x08,0xDA,0xC6,0xAF,0xCA,0x2D,0xCD,0xEC,0x4D,0xAE,0x4D,
0x88,0xED,0x6D,0x6C,0x8D,0x4A,0x2E,0xCC,0x6D,0x8E,0x2D,0x8C,0xAE,0xEC,0x8B,0x4A,0x2E,0xCC,0x6D,0x8E,
0x2D,0x8C,0xAE,0x6C,0x82,0x40,0x2C,0x13,0x04,0x82,0x99,0x20,0x10,0xCD,0x04,0x81,0x70,0x26,0x08,0xC4,
0xB3,0x01,0x41,0x2A,0x8B,0xB8,0xB0,0x4C,0xDB,0x20,0x40,0xDB,0x04,0xE1,0xCA,0xF8,0x55,0xB9,0xA5,0x99,
0xBD,0xC9,0xB5,0x09,0xB1,0xBD,0x8D,0xAD,0x51,0xC9,0x85,0xB9,0xCD,0x99,0xBD,0xC9,0xB5,0x7D,0x51,0xC9,
0x85,0xB9,0xCD,0x99,0xBD,0xC9,0xB5,0x4D,0x10,0x08,0x68,0xC3,0xF0,0x7D,0xDA,0x06,0x04,0xF1,0x34,0x30,
0xB0,0x88,0x0B,0xDB,0x20,0x30,0x61,0xB0,0xA1,0x20,0x28,0xAE,0x13,0x83,0x09,0x82,0x00,0x6C,0x00,0x36,
0x0C,0x44,0x19,0x94,0xC1,0x86,0xC0,0x0C,0x36,0x0C,0x03,0x19,0x9C,0xC1,0x04,0x81,0xEB,0x36,0x04,0x69,
0x40,0xA2,0x2D,0x2C,0xCD,0x8D,0x08,0x55,0x11,0xD6,0xD0,0xD3,0x93,0x14,0xD1,0x04,0xA1,0xA0,0x26,0x08,
0x45,0xB5,0x21,0x20,0x26,0x08,0x85,0xB5,0x41,0xB0,0xAC,0x0D,0x0B,0xC1,0x06,0x6D,0xE0,0x06,0x6F,0xE0,
0x06,0x03,0x1C,0x10,0x6E,0x10,0x07,0x1B,0x82,0x61,0x82,0x50,0x5C,0x13,0x04,0x22,0xDA,0x20,0x58,0x75,
0xB0,0x61,0x19,0xD8,0xA0,0x0D,0xDC,0x60,0x0E,0xDC,0x60,0xA0,0x83,0xC1,0x0D,0xEC,0x60,0x43,0xA0,0x6D,
0x58,0x34,0x36,0x68,0x03,0x37,0xC0,0x03,0x37,0x18,0xE0,0x40,0x73,0x83,0x38,0xD8,0x30,0xC8,0xC1,0x1D,
0xE4,0xC1,0x86,0x85,0x60,0x83,0x36,0x70,0x83,0x37,0x80,0x83,0x81,0x0E,0x08,0x37,0xB0,0x83,0x0D,0xCB,
0xC0,0x06,0x6D,0xE0,0x06,0x73,0x00,0x07,0x03,0x1C,0x0C,0x6E,0x10,0x07,0x5C,0xA6,0xAC,0xBE,0xA0,0xDE,
0xE6,0xD2,0xE8,0xD2,0xDE,0xDC,0x26,0x08,0x05,0xB6,0x61,0xD1,0xFA,0xA0,0x0D,0xFC,0xE0,0x0D,0xE8,0x60,
0xA0,0x03,0xCD,0x0D,0xEC,0x60,0xC3,0xB0,0x07,0x7C,0xF0,0x07,0x1B,0x06,0x3D,0x00,0x05,0x60,0x43,0x41,
0x06,0x6B,0x10,0x0A,0x13,0x40,0xC3,0x8C,0xED,0x2D,0x8C,0x6E,0x6E,0x82,0x40,0x48,0x2C,0xD2,0xDC,0xE6,
0xE8,0xE6,0x26,0x08,0xC4,0x44,0x63,0x2E,0xED,0xEC,0x8B,0x8D,0x8C,0xC6,0x5C,0xDA,0xD9,0xD7,0x1C,0xDD,
0x06,0x64,0x14,0x48,0xA1,0x14,0x4C,0xE1,0x14,0x20,0x54,0x20,0x85,0x2A,0x6C,0x6C,0x76,0x6D,0x2E,0x69,
0x64,0x65,0x6E,0x74,0x53,0x82,0xA0,0x0A,0x19,0x9E,0x8B,0x5D,0x99,0xDC,0x5C,0xDA,0x9B,0xDB,0x94,0x80,
0x68,0x42,0x86,0xE7,0x62,0x17,0xC6,0x66,0x57,0x26,0x37,0x25,0x28,0xEA,0x90,0xE1,0xB9,0xCC,0xA1,0x85,
0x91,0x95,0xC9,0x35,0xBD,0x91,0x95,0xB1,0x4D,0x09,0x90,0x32,0x64,0x78,0x2E,0x72,0x65,0x73,0x6F,0x75,
0x72,0x63,0x65,0x73,0x53,0x02,0xA9,0x12,0x19,0x9E,0x0B,0x5D,0x1E,0x5C,0x59,0x90,0x9B,0xDB,0x1B,0x5D,
0x18,0x5D,0xDA,0x9B,0xDB,0xDC,0x14,0x41,0x0C,0xCE,0xA0,0x0E,0x19,0x9E,0x8B,0x5D,0x5A,0xD9,0x5D,0x12,
0xD9,0x14,0x5D,0x18,0x5D,0xD9,0x94,0x20,0x0D,0xEA,0x90,0xE1,0xB9,0x94,0xB9,0xD1,0xC9,0xE5,0x41,0xBD,
0xA5,0xB9,0xD1,0xCD,0x4D,0x09,0x42,0xA1,0x0B,0x19,0x9E,0xCB,0xD8,0x5B,0x9D,0x1B,0x5D,0x99,0xDC,0xDC,
0x94,0x00,0x15,0x00,0x79,0x18,0x00,0x00,0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,
0x14,0x01,0x3D,0x88,0x43,0x38,0x84,0xC3,0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,
0x00,0x0F,0xED,0x10,0x0E,0xF4,0x80,0x0E,0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,
0x05,0x3D,0x88,0x43,0x38,0x84,0x83,0x1B,0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,
0x74,0x70,0x07,0x7B,0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,
0x87,0x19,0xCC,0x11,0x0E,0xEC,0x90,0x0E,0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,
0x33,0x10,0xC4,0x1D,0xDE,0x21,0x1C,0xD8,0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,
0xD0,0x43,0x39,0xB4,0x03,0x3C,0xBC,0x83,0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,
0x07,0x37,0x68,0x87,0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,
0xF8,0x05,0x76,0x78,0x87,0x77,0x80,0x87,0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,
0x2C,0xEE,0xF0,0x0E,0xEE,0xE0,0x0E,0xF5,0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,
0xCC,0xA1,0x1C,0xE4,0xA1,0x1C,0xDC,0x61,0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,
0x43,0x39,0xC8,0x43,0x39,0x98,0x43,0x39,0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,
0x94,0xC3,0x2F,0xBC,0x83,0x3C,0xFC,0x82,0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,
0x03,0x72,0x10,0x87,0x73,0x70,0x03,0x7B,0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,
0x98,0x81,0x3C,0xE4,0x80,0x0F,0x6E,0x40,0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,
0x18,0x00,0x00,0x00,0x36,0xB0,0x0D,0x97,0xEF,0x3C,0xBE,0x10,0x50,0x45,0x41,0x44,0xA5,0x03,0x0C,0x25,
0x61,0x00,0x02,0xE6,0x17,0xB7,0x6D,0x05,0xD2,0x70,0xF9,0xCE,0xE3,0x0B,0x11,0x01,0x4C,0x44,0x08,0x34,
0xC3,0x42,0x58,0xC0,0x34,0x5C,0xBE,0xF3,0xF8,0x8B,0x03,0x0C,0x62,0xF3,0x50,0x93,0x5F,0xDC,0xB6,0x09,
0x54,0xC3,0xE5,0x3B,0x8F,0x2F,0x4D,0x4E,0x44,0xA0,0xD4,0xF4,0x50,0x93,0x5F,0xDC,0xB6,0x11,0x48,0xC3,
0xE5,0x3B,0x8F,0x3F,0x11,0xD1,0x84,0x00,0x11,0xE6,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,0x00,0x00,
0x00,0x00,0x00,0x00,0x48,0x41,0x53,0x48,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xC5,0xB4,0xCB,
0x0D,0xAD,0xA7,0x7C,0x41,0x76,0x11,0xF5,0xDD,0x93,0xF6,0x75,0x44,0x58,0x49,0x4C,0xC4,0x07,0x00,0x00,
0x60,0x00,0x01,0x00,0xF1,0x01,0x00,0x00,0x44,0x58,0x49,0x4C,0x00,0x01,0x00,0x00,0x10,0x00,0x00,0x00,
0xAC,0x07,0x00,0x00,0x42,0x43,0xC0,0xDE,0x21,0x0C,0x00,0x00,0xE8,0x01,0x00,0x00,0x0B,0x82,0x20,0x00,
0x02,0x00,0x00,0x00,0x13,0x00,0x00,0x00,0x07,0x81,0x23,0x91,0x41,0xC8,0x04,0x49,0x06,0x10,0x32,0x39,
0x92,0x01,0x84,0x0C,0x25,0x05,0x08,0x19,0x1E,0x04,0x8B,0x62,0x80,0x14,0x45,0x02,0x42,0x92,0x0B,0x42,
0xA4,0x10,0x32,0x14,0x38,0x08,0x18,0x4B,0x0A,0x32,0x52,0x88,0x48,0x90,0x14,0x20,0x43,0x46,0x88,0xA5,
0x00,0x19,0x32,0x42,0xE4,0x48,0x0E,0x90,0x91,0x22,0xC4,0x50,0x41,0x51,0x81,0x8C,0xE1,0x83,0xE5,0x8A,
0x04,0x29,0x46,0x06,0x51,0x18,0x00,0x00,0x08,0x00,0x00,0x00,0x1B,0x8C,0xE0,0xFF,0xFF,0xFF,0xFF,0x07,
0x40,0x02,0xA8,0x0D,0x84,0xF0,0xFF,0xFF,0xFF,0xFF,0x03,0x20,0x6D,0x30,0x86,0xFF,0xFF,0xFF,0xFF,0x1F,
0x00,0x09,0xA8,0x00,0x49,0x18,0x00,0x00,0x03,0x00,0x00,0x00,0x13,0x82,0x60,0x42,0x20,0x4C,0x08,0x06,
0x00,0x00,0x00,0x00,0x89,0x20,0x00,0x00,0x2E,0x00,0x00,0x00,0x32,0x22,0x48,0x09,0x20,0x64,0x85,0x04,
0x93,0x22,0xA4,0x84,0x04,0x93,0x22,0xE3,0x84,0xA1,0x90,0x14,0x12,0x4C,0x8A,0x8C,0x0B,0x84,0xA4,0x4C,
0x10,0x74,0x23,0x00,0x25,0x00,0x14,0x66,0x00,0xE6,0x08,0xC0,0x60,0x8E,0x00,0x29,0xC6,0x20,0x84,0x14,
0x42,0xA6,0x18,0x80,0x10,0x52,0x06,0xA1,0xA3,0x86,0xCB,0x9F,0xB0,0x87,0x90,0x7C,0x6E,0xA3,0x8A,0x95,
0x98,0xFC,0xE2,0xB6,0x11,0x31,0xC6,0x18,0x54,0xEE,0x19,0x2E,0x7F,0xC2,0x1E,0x42,0xF2,0x43,0xA0,0x19,
0x16,0x02,0x05,0xAB,0x10,0x8A,0x30,0x42,0xAD,0x14,0x83,0x8C,0x31,0xE8,0xCD,0x11,0x04,0xC5,0x60,0xA4,
0x10,0x12,0x49,0x0E,0x04,0x0C,0x23,0x10,0x43,0x12,0xD4,0x2B,0x83,0xC3,0x91,0xA6,0x05,0xC0,0x1C,0x6A,
0xF2,0x27,0xEC,0x21,0x7E,0xB7,0x41,0x0A,0x27,0x62,0xB6,0xC5,0x11,0x94,0x36,0x02,0x1A,0xA9,0x70,0x22,
0x06,0x05,0x96,0xEE,0x30,0x82,0x30,0x9C,0x36,0x61,0x0F,0xF1,0xBB,0x0D,0x52,0x38,0x11,0xB3,0x2D,0x8E,
0xA0,0xB4,0x11,0xD0,0x48,0x0B,0x30,0x11,0x28,0xC8,0xA4,0x93,0x81,0x00,0x00,0x00,0x13,0x14,0x72,0xC0,
0x87,0x74,0x60,0x87,0x36,0x68,0x87,0x79,0x68,0x03,0x72,0xC0,0x87,0x0D,0xAF,0x50,0x0E,0x6D,0xD0,0x0E,
0x7A,0x50,0x0E,0x6D,0x00,0x0F,0x7A,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0xA0,
0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x78,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x71,0x60,0x07,0x7A,
0x30,0x07,0x72,0xD0,0x06,0xE9,0x30,0x07,0x72,0xA0,0x07,0x73,0x20,0x07,0x6D,0x90,0x0E,0x76,0x40,0x07,
0x7A,0x60,0x07,0x74,0xD0,0x06,0xE6,0x10,0x07,0x76,0xA0,0x07,0x73,0x20,0x07,0x6D,0x60,0x0E,0x73,0x20,
0x07,0x7A,0x30,0x07,0x72,0xD0,0x06,0xE6,0x60,0x07,0x74,0xA0,0x07,0x76,0x40,0x07,0x6D,0xE0,0x0E,0x78,
0xA0,0x07,0x71,0x60,0x07,0x7A,0x30,0x07,0x72,0xA0,0x07,0x76,0x40,0x07,0x43,0x9E,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x3C,0x06,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0C,0x79,0x10,0x20,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0xF2,0x34,0x40,0x00,0x0C,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x30,0xE4,0x79,0x80,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,
0xC8,0x23,0x01,0x01,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x16,0x08,0x00,0x10,0x00,0x00,0x00,
0x32,0x1E,0x98,0x14,0x19,0x11,0x4C,0x90,0x8C,0x09,0x26,0x47,0xC6,0x04,0x43,0x22,0x25,0x30,0x02,0x50,
0x10,0xC5,0x50,0x80,0x01,0x65,0x50,0x1E,0x45,0x40,0xA5,0x24,0x46,0x00,0x8A,0xA0,0x10,0xCA,0x80,0xF0,
0x0C,0x00,0xED,0x19,0x00,0xE2,0x63,0x31,0x0A,0x03,0xE2,0x03,0x88,0x0F,0x00,0x10,0x08,0x04,0x02,0x81,
0x01,0x00,0x00,0x00,0x79,0x18,0x00,0x00,0x61,0x00,0x00,0x00,0x1A,0x03,0x4C,0x90,0x46,0x02,0x13,0xC4,
0x8E,0x0C,0x6F,0xEC,0xED,0x4D,0x0C,0x24,0xC6,0x05,0xC7,0x45,0xA6,0x06,0x46,0xC6,0x05,0x07,0x04,0x45,
0x8C,0xE6,0x26,0x26,0x06,0x67,0x26,0xA7,0x2C,0x65,0x43,0x10,0x4C,0x10,0x08,0x63,0x82,0x40,0x1C,0x1B,
0x84,0x81,0x98,0x20,0x10,0xC8,0x06,0x61,0x30,0x28,0xD8,0xCD,0x4D,0x10,0x88,0x64,0xC3,0x80,0x24,0xC4,
0x04,0x01,0x9B,0x08,0x4C,0x10,0x08,0x65,0x03,0x42,0x2C,0xCC,0x40,0x0C,0x0D,0x30,0x41,0xD8,0xA8,0x0D,
0xC8,0xF0,0x30,0xC3,0x30,0x18,0xC0,0x06,0xC1,0x81,0x36,0x10,0x00,0x10,0x01,0x13,0x04,0xAE,0xDA,0x10,
0x4C,0x13,0x04,0x01,0x20,0xD1,0x16,0x96,0xE6,0x46,0x84,0xAA,0x08,0x6B,0xE8,0xE9,0x49,0x8A,0x68,0x82,
0x50,0x38,0x13,0x84,0xE2,0xD9,0x10,0x10,0x13,0x84,0x02,0x9A,0x20,0x10,0xCB,0x06,0x81,0xE3,0x36,0x2C,
0xC4,0x85,0x65,0x5A,0x36,0x6C,0x44,0xD6,0x6D,0x08,0x86,0x09,0x42,0x11,0x4D,0x10,0x08,0x66,0x83,0xC0,
0x85,0xC1,0x86,0x65,0xB8,0xB0,0xEC,0xCB,0x06,0x30,0x18,0x32,0x31,0x98,0x20,0x10,0xCD,0x86,0x80,0x0C,
0x36,0x2C,0x64,0x70,0x61,0x59,0x19,0x64,0xC3,0x46,0x06,0x59,0xB7,0x61,0xF0,0xC6,0xC0,0x0C,0x36,0x2C,
0xC4,0x85,0x65,0xDA,0x36,0x80,0x01,0x91,0x89,0xC1,0x86,0x65,0xB8,0xB0,0xEC,0xDB,0x86,0x6D,0xC8,0x3A,
0x2E,0x53,0x56,0x5F,0x50,0x6F,0x73,0x69,0x74,0x69,0x6F,0x6E,0x13,0x84,0x42,0xDA,0xB0,0x90,0x81,0x1A,
0x60,0x6B,0xA0,0x81,0xC1,0x00,0x06,0x64,0x90,0x89,0xC1,0x86,0x01,0x0D,0xD2,0x80,0x0D,0x36,0x0C,0x67,
0xD0,0x06,0xC0,0x86,0xA2,0xB2,0xDC,0x40,0x02,0xAA,0xB0,0xB1,0xD9,0xB5,0xB9,0xA4,0x91,0x95,0xB9,0xD1,
0x4D,0x09,0x82,0x2A,0x64,0x78,0x2E,0x76,0x65,0x72,0x73,0x69,0x6F,0x6E,0x53,0x02,0xA2,0x09,0x19,0x9E,
0x8B,0x5D,0x18,0x9B,0x5D,0x99,0xDC,0x94,0xC0,0xA8,0x43,0x86,0xE7,0x32,0x87,0x16,0x46,0x56,0x26,0xD7,
0xF4,0x46,0x56,0xC6,0x36,0x25,0x48,0xCA,0x90,0xE1,0xB9,0xC8,0x95,0xCD,0xBD,0xD5,0xC9,0x8D,0x95,0xCD,
0x4D,0x09,0xA2,0x3A,0x64,0x78,0x2E,0x76,0x69,0x65,0x77,0x49,0x64,0x53,0x74,0x61,0x74,0x65,0x53,0x82,
0xA9,0x0E,0x19,0x9E,0x4B,0x99,0x1B,0x9D,0x5C,0x1E,0xD4,0x5B,0x9A,0x1B,0xDD,0xDC,0x94,0xC0,0x0D,0x00,
0x79,0x18,0x00,0x00,0x4C,0x00,0x00,0x00,0x33,0x08,0x80,0x1C,0xC4,0xE1,0x1C,0x66,0x14,0x01,0x3D,0x88,
0x43,0x38,0x84,0xC3,0x8C,0x42,0x80,0x07,0x79,0x78,0x07,0x73,0x98,0x71,0x0C,0xE6,0x00,0x0F,0xED,0x10,
0x0E,0xF4,0x80,0x0E,0x33,0x0C,0x42,0x1E,0xC2,0xC1,0x1D,0xCE,0xA1,0x1C,0x66,0x30,0x05,0x3D,0x88,0x43,
0x38,0x84,0x83,0x1B,0xCC,0x03,0x3D,0xC8,0x43,0x3D,0x8C,0x03,0x3D,0xCC,0x78,0x8C,0x74,0x70,0x07,0x7B,
0x08,0x07,0x79,0x48,0x87,0x70,0x70,0x07,0x7A,0x70,0x03,0x76,0x78,0x87,0x70,0x20,0x87,0x19,0xCC,0x11,
0x0E,0xEC,0x90,0x0E,0xE1,0x30,0x0F,0x6E,0x30,0x0F,0xE3,0xF0,0x0E,0xF0,0x50,0x0E,0x33,0x10,0xC4,0x1D,
0xDE,0x21,0x1C,0xD8,0x21,0x1D,0xC2,0x61,0x1E,0x66,0x30,0x89,0x3B,0xBC,0x83,0x3B,0xD0,0x43,0x39,0xB4,
0x03,0x3C,0xBC,0x83,0x3C,0x84,0x03,0x3B,0xCC,0xF0,0x14,0x76,0x60,0x07,0x7B,0x68,0x07,0x37,0x68,0x87,
0x72,0x68,0x07,0x37,0x80,0x87,0x70,0x90,0x87,0x70,0x60,0x07,0x76,0x28,0x07,0x76,0xF8,0x05,0x76,0x78,
0x87,0x77,0x80,0x87,0x5F,0x08,0x87,0x71,0x18,0x87,0x72,0x98,0x87,0x79,0x98,0x81,0x2C,0xEE,0xF0,0x0E,
0xEE,0xE0,0x0E,0xF5,0xC0,0x0E,0xEC,0x30,0x03,0x62,0xC8,0xA1,0x1C,0xE4,0xA1,0x1C,0xCC,0xA1,0x1C,0xE4,
0xA1,0x1C,0xDC,0x61,0x1C,0xCA,0x21,0x1C,0xC4,0x81,0x1D,0xCA,0x61,0x06,0xD6,0x90,0x43,0x39,0xC8,0x43,
0x39,0x98,0x43,0x39,0xC8,0x43,0x39,0xB8,0xC3,0x38,0x94,0x43,0x38,0x88,0x03,0x3B,0x94,0xC3,0x2F,0xBC,
0x83,0x3C,0xFC,0x82,0x3B,0xD4,0x03,0x3B,0xB0,0xC3,0x8C,0xC8,0x21,0x07,0x7C,0x70,0x03,0x72,0x10,0x87,
0x73,0x70,0x03,0x7B,0x08,0x07,0x79,0x60,0x87,0x70,0xC8,0x87,0x77,0xA8,0x07,0x7A,0x98,0x81,0x3C,0xE4,
0x80,0x0F,0x6E,0x40,0x0F,0xE5,0xD0,0x0E,0xF0,0x00,0x00,0x00,0x71,0x20,0x00,0x00,0x18,0x00,0x00,0x00,
0x36,0xB0,0x0D,0x97,0xEF,0x3C,0xBE,0x10,0x50,0x45,0x41,0x44,0xA5,0x03,0x0C,0x25,0x61,0x00,0x02,0xE6,
0x17,0xB7,0x6D,0x05,0xD2,0x70,0xF9,0xCE,0xE3,0x0B,0x11,0x01,0x4C,0x44,0x08,0x34,0xC3,0x42,0x58,0xC0,
0x34,0x5C,0xBE,0xF3,0xF8,0x8B,0x03,0x0C,0x62,0xF3,0x50,0x93,0x5F,0xDC,0xB6,0x09,0x54,0xC3,0xE5,0x3B,
0x8F,0x2F,0x4D,0x4E,0x44,0xA0,0xD4,0xF4,0x50,0x93,0x5F,0xDC,0xB6,0x11,0x48,0xC3,0xE5,0x3B,0x8F,0x3F,
0x11,0xD1,0x84,0x00,0x11,0xE6,0x17,0xB7,0x6D,0x00,0x04,0x03,0x20,0x0D,0x00,0x00,0x61,0x20,0x00,0x00,
0x7D,0x00,0x00,0x00,0x13,0x04,0x41,0x2C,0x10,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x44,0x4A,0xA1,0x10,
0x66,0x00,0xCA,0xAE,0xB8,0x4A,0x8E,0x4A,0x09,0x50,0x1C,0x01,0x00,0x00,0x00,0x00,0x23,0x06,0x09,0x00,
0x82,0x60,0x20,0x65,0x83,0x83,0x61,0xC1,0x88,0x41,0x02,0x80,0x20,0x18,0x48,0x1A,0xF1,0x60,0x98,0x30,
0x62,0x90,0x00,0x20,0x08,0x06,0xC6,0x97,0x4C,0x59,0x84,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x01,0x06,
0x0A,0xA5,0x15,0xC9,0x88,0x41,0x02,0x80,0x20,0x18,0x18,0x61,0xB0,0x70,0xDB,0xA4,0x8C,0x18,0x24,0x00,
0x08,0x82,0x81,0x21,0x06,0x4C,0xC7,0x1D,0xCB,0x88,0x41,0x02,0x80,0x20,0x18,0x18,0x63,0xD0,0x78,0x1D,
0xC5,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x41,0x06,0xCE,0xE7,0x4D,0xCD,0x88,0x41,0x02,0x80,0x20,0x18,
0x18,0x65,0xF0,0x7C,0xDF,0xE5,0x8C,0x18,0x24,0x00,0x08,0x82,0x81,0x61,0x06,0x10,0x18,0x80,0xC1,0xF2,
0x8C,0x18,0x1C,0x00,0x08,0x82,0x41,0x53,0x06,0x8F,0x12,0x06,0xA3,0x09,0x01,0x30,0x9A,0x20,0x04,0x26,
0x14,0xF0,0x31,0xA1,0x80,0xCF,0x88,0xC1,0x01,0x80,0x20,0x18,0x34,0x6A,0x40,0x39,0x66,0x30,0x9A,0x10,
0x00,0xA3,0x09,0x42,0x30,0x9A,0x30,0x08,0xA3,0x09,0xC4,0x30,0x62,0x70,0x00,0x20,0x08,0x06,0xCD,0x1B,
0x64,0x13,0x1B,0x8C,0x26,0x04,0xC0,0x68,0x82,0x10,0x8C,0x26,0x0C,0xC2,0x68,0x02,0x31,0x8C,0x18,0x1C,
0x00,0x08,0x82,0x41,0x43,0x07,0x1E,0xC6,0x06,0xA3,0x09,0x01,0x30,0x9A,0x20,0x04,0xA3,0x09,0x83,0x30,
0x9A,0x40,0x0C,0xE6,0x44,0xF2,0x19,0x31,0x40,0x00,0x10,0x04,0x83,0x27,0x0F,0xC6,0x40,0x89,0x02,0x33,
0x02,0xE8,0x18,0x44,0xC9,0x67,0xC4,0x00,0x01,0x40,0x10,0x0C,0x1E,0x3E,0x30,0x03,0x86,0x0A,0x2C,0x40,
0xA0,0x63,0xD2,0x25,0x9F,0x11,0x03,0x04,0x00,0x41,0x30,0x78,0xFE,0x20,0x0D,0x9C,0x2B,0xB0,0x40,0x81,
0x8E,0x51,0x9A,0x7C,0x46,0x0C,0x10,0x00,0x04,0xC1,0xE0,0x11,0x05,0x36,0x80,0xB4,0xC0,0x02,0x06,0x3A,
0x23,0x06,0x09,0x00,0x82,0x60,0x80,0x98,0x82,0x1C,0x84,0x42,0x28,0xE4,0x81,0x19,0x8C,0x18,0x24,0x00,
0x08,0x82,0x01,0x62,0x0A,0x72,0x10,0x0A,0xA1,0xC0,0x06,0x65,0x30,0x62,0x90,0x00,0x20,0x08,0x06,0x88,
0x29,0xC8,0x41,0x28,0x84,0x02,0x1E,0x90,0xC1,0x88,0x41,0x02,0x80,0x20,0x18,0x20,0xA6,0x20,0x07,0xA1,
0x10,0x0A,0x76,0x30,0x06,0x23,0x06,0x09,0x00,0x82,0x60,0x80,0x98,0x82,0x1C,0x88,0x42,0x28,0xE4,0x01,
0x1A,0x8C,0x18,0x24,0x00,0x08,0x82,0x01,0x62,0x0A,0x72,0x20,0x0A,0xA1,0xC0,0x06,0x67,0x30,0x62,0x90,
0x00,0x20,0x08,0x06,0x88,0x29,0xC8,0xC1,0x1E,0x84,0x42,0x1E,0x28,0x23,0x06,0x09,0x00,0x82,0x60,0x80,
0x98,0x82,0x1C,0xEC,0x41,0x28,0xB0,0xC1,0x31,0x62,0x90,0x00,0x20,0x08,0x06,0x88,0x29,0xC8,0xC1,0x1E,
0x84,0x02,0x1E,0x10,0x23,0x06,0x09,0x00,0x82,0x60,0x80,0x98,0x82,0x1C,0xEC,0x41,0x28,0xD8,0x41,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};

View File

@@ -0,0 +1,58 @@
import sys
import os
import subprocess
# Compiles all .frag and .vert files in this directory to SPIR-V, MSL, and DXIL binary C character arrays.
#
# Requires the SDL_shadercross tool. Binaries can be found here: https://github.com/libsdl-org/SDL_shadercross/actions
# Click on the latest workflow and download them for the platform of your choice.
if len(sys.argv) < 2:
print("Usage: python compile_shaders.py <shadercross_path>")
sys.exit(1)
out_file = "ShadersCompiledSPV.h"
current_dir = os.path.dirname(os.path.realpath(__file__));
shadercross_path = os.path.realpath(sys.argv[1])
if not os.path.isfile(shadercross_path):
print(f"Error: The specified shadercross path '{shadercross_path}' does not exist.")
sys.exit(1)
out_path = os.path.join(current_dir, out_file)
with open(out_path,'w') as result_file:
result_file.write('// RmlUi SDL GPU shaders compiled using command: \'python compile_shaders.py\'. Do not edit manually.\n\n#include <stdint.h>\n')
for file in os.listdir(current_dir):
if file.endswith(".vert") or file.endswith(".frag"):
shader_path = os.path.join(current_dir, file)
def compile(target):
print("Compiling '{}' to '{}' using SDL_shadercross.".format(file, target))
variable_name = "{}_{}".format(os.path.splitext(file)[0], target)
temp_path = os.path.join(current_dir, ".temp.{}".format(target))
print(temp_path)
subprocess.run([shadercross_path, "-s", "hlsl", shader_path, "-o", temp_path, "-d", target], check = True)
print("Success, writing output to variable '{}' in {}".format(variable_name, out_file))
i = 0
result_file.write('\nalignas(uint32_t) static const unsigned char {}[] = {{'.format(variable_name))
for b in open(temp_path, 'rb').read():
if i % 20 == 0:
result_file.write('\n\t')
result_file.write('0x%02X,' % b)
i += 1
result_file.write('\n};\n')
os.remove(temp_path)
compile("spirv")
compile("msl")
compile("dxil")

View File

@@ -0,0 +1,3 @@
float4 main(float4 Color : TEXCOORD0) : SV_Target0 {
return Color;
}

View File

@@ -0,0 +1,8 @@
Texture2D<float4> Texture : register(t0, space2);
SamplerState Sampler : register(s0, space2);
// NOTE: Seems like TEXCOORD0 is getting optimized out and TEXCOORD1 is taking its spot
// Might need to fix when later versions of SDL_shadercross preserve unused bindings
float4 main(float4 Color : TEXCOORD0, float2 TexCoord : TEXCOORD1) : SV_Target0 {
return Color * Texture.Sample(Sampler, TexCoord);
}

View File

@@ -0,0 +1,28 @@
cbuffer UniformBlockTransform : register(b0, space1) {
float4x4 Transform : packoffset(c0);
};
cbuffer UniformBlockTranslate : register(b1, space1) {
float2 Translate : packoffset(c0);
};
struct Input {
float2 Position : TEXCOORD0;
float4 InColor : TEXCOORD1;
float2 TexCoord : TEXCOORD2;
};
struct Output {
float4 Color : TEXCOORD0;
float2 TexCoord : TEXCOORD1;
float4 Position : SV_Position;
};
Output main(Input input) {
Output output;
output.TexCoord = input.TexCoord;
output.Color = input.InColor;
float4 position = float4(input.Position + Translate, 0, 1);
output.Position = mul(Transform, position);
return output;
}

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: Never

View File

@@ -0,0 +1,29 @@
The Vulkan backend for RmlUi includes the third-party dependency:
Vulkan Memory Allocator
https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
This dependency is located in the file 'vk_mem_alloc.h' and is licensed
separately from Rmlui. The license of this dependency is recited below.
----
Copyright (c) 2017-2022 Advanced Micro Devices, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,180 @@
// RmlUi SPIR-V Vulkan shaders compiled using command: 'python compile_shaders.py'. Do not edit manually.
#include <stdint.h>
alignas(uint32_t) static const unsigned char shader_frag_color[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x0A,0x00,0x0D,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0B,0x00,0x06,0x00,0x01,0x00,0x00,0x00,0x47,0x4C,0x53,0x4C,
0x2E,0x73,0x74,0x64,0x2E,0x34,0x35,0x30,0x00,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x0F,0x00,0x08,0x00,0x04,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,
0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x10,0x00,0x03,0x00,
0x04,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0x4A,0x01,0x00,0x00,
0x04,0x00,0x09,0x00,0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x65,0x70,0x61,0x72,0x61,0x74,0x65,0x5F,
0x73,0x68,0x61,0x64,0x65,0x72,0x5F,0x6F,0x62,0x6A,0x65,0x63,0x74,0x73,0x00,0x00,0x04,0x00,0x09,0x00,
0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x68,0x61,0x64,0x69,0x6E,0x67,0x5F,0x6C,0x61,0x6E,0x67,0x75,
0x61,0x67,0x65,0x5F,0x34,0x32,0x30,0x70,0x61,0x63,0x6B,0x00,0x04,0x00,0x0A,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x63,0x70,0x70,0x5F,0x73,0x74,0x79,0x6C,0x65,0x5F,0x6C,0x69,0x6E,0x65,
0x5F,0x64,0x69,0x72,0x65,0x63,0x74,0x69,0x76,0x65,0x00,0x00,0x04,0x00,0x08,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x5F,0x64,0x69,0x72,0x65,0x63,0x74,
0x69,0x76,0x65,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x05,0x00,0x05,0x00,0x09,0x00,0x00,0x00,0x66,0x69,0x6E,0x61,0x6C,0x43,0x6F,0x6C,0x6F,0x72,0x00,0x00,
0x05,0x00,0x05,0x00,0x0B,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x43,0x6F,0x6C,0x6F,0x72,0x00,0x00,0x00,
0x05,0x00,0x06,0x00,0x0F,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6F,0x6F,0x72,0x64,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x0F,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0x00,0x02,0x00,0x02,0x00,0x00,0x00,
0x21,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x16,0x00,0x03,0x00,0x06,0x00,0x00,0x00,
0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0A,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0A,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x0E,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x36,0x00,0x05,0x00,0x02,0x00,0x00,0x00,
0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xF8,0x00,0x02,0x00,0x05,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,
0x09,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
};
alignas(uint32_t) static const unsigned char shader_frag_texture[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x0A,0x00,0x0D,0x00,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0B,0x00,0x06,0x00,0x01,0x00,0x00,0x00,0x47,0x4C,0x53,0x4C,
0x2E,0x73,0x74,0x64,0x2E,0x34,0x35,0x30,0x00,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x0F,0x00,0x08,0x00,0x04,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,
0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x10,0x00,0x03,0x00,
0x04,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0x4A,0x01,0x00,0x00,
0x04,0x00,0x09,0x00,0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x65,0x70,0x61,0x72,0x61,0x74,0x65,0x5F,
0x73,0x68,0x61,0x64,0x65,0x72,0x5F,0x6F,0x62,0x6A,0x65,0x63,0x74,0x73,0x00,0x00,0x04,0x00,0x09,0x00,
0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x68,0x61,0x64,0x69,0x6E,0x67,0x5F,0x6C,0x61,0x6E,0x67,0x75,
0x61,0x67,0x65,0x5F,0x34,0x32,0x30,0x70,0x61,0x63,0x6B,0x00,0x04,0x00,0x0A,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x63,0x70,0x70,0x5F,0x73,0x74,0x79,0x6C,0x65,0x5F,0x6C,0x69,0x6E,0x65,
0x5F,0x64,0x69,0x72,0x65,0x63,0x74,0x69,0x76,0x65,0x00,0x00,0x04,0x00,0x08,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x5F,0x64,0x69,0x72,0x65,0x63,0x74,
0x69,0x76,0x65,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x05,0x00,0x05,0x00,0x09,0x00,0x00,0x00,0x74,0x65,0x78,0x43,0x6F,0x6C,0x6F,0x72,0x00,0x00,0x00,0x00,
0x05,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x5F,0x74,0x65,0x78,0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,
0x11,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6F,0x6F,0x72,0x64,0x00,0x00,0x00,0x00,
0x05,0x00,0x05,0x00,0x15,0x00,0x00,0x00,0x66,0x69,0x6E,0x61,0x6C,0x43,0x6F,0x6C,0x6F,0x72,0x00,0x00,
0x05,0x00,0x05,0x00,0x17,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x43,0x6F,0x6C,0x6F,0x72,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x0D,0x00,0x00,0x00,0x21,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x11,0x00,0x00,0x00,
0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x15,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x17,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x13,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x16,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x06,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x19,0x00,0x09,0x00,0x0A,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x1B,0x00,0x03,0x00,0x0B,0x00,0x00,0x00,0x0A,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0C,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0C,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x0F,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x14,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x14,0x00,0x00,0x00,0x15,0x00,0x00,0x00,
0x03,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x16,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x3B,0x00,0x04,0x00,0x16,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x36,0x00,0x05,0x00,
0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xF8,0x00,0x02,0x00,
0x05,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x0F,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x57,0x00,0x05,0x00,0x07,0x00,0x00,0x00,
0x13,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x09,0x00,0x00,0x00,
0x13,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x17,0x00,0x00,0x00,
0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x85,0x00,0x05,0x00,
0x07,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x19,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,
0x15,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,0x38,0x00,0x01,0x00,
};
alignas(uint32_t) static const unsigned char shader_vert[] = {
0x03,0x02,0x23,0x07,0x00,0x00,0x01,0x00,0x0A,0x00,0x0D,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x11,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x0B,0x00,0x06,0x00,0x01,0x00,0x00,0x00,0x47,0x4C,0x53,0x4C,
0x2E,0x73,0x74,0x64,0x2E,0x34,0x35,0x30,0x00,0x00,0x00,0x00,0x0E,0x00,0x03,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x0F,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,
0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x11,0x00,0x00,0x00,
0x15,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x02,0x00,0x00,0x00,0x4A,0x01,0x00,0x00,
0x04,0x00,0x09,0x00,0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x65,0x70,0x61,0x72,0x61,0x74,0x65,0x5F,
0x73,0x68,0x61,0x64,0x65,0x72,0x5F,0x6F,0x62,0x6A,0x65,0x63,0x74,0x73,0x00,0x00,0x04,0x00,0x09,0x00,
0x47,0x4C,0x5F,0x41,0x52,0x42,0x5F,0x73,0x68,0x61,0x64,0x69,0x6E,0x67,0x5F,0x6C,0x61,0x6E,0x67,0x75,
0x61,0x67,0x65,0x5F,0x34,0x32,0x30,0x70,0x61,0x63,0x6B,0x00,0x04,0x00,0x0A,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x63,0x70,0x70,0x5F,0x73,0x74,0x79,0x6C,0x65,0x5F,0x6C,0x69,0x6E,0x65,
0x5F,0x64,0x69,0x72,0x65,0x63,0x74,0x69,0x76,0x65,0x00,0x00,0x04,0x00,0x08,0x00,0x47,0x4C,0x5F,0x47,
0x4F,0x4F,0x47,0x4C,0x45,0x5F,0x69,0x6E,0x63,0x6C,0x75,0x64,0x65,0x5F,0x64,0x69,0x72,0x65,0x63,0x74,
0x69,0x76,0x65,0x00,0x05,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x6D,0x61,0x69,0x6E,0x00,0x00,0x00,0x00,
0x05,0x00,0x06,0x00,0x09,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x54,0x65,0x78,0x43,0x6F,0x6F,0x72,0x64,
0x00,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x0B,0x00,0x00,0x00,0x69,0x6E,0x54,0x65,0x78,0x43,0x6F,0x6F,
0x72,0x64,0x30,0x00,0x05,0x00,0x05,0x00,0x0F,0x00,0x00,0x00,0x66,0x72,0x61,0x67,0x43,0x6F,0x6C,0x6F,
0x72,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x11,0x00,0x00,0x00,0x69,0x6E,0x43,0x6F,0x6C,0x6F,0x72,0x30,
0x00,0x00,0x00,0x00,0x05,0x00,0x06,0x00,0x14,0x00,0x00,0x00,0x74,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,
0x65,0x64,0x50,0x6F,0x73,0x00,0x00,0x00,0x05,0x00,0x05,0x00,0x15,0x00,0x00,0x00,0x69,0x6E,0x50,0x6F,
0x73,0x69,0x74,0x69,0x6F,0x6E,0x00,0x00,0x05,0x00,0x05,0x00,0x18,0x00,0x00,0x00,0x55,0x73,0x65,0x72,
0x44,0x61,0x74,0x61,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x6D,0x5F,0x74,0x72,0x61,0x6E,0x73,0x66,0x6F,0x72,0x6D,0x00,0x06,0x00,0x06,0x00,0x18,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x6D,0x5F,0x74,0x72,0x61,0x6E,0x73,0x6C,0x61,0x74,0x65,0x00,0x05,0x00,0x05,0x00,
0x1A,0x00,0x00,0x00,0x75,0x73,0x65,0x72,0x64,0x61,0x74,0x61,0x00,0x00,0x00,0x00,0x05,0x00,0x04,0x00,
0x22,0x00,0x00,0x00,0x6F,0x75,0x74,0x50,0x6F,0x73,0x00,0x00,0x05,0x00,0x06,0x00,0x31,0x00,0x00,0x00,
0x67,0x6C,0x5F,0x50,0x65,0x72,0x56,0x65,0x72,0x74,0x65,0x78,0x00,0x00,0x00,0x00,0x06,0x00,0x06,0x00,
0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x6C,0x5F,0x50,0x6F,0x73,0x69,0x74,0x69,0x6F,0x6E,0x00,
0x06,0x00,0x07,0x00,0x31,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x67,0x6C,0x5F,0x50,0x6F,0x69,0x6E,0x74,
0x53,0x69,0x7A,0x65,0x00,0x00,0x00,0x00,0x06,0x00,0x07,0x00,0x31,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x67,0x6C,0x5F,0x43,0x6C,0x69,0x70,0x44,0x69,0x73,0x74,0x61,0x6E,0x63,0x65,0x00,0x05,0x00,0x03,0x00,
0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x09,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x0B,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x47,0x00,0x04,0x00,0x0F,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x11,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x15,0x00,0x00,0x00,
0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x05,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x23,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x10,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x18,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x23,0x00,0x00,0x00,
0x40,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x18,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x47,0x00,0x04,0x00,
0x1A,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x00,0x04,0x00,0x1A,0x00,0x00,0x00,
0x21,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x31,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x0B,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x48,0x00,0x05,0x00,0x31,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x0B,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x47,0x00,0x03,0x00,0x31,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x13,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x21,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x16,0x00,0x03,0x00,0x06,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x06,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x08,0x00,0x00,0x00,0x09,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x0A,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x17,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,
0x06,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x0D,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0E,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x03,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x10,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,
0x10,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x13,0x00,0x00,0x00,
0x07,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x0A,0x00,0x00,0x00,0x15,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x18,0x00,0x04,0x00,0x17,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x04,0x00,0x00,0x00,
0x1E,0x00,0x04,0x00,0x18,0x00,0x00,0x00,0x17,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x04,0x00,
0x19,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x19,0x00,0x00,0x00,
0x1A,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x15,0x00,0x04,0x00,0x1B,0x00,0x00,0x00,0x20,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,0x1B,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x01,0x00,0x00,0x00,
0x20,0x00,0x04,0x00,0x1D,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x04,0x00,
0x21,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,0x1B,0x00,0x00,0x00,
0x23,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x24,0x00,0x00,0x00,0x02,0x00,0x00,0x00,
0x17,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x2B,0x00,0x04,0x00,0x06,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x00,0x00,0x80,0x3F,0x15,0x00,0x04,0x00,
0x2E,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2B,0x00,0x04,0x00,0x2E,0x00,0x00,0x00,
0x2F,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x1C,0x00,0x04,0x00,0x30,0x00,0x00,0x00,0x06,0x00,0x00,0x00,
0x2F,0x00,0x00,0x00,0x1E,0x00,0x05,0x00,0x31,0x00,0x00,0x00,0x0D,0x00,0x00,0x00,0x06,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,0x20,0x00,0x04,0x00,0x32,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x31,0x00,0x00,0x00,
0x3B,0x00,0x04,0x00,0x32,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x36,0x00,0x05,0x00,
0x02,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0xF8,0x00,0x02,0x00,
0x05,0x00,0x00,0x00,0x3B,0x00,0x04,0x00,0x13,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x07,0x00,0x00,0x00,
0x3B,0x00,0x04,0x00,0x21,0x00,0x00,0x00,0x22,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x07,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x09,0x00,0x00,0x00,
0x0C,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x11,0x00,0x00,0x00,
0x3E,0x00,0x03,0x00,0x0F,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x16,0x00,0x00,0x00,0x15,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x1D,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,
0x1A,0x00,0x00,0x00,0x1C,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,
0x1E,0x00,0x00,0x00,0x81,0x00,0x05,0x00,0x07,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x16,0x00,0x00,0x00,
0x1F,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x14,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x41,0x00,0x05,0x00,
0x24,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x1A,0x00,0x00,0x00,0x23,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,
0x17,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x07,0x00,0x00,0x00,
0x27,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x2A,0x00,0x00,0x00,
0x27,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x51,0x00,0x05,0x00,0x06,0x00,0x00,0x00,0x2B,0x00,0x00,0x00,
0x27,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x50,0x00,0x07,0x00,0x0D,0x00,0x00,0x00,0x2C,0x00,0x00,0x00,
0x2A,0x00,0x00,0x00,0x2B,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x29,0x00,0x00,0x00,0x91,0x00,0x05,0x00,
0x0D,0x00,0x00,0x00,0x2D,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x2C,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,
0x22,0x00,0x00,0x00,0x2D,0x00,0x00,0x00,0x3D,0x00,0x04,0x00,0x0D,0x00,0x00,0x00,0x34,0x00,0x00,0x00,
0x22,0x00,0x00,0x00,0x41,0x00,0x05,0x00,0x0E,0x00,0x00,0x00,0x35,0x00,0x00,0x00,0x33,0x00,0x00,0x00,
0x23,0x00,0x00,0x00,0x3E,0x00,0x03,0x00,0x35,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0xFD,0x00,0x01,0x00,
0x38,0x00,0x01,0x00,
};

View File

@@ -0,0 +1,38 @@
import sys
import os
import subprocess
# Compiles all .frag and .vert files in this directory to SPIR-V binary C character arrays. Requires 'glslc' installed and available system-wide.
out_file = "ShadersCompiledSPV.h"
current_dir = os.path.dirname(os.path.realpath(__file__));
temp_spirv_path = os.path.join(current_dir, ".temp.spv")
out_path = os.path.join(current_dir, out_file)
with open(out_path,'w') as result_file:
result_file.write('// RmlUi SPIR-V Vulkan shaders compiled using command: \'python compile_shaders.py\'. Do not edit manually.\n\n#include <stdint.h>\n')
for file in os.listdir(current_dir):
if file.endswith(".vert") or file.endswith(".frag"):
shader_path = os.path.join(current_dir, file)
variable_name = os.path.splitext(file)[0]
print("Compiling '{}' to SPIR-V using glslc.".format(file))
subprocess.run(["glslc", shader_path, "-o", temp_spirv_path], check = True)
print("Success, writing output to variable '{}' in {}".format(variable_name, out_file))
i = 0
result_file.write('\nalignas(uint32_t) static const unsigned char {}[] = {{'.format(variable_name))
for b in open(temp_spirv_path, 'rb').read():
if i % 20 == 0:
result_file.write('\n\t')
result_file.write('0x%02X,' % b)
i += 1
result_file.write('\n};\n')
os.remove(temp_spirv_path)

View File

@@ -0,0 +1,12 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (location = 0) in vec2 fragTexCoord;
layout (location = 1) in vec4 fragColor;
layout (location = 0) out vec4 finalColor;
void main() {
finalColor = fragColor;
}

View File

@@ -0,0 +1,15 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(set=1, binding=2) uniform sampler2D _tex;
layout (location = 0) in vec2 fragTexCoord;
layout (location = 1) in vec4 fragColor;
layout (location = 0) out vec4 finalColor;
void main() {
vec4 texColor = texture(_tex, fragTexCoord);
finalColor = fragColor * texColor;
}

View File

@@ -0,0 +1,25 @@
#version 330
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout(set=0, binding=1) uniform UserData
{
mat4 m_transform;
vec2 m_translate;
} userdata;
layout (location = 0) in vec2 inPosition;
layout (location = 1) in vec4 inColor0;
layout (location = 2) in vec2 inTexCoord0;
layout (location = 0) out vec2 fragTexCoord;
layout (location = 1) out vec4 fragColor;
void main() {
fragTexCoord = inTexCoord0;
fragColor = inColor0;
vec2 translatedPos = inPosition + userdata.m_translate.xy;
vec4 outPos = userdata.m_transform * vec4(translatedPos, 0, 1);
gl_Position = outPos;
}

File diff suppressed because it is too large Load Diff

4486
vendor/rmlui_backend/RmlUi_Vulkan/vulkan.h vendored Normal file

File diff suppressed because it is too large Load Diff