Working template (RmlUi 6.2)

Needs:
- Lua plugin
- SVG plugin
This commit is contained in:
2026-08-10 11:13:18 -05:00
commit bced86ed9e
81 changed files with 45737 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
.cache/
.direnv/
.vscode/
.envrc
build/
result
result/
compile_commands.json
android/.gradle/
android/build/
android/app/.cxx/
android/app/build/

57
CMakeLists.txt Normal file
View File

@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.16)
project(rmlandroid 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(SDL3 CONFIG REQUIRED)
find_package(SDL3_image CONFIG REQUIRED)
find_package(RmlUi CONFIG REQUIRED)
add_library(rmlui_backend STATIC
vendor/rmlui_backend/RmlUi_Backend_SDL_SDLrenderer.cpp
vendor/rmlui_backend/RmlUi_Platform_SDL.cpp
vendor/rmlui_backend/RmlUi_Renderer_SDL.cpp
)
target_include_directories(rmlui_backend PUBLIC
vendor/rmlui_backend
)
target_compile_definitions(rmlui_backend PUBLIC
RMLUI_SDL_VERSION_MAJOR=3
)
target_link_libraries(rmlui_backend PUBLIC
RmlUi::RmlUi
SDL3::SDL3
SDL3_image::SDL3_image
Freetype::Freetype
)
add_executable(${PROJECT_NAME}
src/main.cpp
)
target_include_directories(${PROJECT_NAME} PRIVATE
src
${FREETYPE_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
rmlui_backend
RmlUi::RmlUi
RmlUi::Debugger
Freetype::Freetype
)
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
install(DIRECTORY assets DESTINATION bin)
target_compile_definitions(${PROJECT_NAME} PRIVATE
ASSETS_DIR="${CMAKE_SOURCE_DIR}/bin/assets"
)

View File

@@ -0,0 +1,86 @@
cmake_minimum_required(VERSION 3.22.1)
project(rmlandroid LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(ExternalProject)
set(DEPS_INSTALL ${CMAKE_BINARY_DIR}/deps/install)
set(ANDROID_TOOLCHAIN_ARGS
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DANDROID_ABI=${ANDROID_ABI}
-DANDROID_PLATFORM=${ANDROID_PLATFORM}
-DANDROID_STL=c++_shared
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL}
-DCMAKE_PREFIX_PATH=${DEPS_INSTALL}
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_CXX_STANDARD=17
-DCMAKE_CXX_STANDARD_REQUIRED=ON
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH
)
find_package(SDL3 REQUIRED CONFIG)
find_package(SDL3_image REQUIRED CONFIG)
ExternalProject_Add(freetype_ext
GIT_REPOSITORY https://github.com/freetype/freetype.git
GIT_TAG VER-2-13-3
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
-DFT_DISABLE_ZLIB=TRUE
-DFT_DISABLE_BZIP2=TRUE
-DFT_DISABLE_PNG=TRUE
-DFT_DISABLE_HARFBUZZ=TRUE
-DFT_DISABLE_BROTLI=TRUE
)
ExternalProject_Add(rmlui_ext
GIT_REPOSITORY https://github.com/mikke89/RmlUi.git
GIT_TAG 6.2
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
-DFreetype_ROOT=${DEPS_INSTALL}
-DRMLUI_SAMPLES=OFF
-DBUILD_TESTING=OFF
DEPENDS freetype_ext
)
set(PROJECT_SRC_DIR ${CMAKE_SOURCE_DIR}/../../src)
set(RMLUI_BACKEND_DIR ${CMAKE_SOURCE_DIR}/../../vendor/rmlui_backend)
add_library(rmlandroid SHARED
${PROJECT_SRC_DIR}/main.cpp
${RMLUI_BACKEND_DIR}/RmlUi_Backend_SDL_SDLrenderer.cpp
${RMLUI_BACKEND_DIR}/RmlUi_Platform_SDL.cpp
${RMLUI_BACKEND_DIR}/RmlUi_Renderer_SDL.cpp
)
add_dependencies(rmlandroid freetype_ext rmlui_ext)
target_include_directories(rmlandroid PRIVATE
${PROJECT_SRC_DIR}
${RMLUI_BACKEND_DIR}
${DEPS_INSTALL}/include
)
target_compile_definitions(rmlandroid PRIVATE
RMLUI_SDL_VERSION_MAJOR=3
RMLUI_STATIC_LIB
)
target_link_directories(rmlandroid PRIVATE ${DEPS_INSTALL}/lib)
target_link_libraries(rmlandroid PRIVATE
SDL3::SDL3
SDL3_image::SDL3_image
rmlui
rmlui_debugger
freetype
)
target_link_options(rmlandroid PRIVATE "-Wl,-u,SDL_main")

View File

@@ -0,0 +1,67 @@
plugins {
id("com.android.application")
}
android {
namespace = "com.rmlandroid.app"
compileSdk = 34
ndkVersion = "26.3.11579264"
defaultConfig {
applicationId = "com.rmlandroid.app"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "0.1.0"
ndk {
abiFilters += listOf("arm64-v8a")
}
externalNativeBuild {
cmake {
arguments += "-DANDROID_STL=c++_shared"
cppFlags += "-std=c++17"
}
}
}
buildFeatures {
prefab = true
}
externalNativeBuild {
cmake {
path = file("CMakeLists.txt")
version = "3.22.1"
}
}
sourceSets {
getByName("main") {
assets.srcDirs("../../assets")
}
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
dependencies {
implementation(files("libs/SDL3-3.4.14.aar"))
implementation(files("libs/SDL3_image-3.4.4.aar"))
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<!-- <uses-permission android:name="android.permission.INTERNET" /> -->
<application
android:allowBackup="true"
android:label="RmlAndroid"
android:hasCode="true">
<activity
android:name="com.rmlandroid.app.MainActivity"
android:label="RmlAndroid"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="true"
android:screenOrientation="fullSensor"
android:theme="@android:style/Theme.Black.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,14 @@
package com.rmlandroid.app;
import org.libsdl.app.SDLActivity;
public class MainActivity extends SDLActivity {
@Override
protected String[] getLibraries() {
return new String[] {
"SDL3",
"rmlandroid"
};
}
}

3
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,3 @@
plugins {
id("com.android.application") version "8.5.2" apply false
}

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
android/gradlew vendored Executable file
View File

@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
android/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,17 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
rootProject.name = "rmlandroid"
include(":app")

26
assets/main.rcss Normal file
View File

@@ -0,0 +1,26 @@
body {
display: block;
width: 100vw;
height: 100vh;
font-family: "rmlui-debugger-font";
background-color: #ffffff;
color: #000000;
margin: 0dp;
padding: 10dp;
font-size: 12dp;
}
p {
display: block;
margin: 0dp;
}
h1, h2, h3, h4, h5, h6 {
display: block;
font-weight: bold;
margin: 0dp;
}
h1 { font-size: 1.6em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.17em; }

10
assets/main.rml Normal file
View File

@@ -0,0 +1,10 @@
<rml>
<head>
<title>RmlUi Android</title>
<link type="text/rcss" href="main.rcss" />
</head>
<body>
<h1>Hello android world!</h1>
</body>
</rml>

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1786106723,
"narHash": "sha256-zDSUbpoeo/9ZmD2+wXnzxoo1+uhL8vxc0b8yuYMKYq0=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f13ff45afd1bb73e640eaa08a7066dbed07e3238",
"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
}

121
flake.nix Normal file
View File

@@ -0,0 +1,121 @@
{
description = "RmlUi android app";
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;
config = {
allowUnfree = true;
android_sdk.accept_license = true;
};
};
rmlui = pkgs.stdenv.mkDerivation rec {
pname = "rmlui";
version = "6.2";
src = pkgs.fetchFromGitHub {
owner = "mikke89";
repo = "RmlUi";
rev = "6.2";
hash = "sha256-K/znksrli3/FQ+lHgZgMgefFrWAGbxKNvFIIqtybOMc=";
};
nativeBuildInputs = [ pkgs.cmake pkgs.pkg-config ];
buildInputs = [ pkgs.freetype ];
cmakeFlags = [
"-DBUILD_SHARED_LIBS=ON"
"-DBUILD_SAMPLES=OFF"
"-DBUILD_TESTING=OFF"
"-DNO_FONT_INTERFACE_DEFAULT=OFF"
];
};
buildDeps = with pkgs; [
cmake
pkg-config
gcc
clang-tools
];
runtimeDeps = with pkgs; [
sdl3
sdl3-image
libGL
wayland
libxkbcommon
rmlui
freetype
];
ndkVersion = "26.3.11579264";
androidComposition = pkgs.androidenv.composeAndroidPackages {
platformVersions = [ "34" ];
buildToolsVersions = [ "34.0.0" ];
includeNDK = true;
ndkVersions = [ ndkVersion ];
cmakeVersions = [ "3.22.1" ];
abiVersions = [ "arm64-v8a" ];
includeEmulator = false;
};
androidSdkRoot = "${androidComposition.androidsdk}/libexec/android-sdk";
androidNdkRoot = "${androidSdkRoot}/ndk/${ndkVersion}";
androidDeps = [
androidComposition.androidsdk
pkgs.gradle
pkgs.jdk17
pkgs.ninja
];
in
{
devShells.default = pkgs.mkShell {
nativeBuildInputs = buildDeps ++ androidDeps;
buildInputs = runtimeDeps;
shellHook = ''
export CP_PATH="${rmlui}/include"
export LIBRARY_PATH="${pkgs.libGL}/lib:${rmlui}/lib"
export LD_LIBRARY_PATH="$LIBRARY_PATH:$LD_LIBRARY_PATH"
export ANDROID_HOME="${androidSdkRoot}"
export ANDROID_SDK_ROOT="${androidSdkRoot}"
export ANDROID_NDK_HOME="${androidNdkRoot}"
export ANDROID_NDK_ROOT="${androidNdkRoot}"
echo "Env ready (desktop + Android NDK ${ndkVersion}, arm64-v8a)"
'';
};
packages.default = pkgs.stdenv.mkDerivation {
pname = "rmlandroid";
version = "0.1.0";
src = ./.;
nativeBuildInputs = buildDeps ++ [ pkgs.makeWrapper ];
buildInputs = runtimeDeps;
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
];
postInstall = ''
wrapProgram $out/bin/rmlandroid \
--prefix LD_LIBRARY_PATH : "${pkgs.lib.makeLibraryPath runtimeDeps}"
'';
};
}
);
}

17
src/asset_io.hpp Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <SDL3/SDL.h>
#include <string>
inline SDL_IOStream *OpenAssetFile(const std::string &virtual_path) {
std::string resolved = virtual_path;
#if defined(__ANDROID__)
if (resolved.rfind("./assets/", 0) == 0) {
resolved = resolved.substr(9);
} else if (resolved.rfind("assets/", 0) == 0) {
resolved = resolved.substr(7);
}
#endif
return SDL_IOFromFile(resolved.c_str(), "rb");
}

110
src/main.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/ElementDocument.h>
#include <RmlUi/Debugger/Debugger.h>
#include <SDL3/SDL_error.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_log.h>
#include <iostream>
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <RmlUi/Core.h>
#include <RmlUi/Debugger.h>
#include <RmlUi_Backend.h>
#include "sdl_file_interface.hpp"
static Rml::Context *ctx = nullptr;
static Rml::ElementDocument *doc = nullptr;
static bool is_running = false;
static SDLFileInterface file_interface;
int main(int arc, char *argv[]) {
std::cout << "Hello world!\n";
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
SDL_Log("[ERROR] Failed to initalize SDL3");
return 1;
}
if (!Backend::Initialize("main", 800, 600, true)) {
SDL_Log("[ERROR] Failed to initalize RmlUi Backend");
return 1;
}
Rml::SetSystemInterface(Backend::GetSystemInterface());
Rml::SetRenderInterface(Backend::GetRenderInterface());
Rml::SetFileInterface(&file_interface);
Rml::Initialise();
int window_width = 800;
int window_height = 600;
{
int window_count = 0;
SDL_Window **windows = SDL_GetWindows(&window_count);
if (windows && window_count > 0) {
SDL_GetWindowSize(windows[0], &window_width, &window_height);
}
SDL_free(windows);
}
ctx = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height));
{
int window_count = 0;
SDL_Window **windows = SDL_GetWindows(&window_count);
if (windows && window_count > 0) {
float display_scale = SDL_GetWindowDisplayScale(windows[0]);
ctx->SetDensityIndependentPixelRatio(display_scale);
SDL_Log("Display scale (dp ratio): %f", display_scale);
}
SDL_free(windows);
}
if (!ctx) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[ERROR] Failed to create context");
return 1;
}
Rml::Debugger::Initialise(ctx);
doc = ctx->LoadDocument("./assets/main.rml");
if (!doc) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "[ERROR] Failed to load main.rml");
}
doc->Show();
is_running = true;
while (is_running) {
is_running = Backend::ProcessEvents(ctx);
{
int window_count = 0;
SDL_Window **windows = SDL_GetWindows(&window_count);
if (windows && window_count > 0) {
int current_width = 0, current_height = 0;
SDL_GetWindowSize(windows[0], &current_width, &current_height);
float display_scale = SDL_GetWindowDisplayScale(windows[0]);
if (Rml::Vector2i(current_width, current_height) != ctx->GetDimensions()) {
ctx->SetDimensions(Rml::Vector2i(current_width, current_height));
}
if (display_scale != ctx->GetDensityIndependentPixelRatio()) {
ctx->SetDensityIndependentPixelRatio(display_scale);
}
}
SDL_free(windows);
}
ctx->Update();
Backend::BeginFrame();
ctx->Render();
Backend::PresentFrame();
}
Rml::Shutdown();
Backend::Shutdown();
SDL_Quit();
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include "asset_io.hpp"
#include <RmlUi/Core/FileInterface.h>
#include <SDL3/SDL.h>
class SDLFileInterface : public Rml::FileInterface {
public:
Rml::FileHandle Open(const Rml::String &path) override {
SDL_IOStream *io = OpenAssetFile(path);
return reinterpret_cast<Rml::FileHandle>(io);
}
void Close(Rml::FileHandle file) override {
SDL_CloseIO(reinterpret_cast<SDL_IOStream *>(file));
}
size_t Read(void *buffer, size_t size, Rml::FileHandle file) override {
return SDL_ReadIO(reinterpret_cast<SDL_IOStream *>(file), buffer, size);
}
bool Seek(Rml::FileHandle file, long offset, int origin) override {
SDL_IOWhence whence = SDL_IO_SEEK_SET;
if (origin == SEEK_CUR) whence = SDL_IO_SEEK_CUR;
else if (origin == SEEK_END) whence = SDL_IO_SEEK_END;
return SDL_SeekIO(reinterpret_cast<SDL_IOStream *>(file), offset, whence) >= 0;
}
size_t Tell(Rml::FileHandle file) override {
return static_cast<size_t>(SDL_TellIO(reinterpret_cast<SDL_IOStream *>(file)));
}
size_t Length(Rml::FileHandle file) override {
return static_cast<size_t>(SDL_GetIOSize(reinterpret_cast<SDL_IOStream *>(file)));
}
};

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

