diff --git a/build.gradle.kts b/build.gradle.kts index e280e6e..bdea116 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ android { } externalNativeBuild { cmake { + //arguments += "--trace-expand" cppFlags += "" arguments += "-DANDROID_STL=c++_shared" arguments += "-DCMAKE_TOOLCHAIN_FILE=$vcpkgHome/scripts/buildsystems/vcpkg.cmake" diff --git a/src/main/assets/LICENSE.txt b/src/main/assets/LICENSE.txt new file mode 100644 index 0000000..63ef813 --- /dev/null +++ b/src/main/assets/LICENSE.txt @@ -0,0 +1,212 @@ +The RmlUi samples include fonts that are separately licensed. They are listed +below along with their licenses. + +----------------------------------------------------------- +----------------------------------------------------------- +--- Lato --- +--- http://www.latofonts.com/ --- +--- --- +--- LatoLatin-Bold.ttf --- +--- LatoLatin-BoldItalic.ttf --- +--- LatoLatin-Italic.ttf --- +--- LatoLatin-Regular.ttf --- +----------------------------------------------------------- +----------------------------------------------------------- + +Copyright (c) 2010-2015, Łukasz Dziedzic (dziedzic@typoland.com), +with Reserved Font Name Lato. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + + +----------------------------------------------------------- +----------------------------------------------------------- +--- Noto Emoji --- +--- https://www.google.com/get/noto/ --- +--- --- +--- NotoEmoji-Regular.ttf --- +----------------------------------------------------------- +----------------------------------------------------------- + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/assets/LatoLatin-Bold.ttf b/src/main/assets/LatoLatin-Bold.ttf new file mode 100644 index 0000000..c598c24 Binary files /dev/null and b/src/main/assets/LatoLatin-Bold.ttf differ diff --git a/src/main/assets/LatoLatin-BoldItalic.ttf b/src/main/assets/LatoLatin-BoldItalic.ttf new file mode 100644 index 0000000..c1f225a Binary files /dev/null and b/src/main/assets/LatoLatin-BoldItalic.ttf differ diff --git a/src/main/assets/LatoLatin-Italic.ttf b/src/main/assets/LatoLatin-Italic.ttf new file mode 100644 index 0000000..c61fc07 Binary files /dev/null and b/src/main/assets/LatoLatin-Italic.ttf differ diff --git a/src/main/assets/LatoLatin-Regular.ttf b/src/main/assets/LatoLatin-Regular.ttf new file mode 100644 index 0000000..bcc5778 Binary files /dev/null and b/src/main/assets/LatoLatin-Regular.ttf differ diff --git a/src/main/assets/NotoEmoji-Regular.ttf b/src/main/assets/NotoEmoji-Regular.ttf new file mode 100644 index 0000000..19b7bad Binary files /dev/null and b/src/main/assets/NotoEmoji-Regular.ttf differ diff --git a/src/main/assets/Roboto/OFL.txt b/src/main/assets/Roboto/OFL.txt new file mode 100644 index 0000000..9c48e05 --- /dev/null +++ b/src/main/assets/Roboto/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/assets/Roboto/README.txt b/src/main/assets/Roboto/README.txt new file mode 100644 index 0000000..26368fa --- /dev/null +++ b/src/main/assets/Roboto/README.txt @@ -0,0 +1,118 @@ +Roboto Variable Font +==================== + +This download contains Roboto as both variable fonts and static fonts. + +Roboto is a variable font with these axes: + wdth + wght + +This means all the styles are contained in these files: + Roboto-VariableFont_wdth,wght.ttf + Roboto-Italic-VariableFont_wdth,wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Roboto: + static/Roboto_Condensed-Thin.ttf + static/Roboto_Condensed-ExtraLight.ttf + static/Roboto_Condensed-Light.ttf + static/Roboto_Condensed-Regular.ttf + static/Roboto_Condensed-Medium.ttf + static/Roboto_Condensed-SemiBold.ttf + static/Roboto_Condensed-Bold.ttf + static/Roboto_Condensed-ExtraBold.ttf + static/Roboto_Condensed-Black.ttf + static/Roboto_SemiCondensed-Thin.ttf + static/Roboto_SemiCondensed-ExtraLight.ttf + static/Roboto_SemiCondensed-Light.ttf + static/Roboto_SemiCondensed-Regular.ttf + static/Roboto_SemiCondensed-Medium.ttf + static/Roboto_SemiCondensed-SemiBold.ttf + static/Roboto_SemiCondensed-Bold.ttf + static/Roboto_SemiCondensed-ExtraBold.ttf + static/Roboto_SemiCondensed-Black.ttf + static/Roboto-Thin.ttf + static/Roboto-ExtraLight.ttf + static/Roboto-Light.ttf + static/Roboto-Regular.ttf + static/Roboto-Medium.ttf + static/Roboto-SemiBold.ttf + static/Roboto-Bold.ttf + static/Roboto-ExtraBold.ttf + static/Roboto-Black.ttf + static/Roboto_Condensed-ThinItalic.ttf + static/Roboto_Condensed-ExtraLightItalic.ttf + static/Roboto_Condensed-LightItalic.ttf + static/Roboto_Condensed-Italic.ttf + static/Roboto_Condensed-MediumItalic.ttf + static/Roboto_Condensed-SemiBoldItalic.ttf + static/Roboto_Condensed-BoldItalic.ttf + static/Roboto_Condensed-ExtraBoldItalic.ttf + static/Roboto_Condensed-BlackItalic.ttf + static/Roboto_SemiCondensed-ThinItalic.ttf + static/Roboto_SemiCondensed-ExtraLightItalic.ttf + static/Roboto_SemiCondensed-LightItalic.ttf + static/Roboto_SemiCondensed-Italic.ttf + static/Roboto_SemiCondensed-MediumItalic.ttf + static/Roboto_SemiCondensed-SemiBoldItalic.ttf + static/Roboto_SemiCondensed-BoldItalic.ttf + static/Roboto_SemiCondensed-ExtraBoldItalic.ttf + static/Roboto_SemiCondensed-BlackItalic.ttf + static/Roboto-ThinItalic.ttf + static/Roboto-ExtraLightItalic.ttf + static/Roboto-LightItalic.ttf + static/Roboto-Italic.ttf + static/Roboto-MediumItalic.ttf + static/Roboto-SemiBoldItalic.ttf + static/Roboto-BoldItalic.ttf + static/Roboto-ExtraBoldItalic.ttf + static/Roboto-BlackItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf b/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..978e53a Binary files /dev/null and b/src/main/assets/Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf differ diff --git a/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf b/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000..bba55f6 Binary files /dev/null and b/src/main/assets/Roboto/Roboto-VariableFont_wdth,wght.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Black.ttf b/src/main/assets/Roboto/static/Roboto-Black.ttf new file mode 100644 index 0000000..d51221a Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..c71c549 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Bold.ttf b/src/main/assets/Roboto/static/Roboto-Bold.ttf new file mode 100644 index 0000000..4658f9a Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..2ee0765 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf new file mode 100644 index 0000000..7092a88 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf new file mode 100644 index 0000000..a5536f5 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf new file mode 100644 index 0000000..75608c6 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf new file mode 100644 index 0000000..23dbbef Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Italic.ttf b/src/main/assets/Roboto/static/Roboto-Italic.ttf new file mode 100644 index 0000000..c3abaef Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Light.ttf b/src/main/assets/Roboto/static/Roboto-Light.ttf new file mode 100644 index 0000000..6fcd5f9 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto-LightItalic.ttf new file mode 100644 index 0000000..a6e5047 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Medium.ttf b/src/main/assets/Roboto/static/Roboto-Medium.ttf new file mode 100644 index 0000000..d629e98 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..ef9ed1b Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Regular.ttf b/src/main/assets/Roboto/static/Roboto-Regular.ttf new file mode 100644 index 0000000..7e3bb2f Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto-SemiBold.ttf new file mode 100644 index 0000000..3f34834 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf new file mode 100644 index 0000000..132cca1 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-Thin.ttf b/src/main/assets/Roboto/static/Roboto-Thin.ttf new file mode 100644 index 0000000..6ee97b8 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..0381198 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto-ThinItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf new file mode 100644 index 0000000..7529d1b Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf new file mode 100644 index 0000000..0c31e9f Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf new file mode 100644 index 0000000..a7c3cdf Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf new file mode 100644 index 0000000..24513a5 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf new file mode 100644 index 0000000..782442a Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..aeff7c2 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf new file mode 100644 index 0000000..16a1560 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..0f6fe70 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf new file mode 100644 index 0000000..3b387eb Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf new file mode 100644 index 0000000..e70c357 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf new file mode 100644 index 0000000..9f623e0 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf new file mode 100644 index 0000000..dd2842b Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf new file mode 100644 index 0000000..80ff64e Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf new file mode 100644 index 0000000..5af42d4 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf new file mode 100644 index 0000000..4297f17 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..6cb4656 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf new file mode 100644 index 0000000..1ccebcc Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf new file mode 100644 index 0000000..e58e966 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_Condensed-ThinItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf new file mode 100644 index 0000000..8eedb64 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Black.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf new file mode 100644 index 0000000..19a5096 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf new file mode 100644 index 0000000..47d0afa Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Bold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf new file mode 100644 index 0000000..319f62e Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf new file mode 100644 index 0000000..36423c3 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf new file mode 100644 index 0000000..b40ce77 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf new file mode 100644 index 0000000..e1c25a0 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf new file mode 100644 index 0000000..929a093 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf new file mode 100644 index 0000000..23454ff Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Italic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf new file mode 100644 index 0000000..b9aedcd Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Light.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf new file mode 100644 index 0000000..c096473 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-LightItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf new file mode 100644 index 0000000..e9c34d6 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Medium.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf new file mode 100644 index 0000000..ab34b70 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf new file mode 100644 index 0000000..36109ba Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Regular.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf new file mode 100644 index 0000000..6d10b33 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBold.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf new file mode 100644 index 0000000..e88bc4a Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf new file mode 100644 index 0000000..8ed8d79 Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-Thin.ttf differ diff --git a/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf new file mode 100644 index 0000000..81afeea Binary files /dev/null and b/src/main/assets/Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf differ diff --git a/src/main/assets/alien_small.tga b/src/main/assets/alien_small.tga new file mode 100644 index 0000000..6d7c53d Binary files /dev/null and b/src/main/assets/alien_small.tga differ diff --git a/src/main/assets/demo.rml b/src/main/assets/demo.rml new file mode 100644 index 0000000..82f2e6f --- /dev/null +++ b/src/main/assets/demo.rml @@ -0,0 +1,28 @@ + + + Demo + + + + + This is a sample. + + diff --git a/src/main/assets/hello_world.png b/src/main/assets/hello_world.png new file mode 100644 index 0000000..bec0f82 Binary files /dev/null and b/src/main/assets/hello_world.png differ diff --git a/src/main/assets/hello_world.rml b/src/main/assets/hello_world.rml new file mode 100644 index 0000000..42d6132 --- /dev/null +++ b/src/main/assets/hello_world.rml @@ -0,0 +1,13 @@ + + + Hello world + + + + +

RmlUi

+

Hello world!

+

The quick brown fox jumps over the lazy {{animal}}.

+ + +
diff --git a/src/main/assets/high_scores_alien_1.tga b/src/main/assets/high_scores_alien_1.tga new file mode 100644 index 0000000..7225577 Binary files /dev/null and b/src/main/assets/high_scores_alien_1.tga differ diff --git a/src/main/assets/high_scores_alien_2.tga b/src/main/assets/high_scores_alien_2.tga new file mode 100644 index 0000000..025cc33 Binary files /dev/null and b/src/main/assets/high_scores_alien_2.tga differ diff --git a/src/main/assets/high_scores_alien_3.tga b/src/main/assets/high_scores_alien_3.tga new file mode 100644 index 0000000..9f832c3 Binary files /dev/null and b/src/main/assets/high_scores_alien_3.tga differ diff --git a/src/main/assets/high_scores_defender.tga b/src/main/assets/high_scores_defender.tga new file mode 100644 index 0000000..e10f946 Binary files /dev/null and b/src/main/assets/high_scores_defender.tga differ diff --git a/src/main/assets/invader.rcss b/src/main/assets/invader.rcss new file mode 100644 index 0000000..e37239e --- /dev/null +++ b/src/main/assets/invader.rcss @@ -0,0 +1,765 @@ +@spritesheet theme +{ + src: invader.tga; + + /* For high dpi screens, designates the scaling it is intended to be shown at. */ + resolution: 1x; + + /** + The following specifies a list of sprite names and associated rectangles into the image given above. + Any sprite given here can be specified in a decorator. Their names must be globally unique. + Rectangles are specified as: x y width height. With the origin assumed to be at the top left corner. + */ + title-bar-l: 147px 0px 82px 85px; + title-bar-c: 229px 0px 1px 85px; + title-bar-r: 231px 0px 15px 85px; + + /* huditems are vertically flipped titlebars */ + huditem-l: 147px 55px 82px -55px; + huditem-c: 229px 55px 1px -55px; + huditem-r: 231px 55px 15px -55px; + + icon-help: 128px 152px 51px 39px; + icon-invader: 179px 152px 51px 39px; + icon-game: 230px 152px 51px 39px; + icon-hiscore: 281px 152px 51px 39px; + icon-waves: 332px 152px 51px 39px; + icon-flag: 336px 191px 51px 39px; + icon-lives: 383px 152px 51px 39px; + icon-score: 434px 152px 51px 39px; + + window-tl: 0px 0px 133px 140px; + window-t: 134px 0px 1px 140px; + window-tr: 136px 0px 10px 140px; + window-l: 0px 139px 10px 1px; + window-c: 11px 139px 1px 1px; + window-r: 10px 139px -10px 1px; /* mirrored left */ + window-bl: 0px 140px 11px 11px; + window-b: 11px 140px 1px 11px; + window-br: 136px 140px 10px 11px; + + button: 247px 0px 159px 45px; + button-hover: 247px 45px 159px 45px; + button-active: 247px 90px 159px 45px; + + button-inner: 259px 19px 135px 1px; + button-inner-hover: 259px 64px 135px 1px; + button-inner-active: 259px 109px 135px 1px; + + text-l: 162px 192px 14px 31px; + text-c: 176px 192px 1px 31px; + text-focus-l: 162px 230px 14px 31px; + text-focus-c: 176px 230px 1px 31px; + textarea: 162px 193px 145px 31px; + textarea-inner: 173px 206px 127px 10px; + textarea-focus: 162px 231px 145px 31px; + textarea-focus-inner: 173px 244px 127px 10px; + + selectbox-tl: 281px 275px 11px 9px; + selectbox-t: 292px 275px 1px 9px; + selectbox-tr: 294px 275px 11px 9px; + selectbox-l: 281px 283px 11px 1px; + selectbox-c: 292px 283px 1px 1px; + selectbox-bl: 281px 285px 11px 11px; + selectbox-b: 292px 285px 1px 11px; + selectbox-br: 294px 285px 11px 11px; + + selectvalue: 162px 192px 145px 37px; + selectvalue-hover: 162px 230px 145px 37px; + selectarrow: 307px 192px 30px 37px; + selectarrow-hover: 307px 230px 30px 37px; + selectarrow-active: 307px 268px 30px 37px; + + radio: 407px 0px 30px 30px; + radio-hover: 437px 0px 30px 30px; + radio-active: 467px 0px 30px 30px; + radio-checked: 407px 30px 30px 30px; + radio-checked-hover: 437px 30px 30px 30px; + radio-checked-active: 467px 30px 30px 30px; + + checkbox: 407px 60px 30px 30px; + checkbox-hover: 437px 60px 30px 30px; + checkbox-active: 467px 60px 30px 30px; + checkbox-checked: 407px 90px 30px 30px; + checkbox-checked-hover: 437px 90px 30px 30px; + checkbox-checked-active: 467px 90px 30px 30px; + + tableheader-l: 127px 192px 16px 31px; + tableheader-c: 143px 192px 2px 31px; + tableheader-r: 145px 192px 15px 31px; + + expand: 3px 232px 17px 17px; + expand-hover: 21px 232px 17px 17px; + expand-active: 39px 232px 17px 17px; + expand-collapsed: 3px 250px 17px 17px; + expand-collapsed-hover: 21px 250px 17px 17px; + expand-collapsed-active: 39px 250px 17px 17px; + + slidertrack-t: 70px 199px 27px 2px; + slidertrack-c: 70px 201px 27px 1px; + slidertrack-b: 70px 202px 27px 2px; + + sliderbar-t: 56px 152px 23px 23px; + sliderbar-c: 56px 175px 23px 1px; + sliderbar-b: 56px 176px 23px 22px; + sliderbar-hover-t: 80px 152px 23px 23px; + sliderbar-hover-c: 80px 175px 23px 1px; + sliderbar-hover-b: 80px 176px 23px 22px; + sliderbar-active-t: 104px 152px 23px 23px; + sliderbar-active-c: 104px 175px 23px 1px; + sliderbar-active-b: 104px 176px 23px 22px; + + sliderarrowdec: 0px 152px 27px 24px; + sliderarrowdec-hover: 0px 177px 27px 24px; + sliderarrowdec-active: 0px 202px 27px 24px; + + sliderarrowinc: 28px 152px 27px 24px; + sliderarrowinc-hover: 28px 177px 27px 24px; + sliderarrowinc-active: 28px 202px 27px 24px; + + range-track: 219px 194px 3px 32px; + range-track-inner: 220px 204px 1px 14px; + range-track-focus: 219px 232px 3px 32px; + range-track-focus-inner: 220px 242px 1px 14px; + + range-bar: 127px 191px 34px 32px; + range-dec: 3px 232px 17px 17px; + range-dec-hover: 21px 232px 17px 17px; + range-dec-active: 39px 232px 17px 17px; + range-inc: 3px 250px 17px 17px; + range-inc-hover: 21px 250px 17px 17px; + range-inc-active: 39px 250px 17px 17px; + + progress-l: 103px 267px 13px 34px; + progress-c: 116px 267px 54px 34px; + progress-r: 170px 267px 13px 34px; + progress-fill-l: 110px 302px 6px 34px; + progress-fill-c: 140px 302px 6px 34px; + progress-fill-r: 170px 302px 6px 34px; + gauge: 0px 271px 100px 86px; + gauge-fill: 0px 356px 100px 86px; +} + +body +{ + font-family: LatoLatin; + font-weight: normal; + font-style: normal; + font-size: 15dp; + color: white; + nav: auto; +} + +body.window +{ + padding-top: 43dp; + padding-bottom: 20dp; + + min-width: 250dp; + max-width: 800dp; + + min-height: 135dp; + max-height: 700dp; +} + +div#title_bar +{ + z-index: 1; + + position: absolute; + top: 7dp; + left: 0; + + text-align: left; + vertical-align: bottom; +} + +div#title_bar div#icon +{ + position: absolute; + left: 15dp; + top: -4dp; + + width: 51dp; + height: 39dp; +} + + +div#title_bar span +{ + padding-left: 85dp; + padding-right: 25dp; + padding-top: 18dp; + padding-bottom: 43dp; + + vertical-align: top; + + line-height: 24dp; + font-size: 20dp; + font-weight: bold; + + font-effect: glow(1dp black); + decorator: tiled-horizontal( title-bar-l, title-bar-c, title-bar-r ); +} + +div#window +{ + width: auto; + height: 100%; + padding: 10dp 15dp; + + decorator: tiled-box( + window-tl, window-t, window-tr, + window-l, window-c, window-r, + window-bl, window-b, window-br + ); +} + +div#content +{ + z-index: 2; + + width: auto; + height: 100%; + + overflow: hidden auto; + + text-align: center; +} + + + +p +{ + text-align: left; + margin-bottom: 1em; +} + +h1 +{ + margin-left: 0.4em; + margin-bottom: 0.4em; + + text-align: left; + font-size: 16dp; + font-weight: bold; + + font-effect: glow(1dp 1dp 1dp 1dp #1117); +} + + + +input, +select +{ + margin-left: 20dp; +} + +input.submit +{ + margin-left: 0; +} + +input, button, select { + nav: auto; +} + +button, +input.submit +{ + display: inline-block; + + width: 159dp; + height: 33dp; + padding-top: 12dp; + + font-size: 16dp; + text-align: center; + tab-index: auto; + + decorator: image(button); +} + +button:focus, +input.submit:focus +{ + font-effect: blur(3dp #fff); +} + +button:hover, button:focus-visible, +input.submit:hover, input.submit:focus-visible +{ + decorator: image(button-hover); +} + +button:active, +input.submit:active +{ + decorator: image(button-active); +} + +input.submit:disabled +{ + decorator: image(button); + image-color: rgba(50, 150, 150, 120); + cursor: unavailable; +} + +input.text, input.password +{ + box-sizing: border-box; + height: 31dp; + padding: 11dp 10dp 0; + decorator: tiled-horizontal( text-l, text-c, auto ); /* Right becomes mirrored left */ + cursor: text; + text-align: left; +} +input.text:focus-visible, input.password:focus-visible +{ + decorator: tiled-horizontal( text-focus-l, text-focus-c, auto ); +} + +textarea +{ + padding: 5dp 8dp; + decorator: ninepatch(textarea, textarea-inner, 1.0) border-box; + cursor: text; + text-align: left; + line-height: 1.3; + border-width: 14dp 12dp 10dp; + border-color: transparent; +} +textarea:focus-visible +{ + decorator: ninepatch(textarea-focus, textarea-focus-inner, 1.0) border-box; +} + +input.text, +input.password, +select, +textarea +{ + color: #333; + font-size: 13dp; +} + +table input.text +{ + box-sizing: border-box; + width: 100%; + margin: 0; + padding: 1dp 5dp; + height: auto; + line-height: 16dp; + text-align: center; + + border-width: 1dp; + border-color: black; + background-color: white; + + font-size: 15dp; +} +table input.text, table input.text:focus-visible +{ + decorator: none; +} + + +select +{ + width: 175dp; + height: 37dp; +} + +select selectvalue +{ + width: auto; + margin-right: 30dp; + + height: 25dp; + padding: 12dp 10dp 0dp 10dp; + + decorator: image( selectvalue ); +} +select:focus-visible selectvalue { + decorator: image( selectvalue-hover ); +} + +select selectarrow +{ + width: 30dp; + height: 37dp; + + decorator: image( selectarrow ); +} + +select:hover selectarrow, select:focus-visible selectarrow +{ + decorator: image( selectarrow-hover ); +} + +select:active selectarrow, +select selectarrow:checked +{ + decorator: image( selectarrow-active ); +} + +select selectbox +{ + margin-left: 1dp; + margin-top: -7dp; + margin-bottom: -10dp; + width: 162dp; + padding: 1dp 4dp 4dp 4dp; +} + +select selectbox, +tbody +{ + decorator: tiled-box( + selectbox-tl, selectbox-t, selectbox-tr, + selectbox-l, selectbox-c, auto, /* auto mirrors left */ + selectbox-bl, selectbox-b, selectbox-br + ); +} + +select selectbox option +{ + width: auto; + padding: 3dp 0 3dp 6dp; + background: #DDDD; +} + +select selectbox option:nth-child(even), +tr:nth-child(even) +{ + background: #FFFFFFA0; +} +select selectbox option:checked +{ + font-weight: bold; +} +select selectbox option:hover +{ + background: #FF5D5D; +} + + + +input.radio, +input.checkbox +{ + width: 30dp; + height: 30dp; + + vertical-align: -11dp; +} + +input.radio +{ + decorator: image(radio); +} + +input.radio:hover, input.radio:focus-visible +{ + decorator: image(radio-hover); +} + +input.radio:active +{ + decorator: image(radio-active); +} + +input.radio:checked +{ + decorator: image(radio-checked); +} + +input.radio:checked:hover, input.radio:checked:focus-visible +{ + decorator: image(radio-checked-hover); +} + +input.radio:checked:active +{ + decorator: image(radio-checked-active); +} + +input.checkbox +{ + decorator: image(checkbox); +} + +input.checkbox:hover, input.checkbox:focus-visible +{ + decorator: image(checkbox-hover); +} + +input.checkbox:active +{ + decorator: image(checkbox-active); +} + +input.checkbox:checked +{ + decorator: image(checkbox-checked); +} + +input.checkbox:checked:hover, input.checkbox:checked:focus-visible +{ + decorator: image(checkbox-checked-hover); +} + +input.checkbox:checked:active +{ + decorator: image(checkbox-checked-active); +} + +input.range { + width: 200dp; + height: 32dp; + vertical-align: -12dp; +} +input.range slidertrack { + margin-top: 3dp; + height: 22dp; + image-color: #ecc; + decorator: ninepatch( range-track, range-track-inner, 1.0 ); +} +input.range:focus-visible slidertrack { + decorator: ninepatch( range-track-focus, range-track-focus-inner, 1.0 ); +} +input.range sliderprogress { + background: rgba(100, 0, 0, 80); + margin-top: 8dp; + height: 7dp; +} +input.range sliderbar { + margin-left: -8dp; + margin-right: -7dp; + margin-top: -3dp; + width: 34dp; + height: 23dp; + decorator: image( range-bar ); +} +input.range sliderbar:hover, input.range slidertrack:hover + sliderbar { + image-color: #cc0; +} +input.range sliderbar:active, input.range slidertrack:active + sliderbar { + image-color: #c80; +} +input.range sliderarrowdec, input.range sliderarrowinc { + width: 17dp; + height: 17dp; + margin-top: 6dp; +} +input.range sliderarrowdec { decorator: image( range-dec ); } +input.range sliderarrowinc { decorator: image( range-inc ); } +input.range sliderarrowdec:hover { decorator: image( range-dec-hover ); } +input.range sliderarrowinc:hover { decorator: image( range-inc-hover ); } +input.range sliderarrowdec:active { decorator: image( range-dec-active ); } +input.range sliderarrowinc:active { decorator: image( range-inc-active ); } + +thead tr { + height: 35dp; + decorator: tiled-horizontal( tableheader-l, tableheader-c, tableheader-r ); +} +thead td { + padding-top: 11dp; +} + +tbody { + /* Margin left/right only affects the background positioning for the decorator, not the cell placement */ + margin-left: 5dp; + margin-right: 4dp; + /* Padding top/bottom adds extra spacing between the header row and the body, and between the body and table bottom */ + padding-top: 4dp; + padding-bottom: 4dp; +} +tbody tr { + margin-left: 9dp; + margin-right: 8dp; + color: black; +} + + +expand +{ + display: block; + + margin: 1dp 0 1dp 5dp; + height: 17dp; + width: 17dp; + + decorator: image( expand ); +} + +expand:hover +{ + decorator: image( expand-hover ); +} + +expand:active +{ + decorator: image( expand-active ); +} + +expand.collapsed +{ + decorator: image( expand-collapsed ); +} + +expand.collapsed:hover +{ + decorator: image( expand-collapsed-hover ); +} + +expand.collapsed:active +{ + decorator: image( expand-collapsed-active ); +} + + +scrollbarvertical +{ + margin-top: -6dp; + margin-bottom: -6dp; + margin-right: -11dp; + width: 27dp; +} + +scrollbarvertical slidertrack +{ + decorator: tiled-vertical( slidertrack-t, slidertrack-c, slidertrack-b ); +} +scrollbarvertical slidertrack:active +{ + image-color: #aaa; +} + +scrollbarvertical sliderbar +{ + margin-left: 4dp; + width: 23dp; + min-height: 46dp; + + decorator: tiled-vertical( sliderbar-t, sliderbar-c, sliderbar-b ); +} +scrollbarvertical sliderbar:hover +{ + decorator: tiled-vertical( sliderbar-hover-t, sliderbar-hover-c, sliderbar-hover-b ); +} +scrollbarvertical sliderbar:active +{ + decorator: tiled-vertical( sliderbar-active-t, sliderbar-active-c, sliderbar-active-b ); +} + +scrollbarvertical sliderarrowdec, +scrollbarvertical sliderarrowinc +{ + width: 27dp; + height: 24dp; +} +scrollbarvertical sliderarrowdec +{ + decorator: image( sliderarrowdec ); +} +scrollbarvertical sliderarrowdec:hover +{ + decorator: image( sliderarrowdec-hover ); +} +scrollbarvertical sliderarrowdec:active +{ + decorator: image( sliderarrowdec-active ); +} + +scrollbarvertical sliderarrowinc +{ + decorator: image( sliderarrowinc ); +} +scrollbarvertical sliderarrowinc:hover +{ + decorator: image( sliderarrowinc-hover ); +} +scrollbarvertical sliderarrowinc:active +{ + decorator: image( sliderarrowinc-active ); +} + +scrollbarhorizontal +{ + width: 0; + height: 0; +} + +textarea scrollbarvertical +{ + cursor: arrow; + width: 12dp; + margin: -3dp -7dp -3dp 7dp; +} +textarea scrollbarvertical slidertrack +{ + decorator: none; + background-color: #eee; +} +textarea scrollbarvertical sliderbar +{ + decorator: none; + background-color: #bc0000; + border-left: 5dp #cc7272; + width: 7dp; + min-height: 16dp; + margin: 0; +} +textarea scrollbarvertical sliderbar:hover +{ + decorator: none; + background-color: #ca3535; +} + +textarea scrollbarvertical sliderbar:active +{ + decorator: none; + background-color: #880000; +} +textarea scrollbarvertical sliderarrowdec, +textarea scrollbarvertical sliderarrowinc +{ + width: 0; + height: 0; +} + +textarea scrollbarhorizontal +{ + cursor: arrow; + height: 10dp; + margin: 3dp -7dp -3dp -3dp; +} +textarea scrollbarhorizontal sliderbar +{ + background-color: #bc0000; + border-top: 4dp #cc7272; + height: 6dp; + min-width: 10dp; +} +textarea scrollbarhorizontal sliderbar:hover +{ + background-color: #ca3535; +} +textarea scrollbarhorizontal sliderbar:active +{ + background-color: #880000; +} +textarea scrollbarhorizontal slidertrack +{ + background-color: #eee; +} + +textarea scrollbarcorner +{ + cursor: arrow; + background-color: #ccc; + margin-top: 3dp; + margin-left: 7dp; +} diff --git a/src/main/assets/invader.tga b/src/main/assets/invader.tga new file mode 100644 index 0000000..3223e72 Binary files /dev/null and b/src/main/assets/invader.tga differ diff --git a/src/main/assets/present.tga b/src/main/assets/present.tga new file mode 100644 index 0000000..f2c8b1b Binary files /dev/null and b/src/main/assets/present.tga differ diff --git a/src/main/assets/rml.rcss b/src/main/assets/rml.rcss new file mode 100644 index 0000000..0905b24 --- /dev/null +++ b/src/main/assets/rml.rcss @@ -0,0 +1,61 @@ +/* +* Default styles for all the basic elements. +*/ + +div +{ + display: block; +} + +p +{ + display: block; +} + +h1 +{ + display: block; +} + +em +{ + font-style: italic; +} + +strong +{ + font-weight: bold; +} + +select +{ + text-align: left; +} + +tabset tabs +{ + display: block; +} + +table { + box-sizing: border-box; + display: table; +} +tr { + box-sizing: border-box; + display: table-row; +} +td { + box-sizing: border-box; + display: table-cell; +} +col { + box-sizing: border-box; + display: table-column; +} +colgroup { + display: table-column-group; +} +thead, tbody, tfoot { + display: table-row-group; +} diff --git a/src/main/assets/window.rcss b/src/main/assets/window.rcss new file mode 100644 index 0000000..480c5d8 --- /dev/null +++ b/src/main/assets/window.rcss @@ -0,0 +1,36 @@ +body { + font-family: LatoLatin; + font-size: 18px; + color: #02475e; + background: #fefecc; + text-align: center; + padding: 2em 1em; + position: absolute; + border: 2px #ccc; + width: 500px; + height: 200px; + margin: auto; +} + +h1 { + color: #f6470a; + font-size: 1.5em; + font-weight: bold; + margin-bottom: 0.7em; +} + +p { + margin: 0.7em 0; +} + +input.text { + background-color: #fff; + color: #555; + border: 2px #999; + padding: 5px; + tab-index: auto; + cursor: text; + box-sizing: border-box; + width: 200px; + font-size: 0.9em; +} diff --git a/src/main/assets/window.rml b/src/main/assets/window.rml new file mode 100644 index 0000000..e368b36 --- /dev/null +++ b/src/main/assets/window.rml @@ -0,0 +1,19 @@ + diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt index d0106ab..834f6c8 100644 --- a/src/main/cpp/CMakeLists.txt +++ b/src/main/cpp/CMakeLists.txt @@ -9,6 +9,12 @@ set(BINDER_DIR "${ANDROID_SDK}/platforms/android-36/optional/libbinder_ndk_cpp") find_package(RmlUi CONFIG REQUIRED) +#get_cmake_property(_variableNames VARIABLES) +#list(SORT _variableNames) +#foreach(_variableName ${_variableNames}) +# message(STATUS "${_variableName}=${${_variableName}}") +#endforeach() + add_library(mosis-service SHARED mosis-service.cpp com/omixlab/mosis/IMosisService.cpp @@ -23,6 +29,11 @@ add_library(mosis-service SHARED kernel.cpp glad/src/egl.c glad/src/gles2.c + RmlUi_Renderer_GL3.cpp +) +target_compile_definitions(mosis-service PUBLIC + RMLUI_NUM_MSAA_SAMPLES=2 + RMLUI_GL3_CUSTOM_LOADER= ) target_include_directories(mosis-service PUBLIC ${CMAKE_CURRENT_LIST_DIR} @@ -44,7 +55,6 @@ add_library(mosis-test SHARED quad.cpp egl_context.cpp render_target.cpp - kernel.cpp logger.cpp glad/src/egl.c glad/src/gles2.c diff --git a/src/main/cpp/RmlUi_Renderer_GL3.cpp b/src/main/cpp/RmlUi_Renderer_GL3.cpp new file mode 100644 index 0000000..c7d4f32 --- /dev/null +++ b/src/main/cpp/RmlUi_Renderer_GL3.cpp @@ -0,0 +1,2195 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * 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. + * + */ + +#include "RmlUi_Renderer_GL3.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "#version 300 es\nprecision highp float;\n" + #include +#elif defined RMLUI_GL3_CUSTOM_LOADER + #define RMLUI_SHADER_HEADER_VERSION "#version 300 es\n" + #include RMLUI_GL3_CUSTOM_LOADER +#else + #define RMLUI_SHADER_HEADER_VERSION "#version 330\n" + #define GLAD_GL_IMPLEMENTATION + #include "RmlUi_Include_GL3.h" +#endif + +// Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. +#ifndef RMLUI_NUM_MSAA_SAMPLES + #define RMLUI_NUM_MSAA_SAMPLES 2 +#endif + +#define MAX_NUM_STOPS 16 +#define BLUR_SIZE 7 +#define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2) + +#define RMLUI_STRINGIFY_IMPL(x) #x +#define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x) + +#define RMLUI_SHADER_HEADER \ + RMLUI_SHADER_HEADER_VERSION "#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n#line " RMLUI_STRINGIFY(__LINE__) "\n" + +static const char* shader_vert_main = 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; + vec4 outPos = _transform * vec4(translatedPos, 0.0, 1.0); + + gl_Position = outPos; +} +)"; +static const char* shader_frag_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_frag_color = RMLUI_SHADER_HEADER R"( +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 finalColor; + +void main() { + finalColor = fragColor; +} +)"; + +enum class ShaderGradientFunction { Linear, Radial, Conic, RepeatingLinear, RepeatingRadial, RepeatingConic }; // Must match shader definitions below. + +static const char* shader_frag_gradient = RMLUI_SHADER_HEADER R"( +#define LINEAR 0 +#define RADIAL 1 +#define CONIC 2 +#define REPEATING_LINEAR 3 +#define REPEATING_RADIAL 4 +#define REPEATING_CONIC 5 +#define PI 3.14159265 + +uniform int _func; // one of the above definitions +uniform vec2 _p; // linear: starting point, radial: center, conic: center +uniform vec2 _v; // linear: vector to ending point, radial: 2d curvature (inverse radius), conic: angled unit vector +uniform vec4 _stop_colors[MAX_NUM_STOPS]; +uniform float _stop_positions[MAX_NUM_STOPS]; // normalized, 0 -> starting point, 1 -> ending point +uniform int _num_stops; + +in vec2 fragTexCoord; +in vec4 fragColor; +out vec4 finalColor; + +vec4 mix_stop_colors(float t) { + vec4 color = _stop_colors[0]; + + for (int i = 1; i < _num_stops; i++) + color = mix(color, _stop_colors[i], smoothstep(_stop_positions[i-1], _stop_positions[i], t)); + + return color; +} + +void main() { + float t = 0.0; + + if (_func == LINEAR || _func == REPEATING_LINEAR) + { + float dist_square = dot(_v, _v); + vec2 V = fragTexCoord - _p; + t = dot(_v, V) / dist_square; + } + else if (_func == RADIAL || _func == REPEATING_RADIAL) + { + vec2 V = fragTexCoord - _p; + t = length(_v * V); + } + else if (_func == CONIC || _func == REPEATING_CONIC) + { + mat2 R = mat2(_v.x, -_v.y, _v.y, _v.x); + vec2 V = R * (fragTexCoord - _p); + t = 0.5 + atan(-V.x, V.y) / (2.0 * PI); + } + + if (_func == REPEATING_LINEAR || _func == REPEATING_RADIAL || _func == REPEATING_CONIC) + { + float t0 = _stop_positions[0]; + float t1 = _stop_positions[_num_stops - 1]; + t = t0 + mod(t - t0, t1 - t0); + } + + finalColor = fragColor * mix_stop_colors(t); +} +)"; + +// "Creation" by Danilo Guanabara, based on: https://www.shadertoy.com/view/XsXXDn +static const char* shader_frag_creation = RMLUI_SHADER_HEADER R"( +uniform float _value; +uniform vec2 _dimensions; + +in vec2 fragTexCoord; +in vec4 fragColor; +out vec4 finalColor; + +void main() { + float t = _value; + vec3 c; + float l; + for (int i = 0; i < 3; i++) { + vec2 p = fragTexCoord; + vec2 uv = p; + p -= .5; + p.x *= _dimensions.x / _dimensions.y; + float z = t + float(i) * .07; + l = length(p); + uv += p / l * (sin(z) + 1.) * abs(sin(l * 9. - z - z)); + c[i] = .01 / length(mod(uv, 1.) - .5); + } + finalColor = vec4(c / l, fragColor.a); +} +)"; + +static const char* shader_vert_passthrough = RMLUI_SHADER_HEADER R"( +in vec2 inPosition; +in vec2 inTexCoord0; + +out vec2 fragTexCoord; + +void main() { + fragTexCoord = inTexCoord0; + gl_Position = vec4(inPosition, 0.0, 1.0); +} +)"; +static const char* shader_frag_passthrough = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + finalColor = texture(_tex, fragTexCoord); +} +)"; +static const char* shader_frag_color_matrix = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform mat4 _color_matrix; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + // The general case uses a 4x5 color matrix for full rgba transformation, plus a constant term with the last column. + // However, we only consider the case of rgb transformations. Thus, we could in principle use a 3x4 matrix, but we + // keep the alpha row for simplicity. + // In the general case we should do the matrix transformation in non-premultiplied space. However, without alpha + // transformations, we can do it directly in premultiplied space to avoid the extra division and multiplication + // steps. In this space, the constant term needs to be multiplied by the alpha value, instead of unity. + vec4 texColor = texture(_tex, fragTexCoord); + vec3 transformedColor = vec3(_color_matrix * texColor); + finalColor = vec4(transformedColor, texColor.a); +} +)"; +static const char* shader_frag_blend_mask = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform sampler2D _texMask; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + vec4 texColor = texture(_tex, fragTexCoord); + float maskAlpha = texture(_texMask, fragTexCoord).a; + finalColor = texColor * maskAlpha; +} +)"; + +#define RMLUI_SHADER_BLUR_HEADER \ + RMLUI_SHADER_HEADER "\n#define BLUR_SIZE " RMLUI_STRINGIFY(BLUR_SIZE) "\n#define BLUR_NUM_WEIGHTS " RMLUI_STRINGIFY(BLUR_NUM_WEIGHTS) + +static const char* shader_vert_blur = RMLUI_SHADER_BLUR_HEADER R"( +uniform vec2 _texelOffset; + +in vec3 inPosition; +in vec2 inTexCoord0; + +out vec2 fragTexCoord[BLUR_SIZE]; + +void main() { + for(int i = 0; i < BLUR_SIZE; i++) + fragTexCoord[i] = inTexCoord0 - float(i - BLUR_NUM_WEIGHTS + 1) * _texelOffset; + gl_Position = vec4(inPosition, 1.0); +} +)"; +static const char* shader_frag_blur = RMLUI_SHADER_BLUR_HEADER R"( +uniform sampler2D _tex; +uniform float _weights[BLUR_NUM_WEIGHTS]; +uniform vec2 _texCoordMin; +uniform vec2 _texCoordMax; + +in vec2 fragTexCoord[BLUR_SIZE]; +out vec4 finalColor; + +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); + for(int i = 0; i < BLUR_SIZE; i++) + { + vec2 in_region = step(_texCoordMin, fragTexCoord[i]) * step(fragTexCoord[i], _texCoordMax); + color += texture(_tex, fragTexCoord[i]) * in_region.x * in_region.y * _weights[abs(i - BLUR_NUM_WEIGHTS + 1)]; + } + finalColor = color; +} +)"; +static const char* shader_frag_drop_shadow = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform vec2 _texCoordMin; +uniform vec2 _texCoordMax; +uniform vec4 _color; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + vec2 in_region = step(_texCoordMin, fragTexCoord) * step(fragTexCoord, _texCoordMax); + finalColor = texture(_tex, fragTexCoord).a * in_region.x * in_region.y * _color; +} +)"; + +enum class ProgramId { + None, + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class VertShaderId { + Main, + Passthrough, + Blur, + Count, +}; +enum class FragShaderId { + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class UniformId { + Translate, + Transform, + Tex, + Color, + ColorMatrix, + TexelOffset, + TexCoordMin, + TexCoordMax, + TexMask, + Weights, + Func, + P, + V, + StopColors, + StopPositions, + NumStops, + Value, + Dimensions, + Count, +}; + +namespace Gfx { + +static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color", "_color_matrix", + "_texelOffset", "_texCoordMin", "_texCoordMax", "_texMask", "_weights[0]", "_func", "_p", "_v", "_stop_colors[0]", "_stop_positions[0]", + "_num_stops", "_value", "_dimensions"}; + +enum class VertexAttribute { Position, Color0, TexCoord0, Count }; +static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; + +struct VertShaderDefinition { + VertShaderId id; + const char* name_str; + const char* code_str; +}; +struct FragShaderDefinition { + FragShaderId id; + const char* name_str; + const char* code_str; +}; +struct ProgramDefinition { + ProgramId id; + const char* name_str; + VertShaderId vert_shader; + FragShaderId frag_shader; +}; + +// clang-format off +static const VertShaderDefinition vert_shader_definitions[] = { + {VertShaderId::Main, "main", shader_vert_main}, + {VertShaderId::Passthrough, "passthrough", shader_vert_passthrough}, + {VertShaderId::Blur, "blur", shader_vert_blur}, +}; +static const FragShaderDefinition frag_shader_definitions[] = { + {FragShaderId::Color, "color", shader_frag_color}, + {FragShaderId::Texture, "texture", shader_frag_texture}, + {FragShaderId::Gradient, "gradient", shader_frag_gradient}, + {FragShaderId::Creation, "creation", shader_frag_creation}, + {FragShaderId::Passthrough, "passthrough", shader_frag_passthrough}, + {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, + {FragShaderId::BlendMask, "blend_mask", shader_frag_blend_mask}, + {FragShaderId::Blur, "blur", shader_frag_blur}, + {FragShaderId::DropShadow, "drop_shadow", shader_frag_drop_shadow}, +}; +static const ProgramDefinition program_definitions[] = { + {ProgramId::Color, "color", VertShaderId::Main, FragShaderId::Color}, + {ProgramId::Texture, "texture", VertShaderId::Main, FragShaderId::Texture}, + {ProgramId::Gradient, "gradient", VertShaderId::Main, FragShaderId::Gradient}, + {ProgramId::Creation, "creation", VertShaderId::Main, FragShaderId::Creation}, + {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, + {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, + {ProgramId::BlendMask, "blend_mask", VertShaderId::Passthrough, FragShaderId::BlendMask}, + {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, + {ProgramId::DropShadow, "drop_shadow", VertShaderId::Passthrough, FragShaderId::DropShadow}, +}; +// clang-format on + +template +class EnumArray { +public: + const T& operator[](Enum id) const + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + T& operator[](Enum id) + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + auto begin() const { return ids.begin(); } + auto end() const { return ids.end(); } + +private: + Rml::Array ids = {}; +}; + +using Programs = EnumArray; +using VertShaders = EnumArray; +using FragShaders = EnumArray; + +class Uniforms { +public: + GLint Get(ProgramId id, UniformId uniform) const + { + auto it = map.find(ToKey(id, uniform)); + if (it != map.end()) + return it->second; + return -1; + } + void Insert(ProgramId id, UniformId uniform, GLint location) { map[ToKey(id, uniform)] = location; } + +private: + using Key = uint64_t; + Key ToKey(ProgramId id, UniformId uniform) const { return (static_cast(id) << 32) | static_cast(uniform); } + Rml::UnorderedMap map; +}; + +struct ProgramData { + Programs programs; + VertShaders vert_shaders; + FragShaders frag_shaders; + Uniforms uniforms; +}; + +struct CompiledGeometryData { + GLuint vao; + GLuint vbo; + GLuint ibo; + GLsizei draw_count; +}; + +struct FramebufferData { + int width, height; + GLuint framebuffer; + GLuint color_tex_buffer; + GLuint color_render_buffer; + GLuint depth_stencil_buffer; + bool owns_depth_stencil_buffer; +}; + +enum class FramebufferAttachment { None, DepthStencil }; + +static void CheckGLError(const char* operation_name) +{ +#ifdef RMLUI_DEBUG + GLenum error_code = glGetError(); + if (error_code != GL_NO_ERROR) + { + static const Rml::Pair 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 bool CreateShader(GLuint& out_shader_id, GLenum shader_type, const char* code_string) +{ + RMLUI_ASSERT(shader_type == GL_VERTEX_SHADER || shader_type == GL_FRAGMENT_SHADER); + + 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 false; + } + + CheckGLError("CreateShader"); + + out_shader_id = id; + return true; +} + +static bool CreateProgram(GLuint& out_program, Uniforms& inout_uniform_map, ProgramId program_id, GLuint vertex_shader, GLuint fragment_shader) +{ + GLuint id = glCreateProgram(); + RMLUI_ASSERT(id); + + for (GLuint i = 0; i < (GLuint)VertexAttribute::Count; i++) + glBindAttribLocation(id, i, vertex_attribute_names[i]); + + CheckGLError("BindAttribLocations"); + + 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 = 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. + UniformId program_uniform = UniformId::Count; + for (int i = 0; i < (int)UniformId::Count; i++) + { + const char* uniform_name = program_uniform_names[i]; + if (strcmp(name_buf, uniform_name) == 0) + { + program_uniform = (UniformId)i; + break; + } + } + + if ((size_t)program_uniform < (size_t)UniformId::Count) + { + inout_uniform_map.Insert(program_id, 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 CreateFramebuffer(FramebufferData& out_fb, int width, int height, int samples, FramebufferAttachment attachment, + GLuint shared_depth_stencil_buffer) +{ +#ifdef RMLUI_PLATFORM_EMSCRIPTEN + constexpr GLint wrap_mode = GL_CLAMP_TO_EDGE; +#else + constexpr GLint wrap_mode = GL_CLAMP_TO_BORDER; // GL_REPEAT GL_MIRRORED_REPEAT GL_CLAMP_TO_EDGE +#endif + + constexpr GLenum color_format = GL_RGBA8; // GL_RGBA8 GL_SRGB8_ALPHA8 GL_RGBA16F + constexpr GLint min_mag_filter = GL_LINEAR; // GL_NEAREST + const Rml::Colourf border_color(0.f, 0.f); + + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + GLuint color_tex_buffer = 0; + GLuint color_render_buffer = 0; + if (samples > 0) + { + glGenRenderbuffers(1, &color_render_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, color_render_buffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, color_format, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color_render_buffer); + } + else + { + glGenTextures(1, &color_tex_buffer); + glBindTexture(GL_TEXTURE_2D, color_tex_buffer); + glTexImage2D(GL_TEXTURE_2D, 0, color_format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_mag_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, min_mag_filter); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_mode); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_mode); +#ifndef RMLUI_PLATFORM_EMSCRIPTEN + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, &border_color[0]); +#endif + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_tex_buffer, 0); + } + + // Create depth/stencil buffer storage attachment. + GLuint depth_stencil_buffer = 0; + if (attachment != FramebufferAttachment::None) + { + if (shared_depth_stencil_buffer) + { + // Share depth/stencil buffer + depth_stencil_buffer = shared_depth_stencil_buffer; + } + else + { + // Create new depth/stencil buffer + glGenRenderbuffers(1, &depth_stencil_buffer); + glBindRenderbuffer(GL_RENDERBUFFER, depth_stencil_buffer); + + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, width, height); + } + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depth_stencil_buffer); + } + + const GLuint framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL framebuffer could not be generated. Error code %x.", framebuffer_status); + return false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + CheckGLError("CreateFramebuffer"); + + out_fb = {}; + out_fb.width = width; + out_fb.height = height; + out_fb.framebuffer = framebuffer; + out_fb.color_tex_buffer = color_tex_buffer; + out_fb.color_render_buffer = color_render_buffer; + out_fb.depth_stencil_buffer = depth_stencil_buffer; + out_fb.owns_depth_stencil_buffer = !shared_depth_stencil_buffer; + + return true; +} + +static void DestroyFramebuffer(FramebufferData& fb) +{ + if (fb.framebuffer) + glDeleteFramebuffers(1, &fb.framebuffer); + if (fb.color_tex_buffer) + glDeleteTextures(1, &fb.color_tex_buffer); + if (fb.color_render_buffer) + glDeleteRenderbuffers(1, &fb.color_render_buffer); + if (fb.owns_depth_stencil_buffer && fb.depth_stencil_buffer) + glDeleteRenderbuffers(1, &fb.depth_stencil_buffer); + fb = {}; +} + +static GLuint CreateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) +{ + GLuint texture_id = 0; + glGenTextures(1, &texture_id); + if (texture_id == 0) + return 0; + + 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.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); + + glBindTexture(GL_TEXTURE_2D, 0); + + return texture_id; +} + +static void BindTexture(const FramebufferData& fb) +{ + if (!fb.color_tex_buffer) + { + RMLUI_ERRORMSG("Only framebuffers with color textures can be bound as textures. This framebuffer probably uses multisampling which needs a " + "blit step first."); + } + + glBindTexture(GL_TEXTURE_2D, fb.color_tex_buffer); +} + +static bool CreateShaders(ProgramData& data) +{ + RMLUI_ASSERT(std::all_of(data.vert_shaders.begin(), data.vert_shaders.end(), [](auto&& value) { return value == 0; })); + RMLUI_ASSERT(std::all_of(data.frag_shaders.begin(), data.frag_shaders.end(), [](auto&& value) { return value == 0; })); + RMLUI_ASSERT(std::all_of(data.programs.begin(), data.programs.end(), [](auto&& value) { return value == 0; })); + auto ReportError = [](const char* type, const char* name) { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL %s: '%s'.", type, name); + return false; + }; + + for (const VertShaderDefinition& def : vert_shader_definitions) + { + if (!CreateShader(data.vert_shaders[def.id], GL_VERTEX_SHADER, def.code_str)) + return ReportError("vertex shader", def.name_str); + } + + for (const FragShaderDefinition& def : frag_shader_definitions) + { + if (!CreateShader(data.frag_shaders[def.id], GL_FRAGMENT_SHADER, def.code_str)) + return ReportError("fragment shader", def.name_str); + } + + for (const ProgramDefinition& def : program_definitions) + { + if (!CreateProgram(data.programs[def.id], data.uniforms, def.id, data.vert_shaders[def.vert_shader], data.frag_shaders[def.frag_shader])) + return ReportError("program", def.name_str); + } + + glUseProgram(data.programs[ProgramId::BlendMask]); + glUniform1i(data.uniforms.Get(ProgramId::BlendMask, UniformId::TexMask), 1); + + glUseProgram(0); + + return true; +} + +static void DestroyShaders(const ProgramData& data) +{ + for (GLuint id : data.programs) + glDeleteProgram(id); + + for (GLuint id : data.vert_shaders) + glDeleteShader(id); + + for (GLuint id : data.frag_shaders) + glDeleteShader(id); +} + +} // namespace Gfx + +RenderInterface_GL3::RenderInterface_GL3() +{ + auto mut_program_data = Rml::MakeUnique(); + if (Gfx::CreateShaders(*mut_program_data)) + { + program_data = std::move(mut_program_data); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + fullscreen_quad_geometry = RenderInterface_GL3::CompileGeometry(mesh.vertices, mesh.indices); + } +} + +RenderInterface_GL3::~RenderInterface_GL3() +{ + if (fullscreen_quad_geometry) + { + RenderInterface_GL3::ReleaseGeometry(fullscreen_quad_geometry); + fullscreen_quad_geometry = {}; + } + + if (program_data) + { + Gfx::DestroyShaders(*program_data); + program_data.reset(); + } +} + +void RenderInterface_GL3::SetViewport(int width, int height, int offset_x, int offset_y) +{ + viewport_width = Rml::Math::Max(width, 1); + viewport_height = Rml::Math::Max(height, 1); + viewport_offset_x = offset_x; + viewport_offset_y = offset_y; + projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); +} + +void RenderInterface_GL3::BeginFrame() +{ + RMLUI_ASSERT(viewport_width >= 1 && viewport_height >= 1); + + // 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); + glstate_backup.enable_depth_test = glIsEnabled(GL_DEPTH_TEST); + + glGetIntegerv(GL_VIEWPORT, glstate_backup.viewport); + glGetIntegerv(GL_SCISSOR_BOX, glstate_backup.scissor); + + glGetIntegerv(GL_ACTIVE_TEXTURE, &glstate_backup.active_texture); + + glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &glstate_backup.stencil_clear_value); + glGetFloatv(GL_COLOR_CLEAR_VALUE, glstate_backup.color_clear_value); + glGetBooleanv(GL_COLOR_WRITEMASK, glstate_backup.color_writemask); + + 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, 0); + + glActiveTexture(GL_TEXTURE0); + + glDisable(GL_SCISSOR_TEST); + glDisable(GL_CULL_FACE); + + // Set blending function for premultiplied alpha. + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + +#ifndef RMLUI_PLATFORM_EMSCRIPTEN + // We do blending in nonlinear sRGB space because that is the common practice and gives results that we are used to. + //glDisable(GL_FRAMEBUFFER_SRGB); +#endif + + glEnable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); + glStencilMask(GLuint(-1)); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + glDisable(GL_DEPTH_TEST); + + SetTransform(nullptr); + + render_layers.BeginFrame(viewport_width, viewport_height); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + glClear(GL_COLOR_BUFFER_BIT); + + UseProgram(ProgramId::None); + program_transform_dirty.set(); + scissor_state = Rml::Rectanglei::MakeInvalid(); + + Gfx::CheckGLError("BeginFrame"); +} + +void RenderInterface_GL3::EndFrame(unsigned int fb0) +{ + const Gfx::FramebufferData& fb_active = render_layers.GetTopLayer(); + const Gfx::FramebufferData& fb_postprocess = render_layers.GetPostprocessPrimary(); + + // Resolve MSAA to postprocess framebuffer. + glBindFramebuffer(GL_READ_FRAMEBUFFER, fb_active.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fb_postprocess.framebuffer); + + glBlitFramebuffer(0, 0, fb_active.width, fb_active.height, 0, 0, fb_postprocess.width, fb_postprocess.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + // Draw to backbuffer + glBindFramebuffer(GL_FRAMEBUFFER, fb0); + glViewport(viewport_offset_x, viewport_offset_y, viewport_width, viewport_height); + + // Assuming we have an opaque background, we can just write to it with the premultiplied alpha blend mode and we'll get the correct result. + // Instead, if we had a transparent destination that didn't use premultiplied alpha, we would need to perform a manual un-premultiplication step. + glActiveTexture(GL_TEXTURE0); + Gfx::BindTexture(fb_postprocess); + UseProgram(ProgramId::Passthrough); + DrawFullscreenQuad(); + + render_layers.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); + + if (glstate_backup.enable_depth_test) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_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]); + + glActiveTexture(glstate_backup.active_texture); + + 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]); + glColorMask(glstate_backup.color_writemask[0], glstate_backup.color_writemask[1], glstate_backup.color_writemask[2], + glstate_backup.color_writemask[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); + + Gfx::CheckGLError("EndFrame"); +} + +void RenderInterface_GL3::Clear() +{ + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); +} + +Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Span vertices, Rml::Span indices) +{ + 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) * vertices.size(), (const void*)vertices.data(), 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) * indices.size(), (const void*)indices.data(), draw_usage); + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + Gfx::CheckGLError("CompileGeometry"); + + Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData; + geometry->vao = vao; + geometry->vbo = vbo; + geometry->ibo = ibo; + geometry->draw_count = (GLsizei)indices.size(); + + return (Rml::CompiledGeometryHandle)geometry; +} + +void RenderInterface_GL3::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; + + if (texture == TexturePostprocess) + { + // Do nothing. + } + else if (texture) + { + UseProgram(ProgramId::Texture); + SubmitTransformUniform(translation); + if (texture != TextureEnableWithoutBinding) + glBindTexture(GL_TEXTURE_2D, (GLuint)texture); + } + else + { + UseProgram(ProgramId::Color); + glBindTexture(GL_TEXTURE_2D, 0); + SubmitTransformUniform(translation); + } + + glBindVertexArray(geometry->vao); + glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + + Gfx::CheckGLError("RenderCompiledGeometry"); +} + +void RenderInterface_GL3::ReleaseGeometry(Rml::CompiledGeometryHandle handle) +{ + Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; + + glDeleteVertexArrays(1, &geometry->vao); + glDeleteBuffers(1, &geometry->vbo); + glDeleteBuffers(1, &geometry->ibo); + + delete geometry; +} + +/// Flip the vertical axis of the rectangle, and move its origin to the vertically opposite side of the viewport. +/// @note Changes the coordinate system from RmlUi to OpenGL, or equivalently in reverse. +/// @note The Rectangle::Top and Rectangle::Bottom members will have reverse meaning in the returned rectangle. +static Rml::Rectanglei VerticallyFlipped(Rml::Rectanglei rect, int viewport_height) +{ + RMLUI_ASSERT(rect.Valid()); + Rml::Rectanglei flipped_rect = rect; + flipped_rect.p0.y = viewport_height - rect.p1.y; + flipped_rect.p1.y = viewport_height - rect.p0.y; + return flipped_rect; +} + +void RenderInterface_GL3::SetScissor(Rml::Rectanglei region, bool vertically_flip) +{ + if (region.Valid() != scissor_state.Valid()) + { + if (region.Valid()) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); + } + + if (region.Valid() && vertically_flip) + region = VerticallyFlipped(region, viewport_height); + + if (region.Valid() && region != scissor_state) + { + // Some render APIs don't like offscreen positions (WebGL in particular), so clamp them to the viewport. + const int x = Rml::Math::Clamp(region.Left(), 0, viewport_width); + const int y = Rml::Math::Clamp(viewport_height - region.Bottom(), 0, viewport_height); + + glScissor(x, y, region.Width(), region.Height()); + } + + Gfx::CheckGLError("SetScissorRegion"); + scissor_state = region; +} + +void RenderInterface_GL3::EnableScissorRegion(bool enable) +{ + // Assume enable is immediately followed by a SetScissorRegion() call, and ignore it here. + if (!enable) + SetScissor(Rml::Rectanglei::MakeInvalid(), false); +} + +void RenderInterface_GL3::SetScissorRegion(Rml::Rectanglei region) +{ + SetScissor(region); +} + +void RenderInterface_GL3::EnableClipMask(bool enable) +{ + if (enable) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); +} + +void RenderInterface_GL3::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_GL3::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 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 image_dest_buffer(new byte[image_size]); + byte* image_dest = image_dest_buffer.get(); + + // Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha. + 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 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_GL3::GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) +{ + RMLUI_ASSERT(source_data.data() && source_data.size() == size_t(source_dimensions.x * source_dimensions.y * 4)); + + GLuint texture_id = Gfx::CreateTexture(source_data, source_dimensions); + if (texture_id == 0) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture."); + return {}; + } + return (Rml::TextureHandle)texture_id; +} + +void RenderInterface_GL3::DrawFullscreenQuad() +{ + RenderGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); +} + +void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) +{ + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + if (uv_offset != Rml::Vector2f() || uv_scaling != Rml::Vector2f(1.f)) + { + for (Rml::Vertex& vertex : mesh.vertices) + vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; + } + const Rml::CompiledGeometryHandle geometry = CompileGeometry(mesh.vertices, mesh.indices); + RenderGeometry(geometry, {}, RenderInterface_GL3::TexturePostprocess); + ReleaseGeometry(geometry); +} + +static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) +{ + Rml::Colourf result; + for (int i = 0; i < 4; i++) + result[i] = (1.f / 255.f) * float(c0[i]); + return result; +} + +static void SigmaToParameters(const float desired_sigma, int& out_pass_level, float& out_sigma) +{ + constexpr int max_num_passes = 10; + static_assert(max_num_passes < 31, ""); + constexpr float max_single_pass_sigma = 3.0f; + out_pass_level = Rml::Math::Clamp(Rml::Math::Log2(int(desired_sigma * (2.f / max_single_pass_sigma))), 0, max_num_passes); + out_sigma = Rml::Math::Clamp(desired_sigma / float(1 << out_pass_level), 0.0f, max_single_pass_sigma); +} + +static void SetTexCoordLimits(GLint tex_coord_min_location, GLint tex_coord_max_location, Rml::Rectanglei rectangle_flipped, + Rml::Vector2i framebuffer_size) +{ + // Offset by half-texel values so that texture lookups are clamped to fragment centers, thereby avoiding color + // bleeding from neighboring texels due to bilinear interpolation. + const Rml::Vector2f min = (Rml::Vector2f(rectangle_flipped.p0) + Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + const Rml::Vector2f max = (Rml::Vector2f(rectangle_flipped.p1) - Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + + glUniform2f(tex_coord_min_location, min.x, min.y); + glUniform2f(tex_coord_max_location, max.x, max.y); +} + +static void SetBlurWeights(GLint weights_location, float sigma) +{ + constexpr int num_weights = BLUR_NUM_WEIGHTS; + float weights[num_weights]; + float normalization = 0.0f; + for (int i = 0; i < num_weights; i++) + { + if (Rml::Math::Absolute(sigma) < 0.1f) + weights[i] = float(i == 0); + else + weights[i] = Rml::Math::Exp(-float(i * i) / (2.0f * sigma * sigma)) / (Rml::Math::SquareRoot(2.f * Rml::Math::RMLUI_PI) * sigma); + + normalization += (i == 0 ? 1.f : 2.0f) * weights[i]; + } + for (int i = 0; i < num_weights; i++) + weights[i] /= normalization; + + glUniform1fv(weights_location, (GLsizei)num_weights, &weights[0]); +} + +void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, + const Rml::Rectanglei window_flipped) +{ + RMLUI_ASSERT(&source_destination != &temp && source_destination.width == temp.width && source_destination.height == temp.height); + RMLUI_ASSERT(window_flipped.Valid()); + + int pass_level = 0; + SigmaToParameters(sigma, pass_level, sigma); + + const Rml::Rectanglei original_scissor = scissor_state; + + // Begin by downscaling so that the blur pass can be done at a reduced resolution for large sigma. + Rml::Rectanglei scissor = window_flipped; + + UseProgram(ProgramId::Passthrough); + SetScissor(scissor, true); + + // Downscale by iterative half-scaling with bilinear filtering, to reduce aliasing. + glViewport(0, 0, source_destination.width / 2, source_destination.height / 2); + + // Scale UVs if we have even dimensions, such that texture fetches align perfectly between texels, thereby producing a 50% blend of + // neighboring texels. + const Rml::Vector2f uv_scaling = {(source_destination.width % 2 == 1) ? (1.f - 1.f / float(source_destination.width)) : 1.f, + (source_destination.height % 2 == 1) ? (1.f - 1.f / float(source_destination.height)) : 1.f}; + + for (int i = 0; i < pass_level; i++) + { + scissor.p0 = (scissor.p0 + Rml::Vector2i(1)) / 2; + scissor.p1 = Rml::Math::Max(scissor.p1 / 2, scissor.p0); + const bool from_source = (i % 2 == 0); + Gfx::BindTexture(from_source ? source_destination : temp); + glBindFramebuffer(GL_FRAMEBUFFER, (from_source ? temp : source_destination).framebuffer); + SetScissor(scissor, true); + + DrawFullscreenQuad({}, uv_scaling); + } + + glViewport(0, 0, source_destination.width, source_destination.height); + + // Ensure texture data end up in the temp buffer. Depending on the last downscaling, we might need to move it from the source_destination buffer. + const bool transfer_to_temp_buffer = (pass_level % 2 == 0); + if (transfer_to_temp_buffer) + { + Gfx::BindTexture(source_destination); + glBindFramebuffer(GL_FRAMEBUFFER, temp.framebuffer); + DrawFullscreenQuad(); + } + + // Set up uniforms. + UseProgram(ProgramId::Blur); + SetBlurWeights(GetUniformLocation(UniformId::Weights), sigma); + SetTexCoordLimits(GetUniformLocation(UniformId::TexCoordMin), GetUniformLocation(UniformId::TexCoordMax), scissor, + {source_destination.width, source_destination.height}); + + const GLint texel_offset_location = GetUniformLocation(UniformId::TexelOffset); + auto SetTexelOffset = [texel_offset_location](Rml::Vector2f blur_direction, int texture_dimension) { + const Rml::Vector2f texel_offset = blur_direction * (1.0f / float(texture_dimension)); + glUniform2f(texel_offset_location, texel_offset.x, texel_offset.y); + }; + + // Blur render pass - vertical. + Gfx::BindTexture(temp); + glBindFramebuffer(GL_FRAMEBUFFER, source_destination.framebuffer); + + SetTexelOffset({0.f, 1.f}, temp.height); + DrawFullscreenQuad(); + + // Blur render pass - horizontal. + Gfx::BindTexture(source_destination); + glBindFramebuffer(GL_FRAMEBUFFER, temp.framebuffer); + + // Add a 1px transparent border around the blur region by first clearing with a padded scissor. This helps prevent + // artifacts when upscaling the blur result in the later step. On Intel and AMD, we have observed that during + // blitting with linear filtering, pixels outside the 'src' region can be blended into the output. On the other + // hand, it looks like Nvidia clamps the pixels to the source edge, which is what we really want. Regardless, we + // work around the issue with this extra step. + SetScissor(scissor.Extend(1), true); + glClear(GL_COLOR_BUFFER_BIT); + SetScissor(scissor, true); + + SetTexelOffset({1.f, 0.f}, source_destination.width); + DrawFullscreenQuad(); + + // Blit the blurred image to the scissor region with upscaling. + SetScissor(window_flipped, true); + glBindFramebuffer(GL_READ_FRAMEBUFFER, temp.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, source_destination.framebuffer); + + const Rml::Vector2i src_min = scissor.p0; + const Rml::Vector2i src_max = scissor.p1; + const Rml::Vector2i dst_min = window_flipped.p0; + const Rml::Vector2i dst_max = window_flipped.p1; + glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, dst_min.x, dst_min.y, dst_max.x, dst_max.y, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // The above upscale blit might be jittery at low resolutions (large pass levels). This is especially noticeable when moving an element with + // backdrop blur around or when trying to click/hover an element within a blurred region since it may be rendered at an offset. For more stable + // and accurate rendering we next upscale the blur image by an exact power-of-two. However, this may not fill the edges completely so we need to + // do the above first. Note that this strategy may sometimes result in visible seams. Alternatively, we could try to enlarge the window to the + // next power-of-two size and then downsample and blur that. + const Rml::Vector2i target_min = src_min * (1 << pass_level); + const Rml::Vector2i target_max = src_max * (1 << pass_level); + if (target_min != dst_min || target_max != dst_max) + { + glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, target_min.x, target_min.y, target_max.x, target_max.y, GL_COLOR_BUFFER_BIT, + GL_LINEAR); + } + + // Restore render state. + SetScissor(original_scissor); + + Gfx::CheckGLError("Blur"); +} + +void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) +{ + glDeleteTextures(1, (GLuint*)&texture_handle); +} + +void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) +{ + transform = (new_transform ? (projection * (*new_transform)) : projection); + program_transform_dirty.set(); +} + +enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix, MaskImage }; +struct CompiledFilter { + FilterType type; + + // Passthrough + float blend_factor; + + // Blur + float sigma; + + // Drop shadow + Rml::Vector2f offset; + Rml::ColourbPremultiplied color; + + // ColorMatrix + Rml::Matrix4f color_matrix; +}; + +Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) +{ + CompiledFilter filter = {}; + + if (name == "opacity") + { + filter.type = FilterType::Passthrough; + filter.blend_factor = Rml::Get(parameters, "value", 1.0f); + } + else if (name == "blur") + { + filter.type = FilterType::Blur; + filter.sigma = Rml::Get(parameters, "sigma", 1.0f); + } + else if (name == "drop-shadow") + { + filter.type = FilterType::DropShadow; + filter.sigma = Rml::Get(parameters, "sigma", 0.f); + filter.color = Rml::Get(parameters, "color", Rml::Colourb()).ToPremultiplied(); + filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f)); + } + else if (name == "brightness") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + } + else if (name == "contrast") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float grayness = 0.5f - 0.5f * value; + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(grayness, grayness, grayness, 1.f)); + } + else if (name == "invert") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Math::Clamp(Rml::Get(parameters, "value", 1.0f), 0.f, 1.f); + const float inverted = 1.f - 2.f * value; + filter.color_matrix = Rml::Matrix4f::Diag(inverted, inverted, inverted, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(value, value, value, 1.f)); + } + else if (name == "grayscale") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f gray = value * Rml::Vector3f(0.2126f, 0.7152f, 0.0722f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {gray.x + rev_value, gray.y, gray.z, 0.f}, + {gray.x, gray.y + rev_value, gray.z, 0.f}, + {gray.x, gray.y, gray.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "sepia") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f r_mix = value * Rml::Vector3f(0.393f, 0.769f, 0.189f); + const Rml::Vector3f g_mix = value * Rml::Vector3f(0.349f, 0.686f, 0.168f); + const Rml::Vector3f b_mix = value * Rml::Vector3f(0.272f, 0.534f, 0.131f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {r_mix.x + rev_value, r_mix.y, r_mix.z, 0.f}, + {g_mix.x, g_mix.y + rev_value, g_mix.z, 0.f}, + {b_mix.x, b_mix.y, b_mix.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "hue-rotate") + { + // Hue-rotation and saturation values based on: https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-huerotate + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float s = Rml::Math::Sin(value); + const float c = Rml::Math::Cos(value); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * c - 0.213f * s, 0.715f - 0.715f * c - 0.715f * s, 0.072f - 0.072f * c + 0.928f * s, 0.f}, + {0.213f - 0.213f * c + 0.143f * s, 0.715f + 0.285f * c + 0.140f * s, 0.072f - 0.072f * c - 0.283f * s, 0.f}, + {0.213f - 0.213f * c - 0.787f * s, 0.715f - 0.715f * c + 0.715f * s, 0.072f + 0.928f * c + 0.072f * s, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "saturate") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * value, 0.715f - 0.715f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f + 0.285f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f - 0.715f * value, 0.072f + 0.928f * value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + + if (filter.type != FilterType::Invalid) + return reinterpret_cast(new CompiledFilter(std::move(filter))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported filter type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_GL3::ReleaseFilter(Rml::CompiledFilterHandle filter) +{ + delete reinterpret_cast(filter); +} + +enum class CompiledShaderType { Invalid = 0, Gradient, Creation }; +struct CompiledShader { + CompiledShaderType type; + + // Gradient + ShaderGradientFunction gradient_function; + Rml::Vector2f p; + Rml::Vector2f v; + Rml::Vector stop_positions; + Rml::Vector stop_colors; + + // Shader + Rml::Vector2f dimensions; +}; + +Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) +{ + auto ApplyColorStopList = [](CompiledShader& shader, const Rml::Dictionary& shader_parameters) { + auto it = shader_parameters.find("color_stop_list"); + RMLUI_ASSERT(it != shader_parameters.end() && it->second.GetType() == Rml::Variant::COLORSTOPLIST); + const Rml::ColorStopList& color_stop_list = it->second.GetReference(); + const int num_stops = Rml::Math::Min((int)color_stop_list.size(), MAX_NUM_STOPS); + + shader.stop_positions.resize(num_stops); + shader.stop_colors.resize(num_stops); + for (int i = 0; i < num_stops; i++) + { + const Rml::ColorStop& stop = color_stop_list[i]; + RMLUI_ASSERT(stop.position.unit == Rml::Unit::NUMBER); + shader.stop_positions[i] = stop.position.number; + shader.stop_colors[i] = ConvertToColorf(stop.color); + } + }; + + CompiledShader shader = {}; + + if (name == "linear-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingLinear : ShaderGradientFunction::Linear); + shader.p = Rml::Get(parameters, "p0", Rml::Vector2f(0.f)); + shader.v = Rml::Get(parameters, "p1", Rml::Vector2f(0.f)) - shader.p; + ApplyColorStopList(shader, parameters); + } + else if (name == "radial-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingRadial : ShaderGradientFunction::Radial); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + shader.v = Rml::Vector2f(1.f) / Rml::Get(parameters, "radius", Rml::Vector2f(1.f)); + ApplyColorStopList(shader, parameters); + } + else if (name == "conic-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingConic : ShaderGradientFunction::Conic); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + const float angle = Rml::Get(parameters, "angle", 0.f); + shader.v = {Rml::Math::Cos(angle), Rml::Math::Sin(angle)}; + ApplyColorStopList(shader, parameters); + } + else if (name == "shader") + { + const Rml::String value = Rml::Get(parameters, "value", Rml::String()); + if (value == "creation") + { + shader.type = CompiledShaderType::Creation; + shader.dimensions = Rml::Get(parameters, "dimensions", Rml::Vector2f(0.f)); + } + } + + if (shader.type != CompiledShaderType::Invalid) + return reinterpret_cast(new CompiledShader(std::move(shader))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported shader type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, + Rml::Vector2f translation, Rml::TextureHandle /*texture*/) +{ + RMLUI_ASSERT(shader_handle && geometry_handle); + const CompiledShader& shader = *reinterpret_cast(shader_handle); + const CompiledShaderType type = shader.type; + const Gfx::CompiledGeometryData& geometry = *reinterpret_cast(geometry_handle); + + switch (type) + { + case CompiledShaderType::Gradient: + { + RMLUI_ASSERT(shader.stop_positions.size() == shader.stop_colors.size()); + const int num_stops = (int)shader.stop_positions.size(); + + UseProgram(ProgramId::Gradient); + glUniform1i(GetUniformLocation(UniformId::Func), static_cast(shader.gradient_function)); + glUniform2f(GetUniformLocation(UniformId::P), shader.p.x, shader.p.y); + glUniform2f(GetUniformLocation(UniformId::V), shader.v.x, shader.v.y); + glUniform1i(GetUniformLocation(UniformId::NumStops), num_stops); + glUniform1fv(GetUniformLocation(UniformId::StopPositions), num_stops, shader.stop_positions.data()); + glUniform4fv(GetUniformLocation(UniformId::StopColors), num_stops, shader.stop_colors[0]); + + SubmitTransformUniform(translation); + glBindVertexArray(geometry.vao); + glDrawElements(GL_TRIANGLES, geometry.draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + glBindVertexArray(0); + } + break; + case CompiledShaderType::Creation: + { + const double time = Rml::GetSystemInterface()->GetElapsedTime(); + + UseProgram(ProgramId::Creation); + glUniform1f(GetUniformLocation(UniformId::Value), (float)time); + glUniform2f(GetUniformLocation(UniformId::Dimensions), shader.dimensions.x, shader.dimensions.y); + + SubmitTransformUniform(translation); + glBindVertexArray(geometry.vao); + glDrawElements(GL_TRIANGLES, geometry.draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + glBindVertexArray(0); + } + break; + case CompiledShaderType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render shader %d.", (int)type); + } + break; + } + + Gfx::CheckGLError("RenderShader"); +} + +void RenderInterface_GL3::ReleaseShader(Rml::CompiledShaderHandle shader_handle) +{ + delete reinterpret_cast(shader_handle); +} + +void RenderInterface_GL3::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle) +{ + const Gfx::FramebufferData& source = render_layers.GetLayer(layer_handle); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessPrimary(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.framebuffer); + + // Blit and resolve MSAA. Any active scissor state will restrict the size of the blit region. + glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); +} + +void RenderInterface_GL3::RenderFilters(Rml::Span filter_handles) +{ + for (const Rml::CompiledFilterHandle filter_handle : filter_handles) + { + const CompiledFilter& filter = *reinterpret_cast(filter_handle); + const FilterType type = filter.type; + + switch (type) + { + case FilterType::Passthrough: + { + UseProgram(ProgramId::Passthrough); + glBlendFunc(GL_CONSTANT_COLOR, GL_ZERO); + glBlendColor(filter.blend_factor, filter.blend_factor, filter.blend_factor, filter.blend_factor); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + Gfx::BindTexture(source); + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + } + break; + case FilterType::Blur: + { + glDisable(GL_BLEND); + + const Gfx::FramebufferData& source_destination = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& temp = render_layers.GetPostprocessSecondary(); + + const Rml::Rectanglei window_flipped = VerticallyFlipped(scissor_state, viewport_height); + RenderBlur(filter.sigma, source_destination, temp, window_flipped); + + glEnable(GL_BLEND); + } + break; + case FilterType::DropShadow: + { + UseProgram(ProgramId::DropShadow); + glDisable(GL_BLEND); + + Rml::Colourf color = ConvertToColorf(filter.color); + glUniform4fv(GetUniformLocation(UniformId::Color), 1, &color[0]); + + const Gfx::FramebufferData& primary = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& secondary = render_layers.GetPostprocessSecondary(); + Gfx::BindTexture(primary); + glBindFramebuffer(GL_FRAMEBUFFER, secondary.framebuffer); + + const Rml::Rectanglei window_flipped = VerticallyFlipped(scissor_state, viewport_height); + SetTexCoordLimits(GetUniformLocation(UniformId::TexCoordMin), GetUniformLocation(UniformId::TexCoordMax), window_flipped, + {primary.width, primary.height}); + + const Rml::Vector2f uv_offset = filter.offset / Rml::Vector2f(-(float)viewport_width, (float)viewport_height); + DrawFullscreenQuad(uv_offset); + + if (filter.sigma >= 0.5f) + { + const Gfx::FramebufferData& tertiary = render_layers.GetPostprocessTertiary(); + RenderBlur(filter.sigma, secondary, tertiary, window_flipped); + } + + UseProgram(ProgramId::Passthrough); + BindTexture(primary); + glEnable(GL_BLEND); + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + } + break; + case FilterType::ColorMatrix: + { + UseProgram(ProgramId::ColorMatrix); + glDisable(GL_BLEND); + + const GLint uniform_location = program_data->uniforms.Get(ProgramId::ColorMatrix, UniformId::ColorMatrix); + constexpr bool transpose = std::is_same::value; + glUniformMatrix4fv(uniform_location, 1, transpose, filter.color_matrix.data()); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + Gfx::BindTexture(source); + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + glEnable(GL_BLEND); + } + break; + case FilterType::MaskImage: + { + UseProgram(ProgramId::BlendMask); + glDisable(GL_BLEND); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& blend_mask = render_layers.GetBlendMask(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + + Gfx::BindTexture(source); + glActiveTexture(GL_TEXTURE1); + Gfx::BindTexture(blend_mask); + glActiveTexture(GL_TEXTURE0); + + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + glEnable(GL_BLEND); + } + break; + case FilterType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render filter %d.", (int)type); + } + break; + } + } + + Gfx::CheckGLError("RenderFilter"); +} + +Rml::LayerHandle RenderInterface_GL3::PushLayer() +{ + const Rml::LayerHandle layer_handle = render_layers.PushLayer(); + + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetLayer(layer_handle).framebuffer); + glClear(GL_COLOR_BUFFER_BIT); + + return layer_handle; +} + +void RenderInterface_GL3::CompositeLayers(Rml::LayerHandle source_handle, Rml::LayerHandle destination_handle, Rml::BlendMode blend_mode, + Rml::Span filters) +{ + using Rml::BlendMode; + + // Blit source layer to postprocessing buffer. Do this regardless of whether we actually have any filters to be + // applied, because we need to resolve the multi-sampled framebuffer in any case. + // @performance If we have BlendMode::Replace and no filters or mask then we can just blit directly to the destination. + BlitLayerToPostprocessPrimary(source_handle); + + // Render the filters, the PostprocessPrimary framebuffer is used for both input and output. + RenderFilters(filters); + + // Render to the destination layer. + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetLayer(destination_handle).framebuffer); + Gfx::BindTexture(render_layers.GetPostprocessPrimary()); + + UseProgram(ProgramId::Passthrough); + + if (blend_mode == BlendMode::Replace) + glDisable(GL_BLEND); + + DrawFullscreenQuad(); + + if (blend_mode == BlendMode::Replace) + glEnable(GL_BLEND); + + if (destination_handle != render_layers.GetTopLayerHandle()) + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + + Gfx::CheckGLError("CompositeLayers"); +} + +void RenderInterface_GL3::PopLayer() +{ + render_layers.PopLayer(); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); +} + +Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture() +{ + RMLUI_ASSERT(scissor_state.Valid()); + const Rml::Rectanglei bounds = scissor_state; + + GLuint render_texture = Gfx::CreateTexture({}, bounds.Size()); + if (render_texture == 0) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create render texture."); + return {}; + } + + BlitLayerToPostprocessPrimary(render_layers.GetTopLayerHandle()); + + EnableScissorRegion(false); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.framebuffer); + + // Flip the image vertically, as that convention is used for textures, and move to origin. + glBlitFramebuffer( // + bounds.Left(), source.height - bounds.Bottom(), // src0 + bounds.Right(), source.height - bounds.Top(), // src1 + 0, bounds.Height(), // dst0 + bounds.Width(), 0, // dst1 + GL_COLOR_BUFFER_BIT, GL_NEAREST // + ); + + glBindTexture(GL_TEXTURE_2D, render_texture); + + const Gfx::FramebufferData& texture_source = destination; + glBindFramebuffer(GL_READ_FRAMEBUFFER, texture_source.framebuffer); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, bounds.Width(), bounds.Height()); + + SetScissor(bounds); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + Gfx::CheckGLError("SaveLayerAsTexture"); + + return (Rml::TextureHandle)render_texture; +} + +Rml::CompiledFilterHandle RenderInterface_GL3::SaveLayerAsMaskImage() +{ + BlitLayerToPostprocessPrimary(render_layers.GetTopLayerHandle()); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetBlendMask(); + + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + BindTexture(source); + UseProgram(ProgramId::Passthrough); + glDisable(GL_BLEND); + + DrawFullscreenQuad(); + + glEnable(GL_BLEND); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + Gfx::CheckGLError("SaveLayerAsMaskImage"); + + CompiledFilter filter = {}; + filter.type = FilterType::MaskImage; + return reinterpret_cast(new CompiledFilter(std::move(filter))); +} + +void RenderInterface_GL3::UseProgram(ProgramId program_id) +{ + RMLUI_ASSERT(program_data); + if (active_program != program_id) + { + if (program_id != ProgramId::None) + glUseProgram(program_data->programs[program_id]); + active_program = program_id; + } +} + +int RenderInterface_GL3::GetUniformLocation(UniformId uniform_id) const +{ + return program_data->uniforms.Get(active_program, uniform_id); +} + +void RenderInterface_GL3::SubmitTransformUniform(Rml::Vector2f translation) +{ + static_assert((size_t)ProgramId::Count < MaxNumPrograms, "Maximum number of programs exceeded."); + const size_t program_index = (size_t)active_program; + + if (program_transform_dirty.test(program_index)) + { + glUniformMatrix4fv(GetUniformLocation(UniformId::Transform), 1, false, transform.data()); + program_transform_dirty.set(program_index, false); + } + + glUniform2fv(GetUniformLocation(UniformId::Translate), 1, &translation.x); + + Gfx::CheckGLError("SubmitTransformUniform"); +} + +RenderInterface_GL3::RenderLayerStack::RenderLayerStack() +{ + fb_postprocess.resize(4); +} + +RenderInterface_GL3::RenderLayerStack::~RenderLayerStack() +{ + DestroyFramebuffers(); +} + +Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::PushLayer() +{ + RMLUI_ASSERT(layers_size <= (int)fb_layers.size()); + + if (layers_size == (int)fb_layers.size()) + { + // All framebuffers should share a single stencil buffer. + GLuint shared_depth_stencil = (fb_layers.empty() ? 0 : fb_layers.front().depth_stencil_buffer); + + fb_layers.push_back(Gfx::FramebufferData{}); + Gfx::CreateFramebuffer(fb_layers.back(), width, height, RMLUI_NUM_MSAA_SAMPLES, Gfx::FramebufferAttachment::DepthStencil, + shared_depth_stencil); + } + + layers_size += 1; + return GetTopLayerHandle(); +} + +void RenderInterface_GL3::RenderLayerStack::PopLayer() +{ + RMLUI_ASSERT(layers_size > 0); + layers_size -= 1; +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetLayer(Rml::LayerHandle layer) const +{ + RMLUI_ASSERT((size_t)layer < (size_t)layers_size); + return fb_layers[layer]; +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetTopLayer() const +{ + return GetLayer(GetTopLayerHandle()); +} + +Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::GetTopLayerHandle() const +{ + RMLUI_ASSERT(layers_size > 0); + return static_cast(layers_size - 1); +} + +void RenderInterface_GL3::RenderLayerStack::SwapPostprocessPrimarySecondary() +{ + std::swap(fb_postprocess[0], fb_postprocess[1]); +} + +void RenderInterface_GL3::RenderLayerStack::BeginFrame(int new_width, int new_height) +{ + RMLUI_ASSERT(layers_size == 0); + + if (new_width != width || new_height != height) + { + width = new_width; + height = new_height; + + DestroyFramebuffers(); + } + + PushLayer(); +} + +void RenderInterface_GL3::RenderLayerStack::EndFrame() +{ + RMLUI_ASSERT(layers_size == 1); + PopLayer(); +} + +void RenderInterface_GL3::RenderLayerStack::DestroyFramebuffers() +{ + RMLUI_ASSERTMSG(layers_size == 0, "Do not call this during frame rendering, that is, between BeginFrame() and EndFrame()."); + + for (Gfx::FramebufferData& fb : fb_layers) + Gfx::DestroyFramebuffer(fb); + + fb_layers.clear(); + + for (Gfx::FramebufferData& fb : fb_postprocess) + Gfx::DestroyFramebuffer(fb); +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::EnsureFramebufferPostprocess(int index) +{ + RMLUI_ASSERT(index < (int)fb_postprocess.size()) + Gfx::FramebufferData& fb = fb_postprocess[index]; + if (!fb.framebuffer) + Gfx::CreateFramebuffer(fb, width, height, 0, Gfx::FramebufferAttachment::None, 0); + return fb; +} + +const Rml::Matrix4f& RenderInterface_GL3::GetTransform() const +{ + return transform; +} + +void RenderInterface_GL3::ResetProgram() +{ + UseProgram(ProgramId::None); +} + +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 +} diff --git a/src/main/cpp/RmlUi_Renderer_GL3.h b/src/main/cpp/RmlUi_Renderer_GL3.h new file mode 100644 index 0000000..868ece4 --- /dev/null +++ b/src/main/cpp/RmlUi_Renderer_GL3.h @@ -0,0 +1,241 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * 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. + * + */ + +#ifndef RMLUI_BACKENDS_RENDERER_GL3_H +#define RMLUI_BACKENDS_RENDERER_GL3_H + +#include +#include +#include + +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(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(unsigned int fb0); + + // Optional, can be used to clear the active framebuffer. + void Clear(); + + // -- Inherited from Rml::RenderInterface -- + + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span 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 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 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 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 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 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 fb_layers; + Rml::Vector 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 + +#endif diff --git a/src/main/cpp/assets_manager.cpp b/src/main/cpp/assets_manager.cpp index 5d2e0a7..c9833c8 100644 --- a/src/main/cpp/assets_manager.cpp +++ b/src/main/cpp/assets_manager.cpp @@ -19,3 +19,8 @@ std::vector AssetsManager::ReadAll(const std::string &filename) } return {}; } + +AAssetManager *AssetsManager::Native() +{ + return m_asset_manager; +} diff --git a/src/main/cpp/assets_manager.h b/src/main/cpp/assets_manager.h index 5533c56..f141451 100644 --- a/src/main/cpp/assets_manager.h +++ b/src/main/cpp/assets_manager.h @@ -2,6 +2,7 @@ #include struct AAssetManager; +struct AAsset; class AssetsManager { @@ -9,4 +10,5 @@ class AssetsManager public: static void Init(AAssetManager* asset_manager); static std::vector ReadAll(const std::string& filename); + static AAssetManager* Native(); }; diff --git a/src/main/cpp/external_texture.cpp b/src/main/cpp/external_texture.cpp index 65337c4..7117216 100644 --- a/src/main/cpp/external_texture.cpp +++ b/src/main/cpp/external_texture.cpp @@ -33,12 +33,12 @@ void ExternalTexture::destroy() void ExternalTexture::bind() const { - glBindTexture(GL_TEXTURE_2D, texture); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); } void ExternalTexture::unbind() const { - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0); } GLuint ExternalTexture::texture_id() const diff --git a/src/main/cpp/kernel.cpp b/src/main/cpp/kernel.cpp index c92300b..3350a48 100644 --- a/src/main/cpp/kernel.cpp +++ b/src/main/cpp/kernel.cpp @@ -2,12 +2,89 @@ #include "egl_context.h" #include "render_target.h" #include "logger.h" +#include "assets_manager.h" #include "aidl/com/omixlab/mosis/IMosisListener.h" #include +#include +#include +#include "RmlUi_Renderer_GL3.h" +#include +#include using namespace aidl::com::omixlab::mosis; using namespace aidl::android::hardware; +class AssetFilesInterface : public Rml::FileInterface +{ +public: + static AssetFilesInterface& Instance() + { + static AssetFilesInterface instance; + return instance; + } + Rml::FileHandle Open(const Rml::String &path) override + { + AAssetManager* am = AssetsManager::Native(); + AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER); + return reinterpret_cast(asset); + } + void Close(Rml::FileHandle file) override + { + AAsset* asset = reinterpret_cast(file); + AAsset_close(asset); + } + size_t Read(void *buffer, size_t size, Rml::FileHandle file) override + { + AAsset* asset = reinterpret_cast(file); + return AAsset_read(asset, buffer, size); + } + bool Seek(Rml::FileHandle file, long offset, int origin) override + { + AAsset* asset = reinterpret_cast(file); + off_t new_cursor = AAsset_seek(asset, offset, origin); + return new_cursor != -1; + } + size_t Tell(Rml::FileHandle file) override + { + AAsset* asset = reinterpret_cast(file); + return AAsset_seek(asset, 0, SEEK_CUR); + } + + size_t Length(Rml::FileHandle file) override + { + AAsset* asset = reinterpret_cast(file); + return AAsset_getLength(asset); + } + + bool LoadFile(const Rml::String &path, Rml::String &out_data) override + { + AAssetManager* am = AssetsManager::Native(); + AAsset* asset = AAssetManager_open(am, path.c_str(), AASSET_MODE_BUFFER); + if (!asset) + return false; + out_data.resize(AAsset_getLength(asset)); + auto data_ptr = static_cast(AAsset_getBuffer(asset)); + std::span data = std::span(data_ptr, out_data.size()); + std::ranges::copy(data, out_data.begin()); + return true; + } +}; + +class SystemInterface : public Rml::SystemInterface +{ +public: + static SystemInterface& Instance() + { + static SystemInterface instance; + return instance; + } + bool LogMessage(Rml::Log::Type type, const Rml::String &message) override + { + Logger::Log(std::format("RMLUI: {}", message)); + return true; + } +}; + void Kernel::main_loop() { m_egl_context = std::make_unique(); @@ -30,12 +107,45 @@ void Kernel::main_loop() for (const auto& [pid, l] : m_listeners) l->onBufferAvailable(*m_aidl_buffer); + RenderInterface_GL3 rmlui_render_interface; + if (!rmlui_render_interface) + { + Logger::Log("failed to create render interface"); + return; + } + Rml::SetRenderInterface(&rmlui_render_interface); + Rml::SetFileInterface(&AssetFilesInterface::Instance()); + Rml::SetSystemInterface(&SystemInterface::Instance()); + Rml::Initialise(); + Rml::Context* context = Rml::CreateContext("default", Rml::Vector2i(1024, 1024)); + if (!context) + { + Logger::Log("RMLUI failed to create a context"); + Rml::Shutdown(); + return; + } + Rml::LoadFontFace("Roboto/static/Roboto_Condensed-Regular.ttf"); + Rml::LoadFontFace("LatoLatin-Regular.ttf"); + + // Now we are ready to load our document. + Rml::ElementDocument* document = context->LoadDocument("demo.rml"); + document->Show(); + while (true) { static float angle = 0.0f; angle += 0.1f; + m_render_target->bind(); glClearColor(fabs(sin(angle)), 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); + glViewport(0, 0, 1024, 1024); + + context->Update(); + rmlui_render_interface.SetViewport(1024, 1024); + rmlui_render_interface.BeginFrame(); + context->Render(); + rmlui_render_interface.EndFrame(m_render_target->framebuffer()); + glFinish(); { std::lock_guard _lock(m_mutex); @@ -44,6 +154,7 @@ void Kernel::main_loop() } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } + Rml::Shutdown(); } Kernel::Kernel(const std::shared_ptr &listener) diff --git a/src/main/cpp/mosis-service.cpp b/src/main/cpp/mosis-service.cpp index cf08714..90e4187 100644 --- a/src/main/cpp/mosis-service.cpp +++ b/src/main/cpp/mosis-service.cpp @@ -6,9 +6,11 @@ #include #include "logger.h" #include "kernel.h" +#include "assets_manager.h" #include #include #include +#include using namespace aidl::com::omixlab::mosis; using namespace aidl::android::hardware; @@ -58,3 +60,11 @@ Java_com_omixlab_mosis_NativeService_getBinderNative(JNIEnv *env, jobject thiz) static std::shared_ptr g_service = ndk::SharedRefBase::make(); return AIBinder_toJavaBinder(env, g_service->asBinder().get()); } + +extern "C" +JNIEXPORT void JNICALL +Java_com_omixlab_mosis_NativeService_setAssetManager(JNIEnv *env, jobject thiz, + jobject asset_manager) +{ + AssetsManager::Init(AAssetManager_fromJava(env, asset_manager)); +} \ No newline at end of file diff --git a/src/main/cpp/render_target.cpp b/src/main/cpp/render_target.cpp index 492036a..e9545b0 100644 --- a/src/main/cpp/render_target.cpp +++ b/src/main/cpp/render_target.cpp @@ -87,3 +87,8 @@ void RenderTarget::destroy() glDeleteTextures(1, &texture); glDeleteFramebuffers(1, &m_framebuffer); } + +GLuint RenderTarget::framebuffer() const +{ + return m_framebuffer; +} diff --git a/src/main/cpp/render_target.h b/src/main/cpp/render_target.h index 91845aa..3aa15b4 100644 --- a/src/main/cpp/render_target.h +++ b/src/main/cpp/render_target.h @@ -11,6 +11,7 @@ public: bool create(uint32_t width, uint32_t height); bool create_exported(uint32_t width, uint32_t height); [[nodiscard]] AHardwareBuffer* hardware_buffer(); + [[nodiscard]] GLuint framebuffer() const; void bind(); void unbind(); void destroy(); diff --git a/src/main/java/com/omixlab/mosis/NativeService.kt b/src/main/java/com/omixlab/mosis/NativeService.kt index ba0597a..af66ad2 100644 --- a/src/main/java/com/omixlab/mosis/NativeService.kt +++ b/src/main/java/com/omixlab/mosis/NativeService.kt @@ -5,6 +5,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service import android.content.Intent +import android.content.res.AssetManager import android.os.IBinder import androidx.core.app.NotificationCompat @@ -16,10 +17,18 @@ class NativeService : Service() { System.loadLibrary("mosis-service") } } - private external fun getBinderNative(): IBinder + external fun getBinderNative(): IBinder + external fun setAssetManager(assetManager: AssetManager) + override fun onBind(intent: Intent): IBinder { return getBinderNative() } + + override fun onCreate() { + super.onCreate() + setAssetManager(assets) + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { createNotificationChannel() // Create the notification required for the foreground service