@@ -0,0 +1,220 @@
#[[
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_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_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_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,226 @@
#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 {
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>();
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;
}
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,234 @@
#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 {
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>();
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;
}
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,267 @@
#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 {
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>();
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->system_interface.SetWindow(window);
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,339 @@
#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 {
SystemInterface_SDL system_interface;
RenderInterface_GL2_SDL render_interface;
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
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
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>();
data->window = window;
data->glcontext = glcontext;
data->system_interface.SetWindow(window);
data->render_interface.SetViewport(width, height);
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_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_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;
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,371 @@
#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 {
SystemInterface_SDL system_interface;
RenderInterface_GL3_SDL render_interface;
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
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
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>();
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->system_interface.SetWindow(window);
data->render_interface.SetViewport(width, height);
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 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_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_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;
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,235 @@
#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_GPUDevice* device, SDL_Window* window) : render_interface(device, window) {}
SystemInterface_SDL system_interface;
RenderInterface_SDL_GPU render_interface;
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);
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>(device, window);
data->window = window;
data->device = device;
data->system_interface.SetWindow(window);
const char* renderer_name = SDL_GetGPUDeviceDriver(device);
data->system_interface.LogMessage(Rml::Log::LT_INFO, Rml::CreateString("Using SDL device driver: %s", renderer_name));
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;
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;
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,230 @@
#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_Renderer* renderer) : render_interface(renderer) {}
SystemInterface_SDL system_interface;
RenderInterface_SDL render_interface;
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
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
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>(renderer);
data->window = window;
data->renderer = renderer;
data->system_interface.SetWindow(window);
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));
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;
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;
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;
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,305 @@
#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 {
SystemInterface_SDL system_interface;
RenderInterface_VK render_interface;
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
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS))
return false;
#else
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>();
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->system_interface.SetWindow(window);
data->render_interface.SetViewport(width, height);
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_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_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;
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,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

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,340 @@
#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()
{
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
}
void SystemInterface_GLFW::SetWindow(GLFWwindow* in_window)
{
window = in_window;
}
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;
if (window)
glfwSetCursor(window, cursor);
}
void SystemInterface_GLFW::SetClipboardText(const Rml::String& text_utf8)
{
if (window)
glfwSetClipboardString(window, text_utf8.c_str());
}
void SystemInterface_GLFW::GetClipboardText(Rml::String& text)
{
if (window)
{
const char* clipboard = glfwGetClipboardString(window);
if (clipboard != nullptr)
{
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,59 @@
#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();
~SystemInterface_GLFW();
// Optionally, provide or change the window to be used for setting the mouse cursors and clipboard text.
void SetWindow(GLFWwindow* 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:
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,511 @@
#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>
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()
{
#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);
#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);
}
void SystemInterface_SDL::SetWindow(SDL_Window* in_window)
{
window = in_window;
}
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 (window)
{
#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 (window)
{
#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);
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;
}

View File

@@ -0,0 +1,62 @@
#pragma once
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/SystemInterface.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();
~SystemInterface_SDL();
// Optionally, provide or change the window to be used for setting the mouse cursors.
void SetWindow(SDL_Window* 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;
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.
// @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

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 plaform.
*/
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

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<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,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,207 @@
#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();
Rml::UniquePtr<SDL_Vertex[]> sdl_vertices{new SDL_Vertex[num_vertices]};
for (size_t i = 0; i < num_vertices; i++)
{
sdl_vertices[i].position = {vertices[i].position.x + translation.x, vertices[i].position.y + translation.y};
sdl_vertices[i].tex_coord = {vertices[i].tex_coord.x, vertices[i].tex_coord.y};
const auto& color = vertices[i].colour;
#if SDL_MAJOR_VERSION >= 3
sdl_vertices[i].color = {color.red / 255.f, color.green / 255.f, color.blue / 255.f, color.alpha / 255.f};
#else
sdl_vertices[i].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,44 @@
#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;
};

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), copy_pass(nullptr), render_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_CLAMP_TO_EDGE;
info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
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,121 @@
#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 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