From 78db66c15e74085c1bdd77c46d051bad3fc705da Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 29 Apr 2023 16:59:46 +0200 Subject: [PATCH 01/80] Add effect sample --- CMake/SampleFileList.cmake | 7 + CMake/gen_samplelists.sh | 2 +- CMakeLists.txt | 5 +- Samples/basic/effect/data/effect.rml | 112 +++++++++++ Samples/basic/effect/data/effect_style.rcss | 196 ++++++++++++++++++++ Samples/basic/effect/src/main.cpp | 140 ++++++++++++++ 6 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 Samples/basic/effect/data/effect.rml create mode 100644 Samples/basic/effect/data/effect_style.rcss create mode 100644 Samples/basic/effect/src/main.cpp diff --git a/CMake/SampleFileList.cmake b/CMake/SampleFileList.cmake index 75693ada6..1cd8eb16a 100644 --- a/CMake/SampleFileList.cmake +++ b/CMake/SampleFileList.cmake @@ -73,6 +73,13 @@ set(drag_SRC_FILES ${PROJECT_SOURCE_DIR}/Samples/basic/drag/src/main.cpp ) +set(effect_HDR_FILES +) + +set(effect_SRC_FILES + ${PROJECT_SOURCE_DIR}/Samples/basic/effect/src/main.cpp +) + set(loaddocument_HDR_FILES ) diff --git a/CMake/gen_samplelists.sh b/CMake/gen_samplelists.sh index 002365a5d..ae3d884d7 100755 --- a/CMake/gen_samplelists.sh +++ b/CMake/gen_samplelists.sh @@ -7,7 +7,7 @@ hdr='set(sample_HDR_FILES' srcdir='${PROJECT_SOURCE_DIR}' srcpath=Samples samples=( 'shell' - 'basic/animation' 'basic/benchmark' 'basic/bitmapfont' 'basic/customlog' 'basic/databinding' 'basic/demo' 'basic/drag' 'basic/loaddocument' 'basic/treeview' 'basic/transform' + 'basic/animation' 'basic/benchmark' 'basic/bitmapfont' 'basic/customlog' 'basic/databinding' 'basic/demo' 'basic/drag' 'basic/effect' 'basic/loaddocument' 'basic/treeview' 'basic/transform' 'basic/lottie' 'basic/svg' 'tutorial/template' 'tutorial/drag' 'invaders' 'luainvaders' diff --git a/CMakeLists.txt b/CMakeLists.txt index f4020f43a..0c5b11d1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -814,7 +814,7 @@ endif() if(BUILD_SAMPLES) - set(samples treeview customlog drag loaddocument transform bitmapfont animation benchmark demo databinding) + set(samples treeview customlog drag loaddocument transform bitmapfont animation benchmark demo databinding effect) set(tutorials template drag) if(ENABLE_LOTTIE_PLUGIN) @@ -963,6 +963,9 @@ if(BUILD_SAMPLES) install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/demo/data DESTINATION ${SAMPLES_DIR}/basic/demo ) + install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/effect/data + DESTINATION ${SAMPLES_DIR}/basic/effect + ) install(DIRECTORY ${PROJECT_SOURCE_DIR}/Samples/basic/transform/data DESTINATION ${SAMPLES_DIR}/basic/transform ) diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml new file mode 100644 index 000000000..841e4c78c --- /dev/null +++ b/Samples/basic/effect/data/effect.rml @@ -0,0 +1,112 @@ + + + + +Effect Sample + + + +

Effect Sample

+ + + +
+ +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+ +
+ +
diff --git a/Samples/basic/effect/data/effect_style.rcss b/Samples/basic/effect/data/effect_style.rcss new file mode 100644 index 000000000..badb2f839 --- /dev/null +++ b/Samples/basic/effect/data/effect_style.rcss @@ -0,0 +1,196 @@ +body { + font-family: LatoLatin; + font-weight: normal; + font-style: normal; + font-size: 15dp; + + left: 80dp; + right: 80dp; + top: 50dp; + bottom: 50dp; + min-width: 400dp; + min-height: 60dp; + background-color: #a4b6b7; + border: 3dp #d3e9ea; + border-radius: 30dp 8dp; + padding-top: 75dp; + overflow: hidden auto; +} +h1 { + margin: 0em 0 0.7em; + font-size: 22dp; + font-effect: glow(2dp #354c2e); + color: #fed; + padding: 1em 0 1em 40dp; + border-bottom: 3dp #d3e9ea; + background-color: #619158; + z-index: 1; + position: fixed; + top: 0; + right: 0; + left: 0; +} +handle.size { + position: fixed; + bottom: 0; + right: 0; + width: 18dp; + height: 18dp; + background-color: #d3e9ea66; + border-top-left-radius: 5dp; + cursor: resize; +} +handle.size:hover, handle.size:active { + background-color: #d3e9ea; +} + +#menu_button { + position: fixed; + z-index: 2; + top: 15dp; + right: 25dp; + box-sizing: border-box; + width: 36dp; + height: 36dp; + + background: #fffc; + border: 2dp #555a; + border-radius: 5dp; + color: #333; + padding-top: 5dp; + text-align: center; + line-height: 7dp; + font-size: 28dp; + cursor: pointer; +} +#menu_button.open { + background-color: #4bdc; + border-color: transparent; + border-top-right-radius: 15dp; +} +#menu_button:hover { background: #bcbc; } +#menu_button:active { background: #abac; } +#menu_button.open:hover { background: #5cec; } +#menu_button.open:active { background: #4bdc; } + +#menu { + position: fixed; + z-index: 1; + top: 15dp; + right: 25dp; + box-sizing: border-box; + width: 400dp; + height: 415dp; + overflow: auto; + + background: #fffc; + border: 2dp #555a; + border-radius: 15dp; + color: #222; + padding: 20dp 40dp 0dp; +} +#menu table { + margin-bottom: 10dp; +} +#menu td { + vertical-align: middle; + height: 36dp; + line-height: 16dp; +} +#menu td:nth-child(3) { + text-align: right; + white-space: nowrap; + font-size: 0.92em; +} + +#submenu { + display: flex; + text-align: center; + margin-bottom: 20dp; + justify-content: space-around; +} +#submenu div { + flex: 0.35; + height: 25dp; + cursor: pointer; + border-bottom: 1dp #aaa; + box-sizing: border-box; +} +#submenu div:hover { + color: #000; + border-bottom-color: #555; +} +#submenu div.selected { + font-weight: bold; + color: #37a; + border-bottom-color: #4bd; + border-bottom: 2dp #37a; +} + +scrollbarvertical { + margin-top: 75dp; + margin-bottom: 20dp; + margin-right: 0dp; + width: 0dp; +} +scrollbarvertical sliderbar { + margin-left: -14dp; + width: 12dp; + min-height: 25dp; + background: #d3e9ea66; + border-radius: 4dp; +} +scrollbarvertical sliderbar:hover, scrollbarvertical sliderbar:active { + background: #d3e9eaaa; +} + +input.range { + width: 100%; + height: 15dp; + transition: opacity 0.2s cubic-out; +} +input.range:disabled { opacity: 0.3; } +input.range slidertrack { + background-color: #fff; +} +input.range sliderbar { + width: 15dp; + border-radius: 3dp; +} +input.range:hover sliderbar { background-color: #333; } +input.range sliderbar:active { background-color: #111; } +input.range sliderbar, input.range sliderbar:disabled { background-color: #555; } +input.range sliderarrowdec, input.range sliderarrowinc { + width: 12dp; + height: 15dp; +} +input.range sliderarrowdec { border-radius: 2dp 0 0 2dp; } +input.range sliderarrowinc { border-radius: 0 2dp 2dp 0; } +input.range sliderarrowdec:hover, input.range sliderarrowinc:hover { background-color: #ddd; } +input.range sliderarrowdec:active, input.range sliderarrowinc:active { background-color: #eee; } +input.range sliderarrowdec, input.range sliderarrowinc, +input.range sliderarrowdec:disabled, input.range sliderarrowinc:disabled { background-color: #ccc; } + +input.radio, input.checkbox { + width: 15dp; + height: 15dp; + border: 1dp #ccc; + background: #fff; + border-radius: 2dp; +} +input.radio { + border-radius: 8dp; +} +input.radio:hover, input.checkbox:hover { background-color: #ff3; } +input.radio:active, input.checkbox:active { background-color: #ddd; } +input.radio:checked, input.checkbox:checked { background-color: #555; } + +button { + border: 1dp #555; + border-radius: 7dp; + padding: 6dp 13dp; + background-color: #fffa; + cursor: pointer; +} +button:hover { background-color: #ccca; } +button:active { background-color: #bbba; } diff --git a/Samples/basic/effect/src/main.cpp b/Samples/basic/effect/src/main.cpp new file mode 100644 index 000000000..2d5d733bc --- /dev/null +++ b/Samples/basic/effect/src/main.cpp @@ -0,0 +1,140 @@ +/* + * 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 +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include +int APIENTRY WinMain(HINSTANCE /*instance_handle*/, HINSTANCE /*previous_instance_handle*/, char* /*command_line*/, int /*command_show*/) +#else +int main(int /*argc*/, char** /*argv*/) +#endif +{ + const int window_width = 1024; + const int window_height = 768; + + // Initializes the shell which provides common functionality used by the included samples. + if (!Shell::Initialize()) + return -1; + + // Constructs the system and render interfaces, creates a window, and attaches the renderer. + if (!Backend::Initialize("Effect Sample", window_width, window_height, true)) + { + Shell::Shutdown(); + return -1; + } + + // Install the custom interfaces constructed by the backend before initializing RmlUi. + Rml::SetSystemInterface(Backend::GetSystemInterface()); + Rml::SetRenderInterface(Backend::GetRenderInterface()); + + // RmlUi initialisation. + Rml::Initialise(); + + // Create the main RmlUi context. + Rml::Context* context = Rml::CreateContext("main", Rml::Vector2i(window_width, window_height)); + if (!context) + { + Rml::Shutdown(); + Backend::Shutdown(); + Shell::Shutdown(); + return -1; + } + + Rml::Debugger::Initialise(context); + Shell::LoadFonts(); + + static constexpr float perspective_max = 3000.f; + + struct EffectData { + bool show_menu = false; + Rml::String submenu = "filter"; + + struct Filter { + float opacity = 1.0f; + } filter; + + struct Transform { + float scale = 1.0f; + Rml::Vector3f rotate; + float perspective = perspective_max; + Rml::Vector2f perspective_origin = Rml::Vector2f(50.f); + bool transform_all = false; + } transform; + } data; + + if (Rml::DataModelConstructor constructor = context->CreateDataModel("effects")) + { + constructor.Bind("show_menu", &data.show_menu); + constructor.Bind("submenu", &data.submenu); + + constructor.Bind("opacity", &data.filter.opacity); + + constructor.Bind("scale", &data.transform.scale); + constructor.Bind("rotate_x", &data.transform.rotate.x); + constructor.Bind("rotate_y", &data.transform.rotate.y); + constructor.Bind("rotate_z", &data.transform.rotate.z); + constructor.Bind("perspective", &data.transform.perspective); + constructor.Bind("perspective_origin_x", &data.transform.perspective_origin.x); + constructor.Bind("perspective_origin_y", &data.transform.perspective_origin.y); + constructor.Bind("transform_all", &data.transform.transform_all); + + constructor.BindEventCallback("reset", [&data](Rml::DataModelHandle handle, Rml::Event& /*ev*/, const Rml::VariantList& /*arguments*/) { + if (data.submenu == "transform") + data.transform = EffectData::Transform{}; + else if (data.submenu == "filter") + data.filter = EffectData::Filter{}; + handle.DirtyAllVariables(); + }); + } + + if (Rml::ElementDocument* document = context->LoadDocument("basic/effect/data/effect.rml")) + document->Show(); + + bool running = true; + while (running) + { + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} From 05ec09bce30481183d608b61ced403b556c9b06b Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 29 Apr 2023 18:12:56 +0200 Subject: [PATCH 02/80] Refactor: Move DecoratorInstancer into the Decorator files --- CMake/FileList.cmake | 12 -- CMake/SampleFileList.cmake | 8 - Include/RmlUi/Core.h | 1 - Include/RmlUi/Core/Decorator.h | 69 ++++++- Include/RmlUi/Core/DecoratorInstancer.h | 107 ----------- Samples/invaders/src/DecoratorDefender.cpp | 24 +++ Samples/invaders/src/DecoratorDefender.h | 13 ++ .../src/DecoratorInstancerDefender.cpp | 55 ------ .../invaders/src/DecoratorInstancerDefender.h | 51 ----- .../src/DecoratorInstancerStarfield.cpp | 63 ------ .../src/DecoratorInstancerStarfield.h | 52 ----- Samples/invaders/src/DecoratorStarfield.cpp | 32 ++++ Samples/invaders/src/DecoratorStarfield.h | 13 ++ Samples/invaders/src/main.cpp | 4 +- Samples/luainvaders/src/DecoratorDefender.cpp | 24 +++ Samples/luainvaders/src/DecoratorDefender.h | 13 ++ .../src/DecoratorInstancerDefender.cpp | 55 ------ .../src/DecoratorInstancerDefender.h | 51 ----- .../src/DecoratorInstancerStarfield.cpp | 63 ------ .../src/DecoratorInstancerStarfield.h | 52 ----- .../luainvaders/src/DecoratorStarfield.cpp | 32 ++++ Samples/luainvaders/src/DecoratorStarfield.h | 13 ++ Samples/luainvaders/src/main.cpp | 4 +- Source/Core/Decorator.cpp | 39 ++++ Source/Core/DecoratorGradient.h | 1 - Source/Core/DecoratorInstancer.cpp | 73 ------- Source/Core/DecoratorNinePatch.h | 1 - Source/Core/DecoratorTiled.cpp | 148 ++++++++++++++ Source/Core/DecoratorTiled.h | 26 +++ Source/Core/DecoratorTiledBox.cpp | 39 ++++ Source/Core/DecoratorTiledBox.h | 10 + Source/Core/DecoratorTiledBoxInstancer.cpp | 73 ------- Source/Core/DecoratorTiledBoxInstancer.h | 51 ----- Source/Core/DecoratorTiledHorizontal.cpp | 27 +++ Source/Core/DecoratorTiledHorizontal.h | 10 + .../DecoratorTiledHorizontalInstancer.cpp | 62 ------ .../Core/DecoratorTiledHorizontalInstancer.h | 51 ----- Source/Core/DecoratorTiledImage.cpp | 25 +++ Source/Core/DecoratorTiledImage.h | 10 + Source/Core/DecoratorTiledImageInstancer.cpp | 59 ------ Source/Core/DecoratorTiledImageInstancer.h | 51 ----- Source/Core/DecoratorTiledInstancer.cpp | 181 ------------------ Source/Core/DecoratorTiledInstancer.h | 70 ------- Source/Core/DecoratorTiledVertical.cpp | 28 +++ Source/Core/DecoratorTiledVertical.h | 10 + .../Core/DecoratorTiledVerticalInstancer.cpp | 62 ------ Source/Core/DecoratorTiledVerticalInstancer.h | 51 ----- Source/Core/ElementAnimation.cpp | 2 +- Source/Core/ElementDecoration.cpp | 1 - Source/Core/Factory.cpp | 8 +- Source/Core/PropertyParserDecorator.cpp | 2 +- Source/Core/StyleSheet.cpp | 2 +- Source/Core/StyleSheetParser.cpp | 2 +- Source/Core/TypeConverter.cpp | 2 +- 54 files changed, 617 insertions(+), 1371 deletions(-) delete mode 100644 Include/RmlUi/Core/DecoratorInstancer.h delete mode 100644 Samples/invaders/src/DecoratorInstancerDefender.cpp delete mode 100644 Samples/invaders/src/DecoratorInstancerDefender.h delete mode 100644 Samples/invaders/src/DecoratorInstancerStarfield.cpp delete mode 100644 Samples/invaders/src/DecoratorInstancerStarfield.h delete mode 100644 Samples/luainvaders/src/DecoratorInstancerDefender.cpp delete mode 100644 Samples/luainvaders/src/DecoratorInstancerDefender.h delete mode 100644 Samples/luainvaders/src/DecoratorInstancerStarfield.cpp delete mode 100644 Samples/luainvaders/src/DecoratorInstancerStarfield.h delete mode 100644 Source/Core/DecoratorInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledBoxInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledBoxInstancer.h delete mode 100644 Source/Core/DecoratorTiledHorizontalInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledHorizontalInstancer.h delete mode 100644 Source/Core/DecoratorTiledImageInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledImageInstancer.h delete mode 100644 Source/Core/DecoratorTiledInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledInstancer.h delete mode 100644 Source/Core/DecoratorTiledVerticalInstancer.cpp delete mode 100644 Source/Core/DecoratorTiledVerticalInstancer.h diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 31873dfd5..a4e863e97 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -14,14 +14,9 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.h - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBoxInstancer.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.h - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontalInstancer.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImage.h - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImageInstancer.h - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledInstancer.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.h - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.h @@ -142,7 +137,6 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataVariable.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Decorator.h - ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecoratorInstancer.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Dictionary.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.inl @@ -250,18 +244,12 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/DataViewDefault.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Decorator.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBoxInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontalInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImage.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImageInstancer.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp diff --git a/CMake/SampleFileList.cmake b/CMake/SampleFileList.cmake index 1cd8eb16a..eb29e183d 100644 --- a/CMake/SampleFileList.cmake +++ b/CMake/SampleFileList.cmake @@ -135,8 +135,6 @@ set(tutorial_drag_SRC_FILES set(invaders_HDR_FILES ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorDefender.h - ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerDefender.h - ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerStarfield.h ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorStarfield.h ${PROJECT_SOURCE_DIR}/Samples/invaders/src/Defender.h ${PROJECT_SOURCE_DIR}/Samples/invaders/src/ElementGame.h @@ -158,8 +156,6 @@ set(invaders_HDR_FILES set(invaders_SRC_FILES ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorDefender.cpp - ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerDefender.cpp - ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorInstancerStarfield.cpp ${PROJECT_SOURCE_DIR}/Samples/invaders/src/DecoratorStarfield.cpp ${PROJECT_SOURCE_DIR}/Samples/invaders/src/Defender.cpp ${PROJECT_SOURCE_DIR}/Samples/invaders/src/ElementGame.cpp @@ -182,8 +178,6 @@ set(invaders_SRC_FILES set(luainvaders_HDR_FILES ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorDefender.h - ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerDefender.h - ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerStarfield.h ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorStarfield.h ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/Defender.h ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/ElementGame.h @@ -200,8 +194,6 @@ set(luainvaders_HDR_FILES set(luainvaders_SRC_FILES ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorDefender.cpp - ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerDefender.cpp - ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorInstancerStarfield.cpp ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/DecoratorStarfield.cpp ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/Defender.cpp ${PROJECT_SOURCE_DIR}/Samples/luainvaders/src/ElementGame.cpp diff --git a/Include/RmlUi/Core.h b/Include/RmlUi/Core.h index 4d7bda350..4a3c64f71 100644 --- a/Include/RmlUi/Core.h +++ b/Include/RmlUi/Core.h @@ -41,7 +41,6 @@ #include "Core/DataTypes.h" #include "Core/DataVariable.h" #include "Core/Decorator.h" -#include "Core/DecoratorInstancer.h" #include "Core/Element.h" #include "Core/ElementDocument.h" #include "Core/ElementInstancer.h" diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index 2e81bc9dd..9e976dcf1 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -30,6 +30,8 @@ #define RMLUI_CORE_DECORATOR_H #include "Header.h" +#include "PropertyDictionary.h" +#include "PropertySpecification.h" #include "Texture.h" #include "Types.h" @@ -38,8 +40,12 @@ namespace Rml { class DecoratorInstancer; class Element; class PropertyDictionary; -class Property; + +struct Sprite; struct Texture; +class StyleSheet; +class DecoratorInstancerInterface; +class PropertyDefinition; /** The abstract base class for any visual object that can be attached to any element. @@ -87,5 +93,66 @@ class RMLUICORE_API Decorator { Vector additional_textures; }; +/** + An element instancer provides a method for allocating and deallocating decorators. + + It is important at the same instancer that allocated a decorator releases it. This ensures there are no issues with + memory from different DLLs getting mixed up. + + @author Peter Curry + */ + +class RMLUICORE_API DecoratorInstancer { +public: + DecoratorInstancer(); + virtual ~DecoratorInstancer(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + /// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple". + /// @param[in] properties All RCSS properties associated with the decorator. + /// @param[in] instancer_interface An interface for querying the active style sheet. + /// @return A shared_ptr to the decorator if it was instanced successfully. + virtual SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) = 0; + + /// Returns the property specification associated with the instancer. + const PropertySpecification& GetPropertySpecification() const; + +protected: + /// Registers a property for the decorator. + /// @param[in] property_name The name of the new property (how it is specified through RCSS). + /// @param[in] default_value The default value to be used. + /// @return The new property definition, ready to have parsers attached. + PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); + /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators. + /// @param[in] shorthand_name The name to register the new shorthand property under. + /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is + /// the order in which the values will be processed. + /// @param[in] type The type of shorthand to declare. + /// @param True if all the property names exist, false otherwise. + ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); + +private: + PropertySpecification properties; +}; + +class RMLUICORE_API DecoratorInstancerInterface { +public: + DecoratorInstancerInterface(const StyleSheet& style_sheet, const PropertySource* property_source) : + style_sheet(style_sheet), property_source(property_source) + {} + + /// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on. + const Sprite* GetSprite(const String& name) const; + + /// Get a texture using the given filename. + /// This will use the document path where the 'decorator' property was declared to locate relative files, if available. + Texture GetTexture(const String& filename) const; + +private: + const StyleSheet& style_sheet; + const PropertySource* property_source; +}; + } // namespace Rml #endif diff --git a/Include/RmlUi/Core/DecoratorInstancer.h b/Include/RmlUi/Core/DecoratorInstancer.h deleted file mode 100644 index 6db5121dc..000000000 --- a/Include/RmlUi/Core/DecoratorInstancer.h +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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_CORE_DECORATORINSTANCER_H -#define RMLUI_CORE_DECORATORINSTANCER_H - -#include "Header.h" -#include "PropertyDictionary.h" -#include "PropertySpecification.h" - -namespace Rml { - -struct Sprite; -struct Texture; -class StyleSheet; -class Decorator; -class DecoratorInstancerInterface; -class PropertyDefinition; - -/** - An element instancer provides a method for allocating and deallocating decorators. - - It is important at the same instancer that allocated a decorator releases it. This ensures there are no issues with - memory from different DLLs getting mixed up. - - @author Peter Curry - */ - -class RMLUICORE_API DecoratorInstancer { -public: - DecoratorInstancer(); - virtual ~DecoratorInstancer(); - - /// Instances a decorator given the property tag and attributes from the RCSS file. - /// @param[in] name The type of decorator desired. For example, "decorator: simple(...);" is declared as type "simple". - /// @param[in] properties All RCSS properties associated with the decorator. - /// @param[in] instancer_interface An interface for querying the active style sheet. - /// @return A shared_ptr to the decorator if it was instanced successfully. - virtual SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) = 0; - - /// Returns the property specification associated with the instancer. - const PropertySpecification& GetPropertySpecification() const; - -protected: - /// Registers a property for the decorator. - /// @param[in] property_name The name of the new property (how it is specified through RCSS). - /// @param[in] default_value The default value to be used. - /// @return The new property definition, ready to have parsers attached. - PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); - /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators. - /// @param[in] shorthand_name The name to register the new shorthand property under. - /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is - /// the order in which the values will be processed. - /// @param[in] type The type of shorthand to declare. - /// @param True if all the property names exist, false otherwise. - ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); - -private: - PropertySpecification properties; -}; - -class RMLUICORE_API DecoratorInstancerInterface { -public: - DecoratorInstancerInterface(const StyleSheet& style_sheet, const PropertySource* property_source) : - style_sheet(style_sheet), property_source(property_source) - {} - - /// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on. - const Sprite* GetSprite(const String& name) const; - - /// Get a texture using the given filename. - /// This will use the document path where the 'decorator' property was declared to locate relative files, if available. - Texture GetTexture(const String& filename) const; - -private: - const StyleSheet& style_sheet; - const PropertySource* property_source; -}; - -} // namespace Rml -#endif diff --git a/Samples/invaders/src/DecoratorDefender.cpp b/Samples/invaders/src/DecoratorDefender.cpp index a4a8b56b7..5c5e5dccf 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -31,8 +31,10 @@ #include #include #include +#include #include #include +#include DecoratorDefender::~DecoratorDefender() {} @@ -72,3 +74,25 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); } } + +DecoratorInstancerDefender::DecoratorInstancerDefender() +{ + id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); + RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); +} + +DecoratorInstancerDefender::~DecoratorInstancerDefender() {} + +Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) +{ + const Rml::Property* image_source_property = properties.GetProperty(id_image_src); + Rml::String image_source = image_source_property->Get(); + Rml::Texture texture = instancer_interface.GetTexture(image_source); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(texture)) + return decorator; + + return nullptr; +} diff --git a/Samples/invaders/src/DecoratorDefender.h b/Samples/invaders/src/DecoratorDefender.h index d38e3b7e1..7626f2872 100644 --- a/Samples/invaders/src/DecoratorDefender.h +++ b/Samples/invaders/src/DecoratorDefender.h @@ -54,4 +54,17 @@ class DecoratorDefender : public Rml::Decorator { int image_index; }; +class DecoratorInstancerDefender : public Rml::DecoratorInstancer { +public: + DecoratorInstancerDefender(); + ~DecoratorInstancerDefender(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_image_src; +}; + #endif diff --git a/Samples/invaders/src/DecoratorInstancerDefender.cpp b/Samples/invaders/src/DecoratorInstancerDefender.cpp deleted file mode 100644 index f8ede7366..000000000 --- a/Samples/invaders/src/DecoratorInstancerDefender.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 "DecoratorInstancerDefender.h" -#include "DecoratorDefender.h" -#include -#include -#include - -DecoratorInstancerDefender::DecoratorInstancerDefender() -{ - id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); - RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); -} - -DecoratorInstancerDefender::~DecoratorInstancerDefender() {} - -Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) -{ - const Rml::Property* image_source_property = properties.GetProperty(id_image_src); - Rml::String image_source = image_source_property->Get(); - Rml::Texture texture = instancer_interface.GetTexture(image_source); - - auto decorator = Rml::MakeShared(); - if (decorator->Initialise(texture)) - return decorator; - - return nullptr; -} diff --git a/Samples/invaders/src/DecoratorInstancerDefender.h b/Samples/invaders/src/DecoratorInstancerDefender.h deleted file mode 100644 index 9962fae4c..000000000 --- a/Samples/invaders/src/DecoratorInstancerDefender.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_INVADERS_DECORATORINSTANCERDEFENDER_H -#define RMLUI_INVADERS_DECORATORINSTANCERDEFENDER_H - -#include - -/** - @author Robert Curry - */ - -class DecoratorInstancerDefender : public Rml::DecoratorInstancer { -public: - DecoratorInstancerDefender(); - ~DecoratorInstancerDefender(); - - /// Instances a decorator given the property tag and attributes from the RCSS file. - Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) override; - -private: - Rml::PropertyId id_image_src; -}; - -#endif diff --git a/Samples/invaders/src/DecoratorInstancerStarfield.cpp b/Samples/invaders/src/DecoratorInstancerStarfield.cpp deleted file mode 100644 index e4e45dca4..000000000 --- a/Samples/invaders/src/DecoratorInstancerStarfield.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 "DecoratorInstancerStarfield.h" -#include "DecoratorStarfield.h" -#include -#include - -DecoratorInstancerStarfield::DecoratorInstancerStarfield() -{ - id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); - id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); - id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); - id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); - id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); - id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); - id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); -} - -DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} - -Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& /*instancer_interface*/) -{ - int num_layers = properties.GetProperty(id_num_layers)->Get(); - Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); - Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); - float top_speed = properties.GetProperty(id_top_speed)->Get(); - float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); - int top_density = properties.GetProperty(id_top_density)->Get(); - int bottom_density = properties.GetProperty(id_bottom_density)->Get(); - - auto decorator = Rml::MakeShared(); - if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) - return decorator; - - return nullptr; -} diff --git a/Samples/invaders/src/DecoratorInstancerStarfield.h b/Samples/invaders/src/DecoratorInstancerStarfield.h deleted file mode 100644 index c3a6b669e..000000000 --- a/Samples/invaders/src/DecoratorInstancerStarfield.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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_INVADERS_DECORATORINSTANCERSTARFIELD_H -#define RMLUI_INVADERS_DECORATORINSTANCERSTARFIELD_H - -#include "DecoratorStarfield.h" -#include - -/** - @author Robert Curry - */ - -class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { -public: - DecoratorInstancerStarfield(); - ~DecoratorInstancerStarfield(); - - /// Instances a decorator given the property tag and attributes from the RCSS file. - Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) override; - -private: - Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; -}; - -#endif diff --git a/Samples/invaders/src/DecoratorStarfield.cpp b/Samples/invaders/src/DecoratorStarfield.cpp index e6ae0e63b..6b24cd135 100644 --- a/Samples/invaders/src/DecoratorStarfield.cpp +++ b/Samples/invaders/src/DecoratorStarfield.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -151,3 +152,34 @@ void DecoratorStarfield::StarField::Update(double t) } } } + +DecoratorInstancerStarfield::DecoratorInstancerStarfield() +{ + id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); + id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); + id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); + id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); + id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); + id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); + id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); +} + +DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} + +Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& /*instancer_interface*/) +{ + int num_layers = properties.GetProperty(id_num_layers)->Get(); + Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); + Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); + float top_speed = properties.GetProperty(id_top_speed)->Get(); + float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); + int top_density = properties.GetProperty(id_top_density)->Get(); + int bottom_density = properties.GetProperty(id_bottom_density)->Get(); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) + return decorator; + + return nullptr; +} diff --git a/Samples/invaders/src/DecoratorStarfield.h b/Samples/invaders/src/DecoratorStarfield.h index a2bd2adff..7cc528771 100644 --- a/Samples/invaders/src/DecoratorStarfield.h +++ b/Samples/invaders/src/DecoratorStarfield.h @@ -78,4 +78,17 @@ class DecoratorStarfield : public Rml::Decorator { }; }; +class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { +public: + DecoratorInstancerStarfield(); + ~DecoratorInstancerStarfield(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; +}; + #endif diff --git a/Samples/invaders/src/main.cpp b/Samples/invaders/src/main.cpp index 121208d8e..cab7f5089 100644 --- a/Samples/invaders/src/main.cpp +++ b/Samples/invaders/src/main.cpp @@ -26,8 +26,8 @@ * */ -#include "DecoratorInstancerDefender.h" -#include "DecoratorInstancerStarfield.h" +#include "DecoratorDefender.h" +#include "DecoratorStarfield.h" #include "ElementGame.h" #include "EventHandlerHighScore.h" #include "EventHandlerOptions.h" diff --git a/Samples/luainvaders/src/DecoratorDefender.cpp b/Samples/luainvaders/src/DecoratorDefender.cpp index a4a8b56b7..5c5e5dccf 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -31,8 +31,10 @@ #include #include #include +#include #include #include +#include DecoratorDefender::~DecoratorDefender() {} @@ -72,3 +74,25 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); } } + +DecoratorInstancerDefender::DecoratorInstancerDefender() +{ + id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); + RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); +} + +DecoratorInstancerDefender::~DecoratorInstancerDefender() {} + +Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) +{ + const Rml::Property* image_source_property = properties.GetProperty(id_image_src); + Rml::String image_source = image_source_property->Get(); + Rml::Texture texture = instancer_interface.GetTexture(image_source); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(texture)) + return decorator; + + return nullptr; +} diff --git a/Samples/luainvaders/src/DecoratorDefender.h b/Samples/luainvaders/src/DecoratorDefender.h index 9f0e6e1f8..4f808e910 100644 --- a/Samples/luainvaders/src/DecoratorDefender.h +++ b/Samples/luainvaders/src/DecoratorDefender.h @@ -54,4 +54,17 @@ class DecoratorDefender : public Rml::Decorator { int image_index; }; +class DecoratorInstancerDefender : public Rml::DecoratorInstancer { +public: + DecoratorInstancerDefender(); + ~DecoratorInstancerDefender(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_image_src; +}; + #endif diff --git a/Samples/luainvaders/src/DecoratorInstancerDefender.cpp b/Samples/luainvaders/src/DecoratorInstancerDefender.cpp deleted file mode 100644 index f8ede7366..000000000 --- a/Samples/luainvaders/src/DecoratorInstancerDefender.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 "DecoratorInstancerDefender.h" -#include "DecoratorDefender.h" -#include -#include -#include - -DecoratorInstancerDefender::DecoratorInstancerDefender() -{ - id_image_src = RegisterProperty("image-src", "").AddParser("string").GetId(); - RegisterShorthand("decorator", "image-src", Rml::ShorthandType::FallThrough); -} - -DecoratorInstancerDefender::~DecoratorInstancerDefender() {} - -Rml::SharedPtr DecoratorInstancerDefender::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) -{ - const Rml::Property* image_source_property = properties.GetProperty(id_image_src); - Rml::String image_source = image_source_property->Get(); - Rml::Texture texture = instancer_interface.GetTexture(image_source); - - auto decorator = Rml::MakeShared(); - if (decorator->Initialise(texture)) - return decorator; - - return nullptr; -} diff --git a/Samples/luainvaders/src/DecoratorInstancerDefender.h b/Samples/luainvaders/src/DecoratorInstancerDefender.h deleted file mode 100644 index 3123f80d7..000000000 --- a/Samples/luainvaders/src/DecoratorInstancerDefender.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_LUAINVADERS_DECORATORINSTANCERDEFENDER_H -#define RMLUI_LUAINVADERS_DECORATORINSTANCERDEFENDER_H - -#include - -/** - @author Robert Curry - */ - -class DecoratorInstancerDefender : public Rml::DecoratorInstancer { -public: - DecoratorInstancerDefender(); - ~DecoratorInstancerDefender(); - - /// Instances a decorator given the property tag and attributes from the RCSS file. - Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) override; - -private: - Rml::PropertyId id_image_src; -}; - -#endif diff --git a/Samples/luainvaders/src/DecoratorInstancerStarfield.cpp b/Samples/luainvaders/src/DecoratorInstancerStarfield.cpp deleted file mode 100644 index e4e45dca4..000000000 --- a/Samples/luainvaders/src/DecoratorInstancerStarfield.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 "DecoratorInstancerStarfield.h" -#include "DecoratorStarfield.h" -#include -#include - -DecoratorInstancerStarfield::DecoratorInstancerStarfield() -{ - id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); - id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); - id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); - id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); - id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); - id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); - id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); -} - -DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} - -Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& /*instancer_interface*/) -{ - int num_layers = properties.GetProperty(id_num_layers)->Get(); - Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); - Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); - float top_speed = properties.GetProperty(id_top_speed)->Get(); - float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); - int top_density = properties.GetProperty(id_top_density)->Get(); - int bottom_density = properties.GetProperty(id_bottom_density)->Get(); - - auto decorator = Rml::MakeShared(); - if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) - return decorator; - - return nullptr; -} diff --git a/Samples/luainvaders/src/DecoratorInstancerStarfield.h b/Samples/luainvaders/src/DecoratorInstancerStarfield.h deleted file mode 100644 index bd2873988..000000000 --- a/Samples/luainvaders/src/DecoratorInstancerStarfield.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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_LUAINVADERS_DECORATORINSTANCERSTARFIELD_H -#define RMLUI_LUAINVADERS_DECORATORINSTANCERSTARFIELD_H - -#include "DecoratorStarfield.h" -#include - -/** - @author Robert Curry - */ - -class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { -public: - DecoratorInstancerStarfield(); - ~DecoratorInstancerStarfield(); - - /// Instances a decorator given the property tag and attributes from the RCSS file. - Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, - const Rml::DecoratorInstancerInterface& instancer_interface) override; - -private: - Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; -}; - -#endif diff --git a/Samples/luainvaders/src/DecoratorStarfield.cpp b/Samples/luainvaders/src/DecoratorStarfield.cpp index e6ae0e63b..6b24cd135 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.cpp +++ b/Samples/luainvaders/src/DecoratorStarfield.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -151,3 +152,34 @@ void DecoratorStarfield::StarField::Update(double t) } } } + +DecoratorInstancerStarfield::DecoratorInstancerStarfield() +{ + id_num_layers = RegisterProperty("num-layers", "3").AddParser("number").GetId(); + id_top_colour = RegisterProperty("top-colour", "#dddc").AddParser("color").GetId(); + id_bottom_colour = RegisterProperty("bottom-colour", "#333c").AddParser("color").GetId(); + id_top_speed = RegisterProperty("top-speed", "10.0").AddParser("number").GetId(); + id_bottom_speed = RegisterProperty("bottom-speed", "2.0").AddParser("number").GetId(); + id_top_density = RegisterProperty("top-density", "15").AddParser("number").GetId(); + id_bottom_density = RegisterProperty("bottom-density", "10").AddParser("number").GetId(); +} + +DecoratorInstancerStarfield::~DecoratorInstancerStarfield() {} + +Rml::SharedPtr DecoratorInstancerStarfield::InstanceDecorator(const Rml::String& /*name*/, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& /*instancer_interface*/) +{ + int num_layers = properties.GetProperty(id_num_layers)->Get(); + Rml::Colourb top_colour = properties.GetProperty(id_top_colour)->Get(); + Rml::Colourb bottom_colour = properties.GetProperty(id_bottom_colour)->Get(); + float top_speed = properties.GetProperty(id_top_speed)->Get(); + float bottom_speed = properties.GetProperty(id_bottom_speed)->Get(); + int top_density = properties.GetProperty(id_top_density)->Get(); + int bottom_density = properties.GetProperty(id_bottom_density)->Get(); + + auto decorator = Rml::MakeShared(); + if (decorator->Initialise(num_layers, top_colour, bottom_colour, top_speed, bottom_speed, top_density, bottom_density)) + return decorator; + + return nullptr; +} diff --git a/Samples/luainvaders/src/DecoratorStarfield.h b/Samples/luainvaders/src/DecoratorStarfield.h index cc40e111f..80c350bce 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.h +++ b/Samples/luainvaders/src/DecoratorStarfield.h @@ -78,4 +78,17 @@ class DecoratorStarfield : public Rml::Decorator { }; }; +class DecoratorInstancerStarfield : public Rml::DecoratorInstancer { +public: + DecoratorInstancerStarfield(); + ~DecoratorInstancerStarfield(); + + /// Instances a decorator given the property tag and attributes from the RCSS file. + Rml::SharedPtr InstanceDecorator(const Rml::String& name, const Rml::PropertyDictionary& properties, + const Rml::DecoratorInstancerInterface& instancer_interface) override; + +private: + Rml::PropertyId id_num_layers, id_top_colour, id_bottom_colour, id_top_speed, id_bottom_speed, id_top_density, id_bottom_density; +}; + #endif diff --git a/Samples/luainvaders/src/main.cpp b/Samples/luainvaders/src/main.cpp index 191e1689f..f1d034afd 100644 --- a/Samples/luainvaders/src/main.cpp +++ b/Samples/luainvaders/src/main.cpp @@ -26,8 +26,8 @@ * */ -#include "DecoratorInstancerDefender.h" -#include "DecoratorInstancerStarfield.h" +#include "DecoratorDefender.h" +#include "DecoratorStarfield.h" #include "ElementGame.h" #include "HighScores.h" #include "LuaInterface.h" diff --git a/Source/Core/Decorator.cpp b/Source/Core/Decorator.cpp index 44a188466..7a3e6526a 100644 --- a/Source/Core/Decorator.cpp +++ b/Source/Core/Decorator.cpp @@ -28,6 +28,7 @@ #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/StyleSheet.h" #include "../../Include/RmlUi/Core/Texture.h" #include "TextureDatabase.h" #include @@ -76,4 +77,42 @@ const Texture* Decorator::GetTexture(int index) const return &(additional_textures[index]); } +DecoratorInstancer::DecoratorInstancer() : properties(10, 10) {} + +DecoratorInstancer::~DecoratorInstancer() {} + +const PropertySpecification& DecoratorInstancer::GetPropertySpecification() const +{ + return properties; +} + +PropertyDefinition& DecoratorInstancer::RegisterProperty(const String& property_name, const String& default_value) +{ + return properties.RegisterProperty(property_name, default_value, false, false); +} + +ShorthandId DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +{ + return properties.RegisterShorthand(shorthand_name, property_names, type); +} + +const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const +{ + return style_sheet.GetSprite(name); +} + +Texture DecoratorInstancerInterface::GetTexture(const String& filename) const +{ + Texture texture; + + if (!property_source) + { + Log::Message(Log::LT_WARNING, "Texture name '%s' in decorator could not be loaded, no property source available.", filename.c_str()); + return texture; + } + + texture.Set(filename, property_source->path); + + return texture; +} } // namespace Rml diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index 7906cecf6..aa46402c4 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -30,7 +30,6 @@ #define RMLUI_CORE_DECORATORGRADIENT_H #include "../../Include/RmlUi/Core/Decorator.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" #include "../../Include/RmlUi/Core/ID.h" namespace Rml { diff --git a/Source/Core/DecoratorInstancer.cpp b/Source/Core/DecoratorInstancer.cpp deleted file mode 100644 index fc4772dbd..000000000 --- a/Source/Core/DecoratorInstancer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 "../../Include/RmlUi/Core/DecoratorInstancer.h" -#include "../../Include/RmlUi/Core/StyleSheet.h" - -namespace Rml { - -DecoratorInstancer::DecoratorInstancer() : properties(10, 10) {} - -DecoratorInstancer::~DecoratorInstancer() {} - -const PropertySpecification& DecoratorInstancer::GetPropertySpecification() const -{ - return properties; -} - -PropertyDefinition& DecoratorInstancer::RegisterProperty(const String& property_name, const String& default_value) -{ - return properties.RegisterProperty(property_name, default_value, false, false); -} - -ShorthandId DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) -{ - return properties.RegisterShorthand(shorthand_name, property_names, type); -} - -const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const -{ - return style_sheet.GetSprite(name); -} - -Texture DecoratorInstancerInterface::GetTexture(const String& filename) const -{ - Texture texture; - - if (!property_source) - { - Log::Message(Log::LT_WARNING, "Texture name '%s' in decorator could not be loaded, no property source available.", filename.c_str()); - return texture; - } - - texture.Set(filename, property_source->path); - - return texture; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorNinePatch.h b/Source/Core/DecoratorNinePatch.h index 698309bc0..93f13da87 100644 --- a/Source/Core/DecoratorNinePatch.h +++ b/Source/Core/DecoratorNinePatch.h @@ -30,7 +30,6 @@ #define RMLUI_CORE_DECORATORNINEPATCH_H #include "../../Include/RmlUi/Core/Decorator.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" #include "../../Include/RmlUi/Core/ID.h" #include "../../Include/RmlUi/Core/Spritesheet.h" diff --git a/Source/Core/DecoratorTiled.cpp b/Source/Core/DecoratorTiled.cpp index 24d67a4fd..2c80c7391 100644 --- a/Source/Core/DecoratorTiled.cpp +++ b/Source/Core/DecoratorTiled.cpp @@ -31,6 +31,8 @@ #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/Spritesheet.h" #include namespace Rml { @@ -234,4 +236,150 @@ void DecoratorTiled::ScaleTileDimensions(Vector2f& tile_dimensions, float axis_v } } +DecoratorTiledInstancer::DecoratorTiledInstancer(size_t num_tiles) +{ + tile_property_ids.reserve(num_tiles); +} + +void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_fit_modes) +{ + TilePropertyIds ids = {}; + + ids.src = RegisterProperty(CreateString(32, "%s-src", name.c_str()), "").AddParser("string").GetId(); + + String additional_modes; + + if (register_fit_modes) + { + String fit_name = CreateString(32, "%s-fit", name.c_str()); + ids.fit = RegisterProperty(fit_name, "fill").AddParser("keyword", "fill, contain, cover, scale-none, scale-down").GetId(); + + String align_x_name = CreateString(32, "%s-align-x", name.c_str()); + ids.align_x = RegisterProperty(align_x_name, "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + + String align_y_name = CreateString(32, "%s-align-y", name.c_str()); + ids.align_y = RegisterProperty(align_y_name, "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + additional_modes += ", " + fit_name + ", " + align_x_name + ", " + align_y_name; + } + + ids.orientation = RegisterProperty(CreateString(32, "%s-orientation", name.c_str()), "none") + .AddParser("keyword", "none, flip-horizontal, flip-vertical, rotate-180") + .GetId(); + + RegisterShorthand(name, + CreateString(256, ("%s-src, %s-orientation" + additional_modes).c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), + name.c_str()), + ShorthandType::FallThrough); + + tile_property_ids.push_back(ids); +} + +bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, + const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) const +{ + RMLUI_ASSERT(num_tiles_and_textures == tile_property_ids.size()); + + String previous_texture_name; + Texture previous_texture; + + for (size_t i = 0; i < num_tiles_and_textures; i++) + { + const TilePropertyIds& ids = tile_property_ids[i]; + + const Property* src_property = properties.GetProperty(ids.src); + const String texture_name = src_property->Get(); + + // Skip the tile if it has no source name. + // Declaring the name 'auto' is the same as an empty string. This gives an easy way to skip certain + // tiles in a shorthand since we can't always declare an empty string. + if (texture_name.empty() || texture_name == "auto") + continue; + + // We are required to set default values before instancing the tile, thus, all properties should always be + // dereferencable. If the debugger captures a zero-dereference, check that all properties for every tile is set + // and default values are set just before instancing. + + DecoratorTiled::Tile& tile = tiles[i]; + Texture& texture = textures[i]; + + // A tile is always either a sprite or an image. + if (const Sprite* sprite = instancer_interface.GetSprite(texture_name)) + { + tile.position = sprite->rectangle.Position(); + tile.size = sprite->rectangle.Size(); + tile.display_scale = sprite->sprite_sheet->display_scale; + + texture = sprite->sprite_sheet->texture; + } + else + { + // No sprite found, so assume that the name is an image source. Since the common use case is to specify the + // same texture for all tiles, check the previous texture first before fetching from the global database. + if (texture_name == previous_texture_name) + { + texture = previous_texture; + } + else + { + texture = instancer_interface.GetTexture(texture_name); + + if (!texture) + return false; + + previous_texture_name = texture_name; + previous_texture = texture; + } + } + + if (ids.fit != PropertyId::Invalid) + { + RMLUI_ASSERT(ids.align_x != PropertyId::Invalid && ids.align_y != PropertyId::Invalid); + const Property& fit_property = *properties.GetProperty(ids.fit); + tile.fit_mode = (DecoratorTiled::TileFitMode)fit_property.value.Get(); + + const Property* align_properties[2] = {properties.GetProperty(ids.align_x), properties.GetProperty(ids.align_y)}; + + for (int dimension = 0; dimension < 2; dimension++) + { + using Style::LengthPercentage; + + LengthPercentage& align = tile.align[dimension]; + const Property& property = *align_properties[dimension]; + if (property.unit == Unit::KEYWORD) + { + enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; + switch (property.Get()) + { + case TOP_LEFT: align = LengthPercentage(LengthPercentage::Percentage, 0.0f); break; + case CENTER: align = LengthPercentage(LengthPercentage::Percentage, 50.0f); break; + case BOTTOM_RIGHT: align = LengthPercentage(LengthPercentage::Percentage, 100.0f); break; + } + } + else if (property.unit == Unit::PERCENT) + { + align = LengthPercentage(LengthPercentage::Percentage, property.Get()); + } + else if (property.unit == Unit::PX) + { + align = LengthPercentage(LengthPercentage::Length, property.Get()); + } + else + { + Log::Message(Log::LT_WARNING, "Decorator alignment value is '%s' which uses an unsupported unit (use px, %%, or keyword)", + property.ToString().c_str()); + } + } + } + + if (ids.orientation != PropertyId::Invalid) + { + const Property& orientation_property = *properties.GetProperty(ids.orientation); + tile.orientation = (DecoratorTiled::TileOrientation)orientation_property.value.Get(); + } + } + + return true; +} + } // namespace Rml diff --git a/Source/Core/DecoratorTiled.h b/Source/Core/DecoratorTiled.h index 92e65360e..7bd308b3f 100644 --- a/Source/Core/DecoratorTiled.h +++ b/Source/Core/DecoratorTiled.h @@ -126,5 +126,31 @@ class DecoratorTiled : public Decorator { void ScaleTileDimensions(Vector2f& tile_dimensions, float axis_value, Axis axis) const; }; +class DecoratorTiledInstancer : public DecoratorInstancer { +public: + DecoratorTiledInstancer(size_t num_tiles); + +protected: + /// Adds the property declarations for a tile. + /// @param[in] name The name of the tile property. + /// @param[in] register_fit_modes If true, the tile will have the fit modes registered. + void RegisterTileProperty(const String& name, bool register_fit_modes = false); + + /// Retrieves all the properties for a tile from the property dictionary. + /// @param[out] tile The tile structure for storing the tile properties. + /// @param[out] textures Holds the textures declared for the tile. + /// @param[in] properties The user-defined list of parameters for the decorator. + /// @param[in] instancer_interface An interface for querying the active style sheet. + bool GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) const; + +private: + struct TilePropertyIds { + PropertyId src, fit, align_x, align_y, orientation; + }; + + Vector tile_property_ids; +}; + } // namespace Rml #endif diff --git a/Source/Core/DecoratorTiledBox.cpp b/Source/Core/DecoratorTiledBox.cpp index ffbc602f4..c4b2878bd 100644 --- a/Source/Core/DecoratorTiledBox.cpp +++ b/Source/Core/DecoratorTiledBox.cpp @@ -258,4 +258,43 @@ void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle elem data->geometry[i].Render(translation); } +DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9) +{ + RegisterTileProperty("top-left-image"); + RegisterTileProperty("top-right-image"); + RegisterTileProperty("bottom-left-image"); + RegisterTileProperty("bottom-right-image"); + + RegisterTileProperty("left-image"); + RegisterTileProperty("right-image"); + RegisterTileProperty("top-image"); + RegisterTileProperty("bottom-image"); + + RegisterTileProperty("center-image"); + + RegisterShorthand("decorator", + "top-left-image, top-image, top-right-image, left-image, center-image, right-image, bottom-left-image, bottom-image, bottom-right-image", + ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer() {} + +SharedPtr DecoratorTiledBoxInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 9; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} + } // namespace Rml diff --git a/Source/Core/DecoratorTiledBox.h b/Source/Core/DecoratorTiledBox.h index 1a7f2443c..13176c1cd 100644 --- a/Source/Core/DecoratorTiledBox.h +++ b/Source/Core/DecoratorTiledBox.h @@ -72,5 +72,15 @@ class DecoratorTiledBox : public DecoratorTiled { Tile tiles[9]; }; +class DecoratorTiledBoxInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledBoxInstancer(); + ~DecoratorTiledBoxInstancer(); + + /// Instances a box decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + } // namespace Rml #endif diff --git a/Source/Core/DecoratorTiledBoxInstancer.cpp b/Source/Core/DecoratorTiledBoxInstancer.cpp deleted file mode 100644 index 92a88dfdb..000000000 --- a/Source/Core/DecoratorTiledBoxInstancer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 "DecoratorTiledBoxInstancer.h" -#include "DecoratorTiledBox.h" - -namespace Rml { - -DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9) -{ - RegisterTileProperty("top-left-image"); - RegisterTileProperty("top-right-image"); - RegisterTileProperty("bottom-left-image"); - RegisterTileProperty("bottom-right-image"); - - RegisterTileProperty("left-image"); - RegisterTileProperty("right-image"); - RegisterTileProperty("top-image"); - RegisterTileProperty("bottom-image"); - - RegisterTileProperty("center-image"); - - RegisterShorthand("decorator", - "top-left-image, top-image, top-right-image, left-image, center-image, right-image, bottom-left-image, bottom-image, bottom-right-image", - ShorthandType::RecursiveCommaSeparated); -} - -DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer() {} - -SharedPtr DecoratorTiledBoxInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) -{ - constexpr size_t num_tiles = 9; - - DecoratorTiled::Tile tiles[num_tiles]; - Texture textures[num_tiles]; - - if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) - return nullptr; - - auto decorator = MakeShared(); - if (!decorator->Initialise(tiles, textures)) - return nullptr; - - return decorator; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorTiledBoxInstancer.h b/Source/Core/DecoratorTiledBoxInstancer.h deleted file mode 100644 index d377660b4..000000000 --- a/Source/Core/DecoratorTiledBoxInstancer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_CORE_DECORATORTILEDBOXINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDBOXINSTANCER_H - -#include "DecoratorTiledInstancer.h" - -namespace Rml { - -/** - @author Peter Curry - */ - -class DecoratorTiledBoxInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledBoxInstancer(); - ~DecoratorTiledBoxInstancer(); - - /// Instances a box decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; -}; - -} // namespace Rml -#endif diff --git a/Source/Core/DecoratorTiledHorizontal.cpp b/Source/Core/DecoratorTiledHorizontal.cpp index 0bdd713cf..625935ca6 100644 --- a/Source/Core/DecoratorTiledHorizontal.cpp +++ b/Source/Core/DecoratorTiledHorizontal.cpp @@ -143,4 +143,31 @@ void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHand data->geometry[i].Render(translation); } +DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer() : DecoratorTiledInstancer(3) +{ + RegisterTileProperty("left-image"); + RegisterTileProperty("right-image"); + RegisterTileProperty("center-image"); + RegisterShorthand("decorator", "left-image, center-image, right-image", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledHorizontalInstancer::~DecoratorTiledHorizontalInstancer() {} + +SharedPtr DecoratorTiledHorizontalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 3; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} } // namespace Rml diff --git a/Source/Core/DecoratorTiledHorizontal.h b/Source/Core/DecoratorTiledHorizontal.h index b236b1863..6bb881877 100644 --- a/Source/Core/DecoratorTiledHorizontal.h +++ b/Source/Core/DecoratorTiledHorizontal.h @@ -62,5 +62,15 @@ class DecoratorTiledHorizontal : public DecoratorTiled { Tile tiles[3]; }; +class DecoratorTiledHorizontalInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledHorizontalInstancer(); + ~DecoratorTiledHorizontalInstancer(); + + /// Instances a horizontal decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + } // namespace Rml #endif diff --git a/Source/Core/DecoratorTiledHorizontalInstancer.cpp b/Source/Core/DecoratorTiledHorizontalInstancer.cpp deleted file mode 100644 index 69db9fea5..000000000 --- a/Source/Core/DecoratorTiledHorizontalInstancer.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 "DecoratorTiledHorizontalInstancer.h" -#include "DecoratorTiledHorizontal.h" - -namespace Rml { - -DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer() : DecoratorTiledInstancer(3) -{ - RegisterTileProperty("left-image"); - RegisterTileProperty("right-image"); - RegisterTileProperty("center-image"); - RegisterShorthand("decorator", "left-image, center-image, right-image", ShorthandType::RecursiveCommaSeparated); -} - -DecoratorTiledHorizontalInstancer::~DecoratorTiledHorizontalInstancer() {} - -SharedPtr DecoratorTiledHorizontalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) -{ - constexpr size_t num_tiles = 3; - - DecoratorTiled::Tile tiles[num_tiles]; - Texture textures[num_tiles]; - - if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) - return nullptr; - - auto decorator = MakeShared(); - if (!decorator->Initialise(tiles, textures)) - return nullptr; - - return decorator; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorTiledHorizontalInstancer.h b/Source/Core/DecoratorTiledHorizontalInstancer.h deleted file mode 100644 index 77c6549fe..000000000 --- a/Source/Core/DecoratorTiledHorizontalInstancer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_CORE_DECORATORTILEDHORIZONTALINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDHORIZONTALINSTANCER_H - -#include "DecoratorTiledInstancer.h" - -namespace Rml { - -/** - @author Peter Curry - */ - -class DecoratorTiledHorizontalInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledHorizontalInstancer(); - ~DecoratorTiledHorizontalInstancer(); - - /// Instances a horizontal decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; -}; - -} // namespace Rml -#endif diff --git a/Source/Core/DecoratorTiledImage.cpp b/Source/Core/DecoratorTiledImage.cpp index 74fe7cd2e..13ea980a3 100644 --- a/Source/Core/DecoratorTiledImage.cpp +++ b/Source/Core/DecoratorTiledImage.cpp @@ -72,4 +72,29 @@ void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle el data->Render(element->GetAbsoluteOffset(BoxArea::Padding).Round()); } +DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) +{ + RegisterTileProperty("image", true); + RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat); +} + +DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer() {} + +SharedPtr DecoratorTiledImageInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + DecoratorTiled::Tile tile; + Texture texture; + + if (!GetTileProperties(&tile, &texture, 1, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + + if (!decorator->Initialise(tile, texture)) + return nullptr; + + return decorator; +} + } // namespace Rml diff --git a/Source/Core/DecoratorTiledImage.h b/Source/Core/DecoratorTiledImage.h index b912fe770..47b178166 100644 --- a/Source/Core/DecoratorTiledImage.h +++ b/Source/Core/DecoratorTiledImage.h @@ -60,5 +60,15 @@ class DecoratorTiledImage : public DecoratorTiled { Tile tile; }; +class DecoratorTiledImageInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledImageInstancer(); + ~DecoratorTiledImageInstancer(); + + /// Instances an image decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + } // namespace Rml #endif diff --git a/Source/Core/DecoratorTiledImageInstancer.cpp b/Source/Core/DecoratorTiledImageInstancer.cpp deleted file mode 100644 index 7fd95cc88..000000000 --- a/Source/Core/DecoratorTiledImageInstancer.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 "DecoratorTiledImageInstancer.h" -#include "DecoratorTiledImage.h" - -namespace Rml { - -DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) -{ - RegisterTileProperty("image", true); - RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat); -} - -DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer() {} - -SharedPtr DecoratorTiledImageInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) -{ - DecoratorTiled::Tile tile; - Texture texture; - - if (!GetTileProperties(&tile, &texture, 1, properties, instancer_interface)) - return nullptr; - - auto decorator = MakeShared(); - - if (!decorator->Initialise(tile, texture)) - return nullptr; - - return decorator; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorTiledImageInstancer.h b/Source/Core/DecoratorTiledImageInstancer.h deleted file mode 100644 index 433c37d7b..000000000 --- a/Source/Core/DecoratorTiledImageInstancer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_CORE_DECORATORTILEDIMAGEINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDIMAGEINSTANCER_H - -#include "DecoratorTiledInstancer.h" - -namespace Rml { - -/** - @author Peter Curry - */ - -class DecoratorTiledImageInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledImageInstancer(); - ~DecoratorTiledImageInstancer(); - - /// Instances an image decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; -}; - -} // namespace Rml -#endif diff --git a/Source/Core/DecoratorTiledInstancer.cpp b/Source/Core/DecoratorTiledInstancer.cpp deleted file mode 100644 index 5108e52b9..000000000 --- a/Source/Core/DecoratorTiledInstancer.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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 "DecoratorTiledInstancer.h" -#include "../../Include/RmlUi/Core/PropertyDefinition.h" -#include "../../Include/RmlUi/Core/Spritesheet.h" - -namespace Rml { - -DecoratorTiledInstancer::DecoratorTiledInstancer(size_t num_tiles) -{ - tile_property_ids.reserve(num_tiles); -} - -void DecoratorTiledInstancer::RegisterTileProperty(const String& name, bool register_fit_modes) -{ - TilePropertyIds ids = {}; - - ids.src = RegisterProperty(CreateString(32, "%s-src", name.c_str()), "").AddParser("string").GetId(); - - String additional_modes; - - if (register_fit_modes) - { - String fit_name = CreateString(32, "%s-fit", name.c_str()); - ids.fit = RegisterProperty(fit_name, "fill").AddParser("keyword", "fill, contain, cover, scale-none, scale-down").GetId(); - - String align_x_name = CreateString(32, "%s-align-x", name.c_str()); - ids.align_x = RegisterProperty(align_x_name, "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); - - String align_y_name = CreateString(32, "%s-align-y", name.c_str()); - ids.align_y = RegisterProperty(align_y_name, "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); - - additional_modes += ", " + fit_name + ", " + align_x_name + ", " + align_y_name; - } - - ids.orientation = RegisterProperty(CreateString(32, "%s-orientation", name.c_str()), "none") - .AddParser("keyword", "none, flip-horizontal, flip-vertical, rotate-180") - .GetId(); - - RegisterShorthand(name, - CreateString(256, ("%s-src, %s-orientation" + additional_modes).c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str(), - name.c_str()), - ShorthandType::FallThrough); - - tile_property_ids.push_back(ids); -} - -bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, - const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) const -{ - RMLUI_ASSERT(num_tiles_and_textures == tile_property_ids.size()); - - String previous_texture_name; - Texture previous_texture; - - for (size_t i = 0; i < num_tiles_and_textures; i++) - { - const TilePropertyIds& ids = tile_property_ids[i]; - - const Property* src_property = properties.GetProperty(ids.src); - const String texture_name = src_property->Get(); - - // Skip the tile if it has no source name. - // Declaring the name 'auto' is the same as an empty string. This gives an easy way to skip certain - // tiles in a shorthand since we can't always declare an empty string. - if (texture_name.empty() || texture_name == "auto") - continue; - - // We are required to set default values before instancing the tile, thus, all properties should always be - // dereferencable. If the debugger captures a zero-dereference, check that all properties for every tile is set - // and default values are set just before instancing. - - DecoratorTiled::Tile& tile = tiles[i]; - Texture& texture = textures[i]; - - // A tile is always either a sprite or an image. - if (const Sprite* sprite = instancer_interface.GetSprite(texture_name)) - { - tile.position = sprite->rectangle.Position(); - tile.size = sprite->rectangle.Size(); - tile.display_scale = sprite->sprite_sheet->display_scale; - - texture = sprite->sprite_sheet->texture; - } - else - { - // No sprite found, so assume that the name is an image source. Since the common use case is to specify the - // same texture for all tiles, check the previous texture first before fetching from the global database. - if (texture_name == previous_texture_name) - { - texture = previous_texture; - } - else - { - texture = instancer_interface.GetTexture(texture_name); - - if (!texture) - return false; - - previous_texture_name = texture_name; - previous_texture = texture; - } - } - - if (ids.fit != PropertyId::Invalid) - { - RMLUI_ASSERT(ids.align_x != PropertyId::Invalid && ids.align_y != PropertyId::Invalid); - const Property& fit_property = *properties.GetProperty(ids.fit); - tile.fit_mode = (DecoratorTiled::TileFitMode)fit_property.value.Get(); - - const Property* align_properties[2] = {properties.GetProperty(ids.align_x), properties.GetProperty(ids.align_y)}; - - for (int dimension = 0; dimension < 2; dimension++) - { - using Style::LengthPercentage; - - LengthPercentage& align = tile.align[dimension]; - const Property& property = *align_properties[dimension]; - if (property.unit == Unit::KEYWORD) - { - enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; - switch (property.Get()) - { - case TOP_LEFT: align = LengthPercentage(LengthPercentage::Percentage, 0.0f); break; - case CENTER: align = LengthPercentage(LengthPercentage::Percentage, 50.0f); break; - case BOTTOM_RIGHT: align = LengthPercentage(LengthPercentage::Percentage, 100.0f); break; - } - } - else if (property.unit == Unit::PERCENT) - { - align = LengthPercentage(LengthPercentage::Percentage, property.Get()); - } - else if (property.unit == Unit::PX) - { - align = LengthPercentage(LengthPercentage::Length, property.Get()); - } - else - { - Log::Message(Log::LT_WARNING, "Decorator alignment value is '%s' which uses an unsupported unit (use px, %%, or keyword)", - property.ToString().c_str()); - } - } - } - - if (ids.orientation != PropertyId::Invalid) - { - const Property& orientation_property = *properties.GetProperty(ids.orientation); - tile.orientation = (DecoratorTiled::TileOrientation)orientation_property.value.Get(); - } - } - - return true; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorTiledInstancer.h b/Source/Core/DecoratorTiledInstancer.h deleted file mode 100644 index 83ef750c7..000000000 --- a/Source/Core/DecoratorTiledInstancer.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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_CORE_DECORATORTILEDINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDINSTANCER_H - -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" -#include "DecoratorTiled.h" - -namespace Rml { - -class StyleSheet; - -/** - @author Peter Curry - */ - -class DecoratorTiledInstancer : public DecoratorInstancer { -public: - DecoratorTiledInstancer(size_t num_tiles); - -protected: - /// Adds the property declarations for a tile. - /// @param[in] name The name of the tile property. - /// @param[in] register_fit_modes If true, the tile will have the fit modes registered. - void RegisterTileProperty(const String& name, bool register_fit_modes = false); - - /// Retrieves all the properties for a tile from the property dictionary. - /// @param[out] tile The tile structure for storing the tile properties. - /// @param[out] textures Holds the textures declared for the tile. - /// @param[in] properties The user-defined list of parameters for the decorator. - /// @param[in] instancer_interface An interface for querying the active style sheet. - bool GetTileProperties(DecoratorTiled::Tile* tiles, Texture* textures, size_t num_tiles_and_textures, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) const; - -private: - struct TilePropertyIds { - PropertyId src, fit, align_x, align_y, orientation; - }; - - Vector tile_property_ids; -}; - -} // namespace Rml -#endif diff --git a/Source/Core/DecoratorTiledVertical.cpp b/Source/Core/DecoratorTiledVertical.cpp index a37bbf0f5..5004ade68 100644 --- a/Source/Core/DecoratorTiledVertical.cpp +++ b/Source/Core/DecoratorTiledVertical.cpp @@ -145,4 +145,32 @@ void DecoratorTiledVertical::RenderElement(Element* element, DecoratorDataHandle data->geometry[i].Render(translation); } +DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer() : DecoratorTiledInstancer(3) +{ + RegisterTileProperty("top-image"); + RegisterTileProperty("bottom-image"); + RegisterTileProperty("center-image"); + RegisterShorthand("decorator", "top-image, center-image, bottom-image", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorTiledVerticalInstancer::~DecoratorTiledVerticalInstancer() {} + +SharedPtr DecoratorTiledVerticalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) +{ + constexpr size_t num_tiles = 3; + + DecoratorTiled::Tile tiles[num_tiles]; + Texture textures[num_tiles]; + + if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) + return nullptr; + + auto decorator = MakeShared(); + if (!decorator->Initialise(tiles, textures)) + return nullptr; + + return decorator; +} + } // namespace Rml diff --git a/Source/Core/DecoratorTiledVertical.h b/Source/Core/DecoratorTiledVertical.h index 9d2113c55..61bdffa11 100644 --- a/Source/Core/DecoratorTiledVertical.h +++ b/Source/Core/DecoratorTiledVertical.h @@ -62,5 +62,15 @@ class DecoratorTiledVertical : public DecoratorTiled { Tile tiles[3]; }; +class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer { +public: + DecoratorTiledVerticalInstancer(); + ~DecoratorTiledVerticalInstancer(); + + /// Instances a vertical decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; +}; + } // namespace Rml #endif diff --git a/Source/Core/DecoratorTiledVerticalInstancer.cpp b/Source/Core/DecoratorTiledVerticalInstancer.cpp deleted file mode 100644 index 27133ff50..000000000 --- a/Source/Core/DecoratorTiledVerticalInstancer.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 "DecoratorTiledVerticalInstancer.h" -#include "DecoratorTiledVertical.h" - -namespace Rml { - -DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer() : DecoratorTiledInstancer(3) -{ - RegisterTileProperty("top-image"); - RegisterTileProperty("bottom-image"); - RegisterTileProperty("center-image"); - RegisterShorthand("decorator", "top-image, center-image, bottom-image", ShorthandType::RecursiveCommaSeparated); -} - -DecoratorTiledVerticalInstancer::~DecoratorTiledVerticalInstancer() {} - -SharedPtr DecoratorTiledVerticalInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) -{ - constexpr size_t num_tiles = 3; - - DecoratorTiled::Tile tiles[num_tiles]; - Texture textures[num_tiles]; - - if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) - return nullptr; - - auto decorator = MakeShared(); - if (!decorator->Initialise(tiles, textures)) - return nullptr; - - return decorator; -} - -} // namespace Rml diff --git a/Source/Core/DecoratorTiledVerticalInstancer.h b/Source/Core/DecoratorTiledVerticalInstancer.h deleted file mode 100644 index 4c6d31f29..000000000 --- a/Source/Core/DecoratorTiledVerticalInstancer.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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_CORE_DECORATORTILEDVERTICALINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDVERTICALINSTANCER_H - -#include "DecoratorTiledInstancer.h" - -namespace Rml { - -/** - @author Peter Curry - */ - -class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledVerticalInstancer(); - ~DecoratorTiledVerticalInstancer(); - - /// Instances a vertical decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; -}; - -} // namespace Rml -#endif diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index 0b0225584..61ef1f40e 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -27,7 +27,7 @@ */ #include "ElementAnimation.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 76623c208..dce7fb45f 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -29,7 +29,6 @@ #include "ElementDecoration.h" #include "../../Include/RmlUi/Core/ComputedValues.h" #include "../../Include/RmlUi/Core/Decorator.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementDocument.h" #include "../../Include/RmlUi/Core/Profiling.h" diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index d12227249..973de094e 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -50,10 +50,10 @@ #include "DataViewDefault.h" #include "DecoratorGradient.h" #include "DecoratorNinePatch.h" -#include "DecoratorTiledBoxInstancer.h" -#include "DecoratorTiledHorizontalInstancer.h" -#include "DecoratorTiledImageInstancer.h" -#include "DecoratorTiledVerticalInstancer.h" +#include "DecoratorTiledBox.h" +#include "DecoratorTiledHorizontal.h" +#include "DecoratorTiledImage.h" +#include "DecoratorTiledVertical.h" #include "ElementHandle.h" #include "Elements/ElementImage.h" #include "Elements/ElementLabel.h" diff --git a/Source/Core/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 337ae9573..7777a5c59 100644 --- a/Source/Core/PropertyParserDecorator.cpp +++ b/Source/Core/PropertyParserDecorator.cpp @@ -27,7 +27,7 @@ */ #include "PropertyParserDecorator.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/Profiling.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index a8af7cca5..24a637862 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -27,7 +27,7 @@ */ #include "../../Include/RmlUi/Core/StyleSheet.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Profiling.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 6249382e9..3f6d04031 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -27,7 +27,7 @@ */ #include "StyleSheetParser.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/Log.h" #include "../../Include/RmlUi/Core/Profiling.h" diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 76b1a17a9..6f6996b0e 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -28,7 +28,7 @@ #include "../../Include/RmlUi/Core/TypeConverter.h" #include "../../Include/RmlUi/Core/Animation.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" From b6f70fffcd26f879dae2e69b7c8d2c88f2b004df Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 29 Apr 2023 18:42:03 +0200 Subject: [PATCH 03/80] Refactor: Style sheet type names --- Include/RmlUi/Core/StyleSheet.h | 6 +++--- Include/RmlUi/Core/StyleSheetTypes.h | 21 +++------------------ Source/Core/ElementAnimation.cpp | 26 +++++++++++++++++--------- Source/Core/StyleSheet.cpp | 2 +- Source/Core/StyleSheetParser.cpp | 8 ++++---- Source/Core/StyleSheetParser.h | 2 +- 6 files changed, 29 insertions(+), 36 deletions(-) diff --git a/Include/RmlUi/Core/StyleSheet.h b/Include/RmlUi/Core/StyleSheet.h index 6525bd016..cf98ad361 100644 --- a/Include/RmlUi/Core/StyleSheet.h +++ b/Include/RmlUi/Core/StyleSheet.h @@ -67,8 +67,8 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable { /// Builds the node index for a combined style sheet. void BuildNodeIndex(); - /// Returns the DecoratorSpecification of the given name, or null if it does not exist. - const DecoratorSpecification* GetDecoratorSpecification(const String& name) const; + /// Returns the named @decorator, or null if it does not exist. + const NamedDecorator* GetNamedDecorator(const String& name) const; /// Returns the Keyframes of the given name, or null if it does not exist. /// @lifetime The returned pointer becomes invalidated whenever the style sheet is re-generated. Do not store this pointer or references to @@ -103,7 +103,7 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable { KeyframesMap keyframes; // Name of every @decorator mapped to their specification - DecoratorSpecificationMap decorator_map; + NamedDecoratorMap decorator_map; // Name of every @spritesheet and underlying sprites mapped to their values SpritesheetList spritesheet_list; diff --git a/Include/RmlUi/Core/StyleSheetTypes.h b/Include/RmlUi/Core/StyleSheetTypes.h index 3145c7c67..c7647e001 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -52,33 +52,18 @@ struct Keyframes { }; using KeyframesMap = UnorderedMap; -struct DecoratorSpecification { - String decorator_type; +struct NamedDecorator { + String type; PropertyDictionary properties; SharedPtr decorator; }; -using DecoratorSpecificationMap = UnorderedMap; +using NamedDecoratorMap = UnorderedMap; struct DecoratorDeclaration { String type; DecoratorInstancer* instancer; PropertyDictionary properties; }; - -struct DecoratorDeclarationView { - DecoratorDeclarationView(const DecoratorDeclaration& declaration) : - type(declaration.type), instancer(declaration.instancer), properties(declaration.properties) - {} - DecoratorDeclarationView(const DecoratorSpecification* specification) : - type(specification->decorator_type), instancer(Factory::GetDecoratorInstancer(specification->decorator_type)), - properties(specification->properties) - {} - - const String& type; - DecoratorInstancer* instancer; - const PropertyDictionary& properties; -}; - struct DecoratorDeclarationList { Vector list; String value; diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index 61ef1f40e..c0a1aafa3 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -169,31 +169,39 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl return Property{TransformPtr(std::move(t)), Unit::TRANSFORM}; } + struct DecoratorDeclarationView { + DecoratorDeclarationView(const DecoratorDeclaration& declaration) : + type(declaration.type), instancer(declaration.instancer), properties(declaration.properties) + {} + DecoratorDeclarationView(const NamedDecorator* specification) : + type(specification->type), instancer(Factory::GetDecoratorInstancer(specification->type)), properties(specification->properties) + {} + const String& type; + DecoratorInstancer* instancer; + const PropertyDictionary& properties; + }; + if (p0.unit == Unit::DECORATOR && p1.unit == Unit::DECORATOR) { auto DiscreteInterpolation = [&]() { return alpha < 0.5f ? p0 : p1; }; - // We construct DecoratorDeclarationView from declaration if it has instancer, otherwise we look for DecoratorSpecification and return - // DecoratorDeclarationView from it + // If we have an instancer we pass that directly to the declaration view, otherwise look for a named @decorator. auto GetDecoratorDeclarationView = [&](const DecoratorDeclaration& declaration) -> DecoratorDeclarationView { if (declaration.instancer) return DecoratorDeclarationView{declaration}; const StyleSheet* style_sheet = element.GetStyleSheet(); if (!style_sheet) - { - Log::Message(Log::LT_WARNING, "Failed to get element stylesheet for '%s' decorator rule.", declaration.type.c_str()); return DecoratorDeclarationView{declaration}; - } - const DecoratorSpecification* specification = style_sheet->GetDecoratorSpecification(declaration.type); - if (!specification) + const NamedDecorator* named_decorator = style_sheet->GetNamedDecorator(declaration.type); + if (!named_decorator) { - Log::Message(Log::LT_WARNING, "Could not find DecoratorSpecification for '%s' decorator rule.", declaration.type.c_str()); + Log::Message(Log::LT_WARNING, "Could not find a named @decorator '%s'.", declaration.type.c_str()); return DecoratorDeclarationView{declaration}; } - return DecoratorDeclarationView{specification}; + return DecoratorDeclarationView{named_decorator}; }; auto& ptr0 = p0.value.GetReference(); diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index 24a637862..ee612d00f 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -97,7 +97,7 @@ void StyleSheet::BuildNodeIndex() root->BuildIndex(styled_node_index); } -const DecoratorSpecification* StyleSheet::GetDecoratorSpecification(const String& name) const +const NamedDecorator* StyleSheet::GetNamedDecorator(const String& name) const { auto it = decorator_map.find(name); if (it != decorator_map.end()) diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 3f6d04031..e6e11ba03 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -325,7 +325,7 @@ bool StyleSheetParser::ParseKeyframeBlock(KeyframesMap& keyframes_map, const Str return true; } -bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpecificationMap& decorator_map, const StyleSheet& style_sheet, +bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& decorator_map, const StyleSheet& style_sheet, const SharedPtr& source) { StringList name_type; @@ -360,9 +360,9 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci if (it != decorator_map.end()) { // Yes, try to retrieve the instancer from the parent type, and add its property values. - decorator_instancer = Factory::GetDecoratorInstancer(it->second.decorator_type); + decorator_instancer = Factory::GetDecoratorInstancer(it->second.type); properties = it->second.properties; - decorator_type = it->second.decorator_type; + decorator_type = it->second.type; } // If we still don't have an instancer, we cannot continue. @@ -393,7 +393,7 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci return false; } - decorator_map.emplace(name, DecoratorSpecification{std::move(decorator_type), std::move(properties), std::move(decorator)}); + decorator_map.emplace(name, NamedDecorator{std::move(decorator_type), std::move(properties), std::move(decorator)}); return true; } diff --git a/Source/Core/StyleSheetParser.h b/Source/Core/StyleSheetParser.h index 6665d5a3d..0bd75caf7 100644 --- a/Source/Core/StyleSheetParser.h +++ b/Source/Core/StyleSheetParser.h @@ -105,7 +105,7 @@ class StyleSheetParser { bool ParseKeyframeBlock(KeyframesMap& keyframes_map, const String& identifier, const String& rules, const PropertyDictionary& properties); // Attempts to parse a @decorator block - bool ParseDecoratorBlock(const String& at_name, DecoratorSpecificationMap& decorator_map, const StyleSheet& style_sheet, + bool ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& decorator_map, const StyleSheet& style_sheet, const SharedPtr& source); // Attempts to parse the properties of a @media query From 607a8ce60feefdd4705a188b9eb96b90a8079d15 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 17:40:02 +0200 Subject: [PATCH 04/80] Rectangle: Add getters for all corners and math rounding procedure --- Include/RmlUi/Core/Math.h | 6 ++++++ Include/RmlUi/Core/Rectangle.h | 2 ++ Source/Core/Math.cpp | 7 +++++++ 3 files changed, 15 insertions(+) diff --git a/Include/RmlUi/Core/Math.h b/Include/RmlUi/Core/Math.h index 528169334..6c847f7f0 100644 --- a/Include/RmlUi/Core/Math.h +++ b/Include/RmlUi/Core/Math.h @@ -42,6 +42,9 @@ template class Vector2; using Vector2f = Vector2; using Vector2i = Vector2; +template +class Rectangle; +using Rectanglef = Rectangle; namespace Math { @@ -208,6 +211,9 @@ namespace Math { /// @param[inout] position The position, which will be rounded down. /// @param[inout] size The size, which is rounded such that the right and bottom edges are rounded up. RMLUICORE_API void ExpandToPixelGrid(Vector2f& position, Vector2f& size); + /// Round the rectangle to the pixel grid such that it fully covers the original rectangle. + /// @param[inout] position The rectangle to round. + RMLUICORE_API void ExpandToPixelGrid(Rectanglef& rectangle); /// Converts a number to the nearest power of two, rounding up if necessary. /// @param[in] value The value to convert to a power-of-two. diff --git a/Include/RmlUi/Core/Rectangle.h b/Include/RmlUi/Core/Rectangle.h index d6c0a1862..e85370c97 100644 --- a/Include/RmlUi/Core/Rectangle.h +++ b/Include/RmlUi/Core/Rectangle.h @@ -54,7 +54,9 @@ class Rectangle { Vector2Type Size() const { return p1 - p0; } Vector2Type TopLeft() const { return p0; } + Vector2Type TopRight() const { return {p1.x, p0.y}; } Vector2Type BottomRight() const { return p1; } + Vector2Type BottomLeft() const { return {p0.x, p1.y}; } Vector2Type Center() const { return (p0 + p1) / Type(2); } diff --git a/Source/Core/Math.cpp b/Source/Core/Math.cpp index a8306c1ed..225bda480 100644 --- a/Source/Core/Math.cpp +++ b/Source/Core/Math.cpp @@ -188,6 +188,13 @@ namespace Math { size = Vector2f(std::ceil(bottom_right.x), std::ceil(bottom_right.y)) - position; } + RMLUICORE_API void ExpandToPixelGrid(Rectanglef& rectangle) + { + const Vector2f top_left = {std::floor(rectangle.Left()), std::floor(rectangle.Top())}; + const Vector2f bottom_right = {std::ceil(rectangle.Right()), std::ceil(rectangle.Bottom())}; + rectangle = Rectanglef::FromCorners(top_left, bottom_right); + } + RMLUICORE_API int ToPowerOfTwo(int number) { // Check if the number is already a power of two. From 679c615818fab02b61f35ff07782c367021d446d Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 17:41:05 +0200 Subject: [PATCH 05/80] Add number_percent parser and combined unit --- Include/RmlUi/Core/Unit.h | 1 + Source/Core/StyleSheetSpecification.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Include/RmlUi/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 542f2f73b..7ba94b877 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -81,6 +81,7 @@ enum class Unit { LENGTH = PX | DP | VW | VH | EM | REM | PPI_UNIT, LENGTH_PERCENT = LENGTH | PERCENT, + NUMBER_PERCENT = NUMBER | PERCENT, NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT, DP_SCALABLE_LENGTH = DP | PPI_UNIT, ANGLE = DEG | RAD, diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index 53618aea3..57269b460 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -45,10 +45,11 @@ namespace Rml { static StyleSheetSpecification* instance = nullptr; -struct DefaultStyleSheetParsers { +struct DefaultStyleSheetParsers : NonCopyMoveable { PropertyParserNumber number = PropertyParserNumber(Unit::NUMBER); PropertyParserNumber length = PropertyParserNumber(Unit::LENGTH, Unit::PX); PropertyParserNumber length_percent = PropertyParserNumber(Unit::LENGTH_PERCENT, Unit::PX); + PropertyParserNumber number_percent = PropertyParserNumber(Unit::NUMBER_PERCENT); PropertyParserNumber number_length_percent = PropertyParserNumber(Unit::NUMBER_LENGTH_PERCENT, Unit::PX); PropertyParserNumber angle = PropertyParserNumber(Unit::ANGLE, Unit::RAD); PropertyParserKeyword keyword = PropertyParserKeyword(); @@ -242,6 +243,7 @@ void StyleSheetSpecification::RegisterDefaultParsers() RegisterParser("number", &default_parsers->number); RegisterParser("length", &default_parsers->length); RegisterParser("length_percent", &default_parsers->length_percent); + RegisterParser("number_percent", &default_parsers->number_percent); RegisterParser("number_length_percent", &default_parsers->number_length_percent); RegisterParser("angle", &default_parsers->angle); RegisterParser("keyword", &default_parsers->keyword); From 76b855652edb294e6549b14774d3eecd6c5e790a Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 17:55:23 +0200 Subject: [PATCH 06/80] ElementUtilities: Add procedure for determining element bounding box with projection --- Include/RmlUi/Core/ElementUtilities.h | 7 +++ Source/Core/ElementUtilities.cpp | 62 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/Include/RmlUi/Core/ElementUtilities.h b/Include/RmlUi/Core/ElementUtilities.h index 7e5f13096..7749600b4 100644 --- a/Include/RmlUi/Core/ElementUtilities.h +++ b/Include/RmlUi/Core/ElementUtilities.h @@ -101,6 +101,13 @@ class RMLUICORE_API ElementUtilities { /// @param[in] context The context to read the clip region from static void ApplyActiveClipRegion(Context* context); + /// Returns a rectangle covering the element's area in window coordinate space. + /// @param[in] out_rectangle The resulting rectangle covering the projected element's box. + /// @param[in] element The element to find the bounding box of. + /// @param[in] area The box area to consider. + /// @return True on success, otherwise false. + static bool GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea area); + /// Formats the contents of an element. This does not need to be called for ordinary elements, but can be useful /// for non-DOM elements of custom elements. /// @param[in] element The element to lay out. diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 839520189..616d59ba1 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -279,6 +279,68 @@ void ElementUtilities::ApplyActiveClipRegion(Context* context) } } +bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea box_area) +{ + RMLUI_ASSERT(element); + + if (box_area == BoxArea::Auto) + box_area = BoxArea::Border; + + // Element bounds in non-transformed space. + const Rectanglef bounds = Rectanglef::FromPositionSize(element->GetAbsoluteOffset(box_area), element->GetBox().GetSize(box_area)); + + const TransformState* transform_state = element->GetTransformState(); + const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr); + + // Early exit in the common case of no transform. + if (!transform) + { + out_rectangle = bounds; + return true; + } + + Context* context = element->GetContext(); + if (!context) + return false; + + constexpr int num_corners = 4; + Vector2f corners[num_corners] = { + bounds.TopLeft(), + bounds.TopRight(), + bounds.BottomRight(), + bounds.BottomLeft(), + }; + + // Transform and project corners to window coordinates. + constexpr float z_clip = 10'000.f; + const Vector2f window_size = Vector2f(context->GetDimensions()); + const Matrix4f project = Matrix4f::ProjectOrtho(0.f, window_size.x, 0.f, window_size.y, -z_clip, z_clip); + const Matrix4f project_transform = project * (*transform); + bool any_vertex_depth_clipped = false; + + for (int i = 0; i < num_corners; i++) + { + const Vector4f pos_clip_space = project_transform * Vector4f(corners[i].x, corners[i].y, 0, 1); + const Vector2f pos_ndc = Vector2f(pos_clip_space.x, pos_clip_space.y) / pos_clip_space.w; + const Vector2f pos_viewport = 0.5f * window_size * (pos_ndc + Vector2f(1)); + corners[i] = pos_viewport; + any_vertex_depth_clipped |= !(-pos_clip_space.w <= pos_clip_space.z && pos_clip_space.z <= pos_clip_space.w); + } + + // If any part of the box area is outside the depth clip planes we give up finding the bounding box. In this situation a renderer would normally + // clip the underlying triangles against the clip planes. We could in principle do the same, but the added complexity does not seem worthwhile for + // our use cases. + if (any_vertex_depth_clipped) + return false; + + // Find the rectangle covering the projected corners. + out_rectangle = Rectanglef::FromPosition(corners[0]); + for (int i = 1; i < num_corners; i++) + out_rectangle.Join(corners[i]); + + return true; +} + void ElementUtilities::FormatElement(Element* element, Vector2f containing_block) { LayoutEngine::FormatElement(element, containing_block); From 1be3e86be1ebcd77605f945d044a78ef444adc57 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 17:57:02 +0200 Subject: [PATCH 07/80] Add render interface commands for layers and filters --- Include/RmlUi/Core/RenderInterface.h | 29 ++++++++++++++++++++++++++++ Include/RmlUi/Core/Types.h | 3 +++ Source/Core/RenderInterface.cpp | 11 +++++++++++ 3 files changed, 43 insertions(+) diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 6c8cac955..a320cf7c9 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -37,6 +37,19 @@ namespace Rml { +enum class LayerFill { + None, // No operation necessary, does not care about the layer color. + Clear, // Clear the layer to transparent black. + Clone, // Copy the color data from the previous layer. +}; +enum class BlendMode { + Blend, // Normal alpha blending. + Replace, // Replace the destination colors from the source. + Discard, // Leave the destination colors unaltered. +}; + +using FilterHandleList = Vector; + /** The abstract base class for application-specific rendering implementation. Your application must provide a concrete implementation of this class and install it through Rml::SetRenderInterface() in order for anything to be rendered. @@ -110,6 +123,22 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// is submitted. Then it expects the renderer to use an identity matrix or otherwise omit the multiplication with the transform. /// @param[in] transform The new transform to apply, or nullptr if no transform applies to the current element. virtual void SetTransform(const Matrix4f* transform); + + /// Called by RmlUi when it wants to push a new layer onto the render stack. + /// @param[in] layer_fill Specifies how the color data of the new layer should be filled. + virtual void PushLayer(LayerFill layer_fill); + /// Called by RmlUi when it wants to pop the render layer stack, after applying filters to the top layer and blending it into the layer below. + /// @param[in] blend_mode The mode used to blend the top layer into the one below. + /// @param[in] filters A list of compiled filters which should be applied to the top layer before blending. + virtual void PopLayer(BlendMode blend_mode, const FilterHandleList& filters); + + /// Called by RmlUi when it wants to compile a new filter. + /// @param[in] name The name of the filter. + /// @param[in] parameters The list of name-value parameters specified for the filter. + virtual CompiledFilterHandle CompileFilter(const String& name, const Dictionary& parameters); + /// Called by RmlUi when it no longer needs a previously compiled filter. + /// @param[in] filter The handle to a previously compiled filter. + virtual void ReleaseCompiledFilter(CompiledFilterHandle filter); }; } // namespace Rml diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index 3aac5c4cf..4a8af8ddc 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -89,6 +89,7 @@ struct Animation; struct Transition; struct TransitionList; struct DecoratorDeclarationList; +struct FilterDeclarationList; enum class EventId : uint16_t; enum class PropertyId : uint8_t; enum class MediaQueryId : uint8_t; @@ -98,6 +99,7 @@ enum class FamilyId : int; using FileHandle = uintptr_t; using TextureHandle = uintptr_t; using CompiledGeometryHandle = uintptr_t; +using CompiledFilterHandle = uintptr_t; using DecoratorDataHandle = uintptr_t; using FontFaceHandle = uintptr_t; using FontEffectsHandle = uintptr_t; @@ -129,6 +131,7 @@ struct FontEffects { // Additional smart pointers using TransformPtr = SharedPtr; using DecoratorsPtr = SharedPtr; +using FiltersPtr = SharedPtr; using FontEffectsPtr = SharedPtr; // Data binding types diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index f70cbb629..68a2c0656 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -66,4 +66,15 @@ void RenderInterface::ReleaseTexture(TextureHandle /*texture*/) {} void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} +void RenderInterface::PushLayer(LayerFill /*layer_fill*/) {} + +void RenderInterface::PopLayer(BlendMode /*blend_mode*/, const FilterHandleList& /*filters*/) {} + +CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, const Dictionary& /*parameters*/) +{ + return CompiledFilterHandle{}; +} + +void RenderInterface::ReleaseCompiledFilter(CompiledFilterHandle /*filter*/) {} + } // namespace Rml From a5e3c7fc8d98c77cbbb6f4e834b86da65549a432 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 29 Apr 2023 17:48:14 +0200 Subject: [PATCH 08/80] Add Filter class, to be used with the new 'filter' and 'backdrop-filter' properties Filters are implemented partially like decorators. --- CMake/FileList.cmake | 6 + Include/RmlUi/Core.h | 2 + Include/RmlUi/Core/ComputedValues.h | 11 +- Include/RmlUi/Core/Decorator.h | 35 +--- Include/RmlUi/Core/EffectSpecification.h | 69 +++++++ Include/RmlUi/Core/Factory.h | 13 ++ Include/RmlUi/Core/Filter.h | 85 +++++++++ Include/RmlUi/Core/ID.h | 2 + Include/RmlUi/Core/StyleSheetTypes.h | 9 + Include/RmlUi/Core/TypeConverter.h | 10 + Include/RmlUi/Core/Unit.h | 7 +- Include/RmlUi/Core/Variant.h | 5 +- Include/RmlUi/Core/Variant.inl | 1 + Source/Core/Decorator.cpp | 18 +- Source/Core/EffectSpecification.cpp | 53 ++++++ Source/Core/Element.cpp | 9 +- Source/Core/ElementDecoration.cpp | 228 ++++++++++++++++++----- Source/Core/ElementDecoration.h | 25 ++- Source/Core/ElementStyle.cpp | 9 +- Source/Core/Factory.cpp | 19 ++ Source/Core/Filter.cpp | 43 +++++ Source/Core/PropertyParserFilter.cpp | 119 ++++++++++++ Source/Core/PropertyParserFilter.h | 50 +++++ Source/Core/StyleSheetSpecification.cpp | 7 + Source/Core/TypeConverter.cpp | 39 ++-- Source/Core/Variant.cpp | 34 ++++ Tests/Source/UnitTests/Filter.cpp | 177 ++++++++++++++++++ 27 files changed, 961 insertions(+), 124 deletions(-) create mode 100644 Include/RmlUi/Core/EffectSpecification.h create mode 100644 Include/RmlUi/Core/Filter.h create mode 100644 Source/Core/EffectSpecification.cpp create mode 100644 Source/Core/Filter.cpp create mode 100644 Source/Core/PropertyParserFilter.cpp create mode 100644 Source/Core/PropertyParserFilter.h create mode 100644 Tests/Source/UnitTests/Filter.cpp diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index a4e863e97..a22c969ed 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -80,6 +80,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.h + ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h @@ -138,6 +139,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Decorator.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Dictionary.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EffectSpecification.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Element.inl ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ElementDocument.h @@ -158,6 +160,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/EventListenerInstancer.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Factory.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FileInterface.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Filter.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffect.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEffectInstancer.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontEngineInterface.h @@ -251,6 +254,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledImage.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVertical.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/EffectSpecification.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.cpp @@ -298,6 +302,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Factory.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FileInterface.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp @@ -340,6 +345,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp diff --git a/Include/RmlUi/Core.h b/Include/RmlUi/Core.h index 4a3c64f71..3e34cbead 100644 --- a/Include/RmlUi/Core.h +++ b/Include/RmlUi/Core.h @@ -41,6 +41,7 @@ #include "Core/DataTypes.h" #include "Core/DataVariable.h" #include "Core/Decorator.h" +#include "Core/EffectSpecification.h" #include "Core/Element.h" #include "Core/ElementDocument.h" #include "Core/ElementInstancer.h" @@ -53,6 +54,7 @@ #include "Core/EventListenerInstancer.h" #include "Core/Factory.h" #include "Core/FileInterface.h" +#include "Core/Filter.h" #include "Core/FontEffect.h" #include "Core/FontEffectInstancer.h" #include "Core/FontEngineInterface.h" diff --git a/Include/RmlUi/Core/ComputedValues.h b/Include/RmlUi/Core/ComputedValues.h index 54c83cc7d..a27bee029 100644 --- a/Include/RmlUi/Core/ComputedValues.h +++ b/Include/RmlUi/Core/ComputedValues.h @@ -157,7 +157,9 @@ namespace Style { flex_basis_type(LengthPercentageAuto::Auto), row_gap_type(LengthPercentage::Length), column_gap_type(LengthPercentage::Length), - vertical_align_type(VerticalAlign::Baseline), drag(Drag::None), tab_index(TabIndex::None), overscroll_behavior(OverscrollBehavior::Auto) + vertical_align_type(VerticalAlign::Baseline), drag(Drag::None), tab_index(TabIndex::None), overscroll_behavior(OverscrollBehavior::Auto), + + has_filter(false), has_backdrop_filter(false) {} LengthPercentage::Type min_width_type : 1, max_width_type : 1; @@ -175,6 +177,9 @@ namespace Style { TabIndex tab_index : 1; OverscrollBehavior overscroll_behavior : 1; + bool has_filter : 1; + bool has_backdrop_filter : 1; + Clip clip; float min_width = 0, max_width = FLT_MAX; @@ -296,6 +301,8 @@ namespace Style { LengthPercentage column_gap() const { return LengthPercentage(rare.column_gap_type, rare.column_gap); } OverscrollBehavior overscroll_behavior() const { return rare.overscroll_behavior; } float scrollbar_margin() const { return rare.scrollbar_margin; } + bool has_filter() const { return rare.has_filter; } + bool has_backdrop_filter() const { return rare.has_backdrop_filter; } // -- Assignment -- // Common @@ -376,6 +383,8 @@ namespace Style { void image_color (Colourb value) { rare.image_color = value; } void overscroll_behavior (OverscrollBehavior value){ rare.overscroll_behavior = value; } void scrollbar_margin (float value) { rare.scrollbar_margin = value; } + void has_filter (bool value) { rare.has_filter = value; } + void has_backdrop_filter (bool value) { rare.has_backdrop_filter = value; } // clang-format on diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index 9e976dcf1..8a52b5252 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -29,6 +29,7 @@ #ifndef RMLUI_CORE_DECORATOR_H #define RMLUI_CORE_DECORATOR_H +#include "EffectSpecification.h" #include "Header.h" #include "PropertyDictionary.h" #include "PropertySpecification.h" @@ -37,22 +38,18 @@ namespace Rml { -class DecoratorInstancer; class Element; class PropertyDictionary; - struct Sprite; struct Texture; class StyleSheet; class DecoratorInstancerInterface; -class PropertyDefinition; /** The abstract base class for any visual object that can be attached to any element. @author Peter Curry */ - class RMLUICORE_API Decorator { public: Decorator(); @@ -94,15 +91,9 @@ class RMLUICORE_API Decorator { }; /** - An element instancer provides a method for allocating and deallocating decorators. - - It is important at the same instancer that allocated a decorator releases it. This ensures there are no issues with - memory from different DLLs getting mixed up. - - @author Peter Curry + A decorator instancer, which can be inherited from to instance new decorators when encountered in the style sheet. */ - -class RMLUICORE_API DecoratorInstancer { +class RMLUICORE_API DecoratorInstancer : public EffectSpecification { public: DecoratorInstancer(); virtual ~DecoratorInstancer(); @@ -114,26 +105,6 @@ class RMLUICORE_API DecoratorInstancer { /// @return A shared_ptr to the decorator if it was instanced successfully. virtual SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) = 0; - - /// Returns the property specification associated with the instancer. - const PropertySpecification& GetPropertySpecification() const; - -protected: - /// Registers a property for the decorator. - /// @param[in] property_name The name of the new property (how it is specified through RCSS). - /// @param[in] default_value The default value to be used. - /// @return The new property definition, ready to have parsers attached. - PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); - /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators. - /// @param[in] shorthand_name The name to register the new shorthand property under. - /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is - /// the order in which the values will be processed. - /// @param[in] type The type of shorthand to declare. - /// @param True if all the property names exist, false otherwise. - ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); - -private: - PropertySpecification properties; }; class RMLUICORE_API DecoratorInstancerInterface { diff --git a/Include/RmlUi/Core/EffectSpecification.h b/Include/RmlUi/Core/EffectSpecification.h new file mode 100644 index 000000000..fde117e66 --- /dev/null +++ b/Include/RmlUi/Core/EffectSpecification.h @@ -0,0 +1,69 @@ +/* + * 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_CORE_EFFECTSPECIFICATION_H +#define RMLUI_CORE_EFFECTSPECIFICATION_H + +#include "Header.h" +#include "PropertySpecification.h" +#include "Types.h" + +namespace Rml { + +class PropertyDefinition; + +class RMLUICORE_API EffectSpecification { +public: + EffectSpecification(); + + /// Returns the property specification associated with the instancer. + const PropertySpecification& GetPropertySpecification() const; + +protected: + ~EffectSpecification(); + + /// Registers a property for the decorator. + /// @param[in] property_name The name of the new property (how it is specified through RCSS). + /// @param[in] default_value The default value to be used. + /// @return The new property definition, ready to have parsers attached. + PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); + + /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators. + /// @param[in] shorthand_name The name to register the new shorthand property under. + /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is + /// the order in which the values will be processed. + /// @param[in] type The type of shorthand to declare. + /// @param True if all the property names exist, false otherwise. + ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); + +private: + PropertySpecification properties; +}; + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/Factory.h b/Include/RmlUi/Core/Factory.h index 7e39e8c57..7f6dfa92c 100644 --- a/Include/RmlUi/Core/Factory.h +++ b/Include/RmlUi/Core/Factory.h @@ -49,6 +49,8 @@ class EventListener; class EventListenerInstancer; class FontEffect; class FontEffectInstancer; +class Filter; +class FilterInstancer; class StyleSheetContainer; class PropertyDictionary; class PropertySpecification; @@ -126,6 +128,17 @@ class RMLUICORE_API Factory { /// @return The decorator instancer it it exists, nullptr otherwise. static DecoratorInstancer* GetDecoratorInstancer(const String& name); + /// Registers a non-owning pointer to an instancer that will be used to instance filters. + /// @param[in] name The name of the filter the instancer will be called for. + /// @param[in] instancer The instancer to call when the filter name is encountered. + /// @lifetime The instancer must be kept alive until after the call to Rml::Shutdown. + /// @return The added instancer if the registration was successful, nullptr otherwise. + static void RegisterFilterInstancer(const String& name, FilterInstancer* instancer); + /// Retrieves a filter instancer registered with the factory. + /// @param[in] name The name of the desired filter type. + /// @return The filter instancer it it exists, nullptr otherwise. + static FilterInstancer* GetFilterInstancer(const String& name); + /// Registers a non-owning pointer to an instancer that will be used to instance font effects. /// @param[in] name The name of the font effect the instancer will be called for. /// @param[in] instancer The instancer to call when the font effect name is encountered. diff --git a/Include/RmlUi/Core/Filter.h b/Include/RmlUi/Core/Filter.h new file mode 100644 index 000000000..d4e18b5b4 --- /dev/null +++ b/Include/RmlUi/Core/Filter.h @@ -0,0 +1,85 @@ +/* + * 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_CORE_FILTER_H +#define RMLUI_CORE_FILTER_H + +#include "EffectSpecification.h" +#include "Header.h" +#include "Types.h" + +namespace Rml { + +class Element; +class PropertyDictionary; + +/** + The abstract base class for visual filters that are applied when rendering the element. + */ +class RMLUICORE_API Filter { +public: + Filter(); + virtual ~Filter(); + + /// Called on a decorator to generate any required per-element data for a newly decorated element. + /// @param[in] element The newly decorated element. + /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. + virtual CompiledFilterHandle CompileFilter(Element* element) const = 0; + + /// Called to release element data generated by this decorator. + /// @param[in] element_data The element data handle to release. + virtual void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const = 0; + + /// Allows extending the area being affected by this filter beyond the border box of the element. + /// @param[in] element The element the filter is being rendered on. + /// @param[in,out] overflow The ink overflow rectangle determining the clipping region to be applied when filtering the current element. + /// @note Modifying the ink overflow rectangle affects rendering of all filter decorators active on the current element. + /// @note Only affects the 'filter' property, not 'backdrop-filter'. + virtual void ExtendInkOverflow(Element* element, Rectanglef& overflow) const; + + /// Value specifying an invalid or non-existent filter handle. + static constexpr CompiledFilterHandle INVALID_COMPILEDFILTERHANDLE = 0; +}; + +/** + A filter instancer, which can be inherited from to instance new filters when encountered in the style sheet. + */ +class RMLUICORE_API FilterInstancer : public EffectSpecification { +public: + FilterInstancer(); + virtual ~FilterInstancer(); + + /// Instances a filter given the name and attributes from the RCSS file. + /// @param[in] name The type of filter desired. For example, "filter: simple(...)" is declared as type "simple". + /// @param[in] properties All RCSS properties associated with the filter. + /// @return A shared_ptr to the filter if it was instanced successfully. + virtual SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) = 0; +}; + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/ID.h b/Include/RmlUi/Core/ID.h index 4af9d8aa8..05f8c52ba 100644 --- a/Include/RmlUi/Core/ID.h +++ b/Include/RmlUi/Core/ID.h @@ -156,6 +156,8 @@ enum class PropertyId : uint8_t { Decorator, FontEffect, + Filter, + BackdropFilter, FillImage, diff --git a/Include/RmlUi/Core/StyleSheetTypes.h b/Include/RmlUi/Core/StyleSheetTypes.h index c7647e001..f5bc85120 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -68,6 +68,15 @@ struct DecoratorDeclarationList { Vector list; String value; }; +struct FilterDeclaration { + String type; + FilterInstancer* instancer; + PropertyDictionary properties; +}; +struct FilterDeclarationList { + Vector list; + String value; +}; struct MediaBlock { MediaBlock() {} diff --git a/Include/RmlUi/Core/TypeConverter.h b/Include/RmlUi/Core/TypeConverter.h index 19c874421..869ed7d4d 100644 --- a/Include/RmlUi/Core/TypeConverter.h +++ b/Include/RmlUi/Core/TypeConverter.h @@ -123,6 +123,16 @@ class TypeConverter { public: RMLUICORE_API static bool Convert(const DecoratorsPtr& src, String& dest); }; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const FiltersPtr& src, FiltersPtr& dest); +}; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const FiltersPtr& src, String& dest); +}; template <> class TypeConverter { diff --git a/Include/RmlUi/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 7ba94b877..9c4d4cef5 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -75,9 +75,10 @@ enum class Unit { TRANSITION = 1 << 21, // transition; fetch as ANIMATION = 1 << 22, // animation; fetch as DECORATOR = 1 << 23, // decorator; fetch as - FONTEFFECT = 1 << 24, // font-effect; fetch as - COLORSTOPLIST = 1 << 25, // color stop list; fetch as - SHADOWLIST = 1 << 26, // shadow list; fetch as + FILTER = 1 << 24, // decorator; fetch as + FONTEFFECT = 1 << 25, // font-effect; fetch as + COLORSTOPLIST = 1 << 26, // color stop list; fetch as + SHADOWLIST = 1 << 27, // shadow list; fetch as LENGTH = PX | DP | VW | VH | EM | REM | PPI_UNIT, LENGTH_PERCENT = LENGTH | PERCENT, diff --git a/Include/RmlUi/Core/Variant.h b/Include/RmlUi/Core/Variant.h index b10761b89..b25ef63dd 100644 --- a/Include/RmlUi/Core/Variant.h +++ b/Include/RmlUi/Core/Variant.h @@ -70,7 +70,8 @@ class RMLUICORE_API Variant { TRANSITIONLIST = 'T', ANIMATIONLIST = 'A', DECORATORSPTR = 'D', - FONTEFFECTSPTR = 'F', + FILTERSPTR = 'F', + FONTEFFECTSPTR = 'E', VOIDPTR = '*', }; @@ -153,6 +154,8 @@ class RMLUICORE_API Variant { void Set(AnimationList&& value); void Set(const DecoratorsPtr& value); void Set(DecoratorsPtr&& value); + void Set(const FiltersPtr& value); + void Set(FiltersPtr&& value); void Set(const FontEffectsPtr& value); void Set(FontEffectsPtr&& value); diff --git a/Include/RmlUi/Core/Variant.inl b/Include/RmlUi/Core/Variant.inl index b80b7ec82..7e9abe4eb 100644 --- a/Include/RmlUi/Core/Variant.inl +++ b/Include/RmlUi/Core/Variant.inl @@ -73,6 +73,7 @@ bool Variant::GetInto(T& value) const case TRANSITIONLIST: return TypeConverter::Convert(*reinterpret_cast(data), value); case ANIMATIONLIST: return TypeConverter::Convert(*reinterpret_cast(data), value); case DECORATORSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); + case FILTERSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); case FONTEFFECTSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); case NONE: break; } diff --git a/Source/Core/Decorator.cpp b/Source/Core/Decorator.cpp index 7a3e6526a..1dd8ea7d4 100644 --- a/Source/Core/Decorator.cpp +++ b/Source/Core/Decorator.cpp @@ -30,7 +30,6 @@ #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/StyleSheet.h" #include "../../Include/RmlUi/Core/Texture.h" -#include "TextureDatabase.h" #include namespace Rml { @@ -77,25 +76,10 @@ const Texture* Decorator::GetTexture(int index) const return &(additional_textures[index]); } -DecoratorInstancer::DecoratorInstancer() : properties(10, 10) {} +DecoratorInstancer::DecoratorInstancer() {} DecoratorInstancer::~DecoratorInstancer() {} -const PropertySpecification& DecoratorInstancer::GetPropertySpecification() const -{ - return properties; -} - -PropertyDefinition& DecoratorInstancer::RegisterProperty(const String& property_name, const String& default_value) -{ - return properties.RegisterProperty(property_name, default_value, false, false); -} - -ShorthandId DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) -{ - return properties.RegisterShorthand(shorthand_name, property_names, type); -} - const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const { return style_sheet.GetSprite(name); diff --git a/Source/Core/EffectSpecification.cpp b/Source/Core/EffectSpecification.cpp new file mode 100644 index 000000000..785d7613e --- /dev/null +++ b/Source/Core/EffectSpecification.cpp @@ -0,0 +1,53 @@ +/* + * 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 "../../Include/RmlUi/Core/EffectSpecification.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" + +namespace Rml { + +EffectSpecification::EffectSpecification() : properties(10, 10) {} + +EffectSpecification::~EffectSpecification() {} + +const PropertySpecification& EffectSpecification::GetPropertySpecification() const +{ + return properties; +} + +PropertyDefinition& EffectSpecification::RegisterProperty(const String& property_name, const String& default_value) +{ + return properties.RegisterProperty(property_name, default_value, false, false); +} + +ShorthandId EffectSpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +{ + return properties.RegisterShorthand(shorthand_name, property_names, type); +} + +} // namespace Rml diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index aa6e8b474..1ee21d563 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -233,11 +233,13 @@ void Element::Render() // Apply our transform ElementUtilities::ApplyTransform(*this); + meta->decoration.RenderDecorators(RenderStage::Enter); + // Set up the clipping region for this element. if (ElementUtilities::SetClippingRegion(this)) { meta->background_border.Render(this); - meta->decoration.RenderDecorators(); + meta->decoration.RenderDecorators(RenderStage::Decoration); { RMLUI_ZoneScopedNC("OnRender", 0x228B22); @@ -249,6 +251,8 @@ void Element::Render() // Render all elements in our local stacking context. for (Element* element : stacking_context) element->Render(); + + meta->decoration.RenderDecorators(RenderStage::Exit); } ElementPtr Element::Clone() const @@ -1765,6 +1769,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) changed_properties.Contains(PropertyId::BorderBottomRightRadius) || // changed_properties.Contains(PropertyId::BorderBottomLeftRadius) // ); + const bool filter_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter)); // Dirty the background if it's changed. if (border_radius_changed || // @@ -1791,7 +1796,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) } // Dirty the decoration if it's changed. - if (border_radius_changed || changed_properties.Contains(PropertyId::Decorator)) + if (border_radius_changed || filter_changed || changed_properties.Contains(PropertyId::Decorator)) { meta->decoration.DirtyDecorators(); } diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index dce7fb45f..ef5482a9a 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -28,10 +28,14 @@ #include "ElementDecoration.h" #include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" #include "../../Include/RmlUi/Core/StyleSheet.h" namespace Rml { @@ -45,61 +49,89 @@ ElementDecoration::~ElementDecoration() void ElementDecoration::InstanceDecorators() { - if (decorators_dirty) - { - decorators_dirty = false; - decorators_data_dirty = true; - ReloadDecorators(); - } -} + if (!decorators_dirty) + return; + + decorators_dirty = false; + decorators_data_dirty = true; -bool ElementDecoration::ReloadDecorators() -{ RMLUI_ZoneScopedC(0xB22222); ReleaseDecorators(); - if (!element->GetComputedValues().has_decorator()) - return true; + const ComputedValues& computed = element->GetComputedValues(); - const Property* property = element->GetLocalProperty(PropertyId::Decorator); - if (!property || property->unit != Unit::DECORATOR) - return false; + if (computed.has_decorator()) + { + const Property* property = element->GetLocalProperty(PropertyId::Decorator); + if (!property || property->unit != Unit::DECORATOR) + return; - DecoratorsPtr decorators_ptr = property->Get(); - if (!decorators_ptr) - return false; + DecoratorsPtr decorators_ptr = property->Get(); + if (!decorators_ptr) + return; - const StyleSheet* style_sheet = element->GetStyleSheet(); - if (!style_sheet) - return false; + const StyleSheet* style_sheet = element->GetStyleSheet(); + if (!style_sheet) + return; - PropertySource document_source("", 0, ""); - const PropertySource* source = property->source.get(); + PropertySource document_source("", 0, ""); + const PropertySource* source = property->source.get(); - if (!source) - { - if (ElementDocument* document = element->GetOwnerDocument()) + if (!source) { - document_source.path = document->GetSourceURL(); - source = &document_source; + if (ElementDocument* document = element->GetOwnerDocument()) + { + document_source.path = document->GetSourceURL(); + source = &document_source; + } } - } - const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); + const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); - for (const SharedPtr& decorator : decorator_list) - { - if (decorator) + for (const SharedPtr& decorator : decorator_list) { - DecoratorHandle decorator_handle; - decorator_handle.decorator_data = 0; - decorator_handle.decorator = decorator; + if (decorator) + { + DecoratorEntry decorator_handle; + decorator_handle.decorator_data = 0; + decorator_handle.decorator = decorator; - decorators.push_back(std::move(decorator_handle)); + decorators.push_back(std::move(decorator_handle)); + } } } - return true; + if (computed.has_filter() || computed.has_backdrop_filter()) + { + for (const auto id : {PropertyId::Filter, PropertyId::BackdropFilter}) + { + const Property* property = element->GetLocalProperty(id); + if (!property || property->unit != Unit::FILTER) + return; + + FiltersPtr filters_ptr = property->Get(); + if (!filters_ptr) + return; + + FilterEntryList& list = (id == PropertyId::Filter ? filters : backdrop_filters); + list.reserve(filters_ptr->list.size()); + + for (const FilterDeclaration& declaration : filters_ptr->list) + { + SharedPtr filter = declaration.instancer->InstanceFilter(declaration.type, declaration.properties); + if (filter) + { + list.push_back({std::move(filter), CompiledFilterHandle{}}); + } + else + { + const auto& source = property->source; + Log::Message(Log::LT_WARNING, "Filter '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), + filters_ptr->value.c_str(), source ? source->path.c_str() : "", source ? source->line_number : -1); + } + } + } + } } void ElementDecoration::ReloadDecoratorsData() @@ -108,38 +140,142 @@ void ElementDecoration::ReloadDecoratorsData() { decorators_data_dirty = false; - for (DecoratorHandle& decorator : decorators) + for (DecoratorEntry& decorator : decorators) { if (decorator.decorator_data) decorator.decorator->ReleaseElementData(decorator.decorator_data); decorator.decorator_data = decorator.decorator->GenerateElementData(element); } + + for (FilterEntryList* list : {&filters, &backdrop_filters}) + { + for (FilterEntry& filter : *list) + { + if (filter.handle) + filter.filter->ReleaseCompiledFilter(element, filter.handle); + + filter.handle = filter.filter->CompileFilter(element); + } + } } } void ElementDecoration::ReleaseDecorators() { - for (DecoratorHandle& decorator : decorators) + for (DecoratorEntry& decorator : decorators) { if (decorator.decorator_data) decorator.decorator->ReleaseElementData(decorator.decorator_data); } - decorators.clear(); + + for (FilterEntryList* list : {&filters, &backdrop_filters}) + { + for (FilterEntry& filter : *list) + { + if (filter.handle) + filter.filter->ReleaseCompiledFilter(element, filter.handle); + } + list->clear(); + } } -void ElementDecoration::RenderDecorators() +void ElementDecoration::RenderDecorators(RenderStage render_stage) { InstanceDecorators(); ReloadDecoratorsData(); - // Render the decorators attached to this element in its current state. - // Render from back to front for correct render order. - for (int i = (int)decorators.size() - 1; i >= 0; i--) + if (!decorators.empty()) { - DecoratorHandle& decorator = decorators[i]; - decorator.decorator->RenderElement(element, decorator.decorator_data); + if (render_stage == RenderStage::Decoration) + { + // Render the decorators attached to this element in its current state. + // Render from back to front for correct render order. + for (int i = (int)decorators.size() - 1; i >= 0; i--) + { + DecoratorEntry& decorator = decorators[i]; + decorator.decorator->RenderElement(element, decorator.decorator_data); + } + } + } + + if (filters.empty() && backdrop_filters.empty()) + return; + + RenderInterface* render_interface = ::Rml::GetRenderInterface(); + Context* context = element->GetContext(); + if (!render_interface || !context) + return; + + auto ApplyClippingRegion = [this, render_interface, context](bool extend_ink_overflow) { + ElementUtilities::SetClippingRegion(element); // TODO: For backdrop-filter only: Force clipping to our border-box. + + // Find the region being affected by the active filters and apply it as a scissor. + Rectanglef filter_region = Rectanglef::MakeInvalid(); + ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Auto); + + if (extend_ink_overflow) + { + for (const auto& filter : filters) + filter.filter->ExtendInkOverflow(element, filter_region); + } + + Math::ExpandToPixelGrid(filter_region); + + Rectanglei scissor_region = Rectanglei::FromSize(context->GetDimensions()); + Vector2i clip_position, clip_size; + if (context->GetActiveClipRegion(clip_position, clip_size)) + scissor_region.Intersect(Rectanglei::FromPositionSize(clip_position, clip_size)); + + scissor_region.IntersectIfValid(Rectanglei(filter_region)); + + render_interface->EnableScissorRegion(true); + render_interface->SetScissorRegion(scissor_region.Left(), scissor_region.Top(), scissor_region.Width(), scissor_region.Height()); + }; + + if (!backdrop_filters.empty()) + { + if (render_stage == RenderStage::Enter) + { + ApplyClippingRegion(false); + + render_interface->PushLayer(LayerFill::Clone); + + FilterHandleList filter_handles; + for (auto& filter : backdrop_filters) + { + if (filter.handle) + filter_handles.push_back(filter.handle); + } + + render_interface->PopLayer(BlendMode::Replace, filter_handles); + + ElementUtilities::ApplyActiveClipRegion(context); + } + } + + if (!filters.empty()) + { + if (render_stage == RenderStage::Enter) + { + render_interface->PushLayer(LayerFill::Clear); + } + else if (render_stage == RenderStage::Exit) + { + ApplyClippingRegion(true); + + FilterHandleList filter_handles; + for (auto& filter : filters) + { + if (filter.handle) + filter_handles.push_back(filter.handle); + } + + render_interface->PopLayer(BlendMode::Blend, filter_handles); + + ElementUtilities::ApplyActiveClipRegion(context); + } } } diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementDecoration.h index 5b27f5689..a4d4cb00c 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementDecoration.h @@ -34,8 +34,11 @@ namespace Rml { class Decorator; +class Filter; class Element; +enum class RenderStage { Enter, Decoration, Exit }; + /** Manages an elements decorator state @@ -44,8 +47,6 @@ class Element; class ElementDecoration { public: - /// Constructor - /// @param element The element this decorator with acting on ElementDecoration(Element* element); ~ElementDecoration(); @@ -53,7 +54,7 @@ class ElementDecoration { void InstanceDecorators(); /// Renders all appropriate decorators. - void RenderDecorators(); + void RenderDecorators(RenderStage render_stage); /// Mark decorators as dirty and force them to reset themselves. void DirtyDecorators(); @@ -61,25 +62,29 @@ class ElementDecoration { void DirtyDecoratorsData(); private: - // Releases existing decorators and loads all decorators required by the element's definition. - bool ReloadDecorators(); // Releases existing element data of decorators, and regenerates it. void ReloadDecoratorsData(); // Releases all existing decorators and frees their data. void ReleaseDecorators(); - struct DecoratorHandle { + struct DecoratorEntry { SharedPtr decorator; DecoratorDataHandle decorator_data; }; + using DecoratorEntryList = Vector; - using DecoratorHandleList = Vector; + struct FilterEntry { + SharedPtr filter; + CompiledFilterHandle handle; + }; + using FilterEntryList = Vector; - // The element this decorator belongs to Element* element; - // The list of every decorator used by this element in every class. - DecoratorHandleList decorators; + // The list of decorators and filters used by this element from all style rules. + DecoratorEntryList decorators; + FilterEntryList filters; + FilterEntryList backdrop_filters; // If set, a full reload is necessary. bool decorators_dirty = false; diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index b8b2aa9c4..ede3ae36b 100644 --- a/Source/Core/ElementStyle.cpp +++ b/Source/Core/ElementStyle.cpp @@ -850,8 +850,15 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S values.has_decorator(p->unit == Unit::DECORATOR); break; case PropertyId::FontEffect: - values.has_font_effect((p->unit == Unit::FONTEFFECT)); + values.has_font_effect(p->unit == Unit::FONTEFFECT); break; + case PropertyId::Filter: + values.has_filter(p->unit == Unit::FILTER); + break; + case PropertyId::BackdropFilter: + values.has_backdrop_filter(p->unit == Unit::FILTER); + break; + case PropertyId::FlexBasis: values.flex_basis(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); break; diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 973de094e..d2130bfbe 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -87,6 +87,10 @@ static ElementInstancerMap element_instancers; using DecoratorInstancerMap = UnorderedMap; static DecoratorInstancerMap decorator_instancers; +// Filter instancers. +using FilterInstancerMap = UnorderedMap; +static FilterInstancerMap filter_instancers; + // Font effect instancers. using FontEffectInstancerMap = UnorderedMap; static FontEffectInstancerMap font_effect_instancers; @@ -483,6 +487,21 @@ DecoratorInstancer* Factory::GetDecoratorInstancer(const String& name) return iterator->second; } +void Factory::RegisterFilterInstancer(const String& name, FilterInstancer* instancer) +{ + RMLUI_ASSERT(instancer); + filter_instancers[StringUtilities::ToLower(name)] = instancer; +} + +FilterInstancer* Factory::GetFilterInstancer(const String& name) +{ + auto iterator = filter_instancers.find(name); + if (iterator == filter_instancers.end()) + return nullptr; + + return iterator->second; +} + void Factory::RegisterFontEffectInstancer(const String& name, FontEffectInstancer* instancer) { RMLUI_ASSERT(instancer); diff --git a/Source/Core/Filter.cpp b/Source/Core/Filter.cpp new file mode 100644 index 000000000..bd56b23a3 --- /dev/null +++ b/Source/Core/Filter.cpp @@ -0,0 +1,43 @@ +/* + * 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 "../../Include/RmlUi/Core/Filter.h" + +namespace Rml { + +Filter::Filter() {} + +Filter::~Filter() {} + +void Filter::ExtendInkOverflow(Element* /*element*/, Rectanglef& /*scissor_region*/) const {} + +FilterInstancer::FilterInstancer() {} + +FilterInstancer::~FilterInstancer() {} + +} // namespace Rml diff --git a/Source/Core/PropertyParserFilter.cpp b/Source/Core/PropertyParserFilter.cpp new file mode 100644 index 000000000..c27d0332f --- /dev/null +++ b/Source/Core/PropertyParserFilter.cpp @@ -0,0 +1,119 @@ +/* + * 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 "PropertyParserFilter.h" +#include "../../Include/RmlUi/Core/Factory.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/Profiling.h" +#include "../../Include/RmlUi/Core/PropertySpecification.h" +#include "../../Include/RmlUi/Core/StyleSheetTypes.h" + +namespace Rml { + +PropertyParserFilter::PropertyParserFilter() {} + +PropertyParserFilter::~PropertyParserFilter() {} + +bool PropertyParserFilter::ParseValue(Property& property, const String& filter_string_value, const ParameterMap& /*parameters*/) const +{ + // Filters are declared as + // filter: [ ...]; + // Where is specified with inline properties + // filter: brightness( ) ...; + + if (filter_string_value.empty() || filter_string_value == "none") + { + property.value = Variant(FiltersPtr()); + property.unit = Unit::FILTER; + return true; + } + + RMLUI_ZoneScoped; + + // Make sure we don't split inside the parenthesis since they may appear in filter shorthands. + StringList filter_string_list; + StringUtilities::ExpandString(filter_string_list, filter_string_value, ' ', '(', ')', true); + + FilterDeclarationList filters; + filters.value = filter_string_value; + filters.list.reserve(filter_string_list.size()); + + // Get or instance each filter in the comma-separated string list + for (const String& filter_string : filter_string_list) + { + const size_t shorthand_open = filter_string.find('('); + const size_t shorthand_close = filter_string.rfind(')'); + const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); + + if (invalid_parenthesis) + { + // We found no parenthesis, filters can only be declared anonymously for now. + Log::Message(Log::LT_WARNING, "Invalid syntax for font-effect '%s'.", filter_string.c_str()); + return false; + } + else + { + const String type = StringUtilities::StripWhitespace(filter_string.substr(0, shorthand_open)); + + // Check for valid filter type + FilterInstancer* instancer = Factory::GetFilterInstancer(type); + if (!instancer) + { + Log::Message(Log::LT_WARNING, "Filter type '%s' not found.", type.c_str()); + return false; + } + + const String shorthand = filter_string.substr(shorthand_open + 1, shorthand_close - shorthand_open - 1); + const PropertySpecification& specification = instancer->GetPropertySpecification(); + + // Parse the shorthand properties given by the 'filter' shorthand property + PropertyDictionary properties; + if (!specification.ParsePropertyDeclaration(properties, "filter", shorthand)) + { + // Empty values are allowed in filters, if the value is not empty we must have encountered a parser error. + if (!StringUtilities::StripWhitespace(shorthand).empty()) + return false; + } + + // Set unspecified values to their defaults + specification.SetPropertyDefaults(properties); + + filters.list.emplace_back(FilterDeclaration{type, instancer, std::move(properties)}); + } + } + + if (filters.list.empty()) + return false; + + property.value = Variant(MakeShared(std::move(filters))); + property.unit = Unit::FILTER; + + return true; +} + +} // namespace Rml diff --git a/Source/Core/PropertyParserFilter.h b/Source/Core/PropertyParserFilter.h new file mode 100644 index 000000000..f844bfbf6 --- /dev/null +++ b/Source/Core/PropertyParserFilter.h @@ -0,0 +1,50 @@ +/* + * 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_CORE_PROPERTYPARSERFILTER_H +#define RMLUI_CORE_PROPERTYPARSERFILTER_H + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + A property parser for the filter property. + */ + +class PropertyParserFilter : public PropertyParser { +public: + PropertyParserFilter(); + virtual ~PropertyParserFilter(); + + /// Called to parse a decorator declaration. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; +}; + +} // namespace Rml +#endif diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index 57269b460..a003ad9bb 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -33,6 +33,7 @@ #include "PropertyParserAnimation.h" #include "PropertyParserColour.h" #include "PropertyParserDecorator.h" +#include "PropertyParserFilter.h" #include "PropertyParserFontEffect.h" #include "PropertyParserKeyword.h" #include "PropertyParserNumber.h" @@ -58,6 +59,7 @@ struct DefaultStyleSheetParsers : NonCopyMoveable { PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER); PropertyParserColour color = PropertyParserColour(); PropertyParserDecorator decorator = PropertyParserDecorator(); + PropertyParserFilter filter = PropertyParserFilter(); PropertyParserFontEffect font_effect = PropertyParserFontEffect(); PropertyParserTransform transform = PropertyParserTransform(); PropertyParserRatio ratio = PropertyParserRatio(); @@ -252,6 +254,7 @@ void StyleSheetSpecification::RegisterDefaultParsers() RegisterParser("transition", &default_parsers->transition); RegisterParser("color", &default_parsers->color); RegisterParser("decorator", &default_parsers->decorator); + RegisterParser("filter", &default_parsers->filter); RegisterParser("font_effect", &default_parsers->font_effect); RegisterParser("transform", &default_parsers->transform); RegisterParser("ratio", &default_parsers->ratio); @@ -403,8 +406,12 @@ void StyleSheetSpecification::RegisterDefaultProperties() RegisterProperty(PropertyId::Transition, "transition", "none", false, false).AddParser("transition"); RegisterProperty(PropertyId::Animation, "animation", "none", false, false).AddParser("animation"); + // Decorators and effects RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("decorator"); RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("font_effect"); + + RegisterProperty(PropertyId::Filter, "filter", "", false, false).AddParser("filter", "filter"); + RegisterProperty(PropertyId::BackdropFilter, "backdrop-filter", "", false, false).AddParser("filter"); // Rare properties (not added to computed values) RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string"); diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 6f6996b0e..9296f0011 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -29,6 +29,7 @@ #include "../../Include/RmlUi/Core/TypeConverter.h" #include "../../Include/RmlUi/Core/Animation.h" #include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" @@ -161,13 +162,8 @@ bool TypeConverter::Convert(const AnimationList& src, Str return true; } -bool TypeConverter::Convert(const DecoratorsPtr& src, DecoratorsPtr& dest) -{ - dest = src; - return true; -} - -bool TypeConverter::Convert(const DecoratorsPtr& src, String& dest) +template +static bool ConvertEffectToString(const EffectsPtr& src, String& dest, const String& separator) { if (!src || src->list.empty()) dest = "none"; @@ -176,21 +172,42 @@ bool TypeConverter::Convert(const DecoratorsPtr& src, Str else { dest.clear(); - for (const DecoratorDeclaration& declaration : src->list) + for (const auto& declaration : src->list) { dest += declaration.type; if (auto instancer = declaration.instancer) { dest += '(' + instancer->GetPropertySpecification().PropertiesToString(declaration.properties, false, ' ') + ')'; } - dest += ", "; + if (&declaration != &src->list.back()) + dest += separator; } - if (dest.size() > 2) - dest.resize(dest.size() - 2); } return true; } +bool TypeConverter::Convert(const DecoratorsPtr& src, DecoratorsPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const DecoratorsPtr& src, String& dest) +{ + return ConvertEffectToString(src, dest, ", "); +} + +bool TypeConverter::Convert(const FiltersPtr& src, FiltersPtr& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const FiltersPtr& src, String& dest) +{ + return ConvertEffectToString(src, dest, " "); +} + bool TypeConverter::Convert(const FontEffectsPtr& src, FontEffectsPtr& dest) { dest = src; diff --git a/Source/Core/Variant.cpp b/Source/Core/Variant.cpp index f0156c1fc..cd02f2202 100644 --- a/Source/Core/Variant.cpp +++ b/Source/Core/Variant.cpp @@ -42,6 +42,7 @@ Variant::Variant() static_assert(sizeof(TransitionList) <= LOCAL_DATA_SIZE, "Local data too small for TransitionList"); static_assert(sizeof(AnimationList) <= LOCAL_DATA_SIZE, "Local data too small for AnimationList"); static_assert(sizeof(DecoratorsPtr) <= LOCAL_DATA_SIZE, "Local data too small for DecoratorsPtr"); + static_assert(sizeof(FiltersPtr) <= LOCAL_DATA_SIZE, "Local data too small for FiltersPtr"); static_assert(sizeof(FontEffectsPtr) <= LOCAL_DATA_SIZE, "Local data too small for FontEffectsPtr"); } @@ -99,6 +100,12 @@ void Variant::Clear() decorators->~DecoratorsPtr(); } break; + case FILTERSPTR: + { + FiltersPtr* decorators = (FiltersPtr*)data; + decorators->~FiltersPtr(); + } + break; case FONTEFFECTSPTR: { FontEffectsPtr* font_effects = (FontEffectsPtr*)data; @@ -121,6 +128,7 @@ void Variant::Set(const Variant& copy) case TRANSITIONLIST: Set(*reinterpret_cast(copy.data)); break; case ANIMATIONLIST: Set(*reinterpret_cast(copy.data)); break; case DECORATORSPTR: Set(*reinterpret_cast(copy.data)); break; + case FILTERSPTR: Set(*reinterpret_cast(copy.data)); break; case FONTEFFECTSPTR: Set(*reinterpret_cast(copy.data)); break; default: memcpy(data, copy.data, LOCAL_DATA_SIZE); @@ -139,6 +147,7 @@ void Variant::Set(Variant&& other) case TRANSITIONLIST: Set(std::move(*reinterpret_cast(other.data))); break; case ANIMATIONLIST: Set(std::move(*reinterpret_cast(other.data))); break; case DECORATORSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case FILTERSPTR: Set(std::move(*reinterpret_cast(other.data))); break; case FONTEFFECTSPTR: Set(std::move(*reinterpret_cast(other.data))); break; default: memcpy(data, other.data, LOCAL_DATA_SIZE); @@ -373,6 +382,30 @@ void Variant::Set(DecoratorsPtr&& value) new (data) DecoratorsPtr(std::move(value)); } } +void Variant::Set(const FiltersPtr& value) +{ + if (type == FILTERSPTR) + { + *(FiltersPtr*)data = value; + } + else + { + type = FILTERSPTR; + new (data) FiltersPtr(value); + } +} +void Variant::Set(FiltersPtr&& value) +{ + if (type == FILTERSPTR) + { + (*(FiltersPtr*)data) = std::move(value); + } + else + { + type = FILTERSPTR; + new (data) FiltersPtr(std::move(value)); + } +} void Variant::Set(const FontEffectsPtr& value) { if (type == FONTEFFECTSPTR) @@ -448,6 +481,7 @@ bool Variant::operator==(const Variant& other) const case TRANSITIONLIST: return DEFAULT_VARIANT_COMPARE(TransitionList); case ANIMATIONLIST: return DEFAULT_VARIANT_COMPARE(AnimationList); case DECORATORSPTR: return DEFAULT_VARIANT_COMPARE(DecoratorsPtr); + case FILTERSPTR: return DEFAULT_VARIANT_COMPARE(FiltersPtr); case FONTEFFECTSPTR: return DEFAULT_VARIANT_COMPARE(FontEffectsPtr); case NONE: return true; } diff --git a/Tests/Source/UnitTests/Filter.cpp b/Tests/Source/UnitTests/Filter.cpp new file mode 100644 index 000000000..182a1ab60 --- /dev/null +++ b/Tests/Source/UnitTests/Filter.cpp @@ -0,0 +1,177 @@ +/* + * 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 "../Common/TestsShell.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Rml; +class FilterTest; + +struct CompiledTestFilter { + String element_id; + const FilterTest* filter; +}; +static Vector compiled_test_filters; + +class FilterTest : public Filter { +public: + FilterTest(float value, Unit unit) : value(value), unit(unit) {} + ~FilterTest() { CHECK(num_compile == num_release); } + + CompiledFilterHandle CompileFilter(Element* element) const override + { + compiled_test_filters.push_back({element->GetId(), this}); + num_compile += 1; + return CompiledFilterHandle(num_compile); + } + + void ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle /*filter_handle*/) const override { num_release += 1; } + + float value = 0.f; + Unit unit = Unit::UNKNOWN; + mutable int num_compile = 0; + mutable int num_release = 0; +}; + +class FilterTestInstancer : public FilterInstancer { +public: + enum class ValueType { NumberPercent, Angle }; + + FilterTestInstancer() + { + id = RegisterProperty("value", "10cm").AddParser("length").GetId(); + RegisterShorthand("filter", "value", ShorthandType::FallThrough); + } + + SharedPtr InstanceFilter(const String& /*name*/, const PropertyDictionary& properties) override + { + const Property* p_value = properties.GetProperty(id); + if (!p_value) + return nullptr; + + CHECK(Any(p_value->unit & Unit::LENGTH)); + num_instances += 1; + + return MakeShared(p_value->Get(), p_value->unit); + } + + int num_instances = 0; + +private: + PropertyId id = {}; +}; + +static const String document_filter_rml = R"( + + + + + + +
+
+
+
+ + +)"; + +TEST_CASE("filter") +{ + compiled_test_filters.clear(); + Context* context = TestsShell::GetContext(); + REQUIRE(context); + + FilterTestInstancer instancer; + Rml::Factory::RegisterFilterInstancer("test", &instancer); + + ElementDocument* document = context->LoadDocumentFromMemory(document_filter_rml); + document->Show(); + + TestsShell::RenderLoop(); + + struct ExpectedCompiledFilter { + float value; + Unit unit; + }; + + // Map element ID to its expected filter value and unit. + UnorderedMap expected_compiled_filters = { + {"a", {10.f, Unit::CM}}, + {"b", {0.f, Unit::PX}}, + {"c", {1.f, Unit::DP}}, + {"d", {1.f, Unit::DP}}, + }; + for (const auto& compiled_filter : compiled_test_filters) + { + INFO("ID #", compiled_filter.element_id); + auto it = expected_compiled_filters.find(compiled_filter.element_id); + + const bool id_found = (it != expected_compiled_filters.end()); + CHECK(id_found); + if (!id_found) + continue; + + const ExpectedCompiledFilter& expected = it->second; + CHECK(compiled_filter.filter->value == expected.value); + CHECK(compiled_filter.filter->unit == expected.unit); + } + + // Check that filters are not compiled more than once for each element. + CHECK(compiled_test_filters.size() == expected_compiled_filters.size()); + + // Filters aren't cached like decorators are, so each element will instance a new decorator even if they refer to + // the same style rule. Thus, here producing 4 instead of 3 unique instances. + CHECK(instancer.num_instances == 4); + + document->Close(); + TestsShell::ShutdownShell(); +} From bafca42b8d2aa22aaea518141f4dab393a7f7ac3 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 18:08:31 +0200 Subject: [PATCH 09/80] Add basic filters --- CMake/FileList.cmake | 2 + Source/Core/Factory.cpp | 16 +++++++ Source/Core/FilterBasic.cpp | 85 +++++++++++++++++++++++++++++++++++++ Source/Core/FilterBasic.h | 66 ++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 Source/Core/FilterBasic.cpp create mode 100644 Source/Core/FilterBasic.h diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index a22c969ed..6a6577d52 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -47,6 +47,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/EventInstancerDefault.h ${PROJECT_SOURCE_DIR}/Source/Core/EventSpecification.h ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h + ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h @@ -303,6 +304,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FileInterface.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index d2130bfbe..18918f4ae 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -62,6 +62,7 @@ #include "Elements/XMLNodeHandlerTabSet.h" #include "Elements/XMLNodeHandlerTextArea.h" #include "EventInstancerDefault.h" +#include "FilterBasic.h" #include "FontEffectBlur.h" #include "FontEffectGlow.h" #include "FontEffectOutline.h" @@ -151,6 +152,11 @@ struct DefaultInstancers { DecoratorNinePatchInstancer decorator_ninepatch; DecoratorGradientInstancer decorator_gradient; + // Filters + FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; + FilterBasicInstancer filter_basic_d0 = {FilterBasicInstancer::ValueType::NumberPercent, "0"}; + FilterBasicInstancer filter_basic_d1 = {FilterBasicInstancer::ValueType::NumberPercent, "1"}; + // Font effects FontEffectBlurInstancer font_effect_blur; FontEffectGlowInstancer font_effect_glow; @@ -233,6 +239,16 @@ bool Factory::Initialise() RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch); RegisterDecoratorInstancer("gradient", &default_instancers->decorator_gradient); + // Filter instancers + RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate); + RegisterFilterInstancer("brightness", &default_instancers->filter_basic_d1); + RegisterFilterInstancer("contrast", &default_instancers->filter_basic_d1); + RegisterFilterInstancer("grayscale", &default_instancers->filter_basic_d0); + RegisterFilterInstancer("invert", &default_instancers->filter_basic_d0); + RegisterFilterInstancer("opacity", &default_instancers->filter_basic_d1); + RegisterFilterInstancer("saturate", &default_instancers->filter_basic_d1); + RegisterFilterInstancer("sepia", &default_instancers->filter_basic_d0); + // Font effect instancers RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur); RegisterFontEffectInstancer("glow", &default_instancers->font_effect_glow); diff --git a/Source/Core/FilterBasic.cpp b/Source/Core/FilterBasic.cpp new file mode 100644 index 000000000..15d243853 --- /dev/null +++ b/Source/Core/FilterBasic.cpp @@ -0,0 +1,85 @@ +/* + * 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 "FilterBasic.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +bool FilterBasic::Initialise(const String& in_name, float in_value) +{ + name = in_name; + value = in_value; + return true; +} + +CompiledFilterHandle FilterBasic::CompileFilter(Element* /*element*/) const +{ + CompiledFilterHandle handle = GetRenderInterface()->CompileFilter(name, Dictionary{{"value", Variant(value)}}); + return handle; +} + +void FilterBasic::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const +{ + GetRenderInterface()->ReleaseCompiledFilter(filter_handle); +} + +FilterBasicInstancer::FilterBasicInstancer(ValueType value_type, const char* default_value) +{ + switch (value_type) + { + case ValueType::NumberPercent: ids.value = RegisterProperty("value", default_value).AddParser("number_percent").GetId(); break; + case ValueType::Angle: ids.value = RegisterProperty("value", default_value).AddParser("angle").GetId(); break; + } + + RegisterShorthand("filter", "value", ShorthandType::FallThrough); +} + +SharedPtr FilterBasicInstancer::InstanceFilter(const String& name, const PropertyDictionary& properties_) +{ + const Property* p_value = properties_.GetProperty(ids.value); + if (!p_value) + return nullptr; + + float value = p_value->Get(); + if (p_value->unit == Unit::PERCENT) + value *= 0.01f; + else if (p_value->unit == Unit::DEG) + value = Rml::Math::DegreesToRadians(value); + + auto filter = MakeShared(); + if (filter->Initialise(name, value)) + return filter; + + return nullptr; +} + +} // namespace Rml diff --git a/Source/Core/FilterBasic.h b/Source/Core/FilterBasic.h new file mode 100644 index 000000000..822423991 --- /dev/null +++ b/Source/Core/FilterBasic.h @@ -0,0 +1,66 @@ +/* + * 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_CORE_FILTERBASIC_H +#define RMLUI_CORE_FILTERBASIC_H + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" + +namespace Rml { + +class FilterBasic : public Filter { +public: + bool Initialise(const String& name, float value); + + CompiledFilterHandle CompileFilter(Element* element) const override; + + void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + +private: + String name; + float value = 0.f; +}; + +class FilterBasicInstancer : public FilterInstancer { +public: + enum class ValueType { NumberPercent, Angle }; + + FilterBasicInstancer(ValueType value_type, const char* default_value); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId value; + }; + PropertyIds ids = {}; +}; + +} // namespace Rml +#endif From faae241d78c09d31142e0765352b9bf6e30d5f64 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 30 Apr 2023 22:12:36 +0200 Subject: [PATCH 10/80] Take texture out of the compiled geometry --- Backends/RmlUi_Renderer_GL3.cpp | 17 +++---- Backends/RmlUi_Renderer_GL3.h | 5 +- Backends/RmlUi_Renderer_VK.cpp | 71 +++++++++++++--------------- Backends/RmlUi_Renderer_VK.h | 8 +--- Include/RmlUi/Core/RenderInterface.h | 6 +-- Source/Core/Geometry.cpp | 7 ++- Source/Core/RenderInterface.cpp | 5 +- 7 files changed, 52 insertions(+), 67 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index cb239cd9e..2b7f10ef6 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -106,7 +106,6 @@ enum class VertexAttribute { Position, Color0, TexCoord0, Count }; static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; struct CompiledGeometryData { - Rml::TextureHandle texture; GLuint vao; GLuint vbo; GLuint ibo; @@ -449,17 +448,16 @@ void RenderInterface_GL3::Clear() void RenderInterface_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, const Rml::Vector2f& translation) { - Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices, texture); + Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices); if (geometry) { - RenderCompiledGeometry(geometry, translation); + RenderCompiledGeometry(geometry, translation, texture); ReleaseCompiledGeometry(geometry); } } -Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, - Rml::TextureHandle texture) +Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) { constexpr GLenum draw_usage = GL_STATIC_DRAW; @@ -496,7 +494,6 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve Gfx::CheckGLError("CompileGeometry"); Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData; - geometry->texture = texture; geometry->vao = vao; geometry->vbo = vbo; geometry->ibo = ibo; @@ -505,15 +502,15 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve return (Rml::CompiledGeometryHandle)geometry; } -void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation) +void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; - if (geometry->texture) + if (texture) { glUseProgram(shaders->program_texture.id); - if (geometry->texture != TextureEnableWithoutBinding) - glBindTexture(GL_TEXTURE_2D, (GLuint)geometry->texture); + if (texture != TextureEnableWithoutBinding) + glBindTexture(GL_TEXTURE_2D, (GLuint)texture); SubmitTransformUniform(ProgramId::Texture, shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]); glUniform2fv(shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x); } diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 537b61519..a7b49f23c 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -59,9 +59,8 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, const Rml::Vector2f& translation) override; - Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, - Rml::TextureHandle texture) override; - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; + void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; void EnableScissorRegion(bool enable) override; diff --git a/Backends/RmlUi_Renderer_VK.cpp b/Backends/RmlUi_Renderer_VK.cpp index 841a3c258..bbb26d2b5 100644 --- a/Backends/RmlUi_Renderer_VK.cpp +++ b/Backends/RmlUi_Renderer_VK.cpp @@ -104,22 +104,19 @@ RenderInterface_VK::~RenderInterface_VK() {} void RenderInterface_VK::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, const Rml::Vector2f& translation) { - Rml::CompiledGeometryHandle handle = CompileGeometry(vertices, num_vertices, indices, num_indices, texture); + Rml::CompiledGeometryHandle handle = CompileGeometry(vertices, num_vertices, indices, num_indices); if (handle) { - RenderCompiledGeometry(handle, translation); + RenderCompiledGeometry(handle, translation, texture); ReleaseCompiledGeometry(handle); } } -Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, - Rml::TextureHandle texture) +Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) { RMLUI_ZoneScopedN("Vulkan - CompileGeometry"); - texture_data_t* p_texture = reinterpret_cast(texture); - VkDescriptorSet p_current_descriptor_set = nullptr; p_current_descriptor_set = m_p_descriptor_set; @@ -129,29 +126,6 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver auto* p_geometry_handle = new geometry_handle_t{}; - VkDescriptorImageInfo info_descriptor_image = {}; - if (p_texture && p_texture->m_p_vk_descriptor_set == nullptr) - { - VkDescriptorSet p_texture_set = nullptr; - m_manager_descriptors.Alloc_Descriptor(m_p_device, &m_p_descriptor_set_layout_texture, &p_texture_set); - - info_descriptor_image.imageView = p_texture->m_p_vk_image_view; - info_descriptor_image.sampler = p_texture->m_p_vk_sampler; - info_descriptor_image.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - VkWriteDescriptorSet info_write = {}; - - info_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - info_write.dstSet = p_texture_set; - info_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - info_write.dstBinding = 2; - info_write.pImageInfo = &info_descriptor_image; - info_write.descriptorCount = 1; - - vkUpdateDescriptorSets(m_p_device, 1, &info_write, 0, nullptr); - p_texture->m_p_vk_descriptor_set = p_texture_set; - } - uint32_t* pCopyDataToBuffer = nullptr; const void* pData = reinterpret_cast(vertices); @@ -167,14 +141,12 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver memcpy(pCopyDataToBuffer, indices, sizeof(int) * num_indices); - p_geometry_handle->m_has_texture = static_cast(texture); p_geometry_handle->m_num_indices = num_indices; - p_geometry_handle->m_p_texture = p_texture; return Rml::CompiledGeometryHandle(p_geometry_handle); } -void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) +void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) { RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry"); @@ -183,6 +155,31 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom RMLUI_VK_ASSERTMSG(m_p_current_command_buffer, "must be valid otherwise you can't render now!!! (can't be)"); + texture_data_t* p_texture = reinterpret_cast(texture); + + VkDescriptorImageInfo info_descriptor_image = {}; + if (p_texture && p_texture->m_p_vk_descriptor_set == nullptr) + { + VkDescriptorSet p_texture_set = nullptr; + m_manager_descriptors.Alloc_Descriptor(m_p_device, &m_p_descriptor_set_layout_texture, &p_texture_set); + + info_descriptor_image.imageView = p_texture->m_p_vk_image_view; + info_descriptor_image.sampler = p_texture->m_p_vk_sampler; + info_descriptor_image.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet info_write = {}; + + info_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + info_write.dstSet = p_texture_set; + info_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + info_write.dstBinding = 2; + info_write.pImageInfo = &info_descriptor_image; + info_write.descriptorCount = 1; + + vkUpdateDescriptorSets(m_p_device, 1, &info_write, 0, nullptr); + p_texture->m_p_vk_descriptor_set = p_texture_set; + } + geometry_handle_t* p_casted_compiled_geometry = reinterpret_cast(geometry); m_user_data_for_vertex_shader.m_translate = translation; @@ -236,15 +233,15 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom VkDescriptorSet p_texture_descriptor_set = nullptr; - if (p_casted_compiled_geometry->m_p_texture) + if (p_texture) { - p_texture_descriptor_set = p_casted_compiled_geometry->m_p_texture->m_p_vk_descriptor_set; + p_texture_descriptor_set = p_texture->m_p_vk_descriptor_set; } VkDescriptorSet p_sets[] = {p_current_descriptor_set, p_texture_descriptor_set}; int real_size_of_sets = 2; - if (p_casted_compiled_geometry->m_p_texture == nullptr) + if (p_texture == nullptr) real_size_of_sets = 1; vkCmdBindDescriptorSets(m_p_current_command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_p_pipeline_layout, 0, real_size_of_sets, p_sets, 1, @@ -256,7 +253,7 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom } else { - if (p_casted_compiled_geometry->m_has_texture) + if (p_texture) { if (m_is_apply_to_regular_geometry_stencil) { @@ -3015,8 +3012,6 @@ void RenderInterface_VK::MemoryPool::Free_GeometryHandle(geometry_handle_t* p_va p_valid_geometry_handle->m_p_vertex_allocation = nullptr; p_valid_geometry_handle->m_p_shader_allocation = nullptr; p_valid_geometry_handle->m_p_index_allocation = nullptr; - p_valid_geometry_handle->m_p_texture = nullptr; - p_valid_geometry_handle->m_has_texture = false; p_valid_geometry_handle->m_num_indices = 0; } diff --git a/Backends/RmlUi_Renderer_VK.h b/Backends/RmlUi_Renderer_VK.h index b32e8d1f8..c4d377ef4 100644 --- a/Backends/RmlUi_Renderer_VK.h +++ b/Backends/RmlUi_Renderer_VK.h @@ -131,11 +131,10 @@ class RenderInterface_VK : public Rml::RenderInterface { const Rml::Vector2f& translation) override; /// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future. - Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, - Rml::TextureHandle texture) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; /// Called by RmlUi when it wants to render application-compiled geometry. - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override; + void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; /// Called by RmlUi when it wants to release application-compiled geometry. void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; @@ -175,11 +174,8 @@ class RenderInterface_VK : public Rml::RenderInterface { }; struct geometry_handle_t { - bool m_has_texture; int m_num_indices; - texture_data_t* m_p_texture; - VkDescriptorBufferInfo m_p_vertex; VkDescriptorBufferInfo m_p_index; VkDescriptorBufferInfo m_p_shader; diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index a320cf7c9..84b205d56 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -80,14 +80,14 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] num_vertices The number of vertices passed to the function. /// @param[in] indices The geometry's index data. /// @param[in] num_indices The number of indices passed to the function. This will always be a multiple of three. - /// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured. /// @return The application-specific compiled geometry. Compiled geometry will be stored and rendered using RenderCompiledGeometry() in future /// calls, and released with ReleaseCompiledGeometry() when it is no longer needed. - virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture); + virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices); /// Called by RmlUi when it wants to render application-compiled geometry. /// @param[in] geometry The application-specific compiled geometry to render. /// @param[in] translation The translation to apply to the geometry. - virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation); + /// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured. + virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation, TextureHandle texture); /// Called by RmlUi when it wants to release application-compiled geometry. /// @param[in] geometry The application-specific compiled geometry to release. virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry); diff --git a/Source/Core/Geometry.cpp b/Source/Core/Geometry.cpp index 4db1e7f91..1ca7f7c49 100644 --- a/Source/Core/Geometry.cpp +++ b/Source/Core/Geometry.cpp @@ -84,7 +84,7 @@ void Geometry::Render(Vector2f translation) if (compiled_geometry) { RMLUI_ZoneScopedN("RenderCompiled"); - render_interface->RenderCompiledGeometry(compiled_geometry, translation); + render_interface->RenderCompiledGeometry(compiled_geometry, translation, texture ? texture->GetHandle() : 0); } // Otherwise, if we actually have geometry, try to compile it if we haven't already done so, otherwise render it in // immediate mode. @@ -98,14 +98,13 @@ void Geometry::Render(Vector2f translation) if (!compile_attempted) { compile_attempted = true; - compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size(), - texture ? texture->GetHandle() : 0); + compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); // If we managed to compile the geometry, we can clear the local copy of vertices and indices and // immediately render the compiled version. if (compiled_geometry) { - render_interface->RenderCompiledGeometry(compiled_geometry, translation); + render_interface->RenderCompiledGeometry(compiled_geometry, translation, texture ? texture->GetHandle() : 0); return; } } diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 68a2c0656..95108b788 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -42,13 +42,12 @@ RenderInterface::~RenderInterface() "dereference when releasing the textures. Ensure that the render interface is destroyed *after* the call to Rml::Shutdown."); } -CompiledGeometryHandle RenderInterface::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/, - TextureHandle /*texture*/) +CompiledGeometryHandle RenderInterface::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/) { return 0; } -void RenderInterface::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/) {} +void RenderInterface::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/, TextureHandle /*texture*/) {} void RenderInterface::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} From f6230b2d2f88812eb16cf7f4cc801417c9791210 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 1 May 2023 01:23:57 +0200 Subject: [PATCH 11/80] Make elements with 'filter' and 'backdrop-filter' properties create a local stacking context --- Source/Core/Element.cpp | 64 ++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 1ee21d563..5b55093ca 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -1720,57 +1720,43 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) } } - // Update the z-index. - if (changed_properties.Contains(PropertyId::ZIndex)) - { - Style::ZIndex z_index_property = meta->computed_values.z_index(); + const bool border_radius_changed = ( // + changed_properties.Contains(PropertyId::BorderTopLeftRadius) || // + changed_properties.Contains(PropertyId::BorderTopRightRadius) || // + changed_properties.Contains(PropertyId::BorderBottomRightRadius) || // + changed_properties.Contains(PropertyId::BorderBottomLeftRadius) // + ); + const bool filter_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter)); - if (z_index_property.type == Style::ZIndex::Auto) - { - if (local_stacking_context && !local_stacking_context_forced) - { - // We're no longer acting as a stacking context. - local_stacking_context = false; + // Update the z-index and stacking context. + if (changed_properties.Contains(PropertyId::ZIndex) || filter_changed) + { + const Style::ZIndex z_index_property = meta->computed_values.z_index(); - stacking_context_dirty = false; - stacking_context.clear(); - } + const float new_z_index = (z_index_property.type == Style::ZIndex::Auto ? 0.f : z_index_property.value); + const bool enable_local_stacking_context = (z_index_property.type != Style::ZIndex::Auto || local_stacking_context_forced || + meta->computed_values.has_filter() || meta->computed_values.has_backdrop_filter()); - // If our old z-index was not zero, then we must dirty our stacking context so we'll be re-indexed. - if (z_index != 0) - { - z_index = 0; - DirtyStackingContext(); - } - } - else + if (z_index != new_z_index || local_stacking_context != enable_local_stacking_context) { - float new_z_index = z_index_property.value; + z_index = new_z_index; - if (new_z_index != z_index) + if (local_stacking_context != enable_local_stacking_context) { - z_index = new_z_index; + local_stacking_context = enable_local_stacking_context; - if (parent != nullptr) - parent->DirtyStackingContext(); + // If we are no longer acting as a local stacking context, then we clear the list and are all set. Otherwise, we need to rebuild our + // local stacking context. + stacking_context.clear(); + stacking_context_dirty = local_stacking_context; } - if (!local_stacking_context) - { - local_stacking_context = true; - stacking_context_dirty = true; - } + // When our z-index or local stacking context changes, then we must dirty our parent stacking context so we are re-indexed. + if (parent) + parent->DirtyStackingContext(); } } - const bool border_radius_changed = ( // - changed_properties.Contains(PropertyId::BorderTopLeftRadius) || // - changed_properties.Contains(PropertyId::BorderTopRightRadius) || // - changed_properties.Contains(PropertyId::BorderBottomRightRadius) || // - changed_properties.Contains(PropertyId::BorderBottomLeftRadius) // - ); - const bool filter_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter)); - // Dirty the background if it's changed. if (border_radius_changed || // changed_properties.Contains(PropertyId::BackgroundColor) || // From a5ddc1a069aa3d4aa5ca37e03f561e88d41cd5ad Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 1 May 2023 14:10:21 +0200 Subject: [PATCH 12/80] Elements with 'filter' and 'backdrop-filter' properties now act as containing block for absolutely positioned elements --- Source/Core/Layout/ContainerBox.cpp | 4 ++-- Source/Core/Layout/ContainerBox.h | 8 ++++---- Source/Core/Layout/LayoutDetails.cpp | 5 +---- Source/Core/Layout/LayoutDetails.h | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Source/Core/Layout/ContainerBox.cpp b/Source/Core/Layout/ContainerBox.cpp index 34c67e6fc..02a4bca5e 100644 --- a/Source/Core/Layout/ContainerBox.cpp +++ b/Source/Core/Layout/ContainerBox.cpp @@ -131,8 +131,8 @@ ContainerBox::ContainerBox(Type type, Element* element, ContainerBox* parent_con const auto& computed = element->GetComputedValues(); overflow_x = computed.overflow_x(); overflow_y = computed.overflow_y(); - position_property = computed.position(); - has_local_transform_or_perspective = (computed.has_local_transform() || computed.has_local_perspective()); + is_absolute_positioning_containing_block = (computed.position() != Style::Position::Static || computed.has_local_transform() || + computed.has_local_perspective() || computed.has_filter() || computed.has_backdrop_filter()); } } diff --git a/Source/Core/Layout/ContainerBox.h b/Source/Core/Layout/ContainerBox.h index 3fe0e9fd6..e7fbeced6 100644 --- a/Source/Core/Layout/ContainerBox.h +++ b/Source/Core/Layout/ContainerBox.h @@ -55,8 +55,9 @@ class ContainerBox : public LayoutBox { ContainerBox* GetParent() { return parent_container; } Element* GetElement() { return element; } - Style::Position GetPositionProperty() const { return position_property; } - bool HasLocalTransformOrPerspective() const { return has_local_transform_or_perspective; } + + // Returns true if this box acts as a containing block for absolutely positioned descendants. + bool IsAbsolutePositioningContainingBlock() const { return is_absolute_positioning_containing_block; } protected: ContainerBox(Type type, Element* element, ContainerBox* parent_container); @@ -99,8 +100,7 @@ class ContainerBox : public LayoutBox { Style::Overflow overflow_x = Style::Overflow::Visible; Style::Overflow overflow_y = Style::Overflow::Visible; - Style::Position position_property = Style::Position::Static; - bool has_local_transform_or_perspective = false; + bool is_absolute_positioning_containing_block = false; ContainerBox* parent_container = nullptr; }; diff --git a/Source/Core/Layout/LayoutDetails.cpp b/Source/Core/Layout/LayoutDetails.cpp index b4f960720..7ea9c94e5 100644 --- a/Source/Core/Layout/LayoutDetails.cpp +++ b/Source/Core/Layout/LayoutDetails.cpp @@ -186,10 +186,7 @@ ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container { area = BoxArea::Padding; - auto EstablishesAbsoluteContainingBlock = [](ContainerBox* container) -> bool { - return container->GetPositionProperty() != Position::Static || container->HasLocalTransformOrPerspective(); - }; - while (container && container->GetParent() && !EstablishesAbsoluteContainingBlock(container)) + while (container && container->GetParent() && !container->IsAbsolutePositioningContainingBlock()) container = container->GetParent(); } diff --git a/Source/Core/Layout/LayoutDetails.h b/Source/Core/Layout/LayoutDetails.h index c022c8132..82e51ee5a 100644 --- a/Source/Core/Layout/LayoutDetails.h +++ b/Source/Core/Layout/LayoutDetails.h @@ -89,7 +89,7 @@ class LayoutDetails { /// Returns the containing block for a box. /// @param[in] parent_container The parent container of the current box. - /// @param[in] child_position The position property of the current box. + /// @param[in] position The position property of the current box. /// @return The containing block box and size, possibly indefinite along one or both axes. static ContainingBlock GetContainingBlock(ContainerBox* parent_container, Style::Position position); From cb64ba4278d312f06bc67504899f2ff0302374b0 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 1 May 2023 15:50:24 +0200 Subject: [PATCH 13/80] Add a new visual test for stacking context paint order --- ...ng_context.rml => stacking_context_01.rml} | 4 +- .../Data/VisualTests/stacking_context_02.rml | 65 +++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) rename Tests/Data/VisualTests/{stacking_context.rml => stacking_context_01.rml} (93%) create mode 100644 Tests/Data/VisualTests/stacking_context_02.rml diff --git a/Tests/Data/VisualTests/stacking_context.rml b/Tests/Data/VisualTests/stacking_context_01.rml similarity index 93% rename from Tests/Data/VisualTests/stacking_context.rml rename to Tests/Data/VisualTests/stacking_context_01.rml index 5cfff78fd..fd234c3c1 100644 --- a/Tests/Data/VisualTests/stacking_context.rml +++ b/Tests/Data/VisualTests/stacking_context_01.rml @@ -1,7 +1,7 @@ - Stacking context and paint order - + Stacking context and paint order + + + + + +
+
Positioned
+
Unfiltered
+
+ +
+
Positioned
+
Filtered
+
+ +
+
Positioned (z=1)
+
Filtered
+
+ +
+
Filtered (first)
+
Positioned
+
+ + + +
From ebc8b65e56654c8c2e8381de57852ecbd688bc3f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 1 May 2023 15:50:56 +0200 Subject: [PATCH 14/80] Add filters to effect sample --- Samples/basic/effect/data/effect.rml | 32 ++++++++++++++++++++- Samples/basic/effect/data/effect_style.rcss | 2 ++ Samples/basic/effect/src/main.cpp | 14 +++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index 841e4c78c..5177d0f54 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -64,6 +64,16 @@ .transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); } +.brightness { filter: brightness(0.5); } +.contrast { filter: contrast(0.5); } +.sepia { filter: sepia(80%); } +.grayscale { filter: grayscale(0.9); } + +.saturate { filter: saturate(200%); } +.hue_rotate { filter: hue-rotate(260deg); } +.invert { filter: invert(100%); } +.opacity_low { filter: opacity(0.2); } + Opacity{{ opacity }} + Sepia{{ sepia*100 }} % + Grayscale{{ grayscale*100 }} % + Saturate{{ saturate*100 }} % + Brightness{{ brightness*100 }} % + Contrast{{ contrast*100 }} % + Hue{{ hue_rotate }} deg + Invert{{ invert*100 }} % Scale{{ scale | format(1) }}x @@ -100,13 +117,26 @@
Hello, do you feel the funk?
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+ +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+ +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+ +
Hello, do you feel the funk?
+ +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
diff --git a/Samples/basic/effect/data/effect_style.rcss b/Samples/basic/effect/data/effect_style.rcss index badb2f839..075a9f882 100644 --- a/Samples/basic/effect/data/effect_style.rcss +++ b/Samples/basic/effect/data/effect_style.rcss @@ -32,6 +32,7 @@ h1 { } handle.size { position: fixed; + z-index: 100; bottom: 0; right: 0; width: 18dp; @@ -128,6 +129,7 @@ handle.size:hover, handle.size:active { } scrollbarvertical { + z-index: 100; margin-top: 75dp; margin-bottom: 20dp; margin-right: 0dp; diff --git a/Samples/basic/effect/src/main.cpp b/Samples/basic/effect/src/main.cpp index 2d5d733bc..75978d12e 100644 --- a/Samples/basic/effect/src/main.cpp +++ b/Samples/basic/effect/src/main.cpp @@ -80,6 +80,13 @@ int main(int /*argc*/, char** /*argv*/) struct Filter { float opacity = 1.0f; + float sepia = 0.0f; + float grayscale = 0.0f; + float saturate = 1.0f; + float brightness = 1.0f; + float contrast = 1.0f; + float hue_rotate = 0.0f; + float invert = 0.0f; } filter; struct Transform { @@ -97,6 +104,13 @@ int main(int /*argc*/, char** /*argv*/) constructor.Bind("submenu", &data.submenu); constructor.Bind("opacity", &data.filter.opacity); + constructor.Bind("sepia", &data.filter.sepia); + constructor.Bind("grayscale", &data.filter.grayscale); + constructor.Bind("saturate", &data.filter.saturate); + constructor.Bind("brightness", &data.filter.brightness); + constructor.Bind("contrast", &data.filter.contrast); + constructor.Bind("hue_rotate", &data.filter.hue_rotate); + constructor.Bind("invert", &data.filter.invert); constructor.Bind("scale", &data.transform.scale); constructor.Bind("rotate_x", &data.transform.rotate.x); From 1d7b1833dddd6a24df6efca5c0021b48fc693bd2 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 1 May 2023 23:41:12 +0200 Subject: [PATCH 15/80] Add render layer and filter support to GL3 renderer --- Backends/RmlUi_Backend_GLFW_GL3.cpp | 8 +- Backends/RmlUi_Backend_SDL_GL3.cpp | 19 +- Backends/RmlUi_Renderer_GL3.cpp | 990 +++++++++++++++++++++++----- Backends/RmlUi_Renderer_GL3.h | 102 ++- 4 files changed, 928 insertions(+), 191 deletions(-) diff --git a/Backends/RmlUi_Backend_GLFW_GL3.cpp b/Backends/RmlUi_Backend_GLFW_GL3.cpp index 9d6826317..66db5b3f8 100644 --- a/Backends/RmlUi_Backend_GLFW_GL3.cpp +++ b/Backends/RmlUi_Backend_GLFW_GL3.cpp @@ -74,12 +74,6 @@ bool Backend::Initialize(const char* name, int width, int height, bool allow_res glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); - // Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements. - glfwWindowHint(GLFW_STENCIL_BITS, 8); - - // Enable MSAA for better-looking visuals, especially when transforms are applied. - glfwWindowHint(GLFW_SAMPLES, 2); - // Apply window properties and create it. glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE); glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); @@ -182,8 +176,8 @@ void Backend::RequestExit() void Backend::BeginFrame() { RMLUI_ASSERT(data); - data->render_interface.BeginFrame(); data->render_interface.Clear(); + data->render_interface.BeginFrame(); } void Backend::PresentFrame() diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index 0b615b4e9..ae274eb29 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -143,30 +143,15 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); #endif - // Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements. - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - // Enable linear filtering and MSAA for better-looking visuals, especially when transforms are applied. - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2); - const Uint32 window_flags = (SDL_WINDOW_OPENGL | (allow_resize ? SDL_WINDOW_RESIZABLE : 0)); SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags); if (!window) { - // Try again on low-quality settings. - SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0); - window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags); - if (!window) - { - fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError()); - return false; - } + fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError()); + return false; } SDL_GLContext glcontext = SDL_GL_CreateContext(window); diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 2b7f10ef6..9dc3e9576 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -29,6 +29,7 @@ #include "RmlUi_Renderer_GL3.h" #include #include +#include #include #include #include @@ -41,18 +42,30 @@ #endif #if defined RMLUI_PLATFORM_EMSCRIPTEN - #define RMLUI_SHADER_HEADER "#version 300 es\nprecision highp float;\n" + #define RMLUI_SHADER_HEADER_VERSION "#version 300 es\nprecision highp float;\n" #include #elif defined RMLUI_GL3_CUSTOM_LOADER - #define RMLUI_SHADER_HEADER "#version 330\n" + #define RMLUI_SHADER_HEADER_VERSION "#version 330\n" #include RMLUI_GL3_CUSTOM_LOADER #else - #define RMLUI_SHADER_HEADER "#version 330\n" + #define RMLUI_SHADER_HEADER_VERSION "#version 330\n" #define GLAD_GL_IMPLEMENTATION #include "RmlUi_Include_GL3.h" #endif -static const char* shader_main_vertex = RMLUI_SHADER_HEADER R"( +#define RMLUI_PREMULTIPLIED_ALPHA 1 + +#define RMLUI_STRINGIFY_IMPL(x) #x +#define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x) + +#define RMLUI_SHADER_HEADER \ + RMLUI_SHADER_HEADER_VERSION \ + "#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n" + +// Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. +static constexpr int NUM_MSAA_SAMPLES = 2; + +static const char* shader_vert_main = RMLUI_SHADER_HEADER R"( uniform vec2 _translate; uniform mat4 _transform; @@ -67,14 +80,18 @@ void main() { fragTexCoord = inTexCoord0; fragColor = inColor0; - vec2 translatedPos = inPosition + _translate.xy; - vec4 outPos = _transform * vec4(translatedPos, 0, 1); +#if RMLUI_PREMULTIPLIED_ALPHA + // Pre-multiply vertex colors with their alpha. + fragColor.rgb = fragColor.rgb * fragColor.a; +#endif + + vec2 translatedPos = inPosition + _translate; + vec4 outPos = _transform * vec4(translatedPos, 0.0, 1.0); gl_Position = outPos; } )"; - -static const char* shader_main_fragment_texture = RMLUI_SHADER_HEADER R"( +static const char* shader_frag_texture = RMLUI_SHADER_HEADER R"( uniform sampler2D _tex; in vec2 fragTexCoord; in vec4 fragColor; @@ -86,7 +103,7 @@ void main() { finalColor = fragColor * texColor; } )"; -static const char* shader_main_fragment_color = RMLUI_SHADER_HEADER R"( +static const char* shader_frag_color = RMLUI_SHADER_HEADER R"( in vec2 fragTexCoord; in vec4 fragColor; @@ -97,14 +114,158 @@ void main() { } )"; +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() { + vec4 texColor = texture(_tex, fragTexCoord); + finalColor = _color_matrix * texColor; +} +)"; + +enum class ProgramId { + None, + Color, + Texture, + Passthrough, + ColorMatrix, + Count, +}; +enum class VertShaderId { + Main, + Passthrough, + Count, +}; +enum class FragShaderId { + Color, + Texture, + Passthrough, + ColorMatrix, + Count, +}; +enum class UniformId { + Translate, + Transform, + Tex, + ColorMatrix, + Count, +}; + namespace Gfx { -enum class ProgramUniform { Translate, Transform, Tex, Count }; -static const char* const program_uniform_names[(size_t)ProgramUniform::Count] = {"_translate", "_transform", "_tex"}; +static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color_matrix"}; 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}, +}; +static const FragShaderDefinition frag_shader_definitions[] = { + {FragShaderId::Color, "color", shader_frag_color}, + {FragShaderId::Texture, "texture", shader_frag_texture}, + {FragShaderId::Passthrough, "passthrough", shader_frag_passthrough}, + {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, +}; +static const ProgramDefinition program_definitions[] = { + {ProgramId::Color, "color", VertShaderId::Main, FragShaderId::Color}, + {ProgramId::Texture, "texture", VertShaderId::Main, FragShaderId::Texture}, + {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, + {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, +}; +// 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 = std::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; @@ -112,18 +273,16 @@ struct CompiledGeometryData { GLsizei draw_count; }; -struct ProgramData { - GLuint id; - GLint uniform_locations[(size_t)ProgramUniform::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; }; -struct ShadersData { - ProgramData program_color; - ProgramData program_texture; - GLuint shader_main_vertex; - GLuint shader_main_fragment_color; - GLuint shader_main_fragment_texture; -}; +enum class FramebufferAttachment { None, Depth, DepthStencil }; static void CheckGLError(const char* operation_name) { @@ -149,10 +308,11 @@ static void CheckGLError(const char* operation_name) } // Create the shader, 'shader_type' is either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. -static GLuint CreateShader(GLenum shader_type, const char* code_string) +static bool CreateShader(GLuint& out_shader_id, GLenum shader_type, const char* code_string) { - GLuint id = glCreateShader(shader_type); + 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); @@ -168,29 +328,24 @@ static GLuint CreateShader(GLenum shader_type, const char* code_string) Rml::Log::Message(Rml::Log::LT_ERROR, "Compile failure in OpenGL shader: %s", info_log_string); delete[] info_log_string; glDeleteShader(id); - return 0; + return false; } CheckGLError("CreateShader"); - return id; -} - -static void BindAttribLocations(GLuint program) -{ - for (GLuint i = 0; i < (GLuint)VertexAttribute::Count; i++) - { - glBindAttribLocation(program, i, vertex_attribute_names[i]); - } - CheckGLError("BindAttribLocations"); + out_shader_id = id; + return true; } -static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramData& out_program) +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); - BindAttribLocations(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); @@ -215,8 +370,7 @@ static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramD return false; } - out_program = {}; - out_program.id = id; + out_program = id; // Make a lookup table for the uniform locations. GLint num_active_uniforms = 0; @@ -233,20 +387,20 @@ static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramD GLint location = glGetUniformLocation(id, name_buf); // See if we have the name in our pre-defined name list. - ProgramUniform program_uniform = ProgramUniform::Count; - for (int i = 0; i < (int)ProgramUniform::Count; i++) + 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 = (ProgramUniform)i; + program_uniform = (UniformId)i; break; } } - if ((size_t)program_uniform < (size_t)ProgramUniform::Count) + if ((size_t)program_uniform < (size_t)UniformId::Count) { - out_program.uniform_locations[(size_t)program_uniform] = location; + inout_uniform_map.Insert(program_id, program_uniform, location); } else { @@ -260,78 +414,202 @@ static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramD return true; } -static bool CreateShaders(ShadersData& out_shaders) +static bool CreateFramebuffer(FramebufferData& out_fb, int width, int height, int samples, FramebufferAttachment attachment, + GLuint shared_depth_stencil_buffer) { - out_shaders = {}; - GLuint& main_vertex = out_shaders.shader_main_vertex; - GLuint& main_fragment_color = out_shaders.shader_main_fragment_color; - GLuint& main_fragment_texture = out_shaders.shader_main_fragment_texture; +#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); - main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex); - if (!main_vertex) + GLuint color_tex_buffer = 0; + GLuint color_render_buffer = 0; + if (samples > 0) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_vertex'."); - return false; + 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); } - main_fragment_color = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color); - if (!main_fragment_color) + + // Create depth/stencil buffer storage attachment. + GLuint depth_stencil_buffer = 0; + if (attachment != FramebufferAttachment::None) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_color'."); - return false; + 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); + + const GLenum internal_format = (attachment == FramebufferAttachment::DepthStencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, internal_format, width, height); + } + + const GLenum attachment_type = (attachment == FramebufferAttachment::DepthStencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment_type, GL_RENDERBUFFER, depth_stencil_buffer); } - main_fragment_texture = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture); - if (!main_fragment_texture) + + const GLuint framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_texture'."); + Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL framebuffer could not be generated. Error code %x.", framebuffer_status); return false; } - if (!CreateProgram(main_vertex, main_fragment_color, out_shaders.program_color)) + 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 void BindTexture(const FramebufferData& fb) +{ + if (!fb.color_tex_buffer) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_color'."); + 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); } - if (!CreateProgram(main_vertex, main_fragment_texture, out_shaders.program_texture)) + + for (const FragShaderDefinition& def : frag_shader_definitions) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_texture'."); - return false; + 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(0); + return true; } -static void DestroyShaders(ShadersData& shaders) +static void DestroyShaders(const ProgramData& data) { - glDeleteProgram(shaders.program_color.id); - glDeleteProgram(shaders.program_texture.id); + for (GLuint id : data.programs) + glDeleteProgram(id); - glDeleteShader(shaders.shader_main_vertex); - glDeleteShader(shaders.shader_main_fragment_color); - glDeleteShader(shaders.shader_main_fragment_texture); + for (GLuint id : data.vert_shaders) + glDeleteShader(id); - shaders = {}; + for (GLuint id : data.frag_shaders) + glDeleteShader(id); } } // namespace Gfx RenderInterface_GL3::RenderInterface_GL3() { - shaders = Rml::MakeUnique(); + auto mut_program_data = Rml::MakeUnique(); + if (Gfx::CreateShaders(*mut_program_data)) + { + program_data = std::move(mut_program_data); - if (!Gfx::CreateShaders(*shaders)) - shaders.reset(); + Rml::Vertex vertices[4]; + int indices[6]; + Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + fullscreen_quad_geometry = RenderInterface_GL3::CompileGeometry(vertices, 4, indices, 6); + } } RenderInterface_GL3::~RenderInterface_GL3() { - if (shaders) - Gfx::DestroyShaders(*shaders); + if (fullscreen_quad_geometry) + { + RenderInterface_GL3::ReleaseCompiledGeometry(fullscreen_quad_geometry); + fullscreen_quad_geometry = {}; + } + + if (program_data) + { + Gfx::DestroyShaders(*program_data); + program_data.reset(); + } } void RenderInterface_GL3::SetViewport(int width, int height) { viewport_width = width; viewport_height = height; + projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); } void RenderInterface_GL3::BeginFrame() @@ -347,6 +625,8 @@ void RenderInterface_GL3::BeginFrame() 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); @@ -377,25 +657,67 @@ void RenderInterface_GL3::BeginFrame() glViewport(0, 0, viewport_width, viewport_height); glClearStencil(0); - glClearColor(0, 0, 0, 1); + glClearColor(0, 0, 0, 0); + glActiveTexture(GL_TEXTURE0); + + glDisable(GL_SCISSOR_TEST); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); +#if RMLUI_PREMULTIPLIED_ALPHA + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); +#else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +#endif + +#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); - projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); 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() { + 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, 0); + + // 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 pre-multiplied 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); @@ -420,6 +742,8 @@ void RenderInterface_GL3::EndFrame() 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]); @@ -436,13 +760,14 @@ void RenderInterface_GL3::EndFrame() 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() { - glClearStencil(0); glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + glClear(GL_COLOR_BUFFER_BIT); } void RenderInterface_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, @@ -506,27 +831,28 @@ void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle han { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; - if (texture) + if (texture == TexturePostprocess) { - glUseProgram(shaders->program_texture.id); + // Do nothing. + } + else if (texture) + { + UseProgram(ProgramId::Texture); + SubmitTransformUniform(translation); if (texture != TextureEnableWithoutBinding) glBindTexture(GL_TEXTURE_2D, (GLuint)texture); - SubmitTransformUniform(ProgramId::Texture, shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]); - glUniform2fv(shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x); } else { - glUseProgram(shaders->program_color.id); + UseProgram(ProgramId::Color); glBindTexture(GL_TEXTURE_2D, 0); - SubmitTransformUniform(ProgramId::Color, shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]); - glUniform2fv(shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x); + SubmitTransformUniform(translation); } glBindVertexArray(geometry->vao); glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); glBindVertexArray(0); - glUseProgram(0); glBindTexture(GL_TEXTURE_2D, 0); Gfx::CheckGLError("RenderCompiledGeometry"); @@ -543,63 +869,48 @@ void RenderInterface_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle ha delete geometry; } -void RenderInterface_GL3::EnableScissorRegion(bool enable) +/// Flip vertical axis of the rectangle, and move its origin to the vertically opposite side of the viewport. +/// @note Changes 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) { - ScissoringState new_state = ScissoringState::Disable; - - if (enable) - new_state = (transform_active ? ScissoringState::Stencil : ScissoringState::Scissor); - - if (new_state != scissoring_state) - { - // Disable old - if (scissoring_state == ScissoringState::Scissor) - glDisable(GL_SCISSOR_TEST); - else if (scissoring_state == ScissoringState::Stencil) - glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); - - // Enable new - if (new_state == ScissoringState::Scissor) - glEnable(GL_SCISSOR_TEST); - else if (new_state == ScissoringState::Stencil) - glStencilFunc(GL_EQUAL, 1, GLuint(-1)); - - scissoring_state = new_state; - } + 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::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_GL3::SetScissor(Rml::Rectanglei region, bool vertically_flip) { - if (transform_active) + if (region.Valid() != scissor_state.Valid()) { - const float left = float(x); - const float right = float(x + width); - const float top = float(y); - const float bottom = float(y + height); + if (region.Valid()) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); + } - Rml::Vertex vertices[4]; - vertices[0].position = {left, top}; - vertices[1].position = {right, top}; - vertices[2].position = {right, bottom}; - vertices[3].position = {left, bottom}; + if (region.Valid() && vertically_flip) + region = VerticallyFlipped(region, viewport_height); - int indices[6] = {0, 2, 1, 0, 3, 2}; + if (region.Valid() && region != scissor_state) + glScissor(region.Left(), viewport_height - region.Bottom(), region.Width(), region.Height()); - glClear(GL_STENCIL_BUFFER_BIT); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + Gfx::CheckGLError("SetScissorRegion"); + scissor_state = region; +} - RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0)); +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); +} - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, 1, GLuint(-1)); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } - else - { - glScissor(x, viewport_height - (y + height), width, height); - } +void RenderInterface_GL3::SetScissorRegion(int x, int y, int width, int height) +{ + SetScissor(Rml::Rectanglei::FromPositionSize({x, y}, {width, height})); } // Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file @@ -681,19 +992,9 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V image_dest[write_index + 1] = image_src[read_index + 1]; image_dest[write_index + 2] = image_src[read_index]; if (color_mode == 4) - { - const int alpha = image_src[read_index + 3]; -#ifdef RMLUI_SRGB_PREMULTIPLIED_ALPHA - image_dest[write_index + 0] = (image_dest[write_index + 0] * alpha) / 255; - image_dest[write_index + 1] = (image_dest[write_index + 1] * alpha) / 255; - image_dest[write_index + 2] = (image_dest[write_index + 2] * alpha) / 255; -#endif - image_dest[write_index + 3] = (byte)alpha; - } + image_dest[write_index + 3] = image_src[read_index + 3]; else - { image_dest[write_index + 3] = 255; - } write_index += 4; read_index += color_mode; @@ -721,10 +1022,29 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co return false; } +#if RMLUI_PREMULTIPLIED_ALPHA + using Rml::byte; + Rml::UniquePtr source_premultiplied; + if (source) + { + const size_t num_bytes = source_dimensions.x * source_dimensions.y * 4; + source_premultiplied = Rml::UniquePtr(new byte[num_bytes]); + + for (size_t i = 0; i < num_bytes; i += 4) + { + const byte alpha = source[i + 3]; + for (size_t j = 0; j < 3; j++) + source_premultiplied[i + j] = byte((int(source[i + j]) * int(alpha)) / 255); + source_premultiplied[i + 3] = alpha; + } + + source = source_premultiplied.get(); + } +#endif + glBindTexture(GL_TEXTURE_2D, texture_id); - GLint internal_format = GL_RGBA8; - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -738,6 +1058,11 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co return true; } +void RenderInterface_GL3::DrawFullscreenQuad() +{ + RenderCompiledGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); +} + void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) { glDeleteTextures(1, (GLuint*)&texture_handle); @@ -745,18 +1070,375 @@ void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) { - transform_active = (new_transform != nullptr); transform = projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity()); - transform_dirty_state = ProgramId::All; + program_transform_dirty.set(); +} + +enum class FilterType { Invalid = 0, Passthrough, ColorMatrix }; +struct CompiledFilter { + FilterType type; + + // Passthrough + float blend_factor; + + // 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 == "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::ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) +{ + delete reinterpret_cast(filter); +} + +void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() +{ + const Gfx::FramebufferData& source = render_layers.GetTopLayer(); + 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::SubmitTransformUniform(ProgramId program_id, int uniform_location) +void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_handles) { - if ((int)program_id & (int)transform_dirty_state) + for (const Rml::CompiledFilterHandle filter_handle : filter_handles) { - glUniformMatrix4fv(uniform_location, 1, false, transform.data()); - transform_dirty_state = ProgramId((int)transform_dirty_state & ~(int)program_id); + const CompiledFilter& filter = *reinterpret_cast(filter_handle); + const FilterType type = filter.type; + + switch (type) + { + case FilterType::Passthrough: + { + UseProgram(ProgramId::Passthrough); + glBlendFunc(GL_CONSTANT_ALPHA, GL_ZERO); + glBlendColor(0.0f, 0.0f, 0.0f, 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::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::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render filter %d.", (int)type); + } + break; + } } + + Gfx::CheckGLError("RenderFilter"); +} + +void RenderInterface_GL3::PushLayer(Rml::LayerFill layer_fill) +{ + if (layer_fill == Rml::LayerFill::Clone) + render_layers.PushLayerClone(); + else + render_layers.PushLayer(); + + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + if (layer_fill == Rml::LayerFill::Clear) + glClear(GL_COLOR_BUFFER_BIT); +} + +void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, const Rml::FilterHandleList& filters) +{ + using Rml::BlendMode; + + if (blend_mode == BlendMode::Discard) + { + RMLUI_ASSERT(filters.empty()); + render_layers.PopLayer(); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + return; + } + + // Blit stack to filter rendering 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. + BlitTopLayerToPostprocessPrimary(); + + // Render the filters, the PostprocessPrimary framebuffer is used for both input and output. + RenderFilters(filters); + + // Pop the active layer, thereby activating the beneath layer. + render_layers.PopLayer(); + + // Render to the activated layer. Apply any mask if active. + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().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); + + Gfx::CheckGLError("PopLayer"); +} + +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; + } +} + +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(program_data->uniforms.Get(active_program, UniformId::Transform), 1, false, transform.data()); + program_transform_dirty.set(program_index, false); + } + + glUniform2fv(program_data->uniforms.Get(active_program, UniformId::Translate), 1, &translation.x); + + Gfx::CheckGLError("SubmitTransformUniform"); +} + +RenderInterface_GL3::RenderLayerStack::RenderLayerStack() +{ + fb_postprocess.resize(3); +} + +RenderInterface_GL3::RenderLayerStack::~RenderLayerStack() +{ + DestroyFramebuffers(); +} + +void 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, NUM_MSAA_SAMPLES, Gfx::FramebufferAttachment::DepthStencil, shared_depth_stencil); + } + + layers_size += 1; +} + +void RenderInterface_GL3::RenderLayerStack::PushLayerClone() +{ + RMLUI_ASSERT(layers_size > 0); + fb_layers.insert(fb_layers.begin() + layers_size, Gfx::FramebufferData{fb_layers[layers_size - 1]}); + layers_size += 1; +} + +void RenderInterface_GL3::RenderLayerStack::PopLayer() +{ + RMLUI_ASSERT(layers_size > 0); + layers_size -= 1; + + // Only cloned framebuffers are removed. Other framebuffers remain for later re-use. + if (IsCloneOfBelow(layers_size)) + fb_layers.erase(fb_layers.begin() + layers_size); +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetTopLayer() const +{ + RMLUI_ASSERT(layers_size > 0); + return fb_layers[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); +} + +bool RenderInterface_GL3::RenderLayerStack::IsCloneOfBelow(int layer_index) const +{ + const bool result = + (layer_index >= 1 && layer_index < (int)fb_layers.size() && fb_layers[layer_index].framebuffer == fb_layers[layer_index - 1].framebuffer); + return result; +} + +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; } bool RmlGL3::Initialize(Rml::String* out_message) diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index a7b49f23c..92878d733 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -31,10 +31,14 @@ #include #include +#include +enum class ProgramId; +class RenderLayerStack; namespace Gfx { -struct ShadersData; -} +struct ProgramData; +struct FramebufferData; +} // namespace Gfx class RenderInterface_GL3 : public Rml::RenderInterface { public: @@ -42,16 +46,17 @@ class RenderInterface_GL3 : public Rml::RenderInterface { ~RenderInterface_GL3(); // Returns true if the renderer was successfully constructed. - explicit operator bool() const { return static_cast(shaders); } + 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); // Sets up OpenGL states for taking rendering commands from RmlUi. void BeginFrame(); + // Draws the result to the backbuffer and restores OpenGL state. void EndFrame(); - // Optional, can be used to clear the framebuffer. + // Optional, can be used to clear the active framebuffer. void Clear(); // -- Inherited from Rml::RenderInterface -- @@ -72,24 +77,93 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; + void PushLayer(Rml::LayerFill layer_fill) override; + void PopLayer(Rml::BlendMode blend_mode, const Rml::FilterHandleList& filters) override; + + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; + void ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) override; + // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. - static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); + 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); private: - enum class ProgramId { None, Texture = 1, Color = 2, All = (Texture | Color) }; - void SubmitTransformUniform(ProgramId program_id, int uniform_location); + void UseProgram(ProgramId program_id); + void SubmitTransformUniform(Rml::Vector2f translation); + + void BlitTopLayerToPostprocessPrimary(); + void RenderFilters(const Rml::FilterHandleList& filter_handles); + + void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); + + void DrawFullscreenQuad(); + + static constexpr size_t MaxNumPrograms = 32; + std::bitset program_transform_dirty; - Rml::Matrix4f transform, projection; - ProgramId transform_dirty_state = ProgramId::All; - bool transform_active = false; + Rml::Matrix4f transform; + Rml::Matrix4f projection; - enum class ScissoringState { Disable, Scissor, Stencil }; - ScissoringState scissoring_state = ScissoringState::Disable; + ProgramId active_program = {}; + Rml::Rectanglei scissor_state; int viewport_width = 0; int viewport_height = 0; - Rml::UniquePtr shaders; + 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. + void PushLayer(); + + // Push a clone of the active layer. All references to previously retrieved layers are invalidated. + void PushLayerClone(); + + // Pop the top layer. All references to previously retrieved layers are invalidated. + void PopLayer(); + + const Gfx::FramebufferData& GetTopLayer() const; + + const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); } + const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); } + const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); } + + void SwapPostprocessPrimarySecondary(); + + void BeginFrame(int new_width, int new_height); + void EndFrame(); + + private: + void DestroyFramebuffers(); + bool IsCloneOfBelow(int layer_index) const; + 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; @@ -100,6 +174,8 @@ class RenderInterface_GL3 : public Rml::RenderInterface { int viewport[4]; int scissor[4]; + int active_texture; + int stencil_clear_value; float color_clear_value[4]; From edcb854a1a377ab19245977af7b4c59c00a188a9 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 2 May 2023 01:25:29 +0200 Subject: [PATCH 16/80] Improve visual tests automated test result output --- Tests/Source/VisualTests/TestNavigator.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/Source/VisualTests/TestNavigator.cpp b/Tests/Source/VisualTests/TestNavigator.cpp index 185a650ee..014f25b3b 100644 --- a/Tests/Source/VisualTests/TestNavigator.cpp +++ b/Tests/Source/VisualTests/TestNavigator.cpp @@ -502,20 +502,14 @@ void TestNavigator::StopTestSuiteIteration() log += "\n\nNot Equal:\n"; if (!not_equal.empty()) - log += "Percentages are similarity scores. Difference images written to " + GetCaptureOutputDirectory() + "/*-diff.png\n\n"; + log += "Percentages are similarity scores.\n\n"; for (int i : not_equal) { suite.SetIndex(i); - log += Rml::CreateString(256, "%5d %5.1f%% %s\n", i + 1, comparison_results[i].similarity_score * 100.0, suite.GetFilename().c_str()); + log += Rml::CreateString(512, "%5d %5.1f%% %s\n", i + 1, comparison_results[i].similarity_score * 100.0, suite.GetFilename().c_str()); if (!comparison_results[i].error_msg.empty()) log += " " + comparison_results[i].error_msg + "\n"; } - log += "\nEqual:\n"; - for (int i : equal) - { - suite.SetIndex(i); - log += Rml::CreateString(256, "%5d %s\n", i + 1, suite.GetFilename().c_str()); - } log += "\nFailed:\n"; for (int i : failed) { @@ -527,7 +521,13 @@ void TestNavigator::StopTestSuiteIteration() for (int i : skipped) { suite.SetIndex(i); - log += Rml::CreateString(256, "%5d %s\n", i + 1, suite.GetFilename().c_str()); + log += Rml::CreateString(512, "%5d %s\n", i + 1, suite.GetFilename().c_str()); + } + log += "\nEqual:\n"; + for (int i : equal) + { + suite.SetIndex(i); + log += Rml::CreateString(512, "%5d %s\n", i + 1, suite.GetFilename().c_str()); } const Rml::String log_path = GetCaptureOutputDirectory() + "/comparison.log"; From 156d2bd3b8e1b0e8a4c8844e175197ebe2d9d7c9 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Thu, 11 May 2023 21:07:35 +0200 Subject: [PATCH 17/80] Fix backdrop-filter --- Source/Core/ElementDecoration.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index ef5482a9a..85773906f 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -107,11 +107,11 @@ void ElementDecoration::InstanceDecorators() { const Property* property = element->GetLocalProperty(id); if (!property || property->unit != Unit::FILTER) - return; + continue; FiltersPtr filters_ptr = property->Get(); if (!filters_ptr) - return; + continue; FilterEntryList& list = (id == PropertyId::Filter ? filters : backdrop_filters); list.reserve(filters_ptr->list.size()); From 17246f5ea23b959bdc805bbdaf34204f2600ff62 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Thu, 11 May 2023 21:58:35 +0200 Subject: [PATCH 18/80] Add blur filter, implement in GL3 renderer --- Backends/RmlUi_Renderer_GL3.cpp | 245 +++++++++++++++++++- Backends/RmlUi_Renderer_GL3.h | 5 + CMake/FileList.cmake | 2 + Samples/basic/effect/data/effect.rml | 14 +- Samples/basic/effect/data/effect_style.rcss | 2 +- Samples/basic/effect/src/main.cpp | 2 + Source/Core/Factory.cpp | 4 + Source/Core/FilterBasic.cpp | 4 +- Source/Core/FilterBlur.cpp | 81 +++++++ Source/Core/FilterBlur.h | 66 ++++++ 10 files changed, 410 insertions(+), 15 deletions(-) create mode 100644 Source/Core/FilterBlur.cpp create mode 100644 Source/Core/FilterBlur.h diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 9dc3e9576..249abdb0d 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -53,7 +53,12 @@ #include "RmlUi_Include_GL3.h" #endif +// Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. +static constexpr int NUM_MSAA_SAMPLES = 2; + #define RMLUI_PREMULTIPLIED_ALPHA 1 +#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) @@ -62,9 +67,6 @@ RMLUI_SHADER_HEADER_VERSION \ "#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n" -// Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. -static constexpr int NUM_MSAA_SAMPLES = 2; - static const char* shader_vert_main = RMLUI_SHADER_HEADER R"( uniform vec2 _translate; uniform mat4 _transform; @@ -147,17 +149,53 @@ void main() { } )"; +#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++) + color += texture(_tex, clamp(fragTexCoord[i], _texCoordMin, _texCoordMax)) * _weights[abs(i - BLUR_NUM_WEIGHTS + 1)]; + finalColor = color; +} +)"; + enum class ProgramId { None, Color, Texture, Passthrough, ColorMatrix, + Blur, Count, }; enum class VertShaderId { Main, Passthrough, + Blur, Count, }; enum class FragShaderId { @@ -165,6 +203,7 @@ enum class FragShaderId { Texture, Passthrough, ColorMatrix, + Blur, Count, }; enum class UniformId { @@ -172,12 +211,17 @@ enum class UniformId { Transform, Tex, ColorMatrix, + TexelOffset, + TexCoordMin, + TexCoordMax, + Weights, Count, }; namespace Gfx { -static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color_matrix"}; +static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color_matrix", "_texelOffset", + "_texCoordMin", "_texCoordMax", "_weights[0]"}; enum class VertexAttribute { Position, Color0, TexCoord0, Count }; static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; @@ -203,18 +247,21 @@ struct ProgramDefinition { 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::Passthrough, "passthrough", shader_frag_passthrough}, {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, + {FragShaderId::Blur, "blur", shader_frag_blur}, }; static const ProgramDefinition program_definitions[] = { {ProgramId::Color, "color", VertShaderId::Main, FragShaderId::Color}, {ProgramId::Texture, "texture", VertShaderId::Main, FragShaderId::Texture}, {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, + {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, }; // clang-format on @@ -1063,6 +1110,164 @@ void RenderInterface_GL3::DrawFullscreenQuad() RenderCompiledGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); } +void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) +{ + Rml::Vertex vertices[4]; + int indices[6]; + Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + if (uv_offset != Rml::Vector2f() || uv_scaling != Rml::Vector2f(1.f)) + { + for (Rml::Vertex& vertex : vertices) + vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; + } + RenderGeometry(vertices, 4, indices, 6, RenderInterface_GL3::TexturePostprocess, {}); +} + +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); + + 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 noticable 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 enlargen 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); @@ -1074,13 +1279,16 @@ void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) program_transform_dirty.set(); } -enum class FilterType { Invalid = 0, Passthrough, ColorMatrix }; +enum class FilterType { Invalid = 0, Passthrough, Blur, ColorMatrix }; struct CompiledFilter { FilterType type; // Passthrough float blend_factor; + // Blur + float sigma; + // ColorMatrix Rml::Matrix4f color_matrix; }; @@ -1094,6 +1302,11 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& filter.type = FilterType::Passthrough; filter.blend_factor = Rml::Get(parameters, "value", 1.0f); } + else if (name == "blur") + { + filter.type = FilterType::Blur; + filter.sigma = 0.5f * Rml::Get(parameters, "radius", 1.0f); + } else if (name == "brightness") { filter.type = FilterType::ColorMatrix; @@ -1227,6 +1440,19 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand 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::ColorMatrix: { UseProgram(ProgramId::ColorMatrix); @@ -1321,6 +1547,11 @@ void RenderInterface_GL3::UseProgram(ProgramId 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."); @@ -1328,11 +1559,11 @@ void RenderInterface_GL3::SubmitTransformUniform(Rml::Vector2f translation) if (program_transform_dirty.test(program_index)) { - glUniformMatrix4fv(program_data->uniforms.Get(active_program, UniformId::Transform), 1, false, transform.data()); + glUniformMatrix4fv(GetUniformLocation(UniformId::Transform), 1, false, transform.data()); program_transform_dirty.set(program_index, false); } - glUniform2fv(program_data->uniforms.Get(active_program, UniformId::Translate), 1, &translation.x); + glUniform2fv(GetUniformLocation(UniformId::Translate), 1, &translation.x); Gfx::CheckGLError("SubmitTransformUniform"); } diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 92878d733..660e9bf27 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -34,6 +34,7 @@ #include enum class ProgramId; +enum class UniformId; class RenderLayerStack; namespace Gfx { struct ProgramData; @@ -90,6 +91,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { private: void UseProgram(ProgramId program_id); + int GetUniformLocation(UniformId uniform_id) const; void SubmitTransformUniform(Rml::Vector2f translation); void BlitTopLayerToPostprocessPrimary(); @@ -98,6 +100,9 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); void DrawFullscreenQuad(); + void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling); + + 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; diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 6a6577d52..26f6f2ad7 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -48,6 +48,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/EventSpecification.h ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.h + ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h @@ -305,6 +306,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index 5177d0f54..063e61ef3 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -45,15 +45,14 @@ .box.window { position: absolute; - bottom: 50dp; - left: 50dp; + left: 30dp; margin: 0; - cursor: move; } .box.window handle { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: block; + cursor: move; } .box.big { width: 500dp; @@ -74,6 +73,9 @@ .invert { filter: invert(100%); } .opacity_low { filter: opacity(0.2); } +.blur { filter: blur(25px); } +.back_blur { backdrop-filter: blur(15px); } + Contrast{{ contrast*100 }} % Hue{{ hue_rotate }} deg Invert{{ invert*100 }} % + Blur{{ blur }} px Scale{{ scale | format(1) }}x @@ -117,7 +120,7 @@
@@ -133,7 +136,8 @@
Hello, do you feel the funk?
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Samples/basic/effect/data/effect_style.rcss b/Samples/basic/effect/data/effect_style.rcss index 075a9f882..3164c3647 100644 --- a/Samples/basic/effect/data/effect_style.rcss +++ b/Samples/basic/effect/data/effect_style.rcss @@ -81,7 +81,7 @@ handle.size:hover, handle.size:active { right: 25dp; box-sizing: border-box; width: 400dp; - height: 415dp; + height: 440dp; overflow: auto; background: #fffc; diff --git a/Samples/basic/effect/src/main.cpp b/Samples/basic/effect/src/main.cpp index 75978d12e..e08495bd1 100644 --- a/Samples/basic/effect/src/main.cpp +++ b/Samples/basic/effect/src/main.cpp @@ -87,6 +87,7 @@ int main(int /*argc*/, char** /*argv*/) float contrast = 1.0f; float hue_rotate = 0.0f; float invert = 0.0f; + float blur = 0.0f; } filter; struct Transform { @@ -111,6 +112,7 @@ int main(int /*argc*/, char** /*argv*/) constructor.Bind("contrast", &data.filter.contrast); constructor.Bind("hue_rotate", &data.filter.hue_rotate); constructor.Bind("invert", &data.filter.invert); + constructor.Bind("blur", &data.filter.blur); constructor.Bind("scale", &data.transform.scale); constructor.Bind("rotate_x", &data.transform.rotate.x); diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 18918f4ae..96b4798a9 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -63,6 +63,7 @@ #include "Elements/XMLNodeHandlerTextArea.h" #include "EventInstancerDefault.h" #include "FilterBasic.h" +#include "FilterBlur.h" #include "FontEffectBlur.h" #include "FontEffectGlow.h" #include "FontEffectOutline.h" @@ -156,6 +157,7 @@ struct DefaultInstancers { FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; FilterBasicInstancer filter_basic_d0 = {FilterBasicInstancer::ValueType::NumberPercent, "0"}; FilterBasicInstancer filter_basic_d1 = {FilterBasicInstancer::ValueType::NumberPercent, "1"}; + FilterBlurInstancer filter_blur; // Font effects FontEffectBlurInstancer font_effect_blur; @@ -249,6 +251,8 @@ bool Factory::Initialise() RegisterFilterInstancer("saturate", &default_instancers->filter_basic_d1); RegisterFilterInstancer("sepia", &default_instancers->filter_basic_d0); + RegisterFilterInstancer("blur", &default_instancers->filter_blur); + // Font effect instancers RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur); RegisterFontEffectInstancer("glow", &default_instancers->font_effect_glow); diff --git a/Source/Core/FilterBasic.cpp b/Source/Core/FilterBasic.cpp index 15d243853..c2ab22ad0 100644 --- a/Source/Core/FilterBasic.cpp +++ b/Source/Core/FilterBasic.cpp @@ -63,9 +63,9 @@ FilterBasicInstancer::FilterBasicInstancer(ValueType value_type, const char* def RegisterShorthand("filter", "value", ShorthandType::FallThrough); } -SharedPtr FilterBasicInstancer::InstanceFilter(const String& name, const PropertyDictionary& properties_) +SharedPtr FilterBasicInstancer::InstanceFilter(const String& name, const PropertyDictionary& properties) { - const Property* p_value = properties_.GetProperty(ids.value); + const Property* p_value = properties.GetProperty(ids.value); if (!p_value) return nullptr; diff --git a/Source/Core/FilterBlur.cpp b/Source/Core/FilterBlur.cpp new file mode 100644 index 000000000..269b13edb --- /dev/null +++ b/Source/Core/FilterBlur.cpp @@ -0,0 +1,81 @@ +/* + * 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 "FilterBlur.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +bool FilterBlur::Initialise(NumericValue in_radius) +{ + radius_value = in_radius; + return Any(in_radius.unit & Unit::LENGTH); +} + +CompiledFilterHandle FilterBlur::CompileFilter(Element* element) const +{ + const float radius = element->ResolveLength(radius_value); + CompiledFilterHandle handle = GetRenderInterface()->CompileFilter("blur", Dictionary{{"radius", Variant(radius)}}); + return handle; +} + +void FilterBlur::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const +{ + GetRenderInterface()->ReleaseCompiledFilter(filter_handle); +} + +void FilterBlur::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const +{ + const float radius = element->ResolveLength(radius_value); + const float blur_extent = 1.5f * Math::Max(radius, 1.f); + scissor_region.Extend(blur_extent); +} + +FilterBlurInstancer::FilterBlurInstancer() +{ + ids.radius = RegisterProperty("radius", "0px").AddParser("length").GetId(); + RegisterShorthand("filter", "radius", ShorthandType::FallThrough); +} + +SharedPtr FilterBlurInstancer::InstanceFilter(const String& /*name*/, const PropertyDictionary& properties) +{ + const Property* p_radius = properties.GetProperty(ids.radius); + if (!p_radius) + return nullptr; + + auto decorator = MakeShared(); + if (decorator->Initialise(p_radius->GetNumericValue())) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Source/Core/FilterBlur.h b/Source/Core/FilterBlur.h new file mode 100644 index 000000000..f3bb101f7 --- /dev/null +++ b/Source/Core/FilterBlur.h @@ -0,0 +1,66 @@ +/* + * 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_CORE_FILTERBLUR_H +#define RMLUI_CORE_FILTERBLUR_H + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/NumericValue.h" + +namespace Rml { + +class FilterBlur : public Filter { +public: + bool Initialise(NumericValue radius); + + CompiledFilterHandle CompileFilter(Element* element) const override; + + void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + + void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; + +private: + NumericValue radius_value; +}; + +class FilterBlurInstancer : public FilterInstancer { +public: + FilterBlurInstancer(); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId radius; + }; + PropertyIds ids; +}; + +} // namespace Rml +#endif From 19281fd7d14a6f1ae4a02052e2f5303db025495f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 29 May 2023 01:13:22 +0200 Subject: [PATCH 19/80] Implement drop-shadow filter, integrate filter in GL3 renderer, and add to effect sample --- Backends/RmlUi_Renderer_GL3.cpp | 79 ++++++++++++++- Backends/RmlUi_Renderer_GL3.h | 2 +- CMake/FileList.cmake | 2 + Samples/basic/effect/data/effect.rml | 5 +- Samples/basic/effect/data/effect_style.rcss | 2 +- Samples/basic/effect/src/main.cpp | 2 + Source/Core/Factory.cpp | 3 + Source/Core/FilterDropShadow.cpp | 105 ++++++++++++++++++++ Source/Core/FilterDropShadow.h | 67 +++++++++++++ 9 files changed, 261 insertions(+), 6 deletions(-) create mode 100644 Source/Core/FilterDropShadow.cpp create mode 100644 Source/Core/FilterDropShadow.h diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 249abdb0d..c61fb7281 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -182,6 +182,19 @@ void main() { 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() { + finalColor = texture(_tex, clamp(fragTexCoord, _texCoordMin, _texCoordMax)).a * _color; +} +)"; enum class ProgramId { None, @@ -190,6 +203,7 @@ enum class ProgramId { Passthrough, ColorMatrix, Blur, + DropShadow, Count, }; enum class VertShaderId { @@ -204,12 +218,14 @@ enum class FragShaderId { Passthrough, ColorMatrix, Blur, + DropShadow, Count, }; enum class UniformId { Translate, Transform, Tex, + Color, ColorMatrix, TexelOffset, TexCoordMin, @@ -220,8 +236,8 @@ enum class UniformId { namespace Gfx { -static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color_matrix", "_texelOffset", - "_texCoordMin", "_texCoordMax", "_weights[0]"}; +static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color", "_color_matrix", + "_texelOffset", "_texCoordMin", "_texCoordMax", "_weights[0]"}; enum class VertexAttribute { Position, Color0, TexCoord0, Count }; static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; @@ -255,6 +271,7 @@ static const FragShaderDefinition frag_shader_definitions[] = { {FragShaderId::Passthrough, "passthrough", shader_frag_passthrough}, {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, {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}, @@ -262,6 +279,7 @@ static const ProgramDefinition program_definitions[] = { {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, + {ProgramId::DropShadow, "drop_shadow", VertShaderId::Passthrough, FragShaderId::DropShadow}, }; // clang-format on @@ -1279,7 +1297,7 @@ void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) program_transform_dirty.set(); } -enum class FilterType { Invalid = 0, Passthrough, Blur, ColorMatrix }; +enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix }; struct CompiledFilter { FilterType type; @@ -1289,6 +1307,10 @@ struct CompiledFilter { // Blur float sigma; + // Drop shadow + Rml::Vector2f offset; + Rml::Colourb color; + // ColorMatrix Rml::Matrix4f color_matrix; }; @@ -1307,6 +1329,13 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& filter.type = FilterType::Blur; filter.sigma = 0.5f * Rml::Get(parameters, "radius", 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()); + filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f)); + } else if (name == "brightness") { filter.type = FilterType::ColorMatrix; @@ -1414,6 +1443,16 @@ void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } +static inline Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0) +{ + Rml::Colourf result; + result.alpha = (1.f / 255.f) * float(c0.alpha); + result.red = (1.f / 255.f) * float(c0.red) * result.alpha; + result.green = (1.f / 255.f) * float(c0.green) * result.alpha; + result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha; + return result; +} + void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_handles) { for (const Rml::CompiledFilterHandle filter_handle : filter_handles) @@ -1453,6 +1492,40 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand glEnable(GL_BLEND); } break; + case FilterType::DropShadow: + { + UseProgram(ProgramId::DropShadow); + glDisable(GL_BLEND); + + Rml::Colourf color = ToPremultipliedAlpha(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); diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 660e9bf27..aa472eda7 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -100,7 +100,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); void DrawFullscreenQuad(); - void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling); + 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); diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 26f6f2ad7..d7a556a4e 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -49,6 +49,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FileInterfaceDefault.h ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.h ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.h + ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h @@ -307,6 +308,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Filter.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FilterBasic.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FilterBlur.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/FilterDropShadow.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectBlur.cpp ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.cpp diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index 063e61ef3..781ba40bb 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -76,6 +76,8 @@ .blur { filter: blur(25px); } .back_blur { backdrop-filter: blur(15px); } +.dropshadow { filter: drop-shadow(#f33f 30px 20px 5px); } + Hue{{ hue_rotate }} deg Invert{{ invert*100 }} % Blur{{ blur }} px + Scale{{ scale | format(1) }}x @@ -120,7 +123,7 @@
diff --git a/Samples/basic/effect/data/effect_style.rcss b/Samples/basic/effect/data/effect_style.rcss index 3164c3647..7706b22e1 100644 --- a/Samples/basic/effect/data/effect_style.rcss +++ b/Samples/basic/effect/data/effect_style.rcss @@ -81,7 +81,7 @@ handle.size:hover, handle.size:active { right: 25dp; box-sizing: border-box; width: 400dp; - height: 440dp; + height: 480dp; overflow: auto; background: #fffc; diff --git a/Samples/basic/effect/src/main.cpp b/Samples/basic/effect/src/main.cpp index e08495bd1..b2e9d160a 100644 --- a/Samples/basic/effect/src/main.cpp +++ b/Samples/basic/effect/src/main.cpp @@ -88,6 +88,7 @@ int main(int /*argc*/, char** /*argv*/) float hue_rotate = 0.0f; float invert = 0.0f; float blur = 0.0f; + bool drop_shadow = false; } filter; struct Transform { @@ -113,6 +114,7 @@ int main(int /*argc*/, char** /*argv*/) constructor.Bind("hue_rotate", &data.filter.hue_rotate); constructor.Bind("invert", &data.filter.invert); constructor.Bind("blur", &data.filter.blur); + constructor.Bind("drop_shadow", &data.filter.drop_shadow); constructor.Bind("scale", &data.transform.scale); constructor.Bind("rotate_x", &data.transform.rotate.x); diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 96b4798a9..f36ef41cd 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -64,6 +64,7 @@ #include "EventInstancerDefault.h" #include "FilterBasic.h" #include "FilterBlur.h" +#include "FilterDropShadow.h" #include "FontEffectBlur.h" #include "FontEffectGlow.h" #include "FontEffectOutline.h" @@ -158,6 +159,7 @@ struct DefaultInstancers { FilterBasicInstancer filter_basic_d0 = {FilterBasicInstancer::ValueType::NumberPercent, "0"}; FilterBasicInstancer filter_basic_d1 = {FilterBasicInstancer::ValueType::NumberPercent, "1"}; FilterBlurInstancer filter_blur; + FilterDropShadowInstancer filter_drop_shadow; // Font effects FontEffectBlurInstancer font_effect_blur; @@ -252,6 +254,7 @@ bool Factory::Initialise() RegisterFilterInstancer("sepia", &default_instancers->filter_basic_d0); RegisterFilterInstancer("blur", &default_instancers->filter_blur); + RegisterFilterInstancer("drop-shadow", &default_instancers->filter_drop_shadow); // Font effect instancers RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur); diff --git a/Source/Core/FilterDropShadow.cpp b/Source/Core/FilterDropShadow.cpp new file mode 100644 index 000000000..db8bba2d2 --- /dev/null +++ b/Source/Core/FilterDropShadow.cpp @@ -0,0 +1,105 @@ +/* + * 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 "FilterDropShadow.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +bool FilterDropShadow::Initialise(Colourb in_color, NumericValue in_offset_x, NumericValue in_offset_y, NumericValue in_sigma) +{ + color = in_color; + value_offset_x = in_offset_x; + value_offset_y = in_offset_y; + value_sigma = in_sigma; + return Any(in_offset_x.unit & Unit::LENGTH) && Any(in_offset_y.unit & Unit::LENGTH) && Any(in_sigma.unit & Unit::LENGTH); +} + +CompiledFilterHandle FilterDropShadow::CompileFilter(Element* element) const +{ + const float sigma = element->ResolveLength(value_sigma); + const Vector2f offset = { + element->ResolveLength(value_offset_x), + element->ResolveLength(value_offset_y), + }; + + CompiledFilterHandle handle = GetRenderInterface()->CompileFilter("drop-shadow", + Dictionary{{"color", Variant(color)}, {"offset", Variant(offset)}, {"sigma", Variant(sigma)}}); + + return handle; +} + +void FilterDropShadow::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const +{ + GetRenderInterface()->ReleaseCompiledFilter(filter_handle); +} + +void FilterDropShadow::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const +{ + // Expand the ink overflow area to cover both the native element *and* its offset shadow w/blur. + const float sigma = element->ResolveLength(value_sigma); + const Vector2f offset = { + element->ResolveLength(value_offset_x), + element->ResolveLength(value_offset_y), + }; + + const float blur_radius = 2.f * sigma; + const float blur_extent = 1.5f * blur_radius; + scissor_region.ExtendTopLeft(Math::Max(-offset, Vector2f(0.f)) + Vector2f(blur_extent)); + scissor_region.ExtendBottomRight(Math::Max(offset, Vector2f(0.f)) + Vector2f(blur_extent)); +} + +FilterDropShadowInstancer::FilterDropShadowInstancer() +{ + ids.color = RegisterProperty("color", "transparent").AddParser("color").GetId(); + ids.offset_x = RegisterProperty("offset-x", "0px").AddParser("length").GetId(); + ids.offset_y = RegisterProperty("offset-y", "0px").AddParser("length").GetId(); + ids.sigma = RegisterProperty("sigma", "0px").AddParser("length").GetId(); + RegisterShorthand("filter", "color, offset-x, offset-y, sigma", ShorthandType::FallThrough); +} + +SharedPtr FilterDropShadowInstancer::InstanceFilter(const String& /*name*/, const PropertyDictionary& properties) +{ + const Property* p_color = properties.GetProperty(ids.color); + const Property* p_offset_x = properties.GetProperty(ids.offset_x); + const Property* p_offset_y = properties.GetProperty(ids.offset_y); + const Property* p_sigma = properties.GetProperty(ids.sigma); + if (!p_color || !p_offset_x || !p_offset_y || !p_sigma) + return nullptr; + + auto decorator = MakeShared(); + if (decorator->Initialise(p_color->Get(), p_offset_x->GetNumericValue(), p_offset_y->GetNumericValue(), p_sigma->GetNumericValue())) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Source/Core/FilterDropShadow.h b/Source/Core/FilterDropShadow.h new file mode 100644 index 000000000..0e15f1805 --- /dev/null +++ b/Source/Core/FilterDropShadow.h @@ -0,0 +1,67 @@ +/* + * 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_CORE_FILTERDROPSHADOW_H +#define RMLUI_CORE_FILTERDROPSHADOW_H + +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/NumericValue.h" + +namespace Rml { + +class FilterDropShadow : public Filter { +public: + bool Initialise(Colourb color, NumericValue offset_x, NumericValue offset_y, NumericValue sigma); + + CompiledFilterHandle CompileFilter(Element* element) const override; + + void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + + void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; + +private: + Colourb color; + NumericValue value_offset_x, value_offset_y, value_sigma; +}; + +class FilterDropShadowInstancer : public FilterInstancer { +public: + FilterDropShadowInstancer(); + + SharedPtr InstanceFilter(const String& name, const PropertyDictionary& properties) override; + +private: + struct PropertyIds { + PropertyId color, offset_x, offset_y, sigma; + }; + PropertyIds ids; +}; + +} // namespace Rml +#endif From a30d3d57362e228b62aa6eadf835affcd092b161 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Thu, 1 Jun 2023 14:05:12 +0200 Subject: [PATCH 20/80] Clarify Context::GetElementAtPoint --- Source/Core/Context.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Source/Core/Context.cpp b/Source/Core/Context.cpp index f86d79a73..c4a52f3d2 100644 --- a/Source/Core/Context.cpp +++ b/Source/Core/Context.cpp @@ -1160,7 +1160,7 @@ void Context::UpdateHoverChain(Vector2i old_mouse_position, int key_modifier_sta Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_element, Element* element) const { - if (element == nullptr) + if (!element) { if (ignore_element == root.get()) return nullptr; @@ -1174,7 +1174,7 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen if (focus) { ElementDocument* focus_document = focus->GetOwnerDocument(); - if (focus_document != nullptr && focus_document->IsModal()) + if (focus_document && focus_document->IsModal()) { element = focus_document; } @@ -1190,10 +1190,11 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen for (int i = (int)element->stacking_context.size() - 1; i >= 0; --i) { - if (ignore_element != nullptr) + if (ignore_element) { + // Check if the element is a descendant of the element we're ignoring. Element* element_hierarchy = element->stacking_context[i]; - while (element_hierarchy != nullptr) + while (element_hierarchy) { if (element_hierarchy == ignore_element) break; @@ -1201,12 +1202,12 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen element_hierarchy = element_hierarchy->GetParentNode(); } - if (element_hierarchy != nullptr) + if (element_hierarchy) continue; } Element* child_element = GetElementAtPoint(point, ignore_element, element->stacking_context[i]); - if (child_element != nullptr) + if (child_element) return child_element; } } @@ -1222,6 +1223,7 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen bool within_element = (projection_result && element->IsPointWithinElement(point)); if (within_element) { + // The element may have been clipped out of view if it overflows an ancestor, so check its clipping region. Vector2i clip_origin, clip_dimensions; if (ElementUtilities::GetClippingRegion(clip_origin, clip_dimensions, element)) { From 3429285e392ba9921df337cd95d060accd130bc3 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Fri, 2 Jun 2023 01:39:34 +0200 Subject: [PATCH 21/80] Add geometry utility for creating background geometry on any target area with border radius support --- Include/RmlUi/Core/ComputedValues.h | 2 + Include/RmlUi/Core/GeometryUtilities.h | 19 +- Source/Core/DecoratorGradient.cpp | 8 +- Source/Core/ElementBackgroundBorder.cpp | 6 +- Source/Core/GeometryBackgroundBorder.cpp | 212 ++++++++--------------- Source/Core/GeometryBackgroundBorder.h | 45 +++-- Source/Core/GeometryUtilities.cpp | 112 +++++++++++- 7 files changed, 233 insertions(+), 171 deletions(-) diff --git a/Include/RmlUi/Core/ComputedValues.h b/Include/RmlUi/Core/ComputedValues.h index a27bee029..58f7792a4 100644 --- a/Include/RmlUi/Core/ComputedValues.h +++ b/Include/RmlUi/Core/ComputedValues.h @@ -293,6 +293,8 @@ namespace Style { float border_top_right_radius() const { return (float)rare.border_top_right_radius; } float border_bottom_right_radius() const { return (float)rare.border_bottom_right_radius; } float border_bottom_left_radius() const { return (float)rare.border_bottom_left_radius; } + Vector4f border_radius() const { return {(float)rare.border_top_left_radius, (float)rare.border_top_right_radius, + (float)rare.border_bottom_right_radius, (float)rare.border_bottom_left_radius}; } Clip clip() const { return rare.clip; } Drag drag() const { return rare.drag; } TabIndex tab_index() const { return rare.tab_index; } diff --git a/Include/RmlUi/Core/GeometryUtilities.h b/Include/RmlUi/Core/GeometryUtilities.h index f5eb43e12..691f89de0 100644 --- a/Include/RmlUi/Core/GeometryUtilities.h +++ b/Include/RmlUi/Core/GeometryUtilities.h @@ -74,16 +74,27 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] color The color to draw the line in. static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color); - /// Generates a geometry in the same way as element backgrounds and borders are generated, with support for the border-radius property. - /// Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. + /// Generates the geometry for an element's background and border, with support for the border-radius property. /// @param[out] geometry The geometry to append the newly created vertices and indices into. /// @param[in] box The box which determines the background and border geometry. /// @param[in] offset Offset the position of the generated vertices. /// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left. /// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background. - /// @param[in] border_colours Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders. + /// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order. + /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour, - const Colourb* border_colours = nullptr); + const Colourb border_colours[4]); + + /// Generates the background geometry for an element's area, with support for border-radius. + /// @param[out] geometry The geometry to append the newly created vertices and indices into. + /// @param[in] box The box which determines the background geometry. + /// @param[in] offset Offset the position of the generated vertices. + /// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left. + /// @param[in] colour The colour applied to the background. + /// @param[in] area Either the border, padding or content area to be filled. + /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. + static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb colour, + BoxArea area = BoxArea::Padding); private: GeometryUtilities(); diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index f1c9f3510..254d8c054 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -67,13 +67,7 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con const ComputedValues& computed = element->GetComputedValues(); const float opacity = computed.opacity(); - const Vector4f border_radius{ - computed.border_top_left_radius(), - computed.border_top_right_radius(), - computed.border_bottom_right_radius(), - computed.border_bottom_left_radius(), - }; - GeometryUtilities::GenerateBackgroundBorder(geometry, element->GetBox(), Vector2f(0), border_radius, Colourb()); + GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb()); // Apply opacity Colourb colour_start = start; diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 23686923d..4f345cae8 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -71,6 +71,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) computed.border_bottom_color(), computed.border_left_color(), }; + const Vector4f border_radius = computed.border_radius(); // Apply opacity const float opacity = computed.opacity(); @@ -85,14 +86,11 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) geometry.GetVertices().clear(); geometry.GetIndices().clear(); - const Vector4f radii(computed.border_top_left_radius(), computed.border_top_right_radius(), computed.border_bottom_right_radius(), - computed.border_bottom_left_radius()); - for (int i = 0; i < element->GetNumBoxes(); i++) { Vector2f offset; const Box& box = element->GetBox(i, offset); - GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, radii, background_color, border_colors); + GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors); } geometry.Release(); diff --git a/Source/Core/GeometryBackgroundBorder.cpp b/Source/Core/GeometryBackgroundBorder.cpp index 69f0deb57..18c92953f 100644 --- a/Source/Core/GeometryBackgroundBorder.cpp +++ b/Source/Core/GeometryBackgroundBorder.cpp @@ -36,185 +36,121 @@ namespace Rml { GeometryBackgroundBorder::GeometryBackgroundBorder(Vector& vertices, Vector& indices) : vertices(vertices), indices(indices) {} -void GeometryBackgroundBorder::Draw(Vector& vertices, Vector& indices, CornerSizes radii, const Box& box, const Vector2f offset, - const Colourb background_color, const Colourb* border_colors) +BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, + Vector4f outer_radii_def) { - EdgeSizes border_widths = { - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Top)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Right)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Bottom)), - Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Left)), - }; - - int num_borders = 0; - - if (border_colors) - { - for (int i = 0; i < 4; i++) - if (border_colors[i].alpha > 0 && border_widths[i] > 0) - num_borders += 1; - } - - const Vector2f padding_size = box.GetSize(BoxArea::Padding).Round(); - - const bool has_background = (background_color.alpha > 0 && padding_size.x > 0 && padding_size.y > 0); - const bool has_border = (num_borders > 0); - - if (!has_background && !has_border) - return; + BorderMetrics metrics = {}; // -- Find the corner positions -- - const Vector2f border_position = offset.Round(); - const Vector2f padding_position = border_position + Vector2f(border_widths[Edge::LEFT], border_widths[Edge::TOP]); - const Vector2f border_size = - padding_size + Vector2f(border_widths[Edge::LEFT] + border_widths[Edge::RIGHT], border_widths[Edge::TOP] + border_widths[Edge::BOTTOM]); - - // Border edge positions - CornerPositions positions_outer = { - border_position, - border_position + Vector2f(border_size.x, 0), - border_position + border_size, - border_position + Vector2f(0, border_size.y), + const Vector2f inner_position = outer_position + Vector2f(edge_sizes[LEFT], edge_sizes[TOP]); + const Vector2f outer_size = inner_size + Vector2f(edge_sizes[LEFT] + edge_sizes[RIGHT], edge_sizes[TOP] + edge_sizes[BOTTOM]); + + metrics.positions_outer = { + outer_position, + outer_position + Vector2f(outer_size.x, 0), + outer_position + outer_size, + outer_position + Vector2f(0, outer_size.y), }; - // Padding edge positions - CornerPositions positions_inner = { - padding_position, - padding_position + Vector2f(padding_size.x, 0), - padding_position + padding_size, - padding_position + Vector2f(0, padding_size.y), + metrics.positions_inner = { + inner_position, + inner_position + Vector2f(inner_size.x, 0), + inner_position + inner_size, + inner_position + Vector2f(0, inner_size.y), }; // -- For curved borders, find the positions to draw ellipses around, and the scaled outer and inner radii -- - const float sum_radius = (radii[TOP_LEFT] + radii[TOP_RIGHT] + radii[BOTTOM_RIGHT] + radii[BOTTOM_LEFT]); + const float sum_radius = (outer_radii_def[TOP_LEFT] + outer_radii_def[TOP_RIGHT] + outer_radii_def[BOTTOM_RIGHT] + outer_radii_def[BOTTOM_LEFT]); const bool has_radius = (sum_radius > 1.f); - // Curved borders are drawn as circles (outer border) and ellipses (inner border) around the centers. - CornerPositions positions_circle_center; - - // Radii of the padding edges, 2-dimensional as these can be ellipses. - // The inner radii is effectively the (signed) distance from the circle center to the padding edge. - // They can also be zero or negative, in which case a sharp corner should be drawn instead of an arc. - CornerSizes2 inner_radii; - if (has_radius) { + auto& outer_radii = metrics.outer_radii; + outer_radii = {outer_radii_def.x, outer_radii_def.y, outer_radii_def.z, outer_radii_def.w}; + // Scale the radii such that we have no overlapping curves. float scale_factor = FLT_MAX; - scale_factor = Math::Min(scale_factor, padding_size.x / (radii[TOP_LEFT] + radii[TOP_RIGHT])); // Top - scale_factor = Math::Min(scale_factor, padding_size.y / (radii[TOP_RIGHT] + radii[BOTTOM_RIGHT])); // Right - scale_factor = Math::Min(scale_factor, padding_size.x / (radii[BOTTOM_RIGHT] + radii[BOTTOM_LEFT])); // Bottom - scale_factor = Math::Min(scale_factor, padding_size.y / (radii[BOTTOM_LEFT] + radii[TOP_LEFT])); // Left - + scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[TOP_LEFT] + outer_radii[TOP_RIGHT])); // Top + scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[TOP_RIGHT] + outer_radii[BOTTOM_RIGHT])); // Right + scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[BOTTOM_RIGHT] + outer_radii[BOTTOM_LEFT])); // Bottom + scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[BOTTOM_LEFT] + outer_radii[TOP_LEFT])); // Left scale_factor = Math::Min(1.0f, scale_factor); - for (float& radius : radii) + for (float& radius : outer_radii) radius = Math::Round(radius * scale_factor); // Place the circle/ellipse centers - positions_circle_center = { - positions_outer[TOP_LEFT] + Vector2f(1, 1) * radii[TOP_LEFT], - positions_outer[TOP_RIGHT] + Vector2f(-1, 1) * radii[TOP_RIGHT], - positions_outer[BOTTOM_RIGHT] + Vector2f(-1, -1) * radii[BOTTOM_RIGHT], - positions_outer[BOTTOM_LEFT] + Vector2f(1, -1) * radii[BOTTOM_LEFT], + metrics.positions_circle_center = { + metrics.positions_outer[TOP_LEFT] + Vector2f(1, 1) * outer_radii[TOP_LEFT], + metrics.positions_outer[TOP_RIGHT] + Vector2f(-1, 1) * outer_radii[TOP_RIGHT], + metrics.positions_outer[BOTTOM_RIGHT] + Vector2f(-1, -1) * outer_radii[BOTTOM_RIGHT], + metrics.positions_outer[BOTTOM_LEFT] + Vector2f(1, -1) * outer_radii[BOTTOM_LEFT], }; - inner_radii = { - Vector2f(radii[TOP_LEFT]) - Vector2f(border_widths[Edge::LEFT], border_widths[Edge::TOP]), - Vector2f(radii[TOP_RIGHT]) - Vector2f(border_widths[Edge::RIGHT], border_widths[Edge::TOP]), - Vector2f(radii[BOTTOM_RIGHT]) - Vector2f(border_widths[Edge::RIGHT], border_widths[Edge::BOTTOM]), - Vector2f(radii[BOTTOM_LEFT]) - Vector2f(border_widths[Edge::LEFT], border_widths[Edge::BOTTOM]), + metrics.inner_radii = { + Vector2f(outer_radii[TOP_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[TOP]), + Vector2f(outer_radii[TOP_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[TOP]), + Vector2f(outer_radii[BOTTOM_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[BOTTOM]), + Vector2f(outer_radii[BOTTOM_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[BOTTOM]), }; } - // -- Generate the geometry -- - - GeometryBackgroundBorder geometry(vertices, indices); + return metrics; +} - { - // Reserve geometry. A conservative estimate, does not take border-radii into account and assumes same-colored borders. - const int estimated_num_vertices = 4 * int(has_background) + 2 * num_borders; - const int estimated_num_triangles = 2 * int(has_background) + 2 * num_borders; +void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colourb color) +{ + const int offset_vertices = (int)vertices.size(); - vertices.reserve((int)vertices.size() + estimated_num_vertices); - indices.reserve((int)indices.size() + 3 * estimated_num_triangles); - } + for (int corner = 0; corner < 4; corner++) + DrawBackgroundCorner(Corner(corner), metrics.positions_inner[corner], metrics.positions_circle_center[corner], metrics.outer_radii[corner], + metrics.inner_radii[corner], color); - // Draw the background - if (has_background) - { - const int offset_vertices = (int)vertices.size(); + FillBackground(offset_vertices); +} - for (int corner = 0; corner < 4; corner++) - geometry.DrawBackgroundCorner(Corner(corner), positions_inner[corner], positions_circle_center[corner], radii[corner], - inner_radii[corner], background_color); +void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]) +{ + RMLUI_ASSERT(border_colors); - geometry.FillBackground(offset_vertices); - } + const int offset_vertices = (int)vertices.size(); - // Draw the border - if (has_border) - { - const int offset_vertices = (int)vertices.size(); + const bool draw_edge[4] = { + edge_sizes[TOP] > 0 && border_colors[TOP].alpha > 0, + edge_sizes[RIGHT] > 0 && border_colors[RIGHT].alpha > 0, + edge_sizes[BOTTOM] > 0 && border_colors[BOTTOM].alpha > 0, + edge_sizes[LEFT] > 0 && border_colors[LEFT].alpha > 0, + }; - const bool draw_edge[4] = { - border_widths[Edge::TOP] > 0 && border_colors[Edge::TOP].alpha > 0, - border_widths[Edge::RIGHT] > 0 && border_colors[Edge::RIGHT].alpha > 0, - border_widths[Edge::BOTTOM] > 0 && border_colors[Edge::BOTTOM].alpha > 0, - border_widths[Edge::LEFT] > 0 && border_colors[Edge::LEFT].alpha > 0, - }; + const bool draw_corner[4] = { + draw_edge[TOP] || draw_edge[LEFT], + draw_edge[TOP] || draw_edge[RIGHT], + draw_edge[BOTTOM] || draw_edge[RIGHT], + draw_edge[BOTTOM] || draw_edge[LEFT], + }; - const bool draw_corner[4] = { - draw_edge[Edge::TOP] || draw_edge[Edge::LEFT], - draw_edge[Edge::TOP] || draw_edge[Edge::RIGHT], - draw_edge[Edge::BOTTOM] || draw_edge[Edge::RIGHT], - draw_edge[Edge::BOTTOM] || draw_edge[Edge::LEFT], - }; + for (int corner = 0; corner < 4; corner++) + { + const Edge edge0 = Edge((corner + 3) % 4); + const Edge edge1 = Edge(corner); - for (int corner = 0; corner < 4; corner++) + if (draw_corner[corner]) { - const Edge edge0 = Edge((corner + 3) % 4); - const Edge edge1 = Edge(corner); - - if (draw_corner[corner]) - geometry.DrawBorderCorner(Corner(corner), positions_outer[corner], positions_inner[corner], positions_circle_center[corner], - radii[corner], inner_radii[corner], border_colors[edge0], border_colors[edge1]); - - if (draw_edge[edge1]) - { - RMLUI_ASSERTMSG(draw_corner[corner] && draw_corner[(corner + 1) % 4], - "Border edges can only be drawn if both of its connected corners are drawn."); - geometry.FillEdge(edge1 == Edge::LEFT ? offset_vertices : (int)vertices.size()); - } + DrawBorderCorner(Corner(corner), metrics.positions_outer[corner], metrics.positions_inner[corner], + metrics.positions_circle_center[corner], metrics.outer_radii[corner], metrics.inner_radii[corner], border_colors[edge0], + border_colors[edge1]); } - } - -#if 0 - // Debug draw vertices - if (has_radius) - { - const int num_vertices = vertices.size(); - const int num_indices = indices.size(); - - vertices.resize(num_vertices + 4 * num_vertices); - indices.resize(num_indices + 6 * num_indices); - for (int i = 0; i < num_vertices; i++) + if (draw_edge[edge1]) { - GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); - } - } -#endif + RMLUI_ASSERTMSG(draw_corner[corner] && draw_corner[(corner + 1) % 4], + "Border edges can only be drawn if both of its connected corners are drawn."); -#ifdef RMLUI_DEBUG - const int num_vertices = (int)vertices.size(); - for (int index : indices) - { - RMLUI_ASSERT(index < num_vertices); + FillEdge(edge1 == LEFT ? offset_vertices : (int)vertices.size()); + } } -#endif } void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color) diff --git a/Source/Core/GeometryBackgroundBorder.h b/Source/Core/GeometryBackgroundBorder.h index 647693578..6b6dad9ab 100644 --- a/Source/Core/GeometryBackgroundBorder.h +++ b/Source/Core/GeometryBackgroundBorder.h @@ -44,25 +44,46 @@ using CornerSizes = Array; using CornerSizes2 = Array; using CornerPositions = Array; +// The background-border metrics specify an inner and an outer rectangular area, whose corners can be rounded. +struct BorderMetrics { + // Outer corner positions (e.g. at border edge) + CornerPositions positions_outer; + // Inner corner positions (e.g. at padding edge) + CornerPositions positions_inner; + // Curved borders are drawn as circles (outer border) and ellipses (inner border) around the centers. + CornerPositions positions_circle_center; + + // Radii of the outer edges, always circles. + CornerSizes outer_radii; + // Radii of the inner edges, 2-dimensional as these can be elliptic. + // The inner radii is effectively the (signed) distance from the circle center to the inner edge. + // They can also be zero or negative, in which case a sharp corner should be drawn instead of an arc. + CornerSizes2 inner_radii; +}; + class GeometryBackgroundBorder { public: - /// Generate geometry for background and borders. - /// @param[out] vertices Destination vector for generated vertices. - /// @param[out] indices Destination vector for generated indices. - /// @param[in] radii The radius of each corner. - /// @param[in] box The box used for positioning and sizing of the background and borders. - /// @param[in] offset Offset the position of the generated vertices. - /// @param[in] background_color Color of the background, set alpha to zero to not generate a background. - /// @param[in] border_colors Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders. - static void Draw(Vector& vertices, Vector& indices, CornerSizes radii, const Box& box, Vector2f offset, Colourb background_color, - const Colourb* border_colors); + // Construct the background-border geometry drawer, passing in the target vertex and index lists to be filled by later draw operations. + GeometryBackgroundBorder(Vector& vertices, Vector& indices); + + /// Compute background-border metrics used by later draw operations. + /// @param outer_position Top-left position of the outer edge. + /// @param edge_sizes Widths of the border. + /// @param inner_size Size of the inner area. + /// @param outer_radii The radius of the outer edge at each corner. + /// @return The computed metrics. + static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, Vector4f outer_radii); + + // Generate geometry for the background, defined by the inner area of the border metrics. + void DrawBackground(const BorderMetrics& metrics, Colourb color); + + /// Generate geometry for the border, defined by the intersection of the outer and inner areas of the border metrics. + void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]); private: enum Edge { TOP, RIGHT, BOTTOM, LEFT }; enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT }; - GeometryBackgroundBorder(Vector& vertices, Vector& indices); - // -- Background -- // All draw operations place vertices in clockwise order. diff --git a/Source/Core/GeometryUtilities.cpp b/Source/Core/GeometryUtilities.cpp index 511868398..efab06e24 100644 --- a/Source/Core/GeometryUtilities.cpp +++ b/Source/Core/GeometryUtilities.cpp @@ -27,6 +27,7 @@ */ #include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/Box.h" #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" #include "../../Include/RmlUi/Core/Geometry.h" @@ -88,14 +89,113 @@ void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vect GeometryUtilities::GenerateQuad(line_vertices.data() + vertices_i0, line_indices.data() + indices_i0, position, size, color, vertices_i0); } -void GeometryUtilities::GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, - Colourb background_colour, const Colourb* border_colours) +void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, + Colourb background_color, const Colourb border_colors[4]) { - Vector& vertices = geometry->GetVertices(); - Vector& indices = geometry->GetIndices(); + RMLUI_ASSERT(border_colors); + + Vector& vertices = out_geometry->GetVertices(); + Vector& indices = out_geometry->GetIndices(); + + EdgeSizes border_widths = { + // TODO: Move rounding to computed values (round border only). + Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Top)), + Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Right)), + Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Bottom)), + Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Left)), + }; + + int num_borders = 0; + + for (int i = 0; i < 4; i++) + if (border_colors[i].alpha > 0 && border_widths[i] > 0) + num_borders += 1; + + const Vector2f padding_size = box.GetSize(BoxArea::Padding).Round(); + + const bool has_background = (background_color.alpha > 0 && padding_size.x > 0 && padding_size.y > 0); + const bool has_border = (num_borders > 0); + + if (!has_background && !has_border) + return; + + // Reserve geometry. A conservative estimate, does not take border-radii into account and assumes same-colored borders. + const int estimated_num_vertices = 4 * int(has_background) + 2 * num_borders; + const int estimated_num_triangles = 2 * int(has_background) + 2 * num_borders; + vertices.reserve((int)vertices.size() + estimated_num_vertices); + indices.reserve((int)indices.size() + 3 * estimated_num_triangles); + + // Generate the geometry. + GeometryBackgroundBorder geometry(vertices, indices); + const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), border_widths, padding_size, border_radius); + + if (has_background) + geometry.DrawBackground(metrics, background_color); + + if (has_border) + geometry.DrawBorder(metrics, border_widths, border_colors); + +#if 0 + // Debug draw vertices + if (border_radius != Vector4f(0)) + { + const int num_vertices = (int)vertices.size(); + const int num_indices = (int)indices.size(); + + vertices.resize(num_vertices + 4 * num_vertices); + indices.resize(num_indices + 6 * num_indices); + + for (int i = 0; i < num_vertices; i++) + { + GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, + Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); + } + } +#endif + +#ifdef RMLUI_DEBUG + const int num_vertices = (int)vertices.size(); + for (int index : indices) + { + RMLUI_ASSERT(index < num_vertices); + } +#endif +} + +void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb color, + BoxArea fill_area) +{ + RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, + "Rectangle geometry only supports border, padding and content boxes."); + + EdgeSizes edge_sizes = {}; + for (int area = (int)BoxArea::Border; area < (int)fill_area; area++) + { + // TODO: Move rounding to computed values (round border only). + edge_sizes[0] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Top)); + edge_sizes[1] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Right)); + edge_sizes[2] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Bottom)); + edge_sizes[3] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Left)); + } + + const Vector2f inner_size = box.GetSize(fill_area).Round(); + + const bool has_background = (color.alpha > 0 && inner_size.x > 0 && inner_size.y > 0); + if (!has_background) + return; + + const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), edge_sizes, inner_size, border_radius); + + Vector& vertices = out_geometry->GetVertices(); + Vector& indices = out_geometry->GetIndices(); + + // Reserve geometry. A conservative estimate, does not take border-radii into account. + vertices.reserve((int)vertices.size() + 4); + indices.reserve((int)indices.size() + 6); - CornerSizes corner_sizes{border_radius.x, border_radius.y, border_radius.z, border_radius.w}; - GeometryBackgroundBorder::Draw(vertices, indices, corner_sizes, box, offset, background_colour, border_colours); + // Generate the geometry + GeometryBackgroundBorder geometry(vertices, indices); + geometry.DrawBackground(metrics, color); } } // namespace Rml From 69ea397f4f473cccfe945b4f86bf77c1ad493c48 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 3 Jun 2023 00:07:13 +0200 Subject: [PATCH 22/80] Add clip mask to render interface, introduce render manager to keep track of the render state - The clip mask can be rendered to using normal geometry, then during other render commands the clip mask should hide any contents outside its area. - Improved element clipping behavior: Handles more complicated cases, including nested transforms with hidden overflow, and clips to the curved edge of elements with border-radius. - Text culling now also considers the viewport and properly handles transforms. Previously, text was not rendered in some situations, or unnecessarily rendered outside the window. - Clip mask implemented in GL3 renderer using stencil buffer. - Added and modified visual tests for clipping behavior. --- Backends/RmlUi_Renderer_GL3.cpp | 59 ++++++- Backends/RmlUi_Renderer_GL3.h | 3 + CMake/FileList.cmake | 2 + Include/RmlUi/Config/Config.h | 3 + Include/RmlUi/Core.h | 1 + Include/RmlUi/Core/Context.h | 15 +- Include/RmlUi/Core/Element.h | 3 + Include/RmlUi/Core/ElementUtilities.h | 21 ++- Include/RmlUi/Core/Geometry.h | 6 + Include/RmlUi/Core/RenderInterface.h | 15 ++ Include/RmlUi/Core/RenderManager.h | 100 +++++++++++ Source/Core/Context.cpp | 37 ++--- Source/Core/Element.cpp | 5 + Source/Core/ElementBackgroundBorder.cpp | 50 +++++- Source/Core/ElementBackgroundBorder.h | 14 +- Source/Core/ElementDecoration.cpp | 33 ++-- Source/Core/ElementText.cpp | 37 +++-- Source/Core/ElementUtilities.cpp | 153 ++++++++--------- Source/Core/Geometry.cpp | 25 ++- Source/Core/RenderInterface.cpp | 4 + Source/Core/RenderManager.cpp | 156 ++++++++++++++++++ Tests/Data/VisualTests/clip_mask.rml | 71 ++++++++ .../position_absolute_transform.rml | 2 + 23 files changed, 644 insertions(+), 171 deletions(-) create mode 100644 Include/RmlUi/Core/RenderManager.h create mode 100644 Source/Core/RenderManager.cpp create mode 100644 Tests/Data/VisualTests/clip_mask.rml diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index c61fb7281..83c6093e2 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -978,6 +978,63 @@ void RenderInterface_GL3::SetScissorRegion(int x, int y, int width, int height) SetScissor(Rml::Rectanglei::FromPositionSize({x, y}, {width, height})); } +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; + } + + RenderCompiledGeometry(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 { @@ -1293,7 +1350,7 @@ void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) { - transform = projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity()); + transform = (new_transform ? (projection * (*new_transform)) : projection); program_transform_dirty.set(); } diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index aa472eda7..83ace8d02 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -72,6 +72,9 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void EnableScissorRegion(bool enable) override; void SetScissorRegion(int x, int y, int width, int height) override; + void EnableClipMask(bool enable) override; + void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; + bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; void ReleaseTexture(Rml::TextureHandle texture_handle) override; diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index d7a556a4e..707e7426d 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -192,6 +192,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Spritesheet.h @@ -360,6 +361,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterface.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/RenderManager.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Spritesheet.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Stream.cpp diff --git a/Include/RmlUi/Config/Config.h b/Include/RmlUi/Config/Config.h index d5c025826..15d984a3e 100644 --- a/Include/RmlUi/Config/Config.h +++ b/Include/RmlUi/Config/Config.h @@ -47,6 +47,7 @@ #include #include #include + #include #include #include #include @@ -87,6 +88,8 @@ using Queue = std::queue; template using Pair = std::pair; template +using StableMap = std::map; +template using UnorderedMultimap = std::unordered_multimap; #ifdef RMLUI_NO_THIRDPARTY_CONTAINERS diff --git a/Include/RmlUi/Core.h b/Include/RmlUi/Core.h index 3e34cbead..bd0ffc087 100644 --- a/Include/RmlUi/Core.h +++ b/Include/RmlUi/Core.h @@ -76,6 +76,7 @@ #include "Core/PropertyParser.h" #include "Core/PropertySpecification.h" #include "Core/RenderInterface.h" +#include "Core/RenderManager.h" #include "Core/Spritesheet.h" #include "Core/StringUtilities.h" #include "Core/StyleSheet.h" diff --git a/Include/RmlUi/Core/Context.h b/Include/RmlUi/Core/Context.h index 9b59969d8..48a8591db 100644 --- a/Include/RmlUi/Core/Context.h +++ b/Include/RmlUi/Core/Context.h @@ -31,6 +31,7 @@ #include "Header.h" #include "Input.h" +#include "RenderManager.h" #include "ScriptInterface.h" #include "ScrollTypes.h" #include "Traits.h" @@ -246,14 +247,8 @@ class RMLUICORE_API Context : public ScriptInterface { /// @param[in] speed_factor A factor for adjusting the final smooth scrolling speed, must be strictly positive, defaults to 1.0. void SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float speed_factor); - /// Gets the current clipping region for the render traversal - /// @param[out] origin The clipping origin - /// @param[out] dimensions The clipping dimensions - bool GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const; - /// Sets the current clipping region for the render traversal - /// @param[out] origin The clipping origin - /// @param[out] dimensions The clipping dimensions - void SetActiveClipRegion(Vector2i origin, Vector2i dimensions); + /// Retrieves the render manager which can be used to submit changes to the render state. + RenderManager& GetRenderManager(); /// Sets the instancer to use for releasing this object. /// @param[in] instancer The context's instancer. @@ -369,8 +364,8 @@ class RMLUICORE_API Context : public ScriptInterface { // itself can't be part of it. ElementSet drag_hover_chain; - Vector2i clip_origin; - Vector2i clip_dimensions; + // Wrapper around the render interface for tracking the render state. + RenderManager render_manager; using DataModels = UnorderedMap>; DataModels data_models; diff --git a/Include/RmlUi/Core/Element.h b/Include/RmlUi/Core/Element.h index fb7191027..883371180 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -50,6 +50,7 @@ class Decorator; class ElementInstancer; class EventDispatcher; class EventListener; +class ElementBackgroundBorder; class ElementDecoration; class ElementDefinition; class ElementDocument; @@ -571,6 +572,8 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr& GetVertices(); diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 84b205d56..dee8b726c 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -37,6 +37,11 @@ namespace Rml { +enum class ClipMaskOperation { + Set, // Set the clip mask to the area of the rendered geometry, clearing any existing clip mask. + SetInverse, // Set the clip mask to the area *outside* the rendered geometry, clearing any existing clip mask. + Intersect, // Intersect the clip mask with the area of the rendered geometry. +}; enum class LayerFill { None, // No operation necessary, does not care about the layer color. Clear, // Clear the layer to transparent black. @@ -102,6 +107,16 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] height The height of the scissored region. All pixels to below (y + height) should be clipped. virtual void SetScissorRegion(int x, int y, int width, int height) = 0; + /// Called by RmlUi when it wants to enable or disable the clip mask. + /// @param[in] enable True if the clip mask is to be enabled, false if it is to be disabled. + /// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask. + virtual void EnableClipMask(bool enable); + /// Called by RmlUi when it wants to set or modify the contents of the clip mask. + /// @param[in] operation Describes how the geometry should affect the clip mask. + /// @param[in] geometry The compiled geometry to render. + /// @param[in] translation The translation to apply to the geometry. + virtual void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation); + /// Called by RmlUi when a texture is required by the library. /// @param[out] texture_handle The handle to write the texture handle for the loaded texture to. /// @param[out] texture_dimensions The variable to write the dimensions of the loaded texture. diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h new file mode 100644 index 000000000..cea43fad2 --- /dev/null +++ b/Include/RmlUi/Core/RenderManager.h @@ -0,0 +1,100 @@ +/* + * 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_CORE_RENDERMANAGER_H +#define RMLUI_CORE_RENDERMANAGER_H + +#include "Box.h" +#include "RenderInterface.h" +#include "Types.h" + +namespace Rml { + +class Geometry; + +struct ClipMaskGeometry { + ClipMaskOperation operation; + Geometry* geometry; + Vector2f absolute_offset; + const Matrix4f* transform; +}; +inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) +{ + return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; +} +inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) +{ + return !(a == b); +} +using ClipMaskGeometryList = Vector; + +struct RenderState { + Rectanglei scissor_region = Rectanglei::MakeInvalid(); + ClipMaskGeometryList clip_mask_list; + Matrix4f transform = Matrix4f::Identity(); +}; + +/** + A wrapper over the render interface which tracks the following state: + - Scissor + - Clip mask + - Transform + All such operations on the render interface should go through this class. + */ +class RMLUICORE_API RenderManager : NonCopyMoveable { +public: + RenderManager(); + + void DisableScissorRegion(); + void SetScissorRegion(Rectanglei region); + + void DisableClipMask(); + void SetClipMask(ClipMaskGeometryList clip_elements); + void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); + + void SetTransform(const Matrix4f* new_transform); + + const RenderState& GetState() const { return state; } + void SetState(const RenderState& next); + void ResetState(); + + void BeginRender(); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; + +private: + void ApplyClipMask(const ClipMaskGeometryList& clip_elements); + + RenderState state; + RenderInterface* render_interface = nullptr; + Vector2i viewport_dimensions; +}; + +} // namespace Rml + +#endif diff --git a/Source/Core/Context.cpp b/Source/Core/Context.cpp index c4a52f3d2..16233dd05 100644 --- a/Source/Core/Context.cpp +++ b/Source/Core/Context.cpp @@ -55,8 +55,7 @@ static constexpr float DOUBLE_CLICK_MAX_DIST = 3.f; // [dp] static constexpr float UNIT_SCROLL_LENGTH = 80.f; // [dp] Context::Context(const String& name) : - name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), clip_origin(-1, -1), clip_dimensions(-1, -1), - next_update_timeout(0) + name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), next_update_timeout(0) { instancer = nullptr; @@ -126,6 +125,7 @@ void Context::SetDimensions(const Vector2i _dimensions) if (dimensions != _dimensions) { dimensions = _dimensions; + render_manager.SetViewport(dimensions); root->SetBox(Box(Vector2f(dimensions))); root->DirtyLayout(); @@ -141,8 +141,6 @@ void Context::SetDimensions(const Vector2i _dimensions) document->DispatchEvent(EventId::Resize, Dictionary()); } } - - clip_dimensions = dimensions; } } @@ -218,12 +216,10 @@ bool Context::Render() { RMLUI_ZoneScoped; - ElementUtilities::ApplyActiveClipRegion(this); + render_manager.BeginRender(); root->Render(); - ElementUtilities::SetClippingRegion(nullptr, this); - // Render the cursor proxy so that any attached drag clone will be rendered below the cursor. if (drag_clone) { @@ -233,6 +229,8 @@ bool Context::Render() cursor_proxy->Render(); } + render_manager.ResetState(); + return true; } @@ -855,21 +853,9 @@ void Context::SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float spe scroll_controller->SetDefaultScrollBehavior(scroll_behavior, speed_factor); } -bool Context::GetActiveClipRegion(Vector2i& origin, Vector2i& dimensions) const -{ - if (clip_dimensions.x < 0 || clip_dimensions.y < 0) - return false; - - origin = clip_origin; - dimensions = clip_dimensions; - - return true; -} - -void Context::SetActiveClipRegion(const Vector2i origin, const Vector2i dimensions) +RenderManager& Context::GetRenderManager() { - clip_origin = origin; - clip_dimensions = dimensions; + return render_manager; } void Context::SetInstancer(ContextInstancer* _instancer) @@ -1224,12 +1210,9 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen if (within_element) { // The element may have been clipped out of view if it overflows an ancestor, so check its clipping region. - Vector2i clip_origin, clip_dimensions; - if (ElementUtilities::GetClippingRegion(clip_origin, clip_dimensions, element)) - { - within_element = point.x >= clip_origin.x && point.y >= clip_origin.y && point.x <= (clip_origin.x + clip_dimensions.x) && - point.y <= (clip_origin.y + clip_dimensions.y); - } + Rectanglei clip_region; + if (ElementUtilities::GetClippingRegion(element, clip_region)) + within_element = clip_region.Contains(Vector2i(point)); } if (within_element) diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 5b55093ca..a43966ba9 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -1541,6 +1541,11 @@ String Element::GetEventDispatcherSummary() const return meta->event_dispatcher.ToString(); } +ElementBackgroundBorder* Element::GetElementBackgroundBorder() const +{ + return &meta->background_border; +} + ElementDecoration* Element::GetElementDecoration() const { return &meta->decoration; diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 4f345cae8..4ed79642a 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -40,14 +40,17 @@ void ElementBackgroundBorder::Render(Element* element) { if (background_dirty || border_dirty) { + for (auto& background : backgrounds) + background.second.geometry.Release(true); + GenerateGeometry(element); background_dirty = false; border_dirty = false; } - if (geometry) - geometry.Render(element->GetAbsoluteOffset(BoxArea::Border)); + if (Geometry* geometry = GetGeometry(BackgroundType::BackgroundBorder)) + geometry->Render(element->GetAbsoluteOffset(BoxArea::Border)); } void ElementBackgroundBorder::DirtyBackground() @@ -60,6 +63,28 @@ void ElementBackgroundBorder::DirtyBorder() border_dirty = true; } +Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea clip_area) +{ + BackgroundType type = {}; + switch (clip_area) + { + case Rml::BoxArea::Border: type = BackgroundType::ClipBorder; break; + case Rml::BoxArea::Padding: type = BackgroundType::ClipPadding; break; + case Rml::BoxArea::Content: type = BackgroundType::ClipContent; break; + default: RMLUI_ERROR; return nullptr; + } + + Geometry& geometry = GetOrCreateBackground(type).geometry; + if (!geometry) + { + const Box& box = element->GetBox(); + const Vector4f border_radius = element->GetComputedValues().border_radius(); + GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, Colourb(255), clip_area); + } + + return &geometry; +} + void ElementBackgroundBorder::GenerateGeometry(Element* element) { const ComputedValues& computed = element->GetComputedValues(); @@ -83,8 +108,8 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha); } - geometry.GetVertices().clear(); - geometry.GetIndices().clear(); + Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; + RMLUI_ASSERT(!geometry); for (int i = 0; i < element->GetNumBoxes(); i++) { @@ -92,8 +117,23 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) const Box& box = element->GetBox(i, offset); GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors); } +} + +Geometry* ElementBackgroundBorder::GetGeometry(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return &it->second.geometry; + return nullptr; +} + +ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return it->second; - geometry.Release(); + return backgrounds.emplace(type, Background{}).first->second; } } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index 0b302cdaf..a321e4c01 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -30,6 +30,7 @@ #define RMLUI_CORE_ELEMENTBACKGROUNDBORDER_H #include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Texture.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { @@ -43,13 +44,24 @@ class ElementBackgroundBorder { void DirtyBackground(); void DirtyBorder(); + Geometry* GetClipGeometry(Element* element, BoxArea clip_area); + private: + enum class BackgroundType { BackgroundBorder, ClipBorder, ClipPadding, ClipContent, Count }; + struct Background { + Geometry geometry; + Texture texture; + }; + void GenerateGeometry(Element* element); + Geometry* GetGeometry(BackgroundType type); + Background& GetOrCreateBackground(BackgroundType type); + bool background_dirty = false; bool border_dirty = false; - Geometry geometry; + StableMap backgrounds; }; } // namespace Rml diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 85773906f..722ef4897 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -208,14 +208,21 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) if (!render_interface || !context) return; - auto ApplyClippingRegion = [this, render_interface, context](bool extend_ink_overflow) { - ElementUtilities::SetClippingRegion(element); // TODO: For backdrop-filter only: Force clipping to our border-box. + RenderManager& render_manager = context->GetRenderManager(); + Rectanglei initial_scissor_region = render_manager.GetState().scissor_region; + + auto ApplyClippingRegion = [this, &render_manager, initial_scissor_region](PropertyId filter_id) { + RMLUI_ASSERT(filter_id == PropertyId::Filter || filter_id == PropertyId::BackdropFilter); + + const bool force_clip_to_self_border_box = (filter_id == PropertyId::BackdropFilter); + ElementUtilities::SetClippingRegion(element, force_clip_to_self_border_box); // Find the region being affected by the active filters and apply it as a scissor. Rectanglef filter_region = Rectanglef::MakeInvalid(); ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Auto); - if (extend_ink_overflow) + // The filter property may draw outside our normal clipping region due to ink overflow. + if (filter_id == PropertyId::Filter) { for (const auto& filter : filters) filter.filter->ExtendInkOverflow(element, filter_region); @@ -223,22 +230,16 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) Math::ExpandToPixelGrid(filter_region); - Rectanglei scissor_region = Rectanglei::FromSize(context->GetDimensions()); - Vector2i clip_position, clip_size; - if (context->GetActiveClipRegion(clip_position, clip_size)) - scissor_region.Intersect(Rectanglei::FromPositionSize(clip_position, clip_size)); - - scissor_region.IntersectIfValid(Rectanglei(filter_region)); - - render_interface->EnableScissorRegion(true); - render_interface->SetScissorRegion(scissor_region.Left(), scissor_region.Top(), scissor_region.Width(), scissor_region.Height()); + Rectanglei scissor_region = Rectanglei(filter_region); + scissor_region.IntersectIfValid(initial_scissor_region); + render_manager.SetScissorRegion(scissor_region); }; if (!backdrop_filters.empty()) { if (render_stage == RenderStage::Enter) { - ApplyClippingRegion(false); + ApplyClippingRegion(PropertyId::BackdropFilter); render_interface->PushLayer(LayerFill::Clone); @@ -251,7 +252,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) render_interface->PopLayer(BlendMode::Replace, filter_handles); - ElementUtilities::ApplyActiveClipRegion(context); + render_manager.SetScissorRegion(initial_scissor_region); } } @@ -263,7 +264,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) } else if (render_stage == RenderStage::Exit) { - ApplyClippingRegion(true); + ApplyClippingRegion(PropertyId::Filter); FilterHandleList filter_handles; for (auto& filter : filters) @@ -274,7 +275,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) render_interface->PopLayer(BlendMode::Blend, filter_handles); - ElementUtilities::ApplyActiveClipRegion(context); + render_manager.SetScissorRegion(initial_scissor_region); } } } diff --git a/Source/Core/ElementText.cpp b/Source/Core/ElementText.cpp index 3faf8fa3e..16febfaf9 100644 --- a/Source/Core/ElementText.cpp +++ b/Source/Core/ElementText.cpp @@ -36,9 +36,11 @@ #include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Profiling.h" #include "../../Include/RmlUi/Core/Property.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "ComputeProperty.h" #include "ElementDefinition.h" #include "ElementStyle.h" +#include "TransformState.h" namespace Rml { @@ -136,30 +138,31 @@ void ElementText::OnRender() const Vector2f translation = GetAbsoluteOffset(); bool render = true; - Vector2i clip_origin; - Vector2i clip_dimensions; - if (GetContext()->GetActiveClipRegion(clip_origin, clip_dimensions)) + const RenderManager& render_manager = GetContext()->GetRenderManager(); + + // Do a visibility test against the scissor region to avoid unnecessary render calls. Instead of handling + // culling in complicated transform cases, for simplicity we always proceed to render if one is detected. + Rectanglei scissor_region = render_manager.GetState().scissor_region; + if (!scissor_region.Valid()) + scissor_region = Rectanglei::FromSize(render_manager.GetViewport()); + + if (!GetTransformState() || !GetTransformState()->GetTransform()) { const FontMetrics& font_metrics = GetFontEngineInterface()->GetFontMetrics(GetFontFaceHandle()); - float clip_top = (float)clip_origin.y; - float clip_left = (float)clip_origin.x; - float clip_right = (float)(clip_origin.x + clip_dimensions.x); - float clip_bottom = (float)(clip_origin.y + clip_dimensions.y); - float ascent = font_metrics.ascent; - float descent = font_metrics.descent; + const int ascent = Math::RoundUpToInteger(font_metrics.ascent); + const int descent = Math::RoundUpToInteger(font_metrics.descent); render = false; for (const Line& line : lines) { - float x_left = translation.x + line.position.x; - float x_right = x_left + line.width; - float y = translation.y + line.position.y; - float y_top = y - ascent; - float y_bottom = y + descent; - - render = !(x_left > clip_right || x_right < clip_left || y_top > clip_bottom || y_bottom < clip_top); - if (render) + const Vector2i baseline = Vector2i(translation + line.position); + const Rectanglei line_region = Rectanglei::FromCorners(baseline - Vector2i(0, ascent), baseline + Vector2i(line.width, descent)); + + if (line_region.Valid() && scissor_region.Intersects(line_region)) + { + render = true; break; + } } } diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 616d59ba1..1fc144777 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -33,10 +33,13 @@ #include "../../Include/RmlUi/Core/ElementScroll.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "DataController.h" #include "DataModel.h" #include "DataView.h" +#include "ElementBackgroundBorder.h" #include "ElementStyle.h" #include "Layout/LayoutDetails.h" #include "Layout/LayoutEngine.h" @@ -164,14 +167,12 @@ int ElementUtilities::GetStringWidth(Element* element, const String& string, Cha return GetFontEngineInterface()->GetStringWidth(font_face_handle, string, letter_spacing, prior_character); } -bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_dimensions, Element* element) +bool ElementUtilities::GetClippingRegion(Element* element, Rectanglei& out_clip_region, ClipMaskGeometryList* out_clip_mask_list, + bool force_clip_self) { using Style::Clip; - clip_origin = Vector2i(-1, -1); - clip_dimensions = Vector2i(-1, -1); - Clip target_element_clip = element->GetComputedValues().clip(); - if (target_element_clip == Clip::Type::None) + if (target_element_clip == Clip::Type::None && !force_clip_self) return false; int num_ignored_clips = target_element_clip.GetNumber(); @@ -179,44 +180,59 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d // Search through the element's ancestors, finding all elements that clip their overflow and have overflow to clip. // For each that we find, we combine their clipping region with the existing clipping region, and so build up a // complete clipping region for the element. - Element* clipping_element = element->GetOffsetParent(); + Element* clipping_element = (force_clip_self ? element : element->GetParentNode()); + + Rectanglef clip_region = Rectanglef::MakeInvalid(); - while (clipping_element != nullptr) + while (clipping_element) { + const bool force_clip_current_element = (force_clip_self && clipping_element == element); const ComputedValues& clip_computed = clipping_element->GetComputedValues(); const bool clip_enabled = (clip_computed.overflow_x() != Style::Overflow::Visible || clip_computed.overflow_y() != Style::Overflow::Visible); const bool clip_always = (clip_computed.clip() == Clip::Type::Always); const bool clip_none = (clip_computed.clip() == Clip::Type::None); const int clip_number = clip_computed.clip().GetNumber(); - // Merge the existing clip region with the current clip region if we aren't ignoring clip regions. - if ((clip_always || clip_enabled) && num_ignored_clips == 0) + // Merge the existing clip region with the current clip region, unless we are ignoring clip regions. + if (((clip_always || clip_enabled) && num_ignored_clips == 0) || force_clip_current_element) { - // Ignore nodes that don't clip. - if (clip_always || clipping_element->GetClientWidth() < clipping_element->GetScrollWidth() - 0.5f || - clipping_element->GetClientHeight() < clipping_element->GetScrollHeight() - 0.5f) - { - const BoxArea client_area = clipping_element->GetClientArea(); - Vector2f element_origin_f = clipping_element->GetAbsoluteOffset(client_area); - Vector2f element_dimensions_f = clipping_element->GetBox().GetSize(client_area); - Math::SnapToPixelGrid(element_origin_f, element_dimensions_f); + const BoxArea client_area = (force_clip_current_element ? BoxArea::Border : clipping_element->GetClientArea()); + const bool has_clipping_content = + (clip_always || force_clip_current_element || clipping_element->GetClientWidth() < clipping_element->GetScrollWidth() - 0.5f || + clipping_element->GetClientHeight() < clipping_element->GetScrollHeight() - 0.5f); + bool disable_scissor_clipping = false; - const Vector2i element_origin(element_origin_f); - const Vector2i element_dimensions(element_dimensions_f); - - if (clip_origin == Vector2i(-1, -1) && clip_dimensions == Vector2i(-1, -1)) + if (out_clip_mask_list) + { + const TransformState* transform_state = clipping_element->GetTransformState(); + const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr); + const bool has_border_radius = (clip_computed.border_top_left_radius() > 0.f || clip_computed.border_top_right_radius() > 0.f || + clip_computed.border_bottom_right_radius() > 0.f || clip_computed.border_bottom_left_radius() > 0.f); + + // If the element has border-radius we always use a clip mask, since we can't easily predict if content is located on the curved + // region to be clipped. If the element has a transform we only use a clip mask when the content clips. + if (has_border_radius || (transform && has_clipping_content)) { - clip_origin = element_origin; - clip_dimensions = element_dimensions; + Geometry* clip_geometry = clipping_element->GetElementBackgroundBorder()->GetClipGeometry(clipping_element, client_area); + const ClipMaskOperation clip_operation = (out_clip_mask_list->empty() ? ClipMaskOperation::Set : ClipMaskOperation::Intersect); + const Vector2f absolute_offset = clipping_element->GetAbsoluteOffset(BoxArea::Border); + out_clip_mask_list->push_back(ClipMaskGeometry{clip_operation, clip_geometry, absolute_offset, transform}); } - else - { - const Vector2i top_left = Math::Max(clip_origin, element_origin); - const Vector2i bottom_right = Math::Min(clip_origin + clip_dimensions, element_origin + element_dimensions); - clip_origin = top_left; - clip_dimensions = Math::Max(Vector2i(0), bottom_right - top_left); - } + // If we only have border-radius then we add this element to the scissor region as well as the clip mask. This may help with e.g. + // culling text render calls. However, when we have a transform, the element cannot be added to the scissor region since its geometry + // may be projected entirely elsewhere. + if (transform) + disable_scissor_clipping = true; + } + + if (has_clipping_content && !disable_scissor_clipping) + { + // Shrink the scissor region to the element's client area. + Vector2f element_offset = clipping_element->GetAbsoluteOffset(client_area); + Vector2f element_size = clipping_element->GetBox().GetSize(client_area); + + clip_region.IntersectIfValid(Rectanglef::FromPositionSize(element_offset, element_size)); } } @@ -235,48 +251,35 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d clipping_element = clipping_element->GetOffsetParent(); } - return clip_dimensions.x >= 0 && clip_dimensions.y >= 0; + if (clip_region.Valid()) + { + Math::ExpandToPixelGrid(clip_region); + out_clip_region = Rectanglei(clip_region); + } + + return clip_region.Valid(); } -bool ElementUtilities::SetClippingRegion(Element* element, Context* context) +bool ElementUtilities::SetClippingRegion(Element* element, bool force_clip_self) { - if (element && !context) - context = element->GetContext(); - + Context* context = element->GetContext(); if (!context) return false; - Vector2i clip_origin = {-1, -1}; - Vector2i clip_dimensions = {-1, -1}; - bool clip = element && GetClippingRegion(clip_origin, clip_dimensions, element); + RenderManager& render_manager = context->GetRenderManager(); - Vector2i current_origin = {-1, -1}; - Vector2i current_dimensions = {-1, -1}; - bool current_clip = context->GetActiveClipRegion(current_origin, current_dimensions); - if (current_clip != clip || (clip && (clip_origin != current_origin || clip_dimensions != current_dimensions))) - { - context->SetActiveClipRegion(clip_origin, clip_dimensions); - ApplyActiveClipRegion(context); - } - - return true; -} + Rectanglei clip_region; + ClipMaskGeometryList clip_mask_list; -void ElementUtilities::ApplyActiveClipRegion(Context* context) -{ - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - if (!render_interface) - return; + const bool scissoring_enabled = GetClippingRegion(element, clip_region, &clip_mask_list, force_clip_self); + if (scissoring_enabled) + render_manager.SetScissorRegion(clip_region); + else + render_manager.DisableScissorRegion(); - Vector2i origin; - Vector2i dimensions; - bool clip_enabled = context->GetActiveClipRegion(origin, dimensions); + render_manager.SetClipMask(std::move(clip_mask_list)); - render_interface->EnableScissorRegion(clip_enabled); - if (clip_enabled) - { - render_interface->SetScissorRegion(origin.x, origin.y, dimensions.x, dimensions.y); - } + return true; } bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea box_area) @@ -377,31 +380,17 @@ bool ElementUtilities::PositionElement(Element* element, Vector2f offset, Positi bool ElementUtilities::ApplyTransform(Element& element) { - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - if (!render_interface) + Context* context = element.GetContext(); + if (!context) return false; - static const Matrix4f* old_transform_ptr = {}; // This may be expired, dereferencing not allowed! - static Matrix4f old_transform_value = Matrix4f::Identity(); + RenderManager& render_manager = context->GetRenderManager(); - const Matrix4f* new_transform_ptr = nullptr; + const Matrix4f* new_transform = nullptr; if (const TransformState* state = element.GetTransformState()) - new_transform_ptr = state->GetTransform(); - - // Only changed transforms are submitted. - if (old_transform_ptr != new_transform_ptr) - { - // Do a deep comparison as well to avoid submitting a new transform which is equal. - if (!old_transform_ptr || !new_transform_ptr || (old_transform_value != *new_transform_ptr)) - { - render_interface->SetTransform(new_transform_ptr); - - if (new_transform_ptr) - old_transform_value = *new_transform_ptr; - } + new_transform = state->GetTransform(); - old_transform_ptr = new_transform_ptr; - } + render_manager.SetTransform(new_transform); return true; } diff --git a/Source/Core/Geometry.cpp b/Source/Core/Geometry.cpp index 1ca7f7c49..42c634c45 100644 --- a/Source/Core/Geometry.cpp +++ b/Source/Core/Geometry.cpp @@ -75,7 +75,7 @@ Geometry::~Geometry() void Geometry::Render(Vector2f translation) { - RenderInterface* const render_interface = ::Rml::GetRenderInterface(); + RenderInterface* render_interface = GetRenderInterface(); RMLUI_ASSERT(render_interface); translation = translation.Round(); @@ -116,6 +116,29 @@ void Geometry::Render(Vector2f translation) } } +void Geometry::RenderToClipMask(ClipMaskOperation clip_mask, Vector2f translation) +{ + RenderInterface* render_interface = GetRenderInterface(); + RMLUI_ASSERT(render_interface); + + if (!compile_attempted) + { + if (vertices.empty() || indices.empty()) + return; + + RMLUI_ZoneScoped; + + compile_attempted = true; + compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); + } + + if (compiled_geometry) + { + translation = translation.Round(); + render_interface->RenderToClipMask(clip_mask, compiled_geometry, translation); + } +} + Vector& Geometry::GetVertices() { return vertices; diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 95108b788..9c81fc64e 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -51,6 +51,10 @@ void RenderInterface::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/ void RenderInterface::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} +void RenderInterface::EnableClipMask(bool /*enable*/) {} + +void RenderInterface::RenderToClipMask(ClipMaskOperation /*operation*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/) {} + bool RenderInterface::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/) { return false; diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp new file mode 100644 index 000000000..1a4a6641f --- /dev/null +++ b/Source/Core/RenderManager.cpp @@ -0,0 +1,156 @@ +/* + * 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 "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +RenderManager::RenderManager() : render_interface(GetRenderInterface()) +{ + RMLUI_ASSERT(render_interface); +} + +void RenderManager::BeginRender() +{ +#ifdef RMLUI_DEBUG + const RenderState default_state; + RMLUI_ASSERT(state.clip_mask_list == default_state.clip_mask_list); + RMLUI_ASSERT(state.scissor_region == state.scissor_region); + RMLUI_ASSERT(state.transform == state.transform); +#endif +} + +void RenderManager::SetViewport(Vector2i dimensions) +{ + viewport_dimensions = dimensions; +} + +Vector2i RenderManager::GetViewport() const +{ + return viewport_dimensions; +} + +void RenderManager::DisableScissorRegion() +{ + SetScissorRegion(Rectanglei::MakeInvalid()); +} + +void RenderManager::SetScissorRegion(Rectanglei new_region) +{ + const bool old_scissor_enable = state.scissor_region.Valid(); + const bool new_scissor_enable = new_region.Valid(); + + if (new_scissor_enable != old_scissor_enable) + render_interface->EnableScissorRegion(new_scissor_enable); + + if (new_scissor_enable) + { + new_region.Intersect(Rectanglei::FromSize(viewport_dimensions)); + + if (new_region != state.scissor_region) + render_interface->SetScissorRegion(new_region.Left(), new_region.Top(), new_region.Width(), new_region.Height()); + } + + state.scissor_region = new_region; +} + +void RenderManager::DisableClipMask() +{ + if (!state.clip_mask_list.empty()) + { + state.clip_mask_list.clear(); + ApplyClipMask(state.clip_mask_list); + } +} + +void RenderManager::SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation) +{ + RMLUI_ASSERT(geometry); + state.clip_mask_list = {ClipMaskGeometry{operation, geometry, translation, nullptr}}; + ApplyClipMask(state.clip_mask_list); +} + +void RenderManager::SetClipMask(ClipMaskGeometryList in_clip_elements) +{ + if (state.clip_mask_list != in_clip_elements) + { + state.clip_mask_list = std::move(in_clip_elements); + ApplyClipMask(state.clip_mask_list); + } +} + +void RenderManager::SetTransform(const Matrix4f* p_new_transform) +{ + static const Matrix4f identity_transform = Matrix4f::Identity(); + const Matrix4f& new_transform = (p_new_transform ? *p_new_transform : identity_transform); + + if (state.transform != new_transform) + { + render_interface->SetTransform(p_new_transform); + state.transform = new_transform; + } +} + +void RenderManager::ApplyClipMask(const ClipMaskGeometryList& clip_elements) +{ + const bool clip_mask_enabled = !clip_elements.empty(); + render_interface->EnableClipMask(clip_mask_enabled); + + if (clip_mask_enabled) + { + const Matrix4f initial_transform = state.transform; + + for (const ClipMaskGeometry& element_clip : clip_elements) + { + SetTransform(element_clip.transform); + element_clip.geometry->RenderToClipMask(element_clip.operation, element_clip.absolute_offset); + } + + // Apply the initially set transform in case it was changed. + SetTransform(&initial_transform); + } +} + +void RenderManager::SetState(const RenderState& next) +{ + SetScissorRegion(next.scissor_region); + + SetClipMask(next.clip_mask_list); + + SetTransform(&next.transform); +} + +void RenderManager::ResetState() +{ + SetState(RenderState{}); +} + +} // namespace Rml diff --git a/Tests/Data/VisualTests/clip_mask.rml b/Tests/Data/VisualTests/clip_mask.rml new file mode 100644 index 000000000..6954addff --- /dev/null +++ b/Tests/Data/VisualTests/clip_mask.rml @@ -0,0 +1,71 @@ + + + Clip mask + + + + + + +
+ Duck aloft + + A + + X +
+
+ Y + + C + + Some long text + + Z + + W +
+ +
diff --git a/Tests/Data/VisualTests/position_absolute_transform.rml b/Tests/Data/VisualTests/position_absolute_transform.rml index cab70f77d..24f683a05 100644 --- a/Tests/Data/VisualTests/position_absolute_transform.rml +++ b/Tests/Data/VisualTests/position_absolute_transform.rml @@ -5,6 +5,7 @@ + +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Samples/basic/effect/data/effect_style.rcss b/Samples/basic/effect/data/effect_style.rcss index 7706b22e1..106faab4b 100644 --- a/Samples/basic/effect/data/effect_style.rcss +++ b/Samples/basic/effect/data/effect_style.rcss @@ -83,6 +83,7 @@ handle.size:hover, handle.size:active { width: 400dp; height: 480dp; overflow: auto; + overscroll-behavior: contain; background: #fffc; border: 2dp #555a; diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index a43966ba9..2981b81d3 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -1766,7 +1766,8 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) if (border_radius_changed || // changed_properties.Contains(PropertyId::BackgroundColor) || // changed_properties.Contains(PropertyId::Opacity) || // - changed_properties.Contains(PropertyId::ImageColor)) // + changed_properties.Contains(PropertyId::ImageColor) || // + changed_properties.Contains(PropertyId::BoxShadow)) // { meta->background_border.DirtyBackground(); } diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 4ed79642a..ba4716929 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -29,6 +29,8 @@ #include "ElementBackgroundBorder.h" #include "../../Include/RmlUi/Core/Box.h" #include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/GeometryUtilities.h" @@ -49,7 +51,10 @@ void ElementBackgroundBorder::Render(Element* element) border_dirty = false; } - if (Geometry* geometry = GetGeometry(BackgroundType::BackgroundBorder)) + Geometry* shadow_geometry = GetGeometry(BackgroundType::BoxShadow); + if (shadow_geometry && *shadow_geometry) + shadow_geometry->Render(element->GetAbsoluteOffset(BoxArea::Border)); + else if (Geometry* geometry = GetGeometry(BackgroundType::BackgroundBorder)) geometry->Render(element->GetAbsoluteOffset(BoxArea::Border)); } @@ -85,6 +90,23 @@ Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea cli return &geometry; } +Geometry* ElementBackgroundBorder::GetGeometry(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return &it->second.geometry; + return nullptr; +} + +ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return it->second; + + return backgrounds.emplace(type, Background{}).first->second; +} + void ElementBackgroundBorder::GenerateGeometry(Element* element) { const ComputedValues& computed = element->GetComputedValues(); @@ -97,15 +119,20 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) computed.border_left_color(), }; const Vector4f border_radius = computed.border_radius(); + const bool has_box_shadow = computed.has_box_shadow(); - // Apply opacity - const float opacity = computed.opacity(); - background_color.alpha = (byte)(opacity * (float)background_color.alpha); - - if (opacity < 1) + if (!has_box_shadow) { - for (int i = 0; i < 4; ++i) - border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha); + // Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while + // opacity is applied to the entire box-shadow texture when that is rendered. + const float opacity = computed.opacity(); + if (opacity < 1.f) + { + background_color.alpha = (byte)(opacity * (float)background_color.alpha); + + for (int i = 0; i < 4; ++i) + border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha); + } } Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; @@ -117,23 +144,217 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) const Box& box = element->GetBox(i, offset); GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors); } -} -Geometry* ElementBackgroundBorder::GetGeometry(BackgroundType type) -{ - auto it = backgrounds.find(type); - if (it != backgrounds.end()) - return &it->second.geometry; - return nullptr; + if (has_box_shadow) + { + const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + BoxShadowList shadow_list = p_box_shadow->value.Get(); + + GenerateBoxShadow(element, std::move(shadow_list), border_radius, computed.opacity()); + } } -ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type) +void ElementBackgroundBorder::GenerateBoxShadow(Element* element, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) { - auto it = backgrounds.find(type); - if (it != backgrounds.end()) - return it->second; + // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. + Vector2f element_offset_in_texture; + Vector2i texture_dimensions; - return backgrounds.emplace(type, Background{}).first->second; + // Resolve all lengths to px units. + for (BoxShadow& shadow : shadow_list) + { + shadow.blur_radius = NumericValue(element->ResolveLength(shadow.blur_radius), Unit::PX); + shadow.spread_distance = NumericValue(element->ResolveLength(shadow.spread_distance), Unit::PX); + shadow.offset_x = NumericValue(element->ResolveLength(shadow.offset_x), Unit::PX); + shadow.offset_y = NumericValue(element->ResolveLength(shadow.offset_y), Unit::PX); + } + + { + Vector2f extend_min; + Vector2f extend_max; + + // Extend the render-texture to encompass box-shadow blur and spread. + for (const BoxShadow& shadow : shadow_list) + { + if (!shadow.inset) + { + const float extend = 1.5f * shadow.blur_radius.number + shadow.spread_distance.number; + const Vector2f offset = {shadow.offset_x.number, shadow.offset_y.number}; + extend_min = Math::Min(extend_min, offset - Vector2f(extend)); + extend_max = Math::Max(extend_max, offset + Vector2f(extend)); + } + } + + Rectanglef texture_region; + + // Extend the render-texture further to cover all the element's boxes. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + const Box& box = element->GetBox(i, offset); + texture_region.Join(Rectanglef::FromPositionSize(offset, box.GetSize(BoxArea::Border))); + } + + texture_region.ExtendTopLeft(-extend_min); + texture_region.ExtendBottomRight(extend_max); + Math::ExpandToPixelGrid(texture_region); + + element_offset_in_texture = -texture_region.TopLeft(); + texture_dimensions = Vector2i(texture_region.Size()); + } + + Geometry& background_border_geometry = *GetGeometry(BackgroundType::BackgroundBorder); + + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the + // device loses its GPU context and the client calls Rml::ReleaseTextures(). + auto p_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, shadow_list]( + RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, Vector2i& out_dimensions) -> bool { + Context* context = element->GetContext(); + if (!context) + { + RMLUI_ERROR; + return false; + } + + Geometry geometry_padding; // Render geometry for inner box-shadow. + Geometry geometry_padding_border; // Clipping mask for outer box-shadow. + + bool has_inner_shadow = false; + bool has_outer_shadow = false; + for (const BoxShadow& shadow : shadow_list) + { + if (shadow.inset) + has_inner_shadow = true; + else + has_outer_shadow = true; + } + + // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + const Box& box = element->GetBox(i, offset); + + if (has_inner_shadow) + GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding); + if (has_outer_shadow) + GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border); + } + + RenderManager& render_manager = context->GetRenderManager(); + const RenderState initial_render_state = render_manager.GetState(); + render_manager.ResetState(); + render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); + + render_interface->PushLayer(LayerFill::Clear); + + background_border_geometry.Render(element_offset_in_texture); + + for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) + { + const BoxShadow& shadow = shadow_list[shadow_index]; + const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number}; + const bool inset = shadow.inset; + const float spread_distance = shadow.spread_distance.number; + const float blur_radius = shadow.blur_radius.number; + + Vector4f spread_radii = border_radius; + for (int i = 0; i < 4; i++) + { + float& radius = spread_radii[i]; + float spread_factor = (inset ? -1.f : 1.f); + if (radius < spread_distance) + { + const float ratio_minus_one = (radius / spread_distance) - 1.f; + spread_factor *= 1.f + ratio_minus_one * ratio_minus_one * ratio_minus_one; + } + radius = Math::Max(radius + spread_factor * spread_distance, 0.f); + } + + Geometry shadow_geometry; + + // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + Box box = element->GetBox(i, offset); + const float signed_spread_distance = (inset ? -spread_distance : spread_distance); + offset -= Vector2f(signed_spread_distance); + + for (int j = 0; j < Box::num_edges; j++) + { + BoxEdge edge = (BoxEdge)j; + const float new_size = box.GetEdge(BoxArea::Padding, edge) + signed_spread_distance; + box.SetEdge(BoxArea::Padding, edge, new_size); + } + + GeometryUtilities::GenerateBackground(&shadow_geometry, box, offset, spread_radii, shadow.color, + inset ? BoxArea::Padding : BoxArea::Border); + } + + CompiledFilterHandle blur = {}; + if (blur_radius > 0.5f) + { + blur = render_interface->CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); + if (blur) + render_interface->PushLayer(LayerFill::Clear); + } + + if (inset) + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &shadow_geometry, shadow_offset + element_offset_in_texture); + + for (Rml::Vertex& vertex : geometry_padding.GetVertices()) + vertex.colour = shadow.color; + + geometry_padding.Release(); + geometry_padding.Render(element_offset_in_texture); + + render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture); + } + else + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture); + shadow_geometry.Render(shadow_offset + element_offset_in_texture); + } + + if (blur) + { + render_interface->PopLayer(BlendMode::Blend, {blur}); + render_interface->ReleaseCompiledFilter(blur); + } + } + + TextureHandle shadow_texture = render_interface->SaveLayerAsTexture(texture_dimensions); + if (!shadow_texture) + return false; + + render_interface->PopLayer(BlendMode::Discard, {}); + + render_manager.SetState(initial_render_state); + + out_dimensions = texture_dimensions; + out_handle = shadow_texture; + + return true; + }; + + // Generate the geometry for the box-shadow texture. + Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); + Geometry& shadow_geometry = shadow_background.geometry; + Texture& shadow_texture = shadow_background.texture; + RMLUI_ASSERT(!shadow_geometry); + + Vector& vertices = shadow_geometry.GetVertices(); + Vector& indices = shadow_geometry.GetIndices(); + vertices.resize(4); + indices.resize(6); + const byte alpha = byte(opacity * 255.f); + GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha)); + + shadow_texture.Set("box-shadow", p_callback); + shadow_geometry.SetTexture(&shadow_texture); } } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index a321e4c01..9b571cf0a 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -47,17 +47,18 @@ class ElementBackgroundBorder { Geometry* GetClipGeometry(Element* element, BoxArea clip_area); private: - enum class BackgroundType { BackgroundBorder, ClipBorder, ClipPadding, ClipContent, Count }; + enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count }; struct Background { Geometry geometry; Texture texture; }; - void GenerateGeometry(Element* element); - Geometry* GetGeometry(BackgroundType type); Background& GetOrCreateBackground(BackgroundType type); + void GenerateGeometry(Element* element); + void GenerateBoxShadow(Element* element, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity); + bool background_dirty = false; bool border_dirty = false; diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index ede3ae36b..adfe47564 100644 --- a/Source/Core/ElementStyle.cpp +++ b/Source/Core/ElementStyle.cpp @@ -858,6 +858,9 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S case PropertyId::BackdropFilter: values.has_backdrop_filter(p->unit == Unit::FILTER); break; + case PropertyId::BoxShadow: + values.has_box_shadow(p->unit == Unit::BOXSHADOWLIST); + break; case PropertyId::FlexBasis: values.flex_basis(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 1fc144777..228827953 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -29,6 +29,7 @@ #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementScroll.h" #include "../../Include/RmlUi/Core/Factory.h" @@ -286,11 +287,36 @@ bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* elemen { RMLUI_ASSERT(element); + Vector2f shadow_extent_top_left, shadow_extent_bottom_right; if (box_area == BoxArea::Auto) + { + // 'Auto' acts like border box extended to encompass any ink overflow, which includes the element's box-shadow. + // TODO: Include ink overflow due to filters (e.g. blur or drop-shadow). box_area = BoxArea::Border; + if (const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow)) + { + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + const BoxShadowList& shadow_list = p_box_shadow->value.GetReference(); + + for (const BoxShadow& shadow : shadow_list) + { + if (!shadow.inset) + { + const float extent = 1.5f * element->ResolveLength(shadow.blur_radius) + element->ResolveLength(shadow.spread_distance); + const Vector2f offset = {element->ResolveLength(shadow.offset_x), element->ResolveLength(shadow.offset_y)}; + + shadow_extent_top_left = Math::Max(shadow_extent_top_left, -offset + Vector2f(extent)); + shadow_extent_bottom_right = Math::Max(shadow_extent_bottom_right, offset + Vector2f(extent)); + } + } + } + } + // Element bounds in non-transformed space. - const Rectanglef bounds = Rectanglef::FromPositionSize(element->GetAbsoluteOffset(box_area), element->GetBox().GetSize(box_area)); + Rectanglef bounds = Rectanglef::FromPositionSize(element->GetAbsoluteOffset(box_area), element->GetBox().GetSize(box_area)); + bounds.ExtendTopLeft(shadow_extent_top_left); + bounds.ExtendBottomRight(shadow_extent_bottom_right); const TransformState* transform_state = element->GetTransformState(); const Matrix4f* transform = (transform_state ? transform_state->GetTransform() : nullptr); diff --git a/Source/Core/PropertyParserBoxShadow.cpp b/Source/Core/PropertyParserBoxShadow.cpp new file mode 100644 index 000000000..a244e75c5 --- /dev/null +++ b/Source/Core/PropertyParserBoxShadow.cpp @@ -0,0 +1,118 @@ +/* + * 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 "PropertyParserBoxShadow.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +namespace Rml { + +PropertyParserBoxShadow::PropertyParserBoxShadow(PropertyParser* parser_color, PropertyParser* parser_length) : + parser_color(parser_color), parser_length(parser_length) +{ + RMLUI_ASSERT(parser_color && parser_length); +} + +bool PropertyParserBoxShadow::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + if (value.empty() || value == "none") + { + property.value = Variant(); + property.unit = Unit::UNKNOWN; + return true; + } + + StringList shadows_string_list; + { + auto lowercase_value = StringUtilities::ToLower(value); + StringUtilities::ExpandString(shadows_string_list, lowercase_value, ','); + } + + if (shadows_string_list.empty()) + return false; + + const ParameterMap empty_parameter_map; + + BoxShadowList shadow_list; + shadow_list.reserve(shadows_string_list.size()); + + for (const String& shadow_str : shadows_string_list) + { + StringList arguments; + StringUtilities::ExpandString(arguments, shadow_str, ' '); + if (arguments.empty()) + return false; + + shadow_list.push_back({}); + BoxShadow& shadow = shadow_list.back(); + + int length_argument_index = 0; + + for (const String& argument : arguments) + { + if (argument.empty()) + continue; + + Property prop; + if (parser_length->ParseValue(prop, argument, empty_parameter_map)) + { + switch (length_argument_index) + { + case 0: shadow.offset_x = prop.GetNumericValue(); break; + case 1: shadow.offset_y = prop.GetNumericValue(); break; + case 2: shadow.blur_radius = prop.GetNumericValue(); break; + case 3: shadow.spread_distance = prop.GetNumericValue(); break; + default: return false; + } + length_argument_index += 1; + } + else if (argument == "inset") + { + shadow.inset = true; + } + else if (parser_color->ParseValue(prop, argument, empty_parameter_map)) + { + shadow.color = prop.Get(); + } + else + { + return false; + } + } + + if (length_argument_index < 2) + return false; + } + + property.unit = Unit::BOXSHADOWLIST; + property.value = Variant(std::move(shadow_list)); + + return true; +} + +} // namespace Rml diff --git a/Source/Core/PropertyParserBoxShadow.h b/Source/Core/PropertyParserBoxShadow.h new file mode 100644 index 000000000..4995c28d8 --- /dev/null +++ b/Source/Core/PropertyParserBoxShadow.h @@ -0,0 +1,57 @@ +/* + * 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_CORE_PROPERTYPARSERBOXSHADOW_H +#define RMLUI_CORE_PROPERTYPARSERBOXSHADOW_H + +#include "../../Include/RmlUi/Core/PropertyParser.h" + +namespace Rml { + +/** + Parses the RCSS 'box-shadow' property. +*/ + +class PropertyParserBoxShadow : public PropertyParser { +public: + PropertyParserBoxShadow(PropertyParser* parser_color, PropertyParser* parser_length); + + /// Called to parse a RCSS declaration. + /// @param[out] property The property to set the parsed value on. + /// @param[in] value The raw value defined for this property. + /// @param[in] parameters The parameters defined for this property. + /// @return True if the value was validated successfully, false otherwise. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + +private: + PropertyParser* parser_color; + PropertyParser* parser_length; +}; + +} // namespace Rml +#endif diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 9c81fc64e..04b53ec3a 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -73,6 +73,11 @@ void RenderInterface::PushLayer(LayerFill /*layer_fill*/) {} void RenderInterface::PopLayer(BlendMode /*blend_mode*/, const FilterHandleList& /*filters*/) {} +TextureHandle RenderInterface::SaveLayerAsTexture(Vector2i /*dimensions*/) +{ + return TextureHandle{}; +} + CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, const Dictionary& /*parameters*/) { return CompiledFilterHandle{}; diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index a003ad9bb..29f20082e 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -31,6 +31,7 @@ #include "../../Include/RmlUi/Core/PropertyIdSet.h" #include "IdNameMap.h" #include "PropertyParserAnimation.h" +#include "PropertyParserBoxShadow.h" #include "PropertyParserColour.h" #include "PropertyParserDecorator.h" #include "PropertyParserFilter.h" @@ -64,6 +65,7 @@ struct DefaultStyleSheetParsers : NonCopyMoveable { PropertyParserTransform transform = PropertyParserTransform(); PropertyParserRatio ratio = PropertyParserRatio(); PropertyParserNumber resolution = PropertyParserNumber(Unit::X); + PropertyParserBoxShadow box_shadow = PropertyParserBoxShadow(&color, &length); }; StyleSheetSpecification::StyleSheetSpecification() : @@ -259,6 +261,7 @@ void StyleSheetSpecification::RegisterDefaultParsers() RegisterParser("transform", &default_parsers->transform); RegisterParser("ratio", &default_parsers->ratio); RegisterParser("resolution", &default_parsers->resolution); + RegisterParser("box_shadow", &default_parsers->box_shadow); } void StyleSheetSpecification::RegisterDefaultProperties() @@ -412,6 +415,8 @@ void StyleSheetSpecification::RegisterDefaultProperties() RegisterProperty(PropertyId::Filter, "filter", "", false, false).AddParser("filter", "filter"); RegisterProperty(PropertyId::BackdropFilter, "backdrop-filter", "", false, false).AddParser("filter"); + + RegisterProperty(PropertyId::BoxShadow, "box-shadow", "none", false, false).AddParser("box_shadow"); // Rare properties (not added to computed values) RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string"); diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 9296f0011..3d6b3cb0e 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -28,6 +28,7 @@ #include "../../Include/RmlUi/Core/TypeConverter.h" #include "../../Include/RmlUi/Core/Animation.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" @@ -223,4 +224,37 @@ bool TypeConverter::Convert(const FontEffectsPtr& src, S return true; } +bool TypeConverter::Convert(const BoxShadowList& src, BoxShadowList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const BoxShadowList& src, String& dest) +{ + dest.clear(); + String temp, str_unit; + for (size_t i = 0; i < src.size(); i++) + { + const BoxShadow& shadow = src[i]; + for (const NumericValue* value : {&shadow.offset_x, &shadow.offset_y, &shadow.blur_radius, &shadow.spread_distance}) + { + if (TypeConverter::Convert(value->unit, str_unit)) + temp += " " + ToString(value->number) + str_unit; + } + + if (shadow.inset) + temp += " inset"; + + dest += "rgba(" + ToString(shadow.color) + ')' + temp; + + if (i < src.size() - 1) + { + dest += ", "; + temp.clear(); + } + } + return true; +} + } // namespace Rml diff --git a/Source/Core/Variant.cpp b/Source/Core/Variant.cpp index cd02f2202..e79f4877d 100644 --- a/Source/Core/Variant.cpp +++ b/Source/Core/Variant.cpp @@ -27,6 +27,7 @@ */ #include "../../Include/RmlUi/Core/Variant.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include namespace Rml { @@ -112,6 +113,12 @@ void Variant::Clear() font_effects->~shared_ptr(); } break; + case BOXSHADOWLIST: + { + BoxShadowList* value = (BoxShadowList*)data; + value->~BoxShadowList(); + } + break; default: break; } type = NONE; @@ -130,6 +137,7 @@ void Variant::Set(const Variant& copy) case DECORATORSPTR: Set(*reinterpret_cast(copy.data)); break; case FILTERSPTR: Set(*reinterpret_cast(copy.data)); break; case FONTEFFECTSPTR: Set(*reinterpret_cast(copy.data)); break; + case BOXSHADOWLIST: Set(*reinterpret_cast(copy.data)); break; default: memcpy(data, copy.data, LOCAL_DATA_SIZE); type = copy.type; @@ -149,6 +157,7 @@ void Variant::Set(Variant&& other) case DECORATORSPTR: Set(std::move(*reinterpret_cast(other.data))); break; case FILTERSPTR: Set(std::move(*reinterpret_cast(other.data))); break; case FONTEFFECTSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case BOXSHADOWLIST: Set(std::move(*reinterpret_cast(other.data))); break; default: memcpy(data, other.data, LOCAL_DATA_SIZE); type = other.type; @@ -430,6 +439,30 @@ void Variant::Set(FontEffectsPtr&& value) new (data) FontEffectsPtr(std::move(value)); } } +void Variant::Set(const BoxShadowList& value) +{ + if (type == BOXSHADOWLIST) + { + *(BoxShadowList*)data = value; + } + else + { + type = BOXSHADOWLIST; + new (data) BoxShadowList(value); + } +} +void Variant::Set(BoxShadowList&& value) +{ + if (type == BOXSHADOWLIST) + { + (*(BoxShadowList*)data) = std::move(value); + } + else + { + type = BOXSHADOWLIST; + new (data) BoxShadowList(std::move(value)); + } +} Variant& Variant::operator=(const Variant& copy) { @@ -483,6 +516,7 @@ bool Variant::operator==(const Variant& other) const case DECORATORSPTR: return DEFAULT_VARIANT_COMPARE(DecoratorsPtr); case FILTERSPTR: return DEFAULT_VARIANT_COMPARE(FiltersPtr); case FONTEFFECTSPTR: return DEFAULT_VARIANT_COMPARE(FontEffectsPtr); + case BOXSHADOWLIST: return DEFAULT_VARIANT_COMPARE(BoxShadowList); case NONE: return true; } RMLUI_ERRORMSG("Variant comparison not implemented for this type."); From 72c816f42e46b1b337534122d7afa41351e5b5d7 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Jun 2023 13:26:40 +0200 Subject: [PATCH 25/80] GL3 renderer: Fix assertion errors when minimizing the window --- Backends/RmlUi_Renderer_GL3.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 23e1f41af..fa4e44331 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -672,14 +672,14 @@ RenderInterface_GL3::~RenderInterface_GL3() void RenderInterface_GL3::SetViewport(int width, int height) { - viewport_width = width; - viewport_height = height; + viewport_width = Rml::Math::Max(width, 1); + viewport_height = Rml::Math::Max(height, 1); projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); } void RenderInterface_GL3::BeginFrame() { - RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0); + RMLUI_ASSERT(viewport_width >= 1 && viewport_height >= 1); // Backup GL state. glstate_backup.enable_cull_face = glIsEnabled(GL_CULL_FACE); From 2dba53e499c27c3b6ab16848aa2a7260687ed16f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Jun 2023 14:35:03 +0200 Subject: [PATCH 26/80] GL3 renderer: Avoid sampling outside target region --- Backends/RmlUi_Renderer_GL3.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index fa4e44331..2f2c16d76 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -175,10 +175,13 @@ uniform vec2 _texCoordMax; in vec2 fragTexCoord[BLUR_SIZE]; out vec4 finalColor; -void main() { +void main() { vec4 color = vec4(0.0, 0.0, 0.0, 0.0); for(int i = 0; i < BLUR_SIZE; i++) - color += texture(_tex, clamp(fragTexCoord[i], _texCoordMin, _texCoordMax)) * _weights[abs(i - BLUR_NUM_WEIGHTS + 1)]; + { + 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; } )"; @@ -192,7 +195,8 @@ in vec2 fragTexCoord; out vec4 finalColor; void main() { - finalColor = texture(_tex, clamp(fragTexCoord, _texCoordMin, _texCoordMax)).a * _color; + vec2 in_region = step(_texCoordMin, fragTexCoord) * step(fragTexCoord, _texCoordMax); + finalColor = texture(_tex, fragTexCoord).a * in_region.x * in_region.y * _color; } )"; From 2338f2d4dc70442f72978c5ad7454623742ffe0f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Jun 2023 14:38:05 +0200 Subject: [PATCH 27/80] Add visual tests for drop shadow filter, and bounding box with ink overflow --- Tests/Data/VisualTests/filter_drop_shadow.rml | 29 ++++++++++++ .../VisualTests/ink_overflow_bounding_box.rml | 47 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 Tests/Data/VisualTests/filter_drop_shadow.rml create mode 100644 Tests/Data/VisualTests/ink_overflow_bounding_box.rml diff --git a/Tests/Data/VisualTests/filter_drop_shadow.rml b/Tests/Data/VisualTests/filter_drop_shadow.rml new file mode 100644 index 000000000..004a3366b --- /dev/null +++ b/Tests/Data/VisualTests/filter_drop_shadow.rml @@ -0,0 +1,29 @@ + + + Filter: drop-shadow + + + + + + +
+ + +
diff --git a/Tests/Data/VisualTests/ink_overflow_bounding_box.rml b/Tests/Data/VisualTests/ink_overflow_bounding_box.rml new file mode 100644 index 000000000..35709546c --- /dev/null +++ b/Tests/Data/VisualTests/ink_overflow_bounding_box.rml @@ -0,0 +1,47 @@ + + + Ink overflow bounding box + + + + + + + +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+ + +
From 556607d35b3d70d1ea62e61910e63a12d9e292ba Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 6 Jun 2023 13:28:15 +0200 Subject: [PATCH 28/80] Replace gradient decorator syntax 'gradient(horizontal|vertical ...)' with 'horizontal-gradient(...)' and 'vertical-gradient(...)' [deprecation] --- Samples/basic/demo/data/demo.rml | 6 +- Source/Core/DecoratorGradient.cpp | 60 +++++++++--------- Source/Core/DecoratorGradient.h | 29 +++++---- Source/Core/Factory.cpp | 6 +- Tests/Data/VisualTests/border_radius.rml | 2 +- .../Data/VisualTests/color_interpolation.rml | 8 +-- Tests/Source/UnitTests/Animation.cpp | 62 ++++++++++--------- 7 files changed, 93 insertions(+), 80 deletions(-) diff --git a/Samples/basic/demo/data/demo.rml b/Samples/basic/demo/data/demo.rml index 3c6749613..b3eccaf20 100644 --- a/Samples/basic/demo/data/demo.rml +++ b/Samples/basic/demo/data/demo.rml @@ -140,14 +140,14 @@ p.title } #decorators button.gradient { - decorator: gradient( vertical #415857 #5990A3 ); + decorator: vertical-gradient( #415857 #5990A3 ); border: 3dp #415857; border-radius: 8dp; margin-right: 12dp; } #decorators button.gradient.horizontal { - decorator: gradient( horizontal #DB6565 #F1B58A ); + decorator: horizontal-gradient( #DB6565 #F1B58A ); border-color: #DB6565; } #decorators button.gradient:hover @@ -354,7 +354,7 @@ p.title margin-left: -20dp; width: 40dp; height: 200dp; - decorator: gradient( vertical #daf0 #fef6 ); + decorator: vertical-gradient( #daf0 #fef6 ); } #transition:hover .ray { diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index 254d8c054..cb415e0af 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -35,31 +35,21 @@ #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" -/* -Gradient decorator usage in CSS: - -decorator: gradient( direction start-color stop-color ); - -direction: horizontal|vertical; -start-color: #ff00ff; -stop-color: #00ff00; -*/ - namespace Rml { -DecoratorGradient::DecoratorGradient() {} +DecoratorStraightGradient::DecoratorStraightGradient() {} -DecoratorGradient::~DecoratorGradient() {} +DecoratorStraightGradient::~DecoratorStraightGradient() {} -bool DecoratorGradient::Initialise(const Direction dir_, const Colourb start_, const Colourb stop_) +bool DecoratorStraightGradient::Initialise(const Direction in_direction, const Colourb in_start, const Colourb in_stop) { - dir = dir_; - start = start_; - stop = stop_; + direction = in_direction; + start = in_start; + stop = in_stop; return true; } -DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element) const { Geometry* geometry = new Geometry(); const Box& box = element->GetBox(); @@ -80,7 +70,7 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con Vector& vertices = geometry->GetVertices(); - if (dir == Direction::Horizontal) + if (direction == Direction::Horizontal) { for (int i = 0; i < (int)vertices.size(); i++) { @@ -88,7 +78,7 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop); } } - else if (dir == Direction::Vertical) + else if (direction == Direction::Vertical) { for (int i = 0; i < (int)vertices.size(); i++) { @@ -100,40 +90,50 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con return reinterpret_cast(geometry); } -void DecoratorGradient::ReleaseElementData(DecoratorDataHandle element_data) const +void DecoratorStraightGradient::ReleaseElementData(DecoratorDataHandle element_data) const { delete reinterpret_cast(element_data); } -void DecoratorGradient::RenderElement(Element* element, DecoratorDataHandle element_data) const +void DecoratorStraightGradient::RenderElement(Element* element, DecoratorDataHandle element_data) const { auto* data = reinterpret_cast(element_data); data->Render(element->GetAbsoluteOffset(BoxArea::Border)); } -DecoratorGradientInstancer::DecoratorGradientInstancer() +DecoratorStraightGradientInstancer::DecoratorStraightGradientInstancer() { - // register properties for the decorator ids.direction = RegisterProperty("direction", "horizontal").AddParser("keyword", "horizontal, vertical").GetId(); ids.start = RegisterProperty("start-color", "#ffffff").AddParser("color").GetId(); ids.stop = RegisterProperty("stop-color", "#ffffff").AddParser("color").GetId(); RegisterShorthand("decorator", "direction, start-color, stop-color", ShorthandType::FallThrough); } -DecoratorGradientInstancer::~DecoratorGradientInstancer() {} +DecoratorStraightGradientInstancer::~DecoratorStraightGradientInstancer() {} -SharedPtr DecoratorGradientInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties_, +SharedPtr DecoratorStraightGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, const DecoratorInstancerInterface& /*interface_*/) { - DecoratorGradient::Direction dir = (DecoratorGradient::Direction)properties_.GetProperty(ids.direction)->Get(); + using Direction = DecoratorStraightGradient::Direction; + Direction direction; + if (name == "horizontal-gradient") + direction = Direction::Horizontal; + else if (name == "vertical-gradient") + direction = Direction::Vertical; + else + { + direction = (Direction)properties_.GetProperty(ids.direction)->Get(); + Log::Message(Log::LT_WARNING, + "Decorator syntax 'gradient(horizontal|vertical ...)' is deprecated, please replace with 'horizontal-gradient(...)' or " + "'vertical-gradient(...)'"); + } + Colourb start = properties_.GetProperty(ids.start)->Get(); Colourb stop = properties_.GetProperty(ids.stop)->Get(); - auto decorator = MakeShared(); - if (decorator->Initialise(dir, start, stop)) - { + auto decorator = MakeShared(); + if (decorator->Initialise(direction, start, stop)) return decorator; - } return nullptr; } diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index aa46402c4..a3a2e0a32 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -34,14 +34,21 @@ namespace Rml { -class DecoratorGradient : public Decorator { +/** + Straight gradient. + + CSS usage: + decorator: horizontal-gradient( ); + decorator: vertical-gradient( ); + */ +class DecoratorStraightGradient : public Decorator { public: - enum class Direction { Horizontal = 0, Vertical = 1 }; + enum class Direction { Horizontal, Vertical }; - DecoratorGradient(); - virtual ~DecoratorGradient(); + DecoratorStraightGradient(); + virtual ~DecoratorStraightGradient(); - bool Initialise(Direction dir_, Colourb start_, Colourb stop_); + bool Initialise(Direction direction, Colourb start, Colourb stop); DecoratorDataHandle GenerateElementData(Element* element) const override; void ReleaseElementData(DecoratorDataHandle element_data) const override; @@ -49,23 +56,23 @@ class DecoratorGradient : public Decorator { void RenderElement(Element* element, DecoratorDataHandle element_data) const override; private: - Direction dir = {}; + Direction direction = {}; Colourb start, stop; }; -class DecoratorGradientInstancer : public DecoratorInstancer { +class DecoratorStraightGradientInstancer : public DecoratorInstancer { public: - DecoratorGradientInstancer(); - ~DecoratorGradientInstancer(); + DecoratorStraightGradientInstancer(); + ~DecoratorStraightGradientInstancer(); SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) override; private: - struct GradientPropertyIds { + struct PropertyIds { PropertyId direction, start, stop; }; - GradientPropertyIds ids; + PropertyIds ids; }; } // namespace Rml diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index f36ef41cd..746b61a00 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -152,7 +152,7 @@ struct DefaultInstancers { DecoratorTiledBoxInstancer decorator_tiled_box; DecoratorTiledImageInstancer decorator_image; DecoratorNinePatchInstancer decorator_ninepatch; - DecoratorGradientInstancer decorator_gradient; + DecoratorStraightGradientInstancer decorator_straight_gradient; // Filters FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; @@ -241,7 +241,9 @@ bool Factory::Initialise() RegisterDecoratorInstancer("tiled-box", &default_instancers->decorator_tiled_box); RegisterDecoratorInstancer("image", &default_instancers->decorator_image); RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch); - RegisterDecoratorInstancer("gradient", &default_instancers->decorator_gradient); + RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient); + RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient); + RegisterDecoratorInstancer("vertical-gradient", &default_instancers->decorator_straight_gradient); // Filter instancers RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate); diff --git a/Tests/Data/VisualTests/border_radius.rml b/Tests/Data/VisualTests/border_radius.rml index e1cd0408c..f77f252a5 100644 --- a/Tests/Data/VisualTests/border_radius.rml +++ b/Tests/Data/VisualTests/border_radius.rml @@ -19,7 +19,7 @@ .thin { border-width: 10dp 5dp 25dp 20dp; border-radius: 80dp 30dp; - decorator: gradient( horizontal #ff8400 #ffd34f ); + decorator: horizontal-gradient( #ff8400 #ffd34f ); } .thick { border-width: 40dp 20dp; diff --git a/Tests/Data/VisualTests/color_interpolation.rml b/Tests/Data/VisualTests/color_interpolation.rml index 81b1684bc..38ef0c866 100644 --- a/Tests/Data/VisualTests/color_interpolation.rml +++ b/Tests/Data/VisualTests/color_interpolation.rml @@ -30,10 +30,10 @@ width: 300dp; } .rectangle { - decorator: gradient( horizontal #f00 #0f0 ); + decorator: horizontal-gradient( #f00 #0f0 ); } .round { - decorator: gradient( horizontal #f00 #0f0 ); + decorator: horizontal-gradient( #f00 #0f0 ); border-radius: 50dp; } .round-border { @@ -42,10 +42,10 @@ border-color: #0f0 #f00 #0f0 #f00; } .rectangle.black { - decorator: gradient( horizontal #f00 #000 ); + decorator: horizontal-gradient( #f00 #000 ); } .round.black { - decorator: gradient( horizontal #f00 #000 ); + decorator: horizontal-gradient( #f00 #000 ); border-radius: 50dp; } .round-border.black { diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index 50dbf5b89..345b6c772 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -48,8 +48,8 @@ static const String document_decorator_rml = R"( bottom: 0; } - @decorator from_rule : gradient { %s } - @decorator to_rule: gradient{ %s } + @decorator from_rule : horizontal-gradient { %s } + @decorator to_rule: horizontal-gradient{ %s } @keyframes mix { from { decorator: %s; } @@ -86,113 +86,117 @@ TEST_CASE("animation.decorator") "", "", - "gradient(horizontal transparent transparent)", - "gradient(horizontal white white)", + "horizontal-gradient(transparent transparent)", + "horizontal-gradient(white white)", - "gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", }, { "", "", "none", - "gradient(horizontal transparent transparent)", + "horizontal-gradient(transparent transparent)", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", }, { "", "", "none", - "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), horizontal-gradient(horizontal rgba(220,220,220,191) " + "rgba(220,220,220,191))", }, { "", "", - "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", "none", - "gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), gradient(vertical rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), horizontal-gradient(horizontal rgba(127,127,127,63) " + "rgba(127,127,127,63))", }, /// Only rule declaration { - "direction: horizontal; start-color: transparent; stop-color: transparent;", - "direction: horizontal; start-color: white; stop-color: white;", + "start-color: transparent; stop-color: transparent;", + "start-color: white; stop-color: white;", "from_rule", "to_rule", - "gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", }, { "", - "direction: horizontal; start-color: transparent; stop-color: transparent;", + "start-color: transparent; stop-color: transparent;", "from_rule", "to_rule", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", }, { - "direction: vertical; start-color: transparent; stop-color: transparent;", + "start-color: transparent; stop-color: transparent;", "", "from_rule", "to_rule", - "gradient(vertical rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", }, /// Mix rule and standard declaration { - "direction: horizontal; start-color: transparent; stop-color: transparent;", + "start-color: transparent; stop-color: transparent;", "", "from_rule", - "gradient(horizontal white white)", + "horizontal-gradient(white white)", - "gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", }, { "", - "direction: horizontal; start-color: transparent; stop-color: transparent;", + "start-color: transparent; stop-color: transparent;", "none", "to_rule", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", }, { - "direction: vertical; start-color: transparent; stop-color: transparent;", + "start-color: transparent; stop-color: transparent;", "", "from_rule", "none", - "gradient(vertical rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", }, { "", "", "from_rule, to_rule", - "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), horizontal-gradient(horizontal rgba(220,220,220,191) " + "rgba(220,220,220,191))", }, { "", "", - "gradient(horizontal transparent transparent), gradient(vertical transparent transparent)", + "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", "from_rule, to_rule", - "gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), gradient(vertical rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), horizontal-gradient(horizontal rgba(127,127,127,63) " + "rgba(127,127,127,63))", }, }; From 17f1d9583c2ed8b10065209280778567d24f2c59 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 6 Jun 2023 21:03:04 +0200 Subject: [PATCH 29/80] Math: Normalize angle to positive values only --- Include/RmlUi/Core/Math.h | 6 +++--- Source/Core/Math.cpp | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Include/RmlUi/Core/Math.h b/Include/RmlUi/Core/Math.h index 6c847f7f0..af48e9bf0 100644 --- a/Include/RmlUi/Core/Math.h +++ b/Include/RmlUi/Core/Math.h @@ -154,9 +154,9 @@ namespace Math { /// @param[in] The angle, in degrees. /// @return The angle converted to radians. RMLUICORE_API float DegreesToRadians(float angle); - /// Normalises and angle in radians - /// @param[in] The angle, in randians - /// @return The normalised angle + /// Normalises an angle in radians to [0, 2pi). + /// @param[in] The angle, in radians. + /// @return The normalised angle. RMLUICORE_API float NormaliseAngle(float angle); /// Calculates the square root of a value. diff --git a/Source/Core/Math.cpp b/Source/Core/Math.cpp index 225bda480..92e4e364c 100644 --- a/Source/Core/Math.cpp +++ b/Source/Core/Math.cpp @@ -116,7 +116,10 @@ namespace Math { RMLUICORE_API float NormaliseAngle(float angle) { - return fmodf(angle, RMLUI_PI * 2.0f); + float result = fmodf(angle, RMLUI_PI * 2.0f); + if (result < 0.f) + result += RMLUI_PI * 2.0f; + return result; } RMLUICORE_API float SquareRoot(float value) From 0507e487b74590cf8fac899b9c0f57f0e308ae8c Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 6 Jun 2023 22:03:13 +0200 Subject: [PATCH 30/80] Improve PropertySpecification::ParsePropertyValues - Allow splitting by comma - No escaping inside parenthesis - Enable quote mode inside parenthesis - Add unit tests --- Include/RmlUi/Core/PropertySpecification.h | 4 +- Source/Core/PropertySpecification.cpp | 183 +++++++----------- .../UnitTests/PropertySpecification.cpp | 140 +++++++++++++- 3 files changed, 210 insertions(+), 117 deletions(-) diff --git a/Include/RmlUi/Core/PropertySpecification.h b/Include/RmlUi/Core/PropertySpecification.h index d0ef42cf0..1c1421f1f 100644 --- a/Include/RmlUi/Core/PropertySpecification.h +++ b/Include/RmlUi/Core/PropertySpecification.h @@ -136,9 +136,11 @@ class RMLUICORE_API PropertySpecification { PropertyIdSet property_ids_inherited; PropertyIdSet property_ids_forcing_layout; - bool ParsePropertyValues(StringList& values_list, const String& values, bool split_values) const; + enum class SplitOption { None, Whitespace, Comma }; + bool ParsePropertyValues(StringList& values_list, const String& values, SplitOption split_option) const; friend class Rml::StyleSheetSpecification; + friend class TestPropertySpecification; }; } // namespace Rml diff --git a/Source/Core/PropertySpecification.cpp b/Source/Core/PropertySpecification.cpp index 8ed590c2f..6a5bda733 100644 --- a/Source/Core/PropertySpecification.cpp +++ b/Source/Core/PropertySpecification.cpp @@ -242,7 +242,7 @@ bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& diction return false; StringList property_values; - if (!ParsePropertyValues(property_values, property_value, false) || property_values.size() == 0) + if (!ParsePropertyValues(property_values, property_value, SplitOption::None) || property_values.empty()) return false; Property new_property; @@ -255,15 +255,17 @@ bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& diction bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictionary, ShorthandId shorthand_id, const String& property_value) const { - StringList property_values; - if (!ParsePropertyValues(property_values, property_value, true) || property_values.size() == 0) - return false; - - // Parse as a shorthand. const ShorthandDefinition* shorthand_definition = GetShorthand(shorthand_id); if (!shorthand_definition) return false; + const SplitOption split_option = + (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated ? SplitOption::Comma : SplitOption::Whitespace); + + StringList property_values; + if (!ParsePropertyValues(property_values, property_value, split_option) || property_values.empty()) + return false; + // Handle the special behavior of the flex shorthand first, otherwise it acts like 'FallThrough'. if (shorthand_definition->type == ShorthandType::Flex) { @@ -346,30 +348,28 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio } else if (shorthand_definition->type == ShorthandType::RecursiveCommaSeparated) { - StringList subvalues; - StringUtilities::ExpandString(subvalues, property_value); - size_t num_optional = 0; for (auto& item : shorthand_definition->items) if (item.optional) num_optional += 1; - if (subvalues.size() + num_optional < shorthand_definition->items.size()) + if (property_values.size() + num_optional < shorthand_definition->items.size()) { // Not enough subvalues declared. return false; } size_t subvalue_i = 0; - for (size_t i = 0; i < shorthand_definition->items.size() && subvalue_i < subvalues.size(); i++) + for (size_t i = 0; i < shorthand_definition->items.size() && subvalue_i < property_values.size(); i++) { bool result = false; + const String* subvalue = &property_values[subvalue_i]; const ShorthandItem& item = shorthand_definition->items[i]; if (item.type == ShorthandItemType::Property) - result = ParsePropertyDeclaration(dictionary, item.property_id, subvalues[subvalue_i]); + result = ParsePropertyDeclaration(dictionary, item.property_id, *subvalue); else if (item.type == ShorthandItemType::Shorthand) - result = ParseShorthandDeclaration(dictionary, item.shorthand_id, subvalues[subvalue_i]); + result = ParseShorthandDeclaration(dictionary, item.shorthand_id, *subvalue); if (result) subvalue_i += 1; @@ -448,25 +448,33 @@ String PropertySpecification::PropertiesToString(const PropertyDictionary& dicti return result; } -bool PropertySpecification::ParsePropertyValues(StringList& values_list, const String& values, bool split_values) const +bool PropertySpecification::ParsePropertyValues(StringList& values_list, const String& values, const SplitOption split_option) const { + const bool split_values = (split_option != SplitOption::None); + const bool split_by_comma = (split_option == SplitOption::Comma); + const bool split_by_whitespace = (split_option == SplitOption::Whitespace); + String value; - enum ParseState { VALUE, VALUE_PARENTHESIS, VALUE_QUOTE }; + auto SubmitValue = [&]() { + value = StringUtilities::StripWhitespace(value); + if (value.size() > 0) + { + values_list.push_back(value); + value.clear(); + } + }; + + enum ParseState { VALUE, VALUE_PARENTHESIS, VALUE_QUOTE, VALUE_QUOTE_ESCAPE_NEXT }; ParseState state = VALUE; int open_parentheses = 0; - size_t character_index = 0; - bool escape_next = false; while (character_index < values.size()) { const char character = values[character_index]; character_index++; - const bool escape_character = escape_next; - escape_next = false; - switch (state) { case VALUE: @@ -480,37 +488,20 @@ bool PropertySpecification::ParsePropertyValues(StringList& values_list, const S value.clear(); } } - else if (StringUtilities::IsWhitespace(character)) + else if (split_by_comma ? (character == ',') : StringUtilities::IsWhitespace(character)) { if (split_values) - { - value = StringUtilities::StripWhitespace(value); - if (value.size() > 0) - { - values_list.push_back(value); - value.clear(); - } - } + SubmitValue(); else value += character; } else if (character == '"') { - if (split_values) - { - value = StringUtilities::StripWhitespace(value); - if (value.size() > 0) - { - values_list.push_back(value); - value.clear(); - } - state = VALUE_QUOTE; - } + state = VALUE_QUOTE; + if (split_by_whitespace) + SubmitValue(); else - { - value += ' '; - state = VALUE_QUOTE; - } + value += (split_by_comma ? '"' : ' '); } else if (character == '(') { @@ -524,97 +515,73 @@ bool PropertySpecification::ParsePropertyValues(StringList& values_list, const S } } break; - case VALUE_PARENTHESIS: { - if (escape_character) + if (character == '(') { - if (character == ')' || character == '(' || character == '\\') - { - value += character; - } - else - { - value += '\\'; - value += character; - } + open_parentheses++; } - else + else if (character == ')') { - if (character == '(') - { - open_parentheses++; - value += character; - } - else if (character == ')') - { - open_parentheses--; - value += character; - if (open_parentheses == 0) - state = VALUE; - } - else if (character == '\\') - { - escape_next = true; - } - else - { - value += character; - } + open_parentheses--; + if (open_parentheses == 0) + state = VALUE; } + else if (character == '"') + { + state = VALUE_QUOTE; + } + + value += character; } break; - case VALUE_QUOTE: { - if (escape_character) + if (character == '"') { - if (character == '"' || character == '\\') + if (open_parentheses == 0) { - value += character; + state = VALUE; + if (split_by_whitespace) + SubmitValue(); + else + value += (split_by_comma ? '"' : ' '); } else { - value += '\\'; + state = VALUE_PARENTHESIS; value += character; } } + else if (character == '\\') + { + state = VALUE_QUOTE_ESCAPE_NEXT; + } else { - if (character == '"') - { - if (split_values) - { - value = StringUtilities::StripWhitespace(value); - if (value.size() > 0) - { - values_list.push_back(value); - value.clear(); - } - } - else - value += ' '; - state = VALUE; - } - else if (character == '\\') - { - escape_next = true; - } - else - { - value += character; - } + value += character; + } + } + break; + case VALUE_QUOTE_ESCAPE_NEXT: + { + if (character == '"' || character == '\\') + { + value += character; } + else + { + value += '\\'; + value += character; + } + state = VALUE_QUOTE; } + break; } } if (state == VALUE) - { - value = StringUtilities::StripWhitespace(value); - if (value.size() > 0) - values_list.push_back(value); - } + SubmitValue(); return true; } diff --git a/Tests/Source/UnitTests/PropertySpecification.cpp b/Tests/Source/UnitTests/PropertySpecification.cpp index 83dce50d5..f0c566eab 100644 --- a/Tests/Source/UnitTests/PropertySpecification.cpp +++ b/Tests/Source/UnitTests/PropertySpecification.cpp @@ -31,12 +31,142 @@ #include #include #include +#include #include #include +namespace Rml { +class TestPropertySpecification { +public: + using SplitOption = PropertySpecification::SplitOption; + TestPropertySpecification(const PropertySpecification& specification) : specification(specification) {} + + bool ParsePropertyValues(StringList& values_list, const String& values, SplitOption split_option) const + { + return specification.ParsePropertyValues(values_list, values, split_option); + } + +private: + const PropertySpecification& specification; +}; +} // namespace Rml + using namespace Rml; -TEST_CASE("PropertySpecification") +static String Stringify(const StringList& list) +{ + String result = "["; + for (int i = 0; i < (int)list.size(); i++) + { + if (i != 0) + result += "; "; + result += list[i]; + } + result += ']'; + return result; +} + +TEST_CASE("PropertySpecification.ParsePropertyValues") +{ + TestsSystemInterface system_interface; + TestsRenderInterface render_interface; + SetRenderInterface(&render_interface); + SetSystemInterface(&system_interface); + Rml::Initialise(); + + using SplitOption = TestPropertySpecification::SplitOption; + const TestPropertySpecification& specification = TestPropertySpecification(StyleSheetSpecification::GetPropertySpecification()); + + struct Expected { + Expected(const char* value) : values{String(value)} {} + Expected(std::initializer_list list) : values(list) {} + StringList values; + }; + + auto Parse = [&](const String& test_value, const Expected& expected, SplitOption split = SplitOption::Whitespace) { + StringList parsed_values; + bool success = specification.ParsePropertyValues(parsed_values, test_value, split); + const String split_str[] = {"none", "whitespace", "comma"}; + + INFO("\n\tSplit: ", split_str[(int)split], "\n\tInput: ", test_value, "\n\tExpected: ", Stringify(expected.values), + "\n\tResult: ", Stringify(parsed_values)); + CHECK(success); + CHECK(parsed_values == expected.values); + }; + + Parse("red", "red"); + Parse(" red ", "red"); + Parse("inline-block", "inline-block"); + + Parse("none red", {"none", "red"}); + Parse("none red", {"none", "red"}); + Parse("none\t \r \nred", {"none", "red"}); + + Parse("none red", "none red", SplitOption::None); + Parse(" none red ", "none red", SplitOption::None); + Parse("none red", "none red", SplitOption::None); + Parse("none\t \r \nred", "none\t \r \nred", SplitOption::None); + Parse("none,red", "none,red", SplitOption::None); + Parse(" \"none,red\" ", "none,red", SplitOption::None); + + Parse("none,red", {"none,red"}); + Parse("none, red", {"none,", "red"}); + Parse("none , red", {"none", ",", "red"}); + Parse("none , red", {"none", ",", "red"}); + Parse("none,,red", "none,,red"); + Parse("none,,,red", "none,,,red"); + + Parse("none,red", {"none", "red"}, SplitOption::Comma); + Parse("none, red", {"none", "red"}, SplitOption::Comma); + Parse("none , red", {"none", "red"}, SplitOption::Comma); + Parse("none , red", {"none", "red"}, SplitOption::Comma); + Parse("none,,red", {"none", "red"}, SplitOption::Comma); + Parse("none,,,red", {"none", "red"}, SplitOption::Comma); + Parse("none, , ,red", {"none", "red"}, SplitOption::Comma); + + Parse("\"string with spaces\"", "string with spaces"); + Parse("\"string with spaces\" two", {"string with spaces", "two"}); + Parse("\"string with spaces\"two", {"string with spaces", "two"}); + Parse("\"string with spaces\"two", "string with spaces two", SplitOption::None); + + Parse("\"string (with) ((parenthesis\" two", {"string (with) ((parenthesis", "two"}); + Parse("\"none,,red\" two", {"none,,red", "two"}); + + Parse("aa(bb( cc ) dd) ee", {"aa(bb( cc ) dd)", "ee"}); + Parse("aa(\"bb cc ) dd\") ee", {"aa(\"bb cc ) dd\")", "ee"}); + Parse("aa(\"bb cc \\) dd\") ee", {"aa(\"bb cc \\) dd\")", "ee"}); + Parse("aa(\"bb cc \\) dd\") ee", "aa(\"bb cc \\) dd\") ee", SplitOption::Comma); + + Parse("none(\"long string\"), aa, \"bb() cc\"", {"none(\"long string\"),", "aa,", "bb() cc"}); + Parse("none(\"long string\"), aa, \"bb() cc\"", {"none(\"long string\")", "aa", "\"bb() cc\""}, SplitOption::Comma); + Parse("none(\"long string\"), aa, bb() cc", {"none(\"long string\")", "aa", "bb() cc"}, SplitOption::Comma); + + Parse("tiled-horizontal( title-bar-l, title-bar-c, title-bar-r )", "tiled-horizontal( title-bar-l, title-bar-c, title-bar-r )"); + Parse("tiled-horizontal( title-bar-l, title-bar-c,\n\ttitle-bar-r )", "tiled-horizontal( title-bar-l, title-bar-c,\n\ttitle-bar-r )"); + Parse("tiled-horizontal( title-bar-l, title-bar-c )", "tiled-horizontal( title-bar-l, title-bar-c )", SplitOption::Comma); + + Parse("linear-gradient(110deg, #fff, #000 10%) border-box, image(invader.png)", + {"linear-gradient(110deg, #fff, #000 10%)", "border-box,", "image(invader.png)"}); + Parse("linear-gradient(110deg, #fff, #000 10%) border-box, image(invader.png)", + {"linear-gradient(110deg, #fff, #000 10%) border-box", "image(invader.png)"}, SplitOption::Comma); + + Parse(R"(image( a\) b ))", {R"(image( a\))", "b", ")"}); + Parse(R"(image( a\) b ))", R"(image( a\) b ))", SplitOption::Comma); + + Parse(R"(image( ))", R"(image( ))"); + Parse(R"(image( a\\b ))", R"(image( a\\b ))"); + Parse(R"(image( a\\\b ))", R"(image( a\\\b ))"); + Parse(R"(image( a\\\\b ))", R"(image( a\\\\b ))"); + Parse(R"(image("a\)b"))", R"(image("a\)b"))"); + Parse(R"(image("a\\)b"))", R"(image("a\)b"))"); + Parse(R"(image("a\\b"))", R"(image("a\b"))"); + Parse(R"(image("a\\\b"))", R"(image("a\\b"))"); + Parse(R"(image("a\\\\b"))", R"(image("a\\b"))"); + + Rml::Shutdown(); +} + +TEST_CASE("PropertySpecification.string") { TestsSystemInterface system_interface; TestsRenderInterface render_interface; @@ -89,13 +219,6 @@ TEST_CASE("PropertySpecification") Parse(R"(image(a, "b"))", R"(image(a, "b"))"); Parse(R"V("image(a, \"b\")")V", R"V(image(a, "b"))V"); - Parse(R"(image( ))", R"(image( ))"); - Parse(R"(image( a\)b ))", R"(image( a)b ))"); - Parse(R"(image("a\)b"))", R"(image("a)b"))"); - Parse(R"(image( a\\b ))", R"(image( a\b ))"); - Parse(R"(image( a\\\b ))", R"(image( a\\b ))"); - Parse(R"(image( a\\\\b ))", R"(image( a\\b ))"); - Rml::Shutdown(); } @@ -107,6 +230,7 @@ TEST_CASE("PropertyParser.Keyword") SetSystemInterface(&system_interface); Rml::Initialise(); + // Test keyword parser. Ensure that the keyword values are correct. PropertySpecification specification(20, 0); auto Parse = [&](const PropertyId id, const String& test_value, int expected_value) { From 1525fd7badb383a1d2052e660614f381fab2f08a Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Wed, 7 Jun 2023 11:40:22 +0200 Subject: [PATCH 31/80] Add support for rendering geometry with shaders through the render interface --- Include/RmlUi/Core/Geometry.h | 5 +++++ Include/RmlUi/Core/RenderInterface.h | 14 ++++++++++++++ Source/Core/Geometry.cpp | 24 ++++++++++++++++++++++++ Source/Core/RenderInterface.cpp | 11 +++++++++++ 4 files changed, 54 insertions(+) diff --git a/Include/RmlUi/Core/Geometry.h b/Include/RmlUi/Core/Geometry.h index 5f62aff05..df92b0dbe 100644 --- a/Include/RmlUi/Core/Geometry.h +++ b/Include/RmlUi/Core/Geometry.h @@ -63,6 +63,11 @@ class RMLUICORE_API Geometry { /// @param[in] translation The translation of the geometry. void Render(Vector2f translation); + /// Render the geometry with a compiled shader. Requires that the geometry can be compiled. + /// @param[in] shader The shader to use for rendering the geometry. + /// @param[in] translation The translation of the geometry. + void RenderWithShader(CompiledShaderHandle shader, Vector2f translation); + /// Use the geometry to set the clip mask through the render interface. Requires that the geometry can be compiled. /// @param[in] operation The clip mask operation to apply. /// @param[in] translation The translation of the geometry. diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 6e0e70522..439ad023c 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -159,6 +159,20 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// Called by RmlUi when it no longer needs a previously compiled filter. /// @param[in] filter The handle to a previously compiled filter. virtual void ReleaseCompiledFilter(CompiledFilterHandle filter); + + /// Called by RmlUi when it wants to compile a new shader. + /// @param[in] name The name of the shader. + /// @param[in] parameters The list of name-value parameters specified for the filter. + virtual CompiledShaderHandle CompileShader(const String& name, const Dictionary& parameters); + /// Called by RmlUi when it wants to render geometry using the given shader. + /// @param[in] shader The handle to a previously compiled shader. + /// @param[in] geometry The handle to a previously compiled geometry. + /// @param[in] translation The translation to apply to the geometry. + /// @param[in] texture The texture to use when rendering the geometry, or nullptr if no texture is required. + virtual void RenderShader(CompiledShaderHandle shader, CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture); + /// Called by RmlUi when it no longer needs a previously compiled shader. + /// @param[in] shader The handle to a previously compiled shader. + virtual void ReleaseCompiledShader(CompiledShaderHandle shader); }; } // namespace Rml diff --git a/Source/Core/Geometry.cpp b/Source/Core/Geometry.cpp index 42c634c45..08ade2363 100644 --- a/Source/Core/Geometry.cpp +++ b/Source/Core/Geometry.cpp @@ -116,6 +116,30 @@ void Geometry::Render(Vector2f translation) } } +void Geometry::RenderWithShader(CompiledShaderHandle shader, Vector2f translation) +{ + RenderInterface* render_interface = GetRenderInterface(); + if (!render_interface) + return; + + if (!compile_attempted) + { + if (vertices.empty() || indices.empty()) + return; + + RMLUI_ZoneScoped; + + compile_attempted = true; + compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); + } + + if (compiled_geometry) + { + translation = translation.Round(); + render_interface->RenderShader(shader, compiled_geometry, translation, texture ? texture->GetHandle() : 0); + } +} + void Geometry::RenderToClipMask(ClipMaskOperation clip_mask, Vector2f translation) { RenderInterface* render_interface = GetRenderInterface(); diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 04b53ec3a..c27fd21e6 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -85,4 +85,15 @@ CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, cons void RenderInterface::ReleaseCompiledFilter(CompiledFilterHandle /*filter*/) {} +CompiledShaderHandle RenderInterface::CompileShader(const String& /*name*/, const Dictionary& /*parameters*/) +{ + return CompiledShaderHandle{}; +} + +void RenderInterface::RenderShader(CompiledShaderHandle /*shader*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/, + TextureHandle /*texture*/) +{} + +void RenderInterface::ReleaseCompiledShader(CompiledShaderHandle /*shader*/) {} + } // namespace Rml From b6efab1d8bd15fabe7ebe98f6da983537c6b503c Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Wed, 7 Jun 2023 11:55:44 +0200 Subject: [PATCH 32/80] Add 'linear-gradient' and 'repeating-linear-gradient' decorators - New property parser for color stop lists. - Shorthand parser: Support for repeating, comma-separated patterns. - Supports most of CSS syntax: - Angle and 'to ' syntax for direction. - Multiple color stops, locations in length or percentage units, up to two locations per color. - Hints and color interpolation methods are not supported. - Uses shader to draw the gradient. - GL3 renderer support. --- Backends/RmlUi_Renderer_GL3.cpp | 175 ++++++++++- Backends/RmlUi_Renderer_GL3.h | 5 + CMake/FileList.cmake | 2 + Include/RmlUi/Core.h | 1 + Include/RmlUi/Core/DecorationTypes.h | 13 + Include/RmlUi/Core/TypeConverter.h | 11 + Include/RmlUi/Core/Types.h | 3 + Include/RmlUi/Core/Variant.h | 3 + Include/RmlUi/Core/Variant.inl | 1 + Samples/basic/effect/data/effect.rml | 3 + Source/Core/DecoratorGradient.cpp | 281 ++++++++++++++++++ Source/Core/DecoratorGradient.h | 66 ++++ Source/Core/Factory.cpp | 3 + Source/Core/PropertyParserColorStopList.cpp | 100 +++++++ Source/Core/PropertyParserColorStopList.h | 55 ++++ Source/Core/PropertyShorthandDefinition.h | 15 +- Source/Core/PropertySpecification.cpp | 32 +- Source/Core/StyleSheetSpecification.cpp | 3 + Source/Core/TypeConverter.cpp | 23 ++ Source/Core/Variant.cpp | 35 +++ .../VisualTests/shader_linear_gradient.rml | 102 +++++++ Tests/Source/UnitTests/Properties.cpp | 129 ++++++-- 22 files changed, 1003 insertions(+), 58 deletions(-) create mode 100644 Source/Core/PropertyParserColorStopList.cpp create mode 100644 Source/Core/PropertyParserColorStopList.h create mode 100644 Tests/Data/VisualTests/shader_linear_gradient.rml diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 2f2c16d76..ae908d18e 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -28,6 +28,7 @@ #include "RmlUi_Renderer_GL3.h" #include +#include #include #include #include @@ -57,6 +58,8 @@ static constexpr int NUM_MSAA_SAMPLES = 2; #define RMLUI_PREMULTIPLIED_ALPHA 1 + +#define MAX_NUM_STOPS 16 #define BLUR_SIZE 7 #define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2) @@ -65,7 +68,7 @@ static constexpr int NUM_MSAA_SAMPLES = 2; #define RMLUI_SHADER_HEADER \ RMLUI_SHADER_HEADER_VERSION \ - "#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n" + "#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n" static const char* shader_vert_main = RMLUI_SHADER_HEADER R"( uniform vec2 _translate; @@ -116,6 +119,50 @@ void main() { } )"; +enum class ShaderGradientFunction { Linear, RepeatingLinear }; // Must match shader definitions below. +static const char* shader_frag_gradient = RMLUI_SHADER_HEADER R"( +#define LINEAR 0 +#define REPEATING_LINEAR 1 +#define PI 3.14159265 + +uniform int _func; // one of the above definitions +uniform vec2 _p; // starting point +uniform vec2 _v; // vector to ending point +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; + + float dist_square = dot(_v, _v); + vec2 V = fragTexCoord - _p; + t = dot(_v, V) / dist_square; + + if (_func == REPEATING_LINEAR) + { + 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); +} +)"; + static const char* shader_vert_passthrough = RMLUI_SHADER_HEADER R"( in vec2 inPosition; in vec2 inTexCoord0; @@ -204,6 +251,7 @@ enum class ProgramId { None, Color, Texture, + Gradient, Passthrough, ColorMatrix, Blur, @@ -219,6 +267,7 @@ enum class VertShaderId { enum class FragShaderId { Color, Texture, + Gradient, Passthrough, ColorMatrix, Blur, @@ -235,13 +284,19 @@ enum class UniformId { TexCoordMin, TexCoordMax, Weights, + Func, + P, + V, + StopColors, + StopPositions, + NumStops, Count, }; namespace Gfx { static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color", "_color_matrix", - "_texelOffset", "_texCoordMin", "_texCoordMax", "_weights[0]"}; + "_texelOffset", "_texCoordMin", "_texCoordMax", "_weights[0]", "_func", "_p", "_v", "_stop_colors[0]", "_stop_positions[0]", "_num_stops"}; enum class VertexAttribute { Position, Color0, TexCoord0, Count }; static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; @@ -272,6 +327,7 @@ static const VertShaderDefinition vert_shader_definitions[] = { 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::Passthrough, "passthrough", shader_frag_passthrough}, {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, {FragShaderId::Blur, "blur", shader_frag_blur}, @@ -280,6 +336,7 @@ static const FragShaderDefinition frag_shader_definitions[] = { 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::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, @@ -1202,6 +1259,16 @@ void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vecto RenderGeometry(vertices, 4, indices, 6, RenderInterface_GL3::TexturePostprocess, {}); } +static Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0) +{ + Rml::Colourf result; + result.alpha = (1.f / 255.f) * float(c0.alpha); + result.red = (1.f / 255.f) * float(c0.red) * result.alpha; + result.green = (1.f / 255.f) * float(c0.green) * result.alpha; + result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha; + return result; +} + static void SigmaToParameters(const float desired_sigma, int& out_pass_level, float& out_sigma) { constexpr int max_num_passes = 10; @@ -1493,6 +1560,100 @@ void RenderInterface_GL3::ReleaseCompiledFilter(Rml::CompiledFilterHandle filter delete reinterpret_cast(filter); } +enum class CompiledShaderType { Invalid = 0, Gradient }; +struct CompiledShader { + CompiledShaderType type; + + // Gradient + ShaderGradientFunction gradient_function; + Rml::Vector2f p; + Rml::Vector2f v; + Rml::Vector stop_positions; + Rml::Vector stop_colors; +}; + +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] = ToPremultipliedAlpha(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); + } + + 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(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::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render shader %d.", (int)type); + } + break; + } + + Gfx::CheckGLError("RenderShader"); +} + +void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle effect_handle) +{ + delete reinterpret_cast(effect_handle); +} + void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() { const Gfx::FramebufferData& source = render_layers.GetTopLayer(); @@ -1504,16 +1665,6 @@ void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } -static inline Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0) -{ - Rml::Colourf result; - result.alpha = (1.f / 255.f) * float(c0.alpha); - result.red = (1.f / 255.f) * float(c0.red) * result.alpha; - result.green = (1.f / 255.f) * float(c0.green) * result.alpha; - result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha; - return result; -} - void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_handles) { for (const Rml::CompiledFilterHandle filter_handle : filter_handles) diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index ffb156a54..8511eed70 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -89,6 +89,11 @@ class RenderInterface_GL3 : public Rml::RenderInterface { Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; void ReleaseCompiledFilter(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 ReleaseCompiledShader(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. diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 62694c625..f5cf1b933 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -82,6 +82,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertiesIterator.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserBoxShadow.h + ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColorStopList.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.h @@ -353,6 +354,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDictionary.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserAnimation.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserBoxShadow.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColorStopList.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserDecorator.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFilter.cpp diff --git a/Include/RmlUi/Core.h b/Include/RmlUi/Core.h index bd0ffc087..bfdb0b5e3 100644 --- a/Include/RmlUi/Core.h +++ b/Include/RmlUi/Core.h @@ -40,6 +40,7 @@ #include "Core/DataTypeRegister.h" #include "Core/DataTypes.h" #include "Core/DataVariable.h" +#include "Core/DecorationTypes.h" #include "Core/Decorator.h" #include "Core/EffectSpecification.h" #include "Core/Element.h" diff --git a/Include/RmlUi/Core/DecorationTypes.h b/Include/RmlUi/Core/DecorationTypes.h index f53c46bbc..eccb1ec90 100644 --- a/Include/RmlUi/Core/DecorationTypes.h +++ b/Include/RmlUi/Core/DecorationTypes.h @@ -34,6 +34,19 @@ namespace Rml { +struct ColorStop { + Colourb color; + NumericValue position; +}; +inline bool operator==(const ColorStop& a, const ColorStop& b) +{ + return a.color == b.color && a.position == b.position; +} +inline bool operator!=(const ColorStop& a, const ColorStop& b) +{ + return !(a == b); +} + struct BoxShadow { Colourb color; NumericValue offset_x, offset_y; diff --git a/Include/RmlUi/Core/TypeConverter.h b/Include/RmlUi/Core/TypeConverter.h index d5a176b3f..6583b295e 100644 --- a/Include/RmlUi/Core/TypeConverter.h +++ b/Include/RmlUi/Core/TypeConverter.h @@ -145,6 +145,17 @@ class TypeConverter { RMLUICORE_API static bool Convert(const FontEffectsPtr& src, String& dest); }; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const ColorStopList& src, ColorStopList& dest); +}; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const ColorStopList& src, String& dest); +}; + template <> class TypeConverter { public: diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index a651eb8c4..587f99222 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -90,6 +90,7 @@ struct Transition; struct TransitionList; struct DecoratorDeclarationList; struct FilterDeclarationList; +struct ColorStop; struct BoxShadow; enum class EventId : uint16_t; enum class PropertyId : uint8_t; @@ -101,6 +102,7 @@ using FileHandle = uintptr_t; using TextureHandle = uintptr_t; using CompiledGeometryHandle = uintptr_t; using CompiledFilterHandle = uintptr_t; +using CompiledShaderHandle = uintptr_t; using DecoratorDataHandle = uintptr_t; using FontFaceHandle = uintptr_t; using FontEffectsHandle = uintptr_t; @@ -128,6 +130,7 @@ struct FontEffects { FontEffectList list; String value; }; +using ColorStopList = Vector; using BoxShadowList = Vector; // Additional smart pointers diff --git a/Include/RmlUi/Core/Variant.h b/Include/RmlUi/Core/Variant.h index 980efdd12..c9b1903cb 100644 --- a/Include/RmlUi/Core/Variant.h +++ b/Include/RmlUi/Core/Variant.h @@ -72,6 +72,7 @@ class RMLUICORE_API Variant { DECORATORSPTR = 'D', FILTERSPTR = 'F', FONTEFFECTSPTR = 'E', + COLORSTOPLIST = 'C', BOXSHADOWLIST = 'S', VOIDPTR = '*', }; @@ -159,6 +160,8 @@ class RMLUICORE_API Variant { void Set(FiltersPtr&& value); void Set(const FontEffectsPtr& value); void Set(FontEffectsPtr&& value); + void Set(const ColorStopList& value); + void Set(ColorStopList&& value); void Set(const BoxShadowList& value); void Set(BoxShadowList&& value); diff --git a/Include/RmlUi/Core/Variant.inl b/Include/RmlUi/Core/Variant.inl index 0b452068d..c5422af29 100644 --- a/Include/RmlUi/Core/Variant.inl +++ b/Include/RmlUi/Core/Variant.inl @@ -75,6 +75,7 @@ bool Variant::GetInto(T& value) const case DECORATORSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); case FILTERSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); case FONTEFFECTSPTR: return TypeConverter::Convert(*reinterpret_cast(data), value); + case COLORSTOPLIST: return TypeConverter::Convert(*(ColorStopList*)data, value); break; case BOXSHADOWLIST: return TypeConverter::Convert(*reinterpret_cast(data), value); case NONE: break; } diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index a672f6eac..b4f0768dd 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -63,6 +63,8 @@ .transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); } +.gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003); } + .brightness { filter: brightness(0.5); } .contrast { filter: contrast(0.5); } .sepia { filter: sepia(80%); } @@ -155,6 +157,7 @@
Hello, do you feel the funk?
Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index cb415e0af..ac636ffa1 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -34,9 +34,120 @@ #include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "ComputeProperty.h" namespace Rml { +// Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'. +static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector) +{ + const Vector2f delta = line_point - point; + return line_point - delta.DotProduct(line_vector) * line_vector; +} + +/// Convert all color stop positions to normalized numbers. +/// @param[in] element The element to resolve lengths against. +/// @param[in] gradient_line_length The length of the gradient line, along which color stops are placed. +/// @param[in] soft_spacing The desired minimum distance between stops to avoid aliasing, in normalized number units. +/// @param[in] unresolved_stops +/// @return A list of resolved color stops, all in number units. +static ColorStopList ResolveColorStops(Element* element, const float gradient_line_length, const float soft_spacing, + const ColorStopList& unresolved_stops) +{ + ColorStopList stops = unresolved_stops; + const int num_stops = (int)stops.size(); + + // Resolve all lengths, percentages, and angles to numbers. After this step all stops with a unit other than Number are considered as Auto. + for (ColorStop& stop : stops) + { + if (Any(stop.position.unit & Unit::LENGTH)) + { + const float resolved_position = element->ResolveLength(stop.position); + stop.position = NumericValue(resolved_position / gradient_line_length, Unit::NUMBER); + } + else if (stop.position.unit == Unit::PERCENT) + { + stop.position = NumericValue(stop.position.number * 0.01f, Unit::NUMBER); + } + else if (Any(stop.position.unit & Unit::ANGLE)) + { + stop.position = NumericValue(ComputeAngle(stop.position) * (1.f / (2.f * Math::RMLUI_PI)), Unit::NUMBER); + } + } + + // Resolve auto positions of the first and last color stops. + auto resolve_edge_stop = [](ColorStop& stop, float auto_to_number) { + if (stop.position.unit != Unit::NUMBER) + stop.position = NumericValue(auto_to_number, Unit::NUMBER); + }; + resolve_edge_stop(stops[0], 0.f); + resolve_edge_stop(stops[num_stops - 1], 1.f); + + // Ensures that color stop positions are strictly increasing, and have at least 1px spacing to avoid aliasing. + auto nudge_stop = [prev_position = stops[0].position.number](ColorStop& stop, bool update_prev = true) mutable { + stop.position.number = Math::Max(stop.position.number, prev_position); + if (update_prev) + prev_position = stop.position.number; + }; + int auto_begin_i = -1; + + // Evenly space stops with sequential auto indices, and nudge stop positions to ensure strictly increasing positions. + for (int i = 1; i < num_stops; i++) + { + ColorStop& stop = stops[i]; + if (stop.position.unit != Unit::NUMBER) + { + // Mark the first of any consecutive auto stops. + if (auto_begin_i < 0) + auto_begin_i = i; + } + else if (auto_begin_i < 0) + { + // The stop has a definite position and there are no previous autos to handle, just ensure it is properly spaced. + nudge_stop(stop); + } + else + { + // Space out all the previous auto stops, indices [auto_begin_i, i). + nudge_stop(stop, false); + const int num_auto_stops = i - auto_begin_i; + const float t0 = stops[auto_begin_i - 1].position.number; + const float t1 = stop.position.number; + + for (int j = 0; j < num_auto_stops; j++) + { + const float fraction_along_t0_t1 = float(j + 1) / float(num_auto_stops + 1); + stops[j + auto_begin_i].position = NumericValue(t0 + (t1 - t0) * fraction_along_t0_t1, Unit::NUMBER); + nudge_stop(stops[j + auto_begin_i]); + } + + nudge_stop(stop); + auto_begin_i = -1; + } + } + + // Ensures that stops are placed some minimum distance from each other to avoid aliasing, if possible. + for (int i = 1; i < num_stops - 1; i++) + { + const float p0 = stops[i - 1].position.number; + const float p1 = stops[i].position.number; + const float p2 = stops[i + 1].position.number; + float& new_position = stops[i].position.number; + + if (p1 - p0 < soft_spacing) + { + if (p2 - p0 < 2.f * soft_spacing) + new_position = 0.5f * (p2 + p0); + else + new_position = p0 + soft_spacing; + } + } + + RMLUI_ASSERT(std::all_of(stops.begin(), stops.end(), [](auto&& stop) { return stop.position.unit == Unit::NUMBER; })); + + return stops; +} + DecoratorStraightGradient::DecoratorStraightGradient() {} DecoratorStraightGradient::~DecoratorStraightGradient() {} @@ -138,4 +249,174 @@ SharedPtr DecoratorStraightGradientInstancer::InstanceDecorator(const return nullptr; } +DecoratorLinearGradient::DecoratorLinearGradient() {} + +DecoratorLinearGradient::~DecoratorLinearGradient() {} + +bool DecoratorLinearGradient::Initialise(bool in_repeating, Corner in_corner, float in_angle, const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + corner = in_corner; + angle = in_angle; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element) const +{ + RenderInterface* render_interface = GetRenderInterface(); + if (!render_interface) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty()); + + BoxArea box_area = BoxArea::Padding; + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(box_area); + + LinearGradientShape gradient_shape = CalculateShape(dimensions); + + // One-pixel minimum color stop spacing to avoid aliasing. + const float soft_spacing = 1.f / gradient_shape.length; + + ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops); + + auto element_data = MakeUnique(); + element_data->shader = render_interface->CompileShader("linear-gradient", + Dictionary{ + {"angle", Variant(angle)}, + {"p0", Variant(gradient_shape.p0)}, + {"p1", Variant(gradient_shape.p1)}, + {"length", Variant(gradient_shape.length)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + + if (!element_data->shader) + return INVALID_DECORATORDATAHANDLE; + + Geometry& geometry = element_data->geometry; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + + const Vector2f render_offset = box.GetPosition(box_area); + for (Vertex& vertex : geometry.GetVertices()) + vertex.tex_coord = vertex.position - render_offset; + + return reinterpret_cast(element_data.release()); +} + +void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + ElementData* element_data = reinterpret_cast(handle); + GetRenderInterface()->ReleaseCompiledShader(element_data->shader); + delete element_data; +} + +void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ElementData* element_data = reinterpret_cast(handle); + element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); +} + +DecoratorLinearGradient::LinearGradientShape DecoratorLinearGradient::CalculateShape(Vector2f dim) const +{ + using uint = unsigned int; + const Vector2f corners[(int)Corner::Count] = {Vector2f(dim.x, 0), dim, Vector2f(0, dim.y), Vector2f(0, 0)}; + const Vector2f center = 0.5f * dim; + + uint quadrant = 0; + Vector2f line_vector; + + if (corner == Corner::None) + { + // Find the target quadrant and unit vector for the given angle. + quadrant = uint(Math::NormaliseAngle(angle) * (4.f / (2.f * Math::RMLUI_PI))) % 4u; + line_vector = Vector2f(Math::Sin(angle), -Math::Cos(angle)); + } + else + { + // Quadrant given by the corner, need to find the vector perpendicular to the line connecting the neighboring corners. + quadrant = uint(corner); + const Vector2f v_neighbors = (corners[(quadrant + 1u) % 4u] - corners[(quadrant + 3u) % 4u]).Normalise(); + line_vector = {v_neighbors.y, -v_neighbors.x}; + } + + const uint quadrant_opposite = (quadrant + 2u) % 4u; + + const Vector2f starting_point = IntersectionPointToLineNormal(corners[quadrant_opposite], center, line_vector); + const Vector2f ending_point = IntersectionPointToLineNormal(corners[quadrant], center, line_vector); + + const float length = Math::Absolute(dim.x * line_vector.x) + Math::Absolute(-dim.y * line_vector.y); + + return LinearGradientShape{starting_point, ending_point, length}; +} + +DecoratorLinearGradientInstancer::DecoratorLinearGradientInstancer() +{ + ids.angle = RegisterProperty("angle", "180deg").AddParser("angle").GetId(); + ids.direction_to = RegisterProperty("to", "unspecified").AddParser("keyword", "unspecified, to").GetId(); + // See Direction enum for keyword values. + ids.direction_x = RegisterProperty("direction-x", "unspecified").AddParser("keyword", "unspecified=0, left=8, right=2").GetId(); + ids.direction_y = RegisterProperty("direction-y", "unspecified").AddParser("keyword", "unspecified=0, top=1, bottom=4").GetId(); + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId(); + + RegisterShorthand("direction", "angle, to, direction-x, direction-y, direction-x", ShorthandType::FallThrough); + RegisterShorthand("decorator", "direction?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorLinearGradientInstancer::~DecoratorLinearGradientInstancer() {} + +SharedPtr DecoratorLinearGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_angle = properties_.GetProperty(ids.angle); + const Property* p_direction_to = properties_.GetProperty(ids.direction_to); + const Property* p_direction_x = properties_.GetProperty(ids.direction_x); + const Property* p_direction_y = properties_.GetProperty(ids.direction_y); + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_angle || !p_direction_to || !p_direction_x || !p_direction_y || !p_color_stop_list) + return nullptr; + + using Corner = DecoratorLinearGradient::Corner; + Corner corner = Corner::None; + float angle = 0.f; + + if (p_direction_to->Get()) + { + const Direction direction = (Direction)(p_direction_x->Get() | p_direction_y->Get()); + switch (direction) + { + case Direction::Top: angle = 0.f; break; + case Direction::Right: angle = 0.5f * Math::RMLUI_PI; break; + case Direction::Bottom: angle = Math::RMLUI_PI; break; + case Direction::Left: angle = 1.5f * Math::RMLUI_PI; break; + case Direction::TopLeft: corner = Corner::TopLeft; break; + case Direction::TopRight: corner = Corner::TopRight; break; + case Direction::BottomRight: corner = Corner::BottomRight; break; + case Direction::BottomLeft: corner = Corner::BottomLeft; break; + case Direction::None: + default: return nullptr; break; + } + } + else + { + angle = ComputeAngle(p_angle->GetNumericValue()); + } + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + const bool repeating = (name == "repeating-linear-gradient"); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, corner, angle, color_stop_list)) + return decorator; + + return nullptr; +} + } // namespace Rml diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index a3a2e0a32..73e3edaa8 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -29,7 +29,9 @@ #ifndef RMLUI_CORE_DECORATORGRADIENT_H #define RMLUI_CORE_DECORATORGRADIENT_H +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/ID.h" namespace Rml { @@ -75,5 +77,69 @@ class DecoratorStraightGradientInstancer : public DecoratorInstancer { PropertyIds ids; }; +/** + Linear gradient. + */ +class DecoratorLinearGradient : public Decorator { +public: + enum class Corner { TopRight, BottomRight, BottomLeft, TopLeft, None, Count = None }; + + DecoratorLinearGradient(); + virtual ~DecoratorLinearGradient(); + + bool Initialise(bool repeating, Corner corner, float angle, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct LinearGradientShape { + // Gradient line starting and ending points. + Vector2f p0, p1; + float length; + }; + struct ElementData { + Geometry geometry; + CompiledShaderHandle shader; + }; + + LinearGradientShape CalculateShape(Vector2f box_dimensions) const; + + bool repeating = false; + Corner corner = Corner::None; + float angle = 0.f; + ColorStopList color_stops; +}; + +class DecoratorLinearGradientInstancer : public DecoratorInstancer { +public: + DecoratorLinearGradientInstancer(); + ~DecoratorLinearGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + enum class Direction { + None = 0, + Top = 1, + Right = 2, + Bottom = 4, + Left = 8, + TopLeft = Top | Left, + TopRight = Top | Right, + BottomRight = Bottom | Right, + BottomLeft = Bottom | Left, + }; + struct PropertyIds { + PropertyId angle; + PropertyId direction_to, direction_x, direction_y; + PropertyId color_stop_list; + }; + PropertyIds ids; +}; + } // namespace Rml #endif diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 746b61a00..6f6594bc7 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -153,6 +153,7 @@ struct DefaultInstancers { DecoratorTiledImageInstancer decorator_image; DecoratorNinePatchInstancer decorator_ninepatch; DecoratorStraightGradientInstancer decorator_straight_gradient; + DecoratorLinearGradientInstancer decorator_linear_gradient; // Filters FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; @@ -244,6 +245,8 @@ bool Factory::Initialise() RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient); RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient); RegisterDecoratorInstancer("vertical-gradient", &default_instancers->decorator_straight_gradient); + RegisterDecoratorInstancer("linear-gradient", &default_instancers->decorator_linear_gradient); + RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers->decorator_linear_gradient); // Filter instancers RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate); diff --git a/Source/Core/PropertyParserColorStopList.cpp b/Source/Core/PropertyParserColorStopList.cpp new file mode 100644 index 000000000..3bc717773 --- /dev/null +++ b/Source/Core/PropertyParserColorStopList.cpp @@ -0,0 +1,100 @@ +/* + * 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 "PropertyParserColorStopList.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include + +namespace Rml { + +PropertyParserColorStopList::PropertyParserColorStopList(PropertyParser* parser_color) : + parser_color(parser_color), parser_length_percent_angle(Unit::LENGTH_PERCENT | Unit::ANGLE, Unit::PERCENT) +{ + RMLUI_ASSERT(parser_color); +} + +PropertyParserColorStopList::~PropertyParserColorStopList() {} + +bool PropertyParserColorStopList::ParseValue(Property& property, const String& value, const ParameterMap& parameters) const +{ + const ParameterMap empty_parameter_map; + + if (value.empty()) + return false; + + StringList color_stop_str_list; + StringUtilities::ExpandString(color_stop_str_list, value, ',', '(', ')'); + + if (color_stop_str_list.empty()) + return false; + + const Unit accepted_units = (parameters.count("angle") ? (Unit::ANGLE | Unit::PERCENT) : Unit::LENGTH_PERCENT); + + ColorStopList color_stops; + color_stops.reserve(color_stop_str_list.size()); + + for (const String& color_stop_str : color_stop_str_list) + { + StringList values; + StringUtilities::ExpandString(values, color_stop_str, ' ', '(', ')', true); + + if (values.empty() || values.size() > 3) + return false; + + Property p_color; + if (!parser_color->ParseValue(p_color, values[0], empty_parameter_map)) + return false; + + ColorStop color_stop = {}; + color_stop.color = p_color.Get(); + + if (values.size() <= 1) + color_stops.push_back(color_stop); + + for (size_t i = 1; i < values.size(); i++) + { + Property p_position(Style::LengthPercentageAuto::Auto); + if (!parser_length_percent_angle.ParseValue(p_position, values[i], empty_parameter_map)) + return false; + + if (Any(p_position.unit & accepted_units)) + color_stop.position = NumericValue(p_position.Get(), p_position.unit); + else if (p_position.unit != Unit::KEYWORD) + return false; + + color_stops.push_back(color_stop); + } + } + + property.value = Variant(std::move(color_stops)); + property.unit = Unit::COLORSTOPLIST; + + return true; +} +} // namespace Rml diff --git a/Source/Core/PropertyParserColorStopList.h b/Source/Core/PropertyParserColorStopList.h new file mode 100644 index 000000000..960042c9b --- /dev/null +++ b/Source/Core/PropertyParserColorStopList.h @@ -0,0 +1,55 @@ +/* + * 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_CORE_PROPERTYPARSERCOLORSTOPLIST_H +#define RMLUI_CORE_PROPERTYPARSERCOLORSTOPLIST_H + +#include "../../Include/RmlUi/Core/PropertyParser.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "PropertyParserNumber.h" + +namespace Rml { + +/** + A property parser that parses color stop lists, particularly for gradients. + */ + +class PropertyParserColorStopList : public PropertyParser { +public: + PropertyParserColorStopList(PropertyParser* parser_color); + virtual ~PropertyParserColorStopList(); + + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + +private: + PropertyParser* parser_color; + PropertyParserNumber parser_length_percent_angle; +}; + +} // namespace Rml +#endif diff --git a/Source/Core/PropertyShorthandDefinition.h b/Source/Core/PropertyShorthandDefinition.h index f830644cb..4c23cb43c 100644 --- a/Source/Core/PropertyShorthandDefinition.h +++ b/Source/Core/PropertyShorthandDefinition.h @@ -41,15 +41,17 @@ enum class ShorthandItemType { Invalid, Property, Shorthand }; // Each entry in a shorthand points either to another shorthand or a property struct ShorthandItem { - ShorthandItem() : type(ShorthandItemType::Invalid), property_id(PropertyId::Invalid), property_definition(nullptr), optional(false) {} - ShorthandItem(PropertyId id, const PropertyDefinition* definition, bool optional) : - type(ShorthandItemType::Property), property_id(id), property_definition(definition), optional(optional) + ShorthandItem() : type(ShorthandItemType::Invalid), property_id(PropertyId::Invalid), property_definition(nullptr) {} + ShorthandItem(PropertyId id, const PropertyDefinition* definition, bool optional, bool repeats) : + type(ShorthandItemType::Property), optional(optional), repeats(repeats), property_id(id), property_definition(definition) {} - ShorthandItem(ShorthandId id, const ShorthandDefinition* definition, bool optional) : - type(ShorthandItemType::Shorthand), shorthand_id(id), shorthand_definition(definition), optional(optional) + ShorthandItem(ShorthandId id, const ShorthandDefinition* definition, bool optional, bool repeats) : + type(ShorthandItemType::Shorthand), optional(optional), repeats(repeats), shorthand_id(id), shorthand_definition(definition) {} - ShorthandItemType type; + ShorthandItemType type = ShorthandItemType::Invalid; + bool optional = false; + bool repeats = false; union { PropertyId property_id; ShorthandId shorthand_id; @@ -58,7 +60,6 @@ struct ShorthandItem { const PropertyDefinition* property_definition; const ShorthandDefinition* shorthand_definition; }; - bool optional; }; // A list of shorthands or properties diff --git a/Source/Core/PropertySpecification.cpp b/Source/Core/PropertySpecification.cpp index 6a5bda733..89b0a14e2 100644 --- a/Source/Core/PropertySpecification.cpp +++ b/Source/Core/PropertySpecification.cpp @@ -138,6 +138,7 @@ ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_nam { ShorthandItem item; bool optional = false; + bool repeats = false; String name = raw_name; if (!raw_name.empty() && raw_name.back() == '?') @@ -145,13 +146,18 @@ ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_nam optional = true; name.pop_back(); } + if (!raw_name.empty() && raw_name.back() == '#') + { + repeats = true; + name.pop_back(); + } PropertyId property_id = property_map->GetId(name); if (property_id != PropertyId::Invalid) { // We have a valid property if (const PropertyDefinition* property = GetProperty(property_id)) - item = ShorthandItem(property_id, property, optional); + item = ShorthandItem(property_id, property, optional, repeats); } else { @@ -162,7 +168,7 @@ ShorthandId PropertySpecification::RegisterShorthand(const String& shorthand_nam if (shorthand_id != ShorthandId::Invalid && (type == ShorthandType::RecursiveRepeat || type == ShorthandType::RecursiveCommaSeparated)) { if (const ShorthandDefinition* shorthand = GetShorthand(shorthand_id)) - item = ShorthandItem(shorthand_id, shorthand, optional); + item = ShorthandItem(shorthand_id, shorthand, optional, repeats); } } @@ -267,10 +273,10 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio return false; // Handle the special behavior of the flex shorthand first, otherwise it acts like 'FallThrough'. - if (shorthand_definition->type == ShorthandType::Flex) + if (shorthand_definition->type == ShorthandType::Flex && !property_values.empty()) { RMLUI_ASSERT(shorthand_definition->items.size() == 3); - if (!property_values.empty() && property_values[0] == "none") + if (property_values[0] == "none") { property_values = {"0", "0", "auto"}; } @@ -292,8 +298,7 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio } } - // If this definition is a 'box'-style shorthand (x-top, x-right, x-bottom, x-left, etc) and there are fewer - // than four values + // If this definition is a 'box'-style shorthand (x-top, x-right, x-bottom, x-left, etc) and there are fewer than four values if (shorthand_definition->type == ShorthandType::Box && property_values.size() < 4) { // This array tells which property index each side is parsed from @@ -360,12 +365,22 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio } size_t subvalue_i = 0; + String temp_subvalue; for (size_t i = 0; i < shorthand_definition->items.size() && subvalue_i < property_values.size(); i++) { bool result = false; const String* subvalue = &property_values[subvalue_i]; + const ShorthandItem& item = shorthand_definition->items[i]; + if (item.repeats) + { + property_values.erase(property_values.begin(), property_values.begin() + subvalue_i); + temp_subvalue.clear(); + StringUtilities::JoinString(temp_subvalue, property_values); + subvalue = &temp_subvalue; + } + if (item.type == ShorthandItemType::Property) result = ParsePropertyDeclaration(dictionary, item.property_id, *subvalue); else if (item.type == ShorthandItemType::Shorthand) @@ -373,8 +388,11 @@ bool PropertySpecification::ParseShorthandDeclaration(PropertyDictionary& dictio if (result) subvalue_i += 1; - else if (!item.optional) + else if (item.repeats || !item.optional) return false; + + if (item.repeats) + break; } } else diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index 29f20082e..a907e249a 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -32,6 +32,7 @@ #include "IdNameMap.h" #include "PropertyParserAnimation.h" #include "PropertyParserBoxShadow.h" +#include "PropertyParserColorStopList.h" #include "PropertyParserColour.h" #include "PropertyParserDecorator.h" #include "PropertyParserFilter.h" @@ -59,6 +60,7 @@ struct DefaultStyleSheetParsers : NonCopyMoveable { PropertyParserAnimation animation = PropertyParserAnimation(PropertyParserAnimation::ANIMATION_PARSER); PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER); PropertyParserColour color = PropertyParserColour(); + PropertyParserColorStopList color_stop_list = PropertyParserColorStopList(&color); PropertyParserDecorator decorator = PropertyParserDecorator(); PropertyParserFilter filter = PropertyParserFilter(); PropertyParserFontEffect font_effect = PropertyParserFontEffect(); @@ -255,6 +257,7 @@ void StyleSheetSpecification::RegisterDefaultParsers() RegisterParser("animation", &default_parsers->animation); RegisterParser("transition", &default_parsers->transition); RegisterParser("color", &default_parsers->color); + RegisterParser("color_stop_list", &default_parsers->color_stop_list); RegisterParser("decorator", &default_parsers->decorator); RegisterParser("filter", &default_parsers->filter); RegisterParser("font_effect", &default_parsers->font_effect); diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 3d6b3cb0e..cade95bdc 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -224,6 +224,29 @@ bool TypeConverter::Convert(const FontEffectsPtr& src, S return true; } +bool TypeConverter::Convert(const ColorStopList& src, ColorStopList& dest) +{ + dest = src; + return true; +} + +bool TypeConverter::Convert(const ColorStopList& src, String& dest) +{ + dest.clear(); + for (size_t i = 0; i < src.size(); i++) + { + const ColorStop& stop = src[i]; + dest += ToString(stop.color); + + if (Any(stop.position.unit & Unit::NUMBER_LENGTH_PERCENT)) + dest += " " + ToString(stop.position.number) + ToString(stop.position.unit); + + if (i < src.size() - 1) + dest += ", "; + } + return true; +} + bool TypeConverter::Convert(const BoxShadowList& src, BoxShadowList& dest) { dest = src; diff --git a/Source/Core/Variant.cpp b/Source/Core/Variant.cpp index e79f4877d..df5b6c563 100644 --- a/Source/Core/Variant.cpp +++ b/Source/Core/Variant.cpp @@ -45,6 +45,8 @@ Variant::Variant() static_assert(sizeof(DecoratorsPtr) <= LOCAL_DATA_SIZE, "Local data too small for DecoratorsPtr"); static_assert(sizeof(FiltersPtr) <= LOCAL_DATA_SIZE, "Local data too small for FiltersPtr"); static_assert(sizeof(FontEffectsPtr) <= LOCAL_DATA_SIZE, "Local data too small for FontEffectsPtr"); + static_assert(sizeof(ColorStopList) <= LOCAL_DATA_SIZE, "Local data too small for ColorStopList"); + static_assert(sizeof(BoxShadowList) <= LOCAL_DATA_SIZE, "Local data too small for BoxShadowList"); } Variant::Variant(const Variant& copy) @@ -113,6 +115,12 @@ void Variant::Clear() font_effects->~shared_ptr(); } break; + case COLORSTOPLIST: + { + ColorStopList* value = (ColorStopList*)data; + value->~ColorStopList(); + } + break; case BOXSHADOWLIST: { BoxShadowList* value = (BoxShadowList*)data; @@ -137,6 +145,7 @@ void Variant::Set(const Variant& copy) case DECORATORSPTR: Set(*reinterpret_cast(copy.data)); break; case FILTERSPTR: Set(*reinterpret_cast(copy.data)); break; case FONTEFFECTSPTR: Set(*reinterpret_cast(copy.data)); break; + case COLORSTOPLIST: Set(*reinterpret_cast(copy.data)); break; case BOXSHADOWLIST: Set(*reinterpret_cast(copy.data)); break; default: memcpy(data, copy.data, LOCAL_DATA_SIZE); @@ -157,6 +166,7 @@ void Variant::Set(Variant&& other) case DECORATORSPTR: Set(std::move(*reinterpret_cast(other.data))); break; case FILTERSPTR: Set(std::move(*reinterpret_cast(other.data))); break; case FONTEFFECTSPTR: Set(std::move(*reinterpret_cast(other.data))); break; + case COLORSTOPLIST: Set(std::move(*reinterpret_cast(other.data))); break; case BOXSHADOWLIST: Set(std::move(*reinterpret_cast(other.data))); break; default: memcpy(data, other.data, LOCAL_DATA_SIZE); @@ -439,6 +449,30 @@ void Variant::Set(FontEffectsPtr&& value) new (data) FontEffectsPtr(std::move(value)); } } +void Variant::Set(const ColorStopList& value) +{ + if (type == COLORSTOPLIST) + { + *(ColorStopList*)data = value; + } + else + { + type = COLORSTOPLIST; + new (data) ColorStopList(value); + } +} +void Variant::Set(ColorStopList&& value) +{ + if (type == COLORSTOPLIST) + { + (*(ColorStopList*)data) = std::move(value); + } + else + { + type = COLORSTOPLIST; + new (data) ColorStopList(std::move(value)); + } +} void Variant::Set(const BoxShadowList& value) { if (type == BOXSHADOWLIST) @@ -516,6 +550,7 @@ bool Variant::operator==(const Variant& other) const case DECORATORSPTR: return DEFAULT_VARIANT_COMPARE(DecoratorsPtr); case FILTERSPTR: return DEFAULT_VARIANT_COMPARE(FiltersPtr); case FONTEFFECTSPTR: return DEFAULT_VARIANT_COMPARE(FontEffectsPtr); + case COLORSTOPLIST: return DEFAULT_VARIANT_COMPARE(ColorStopList); case BOXSHADOWLIST: return DEFAULT_VARIANT_COMPARE(BoxShadowList); case NONE: return true; } diff --git a/Tests/Data/VisualTests/shader_linear_gradient.rml b/Tests/Data/VisualTests/shader_linear_gradient.rml new file mode 100644 index 000000000..31cfe8913 --- /dev/null +++ b/Tests/Data/VisualTests/shader_linear_gradient.rml @@ -0,0 +1,102 @@ + + + linear-gradient + + + + + + + + +Yellow (top) to blue (bottom) [equivalent] + +
+
+
+
+
+
+ + +Yellow (top-left) to blue (bottom-right) [equivalent] + +
+
+ + +Yellow (top), blue, green (bottom) + +
+ + +Corner-to-corner, first: red (bottom-left), white, blue (top-right) + +
+
+
+
+
+
+
+
+ + +Repeating linear gradients + +
+
+ + +Red (left), white, blue (right). Should not show any grayish transition colors if the backend correctly interpolates in premultiplied alpha space. + +
+ + + diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index 51f4bf7bf..ad25400c2 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -29,8 +29,10 @@ #include "../Common/TestsInterface.h" #include #include +#include #include #include +#include #include using namespace Rml; @@ -50,42 +52,105 @@ TEST_CASE("Properties") Context* context = Rml::CreateContext("main", window_size); ElementDocument* document = context->CreateDocument(); - struct FlexTestCase { - String flex_value; - - struct ExpectedValues { - float flex_grow; - float flex_shrink; - String flex_basis; - } expected; - }; - - FlexTestCase tests[] = { - {"", {0.f, 1.f, "auto"}}, - {"none", {0.f, 0.f, "auto"}}, - {"auto", {1.f, 1.f, "auto"}}, - {"1", {1.f, 1.f, "0px"}}, - {"2", {2.f, 1.f, "0px"}}, - {"2 0", {2.f, 0.f, "0px"}}, - {"2 3", {2.f, 3.f, "0px"}}, - {"2 auto", {2.f, 1.f, "auto"}}, - {"2 0 auto", {2.f, 0.f, "auto"}}, - {"0 0 auto", {0.f, 0.f, "auto"}}, - {"0 0 50px", {0.f, 0.f, "50px"}}, - {"0 0 50px", {0.f, 0.f, "50px"}}, - {"0 0 0", {0.f, 0.f, "0px"}}, - }; - - for (const FlexTestCase& test : tests) + SUBCASE("flex") { - if (!test.flex_value.empty()) + struct FlexTestCase { + String flex_value; + + struct ExpectedValues { + float flex_grow; + float flex_shrink; + String flex_basis; + } expected; + }; + + FlexTestCase tests[] = { + {"", {0.f, 1.f, "auto"}}, + {"none", {0.f, 0.f, "auto"}}, + {"auto", {1.f, 1.f, "auto"}}, + {"1", {1.f, 1.f, "0px"}}, + {"2", {2.f, 1.f, "0px"}}, + {"2 0", {2.f, 0.f, "0px"}}, + {"2 3", {2.f, 3.f, "0px"}}, + {"2 auto", {2.f, 1.f, "auto"}}, + {"2 0 auto", {2.f, 0.f, "auto"}}, + {"0 0 auto", {0.f, 0.f, "auto"}}, + {"0 0 50px", {0.f, 0.f, "50px"}}, + {"0 0 50px", {0.f, 0.f, "50px"}}, + {"0 0 0", {0.f, 0.f, "0px"}}, + }; + + for (const FlexTestCase& test : tests) { - CHECK(document->SetProperty("flex", test.flex_value)); + if (!test.flex_value.empty()) + { + CHECK(document->SetProperty("flex", test.flex_value)); + } + + CHECK(document->GetProperty("flex-grow") == test.expected.flex_grow); + CHECK(document->GetProperty("flex-shrink") == test.expected.flex_shrink); + CHECK(document->GetProperty("flex-basis")->ToString() == test.expected.flex_basis); } + } - CHECK(document->GetProperty("flex-grow") == test.expected.flex_grow); - CHECK(document->GetProperty("flex-shrink") == test.expected.flex_shrink); - CHECK(document->GetProperty("flex-basis")->ToString() == test.expected.flex_basis); + SUBCASE("gradient") + { + auto ParseGradient = [&](const String& value) -> ColorStopList { + document->SetProperty("decorator", "linear-gradient(" + value + ")"); + auto decorators = document->GetProperty("decorator"); + if (!decorators || decorators->list.size() != 1) + return {}; + for (auto& id_property : decorators->list.front().properties.GetProperties()) + { + if (id_property.second.unit == Unit::COLORSTOPLIST) + return id_property.second.Get(); + } + return {}; + }; + + struct GradientTestCase { + String value; + ColorStopList expected_color_stops; + }; + + GradientTestCase test_cases[] = { + { + "red, blue", + { + ColorStop{Colourb(255, 0, 0), NumericValue{}}, + ColorStop{Colourb(0, 0, 255), NumericValue{}}, + }, + }, + { + "red 5px, blue 50%", + { + ColorStop{Colourb(255, 0, 0), NumericValue{5.f, Unit::PX}}, + ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + }, + }, + { + "red, #00f 50%, rgba(0, 255,0, 150) 10dp", + { + ColorStop{Colourb(255, 0, 0), NumericValue{}}, + ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + ColorStop{Colourb(0, 255, 0, 150), NumericValue{10.f, Unit::DP}}, + }, + }, + { + "red 50px 20%, blue 10in", + { + ColorStop{Colourb(255, 0, 0), NumericValue{50.f, Unit::PX}}, + ColorStop{Colourb(255, 0, 0), NumericValue{20.f, Unit::PERCENT}}, + ColorStop{Colourb(0, 0, 255), NumericValue{10.f, Unit::INCH}}, + }, + }, + }; + + for (const GradientTestCase& test_case : test_cases) + { + const ColorStopList result = ParseGradient(test_case.value); + CHECK(result == test_case.expected_color_stops); + } } Rml::Shutdown(); From c3622e669c02986a3e1e7f1205b0f51fdf02f7e4 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Wed, 7 Jun 2023 14:25:18 +0200 Subject: [PATCH 33/80] Allow decorators to specify paint area [breaking change] Breaking change: 'Decorator::GenerateElementData()' has a new paint area parameter. --- Include/RmlUi/Core/Decorator.h | 3 +- Include/RmlUi/Core/StyleSheetTypes.h | 1 + Samples/basic/effect/data/effect.rml | 2 +- Samples/invaders/src/DecoratorDefender.cpp | 2 +- Samples/invaders/src/DecoratorDefender.h | 7 +-- Samples/invaders/src/DecoratorStarfield.cpp | 2 +- Samples/invaders/src/DecoratorStarfield.h | 7 +-- Samples/luainvaders/src/DecoratorDefender.cpp | 2 +- Samples/luainvaders/src/DecoratorDefender.h | 7 +-- .../luainvaders/src/DecoratorStarfield.cpp | 2 +- Samples/luainvaders/src/DecoratorStarfield.h | 7 +-- Source/Core/DecoratorGradient.cpp | 21 +++---- Source/Core/DecoratorGradient.h | 4 +- Source/Core/DecoratorNinePatch.cpp | 11 +++- Source/Core/DecoratorNinePatch.h | 2 +- Source/Core/DecoratorTiledBox.cpp | 61 ++++++++++--------- Source/Core/DecoratorTiledBox.h | 2 +- Source/Core/DecoratorTiledHorizontal.cpp | 27 ++++---- Source/Core/DecoratorTiledHorizontal.h | 2 +- Source/Core/DecoratorTiledImage.cpp | 10 +-- Source/Core/DecoratorTiledImage.h | 2 +- Source/Core/DecoratorTiledVertical.cpp | 25 ++++---- Source/Core/DecoratorTiledVertical.h | 2 +- Source/Core/ElementAnimation.cpp | 9 +-- Source/Core/ElementDecoration.cpp | 20 +++--- Source/Core/ElementDecoration.h | 1 + Source/Core/PropertyParserDecorator.cpp | 51 ++++++++++++++-- Source/Core/PropertyParserDecorator.h | 5 ++ Source/Core/TypeConverter.cpp | 17 +++++- 29 files changed, 187 insertions(+), 127 deletions(-) diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index 8a52b5252..40a1925ce 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -57,8 +57,9 @@ class RMLUICORE_API Decorator { /// Called on a decorator to generate any required per-element data for a newly decorated element. /// @param[in] element The newly decorated element. + /// @param[in] paint_area Determines the element's area to be painted by the decorator. /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - virtual DecoratorDataHandle GenerateElementData(Element* element) const = 0; + virtual DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const = 0; /// Called to release element data generated by this decorator. /// @param[in] element_data The element data handle to release. virtual void ReleaseElementData(DecoratorDataHandle element_data) const = 0; diff --git a/Include/RmlUi/Core/StyleSheetTypes.h b/Include/RmlUi/Core/StyleSheetTypes.h index f5bc85120..e16c994bb 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -63,6 +63,7 @@ struct DecoratorDeclaration { String type; DecoratorInstancer* instancer; PropertyDictionary properties; + BoxArea paint_area; }; struct DecoratorDeclarationList { Vector list; diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index b4f0768dd..0d652f24c 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -63,7 +63,7 @@ .transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); } -.gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003); } +.gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box; } .brightness { filter: brightness(0.5); } .contrast { filter: contrast(0.5); } diff --git a/Samples/invaders/src/DecoratorDefender.cpp b/Samples/invaders/src/DecoratorDefender.cpp index 5c5e5dccf..686164d5d 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -49,7 +49,7 @@ bool DecoratorDefender::Initialise(const Rml::Texture& texture) return true; } -Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/) const +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/, Rml::BoxArea /*paint_area*/) const { return Rml::Decorator::INVALID_DECORATORDATAHANDLE; } diff --git a/Samples/invaders/src/DecoratorDefender.h b/Samples/invaders/src/DecoratorDefender.h index 7626f2872..2324db157 100644 --- a/Samples/invaders/src/DecoratorDefender.h +++ b/Samples/invaders/src/DecoratorDefender.h @@ -38,16 +38,11 @@ class DecoratorDefender : public Rml::Decorator { bool Initialise(const Rml::Texture& texture); /// Called on a decorator to generate any required per-element data for a newly decorated element. - /// @param element[in] The newly decorated element. - /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) const override; + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; /// Called to release element data generated by this decorator. - /// @param element_data[in] The element data handle to release. void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; /// Called to render the decorator on an element. - /// @param element[in] The element to render the decorator on. - /// @param element_data[in] The handle to the data generated by the decorator for the element. void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; private: diff --git a/Samples/invaders/src/DecoratorStarfield.cpp b/Samples/invaders/src/DecoratorStarfield.cpp index 6b24cd135..3805ff3f7 100644 --- a/Samples/invaders/src/DecoratorStarfield.cpp +++ b/Samples/invaders/src/DecoratorStarfield.cpp @@ -55,7 +55,7 @@ bool DecoratorStarfield::Initialise(int _num_layers, const Rml::Colourb& _top_co return true; } -Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element) const +Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const { const double t = Rml::GetSystemInterface()->GetElapsedTime(); diff --git a/Samples/invaders/src/DecoratorStarfield.h b/Samples/invaders/src/DecoratorStarfield.h index 7cc528771..d53e10291 100644 --- a/Samples/invaders/src/DecoratorStarfield.h +++ b/Samples/invaders/src/DecoratorStarfield.h @@ -40,16 +40,11 @@ class DecoratorStarfield : public Rml::Decorator { int top_density, int bottom_density); /// Called on a decorator to generate any required per-element data for a newly decorated element. - /// @param[in] element The newly decorated element. - /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) const override; + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; /// Called to release element data generated by this decorator. - /// @param[in] element_data The element data handle to release. void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; /// Called to render the decorator on an element. - /// @param[in] element The element to render the decorator on. - /// @param[in] element_data The handle to the data generated by the decorator for the element. void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; private: diff --git a/Samples/luainvaders/src/DecoratorDefender.cpp b/Samples/luainvaders/src/DecoratorDefender.cpp index 5c5e5dccf..686164d5d 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -49,7 +49,7 @@ bool DecoratorDefender::Initialise(const Rml::Texture& texture) return true; } -Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/) const +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/, Rml::BoxArea /*paint_area*/) const { return Rml::Decorator::INVALID_DECORATORDATAHANDLE; } diff --git a/Samples/luainvaders/src/DecoratorDefender.h b/Samples/luainvaders/src/DecoratorDefender.h index 4f808e910..d23cf42c8 100644 --- a/Samples/luainvaders/src/DecoratorDefender.h +++ b/Samples/luainvaders/src/DecoratorDefender.h @@ -38,16 +38,11 @@ class DecoratorDefender : public Rml::Decorator { bool Initialise(const Rml::Texture& texture); /// Called on a decorator to generate any required per-element data for a newly decorated element. - /// @param element[in] The newly decorated element. - /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) const override; + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; /// Called to release element data generated by this decorator. - /// @param element_data[in] The element data handle to release. void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; /// Called to render the decorator on an element. - /// @param element[in] The element to render the decorator on. - /// @param element_data[in] The handle to the data generated by the decorator for the element. void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; private: diff --git a/Samples/luainvaders/src/DecoratorStarfield.cpp b/Samples/luainvaders/src/DecoratorStarfield.cpp index 6b24cd135..3805ff3f7 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.cpp +++ b/Samples/luainvaders/src/DecoratorStarfield.cpp @@ -55,7 +55,7 @@ bool DecoratorStarfield::Initialise(int _num_layers, const Rml::Colourb& _top_co return true; } -Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element) const +Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const { const double t = Rml::GetSystemInterface()->GetElapsedTime(); diff --git a/Samples/luainvaders/src/DecoratorStarfield.h b/Samples/luainvaders/src/DecoratorStarfield.h index 80c350bce..329ea2e2c 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.h +++ b/Samples/luainvaders/src/DecoratorStarfield.h @@ -40,16 +40,11 @@ class DecoratorStarfield : public Rml::Decorator { int top_density, int bottom_density); /// Called on a decorator to generate any required per-element data for a newly decorated element. - /// @param[in] element The newly decorated element. - /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element) const override; + Rml::DecoratorDataHandle GenerateElementData(Rml::Element* element, Rml::BoxArea paint_area) const override; /// Called to release element data generated by this decorator. - /// @param[in] element_data The element data handle to release. void ReleaseElementData(Rml::DecoratorDataHandle element_data) const override; /// Called to render the decorator on an element. - /// @param[in] element The element to render the decorator on. - /// @param[in] element_data The handle to the data generated by the decorator for the element. void RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data) const override; private: diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index ac636ffa1..982c4e415 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -160,7 +160,7 @@ bool DecoratorStraightGradient::Initialise(const Direction in_direction, const C return true; } -DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element, BoxArea paint_area) const { Geometry* geometry = new Geometry(); const Box& box = element->GetBox(); @@ -168,7 +168,7 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem const ComputedValues& computed = element->GetComputedValues(); const float opacity = computed.opacity(); - GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb()); + GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb(), paint_area); // Apply opacity Colourb colour_start = start; @@ -176,8 +176,8 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem Colourb colour_stop = stop; colour_stop.alpha = (byte)(opacity * (float)colour_stop.alpha); - const Vector2f padding_offset = box.GetPosition(BoxArea::Padding); - const Vector2f padding_size = box.GetSize(BoxArea::Padding); + const Vector2f offset = box.GetPosition(paint_area); + const Vector2f size = box.GetSize(paint_area); Vector& vertices = geometry->GetVertices(); @@ -185,7 +185,7 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem { for (int i = 0; i < (int)vertices.size(); i++) { - const float t = Math::Clamp((vertices[i].position.x - padding_offset.x) / padding_size.x, 0.0f, 1.0f); + const float t = Math::Clamp((vertices[i].position.x - offset.x) / size.x, 0.0f, 1.0f); vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop); } } @@ -193,7 +193,7 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem { for (int i = 0; i < (int)vertices.size(); i++) { - const float t = Math::Clamp((vertices[i].position.y - padding_offset.y) / padding_size.y, 0.0f, 1.0f); + const float t = Math::Clamp((vertices[i].position.y - offset.y) / size.y, 0.0f, 1.0f); vertices[i].colour = Math::RoundedLerp(t, colour_start, colour_stop); } } @@ -262,7 +262,7 @@ bool DecoratorLinearGradient::Initialise(bool in_repeating, Corner in_corner, fl return !color_stops.empty(); } -DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element, BoxArea paint_area) const { RenderInterface* render_interface = GetRenderInterface(); if (!render_interface) @@ -270,9 +270,8 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen RMLUI_ASSERT(!color_stops.empty()); - BoxArea box_area = BoxArea::Padding; const Box& box = element->GetBox(); - const Vector2f dimensions = box.GetSize(box_area); + const Vector2f dimensions = box.GetSize(paint_area); LinearGradientShape gradient_shape = CalculateShape(dimensions); @@ -299,9 +298,9 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), paint_area); - const Vector2f render_offset = box.GetPosition(box_area); + const Vector2f render_offset = box.GetPosition(paint_area); for (Vertex& vertex : geometry.GetVertices()) vertex.tex_coord = vertex.position - render_offset; diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index 73e3edaa8..5243fd571 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -52,7 +52,7 @@ class DecoratorStraightGradient : public Decorator { bool Initialise(Direction direction, Colourb start, Colourb stop); - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; void ReleaseElementData(DecoratorDataHandle element_data) const override; void RenderElement(Element* element, DecoratorDataHandle element_data) const override; @@ -89,7 +89,7 @@ class DecoratorLinearGradient : public Decorator { bool Initialise(bool repeating, Corner corner, float angle, const ColorStopList& color_stops); - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; void ReleaseElementData(DecoratorDataHandle element_data) const override; void RenderElement(Element* element, DecoratorDataHandle element_data) const override; diff --git a/Source/Core/DecoratorNinePatch.cpp b/Source/Core/DecoratorNinePatch.cpp index ba5587e59..8a0dfdbd0 100644 --- a/Source/Core/DecoratorNinePatch.cpp +++ b/Source/Core/DecoratorNinePatch.cpp @@ -54,7 +54,7 @@ bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectang return (texture_index >= 0); } -DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, BoxArea paint_area) const { const auto& computed = element->GetComputedValues(); @@ -64,7 +64,8 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co data->SetTexture(texture); const Vector2f texture_dimensions(texture->GetDimensions()); - const Vector2f surface_dimensions = element->GetBox().GetSize(BoxArea::Padding).Round(); + const Vector2f surface_offset = element->GetBox().GetPosition(paint_area); + const Vector2f surface_dimensions = element->GetBox().GetSize(paint_area).Round(); const float opacity = computed.opacity(); Colourb quad_colour = computed.image_color(); @@ -126,6 +127,10 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co } } + // Now offset all positions, relative to the border box. + for (Vector2f& surface_pos_entry : surface_pos) + surface_pos_entry += surface_offset; + // Round the inner corners surface_pos[1] = surface_pos[1].Round(); surface_pos[2] = surface_pos[2].Round(); @@ -176,7 +181,7 @@ void DecoratorNinePatch::ReleaseElementData(DecoratorDataHandle element_data) co void DecoratorNinePatch::RenderElement(Element* element, DecoratorDataHandle element_data) const { Geometry* data = reinterpret_cast(element_data); - data->Render(element->GetAbsoluteOffset(BoxArea::Padding)); + data->Render(element->GetAbsoluteOffset(BoxArea::Border)); } DecoratorNinePatchInstancer::DecoratorNinePatchInstancer() diff --git a/Source/Core/DecoratorNinePatch.h b/Source/Core/DecoratorNinePatch.h index 93f13da87..8657835b1 100644 --- a/Source/Core/DecoratorNinePatch.h +++ b/Source/Core/DecoratorNinePatch.h @@ -43,7 +43,7 @@ class DecoratorNinePatch : public Decorator { bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array* _edges, const Texture& texture, float display_scale); - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; void ReleaseElementData(DecoratorDataHandle element_data) const override; void RenderElement(Element* element, DecoratorDataHandle element_data) const override; diff --git a/Source/Core/DecoratorTiledBox.cpp b/Source/Core/DecoratorTiledBox.cpp index c4b2878bd..998f23048 100644 --- a/Source/Core/DecoratorTiledBox.cpp +++ b/Source/Core/DecoratorTiledBox.cpp @@ -95,7 +95,7 @@ bool DecoratorTiledBox::Initialise(const Tile* _tiles, const Texture* _textures) return true; } -DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element, BoxArea paint_area) const { // Initialise the tiles for this element. for (int i = 0; i < 9; i++) @@ -104,7 +104,8 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element) con tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); } - Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding); + const Vector2f offset = element->GetBox().GetPosition(paint_area); + const Vector2f size = element->GetBox().GetSize(paint_area); // Calculate the natural dimensions of tile corners and edges. const Vector2f natural_top_left = tiles[TOP_LEFT_CORNER].GetNaturalDimensions(element); @@ -132,60 +133,60 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element) con // Scale the top corners down if appropriate. If they are scaled, then the left and right edges are also scaled // if they shared a width with their corner. Best solution? Don't know. - if (padded_size.x < top_left.x + top_right.x) + if (size.x < top_left.x + top_right.x) { float minimum_width = top_left.x + top_right.x; - top_left.x = padded_size.x * (top_left.x / minimum_width); + top_left.x = size.x * (top_left.x / minimum_width); if (natural_top_left.x == natural_left.x) left.x = top_left.x; - top_right.x = padded_size.x * (top_right.x / minimum_width); + top_right.x = size.x * (top_right.x / minimum_width); if (natural_top_right.x == natural_right.x) right.x = top_right.x; } // Scale the bottom corners down if appropriate. If they are scaled, then the left and right edges are also scaled // if they shared a width with their corner. Best solution? Don't know. - if (padded_size.x < bottom_left.x + bottom_right.x) + if (size.x < bottom_left.x + bottom_right.x) { float minimum_width = bottom_left.x + bottom_right.x; - bottom_left.x = padded_size.x * (bottom_left.x / minimum_width); + bottom_left.x = size.x * (bottom_left.x / minimum_width); if (natural_bottom_left.x == natural_left.x) left.x = bottom_left.x; - bottom_right.x = padded_size.x * (bottom_right.x / minimum_width); + bottom_right.x = size.x * (bottom_right.x / minimum_width); if (natural_bottom_right.x == natural_right.x) right.x = bottom_right.x; } // Scale the left corners down if appropriate. If they are scaled, then the top and bottom edges are also scaled // if they shared a width with their corner. Best solution? Don't know. - if (padded_size.y < top_left.y + bottom_left.y) + if (size.y < top_left.y + bottom_left.y) { float minimum_height = top_left.y + bottom_left.y; - top_left.y = padded_size.y * (top_left.y / minimum_height); + top_left.y = size.y * (top_left.y / minimum_height); if (natural_top_left.y == natural_top.y) top.y = top_left.y; - bottom_left.y = padded_size.y * (bottom_left.y / minimum_height); + bottom_left.y = size.y * (bottom_left.y / minimum_height); if (natural_bottom_left.y == natural_bottom.y) bottom.y = bottom_left.y; } // Scale the right corners down if appropriate. If they are scaled, then the top and bottom edges are also scaled // if they shared a width with their corner. Best solution? Don't know. - if (padded_size.y < top_right.y + bottom_right.y) + if (size.y < top_right.y + bottom_right.y) { float minimum_height = top_right.y + bottom_right.y; - top_right.y = padded_size.y * (top_right.y / minimum_height); + top_right.y = size.y * (top_right.y / minimum_height); if (natural_top_right.y == natural_top.y) top.y = top_right.y; - bottom_right.y = padded_size.y * (bottom_right.y / minimum_height); + bottom_right.y = size.y * (bottom_right.y / minimum_height); if (natural_bottom_right.y == natural_bottom.y) bottom.y = bottom_right.y; } @@ -196,44 +197,46 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element) con // Generate the geometry for the top-left tile. tiles[TOP_LEFT_CORNER].GenerateGeometry(data->geometry[tiles[TOP_LEFT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[TOP_LEFT_CORNER].texture_index].GetIndices(), computed, Vector2f(0, 0), top_left, top_left); + data->geometry[tiles[TOP_LEFT_CORNER].texture_index].GetIndices(), computed, offset, top_left, top_left); // Generate the geometry for the top edge tiles. tiles[TOP_EDGE].GenerateGeometry(data->geometry[tiles[TOP_EDGE].texture_index].GetVertices(), - data->geometry[tiles[TOP_EDGE].texture_index].GetIndices(), computed, Vector2f(top_left.x, 0), - Vector2f(padded_size.x - (top_left.x + top_right.x), top.y), top); + data->geometry[tiles[TOP_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(top_left.x, 0), + Vector2f(size.x - (top_left.x + top_right.x), top.y), top); // Generate the geometry for the top-right tile. tiles[TOP_RIGHT_CORNER].GenerateGeometry(data->geometry[tiles[TOP_RIGHT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[TOP_RIGHT_CORNER].texture_index].GetIndices(), computed, Vector2f(padded_size.x - top_right.x, 0), top_right, top_right); + data->geometry[tiles[TOP_RIGHT_CORNER].texture_index].GetIndices(), computed, offset + Vector2f(size.x - top_right.x, 0), top_right, + top_right); // Generate the geometry for the left side. tiles[LEFT_EDGE].GenerateGeometry(data->geometry[tiles[LEFT_EDGE].texture_index].GetVertices(), - data->geometry[tiles[LEFT_EDGE].texture_index].GetIndices(), computed, Vector2f(0, top_left.y), - Vector2f(left.x, padded_size.y - (top_left.y + bottom_left.y)), left); + data->geometry[tiles[LEFT_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(0, top_left.y), + Vector2f(left.x, size.y - (top_left.y + bottom_left.y)), left); // Generate the geometry for the right side. tiles[RIGHT_EDGE].GenerateGeometry(data->geometry[tiles[RIGHT_EDGE].texture_index].GetVertices(), - data->geometry[tiles[RIGHT_EDGE].texture_index].GetIndices(), computed, Vector2f((padded_size.x - right.x), top_right.y), - Vector2f(right.x, padded_size.y - (top_right.y + bottom_right.y)), right); + data->geometry[tiles[RIGHT_EDGE].texture_index].GetIndices(), computed, offset + Vector2f((size.x - right.x), top_right.y), + Vector2f(right.x, size.y - (top_right.y + bottom_right.y)), right); // Generate the geometry for the bottom-left tile. tiles[BOTTOM_LEFT_CORNER].GenerateGeometry(data->geometry[tiles[BOTTOM_LEFT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM_LEFT_CORNER].texture_index].GetIndices(), computed, Vector2f(0, padded_size.y - bottom_left.y), bottom_left, + data->geometry[tiles[BOTTOM_LEFT_CORNER].texture_index].GetIndices(), computed, offset + Vector2f(0, size.y - bottom_left.y), bottom_left, bottom_left); // Generate the geometry for the bottom edge tiles. tiles[BOTTOM_EDGE].GenerateGeometry(data->geometry[tiles[BOTTOM_EDGE].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM_EDGE].texture_index].GetIndices(), computed, Vector2f(bottom_left.x, padded_size.y - bottom.y), - Vector2f(padded_size.x - (bottom_left.x + bottom_right.x), bottom.y), bottom); + data->geometry[tiles[BOTTOM_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(bottom_left.x, size.y - bottom.y), + Vector2f(size.x - (bottom_left.x + bottom_right.x), bottom.y), bottom); // Generate the geometry for the bottom-right tile. tiles[BOTTOM_RIGHT_CORNER].GenerateGeometry(data->geometry[tiles[BOTTOM_RIGHT_CORNER].texture_index].GetVertices(), data->geometry[tiles[BOTTOM_RIGHT_CORNER].texture_index].GetIndices(), computed, - Vector2f(padded_size.x - bottom_right.x, padded_size.y - bottom_right.y), bottom_right, bottom_right); + offset + Vector2f(size.x - bottom_right.x, size.y - bottom_right.y), bottom_right, bottom_right); // Generate the centre geometry. Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); - Vector2f centre_surface_dimensions(padded_size.x - (left.x + right.x), padded_size.y - (top.y + bottom.y)); + Vector2f centre_surface_dimensions(size.x - (left.x + right.x), size.y - (top.y + bottom.y)); tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, Vector2f(left.x, top.y), centre_surface_dimensions, centre_dimensions); + data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(left.x, top.y), centre_surface_dimensions, + centre_dimensions); // Set the textures on the geometry. const Texture* texture = nullptr; @@ -251,7 +254,7 @@ void DecoratorTiledBox::ReleaseElementData(DecoratorDataHandle element_data) con void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle element_data) const { - Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round(); + Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); DecoratorTiledBoxData* data = reinterpret_cast(element_data); for (int i = 0; i < data->num_textures; i++) diff --git a/Source/Core/DecoratorTiledBox.h b/Source/Core/DecoratorTiledBox.h index 13176c1cd..65b18dc83 100644 --- a/Source/Core/DecoratorTiledBox.h +++ b/Source/Core/DecoratorTiledBox.h @@ -49,7 +49,7 @@ class DecoratorTiledBox : public DecoratorTiled { bool Initialise(const Tile* tiles, const Texture* textures); /// Called on a decorator to generate any required per-element data for a newly decorated element. - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; /// Called to release element data generated by this decorator. void ReleaseElementData(DecoratorDataHandle element_data) const override; diff --git a/Source/Core/DecoratorTiledHorizontal.cpp b/Source/Core/DecoratorTiledHorizontal.cpp index 625935ca6..4e83e5241 100644 --- a/Source/Core/DecoratorTiledHorizontal.cpp +++ b/Source/Core/DecoratorTiledHorizontal.cpp @@ -75,7 +75,7 @@ bool DecoratorTiledHorizontal::Initialise(const Tile* _tiles, const Texture* _te return true; } -DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* element, BoxArea paint_area) const { // Initialise the tiles for this element. for (int i = 0; i < 3; i++) @@ -84,41 +84,42 @@ DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* eleme const int num_textures = GetNumTextures(); DecoratorTiledHorizontalData* data = new DecoratorTiledHorizontalData(num_textures); - Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding); + const Vector2f offset = element->GetBox().GetPosition(paint_area); + const Vector2f size = element->GetBox().GetSize(paint_area); Vector2f left_dimensions = tiles[LEFT].GetNaturalDimensions(element); Vector2f right_dimensions = tiles[RIGHT].GetNaturalDimensions(element); Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); // Scale the tile sizes by the height scale. - ScaleTileDimensions(left_dimensions, padded_size.y, Axis::Vertical); - ScaleTileDimensions(right_dimensions, padded_size.y, Axis::Vertical); - ScaleTileDimensions(centre_dimensions, padded_size.y, Axis::Vertical); + ScaleTileDimensions(left_dimensions, size.y, Axis::Vertical); + ScaleTileDimensions(right_dimensions, size.y, Axis::Vertical); + ScaleTileDimensions(centre_dimensions, size.y, Axis::Vertical); // Round the outer tile widths now so that we don't get gaps when rounding again in GenerateGeometry. left_dimensions.x = Math::Round(left_dimensions.x); right_dimensions.x = Math::Round(right_dimensions.x); // Shrink the x-sizes on the left and right tiles if necessary. - if (padded_size.x < left_dimensions.x + right_dimensions.x) + if (size.x < left_dimensions.x + right_dimensions.x) { float minimum_width = left_dimensions.x + right_dimensions.x; - left_dimensions.x = padded_size.x * (left_dimensions.x / minimum_width); - right_dimensions.x = padded_size.x * (right_dimensions.x / minimum_width); + left_dimensions.x = size.x * (left_dimensions.x / minimum_width); + right_dimensions.x = size.x * (right_dimensions.x / minimum_width); } const ComputedValues& computed = element->GetComputedValues(); // Generate the geometry for the left tile. tiles[LEFT].GenerateGeometry(data->geometry[tiles[LEFT].texture_index].GetVertices(), data->geometry[tiles[LEFT].texture_index].GetIndices(), - computed, Vector2f(0, 0), left_dimensions, left_dimensions); + computed, offset, left_dimensions, left_dimensions); // Generate the geometry for the centre tiles. tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, Vector2f(left_dimensions.x, 0), - Vector2f(padded_size.x - (left_dimensions.x + right_dimensions.x), centre_dimensions.y), centre_dimensions); + data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(left_dimensions.x, 0), + Vector2f(size.x - (left_dimensions.x + right_dimensions.x), centre_dimensions.y), centre_dimensions); // Generate the geometry for the right tile. tiles[RIGHT].GenerateGeometry(data->geometry[tiles[RIGHT].texture_index].GetVertices(), data->geometry[tiles[RIGHT].texture_index].GetIndices(), - computed, Vector2f(padded_size.x - right_dimensions.x, 0), right_dimensions, right_dimensions); + computed, offset + Vector2f(size.x - right_dimensions.x, 0), right_dimensions, right_dimensions); // Set the textures on the geometry. const Texture* texture = nullptr; @@ -136,7 +137,7 @@ void DecoratorTiledHorizontal::ReleaseElementData(DecoratorDataHandle element_da void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHandle element_data) const { - Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round(); + Vector2f translation = element->GetAbsoluteOffset(BoxArea::Border); DecoratorTiledHorizontalData* data = reinterpret_cast(element_data); for (int i = 0; i < data->num_textures; i++) diff --git a/Source/Core/DecoratorTiledHorizontal.h b/Source/Core/DecoratorTiledHorizontal.h index 6bb881877..e5858933b 100644 --- a/Source/Core/DecoratorTiledHorizontal.h +++ b/Source/Core/DecoratorTiledHorizontal.h @@ -49,7 +49,7 @@ class DecoratorTiledHorizontal : public DecoratorTiled { bool Initialise(const Tile* tiles, const Texture* textures); /// Called on a decorator to generate any required per-element data for a newly decorated element. - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; /// Called to release element data generated by this decorator. void ReleaseElementData(DecoratorDataHandle element_data) const override; diff --git a/Source/Core/DecoratorTiledImage.cpp b/Source/Core/DecoratorTiledImage.cpp index 13ea980a3..fcead3732 100644 --- a/Source/Core/DecoratorTiledImage.cpp +++ b/Source/Core/DecoratorTiledImage.cpp @@ -44,7 +44,7 @@ bool DecoratorTiledImage::Initialise(const Tile& _tile, const Texture& _texture) return (tile.texture_index >= 0); } -DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element, BoxArea paint_area) const { // Calculate the tile's dimensions for this element. tile.CalculateDimensions(*GetTexture(tile.texture_index)); @@ -54,9 +54,11 @@ DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element) c const ComputedValues& computed = element->GetComputedValues(); + const Vector2f offset = element->GetBox().GetPosition(paint_area); + const Vector2f size = element->GetBox().GetSize(paint_area); + // Generate the geometry for the tile. - tile.GenerateGeometry(data->GetVertices(), data->GetIndices(), computed, Vector2f(0, 0), element->GetBox().GetSize(BoxArea::Padding), - tile.GetNaturalDimensions(element)); + tile.GenerateGeometry(data->GetVertices(), data->GetIndices(), computed, offset, size, tile.GetNaturalDimensions(element)); return reinterpret_cast(data); } @@ -69,7 +71,7 @@ void DecoratorTiledImage::ReleaseElementData(DecoratorDataHandle element_data) c void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle element_data) const { Geometry* data = reinterpret_cast(element_data); - data->Render(element->GetAbsoluteOffset(BoxArea::Padding).Round()); + data->Render(element->GetAbsoluteOffset(BoxArea::Border)); } DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) diff --git a/Source/Core/DecoratorTiledImage.h b/Source/Core/DecoratorTiledImage.h index 47b178166..77ad57add 100644 --- a/Source/Core/DecoratorTiledImage.h +++ b/Source/Core/DecoratorTiledImage.h @@ -49,7 +49,7 @@ class DecoratorTiledImage : public DecoratorTiled { bool Initialise(const Tile& tile, const Texture& texture); /// Called on a decorator to generate any required per-element data for a newly decorated element. - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; /// Called to release element data generated by this decorator. void ReleaseElementData(DecoratorDataHandle element_data) const override; diff --git a/Source/Core/DecoratorTiledVertical.cpp b/Source/Core/DecoratorTiledVertical.cpp index 5004ade68..42ee349d7 100644 --- a/Source/Core/DecoratorTiledVertical.cpp +++ b/Source/Core/DecoratorTiledVertical.cpp @@ -76,7 +76,7 @@ bool DecoratorTiledVertical::Initialise(const Tile* _tiles, const Texture* _text return true; } -DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element) const +DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element, BoxArea paint_area) const { // Initialise the tile for this element. for (int i = 0; i < 3; i++) @@ -85,41 +85,42 @@ DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element const int num_textures = GetNumTextures(); DecoratorTiledVerticalData* data = new DecoratorTiledVerticalData(num_textures); - Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding); + const Vector2f offset = element->GetBox().GetPosition(paint_area); + const Vector2f size = element->GetBox().GetSize(paint_area); Vector2f top_dimensions = tiles[TOP].GetNaturalDimensions(element); Vector2f bottom_dimensions = tiles[BOTTOM].GetNaturalDimensions(element); Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); // Scale the tile sizes by the width scale. - ScaleTileDimensions(top_dimensions, padded_size.x, Axis::Horizontal); - ScaleTileDimensions(bottom_dimensions, padded_size.x, Axis::Horizontal); - ScaleTileDimensions(centre_dimensions, padded_size.x, Axis::Horizontal); + ScaleTileDimensions(top_dimensions, size.x, Axis::Horizontal); + ScaleTileDimensions(bottom_dimensions, size.x, Axis::Horizontal); + ScaleTileDimensions(centre_dimensions, size.x, Axis::Horizontal); // Round the outer tile heights now so that we don't get gaps when rounding again in GenerateGeometry. top_dimensions.y = Math::Round(top_dimensions.y); bottom_dimensions.y = Math::Round(bottom_dimensions.y); // Shrink the y-sizes on the left and right tiles if necessary. - if (padded_size.y < top_dimensions.y + bottom_dimensions.y) + if (size.y < top_dimensions.y + bottom_dimensions.y) { float minimum_height = top_dimensions.y + bottom_dimensions.y; - top_dimensions.y = padded_size.y * (top_dimensions.y / minimum_height); - bottom_dimensions.y = padded_size.y * (bottom_dimensions.y / minimum_height); + top_dimensions.y = size.y * (top_dimensions.y / minimum_height); + bottom_dimensions.y = size.y * (bottom_dimensions.y / minimum_height); } const ComputedValues& computed = element->GetComputedValues(); // Generate the geometry for the left tile. tiles[TOP].GenerateGeometry(data->geometry[tiles[TOP].texture_index].GetVertices(), data->geometry[tiles[TOP].texture_index].GetIndices(), - computed, Vector2f(0, 0), top_dimensions, top_dimensions); + computed, offset, top_dimensions, top_dimensions); // Generate the geometry for the centre tiles. tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, Vector2f(0, top_dimensions.y), - Vector2f(centre_dimensions.x, padded_size.y - (top_dimensions.y + bottom_dimensions.y)), centre_dimensions); + data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(0, top_dimensions.y), + Vector2f(centre_dimensions.x, size.y - (top_dimensions.y + bottom_dimensions.y)), centre_dimensions); // Generate the geometry for the right tile. tiles[BOTTOM].GenerateGeometry(data->geometry[tiles[BOTTOM].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM].texture_index].GetIndices(), computed, Vector2f(0, padded_size.y - bottom_dimensions.y), bottom_dimensions, + data->geometry[tiles[BOTTOM].texture_index].GetIndices(), computed, offset + Vector2f(0, size.y - bottom_dimensions.y), bottom_dimensions, bottom_dimensions); // Set the textures on the geometry. diff --git a/Source/Core/DecoratorTiledVertical.h b/Source/Core/DecoratorTiledVertical.h index 61bdffa11..6ca820078 100644 --- a/Source/Core/DecoratorTiledVertical.h +++ b/Source/Core/DecoratorTiledVertical.h @@ -49,7 +49,7 @@ class DecoratorTiledVertical : public DecoratorTiled { bool Initialise(const Tile* tiles, const Texture* textures); /// Called on a decorator to generate any required per-element data for a newly decorated element. - DecoratorDataHandle GenerateElementData(Element* element) const override; + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; /// Called to release element data generated by this decorator. void ReleaseElementData(DecoratorDataHandle element_data) const override; diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index c0a1aafa3..a7ba6761a 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -171,7 +171,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl struct DecoratorDeclarationView { DecoratorDeclarationView(const DecoratorDeclaration& declaration) : - type(declaration.type), instancer(declaration.instancer), properties(declaration.properties) + type(declaration.type), instancer(declaration.instancer), properties(declaration.properties), paint_area(declaration.paint_area) {} DecoratorDeclarationView(const NamedDecorator* specification) : type(specification->type), instancer(Factory::GetDecoratorInstancer(specification->type)), properties(specification->properties) @@ -179,6 +179,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl const String& type; DecoratorInstancer* instancer; const PropertyDictionary& properties; + BoxArea paint_area = BoxArea::Auto; }; if (p0.unit == Unit::DECORATOR && p1.unit == Unit::DECORATOR) @@ -230,13 +231,13 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl return DiscreteInterpolation(); if (d0_view.instancer != d1_view.instancer || d0_view.type != d1_view.type || - d0_view.properties.GetNumProperties() != d1_view.properties.GetNumProperties()) + d0_view.properties.GetNumProperties() != d1_view.properties.GetNumProperties() || d0_view.paint_area != d1_view.paint_area) { // Incompatible decorators, fall back to discrete interpolation. return DiscreteInterpolation(); } - decorator->list.push_back(DecoratorDeclaration{d0_view.type, d0_view.instancer, PropertyDictionary()}); + decorator->list.push_back(DecoratorDeclaration{d0_view.type, d0_view.instancer, PropertyDictionary(), d0_view.paint_area}); PropertyDictionary& props = decorator->list.back().properties; const auto& props0 = d0_view.properties.GetProperties(); @@ -269,7 +270,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl if (!dbig_view.instancer) return DiscreteInterpolation(); - decorator->list.push_back(DecoratorDeclaration{dbig_view.type, dbig_view.instancer, PropertyDictionary()}); + decorator->list.push_back(DecoratorDeclaration{dbig_view.type, dbig_view.instancer, PropertyDictionary(), dbig_view.paint_area}); DecoratorDeclaration& d_new = decorator->list.back(); const PropertySpecification& specification = d_new.instancer->GetPropertySpecification(); diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 722ef4897..3e4cecabf 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -87,16 +87,22 @@ void ElementDecoration::InstanceDecorators() } const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); + RMLUI_ASSERT(decorator_list.empty() || decorator_list.size() == decorators_ptr->list.size()); - for (const SharedPtr& decorator : decorator_list) + for (size_t i = 0; i < decorator_list.size() && i < decorators_ptr->list.size(); i++) { + const SharedPtr& decorator = decorator_list[i]; if (decorator) { - DecoratorEntry decorator_handle; - decorator_handle.decorator_data = 0; - decorator_handle.decorator = decorator; - - decorators.push_back(std::move(decorator_handle)); + DecoratorEntry entry; + entry.decorator_data = 0; + entry.decorator = decorator; + entry.paint_area = decorators_ptr->list[i].paint_area; + if (entry.paint_area == BoxArea::Auto) + entry.paint_area = BoxArea::Padding; + + RMLUI_ASSERT(entry.paint_area >= BoxArea::Border && entry.paint_area <= BoxArea::Content); + decorators.push_back(std::move(entry)); } } } @@ -145,7 +151,7 @@ void ElementDecoration::ReloadDecoratorsData() if (decorator.decorator_data) decorator.decorator->ReleaseElementData(decorator.decorator_data); - decorator.decorator_data = decorator.decorator->GenerateElementData(element); + decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); } for (FilterEntryList* list : {&filters, &backdrop_filters}) diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementDecoration.h index a4d4cb00c..9c0083609 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementDecoration.h @@ -70,6 +70,7 @@ class ElementDecoration { struct DecoratorEntry { SharedPtr decorator; DecoratorDataHandle decorator_data; + BoxArea paint_area; }; using DecoratorEntryList = Vector; diff --git a/Source/Core/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 7777a5c59..3ab4d3610 100644 --- a/Source/Core/PropertyParserDecorator.cpp +++ b/Source/Core/PropertyParserDecorator.cpp @@ -35,6 +35,12 @@ namespace Rml { +const SmallUnorderedMap PropertyParserDecorator::area_keywords = { + {"border-box", BoxArea::Border}, + {"padding-box", BoxArea::Padding}, + {"content-box", BoxArea::Content}, +}; + PropertyParserDecorator::PropertyParserDecorator() {} PropertyParserDecorator::~PropertyParserDecorator() {} @@ -44,9 +50,11 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor // Decorators are declared as // decorator: [, ...]; // Where is either a @decorator name: - // decorator: invader-theme-background, ...; + // decorator: invader-theme-background ?, ...; // or is an anonymous decorator with inline properties - // decorator: tiled-box( ), ...; + // decorator: tiled-box( ) ?, ...; + // where is one of + // border-box, padding-box, content-box if (decorator_string_value.empty() || decorator_string_value == "none") { @@ -57,6 +65,8 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor RMLUI_ZoneScoped; + const BoxArea default_paint_area = BoxArea::Padding; + // Make sure we don't split inside the parenthesis since they may appear in decorator shorthands. StringList decorator_string_list; StringUtilities::ExpandString(decorator_string_list, decorator_string_value, ',', '(', ')'); @@ -72,10 +82,33 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor const size_t shorthand_close = decorator_string.rfind(')'); const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); + // Find the paint area for the decorator. + BoxArea paint_area = default_paint_area; + + // Look-up keywords for customized paint area. + { + const size_t keywords_begin = (invalid_parenthesis ? decorator_string.find(' ') : shorthand_close + 1); + StringList keywords; + if (keywords_begin < decorator_string.size()) + StringUtilities::ExpandString(keywords, decorator_string.substr(keywords_begin), ' '); + + for (const String& keyword : keywords) + { + if (keyword.empty()) + continue; + + auto it = area_keywords.find(StringUtilities::ToLower(keyword)); + if (it == area_keywords.end()) + return false; // Bail out if we have an invalid keyword. + + paint_area = it->second; + } + } + if (invalid_parenthesis) { // We found no parenthesis, that means the value must be a name of a @decorator rule. - decorators.list.emplace_back(DecoratorDeclaration{decorator_string, nullptr, {}}); + decorators.list.emplace_back(DecoratorDeclaration{decorator_string, nullptr, {}, paint_area}); } else { @@ -105,7 +138,7 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor // Set unspecified values to their defaults specification.SetPropertyDefaults(properties); - decorators.list.emplace_back(DecoratorDeclaration{type, instancer, std::move(properties)}); + decorators.list.emplace_back(DecoratorDeclaration{type, instancer, std::move(properties), paint_area}); } } @@ -118,4 +151,14 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor return true; } +String PropertyParserDecorator::ConvertAreaToString(BoxArea area) +{ + for (const auto& it : area_keywords) + { + if (it.second == area) + return it.first; + } + return String(); +} + } // namespace Rml diff --git a/Source/Core/PropertyParserDecorator.h b/Source/Core/PropertyParserDecorator.h index c01865a0b..892546e65 100644 --- a/Source/Core/PropertyParserDecorator.h +++ b/Source/Core/PropertyParserDecorator.h @@ -44,6 +44,11 @@ class PropertyParserDecorator : public PropertyParser { /// Called to parse a decorator declaration. bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + + static String ConvertAreaToString(BoxArea area); + +private: + static const SmallUnorderedMap area_keywords; }; } // namespace Rml diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index cade95bdc..9250fc93d 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -37,6 +37,7 @@ #include "../../Include/RmlUi/Core/StyleSheetTypes.h" #include "../../Include/RmlUi/Core/Transform.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "PropertyParserDecorator.h" #include "TransformUtilities.h" namespace Rml { @@ -163,6 +164,16 @@ bool TypeConverter::Convert(const AnimationList& src, Str return true; } +template +void AppendPaintArea(const EffectDeclaration& /*declaration*/, String& /*dest*/) +{} +template <> +void AppendPaintArea(const DecoratorDeclaration& declaration, String& dest) +{ + if (declaration.paint_area >= BoxArea::Border && declaration.paint_area <= BoxArea::Padding) + dest += " " + PropertyParserDecorator::ConvertAreaToString(declaration.paint_area); +} + template static bool ConvertEffectToString(const EffectsPtr& src, String& dest, const String& separator) { @@ -176,10 +187,10 @@ static bool ConvertEffectToString(const EffectsPtr& src, String& dest, const Str for (const auto& declaration : src->list) { dest += declaration.type; - if (auto instancer = declaration.instancer) - { + if (auto* instancer = declaration.instancer) dest += '(' + instancer->GetPropertySpecification().PropertiesToString(declaration.properties, false, ' ') + ')'; - } + + AppendPaintArea(declaration, dest); if (&declaration != &src->list.back()) dest += separator; } From 81ae0c80e8ee104300ee71ba4598c7f227a00fb0 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Thu, 8 Jun 2023 00:36:49 +0200 Subject: [PATCH 34/80] Visual tests: Highlight differences when comparing to previous capture by holding shift key --- Tests/Data/visual_tests_help.rml | 4 +- Tests/Source/VisualTests/CaptureScreen.cpp | 79 ++++++++++++---------- Tests/Source/VisualTests/CaptureScreen.h | 3 +- Tests/Source/VisualTests/TestNavigator.cpp | 75 ++++++++++++++------ Tests/Source/VisualTests/TestNavigator.h | 6 +- 5 files changed, 108 insertions(+), 59 deletions(-) diff --git a/Tests/Data/visual_tests_help.rml b/Tests/Data/visual_tests_help.rml index 02562f3d1..a1da8f5ae 100644 --- a/Tests/Data/visual_tests_help.rml +++ b/Tests/Data/visual_tests_help.rml @@ -19,7 +19,7 @@ font-size: 17dp; width: 700dp; margin: 0 auto; - padding-top: 30dp; + padding-top: 20dp; } p { clear: both; @@ -64,7 +64,7 @@ Ctrl+R Reload test Ctrl+S View source of test Ctrl+Shift+S View source of reference -Ctrl+Q Show previous capture of test +Ctrl+Q Show previous capture of test
(hold Shift to highlight differences)
Left Mouse Button on Link Copy link to clipboard (on supported platforms) F1 Show / hide help diff --git a/Tests/Source/VisualTests/CaptureScreen.cpp b/Tests/Source/VisualTests/CaptureScreen.cpp index fd88f3f9a..bb83b2f5d 100644 --- a/Tests/Source/VisualTests/CaptureScreen.cpp +++ b/Tests/Source/VisualTests/CaptureScreen.cpp @@ -92,7 +92,8 @@ struct DeferFree { ~DeferFree() { free(ptr); } }; -ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_interface, const Rml::String& filename, TextureGeometry* out_geometry) +ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_interface, const Rml::String& filename, TextureGeometry* out_reference, + TextureGeometry* out_highlight) { using Image = RendererExtensions::Image; @@ -114,25 +115,6 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int } RMLUI_ASSERT(w_ref > 0 && h_ref > 0 && data_ref); - // Optionally render the previous capture to a texture. - if (out_geometry) - { - if (!render_interface->GenerateTexture(out_geometry->texture_handle, data_ref, Rml::Vector2i((int)w_ref, (int)h_ref))) - { - ComparisonResult result; - result.success = false; - result.error_msg = Rml::CreateString(1024, "Could not generate texture from file %s", input_path.c_str()); - return result; - } - - const Rml::Colourb colour = {255, 255, 255, 255}; - const Rml::Vector2f uv_top_left = {0, 0}; - const Rml::Vector2f uv_bottom_right = {1, 1}; - - Rml::GeometryUtilities::GenerateQuad(out_geometry->vertices, out_geometry->indices, Rml::Vector2f(0, 0), - Rml::Vector2f((float)w_ref, (float)h_ref), colour, uv_top_left, uv_bottom_right, 0); - } - Image screen = RendererExtensions::CaptureScreen(); if (!screen.data) { @@ -146,7 +128,7 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int Image diff; diff.width = w_ref; diff.height = h_ref; - diff.num_components = 3; + diff.num_components = 4; diff.data = Rml::UniquePtr(new Rml::byte[diff.width * diff.height * diff.num_components]); // So we have both images now, compare them! Also create a diff image. @@ -162,24 +144,30 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int return result; } + const Rml::Colourb highlight_color(255, 0, 255, 255); size_t sum_diff = 0; - - constexpr int c = 3; for (int y = 0; y < (int)h_ref; y++) { const int y_flipped_screen = screen.height - y - 1; - const int yb_screen = y_flipped_screen * screen.width * c; - - const int wb_ref = w_ref * c; - const int yb_ref = y * w_ref * c; - - for (int xb = 0; xb < wb_ref; xb++) + for (int x = 0; x < (int)w_ref; x++) { - const int i_ref = yb_ref + xb; - Rml::byte pix_ref = data_ref[(i_ref * 4) / 3]; - Rml::byte pix_screen = screen.data[yb_screen + xb]; - diff.data[i_ref] = (Rml::byte)std::abs((int)pix_ref - (int)pix_screen); - sum_diff += (size_t)diff.data[i_ref]; + const int i0_screen = (y_flipped_screen * screen.width + x) * 3; + const int i0_ref = (y * w_ref + x) * 4; + const int i0_diff = (y * diff.width + x) * 4; + + int pixel_diff = 0; + for (int z = 0; z < 3; z++) + { + const Rml::byte pix_ref = data_ref[i0_ref + z]; + const Rml::byte pix_screen = screen.data[i0_screen + z]; + pixel_diff += Rml::Math::Absolute((int)pix_ref - (int)pix_screen); + } + + diff.data[i0_diff + 0] = (pixel_diff ? highlight_color[0] : screen.data[i0_screen + 0]); + diff.data[i0_diff + 1] = (pixel_diff ? highlight_color[1] : screen.data[i0_screen + 1]); + diff.data[i0_diff + 2] = (pixel_diff ? highlight_color[2] : screen.data[i0_screen + 2]); + diff.data[i0_diff + 3] = highlight_color[3]; + sum_diff += (size_t)pixel_diff; } } @@ -189,9 +177,30 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int result.is_equal = (sum_diff == 0); result.absolute_difference_sum = sum_diff; - const size_t max_diff = size_t(c * 255) * size_t(w_ref) * size_t(h_ref); + const size_t max_diff = size_t(3 * 255) * size_t(w_ref) * size_t(h_ref); result.similarity_score = (sum_diff == 0 ? 1.0 : 1.0 - std::log(double(sum_diff)) / std::log(double(max_diff))); + // Optionally render the screen capture or diff to a texture. + auto GenerateGeometry = [&](TextureGeometry& geometry, Rml::byte* data, Rml::Vector2i dimensions) -> bool { + if (!render_interface->GenerateTexture(geometry.texture_handle, data, dimensions)) + return false; + const Rml::Colourb colour = {255, 255, 255, 255}; + const Rml::Vector2f uv_top_left = {0, 0}; + const Rml::Vector2f uv_bottom_right = {1, 1}; + Rml::GeometryUtilities::GenerateQuad(geometry.vertices, geometry.indices, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref), + colour, uv_top_left, uv_bottom_right, 0); + return true; + }; + + if (out_reference && result.success) + result.success = GenerateGeometry(*out_reference, data_ref, {(int)w_ref, (int)h_ref}); + + if (out_highlight && result.success) + result.success = GenerateGeometry(*out_highlight, diff.data.get(), {diff.width, diff.height}); + + if (!result.success) + result.error_msg = Rml::CreateString(1024, "Could not generate texture from file %s", input_path.c_str()); + return result; } diff --git a/Tests/Source/VisualTests/CaptureScreen.h b/Tests/Source/VisualTests/CaptureScreen.h index 7cc179845..bcb8c84ee 100644 --- a/Tests/Source/VisualTests/CaptureScreen.h +++ b/Tests/Source/VisualTests/CaptureScreen.h @@ -50,7 +50,8 @@ struct TextureGeometry { bool CaptureScreenshot(const Rml::String& filename, int clip_width); -ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_interface, const Rml::String& filename, TextureGeometry* out_geometry); +ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_interface, const Rml::String& filename, TextureGeometry* out_reference, + TextureGeometry* out_highlight); void RenderTextureGeometry(Rml::RenderInterface* render_interface, TextureGeometry& geometry); diff --git a/Tests/Source/VisualTests/TestNavigator.cpp b/Tests/Source/VisualTests/TestNavigator.cpp index 014f25b3b..2fb717d16 100644 --- a/Tests/Source/VisualTests/TestNavigator.cpp +++ b/Tests/Source/VisualTests/TestNavigator.cpp @@ -49,6 +49,7 @@ TestNavigator::TestNavigator(Rml::RenderInterface* render_interface, Rml::Contex RMLUI_ASSERT(context); RMLUI_ASSERTMSG(!test_suites.empty(), "At least one test suite is required."); context->GetRootElement()->AddEventListener(Rml::EventId::Keydown, this, true); + context->GetRootElement()->AddEventListener(Rml::EventId::Keyup, this, true); context->GetRootElement()->AddEventListener(Rml::EventId::Keydown, this); context->GetRootElement()->AddEventListener(Rml::EventId::Textinput, this); context->GetRootElement()->AddEventListener(Rml::EventId::Change, this); @@ -63,10 +64,12 @@ TestNavigator::TestNavigator(Rml::RenderInterface* render_interface, Rml::Contex TestNavigator::~TestNavigator() { context->GetRootElement()->RemoveEventListener(Rml::EventId::Keydown, this, true); + context->GetRootElement()->RemoveEventListener(Rml::EventId::Keyup, this, true); context->GetRootElement()->RemoveEventListener(Rml::EventId::Keydown, this); context->GetRootElement()->RemoveEventListener(Rml::EventId::Textinput, this); context->GetRootElement()->RemoveEventListener(Rml::EventId::Change, this); ReleaseTextureGeometry(render_interface, reference_geometry); + ReleaseTextureGeometry(render_interface, reference_highlight_geometry); } void TestNavigator::Update() @@ -112,10 +115,10 @@ void TestNavigator::Update() void TestNavigator::Render() { - if (show_reference && reference_geometry.texture_handle) + if (reference_state != ReferenceState::None && source_state == SourceType::None && !viewer->IsHelpVisible()) { - render_interface->RenderGeometry(reference_geometry.vertices, 4, reference_geometry.indices, 6, reference_geometry.texture_handle, - Rml::Vector2f(0, 0)); + TextureGeometry& geometry = (reference_state == ReferenceState::ShowReferenceHighlight ? reference_highlight_geometry : reference_geometry); + render_interface->RenderGeometry(geometry.vertices, 4, geometry.indices, 6, geometry.texture_handle, Rml::Vector2f(0, 0)); } } @@ -179,6 +182,7 @@ void TestNavigator::ProcessEvent(Rml::Event& event) } else if (key_identifier == Rml::Input::KI_F1) { + ShowReference(ReferenceState::None); viewer->ShowHelp(!viewer->IsHelpVisible()); } else if (key_identifier == Rml::Input::KI_F && key_ctrl) @@ -205,11 +209,19 @@ void TestNavigator::ProcessEvent(Rml::Event& event) source_state = SourceType::None; } viewer->ShowSource(source_state); - ShowReference(false, false); + ShowReference(ReferenceState::None); } else if (key_identifier == Rml::Input::KI_Q && key_ctrl) { - ShowReference(!show_reference, false); + if (reference_state != ReferenceState::None) + ShowReference(ReferenceState::None); + else + ShowReference(key_shift ? ReferenceState::ShowReferenceHighlight : ReferenceState::ShowReference); + } + else if (key_identifier == Rml::Input::KI_LSHIFT || key_identifier == Rml::Input::KI_RSHIFT) + { + if (reference_state == ReferenceState::ShowReference) + ShowReference(ReferenceState::ShowReferenceHighlight); } else if (key_identifier == Rml::Input::KI_ESCAPE) { @@ -226,6 +238,10 @@ void TestNavigator::ProcessEvent(Rml::Event& event) source_state = SourceType::None; viewer->ShowSource(source_state); } + else if (reference_state != ReferenceState::None) + { + ShowReference(ReferenceState::None); + } else if (element_filter_input->IsPseudoClassSet("focus")) { element_filter_input->Blur(); @@ -251,6 +267,16 @@ void TestNavigator::ProcessEvent(Rml::Event& event) } } + if (event == Rml::EventId::Keyup && event.GetPhase() == Rml::EventPhase::Capture) + { + const auto key_identifier = (Rml::Input::KeyIdentifier)event.GetParameter("key_identifier", 0); + if (key_identifier == Rml::Input::KI_LSHIFT || key_identifier == Rml::Input::KI_RSHIFT) + { + if (reference_state == ReferenceState::ShowReferenceHighlight) + ShowReference(ReferenceState::ShowReference); + } + } + // Keydown events in target/bubble phase ignored when focusing on input. if (event == Rml::EventId::Keydown && event.GetPhase() != Rml::EventPhase::Capture) { @@ -359,7 +385,7 @@ void TestNavigator::LoadActiveTest(bool keep_scroll_position) viewer->LoadTest(suite.GetDirectory(), suite.GetFilename(), suite.GetIndex(), suite.GetNumTests(), suite.GetFilterIndex(), suite.GetNumFilteredTests(), suite_index, (int)test_suites.size(), keep_scroll_position); viewer->ShowSource(source_state); - ShowReference(false, true); + ShowReference(ReferenceState::None); UpdateGoToText(); } @@ -373,7 +399,7 @@ ComparisonResult TestNavigator::CompareCurrentView() { const Rml::String filename = GetImageFilenameFromCurrentTest(); - ComparisonResult result = CompareScreenToPreviousCapture(render_interface, filename, nullptr); + ComparisonResult result = CompareScreenToPreviousCapture(render_interface, filename, nullptr, nullptr); return result; } @@ -562,31 +588,42 @@ void TestNavigator::UpdateGoToText(bool out_of_bounds) viewer->SetGoToText("Capturing all tests"); else if (iteration_state == IterationState::Comparison) viewer->SetGoToText("Comparing all tests"); - else if (show_reference) + else if (reference_state == ReferenceState::ShowReference) viewer->SetGoToText(Rml::CreateString(100, "Showing reference capture (%.1f%% similar)", reference_comparison.similarity_score * 100.)); + else if (reference_state == ReferenceState::ShowReferenceHighlight) + viewer->SetGoToText("Showing reference capture (highlight differences)"); else viewer->SetGoToText("Press 'F1' for keyboard shortcuts."); } -void TestNavigator::ShowReference(bool show, bool clear) +void TestNavigator::ShowReference(ReferenceState new_reference_state) { - if (clear) - { - ReleaseTextureGeometry(render_interface, reference_geometry); - reference_comparison = {}; - } + if (new_reference_state == reference_state || viewer->IsHelpVisible()) + return; Rml::String error_msg; - if (show && !reference_geometry.texture_handle) + + if (reference_state == ReferenceState::None) { - reference_comparison = CompareScreenToPreviousCapture(render_interface, GetImageFilenameFromCurrentTest(), &reference_geometry); + reference_comparison = + CompareScreenToPreviousCapture(render_interface, GetImageFilenameFromCurrentTest(), &reference_geometry, &reference_highlight_geometry); - if (!reference_comparison.success) + if (!reference_comparison.success || !reference_geometry.texture_handle || !reference_highlight_geometry.texture_handle) + { error_msg = reference_comparison.error_msg; + new_reference_state = ReferenceState::None; + } + } + + if (new_reference_state == ReferenceState::None) + { + ReleaseTextureGeometry(render_interface, reference_geometry); + ReleaseTextureGeometry(render_interface, reference_highlight_geometry); + reference_comparison = {}; } - show_reference = (show && reference_comparison.success && !reference_comparison.is_equal); - viewer->SetAttention(show_reference); + reference_state = new_reference_state; + viewer->SetAttention(reference_state != ReferenceState::None); if (!error_msg.empty()) viewer->SetGoToText(error_msg); diff --git a/Tests/Source/VisualTests/TestNavigator.h b/Tests/Source/VisualTests/TestNavigator.h index 8ac348601..7a012358e 100644 --- a/Tests/Source/VisualTests/TestNavigator.h +++ b/Tests/Source/VisualTests/TestNavigator.h @@ -50,6 +50,7 @@ class TestNavigator : public Rml::EventListener { private: enum class IterationState { None, Capture, Comparison }; + enum class ReferenceState { None, ShowReference, ShowReferenceHighlight }; TestSuite& CurrentSuite() { return test_suites[suite_index]; } @@ -64,7 +65,7 @@ class TestNavigator : public Rml::EventListener { void UpdateGoToText(bool out_of_bounds = false); - void ShowReference(bool show, bool clear); + void ShowReference(ReferenceState new_reference_state); Rml::String GetImageFilenameFromCurrentTest(); @@ -79,9 +80,10 @@ class TestNavigator : public Rml::EventListener { int goto_index = -1; SourceType source_state = SourceType::None; - bool show_reference = false; + ReferenceState reference_state = ReferenceState::None; ComparisonResult reference_comparison; TextureGeometry reference_geometry; + TextureGeometry reference_highlight_geometry; IterationState iteration_state = IterationState::None; From 2f146f4150333559427fefb53305056becc2bb6c Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Fri, 9 Jun 2023 01:32:10 +0200 Subject: [PATCH 35/80] Add support for conic-gradient and radial-gradient, as well as their repeating variations --- Backends/RmlUi_Renderer_GL3.cpp | 59 ++- Source/Core/DecoratorGradient.cpp | 364 +++++++++++++++++- Source/Core/DecoratorGradient.h | 100 ++++- Source/Core/Factory.cpp | 8 + Source/Core/PropertyParserDecorator.cpp | 7 +- .../VisualTests/shader_conic_gradient.rml | 105 +++++ .../VisualTests/shader_radial_gradient.rml | 147 +++++++ 7 files changed, 760 insertions(+), 30 deletions(-) create mode 100644 Tests/Data/VisualTests/shader_conic_gradient.rml create mode 100644 Tests/Data/VisualTests/shader_radial_gradient.rml diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index ae908d18e..5875ede92 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -119,15 +119,19 @@ void main() { } )"; -enum class ShaderGradientFunction { Linear, RepeatingLinear }; // Must match shader definitions below. +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 REPEATING_LINEAR 1 +#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; // starting point -uniform vec2 _v; // vector to ending point +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; @@ -148,11 +152,25 @@ vec4 mix_stop_colors(float t) { void main() { float t = 0; - float dist_square = dot(_v, _v); - vec2 V = fragTexCoord - _p; - t = dot(_v, V) / dist_square; + 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) + if (_func == REPEATING_LINEAR || _func == REPEATING_RADIAL || _func == REPEATING_CONIC) { float t0 = _stop_positions[0]; float t1 = _stop_positions[_num_stops - 1]; @@ -1602,6 +1620,25 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& 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); + } if (shader.type != CompiledShaderType::Invalid) return reinterpret_cast(new CompiledShader(std::move(shader))); @@ -1613,7 +1650,7 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation, Rml::TextureHandle /*texture*/) { - RMLUI_ASSERT(geometry_handle); + 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); @@ -1649,9 +1686,9 @@ void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Gfx::CheckGLError("RenderShader"); } -void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle effect_handle) +void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle shader_handle) { - delete reinterpret_cast(effect_handle); + delete reinterpret_cast(shader_handle); } void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index 982c4e415..12e952eea 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -35,9 +35,22 @@ #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "ComputeProperty.h" +#include "Pool.h" namespace Rml { +struct GradientElementData { + GradientElementData(Geometry&& geometry, CompiledShaderHandle shader) : geometry(std::move(geometry)), shader(shader) {} + Geometry geometry; + CompiledShaderHandle shader; +}; + +Pool& GetGradientElementDataPool() +{ + static Pool gradient_element_data_pool(20, true); + return gradient_element_data_pool; +} + // Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'. static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector) { @@ -148,6 +161,32 @@ static ColorStopList ResolveColorStops(Element* element, const float gradient_li return stops; } +// Compute a 2d-position property value into a percentage-length vector. +static Vector2Numeric ComputePosition(const Property* p_position[2]) +{ + Vector2Numeric position; + for (int dimension = 0; dimension < 2; dimension++) + { + NumericValue& value = position[dimension]; + const Property& property = *p_position[dimension]; + if (property.unit == Unit::KEYWORD) + { + enum { TOP_LEFT, CENTER, BOTTOM_RIGHT }; + switch (property.Get()) + { + case TOP_LEFT: value = NumericValue(0.f, Unit::PERCENT); break; + case CENTER: value = NumericValue(50.f, Unit::PERCENT); break; + case BOTTOM_RIGHT: value = NumericValue(100.f, Unit::PERCENT); break; + } + } + else + { + value = property.GetNumericValue(); + } + } + return position; +} + DecoratorStraightGradient::DecoratorStraightGradient() {} DecoratorStraightGradient::~DecoratorStraightGradient() {} @@ -280,8 +319,7 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops); - auto element_data = MakeUnique(); - element_data->shader = render_interface->CompileShader("linear-gradient", + CompiledShaderHandle shader_handle = render_interface->CompileShader("linear-gradient", Dictionary{ {"angle", Variant(angle)}, {"p0", Variant(gradient_shape.p0)}, @@ -291,10 +329,10 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen {"color_stop_list", Variant(std::move(resolved_stops))}, }); - if (!element_data->shader) + if (!shader_handle) return INVALID_DECORATORDATAHANDLE; - Geometry& geometry = element_data->geometry; + Geometry geometry; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); @@ -304,19 +342,21 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen for (Vertex& vertex : geometry.GetVertices()) vertex.tex_coord = vertex.position - render_offset; - return reinterpret_cast(element_data.release()); + GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + + return reinterpret_cast(element_data); } void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const { - ElementData* element_data = reinterpret_cast(handle); + GradientElementData* element_data = reinterpret_cast(handle); GetRenderInterface()->ReleaseCompiledShader(element_data->shader); - delete element_data; + GetGradientElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { - ElementData* element_data = reinterpret_cast(handle); + GradientElementData* element_data = reinterpret_cast(handle); element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); } @@ -418,4 +458,312 @@ SharedPtr DecoratorLinearGradientInstancer::InstanceDecorator(const S return nullptr; } +DecoratorRadialGradient::DecoratorRadialGradient() {} + +DecoratorRadialGradient::~DecoratorRadialGradient() {} + +bool DecoratorRadialGradient::Initialise(bool in_repeating, Shape in_shape, SizeType in_size_type, Vector2Numeric in_size, Vector2Numeric in_position, + const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + shape = in_shape; + size_type = in_size_type; + size = in_size; + position = in_position; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea box_area) const +{ + RenderInterface* render_interface = GetRenderInterface(); + if (!render_interface) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty() && (shape == Shape::Circle || shape == Shape::Ellipse)); + + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(box_area); + + RadialGradientShape gradient_shape = CalculateRadialGradientShape(element, dimensions); + + // One-pixel minimum color stop spacing to avoid aliasing. + const float soft_spacing = 1.f / Math::Min(gradient_shape.radius.x, gradient_shape.radius.y); + + ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.radius.x, soft_spacing, color_stops); + + CompiledShaderHandle shader_handle = render_interface->CompileShader("radial-gradient", + Dictionary{ + {"center", Variant(gradient_shape.center)}, + {"radius", Variant(gradient_shape.radius)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + + Geometry geometry; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + + const Vector2f render_offset = box.GetPosition(box_area); + for (Vertex& vertex : geometry.GetVertices()) + vertex.tex_coord = vertex.position - render_offset; + + GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + return reinterpret_cast(element_data); +} + +void DecoratorRadialGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + GradientElementData* element_data = reinterpret_cast(handle); + GetRenderInterface()->ReleaseCompiledShader(element_data->shader); + GetGradientElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + GradientElementData* element_data = reinterpret_cast(handle); + element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); +} + +DecoratorRadialGradient::RadialGradientShape DecoratorRadialGradient::CalculateRadialGradientShape(Element* element, Vector2f dimensions) const +{ + RadialGradientShape result; + result.center.x = element->ResolveNumericValue(position.x, dimensions.x); + result.center.y = element->ResolveNumericValue(position.y, dimensions.y); + const bool is_circle = (shape == Shape::Circle); + + auto Abs = [](Vector2f v) { return Vector2f{Math::Absolute(v.x), Math::Absolute(v.y)}; }; + auto d = dimensions; + auto c = result.center; + Vector2f r; + + switch (size_type) + { + case SizeType::ClosestSide: + { + r = Abs(Math::Min(c, d - c)); + result.radius = (is_circle ? Vector2f(Math::Min(r.x, r.y)) : r); + } + break; + case SizeType::FarthestSide: + { + r = Abs(Math::Max(c, d - c)); + result.radius = (is_circle ? Vector2f(Math::Max(r.x, r.y)) : r); + } + break; + case SizeType::ClosestCorner: + case SizeType::FarthestCorner: + { + if (size_type == SizeType::ClosestCorner) + r = Abs(Math::Min(c, d - c)); // Same as closest-side. + else + r = Abs(Math::Max(c, d - c)); // Same as farthest-side. + + if (is_circle) + { + result.radius = Vector2f(r.Magnitude()); + } + else + { + r = Math::Max(r, Vector2f(1)); // In case r.x ~= 0 + result.radius.x = Math::SquareRoot(2.f * r.x * r.x); + result.radius.y = result.radius.x * (r.y / r.x); + } + } + break; + case SizeType::LengthPercentage: + { + result.radius.x = element->ResolveNumericValue(size.x, d.x); + result.radius.y = (is_circle ? result.radius.x : element->ResolveNumericValue(size.y, d.y)); + result.radius = Abs(result.radius); + } + break; + } + + result.radius = Math::Max(result.radius, Vector2f(1.f)); + return result; +} + +DecoratorRadialGradientInstancer::DecoratorRadialGradientInstancer() +{ + ids.ending_shape = RegisterProperty("ending-shape", "unspecified").AddParser("keyword", "circle, ellipse, unspecified").GetId(); + ids.size_x = RegisterProperty("size-x", "farthest-corner") + .AddParser("keyword", "closest-side, farthest-side, closest-corner, farthest-corner") + .AddParser("length_percent") + .GetId(); + ids.size_y = RegisterProperty("size-y", "unspecified").AddParser("keyword", "unspecified").AddParser("length_percent").GetId(); + + RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified"); + ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list").GetId(); + + RegisterShorthand("shape", "ending-shape, size-x, size-y, at, position-x, position-y, position-x", ShorthandType::FallThrough); + + RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorRadialGradientInstancer::~DecoratorRadialGradientInstancer() {} + +SharedPtr DecoratorRadialGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_ending_shape = properties_.GetProperty(ids.ending_shape); + const Property* p_size_x = properties_.GetProperty(ids.size_x); + const Property* p_size_y = properties_.GetProperty(ids.size_y); + const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_ending_shape || !p_size_x || !p_size_y || !p_position[0] || !p_position[1] || !p_color_stop_list) + return nullptr; + + using SizeType = DecoratorRadialGradient::SizeType; + using Shape = DecoratorRadialGradient::Shape; + + Shape shape = (Shape)p_ending_shape->Get(); + if (shape == Shape::Unspecified) + { + const bool circle_sized = (Any(p_size_x->unit & Unit::LENGTH_PERCENT) && p_size_y->unit == Unit::KEYWORD); + shape = (circle_sized ? Shape::Circle : Shape::Ellipse); + } + if (shape == Shape::Circle && (p_size_x->unit == Unit::PERCENT || p_size_y->unit != Unit::KEYWORD)) + return nullptr; + + SizeType size_type = {}; + Vector2Numeric size; + if (p_size_x->unit == Unit::KEYWORD) + { + size_type = (SizeType)p_size_x->Get(); + } + else + { + size_type = SizeType::LengthPercentage; + size.x = p_size_x->GetNumericValue(); + size.y = (p_size_y->unit == Unit::KEYWORD ? size.x : p_size_y->GetNumericValue()); + } + + const Vector2Numeric position = ComputePosition(p_position); + const bool repeating = (name == "repeating-radial-gradient"); + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, shape, size_type, size, position, color_stop_list)) + return decorator; + + return nullptr; +} + +DecoratorConicGradient::DecoratorConicGradient() {} + +DecoratorConicGradient::~DecoratorConicGradient() {} + +bool DecoratorConicGradient::Initialise(bool in_repeating, float in_angle, Vector2Numeric in_position, const ColorStopList& in_color_stops) +{ + repeating = in_repeating; + angle = in_angle; + position = in_position; + color_stops = in_color_stops; + return !color_stops.empty(); +} + +DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea box_area) const +{ + RenderInterface* render_interface = GetRenderInterface(); + if (!render_interface) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty()); + + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(box_area); + + const Vector2f center = + Vector2f{element->ResolveNumericValue(position.x, dimensions.x), element->ResolveNumericValue(position.y, dimensions.y)}.Round(); + + ColorStopList resolved_stops = ResolveColorStops(element, 1.f, 0.f, color_stops); + + CompiledShaderHandle shader_handle = render_interface->CompileShader("conic-gradient", + Dictionary{ + {"angle", Variant(angle)}, + {"center", Variant(center)}, + {"repeating", Variant(repeating)}, + {"color_stop_list", Variant(std::move(resolved_stops))}, + }); + + Geometry geometry; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + + const Vector2f render_offset = box.GetPosition(box_area); + for (Vertex& vertex : geometry.GetVertices()) + vertex.tex_coord = vertex.position - render_offset; + + GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + return reinterpret_cast(element_data); +} + +void DecoratorConicGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + GradientElementData* element_data = reinterpret_cast(handle); + GetRenderInterface()->ReleaseCompiledShader(element_data->shader); + + GetGradientElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + GradientElementData* element_data = reinterpret_cast(handle); + element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); +} + +DecoratorConicGradientInstancer::DecoratorConicGradientInstancer() +{ + RegisterProperty("from", "from").AddParser("keyword", "from"); + ids.angle = RegisterProperty("angle", "0deg").AddParser("angle").GetId(); + + RegisterProperty("at", "unspecified").AddParser("keyword", "at, unspecified"); + ids.position_x = RegisterProperty("position-x", "center").AddParser("keyword", "left, center, right").AddParser("length_percent").GetId(); + ids.position_y = RegisterProperty("position-y", "center").AddParser("keyword", "top, center, bottom").AddParser("length_percent").GetId(); + + ids.color_stop_list = RegisterProperty("color-stops", "").AddParser("color_stop_list", "angle").GetId(); + + RegisterShorthand("shape", "from, angle, at, position-x, position-y, position-x", ShorthandType::FallThrough); + RegisterShorthand("decorator", "shape?, color-stops#", ShorthandType::RecursiveCommaSeparated); +} + +DecoratorConicGradientInstancer::~DecoratorConicGradientInstancer() {} + +SharedPtr DecoratorConicGradientInstancer::InstanceDecorator(const String& name, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_angle = properties_.GetProperty(ids.angle); + const Property* p_position[2] = {properties_.GetProperty(ids.position_x), properties_.GetProperty(ids.position_y)}; + const Property* p_color_stop_list = properties_.GetProperty(ids.color_stop_list); + + if (!p_angle || !p_position[0] || !p_position[1] || !p_color_stop_list) + return nullptr; + + const float angle = ComputeAngle(p_angle->GetNumericValue()); + const Vector2Numeric position = ComputePosition(p_position); + const bool repeating = (name == "repeating-conic-gradient"); + + if (p_color_stop_list->unit != Unit::COLORSTOPLIST) + return nullptr; + const ColorStopList& color_stop_list = p_color_stop_list->value.GetReference(); + + auto decorator = MakeShared(); + if (decorator->Initialise(repeating, angle, position, color_stop_list)) + return decorator; + + return nullptr; +} + } // namespace Rml diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index 5243fd571..cee2f7645 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -36,6 +36,8 @@ namespace Rml { +using Vector2Numeric = Vector2; + /** Straight gradient. @@ -65,7 +67,7 @@ class DecoratorStraightGradient : public Decorator { class DecoratorStraightGradientInstancer : public DecoratorInstancer { public: DecoratorStraightGradientInstancer(); - ~DecoratorStraightGradientInstancer(); + virtual ~DecoratorStraightGradientInstancer(); SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) override; @@ -100,10 +102,6 @@ class DecoratorLinearGradient : public Decorator { Vector2f p0, p1; float length; }; - struct ElementData { - Geometry geometry; - CompiledShaderHandle shader; - }; LinearGradientShape CalculateShape(Vector2f box_dimensions) const; @@ -116,7 +114,7 @@ class DecoratorLinearGradient : public Decorator { class DecoratorLinearGradientInstancer : public DecoratorInstancer { public: DecoratorLinearGradientInstancer(); - ~DecoratorLinearGradientInstancer(); + virtual ~DecoratorLinearGradientInstancer(); SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) override; @@ -141,5 +139,95 @@ class DecoratorLinearGradientInstancer : public DecoratorInstancer { PropertyIds ids; }; +/** + Radial gradient. + */ +class DecoratorRadialGradient : public Decorator { +public: + enum class Shape { Circle, Ellipse, Unspecified }; + enum class SizeType { ClosestSide, FarthestSide, ClosestCorner, FarthestCorner, LengthPercentage }; + + DecoratorRadialGradient(); + virtual ~DecoratorRadialGradient(); + + bool Initialise(bool repeating, Shape shape, SizeType size_type, Vector2Numeric size, Vector2Numeric position, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + struct RadialGradientShape { + Vector2f center, radius; + }; + RadialGradientShape CalculateRadialGradientShape(Element* element, Vector2f dimensions) const; + + bool repeating = false; + Shape shape = {}; + SizeType size_type = {}; + Vector2Numeric size; + Vector2Numeric position; + + ColorStopList color_stops; +}; + +class DecoratorRadialGradientInstancer : public DecoratorInstancer { +public: + DecoratorRadialGradientInstancer(); + virtual ~DecoratorRadialGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct GradientPropertyIds { + PropertyId ending_shape; + PropertyId size_x, size_y; + PropertyId position_x, position_y; + PropertyId color_stop_list; + }; + GradientPropertyIds ids; +}; + +/** + Conic gradient. + */ +class DecoratorConicGradient : public Decorator { +public: + DecoratorConicGradient(); + virtual ~DecoratorConicGradient(); + + bool Initialise(bool repeating, float angle, Vector2Numeric position, const ColorStopList& color_stops); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + bool repeating = false; + float angle = {}; + Vector2Numeric position; + ColorStopList color_stops; +}; + +class DecoratorConicGradientInstancer : public DecoratorInstancer { +public: + DecoratorConicGradientInstancer(); + virtual ~DecoratorConicGradientInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct GradientPropertyIds { + PropertyId angle; + PropertyId position_x, position_y; + PropertyId color_stop_list; + }; + GradientPropertyIds ids; +}; + } // namespace Rml #endif diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 6f6594bc7..4636a2bc7 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -154,6 +154,8 @@ struct DefaultInstancers { DecoratorNinePatchInstancer decorator_ninepatch; DecoratorStraightGradientInstancer decorator_straight_gradient; DecoratorLinearGradientInstancer decorator_linear_gradient; + DecoratorRadialGradientInstancer decorator_radial_gradient; + DecoratorConicGradientInstancer decorator_conic_gradient; // Filters FilterBasicInstancer filter_hue_rotate = {FilterBasicInstancer::ValueType::Angle, "0rad"}; @@ -242,11 +244,17 @@ bool Factory::Initialise() RegisterDecoratorInstancer("tiled-box", &default_instancers->decorator_tiled_box); RegisterDecoratorInstancer("image", &default_instancers->decorator_image); RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch); + RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient); RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient); RegisterDecoratorInstancer("vertical-gradient", &default_instancers->decorator_straight_gradient); + RegisterDecoratorInstancer("linear-gradient", &default_instancers->decorator_linear_gradient); RegisterDecoratorInstancer("repeating-linear-gradient", &default_instancers->decorator_linear_gradient); + RegisterDecoratorInstancer("radial-gradient", &default_instancers->decorator_radial_gradient); + RegisterDecoratorInstancer("repeating-radial-gradient", &default_instancers->decorator_radial_gradient); + RegisterDecoratorInstancer("conic-gradient", &default_instancers->decorator_conic_gradient); + RegisterDecoratorInstancer("repeating-conic-gradient", &default_instancers->decorator_conic_gradient); // Filter instancers RegisterFilterInstancer("hue-rotate", &default_instancers->filter_hue_rotate); diff --git a/Source/Core/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 3ab4d3610..34a2118c4 100644 --- a/Source/Core/PropertyParserDecorator.cpp +++ b/Source/Core/PropertyParserDecorator.cpp @@ -65,8 +65,6 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor RMLUI_ZoneScoped; - const BoxArea default_paint_area = BoxArea::Padding; - // Make sure we don't split inside the parenthesis since they may appear in decorator shorthands. StringList decorator_string_list; StringUtilities::ExpandString(decorator_string_list, decorator_string_value, ',', '(', ')'); @@ -82,10 +80,9 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor const size_t shorthand_close = decorator_string.rfind(')'); const bool invalid_parenthesis = (shorthand_open == String::npos || shorthand_close == String::npos || shorthand_open >= shorthand_close); - // Find the paint area for the decorator. - BoxArea paint_area = default_paint_area; - // Look-up keywords for customized paint area. + BoxArea paint_area = BoxArea::Auto; + { const size_t keywords_begin = (invalid_parenthesis ? decorator_string.find(' ') : shorthand_close + 1); StringList keywords; diff --git a/Tests/Data/VisualTests/shader_conic_gradient.rml b/Tests/Data/VisualTests/shader_conic_gradient.rml new file mode 100644 index 000000000..688d8ff27 --- /dev/null +++ b/Tests/Data/VisualTests/shader_conic_gradient.rml @@ -0,0 +1,105 @@ + + + conic-gradient + + + + + + + + +Red to gold clockwise, from top [equivalent] + +
+
+
+
+
+
+
+
+ + +Positioned red to gold clockwise, from top [equivalent] + +
+
+
+
+ + +Light gray to dark gray, from top [equivalent] + +
+
+ + +Smooth cone + +
+
+ + +Color wheel, pie chart, and checkerboard + +
+
+
+ + +Repeating + +
+ + + diff --git a/Tests/Data/VisualTests/shader_radial_gradient.rml b/Tests/Data/VisualTests/shader_radial_gradient.rml new file mode 100644 index 000000000..93acdadbc --- /dev/null +++ b/Tests/Data/VisualTests/shader_radial_gradient.rml @@ -0,0 +1,147 @@ + + + radial-gradient + + + + + + + + +Ellipse, yellow (center) to green (corners) [equivalent] + +
+
+
+ + +Circle, yellow (center) to green (corners) + +
+ + +Ellipse, red (center), yellow, green (corners) + +
+ + +Red (bottom-left), yellow, green (top-right) + +
+ + +Repeating radial gradients + +
+
+
+
+
+
+
+
+ + +Ellipse (top-left, closest-side), red, yellow, green [equivalent] + +
+
+ + +Circle (top-left, closest-side), red, yellow, green [equivalent] + +
+
+ + +Ellipse (top-left, closest-corner), red, yellow, green + +
+ + +Circle (top-left, closest-corner), red, yellow, green + +
+ + +Sharp color transitions for testing anti-aliasing + +
+
+
+
+ + +Edge cases, should be green, possibly with a tiny ellipse. + +
+
+
+
+
+
+ + + From 58884979923fdb0001ba79b8890d957b0a58958a Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Fri, 9 Jun 2023 01:42:19 +0200 Subject: [PATCH 36/80] Add interpolation support for angle values, support conic gradient animations --- Source/Core/ElementAnimation.cpp | 14 +++++++++----- Tests/Data/VisualTests/shader_conic_gradient.rml | 6 ++++++ Tests/Source/UnitTests/Animation.cpp | 9 +++++++++ 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index a7ba6761a..3b6b1c014 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -29,7 +29,6 @@ #include "ElementAnimation.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" -#include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheet.h" @@ -37,6 +36,7 @@ #include "../../Include/RmlUi/Core/StyleSheetTypes.h" #include "../../Include/RmlUi/Core/Transform.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "ComputeProperty.h" #include "ElementStyle.h" #include "TransformUtilities.h" @@ -111,6 +111,14 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl } } + if (Any(p0.unit & Unit::ANGLE) && Any(p1.unit & Unit::ANGLE)) + { + float f0 = ComputeAngle(p0.GetNumericValue()); + float f1 = ComputeAngle(p1.GetNumericValue()); + float f = (1.0f - alpha) * f0 + alpha * f1; + return Property{f, Unit::RAD}; + } + if (p0.unit == Unit::KEYWORD && p1.unit == Unit::KEYWORD) { // Discrete interpolation, swap at alpha = 0.5. @@ -376,13 +384,9 @@ static PrepareTransformResult PrepareTransformPair(Transform& t0, Transform& t1, if (TransformUtilities::TryConvertToMatchingGenericType(small[i_small], big[i_big])) { // They matched exactly or in their more generic form. One or both primitives may have been converted. - match_success = true; if (big[i_big].type != big_type) changed_big = true; - } - if (match_success) - { matching_indices.push_back(i_big); match_success = true; i_big += 1; diff --git a/Tests/Data/VisualTests/shader_conic_gradient.rml b/Tests/Data/VisualTests/shader_conic_gradient.rml index 688d8ff27..e89ad9ffc 100644 --- a/Tests/Data/VisualTests/shader_conic_gradient.rml +++ b/Tests/Data/VisualTests/shader_conic_gradient.rml @@ -53,7 +53,12 @@ .color_wheel > :nth-child(2) { decorator: conic-gradient(#9acd32 40%, #ffd700 0 75%, #f06 0); } .color_wheel > :nth-child(3) { decorator: conic-gradient(black 25%, white 0 50%, black 0 75%, white 0); } + @keyframes spinner { + from { decorator: repeating-conic-gradient(from 0deg, #fff3 0 15deg, #fff0 0 30deg); } + to { decorator: repeating-conic-gradient(from 360deg, #fff3 0 15deg, #fff0 0 30deg); } + } .repeating > :nth-child(1) { decorator: repeating-conic-gradient(#ffd700, #f06 20deg); } + .repeating > :nth-child(2) { background: #0ac; animation: 5s spinner infinite; } @@ -100,6 +105,7 @@ Color wheel, pie chart, and checkerboard Repeating
+
diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index 345b6c772..45a963caa 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -95,6 +95,15 @@ TEST_CASE("animation.decorator") "", "", + "horizontal-gradient(transparent transparent) border-box", + "horizontal-gradient(white white) border-box", + + "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)) border-box", + }, + { + "", + "", + "none", "horizontal-gradient(transparent transparent)", From 5d52f71d765b9fab1ed264754131e2e133e542b1 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 10 Jun 2023 22:52:19 +0200 Subject: [PATCH 37/80] Add shader decorator --- Backends/RmlUi_Renderer_GL3.cpp | 65 ++++++++++++++- CMake/FileList.cmake | 2 + Samples/basic/effect/data/effect.rml | 3 +- Samples/basic/effect/src/main.cpp | 2 +- Source/Core/DecoratorGradient.cpp | 38 +++------ Source/Core/DecoratorShader.cpp | 118 +++++++++++++++++++++++++++ Source/Core/DecoratorShader.h | 79 ++++++++++++++++++ Source/Core/Factory.cpp | 3 + 8 files changed, 281 insertions(+), 29 deletions(-) create mode 100644 Source/Core/DecoratorShader.cpp create mode 100644 Source/Core/DecoratorShader.h diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 5875ede92..72337d21a 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #if defined(RMLUI_PLATFORM_WIN32) && !defined(__MINGW32__) @@ -181,6 +182,33 @@ void main() { } )"; +// "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; @@ -270,6 +298,7 @@ enum class ProgramId { Color, Texture, Gradient, + Creation, Passthrough, ColorMatrix, Blur, @@ -286,6 +315,7 @@ enum class FragShaderId { Color, Texture, Gradient, + Creation, Passthrough, ColorMatrix, Blur, @@ -308,13 +338,16 @@ enum class UniformId { 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", "_weights[0]", "_func", "_p", "_v", "_stop_colors[0]", "_stop_positions[0]", "_num_stops"}; + "_texelOffset", "_texCoordMin", "_texCoordMax", "_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"}; @@ -346,6 +379,7 @@ 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::Blur, "blur", shader_frag_blur}, @@ -355,6 +389,7 @@ 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::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, @@ -1578,7 +1613,7 @@ void RenderInterface_GL3::ReleaseCompiledFilter(Rml::CompiledFilterHandle filter delete reinterpret_cast(filter); } -enum class CompiledShaderType { Invalid = 0, Gradient }; +enum class CompiledShaderType { Invalid = 0, Gradient, Creation }; struct CompiledShader { CompiledShaderType type; @@ -1588,6 +1623,9 @@ struct CompiledShader { 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) @@ -1639,6 +1677,15 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& 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))); @@ -1676,6 +1723,20 @@ void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, 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); diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index f5cf1b933..49b0d8844 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -12,6 +12,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/DataViewDefault.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.h + ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorShader.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.h ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.h @@ -255,6 +256,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Decorator.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorGradient.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorNinePatch.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorShader.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiled.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledBox.cpp ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledHorizontal.cpp diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index 0d652f24c..f5edab426 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -63,6 +63,7 @@ .transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); } +.shader { decorator: shader("creation"); } .gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box; } .brightness { filter: brightness(0.5); } @@ -156,7 +157,7 @@
Hello, do you feel the funk?
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
"Creation" (Danilo Guanabara)
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Samples/basic/effect/src/main.cpp b/Samples/basic/effect/src/main.cpp index b2e9d160a..cf184aefb 100644 --- a/Samples/basic/effect/src/main.cpp +++ b/Samples/basic/effect/src/main.cpp @@ -140,7 +140,7 @@ int main(int /*argc*/, char** /*argv*/) bool running = true; while (running) { - running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, false); context->Update(); diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index 12e952eea..be25301c6 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -35,22 +35,10 @@ #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "ComputeProperty.h" -#include "Pool.h" +#include "DecoratorShader.h" namespace Rml { -struct GradientElementData { - GradientElementData(Geometry&& geometry, CompiledShaderHandle shader) : geometry(std::move(geometry)), shader(shader) {} - Geometry geometry; - CompiledShaderHandle shader; -}; - -Pool& GetGradientElementDataPool() -{ - static Pool gradient_element_data_pool(20, true); - return gradient_element_data_pool; -} - // Returns the point along the input line ('line_point', 'line_vector') closest to the input 'point'. static Vector2f IntersectionPointToLineNormal(const Vector2f point, const Vector2f line_point, const Vector2f line_vector) { @@ -342,21 +330,21 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen for (Vertex& vertex : geometry.GetVertices()) vertex.tex_coord = vertex.position - render_offset; - GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); return reinterpret_cast(element_data); } void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); GetRenderInterface()->ReleaseCompiledShader(element_data->shader); - GetGradientElementDataPool().DestroyAndDeallocate(element_data); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); } @@ -510,20 +498,20 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen for (Vertex& vertex : geometry.GetVertices()) vertex.tex_coord = vertex.position - render_offset; - GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); return reinterpret_cast(element_data); } void DecoratorRadialGradient::ReleaseElementData(DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); GetRenderInterface()->ReleaseCompiledShader(element_data->shader); - GetGradientElementDataPool().DestroyAndDeallocate(element_data); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); } @@ -706,21 +694,21 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element for (Vertex& vertex : geometry.GetVertices()) vertex.tex_coord = vertex.position - render_offset; - GradientElementData* element_data = GetGradientElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); return reinterpret_cast(element_data); } void DecoratorConicGradient::ReleaseElementData(DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); GetRenderInterface()->ReleaseCompiledShader(element_data->shader); - GetGradientElementDataPool().DestroyAndDeallocate(element_data); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { - GradientElementData* element_data = reinterpret_cast(handle); + ShaderElementData* element_data = reinterpret_cast(handle); element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); } diff --git a/Source/Core/DecoratorShader.cpp b/Source/Core/DecoratorShader.cpp new file mode 100644 index 000000000..3e36e0431 --- /dev/null +++ b/Source/Core/DecoratorShader.cpp @@ -0,0 +1,118 @@ +/* + * 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 "DecoratorShader.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" + +namespace Rml { + +Pool& GetShaderElementDataPool() +{ + static Pool gradient_element_data_pool(20, true); + return gradient_element_data_pool; +} + +DecoratorShader::DecoratorShader() {} + +DecoratorShader::~DecoratorShader() {} + +bool DecoratorShader::Initialise(String&& in_value) +{ + value = std::move(in_value); + return true; +} + +DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxArea render_area) const +{ + RenderInterface* render_interface = GetRenderInterface(); + if (!render_interface) + return INVALID_DECORATORDATAHANDLE; + + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(render_area); + CompiledShaderHandle effect_handle = + render_interface->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); + + Geometry geometry; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), render_area); + + const Vector2f offset = box.GetPosition(render_area); + for (Vertex& vertex : geometry.GetVertices()) + vertex.tex_coord = (vertex.position - offset) / dimensions; + + ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), effect_handle); + + return reinterpret_cast(element_data); +} + +void DecoratorShader::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetRenderInterface()->ReleaseCompiledShader(element_data->shader); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorShader::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); +} + +DecoratorShaderInstancer::DecoratorShaderInstancer() +{ + ids.value = RegisterProperty("value", String()).AddParser("string").GetId(); + RegisterShorthand("decorator", "value", ShorthandType::FallThrough); +} + +DecoratorShaderInstancer::~DecoratorShaderInstancer() {} + +SharedPtr DecoratorShaderInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties_, + const DecoratorInstancerInterface& /*interface_*/) +{ + const Property* p_value = properties_.GetProperty(ids.value); + if (!p_value) + return nullptr; + + String value = p_value->Get(); + + auto decorator = MakeShared(); + if (decorator->Initialise(std::move(value))) + return decorator; + + return nullptr; +} + +} // namespace Rml diff --git a/Source/Core/DecoratorShader.h b/Source/Core/DecoratorShader.h new file mode 100644 index 000000000..16004349c --- /dev/null +++ b/Source/Core/DecoratorShader.h @@ -0,0 +1,79 @@ +/* + * 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_CORE_DECORATORSHADER_H +#define RMLUI_CORE_DECORATORSHADER_H + +#include "../../Include/RmlUi/Core/Decorator.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/ID.h" +#include "../../Include/RmlUi/Core/Spritesheet.h" +#include "Pool.h" + +namespace Rml { + +class DecoratorShader : public Decorator { +public: + DecoratorShader(); + virtual ~DecoratorShader(); + + bool Initialise(String&& value); + + DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; + void ReleaseElementData(DecoratorDataHandle element_data) const override; + + void RenderElement(Element* element, DecoratorDataHandle element_data) const override; + +private: + String value; +}; + +class DecoratorShaderInstancer : public DecoratorInstancer { +public: + DecoratorShaderInstancer(); + ~DecoratorShaderInstancer(); + + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; + +private: + struct PropertyIds { + PropertyId value; + }; + PropertyIds ids; +}; + +struct ShaderElementData { + ShaderElementData(Geometry&& geometry, CompiledShaderHandle shader) : geometry(std::move(geometry)), shader(shader) {} + Geometry geometry; + CompiledShaderHandle shader; +}; +Pool& GetShaderElementDataPool(); + +} // namespace Rml +#endif diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 4636a2bc7..5f3db9678 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -50,6 +50,7 @@ #include "DataViewDefault.h" #include "DecoratorGradient.h" #include "DecoratorNinePatch.h" +#include "DecoratorShader.h" #include "DecoratorTiledBox.h" #include "DecoratorTiledHorizontal.h" #include "DecoratorTiledImage.h" @@ -152,6 +153,7 @@ struct DefaultInstancers { DecoratorTiledBoxInstancer decorator_tiled_box; DecoratorTiledImageInstancer decorator_image; DecoratorNinePatchInstancer decorator_ninepatch; + DecoratorShaderInstancer decorator_shader; DecoratorStraightGradientInstancer decorator_straight_gradient; DecoratorLinearGradientInstancer decorator_linear_gradient; DecoratorRadialGradientInstancer decorator_radial_gradient; @@ -244,6 +246,7 @@ bool Factory::Initialise() RegisterDecoratorInstancer("tiled-box", &default_instancers->decorator_tiled_box); RegisterDecoratorInstancer("image", &default_instancers->decorator_image); RegisterDecoratorInstancer("ninepatch", &default_instancers->decorator_ninepatch); + RegisterDecoratorInstancer("shader", &default_instancers->decorator_shader); RegisterDecoratorInstancer("gradient", &default_instancers->decorator_straight_gradient); RegisterDecoratorInstancer("horizontal-gradient", &default_instancers->decorator_straight_gradient); From 0471ce737eb00146214489cb41d0b306f1fbb403 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 10 Jun 2023 23:49:52 +0200 Subject: [PATCH 38/80] Add support for 'mask-image' property --- Backends/RmlUi_Renderer_GL3.cpp | 74 ++++++++++++++- Backends/RmlUi_Renderer_GL3.h | 3 + Include/RmlUi/Core/ComputedValues.h | 5 +- Include/RmlUi/Core/ID.h | 2 + Include/RmlUi/Core/RenderInterface.h | 8 +- Samples/basic/effect/data/effect.rml | 5 ++ Source/Core/Element.cpp | 9 +- Source/Core/ElementDecoration.cpp | 115 +++++++++++++++--------- Source/Core/ElementDecoration.h | 1 + Source/Core/ElementStyle.cpp | 3 + Source/Core/RenderInterface.cpp | 5 ++ Source/Core/StyleSheetSpecification.cpp | 1 + 12 files changed, 179 insertions(+), 52 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 72337d21a..46a5a7c7b 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -241,6 +241,19 @@ void main() { finalColor = _color_matrix * texColor; } )"; +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) @@ -301,6 +314,7 @@ enum class ProgramId { Creation, Passthrough, ColorMatrix, + BlendMask, Blur, DropShadow, Count, @@ -318,6 +332,7 @@ enum class FragShaderId { Creation, Passthrough, ColorMatrix, + BlendMask, Blur, DropShadow, Count, @@ -331,6 +346,7 @@ enum class UniformId { TexelOffset, TexCoordMin, TexCoordMax, + TexMask, Weights, Func, P, @@ -346,8 +362,8 @@ enum class UniformId { namespace Gfx { static const char* const program_uniform_names[(size_t)UniformId::Count] = {"_translate", "_transform", "_tex", "_color", "_color_matrix", - "_texelOffset", "_texCoordMin", "_texCoordMax", "_weights[0]", "_func", "_p", "_v", "_stop_colors[0]", "_stop_positions[0]", "_num_stops", - "_value", "_dimensions"}; + "_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"}; @@ -382,6 +398,7 @@ static const FragShaderDefinition frag_shader_definitions[] = { {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}, }; @@ -392,6 +409,7 @@ static const ProgramDefinition program_definitions[] = { {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}, }; @@ -736,6 +754,9 @@ static bool CreateShaders(ProgramData& data) return ReportError("program", def.name_str); } + glUseProgram(data.programs[ProgramId::BlendMask]); + glUniform1i(data.uniforms.Get(ProgramId::BlendMask, UniformId::TexMask), 1); + glUseProgram(0); return true; @@ -1478,7 +1499,7 @@ void RenderInterface_GL3::SetTransform(const Rml::Matrix4f* new_transform) program_transform_dirty.set(); } -enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix }; +enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix, MaskImage }; struct CompiledFilter { FilterType type; @@ -1856,6 +1877,28 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand 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); @@ -1960,6 +2003,29 @@ Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture(Rml::Vector2i dimensi return render_texture; } +Rml::CompiledFilterHandle RenderInterface_GL3::SaveLayerAsMaskImage() +{ + BlitTopLayerToPostprocessPrimary(); + + 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); @@ -1994,7 +2060,7 @@ void RenderInterface_GL3::SubmitTransformUniform(Rml::Vector2f translation) RenderInterface_GL3::RenderLayerStack::RenderLayerStack() { - fb_postprocess.resize(3); + fb_postprocess.resize(4); } RenderInterface_GL3::RenderLayerStack::~RenderLayerStack() diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 8511eed70..f145685e3 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -86,6 +86,8 @@ class RenderInterface_GL3 : public Rml::RenderInterface { Rml::TextureHandle SaveLayerAsTexture(Rml::Vector2i dimensions) override; + Rml::CompiledFilterHandle SaveLayerAsMaskImage() override; + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; void ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) override; @@ -158,6 +160,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { 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(); diff --git a/Include/RmlUi/Core/ComputedValues.h b/Include/RmlUi/Core/ComputedValues.h index 5f330b312..4e0c66aaf 100644 --- a/Include/RmlUi/Core/ComputedValues.h +++ b/Include/RmlUi/Core/ComputedValues.h @@ -159,7 +159,7 @@ namespace Style { vertical_align_type(VerticalAlign::Baseline), drag(Drag::None), tab_index(TabIndex::None), overscroll_behavior(OverscrollBehavior::Auto), - has_filter(false), has_backdrop_filter(false), has_box_shadow(false) + has_mask_image(false), has_filter(false), has_backdrop_filter(false), has_box_shadow(false) {} LengthPercentage::Type min_width_type : 1, max_width_type : 1; @@ -177,6 +177,7 @@ namespace Style { TabIndex tab_index : 1; OverscrollBehavior overscroll_behavior : 1; + bool has_mask_image : 1; bool has_filter : 1; bool has_backdrop_filter : 1; bool has_box_shadow : 1; @@ -304,6 +305,7 @@ namespace Style { LengthPercentage column_gap() const { return LengthPercentage(rare.column_gap_type, rare.column_gap); } OverscrollBehavior overscroll_behavior() const { return rare.overscroll_behavior; } float scrollbar_margin() const { return rare.scrollbar_margin; } + bool has_mask_image() const { return rare.has_mask_image; } bool has_filter() const { return rare.has_filter; } bool has_backdrop_filter() const { return rare.has_backdrop_filter; } bool has_box_shadow() const { return rare.has_box_shadow; } @@ -387,6 +389,7 @@ namespace Style { void image_color (Colourb value) { rare.image_color = value; } void overscroll_behavior (OverscrollBehavior value){ rare.overscroll_behavior = value; } void scrollbar_margin (float value) { rare.scrollbar_margin = value; } + void has_mask_image (bool value) { rare.has_mask_image = value; } void has_filter (bool value) { rare.has_filter = value; } void has_backdrop_filter (bool value) { rare.has_backdrop_filter = value; } void has_box_shadow (bool value) { rare.has_box_shadow = value; } diff --git a/Include/RmlUi/Core/ID.h b/Include/RmlUi/Core/ID.h index ed38504e5..344e8517f 100644 --- a/Include/RmlUi/Core/ID.h +++ b/Include/RmlUi/Core/ID.h @@ -155,7 +155,9 @@ enum class PropertyId : uint8_t { Focus, Decorator, + MaskImage, FontEffect, + Filter, BackdropFilter, BoxShadow, diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 439ad023c..953a8989e 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -130,7 +130,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @return True if the texture generation succeeded and the handle is valid, false if not. virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); /// Called by RmlUi when a loaded texture is no longer required. - /// @param texture The texture handle to release. + /// @param[in] texture The texture handle to release. virtual void ReleaseTexture(TextureHandle texture); /// Called by RmlUi when it wants the renderer to use a new transform matrix. @@ -152,9 +152,14 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @return The handle to the new texture. virtual TextureHandle SaveLayerAsTexture(Vector2i dimensions); + /// Called by RmlUi when it wants to store the current layer as a mask image, to be applied later as a filter. + /// @return The handle to a new filter representng the stored mask image. + virtual CompiledFilterHandle SaveLayerAsMaskImage(); + /// Called by RmlUi when it wants to compile a new filter. /// @param[in] name The name of the filter. /// @param[in] parameters The list of name-value parameters specified for the filter. + /// @return The handle representing the compiled filter. virtual CompiledFilterHandle CompileFilter(const String& name, const Dictionary& parameters); /// Called by RmlUi when it no longer needs a previously compiled filter. /// @param[in] filter The handle to a previously compiled filter. @@ -163,6 +168,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// Called by RmlUi when it wants to compile a new shader. /// @param[in] name The name of the shader. /// @param[in] parameters The list of name-value parameters specified for the filter. + /// @return The handle representing the compiled shader. virtual CompiledShaderHandle CompileShader(const String& name, const Dictionary& parameters); /// Called by RmlUi when it wants to render geometry using the given shader. /// @param[in] shader The handle to a previously compiled shader. diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index f5edab426..d86a8c6b1 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -63,6 +63,10 @@ .transform, .filter.transform_all > .box { transform: rotate3d(0.2, 0.4, 0.1, 15deg); } +.mask { + decorator: horizontal-gradient(#f00 #ff0); + mask-image: image(icon-invader scale-none 15px 50%), horizontal-gradient(#0000 #000f); +} .shader { decorator: shader("creation"); } .gradient { decorator: linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box; } @@ -159,6 +163,7 @@
Hello, do you feel the funk?
"Creation" (Danilo Guanabara)
Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 2981b81d3..0b5288ad0 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -1731,16 +1731,17 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) changed_properties.Contains(PropertyId::BorderBottomRightRadius) || // changed_properties.Contains(PropertyId::BorderBottomLeftRadius) // ); - const bool filter_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter)); + const bool filter_or_mask_changed = (changed_properties.Contains(PropertyId::Filter) || changed_properties.Contains(PropertyId::BackdropFilter) || + changed_properties.Contains(PropertyId::MaskImage)); // Update the z-index and stacking context. - if (changed_properties.Contains(PropertyId::ZIndex) || filter_changed) + if (changed_properties.Contains(PropertyId::ZIndex) || filter_or_mask_changed) { const Style::ZIndex z_index_property = meta->computed_values.z_index(); const float new_z_index = (z_index_property.type == Style::ZIndex::Auto ? 0.f : z_index_property.value); const bool enable_local_stacking_context = (z_index_property.type != Style::ZIndex::Auto || local_stacking_context_forced || - meta->computed_values.has_filter() || meta->computed_values.has_backdrop_filter()); + meta->computed_values.has_filter() || meta->computed_values.has_backdrop_filter() || meta->computed_values.has_mask_image()); if (z_index != new_z_index || local_stacking_context != enable_local_stacking_context) { @@ -1788,7 +1789,7 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) } // Dirty the decoration if it's changed. - if (border_radius_changed || filter_changed || changed_properties.Contains(PropertyId::Decorator)) + if (border_radius_changed || filter_or_mask_changed || changed_properties.Contains(PropertyId::Decorator)) { meta->decoration.DirtyDecorators(); } diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 3e4cecabf..383b65e5b 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -60,49 +60,55 @@ void ElementDecoration::InstanceDecorators() const ComputedValues& computed = element->GetComputedValues(); - if (computed.has_decorator()) + if (computed.has_decorator() || computed.has_mask_image()) { - const Property* property = element->GetLocalProperty(PropertyId::Decorator); - if (!property || property->unit != Unit::DECORATOR) - return; - - DecoratorsPtr decorators_ptr = property->Get(); - if (!decorators_ptr) - return; - const StyleSheet* style_sheet = element->GetStyleSheet(); if (!style_sheet) return; - PropertySource document_source("", 0, ""); - const PropertySource* source = property->source.get(); - - if (!source) + for (const auto id : {PropertyId::Decorator, PropertyId::MaskImage}) { - if (ElementDocument* document = element->GetOwnerDocument()) + const Property* property = element->GetLocalProperty(id); + if (!property || property->unit != Unit::DECORATOR) + continue; + + DecoratorsPtr decorators_ptr = property->Get(); + if (!decorators_ptr) + continue; + + PropertySource document_source("", 0, ""); + const PropertySource* source = property->source.get(); + + if (!source) { - document_source.path = document->GetSourceURL(); - source = &document_source; + if (ElementDocument* document = element->GetOwnerDocument()) + { + document_source.path = document->GetSourceURL(); + source = &document_source; + } } - } - const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); - RMLUI_ASSERT(decorator_list.empty() || decorator_list.size() == decorators_ptr->list.size()); + const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); + RMLUI_ASSERT(decorator_list.empty() || decorator_list.size() == decorators_ptr->list.size()); - for (size_t i = 0; i < decorator_list.size() && i < decorators_ptr->list.size(); i++) - { - const SharedPtr& decorator = decorator_list[i]; - if (decorator) + DecoratorEntryList& decorators_target = (id == PropertyId::Decorator ? decorators : mask_images); + decorators_target.reserve(decorators_ptr->list.size()); + + for (size_t i = 0; i < decorator_list.size() && i < decorators_ptr->list.size(); i++) { - DecoratorEntry entry; - entry.decorator_data = 0; - entry.decorator = decorator; - entry.paint_area = decorators_ptr->list[i].paint_area; - if (entry.paint_area == BoxArea::Auto) - entry.paint_area = BoxArea::Padding; - - RMLUI_ASSERT(entry.paint_area >= BoxArea::Border && entry.paint_area <= BoxArea::Content); - decorators.push_back(std::move(entry)); + const SharedPtr& decorator = decorator_list[i]; + if (decorator) + { + DecoratorEntry entry; + entry.decorator_data = 0; + entry.decorator = decorator; + entry.paint_area = decorators_ptr->list[i].paint_area; + if (entry.paint_area == BoxArea::Auto) + entry.paint_area = (id == PropertyId::Decorator ? BoxArea::Padding : BoxArea::Border); + + RMLUI_ASSERT(entry.paint_area >= BoxArea::Border && entry.paint_area <= BoxArea::Content); + decorators_target.push_back(std::move(entry)); + } } } } @@ -146,12 +152,15 @@ void ElementDecoration::ReloadDecoratorsData() { decorators_data_dirty = false; - for (DecoratorEntry& decorator : decorators) + for (DecoratorEntryList* list : {&decorators, &mask_images}) { - if (decorator.decorator_data) - decorator.decorator->ReleaseElementData(decorator.decorator_data); + for (DecoratorEntry& decorator : *list) + { + if (decorator.decorator_data) + decorator.decorator->ReleaseElementData(decorator.decorator_data); - decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); + decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); + } } for (FilterEntryList* list : {&filters, &backdrop_filters}) @@ -169,12 +178,15 @@ void ElementDecoration::ReloadDecoratorsData() void ElementDecoration::ReleaseDecorators() { - for (DecoratorEntry& decorator : decorators) + for (DecoratorEntryList* list : {&decorators, &mask_images}) { - if (decorator.decorator_data) - decorator.decorator->ReleaseElementData(decorator.decorator_data); + for (DecoratorEntry& decorator : *list) + { + if (decorator.decorator_data) + decorator.decorator->ReleaseElementData(decorator.decorator_data); + } + list->clear(); } - decorators.clear(); for (FilterEntryList* list : {&filters, &backdrop_filters}) { @@ -206,7 +218,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) } } - if (filters.empty() && backdrop_filters.empty()) + if (filters.empty() && backdrop_filters.empty() && mask_images.empty()) return; RenderInterface* render_interface = ::Rml::GetRenderInterface(); @@ -262,7 +274,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) } } - if (!filters.empty()) + if (!filters.empty() || !mask_images.empty()) { if (render_stage == RenderStage::Enter) { @@ -272,15 +284,34 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) { ApplyClippingRegion(PropertyId::Filter); + CompiledFilterHandle mask_image_handle = {}; FilterHandleList filter_handles; + for (auto& filter : filters) { if (filter.handle) filter_handles.push_back(filter.handle); } + if (!mask_images.empty()) + { + render_interface->PushLayer(LayerFill::Clear); + + for (int i = (int)mask_images.size() - 1; i >= 0; i--) + { + DecoratorEntry& mask_image = mask_images[i]; + mask_image.decorator->RenderElement(element, mask_image.decorator_data); + } + mask_image_handle = render_interface->SaveLayerAsMaskImage(); + if (mask_image_handle) + filter_handles.push_back(mask_image_handle); + render_interface->PopLayer(BlendMode::Discard, {}); + } + render_interface->PopLayer(BlendMode::Blend, filter_handles); + if (mask_image_handle) + render_interface->ReleaseCompiledFilter(mask_image_handle); render_manager.SetScissorRegion(initial_scissor_region); } } diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementDecoration.h index 9c0083609..9fa17770c 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementDecoration.h @@ -84,6 +84,7 @@ class ElementDecoration { // The list of decorators and filters used by this element from all style rules. DecoratorEntryList decorators; + DecoratorEntryList mask_images; FilterEntryList filters; FilterEntryList backdrop_filters; diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index adfe47564..0c0022261 100644 --- a/Source/Core/ElementStyle.cpp +++ b/Source/Core/ElementStyle.cpp @@ -849,6 +849,9 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S case PropertyId::Decorator: values.has_decorator(p->unit == Unit::DECORATOR); break; + case PropertyId::MaskImage: + values.has_mask_image(p->unit == Unit::DECORATOR); + break; case PropertyId::FontEffect: values.has_font_effect(p->unit == Unit::FONTEFFECT); break; diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index c27fd21e6..aafd2894f 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -78,6 +78,11 @@ TextureHandle RenderInterface::SaveLayerAsTexture(Vector2i /*dimensions*/) return TextureHandle{}; } +CompiledFilterHandle RenderInterface::SaveLayerAsMaskImage() +{ + return CompiledFilterHandle{}; +} + CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, const Dictionary& /*parameters*/) { return CompiledFilterHandle{}; diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index a907e249a..b524ab9a7 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -414,6 +414,7 @@ void StyleSheetSpecification::RegisterDefaultProperties() // Decorators and effects RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("decorator"); + RegisterProperty(PropertyId::MaskImage, "mask-image", "", false, false).AddParser("decorator"); RegisterProperty(PropertyId::FontEffect, "font-effect", "", true, false).AddParser("font_effect"); RegisterProperty(PropertyId::Filter, "filter", "", false, false).AddParser("filter", "filter"); From c6f550dabe33e857ebf1a96a76d57ca2f83d61ef Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Wed, 14 Jun 2023 21:11:42 +0200 Subject: [PATCH 39/80] Add animation support for filters --- Samples/basic/effect/data/effect.rml | 10 ++ Source/Core/ElementAnimation.cpp | 259 +++++++++++++++++---------- Tests/Source/UnitTests/Animation.cpp | 122 +++++++++++++ 3 files changed, 297 insertions(+), 94 deletions(-) diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index d86a8c6b1..5511fb8f3 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -107,6 +107,14 @@ } .boxshadow_inset { box-shadow: #f4fd 10px 5px 5px 3px inset; } +@keyframes animate-filter { + from { filter: drop-shadow(#f00) opacity(1.0) sepia(1.0); } + to { filter: drop-shadow(#000 30px 20px 5px) opacity(0.2) sepia(0.2); } +} +.animate { + animation: animate-filter 1.5s cubic-in-out infinite alternate; +} + Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
Hello, do you feel the funk?
+
Hello, do you feel the funk?
Hello, do you feel the funk?
Hello, do you feel the funk?
diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index 3b6b1c014..e48fe06a7 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -29,6 +29,7 @@ #include "ElementAnimation.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheet.h" @@ -42,6 +43,8 @@ namespace Rml { +static Property InterpolateProperties(const Property& p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition); + static Colourf ColourToLinearSpace(Colourb c) { Colourf result; @@ -88,8 +91,96 @@ static bool CombineAndDecompose(Transform& t, Element& e) return true; } +/** + An abstraction for decorator and filter declarations. + */ +struct EffectDeclarationView { + EffectDeclarationView() = default; + EffectDeclarationView(const DecoratorDeclaration& declaration) : + instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties), paint_area(declaration.paint_area) + {} + EffectDeclarationView(const NamedDecorator* named_decorator) : + instancer(Factory::GetDecoratorInstancer(named_decorator->type)), type(&named_decorator->type), properties(&named_decorator->properties) + {} + EffectDeclarationView(const FilterDeclaration& declaration) : + instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties) + {} + + EffectSpecification* instancer = nullptr; + const String* type = nullptr; + const PropertyDictionary* properties = nullptr; + BoxArea paint_area = BoxArea::Auto; + + explicit operator bool() const { return instancer != nullptr; } +}; + +// Interpolate two effect declarations. One of them can be empty, in which case the empty one is replaced by default values. +static bool InterpolateEffectProperties(PropertyDictionary& properties, const EffectDeclarationView& d0, const EffectDeclarationView& d1, float alpha, + Element& element) +{ + if (d0 && d1) + { + // Both declarations are specified, check if they are compatible for interpolation. + if (!d0.instancer || d0.instancer != d1.instancer || *d0.type != *d1.type || + d0.properties->GetNumProperties() != d1.properties->GetNumProperties() || d0.paint_area != d1.paint_area) + return false; + + const auto& properties0 = d0.properties->GetProperties(); + const auto& properties1 = d1.properties->GetProperties(); + + for (const auto& pair0 : properties0) + { + const PropertyId id = pair0.first; + const Property& prop0 = pair0.second; + + auto it = properties1.find(id); + if (it == properties1.end()) + { + RMLUI_ERRORMSG("Incompatible decorator properties."); + return false; + } + const Property& prop1 = it->second; + + Property p = InterpolateProperties(prop0, prop1, alpha, element, prop0.definition); + p.definition = prop0.definition; + properties.SetProperty(id, p); + } + return true; + } + else if ((d0 && !d1) || (!d0 && d1)) + { + // One of the declarations is empty, interpolate against the default values of its type. + const auto& d_filled = (d0 ? d0 : d1); + + const PropertySpecification& specification = d_filled.instancer->GetPropertySpecification(); + const PropertyMap& properties_filled = d_filled.properties->GetProperties(); + + for (const auto& pair_filled : properties_filled) + { + const PropertyId id = pair_filled.first; + const PropertyDefinition* underlying_definition = specification.GetProperty(id); + if (!underlying_definition) + return false; + + const Property& p_filled = pair_filled.second; + const Property& p_default = *underlying_definition->GetDefaultValue(); + const Property& p_interp0 = (d0 ? p_filled : p_default); + const Property& p_interp1 = (d1 ? p_filled : p_default); + + Property p = InterpolateProperties(p_interp0, p_interp1, alpha, element, p_filled.definition); + p.definition = p_filled.definition; + properties.SetProperty(id, p); + } + return true; + } + + return false; +} + static Property InterpolateProperties(const Property& p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition) { + const Property& p_discrete = (alpha < 0.5f ? p0 : p1); + if (Any(p0.unit & Unit::NUMBER_LENGTH_PERCENT) && Any(p1.unit & Unit::NUMBER_LENGTH_PERCENT)) { if (p0.unit == p1.unit || !definition) @@ -132,7 +223,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl return alpha <= 0.f ? p0 : p1; } - return alpha < 0.5f ? p0 : p1; + return p_discrete; } if (p0.unit == Unit::COLOUR && p1.unit == Unit::COLOUR) @@ -177,40 +268,29 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl return Property{TransformPtr(std::move(t)), Unit::TRANSFORM}; } - struct DecoratorDeclarationView { - DecoratorDeclarationView(const DecoratorDeclaration& declaration) : - type(declaration.type), instancer(declaration.instancer), properties(declaration.properties), paint_area(declaration.paint_area) - {} - DecoratorDeclarationView(const NamedDecorator* specification) : - type(specification->type), instancer(Factory::GetDecoratorInstancer(specification->type)), properties(specification->properties) - {} - const String& type; - DecoratorInstancer* instancer; - const PropertyDictionary& properties; - BoxArea paint_area = BoxArea::Auto; - }; - if (p0.unit == Unit::DECORATOR && p1.unit == Unit::DECORATOR) { - auto DiscreteInterpolation = [&]() { return alpha < 0.5f ? p0 : p1; }; + auto GetEffectDeclarationView = [](const Vector& declarations, size_t i, Element& element) -> EffectDeclarationView { + if (i >= declarations.size()) + return EffectDeclarationView(); - // If we have an instancer we pass that directly to the declaration view, otherwise look for a named @decorator. - auto GetDecoratorDeclarationView = [&](const DecoratorDeclaration& declaration) -> DecoratorDeclarationView { + const DecoratorDeclaration& declaration = declarations[i]; if (declaration.instancer) - return DecoratorDeclarationView{declaration}; + return EffectDeclarationView(declaration); + // If we don't have a decorator instancer, then this should be a named @decorator, look for one now. const StyleSheet* style_sheet = element.GetStyleSheet(); if (!style_sheet) - return DecoratorDeclarationView{declaration}; + return EffectDeclarationView(); const NamedDecorator* named_decorator = style_sheet->GetNamedDecorator(declaration.type); if (!named_decorator) { Log::Message(Log::LT_WARNING, "Could not find a named @decorator '%s'.", declaration.type.c_str()); - return DecoratorDeclarationView{declaration}; + return EffectDeclarationView(); } - return DecoratorDeclarationView{named_decorator}; + return EffectDeclarationView(named_decorator); }; auto& ptr0 = p0.value.GetReference(); @@ -218,95 +298,72 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl if (!ptr0 || !ptr1) { RMLUI_ERRORMSG("Invalid decorator pointer, were the decorator keys properly prepared?"); - return DiscreteInterpolation(); + return p_discrete; } - const bool p0_smaller = (ptr0->list.size() < ptr1->list.size()); - auto& small = (p0_smaller ? ptr0->list : ptr1->list); - auto& big = (p0_smaller ? ptr1->list : ptr0->list); - - // Build the new, interpolated decorator. - UniquePtr decorator(new DecoratorDeclarationList); - decorator->list.reserve(ptr0->list.size()); + // Build the new, interpolated decorator list. + const bool p0_bigger = ptr0->list.size() > ptr1->list.size(); + auto& big_list = (p0_bigger ? ptr0->list : ptr1->list); + auto decorator = MakeUnique(); + auto& list = decorator->list; + list.reserve(big_list.size()); - // Interpolate decorators that have common types. - for (size_t i = 0; i < small.size(); i++) + for (size_t i = 0; i < big_list.size(); i++) { - DecoratorDeclarationView d0_view{GetDecoratorDeclarationView(ptr0->list[i])}; - DecoratorDeclarationView d1_view{GetDecoratorDeclarationView(ptr1->list[i])}; + EffectDeclarationView d0 = GetEffectDeclarationView(ptr0->list, i, element); + EffectDeclarationView d1 = GetEffectDeclarationView(ptr1->list, i, element); - if (!d0_view.instancer || !d1_view.instancer) - return DiscreteInterpolation(); + const EffectDeclarationView& declaration = (p0_bigger ? d0 : d1); + list.push_back(DecoratorDeclaration{*declaration.type, static_cast(declaration.instancer), PropertyDictionary(), + declaration.paint_area}); - if (d0_view.instancer != d1_view.instancer || d0_view.type != d1_view.type || - d0_view.properties.GetNumProperties() != d1_view.properties.GetNumProperties() || d0_view.paint_area != d1_view.paint_area) - { - // Incompatible decorators, fall back to discrete interpolation. - return DiscreteInterpolation(); - } - - decorator->list.push_back(DecoratorDeclaration{d0_view.type, d0_view.instancer, PropertyDictionary(), d0_view.paint_area}); - PropertyDictionary& props = decorator->list.back().properties; - - const auto& props0 = d0_view.properties.GetProperties(); - const auto& props1 = d1_view.properties.GetProperties(); - - for (const auto& pair0 : props0) - { - const PropertyId id = pair0.first; - const Property& prop0 = pair0.second; + if (!InterpolateEffectProperties(list.back().properties, d0, d1, alpha, element)) + return p_discrete; + } - auto it = props1.find(id); - if (it == props1.end()) - { - RMLUI_ERRORMSG("Incompatible decorator properties."); - return DiscreteInterpolation(); - } - const Property& prop1 = it->second; + return Property{DecoratorsPtr(std::move(decorator)), Unit::DECORATOR}; + } - Property p = InterpolateProperties(prop0, prop1, alpha, element, prop0.definition); - p.definition = prop0.definition; - props.SetProperty(id, p); - } - } + if (p0.unit == Unit::FILTER && p1.unit == Unit::FILTER) + { + auto GetEffectDeclarationView = [](const Vector& declarations, size_t i) -> EffectDeclarationView { + if (i >= declarations.size()) + return EffectDeclarationView(); + return EffectDeclarationView(declarations[i]); + }; - // Append any trailing decorators from the largest list and interpolate against the default values of its type. - for (size_t i = small.size(); i < big.size(); i++) + auto& ptr0 = p0.value.GetReference(); + auto& ptr1 = p1.value.GetReference(); + if (!ptr0 || !ptr1) { - DecoratorDeclarationView dbig_view{GetDecoratorDeclarationView(big[i])}; + RMLUI_ERRORMSG("Invalid filter pointer, were the filter keys properly prepared?"); + return p_discrete; + } - if (!dbig_view.instancer) - return DiscreteInterpolation(); + // Build the new, interpolated filter list. + const bool p0_bigger = ptr0->list.size() > ptr1->list.size(); + auto& big_list = (p0_bigger ? ptr0->list : ptr1->list); + auto filter = MakeUnique(); + auto& list = filter->list; + list.reserve(big_list.size()); - decorator->list.push_back(DecoratorDeclaration{dbig_view.type, dbig_view.instancer, PropertyDictionary(), dbig_view.paint_area}); - DecoratorDeclaration& d_new = decorator->list.back(); + for (size_t i = 0; i < big_list.size(); i++) + { + EffectDeclarationView d0 = GetEffectDeclarationView(ptr0->list, i); + EffectDeclarationView d1 = GetEffectDeclarationView(ptr1->list, i); - const PropertySpecification& specification = d_new.instancer->GetPropertySpecification(); + const EffectDeclarationView& declaration = (p0_bigger ? d0 : d1); + list.push_back(FilterDeclaration{*declaration.type, static_cast(declaration.instancer), PropertyDictionary()}); - const PropertyMap& props_big = dbig_view.properties.GetProperties(); - for (const auto& pair_big : props_big) - { - const PropertyId id = pair_big.first; - const PropertyDefinition* underlying_definition = specification.GetProperty(id); - if (!underlying_definition) - return DiscreteInterpolation(); - - const Property& p_big = pair_big.second; - const Property& p_small = *underlying_definition->GetDefaultValue(); - const Property& p_interp0 = (p0_smaller ? p_small : p_big); - const Property& p_interp1 = (p0_smaller ? p_big : p_small); - - Property p = InterpolateProperties(p_interp0, p_interp1, alpha, element, p_big.definition); - p.definition = p_big.definition; - d_new.properties.SetProperty(id, p); - } + if (!InterpolateEffectProperties(list.back().properties, d0, d1, alpha, element)) + return p_discrete; } - return Property{DecoratorsPtr(std::move(decorator)), Unit::DECORATOR}; + return Property{FiltersPtr(std::move(filter)), Unit::FILTER}; } // Fall back to discrete interpolation for incompatible units. - return alpha < 0.5f ? p0 : p1; + return p_discrete; } enum class PrepareTransformResult { Unchanged = 0, ChangedT0 = 1, ChangedT1 = 2, ChangedT0andT1 = 3, Invalid = 4 }; @@ -530,6 +587,14 @@ static void PrepareDecorator(AnimationKey& key) if (!property.value.GetReference()) property.value = MakeShared(); } +static void PrepareFilter(AnimationKey& key) +{ + Property& property = key.property; + RMLUI_ASSERT(property.value.GetType() == Variant::FILTERSPTR); + + if (!property.value.GetReference()) + property.value = MakeShared(); +} ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigin origin, const Property& current_value, Element& element, double start_world_time, float duration, int num_iterations, bool alternate_direction) : @@ -547,7 +612,8 @@ ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigi bool ElementAnimation::InternalAddKey(float time, const Property& in_property, Element& element, Tween tween) { - const Units valid_units = (Unit::NUMBER_LENGTH_PERCENT | Unit::ANGLE | Unit::COLOUR | Unit::TRANSFORM | Unit::KEYWORD | Unit::DECORATOR); + const Units valid_units = + (Unit::NUMBER_LENGTH_PERCENT | Unit::ANGLE | Unit::COLOUR | Unit::TRANSFORM | Unit::KEYWORD | Unit::DECORATOR | Unit::FILTER); if (!Any(in_property.unit & valid_units)) { @@ -556,16 +622,21 @@ bool ElementAnimation::InternalAddKey(float time, const Property& in_property, E } keys.emplace_back(time, in_property, tween); + Property& property = keys.back().property; bool result = true; - if (keys.back().property.unit == Unit::TRANSFORM) + if (property.unit == Unit::TRANSFORM) { result = PrepareTransforms(keys, element, (int)keys.size() - 1); } - else if (keys.back().property.unit == Unit::DECORATOR) + else if (property.unit == Unit::DECORATOR) { PrepareDecorator(keys.back()); } + else if (property.unit == Unit::FILTER) + { + PrepareFilter(keys.back()); + } if (!result) { diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index 45a963caa..eda35c408 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -238,3 +238,125 @@ TEST_CASE("animation.decorator") TestsShell::ShutdownShell(); } + +static const String document_filter_rml = R"( + + + Test + + + + + +
+ + +)"; + +TEST_CASE("animation.filter") +{ + struct Test { + String from; + String to; + String expected_25p; // expected interpolated value at 25% progression + }; + + Vector tests{ + { + "blur( 0px)", + "blur(40px)", + "blur(10px)", + }, + { + "blur(10px)", + "blur(25dp)", // assumes dp-ratio == 2 + "blur(20px)", + }, + { + "blur(40px)", + "none", + "blur(30px)", + }, + { + "none", + "blur(40px)", + "blur(10px)", + }, + { + "drop-shadow(#000 30px 20px 0px)", + "drop-shadow(#f00 30px 20px 4px)", // colors interpolated in linear space + "drop-shadow(rgba(127,0,0,255) 30px 20px 1px)", + }, + { + "opacity(0) brightness(2)", + "none", + "opacity(0.25) brightness(1.75)", + }, + { + "opacity(0) brightness(0)", + "opacity(0.5)", + "opacity(0.125) brightness(0.25)", + }, + { + "opacity(0.5)", + "opacity(0) brightness(0)", + "opacity(0.375) brightness(0.75)", + }, + { + "opacity(0) brightness(0)", + "brightness(1) opacity(0.5)", // discrete interpolation due to non-matching types + "opacity(0) brightness(0)", + }, + { + "none", // Test initial values of various filters. + "brightness(2.00) contrast(2.00) grayscale(1.00) hue-rotate(4rad) invert(1.00) opacity(0.00) sepia(1.00) saturate(2.00)", + "brightness(1.25) contrast(1.25) grayscale(0.25) hue-rotate(1rad) invert(0.25) opacity(0.75) sepia(0.25) saturate(1.25)", + }, + }; + + TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface(); + Context* context = TestsShell::GetContext(); + context->SetDensityIndependentPixelRatio(2.0f); + + for (const Test& test : tests) + { + const double t_final = 0.1; + + system_interface->SetTime(0.0); + String document_rml = Rml::CreateString(document_filter_rml.size() + 512, document_filter_rml.c_str(), test.from.c_str(), test.to.c_str()); + + ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); + Element* element = document->GetChild(0); + + document->Show(); + + system_interface->SetTime(0.25 * t_final); + TestsShell::RenderLoop(); + + CHECK_MESSAGE(element->GetProperty("filter") == test.expected_25p, "from: ", test.from, ", to: ", test.to); + + document->Close(); + } + + system_interface->SetTime(0.0); + + TestsShell::ShutdownShell(); +} From ecaaefa9dc1793f919ab9d18e9d35d3a0d032ce6 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Thu, 15 Jun 2023 12:13:03 +0200 Subject: [PATCH 40/80] Add 'mask-image' and 'backdrop-filter' animation tests --- Tests/Source/UnitTests/Animation.cpp | 64 +++++++++++++++------------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index eda35c408..ce6b8aba7 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -52,8 +52,8 @@ static const String document_decorator_rml = R"( @decorator to_rule: horizontal-gradient{ %s } @keyframes mix { - from { decorator: %s; } - to { decorator: %s; } + from { %s: %s; } + to { %s: %s; } } div { background: #333; @@ -211,27 +211,29 @@ TEST_CASE("animation.decorator") TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface(); Context* context = TestsShell::GetContext(); - context->SetDensityIndependentPixelRatio(2.0f); - for (const Test& test : tests) + for (const char* property_str : {"decorator", "mask-image"}) { - const double t_final = 0.1; + for (const Test& test : tests) + { + const double t_final = 0.1; - system_interface->SetTime(0.0); - String document_rml = Rml::CreateString(document_decorator_rml.size() + 512, document_decorator_rml.c_str(), test.from_rule.c_str(), - test.to_rule.c_str(), test.from.c_str(), test.to.c_str()); + system_interface->SetTime(0.0); + String document_rml = Rml::CreateString(document_decorator_rml.size() + 512, document_decorator_rml.c_str(), test.from_rule.c_str(), + test.to_rule.c_str(), property_str, test.from.c_str(), property_str, test.to.c_str()); - ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); - Element* element = document->GetChild(0); + ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); + Element* element = document->GetChild(0); - document->Show(); - TestsShell::RenderLoop(); + document->Show(); + TestsShell::RenderLoop(); - system_interface->SetTime(0.25 * t_final); - TestsShell::RenderLoop(); - CHECK_MESSAGE(element->GetProperty("decorator") == test.expected_25p, "from: ", test.from, ", to: ", test.to); + system_interface->SetTime(0.25 * t_final); + TestsShell::RenderLoop(); + CHECK_MESSAGE(element->GetProperty(property_str) == test.expected_25p, property_str, " from: ", test.from, ", to: ", test.to); - document->Close(); + document->Close(); + } } system_interface->SetTime(0.0); @@ -252,8 +254,8 @@ static const String document_filter_rml = R"( bottom: 0; } @keyframes mix { - from { filter: %s; } - to { filter: %s; } + from { %s: %s; } + to { %s: %s; } } div { background: #333; @@ -336,24 +338,28 @@ TEST_CASE("animation.filter") Context* context = TestsShell::GetContext(); context->SetDensityIndependentPixelRatio(2.0f); - for (const Test& test : tests) + for (const char* property_str : {"filter", "backdrop-filter"}) { - const double t_final = 0.1; + for (const Test& test : tests) + { + const double t_final = 0.1; - system_interface->SetTime(0.0); - String document_rml = Rml::CreateString(document_filter_rml.size() + 512, document_filter_rml.c_str(), test.from.c_str(), test.to.c_str()); + system_interface->SetTime(0.0); + String document_rml = Rml::CreateString(document_filter_rml.size() + 512, document_filter_rml.c_str(), property_str, test.from.c_str(), + property_str, test.to.c_str()); - ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); - Element* element = document->GetChild(0); + ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); + Element* element = document->GetChild(0); - document->Show(); + document->Show(); - system_interface->SetTime(0.25 * t_final); - TestsShell::RenderLoop(); + system_interface->SetTime(0.25 * t_final); + TestsShell::RenderLoop(); - CHECK_MESSAGE(element->GetProperty("filter") == test.expected_25p, "from: ", test.from, ", to: ", test.to); + CHECK_MESSAGE(element->GetProperty(property_str) == test.expected_25p, property_str, " from: ", test.from, ", to: ", test.to); - document->Close(); + document->Close(); + } } system_interface->SetTime(0.0); From aec3f2ac736c56437fb5a9f0cdb20c8077b53a72 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Fri, 30 Jun 2023 22:42:09 +0200 Subject: [PATCH 41/80] Fix backdrop-filter clipping to border background, add visual test --- Source/Core/ElementDecoration.cpp | 6 +-- Source/Core/ElementUtilities.cpp | 4 +- .../filter_backdrop_filter_box_shadow.rml | 44 +++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 383b65e5b..5da6172bd 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -229,7 +229,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) RenderManager& render_manager = context->GetRenderManager(); Rectanglei initial_scissor_region = render_manager.GetState().scissor_region; - auto ApplyClippingRegion = [this, &render_manager, initial_scissor_region](PropertyId filter_id) { + auto ApplyClippingRegion = [this, &render_manager](PropertyId filter_id) { RMLUI_ASSERT(filter_id == PropertyId::Filter || filter_id == PropertyId::BackdropFilter); const bool force_clip_to_self_border_box = (filter_id == PropertyId::BackdropFilter); @@ -237,7 +237,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) // Find the region being affected by the active filters and apply it as a scissor. Rectanglef filter_region = Rectanglef::MakeInvalid(); - ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Auto); + ElementUtilities::GetBoundingBox(filter_region, element, force_clip_to_self_border_box ? BoxArea::Border : BoxArea::Auto); // The filter property may draw outside our normal clipping region due to ink overflow. if (filter_id == PropertyId::Filter) @@ -249,7 +249,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) Math::ExpandToPixelGrid(filter_region); Rectanglei scissor_region = Rectanglei(filter_region); - scissor_region.IntersectIfValid(initial_scissor_region); + scissor_region.IntersectIfValid(render_manager.GetState().scissor_region); render_manager.SetScissorRegion(scissor_region); }; diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 228827953..1fe5f1483 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -290,8 +290,8 @@ bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* elemen Vector2f shadow_extent_top_left, shadow_extent_bottom_right; if (box_area == BoxArea::Auto) { - // 'Auto' acts like border box extended to encompass any ink overflow, which includes the element's box-shadow. - // TODO: Include ink overflow due to filters (e.g. blur or drop-shadow). + // 'Auto' acts like border box extended to encompass any ink overflow, including the element's box-shadow. + // Note: Does not currently include ink overflow due to filters, as that is handled manually in ElementDecoration. box_area = BoxArea::Border; if (const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow)) diff --git a/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml b/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml new file mode 100644 index 000000000..dd3fcc331 --- /dev/null +++ b/Tests/Data/VisualTests/filter_backdrop_filter_box_shadow.rml @@ -0,0 +1,44 @@ + + + Filter, backdrop filter, and box shadow combinations + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ + + +
From ae8f2a4a5e0e6b7c2f1e1c7eea1aab514776c99f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 2 Jul 2023 22:25:13 +0200 Subject: [PATCH 42/80] Refactor: Move the box shadow generation into a separate file --- CMake/FileList.cmake | 2 + Source/Core/ElementBackgroundBorder.cpp | 211 +-------------------- Source/Core/ElementBackgroundBorder.h | 1 - Source/Core/GeometryBoxShadow.cpp | 241 ++++++++++++++++++++++++ Source/Core/GeometryBoxShadow.h | 55 ++++++ 5 files changed, 307 insertions(+), 203 deletions(-) create mode 100644 Source/Core/GeometryBoxShadow.cpp create mode 100644 Source/Core/GeometryBoxShadow.h diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 49b0d8844..cbb4bed61 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -56,6 +56,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h + ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.h @@ -324,6 +325,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FontEngineInterface.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.cpp ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.cpp diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index ba4716929..c501402b0 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -33,6 +33,7 @@ #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "GeometryBoxShadow.h" namespace Rml { @@ -147,214 +148,20 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) if (has_box_shadow) { + Geometry& background_border_geometry = *GetGeometry(BackgroundType::BackgroundBorder); + const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); BoxShadowList shadow_list = p_box_shadow->value.Get(); - GenerateBoxShadow(element, std::move(shadow_list), border_radius, computed.opacity()); - } -} - -void ElementBackgroundBorder::GenerateBoxShadow(Element* element, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) -{ - // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. - Vector2f element_offset_in_texture; - Vector2i texture_dimensions; - - // Resolve all lengths to px units. - for (BoxShadow& shadow : shadow_list) - { - shadow.blur_radius = NumericValue(element->ResolveLength(shadow.blur_radius), Unit::PX); - shadow.spread_distance = NumericValue(element->ResolveLength(shadow.spread_distance), Unit::PX); - shadow.offset_x = NumericValue(element->ResolveLength(shadow.offset_x), Unit::PX); - shadow.offset_y = NumericValue(element->ResolveLength(shadow.offset_y), Unit::PX); - } - - { - Vector2f extend_min; - Vector2f extend_max; - - // Extend the render-texture to encompass box-shadow blur and spread. - for (const BoxShadow& shadow : shadow_list) - { - if (!shadow.inset) - { - const float extend = 1.5f * shadow.blur_radius.number + shadow.spread_distance.number; - const Vector2f offset = {shadow.offset_x.number, shadow.offset_y.number}; - extend_min = Math::Min(extend_min, offset - Vector2f(extend)); - extend_max = Math::Max(extend_max, offset + Vector2f(extend)); - } - } - - Rectanglef texture_region; + // Generate the geometry for the box-shadow texture. + Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); + Geometry& shadow_geometry = shadow_background.geometry; + Texture& shadow_texture = shadow_background.texture; - // Extend the render-texture further to cover all the element's boxes. - for (int i = 0; i < element->GetNumBoxes(); i++) - { - Vector2f offset; - const Box& box = element->GetBox(i, offset); - texture_region.Join(Rectanglef::FromPositionSize(offset, box.GetSize(BoxArea::Border))); - } - - texture_region.ExtendTopLeft(-extend_min); - texture_region.ExtendBottomRight(extend_max); - Math::ExpandToPixelGrid(texture_region); - - element_offset_in_texture = -texture_region.TopLeft(); - texture_dimensions = Vector2i(texture_region.Size()); + GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, element, background_border_geometry, std::move(shadow_list), border_radius, + computed.opacity()); } - - Geometry& background_border_geometry = *GetGeometry(BackgroundType::BackgroundBorder); - - // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the - // device loses its GPU context and the client calls Rml::ReleaseTextures(). - auto p_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, shadow_list]( - RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, Vector2i& out_dimensions) -> bool { - Context* context = element->GetContext(); - if (!context) - { - RMLUI_ERROR; - return false; - } - - Geometry geometry_padding; // Render geometry for inner box-shadow. - Geometry geometry_padding_border; // Clipping mask for outer box-shadow. - - bool has_inner_shadow = false; - bool has_outer_shadow = false; - for (const BoxShadow& shadow : shadow_list) - { - if (shadow.inset) - has_inner_shadow = true; - else - has_outer_shadow = true; - } - - // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. - for (int i = 0; i < element->GetNumBoxes(); i++) - { - Vector2f offset; - const Box& box = element->GetBox(i, offset); - - if (has_inner_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding); - if (has_outer_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border); - } - - RenderManager& render_manager = context->GetRenderManager(); - const RenderState initial_render_state = render_manager.GetState(); - render_manager.ResetState(); - render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); - - render_interface->PushLayer(LayerFill::Clear); - - background_border_geometry.Render(element_offset_in_texture); - - for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) - { - const BoxShadow& shadow = shadow_list[shadow_index]; - const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number}; - const bool inset = shadow.inset; - const float spread_distance = shadow.spread_distance.number; - const float blur_radius = shadow.blur_radius.number; - - Vector4f spread_radii = border_radius; - for (int i = 0; i < 4; i++) - { - float& radius = spread_radii[i]; - float spread_factor = (inset ? -1.f : 1.f); - if (radius < spread_distance) - { - const float ratio_minus_one = (radius / spread_distance) - 1.f; - spread_factor *= 1.f + ratio_minus_one * ratio_minus_one * ratio_minus_one; - } - radius = Math::Max(radius + spread_factor * spread_distance, 0.f); - } - - Geometry shadow_geometry; - - // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask. - for (int i = 0; i < element->GetNumBoxes(); i++) - { - Vector2f offset; - Box box = element->GetBox(i, offset); - const float signed_spread_distance = (inset ? -spread_distance : spread_distance); - offset -= Vector2f(signed_spread_distance); - - for (int j = 0; j < Box::num_edges; j++) - { - BoxEdge edge = (BoxEdge)j; - const float new_size = box.GetEdge(BoxArea::Padding, edge) + signed_spread_distance; - box.SetEdge(BoxArea::Padding, edge, new_size); - } - - GeometryUtilities::GenerateBackground(&shadow_geometry, box, offset, spread_radii, shadow.color, - inset ? BoxArea::Padding : BoxArea::Border); - } - - CompiledFilterHandle blur = {}; - if (blur_radius > 0.5f) - { - blur = render_interface->CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); - if (blur) - render_interface->PushLayer(LayerFill::Clear); - } - - if (inset) - { - render_manager.SetClipMask(ClipMaskOperation::SetInverse, &shadow_geometry, shadow_offset + element_offset_in_texture); - - for (Rml::Vertex& vertex : geometry_padding.GetVertices()) - vertex.colour = shadow.color; - - geometry_padding.Release(); - geometry_padding.Render(element_offset_in_texture); - - render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture); - } - else - { - render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture); - shadow_geometry.Render(shadow_offset + element_offset_in_texture); - } - - if (blur) - { - render_interface->PopLayer(BlendMode::Blend, {blur}); - render_interface->ReleaseCompiledFilter(blur); - } - } - - TextureHandle shadow_texture = render_interface->SaveLayerAsTexture(texture_dimensions); - if (!shadow_texture) - return false; - - render_interface->PopLayer(BlendMode::Discard, {}); - - render_manager.SetState(initial_render_state); - - out_dimensions = texture_dimensions; - out_handle = shadow_texture; - - return true; - }; - - // Generate the geometry for the box-shadow texture. - Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); - Geometry& shadow_geometry = shadow_background.geometry; - Texture& shadow_texture = shadow_background.texture; - RMLUI_ASSERT(!shadow_geometry); - - Vector& vertices = shadow_geometry.GetVertices(); - Vector& indices = shadow_geometry.GetIndices(); - vertices.resize(4); - indices.resize(6); - const byte alpha = byte(opacity * 255.f); - GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha)); - - shadow_texture.Set("box-shadow", p_callback); - shadow_geometry.SetTexture(&shadow_texture); } } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index 9b571cf0a..133e2bc18 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -57,7 +57,6 @@ class ElementBackgroundBorder { Background& GetOrCreateBackground(BackgroundType type); void GenerateGeometry(Element* element); - void GenerateBoxShadow(Element* element, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity); bool background_dirty = false; bool border_dirty = false; diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp new file mode 100644 index 000000000..0ece51213 --- /dev/null +++ b/Source/Core/GeometryBoxShadow.cpp @@ -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. + * + */ + +#include "GeometryBoxShadow.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Texture.h" + +namespace Rml { + +void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_shadow_texture, Element* element, Geometry& background_border_geometry, + BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) +{ + // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. + Vector2f element_offset_in_texture; + Vector2i texture_dimensions; + + // Resolve all lengths to px units. + for (BoxShadow& shadow : shadow_list) + { + shadow.blur_radius = NumericValue(element->ResolveLength(shadow.blur_radius), Unit::PX); + shadow.spread_distance = NumericValue(element->ResolveLength(shadow.spread_distance), Unit::PX); + shadow.offset_x = NumericValue(element->ResolveLength(shadow.offset_x), Unit::PX); + shadow.offset_y = NumericValue(element->ResolveLength(shadow.offset_y), Unit::PX); + } + + { + Vector2f extend_min; + Vector2f extend_max; + + // Extend the render-texture to encompass box-shadow blur and spread. + for (const BoxShadow& shadow : shadow_list) + { + if (!shadow.inset) + { + const float extend = 1.5f * shadow.blur_radius.number + shadow.spread_distance.number; + const Vector2f offset = {shadow.offset_x.number, shadow.offset_y.number}; + extend_min = Math::Min(extend_min, offset - Vector2f(extend)); + extend_max = Math::Max(extend_max, offset + Vector2f(extend)); + } + } + + Rectanglef texture_region; + + // Extend the render-texture further to cover all the element's boxes. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + const Box& box = element->GetBox(i, offset); + texture_region.Join(Rectanglef::FromPositionSize(offset, box.GetSize(BoxArea::Border))); + } + + texture_region.ExtendTopLeft(-extend_min); + texture_region.ExtendBottomRight(extend_max); + Math::ExpandToPixelGrid(texture_region); + + element_offset_in_texture = -texture_region.TopLeft(); + texture_dimensions = Vector2i(texture_region.Size()); + } + + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the + // device loses its GPU context and the client calls Rml::ReleaseTextures(). + auto texture_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, + shadow_list = std::move(shadow_list)](RenderInterface* render_interface, const String& /*name*/, + TextureHandle& out_handle, Vector2i& out_dimensions) -> bool { + Context* context = element->GetContext(); + if (!context) + { + RMLUI_ERROR; + return false; + } + + Geometry geometry_padding; // Render geometry for inner box-shadow. + Geometry geometry_padding_border; // Clipping mask for outer box-shadow. + + bool has_inner_shadow = false; + bool has_outer_shadow = false; + for (const BoxShadow& shadow : shadow_list) + { + if (shadow.inset) + has_inner_shadow = true; + else + has_outer_shadow = true; + } + + // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + const Box& box = element->GetBox(i, offset); + + if (has_inner_shadow) + GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding); + if (has_outer_shadow) + GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border); + } + + RenderManager& render_manager = context->GetRenderManager(); + const RenderState initial_render_state = render_manager.GetState(); + render_manager.ResetState(); + render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); + + render_interface->PushLayer(LayerFill::Clear); + + background_border_geometry.Render(element_offset_in_texture); + + for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) + { + const BoxShadow& shadow = shadow_list[shadow_index]; + const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number}; + const bool inset = shadow.inset; + const float spread_distance = shadow.spread_distance.number; + const float blur_radius = shadow.blur_radius.number; + + Vector4f spread_radii = border_radius; + for (int i = 0; i < 4; i++) + { + float& radius = spread_radii[i]; + float spread_factor = (inset ? -1.f : 1.f); + if (radius < spread_distance) + { + const float ratio_minus_one = (radius / spread_distance) - 1.f; + spread_factor *= 1.f + ratio_minus_one * ratio_minus_one * ratio_minus_one; + } + radius = Math::Max(radius + spread_factor * spread_distance, 0.f); + } + + Geometry shadow_geometry; + + // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask. + for (int i = 0; i < element->GetNumBoxes(); i++) + { + Vector2f offset; + Box box = element->GetBox(i, offset); + const float signed_spread_distance = (inset ? -spread_distance : spread_distance); + offset -= Vector2f(signed_spread_distance); + + for (int j = 0; j < Box::num_edges; j++) + { + BoxEdge edge = (BoxEdge)j; + const float new_size = box.GetEdge(BoxArea::Padding, edge) + signed_spread_distance; + box.SetEdge(BoxArea::Padding, edge, new_size); + } + + GeometryUtilities::GenerateBackground(&shadow_geometry, box, offset, spread_radii, shadow.color, + inset ? BoxArea::Padding : BoxArea::Border); + } + + CompiledFilterHandle blur = {}; + if (blur_radius > 0.5f) + { + blur = render_interface->CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); + if (blur) + render_interface->PushLayer(LayerFill::Clear); + } + + if (inset) + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &shadow_geometry, shadow_offset + element_offset_in_texture); + + for (Rml::Vertex& vertex : geometry_padding.GetVertices()) + vertex.colour = shadow.color; + + geometry_padding.Release(); + geometry_padding.Render(element_offset_in_texture); + + render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture); + } + else + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture); + shadow_geometry.Render(shadow_offset + element_offset_in_texture); + } + + if (blur) + { + render_interface->PopLayer(BlendMode::Blend, {blur}); + render_interface->ReleaseCompiledFilter(blur); + } + } + + TextureHandle shadow_texture = render_interface->SaveLayerAsTexture(texture_dimensions); + if (!shadow_texture) + return false; + + render_interface->PopLayer(BlendMode::Discard, {}); + + render_manager.SetState(initial_render_state); + + out_dimensions = texture_dimensions; + out_handle = shadow_texture; + + return true; + }; + + RMLUI_ASSERT(!out_shadow_geometry); + + Vector& vertices = out_shadow_geometry.GetVertices(); + Vector& indices = out_shadow_geometry.GetIndices(); + vertices.resize(4); + indices.resize(6); + const byte alpha = byte(opacity * 255.f); + GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha)); + + out_shadow_texture.Set("box-shadow", texture_callback); + out_shadow_geometry.SetTexture(&out_shadow_texture); +} + +} // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h new file mode 100644 index 000000000..8204967ed --- /dev/null +++ b/Source/Core/GeometryBoxShadow.h @@ -0,0 +1,55 @@ +/* + * 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_CORE_GEOMETRYBOXSHADOW_H +#define RMLUI_CORE_GEOMETRYBOXSHADOW_H + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Geometry; +struct Texture; + +class GeometryBoxShadow { +public: + /// Generate the texture and geometry for a box shadow. + /// @param[out] out_shadow_geometry The target geometry. + /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[in] element The element to generate the shadow for. + /// @param[in] background_border_geometry The geometry of the background and border, assumed to already have been generated. Assumes pointer + /// stability during the lifetime of the shadow geometry. + /// @param[in] shadow_list The list of box-shadows to generate. + /// @param[in] border_radius The border radius of the element. + /// @param[in] opacity The opacity of the element. + static void Generate(Geometry& out_shadow_geometry, Texture& out_shadow_texture, Element* element, Geometry& background_border_geometry, + BoxShadowList shadow_list, Vector4f border_radius, float opacity); +}; + +} // namespace Rml +#endif From bdf560880a3c8dc2b9573098807103b63f79183b Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 2 Jul 2023 22:34:08 +0200 Subject: [PATCH 43/80] Use move semantics for texture callbacks --- Include/RmlUi/Core/Texture.h | 2 +- Source/Core/FontEngineDefault/FontFaceLayer.cpp | 2 +- Source/Core/Texture.cpp | 4 ++-- Source/Core/TextureResource.cpp | 4 ++-- Source/Core/TextureResource.h | 2 +- Source/Lottie/ElementLottie.cpp | 6 +++--- Source/SVG/ElementSVG.cpp | 5 +++-- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Include/RmlUi/Core/Texture.h b/Include/RmlUi/Core/Texture.h index 533874f4b..024b85428 100644 --- a/Include/RmlUi/Core/Texture.h +++ b/Include/RmlUi/Core/Texture.h @@ -63,7 +63,7 @@ struct RMLUICORE_API Texture { /// Set a callback function for generating the texture on first use. The texture is never added to the global cache. /// @param[in] name The name of the texture. /// @param[in] callback The callback function which generates the data of the texture, see TextureCallback. - void Set(const String& name, const TextureCallback& callback); + void Set(const String& name, TextureCallback&& callback); /// Returns the texture's source name. This is usually the name of the file the texture was loaded from. /// @return The name of the this texture's source. This will be the empty string if this texture is not loaded. diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Source/Core/FontEngineDefault/FontFaceLayer.cpp index 0c559e041..e7ce7697c 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.cpp +++ b/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -171,7 +171,7 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace }; Texture texture; - texture.Set("font-face-layer", texture_callback); + texture.Set("font-face-layer", std::move(texture_callback)); textures.push_back(texture); } } diff --git a/Source/Core/Texture.cpp b/Source/Core/Texture.cpp index 277a96986..99d5a03d6 100644 --- a/Source/Core/Texture.cpp +++ b/Source/Core/Texture.cpp @@ -37,10 +37,10 @@ void Texture::Set(const String& source, const String& source_path) resource = TextureDatabase::Fetch(source, source_path); } -void Texture::Set(const String& name, const TextureCallback& callback) +void Texture::Set(const String& name, TextureCallback&& callback) { resource = MakeShared(); - resource->Set(name, callback); + resource->Set(name, std::move(callback)); } const String& Texture::GetSource() const diff --git a/Source/Core/TextureResource.cpp b/Source/Core/TextureResource.cpp index be2399e23..e676bae44 100644 --- a/Source/Core/TextureResource.cpp +++ b/Source/Core/TextureResource.cpp @@ -48,11 +48,11 @@ void TextureResource::Set(const String& _source) source = _source; } -void TextureResource::Set(const String& name, const TextureCallback& callback) +void TextureResource::Set(const String& name, TextureCallback&& callback) { Reset(); source = name; - texture_callback = MakeUnique(callback); + texture_callback = MakeUnique(std::move(callback)); TextureDatabase::AddCallbackTexture(this); } diff --git a/Source/Core/TextureResource.h b/Source/Core/TextureResource.h index 4fd65ab09..8f4b15ebb 100644 --- a/Source/Core/TextureResource.h +++ b/Source/Core/TextureResource.h @@ -52,7 +52,7 @@ class TextureResource : public NonCopyMoveable { /// Clear any existing data and set a callback function for loading the data. /// Texture loading is delayed until the texture is accessed by a specific render interface. - void Set(const String& name, const TextureCallback& callback); + void Set(const String& name, TextureCallback&& callback); /// Returns the resource's underlying texture handle. TextureHandle GetHandle(); diff --git a/Source/Lottie/ElementLottie.cpp b/Source/Lottie/ElementLottie.cpp index 8f6c784df..97e4123cd 100644 --- a/Source/Lottie/ElementLottie.cpp +++ b/Source/Lottie/ElementLottie.cpp @@ -225,8 +225,8 @@ void ElementLottie::UpdateTexture() } // Callback for generating texture. - auto p_callback = [this, next_frame](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, - Vector2i& out_dimensions) -> bool { + auto texture_callback = [this, next_frame](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, + Vector2i& out_dimensions) -> bool { RMLUI_ASSERT(animation); const size_t bytes_per_line = 4 * render_dimensions.x; @@ -259,7 +259,7 @@ void ElementLottie::UpdateTexture() return true; }; - texture.Set("lottie", p_callback); + texture.Set("lottie", std::move(texture_callback)); geometry.SetTexture(&texture); prev_animation_frame = next_frame; texture_size_dirty = false; diff --git a/Source/SVG/ElementSVG.cpp b/Source/SVG/ElementSVG.cpp index 31149ab53..1c2a2e2f2 100644 --- a/Source/SVG/ElementSVG.cpp +++ b/Source/SVG/ElementSVG.cpp @@ -194,7 +194,8 @@ void ElementSVG::UpdateTexture() return; // Callback for generating texture. - auto p_callback = [this](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, Vector2i& out_dimensions) -> bool { + auto texture_callback = [this](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, + Vector2i& out_dimensions) -> bool { RMLUI_ASSERT(svg_document); lunasvg::Bitmap bitmap = svg_document->renderToBitmap(render_dimensions.x, render_dimensions.y); if (!bitmap.valid() || !bitmap.data()) @@ -205,7 +206,7 @@ void ElementSVG::UpdateTexture() return true; }; - texture.Set("svg", p_callback); + texture.Set("svg", std::move(texture_callback)); geometry.SetTexture(&texture); texture_dirty = false; } From 9a00f2cd6d811087d6aebd896958c8a8d7c32da6 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 15 Jul 2023 22:43:02 +0200 Subject: [PATCH 44/80] Unit tests: Make doctest string conversion functions inline --- Tests/Source/Common/TypesToString.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Source/Common/TypesToString.h b/Tests/Source/Common/TypesToString.h index c54fe8693..0c070a5d6 100644 --- a/Tests/Source/Common/TypesToString.h +++ b/Tests/Source/Common/TypesToString.h @@ -39,17 +39,17 @@ */ namespace Rml { -std::ostream& operator<<(std::ostream& os, const Colourb& value) +inline std::ostream& operator<<(std::ostream& os, const Colourb& value) { os << ToString(value); return os; } -std::ostream& operator<<(std::ostream& os, const Vector2f& value) +inline std::ostream& operator<<(std::ostream& os, const Vector2f& value) { os << ToString(value); return os; } -std::ostream& operator<<(std::ostream& os, const Vector2i& value) +inline std::ostream& operator<<(std::ostream& os, const Vector2i& value) { os << ToString(value); return os; From 59146e9fe33cffee3b57b9d2ae6eeffdd52bdafe Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 45/80] Update clang-format --- .clang-format | 1 + 1 file changed, 1 insertion(+) diff --git a/.clang-format b/.clang-format index a36d49d80..cde3a50bd 100644 --- a/.clang-format +++ b/.clang-format @@ -63,3 +63,4 @@ StatementMacros: - RMLUI_RTTI_DefineWithParent TabWidth: 4 UseTab: AlignWithSpaces +WhitespaceSensitiveMacros: ['RMLUI_STRINGIFY'] From 70bcdc7d3647cdac9df8630c4b6de316ff405469 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 46/80] Update SVG plugin for LunaSVG v2.3.2 and newer --- Source/SVG/ElementSVG.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Source/SVG/ElementSVG.cpp b/Source/SVG/ElementSVG.cpp index 1c2a2e2f2..372dedd13 100644 --- a/Source/SVG/ElementSVG.cpp +++ b/Source/SVG/ElementSVG.cpp @@ -198,6 +198,13 @@ void ElementSVG::UpdateTexture() Vector2i& out_dimensions) -> bool { RMLUI_ASSERT(svg_document); lunasvg::Bitmap bitmap = svg_document->renderToBitmap(render_dimensions.x, render_dimensions.y); + + // Swap red and blue channels, assuming LunaSVG v2.3.2 or newer, to convert to RmlUi's expected RGBA-ordering. + const size_t bitmap_size = bitmap.width() * bitmap.height(); + uint8_t* bitmap_data = bitmap.data(); + for (size_t i = 0; i < bitmap_size; i++) + std::swap(bitmap_data[i * 4], bitmap_data[i * 4 + 2]); + if (!bitmap.valid() || !bitmap.data()) return false; if (!render_interface->GenerateTexture(out_handle, reinterpret_cast(bitmap.data()), render_dimensions)) From a0646126853e464e8ecc9499534aae9f3b24373b Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 47/80] Avoid setting computed 'has_decorator' and similar when their pointer is empty --- Source/Core/ElementStyle.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index 0c0022261..e24197296 100644 --- a/Source/Core/ElementStyle.cpp +++ b/Source/Core/ElementStyle.cpp @@ -847,22 +847,22 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S break; case PropertyId::Decorator: - values.has_decorator(p->unit == Unit::DECORATOR); + values.has_decorator(p->unit == Unit::DECORATOR && p->value.GetType() == Variant::DECORATORSPTR && p->value.GetReference()); break; case PropertyId::MaskImage: - values.has_mask_image(p->unit == Unit::DECORATOR); + values.has_mask_image(p->unit == Unit::DECORATOR && p->value.GetType() == Variant::DECORATORSPTR && p->value.GetReference()); break; case PropertyId::FontEffect: - values.has_font_effect(p->unit == Unit::FONTEFFECT); + values.has_font_effect(p->unit == Unit::FONTEFFECT && p->value.GetType() == Variant::FONTEFFECTSPTR && p->value.GetReference()); break; case PropertyId::Filter: - values.has_filter(p->unit == Unit::FILTER); + values.has_filter(p->unit == Unit::FILTER && p->value.GetType() == Variant::FILTERSPTR && p->value.GetReference()); break; case PropertyId::BackdropFilter: - values.has_backdrop_filter(p->unit == Unit::FILTER); + values.has_backdrop_filter(p->unit == Unit::FILTER && p->value.GetType() == Variant::FILTERSPTR && p->value.GetReference()); break; case PropertyId::BoxShadow: - values.has_box_shadow(p->unit == Unit::BOXSHADOWLIST); + values.has_box_shadow(p->unit == Unit::BOXSHADOWLIST && p->value.GetType() == Variant::BOXSHADOWLIST && !p->value.GetReference().empty()); break; case PropertyId::FlexBasis: From 641f8b59c83a2d938aab1a99f703ce906b63911a Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 48/80] Visual tests: Add alpha blending tests for renderer --- .../renderer_alpha_blending_01.rml | 101 ++++++++++++++++++ .../renderer_alpha_blending_02.rml | 68 ++++++++++++ .../renderer_alpha_blending_03.rml | 68 ++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 Tests/Data/VisualTests/renderer_alpha_blending_01.rml create mode 100644 Tests/Data/VisualTests/renderer_alpha_blending_02.rml create mode 100644 Tests/Data/VisualTests/renderer_alpha_blending_03.rml diff --git a/Tests/Data/VisualTests/renderer_alpha_blending_01.rml b/Tests/Data/VisualTests/renderer_alpha_blending_01.rml new file mode 100644 index 000000000..95ae73e4d --- /dev/null +++ b/Tests/Data/VisualTests/renderer_alpha_blending_01.rml @@ -0,0 +1,101 @@ + + + Alpha blending 01 + + + + + + + +The following box should be filled by perfect white. +
+
+
+
+
+
+
+
+
+
+
+ +The following box should be filled by a single gray color. +
+
+
+
+
+
+
+
+
+
+ +The following box should be filled by perfect black. +
+
+
+
+
+
+
+
+
+
+ + diff --git a/Tests/Data/VisualTests/renderer_alpha_blending_02.rml b/Tests/Data/VisualTests/renderer_alpha_blending_02.rml new file mode 100644 index 000000000..af3676c1f --- /dev/null +++ b/Tests/Data/VisualTests/renderer_alpha_blending_02.rml @@ -0,0 +1,68 @@ + + + Alpha blending 02 + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ + diff --git a/Tests/Data/VisualTests/renderer_alpha_blending_03.rml b/Tests/Data/VisualTests/renderer_alpha_blending_03.rml new file mode 100644 index 000000000..f49ebf06e --- /dev/null +++ b/Tests/Data/VisualTests/renderer_alpha_blending_03.rml @@ -0,0 +1,68 @@ + + + Alpha blending 03 + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + From 7ae3bb3a2973c6710db2de8ab679cd1d003cf831 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 49/80] Use premultiplied alpha for textures submitted through the render interface --- Backends/RmlUi_Backend_SDL_GL3.cpp | 13 +++++- Backends/RmlUi_Renderer_GL3.cpp | 46 +++++-------------- Include/RmlUi/Core/RenderInterface.h | 2 +- .../Core/FontEngineDefault/FontFaceLayer.cpp | 4 +- .../FontEngineDefault/FreeTypeInterface.cpp | 24 +++------- Source/Core/TextureLayoutTexture.cpp | 7 +-- Source/Lottie/ElementLottie.cpp | 15 +++--- 7 files changed, 42 insertions(+), 69 deletions(-) diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index ae274eb29..5256d719c 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -68,8 +68,8 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { file_interface->Read(buffer.get(), buffer_size, file_handle); file_interface->Close(file_handle); - const size_t i = source.rfind('.'); - Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1)); + const size_t i_ext = source.rfind('.'); + Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1)); SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); @@ -95,6 +95,15 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { surface = new_surface; } + // RmlUi assumes premultiplied alpha, convert the color values accordingly. + byte* pixels = static_cast(surface->pixels); + for (int i = 0; i < surface->w * surface->h * 4; i += 4) + { + const byte alpha = pixels[i + 3]; + for (int j = 0; j < 3; ++j) + pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); + } + success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions); SDL_FreeSurface(surface); } diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 46a5a7c7b..81e738175 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -58,8 +58,6 @@ // Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. static constexpr int NUM_MSAA_SAMPLES = 2; -#define RMLUI_PREMULTIPLIED_ALPHA 1 - #define MAX_NUM_STOPS 16 #define BLUR_SIZE 7 #define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2) @@ -67,9 +65,7 @@ static constexpr int NUM_MSAA_SAMPLES = 2; #define RMLUI_STRINGIFY_IMPL(x) #x #define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x) -#define RMLUI_SHADER_HEADER \ - RMLUI_SHADER_HEADER_VERSION \ - "#define RMLUI_PREMULTIPLIED_ALPHA " RMLUI_STRINGIFY(RMLUI_PREMULTIPLIED_ALPHA) "\n#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n" +#define RMLUI_SHADER_HEADER RMLUI_SHADER_HEADER_VERSION "#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n" static const char* shader_vert_main = RMLUI_SHADER_HEADER R"( uniform vec2 _translate; @@ -86,7 +82,7 @@ void main() { fragTexCoord = inTexCoord0; fragColor = inColor0; -#if RMLUI_PREMULTIPLIED_ALPHA +#if 1 // TODO: Make all vertex colors already premultiplied, and remove this step. // Pre-multiply vertex colors with their alpha. fragColor.rgb = fragColor.rgb * fragColor.a; #endif @@ -864,13 +860,10 @@ void RenderInterface_GL3::BeginFrame() glDisable(GL_SCISSOR_TEST); glDisable(GL_CULL_FACE); + // Set blending function for premultiplied alpha. glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); -#if RMLUI_PREMULTIPLIED_ALPHA glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); -#else - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -#endif #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. @@ -910,7 +903,7 @@ void RenderInterface_GL3::EndFrame() glBindFramebuffer(GL_FRAMEBUFFER, 0); // 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 pre-multiplied alpha, we would need to perform a manual un-premultiplication step. + // 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); @@ -1245,11 +1238,16 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V 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] = image_src[read_index + 1]; + image_dest[write_index + 1] = image_src[read_index + 2]; image_dest[write_index + 2] = image_src[read_index]; if (color_mode == 4) - image_dest[write_index + 3] = image_src[read_index + 3]; + { + const byte alpha = image_src[read_index + 3]; + for (size_t j = 0; j < 3; j++) + image_dest[write_index + j] = byte((int(image_dest[write_index + j]) * int(alpha)) / 255); + image_dest[write_index + 3] = alpha; + } else image_dest[write_index + 3] = 255; @@ -1279,26 +1277,6 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co return false; } -#if RMLUI_PREMULTIPLIED_ALPHA - using Rml::byte; - Rml::UniquePtr source_premultiplied; - if (source) - { - const size_t num_bytes = source_dimensions.x * source_dimensions.y * 4; - source_premultiplied = Rml::UniquePtr(new byte[num_bytes]); - - for (size_t i = 0; i < num_bytes; i += 4) - { - const byte alpha = source[i + 3]; - for (size_t j = 0; j < 3; j++) - source_premultiplied[i + j] = byte((int(source[i + j]) * int(alpha)) / 255); - source_premultiplied[i + 3] = alpha; - } - - source = source_premultiplied.get(); - } -#endif - 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); diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 953a8989e..c5ae9abc3 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -125,7 +125,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source); /// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels. /// @param[out] texture_handle The handle to write the texture handle for the generated texture to. - /// @param[in] source The raw 8-bit texture data. Each pixel is made up of four 8-bit values, indicating red, green, blue and alpha in that order. + /// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order. /// @param[in] source_dimensions The dimensions, in pixels, of the source data. /// @return True if the texture generation succeeded and the handle is valid, false if not. virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Source/Core/FontEngineDefault/FontFaceLayer.cpp index e7ce7697c..5ee7f354f 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.cpp +++ b/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -220,8 +220,10 @@ bool FontFaceLayer::GenerateTexture(UniquePtr& texture_data, Vecto { case ColorFormat::A8: { + // We use premultiplied alpha, so copy the alpha into all four channels. for (int k = 0; k < num_bytes_per_line; ++k) - destination[k * 4 + 3] = source[k]; + for (int c = 0; c < 4; ++c) + destination[k * 4 + c] = source[k]; } break; case ColorFormat::RGBA8: diff --git a/Source/Core/FontEngineDefault/FreeTypeInterface.cpp b/Source/Core/FontEngineDefault/FreeTypeInterface.cpp index eceafa087..ce372f840 100644 --- a/Source/Core/FontEngineDefault/FreeTypeInterface.cpp +++ b/Source/Core/FontEngineDefault/FreeTypeInterface.cpp @@ -438,28 +438,18 @@ static bool BuildGlyph(FT_Face ft_face, const Character character, FontGlyphMap& if (glyph.color_format == ColorFormat::RGBA8) { - // Swizzle channels (BGRA -> RGBA) and un-premultiply alpha. + // Swizzle channels (BGRA -> RGBA) destination_bitmap = glyph.bitmap_owned_data.get(); for (int k = 0; k < glyph.bitmap_dimensions.x * glyph.bitmap_dimensions.y * num_bytes_per_pixel; k += 4) { - byte b = destination_bitmap[k]; - byte g = destination_bitmap[k + 1]; - byte r = destination_bitmap[k + 2]; + std::swap(destination_bitmap[k], destination_bitmap[k + 2]); +#ifdef RMLUI_DEBUG const byte alpha = destination_bitmap[k + 3]; - RMLUI_ASSERTMSG(b <= alpha && g <= alpha && r <= alpha, "Assumption of glyph data being premultiplied is broken."); - - if (alpha > 0 && alpha < 255) - { - b = byte((b * 255) / alpha); - g = byte((g * 255) / alpha); - r = byte((r * 255) / alpha); - } - - destination_bitmap[k] = r; - destination_bitmap[k + 1] = g; - destination_bitmap[k + 2] = b; - destination_bitmap[k + 3] = alpha; + for (int c = 0; c < 3; c++) + RMLUI_ASSERTMSG(destination_bitmap[k + c] <= alpha, + "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case."); +#endif } } } diff --git a/Source/Core/TextureLayoutTexture.cpp b/Source/Core/TextureLayoutTexture.cpp index 0bf81b396..7fdf65f27 100644 --- a/Source/Core/TextureLayoutTexture.cpp +++ b/Source/Core/TextureLayoutTexture.cpp @@ -139,11 +139,8 @@ UniquePtr TextureLayoutTexture::AllocateTexture() if (dimensions.x > 0 && dimensions.y > 0) { - texture_data.reset(new byte[dimensions.x * dimensions.y * 4]); - - // Set the texture to transparent white. - for (int i = 0; i < dimensions.x * dimensions.y; i++) - ((unsigned int*)(texture_data.get()))[i] = 0x00ffffff; + // Set the texture to transparent black. + texture_data.reset(new byte[dimensions.x * dimensions.y * 4]()); for (size_t i = 0; i < rows.size(); ++i) rows[i].Allocate(texture_data.get(), dimensions.x * 4); diff --git a/Source/Lottie/ElementLottie.cpp b/Source/Lottie/ElementLottie.cpp index 97e4123cd..de8360f6d 100644 --- a/Source/Lottie/ElementLottie.cpp +++ b/Source/Lottie/ElementLottie.cpp @@ -236,20 +236,17 @@ void ElementLottie::UpdateTexture() rlottie::Surface surface(reinterpret_cast(p_data), render_dimensions.x, render_dimensions.y, bytes_per_line); animation->renderSync(next_frame, surface); - // Swizzle the channel order from rlottie's BGRA to RmlUi's RGBA, and change pre-multiplied to post-multiplied alpha. + // Swizzle the channel order from rlottie's BGRA to RmlUi's RGBA. for (size_t i = 0; i < total_bytes; i += 4) { // Swap the RB order for correct color channels. std::swap(p_data[i], p_data[i + 2]); - // The RmlUi samples shell uses post-multiplied alpha, while rlottie serves pre-multiplied alpha. - // Here, we un-premultiply the colors. - const byte a = p_data[i + 3]; - if (a > 0 && a < 255) - { - for (size_t j = 0; j < 3; j++) - p_data[i + j] = (p_data[i + j] * 255) / a; - } +#ifdef RMLUI_DEBUG + const byte alpha = p_data[i + 3]; + for (int c = 0; c < 3; c++) + RMLUI_ASSERTMSG(p_data[i + c] <= alpha, "Glyph data is assumed to be encoded in premultiplied alpha, but that is not the case."); +#endif } if (!render_interface->GenerateTexture(out_handle, p_data, render_dimensions)) From c802aeb21be52517295fd3e8cdd7250a39c5ee7e Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 50/80] Use premultiplied alpha for vertex colors Introduce a new type for colors with premultiplied alpha. --- Backends/RmlUi_Renderer_GL3.cpp | 21 +++------ Include/RmlUi/Core/Colour.h | 30 +++++++++++- Include/RmlUi/Core/Colour.inl | 46 ++++++++++--------- Include/RmlUi/Core/DecorationTypes.h | 4 +- Include/RmlUi/Core/ElementText.h | 2 +- Include/RmlUi/Core/FontEffect.h | 8 +++- Include/RmlUi/Core/FontEngineInterface.h | 4 +- Include/RmlUi/Core/GeometryUtilities.h | 14 +++--- Include/RmlUi/Core/Math.h | 7 +-- Include/RmlUi/Core/Types.h | 5 +- Include/RmlUi/Core/Vertex.h | 4 +- .../basic/bitmapfont/src/FontEngineBitmap.cpp | 2 +- .../basic/bitmapfont/src/FontEngineBitmap.h | 2 +- .../src/FontEngineInterfaceBitmap.cpp | 2 +- .../src/FontEngineInterfaceBitmap.h | 4 +- Samples/invaders/src/DecoratorDefender.cpp | 2 +- Samples/invaders/src/DecoratorStarfield.cpp | 2 +- Samples/invaders/src/Defender.cpp | 2 +- Samples/invaders/src/Invader.cpp | 4 +- Samples/invaders/src/Shield.cpp | 2 +- Samples/invaders/src/Sprite.cpp | 2 +- Samples/invaders/src/Sprite.h | 4 +- Samples/luainvaders/src/DecoratorDefender.cpp | 2 +- .../luainvaders/src/DecoratorStarfield.cpp | 2 +- Samples/luainvaders/src/Defender.cpp | 2 +- Samples/luainvaders/src/Invader.cpp | 4 +- Samples/luainvaders/src/Shield.cpp | 2 +- Samples/luainvaders/src/Sprite.cpp | 2 +- Samples/luainvaders/src/Sprite.h | 4 +- Source/Core/ConvolutionFilter.cpp | 4 +- Source/Core/DecoratorGradient.cpp | 15 +++--- Source/Core/DecoratorNinePatch.cpp | 5 +- Source/Core/DecoratorShader.cpp | 2 +- Source/Core/DecoratorTiled.cpp | 6 +-- Source/Core/ElementBackgroundBorder.cpp | 43 +++++++++-------- Source/Core/ElementText.cpp | 3 +- Source/Core/Elements/ElementImage.cpp | 6 +-- Source/Core/Elements/ElementProgress.cpp | 9 +--- Source/Core/Elements/WidgetTextInput.cpp | 16 ++++--- Source/Core/Elements/WidgetTextInput.h | 2 +- Source/Core/FontEffect.cpp | 13 ++++++ Source/Core/FontEffectBlur.cpp | 2 + Source/Core/FontEffectGlow.cpp | 2 + Source/Core/FontEffectOutline.cpp | 2 + .../FontEngineInterfaceDefault.cpp | 2 +- .../FontEngineInterfaceDefault.h | 2 +- .../FontFaceHandleDefault.cpp | 17 +++---- .../FontEngineDefault/FontFaceHandleDefault.h | 4 +- .../Core/FontEngineDefault/FontFaceLayer.cpp | 4 +- Source/Core/FontEngineDefault/FontFaceLayer.h | 6 +-- Source/Core/FontEngineInterface.cpp | 2 +- Source/Core/GeometryBackgroundBorder.cpp | 26 ++++++----- Source/Core/GeometryBackgroundBorder.h | 22 +++++---- Source/Core/GeometryBoxShadow.cpp | 8 ++-- Source/Core/GeometryUtilities.cpp | 17 +++---- Source/Core/Math.cpp | 4 +- Source/Core/PropertyParserBoxShadow.cpp | 2 +- Source/Core/PropertyParserColorStopList.cpp | 2 +- Source/Debugger/Geometry.cpp | 12 +++-- Source/Lottie/ElementLottie.cpp | 5 +- Source/SVG/ElementSVG.cpp | 5 +- Tests/Source/UnitTests/Math.cpp | 8 ++-- Tests/Source/UnitTests/Properties.cpp | 20 ++++---- Tests/Source/VisualTests/CaptureScreen.cpp | 2 +- 64 files changed, 258 insertions(+), 233 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 81e738175..0d074b2fe 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -82,11 +82,6 @@ void main() { fragTexCoord = inTexCoord0; fragColor = inColor0; -#if 1 // TODO: Make all vertex colors already premultiplied, and remove this step. - // Pre-multiply vertex colors with their alpha. - fragColor.rgb = fragColor.rgb * fragColor.a; -#endif - vec2 translatedPos = inPosition + _translate; vec4 outPos = _transform * vec4(translatedPos, 0.0, 1.0); @@ -1311,13 +1306,11 @@ void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vecto RenderGeometry(vertices, 4, indices, 6, RenderInterface_GL3::TexturePostprocess, {}); } -static Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0) +static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) { Rml::Colourf result; - result.alpha = (1.f / 255.f) * float(c0.alpha); - result.red = (1.f / 255.f) * float(c0.red) * result.alpha; - result.green = (1.f / 255.f) * float(c0.green) * result.alpha; - result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha; + for (int i = 0; i < 4; i++) + result[i] = (1.f / 255.f) * float(c0[i]); return result; } @@ -1489,7 +1482,7 @@ struct CompiledFilter { // Drop shadow Rml::Vector2f offset; - Rml::Colourb color; + Rml::ColourbPremultiplied color; // ColorMatrix Rml::Matrix4f color_matrix; @@ -1513,7 +1506,7 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& { filter.type = FilterType::DropShadow; filter.sigma = Rml::Get(parameters, "sigma", 0.f); - filter.color = Rml::Get(parameters, "color", Rml::Colourb()); + filter.color = Rml::Get(parameters, "color", Rml::Colourb()).ToPremultiplied(); filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f)); } else if (name == "brightness") @@ -1642,7 +1635,7 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& 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] = ToPremultipliedAlpha(stop.color); + shader.stop_colors[i] = ConvertToColorf(stop.color); } }; @@ -1806,7 +1799,7 @@ void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_hand UseProgram(ProgramId::DropShadow); glDisable(GL_BLEND); - Rml::Colourf color = ToPremultipliedAlpha(filter.color); + Rml::Colourf color = ConvertToColorf(filter.color); glUniform4fv(GetUniformLocation(UniformId::Color), 1, &color[0]); const Gfx::FramebufferData& primary = render_layers.GetPostprocessPrimary(); diff --git a/Include/RmlUi/Core/Colour.h b/Include/RmlUi/Core/Colour.h index 9fabe43ff..7c7abbbf8 100644 --- a/Include/RmlUi/Core/Colour.h +++ b/Include/RmlUi/Core/Colour.h @@ -33,13 +33,15 @@ namespace Rml { +using byte = unsigned char; + /** Templated class for a four-component RGBA colour. @author Peter Curry */ -template +template class Colour { public: /// Initialising constructor. @@ -99,6 +101,32 @@ class Colour { /// @return A constant pointer to the first value. inline operator ColourType*() { return &red; } + // Convert color to premultiplied alpha. + template , + typename = typename std::enable_if_t::value>> + inline Colour ToPremultiplied() const + { + return Colour{ + ColourType((red * alpha) / 255), + ColourType((green * alpha) / 255), + ColourType((blue * alpha) / 255), + alpha, + }; + } + // Convert color to premultiplied alpha, after multiplying alpha by opacity. + template , + typename = typename std::enable_if_t::value>> + inline Colour ToPremultiplied(float opacity) const + { + const float new_alpha = alpha * opacity; + return Colour{ + ColourType(red * (new_alpha / 255.f)), + ColourType(green * (new_alpha / 255.f)), + ColourType(blue * (new_alpha / 255.f)), + ColourType(new_alpha), + }; + } + ColourType red, green, blue, alpha; #if defined(RMLUI_COLOUR_USER_EXTRA) diff --git a/Include/RmlUi/Core/Colour.inl b/Include/RmlUi/Core/Colour.inl index 6769669cb..712e91725 100644 --- a/Include/RmlUi/Core/Colour.inl +++ b/Include/RmlUi/Core/Colour.inl @@ -28,41 +28,43 @@ namespace Rml { -template -Colour::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha) +template +Colour::Colour(ColourType rgb, ColourType alpha) : red(rgb), green(rgb), blue(rgb), alpha(alpha) {} -template -Colour::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) : +template +Colour::Colour(ColourType red, ColourType green, ColourType blue, ColourType alpha) : red(red), green(green), blue(blue), alpha(alpha) {} -template -Colour Colour::operator+(const Colour rhs) const +template +Colour Colour::operator+( + const Colour rhs) const { - return Colour(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha); + return Colour(red + rhs.red, green + rhs.green, blue + rhs.blue, alpha + rhs.alpha); } -template -Colour Colour::operator-(const Colour rhs) const +template +Colour Colour::operator-( + const Colour rhs) const { - return Colour(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha); + return Colour(red - rhs.red, green - rhs.green, blue - rhs.blue, alpha - rhs.alpha); } -template -Colour Colour::operator*(float rhs) const +template +Colour Colour::operator*(float rhs) const { return Colour((ColourType)(red * rhs), (ColourType)(green * rhs), (ColourType)(blue * rhs), (ColourType)(alpha * rhs)); } -template -Colour Colour::operator/(float rhs) const +template +Colour Colour::operator/(float rhs) const { return Colour((ColourType)(red / rhs), (ColourType)(green / rhs), (ColourType)(blue / rhs), (ColourType)(alpha / rhs)); } -template -void Colour::operator+=(const Colour rhs) +template +void Colour::operator+=(const Colour rhs) { red += rhs.red; green += rhs.green; @@ -70,8 +72,8 @@ void Colour::operator+=(const Colour rhs) alpha += rhs.alpha; } -template -void Colour::operator-=(const Colour rhs) +template +void Colour::operator-=(const Colour rhs) { red -= rhs.red; green -= rhs.green; @@ -79,8 +81,8 @@ void Colour::operator-=(const Colour rhs) alpha -= rhs.alpha; } -template -void Colour::operator*=(float rhs) +template +void Colour::operator*=(float rhs) { red = (ColourType)(red * rhs); green = (ColourType)(green * rhs); @@ -88,8 +90,8 @@ void Colour::operator*=(float rhs) alpha = (ColourType)(alpha * rhs); } -template -void Colour::operator/=(float rhs) +template +void Colour::operator/=(float rhs) { *this *= (1.0f / rhs); } diff --git a/Include/RmlUi/Core/DecorationTypes.h b/Include/RmlUi/Core/DecorationTypes.h index eccb1ec90..3098db192 100644 --- a/Include/RmlUi/Core/DecorationTypes.h +++ b/Include/RmlUi/Core/DecorationTypes.h @@ -35,7 +35,7 @@ namespace Rml { struct ColorStop { - Colourb color; + ColourbPremultiplied color; NumericValue position; }; inline bool operator==(const ColorStop& a, const ColorStop& b) @@ -48,7 +48,7 @@ inline bool operator!=(const ColorStop& a, const ColorStop& b) } struct BoxShadow { - Colourb color; + ColourbPremultiplied color; NumericValue offset_x, offset_y; NumericValue blur_radius; NumericValue spread_distance; diff --git a/Include/RmlUi/Core/ElementText.h b/Include/RmlUi/Core/ElementText.h index 2fd97c257..07fc4a10d 100644 --- a/Include/RmlUi/Core/ElementText.h +++ b/Include/RmlUi/Core/ElementText.h @@ -114,7 +114,7 @@ class RMLUICORE_API ElementText final : public Element { // The decoration geometry we've generated for this string. UniquePtr decoration; - Colourb colour; + ColourbPremultiplied colour; float opacity; int font_handle_version; diff --git a/Include/RmlUi/Core/FontEffect.h b/Include/RmlUi/Core/FontEffect.h index 3d3aa947e..07adc8598 100644 --- a/Include/RmlUi/Core/FontEffect.h +++ b/Include/RmlUi/Core/FontEffect.h @@ -59,8 +59,8 @@ class RMLUICORE_API FontEffect { virtual bool GetGlyphMetrics(Vector2i& origin, Vector2i& dimensions, const FontGlyph& glyph) const; /// Requests the effect to generate the texture data for a single glyph's bitmap. The default implementation does nothing. - /// @param[out] destination_data The top-left corner of the glyph's 32-bit, RGBA-ordered, destination texture. Note that the glyph shares its - /// texture with other glyphs. + /// @param[out] destination_data The top-left corner of the glyph's 32-bit, destination texture, RGBA-ordered with pre-multiplied alpha. Note that + /// the glyph shares its texture with other glyphs. /// @param[in] destination_dimensions The dimensions of the glyph's area on its texture. /// @param[in] destination_stride The stride of the glyph's texture. /// @param[in] glyph The glyph the effect is being asked to generate an effect texture for. @@ -79,6 +79,10 @@ class RMLUICORE_API FontEffect { size_t GetFingerprint() const; void SetFingerprint(size_t fingerprint); +protected: + // Helper function to copy the alpha value to the colour channels for each pixel, assuming RGBA-ordered bytes, resulting in a grayscale texture. + static void FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride); + private: Layer layer; diff --git a/Include/RmlUi/Core/FontEngineInterface.h b/Include/RmlUi/Core/FontEngineInterface.h index bfefca97e..1bacbd4bd 100644 --- a/Include/RmlUi/Core/FontEngineInterface.h +++ b/Include/RmlUi/Core/FontEngineInterface.h @@ -101,13 +101,13 @@ class RMLUICORE_API FontEngineInterface { /// @param[in] font_effects_handle The handle to the prepared font effects for which the geometry should be generated. /// @param[in] string The string to render. /// @param[in] position The position of the baseline of the first character to render. - /// @param[in] colour The colour to render the text. Colour alpha is premultiplied with opacity. + /// @param[in] colour The colour to render the text. /// @param[in] opacity The opacity of the text, should be applied to font effects. /// @param[in] letter_spacing The letter spacing size in pixels. /// @param[out] geometry An array of geometries to generate the geometry into. /// @return The width, in pixels, of the string geometry. virtual int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position, - const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry); + ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry); /// Called by RmlUi to determine if the text geometry is required to be re-generated. Whenever the returned version /// is changed, all geometry belonging to the given face handle will be re-generated. diff --git a/Include/RmlUi/Core/GeometryUtilities.h b/Include/RmlUi/Core/GeometryUtilities.h index 691f89de0..7a776803f 100644 --- a/Include/RmlUi/Core/GeometryUtilities.h +++ b/Include/RmlUi/Core/GeometryUtilities.h @@ -54,7 +54,7 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] dimensions The dimensions of the quad to generate. /// @param[in] colour The colour to be assigned to each of the quad's vertices. /// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array. - static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset = 0); + static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, int index_offset = 0); /// Generates a quad from a position, size, colour and texture coordinates. /// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into. /// @param[out] indices An array of at least six indices that the generated index data will be written into. @@ -64,15 +64,15 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad. /// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad. /// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array. - static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord, - Vector2f bottom_right_texcoord, int index_offset = 0); + static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, + Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset = 0); /// Generates the geometry required to render a line. /// @param[out] geometry The geometry to append the newly created geometry into. /// @param[in] position The top-left position the line. /// @param[in] position The size of the line. /// @param[in] color The color to draw the line in. - static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color); + static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color); /// Generates the geometry for an element's background and border, with support for the border-radius property. /// @param[out] geometry The geometry to append the newly created vertices and indices into. @@ -82,8 +82,8 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background. /// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order. /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. - static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour, - const Colourb border_colours[4]); + static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, + ColourbPremultiplied background_colour, const ColourbPremultiplied border_colours[4]); /// Generates the background geometry for an element's area, with support for border-radius. /// @param[out] geometry The geometry to append the newly created vertices and indices into. @@ -93,7 +93,7 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] colour The colour applied to the background. /// @param[in] area Either the border, padding or content area to be filled. /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. - static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb colour, + static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied colour, BoxArea area = BoxArea::Padding); private: diff --git a/Include/RmlUi/Core/Math.h b/Include/RmlUi/Core/Math.h index af48e9bf0..856a55cad 100644 --- a/Include/RmlUi/Core/Math.h +++ b/Include/RmlUi/Core/Math.h @@ -35,9 +35,10 @@ namespace Rml { using byte = unsigned char; -template +template class Colour; -using Colourb = Colour; +using Colourb = Colour; +using ColourbPremultiplied = Colour; template class Vector2; using Vector2f = Vector2; @@ -92,7 +93,7 @@ namespace Math { RMLUICORE_API Vector2i Clamp(Vector2i value, Vector2i min, Vector2i max); /// Color interpolation. - RMLUICORE_API Colourb RoundedLerp(float t, Colourb c0, Colourb c1); + RMLUICORE_API ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied c0, ColourbPremultiplied c1); /// Evaluates if a number is, or close to, zero. /// @param[in] value The number to compare to zero. diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index 587f99222..0fda323d7 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -59,8 +59,9 @@ namespace Rml { // Color and linear algebra enum class ColorFormat { RGBA8, A8 }; -using Colourf = Colour; -using Colourb = Colour; +using Colourf = Colour; +using Colourb = Colour; +using ColourbPremultiplied = Colour; using Vector2i = Vector2; using Vector2f = Vector2; using Vector3i = Vector3; diff --git a/Include/RmlUi/Core/Vertex.h b/Include/RmlUi/Core/Vertex.h index a056fdd51..74daf8047 100644 --- a/Include/RmlUi/Core/Vertex.h +++ b/Include/RmlUi/Core/Vertex.h @@ -43,8 +43,8 @@ namespace Rml { struct RMLUICORE_API Vertex { /// Two-dimensional position of the vertex (usually in pixels). Vector2f position; - /// RGBA-ordered 8-bit / channel colour. - Colourb colour; + /// RGBA-ordered 8-bit/channel colour with premultiplied alpha. + ColourbPremultiplied colour; /// Texture coordinate for any associated texture. Vector2f tex_coord; }; diff --git a/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp b/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp index 3a7cb8a6a..bac784697 100644 --- a/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp +++ b/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp @@ -155,7 +155,7 @@ int FontFaceBitmap::GetStringWidth(const String& string, Character previous_char return width; } -int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, const Colourb& colour, GeometryList& geometry_list) +int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, ColourbPremultiplied colour, GeometryList& geometry_list) { int width = 0; diff --git a/Samples/basic/bitmapfont/src/FontEngineBitmap.h b/Samples/basic/bitmapfont/src/FontEngineBitmap.h index 12591746d..f02cb87d1 100644 --- a/Samples/basic/bitmapfont/src/FontEngineBitmap.h +++ b/Samples/basic/bitmapfont/src/FontEngineBitmap.h @@ -65,7 +65,7 @@ class FontFaceBitmap { int GetStringWidth(const String& string, Character prior_character); // Generate the string geometry, returning its width. - int GenerateString(const String& string, const Vector2f& position, const Colourb& colour, GeometryList& geometry); + int GenerateString(const String& string, const Vector2f& position, ColourbPremultiplied colour, GeometryList& geometry); const FontMetrics& GetMetrics() const { return metrics; } diff --git a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp index 0e7a8d535..907497dc8 100644 --- a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp +++ b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp @@ -81,7 +81,7 @@ int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, const Strin } int FontEngineInterfaceBitmap::GenerateString(FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, const String& string, - const Vector2f& position, const Colourb& colour, float /*opacity*/, float /*letter_spacing*/, GeometryList& geometry) + const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/, float /*letter_spacing*/, GeometryList& geometry) { auto handle_bitmap = reinterpret_cast(handle); return handle_bitmap->GenerateString(string, position, colour, geometry); diff --git a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h index d06fa9ee3..b1ca0079a 100644 --- a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h +++ b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h @@ -38,7 +38,7 @@ using Rml::FontFaceHandle; using Rml::byte; using Rml::Character; -using Rml::Colourb; +using Rml::ColourbPremultiplied; using Rml::String; using Rml::Texture; using Rml::Vector2f; @@ -77,7 +77,7 @@ class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { /// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text. int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position, - const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry) override; + ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) override; /// Called by RmlUi to determine if the text geometry is required to be re-generated.eometry. int GetVersion(FontFaceHandle handle) override; diff --git a/Samples/invaders/src/DecoratorDefender.cpp b/Samples/invaders/src/DecoratorDefender.cpp index 686164d5d..833345ebc 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -65,7 +65,7 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface()) { Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); - Rml::Colourb color = element->GetProperty("color"); + Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); Rml::Vertex vertices[4]; int indices[6]; diff --git a/Samples/invaders/src/DecoratorStarfield.cpp b/Samples/invaders/src/DecoratorStarfield.cpp index 3805ff3f7..1202101e9 100644 --- a/Samples/invaders/src/DecoratorStarfield.cpp +++ b/Samples/invaders/src/DecoratorStarfield.cpp @@ -117,7 +117,7 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData for (size_t i = 0; i < star_field->star_layers.size(); i++) { - Rml::Colourb color = star_field->star_layers[i].colour; + Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied(); for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++) { diff --git a/Samples/invaders/src/Defender.cpp b/Samples/invaders/src/Defender.cpp index b75a9512a..0a4f2ea50 100644 --- a/Samples/invaders/src/Defender.cpp +++ b/Samples/invaders/src/Defender.cpp @@ -95,7 +95,7 @@ void Defender::Update(double t) void Defender::Render(float dp_ratio, Rml::TextureHandle texture) { - Rml::Colourb color = GameDetails::GetDefenderColour(); + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); // Render our sprite if rendering is enabled if (render) diff --git a/Samples/invaders/src/Invader.cpp b/Samples/invaders/src/Invader.cpp index ceae8f90c..9bf248d87 100644 --- a/Samples/invaders/src/Invader.cpp +++ b/Samples/invaders/src/Invader.cpp @@ -173,10 +173,10 @@ void Invader::UpdateAnimation() void Invader::Render(float dp_ratio, Rml::TextureHandle texture) { - Rml::Colourb color(255); + Rml::ColourbPremultiplied color(255); if (type == MOTHERSHIP) - color = MOTHERSHIP_COLOUR; + color = MOTHERSHIP_COLOUR.ToPremultiplied(); int sprite_index = GetSpriteIndex(); int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); diff --git a/Samples/invaders/src/Shield.cpp b/Samples/invaders/src/Shield.cpp index 741e2637e..9c9c22130 100644 --- a/Samples/invaders/src/Shield.cpp +++ b/Samples/invaders/src/Shield.cpp @@ -123,7 +123,7 @@ void Shield::Render(float dp_ratio) const Rml::Vector2f scaled_position = (dp_ratio * position).Round(); const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio); - Rml::Colourb color = GameDetails::GetDefenderColour(); + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); ColoredPointList points; points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS); diff --git a/Samples/invaders/src/Sprite.cpp b/Samples/invaders/src/Sprite.cpp index e5545ae00..e9aaf6676 100644 --- a/Samples/invaders/src/Sprite.cpp +++ b/Samples/invaders/src/Sprite.cpp @@ -38,7 +38,7 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te Sprite::~Sprite() {} -void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture) +void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture) { Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); if (!render_interface) diff --git a/Samples/invaders/src/Sprite.h b/Samples/invaders/src/Sprite.h index 9b43d94cd..6eac2e9a6 100644 --- a/Samples/invaders/src/Sprite.h +++ b/Samples/invaders/src/Sprite.h @@ -40,7 +40,7 @@ class Sprite { Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); ~Sprite(); - void Render(Rml::Vector2f position, float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture); + void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture); Rml::Vector2f dimensions; Rml::Vector2f top_left_texcoord; @@ -48,7 +48,7 @@ class Sprite { }; struct ColoredPoint { - Rml::Colourb color; + Rml::ColourbPremultiplied color; Rml::Vector2f position; }; using ColoredPointList = Rml::Vector; diff --git a/Samples/luainvaders/src/DecoratorDefender.cpp b/Samples/luainvaders/src/DecoratorDefender.cpp index 686164d5d..833345ebc 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -65,7 +65,7 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface()) { Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); - Rml::Colourb color = element->GetProperty("color"); + Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); Rml::Vertex vertices[4]; int indices[6]; diff --git a/Samples/luainvaders/src/DecoratorStarfield.cpp b/Samples/luainvaders/src/DecoratorStarfield.cpp index 3805ff3f7..1202101e9 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.cpp +++ b/Samples/luainvaders/src/DecoratorStarfield.cpp @@ -117,7 +117,7 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData for (size_t i = 0; i < star_field->star_layers.size(); i++) { - Rml::Colourb color = star_field->star_layers[i].colour; + Rml::ColourbPremultiplied color = star_field->star_layers[i].colour.ToPremultiplied(); for (size_t j = 0; j < star_field->star_layers[i].stars.size(); j++) { diff --git a/Samples/luainvaders/src/Defender.cpp b/Samples/luainvaders/src/Defender.cpp index b75a9512a..0a4f2ea50 100644 --- a/Samples/luainvaders/src/Defender.cpp +++ b/Samples/luainvaders/src/Defender.cpp @@ -95,7 +95,7 @@ void Defender::Update(double t) void Defender::Render(float dp_ratio, Rml::TextureHandle texture) { - Rml::Colourb color = GameDetails::GetDefenderColour(); + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); // Render our sprite if rendering is enabled if (render) diff --git a/Samples/luainvaders/src/Invader.cpp b/Samples/luainvaders/src/Invader.cpp index ceae8f90c..9bf248d87 100644 --- a/Samples/luainvaders/src/Invader.cpp +++ b/Samples/luainvaders/src/Invader.cpp @@ -173,10 +173,10 @@ void Invader::UpdateAnimation() void Invader::Render(float dp_ratio, Rml::TextureHandle texture) { - Rml::Colourb color(255); + Rml::ColourbPremultiplied color(255); if (type == MOTHERSHIP) - color = MOTHERSHIP_COLOUR; + color = MOTHERSHIP_COLOUR.ToPremultiplied(); int sprite_index = GetSpriteIndex(); int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); diff --git a/Samples/luainvaders/src/Shield.cpp b/Samples/luainvaders/src/Shield.cpp index 741e2637e..9c9c22130 100644 --- a/Samples/luainvaders/src/Shield.cpp +++ b/Samples/luainvaders/src/Shield.cpp @@ -123,7 +123,7 @@ void Shield::Render(float dp_ratio) const Rml::Vector2f scaled_position = (dp_ratio * position).Round(); const int scaled_pixel = Rml::Math::RoundUpToInteger(PIXEL_SIZE * dp_ratio); - Rml::Colourb color = GameDetails::GetDefenderColour(); + Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); ColoredPointList points; points.reserve(NUM_SHIELD_CELLS * NUM_SHIELD_CELLS); diff --git a/Samples/luainvaders/src/Sprite.cpp b/Samples/luainvaders/src/Sprite.cpp index e5545ae00..e9aaf6676 100644 --- a/Samples/luainvaders/src/Sprite.cpp +++ b/Samples/luainvaders/src/Sprite.cpp @@ -38,7 +38,7 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te Sprite::~Sprite() {} -void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture) +void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture) { Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); if (!render_interface) diff --git a/Samples/luainvaders/src/Sprite.h b/Samples/luainvaders/src/Sprite.h index ae2c7f740..8ed401f42 100644 --- a/Samples/luainvaders/src/Sprite.h +++ b/Samples/luainvaders/src/Sprite.h @@ -40,7 +40,7 @@ class Sprite { Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); ~Sprite(); - void Render(Rml::Vector2f position, float dp_ratio, Rml::Colourb color, Rml::TextureHandle texture); + void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture); Rml::Vector2f dimensions; Rml::Vector2f top_left_texcoord; @@ -48,7 +48,7 @@ class Sprite { }; struct ColoredPoint { - Rml::Colourb color; + Rml::ColourbPremultiplied color; Rml::Vector2f position; }; using ColoredPointList = Rml::Vector; diff --git a/Source/Core/ConvolutionFilter.cpp b/Source/Core/ConvolutionFilter.cpp index 3b4695e69..c12ba44fd 100644 --- a/Source/Core/ConvolutionFilter.cpp +++ b/Source/Core/ConvolutionFilter.cpp @@ -109,11 +109,9 @@ void ConvolutionFilter::Run(byte* destination, const Vector2i destination_dimens opacity = Math::Min(255.f, opacity); - const int destination_index = x * destination_bytes_per_pixel + destination_alpha_offset; + const int destination_index = y * destination_stride + x * destination_bytes_per_pixel + destination_alpha_offset; destination[destination_index] = byte(opacity); } - - destination += destination_stride; } } diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index be25301c6..2a1bc2282 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -195,13 +195,10 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem const ComputedValues& computed = element->GetComputedValues(); const float opacity = computed.opacity(); - GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb(), paint_area); + GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), ColourbPremultiplied(), paint_area); - // Apply opacity - Colourb colour_start = start; - colour_start.alpha = (byte)(opacity * (float)colour_start.alpha); - Colourb colour_stop = stop; - colour_stop.alpha = (byte)(opacity * (float)colour_stop.alpha); + ColourbPremultiplied colour_start = start.ToPremultiplied(opacity); + ColourbPremultiplied colour_stop = stop.ToPremultiplied(opacity); const Vector2f offset = box.GetPosition(paint_area); const Vector2f size = box.GetSize(paint_area); @@ -324,7 +321,7 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), paint_area); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area); const Vector2f render_offset = box.GetPosition(paint_area); for (Vertex& vertex : geometry.GetVertices()) @@ -492,7 +489,7 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); const Vector2f render_offset = box.GetPosition(box_area); for (Vertex& vertex : geometry.GetVertices()) @@ -688,7 +685,7 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), box_area); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); const Vector2f render_offset = box.GetPosition(box_area); for (Vertex& vertex : geometry.GetVertices()) diff --git a/Source/Core/DecoratorNinePatch.cpp b/Source/Core/DecoratorNinePatch.cpp index 8a0dfdbd0..1aa263fe8 100644 --- a/Source/Core/DecoratorNinePatch.cpp +++ b/Source/Core/DecoratorNinePatch.cpp @@ -67,10 +67,7 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo const Vector2f surface_offset = element->GetBox().GetPosition(paint_area); const Vector2f surface_dimensions = element->GetBox().GetSize(paint_area).Round(); - const float opacity = computed.opacity(); - Colourb quad_colour = computed.image_color(); - - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); /* In the following, we operate on the four diagonal vertices in the grid, as they define the whole grid. */ diff --git a/Source/Core/DecoratorShader.cpp b/Source/Core/DecoratorShader.cpp index 3e36e0431..fad506796 100644 --- a/Source/Core/DecoratorShader.cpp +++ b/Source/Core/DecoratorShader.cpp @@ -67,7 +67,7 @@ DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxAr const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), Colourb(255, alpha), render_area); + GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area); const Vector2f offset = box.GetPosition(render_area); for (Vertex& vertex : geometry.GetVertices()) diff --git a/Source/Core/DecoratorTiled.cpp b/Source/Core/DecoratorTiled.cpp index 2c80c7391..d27ca13e9 100644 --- a/Source/Core/DecoratorTiled.cpp +++ b/Source/Core/DecoratorTiled.cpp @@ -104,11 +104,7 @@ void DecoratorTiled::Tile::GenerateGeometry(Vector& vertices, VectorGetBox(); const Vector4f border_radius = element->GetComputedValues().border_radius(); - GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, Colourb(255), clip_area); + GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, ColourbPremultiplied(255), clip_area); } return &geometry; @@ -112,29 +112,28 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) { const ComputedValues& computed = element->GetComputedValues(); - Colourb background_color = computed.background_color(); - Colourb border_colors[4] = { - computed.border_top_color(), - computed.border_right_color(), - computed.border_bottom_color(), - computed.border_left_color(), - }; - const Vector4f border_radius = computed.border_radius(); const bool has_box_shadow = computed.has_box_shadow(); + const float opacity = computed.opacity(); - if (!has_box_shadow) - { - // Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while - // opacity is applied to the entire box-shadow texture when that is rendered. - const float opacity = computed.opacity(); - if (opacity < 1.f) - { - background_color.alpha = (byte)(opacity * (float)background_color.alpha); - - for (int i = 0; i < 4; ++i) - border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha); - } - } + // Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while + // opacity is applied to the entire box-shadow texture when that is rendered. + bool apply_opacity = (!has_box_shadow && opacity < 1.f); + + auto ConvertColor = [=](Colourb color) { + if (apply_opacity) + return color.ToPremultiplied(opacity); + else + return color.ToPremultiplied(); + }; + + ColourbPremultiplied background_color = ConvertColor(computed.background_color()); + ColourbPremultiplied border_colors[4] = { + ConvertColor(computed.border_top_color()), + ConvertColor(computed.border_right_color()), + ConvertColor(computed.border_bottom_color()), + ConvertColor(computed.border_left_color()), + }; + const Vector4f border_radius = computed.border_radius(); Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; RMLUI_ASSERT(!geometry); diff --git a/Source/Core/ElementText.cpp b/Source/Core/ElementText.cpp index 16febfaf9..4cf61ca19 100644 --- a/Source/Core/ElementText.cpp +++ b/Source/Core/ElementText.cpp @@ -340,8 +340,7 @@ void ElementText::OnPropertyChange(const PropertyIdSet& changed_properties) const float new_opacity = computed.opacity(); const bool opacity_changed = opacity != new_opacity; - Colourb new_colour = computed.color(); - new_colour.alpha = byte(new_opacity * float(new_colour.alpha)); + ColourbPremultiplied new_colour = computed.color().ToPremultiplied(new_opacity); colour_changed = colour != new_colour; if (colour_changed) diff --git a/Source/Core/Elements/ElementImage.cpp b/Source/Core/Elements/ElementImage.cpp index 843a5dc18..46716806c 100644 --- a/Source/Core/Elements/ElementImage.cpp +++ b/Source/Core/Elements/ElementImage.cpp @@ -192,11 +192,7 @@ void ElementImage::GenerateGeometry() } const ComputedValues& computed = GetComputedValues(); - - float opacity = computed.opacity(); - Colourb quad_colour = computed.image_color(); - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); - + ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); Vector2f quad_size = GetBox().GetSize(BoxArea::Content).Round(); GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]); diff --git a/Source/Core/Elements/ElementProgress.cpp b/Source/Core/Elements/ElementProgress.cpp index 91c626125..0b382590c 100644 --- a/Source/Core/Elements/ElementProgress.cpp +++ b/Source/Core/Elements/ElementProgress.cpp @@ -243,13 +243,8 @@ void ElementProgress::GenerateGeometry() texcoords[1] = Vector2f(1, 1); } - Colourb quad_colour; - { - const ComputedValues& computed = GetComputedValues(); - const float opacity = computed.opacity(); - quad_colour = computed.image_color(); - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); - } + const ComputedValues& computed = GetComputedValues(); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); switch (direction) { diff --git a/Source/Core/Elements/WidgetTextInput.cpp b/Source/Core/Elements/WidgetTextInput.cpp index ee1d910d1..33573dc86 100644 --- a/Source/Core/Elements/WidgetTextInput.cpp +++ b/Source/Core/Elements/WidgetTextInput.cpp @@ -287,8 +287,8 @@ void WidgetTextInput::UpdateSelectionColours() // Determine what the colour of the selected text is. If our 'selection' element has the 'color' // attribute set, then use that. Otherwise, use the inverse of our own text colour. Colourb colour; - const Property* colour_property = selection_element->GetLocalProperty("color"); - if (colour_property != nullptr) + const Property* colour_property = selection_element->GetLocalProperty(PropertyId::Color); + if (colour_property) colour = colour_property->Get(); else { @@ -304,11 +304,13 @@ void WidgetTextInput::UpdateSelectionColours() // If the 'background-color' property has been set on the 'selection' element, use that as the // background colour for the selected text. Otherwise, use the inverse of the selected text // colour. - colour_property = selection_element->GetLocalProperty("background-color"); - if (colour_property != nullptr) - selection_colour = colour_property->Get(); + colour_property = selection_element->GetLocalProperty(PropertyId::BackgroundColor); + if (colour_property) + colour = colour_property->Get(); else - selection_colour = Colourb(255 - colour.red, 255 - colour.green, 255 - colour.blue, colour.alpha); + colour = Colourb(255 - colour.red, 255 - colour.green, 255 - colour.blue, colour.alpha); + + selection_colour = colour.ToPremultiplied(); // Color may have changed, so we update the cursor geometry. GenerateCursor(); @@ -1268,7 +1270,7 @@ void WidgetTextInput::GenerateCursor() color = property->Get(); } - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color); + GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color.ToPremultiplied()); } void WidgetTextInput::ForceFormattingOnNextLayout() diff --git a/Source/Core/Elements/WidgetTextInput.h b/Source/Core/Elements/WidgetTextInput.h index 9a181b4d6..b8c3dbdbd 100644 --- a/Source/Core/Elements/WidgetTextInput.h +++ b/Source/Core/Elements/WidgetTextInput.h @@ -242,7 +242,7 @@ class WidgetTextInput : public EventListener { int selection_length; // The colour of the background of selected text. - Colourb selection_colour; + ColourbPremultiplied selection_colour; // The selection background. Geometry selection_geometry; diff --git a/Source/Core/FontEffect.cpp b/Source/Core/FontEffect.cpp index 084ae8f64..66d83f911 100644 --- a/Source/Core/FontEffect.cpp +++ b/Source/Core/FontEffect.cpp @@ -79,4 +79,17 @@ void FontEffect::SetFingerprint(size_t _fingerprint) fingerprint = _fingerprint; } +void FontEffect::FillColorValuesFromAlpha(byte* destination, Vector2i dimensions, int stride) +{ + for (int y = 0; y < dimensions.y; ++y) + { + for (int x = 0; x < dimensions.x; ++x) + { + const int i = y * stride + x * 4; + const byte alpha = destination[i + 3]; + destination[i + 0] = destination[i + 1] = destination[i + 2] = alpha; + } + } +} + } // namespace Rml diff --git a/Source/Core/FontEffectBlur.cpp b/Source/Core/FontEffectBlur.cpp index e3c1ed42a..819b49486 100644 --- a/Source/Core/FontEffectBlur.cpp +++ b/Source/Core/FontEffectBlur.cpp @@ -110,6 +110,8 @@ void FontEffectBlur::GenerateGlyphTexture(byte* destination_data, const Vector2i filter_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, x_output.data(), buf_dimensions, Vector2i(0), ColorFormat::A8); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); } FontEffectBlurInstancer::FontEffectBlurInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid) diff --git a/Source/Core/FontEffectGlow.cpp b/Source/Core/FontEffectGlow.cpp index 0f97704b9..02e3c2afa 100644 --- a/Source/Core/FontEffectGlow.cpp +++ b/Source/Core/FontEffectGlow.cpp @@ -140,6 +140,8 @@ void FontEffectGlow::GenerateGlyphTexture(byte* destination_data, const Vector2i filter_blur_y.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, blur_x_output.data(), buf_dimensions, Vector2i(0), ColorFormat::A8); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); } FontEffectGlowInstancer::FontEffectGlowInstancer() : diff --git a/Source/Core/FontEffectOutline.cpp b/Source/Core/FontEffectOutline.cpp index 0fae8cbc1..7dc1f44ef 100644 --- a/Source/Core/FontEffectOutline.cpp +++ b/Source/Core/FontEffectOutline.cpp @@ -93,6 +93,8 @@ void FontEffectOutline::GenerateGlyphTexture(byte* destination_data, const Vecto { filter.Run(destination_data, destination_dimensions, destination_stride, ColorFormat::RGBA8, glyph.bitmap_data, glyph.bitmap_dimensions, Vector2i(width), glyph.color_format); + + FillColorValuesFromAlpha(destination_data, destination_dimensions, destination_stride); } FontEffectOutlineInstancer::FontEffectOutlineInstancer() : id_width(PropertyId::Invalid), id_color(PropertyId::Invalid) diff --git a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp index 4d2288655..d07e56b2e 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp @@ -78,7 +78,7 @@ int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, const Stri } int FontEngineInterfaceDefault::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string, - const Vector2f& position, const Colourb& colour, float opacity, float letter_spacing, GeometryList& geometry) + const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) { auto handle_default = reinterpret_cast(handle); return handle_default->GenerateString(geometry, string, position, colour, opacity, letter_spacing, (int)font_effects_handle); diff --git a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h index baa2fbeee..405f82d71 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h @@ -59,7 +59,7 @@ class RMLUICORE_API FontEngineInterfaceDefault : public FontEngineInterface { int GetStringWidth(FontFaceHandle, const String& string, float letter_spacing, Character prior_character) override; /// Generates the geometry required to render a single line of text. - int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, const Colourb& colour, float opacity, + int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) override; /// Returns the current version of the font face. diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp index fc8fa5ba0..f837d900e 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp @@ -188,7 +188,7 @@ bool FontFaceHandleDefault::GenerateLayerTexture(UniquePtr& textur return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs); } -int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const Colourb colour, +int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const ColourbPremultiplied colour, const float opacity, const float letter_spacing, const int layer_configuration_index) { int geometry_index = 0; @@ -209,17 +209,11 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& { FontFaceLayer* layer = layer_configuration[i]; - Colourb layer_colour; + ColourbPremultiplied layer_colour; if (layer == base_layer) - { layer_colour = colour; - } else - { - layer_colour = layer->GetColour(); - if (opacity < 1.f) - layer_colour.alpha = byte(opacity * float(layer_colour.alpha)); - } + layer_colour = layer->GetColour(opacity); const int num_textures = layer->GetNumTextures(); @@ -253,9 +247,10 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& // Adjust the cursor for the kerning between this character and the previous one. line_width += GetKerning(prior_character, character); + ColourbPremultiplied glyph_color = layer_colour; // Use white vertex colors on RGB glyphs. - const Colourb glyph_color = - (layer == base_layer && glyph->color_format == ColorFormat::RGBA8 ? Colourb(255, layer_colour.alpha) : layer_colour); + if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8) + glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha); layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color); diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h index f4c9079c1..11c3f0862 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h @@ -84,8 +84,8 @@ class FontFaceHandleDefault final : public NonCopyMoveable { /// @param[in] opacity The opacity of the text, should be applied to font effects. /// @param[in] layer_configuration Face configuration index to use for generating string. /// @return The width, in pixels, of the string geometry. - int GenerateString(GeometryList& geometry, const String& string, Vector2f position, Colourb colour, float opacity, float letter_spacing, - int layer_configuration = 0); + int GenerateString(GeometryList& geometry, const String& string, Vector2f position, ColourbPremultiplied colour, float opacity, + float letter_spacing, int layer_configuration = 0); /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. int GetVersion() const; diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Source/Core/FontEngineDefault/FontFaceLayer.cpp index 5ee7f354f..bdd5de153 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.cpp +++ b/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -265,9 +265,9 @@ int FontFaceLayer::GetNumTextures() const return (int)textures.size(); } -Colourb FontFaceLayer::GetColour() const +ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const { - return colour; + return colour.ToPremultiplied(opacity); } } // namespace Rml diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.h b/Source/Core/FontEngineDefault/FontFaceLayer.h index dc06bc1c1..5f8c69509 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.h +++ b/Source/Core/FontEngineDefault/FontFaceLayer.h @@ -71,7 +71,7 @@ class FontFaceLayer { /// @param[in] character_code The character to generate geometry for. /// @param[in] position The position of the baseline. /// @param[in] colour The colour of the string. - inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const Colourb colour) const + inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const ColourbPremultiplied colour) const { auto it = character_boxes.find(character_code); if (it == character_boxes.end()) @@ -101,8 +101,8 @@ class FontFaceLayer { /// Returns the number of textures employed by this layer. int GetNumTextures() const; - /// Returns the layer's colour. - Colourb GetColour() const; + /// Returns the layer's colour after applying the given opacity. + ColourbPremultiplied GetColour(float opacity) const; private: struct TextureBox { diff --git a/Source/Core/FontEngineInterface.cpp b/Source/Core/FontEngineInterface.cpp index 138342ed4..8dab86430 100644 --- a/Source/Core/FontEngineInterface.cpp +++ b/Source/Core/FontEngineInterface.cpp @@ -68,7 +68,7 @@ int FontEngineInterface::GetStringWidth(FontFaceHandle /*handle*/, const String& } int FontEngineInterface::GenerateString(FontFaceHandle /*face_handle*/, FontEffectsHandle /*font_effects_handle*/, const String& /*string*/, - const Vector2f& /*position*/, const Colourb& /*colour*/, float /*opacity*/, float /*letter_spacing*/, GeometryList& /*geometry*/) + const Vector2f& /*position*/, ColourbPremultiplied /*colour*/, float /*opacity*/, float /*letter_spacing*/, GeometryList& /*geometry*/) { return 0; } diff --git a/Source/Core/GeometryBackgroundBorder.cpp b/Source/Core/GeometryBackgroundBorder.cpp index 18c92953f..3250253d8 100644 --- a/Source/Core/GeometryBackgroundBorder.cpp +++ b/Source/Core/GeometryBackgroundBorder.cpp @@ -100,7 +100,7 @@ BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_posi return metrics; } -void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colourb color) +void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color) { const int offset_vertices = (int)vertices.size(); @@ -111,7 +111,7 @@ void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colo FillBackground(offset_vertices); } -void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]) +void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4]) { RMLUI_ASSERT(border_colors); @@ -153,7 +153,8 @@ void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSize } } -void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color) +void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, + ColourbPremultiplied color) { if (R == 0 || r.x <= 0 || r.y <= 0) { @@ -168,7 +169,7 @@ void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_ } } -void GeometryBackgroundBorder::DrawPoint(Vector2f pos, Colourb color) +void GeometryBackgroundBorder::DrawPoint(Vector2f pos, ColourbPremultiplied color) { const int offset_vertices = (int)vertices.size(); @@ -178,7 +179,8 @@ void GeometryBackgroundBorder::DrawPoint(Vector2f pos, Colourb color) vertices[offset_vertices].colour = color; } -void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points) +void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points) { RMLUI_ASSERT(num_points >= 2 && r.x > 0 && r.y > 0); @@ -217,7 +219,7 @@ void GeometryBackgroundBorder::FillBackground(int index_start) } void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, - Vector2f r, Colourb color0, Colourb color1) + Vector2f r, ColourbPremultiplied color0, ColourbPremultiplied color1) { const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI; const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI; @@ -236,7 +238,7 @@ void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_oute } } -void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1) +void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1) { const bool different_color = (color0 != color1); @@ -252,8 +254,8 @@ void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_i } } -void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, - int num_points) +void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0, + ColourbPremultiplied color1, int num_points) { RMLUI_ASSERT(num_points >= 2 && R > 0 && r.x > 0 && r.y > 0); @@ -270,7 +272,7 @@ void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f const float t = float(i) / float(num_points - 1); const float a = Math::Lerp(t, a0, a1); - const Colourb color = Math::RoundedLerp(t, color0, color1); + const ColourbPremultiplied color = Math::RoundedLerp(t, color0, color1); const Vector2f unit_vector(Math::Cos(a), Math::Sin(a)); vertices[offset_vertices + 2 * i].position = unit_vector * r + pos_center; @@ -291,8 +293,8 @@ void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f } } -void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1, - int num_points) +void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0, + ColourbPremultiplied color1, int num_points) { RMLUI_ASSERT(R > 0 && num_points >= 2); diff --git a/Source/Core/GeometryBackgroundBorder.h b/Source/Core/GeometryBackgroundBorder.h index 6b6dad9ab..f4bb4cef6 100644 --- a/Source/Core/GeometryBackgroundBorder.h +++ b/Source/Core/GeometryBackgroundBorder.h @@ -75,10 +75,10 @@ class GeometryBackgroundBorder { static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, Vector4f outer_radii); // Generate geometry for the background, defined by the inner area of the border metrics. - void DrawBackground(const BorderMetrics& metrics, Colourb color); + void DrawBackground(const BorderMetrics& metrics, ColourbPremultiplied color); /// Generate geometry for the border, defined by the intersection of the outer and inner areas of the border metrics. - void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]); + void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const ColourbPremultiplied border_colors[4]); private: enum Edge { TOP, RIGHT, BOTTOM, LEFT }; @@ -88,14 +88,14 @@ class GeometryBackgroundBorder { // All draw operations place vertices in clockwise order. // Draw the corner, delegate to the specific corner shape drawing function. - void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color); + void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, ColourbPremultiplied color); // Add a single point. - void DrawPoint(Vector2f pos, Colourb color); + void DrawPoint(Vector2f pos, ColourbPremultiplied color); // Draw an arc by placing vertices along the ellipse formed by the two-axis radius r, spaced evenly between angles a0,a1 (inclusive). Colors are // interpolated. - void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points); + void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, int num_points); // Generates triangles by connecting the added vertices. void FillBackground(int index_start); @@ -110,18 +110,20 @@ class GeometryBackgroundBorder { // Where 'next' corner means along the clockwise direction. This way we can easily fill the triangles of the edges in FillEdge(). // Draw the corner, delegate to the specific corner shape drawing function. - void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color0, - Colourb color1); + void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, + ColourbPremultiplied color0, ColourbPremultiplied color1); // Draw a sharp border corner, ie. no border-radius. Does not produce any triangles. - void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1); + void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, ColourbPremultiplied color0, ColourbPremultiplied color1); // Draw an arc along the outer edge (radius R), and an arc along the inner edge (two-axis radius r), // spaced evenly between angles a0,a1 (inclusive). Connect them by triangles. Colors are interpolated. - void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points); + void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points); // Draw an arc along the outer edge, and connect them by triangles to a point on the inner edge. - void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1, int num_points); + void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, ColourbPremultiplied color0, ColourbPremultiplied color1, + int num_points); // Add triangles between the previous corner to another one specified by the index (possibly yet-to-be-drawn). void FillEdge(int index_next_corner); diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 0ece51213..c23ff391d 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -120,11 +120,12 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha { Vector2f offset; const Box& box = element->GetBox(i, offset); + ColourbPremultiplied white(255); if (has_inner_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, Colourb(255), BoxArea::Padding); + GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, white, BoxArea::Padding); if (has_outer_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, Colourb(255), BoxArea::Border); + GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, white, BoxArea::Border); } RenderManager& render_manager = context->GetRenderManager(); @@ -232,7 +233,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha vertices.resize(4); indices.resize(6); const byte alpha = byte(opacity * 255.f); - GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), Colourb(255, alpha)); + GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), + ColourbPremultiplied(alpha, alpha)); out_shadow_texture.Set("box-shadow", texture_callback); out_shadow_geometry.SetTexture(&out_shadow_texture); diff --git a/Source/Core/GeometryUtilities.cpp b/Source/Core/GeometryUtilities.cpp index efab06e24..5335ebf54 100644 --- a/Source/Core/GeometryUtilities.cpp +++ b/Source/Core/GeometryUtilities.cpp @@ -40,13 +40,14 @@ GeometryUtilities::GeometryUtilities() {} GeometryUtilities::~GeometryUtilities() {} -void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset) +void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, + int index_offset) { GenerateQuad(vertices, indices, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1), index_offset); } -void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord, - Vector2f bottom_right_texcoord, int index_offset) +void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, + Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset) { vertices[0].position = origin; vertices[0].colour = colour; @@ -73,7 +74,7 @@ void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f or indices[5] = index_offset + 2; } -void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color) +void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color) { Math::SnapToPixelGrid(position, size); @@ -90,7 +91,7 @@ void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vect } void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, - Colourb background_color, const Colourb border_colors[4]) + ColourbPremultiplied background_color, const ColourbPremultiplied border_colors[4]) { RMLUI_ASSERT(border_colors); @@ -148,7 +149,7 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B for (int i = 0; i < num_vertices; i++) { GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, - Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); + Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); } } #endif @@ -162,8 +163,8 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B #endif } -void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb color, - BoxArea fill_area) +void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, + ColourbPremultiplied color, BoxArea fill_area) { RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, "Rectangle geometry only supports border, padding and content boxes."); diff --git a/Source/Core/Math.cpp b/Source/Core/Math.cpp index 92e4e364c..2ac364712 100644 --- a/Source/Core/Math.cpp +++ b/Source/Core/Math.cpp @@ -279,9 +279,9 @@ namespace Math { return Vector2i(Clamp(value.x, min.x, max.x), Clamp(value.y, min.y, max.y)); } - Colourb RoundedLerp(float t, Colourb v0, Colourb v1) + ColourbPremultiplied RoundedLerp(float t, ColourbPremultiplied v0, ColourbPremultiplied v1) { - return Colourb{ + return ColourbPremultiplied{ static_cast(RoundToInteger(Lerp(t, static_cast(v0[0]), static_cast(v1[0])))), static_cast(RoundToInteger(Lerp(t, static_cast(v0[1]), static_cast(v1[1])))), static_cast(RoundToInteger(Lerp(t, static_cast(v0[2]), static_cast(v1[2])))), diff --git a/Source/Core/PropertyParserBoxShadow.cpp b/Source/Core/PropertyParserBoxShadow.cpp index a244e75c5..06a8eaaab 100644 --- a/Source/Core/PropertyParserBoxShadow.cpp +++ b/Source/Core/PropertyParserBoxShadow.cpp @@ -97,7 +97,7 @@ bool PropertyParserBoxShadow::ParseValue(Property& property, const String& value } else if (parser_color->ParseValue(prop, argument, empty_parameter_map)) { - shadow.color = prop.Get(); + shadow.color = prop.Get().ToPremultiplied(); } else { diff --git a/Source/Core/PropertyParserColorStopList.cpp b/Source/Core/PropertyParserColorStopList.cpp index 3bc717773..7942f36cf 100644 --- a/Source/Core/PropertyParserColorStopList.cpp +++ b/Source/Core/PropertyParserColorStopList.cpp @@ -72,7 +72,7 @@ bool PropertyParserColorStopList::ParseValue(Property& property, const String& v return false; ColorStop color_stop = {}; - color_stop.color = p_color.Get(); + color_stop.color = p_color.Get().ToPremultiplied(); if (values.size() <= 1) color_stops.push_back(color_stop); diff --git a/Source/Debugger/Geometry.cpp b/Source/Debugger/Geometry.cpp index 333cd9153..61290c39a 100644 --- a/Source/Debugger/Geometry.cpp +++ b/Source/Debugger/Geometry.cpp @@ -53,10 +53,12 @@ void Geometry::RenderOutline(const Vector2f origin, const Vector2f dimensions, c Vertex vertices[4 * 4]; int indices[6 * 4]; - GeometryUtilities::GenerateQuad(vertices + 0, indices + 0, Vector2f(0, 0), Vector2f(dimensions.x, width), colour, 0); - GeometryUtilities::GenerateQuad(vertices + 4, indices + 6, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour, 4); - GeometryUtilities::GenerateQuad(vertices + 8, indices + 12, Vector2f(0, 0), Vector2f(width, dimensions.y), colour, 8); - GeometryUtilities::GenerateQuad(vertices + 12, indices + 18, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour, 12); + ColourbPremultiplied colour_pre = colour.ToPremultiplied(); + + GeometryUtilities::GenerateQuad(vertices + 0, indices + 0, Vector2f(0, 0), Vector2f(dimensions.x, width), colour_pre, 0); + GeometryUtilities::GenerateQuad(vertices + 4, indices + 6, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour_pre, 4); + GeometryUtilities::GenerateQuad(vertices + 8, indices + 12, Vector2f(0, 0), Vector2f(width, dimensions.y), colour_pre, 8); + GeometryUtilities::GenerateQuad(vertices + 12, indices + 18, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour_pre, 12); render_interface->RenderGeometry(vertices, 4 * 4, indices, 6 * 4, 0, origin); } @@ -70,7 +72,7 @@ void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const Vertex vertices[4]; int indices[6]; - GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour, 0); + GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied(), 0); render_interface->RenderGeometry(vertices, 4, indices, 6, 0, origin); } diff --git a/Source/Lottie/ElementLottie.cpp b/Source/Lottie/ElementLottie.cpp index de8360f6d..1a43a0c24 100644 --- a/Source/Lottie/ElementLottie.cpp +++ b/Source/Lottie/ElementLottie.cpp @@ -135,10 +135,7 @@ void ElementLottie::GenerateGeometry() }; const ComputedValues& computed = GetComputedValues(); - - const float opacity = computed.opacity(); - Colourb quad_colour = computed.image_color(); - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); + ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round(); render_dimensions = Vector2i(render_dimensions_f); diff --git a/Source/SVG/ElementSVG.cpp b/Source/SVG/ElementSVG.cpp index 372dedd13..0b283161e 100644 --- a/Source/SVG/ElementSVG.cpp +++ b/Source/SVG/ElementSVG.cpp @@ -128,10 +128,7 @@ void ElementSVG::GenerateGeometry() }; const ComputedValues& computed = GetComputedValues(); - - const float opacity = computed.opacity(); - Colourb quad_colour = computed.image_color(); - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); + ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round(); render_dimensions.x = int(render_dimensions_f.x); diff --git a/Tests/Source/UnitTests/Math.cpp b/Tests/Source/UnitTests/Math.cpp index 810d39194..85d1f657c 100644 --- a/Tests/Source/UnitTests/Math.cpp +++ b/Tests/Source/UnitTests/Math.cpp @@ -34,11 +34,11 @@ using namespace Rml; TEST_CASE("Math.RoundedLerp") { - const Colourb c0(0, 0, 0, 255); - const Colourb c1(255, 0, 0, 255); - const Colourb c2(127, 0, 0, 255); + const ColourbPremultiplied c0(0, 0, 0, 255); + const ColourbPremultiplied c1(255, 0, 0, 255); + const ColourbPremultiplied c2(127, 0, 0, 255); - Colourb c; + ColourbPremultiplied c; c = Math::RoundedLerp(0.0f, c0, c1); REQUIRE(c.red == c0.red); diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index ad25400c2..3e1a7ca67 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -117,31 +117,31 @@ TEST_CASE("Properties") { "red, blue", { - ColorStop{Colourb(255, 0, 0), NumericValue{}}, - ColorStop{Colourb(0, 0, 255), NumericValue{}}, + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{}}, }, }, { "red 5px, blue 50%", { - ColorStop{Colourb(255, 0, 0), NumericValue{5.f, Unit::PX}}, - ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{5.f, Unit::PX}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, }, }, { "red, #00f 50%, rgba(0, 255,0, 150) 10dp", { - ColorStop{Colourb(255, 0, 0), NumericValue{}}, - ColorStop{Colourb(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, - ColorStop{Colourb(0, 255, 0, 150), NumericValue{10.f, Unit::DP}}, + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + ColorStop{ColourbPremultiplied(0, 150, 0, 150), NumericValue{10.f, Unit::DP}}, }, }, { "red 50px 20%, blue 10in", { - ColorStop{Colourb(255, 0, 0), NumericValue{50.f, Unit::PX}}, - ColorStop{Colourb(255, 0, 0), NumericValue{20.f, Unit::PERCENT}}, - ColorStop{Colourb(0, 0, 255), NumericValue{10.f, Unit::INCH}}, + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{50.f, Unit::PX}}, + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{20.f, Unit::PERCENT}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{10.f, Unit::INCH}}, }, }, }; diff --git a/Tests/Source/VisualTests/CaptureScreen.cpp b/Tests/Source/VisualTests/CaptureScreen.cpp index bb83b2f5d..62ea66d3d 100644 --- a/Tests/Source/VisualTests/CaptureScreen.cpp +++ b/Tests/Source/VisualTests/CaptureScreen.cpp @@ -184,7 +184,7 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int auto GenerateGeometry = [&](TextureGeometry& geometry, Rml::byte* data, Rml::Vector2i dimensions) -> bool { if (!render_interface->GenerateTexture(geometry.texture_handle, data, dimensions)) return false; - const Rml::Colourb colour = {255, 255, 255, 255}; + const Rml::ColourbPremultiplied colour = {255, 255, 255, 255}; const Rml::Vector2f uv_top_left = {0, 0}; const Rml::Vector2f uv_bottom_right = {1, 1}; Rml::GeometryUtilities::GenerateQuad(geometry.vertices, geometry.indices, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref), From 2f80bdd163c24339b10318af499097b603f20edf Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 22 Jul 2023 09:59:58 +0200 Subject: [PATCH 51/80] Backends: Handle premultiplied alpha for textures and vertices --- Backends/RmlUi_Backend_SDL_GL2.cpp | 31 +++++++---- Backends/RmlUi_Backend_SDL_GL3.cpp | 52 ++++++++---------- Backends/RmlUi_Backend_SFML_GL2.cpp | 40 +++++++++----- Backends/RmlUi_Renderer_GL2.cpp | 30 ++++++----- Backends/RmlUi_Renderer_GL3.cpp | 33 ++++++------ Backends/RmlUi_Renderer_SDL.cpp | 82 +++++++++++++++++------------ Backends/RmlUi_Renderer_SDL.h | 1 + Backends/RmlUi_Renderer_VK.cpp | 38 +++++++------ 8 files changed, 174 insertions(+), 133 deletions(-) diff --git a/Backends/RmlUi_Backend_SDL_GL2.cpp b/Backends/RmlUi_Backend_SDL_GL2.cpp index 85204f6d7..37f916395 100644 --- a/Backends/RmlUi_Backend_SDL_GL2.cpp +++ b/Backends/RmlUi_Backend_SDL_GL2.cpp @@ -76,23 +76,25 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { return false; file_interface->Seek(file_handle, 0, SEEK_END); - size_t buffer_size = file_interface->Tell(file_handle); + const size_t buffer_size = file_interface->Tell(file_handle); file_interface->Seek(file_handle, 0, SEEK_SET); - Rml::UniquePtr buffer(new char[buffer_size]); + using Rml::byte; + Rml::UniquePtr buffer(new byte[buffer_size]); file_interface->Read(buffer.get(), buffer_size, file_handle); file_interface->Close(file_handle); - const size_t i = source.rfind('.'); - Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1)); + const size_t i_ext = source.rfind('.'); + Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1)); SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); if (!surface) return false; - if (surface->format->Amask == 0) + if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32) { - // Fix for rendering images with no alpha channel, see https://github.com/mikke89/RmlUi/issues/239 + // Ensure correct format for premultiplied alpha conversion below. Additionally, fix rendering images with + // no alpha channel, see https://github.com/mikke89/RmlUi/issues/239 SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0); SDL_FreeSurface(surface); @@ -102,15 +104,24 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { surface = converted_surface; } - SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - if (texture) + // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. + byte* pixels = static_cast(surface->pixels); + for (int i = 0; i < surface->w * surface->h * 4; i += 4) { - texture_handle = (Rml::TextureHandle)texture; - texture_dimensions = Rml::Vector2i(surface->w, surface->h); + const byte alpha = pixels[i + 3]; + for (int j = 0; j < 3; ++j) + pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); } + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + + texture_dimensions = Rml::Vector2i(surface->w, surface->h); + texture_handle = (Rml::TextureHandle)texture; SDL_FreeSurface(surface); + if (!texture) + return false; + return true; } diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index 5256d719c..aaf2bf6ce 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -72,42 +72,36 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1)); SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); + if (!surface) + return false; - bool success = false; - if (surface) - { - texture_dimensions.x = surface->w; - texture_dimensions.y = surface->h; - - if (surface->format->format != SDL_PIXELFORMAT_RGBA32) - { - SDL_SetSurfaceAlphaMod(surface, SDL_ALPHA_OPAQUE); - SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); - - SDL_Surface* new_surface = SDL_CreateRGBSurfaceWithFormat(0, surface->w, surface->h, 32, SDL_PIXELFORMAT_RGBA32); - if (!new_surface) - return false; + texture_dimensions.x = surface->w; + texture_dimensions.y = surface->h; - if (SDL_BlitSurface(surface, 0, new_surface, 0) != 0) - return false; + if (surface->format->format != SDL_PIXELFORMAT_RGBA32) + { + // Ensure correct format for premultiplied alpha conversion and GenerateTexture below. + SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0); + SDL_FreeSurface(surface); - SDL_FreeSurface(surface); - surface = new_surface; - } + if (!converted_surface) + return false; - // RmlUi assumes premultiplied alpha, convert the color values accordingly. - byte* pixels = static_cast(surface->pixels); - for (int i = 0; i < surface->w * surface->h * 4; i += 4) - { - const byte alpha = pixels[i + 3]; - for (int j = 0; j < 3; ++j) - pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); - } + surface = converted_surface; + } - success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions); - SDL_FreeSurface(surface); + // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. + byte* pixels = static_cast(surface->pixels); + for (int i = 0; i < surface->w * surface->h * 4; i += 4) + { + const byte alpha = pixels[i + 3]; + for (int j = 0; j < 3; ++j) + pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); } + bool success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions); + SDL_FreeSurface(surface); + return success; } }; diff --git a/Backends/RmlUi_Backend_SFML_GL2.cpp b/Backends/RmlUi_Backend_SFML_GL2.cpp index 08060370e..64d22b6f7 100644 --- a/Backends/RmlUi_Backend_SFML_GL2.cpp +++ b/Backends/RmlUi_Backend_SFML_GL2.cpp @@ -69,28 +69,41 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { size_t buffer_size = file_interface->Tell(file_handle); file_interface->Seek(file_handle, 0, SEEK_SET); - char* buffer = new char[buffer_size]; - file_interface->Read(buffer, buffer_size, file_handle); + using Rml::byte; + Rml::UniquePtr buffer(new byte[buffer_size]); + file_interface->Read(buffer.get(), buffer_size, file_handle); file_interface->Close(file_handle); - sf::Texture* texture = new sf::Texture(); - texture->setSmooth(true); - - bool success = texture->loadFromMemory(buffer, buffer_size); - - delete[] buffer; + sf::Image image; + if (!image.loadFromMemory(buffer.get(), buffer_size)) + return false; - if (success) + // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. + for (unsigned int x = 0; x < image.getSize().x; x++) { - texture_handle = (Rml::TextureHandle)texture; - texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y); + for (unsigned int y = 0; y < image.getSize().y; y++) + { + sf::Color color = image.getPixel(x, y); + color.r = (sf::Uint8)((color.r * color.a) / 255); + color.g = (sf::Uint8)((color.g * color.a) / 255); + color.b = (sf::Uint8)((color.b * color.a) / 255); + image.setPixel(x, y, color); + } } - else + + sf::Texture* texture = new sf::Texture(); + texture->setSmooth(true); + + if (!texture->loadFromImage(image)) { delete texture; + return false; } - return success; + texture_handle = (Rml::TextureHandle)texture; + texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y); + + return true; } bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override @@ -105,6 +118,7 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { } texture->update(source, source_dimensions.x, source_dimensions.y, 0, 0); + texture_handle = (Rml::TextureHandle)texture; return true; diff --git a/Backends/RmlUi_Renderer_GL2.cpp b/Backends/RmlUi_Renderer_GL2.cpp index 047b55a7d..2b3d6580d 100644 --- a/Backends/RmlUi_Renderer_GL2.cpp +++ b/Backends/RmlUi_Renderer_GL2.cpp @@ -70,7 +70,7 @@ void RenderInterface_GL2::BeginFrame() glDisableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); glMatrixMode(GL_PROJECTION); @@ -222,12 +222,13 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V return false; } - char* buffer = new char[buffer_size]; - file_interface->Read(buffer, buffer_size, file_handle); + 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, sizeof(TGAHeader)); + memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; int image_size = header.width * header.height * 4; // We always make 32bit textures @@ -235,7 +236,6 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V if (header.dataType != 2) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); - delete[] buffer; return false; } @@ -243,25 +243,30 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V if (color_mode < 3) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); - delete[] buffer; return false; } - const char* image_src = buffer + sizeof(TGAHeader); - unsigned char* image_dest = new unsigned char[image_size]; + 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 and flip Y axis + // 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 * 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) - image_dest[write_index + 3] = image_src[read_index + 3]; + { + 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; @@ -275,9 +280,6 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - delete[] image_dest; - delete[] buffer; - return success; } diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 0d074b2fe..7a675c855 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -228,8 +228,15 @@ 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); - finalColor = _color_matrix * texColor; + vec3 transformedColor = vec3(_color_matrix * texColor); + finalColor = vec4(transformedColor, texColor.a); } )"; static const char* shader_frag_blend_mask = RMLUI_SHADER_HEADER R"( @@ -1198,12 +1205,12 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V } using Rml::byte; - byte* buffer = new byte[buffer_size]; - file_interface->Read(buffer, buffer_size, file_handle); + 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, sizeof(TGAHeader)); + memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; int image_size = header.width * header.height * 4; // We always make 32bit textures @@ -1211,7 +1218,6 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V if (header.dataType != 2) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); - delete[] buffer; return false; } @@ -1219,28 +1225,28 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V if (color_mode < 3) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); - delete[] buffer; return false; } - const byte* image_src = buffer + sizeof(TGAHeader); - byte* image_dest = new byte[image_size]; + 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 and flip Y axis + // 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 + 1]; - image_dest[write_index + 1] = image_src[read_index + 2]; + 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((int(image_dest[write_index + j]) * int(alpha)) / 255); + image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255); image_dest[write_index + 3] = alpha; } else @@ -1256,9 +1262,6 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - delete[] image_dest; - delete[] buffer; - return success; } diff --git a/Backends/RmlUi_Renderer_SDL.cpp b/Backends/RmlUi_Renderer_SDL.cpp index 742d6395e..1a0bdcb04 100644 --- a/Backends/RmlUi_Renderer_SDL.cpp +++ b/Backends/RmlUi_Renderer_SDL.cpp @@ -33,14 +33,20 @@ #include #include -RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer) {} +RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer) +{ + // RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly. + // Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA). + blend_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE, + SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD); +} void RenderInterface_SDL::BeginFrame() { SDL_RenderSetViewport(renderer, nullptr); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawBlendMode(renderer, blend_mode); } void RenderInterface_SDL::EndFrame() {} @@ -96,54 +102,60 @@ bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V size_t buffer_size = file_interface->Tell(file_handle); file_interface->Seek(file_handle, 0, SEEK_SET); - char* buffer = new char[buffer_size]; - file_interface->Read(buffer, buffer_size, file_handle); + using Rml::byte; + Rml::UniquePtr buffer(new byte[buffer_size]); + file_interface->Read(buffer.get(), buffer_size, file_handle); file_interface->Close(file_handle); - const size_t i = source.rfind('.'); - Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1)); - - SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer, int(buffer_size)), 1, extension.c_str()); + const size_t i_ext = source.rfind('.'); + Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1)); - bool success = false; + SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); + if (!surface) + return false; - if (surface) + if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32) { - SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0); + SDL_FreeSurface(surface); - if (texture) - { - texture_handle = (Rml::TextureHandle)texture; - texture_dimensions = Rml::Vector2i(surface->w, surface->h); - success = true; - } + if (!converted_surface) + return false; - SDL_FreeSurface(surface); + surface = converted_surface; } - delete[] buffer; + // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. + byte* pixels = static_cast(surface->pixels); + for (int i = 0; i < surface->w * surface->h * 4; i += 4) + { + const byte alpha = pixels[i + 3]; + for (int j = 0; j < 3; ++j) + pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); + } + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - return success; + texture_dimensions = Rml::Vector2i(surface->w, surface->h); + texture_handle = (Rml::TextureHandle)texture; + SDL_FreeSurface(surface); + + if (!texture) + return false; + + SDL_SetTextureBlendMode(texture, blend_mode); + + return true; } bool RenderInterface_SDL::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) { -#if SDL_BYTEORDER == SDL_BIG_ENDIAN - Uint32 rmask = 0xff000000; - Uint32 gmask = 0x00ff0000; - Uint32 bmask = 0x0000ff00; - Uint32 amask = 0x000000ff; -#else - Uint32 rmask = 0x000000ff; - Uint32 gmask = 0x0000ff00; - Uint32 bmask = 0x00ff0000; - Uint32 amask = 0xff000000; -#endif - - SDL_Surface* surface = - SDL_CreateRGBSurfaceFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, rmask, gmask, bmask, amask); + SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, + SDL_PIXELFORMAT_RGBA32); + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); + SDL_SetTextureBlendMode(texture, blend_mode); + SDL_FreeSurface(surface); texture_handle = (Rml::TextureHandle)texture; return true; diff --git a/Backends/RmlUi_Renderer_SDL.h b/Backends/RmlUi_Renderer_SDL.h index 2f4031eb7..4ee1c93f2 100644 --- a/Backends/RmlUi_Renderer_SDL.h +++ b/Backends/RmlUi_Renderer_SDL.h @@ -54,6 +54,7 @@ class RenderInterface_SDL : public Rml::RenderInterface { private: SDL_Renderer* renderer; + SDL_BlendMode blend_mode = {}; SDL_Rect rect_scissor = {}; bool scissor_region_enabled = false; }; diff --git a/Backends/RmlUi_Renderer_VK.cpp b/Backends/RmlUi_Renderer_VK.cpp index bbb26d2b5..151b4b889 100644 --- a/Backends/RmlUi_Renderer_VK.cpp +++ b/Backends/RmlUi_Renderer_VK.cpp @@ -427,19 +427,20 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve size_t buffer_size = file_interface->Tell(file_handle); file_interface->Seek(file_handle, 0, SEEK_SET); - RMLUI_ASSERTMSG(buffer_size > sizeof(TGAHeader), "Texture file size is smaller than TGAHeader, file must be corrupt or otherwise invalid"); if (buffer_size <= sizeof(TGAHeader)) { + Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image."); file_interface->Close(file_handle); return false; } - char* buffer = new char[buffer_size]; - file_interface->Read(buffer, buffer_size, file_handle); + 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, sizeof(TGAHeader)); + memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; int image_size = header.width * header.height * 4; // We always make 32bit textures @@ -453,25 +454,31 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve // 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"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); return false; } - const char* image_src = buffer + sizeof(TGAHeader); - unsigned char* image_dest = new unsigned char[image_size]; + 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 and flip Y axis + // 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 * 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) - image_dest[write_index + 3] = image_src[read_index + 3]; + { + 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; @@ -483,12 +490,9 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve texture_dimensions.x = header.width; texture_dimensions.y = header.height; - bool status = CreateTexture(texture_handle, image_dest, texture_dimensions, source); - - delete[] image_dest; - delete[] buffer; + bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - return status; + return success; } bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) @@ -1961,10 +1965,10 @@ void RenderInterface_VK::Create_Pipelines() noexcept VkPipelineColorBlendAttachmentState info_color_blend_att = {}; info_color_blend_att.colorWriteMask = 0xf; info_color_blend_att.blendEnable = VK_TRUE; - info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA; + info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE; info_color_blend_att.dstColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; info_color_blend_att.colorBlendOp = VkBlendOp::VK_BLEND_OP_ADD; - info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA; + info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE; info_color_blend_att.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; info_color_blend_att.alphaBlendOp = VkBlendOp::VK_BLEND_OP_SUBTRACT; From cfd963c465fedaf35331a1811183389d3859d216 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 23 Jul 2023 10:53:46 +0200 Subject: [PATCH 52/80] Fix minor GCC warning on Win32 backends --- Backends/RmlUi_Backend_Win32_GL2.cpp | 2 +- Backends/RmlUi_Backend_Win32_VK.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Backends/RmlUi_Backend_Win32_GL2.cpp b/Backends/RmlUi_Backend_Win32_GL2.cpp index 246dafee9..dae4cd697 100644 --- a/Backends/RmlUi_Backend_Win32_GL2.cpp +++ b/Backends/RmlUi_Backend_Win32_GL2.cpp @@ -193,7 +193,7 @@ static bool NextEvent(MSG& message, UINT timeout) { if (timeout != 0) { - UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL); + UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL); BOOL res = GetMessage(&message, NULL, 0, 0); KillTimer(NULL, timer_id); if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id) diff --git a/Backends/RmlUi_Backend_Win32_VK.cpp b/Backends/RmlUi_Backend_Win32_VK.cpp index cdad95db7..65d9badfc 100644 --- a/Backends/RmlUi_Backend_Win32_VK.cpp +++ b/Backends/RmlUi_Backend_Win32_VK.cpp @@ -195,7 +195,7 @@ static bool NextEvent(MSG& message, UINT timeout) { if (timeout != 0) { - UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL); + UINT_PTR timer_id = SetTimer(NULL, 0, timeout, NULL); BOOL res = GetMessage(&message, NULL, 0, 0); KillTimer(NULL, timer_id); if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id) From f4414b856bce7a94b757fc2463d201a2f2397b8f Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 23 Jul 2023 14:55:33 +0200 Subject: [PATCH 53/80] Render interface: Mark optional functions for advanced effects --- Include/RmlUi/Core/RenderInterface.h | 36 +++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index c5ae9abc3..008f4114c 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -97,6 +97,22 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] geometry The application-specific compiled geometry to release. virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry); + /// Called by RmlUi when a texture is required by the library. + /// @param[out] texture_handle The handle to write the texture handle for the loaded texture to. + /// @param[out] texture_dimensions The variable to write the dimensions of the loaded texture. + /// @param[in] source The application-defined image source, joined with the path of the referencing document. + /// @return True if the load attempt succeeded and the handle and dimensions are valid, false if not. + virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source); + /// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels. + /// @param[out] texture_handle The handle to write the texture handle for the generated texture to. + /// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order. + /// @param[in] source_dimensions The dimensions, in pixels, of the source data. + /// @return True if the texture generation succeeded and the handle is valid, false if not. + virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); + /// Called by RmlUi when a loaded texture is no longer required. + /// @param[in] texture The texture handle to release. + virtual void ReleaseTexture(TextureHandle texture); + /// Called by RmlUi when it wants to enable or disable scissoring to clip content. /// @param[in] enable True if scissoring is to enabled, false if it is to be disabled. virtual void EnableScissorRegion(bool enable) = 0; @@ -107,6 +123,10 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] height The height of the scissored region. All pixels to below (y + height) should be clipped. virtual void SetScissorRegion(int x, int y, int width, int height) = 0; + /** + @name Remaining functions are optional to implement for advanced effects. + */ + /// Called by RmlUi when it wants to enable or disable the clip mask. /// @param[in] enable True if the clip mask is to be enabled, false if it is to be disabled. /// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask. @@ -117,22 +137,6 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] translation The translation to apply to the geometry. virtual void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation); - /// Called by RmlUi when a texture is required by the library. - /// @param[out] texture_handle The handle to write the texture handle for the loaded texture to. - /// @param[out] texture_dimensions The variable to write the dimensions of the loaded texture. - /// @param[in] source The application-defined image source, joined with the path of the referencing document. - /// @return True if the load attempt succeeded and the handle and dimensions are valid, false if not. - virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source); - /// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels. - /// @param[out] texture_handle The handle to write the texture handle for the generated texture to. - /// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order. - /// @param[in] source_dimensions The dimensions, in pixels, of the source data. - /// @return True if the texture generation succeeded and the handle is valid, false if not. - virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); - /// Called by RmlUi when a loaded texture is no longer required. - /// @param[in] texture The texture handle to release. - virtual void ReleaseTexture(TextureHandle texture); - /// Called by RmlUi when it wants the renderer to use a new transform matrix. /// This will only be called if 'transform' properties are encountered. If no transform applies to the current element, nullptr /// is submitted. Then it expects the renderer to use an identity matrix or otherwise omit the multiplication with the transform. From a076be24945c7001b566835727af8472e9958eac Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 5 Aug 2023 17:40:02 +0200 Subject: [PATCH 54/80] Postpone instancing named decorators until other decorators are instanced Previously, they were instanced the moment they were encountered in the style sheet parser. With this change, the render manager is always available at the time of instancing. --- Include/RmlUi/Core/StyleSheet.h | 2 +- Include/RmlUi/Core/StyleSheetTypes.h | 2 +- Source/Core/ElementAnimation.cpp | 2 +- Source/Core/StyleSheet.cpp | 19 ++++++++++--------- Source/Core/StyleSheetParser.cpp | 23 +++++++---------------- Source/Core/StyleSheetParser.h | 3 +-- 6 files changed, 21 insertions(+), 30 deletions(-) diff --git a/Include/RmlUi/Core/StyleSheet.h b/Include/RmlUi/Core/StyleSheet.h index cf98ad361..8a3a2d2db 100644 --- a/Include/RmlUi/Core/StyleSheet.h +++ b/Include/RmlUi/Core/StyleSheet.h @@ -103,7 +103,7 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable { KeyframesMap keyframes; // Name of every @decorator mapped to their specification - NamedDecoratorMap decorator_map; + NamedDecoratorMap named_decorator_map; // Name of every @spritesheet and underlying sprites mapped to their values SpritesheetList spritesheet_list; diff --git a/Include/RmlUi/Core/StyleSheetTypes.h b/Include/RmlUi/Core/StyleSheetTypes.h index e16c994bb..19301e7af 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -54,8 +54,8 @@ using KeyframesMap = UnorderedMap; struct NamedDecorator { String type; + DecoratorInstancer* instancer; PropertyDictionary properties; - SharedPtr decorator; }; using NamedDecoratorMap = UnorderedMap; diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index e48fe06a7..7e138408d 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -100,7 +100,7 @@ struct EffectDeclarationView { instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties), paint_area(declaration.paint_area) {} EffectDeclarationView(const NamedDecorator* named_decorator) : - instancer(Factory::GetDecoratorInstancer(named_decorator->type)), type(&named_decorator->type), properties(&named_decorator->properties) + instancer(named_decorator->instancer), type(&named_decorator->type), properties(&named_decorator->properties) {} EffectDeclarationView(const FilterDeclaration& declaration) : instancer(declaration.instancer), type(&declaration.type), properties(&declaration.properties) diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index ee612d00f..16eacd15e 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -56,7 +56,7 @@ UniquePtr StyleSheet::CombineStyleSheet(const StyleSheet& other_shee new_sheet->root = root->DeepCopy(); new_sheet->specificity_offset = specificity_offset; new_sheet->keyframes = keyframes; - new_sheet->decorator_map = decorator_map; + new_sheet->named_decorator_map = named_decorator_map; new_sheet->spritesheet_list = spritesheet_list; new_sheet->MergeStyleSheet(other_sheet); @@ -79,10 +79,10 @@ void StyleSheet::MergeStyleSheet(const StyleSheet& other_sheet) } // Copy over the decorators, and replace any matching decorator names from other_sheet - decorator_map.reserve(decorator_map.size() + other_sheet.decorator_map.size()); - for (auto& other_decorator : other_sheet.decorator_map) + named_decorator_map.reserve(named_decorator_map.size() + other_sheet.named_decorator_map.size()); + for (auto& other_decorator : other_sheet.named_decorator_map) { - decorator_map[other_decorator.first] = other_decorator.second; + named_decorator_map[other_decorator.first] = other_decorator.second; } spritesheet_list.Reserve(spritesheet_list.NumSpriteSheets() + other_sheet.spritesheet_list.NumSpriteSheets(), @@ -99,8 +99,8 @@ void StyleSheet::BuildNodeIndex() const NamedDecorator* StyleSheet::GetNamedDecorator(const String& name) const { - auto it = decorator_map.find(name); - if (it != decorator_map.end()) + auto it = named_decorator_map.find(name); + if (it != named_decorator_map.end()) return &(it->second); return nullptr; } @@ -162,9 +162,10 @@ const DecoratorPtrList& StyleSheet::InstanceDecorators(const DecoratorDeclaratio else { // If we have no instancer, this means the type is the name of an @decorator rule. - auto it_map = decorator_map.find(declaration.type); - if (it_map != decorator_map.end()) - decorator = it_map->second.decorator; + auto it_map = named_decorator_map.find(declaration.type); + if (it_map != named_decorator_map.end()) + decorator = it_map->second.instancer->InstanceDecorator(it_map->second.type, it_map->second.properties, + DecoratorInstancerInterface(*this, source)); if (!decorator) Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index e6e11ba03..0052b5155 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -325,7 +325,7 @@ bool StyleSheetParser::ParseKeyframeBlock(KeyframesMap& keyframes_map, const Str return true; } -bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& decorator_map, const StyleSheet& style_sheet, +bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& named_decorator_map, const SharedPtr& source) { StringList name_type; @@ -341,8 +341,8 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecorator const String& name = name_type[0]; String decorator_type = name_type[1]; - auto it_find = decorator_map.find(name); - if (it_find != decorator_map.end()) + auto it_find = named_decorator_map.find(name); + if (it_find != named_decorator_map.end()) { Log::Message(Log::LT_WARNING, "Decorator with name '%s' already declared, ignoring decorator at %s:%d.", name.c_str(), stream_file_name.c_str(), line_number); @@ -356,8 +356,8 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecorator if (!decorator_instancer) { // Type is not a declared decorator type, instead, see if it is another decorator name, then we inherit its properties. - auto it = decorator_map.find(decorator_type); - if (it != decorator_map.end()) + auto it = named_decorator_map.find(decorator_type); + if (it != named_decorator_map.end()) { // Yes, try to retrieve the instancer from the parent type, and add its property values. decorator_instancer = Factory::GetDecoratorInstancer(it->second.type); @@ -384,16 +384,7 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, NamedDecorator property_specification.SetPropertyDefaults(properties); properties.SetSourceOfAllProperties(source); - SharedPtr decorator = - decorator_instancer->InstanceDecorator(decorator_type, properties, DecoratorInstancerInterface(style_sheet, source.get())); - if (!decorator) - { - Log::Message(Log::LT_WARNING, "Could not instance decorator of type '%s' declared at %s:%d.", decorator_type.c_str(), - stream_file_name.c_str(), line_number); - return false; - } - - decorator_map.emplace(name, NamedDecorator{std::move(decorator_type), std::move(properties), std::move(decorator)}); + named_decorator_map.emplace(name, NamedDecorator{std::move(decorator_type), decorator_instancer, std::move(properties)}); return true; } @@ -599,7 +590,7 @@ bool StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int else if (at_rule_identifier == "decorator") { auto source = MakeShared(stream_file_name, (int)line_number, pre_token_str); - ParseDecoratorBlock(at_rule_name, current_block.stylesheet->decorator_map, *current_block.stylesheet, source); + ParseDecoratorBlock(at_rule_name, current_block.stylesheet->named_decorator_map, source); at_rule_name.clear(); state = State::Global; diff --git a/Source/Core/StyleSheetParser.h b/Source/Core/StyleSheetParser.h index 0bd75caf7..d575bac52 100644 --- a/Source/Core/StyleSheetParser.h +++ b/Source/Core/StyleSheetParser.h @@ -105,8 +105,7 @@ class StyleSheetParser { bool ParseKeyframeBlock(KeyframesMap& keyframes_map, const String& identifier, const String& rules, const PropertyDictionary& properties); // Attempts to parse a @decorator block - bool ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& decorator_map, const StyleSheet& style_sheet, - const SharedPtr& source); + bool ParseDecoratorBlock(const String& at_name, NamedDecoratorMap& named_decorator_map, const SharedPtr& source); // Attempts to parse the properties of a @media query bool ParseMediaFeatureMap(PropertyDictionary& properties, const String& rules); From a452f26951f9450d484496cccdfad9c94b3fd294 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 10 Sep 2023 23:41:11 +0200 Subject: [PATCH 55/80] Refactor Geometry and Texture, introduce unique render resources Unique render resources - Consist of a unique handle and a pointer to its render manger. - Automatically cleans up its underlying resource when out of scope. - Ensuring resources are always released in the correct render interface. - Geometry, CompiledShader, CompiledFilter and CallbackTexture, are now unique render resources. - All of which are constructed through the render manager. RenderManager - Now the owner of all resources constructed through the render interface. - Wraps all calls to the render interface, the interface should no longer be called directly. - Added back ability to use multiple render interfaces for separate contexts. - Each render interface is wrapped by a unique render manager. Geometry - Geometry is now a unique render resource. - Geometry is now constructed from a Mesh through the render manager. The mesh cannot be modified after it is submitted. However, it can be released to reuse its buffers, then resubmitted. - With this change, we can guarantee pointer stability of submitted vertex and index pointers, until the call to ReleaseCompiledGeometry. - Move texture out of Geometry, instead, a texture can be provided during the call to render. Mesh - Simple data structure, contains indices and vertices defining the mesh. - Meshes can be constructed directly or using MeshUtilities (previously GeometryUtilities). Texture - The render manager now owns and stores file textures. - File textures are released when the render manager is destroyed, during Rml::Shutdown. - Each render manager has its own texture database to lookup and reuse textures with the same path. - `Texture` is now simply a non-owning view and can be freely copied. - The user is responsible for ensuring validity of the underlying resource's lifetime. - `CallbackTexture` on the other hand is a unique render resource, automatically released when out of scope. - Can make a non-owning reference (Texture). - Handlers are available for managing texture sources for unknown or multiple render managers, generating references (Texture) as needed. - `TextureSource` for file textures and `CallbackTextureSource` for callback textures. StableVector - New container for stable indices which remain valid after entries are erased. Breaking changes - New procedure for generating geometry and textures (see above for details). - GeometryUtilities renamed to MeshUtilities. --- Backends/RmlUi_Renderer_GL3.cpp | 22 +- CMake/FileList.cmake | 17 +- Include/RmlUi/Core.h | 6 +- Include/RmlUi/Core/CallbackTexture.h | 117 ++++++++++ Include/RmlUi/Core/CompiledFilterShader.h | 71 ++++++ Include/RmlUi/Core/Context.h | 18 +- Include/RmlUi/Core/ContextInstancer.h | 4 +- Include/RmlUi/Core/Core.h | 15 +- Include/RmlUi/Core/Decorator.h | 15 +- Include/RmlUi/Core/Element.h | 4 + Include/RmlUi/Core/ElementText.h | 12 +- Include/RmlUi/Core/Factory.h | 4 +- Include/RmlUi/Core/Filter.h | 10 +- Include/RmlUi/Core/FontEngineInterface.h | 13 +- Include/RmlUi/Core/Geometry.h | 78 ++----- .../RmlUi/Core/Mesh.h | 40 ++-- .../{GeometryUtilities.h => MeshUtilities.h} | 31 ++- Include/RmlUi/Core/RenderInterface.h | 3 - Include/RmlUi/Core/RenderManager.h | 72 +++++- Include/RmlUi/Core/Spritesheet.h | 7 +- Include/RmlUi/Core/StableVector.h | 124 +++++++++++ Include/RmlUi/Core/StyleSheet.h | 4 +- Include/RmlUi/Core/Texture.h | 75 +++---- Include/RmlUi/Core/Types.h | 8 +- Include/RmlUi/Core/UniqueRenderResource.h | 87 ++++++++ Include/RmlUi/Lottie/ElementLottie.h | 4 +- Include/RmlUi/SVG/ElementSVG.h | 4 +- .../basic/bitmapfont/src/FontEngineBitmap.cpp | 33 ++- .../basic/bitmapfont/src/FontEngineBitmap.h | 9 +- .../src/FontEngineInterfaceBitmap.cpp | 7 +- .../src/FontEngineInterfaceBitmap.h | 7 +- Samples/invaders/src/DecoratorDefender.cpp | 17 +- Samples/invaders/src/DecoratorStarfield.cpp | 6 +- Samples/invaders/src/Defender.cpp | 6 +- Samples/invaders/src/Defender.h | 2 +- Samples/invaders/src/ElementGame.cpp | 3 +- Samples/invaders/src/Game.cpp | 13 +- Samples/invaders/src/Game.h | 2 +- Samples/invaders/src/Invader.cpp | 6 +- Samples/invaders/src/Invader.h | 2 +- Samples/invaders/src/Shield.cpp | 4 +- Samples/invaders/src/Shield.h | 2 +- Samples/invaders/src/Sprite.cpp | 46 ++-- Samples/invaders/src/Sprite.h | 6 +- Samples/luainvaders/src/DecoratorDefender.cpp | 17 +- .../luainvaders/src/DecoratorStarfield.cpp | 6 +- Samples/luainvaders/src/Defender.cpp | 6 +- Samples/luainvaders/src/Defender.h | 2 +- Samples/luainvaders/src/ElementGame.cpp | 3 +- Samples/luainvaders/src/Game.cpp | 13 +- Samples/luainvaders/src/Game.h | 2 +- Samples/luainvaders/src/Invader.cpp | 6 +- Samples/luainvaders/src/Invader.h | 2 +- Samples/luainvaders/src/Shield.cpp | 4 +- Samples/luainvaders/src/Shield.h | 2 +- Samples/luainvaders/src/Sprite.cpp | 46 ++-- Samples/luainvaders/src/Sprite.h | 5 +- Source/Core/CallbackTexture.cpp | 108 +++++++++ Source/Core/CompiledFilterShader.cpp | 60 +++++ Source/Core/Context.cpp | 13 +- Source/Core/ContextInstancerDefault.cpp | 4 +- Source/Core/ContextInstancerDefault.h | 5 +- Source/Core/Core.cpp | 94 +++++--- Source/Core/Decorator.cpp | 23 +- Source/Core/DecoratorGradient.cpp | 75 ++++--- Source/Core/DecoratorNinePatch.cpp | 23 +- Source/Core/DecoratorNinePatch.h | 2 +- Source/Core/DecoratorShader.cpp | 25 ++- Source/Core/DecoratorShader.h | 4 +- Source/Core/DecoratorTiled.cpp | 24 +- Source/Core/DecoratorTiled.h | 9 +- Source/Core/DecoratorTiledBox.cpp | 51 ++--- Source/Core/DecoratorTiledBox.h | 21 +- Source/Core/DecoratorTiledHorizontal.cpp | 36 ++- Source/Core/DecoratorTiledHorizontal.h | 4 +- Source/Core/DecoratorTiledImage.cpp | 17 +- Source/Core/DecoratorTiledImage.h | 2 +- Source/Core/DecoratorTiledVertical.cpp | 37 ++- Source/Core/DecoratorTiledVertical.h | 4 +- Source/Core/Element.cpp | 7 + Source/Core/ElementBackgroundBorder.cpp | 48 ++-- Source/Core/ElementBackgroundBorder.h | 6 +- Source/Core/ElementDecoration.cpp | 86 +++---- Source/Core/ElementDecoration.h | 7 +- Source/Core/ElementText.cpp | 68 +++--- Source/Core/ElementUtilities.cpp | 3 +- Source/Core/Elements/ElementImage.cpp | 38 ++-- Source/Core/Elements/ElementProgress.cpp | 30 +-- Source/Core/Elements/WidgetTextInput.cpp | 31 +-- Source/Core/Factory.cpp | 4 +- Source/Core/Filter.cpp | 1 + Source/Core/FilterBasic.cpp | 15 +- Source/Core/FilterBasic.h | 4 +- Source/Core/FilterBlur.cpp | 13 +- Source/Core/FilterBlur.h | 4 +- Source/Core/FilterDropShadow.cpp | 14 +- Source/Core/FilterDropShadow.h | 4 +- .../FontEngineInterfaceDefault.cpp | 6 +- .../FontEngineInterfaceDefault.h | 8 +- .../FontFaceHandleDefault.cpp | 40 ++-- .../FontEngineDefault/FontFaceHandleDefault.h | 8 +- .../Core/FontEngineDefault/FontFaceLayer.cpp | 34 +-- Source/Core/FontEngineDefault/FontFaceLayer.h | 34 ++- Source/Core/FontEngineInterface.cpp | 5 +- Source/Core/Geometry.cpp | 178 ++------------- Source/Core/GeometryBoxShadow.cpp | 85 +++---- Source/Core/GeometryBoxShadow.h | 8 +- Source/Core/GeometryDatabase.cpp | 158 ------------- ...eometryUtilities.cpp => MeshUtilities.cpp} | 90 ++++---- Source/Core/RenderInterface.cpp | 15 +- Source/Core/RenderManager.cpp | 210 +++++++++++++++++- Source/Core/RenderManagerAccess.cpp | 66 ++++++ Source/Core/RenderManagerAccess.h | 75 +++++++ Source/Core/Spritesheet.cpp | 18 +- Source/Core/StyleSheet.cpp | 9 +- Source/Core/Texture.cpp | 53 ++--- Source/Core/TextureDatabase.cpp | 172 +++++++------- Source/Core/TextureDatabase.h | 76 ++++--- Source/Core/TextureLayoutTexture.h | 1 - Source/Core/TextureResource.cpp | 144 ------------ Source/Core/TextureResource.h | 87 -------- Source/Debugger/Geometry.cpp | 42 ++-- Source/Lottie/ElementLottie.cpp | 40 ++-- Source/SVG/ElementSVG.cpp | 41 ++-- Tests/Source/Common/TestsInterface.cpp | 20 ++ Tests/Source/Common/TestsInterface.h | 17 +- Tests/Source/UnitTests/Core.cpp | 36 ++- Tests/Source/UnitTests/Filter.cpp | 11 +- ...{GeometryDatabase.cpp => StableVector.cpp} | 60 ++--- Tests/Source/VisualTests/CaptureScreen.cpp | 9 +- Tests/Source/VisualTests/CaptureScreen.h | 5 +- Tests/Source/VisualTests/TestNavigator.cpp | 3 +- 132 files changed, 2217 insertions(+), 1821 deletions(-) create mode 100644 Include/RmlUi/Core/CallbackTexture.h create mode 100644 Include/RmlUi/Core/CompiledFilterShader.h rename Source/Core/GeometryDatabase.h => Include/RmlUi/Core/Mesh.h (63%) rename Include/RmlUi/Core/{GeometryUtilities.h => MeshUtilities.h} (81%) create mode 100644 Include/RmlUi/Core/StableVector.h create mode 100644 Include/RmlUi/Core/UniqueRenderResource.h create mode 100644 Source/Core/CallbackTexture.cpp create mode 100644 Source/Core/CompiledFilterShader.cpp delete mode 100644 Source/Core/GeometryDatabase.cpp rename Source/Core/{GeometryUtilities.cpp => MeshUtilities.cpp} (65%) create mode 100644 Source/Core/RenderManagerAccess.cpp create mode 100644 Source/Core/RenderManagerAccess.h delete mode 100644 Source/Core/TextureResource.cpp delete mode 100644 Source/Core/TextureResource.h rename Tests/Source/UnitTests/{GeometryDatabase.cpp => StableVector.cpp} (59%) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 7a675c855..66113ffd0 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -30,7 +30,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -780,11 +781,10 @@ RenderInterface_GL3::RenderInterface_GL3() if (Gfx::CreateShaders(*mut_program_data)) { program_data = std::move(mut_program_data); - - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(-1), Rml::Vector2f(2), {}); - fullscreen_quad_geometry = RenderInterface_GL3::CompileGeometry(vertices, 4, indices, 6); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + fullscreen_quad_geometry = + RenderInterface_GL3::CompileGeometry(mesh.vertices.data(), (int)mesh.vertices.size(), mesh.indices.data(), (int)mesh.indices.size()); } } @@ -1298,15 +1298,15 @@ void RenderInterface_GL3::DrawFullscreenQuad() void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) { - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + 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 : vertices) + for (Rml::Vertex& vertex : mesh.vertices) vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; } - RenderGeometry(vertices, 4, indices, 6, RenderInterface_GL3::TexturePostprocess, {}); + RenderGeometry(mesh.vertices.data(), (int)mesh.vertices.size(), mesh.indices.data(), (int)mesh.indices.size(), + RenderInterface_GL3::TexturePostprocess, {}); } static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index cbb4bed61..940cadd8b 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -57,7 +57,6 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.h - ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.h @@ -95,6 +94,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyShorthandDefinition.h + ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.h ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.h ${PROJECT_SOURCE_DIR}/Source/Core/StreamFile.h ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.h @@ -108,7 +108,6 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRectangle.h ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRow.h ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutTexture.h - ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.h ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.h ${PROJECT_SOURCE_DIR}/Source/Core/TransformUtilities.h ${PROJECT_SOURCE_DIR}/Source/Core/WidgetScroll.h @@ -128,8 +127,10 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Animation.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/BaseXMLParser.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Box.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CallbackTexture.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Colour.inl + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/CompiledFilterShader.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ComputedValues.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_map.hpp ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Containers/itlib/flat_set.hpp @@ -175,7 +176,6 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontGlyph.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/FontMetrics.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Geometry.h - ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/GeometryUtilities.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Header.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ID.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Input.h @@ -183,6 +183,8 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Math.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Mesh.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/MeshUtilities.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/NumericValue.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h @@ -201,6 +203,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Spritesheet.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StableVector.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Stream.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h @@ -218,6 +221,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/UniqueRenderResource.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Unit.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Utilities.h @@ -237,7 +241,9 @@ set(Core_PUB_HDR_FILES set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/BaseXMLParser.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Box.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/CallbackTexture.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Clock.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/CompiledFilterShader.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ComputedValues.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ComputeProperty.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Context.cpp @@ -326,8 +332,6 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ContainerBox.cpp @@ -348,6 +352,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/MeshUtilities.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp @@ -371,6 +376,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterface.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderManager.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Spritesheet.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Stream.cpp @@ -393,7 +399,6 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRectangle.cpp ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutRow.cpp ${PROJECT_SOURCE_DIR}/Source/Core/TextureLayoutTexture.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/TextureResource.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Transform.cpp ${PROJECT_SOURCE_DIR}/Source/Core/TransformPrimitive.cpp ${PROJECT_SOURCE_DIR}/Source/Core/TransformState.cpp diff --git a/Include/RmlUi/Core.h b/Include/RmlUi/Core.h index bfdb0b5e3..03ce2ae55 100644 --- a/Include/RmlUi/Core.h +++ b/Include/RmlUi/Core.h @@ -31,6 +31,8 @@ #include "Core/Animation.h" #include "Core/Box.h" +#include "Core/CallbackTexture.h" +#include "Core/CompiledFilterShader.h" #include "Core/ComputedValues.h" #include "Core/Context.h" #include "Core/ContextInstancer.h" @@ -61,12 +63,13 @@ #include "Core/FontEngineInterface.h" #include "Core/FontGlyph.h" #include "Core/Geometry.h" -#include "Core/GeometryUtilities.h" #include "Core/Header.h" #include "Core/ID.h" #include "Core/Input.h" #include "Core/Log.h" #include "Core/Math.h" +#include "Core/Mesh.h" +#include "Core/MeshUtilities.h" #include "Core/NumericValue.h" #include "Core/Plugin.h" #include "Core/PropertiesIteratorView.h" @@ -91,6 +94,7 @@ #include "Core/Tween.h" #include "Core/TypeConverter.h" #include "Core/Types.h" +#include "Core/UniqueRenderResource.h" #include "Core/Unit.h" #include "Core/Vertex.h" #include "Core/XMLNodeHandler.h" diff --git a/Include/RmlUi/Core/CallbackTexture.h b/Include/RmlUi/Core/CallbackTexture.h new file mode 100644 index 000000000..ef1aa41f6 --- /dev/null +++ b/Include/RmlUi/Core/CallbackTexture.h @@ -0,0 +1,117 @@ +/* + * 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_CORE_CALLBACKTEXTURE_H +#define RMLUI_CORE_CALLBACKTEXTURE_H + +#include "Header.h" +#include "Types.h" +#include "UniqueRenderResource.h" + +namespace Rml { + +class RenderInterface; +class RenderManager; +class CallbackTextureInterface; +class Texture; + +/* + Callback function for generating textures on demand. + /// @param[in] texture_interface The interface used to specify the texture. + /// @return True on success. + */ +using CallbackTextureFunction = Function; + +/** + Callback texture is a unique render resource for generating textures on demand. + + It is constructed through the render manager. + */ +class RMLUICORE_API CallbackTexture final : public UniqueRenderResource { +public: + CallbackTexture() = default; + + operator Texture() const; + + void Release(); + +private: + CallbackTexture(RenderManager* render_manager, StableVectorIndex resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} + friend class RenderManager; +}; + +/** + Interface handed to the texture callback function, which the client can use to submit a single texture. + */ +class RMLUICORE_API CallbackTextureInterface { +public: + CallbackTextureInterface(RenderManager& render_manager, RenderInterface& render_interface, TextureHandle& texture_handle, Vector2i& dimensions); + + /// Generate texture from byte source. + /// @param[in] source Texture data in 8-bit RGBA (premultiplied) format. + /// @param[in] dimensions The width and height of the texture. + /// @return True on success. + bool GenerateTexture(const byte* source, Vector2i dimensions) const; + + /// Store the current layer as a texture, so that it can be rendered with geometry later. + /// @param[in] dimensions The dimensions of the resulting texture, which will be copied from the top-left part of the active layer. + void SaveLayerAsTexture(Vector2i dimensions) const; + + RenderManager& GetRenderManager() const; + +private: + RenderManager& render_manager; + RenderInterface& render_interface; + TextureHandle& texture_handle; + Vector2i& dimensions; +}; + +/** + Stores a texture callback function, which is used to generate and cache callback textures possibly for multiple render managers. + */ +class RMLUICORE_API CallbackTextureSource { +public: + CallbackTextureSource() = default; + CallbackTextureSource(CallbackTextureFunction&& callback); + ~CallbackTextureSource() = default; + + CallbackTextureSource(const CallbackTextureSource&) = delete; + CallbackTextureSource& operator=(const CallbackTextureSource&) = delete; + + CallbackTextureSource(CallbackTextureSource&& other) noexcept; + CallbackTextureSource& operator=(CallbackTextureSource&& other) noexcept; + + Texture GetTexture(RenderManager& render_manager) const; + +private: + CallbackTextureFunction callback; + mutable SmallUnorderedMap textures; +}; + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/CompiledFilterShader.h b/Include/RmlUi/Core/CompiledFilterShader.h new file mode 100644 index 000000000..16843c9bf --- /dev/null +++ b/Include/RmlUi/Core/CompiledFilterShader.h @@ -0,0 +1,71 @@ +/* + * 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_CORE_COMPILEDFILTERSHADER_H +#define RMLUI_CORE_COMPILEDFILTERSHADER_H + +#include "Header.h" +#include "UniqueRenderResource.h" + +namespace Rml { + +class RenderManager; + +/** + A compiled filter to be applied during layer pop in its render manager. A unique resource constructed through the render manager. + */ +class RMLUICORE_API CompiledFilter final : public UniqueRenderResource { +public: + CompiledFilter() = default; + + void AddHandleTo(FilterHandleList& list); + + void Release(); + +private: + CompiledFilter(RenderManager* render_manager, CompiledFilterHandle resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} + friend class RenderManager; +}; + +/** + A compiled shader to be used when rendering geometry. A unique resource constructed through the render manager. + */ +class RMLUICORE_API CompiledShader final : public UniqueRenderResource { +public: + CompiledShader() = default; + + void Release(); + +private: + CompiledShader(RenderManager* render_manager, CompiledShaderHandle resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} + friend class RenderManager; +}; + +} // namespace Rml + +#endif diff --git a/Include/RmlUi/Core/Context.h b/Include/RmlUi/Core/Context.h index 48a8591db..6958b066a 100644 --- a/Include/RmlUi/Core/Context.h +++ b/Include/RmlUi/Core/Context.h @@ -31,7 +31,6 @@ #include "Header.h" #include "Input.h" -#include "RenderManager.h" #include "ScriptInterface.h" #include "ScrollTypes.h" #include "Traits.h" @@ -47,6 +46,7 @@ class DataModel; class DataModelConstructor; class DataTypeRegister; class ScrollController; +class RenderManager; enum class EventId : uint16_t; /** @@ -57,10 +57,10 @@ enum class EventId : uint16_t; class RMLUICORE_API Context : public ScriptInterface { public: - /// Constructs a new, uninitialised context. This should not be called directly, use CreateContext() - /// instead. + /// Constructs a new, uninitialised context. This should not be called directly, use CreateContext() instead. /// @param[in] name The name of the context. - Context(const String& name); + /// @param[in] render_manager The render manager used for this context. + Context(const String& name, RenderManager* render_manager); /// Destroys a context. virtual ~Context(); @@ -300,9 +300,12 @@ class RMLUICORE_API Context : public ScriptInterface { private: String name; Vector2i dimensions; - float density_independent_pixel_ratio; + float density_independent_pixel_ratio = 1.f; String documents_base_tag = "body"; + // Wrapper around the render interface for tracking the render state. + RenderManager* render_manager; + SmallUnorderedSet active_themes; ContextInstancer* instancer; @@ -364,9 +367,6 @@ class RMLUICORE_API Context : public ScriptInterface { // itself can't be part of it. ElementSet drag_hover_chain; - // Wrapper around the render interface for tracking the render state. - RenderManager render_manager; - using DataModels = UnorderedMap>; DataModels data_models; @@ -374,7 +374,7 @@ class RMLUICORE_API Context : public ScriptInterface { // Time in seconds until Update and Render should be called again. This allows applications to only redraw the ui if needed. // See RequestNextUpdate() and NextUpdateRequested() for details. - double next_update_timeout; + double next_update_timeout = 0; // Internal callback for when an element is detached or removed from the hierarchy. void OnElementDetach(Element* element); diff --git a/Include/RmlUi/Core/ContextInstancer.h b/Include/RmlUi/Core/ContextInstancer.h index 111475ad0..7d1859a02 100644 --- a/Include/RmlUi/Core/ContextInstancer.h +++ b/Include/RmlUi/Core/ContextInstancer.h @@ -35,6 +35,7 @@ namespace Rml { +class RenderManager; class Context; class Event; @@ -50,8 +51,9 @@ class RMLUICORE_API ContextInstancer : public Releasable { /// Instances a context. /// @param[in] name Name of this context. + /// @param[in] render_manager The render manager used for this context. /// @return The instanced context. - virtual ContextPtr InstanceContext(const String& name) = 0; + virtual ContextPtr InstanceContext(const String& name, RenderManager* render_manager) = 0; /// Releases a context previously created by this context. /// @param[in] context The context to release. diff --git a/Include/RmlUi/Core/Core.h b/Include/RmlUi/Core/Core.h index beb142a47..1d7327429 100644 --- a/Include/RmlUi/Core/Core.h +++ b/Include/RmlUi/Core/Core.h @@ -66,7 +66,9 @@ RMLUICORE_API void SetSystemInterface(SystemInterface* system_interface); /// Returns RmlUi's system interface. RMLUICORE_API SystemInterface* GetSystemInterface(); -/// Sets the interface through which all rendering requests are made. This must be called before Initialise(). +/// Sets the interface through which all rendering requests are made. This is not required to be called, but if it is, +/// it must be called before Initialise(). If no render interface is specified, then all contexts must specify a render +/// interface when created. /// @param[in] render_interface A non-owning pointer to the render interface implementation. /// @lifetime The interface must be kept alive until after the call to Rml::Shutdown. RMLUICORE_API void SetRenderInterface(RenderInterface* render_interface); @@ -92,8 +94,11 @@ RMLUICORE_API FontEngineInterface* GetFontEngineInterface(); /// Creates a new element context. /// @param[in] name The new name of the context. This must be unique. /// @param[in] dimensions The initial dimensions of the new context. +/// @param[in] render_interface The custom render interface to use, or nullptr to use the default. +/// @lifetime If specified, the render interface must be kept alive until after the call to Rml::Shutdown. Alternatively, the render interface can be +/// destroyed after all contexts it belongs to have been destroyed and a subsequent call has been made to Rml::ReleaseTextures. /// @return A non-owning pointer to the new context, or nullptr if the context could not be created. -RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions); +RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions, RenderInterface* render_interface = nullptr); /// Removes and destroys a context. /// @param[in] name The name of the context to remove. /// @return True if name is a valid context, false otherwise. @@ -149,9 +154,11 @@ RMLUICORE_API EventId RegisterEventType(const String& type, bool interruptible, /// Returns a list of source URLs to textures in all loaded documents. RMLUICORE_API StringList GetTextureSourceList(); /// Forces all texture handles loaded and generated by RmlUi to be released. -RMLUICORE_API void ReleaseTextures(); +/// @param[in] render_interface Release all textures belonging to the given interface, or nullptr to release all textures in all interfaces. +RMLUICORE_API void ReleaseTextures(RenderInterface* render_interface = nullptr); /// Forces all compiled geometry handles generated by RmlUi to be released. -RMLUICORE_API void ReleaseCompiledGeometry(); +/// @param[in] render_interface Release all geometry belonging to the given interface, or nullptr to release all geometry in all interfaces. +RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = nullptr); /// Releases unused font textures and rendered glyphs to free up memory, and regenerates actively used fonts. /// @note Invalidates all existing FontFaceHandles returned from the font engine. RMLUICORE_API void ReleaseFontResources(); diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index 40a1925ce..d56e79a44 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -41,8 +41,9 @@ namespace Rml { class Element; class PropertyDictionary; struct Sprite; -struct Texture; +class Texture; class StyleSheet; +class RenderManager; class DecoratorInstancerInterface; /** @@ -76,13 +77,13 @@ class RMLUICORE_API Decorator { /// Adds a texture if it is valid into the list of textures in use by the decorator. /// @param[in] texture The texture to add. /// @return The index of the texture if it is successful, or -1 if it is invalid. - int AddTexture(const Texture& texture); + int AddTexture(Texture texture); /// Get number of textures in use by the decorator. int GetNumTextures() const; /// Returns one of the decorator's previously loaded textures. /// @param[in] index The index of the desired texture. /// @return The texture at the appropriate index, or nullptr if the index was invalid. - const Texture* GetTexture(int index = 0) const; + Texture GetTexture(int index = 0) const; private: // Stores a list of textures in use by this decorator. @@ -110,8 +111,8 @@ class RMLUICORE_API DecoratorInstancer : public EffectSpecification { class RMLUICORE_API DecoratorInstancerInterface { public: - DecoratorInstancerInterface(const StyleSheet& style_sheet, const PropertySource* property_source) : - style_sheet(style_sheet), property_source(property_source) + DecoratorInstancerInterface(RenderManager& render_manager, const StyleSheet& style_sheet, const PropertySource* property_source) : + render_manager(render_manager), style_sheet(style_sheet), property_source(property_source) {} /// Get a sprite from any @spritesheet in the style sheet the decorator is being instanced on. @@ -121,7 +122,11 @@ class RMLUICORE_API DecoratorInstancerInterface { /// This will use the document path where the 'decorator' property was declared to locate relative files, if available. Texture GetTexture(const String& filename) const; + /// Get the render manager for the decorator being instanced. + RenderManager& GetRenderManager() const; + private: + RenderManager& render_manager; const StyleSheet& style_sheet; const PropertySource* property_source; }; diff --git a/Include/RmlUi/Core/Element.h b/Include/RmlUi/Core/Element.h index 883371180..42830a03b 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -61,6 +61,7 @@ class InlineLevelBox; class ReplacedBox; class PropertiesIteratorView; class PropertyDictionary; +class RenderManager; class StyleSheet; class StyleSheetContainer; class TransformState; @@ -337,6 +338,9 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr; LineList lines; - GeometryList geometry; + struct TexturedGeometry { + Geometry geometry; + Texture texture; + }; + Vector geometry; // The decoration geometry we've generated for this string. UniquePtr decoration; diff --git a/Include/RmlUi/Core/Factory.h b/Include/RmlUi/Core/Factory.h index 7f6dfa92c..72be6ae90 100644 --- a/Include/RmlUi/Core/Factory.h +++ b/Include/RmlUi/Core/Factory.h @@ -55,6 +55,7 @@ class StyleSheetContainer; class PropertyDictionary; class PropertySpecification; class DecoratorInstancerInterface; +class RenderManager; enum class EventId : uint16_t; /** @@ -79,8 +80,9 @@ class RMLUICORE_API Factory { static void RegisterContextInstancer(ContextInstancer* instancer); /// Instances a new context. /// @param[in] name The name of the new context. + /// @param[in] render_manager The render manager used for the new context. /// @return The new context, or nullptr if no context could be created. - static ContextPtr InstanceContext(const String& name); + static ContextPtr InstanceContext(const String& name, RenderManager* render_manager); /// Registers a non-owning pointer to the element instancer that will be used to instance an element when the specified tag is encountered. /// @param[in] name Name of the instancer; elements with this as their tag will use this instancer. diff --git a/Include/RmlUi/Core/Filter.h b/Include/RmlUi/Core/Filter.h index d4e18b5b4..ffb04ce1f 100644 --- a/Include/RmlUi/Core/Filter.h +++ b/Include/RmlUi/Core/Filter.h @@ -37,6 +37,7 @@ namespace Rml { class Element; class PropertyDictionary; +class CompiledFilter; /** The abstract base class for visual filters that are applied when rendering the element. @@ -49,11 +50,7 @@ class RMLUICORE_API Filter { /// Called on a decorator to generate any required per-element data for a newly decorated element. /// @param[in] element The newly decorated element. /// @return A handle to a decorator-defined data handle, or nullptr if none is needed for the element. - virtual CompiledFilterHandle CompileFilter(Element* element) const = 0; - - /// Called to release element data generated by this decorator. - /// @param[in] element_data The element data handle to release. - virtual void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const = 0; + virtual CompiledFilter CompileFilter(Element* element) const = 0; /// Allows extending the area being affected by this filter beyond the border box of the element. /// @param[in] element The element the filter is being rendered on. @@ -61,9 +58,6 @@ class RMLUICORE_API Filter { /// @note Modifying the ink overflow rectangle affects rendering of all filter decorators active on the current element. /// @note Only affects the 'filter' property, not 'backdrop-filter'. virtual void ExtendInkOverflow(Element* element, Rectanglef& overflow) const; - - /// Value specifying an invalid or non-existent filter handle. - static constexpr CompiledFilterHandle INVALID_COMPILEDFILTERHANDLE = 0; }; /** diff --git a/Include/RmlUi/Core/FontEngineInterface.h b/Include/RmlUi/Core/FontEngineInterface.h index 1bacbd4bd..adea5d85d 100644 --- a/Include/RmlUi/Core/FontEngineInterface.h +++ b/Include/RmlUi/Core/FontEngineInterface.h @@ -29,8 +29,8 @@ #define RMLUI_CORE_FONTENGINEINTERFACE_H #include "FontMetrics.h" -#include "Geometry.h" #include "Header.h" +#include "Mesh.h" #include "StyleTypes.h" #include "Types.h" @@ -96,7 +96,8 @@ class RMLUICORE_API FontEngineInterface { /// @return The width, in pixels, this string will occupy if rendered with this handle. virtual int GetStringWidth(FontFaceHandle handle, const String& string, float letter_spacing, Character prior_character = Character::Null); - /// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text. + /// Called by RmlUi when it wants to retrieve the meshes required to render a single line of text. + /// @param[in] render_manager The render manager responsible for rendering the string. /// @param[in] face_handle The font handle. /// @param[in] font_effects_handle The handle to the prepared font effects for which the geometry should be generated. /// @param[in] string The string to render. @@ -104,10 +105,10 @@ class RMLUICORE_API FontEngineInterface { /// @param[in] colour The colour to render the text. /// @param[in] opacity The opacity of the text, should be applied to font effects. /// @param[in] letter_spacing The letter spacing size in pixels. - /// @param[out] geometry An array of geometries to generate the geometry into. - /// @return The width, in pixels, of the string geometry. - virtual int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position, - ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry); + /// @param[out] mesh_list A list to place the meshes and textures representing the string to be rendered. + /// @return The width, in pixels, of the string mesh. + virtual int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, + const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, TexturedMeshList& mesh_list); /// Called by RmlUi to determine if the text geometry is required to be re-generated. Whenever the returned version /// is changed, all geometry belonging to the given face handle will be re-generated. diff --git a/Include/RmlUi/Core/Geometry.h b/Include/RmlUi/Core/Geometry.h index df92b0dbe..92a433e47 100644 --- a/Include/RmlUi/Core/Geometry.h +++ b/Include/RmlUi/Core/Geometry.h @@ -29,85 +29,35 @@ #ifndef RMLUI_CORE_GEOMETRY_H #define RMLUI_CORE_GEOMETRY_H +#include "CompiledFilterShader.h" #include "Header.h" -#include "Vertex.h" -#include +#include "Mesh.h" +#include "Texture.h" +#include "UniqueRenderResource.h" namespace Rml { -class Context; -class Element; -struct Texture; -enum class ClipMaskOperation; -using GeometryDatabaseHandle = uint32_t; +class RenderManager; /** - A helper object for holding an array of vertices and indices, and compiling it as necessary when rendered. + A representation of geometry to be rendered through its underlying render interface. - @author Peter Curry + A unique resource constructed through the render manager. */ - -class RMLUICORE_API Geometry { +class RMLUICORE_API Geometry final : public UniqueRenderResource { public: - Geometry(); - - Geometry(const Geometry&) = delete; - Geometry& operator=(const Geometry&) = delete; - - Geometry(Geometry&& other) noexcept; - Geometry& operator=(Geometry&& other) noexcept; - - ~Geometry(); - - /// Attempts to compile the geometry if appropriate, then renders the geometry, compiled if it can. - /// @param[in] translation The translation of the geometry. - void Render(Vector2f translation); - - /// Render the geometry with a compiled shader. Requires that the geometry can be compiled. - /// @param[in] shader The shader to use for rendering the geometry. - /// @param[in] translation The translation of the geometry. - void RenderWithShader(CompiledShaderHandle shader, Vector2f translation); + enum class ReleaseMode { ReturnMesh, ClearMesh }; - /// Use the geometry to set the clip mask through the render interface. Requires that the geometry can be compiled. - /// @param[in] operation The clip mask operation to apply. - /// @param[in] translation The translation of the geometry. - void RenderToClipMask(ClipMaskOperation operation, Vector2f translation); + Geometry() = default; - /// Returns the geometry's vertices. If these are written to, Release() should be called to force a recompile. - /// @return The geometry's vertex array. - Vector& GetVertices(); - /// Returns the geometry's indices. If these are written to, Release() should be called to force a recompile. - /// @return The geometry's index array. - Vector& GetIndices(); + void Render(Vector2f translation, Texture texture = {}, const CompiledShader& shader = {}) const; - /// Gets the geometry's texture. - /// @return The geometry's texture. - const Texture* GetTexture() const; - /// Sets the geometry's texture. - void SetTexture(const Texture* texture); - - /// Releases any previously-compiled geometry, and forces any new geometry to have a compile attempted. - /// @param[in] clear_buffers True to also clear the vertex and index buffers, false to leave intact. - void Release(bool clear_buffers = false); - - /// Returns true if there is geometry to be rendered. - explicit operator bool() const; + Mesh Release(ReleaseMode mode = ReleaseMode::ReturnMesh); private: - // Move members from another geometry. - void MoveFrom(Geometry& other) noexcept; - - Vector vertices; - Vector indices; - const Texture* texture = nullptr; - - CompiledGeometryHandle compiled_geometry = 0; - bool compile_attempted = false; - - GeometryDatabaseHandle database_handle; + Geometry(RenderManager* render_manager, StableVectorIndex resource_handle); + friend class RenderManager; }; -using GeometryList = Vector; - } // namespace Rml #endif diff --git a/Source/Core/GeometryDatabase.h b/Include/RmlUi/Core/Mesh.h similarity index 63% rename from Source/Core/GeometryDatabase.h rename to Include/RmlUi/Core/Mesh.h index ebedf6a6d..147d78733 100644 --- a/Source/Core/GeometryDatabase.h +++ b/Include/RmlUi/Core/Mesh.h @@ -26,38 +26,28 @@ * */ -#ifndef RMLUI_CORE_GEOMETRYDATABASE_H -#define RMLUI_CORE_GEOMETRYDATABASE_H +#ifndef RMLUI_CORE_MESH_H +#define RMLUI_CORE_MESH_H -#include "../../Include/RmlUi/Core/Types.h" -#include +#include "Header.h" +#include "Texture.h" +#include "Vertex.h" namespace Rml { -class Geometry; -using GeometryDatabaseHandle = uint32_t; +struct Mesh { + Vector vertices; + Vector indices; -/** - The geometry database stores a reference to all active geometry. + explicit operator bool() const { return !indices.empty(); } +}; - The intention is for the user to be able to re-compile all geometry in use. +struct TexturedMesh { + Mesh mesh; + Texture texture; +}; - It is expected that every Insert() call is followed (at some later time) by - exactly one Erase() call with the same handle value. -*/ - -namespace GeometryDatabase { - - GeometryDatabaseHandle Insert(Geometry* geometry); - void Erase(GeometryDatabaseHandle handle); - - void ReleaseAll(); - -#ifdef RMLUI_TESTS_ENABLED - bool PrepareForTests(); - bool ListMatchesDatabase(const Vector& geometry_list); -#endif -} // namespace GeometryDatabase +using TexturedMeshList = Vector; } // namespace Rml #endif diff --git a/Include/RmlUi/Core/GeometryUtilities.h b/Include/RmlUi/Core/MeshUtilities.h similarity index 81% rename from Include/RmlUi/Core/GeometryUtilities.h rename to Include/RmlUi/Core/MeshUtilities.h index 7a776803f..7da2093ff 100644 --- a/Include/RmlUi/Core/GeometryUtilities.h +++ b/Include/RmlUi/Core/MeshUtilities.h @@ -26,26 +26,22 @@ * */ -#ifndef RMLUI_CORE_GEOMETRYUTILITIES_H -#define RMLUI_CORE_GEOMETRYUTILITIES_H +#ifndef RMLUI_CORE_MESHUTILITIES_H +#define RMLUI_CORE_MESHUTILITIES_H #include "Header.h" -#include "StyleTypes.h" #include "Types.h" -#include "Vertex.h" namespace Rml { class Box; -class Geometry; +struct Mesh; /** - A class containing helper functions for rendering geometry. + A class containing helper functions for generating meshes. - @author Robert Curry */ - -class RMLUICORE_API GeometryUtilities { +class RMLUICORE_API MeshUtilities { public: /// Generates a quad from a position, size and colour. /// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into. @@ -54,7 +50,7 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] dimensions The dimensions of the quad to generate. /// @param[in] colour The colour to be assigned to each of the quad's vertices. /// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array. - static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, int index_offset = 0); + static void GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour); /// Generates a quad from a position, size, colour and texture coordinates. /// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into. /// @param[out] indices An array of at least six indices that the generated index data will be written into. @@ -64,15 +60,15 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad. /// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad. /// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array. - static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, - Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset = 0); + static void GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, Vector2f top_left_texcoord, + Vector2f bottom_right_texcoord); /// Generates the geometry required to render a line. /// @param[out] geometry The geometry to append the newly created geometry into. /// @param[in] position The top-left position the line. /// @param[in] position The size of the line. /// @param[in] color The color to draw the line in. - static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color); + static void GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied color); /// Generates the geometry for an element's background and border, with support for the border-radius property. /// @param[out] geometry The geometry to append the newly created vertices and indices into. @@ -82,8 +78,8 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background. /// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order. /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. - static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, - ColourbPremultiplied background_colour, const ColourbPremultiplied border_colours[4]); + static void GenerateBackgroundBorder(Mesh& mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied background_colour, + const ColourbPremultiplied border_colours[4]); /// Generates the background geometry for an element's area, with support for border-radius. /// @param[out] geometry The geometry to append the newly created vertices and indices into. @@ -93,12 +89,11 @@ class RMLUICORE_API GeometryUtilities { /// @param[in] colour The colour applied to the background. /// @param[in] area Either the border, padding or content area to be filled. /// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized. - static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied colour, + static void GenerateBackground(Mesh& mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied colour, BoxArea area = BoxArea::Padding); private: - GeometryUtilities(); - ~GeometryUtilities(); + MeshUtilities() = delete; }; } // namespace Rml diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 008f4114c..b931d6b77 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -30,7 +30,6 @@ #define RMLUI_CORE_RENDERINTERFACE_H #include "Header.h" -#include "Texture.h" #include "Traits.h" #include "Types.h" #include "Vertex.h" @@ -53,8 +52,6 @@ enum class BlendMode { Discard, // Leave the destination colors unaltered. }; -using FilterHandleList = Vector; - /** The abstract base class for application-specific rendering implementation. Your application must provide a concrete implementation of this class and install it through Rml::SetRenderInterface() in order for anything to be rendered. diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index cea43fad2..808a63110 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -29,13 +29,20 @@ #ifndef RMLUI_CORE_RENDERMANAGER_H #define RMLUI_CORE_RENDERMANAGER_H -#include "Box.h" +#include "CallbackTexture.h" +#include "Mesh.h" #include "RenderInterface.h" +#include "StableVector.h" #include "Types.h" namespace Rml { class Geometry; +class CompiledFilter; +class CompiledShader; +class TextureDatabase; +class Texture; +class RenderManagerAccess; struct ClipMaskGeometry { ClipMaskOperation operation; @@ -60,15 +67,18 @@ struct RenderState { }; /** - A wrapper over the render interface which tracks the following state: - - Scissor - - Clip mask - - Transform - All such operations on the render interface should go through this class. + A wrapper over the render interface, which tracks its state and resources. + + All operations to be submitted to the render interface should go through this class. */ class RMLUICORE_API RenderManager : NonCopyMoveable { public: - RenderManager(); + RenderManager(RenderInterface* render_interface); + ~RenderManager(); + + void PrepareRender(); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; void DisableScissorRegion(); void SetScissorRegion(Rectanglei region); @@ -79,20 +89,60 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { void SetTransform(const Matrix4f* new_transform); + // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are + // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. const RenderState& GetState() const { return state; } void SetState(const RenderState& next); void ResetState(); - void BeginRender(); - void SetViewport(Vector2i dimensions); - Vector2i GetViewport() const; + Geometry MakeGeometry(Mesh&& mesh); + + Texture LoadTexture(const String& source, const String& document_path = String()); + CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); + + CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); + CompiledShader CompileShader(const String& name, const Dictionary& parameters); + + void PushLayer(LayerFill layer_fill); + void PopLayer(BlendMode blend_mode, const FilterHandleList& filters); + + CompiledFilter SaveLayerAsMaskImage(); private: void ApplyClipMask(const ClipMaskGeometryList& clip_elements); - RenderState state; + StableVectorIndex InsertGeometry(Mesh&& mesh); + CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); + + void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + void GetTextureSourceList(StringList& source_list) const; + + void ReleaseAllTextures(); + void ReleaseAllCompiledGeometry(); + + void ReleaseResource(const CallbackTexture& texture); + Mesh ReleaseResource(const Geometry& geometry); + void ReleaseResource(const CompiledFilter& filter); + void ReleaseResource(const CompiledShader& shader); + + struct GeometryData { + Mesh mesh; + CompiledGeometryHandle handle = {}; + }; + RenderInterface* render_interface = nullptr; + + StableVector geometry_list; + UniquePtr texture_database; + + int compiled_filter_count = 0; + int compiled_shader_count = 0; + + RenderState state; Vector2i viewport_dimensions; + + friend class RenderManagerAccess; }; } // namespace Rml diff --git a/Include/RmlUi/Core/Spritesheet.h b/Include/RmlUi/Core/Spritesheet.h index 8d705e2bd..003e411fe 100644 --- a/Include/RmlUi/Core/Spritesheet.h +++ b/Include/RmlUi/Core/Spritesheet.h @@ -46,14 +46,11 @@ using SpriteMap = UnorderedMap; // key: sprite name (as given in */ struct Spritesheet { String name; - String image_source; - String definition_source; int definition_line_number; float display_scale; // The inverse of the 'resolution' spritesheet property. - Texture texture; + TextureSource texture_source; - Spritesheet(const String& name, const String& image_source, const String& definition_source, int definition_line_number, float display_scale, - const Texture& texture); + Spritesheet(const String& name, const String& source, const String& document_path, int definition_line_number, float display_scale); }; using SpriteDefinitionList = Vector>; // Sprite name and rectangle diff --git a/Include/RmlUi/Core/StableVector.h b/Include/RmlUi/Core/StableVector.h new file mode 100644 index 000000000..1e463932d --- /dev/null +++ b/Include/RmlUi/Core/StableVector.h @@ -0,0 +1,124 @@ +/* + * 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 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_CORE_STABLEVECTOR_H +#define RMLUI_CORE_STABLEVECTOR_H + +#include "Header.h" +#include "Types.h" +#include +#include + +namespace Rml { + +/** + A vector-like container that returns stable indices to refer to entries. + + The indices are only invalidated when the element is erased. Pointers on the other hand are invalidated just like for a + vector. The container is implemented as a vector with a separate bit mask to track free slots. For simplicity, freed + slots are simply replaced with value-initialized elements. + */ +template +class StableVector { +public: + StableVector() = default; + + StableVectorIndex insert(T value) + { + const auto it_free = std::find(free_slots.begin(), free_slots.end(), true); + StableVectorIndex index; + if (it_free == free_slots.end()) + { + index = StableVectorIndex(elements.size()); + elements.push_back(std::move(value)); + free_slots.push_back(false); + } + else + { + const size_t numeric_index = static_cast(it_free - free_slots.begin()); + index = static_cast(numeric_index); + elements[numeric_index] = std::move(value); + *it_free = false; + } + return index; + } + + bool empty() const { return elements.size() == count_free_slots(); } + size_t size() const { return elements.size() - count_free_slots(); } + + void reserve(size_t reserve_size) + { + elements.reserve(reserve_size); + free_slots.reserve(reserve_size); + } + + void clear() + { + elements.clear(); + free_slots.clear(); + } + T erase(StableVectorIndex index) + { + RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]); + free_slots[size_t(index)] = true; + return std::exchange(elements[size_t(index)], T()); + } + + T& operator[](StableVectorIndex index) + { + RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]); + return elements[size_t(index)]; + } + const T& operator[](StableVectorIndex index) const + { + RMLUI_ASSERT(size_t(index) < elements.size() && !free_slots[size_t(index)]); + return elements[size_t(index)]; + } + + // Iterate over every item in the vector, skipping free slots. + template + void for_each(Func&& func) + { + for (size_t i = 0; i < elements.size(); i++) + { + if (!free_slots[i]) + func(elements[i]); + } + } + +private: + size_t count_free_slots() const { return std::count(free_slots.begin(), free_slots.end(), true); } + + // List of all active elements, including any free slots. + Vector elements; + // Declares free slots in 'elements'. + Vector free_slots; +}; + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/StyleSheet.h b/Include/RmlUi/Core/StyleSheet.h index 8a3a2d2db..c6f87f8fc 100644 --- a/Include/RmlUi/Core/StyleSheet.h +++ b/Include/RmlUi/Core/StyleSheet.h @@ -40,6 +40,7 @@ class Element; class ElementDefinition; class StyleSheetNode; class Decorator; +class RenderManager; class SpritesheetList; class StyleSheetContainer; class StyleSheetParser; @@ -84,7 +85,8 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable { SharedPtr GetElementDefinition(const Element* element) const; /// Returns a list of instanced decorators from the declarations. The instances are cached for faster future retrieval. - const DecoratorPtrList& InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* decorator_source) const; + const DecoratorPtrList& InstanceDecorators(RenderManager& render_manager, const DecoratorDeclarationList& declaration_list, + const PropertySource* decorator_source) const; private: StyleSheet(); diff --git a/Include/RmlUi/Core/Texture.h b/Include/RmlUi/Core/Texture.h index 024b85428..71d9f38fe 100644 --- a/Include/RmlUi/Core/Texture.h +++ b/Include/RmlUi/Core/Texture.h @@ -34,55 +34,52 @@ namespace Rml { -class TextureResource; -class RenderInterface; - -/* - Callback function for generating textures. - /// @param[in] render_interface The render interface to use for generating the texture. - /// @param[in] name The name used to set the texture. - /// @param[out] handle The texture handle obtained through the render interface. - /// @param[out] dimensions The width and height of the generated texture. - /// @return True on success. -*/ -using TextureCallback = Function; +class CallbackTexture; +class RenderManager; /** - Abstraction of a two-dimensional texture image, with an application-specific texture handle. + Texture is a simple view of either a file texture or a callback texture. - @author Peter Curry + It is constructed through the render manager. It can be freely copied, and does not own or release the underlying + resource. The user is responsible for ensuring that the lifetime of the texture is valid. */ - -struct RMLUICORE_API Texture { +class RMLUICORE_API Texture { public: - /// Set the texture source and path. The texture is added to the global cache and only loaded on first use. - /// @param[in] source The source of the texture. - /// @param[in] source_path The path of the resource that is requesting the texture (ie, the RCSS file in which it was specified, etc). - void Set(const String& source, const String& source_path = ""); - - /// Set a callback function for generating the texture on first use. The texture is never added to the global cache. - /// @param[in] name The name of the texture. - /// @param[in] callback The callback function which generates the data of the texture, see TextureCallback. - void Set(const String& name, TextureCallback&& callback); - - /// Returns the texture's source name. This is usually the name of the file the texture was loaded from. - /// @return The name of the this texture's source. This will be the empty string if this texture is not loaded. - const String& GetSource() const; - /// Returns the texture's handle, will attempt to load the texture as necessary. - /// @return The texture's handle. This will be 0 if the texture cannot be loaded. - TextureHandle GetHandle() const; - /// Returns the texture's dimensions, will attempt to load the texture as necessary. - /// @return The texture's dimensions. This will be (0, 0) if the texture cannot be loaded. - Vector2i GetDimensions() const; + Texture() = default; - /// Returns true if the texture points to the same underlying resource. - bool operator==(const Texture&) const; + Vector2i GetDimensions() const; - /// Returns true if the underlying resource is set. explicit operator bool() const; + bool operator==(const Texture& other) const; + +private: + Texture(RenderManager* render_manager, TextureFileIndex file_index); + Texture(RenderManager* render_manager, StableVectorIndex callback_index); + + RenderManager* render_manager = nullptr; + TextureFileIndex file_index = TextureFileIndex::Invalid; + StableVectorIndex callback_index = StableVectorIndex::Invalid; + + friend class RenderManager; + friend class CallbackTexture; +}; + +/** + Stores the file source for a texture, which is used to generate textures possibly for multiple render managers. + */ +class RMLUICORE_API TextureSource : NonCopyMoveable { +public: + TextureSource() = default; + TextureSource(String source, String document_path); + + Texture GetTexture(RenderManager& render_manager) const; + + const String& GetDefinitionSource() const; private: - SharedPtr resource; + String source; + String document_path; + mutable SmallUnorderedMap textures; }; } // namespace Rml diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index 0fda323d7..4074a4d8d 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -32,8 +32,8 @@ #include "../Config/Config.h" #include "Traits.h" #include -#include #include +#include namespace Rml { @@ -78,6 +78,8 @@ using Matrix4f = RMLUI_MATRIX4_TYPE; class Element; class ElementInstancer; class ElementAnimation; +class RenderManager; +class Texture; class Context; class Event; class Property; @@ -112,6 +114,9 @@ using ElementPtr = UniqueReleaserPtr; using ContextPtr = UniqueReleaserPtr; using EventPtr = UniqueReleaserPtr; +enum class StableVectorIndex : uint32_t { Invalid = uint32_t(-1) }; +enum class TextureFileIndex : uint32_t { Invalid = uint32_t(-1) }; + // Container types for common classes using ElementList = Vector; using OwnedElementList = Vector; @@ -133,6 +138,7 @@ struct FontEffects { }; using ColorStopList = Vector; using BoxShadowList = Vector; +using FilterHandleList = Vector; // Additional smart pointers using TransformPtr = SharedPtr; diff --git a/Include/RmlUi/Core/UniqueRenderResource.h b/Include/RmlUi/Core/UniqueRenderResource.h new file mode 100644 index 000000000..899150d7d --- /dev/null +++ b/Include/RmlUi/Core/UniqueRenderResource.h @@ -0,0 +1,87 @@ +/* + * 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_CORE_UNIQUERENDERRESOURCE_H +#define RMLUI_CORE_UNIQUERENDERRESOURCE_H + +#include "Types.h" +#include + +namespace Rml { + +class RenderManager; + +/** + Abstraction for a uniquely owned render resource. The underlying resource is released upon destruction. + */ +template +class RMLUICORE_API UniqueRenderResource { +public: + static constexpr Handle InvalidHandle() { return InvalidHandleValue; } + + explicit operator bool() const { return resource_handle != InvalidHandleValue; } + +protected: + UniqueRenderResource() = default; + UniqueRenderResource(RenderManager* render_manager, Handle resource_handle) : render_manager(render_manager), resource_handle(resource_handle) {} + + ~UniqueRenderResource() noexcept { ReleaseInDerived(); } + + UniqueRenderResource(const UniqueRenderResource&) = delete; + UniqueRenderResource& operator=(const UniqueRenderResource&) = delete; + + UniqueRenderResource(UniqueRenderResource&& other) noexcept { MoveFrom(other); } + UniqueRenderResource& operator=(UniqueRenderResource&& other) noexcept + { + ReleaseInDerived(); + MoveFrom(other); + return *this; + } + + void Clear() noexcept + { + render_manager = nullptr; + resource_handle = InvalidHandleValue; + } + + RenderManager* render_manager = nullptr; + Handle resource_handle = InvalidHandleValue; + +private: + void ReleaseInDerived() { static_cast(this)->Release(); } + + void MoveFrom(UniqueRenderResource& other) noexcept + { + render_manager = std::exchange(other.render_manager, nullptr); + resource_handle = std::exchange(other.resource_handle, InvalidHandleValue); + } +}; + +} // namespace Rml + +#endif diff --git a/Include/RmlUi/Lottie/ElementLottie.h b/Include/RmlUi/Lottie/ElementLottie.h index f8db06cac..754df0b32 100644 --- a/Include/RmlUi/Lottie/ElementLottie.h +++ b/Include/RmlUi/Lottie/ElementLottie.h @@ -29,10 +29,10 @@ #ifndef RMLUI_LOTTIE_ELEMENT_LOTTIE_H #define RMLUI_LOTTIE_ELEMENT_LOTTIE_H +#include "../Core/CallbackTexture.h" #include "../Core/Element.h" #include "../Core/Geometry.h" #include "../Core/Header.h" -#include "../Core/Texture.h" namespace rlottie { class Animation; @@ -81,7 +81,7 @@ class RMLUICORE_API ElementLottie : public Element { bool texture_size_dirty = false; // The texture this element is rendering from. - Texture texture; + CallbackTexture texture; // The texture data buffer. size_t texture_data_size = 0; UniquePtr texture_data; diff --git a/Include/RmlUi/SVG/ElementSVG.h b/Include/RmlUi/SVG/ElementSVG.h index 67614ac4d..d010af166 100644 --- a/Include/RmlUi/SVG/ElementSVG.h +++ b/Include/RmlUi/SVG/ElementSVG.h @@ -29,10 +29,10 @@ #ifndef RMLUI_SVG_ELEMENT_SVG_H #define RMLUI_SVG_ELEMENT_SVG_H +#include "../Core/CallbackTexture.h" #include "../Core/Element.h" #include "../Core/Geometry.h" #include "../Core/Header.h" -#include "../Core/Texture.h" namespace lunasvg { class Document; @@ -78,7 +78,7 @@ class RMLUICORE_API ElementSVG : public Element { bool texture_dirty = false; // The texture this element is rendering from. - Texture texture; + CallbackTexture texture; // The image's intrinsic dimensions. Vector2f intrinsic_dimensions; diff --git a/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp b/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp index bac784697..add499685 100644 --- a/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp +++ b/Samples/basic/bitmapfont/src/FontEngineBitmap.cpp @@ -29,7 +29,7 @@ #include "FontEngineBitmap.h" #include #include -#include +#include #include #include @@ -84,12 +84,9 @@ bool LoadFontFace(const String& file_name) parser.metrics.underline_thickness = 1.f; } - Texture texture; - texture.Set(parser.texture_name, file_name); - // Construct and add the font face - fonts.push_back(Rml::MakeUnique(parser.family, parser.style, parser.weight, parser.metrics, texture, parser.texture_dimensions, - std::move(parser.glyphs), std::move(parser.kerning))); + fonts.push_back(Rml::MakeUnique(parser.family, parser.style, parser.weight, parser.metrics, parser.texture_name, file_name, + parser.texture_dimensions, std::move(parser.glyphs), std::move(parser.kerning))); return true; } @@ -125,11 +122,11 @@ FontFaceBitmap* GetFontFaceHandle(const String& family, FontStyle style, FontWei } // namespace FontProviderBitmap -FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions, - FontGlyphs&& glyphs, FontKerning&& kerning) : +FontFaceBitmap::FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path, + Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning) : family(family), - style(style), weight(weight), metrics(metrics), texture(texture), texture_dimensions(texture_dimensions), glyphs(std::move(glyphs)), - kerning(std::move(kerning)) + style(style), weight(weight), metrics(metrics), texture_source(texture_name, texture_path), texture_dimensions(texture_dimensions), + glyphs(std::move(glyphs)), kerning(std::move(kerning)) {} int FontFaceBitmap::GetStringWidth(const String& string, Character previous_character) @@ -155,17 +152,18 @@ int FontFaceBitmap::GetStringWidth(const String& string, Character previous_char return width; } -int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_position, ColourbPremultiplied colour, GeometryList& geometry_list) +int FontFaceBitmap::GenerateString(RenderManager& render_manager, const String& string, const Vector2f& string_position, ColourbPremultiplied colour, + TexturedMeshList& mesh_list) { int width = 0; - geometry_list.resize(1); - Rml::Geometry& geometry = geometry_list[0]; + mesh_list.resize(1); - geometry.SetTexture(&texture); + mesh_list[0].texture = texture_source.GetTexture(render_manager); - auto& vertices = geometry.GetVertices(); - auto& indices = geometry.GetIndices(); + Rml::Mesh& mesh = mesh_list[0].mesh; + auto& vertices = mesh.vertices; + auto& indices = mesh.indices; vertices.reserve(string.size() * 4); indices.reserve(string.size() * 6); @@ -195,8 +193,7 @@ int FontFaceBitmap::GenerateString(const String& string, const Vector2f& string_ Vector2f uv_top_left = glyph.position / texture_dimensions; Vector2f uv_bottom_right = (glyph.position + glyph.dimension) / texture_dimensions; - Rml::GeometryUtilities::GenerateQuad(&vertices[0] + (vertices.size() - 4), &indices[0] + (indices.size() - 6), - Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right, (int)vertices.size() - 4); + Rml::MeshUtilities::GenerateQuad(mesh, Vector2f(position + glyph.offset).Round(), glyph.dimension, colour, uv_top_left, uv_bottom_right); width += glyph.advance; position.x += glyph.advance; diff --git a/Samples/basic/bitmapfont/src/FontEngineBitmap.h b/Samples/basic/bitmapfont/src/FontEngineBitmap.h index f02cb87d1..3b1f5d993 100644 --- a/Samples/basic/bitmapfont/src/FontEngineBitmap.h +++ b/Samples/basic/bitmapfont/src/FontEngineBitmap.h @@ -35,6 +35,7 @@ #include class FontFaceBitmap; +using Rml::TextureSource; namespace FontProviderBitmap { void Initialise(); @@ -58,14 +59,14 @@ using FontKerning = Rml::UnorderedMap; class FontFaceBitmap { public: - FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, Texture texture, Vector2f texture_dimensions, - FontGlyphs&& glyphs, FontKerning&& kerning); + FontFaceBitmap(String family, FontStyle style, FontWeight weight, FontMetrics metrics, String texture_name, String texture_path, + Vector2f texture_dimensions, FontGlyphs&& glyphs, FontKerning&& kerning); // Get width of string. int GetStringWidth(const String& string, Character prior_character); // Generate the string geometry, returning its width. - int GenerateString(const String& string, const Vector2f& position, ColourbPremultiplied colour, GeometryList& geometry); + int GenerateString(RenderManager& render_manager, const String& string, const Vector2f& position, ColourbPremultiplied colour, TexturedMeshList& mesh_list); const FontMetrics& GetMetrics() const { return metrics; } @@ -82,7 +83,7 @@ class FontFaceBitmap { FontMetrics metrics; - Texture texture; + TextureSource texture_source; Vector2f texture_dimensions; FontGlyphs glyphs; diff --git a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp index 907497dc8..d88a44e62 100644 --- a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp +++ b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp @@ -80,11 +80,12 @@ int FontEngineInterfaceBitmap::GetStringWidth(FontFaceHandle handle, const Strin return handle_bitmap->GetStringWidth(string, prior_character); } -int FontEngineInterfaceBitmap::GenerateString(FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, const String& string, - const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/, float /*letter_spacing*/, GeometryList& geometry) +int FontEngineInterfaceBitmap::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, + const String& string, const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/, float /*letter_spacing*/, + TexturedMeshList& mesh_list) { auto handle_bitmap = reinterpret_cast(handle); - return handle_bitmap->GenerateString(string, position, colour, geometry); + return handle_bitmap->GenerateString(render_manager, string, position, colour, mesh_list); } int FontEngineInterfaceBitmap::GetVersion(FontFaceHandle /*handle*/) diff --git a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h index b1ca0079a..b970ebbf3 100644 --- a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h +++ b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.h @@ -48,7 +48,8 @@ using Rml::Style::FontWeight; using Rml::FontEffectList; using Rml::FontMetrics; -using Rml::GeometryList; +using Rml::RenderManager; +using Rml::TexturedMeshList; class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { public: @@ -76,8 +77,8 @@ class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { int GetStringWidth(FontFaceHandle handle, const String& string, float letter_spacing, Character prior_character = Character::Null) override; /// Called by RmlUi when it wants to retrieve the geometry required to render a single line of text. - int GenerateString(FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, const Vector2f& position, - ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) override; + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle font_effects_handle, const String& string, + const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, TexturedMeshList& mesh_list) override; /// Called by RmlUi to determine if the text geometry is required to be re-generated.eometry. int GetVersion(FontFaceHandle handle) override; diff --git a/Samples/invaders/src/DecoratorDefender.cpp b/Samples/invaders/src/DecoratorDefender.cpp index 833345ebc..2f2996793 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -29,10 +29,11 @@ #include "DecoratorDefender.h" #include #include -#include +#include #include +#include #include -#include +#include #include #include @@ -62,16 +63,16 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); Rml::Math::SnapToPixelGrid(position, size); - if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface()) + if (Rml::RenderManager* render_manager = element->GetRenderManager()) { - Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); + Rml::Texture texture = GetTexture(image_index); Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), size, color); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); + Rml::Geometry geometry = render_manager->MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); } } diff --git a/Samples/invaders/src/DecoratorStarfield.cpp b/Samples/invaders/src/DecoratorStarfield.cpp index 1202101e9..3a59dd484 100644 --- a/Samples/invaders/src/DecoratorStarfield.cpp +++ b/Samples/invaders/src/DecoratorStarfield.cpp @@ -32,12 +32,9 @@ #include #include #include -#include #include #include -#include #include -#include DecoratorStarfield::~DecoratorStarfield() {} @@ -126,7 +123,8 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData } } - DrawPoints(point_size, points); + if (Rml::RenderManager* render_manager = element->GetRenderManager()) + DrawPoints(*render_manager, point_size, points); } void DecoratorStarfield::StarField::Update(double t) diff --git a/Samples/invaders/src/Defender.cpp b/Samples/invaders/src/Defender.cpp index 0a4f2ea50..78ad4e200 100644 --- a/Samples/invaders/src/Defender.cpp +++ b/Samples/invaders/src/Defender.cpp @@ -93,17 +93,17 @@ void Defender::Update(double t) } } -void Defender::Render(float dp_ratio, Rml::TextureHandle texture) +void Defender::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) { Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); // Render our sprite if rendering is enabled if (render) - defender_sprite.Render(position, dp_ratio, color, texture); + defender_sprite.Render(render_manager, position, dp_ratio, color, texture); // Render the bullet if (bullet_in_flight) - bullet_sprite.Render(bullet_position, dp_ratio, color, texture); + bullet_sprite.Render(render_manager, bullet_position, dp_ratio, color, texture); } void Defender::StartMove(float direction) diff --git a/Samples/invaders/src/Defender.h b/Samples/invaders/src/Defender.h index 9bb84ded8..8b7a0919f 100644 --- a/Samples/invaders/src/Defender.h +++ b/Samples/invaders/src/Defender.h @@ -47,7 +47,7 @@ class Defender { /// Update the defender state. void Update(double t); /// Render the defender. - void Render(float dp_ratio, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); /// Move the defender left. void StartMove(float direction); diff --git a/Samples/invaders/src/ElementGame.cpp b/Samples/invaders/src/ElementGame.cpp index a03345630..247c58f0e 100644 --- a/Samples/invaders/src/ElementGame.cpp +++ b/Samples/invaders/src/ElementGame.cpp @@ -90,7 +90,8 @@ void ElementGame::OnUpdate() void ElementGame::OnRender() { - game->Render(GetContext()->GetDensityIndependentPixelRatio()); + if (Rml::Context* context = GetContext()) + game->Render(context->GetRenderManager(), context->GetDensityIndependentPixelRatio()); } void ElementGame::OnChildAdd(Rml::Element* element) diff --git a/Samples/invaders/src/Game.cpp b/Samples/invaders/src/Game.cpp index e51307bd5..8c0c10ea1 100644 --- a/Samples/invaders/src/Game.cpp +++ b/Samples/invaders/src/Game.cpp @@ -79,8 +79,6 @@ Game::Game() for (int i = 0; i < NUM_SHIELDS; i++) shields[i] = nullptr; - texture.Set("invaders/data/invaders.tga"); - defender = new Defender(this); } @@ -139,22 +137,23 @@ void Game::Update(double t) } } -void Game::Render(float dp_ratio) +void Game::Render(Rml::RenderManager& render_manager, float dp_ratio) { if (defender_lives <= 0) return; - Rml::TextureHandle texture_handle = texture.GetHandle(); + if (!texture) + texture = render_manager.LoadTexture("invaders/data/invaders.tga"); // Render all available shields for (int i = 0; i < NUM_SHIELDS; i++) - shields[i]->Render(dp_ratio); + shields[i]->Render(render_manager, dp_ratio); // Render all available invaders for (int i = 0; i < NUM_INVADERS + 1; i++) - invaders[i]->Render(dp_ratio, texture_handle); + invaders[i]->Render(render_manager, dp_ratio, texture); - defender->Render(dp_ratio, texture_handle); + defender->Render(render_manager, dp_ratio, texture); } Defender* Game::GetDefender() diff --git a/Samples/invaders/src/Game.h b/Samples/invaders/src/Game.h index 936e9b18f..860c07089 100644 --- a/Samples/invaders/src/Game.h +++ b/Samples/invaders/src/Game.h @@ -57,7 +57,7 @@ class Game { void Update(double t); /// Render the game - void Render(float dp_ratio); + void Render(Rml::RenderManager& render_manager, float dp_ratio); /// Access the defender Defender* GetDefender(); diff --git a/Samples/invaders/src/Invader.cpp b/Samples/invaders/src/Invader.cpp index 9bf248d87..803f7beb1 100644 --- a/Samples/invaders/src/Invader.cpp +++ b/Samples/invaders/src/Invader.cpp @@ -171,7 +171,7 @@ void Invader::UpdateAnimation() } } -void Invader::Render(float dp_ratio, Rml::TextureHandle texture) +void Invader::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) { Rml::ColourbPremultiplied color(255); @@ -182,10 +182,10 @@ void Invader::Render(float dp_ratio, Rml::TextureHandle texture) int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); if (state != DEAD) - invader_sprites[sprite_index].Render(Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); + invader_sprites[sprite_index].Render(render_manager, Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); if (bomb != NONE) - bomb_sprites[bomb_animation_frame].Render(bomb_position, dp_ratio, color, texture); + bomb_sprites[bomb_animation_frame].Render(render_manager, bomb_position, dp_ratio, color, texture); } Invader::InvaderState Invader::GetState() diff --git a/Samples/invaders/src/Invader.h b/Samples/invaders/src/Invader.h index bc6fc68b6..0e21e5a01 100644 --- a/Samples/invaders/src/Invader.h +++ b/Samples/invaders/src/Invader.h @@ -59,7 +59,7 @@ class Invader { virtual void Update(double t); /// Render the invader - void Render(float dp_ratio, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); /// Update the invaders animation void UpdateAnimation(); diff --git a/Samples/invaders/src/Shield.cpp b/Samples/invaders/src/Shield.cpp index 9c9c22130..9b61b04ab 100644 --- a/Samples/invaders/src/Shield.cpp +++ b/Samples/invaders/src/Shield.cpp @@ -116,7 +116,7 @@ const Rml::Vector2f& Shield::GetPosition() const return position; } -void Shield::Render(float dp_ratio) +void Shield::Render(Rml::RenderManager& render_manager, float dp_ratio) { if (health > 0) { @@ -139,7 +139,7 @@ void Shield::Render(float dp_ratio) } } - DrawPoints((float)scaled_pixel, points); + DrawPoints(render_manager, (float)scaled_pixel, points); } } diff --git a/Samples/invaders/src/Shield.h b/Samples/invaders/src/Shield.h index 1e2daf404..259380dae 100644 --- a/Samples/invaders/src/Shield.h +++ b/Samples/invaders/src/Shield.h @@ -61,7 +61,7 @@ class Shield { const Rml::Vector2f& GetPosition() const; /// Render the shield. - void Render(float dp_ratio); + void Render(Rml::RenderManager& render_manager, float dp_ratio); /// Returns true if the position hits the shield /// If a hit is detected, will degrade the shield. diff --git a/Samples/invaders/src/Sprite.cpp b/Samples/invaders/src/Sprite.cpp index e9aaf6676..9ca643929 100644 --- a/Samples/invaders/src/Sprite.cpp +++ b/Samples/invaders/src/Sprite.cpp @@ -28,9 +28,10 @@ #include "Sprite.h" #include -#include +#include #include -#include +#include +#include Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord) : dimensions(dimensions), top_left_texcoord(top_left_texcoord), bottom_right_texcoord(bottom_right_texcoord) @@ -38,53 +39,38 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te Sprite::~Sprite() {} -void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture) +void Sprite::Render(Rml::RenderManager& render_manager, Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, + Rml::Texture texture) { - Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); - if (!render_interface) - return; - position = dp_ratio * position; Rml::Vector2f dimensions_px = dp_ratio * dimensions; Rml::Math::SnapToPixelGrid(position, dimensions_px); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); } -void DrawPoints(float point_size, const ColoredPointList& points) +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points) { - Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); - if (!render_interface) - return; - constexpr int num_quad_vertices = 4; constexpr int num_quad_indices = 6; const int num_points = (int)points.size(); - Rml::Vector vertices(num_points * num_quad_vertices); - Rml::Vector indices(num_points * num_quad_indices); - - int vertex_offset = 0; - int index_offset = 0; + Rml::Mesh mesh; + mesh.vertices.reserve(num_points * num_quad_vertices); + mesh.indices.reserve(num_points * num_quad_indices); for (const ColoredPoint& point : points) { Rml::Vector2f position = point.position; Rml::Vector2f size = Rml::Vector2f(point_size); - Rml::GeometryUtilities::GenerateQuad(vertices.data() + vertex_offset, indices.data() + index_offset, position, size, point.color, - vertex_offset); - - vertex_offset += num_quad_vertices; - index_offset += num_quad_indices; + Rml::MeshUtilities::GenerateQuad(mesh, position, size, point.color); } - RMLUI_ASSERT(vertex_offset == (int)vertices.size()); - RMLUI_ASSERT(index_offset == (int)indices.size()); - - render_interface->RenderGeometry(vertices.data(), vertex_offset, indices.data(), index_offset, {}, Rml::Vector2f(0.f)); + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(Rml::Vector2f(0.f)); } diff --git a/Samples/invaders/src/Sprite.h b/Samples/invaders/src/Sprite.h index 6eac2e9a6..2b5e01cbf 100644 --- a/Samples/invaders/src/Sprite.h +++ b/Samples/invaders/src/Sprite.h @@ -29,6 +29,7 @@ #ifndef RMLUI_INVADERS_SPRITE_H #define RMLUI_INVADERS_SPRITE_H +#include #include /** @@ -40,7 +41,8 @@ class Sprite { Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); ~Sprite(); - void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, + Rml::Texture texture); Rml::Vector2f dimensions; Rml::Vector2f top_left_texcoord; @@ -53,6 +55,6 @@ struct ColoredPoint { }; using ColoredPointList = Rml::Vector; -void DrawPoints(float point_size, const ColoredPointList& points); +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points); #endif diff --git a/Samples/luainvaders/src/DecoratorDefender.cpp b/Samples/luainvaders/src/DecoratorDefender.cpp index 833345ebc..1214592a2 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -29,10 +29,11 @@ #include "DecoratorDefender.h" #include #include -#include +#include +#include #include #include -#include +#include #include #include @@ -62,16 +63,16 @@ void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataH Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); Rml::Math::SnapToPixelGrid(position, size); - if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface()) + if (Rml::RenderManager* render_manager = element->GetRenderManager()) { - Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); + Rml::Texture texture = GetTexture(image_index); Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), size, color); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); + Rml::Geometry geometry = render_manager->MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); } } diff --git a/Samples/luainvaders/src/DecoratorStarfield.cpp b/Samples/luainvaders/src/DecoratorStarfield.cpp index 1202101e9..3a59dd484 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.cpp +++ b/Samples/luainvaders/src/DecoratorStarfield.cpp @@ -32,12 +32,9 @@ #include #include #include -#include #include #include -#include #include -#include DecoratorStarfield::~DecoratorStarfield() {} @@ -126,7 +123,8 @@ void DecoratorStarfield::RenderElement(Rml::Element* element, Rml::DecoratorData } } - DrawPoints(point_size, points); + if (Rml::RenderManager* render_manager = element->GetRenderManager()) + DrawPoints(*render_manager, point_size, points); } void DecoratorStarfield::StarField::Update(double t) diff --git a/Samples/luainvaders/src/Defender.cpp b/Samples/luainvaders/src/Defender.cpp index 0a4f2ea50..78ad4e200 100644 --- a/Samples/luainvaders/src/Defender.cpp +++ b/Samples/luainvaders/src/Defender.cpp @@ -93,17 +93,17 @@ void Defender::Update(double t) } } -void Defender::Render(float dp_ratio, Rml::TextureHandle texture) +void Defender::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) { Rml::ColourbPremultiplied color = GameDetails::GetDefenderColour().ToPremultiplied(); // Render our sprite if rendering is enabled if (render) - defender_sprite.Render(position, dp_ratio, color, texture); + defender_sprite.Render(render_manager, position, dp_ratio, color, texture); // Render the bullet if (bullet_in_flight) - bullet_sprite.Render(bullet_position, dp_ratio, color, texture); + bullet_sprite.Render(render_manager, bullet_position, dp_ratio, color, texture); } void Defender::StartMove(float direction) diff --git a/Samples/luainvaders/src/Defender.h b/Samples/luainvaders/src/Defender.h index 106df1eb9..64003d5fa 100644 --- a/Samples/luainvaders/src/Defender.h +++ b/Samples/luainvaders/src/Defender.h @@ -47,7 +47,7 @@ class Defender { /// Update the defender state. void Update(double t); /// Render the defender. - void Render(float dp_ratio, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); /// Move the defender left. void StartMove(float direction); diff --git a/Samples/luainvaders/src/ElementGame.cpp b/Samples/luainvaders/src/ElementGame.cpp index 0e0d7eadb..8bec0fe21 100644 --- a/Samples/luainvaders/src/ElementGame.cpp +++ b/Samples/luainvaders/src/ElementGame.cpp @@ -88,7 +88,8 @@ void ElementGame::OnUpdate() void ElementGame::OnRender() { - game->Render(GetContext()->GetDensityIndependentPixelRatio()); + if (Rml::Context* context = GetContext()) + game->Render(context->GetRenderManager(), context->GetDensityIndependentPixelRatio()); } void ElementGame::OnChildAdd(Rml::Element* element) diff --git a/Samples/luainvaders/src/Game.cpp b/Samples/luainvaders/src/Game.cpp index 3062c14f7..bc100c787 100644 --- a/Samples/luainvaders/src/Game.cpp +++ b/Samples/luainvaders/src/Game.cpp @@ -79,8 +79,6 @@ Game::Game() for (int i = 0; i < NUM_SHIELDS; i++) shields[i] = nullptr; - texture.Set("luainvaders/data/invaders.tga"); - defender = new Defender(this); } @@ -137,22 +135,23 @@ void Game::Update(double t) } } -void Game::Render(float dp_ratio) +void Game::Render(Rml::RenderManager& render_manager, float dp_ratio) { if (defender_lives <= 0) return; - Rml::TextureHandle texture_handle = texture.GetHandle(); + if (!texture) + texture = render_manager.LoadTexture("luainvaders/data/invaders.tga"); // Render all available shields for (int i = 0; i < NUM_SHIELDS; i++) - shields[i]->Render(dp_ratio); + shields[i]->Render(render_manager, dp_ratio); // Render all available invaders for (int i = 0; i < NUM_INVADERS + 1; i++) - invaders[i]->Render(dp_ratio, texture_handle); + invaders[i]->Render(render_manager, dp_ratio, texture); - defender->Render(dp_ratio, texture_handle); + defender->Render(render_manager, dp_ratio, texture); } Defender* Game::GetDefender() diff --git a/Samples/luainvaders/src/Game.h b/Samples/luainvaders/src/Game.h index 1acd060e2..116d267a8 100644 --- a/Samples/luainvaders/src/Game.h +++ b/Samples/luainvaders/src/Game.h @@ -57,7 +57,7 @@ class Game { void Update(double t); /// Render the game - void Render(float dp_ratio); + void Render(Rml::RenderManager& render_manager, float dp_ratio); /// Access the defender Defender* GetDefender(); diff --git a/Samples/luainvaders/src/Invader.cpp b/Samples/luainvaders/src/Invader.cpp index 9bf248d87..803f7beb1 100644 --- a/Samples/luainvaders/src/Invader.cpp +++ b/Samples/luainvaders/src/Invader.cpp @@ -171,7 +171,7 @@ void Invader::UpdateAnimation() } } -void Invader::Render(float dp_ratio, Rml::TextureHandle texture) +void Invader::Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture) { Rml::ColourbPremultiplied color(255); @@ -182,10 +182,10 @@ void Invader::Render(float dp_ratio, Rml::TextureHandle texture) int sprite_offset = int((invader_sprites[sprite_index].dimensions.x - 48) / 2); if (state != DEAD) - invader_sprites[sprite_index].Render(Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); + invader_sprites[sprite_index].Render(render_manager, Rml::Vector2f(position.x - sprite_offset, position.y), dp_ratio, color, texture); if (bomb != NONE) - bomb_sprites[bomb_animation_frame].Render(bomb_position, dp_ratio, color, texture); + bomb_sprites[bomb_animation_frame].Render(render_manager, bomb_position, dp_ratio, color, texture); } Invader::InvaderState Invader::GetState() diff --git a/Samples/luainvaders/src/Invader.h b/Samples/luainvaders/src/Invader.h index 780d6eafe..e173d2d17 100644 --- a/Samples/luainvaders/src/Invader.h +++ b/Samples/luainvaders/src/Invader.h @@ -59,7 +59,7 @@ class Invader { virtual void Update(double t); /// Render the invader - void Render(float dp_ratio, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, float dp_ratio, Rml::Texture texture); /// Update the invaders animation void UpdateAnimation(); diff --git a/Samples/luainvaders/src/Shield.cpp b/Samples/luainvaders/src/Shield.cpp index 9c9c22130..9b61b04ab 100644 --- a/Samples/luainvaders/src/Shield.cpp +++ b/Samples/luainvaders/src/Shield.cpp @@ -116,7 +116,7 @@ const Rml::Vector2f& Shield::GetPosition() const return position; } -void Shield::Render(float dp_ratio) +void Shield::Render(Rml::RenderManager& render_manager, float dp_ratio) { if (health > 0) { @@ -139,7 +139,7 @@ void Shield::Render(float dp_ratio) } } - DrawPoints((float)scaled_pixel, points); + DrawPoints(render_manager, (float)scaled_pixel, points); } } diff --git a/Samples/luainvaders/src/Shield.h b/Samples/luainvaders/src/Shield.h index ebe8d128f..16d4073dd 100644 --- a/Samples/luainvaders/src/Shield.h +++ b/Samples/luainvaders/src/Shield.h @@ -61,7 +61,7 @@ class Shield { const Rml::Vector2f& GetPosition() const; /// Render the shield. - void Render(float dp_ratio); + void Render(Rml::RenderManager& render_manager, float dp_ratio); /// Returns true if the position hits the shield /// If a hit is detected, will degrade the shield. diff --git a/Samples/luainvaders/src/Sprite.cpp b/Samples/luainvaders/src/Sprite.cpp index e9aaf6676..9ca643929 100644 --- a/Samples/luainvaders/src/Sprite.cpp +++ b/Samples/luainvaders/src/Sprite.cpp @@ -28,9 +28,10 @@ #include "Sprite.h" #include -#include +#include #include -#include +#include +#include Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord) : dimensions(dimensions), top_left_texcoord(top_left_texcoord), bottom_right_texcoord(bottom_right_texcoord) @@ -38,53 +39,38 @@ Sprite::Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_te Sprite::~Sprite() {} -void Sprite::Render(Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture) +void Sprite::Render(Rml::RenderManager& render_manager, Rml::Vector2f position, const float dp_ratio, Rml::ColourbPremultiplied color, + Rml::Texture texture) { - Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); - if (!render_interface) - return; - position = dp_ratio * position; Rml::Vector2f dimensions_px = dp_ratio * dimensions; Rml::Math::SnapToPixelGrid(position, dimensions_px); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), dimensions_px, color, top_left_texcoord, bottom_right_texcoord); - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(position, texture); } -void DrawPoints(float point_size, const ColoredPointList& points) +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points) { - Rml::RenderInterface* render_interface = Rml::GetRenderInterface(); - if (!render_interface) - return; - constexpr int num_quad_vertices = 4; constexpr int num_quad_indices = 6; const int num_points = (int)points.size(); - Rml::Vector vertices(num_points * num_quad_vertices); - Rml::Vector indices(num_points * num_quad_indices); - - int vertex_offset = 0; - int index_offset = 0; + Rml::Mesh mesh; + mesh.vertices.reserve(num_points * num_quad_vertices); + mesh.indices.reserve(num_points * num_quad_indices); for (const ColoredPoint& point : points) { Rml::Vector2f position = point.position; Rml::Vector2f size = Rml::Vector2f(point_size); - Rml::GeometryUtilities::GenerateQuad(vertices.data() + vertex_offset, indices.data() + index_offset, position, size, point.color, - vertex_offset); - - vertex_offset += num_quad_vertices; - index_offset += num_quad_indices; + Rml::MeshUtilities::GenerateQuad(mesh, position, size, point.color); } - RMLUI_ASSERT(vertex_offset == (int)vertices.size()); - RMLUI_ASSERT(index_offset == (int)indices.size()); - - render_interface->RenderGeometry(vertices.data(), vertex_offset, indices.data(), index_offset, {}, Rml::Vector2f(0.f)); + Rml::Geometry geometry = render_manager.MakeGeometry(std::move(mesh)); + geometry.Render(Rml::Vector2f(0.f)); } diff --git a/Samples/luainvaders/src/Sprite.h b/Samples/luainvaders/src/Sprite.h index 8ed401f42..e202dfe08 100644 --- a/Samples/luainvaders/src/Sprite.h +++ b/Samples/luainvaders/src/Sprite.h @@ -29,6 +29,7 @@ #ifndef RMLUI_LUAINVADERS_SPRITE_H #define RMLUI_LUAINVADERS_SPRITE_H +#include #include /** @@ -40,7 +41,7 @@ class Sprite { Sprite(const Rml::Vector2f& dimensions, const Rml::Vector2f& top_left_texcoord, const Rml::Vector2f& bottom_right_texcoord); ~Sprite(); - void Render(Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::TextureHandle texture); + void Render(Rml::RenderManager& render_manager, Rml::Vector2f position, float dp_ratio, Rml::ColourbPremultiplied color, Rml::Texture texture); Rml::Vector2f dimensions; Rml::Vector2f top_left_texcoord; @@ -53,6 +54,6 @@ struct ColoredPoint { }; using ColoredPointList = Rml::Vector; -void DrawPoints(float point_size, const ColoredPointList& points); +void DrawPoints(Rml::RenderManager& render_manager, float point_size, const ColoredPointList& points); #endif diff --git a/Source/Core/CallbackTexture.cpp b/Source/Core/CallbackTexture.cpp new file mode 100644 index 000000000..286211448 --- /dev/null +++ b/Source/Core/CallbackTexture.cpp @@ -0,0 +1,108 @@ +/* + * 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 "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +void CallbackTexture::Release() +{ + if (resource_handle != StableVectorIndex::Invalid) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +Rml::CallbackTexture::operator Texture() const +{ + return Texture(render_manager, resource_handle); +} + +CallbackTextureInterface::CallbackTextureInterface(RenderManager& render_manager, RenderInterface& render_interface, TextureHandle& texture_handle, + Vector2i& dimensions) : + render_manager(render_manager), + render_interface(render_interface), texture_handle(texture_handle), dimensions(dimensions) +{} + +bool CallbackTextureInterface::GenerateTexture(const byte* source, Vector2i new_dimensions) const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return false; + } + const bool result = render_interface.GenerateTexture(texture_handle, source, new_dimensions); + if (result) + dimensions = new_dimensions; + else + texture_handle = {}; + return result; +} + +void CallbackTextureInterface::SaveLayerAsTexture(Vector2i new_dimensions) const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return; + } + texture_handle = render_interface.SaveLayerAsTexture(new_dimensions); + if (texture_handle) + dimensions = new_dimensions; +} + +RenderManager& CallbackTextureInterface::GetRenderManager() const +{ + return render_manager; +} + +CallbackTextureSource::CallbackTextureSource(CallbackTextureFunction&& callback) : callback(std::move(callback)) {} + +CallbackTextureSource::CallbackTextureSource(CallbackTextureSource&& other) noexcept : + callback(std::move(other.callback)), textures(std::move(other.textures)) +{} + +CallbackTextureSource& CallbackTextureSource::operator=(CallbackTextureSource&& other) noexcept +{ + callback = std::move(other.callback); + textures = std::move(other.textures); + return *this; +} + +Texture CallbackTextureSource::GetTexture(RenderManager& render_manager) const +{ + CallbackTexture& texture = textures[&render_manager]; + if (!texture) + texture = render_manager.MakeCallbackTexture(callback); + return Texture(texture); +} + +} // namespace Rml diff --git a/Source/Core/CompiledFilterShader.cpp b/Source/Core/CompiledFilterShader.cpp new file mode 100644 index 000000000..ecefd895a --- /dev/null +++ b/Source/Core/CompiledFilterShader.cpp @@ -0,0 +1,60 @@ +/* + * 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 "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "RenderManagerAccess.h" + +namespace Rml { + +void CompiledFilter::AddHandleTo(FilterHandleList& list) +{ + if (resource_handle != InvalidHandle()) + { + list.push_back(resource_handle); + } +} + +void CompiledFilter::Release() +{ + if (resource_handle != InvalidHandle()) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +void CompiledShader::Release() +{ + if (resource_handle != InvalidHandle()) + { + RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + } +} + +} // namespace Rml diff --git a/Source/Core/Context.cpp b/Source/Core/Context.cpp index 16233dd05..ec7f348fe 100644 --- a/Source/Core/Context.cpp +++ b/Source/Core/Context.cpp @@ -35,7 +35,7 @@ #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/StreamMemory.h" #include "../../Include/RmlUi/Core/SystemInterface.h" #include "DataModel.h" @@ -54,8 +54,7 @@ static constexpr float DOUBLE_CLICK_TIME = 0.5f; // [s] static constexpr float DOUBLE_CLICK_MAX_DIST = 3.f; // [dp] static constexpr float UNIT_SCROLL_LENGTH = 80.f; // [dp] -Context::Context(const String& name) : - name(name), dimensions(0, 0), density_independent_pixel_ratio(1.0f), mouse_position(0, 0), next_update_timeout(0) +Context::Context(const String& name, RenderManager* render_manager) : name(name), render_manager(render_manager) { instancer = nullptr; @@ -125,7 +124,7 @@ void Context::SetDimensions(const Vector2i _dimensions) if (dimensions != _dimensions) { dimensions = _dimensions; - render_manager.SetViewport(dimensions); + render_manager->SetViewport(dimensions); root->SetBox(Box(Vector2f(dimensions))); root->DirtyLayout(); @@ -216,7 +215,7 @@ bool Context::Render() { RMLUI_ZoneScoped; - render_manager.BeginRender(); + render_manager->PrepareRender(); root->Render(); @@ -229,7 +228,7 @@ bool Context::Render() cursor_proxy->Render(); } - render_manager.ResetState(); + render_manager->ResetState(); return true; } @@ -855,7 +854,7 @@ void Context::SetDefaultScrollBehavior(ScrollBehavior scroll_behavior, float spe RenderManager& Context::GetRenderManager() { - return render_manager; + return *render_manager; } void Context::SetInstancer(ContextInstancer* _instancer) diff --git a/Source/Core/ContextInstancerDefault.cpp b/Source/Core/ContextInstancerDefault.cpp index d2c1d8369..351160b7b 100644 --- a/Source/Core/ContextInstancerDefault.cpp +++ b/Source/Core/ContextInstancerDefault.cpp @@ -35,9 +35,9 @@ ContextInstancerDefault::ContextInstancerDefault() {} ContextInstancerDefault::~ContextInstancerDefault() {} -ContextPtr ContextInstancerDefault::InstanceContext(const String& name) +ContextPtr ContextInstancerDefault::InstanceContext(const String& name, RenderManager* render_manager) { - return ContextPtr(new Context(name)); + return ContextPtr(new Context(name, render_manager)); } void ContextInstancerDefault::ReleaseContext(Context* context) diff --git a/Source/Core/ContextInstancerDefault.h b/Source/Core/ContextInstancerDefault.h index fd10ad54c..afffdd71a 100644 --- a/Source/Core/ContextInstancerDefault.h +++ b/Source/Core/ContextInstancerDefault.h @@ -45,12 +45,9 @@ class ContextInstancerDefault : public ContextInstancer { virtual ~ContextInstancerDefault(); /// Instances a context. - /// @param[in] name Name of this context. - /// @return The instanced context. - ContextPtr InstanceContext(const String& name) override; + ContextPtr InstanceContext(const String& name, RenderManager* render_manager) override; /// Releases a context previously created by this context. - /// @param[in] context The context to release. void ReleaseContext(Context* context) override; /// Releases this context instancer. diff --git a/Source/Core/Core.cpp b/Source/Core/Core.cpp index c58e0f900..7836b5ada 100644 --- a/Source/Core/Core.cpp +++ b/Source/Core/Core.cpp @@ -34,13 +34,14 @@ #include "../../Include/RmlUi/Core/FontEngineInterface.h" #include "../../Include/RmlUi/Core/Plugin.h" #include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #include "../../Include/RmlUi/Core/SystemInterface.h" #include "../../Include/RmlUi/Core/Types.h" #include "EventSpecification.h" #include "FileInterfaceDefault.h" -#include "GeometryDatabase.h" #include "PluginRegistry.h" +#include "RenderManagerAccess.h" #include "StyleSheetFactory.h" #include "StyleSheetParser.h" #include "TemplateCache.h" @@ -75,6 +76,8 @@ static FontEngineInterface* font_interface = nullptr; static UniquePtr default_file_interface; static UniquePtr default_font_interface; +static UniquePtr>> render_managers; + static bool initialised = false; using ContextMap = UnorderedMap; @@ -99,11 +102,6 @@ bool Initialise() Log::Message(Log::LT_ERROR, "No system interface set!"); return false; } - if (!render_interface) - { - Log::Message(Log::LT_ERROR, "No render interface set!"); - return false; - } if (!file_interface) { @@ -118,7 +116,9 @@ bool Initialise() EventSpecificationInterface::Initialize(); - TextureDatabase::Initialise(); + render_managers = MakeUnique>>(); + if (render_interface) + (*render_managers)[render_interface] = MakeUnique(render_interface); if (!font_interface) { @@ -174,7 +174,7 @@ void Shutdown() font_interface = nullptr; default_font_interface.reset(); - TextureDatabase::Shutdown(); + render_managers.reset(); initialised = false; @@ -207,12 +207,6 @@ SystemInterface* GetSystemInterface() void SetRenderInterface(RenderInterface* _render_interface) { - if (initialised) - { - Log::Message(Log::LT_ERROR, "The render interface is not allowed to be set or changed after RmlUi has been initialised."); - return; - } - render_interface = _render_interface; } @@ -241,18 +235,33 @@ FontEngineInterface* GetFontEngineInterface() return font_interface; } -Context* CreateContext(const String& name, const Vector2i dimensions) +Context* CreateContext(const String& name, const Vector2i dimensions, RenderInterface* render_interface_for_context) { if (!initialised) return nullptr; + if (!render_interface_for_context) + render_interface_for_context = render_interface; + + if (!render_interface_for_context) + { + Log::Message(Log::LT_WARNING, "Failed to create context '%s', no render interface specified and no default render interface exists.", + name.c_str()); + return nullptr; + } + if (GetContext(name)) { Log::Message(Log::LT_WARNING, "Failed to create context '%s', context already exists.", name.c_str()); return nullptr; } - ContextPtr new_context = Factory::InstanceContext(name); + // Each unique render interface gets its own render manager. + auto& render_manager = (*render_managers)[render_interface_for_context]; + if (!render_manager) + render_manager = MakeUnique(render_interface_for_context); + + ContextPtr new_context = Factory::InstanceContext(name, render_manager.get()); if (!new_context) { Log::Message(Log::LT_WARNING, "Failed to instance context '%s', instancer returned nullptr.", name.c_str()); @@ -347,25 +356,35 @@ EventId RegisterEventType(const String& type, bool interruptible, bool bubbles, StringList GetTextureSourceList() { - return TextureDatabase::GetSourceList(); -} - -void ReleaseTextures() -{ - TextureDatabase::ReleaseTextures(); + StringList result; + if (!render_managers) + return result; + for (const auto& render_manager : *render_managers) + { + RenderManagerAccess::GetTextureSourceList(render_manager.second.get(), result); + } + return result; } -void ReleaseCompiledGeometry() +void ReleaseTextures(RenderInterface* match_render_interface) { - return GeometryDatabase::ReleaseAll(); + if (!render_managers) + return; + for (auto& render_manager : *render_managers) + { + if (!match_render_interface || render_manager.first == match_render_interface) + RenderManagerAccess::ReleaseAllTextures(render_manager.second.get()); + } } -void ReleaseMemoryPools() +void ReleaseCompiledGeometry(RenderInterface* match_render_interface) { - if (observerPtrBlockPool && observerPtrBlockPool->GetNumAllocatedObjects() <= 0) + if (!render_managers) + return; + for (auto& render_manager : *render_managers) { - delete observerPtrBlockPool; - observerPtrBlockPool = nullptr; + if (!match_render_interface || render_manager.first == match_render_interface) + RenderManagerAccess::ReleaseAllCompiledGeometry(render_manager.second.get()); } } @@ -383,4 +402,23 @@ void ReleaseFontResources() } } +void ReleaseMemoryPools() +{ + if (observerPtrBlockPool && observerPtrBlockPool->GetNumAllocatedObjects() <= 0) + { + delete observerPtrBlockPool; + observerPtrBlockPool = nullptr; + } +} + +// Functions that need to be accessible within the Core library, but not publicly. +namespace CoreInternal { + + bool HasRenderManager(RenderInterface* match_render_interface) + { + return render_managers && (*render_managers).find(match_render_interface) != render_managers->end(); + } + +} // namespace CoreInternal + } // namespace Rml diff --git a/Source/Core/Decorator.cpp b/Source/Core/Decorator.cpp index 1dd8ea7d4..074876d56 100644 --- a/Source/Core/Decorator.cpp +++ b/Source/Core/Decorator.cpp @@ -28,6 +28,7 @@ #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/StyleSheet.h" #include "../../Include/RmlUi/Core/Texture.h" #include @@ -38,7 +39,7 @@ Decorator::Decorator() {} Decorator::~Decorator() {} -int Decorator::AddTexture(const Texture& texture) +int Decorator::AddTexture(Texture texture) { if (!texture) return -1; @@ -64,16 +65,16 @@ int Decorator::GetNumTextures() const return result; } -const Texture* Decorator::GetTexture(int index) const +Texture Decorator::GetTexture(int index) const { if (index == 0) - return &first_texture; + return first_texture; index -= 1; if (index < 0 || index >= (int)additional_textures.size()) - return nullptr; + return {}; - return &(additional_textures[index]); + return additional_textures[index]; } DecoratorInstancer::DecoratorInstancer() {} @@ -87,16 +88,18 @@ const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const Texture DecoratorInstancerInterface::GetTexture(const String& filename) const { - Texture texture; - if (!property_source) { Log::Message(Log::LT_WARNING, "Texture name '%s' in decorator could not be loaded, no property source available.", filename.c_str()); - return texture; + return {}; } - texture.Set(filename, property_source->path); + return render_manager.LoadTexture(filename, property_source->path); +} - return texture; +RenderManager& DecoratorInstancerInterface::GetRenderManager() const +{ + return render_manager; } + } // namespace Rml diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index 2a1bc2282..b86c49492 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -31,8 +31,8 @@ #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "ComputeProperty.h" #include "DecoratorShader.h" @@ -189,13 +189,13 @@ bool DecoratorStraightGradient::Initialise(const Direction in_direction, const C DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* element, BoxArea paint_area) const { - Geometry* geometry = new Geometry(); const Box& box = element->GetBox(); const ComputedValues& computed = element->GetComputedValues(); const float opacity = computed.opacity(); - GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), ColourbPremultiplied(), paint_area); + Mesh mesh; + MeshUtilities::GenerateBackground(mesh, element->GetBox(), Vector2f(0), computed.border_radius(), ColourbPremultiplied(), paint_area); ColourbPremultiplied colour_start = start.ToPremultiplied(opacity); ColourbPremultiplied colour_stop = stop.ToPremultiplied(opacity); @@ -203,7 +203,7 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem const Vector2f offset = box.GetPosition(paint_area); const Vector2f size = box.GetSize(paint_area); - Vector& vertices = geometry->GetVertices(); + Vector& vertices = mesh.vertices; if (direction == Direction::Horizontal) { @@ -222,6 +222,8 @@ DecoratorDataHandle DecoratorStraightGradient::GenerateElementData(Element* elem } } + Geometry* geometry = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + return reinterpret_cast(geometry); } @@ -288,8 +290,8 @@ bool DecoratorLinearGradient::Initialise(bool in_repeating, Corner in_corner, fl DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* element, BoxArea paint_area) const { - RenderInterface* render_interface = GetRenderInterface(); - if (!render_interface) + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) return INVALID_DECORATORDATAHANDLE; RMLUI_ASSERT(!color_stops.empty()); @@ -304,7 +306,7 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.length, soft_spacing, color_stops); - CompiledShaderHandle shader_handle = render_interface->CompileShader("linear-gradient", + CompiledShader shader = render_manager->CompileShader("linear-gradient", Dictionary{ {"angle", Variant(angle)}, {"p0", Variant(gradient_shape.p0)}, @@ -313,21 +315,20 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen {"repeating", Variant(repeating)}, {"color_stop_list", Variant(std::move(resolved_stops))}, }); - - if (!shader_handle) + if (!shader) return INVALID_DECORATORDATAHANDLE; - Geometry geometry; - + Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area); const Vector2f render_offset = box.GetPosition(paint_area); - for (Vertex& vertex : geometry.GetVertices()) + for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; - ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); return reinterpret_cast(element_data); } @@ -335,14 +336,13 @@ DecoratorDataHandle DecoratorLinearGradient::GenerateElementData(Element* elemen void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - GetRenderInterface()->ReleaseCompiledShader(element_data->shader); GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); } DecoratorLinearGradient::LinearGradientShape DecoratorLinearGradient::CalculateShape(Vector2f dim) const @@ -461,8 +461,8 @@ bool DecoratorRadialGradient::Initialise(bool in_repeating, Shape in_shape, Size DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* element, BoxArea box_area) const { - RenderInterface* render_interface = GetRenderInterface(); - if (!render_interface) + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) return INVALID_DECORATORDATAHANDLE; RMLUI_ASSERT(!color_stops.empty() && (shape == Shape::Circle || shape == Shape::Ellipse)); @@ -477,39 +477,40 @@ DecoratorDataHandle DecoratorRadialGradient::GenerateElementData(Element* elemen ColorStopList resolved_stops = ResolveColorStops(element, gradient_shape.radius.x, soft_spacing, color_stops); - CompiledShaderHandle shader_handle = render_interface->CompileShader("radial-gradient", + CompiledShader shader = render_manager->CompileShader("radial-gradient", Dictionary{ {"center", Variant(gradient_shape.center)}, {"radius", Variant(gradient_shape.radius)}, {"repeating", Variant(repeating)}, {"color_stop_list", Variant(std::move(resolved_stops))}, }); + if (!shader) + return INVALID_DECORATORDATAHANDLE; - Geometry geometry; - + Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); const Vector2f render_offset = box.GetPosition(box_area); - for (Vertex& vertex : geometry.GetVertices()) + for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; - ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); return reinterpret_cast(element_data); } void DecoratorRadialGradient::ReleaseElementData(DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - GetRenderInterface()->ReleaseCompiledShader(element_data->shader); GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); } DecoratorRadialGradient::RadialGradientShape DecoratorRadialGradient::CalculateRadialGradientShape(Element* element, Vector2f dimensions) const @@ -659,8 +660,8 @@ bool DecoratorConicGradient::Initialise(bool in_repeating, float in_angle, Vecto DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element, BoxArea box_area) const { - RenderInterface* render_interface = GetRenderInterface(); - if (!render_interface) + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) return INVALID_DECORATORDATAHANDLE; RMLUI_ASSERT(!color_stops.empty()); @@ -673,40 +674,40 @@ DecoratorDataHandle DecoratorConicGradient::GenerateElementData(Element* element ColorStopList resolved_stops = ResolveColorStops(element, 1.f, 0.f, color_stops); - CompiledShaderHandle shader_handle = render_interface->CompileShader("conic-gradient", + CompiledShader shader = render_manager->CompileShader("conic-gradient", Dictionary{ {"angle", Variant(angle)}, {"center", Variant(center)}, {"repeating", Variant(repeating)}, {"color_stop_list", Variant(std::move(resolved_stops))}, }); + if (!shader) + return INVALID_DECORATORDATAHANDLE; - Geometry geometry; - + Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); const Vector2f render_offset = box.GetPosition(box_area); - for (Vertex& vertex : geometry.GetVertices()) + for (Vertex& vertex : mesh.vertices) vertex.tex_coord = vertex.position - render_offset; - ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), shader_handle); + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); return reinterpret_cast(element_data); } void DecoratorConicGradient::ReleaseElementData(DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - GetRenderInterface()->ReleaseCompiledShader(element_data->shader); - GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); } DecoratorConicGradientInstancer::DecoratorConicGradientInstancer() diff --git a/Source/Core/DecoratorNinePatch.cpp b/Source/Core/DecoratorNinePatch.cpp index 1aa263fe8..e6abfe650 100644 --- a/Source/Core/DecoratorNinePatch.cpp +++ b/Source/Core/DecoratorNinePatch.cpp @@ -40,7 +40,7 @@ DecoratorNinePatch::DecoratorNinePatch() {} DecoratorNinePatch::~DecoratorNinePatch() {} bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectanglef& _rect_inner, const Array* _edges, - const Texture& _texture, float _display_scale) + Texture _texture, float _display_scale) { rect_outer = _rect_outer; rect_inner = _rect_inner; @@ -58,11 +58,8 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo { const auto& computed = element->GetComputedValues(); - Geometry* data = new Geometry(); - - const Texture* texture = GetTexture(); - data->SetTexture(texture); - const Vector2f texture_dimensions(texture->GetDimensions()); + Texture texture = GetTexture(); + const Vector2f texture_dimensions(texture.GetDimensions()); const Vector2f surface_offset = element->GetBox().GetPosition(paint_area); const Vector2f surface_dimensions = element->GetBox().GetSize(paint_area).Round(); @@ -133,9 +130,9 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo surface_pos[2] = surface_pos[2].Round(); /* Now we have all the coordinates we need. Expand the diagonal vertices to the 16 individual vertices. */ - - Vector& vertices = data->GetVertices(); - Vector& indices = data->GetIndices(); + Mesh mesh; + Vector& vertices = mesh.vertices; + Vector& indices = mesh.indices; vertices.resize(4 * 4); @@ -167,6 +164,8 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element, Bo indices[i + 5] = top_left_index + 5; } + Geometry* data = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + return reinterpret_cast(data); } @@ -178,7 +177,7 @@ void DecoratorNinePatch::ReleaseElementData(DecoratorDataHandle element_data) co void DecoratorNinePatch::RenderElement(Element* element, DecoratorDataHandle element_data) const { Geometry* data = reinterpret_cast(element_data); - data->Render(element->GetAbsoluteOffset(BoxArea::Border)); + data->Render(element->GetAbsoluteOffset(BoxArea::Border), GetTexture()); } DecoratorNinePatchInstancer::DecoratorNinePatchInstancer() @@ -243,8 +242,8 @@ SharedPtr DecoratorNinePatchInstancer::InstanceDecorator(const String auto decorator = MakeShared(); - if (!decorator->Initialise(sprite_outer->rectangle, sprite_inner->rectangle, (edges_set ? &edges : nullptr), sprite_outer->sprite_sheet->texture, - sprite_outer->sprite_sheet->display_scale)) + if (!decorator->Initialise(sprite_outer->rectangle, sprite_inner->rectangle, (edges_set ? &edges : nullptr), + sprite_outer->sprite_sheet->texture_source.GetTexture(instancer_interface.GetRenderManager()), sprite_outer->sprite_sheet->display_scale)) { return nullptr; } diff --git a/Source/Core/DecoratorNinePatch.h b/Source/Core/DecoratorNinePatch.h index 8657835b1..379650e0c 100644 --- a/Source/Core/DecoratorNinePatch.h +++ b/Source/Core/DecoratorNinePatch.h @@ -40,7 +40,7 @@ class DecoratorNinePatch : public Decorator { DecoratorNinePatch(); virtual ~DecoratorNinePatch(); - bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array* _edges, const Texture& texture, + bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array* _edges, Texture texture, float display_scale); DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; diff --git a/Source/Core/DecoratorShader.cpp b/Source/Core/DecoratorShader.cpp index fad506796..997068362 100644 --- a/Source/Core/DecoratorShader.cpp +++ b/Source/Core/DecoratorShader.cpp @@ -30,9 +30,9 @@ #include "../../Include/RmlUi/Core/ComputedValues.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -54,26 +54,28 @@ bool DecoratorShader::Initialise(String&& in_value) DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxArea render_area) const { - RenderInterface* render_interface = GetRenderInterface(); - if (!render_interface) + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) return INVALID_DECORATORDATAHANDLE; const Box& box = element->GetBox(); const Vector2f dimensions = box.GetSize(render_area); - CompiledShaderHandle effect_handle = - render_interface->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); + CompiledShader shader = render_manager->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); + if (!shader) + return INVALID_DECORATORDATAHANDLE; - Geometry geometry; + Mesh mesh; const ComputedValues& computed = element->GetComputedValues(); const byte alpha = byte(computed.opacity() * 255.f); - GeometryUtilities::GenerateBackground(&geometry, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area); const Vector2f offset = box.GetPosition(render_area); - for (Vertex& vertex : geometry.GetVertices()) + for (Vertex& vertex : mesh.vertices) vertex.tex_coord = (vertex.position - offset) / dimensions; - ShaderElementData* element_data = GetShaderElementDataPool().AllocateAndConstruct(std::move(geometry), effect_handle); + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); return reinterpret_cast(element_data); } @@ -81,14 +83,13 @@ DecoratorDataHandle DecoratorShader::GenerateElementData(Element* element, BoxAr void DecoratorShader::ReleaseElementData(DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - GetRenderInterface()->ReleaseCompiledShader(element_data->shader); GetShaderElementDataPool().DestroyAndDeallocate(element_data); } void DecoratorShader::RenderElement(Element* element, DecoratorDataHandle handle) const { ShaderElementData* element_data = reinterpret_cast(handle); - element_data->geometry.RenderWithShader(element_data->shader, element->GetAbsoluteOffset(BoxArea::Border)); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); } DecoratorShaderInstancer::DecoratorShaderInstancer() diff --git a/Source/Core/DecoratorShader.h b/Source/Core/DecoratorShader.h index 16004349c..2ca3e8cc4 100644 --- a/Source/Core/DecoratorShader.h +++ b/Source/Core/DecoratorShader.h @@ -69,9 +69,9 @@ class DecoratorShaderInstancer : public DecoratorInstancer { }; struct ShaderElementData { - ShaderElementData(Geometry&& geometry, CompiledShaderHandle shader) : geometry(std::move(geometry)), shader(shader) {} + ShaderElementData(Geometry&& geometry, CompiledShader&& shader) : geometry(std::move(geometry)), shader(std::move(shader)) {} Geometry geometry; - CompiledShaderHandle shader; + CompiledShader shader; }; Pool& GetShaderElementDataPool(); diff --git a/Source/Core/DecoratorTiled.cpp b/Source/Core/DecoratorTiled.cpp index d27ca13e9..99ad433a2 100644 --- a/Source/Core/DecoratorTiled.cpp +++ b/Source/Core/DecoratorTiled.cpp @@ -29,8 +29,9 @@ #include "DecoratorTiled.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementUtilities.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/Spritesheet.h" #include @@ -55,7 +56,7 @@ DecoratorTiled::Tile::Tile() : display_scale(1), position(0, 0), size(0, 0) orientation = ORIENTATION_NONE; } -void DecoratorTiled::Tile::CalculateDimensions(const Texture& texture) const +void DecoratorTiled::Tile::CalculateDimensions(Texture texture) const { if (!tile_data_calculated) { @@ -98,8 +99,8 @@ Vector2f DecoratorTiled::Tile::GetNaturalDimensions(Element* element) const return raw_dimensions * scale_raw_to_natural_dimensions; } -void DecoratorTiled::Tile::GenerateGeometry(Vector& vertices, Vector& indices, const ComputedValues& computed, - const Vector2f surface_origin, const Vector2f surface_dimensions, const Vector2f tile_dimensions) const +void DecoratorTiled::Tile::GenerateGeometry(Mesh& mesh, const ComputedValues& computed, const Vector2f surface_origin, + const Vector2f surface_dimensions, const Vector2f tile_dimensions) const { if (surface_dimensions.x <= 0 || surface_dimensions.y <= 0) return; @@ -204,22 +205,11 @@ void DecoratorTiled::Tile::GenerateGeometry(Vector& vertices, Vectorrectangle.Size(); tile.display_scale = sprite->sprite_sheet->display_scale; - texture = sprite->sprite_sheet->texture; + texture = sprite->sprite_sheet->texture_source.GetTexture(instancer_interface.GetRenderManager()); } else { diff --git a/Source/Core/DecoratorTiled.h b/Source/Core/DecoratorTiled.h index 7bd308b3f..ccef650b1 100644 --- a/Source/Core/DecoratorTiled.h +++ b/Source/Core/DecoratorTiled.h @@ -35,7 +35,8 @@ namespace Rml { -struct Texture; +class Texture; +struct Mesh; /** Base class for tiled decorators. @@ -79,7 +80,7 @@ class DecoratorTiled : public Decorator { Tile(); /// Calculates the tile's dimensions from the texture and texture coordinates. - void CalculateDimensions(const Texture& texture) const; + void CalculateDimensions(Texture texture) const; /// Get the dimensions (in px) that this tile is ideally displayed as. /// Uses the dp-ratio of the current element and 'display_scale' to calculate the dimensions. Vector2f GetNaturalDimensions(Element* element) const; @@ -91,8 +92,8 @@ class DecoratorTiled : public Decorator { /// @param[in] surface_origin The starting point of the first tile to generate. /// @param[in] surface_dimensions The dimensions of the surface to be tiled. /// @param[in] tile_dimensions The dimensions to render this tile at. - void GenerateGeometry(Vector& vertices, Vector& indices, const ComputedValues& computed_values, Vector2f surface_origin, - Vector2f surface_dimensions, Vector2f tile_dimensions) const; + void GenerateGeometry(Mesh& mesh, const ComputedValues& computed_values, Vector2f surface_origin, Vector2f surface_dimensions, + Vector2f tile_dimensions) const; struct TileData { Vector2f size; // 'px' units diff --git a/Source/Core/DecoratorTiledBox.cpp b/Source/Core/DecoratorTiledBox.cpp index 998f23048..00570c2e8 100644 --- a/Source/Core/DecoratorTiledBox.cpp +++ b/Source/Core/DecoratorTiledBox.cpp @@ -29,6 +29,7 @@ #include "DecoratorTiledBox.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -101,7 +102,7 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element, Box for (int i = 0; i < 9; i++) { RMLUI_ASSERT(tiles[i].texture_index >= 0); - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); } const Vector2f offset = element->GetBox().GetPosition(paint_area); @@ -191,58 +192,50 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element, Box bottom.y = bottom_right.y; } - const int num_textures = GetNumTextures(); - DecoratorTiledBoxData* data = new DecoratorTiledBoxData(num_textures); const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; // Generate the geometry for the top-left tile. - tiles[TOP_LEFT_CORNER].GenerateGeometry(data->geometry[tiles[TOP_LEFT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[TOP_LEFT_CORNER].texture_index].GetIndices(), computed, offset, top_left, top_left); + tiles[TOP_LEFT_CORNER].GenerateGeometry(mesh[tiles[TOP_LEFT_CORNER].texture_index], computed, offset, top_left, top_left); // Generate the geometry for the top edge tiles. - tiles[TOP_EDGE].GenerateGeometry(data->geometry[tiles[TOP_EDGE].texture_index].GetVertices(), - data->geometry[tiles[TOP_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(top_left.x, 0), + tiles[TOP_EDGE].GenerateGeometry(mesh[tiles[TOP_EDGE].texture_index], computed, offset + Vector2f(top_left.x, 0), Vector2f(size.x - (top_left.x + top_right.x), top.y), top); // Generate the geometry for the top-right tile. - tiles[TOP_RIGHT_CORNER].GenerateGeometry(data->geometry[tiles[TOP_RIGHT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[TOP_RIGHT_CORNER].texture_index].GetIndices(), computed, offset + Vector2f(size.x - top_right.x, 0), top_right, - top_right); + tiles[TOP_RIGHT_CORNER].GenerateGeometry(mesh[tiles[TOP_RIGHT_CORNER].texture_index], computed, offset + Vector2f(size.x - top_right.x, 0), + top_right, top_right); // Generate the geometry for the left side. - tiles[LEFT_EDGE].GenerateGeometry(data->geometry[tiles[LEFT_EDGE].texture_index].GetVertices(), - data->geometry[tiles[LEFT_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(0, top_left.y), + tiles[LEFT_EDGE].GenerateGeometry(mesh[tiles[LEFT_EDGE].texture_index], computed, offset + Vector2f(0, top_left.y), Vector2f(left.x, size.y - (top_left.y + bottom_left.y)), left); // Generate the geometry for the right side. - tiles[RIGHT_EDGE].GenerateGeometry(data->geometry[tiles[RIGHT_EDGE].texture_index].GetVertices(), - data->geometry[tiles[RIGHT_EDGE].texture_index].GetIndices(), computed, offset + Vector2f((size.x - right.x), top_right.y), + tiles[RIGHT_EDGE].GenerateGeometry(mesh[tiles[RIGHT_EDGE].texture_index], computed, offset + Vector2f((size.x - right.x), top_right.y), Vector2f(right.x, size.y - (top_right.y + bottom_right.y)), right); // Generate the geometry for the bottom-left tile. - tiles[BOTTOM_LEFT_CORNER].GenerateGeometry(data->geometry[tiles[BOTTOM_LEFT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM_LEFT_CORNER].texture_index].GetIndices(), computed, offset + Vector2f(0, size.y - bottom_left.y), bottom_left, - bottom_left); + tiles[BOTTOM_LEFT_CORNER].GenerateGeometry(mesh[tiles[BOTTOM_LEFT_CORNER].texture_index], computed, offset + Vector2f(0, size.y - bottom_left.y), + bottom_left, bottom_left); // Generate the geometry for the bottom edge tiles. - tiles[BOTTOM_EDGE].GenerateGeometry(data->geometry[tiles[BOTTOM_EDGE].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM_EDGE].texture_index].GetIndices(), computed, offset + Vector2f(bottom_left.x, size.y - bottom.y), + tiles[BOTTOM_EDGE].GenerateGeometry(mesh[tiles[BOTTOM_EDGE].texture_index], computed, offset + Vector2f(bottom_left.x, size.y - bottom.y), Vector2f(size.x - (bottom_left.x + bottom_right.x), bottom.y), bottom); // Generate the geometry for the bottom-right tile. - tiles[BOTTOM_RIGHT_CORNER].GenerateGeometry(data->geometry[tiles[BOTTOM_RIGHT_CORNER].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM_RIGHT_CORNER].texture_index].GetIndices(), computed, + tiles[BOTTOM_RIGHT_CORNER].GenerateGeometry(mesh[tiles[BOTTOM_RIGHT_CORNER].texture_index], computed, offset + Vector2f(size.x - bottom_right.x, size.y - bottom_right.y), bottom_right, bottom_right); // Generate the centre geometry. Vector2f centre_dimensions = tiles[CENTRE].GetNaturalDimensions(element); Vector2f centre_surface_dimensions(size.x - (left.x + right.x), size.y - (top.y + bottom.y)); - tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(left.x, top.y), centre_surface_dimensions, + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(left.x, top.y), centre_surface_dimensions, centre_dimensions); - // Set the textures on the geometry. - const Texture* texture = nullptr; - int texture_index = 0; - while ((texture = GetTexture(texture_index)) != nullptr) - data->geometry[texture_index++].SetTexture(texture); + const int num_textures = GetNumTextures(); + DecoratorTiledBoxData* data = new DecoratorTiledBoxData(num_textures); + RenderManager* render_manager = element->GetRenderManager(); + + // Set the mesh and textures on the geometry. + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); return reinterpret_cast(data); } @@ -258,7 +251,7 @@ void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle elem DecoratorTiledBoxData* data = reinterpret_cast(element_data); for (int i = 0; i < data->num_textures; i++) - data->geometry[i].Render(translation); + data->geometry[i].Render(translation, GetTexture(i)); } DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9) diff --git a/Source/Core/DecoratorTiledBox.h b/Source/Core/DecoratorTiledBox.h index 65b18dc83..34de15abc 100644 --- a/Source/Core/DecoratorTiledBox.h +++ b/Source/Core/DecoratorTiledBox.h @@ -58,18 +58,19 @@ class DecoratorTiledBox : public DecoratorTiled { private: enum { - TOP_LEFT_CORNER = 0, - TOP_RIGHT_CORNER = 1, - BOTTOM_LEFT_CORNER = 2, - BOTTOM_RIGHT_CORNER = 3, - LEFT_EDGE = 4, - RIGHT_EDGE = 5, - TOP_EDGE = 6, - BOTTOM_EDGE = 7, - CENTRE = 8 + TOP_LEFT_CORNER, + TOP_RIGHT_CORNER, + BOTTOM_LEFT_CORNER, + BOTTOM_RIGHT_CORNER, + LEFT_EDGE, + RIGHT_EDGE, + TOP_EDGE, + BOTTOM_EDGE, + CENTRE, + COUNT, }; - Tile tiles[9]; + Tile tiles[COUNT]; }; class DecoratorTiledBoxInstancer : public DecoratorTiledInstancer { diff --git a/Source/Core/DecoratorTiledHorizontal.cpp b/Source/Core/DecoratorTiledHorizontal.cpp index 4e83e5241..3d4a385e8 100644 --- a/Source/Core/DecoratorTiledHorizontal.cpp +++ b/Source/Core/DecoratorTiledHorizontal.cpp @@ -29,6 +29,7 @@ #include "DecoratorTiledHorizontal.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/Texture.h" namespace Rml { @@ -79,10 +80,7 @@ DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* eleme { // Initialise the tiles for this element. for (int i = 0; i < 3; i++) - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); - - const int num_textures = GetNumTextures(); - DecoratorTiledHorizontalData* data = new DecoratorTiledHorizontalData(num_textures); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); const Vector2f offset = element->GetBox().GetPosition(paint_area); const Vector2f size = element->GetBox().GetSize(paint_area); @@ -109,23 +107,23 @@ DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* eleme } const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; + + tiles[LEFT].GenerateGeometry(mesh[tiles[LEFT].texture_index], computed, offset, left_dimensions, left_dimensions); - // Generate the geometry for the left tile. - tiles[LEFT].GenerateGeometry(data->geometry[tiles[LEFT].texture_index].GetVertices(), data->geometry[tiles[LEFT].texture_index].GetIndices(), - computed, offset, left_dimensions, left_dimensions); - // Generate the geometry for the centre tiles. - tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(left_dimensions.x, 0), + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(left_dimensions.x, 0), Vector2f(size.x - (left_dimensions.x + right_dimensions.x), centre_dimensions.y), centre_dimensions); - // Generate the geometry for the right tile. - tiles[RIGHT].GenerateGeometry(data->geometry[tiles[RIGHT].texture_index].GetVertices(), data->geometry[tiles[RIGHT].texture_index].GetIndices(), - computed, offset + Vector2f(size.x - right_dimensions.x, 0), right_dimensions, right_dimensions); - // Set the textures on the geometry. - const Texture* texture = nullptr; - int texture_index = 0; - while ((texture = GetTexture(texture_index)) != nullptr) - data->geometry[texture_index++].SetTexture(texture); + tiles[RIGHT].GenerateGeometry(mesh[tiles[RIGHT].texture_index], computed, offset + Vector2f(size.x - right_dimensions.x, 0), right_dimensions, + right_dimensions); + + const int num_textures = GetNumTextures(); + DecoratorTiledHorizontalData* data = new DecoratorTiledHorizontalData(num_textures); + RenderManager* render_manager = element->GetRenderManager(); + + // Set the mesh and textures on the geometry. + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); return reinterpret_cast(data); } @@ -141,7 +139,7 @@ void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHand DecoratorTiledHorizontalData* data = reinterpret_cast(element_data); for (int i = 0; i < data->num_textures; i++) - data->geometry[i].Render(translation); + data->geometry[i].Render(translation, GetTexture(i)); } DecoratorTiledHorizontalInstancer::DecoratorTiledHorizontalInstancer() : DecoratorTiledInstancer(3) diff --git a/Source/Core/DecoratorTiledHorizontal.h b/Source/Core/DecoratorTiledHorizontal.h index e5858933b..6d3b92b38 100644 --- a/Source/Core/DecoratorTiledHorizontal.h +++ b/Source/Core/DecoratorTiledHorizontal.h @@ -57,9 +57,9 @@ class DecoratorTiledHorizontal : public DecoratorTiled { void RenderElement(Element* element, DecoratorDataHandle element_data) const override; private: - enum { LEFT = 0, RIGHT = 1, CENTRE = 2 }; + enum { LEFT, RIGHT, CENTRE, COUNT }; - Tile tiles[3]; + Tile tiles[COUNT]; }; class DecoratorTiledHorizontalInstancer : public DecoratorTiledInstancer { diff --git a/Source/Core/DecoratorTiledImage.cpp b/Source/Core/DecoratorTiledImage.cpp index fcead3732..a81968959 100644 --- a/Source/Core/DecoratorTiledImage.cpp +++ b/Source/Core/DecoratorTiledImage.cpp @@ -29,7 +29,8 @@ #include "DecoratorTiledImage.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -37,7 +38,7 @@ DecoratorTiledImage::DecoratorTiledImage() {} DecoratorTiledImage::~DecoratorTiledImage() {} -bool DecoratorTiledImage::Initialise(const Tile& _tile, const Texture& _texture) +bool DecoratorTiledImage::Initialise(const Tile& _tile, Texture _texture) { tile = _tile; tile.texture_index = AddTexture(_texture); @@ -47,10 +48,7 @@ bool DecoratorTiledImage::Initialise(const Tile& _tile, const Texture& _texture) DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element, BoxArea paint_area) const { // Calculate the tile's dimensions for this element. - tile.CalculateDimensions(*GetTexture(tile.texture_index)); - - Geometry* data = new Geometry(); - data->SetTexture(GetTexture()); + tile.CalculateDimensions(GetTexture()); const ComputedValues& computed = element->GetComputedValues(); @@ -58,7 +56,10 @@ DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element, B const Vector2f size = element->GetBox().GetSize(paint_area); // Generate the geometry for the tile. - tile.GenerateGeometry(data->GetVertices(), data->GetIndices(), computed, offset, size, tile.GetNaturalDimensions(element)); + Mesh mesh; + tile.GenerateGeometry(mesh, computed, offset, size, tile.GetNaturalDimensions(element)); + + Geometry* data = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); return reinterpret_cast(data); } @@ -71,7 +72,7 @@ void DecoratorTiledImage::ReleaseElementData(DecoratorDataHandle element_data) c void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle element_data) const { Geometry* data = reinterpret_cast(element_data); - data->Render(element->GetAbsoluteOffset(BoxArea::Border)); + data->Render(element->GetAbsoluteOffset(BoxArea::Border), GetTexture()); } DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) diff --git a/Source/Core/DecoratorTiledImage.h b/Source/Core/DecoratorTiledImage.h index 77ad57add..de4419c29 100644 --- a/Source/Core/DecoratorTiledImage.h +++ b/Source/Core/DecoratorTiledImage.h @@ -46,7 +46,7 @@ class DecoratorTiledImage : public DecoratorTiled { /// @param tile[in] The declaration for the tile. /// @param texture[in] The texture to apply to the tile. /// @return True if the image is valid, false otherwise. - bool Initialise(const Tile& tile, const Texture& texture); + bool Initialise(const Tile& tile, Texture texture); /// Called on a decorator to generate any required per-element data for a newly decorated element. DecoratorDataHandle GenerateElementData(Element* element, BoxArea paint_area) const override; diff --git a/Source/Core/DecoratorTiledVertical.cpp b/Source/Core/DecoratorTiledVertical.cpp index 42ee349d7..c55a1a363 100644 --- a/Source/Core/DecoratorTiledVertical.cpp +++ b/Source/Core/DecoratorTiledVertical.cpp @@ -29,7 +29,8 @@ #include "DecoratorTiledVertical.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/Texture.h" namespace Rml { @@ -80,10 +81,7 @@ DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element { // Initialise the tile for this element. for (int i = 0; i < 3; i++) - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); - - const int num_textures = GetNumTextures(); - DecoratorTiledVerticalData* data = new DecoratorTiledVerticalData(num_textures); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); const Vector2f offset = element->GetBox().GetPosition(paint_area); const Vector2f size = element->GetBox().GetSize(paint_area); @@ -110,24 +108,23 @@ DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element } const ComputedValues& computed = element->GetComputedValues(); + Mesh mesh[COUNT]; + + tiles[TOP].GenerateGeometry(mesh[tiles[TOP].texture_index], computed, offset, top_dimensions, top_dimensions); - // Generate the geometry for the left tile. - tiles[TOP].GenerateGeometry(data->geometry[tiles[TOP].texture_index].GetVertices(), data->geometry[tiles[TOP].texture_index].GetIndices(), - computed, offset, top_dimensions, top_dimensions); - // Generate the geometry for the centre tiles. - tiles[CENTRE].GenerateGeometry(data->geometry[tiles[CENTRE].texture_index].GetVertices(), - data->geometry[tiles[CENTRE].texture_index].GetIndices(), computed, offset + Vector2f(0, top_dimensions.y), + tiles[CENTRE].GenerateGeometry(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(0, top_dimensions.y), Vector2f(centre_dimensions.x, size.y - (top_dimensions.y + bottom_dimensions.y)), centre_dimensions); - // Generate the geometry for the right tile. - tiles[BOTTOM].GenerateGeometry(data->geometry[tiles[BOTTOM].texture_index].GetVertices(), - data->geometry[tiles[BOTTOM].texture_index].GetIndices(), computed, offset + Vector2f(0, size.y - bottom_dimensions.y), bottom_dimensions, + + tiles[BOTTOM].GenerateGeometry(mesh[tiles[BOTTOM].texture_index], computed, offset + Vector2f(0, size.y - bottom_dimensions.y), bottom_dimensions, bottom_dimensions); - // Set the textures on the geometry. - const Texture* texture = nullptr; - int texture_index = 0; - while ((texture = GetTexture(texture_index)) != nullptr) - data->geometry[texture_index++].SetTexture(texture); + const int num_textures = GetNumTextures(); + DecoratorTiledVerticalData* data = new DecoratorTiledVerticalData(num_textures); + + // Set the mesh and textures on the geometry. + RenderManager* render_manager = element->GetRenderManager(); + for (int i = 0; i < num_textures; i++) + data->geometry[i] = render_manager->MakeGeometry(std::move(mesh[i])); return reinterpret_cast(data); } @@ -143,7 +140,7 @@ void DecoratorTiledVertical::RenderElement(Element* element, DecoratorDataHandle DecoratorTiledVerticalData* data = reinterpret_cast(element_data); for (int i = 0; i < data->num_textures; i++) - data->geometry[i].Render(translation); + data->geometry[i].Render(translation, GetTexture(i)); } DecoratorTiledVerticalInstancer::DecoratorTiledVerticalInstancer() : DecoratorTiledInstancer(3) diff --git a/Source/Core/DecoratorTiledVertical.h b/Source/Core/DecoratorTiledVertical.h index 6ca820078..17e1177fb 100644 --- a/Source/Core/DecoratorTiledVertical.h +++ b/Source/Core/DecoratorTiledVertical.h @@ -57,9 +57,9 @@ class DecoratorTiledVertical : public DecoratorTiled { void RenderElement(Element* element, DecoratorDataHandle element_data) const override; private: - enum { TOP = 0, BOTTOM = 1, CENTRE = 2 }; + enum { TOP, BOTTOM, CENTRE, COUNT }; - Tile tiles[3]; + Tile tiles[COUNT]; }; class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer { diff --git a/Source/Core/Element.cpp b/Source/Core/Element.cpp index 0b5288ad0..f4b4c4386 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -830,6 +830,13 @@ Context* Element::GetContext() const return nullptr; } +RenderManager* Element::GetRenderManager() const +{ + if (Context* context = GetContext()) + return &context->GetRenderManager(); + return nullptr; +} + void Element::SetAttributes(const ElementAttributes& _attributes) { attributes.reserve(attributes.size() + _attributes.size()); diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 8c3b58fdc..e2c1beb95 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -32,7 +32,8 @@ #include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "GeometryBoxShadow.h" namespace Rml { @@ -44,7 +45,10 @@ void ElementBackgroundBorder::Render(Element* element) if (background_dirty || border_dirty) { for (auto& background : backgrounds) - background.second.geometry.Release(true); + { + if (background.first != BackgroundType::BackgroundBorder) + background.second.geometry.Release(); + } GenerateGeometry(element); @@ -52,11 +56,11 @@ void ElementBackgroundBorder::Render(Element* element) border_dirty = false; } - Geometry* shadow_geometry = GetGeometry(BackgroundType::BoxShadow); - if (shadow_geometry && *shadow_geometry) - shadow_geometry->Render(element->GetAbsoluteOffset(BoxArea::Border)); - else if (Geometry* geometry = GetGeometry(BackgroundType::BackgroundBorder)) - geometry->Render(element->GetAbsoluteOffset(BoxArea::Border)); + Background* shadow = GetBackground(BackgroundType::BoxShadow); + if (shadow && shadow->geometry) + shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); + else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) + background->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border)); } void ElementBackgroundBorder::DirtyBackground() @@ -83,19 +87,22 @@ Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea cli Geometry& geometry = GetOrCreateBackground(type).geometry; if (!geometry) { + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); const Box& box = element->GetBox(); const Vector4f border_radius = element->GetComputedValues().border_radius(); - GeometryUtilities::GenerateBackground(&geometry, box, {}, border_radius, ColourbPremultiplied(255), clip_area); + MeshUtilities::GenerateBackground(mesh, box, {}, border_radius, ColourbPremultiplied(255), clip_area); + if (RenderManager* render_manager = element->GetRenderManager()) + geometry = render_manager->MakeGeometry(std::move(mesh)); } return &geometry; } -Geometry* ElementBackgroundBorder::GetGeometry(BackgroundType type) +ElementBackgroundBorder::Background* ElementBackgroundBorder::GetBackground(BackgroundType type) { auto it = backgrounds.find(type); if (it != backgrounds.end()) - return &it->second.geometry; + return &it->second; return nullptr; } @@ -105,13 +112,17 @@ ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackgro if (it != backgrounds.end()) return it->second; - return backgrounds.emplace(type, Background{}).first->second; + Background& background = backgrounds[type]; + return background; } void ElementBackgroundBorder::GenerateGeometry(Element* element) { - const ComputedValues& computed = element->GetComputedValues(); + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return; + const ComputedValues& computed = element->GetComputedValues(); const bool has_box_shadow = computed.has_box_shadow(); const float opacity = computed.opacity(); @@ -136,18 +147,19 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) const Vector4f border_radius = computed.border_radius(); Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; - RMLUI_ASSERT(!geometry); + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); for (int i = 0; i < element->GetNumBoxes(); i++) { Vector2f offset; const Box& box = element->GetBox(i, offset); - GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors); + MeshUtilities::GenerateBackgroundBorder(mesh, box, offset, border_radius, background_color, border_colors); } + geometry = render_manager->MakeGeometry(std::move(mesh)); if (has_box_shadow) { - Geometry& background_border_geometry = *GetGeometry(BackgroundType::BackgroundBorder); + Geometry& background_border_geometry = geometry; const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); @@ -156,10 +168,10 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) // Generate the geometry for the box-shadow texture. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); Geometry& shadow_geometry = shadow_background.geometry; - Texture& shadow_texture = shadow_background.texture; + CallbackTexture& shadow_texture = shadow_background.texture; - GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, element, background_border_geometry, std::move(shadow_list), border_radius, - computed.opacity()); + GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, *render_manager, element, background_border_geometry, std::move(shadow_list), + border_radius, computed.opacity()); } } diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index 133e2bc18..d93038606 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -29,8 +29,8 @@ #ifndef RMLUI_CORE_ELEMENTBACKGROUNDBORDER_H #define RMLUI_CORE_ELEMENTBACKGROUNDBORDER_H +#include "../../Include/RmlUi/Core/CallbackTexture.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/Texture.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { @@ -50,10 +50,10 @@ class ElementBackgroundBorder { enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count }; struct Background { Geometry geometry; - Texture texture; + CallbackTexture texture; }; - Geometry* GetGeometry(BackgroundType type); + Background* GetBackground(BackgroundType type); Background& GetOrCreateBackground(BackgroundType type); void GenerateGeometry(Element* element); diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 5da6172bd..de2a35dce 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -28,14 +28,12 @@ #include "ElementDecoration.h" #include "../../Include/RmlUi/Core/ComputedValues.h" -#include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementDocument.h" #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" #include "../../Include/RmlUi/Core/StyleSheet.h" namespace Rml { @@ -58,6 +56,13 @@ void ElementDecoration::InstanceDecorators() RMLUI_ZoneScopedC(0xB22222); ReleaseDecorators(); + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + { + RMLUI_ERRORMSG("Decorators are being instanced before a render manager is available. Is this element attached to the document?"); + return; + } + const ComputedValues& computed = element->GetComputedValues(); if (computed.has_decorator() || computed.has_mask_image()) @@ -88,7 +93,7 @@ void ElementDecoration::InstanceDecorators() } } - const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); + const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*render_manager, *decorators_ptr, source); RMLUI_ASSERT(decorator_list.empty() || decorator_list.size() == decorators_ptr->list.size()); DecoratorEntryList& decorators_target = (id == PropertyId::Decorator ? decorators : mask_images); @@ -133,7 +138,7 @@ void ElementDecoration::InstanceDecorators() SharedPtr filter = declaration.instancer->InstanceFilter(declaration.type, declaration.properties); if (filter) { - list.push_back({std::move(filter), CompiledFilterHandle{}}); + list.push_back({std::move(filter), CompiledFilter{}}); } else { @@ -166,12 +171,7 @@ void ElementDecoration::ReloadDecoratorsData() for (FilterEntryList* list : {&filters, &backdrop_filters}) { for (FilterEntry& filter : *list) - { - if (filter.handle) - filter.filter->ReleaseCompiledFilter(element, filter.handle); - - filter.handle = filter.filter->CompileFilter(element); - } + filter.compiled = filter.filter->CompileFilter(element); } } } @@ -188,15 +188,8 @@ void ElementDecoration::ReleaseDecorators() list->clear(); } - for (FilterEntryList* list : {&filters, &backdrop_filters}) - { - for (FilterEntry& filter : *list) - { - if (filter.handle) - filter.filter->ReleaseCompiledFilter(element, filter.handle); - } - list->clear(); - } + filters.clear(); + backdrop_filters.clear(); } void ElementDecoration::RenderDecorators(RenderStage render_stage) @@ -213,7 +206,8 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) for (int i = (int)decorators.size() - 1; i >= 0; i--) { DecoratorEntry& decorator = decorators[i]; - decorator.decorator->RenderElement(element, decorator.decorator_data); + if (decorator.decorator_data) + decorator.decorator->RenderElement(element, decorator.decorator_data); } } } @@ -221,13 +215,11 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) if (filters.empty() && backdrop_filters.empty() && mask_images.empty()) return; - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - Context* context = element->GetContext(); - if (!render_interface || !context) + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) return; - RenderManager& render_manager = context->GetRenderManager(); - Rectanglei initial_scissor_region = render_manager.GetState().scissor_region; + Rectanglei initial_scissor_region = render_manager->GetState().scissor_region; auto ApplyClippingRegion = [this, &render_manager](PropertyId filter_id) { RMLUI_ASSERT(filter_id == PropertyId::Filter || filter_id == PropertyId::BackdropFilter); @@ -249,8 +241,8 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) Math::ExpandToPixelGrid(filter_region); Rectanglei scissor_region = Rectanglei(filter_region); - scissor_region.IntersectIfValid(render_manager.GetState().scissor_region); - render_manager.SetScissorRegion(scissor_region); + scissor_region.IntersectIfValid(render_manager->GetState().scissor_region); + render_manager->SetScissorRegion(scissor_region); }; if (!backdrop_filters.empty()) @@ -259,18 +251,15 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) { ApplyClippingRegion(PropertyId::BackdropFilter); - render_interface->PushLayer(LayerFill::Clone); + render_manager->PushLayer(LayerFill::Clone); FilterHandleList filter_handles; for (auto& filter : backdrop_filters) - { - if (filter.handle) - filter_handles.push_back(filter.handle); - } + filter.compiled.AddHandleTo(filter_handles); - render_interface->PopLayer(BlendMode::Replace, filter_handles); + render_manager->PopLayer(BlendMode::Replace, filter_handles); - render_manager.SetScissorRegion(initial_scissor_region); + render_manager->SetScissorRegion(initial_scissor_region); } } @@ -278,41 +267,36 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) { if (render_stage == RenderStage::Enter) { - render_interface->PushLayer(LayerFill::Clear); + render_manager->PushLayer(LayerFill::Clear); } else if (render_stage == RenderStage::Exit) { ApplyClippingRegion(PropertyId::Filter); - CompiledFilterHandle mask_image_handle = {}; + CompiledFilter mask_image_filter; FilterHandleList filter_handles; + filter_handles.reserve(filters.size() + (mask_images.empty() ? 0 : 1)); for (auto& filter : filters) - { - if (filter.handle) - filter_handles.push_back(filter.handle); - } + filter.compiled.AddHandleTo(filter_handles); if (!mask_images.empty()) { - render_interface->PushLayer(LayerFill::Clear); + render_manager->PushLayer(LayerFill::Clear); for (int i = (int)mask_images.size() - 1; i >= 0; i--) { DecoratorEntry& mask_image = mask_images[i]; - mask_image.decorator->RenderElement(element, mask_image.decorator_data); + if (mask_image.decorator_data) + mask_image.decorator->RenderElement(element, mask_image.decorator_data); } - mask_image_handle = render_interface->SaveLayerAsMaskImage(); - if (mask_image_handle) - filter_handles.push_back(mask_image_handle); - render_interface->PopLayer(BlendMode::Discard, {}); + mask_image_filter = render_manager->SaveLayerAsMaskImage(); + mask_image_filter.AddHandleTo(filter_handles); + render_manager->PopLayer(BlendMode::Discard, {}); } - render_interface->PopLayer(BlendMode::Blend, filter_handles); - - if (mask_image_handle) - render_interface->ReleaseCompiledFilter(mask_image_handle); - render_manager.SetScissorRegion(initial_scissor_region); + render_manager->PopLayer(BlendMode::Blend, filter_handles); + render_manager->SetScissorRegion(initial_scissor_region); } } } diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementDecoration.h index 9fa17770c..636a23144 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementDecoration.h @@ -29,13 +29,16 @@ #ifndef RMLUI_CORE_ELEMENTDECORATION_H #define RMLUI_CORE_ELEMENTDECORATION_H +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { class Decorator; -class Filter; class Element; +class Filter; +class CompiledFilter; enum class RenderStage { Enter, Decoration, Exit }; @@ -76,7 +79,7 @@ class ElementDecoration { struct FilterEntry { SharedPtr filter; - CompiledFilterHandle handle; + CompiledFilter compiled; }; using FilterEntryList = Vector; diff --git a/Source/Core/ElementText.cpp b/Source/Core/ElementText.cpp index 4cf61ca19..f117076fc 100644 --- a/Source/Core/ElementText.cpp +++ b/Source/Core/ElementText.cpp @@ -33,7 +33,7 @@ #include "../../Include/RmlUi/Core/ElementUtilities.h" #include "../../Include/RmlUi/Core/Event.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/Profiling.h" #include "../../Include/RmlUi/Core/Property.h" #include "../../Include/RmlUi/Core/RenderManager.h" @@ -99,6 +99,8 @@ void ElementText::OnRender() if (font_face_handle == 0) return; + RenderManager& render_manager = GetContext()->GetRenderManager(); + // If our font effects have potentially changed, update it and force a geometry generation if necessary. if (font_effects_dirty && UpdateFontEffects()) geometry_dirty = true; @@ -113,7 +115,7 @@ void ElementText::OnRender() // Regenerate the geometry if the colour or font configuration has altered. if (geometry_dirty) - GenerateGeometry(font_face_handle); + GenerateGeometry(render_manager, font_face_handle); // Regenerate text decoration if necessary. if (decoration_property != generated_decoration) @@ -124,12 +126,14 @@ void ElementText::OnRender() } else { + Mesh mesh; if (decoration) - decoration->Release(true); + mesh = decoration->Release(Geometry::ReleaseMode::ClearMesh); else decoration = MakeUnique(); - GenerateDecoration(font_face_handle); + GenerateDecoration(mesh, font_face_handle); + *decoration = GetRenderManager()->MakeGeometry(std::move(mesh)); } generated_decoration = decoration_property; @@ -138,7 +142,6 @@ void ElementText::OnRender() const Vector2f translation = GetAbsoluteOffset(); bool render = true; - const RenderManager& render_manager = GetContext()->GetRenderManager(); // Do a visibility test against the scissor region to avoid unnecessary render calls. Instead of handling // culling in complicated transform cases, for simplicity we always proceed to render if one is detected. @@ -169,7 +172,7 @@ void ElementText::OnRender() if (render) { for (size_t i = 0; i < geometry.size(); ++i) - geometry[i].Render(translation); + geometry[i].geometry.Render(translation, geometry[i].texture); } if (decoration) @@ -302,10 +305,7 @@ bool ElementText::GenerateLine(String& line, int& line_length, float& line_width void ElementText::ClearLines() { - // Clear the rendering information. - for (size_t i = 0; i < geometry.size(); ++i) - geometry[i].Release(true); - + geometry.clear(); lines.clear(); generated_decoration = Style::TextDecoration::None; } @@ -397,11 +397,12 @@ void ElementText::OnPropertyChange(const PropertyIdSet& changed_properties) // Re-colour the decoration geometry. if (decoration) { - Vector& vertices = decoration->GetVertices(); - for (size_t i = 0; i < vertices.size(); ++i) - vertices[i].colour = colour; + Mesh mesh = decoration->Release(); + for (Vertex& vertex : mesh.vertices) + vertex.colour = colour; - decoration->Release(); + if (RenderManager* render_manager = GetRenderManager()) + *decoration = render_manager->MakeGeometry(std::move(mesh)); } } } @@ -443,32 +444,37 @@ bool ElementText::UpdateFontEffects() return false; } -void ElementText::GenerateGeometry(const FontFaceHandle font_face_handle) +void ElementText::GenerateGeometry(RenderManager& render_manager, const FontFaceHandle font_face_handle) { RMLUI_ZoneScopedC(0xD2691E); - // Release the old geometry ... - for (size_t i = 0; i < geometry.size(); ++i) - geometry[i].Release(true); + const float letter_spacing = GetComputedValues().letter_spacing(); + + // Release the old geometry, and reuse the mesh buffers. + TexturedMeshList mesh_list(geometry.size()); + for (size_t i = 0; i < geometry.size(); i++) + mesh_list[i].mesh = geometry[i].geometry.Release(Geometry::ReleaseMode::ClearMesh); - // ... and generate it all again! + // Generate the new geometry, one line at a time. for (size_t i = 0; i < lines.size(); ++i) - GenerateGeometry(font_face_handle, lines[i]); + { + lines[i].width = GetFontEngineInterface()->GenerateString(render_manager, font_face_handle, font_effects_handle, lines[i].text, + lines[i].position, colour, opacity, letter_spacing, mesh_list); + } - generated_decoration = Style::TextDecoration::None; + // Apply the new geometry and textures. + geometry.resize(mesh_list.size()); + for (size_t i = 0; i < geometry.size(); i++) + { + geometry[i].geometry = render_manager.MakeGeometry(std::move(mesh_list[i].mesh)); + geometry[i].texture = mesh_list[i].texture; + } + generated_decoration = Style::TextDecoration::None; geometry_dirty = false; } -void ElementText::GenerateGeometry(const FontFaceHandle font_face_handle, Line& line) -{ - const float letter_spacing = GetComputedValues().letter_spacing(); - - line.width = GetFontEngineInterface()->GenerateString(font_face_handle, font_effects_handle, line.text, line.position, colour, opacity, - letter_spacing, geometry); -} - -void ElementText::GenerateDecoration(const FontFaceHandle font_face_handle) +void ElementText::GenerateDecoration(Mesh& mesh, const FontFaceHandle font_face_handle) { RMLUI_ZoneScopedC(0xA52A2A); RMLUI_ASSERT(decoration); @@ -488,7 +494,7 @@ void ElementText::GenerateDecoration(const FontFaceHandle font_face_handle) { const Vector2f position = {line.position.x, line.position.y + offset}; const Vector2f size = {(float)line.width, metrics.underline_thickness}; - GeometryUtilities::GenerateLine(decoration.get(), position, size, colour); + MeshUtilities::GenerateLine(mesh, position, size, colour); } } diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index 1fe5f1483..f38f605ba 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -27,6 +27,7 @@ */ #include "../../Include/RmlUi/Core/ElementUtilities.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" #include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" @@ -35,13 +36,11 @@ #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" #include "../../Include/RmlUi/Core/Math.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" #include "../../Include/RmlUi/Core/RenderManager.h" #include "DataController.h" #include "DataModel.h" #include "DataView.h" #include "ElementBackgroundBorder.h" -#include "ElementStyle.h" #include "Layout/LayoutDetails.h" #include "Layout/LayoutEngine.h" #include "TransformState.h" diff --git a/Source/Core/Elements/ElementImage.cpp b/Source/Core/Elements/ElementImage.cpp index 46716806c..6f087db14 100644 --- a/Source/Core/Elements/ElementImage.cpp +++ b/Source/Core/Elements/ElementImage.cpp @@ -30,9 +30,10 @@ #include "../../../Include/RmlUi/Core/ComputedValues.h" #include "../../../Include/RmlUi/Core/ElementDocument.h" #include "../../../Include/RmlUi/Core/ElementUtilities.h" -#include "../../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" #include "../../../Include/RmlUi/Core/PropertyIdSet.h" #include "../../../Include/RmlUi/Core/StyleSheet.h" +#include "../../../Include/RmlUi/Core/Texture.h" #include "../../../Include/RmlUi/Core/URL.h" #include "../TextureDatabase.h" @@ -86,7 +87,7 @@ void ElementImage::OnRender() GenerateGeometry(); // Render the geometry beginning at this element's content region. - geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round()); + geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round(), texture); } void ElementImage::OnAttributeChange(const ElementAttributes& changed_attributes) @@ -141,9 +142,7 @@ void ElementImage::OnChildAdd(Element* child) // texture won't actually be loaded from the backend before it is shown. However, only do this if we have an active context so that the dp-ratio // can be retrieved. If there is no context now the texture loading will be deferred until the next layout update. if (child == this && texture_dirty && GetContext()) - { LoadTexture(); - } } void ElementImage::OnResize() @@ -169,13 +168,7 @@ void ElementImage::OnStyleSheetChange() void ElementImage::GenerateGeometry() { // Release the old geometry before specifying the new vertices. - geometry.Release(true); - - Vector& vertices = geometry.GetVertices(); - Vector& indices = geometry.GetIndices(); - - vertices.resize(4); - indices.resize(6); + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); // Generate the texture coordinates. Vector2f texcoords[2]; @@ -195,7 +188,10 @@ void ElementImage::GenerateGeometry() ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); Vector2f quad_size = GetBox().GetSize(BoxArea::Content).Round(); - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]); + + if (RenderManager* render_manager = GetRenderManager()) + geometry = render_manager->MakeGeometry(std::move(mesh)); geometry_dirty = false; } @@ -206,6 +202,13 @@ bool ElementImage::LoadTexture() geometry_dirty = true; dimensions_scale = 1.0f; + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + { + texture = {}; + return false; + } + const float dp_ratio = ElementUtilities::GetDensityIndependentPixelRatio(this); // Check for a sprite first, this takes precedence. @@ -223,7 +226,7 @@ bool ElementImage::LoadTexture() { rect = sprite->rectangle; rect_source = RectSource::Sprite; - texture = sprite->sprite_sheet->texture; + texture = sprite->sprite_sheet->texture_source.GetTexture(*render_manager); dimensions_scale = sprite->sprite_sheet->display_scale * dp_ratio; valid_sprite = true; } @@ -232,7 +235,7 @@ bool ElementImage::LoadTexture() if (!valid_sprite) { - texture = Texture(); + texture = {}; rect_source = RectSource::None; UpdateRect(); Log::Message(Log::LT_WARNING, "Could not find sprite '%s' specified in img element %s", sprite_name.c_str(), GetAddress().c_str()); @@ -245,7 +248,7 @@ bool ElementImage::LoadTexture() const String source_name = GetAttribute("src", ""); if (source_name.empty()) { - texture = Texture(); + texture = {}; rect_source = RectSource::None; return false; } @@ -255,14 +258,11 @@ bool ElementImage::LoadTexture() if (ElementDocument* document = GetOwnerDocument()) source_url.SetURL(document->GetSourceURL()); - texture.Set(source_name, source_url.GetPath()); + texture = render_manager->LoadTexture(source_name, source_url.GetPath()); dimensions_scale = dp_ratio; } - // Set the texture onto our geometry object. - geometry.SetTexture(&texture); - return true; } diff --git a/Source/Core/Elements/ElementProgress.cpp b/Source/Core/Elements/ElementProgress.cpp index 0b382590c..522897110 100644 --- a/Source/Core/Elements/ElementProgress.cpp +++ b/Source/Core/Elements/ElementProgress.cpp @@ -31,8 +31,8 @@ #include "../../../Include/RmlUi/Core/ElementDocument.h" #include "../../../Include/RmlUi/Core/ElementUtilities.h" #include "../../../Include/RmlUi/Core/Factory.h" -#include "../../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" #include "../../../Include/RmlUi/Core/PropertyIdSet.h" #include "../../../Include/RmlUi/Core/StyleSheet.h" #include "../../../Include/RmlUi/Core/URL.h" @@ -92,7 +92,7 @@ void ElementProgress::OnRender() GenerateGeometry(); // Render the geometry at the fill element's content region. - geometry.Render(fill->GetAbsoluteOffset()); + geometry.Render(fill->GetAbsoluteOffset(), texture); } void ElementProgress::OnAttributeChange(const ElementAttributes& changed_attributes) @@ -179,6 +179,8 @@ void ElementProgress::OnResize() void ElementProgress::GenerateGeometry() { + geometry_dirty = false; + // Warn the user when using the old approach of adding the 'fill-image' property to the 'fill' element. if (fill->GetLocalProperty(PropertyId::FillImage)) Log::Message(Log::LT_WARNING, @@ -218,8 +220,7 @@ void ElementProgress::GenerateGeometry() fill->SetOffset(offset, this); } - geometry.Release(true); - geometry_dirty = false; + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); // If we don't have a fill texture, then there is no need to generate manual geometry, and we are done here. // Instead, users can style the fill element eg. by decorators. @@ -227,8 +228,8 @@ void ElementProgress::GenerateGeometry() return; // Otherwise, the 'fill-image' property is set, let's generate its geometry. - auto& vertices = geometry.GetVertices(); - auto& indices = geometry.GetIndices(); + Vector& vertices = mesh.vertices; + Vector& indices = mesh.indices; Vector2f texcoords[2]; if (rect_set) @@ -329,10 +330,10 @@ void ElementProgress::GenerateGeometry() if (!is_circular) { - vertices.resize(4); - indices.resize(6); - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0), render_size, quad_colour, texcoords[0], texcoords[1]); + MeshUtilities::GenerateQuad(mesh, Vector2f(0), render_size, quad_colour, texcoords[0], texcoords[1]); } + + geometry = GetRenderManager()->MakeGeometry(std::move(mesh)); } bool ElementProgress::LoadTexture() @@ -345,6 +346,10 @@ bool ElementProgress::LoadTexture() if (const Property* property = GetLocalProperty(PropertyId::FillImage)) name = property->Get(); + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + return false; + ElementDocument* document = GetOwnerDocument(); bool texture_set = false; @@ -358,7 +363,7 @@ bool ElementProgress::LoadTexture() { rect = sprite->rectangle; rect_set = true; - texture = sprite->sprite_sheet->texture; + texture = sprite->sprite_sheet->texture_source.GetTexture(*render_manager); texture_set = true; } } @@ -368,7 +373,7 @@ bool ElementProgress::LoadTexture() { URL source_url; source_url.SetURL(document->GetSourceURL()); - texture.Set(name, source_url.GetPath()); + texture = render_manager->LoadTexture(name, source_url.GetPath()); texture_set = true; } } @@ -379,9 +384,6 @@ bool ElementProgress::LoadTexture() rect = {}; } - // Set the texture onto our geometry object. - geometry.SetTexture(&texture); - return true; } diff --git a/Source/Core/Elements/WidgetTextInput.cpp b/Source/Core/Elements/WidgetTextInput.cpp index 33573dc86..1866c27c0 100644 --- a/Source/Core/Elements/WidgetTextInput.cpp +++ b/Source/Core/Elements/WidgetTextInput.cpp @@ -36,9 +36,9 @@ #include "../../../Include/RmlUi/Core/Elements/ElementFormControl.h" #include "../../../Include/RmlUi/Core/Factory.h" #include "../../../Include/RmlUi/Core/FontEngineInterface.h" -#include "../../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../../Include/RmlUi/Core/Input.h" #include "../../../Include/RmlUi/Core/Math.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" #include "../../../Include/RmlUi/Core/StringUtilities.h" #include "../../../Include/RmlUi/Core/SystemInterface.h" #include "../Clock.h" @@ -1065,12 +1065,6 @@ Vector2f WidgetTextInput::FormatText(float height_constraint) text_element->ClearLines(); selected_text_element->ClearLines(); - // Clear the selection background geometry, and get the vertices and indices so the new geo can - // be generated. - selection_geometry.Release(true); - Vector& selection_vertices = selection_geometry.GetVertices(); - Vector& selection_indices = selection_geometry.GetIndices(); - // Determine the line-height of the text element. const float line_height = parent->GetLineHeight(); const float font_baseline = GetFontEngineInterface()->GetFontMetrics(font_handle).ascent; @@ -1220,6 +1214,9 @@ Vector2f WidgetTextInput::FormatText(float height_constraint) // Clamp the cursor to a valid range. absolute_cursor_index = Math::Min(absolute_cursor_index, (int)GetValue().size()); + // Clear the selection background geometry, and get the vertices and indices so the new geometry can be generated. + Mesh selection_mesh = selection_geometry.Release(Geometry::ReleaseMode::ClearMesh); + // Transform segments according to text alignment for (auto& it : segments) { @@ -1234,10 +1231,7 @@ Vector2f WidgetTextInput::FormatText(float height_constraint) const bool selection_contains_endline = (selection_begin_index + selection_length > line_begin + lines[it.line_index].editable_length); const Vector2f selection_size(float(it.width + (selection_contains_endline ? endline_selection_width : 0)), line_height); - selection_vertices.resize(selection_vertices.size() + 4); - selection_indices.resize(selection_indices.size() + 6); - GeometryUtilities::GenerateQuad(&selection_vertices[selection_vertices.size() - 4], &selection_indices[selection_indices.size() - 6], - it.position - Vector2f(0, font_baseline), selection_size, selection_colour, (int)selection_vertices.size() - 4); + MeshUtilities::GenerateQuad(selection_mesh, it.position - Vector2f(0, font_baseline), selection_size, selection_colour); selected_text_element->AddLine(it.position, it.content); } @@ -1245,20 +1239,13 @@ Vector2f WidgetTextInput::FormatText(float height_constraint) text_element->AddLine(it.position, it.content); } + selection_geometry = parent->GetRenderManager()->MakeGeometry(std::move(selection_mesh)); + return content_area; } void WidgetTextInput::GenerateCursor() { - // Generates the cursor. - cursor_geometry.Release(); - - Vector& vertices = cursor_geometry.GetVertices(); - vertices.resize(4); - - Vector& indices = cursor_geometry.GetIndices(); - indices.resize(6); - cursor_size.x = Math::Round(ElementUtilities::GetDensityIndependentPixelRatio(text_element)); cursor_size.y = text_element->GetLineHeight() + 2.0f; @@ -1270,7 +1257,9 @@ void WidgetTextInput::GenerateCursor() color = property->Get(); } - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color.ToPremultiplied()); + Mesh mesh = cursor_geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), cursor_size, color.ToPremultiplied()); + cursor_geometry = parent->GetRenderManager()->MakeGeometry(std::move(mesh)); } void WidgetTextInput::ForceFormattingOnNextLayout() diff --git a/Source/Core/Factory.cpp b/Source/Core/Factory.cpp index 5f3db9678..fdeaef91c 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -342,9 +342,9 @@ void Factory::RegisterContextInstancer(ContextInstancer* instancer) context_instancer = instancer; } -ContextPtr Factory::InstanceContext(const String& name) +ContextPtr Factory::InstanceContext(const String& name, RenderManager* render_manager) { - ContextPtr new_context = context_instancer->InstanceContext(name); + ContextPtr new_context = context_instancer->InstanceContext(name, render_manager); if (new_context) new_context->SetInstancer(context_instancer); return new_context; diff --git a/Source/Core/Filter.cpp b/Source/Core/Filter.cpp index bd56b23a3..ee1ed7b3e 100644 --- a/Source/Core/Filter.cpp +++ b/Source/Core/Filter.cpp @@ -27,6 +27,7 @@ */ #include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { diff --git a/Source/Core/FilterBasic.cpp b/Source/Core/FilterBasic.cpp index c2ab22ad0..79c534d80 100644 --- a/Source/Core/FilterBasic.cpp +++ b/Source/Core/FilterBasic.cpp @@ -27,10 +27,11 @@ */ #include "FilterBasic.h" -#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -41,15 +42,9 @@ bool FilterBasic::Initialise(const String& in_name, float in_value) return true; } -CompiledFilterHandle FilterBasic::CompileFilter(Element* /*element*/) const +CompiledFilter FilterBasic::CompileFilter(Element* element) const { - CompiledFilterHandle handle = GetRenderInterface()->CompileFilter(name, Dictionary{{"value", Variant(value)}}); - return handle; -} - -void FilterBasic::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const -{ - GetRenderInterface()->ReleaseCompiledFilter(filter_handle); + return element->GetRenderManager()->CompileFilter(name, Dictionary{{"value", Variant(value)}}); } FilterBasicInstancer::FilterBasicInstancer(ValueType value_type, const char* default_value) diff --git a/Source/Core/FilterBasic.h b/Source/Core/FilterBasic.h index 822423991..e1a546c0c 100644 --- a/Source/Core/FilterBasic.h +++ b/Source/Core/FilterBasic.h @@ -38,9 +38,7 @@ class FilterBasic : public Filter { public: bool Initialise(const String& name, float value); - CompiledFilterHandle CompileFilter(Element* element) const override; - - void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + CompiledFilter CompileFilter(Element* element) const override; private: String name; diff --git a/Source/Core/FilterBlur.cpp b/Source/Core/FilterBlur.cpp index 269b13edb..10372cf98 100644 --- a/Source/Core/FilterBlur.cpp +++ b/Source/Core/FilterBlur.cpp @@ -27,10 +27,11 @@ */ #include "FilterBlur.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -40,16 +41,10 @@ bool FilterBlur::Initialise(NumericValue in_radius) return Any(in_radius.unit & Unit::LENGTH); } -CompiledFilterHandle FilterBlur::CompileFilter(Element* element) const +CompiledFilter FilterBlur::CompileFilter(Element* element) const { const float radius = element->ResolveLength(radius_value); - CompiledFilterHandle handle = GetRenderInterface()->CompileFilter("blur", Dictionary{{"radius", Variant(radius)}}); - return handle; -} - -void FilterBlur::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const -{ - GetRenderInterface()->ReleaseCompiledFilter(filter_handle); + return element->GetRenderManager()->CompileFilter("blur", Dictionary{{"radius", Variant(radius)}}); } void FilterBlur::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const diff --git a/Source/Core/FilterBlur.h b/Source/Core/FilterBlur.h index f3bb101f7..5531a56ad 100644 --- a/Source/Core/FilterBlur.h +++ b/Source/Core/FilterBlur.h @@ -39,9 +39,7 @@ class FilterBlur : public Filter { public: bool Initialise(NumericValue radius); - CompiledFilterHandle CompileFilter(Element* element) const override; - - void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + CompiledFilter CompileFilter(Element* element) const override; void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; diff --git a/Source/Core/FilterDropShadow.cpp b/Source/Core/FilterDropShadow.cpp index db8bba2d2..803f447c8 100644 --- a/Source/Core/FilterDropShadow.cpp +++ b/Source/Core/FilterDropShadow.cpp @@ -27,10 +27,11 @@ */ #include "FilterDropShadow.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertyDictionary.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { @@ -43,7 +44,7 @@ bool FilterDropShadow::Initialise(Colourb in_color, NumericValue in_offset_x, Nu return Any(in_offset_x.unit & Unit::LENGTH) && Any(in_offset_y.unit & Unit::LENGTH) && Any(in_sigma.unit & Unit::LENGTH); } -CompiledFilterHandle FilterDropShadow::CompileFilter(Element* element) const +CompiledFilter FilterDropShadow::CompileFilter(Element* element) const { const float sigma = element->ResolveLength(value_sigma); const Vector2f offset = { @@ -51,15 +52,10 @@ CompiledFilterHandle FilterDropShadow::CompileFilter(Element* element) const element->ResolveLength(value_offset_y), }; - CompiledFilterHandle handle = GetRenderInterface()->CompileFilter("drop-shadow", + CompiledFilter filter = element->GetRenderManager()->CompileFilter("drop-shadow", Dictionary{{"color", Variant(color)}, {"offset", Variant(offset)}, {"sigma", Variant(sigma)}}); - return handle; -} - -void FilterDropShadow::ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle filter_handle) const -{ - GetRenderInterface()->ReleaseCompiledFilter(filter_handle); + return filter; } void FilterDropShadow::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const diff --git a/Source/Core/FilterDropShadow.h b/Source/Core/FilterDropShadow.h index 0e15f1805..837a44d9a 100644 --- a/Source/Core/FilterDropShadow.h +++ b/Source/Core/FilterDropShadow.h @@ -39,9 +39,7 @@ class FilterDropShadow : public Filter { public: bool Initialise(Colourb color, NumericValue offset_x, NumericValue offset_y, NumericValue sigma); - CompiledFilterHandle CompileFilter(Element* element) const override; - - void ReleaseCompiledFilter(Element* element, CompiledFilterHandle filter_handle) const override; + CompiledFilter CompileFilter(Element* element) const override; void ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const override; diff --git a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp index d07e56b2e..76255e67a 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp @@ -77,11 +77,11 @@ int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, const Stri return handle_default->GetStringWidth(string, letter_spacing, prior_character); } -int FontEngineInterfaceDefault::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string, - const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, GeometryList& geometry) +int FontEngineInterfaceDefault::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle, + const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, TexturedMeshList& mesh_list) { auto handle_default = reinterpret_cast(handle); - return handle_default->GenerateString(geometry, string, position, colour, opacity, letter_spacing, (int)font_effects_handle); + return handle_default->GenerateString(render_manager, mesh_list, string, position, colour, opacity, letter_spacing, (int)font_effects_handle); } int FontEngineInterfaceDefault::GetVersion(FontFaceHandle handle) diff --git a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h index 405f82d71..6fd8e2732 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h @@ -50,17 +50,17 @@ class RMLUICORE_API FontEngineInterfaceDefault : public FontEngineInterface { FontFaceHandle GetFontFaceHandle(const String& family, Style::FontStyle style, Style::FontWeight weight, int size) override; /// Prepares for font effects by configuring a new, or returning an existing, layer configuration. - FontEffectsHandle PrepareFontEffects(FontFaceHandle, const FontEffectList& font_effects) override; + FontEffectsHandle PrepareFontEffects(FontFaceHandle handle, const FontEffectList& font_effects) override; /// Returns the font metrics of the given font face. const FontMetrics& GetFontMetrics(FontFaceHandle handle) override; /// Returns the width a string will take up if rendered with this handle. - int GetStringWidth(FontFaceHandle, const String& string, float letter_spacing, Character prior_character) override; + int GetStringWidth(FontFaceHandle handle, const String& string, float letter_spacing, Character prior_character) override; /// Generates the geometry required to render a single line of text. - int GenerateString(FontFaceHandle, FontEffectsHandle, const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, - float letter_spacing, GeometryList& geometry) override; + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, const String& string, + const Vector2f& position, ColourbPremultiplied colour, float opacity, float letter_spacing, TexturedMeshList& mesh_list) override; /// Returns the current version of the font face. int GetVersion(FontFaceHandle handle) override; diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp index f837d900e..6225ed66f 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp @@ -33,6 +33,7 @@ #include "FontProvider.h" #include "FreeTypeInterface.h" #include +#include namespace Rml { @@ -188,8 +189,8 @@ bool FontFaceHandleDefault::GenerateLayerTexture(UniquePtr& textur return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs); } -int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const ColourbPremultiplied colour, - const float opacity, const float letter_spacing, const int layer_configuration_index) +int FontFaceHandleDefault::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, const Vector2f position, + const ColourbPremultiplied colour, const float opacity, const float letter_spacing, const int layer_configuration_index) { int geometry_index = 0; int line_width = 0; @@ -202,12 +203,15 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& // Fetch the requested configuration and generate the geometry for each one. const LayerConfiguration& layer_configuration = layer_configurations[layer_configuration_index]; - // Reserve for the common case of one texture per layer. - geometry.reserve(layer_configuration.size()); + // Each texture represents one geometry. + const int num_geometries = std::accumulate(layer_configuration.begin(), layer_configuration.end(), 0, + [](int sum, const FontFaceLayer* layer) { return sum + layer->GetNumTextures(); }); - for (size_t i = 0; i < layer_configuration.size(); ++i) + mesh_list.resize(num_geometries); + + for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index) { - FontFaceLayer* layer = layer_configuration[i]; + FontFaceLayer* layer = layer_configuration[layer_index]; ColourbPremultiplied layer_colour; if (layer == base_layer) @@ -216,25 +220,20 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& layer_colour = layer->GetColour(opacity); const int num_textures = layer->GetNumTextures(); - if (num_textures == 0) continue; - // Resize the geometry list if required. - if ((int)geometry.size() < geometry_index + num_textures) - geometry.resize(geometry_index + num_textures); - - RMLUI_ASSERT(geometry_index < (int)geometry.size()); - - // Bind the textures to the geometries. - for (int tex_index = 0; tex_index < num_textures; ++tex_index) - geometry[geometry_index + tex_index].SetTexture(layer->GetTexture(tex_index)); + RMLUI_ASSERT(geometry_index + num_textures <= (int)mesh_list.size()); line_width = 0; Character prior_character = Character::Null; - geometry[geometry_index].GetIndices().reserve(string.size() * 6); - geometry[geometry_index].GetVertices().reserve(string.size() * 4); + // Set the mesh and textures to the geometries. + for (int tex_index = 0; tex_index < num_textures; ++tex_index) + mesh_list[geometry_index + tex_index].texture = layer->GetTexture(render_manager, tex_index); + + mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6); + mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4); for (auto it_string = StringIteratorU8(string); it_string; ++it_string) { @@ -252,7 +251,7 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& if (layer == base_layer && glyph->color_format == ColorFormat::RGBA8) glyph_color = ColourbPremultiplied(layer_colour.alpha, layer_colour.alpha); - layer->GenerateGeometry(&geometry[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color); + layer->GenerateGeometry(&mesh_list[geometry_index], character, Vector2f(position.x + line_width, position.y), glyph_color); line_width += glyph->advance; line_width += (int)letter_spacing; @@ -262,9 +261,6 @@ int FontFaceHandleDefault::GenerateString(GeometryList& geometry, const String& geometry_index += num_textures; } - // Cull any excess geometry from a previous generation. - geometry.resize(geometry_index); - return Math::Max(line_width, 0); } diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h index 11c3f0862..0b30d43d9 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h @@ -77,15 +77,17 @@ class FontFaceHandleDefault final : public NonCopyMoveable { int handle_version) const; /// Generates the geometry required to render a single line of text. - /// @param[out] geometry An array of geometries to generate the geometry into. + /// @param[in] render_manager The render manager responsible for rendering the string. + /// @param[out] mesh_list A list to place the new meshes into. /// @param[in] string The string to render. /// @param[in] position The position of the baseline of the first character to render. /// @param[in] colour The colour to render the text. /// @param[in] opacity The opacity of the text, should be applied to font effects. + /// @param[in] letter_spacing The letter spacing size in pixels. /// @param[in] layer_configuration Face configuration index to use for generating string. /// @return The width, in pixels, of the string geometry. - int GenerateString(GeometryList& geometry, const String& string, Vector2f position, ColourbPremultiplied colour, float opacity, - float letter_spacing, int layer_configuration = 0); + int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, Vector2f position, + ColourbPremultiplied colour, float opacity, float letter_spacing, int layer_configuration); /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. int GetVersion() const; diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Source/Core/FontEngineDefault/FontFaceLayer.cpp index bdd5de153..c81947168 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.cpp +++ b/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -27,10 +27,10 @@ */ #include "FontFaceLayer.h" -#include "../../../Include/RmlUi/Core/Log.h" -#include "../../../Include/RmlUi/Core/RenderInterface.h" +#include "../../../Include/RmlUi/Core/RenderManager.h" #include "FontFaceHandleDefault.h" #include +#include namespace Rml { @@ -51,7 +51,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace // Right now we re-generate the whole thing, including textures. texture_layout = TextureLayout{}; character_boxes.clear(); - textures.clear(); + textures_owned.clear(); + textures_ptr = &textures_owned; } const FontGlyphMap& glyphs = handle->GetGlyphs(); @@ -62,9 +63,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace // Clone the geometry and textures from the clone layer. character_boxes = clone->character_boxes; - // Copy the cloned layer's textures. - for (size_t i = 0; i < clone->textures.size(); ++i) - textures.push_back(clone->textures[i]); + // Point our textures to the cloned layer's textures. + textures_ptr = clone->textures_ptr; // Request the effect (if we have one) and adjust the origins as appropriate. if (effect && !clone_glyph_origins) @@ -160,19 +160,21 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace { const int texture_id = i; - TextureCallback texture_callback = [handle, effect_ptr, texture_id, handle_version](RenderInterface* render_interface, - const String& /*name*/, TextureHandle& out_texture_handle, Vector2i& out_dimensions) -> bool { + CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( + const CallbackTextureInterface& texture_interface) -> bool { + Vector2i dimensions; UniquePtr data; - if (!handle->GenerateLayerTexture(data, out_dimensions, effect_ptr, texture_id, handle_version) || !data) + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || !data) return false; - if (!render_interface->GenerateTexture(out_texture_handle, data.get(), out_dimensions)) + if (!texture_interface.GenerateTexture(data.get(), dimensions)) return false; return true; }; - Texture texture; - texture.Set("font-face-layer", std::move(texture_callback)); - textures.push_back(texture); + static_assert(std::is_nothrow_move_constructible::value, + "CallbackTextureSource must be nothrow move constructible so that it can be placed in the vector below."); + + textures_owned.emplace_back(std::move(texture_callback)); } } @@ -252,17 +254,17 @@ const FontEffect* FontFaceLayer::GetFontEffect() const return effect.get(); } -const Texture* FontFaceLayer::GetTexture(int index) +Texture FontFaceLayer::GetTexture(RenderManager& render_manager, int index) { RMLUI_ASSERT(index >= 0); RMLUI_ASSERT(index < GetNumTextures()); - return &(textures[index]); + return (*textures_ptr)[index].GetTexture(render_manager); } int FontFaceLayer::GetNumTextures() const { - return (int)textures.size(); + return (int)textures_ptr->size(); } ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.h b/Source/Core/FontEngineDefault/FontFaceLayer.h index 5f8c69509..e19cc2938 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.h +++ b/Source/Core/FontEngineDefault/FontFaceLayer.h @@ -29,10 +29,10 @@ #ifndef RMLUI_CORE_FONTENGINEDEFAULT_FONTFACELAYER_H #define RMLUI_CORE_FONTENGINEDEFAULT_FONTFACELAYER_H +#include "../../../Include/RmlUi/Core/CallbackTexture.h" #include "../../../Include/RmlUi/Core/FontGlyph.h" #include "../../../Include/RmlUi/Core/Geometry.h" -#include "../../../Include/RmlUi/Core/GeometryUtilities.h" -#include "../../../Include/RmlUi/Core/Texture.h" +#include "../../../Include/RmlUi/Core/MeshUtilities.h" #include "../TextureLayout.h" namespace Rml { @@ -54,8 +54,8 @@ class FontFaceLayer { /// Generates or re-generates the character and texture data for the layer. /// @param[in] handle The handle generating this layer. - /// @param[in] effect The effect to initialise the layer with. /// @param[in] clone The layer to optionally clone geometry and texture data from. + /// @param[in] clone_glyph_origins True to keep the character origins from the cloned layer, false to generate new ones. /// @return True if the layer was generated successfully, false if not. bool Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false); @@ -67,11 +67,12 @@ class FontFaceLayer { bool GenerateTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs); /// Generates the geometry required to render a single character. - /// @param[out] geometry An array of geometries this layer will write to. It must be at least as big as the number of textures in this layer. + /// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer. /// @param[in] character_code The character to generate geometry for. /// @param[in] position The position of the baseline. /// @param[in] colour The colour of the string. - inline void GenerateGeometry(Geometry* geometry, const Character character_code, const Vector2f position, const ColourbPremultiplied colour) const + inline void GenerateGeometry(TexturedMesh* mesh_list, const Character character_code, const Vector2f position, + const ColourbPremultiplied colour) const { auto it = character_boxes.find(character_code); if (it == character_boxes.end()) @@ -83,21 +84,15 @@ class FontFaceLayer { return; // Generate the geometry for the character. - Vector& character_vertices = geometry[box.texture_index].GetVertices(); - Vector& character_indices = geometry[box.texture_index].GetIndices(); - - character_vertices.resize(character_vertices.size() + 4); - character_indices.resize(character_indices.size() + 6); - GeometryUtilities::GenerateQuad(&character_vertices[0] + (character_vertices.size() - 4), - &character_indices[0] + (character_indices.size() - 6), Vector2f(position.x + box.origin.x, position.y + box.origin.y).Round(), - box.dimensions, colour, box.texcoords[0], box.texcoords[1], (int)character_vertices.size() - 4); + Mesh& mesh = mesh_list[box.texture_index].mesh; + MeshUtilities::GenerateQuad(mesh, (position + box.origin).Round(), box.dimensions, colour, box.texcoords[0], box.texcoords[1]); } /// Returns the effect used to generate the layer. const FontEffect* GetFontEffect() const; /// Returns one of the layer's textures. - const Texture* GetTexture(int index); + Texture GetTexture(RenderManager& render_manager, int index); /// Returns the number of textures employed by this layer. int GetNumTextures() const; @@ -106,8 +101,6 @@ class FontFaceLayer { private: struct TextureBox { - TextureBox() : texture_index(-1) {} - // The offset, in pixels, of the baseline from the start of this character's geometry. Vector2f origin; // The width and height, in pixels, of this character's geometry. @@ -116,18 +109,19 @@ class FontFaceLayer { Vector2f texcoords[2]; // The texture this character renders from. - int texture_index; + int texture_index = -1; }; using CharacterMap = UnorderedMap; - using TextureList = Vector; + using TextureList = Vector; SharedPtr effect; - TextureLayout texture_layout; + TextureList textures_owned; + TextureList* textures_ptr = &textures_owned; + TextureLayout texture_layout; CharacterMap character_boxes; - TextureList textures; Colourb colour; }; diff --git a/Source/Core/FontEngineInterface.cpp b/Source/Core/FontEngineInterface.cpp index 8dab86430..b8caa1b7e 100644 --- a/Source/Core/FontEngineInterface.cpp +++ b/Source/Core/FontEngineInterface.cpp @@ -67,8 +67,9 @@ int FontEngineInterface::GetStringWidth(FontFaceHandle /*handle*/, const String& return 0; } -int FontEngineInterface::GenerateString(FontFaceHandle /*face_handle*/, FontEffectsHandle /*font_effects_handle*/, const String& /*string*/, - const Vector2f& /*position*/, ColourbPremultiplied /*colour*/, float /*opacity*/, float /*letter_spacing*/, GeometryList& /*geometry*/) +int FontEngineInterface::GenerateString(RenderManager& /*render_manager*/, FontFaceHandle /*face_handle*/, FontEffectsHandle /*font_effects_handle*/, + const String& /*string*/, const Vector2f& /*position*/, ColourbPremultiplied /*colour*/, float /*opacity*/, float /*letter_spacing*/, + TexturedMeshList& /*mesh_list*/) { return 0; } diff --git a/Source/Core/Geometry.cpp b/Source/Core/Geometry.cpp index 08ade2363..0be6033c2 100644 --- a/Source/Core/Geometry.cpp +++ b/Source/Core/Geometry.cpp @@ -27,183 +27,35 @@ */ #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/Context.h" -#include "../../Include/RmlUi/Core/Core.h" -#include "../../Include/RmlUi/Core/Element.h" -#include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" -#include "GeometryDatabase.h" -#include +#include "RenderManagerAccess.h" namespace Rml { -Geometry::Geometry() -{ - database_handle = GeometryDatabase::Insert(this); -} - -Geometry::Geometry(Geometry&& other) noexcept -{ - MoveFrom(other); - database_handle = GeometryDatabase::Insert(this); -} - -Geometry& Geometry::operator=(Geometry&& other) noexcept -{ - MoveFrom(other); - // Keep the database handles from construction unchanged, they are tied to the *this* pointer and should not change. - return *this; -} - -void Geometry::MoveFrom(Geometry& other) noexcept -{ - vertices = std::move(other.vertices); - indices = std::move(other.indices); - - texture = std::exchange(other.texture, nullptr); - - compiled_geometry = std::exchange(other.compiled_geometry, 0); - compile_attempted = std::exchange(other.compile_attempted, false); -} - -Geometry::~Geometry() -{ - GeometryDatabase::Erase(database_handle); - - Release(); -} +Geometry::Geometry(RenderManager* render_manager, StableVectorIndex resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} -void Geometry::Render(Vector2f translation) +void Geometry::Render(Vector2f translation, Texture texture, const CompiledShader& shader) const { - RenderInterface* render_interface = GetRenderInterface(); - RMLUI_ASSERT(render_interface); - - translation = translation.Round(); - - // Render our compiled geometry if possible. - if (compiled_geometry) - { - RMLUI_ZoneScopedN("RenderCompiled"); - render_interface->RenderCompiledGeometry(compiled_geometry, translation, texture ? texture->GetHandle() : 0); - } - // Otherwise, if we actually have geometry, try to compile it if we haven't already done so, otherwise render it in - // immediate mode. - else - { - if (vertices.empty() || indices.empty()) - return; - - RMLUI_ZoneScopedN("RenderGeometry"); - - if (!compile_attempted) - { - compile_attempted = true; - compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); - - // If we managed to compile the geometry, we can clear the local copy of vertices and indices and - // immediately render the compiled version. - if (compiled_geometry) - { - render_interface->RenderCompiledGeometry(compiled_geometry, translation, texture ? texture->GetHandle() : 0); - return; - } - } - - // Either we've attempted to compile before (and failed), or the compile we just attempted failed; either way, - // render the uncompiled version. - render_interface->RenderGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size(), texture ? texture->GetHandle() : 0, - translation); - } -} - -void Geometry::RenderWithShader(CompiledShaderHandle shader, Vector2f translation) -{ - RenderInterface* render_interface = GetRenderInterface(); - if (!render_interface) + if (resource_handle == StableVectorIndex::Invalid) return; - if (!compile_attempted) - { - if (vertices.empty() || indices.empty()) - return; - - RMLUI_ZoneScoped; - - compile_attempted = true; - compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); - } - - if (compiled_geometry) - { - translation = translation.Round(); - render_interface->RenderShader(shader, compiled_geometry, translation, texture ? texture->GetHandle() : 0); - } -} - -void Geometry::RenderToClipMask(ClipMaskOperation clip_mask, Vector2f translation) -{ - RenderInterface* render_interface = GetRenderInterface(); - RMLUI_ASSERT(render_interface); - - if (!compile_attempted) - { - if (vertices.empty() || indices.empty()) - return; - - RMLUI_ZoneScoped; - - compile_attempted = true; - compiled_geometry = render_interface->CompileGeometry(&vertices[0], (int)vertices.size(), &indices[0], (int)indices.size()); - } - - if (compiled_geometry) - { - translation = translation.Round(); - render_interface->RenderToClipMask(clip_mask, compiled_geometry, translation); - } -} - -Vector& Geometry::GetVertices() -{ - return vertices; -} - -Vector& Geometry::GetIndices() -{ - return indices; -} - -const Texture* Geometry::GetTexture() const -{ - return texture; -} + translation = translation.Round(); -void Geometry::SetTexture(const Texture* _texture) -{ - texture = _texture; - Release(); + RenderManagerAccess::Render(render_manager, *this, translation, texture, shader); } -void Geometry::Release(bool clear_buffers) +Mesh Geometry::Release(ReleaseMode mode) { - if (compiled_geometry) - { - ::Rml::GetRenderInterface()->ReleaseCompiledGeometry(compiled_geometry); - compiled_geometry = 0; - } + if (resource_handle == StableVectorIndex::Invalid) + return Mesh(); - compile_attempted = false; - - if (clear_buffers) + Mesh mesh = RenderManagerAccess::ReleaseResource(render_manager, *this); + Clear(); + if (mode == ReleaseMode::ClearMesh) { - vertices.clear(); - indices.clear(); + mesh.vertices.clear(); + mesh.indices.clear(); } -} - -Geometry::operator bool() const -{ - return !indices.empty(); + return mesh; } } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index c23ff391d..c83b5de54 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -28,20 +28,18 @@ #include "GeometryBoxShadow.h" #include "../../Include/RmlUi/Core/Box.h" -#include "../../Include/RmlUi/Core/Context.h" +#include "../../Include/RmlUi/Core/CompiledFilterShader.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/Geometry.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Math.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/RenderManager.h" -#include "../../Include/RmlUi/Core/Texture.h" namespace Rml { -void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_shadow_texture, Element* element, Geometry& background_border_geometry, - BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) +void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, + Geometry& background_border_geometry, BoxShadowList shadow_list, const Vector4f border_radius, const float opacity) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; @@ -93,17 +91,11 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the // device loses its GPU context and the client calls Rml::ReleaseTextures(). auto texture_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, - shadow_list = std::move(shadow_list)](RenderInterface* render_interface, const String& /*name*/, - TextureHandle& out_handle, Vector2i& out_dimensions) -> bool { - Context* context = element->GetContext(); - if (!context) - { - RMLUI_ERROR; - return false; - } + shadow_list = std::move(shadow_list)](const CallbackTextureInterface& texture_interface) -> bool { + RenderManager& render_manager = texture_interface.GetRenderManager(); - Geometry geometry_padding; // Render geometry for inner box-shadow. - Geometry geometry_padding_border; // Clipping mask for outer box-shadow. + Mesh mesh_padding; // Render geometry for inner box-shadow. + Mesh mesh_padding_border; // Clipping mask for outer box-shadow. bool has_inner_shadow = false; bool has_outer_shadow = false; @@ -123,17 +115,16 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha ColourbPremultiplied white(255); if (has_inner_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding, box, offset, border_radius, white, BoxArea::Padding); + MeshUtilities::GenerateBackground(mesh_padding, box, offset, border_radius, white, BoxArea::Padding); if (has_outer_shadow) - GeometryUtilities::GenerateBackground(&geometry_padding_border, box, offset, border_radius, white, BoxArea::Border); + MeshUtilities::GenerateBackground(mesh_padding_border, box, offset, border_radius, white, BoxArea::Border); } - RenderManager& render_manager = context->GetRenderManager(); const RenderState initial_render_state = render_manager.GetState(); render_manager.ResetState(); render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); - render_interface->PushLayer(LayerFill::Clear); + render_manager.PushLayer(LayerFill::Clear); background_border_geometry.Render(element_offset_in_texture); @@ -158,7 +149,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha radius = Math::Max(radius + spread_factor * spread_distance, 0.f); } - Geometry shadow_geometry; + Mesh mesh_shadow; // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inner box-shadows it is used as a clipping mask. for (int i = 0; i < element->GetNumBoxes(); i++) @@ -175,69 +166,63 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, Texture& out_sha box.SetEdge(BoxArea::Padding, edge, new_size); } - GeometryUtilities::GenerateBackground(&shadow_geometry, box, offset, spread_radii, shadow.color, - inset ? BoxArea::Padding : BoxArea::Border); + MeshUtilities::GenerateBackground(mesh_shadow, box, offset, spread_radii, shadow.color, inset ? BoxArea::Padding : BoxArea::Border); } - CompiledFilterHandle blur = {}; + CompiledFilter blur; if (blur_radius > 0.5f) { - blur = render_interface->CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); + blur = render_manager.CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); if (blur) - render_interface->PushLayer(LayerFill::Clear); + render_manager.PushLayer(LayerFill::Clear); } + Geometry geometry_shadow = render_manager.MakeGeometry(std::move(mesh_shadow)); + if (inset) { - render_manager.SetClipMask(ClipMaskOperation::SetInverse, &shadow_geometry, shadow_offset + element_offset_in_texture); + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_shadow, shadow_offset + element_offset_in_texture); - for (Rml::Vertex& vertex : geometry_padding.GetVertices()) + for (Rml::Vertex& vertex : mesh_padding.vertices) vertex.colour = shadow.color; - geometry_padding.Release(); + // @performance: Don't need to copy the mesh if this is the last use of it. + Geometry geometry_padding = render_manager.MakeGeometry(Mesh(mesh_padding)); geometry_padding.Render(element_offset_in_texture); render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture); } else { + Mesh mesh = mesh_padding_border; + Geometry geometry_padding_border = render_manager.MakeGeometry(std::move(mesh)); render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture); - shadow_geometry.Render(shadow_offset + element_offset_in_texture); + geometry_shadow.Render(shadow_offset + element_offset_in_texture); } if (blur) { - render_interface->PopLayer(BlendMode::Blend, {blur}); - render_interface->ReleaseCompiledFilter(blur); + FilterHandleList filters; + blur.AddHandleTo(filters); + render_manager.PopLayer(BlendMode::Blend, filters); + blur.Release(); } } - TextureHandle shadow_texture = render_interface->SaveLayerAsTexture(texture_dimensions); - if (!shadow_texture) - return false; - - render_interface->PopLayer(BlendMode::Discard, {}); + texture_interface.SaveLayerAsTexture(texture_dimensions); + render_manager.PopLayer(BlendMode::Discard, {}); render_manager.SetState(initial_render_state); - out_dimensions = texture_dimensions; - out_handle = shadow_texture; - return true; }; - RMLUI_ASSERT(!out_shadow_geometry); - - Vector& vertices = out_shadow_geometry.GetVertices(); - Vector& indices = out_shadow_geometry.GetIndices(); - vertices.resize(4); - indices.resize(6); + Mesh mesh = out_shadow_geometry.Release(Geometry::ReleaseMode::ClearMesh); const byte alpha = byte(opacity * 255.f); - GeometryUtilities::GenerateQuad(vertices.data(), indices.data(), -element_offset_in_texture, Vector2f(texture_dimensions), - ColourbPremultiplied(alpha, alpha)); + MeshUtilities::GenerateQuad(mesh, -element_offset_in_texture, Vector2f(texture_dimensions), ColourbPremultiplied(alpha, alpha)); - out_shadow_texture.Set("box-shadow", texture_callback); - out_shadow_geometry.SetTexture(&out_shadow_texture); + out_shadow_texture = render_manager.MakeCallbackTexture(std::move(texture_callback)); + out_shadow_geometry = render_manager.MakeGeometry(std::move(mesh)); } } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 8204967ed..3d39a2dce 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -34,21 +34,23 @@ namespace Rml { class Geometry; -struct Texture; +class CallbackTexture; +class RenderManager; class GeometryBoxShadow { public: /// Generate the texture and geometry for a box shadow. /// @param[out] out_shadow_geometry The target geometry. /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[in] render_manager The render manager to generate the shadow for. /// @param[in] element The element to generate the shadow for. /// @param[in] background_border_geometry The geometry of the background and border, assumed to already have been generated. Assumes pointer /// stability during the lifetime of the shadow geometry. /// @param[in] shadow_list The list of box-shadows to generate. /// @param[in] border_radius The border radius of the element. /// @param[in] opacity The opacity of the element. - static void Generate(Geometry& out_shadow_geometry, Texture& out_shadow_texture, Element* element, Geometry& background_border_geometry, - BoxShadowList shadow_list, Vector4f border_radius, float opacity); + static void Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, + Geometry& background_border_geometry, BoxShadowList shadow_list, Vector4f border_radius, float opacity); }; } // namespace Rml diff --git a/Source/Core/GeometryDatabase.cpp b/Source/Core/GeometryDatabase.cpp deleted file mode 100644 index 8d29352cc..000000000 --- a/Source/Core/GeometryDatabase.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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 "GeometryDatabase.h" -#include "../../Include/RmlUi/Core/Geometry.h" -#include - -namespace Rml { -namespace GeometryDatabase { - - class Database { - public: - Database() - { - constexpr size_t reserve_size = 512; - geometry_list.reserve(reserve_size); - free_list.reserve(reserve_size); - } - - ~Database() - { -#ifdef RMLUI_TESTS_ENABLED - RMLUI_ASSERT(geometry_list.size() == free_list.size()); - std::sort(free_list.begin(), free_list.end()); - for (size_t i = 0; i < free_list.size(); i++) - { - RMLUI_ASSERT(i == free_list[i]); - } -#endif - } - - GeometryDatabaseHandle insert(Geometry* value) - { - GeometryDatabaseHandle handle; - if (free_list.empty()) - { - handle = GeometryDatabaseHandle(geometry_list.size()); - geometry_list.push_back(value); - } - else - { - handle = free_list.back(); - free_list.pop_back(); - geometry_list[handle] = value; - } - return handle; - } - - int size() const { return (int)geometry_list.size() - (int)free_list.size(); } - - void clear() - { - geometry_list.clear(); - free_list.clear(); - } - void erase(GeometryDatabaseHandle handle) { free_list.push_back(handle); } - - // Iterate over every item in the database, skipping free slots. - template - void for_each(Func&& func) - { - std::sort(free_list.begin(), free_list.end()); - - size_t i_begin_next = 0; - for (GeometryDatabaseHandle freelist_entry : free_list) - { - const size_t i_end = size_t(freelist_entry); - const size_t i_begin = i_begin_next; - i_begin_next = i_end + 1; - - for (size_t i = i_begin; i < i_end; i++) - func(geometry_list[i]); - } - - for (size_t i = i_begin_next; i < geometry_list.size(); i++) - func(geometry_list[i]); - } - - private: - // List of all active geometry, in addition to free slots. - // Free slots (as defined by the 'free_list') may contain dangling pointers and must not be dereferenced. - Vector geometry_list; - // Declares free slots in the 'geometry_list' as indices. - Vector free_list; - }; - - static Database geometry_database; - - GeometryDatabaseHandle Insert(Geometry* geometry) - { - return geometry_database.insert(geometry); - } - - void Erase(GeometryDatabaseHandle handle) - { - geometry_database.erase(handle); - } - - void ReleaseAll() - { - geometry_database.for_each([](Geometry* geometry) { geometry->Release(); }); - } - -#ifdef RMLUI_TESTS_ENABLED - - bool PrepareForTests() - { - if (geometry_database.size() > 0) - return false; - - // Even with size()==0 we can have items in the geometry list which should all be duplicated by the free list. - // We want to clear them for the tests. - geometry_database.clear(); - - return true; - } - - bool ListMatchesDatabase(const Vector& geometry_list) - { - Vector geometry_list_ptrs; - std::for_each(geometry_list.begin(), geometry_list.end(), [&](const Geometry& geometry) { geometry_list_ptrs.push_back(&geometry); }); - - Vector database_ptrs; - geometry_database.for_each([&](const Geometry* geometry) { database_ptrs.push_back(geometry); }); - - const bool result = std::is_permutation(geometry_list_ptrs.begin(), geometry_list_ptrs.end(), database_ptrs.begin(), database_ptrs.end()); - return result; - } - -#endif // RMLUI_TESTS_ENABLED - -} // namespace GeometryDatabase -} // namespace Rml diff --git a/Source/Core/GeometryUtilities.cpp b/Source/Core/MeshUtilities.cpp similarity index 65% rename from Source/Core/GeometryUtilities.cpp rename to Source/Core/MeshUtilities.cpp index 5335ebf54..5776e5880 100644 --- a/Source/Core/GeometryUtilities.cpp +++ b/Source/Core/MeshUtilities.cpp @@ -26,77 +26,69 @@ * */ -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/Box.h" #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" -#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/Types.h" #include "GeometryBackgroundBorder.h" namespace Rml { -GeometryUtilities::GeometryUtilities() {} - -GeometryUtilities::~GeometryUtilities() {} - -void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, - int index_offset) +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour) { - GenerateQuad(vertices, indices, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1), index_offset); + GenerateQuad(mesh, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1)); } -void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, - Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset) +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, Vector2f top_left_texcoord, + Vector2f bottom_right_texcoord) { - vertices[0].position = origin; - vertices[0].colour = colour; - vertices[0].tex_coord = top_left_texcoord; + const int v0 = (int)mesh.vertices.size(); + const int i0 = (int)mesh.indices.size(); + + mesh.vertices.resize(mesh.vertices.size() + 4); + mesh.indices.resize(mesh.indices.size() + 6); + Vertex* vertices = mesh.vertices.data(); + int* indices = mesh.indices.data(); + + vertices[v0 + 0].position = origin; + vertices[v0 + 0].colour = colour; + vertices[v0 + 0].tex_coord = top_left_texcoord; - vertices[1].position = Vector2f(origin.x + dimensions.x, origin.y); - vertices[1].colour = colour; - vertices[1].tex_coord = Vector2f(bottom_right_texcoord.x, top_left_texcoord.y); + vertices[v0 + 1].position = Vector2f(origin.x + dimensions.x, origin.y); + vertices[v0 + 1].colour = colour; + vertices[v0 + 1].tex_coord = Vector2f(bottom_right_texcoord.x, top_left_texcoord.y); - vertices[2].position = origin + dimensions; - vertices[2].colour = colour; - vertices[2].tex_coord = bottom_right_texcoord; + vertices[v0 + 2].position = origin + dimensions; + vertices[v0 + 2].colour = colour; + vertices[v0 + 2].tex_coord = bottom_right_texcoord; - vertices[3].position = Vector2f(origin.x, origin.y + dimensions.y); - vertices[3].colour = colour; - vertices[3].tex_coord = Vector2f(top_left_texcoord.x, bottom_right_texcoord.y); + vertices[v0 + 3].position = Vector2f(origin.x, origin.y + dimensions.y); + vertices[v0 + 3].colour = colour; + vertices[v0 + 3].tex_coord = Vector2f(top_left_texcoord.x, bottom_right_texcoord.y); - indices[0] = index_offset + 0; - indices[1] = index_offset + 3; - indices[2] = index_offset + 1; + indices[i0 + 0] = v0 + 0; + indices[i0 + 1] = v0 + 3; + indices[i0 + 2] = v0 + 1; - indices[3] = index_offset + 1; - indices[4] = index_offset + 3; - indices[5] = index_offset + 2; + indices[i0 + 3] = v0 + 1; + indices[i0 + 4] = v0 + 3; + indices[i0 + 5] = v0 + 2; } -void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, ColourbPremultiplied color) +void MeshUtilities::GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied color) { Math::SnapToPixelGrid(position, size); - - Vector& line_vertices = geometry->GetVertices(); - Vector& line_indices = geometry->GetIndices(); - - const int vertices_i0 = (int)line_vertices.size(); - const int indices_i0 = (int)line_indices.size(); - - line_vertices.resize(line_vertices.size() + 4); - line_indices.resize(line_indices.size() + 6); - - GeometryUtilities::GenerateQuad(line_vertices.data() + vertices_i0, line_indices.data() + indices_i0, position, size, color, vertices_i0); + MeshUtilities::GenerateQuad(mesh, position, size, color); } -void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, +void MeshUtilities::GenerateBackgroundBorder(Mesh& out_mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied background_color, const ColourbPremultiplied border_colors[4]) { RMLUI_ASSERT(border_colors); - Vector& vertices = out_geometry->GetVertices(); - Vector& indices = out_geometry->GetIndices(); + Vector& vertices = out_mesh.vertices; + Vector& indices = out_mesh.indices; EdgeSizes border_widths = { // TODO: Move rounding to computed values (round border only). @@ -148,7 +140,7 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B for (int i = 0; i < num_vertices; i++) { - GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, + MeshUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, Vector2f(3, 3), ColourbPremultiplied(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i); } } @@ -163,8 +155,8 @@ void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const B #endif } -void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, - ColourbPremultiplied color, BoxArea fill_area) +void MeshUtilities::GenerateBackground(Mesh& out_mesh, const Box& box, Vector2f offset, Vector4f border_radius, ColourbPremultiplied color, + BoxArea fill_area) { RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content, "Rectangle geometry only supports border, padding and content boxes."); @@ -187,8 +179,8 @@ void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& bo const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), edge_sizes, inner_size, border_radius); - Vector& vertices = out_geometry->GetVertices(); - Vector& indices = out_geometry->GetIndices(); + Vector& vertices = out_mesh.vertices; + Vector& indices = out_mesh.indices; // Reserve geometry. A conservative estimate, does not take border-radii into account. vertices.reserve((int)vertices.size() + 4); diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index aafd2894f..3fd9e69a2 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -27,19 +27,22 @@ */ #include "../../Include/RmlUi/Core/RenderInterface.h" -#include "TextureDatabase.h" namespace Rml { +namespace CoreInternal { + bool HasRenderManager(RenderInterface* render_interface); +} + RenderInterface::RenderInterface() {} RenderInterface::~RenderInterface() { - // Note: We cannot automatically release the textures from the database here, because that involves a virtual call to this interface during its - // destruction which is illegal. - RMLUI_ASSERTMSG(TextureDatabase::AllTexturesReleased(), - "RenderInterface is being destroyed, but there are still active textures in the texture database. This may lead to use-after-free or nullptr " - "dereference when releasing the textures. Ensure that the render interface is destroyed *after* the call to Rml::Shutdown."); + // Note: We cannot automatically release render resources here, because that involves a virtual call to this interface during its destruction + // which is illegal. + RMLUI_ASSERTMSG(!CoreInternal::HasRenderManager(this), + "RenderInterface is being destroyed, but it is still actively referenced and used within the RmlUi library. This may lead to use-after-free " + "or nullptr dereference when releasing render resources. Ensure that the render interface is destroyed *after* the call to Rml::Shutdown."); } CompiledGeometryHandle RenderInterface::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/) diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index 1a4a6641f..253d907a3 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -30,21 +30,51 @@ #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "TextureDatabase.h" namespace Rml { -RenderManager::RenderManager() : render_interface(GetRenderInterface()) +RenderManager::RenderManager(RenderInterface* render_interface) : render_interface(render_interface), texture_database(MakeUnique()) { RMLUI_ASSERT(render_interface); + + constexpr size_t reserve_geometry = 256; + geometry_list.reserve(reserve_geometry); +} + +RenderManager::~RenderManager() +{ + struct ResourceCount { + const char* name; + int count; + }; + ResourceCount elements[] = { + {"Geometry", (int)geometry_list.size()}, + {"CompiledFilter", compiled_filter_count}, + {"CompiledShader", compiled_shader_count}, + {"CallbackTexture", (int)texture_database->callback_database.size()}, + }; + + for (auto& element : elements) + { + if (element.count != 0) + { + Log::Message(Log::LT_ERROR, "Leaking %s detected (%d). Ensure that all RmlUi resources have been released by the end of Rml::Shutdown.", + element.name, element.count); + } + } + + ReleaseAllTextures(); } -void RenderManager::BeginRender() +void RenderManager::PrepareRender() { #ifdef RMLUI_DEBUG const RenderState default_state; RMLUI_ASSERT(state.clip_mask_list == default_state.clip_mask_list); - RMLUI_ASSERT(state.scissor_region == state.scissor_region); - RMLUI_ASSERT(state.transform == state.transform); + RMLUI_ASSERT(state.scissor_region == default_state.scissor_region); + RMLUI_ASSERT(state.transform == default_state.transform); #endif } @@ -58,6 +88,27 @@ Vector2i RenderManager::GetViewport() const return viewport_dimensions; } +Geometry RenderManager::MakeGeometry(Mesh&& mesh) +{ + return Geometry(this, InsertGeometry(std::move(mesh))); +} + +Texture RenderManager::LoadTexture(const String& source, const String& document_path) +{ + String path; + if (source.size() > 0 && source[0] == '?') + path = source; + else + GetSystemInterface()->JoinPath(path, StringUtilities::Replace(document_path, '|', ':'), source); + + return Texture(this, texture_database->file_database.LoadTexture(render_interface, path)); +} + +CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callback) +{ + return CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback))); +} + void RenderManager::DisableScissorRegion() { SetScissorRegion(Rectanglei::MakeInvalid()); @@ -93,7 +144,7 @@ void RenderManager::DisableClipMask() void RenderManager::SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation) { - RMLUI_ASSERT(geometry); + RMLUI_ASSERT(geometry && geometry->render_manager == this); state.clip_mask_list = {ClipMaskGeometry{operation, geometry, translation, nullptr}}; ApplyClipMask(state.clip_mask_list); } @@ -130,8 +181,10 @@ void RenderManager::ApplyClipMask(const ClipMaskGeometryList& clip_elements) for (const ClipMaskGeometry& element_clip : clip_elements) { + RMLUI_ASSERT(element_clip.geometry->render_manager == this); SetTransform(element_clip.transform); - element_clip.geometry->RenderToClipMask(element_clip.operation, element_clip.absolute_offset); + if (CompiledGeometryHandle handle = GetCompiledGeometryHandle(element_clip.geometry->resource_handle)) + render_interface->RenderToClipMask(element_clip.operation, handle, element_clip.absolute_offset); } // Apply the initially set transform in case it was changed. @@ -153,4 +206,149 @@ void RenderManager::ResetState() SetState(RenderState{}); } +StableVectorIndex RenderManager::InsertGeometry(Mesh&& mesh) +{ + return geometry_list.insert(GeometryData{std::move(mesh), CompiledGeometryHandle{}}); +} + +CompiledGeometryHandle RenderManager::GetCompiledGeometryHandle(StableVectorIndex index) +{ + if (index == StableVectorIndex::Invalid) + return {}; + + GeometryData& geometry = geometry_list[index]; + if (!geometry.handle && !geometry.mesh.indices.empty()) + { + geometry.handle = render_interface->CompileGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), + geometry.mesh.indices.data(), (int)geometry.mesh.indices.size()); + } + return geometry.handle; +} + +void RenderManager::Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader) +{ + RMLUI_ASSERT(geometry); + if (geometry.render_manager != this || (shader && shader.render_manager != this) || (texture && texture.render_manager != this)) + { + RMLUI_ERRORMSG("Trying to render geometry with resources constructed in different render managers."); + return; + } + + if (CompiledGeometryHandle geometry_handle = GetCompiledGeometryHandle(geometry.resource_handle)) + { + TextureHandle texture_handle = {}; + if (texture.file_index != TextureFileIndex::Invalid) + texture_handle = texture_database->file_database.GetHandle(texture.file_index); + else if (texture.callback_index != StableVectorIndex::Invalid) + texture_handle = texture_database->callback_database.GetHandle(this, render_interface, texture.callback_index); + + if (shader) + render_interface->RenderShader(shader.resource_handle, geometry_handle, translation, texture_handle); + else + render_interface->RenderCompiledGeometry(geometry_handle, translation, texture_handle); + } +} + +void RenderManager::GetTextureSourceList(StringList& source_list) const +{ + texture_database->file_database.GetSourceList(source_list); +} + +void RenderManager::ReleaseAllTextures() +{ + texture_database->callback_database.ReleaseAllTextures(render_interface); + texture_database->file_database.ReleaseAllTextures(render_interface); +} + +void RenderManager::ReleaseAllCompiledGeometry() +{ + geometry_list.for_each([this](GeometryData& data) { + if (data.handle) + { + render_interface->ReleaseCompiledGeometry(data.handle); + data.handle = {}; + } + }); +} + +CompiledFilter RenderManager::CompileFilter(const String& name, const Dictionary& parameters) +{ + if (CompiledFilterHandle handle = render_interface->CompileFilter(name, parameters)) + { + compiled_filter_count += 1; + return CompiledFilter(this, handle); + } + + return CompiledFilter(); +} + +CompiledShader RenderManager::CompileShader(const String& name, const Dictionary& parameters) +{ + if (CompiledShaderHandle handle = render_interface->CompileShader(name, parameters)) + { + compiled_shader_count += 1; + return CompiledShader(this, handle); + } + + return CompiledShader(); +} + +void RenderManager::PushLayer(LayerFill layer_fill) +{ + render_interface->PushLayer(layer_fill); +} + +void RenderManager::PopLayer(BlendMode blend_mode, const FilterHandleList& filters) +{ + render_interface->PopLayer(blend_mode, filters); +} + +CompiledFilter RenderManager::SaveLayerAsMaskImage() +{ + if (CompiledFilterHandle handle = render_interface->SaveLayerAsMaskImage()) + { + compiled_filter_count += 1; + return CompiledFilter(this, handle); + } + return CompiledFilter(); +} + +void RenderManager::ReleaseResource(const CallbackTexture& texture) +{ + RMLUI_ASSERT(texture.render_manager == this && texture.resource_handle != texture.InvalidHandle()); + + texture_database->callback_database.ReleaseTexture(render_interface, texture.resource_handle); +} + +Mesh RenderManager::ReleaseResource(const Geometry& geometry) +{ + RMLUI_ASSERT(geometry.render_manager == this && geometry.resource_handle != geometry.InvalidHandle()); + + GeometryData& data = geometry_list[geometry.resource_handle]; + if (data.handle) + { + render_interface->ReleaseCompiledGeometry(data.handle); + data.handle = {}; + } + Mesh result = std::exchange(data.mesh, Mesh()); + geometry_list.erase(geometry.resource_handle); + return result; +} + +void RenderManager::ReleaseResource(const CompiledFilter& filter) +{ + RMLUI_ASSERT(filter.render_manager == this && filter.resource_handle != filter.InvalidHandle()); + + render_interface->ReleaseCompiledFilter(filter.resource_handle); + compiled_filter_count -= 1; +} + +void RenderManager::ReleaseResource(const CompiledShader& shader) +{ + RMLUI_ASSERT(shader.render_manager == this && shader.resource_handle != shader.InvalidHandle()); + + render_interface->ReleaseCompiledShader(shader.resource_handle); + compiled_shader_count -= 1; +} + } // namespace Rml diff --git a/Source/Core/RenderManagerAccess.cpp b/Source/Core/RenderManagerAccess.cpp new file mode 100644 index 000000000..57bbf0b5b --- /dev/null +++ b/Source/Core/RenderManagerAccess.cpp @@ -0,0 +1,66 @@ +/* + * 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 "RenderManagerAccess.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "TextureDatabase.h" + +namespace Rml { + +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, TextureFileIndex texture) +{ + return render_manager->texture_database->file_database.GetDimensions(texture); +} + +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture) +{ + return render_manager->texture_database->callback_database.GetDimensions(render_manager, render_manager->render_interface, callback_texture); +} + +void RenderManagerAccess::Render(RenderManager* render_manager, const Geometry& geometry, Vector2f translation, Texture texture, + const CompiledShader& shader) +{ + render_manager->Render(geometry, translation, texture, shader); +} + +void RenderManagerAccess::GetTextureSourceList(RenderManager* render_manager, StringList& source_list) +{ + render_manager->GetTextureSourceList(source_list); +} + +void RenderManagerAccess::ReleaseAllTextures(RenderManager* render_manager) +{ + render_manager->ReleaseAllTextures(); +} + +void RenderManagerAccess::ReleaseAllCompiledGeometry(RenderManager* render_manager) +{ + render_manager->ReleaseAllCompiledGeometry(); +} + +} // namespace Rml diff --git a/Source/Core/RenderManagerAccess.h b/Source/Core/RenderManagerAccess.h new file mode 100644 index 000000000..7b07dbfba --- /dev/null +++ b/Source/Core/RenderManagerAccess.h @@ -0,0 +1,75 @@ +/* + * 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_CORE_RENDERMANAGERACCESS_H +#define RMLUI_CORE_RENDERMANAGERACCESS_H + +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class CompiledFilter; +class CompiledShader; +class CallbackTexture; +class Geometry; +class Texture; + +class RenderManagerAccess { +private: + template + static auto ReleaseResource(RenderManager* render_manager, T& resource) + { + return render_manager->ReleaseResource(resource); + } + + static Vector2i GetDimensions(RenderManager* render_manager, TextureFileIndex texture); + static Vector2i GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture); + + static void Render(RenderManager* render_manager, const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + static void GetTextureSourceList(RenderManager* render_manager, StringList& source_list); + + static void ReleaseAllTextures(RenderManager* render_manager); + static void ReleaseAllCompiledGeometry(RenderManager* render_manager); + + friend class CompiledFilter; + friend class CompiledShader; + friend class CallbackTexture; + friend class Geometry; + friend class Texture; + + friend StringList Rml::GetTextureSourceList(); + friend void Rml::ReleaseTextures(RenderInterface*); + friend void Rml::ReleaseCompiledGeometry(RenderInterface*); +}; + +} // namespace Rml + +#endif diff --git a/Source/Core/Spritesheet.cpp b/Source/Core/Spritesheet.cpp index 09a133371..fe143c75a 100644 --- a/Source/Core/Spritesheet.cpp +++ b/Source/Core/Spritesheet.cpp @@ -28,25 +28,17 @@ #include "../../Include/RmlUi/Core/Spritesheet.h" #include "../../Include/RmlUi/Core/Log.h" -#include "../../Include/RmlUi/Core/StringUtilities.h" namespace Rml { -Spritesheet::Spritesheet(const String& name, const String& image_source, const String& definition_source, int definition_line_number, - float display_scale, const Texture& texture) : - name(name), - image_source(image_source), definition_source(definition_source), definition_line_number(definition_line_number), display_scale(display_scale), - texture(texture) +Spritesheet::Spritesheet(const String& name, const String& source, const String& document_path, int definition_line_number, float display_scale) : + name(name), definition_line_number(definition_line_number), display_scale(display_scale), texture_source(source, document_path) {} bool SpritesheetList::AddSpriteSheet(const String& name, const String& image_source, const String& definition_source, int definition_line_number, float display_scale, const SpriteDefinitionList& sprite_definitions) { - // Load the texture - Texture texture; - texture.Set(image_source, definition_source); - - auto sprite_sheet_ptr = MakeShared(name, image_source, definition_source, definition_line_number, display_scale, texture); + auto sprite_sheet_ptr = MakeShared(name, image_source, definition_source, definition_line_number, display_scale); Spritesheet* sprite_sheet = sprite_sheet_ptr.get(); spritesheets.push_back(std::move(sprite_sheet_ptr)); @@ -62,8 +54,8 @@ bool SpritesheetList::AddSpriteSheet(const String& name, const String& image_sou if (new_sprite.sprite_sheet) { Log::Message(Log::LT_WARNING, "Sprite '%s' was overwritten due to duplicate names at the same block scope. Declared at %s:%d and %s:%d", - sprite_name.c_str(), new_sprite.sprite_sheet->definition_source.c_str(), new_sprite.sprite_sheet->definition_line_number, - definition_source.c_str(), definition_line_number); + sprite_name.c_str(), new_sprite.sprite_sheet->texture_source.GetDefinitionSource().c_str(), + new_sprite.sprite_sheet->definition_line_number, definition_source.c_str(), definition_line_number); } new_sprite = Sprite{sprite_rectangle, sprite_sheet}; diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index 16eacd15e..8f563bcbe 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -113,7 +113,8 @@ const Keyframes* StyleSheet::GetKeyframes(const String& name) const return nullptr; } -const DecoratorPtrList& StyleSheet::InstanceDecorators(const DecoratorDeclarationList& declaration_list, const PropertySource* source) const +const DecoratorPtrList& StyleSheet::InstanceDecorators(RenderManager& render_manager, const DecoratorDeclarationList& declaration_list, + const PropertySource* source) const { RMLUI_ASSERT_NONRECURSIVE; // Since we may return a reference to the below static variable. static DecoratorPtrList non_cached_decorator_list; @@ -152,8 +153,8 @@ const DecoratorPtrList& StyleSheet::InstanceDecorators(const DecoratorDeclaratio if (declaration.instancer) { RMLUI_ZoneScopedN("InstanceDecorator"); - decorator = - declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, DecoratorInstancerInterface(*this, source)); + decorator = declaration.instancer->InstanceDecorator(declaration.type, declaration.properties, + DecoratorInstancerInterface(render_manager, *this, source)); if (!decorator) Log::Message(Log::LT_WARNING, "Decorator '%s' in '%s' could not be instanced, declared at %s:%d", declaration.type.c_str(), @@ -165,7 +166,7 @@ const DecoratorPtrList& StyleSheet::InstanceDecorators(const DecoratorDeclaratio auto it_map = named_decorator_map.find(declaration.type); if (it_map != named_decorator_map.end()) decorator = it_map->second.instancer->InstanceDecorator(it_map->second.type, it_map->second.properties, - DecoratorInstancerInterface(*this, source)); + DecoratorInstancerInterface(render_manager, *this, source)); if (!decorator) Log::Message(Log::LT_WARNING, "Decorator name '%s' could not be found in any @decorator rule, declared at %s:%d", diff --git a/Source/Core/Texture.cpp b/Source/Core/Texture.cpp index 99d5a03d6..18ff285d6 100644 --- a/Source/Core/Texture.cpp +++ b/Source/Core/Texture.cpp @@ -27,55 +27,46 @@ */ #include "../../Include/RmlUi/Core/Texture.h" -#include "TextureDatabase.h" -#include "TextureResource.h" +#include "RenderManagerAccess.h" namespace Rml { -void Texture::Set(const String& source, const String& source_path) -{ - resource = TextureDatabase::Fetch(source, source_path); -} +Texture::Texture(RenderManager* render_manager, TextureFileIndex file_index) : render_manager(render_manager), file_index(file_index) {} -void Texture::Set(const String& name, TextureCallback&& callback) -{ - resource = MakeShared(); - resource->Set(name, std::move(callback)); -} +Texture::Texture(RenderManager* render_manager, StableVectorIndex callback_index) : render_manager(render_manager), callback_index(callback_index) {} -const String& Texture::GetSource() const +Vector2i Texture::GetDimensions() const { - static String empty_string; - if (!resource) - return empty_string; - - return resource->GetSource(); + if (file_index != TextureFileIndex::Invalid) + return RenderManagerAccess::GetDimensions(render_manager, file_index); + if (callback_index != StableVectorIndex::Invalid) + return RenderManagerAccess::GetDimensions(render_manager, callback_index); + return {}; } -TextureHandle Texture::GetHandle() const +Texture::operator bool() const { - if (!resource) - return 0; - - return resource->GetHandle(); + return callback_index != StableVectorIndex::Invalid || file_index != TextureFileIndex::Invalid; } -Vector2i Texture::GetDimensions() const +bool Texture::operator==(const Texture& other) const { - if (!resource) - return {}; - - return resource->GetDimensions(); + return render_manager == other.render_manager && file_index == other.file_index && callback_index == other.callback_index; } -bool Texture::operator==(const Texture& other) const +TextureSource::TextureSource(String source, String document_path) : source(std::move(source)), document_path(std::move(document_path)) {} + +Texture TextureSource::GetTexture(RenderManager& render_manager) const { - return resource == other.resource; + Texture& texture = textures[&render_manager]; + if (!texture) + texture = render_manager.LoadTexture(source, document_path); + return texture; } -Texture::operator bool() const +const String& TextureSource::GetDefinitionSource() const { - return static_cast(resource); + return document_path; } } // namespace Rml diff --git a/Source/Core/TextureDatabase.cpp b/Source/Core/TextureDatabase.cpp index bd7c5839d..13c762ab8 100644 --- a/Source/Core/TextureDatabase.cpp +++ b/Source/Core/TextureDatabase.cpp @@ -27,127 +27,143 @@ */ #include "TextureDatabase.h" -#include "../../Include/RmlUi/Core/Core.h" -#include "../../Include/RmlUi/Core/StringUtilities.h" -#include "../../Include/RmlUi/Core/SystemInterface.h" -#include "TextureResource.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/RenderInterface.h" namespace Rml { -static TextureDatabase* texture_database = nullptr; - -TextureDatabase::TextureDatabase() +CallbackTextureDatabase::CallbackTextureDatabase() { - RMLUI_ASSERT(texture_database == nullptr); - texture_database = this; + constexpr size_t reserve_callback_textures = 30; + texture_list.reserve(reserve_callback_textures); } -TextureDatabase::~TextureDatabase() +CallbackTextureDatabase::~CallbackTextureDatabase() { - RMLUI_ASSERT(texture_database == this); - -#ifdef RMLUI_DEBUG - // All textures not owned by the database should have been released at this point. - int num_leaks_file = 0; - - for (auto& texture : textures) - num_leaks_file += (texture.second.use_count() > 1); - - const int num_leaks_callback = (int)callback_textures.size(); - const int total_num_leaks = num_leaks_file + num_leaks_callback; - - if (total_num_leaks > 0) + if (!texture_list.empty()) { - Log::Message(Log::LT_ERROR, "Textures leaked during shutdown. Total: %d From file: %d Generated: %d.", total_num_leaks, num_leaks_file, - num_leaks_callback); + Log::Message(Log::LT_ERROR, "TextureDatabase destroyed with outstanding callback textures. Will likely result in memory corruption."); + RMLUI_ERROR; } -#endif - - texture_database = nullptr; } -void TextureDatabase::Initialise() +StableVectorIndex CallbackTextureDatabase::CreateTexture(CallbackTextureFunction&& callback) { - new TextureDatabase(); + RMLUI_ASSERT(callback); + return texture_list.insert(CallbackTextureEntry{std::move(callback), TextureHandle(), Vector2i()}); } -void TextureDatabase::Shutdown() +void CallbackTextureDatabase::ReleaseTexture(RenderInterface* render_interface, StableVectorIndex callback_index) { - delete texture_database; + CallbackTextureEntry& data = texture_list[callback_index]; + if (data.texture_handle) + render_interface->ReleaseTexture(data.texture_handle); + texture_list.erase(callback_index); } -SharedPtr TextureDatabase::Fetch(const String& source, const String& source_directory) +Vector2i CallbackTextureDatabase::GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) { - String path; - if (source.size() > 0 && source[0] == '?') - path = source; - else - GetSystemInterface()->JoinPath(path, StringUtilities::Replace(source_directory, '|', ':'), source); + return EnsureLoaded(render_manager, render_interface, callback_index).dimensions; +} - auto iterator = texture_database->textures.find(path); - if (iterator != texture_database->textures.end()) - return iterator->second; +TextureHandle CallbackTextureDatabase::GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) +{ + return EnsureLoaded(render_manager, render_interface, callback_index).texture_handle; +} - auto resource = MakeShared(); - resource->Set(path); +auto CallbackTextureDatabase::EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) + -> CallbackTextureEntry& +{ + CallbackTextureEntry& data = texture_list[callback_index]; + if (!data.texture_handle) + { + if (!data.callback(CallbackTextureInterface(*render_manager, *render_interface, data.texture_handle, data.dimensions))) + { + data.texture_handle = {}; + data.dimensions = {}; + } + } + return data; +} - texture_database->textures[path] = resource; - return resource; +size_t CallbackTextureDatabase::size() const +{ + return texture_list.size(); } -void TextureDatabase::AddCallbackTexture(TextureResource* texture) +void CallbackTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) { - if (texture_database) - texture_database->callback_textures.insert(texture); + texture_list.for_each([render_interface](CallbackTextureEntry& texture) { + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture.texture_handle = {}; + texture.dimensions = {}; + } + }); } -void TextureDatabase::RemoveCallbackTexture(TextureResource* texture) +FileTextureDatabase::FileTextureDatabase() {} + +FileTextureDatabase::~FileTextureDatabase() { - if (texture_database) - texture_database->callback_textures.erase(texture); +#ifdef RMLUI_DEBUG + for (const FileTextureEntry& texture : texture_list) + { + RMLUI_ASSERTMSG(!texture.texture_handle, + "TextureDatabase destroyed without releasing all file textures first. Ensure that 'ReleaseAllTextures' is called before destruction."); + } +#endif } -StringList TextureDatabase::GetSourceList() +TextureFileIndex FileTextureDatabase::LoadTexture(RenderInterface* render_interface, const String& source) { - StringList result; + auto it = texture_map.find(source); + if (it != texture_map.end()) + return it->second; - if (texture_database) + TextureHandle handle = {}; + Vector2i dimensions; + if (render_interface->LoadTexture(handle, dimensions, source) && handle) { - result.reserve(texture_database->textures.size()); - - for (const auto& pair : texture_database->textures) - result.push_back(pair.first); + TextureFileIndex result = TextureFileIndex(texture_list.size()); + texture_map[source] = result; + texture_list.push_back(FileTextureEntry{handle, dimensions}); + return result; } - return result; + return TextureFileIndex::Invalid; } -void TextureDatabase::ReleaseTextures() +TextureHandle FileTextureDatabase::GetHandle(TextureFileIndex index) const { - if (texture_database) - { - for (const auto& texture : texture_database->textures) - texture.second->Release(); + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return texture_list[size_t(index)].texture_handle; +} - for (const auto& texture : texture_database->callback_textures) - texture->Release(); - } +Vector2i FileTextureDatabase::GetDimensions(TextureFileIndex index) const +{ + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return texture_list[size_t(index)].dimensions; } -bool TextureDatabase::AllTexturesReleased() +void FileTextureDatabase::GetSourceList(StringList& source_list) const { - if (texture_database) - { - for (const auto& texture : texture_database->textures) - if (!texture.second->IsLoaded()) - return false; + source_list.reserve(source_list.size() + texture_list.size()); + for (const auto& texture : texture_map) + source_list.push_back(texture.first); +} - for (const auto& texture : texture_database->callback_textures) - if (!texture->IsLoaded()) - return false; +void FileTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) +{ + for (FileTextureEntry& texture : texture_list) + { + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture.texture_handle = {}; + } } - - return true; } } // namespace Rml diff --git a/Source/Core/TextureDatabase.h b/Source/Core/TextureDatabase.h index 0a3fc0ef7..f9ca38b1c 100644 --- a/Source/Core/TextureDatabase.h +++ b/Source/Core/TextureDatabase.h @@ -29,50 +29,68 @@ #ifndef RMLUI_CORE_TEXTUREDATABASE_H #define RMLUI_CORE_TEXTUREDATABASE_H +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/StableVector.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { -class TextureResource; +class RenderInterface; -/** - @author Peter Curry - */ - -class TextureDatabase { +class CallbackTextureDatabase : NonCopyMoveable { public: - static void Initialise(); - static void Shutdown(); + CallbackTextureDatabase(); + ~CallbackTextureDatabase(); + + StableVectorIndex CreateTexture(CallbackTextureFunction&& callback); + void ReleaseTexture(RenderInterface* render_interface, StableVectorIndex callback_index); - /// Fetch a texture resource from file. - /// The texture will be returned from the database if it already exists, otherwise a new - /// entry will be added and returned. - static SharedPtr Fetch(const String& source, const String& source_directory); + Vector2i GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + TextureHandle GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); - /// Release all textures in the database. - static void ReleaseTextures(); + size_t size() const; - /// Adds a texture resource with a callback function and stores it as a weak (raw) pointer in the database. - static void AddCallbackTexture(TextureResource* texture); + void ReleaseAllTextures(RenderInterface* render_interface); - /// Removes a callback texture from the database. - static void RemoveCallbackTexture(TextureResource* texture); +private: + struct CallbackTextureEntry { + CallbackTextureFunction callback; + TextureHandle texture_handle = {}; + Vector2i dimensions; + }; - /// Return a list of all texture sources currently in the database. - static StringList GetSourceList(); + CallbackTextureEntry& EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); - /// Returns true if there are no textures in the database yet to be released through the render interface. - static bool AllTexturesReleased(); + StableVector texture_list; +}; -private: - TextureDatabase(); - ~TextureDatabase(); +class FileTextureDatabase : NonCopyMoveable { +public: + FileTextureDatabase(); + ~FileTextureDatabase(); - using TextureMap = UnorderedMap>; - TextureMap textures; + TextureFileIndex LoadTexture(RenderInterface* render_interface, const String& source); - using CallbackTextureMap = UnorderedSet; - CallbackTextureMap callback_textures; + TextureHandle GetHandle(TextureFileIndex index) const; + Vector2i GetDimensions(TextureFileIndex index) const; + + void GetSourceList(StringList& source_list) const; + + void ReleaseAllTextures(RenderInterface* render_interface); + +private: + struct FileTextureEntry { + TextureHandle texture_handle = {}; + Vector2i dimensions; + }; + Vector texture_list; + UnorderedMap texture_map; // key: source, value: index into 'texture_list' +}; + +class TextureDatabase { +public: + FileTextureDatabase file_database; + CallbackTextureDatabase callback_database; }; } // namespace Rml diff --git a/Source/Core/TextureLayoutTexture.h b/Source/Core/TextureLayoutTexture.h index e5c3b9e2a..4d9c43bb2 100644 --- a/Source/Core/TextureLayoutTexture.h +++ b/Source/Core/TextureLayoutTexture.h @@ -35,7 +35,6 @@ namespace Rml { class TextureLayout; -class TextureResource; /** A texture layout texture is a single rectangular area which sub-rectangles are placed on within diff --git a/Source/Core/TextureResource.cpp b/Source/Core/TextureResource.cpp deleted file mode 100644 index e676bae44..000000000 --- a/Source/Core/TextureResource.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 "TextureResource.h" -#include "../../Include/RmlUi/Core/Core.h" -#include "../../Include/RmlUi/Core/Log.h" -#include "../../Include/RmlUi/Core/Profiling.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" -#include "TextureDatabase.h" - -namespace Rml { - -TextureResource::TextureResource() {} - -TextureResource::~TextureResource() -{ - Reset(); -} - -void TextureResource::Set(const String& _source) -{ - Reset(); - source = _source; -} - -void TextureResource::Set(const String& name, TextureCallback&& callback) -{ - Reset(); - source = name; - texture_callback = MakeUnique(std::move(callback)); - TextureDatabase::AddCallbackTexture(this); -} - -void TextureResource::Reset() -{ - Release(); - - if (texture_callback) - { - TextureDatabase::RemoveCallbackTexture(this); - texture_callback.reset(); - } - - source.clear(); -} - -TextureHandle TextureResource::GetHandle() -{ - if (!loaded) - Load(); - return handle; -} - -Vector2i TextureResource::GetDimensions() -{ - if (!loaded) - Load(); - return dimensions; -} - -const String& TextureResource::GetSource() const -{ - return source; -} - -void TextureResource::Release() -{ - if (loaded) - { - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - RMLUI_ASSERT(render_interface); - render_interface->ReleaseTexture(handle); - - handle = {}; - dimensions = {}; - loaded = false; - } -} - -bool TextureResource::IsLoaded() const -{ - return loaded; -} - -bool TextureResource::Load() -{ - RMLUI_ZoneScoped; - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - - loaded = true; - - // Generate the texture from the callback function if we have one. - if (texture_callback) - { - TextureCallback& callback_fnc = *texture_callback; - if (!callback_fnc(render_interface, source, handle, dimensions) || !handle) - { - Log::Message(Log::LT_WARNING, "Failed to generate texture from callback function %s.", source.c_str()); - handle = {}; - dimensions = {}; - return false; - } - - return true; - } - - // No callback function, load the texture through the render interface. - if (!render_interface->LoadTexture(handle, dimensions, source)) - { - Log::Message(Log::LT_WARNING, "Failed to load texture from %s.", source.c_str()); - handle = {}; - dimensions = {}; - return false; - } - - return true; -} - -} // namespace Rml diff --git a/Source/Core/TextureResource.h b/Source/Core/TextureResource.h deleted file mode 100644 index 8f4b15ebb..000000000 --- a/Source/Core/TextureResource.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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_CORE_TEXTURERESOURCE_H -#define RMLUI_CORE_TEXTURERESOURCE_H - -#include "../../Include/RmlUi/Core/Texture.h" -#include "../../Include/RmlUi/Core/Traits.h" - -namespace Rml { - -/** - A texture resource stores application-generated texture data (handle and dimensions) for each - unique render interface that needs to render the data. It is used through a Texture object. - - @author Peter Curry - */ - -class TextureResource : public NonCopyMoveable { -public: - TextureResource(); - ~TextureResource(); - - /// Clear any existing data and set the source path. - /// Texture loading is delayed until the texture is accessed by a specific render interface. - void Set(const String& source); - - /// Clear any existing data and set a callback function for loading the data. - /// Texture loading is delayed until the texture is accessed by a specific render interface. - void Set(const String& name, TextureCallback&& callback); - - /// Returns the resource's underlying texture handle. - TextureHandle GetHandle(); - /// Returns the dimensions of the resource's texture. - Vector2i GetDimensions(); - - /// Returns the resource's source. - const String& GetSource() const; - - /// Releases the texture's handle. - void Release(); - - /// Returns true if the texture has been loaded through the render interface, and not yet released. - bool IsLoaded() const; - -private: - void Reset(); - - /// Attempts to load the texture from the source, or the callback function if set. - bool Load(); - - String source; - - TextureHandle handle = {}; - Vector2i dimensions; - bool loaded = false; - - UniquePtr texture_callback; -}; - -} // namespace Rml -#endif diff --git a/Source/Debugger/Geometry.cpp b/Source/Debugger/Geometry.cpp index 61290c39a..839b38b11 100644 --- a/Source/Debugger/Geometry.cpp +++ b/Source/Debugger/Geometry.cpp @@ -29,52 +29,52 @@ #include "Geometry.h" #include "../../Include/RmlUi/Core/Context.h" #include "../../Include/RmlUi/Core/Core.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { namespace Debugger { -static Context* context; +static Context* g_context = nullptr; +static RenderManager* g_render_manager = nullptr; Geometry::Geometry() {} -void Geometry::SetContext(Context* _context) +void Geometry::SetContext(Context* context) { - context = _context; + g_context = context; + g_render_manager = context ? &context->GetRenderManager() : nullptr; } void Geometry::RenderOutline(const Vector2f origin, const Vector2f dimensions, const Colourb colour, float width) { - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - if (!context || !render_interface) + if (!g_context || !g_render_manager) return; - Vertex vertices[4 * 4]; - int indices[6 * 4]; + Mesh mesh; + mesh.vertices.reserve(4 * 4); + mesh.indices.reserve(6 * 4); ColourbPremultiplied colour_pre = colour.ToPremultiplied(); - GeometryUtilities::GenerateQuad(vertices + 0, indices + 0, Vector2f(0, 0), Vector2f(dimensions.x, width), colour_pre, 0); - GeometryUtilities::GenerateQuad(vertices + 4, indices + 6, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour_pre, 4); - GeometryUtilities::GenerateQuad(vertices + 8, indices + 12, Vector2f(0, 0), Vector2f(width, dimensions.y), colour_pre, 8); - GeometryUtilities::GenerateQuad(vertices + 12, indices + 18, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour_pre, 12); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(dimensions.x, width), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, dimensions.y - width), Vector2f(dimensions.x, width), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(width, dimensions.y), colour_pre); + MeshUtilities::GenerateQuad(mesh, Vector2f(dimensions.x - width, 0), Vector2f(width, dimensions.y), colour_pre); - render_interface->RenderGeometry(vertices, 4 * 4, indices, 6 * 4, 0, origin); + g_render_manager->MakeGeometry(std::move(mesh)).Render(origin); } void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const Colourb colour) { - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - if (!context || !render_interface) + if (!g_context || !g_render_manager) return; - Vertex vertices[4]; - int indices[6]; + Mesh mesh; + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied()); - GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied(), 0); - - render_interface->RenderGeometry(vertices, 4, indices, 6, 0, origin); + g_render_manager->MakeGeometry(std::move(mesh)).Render(origin); } void Geometry::RenderBox(const Vector2f origin, const Vector2f dimensions, const Vector2f hole_origin, const Vector2f hole_dimensions, diff --git a/Source/Lottie/ElementLottie.cpp b/Source/Lottie/ElementLottie.cpp index 1a43a0c24..e59ae910b 100644 --- a/Source/Lottie/ElementLottie.cpp +++ b/Source/Lottie/ElementLottie.cpp @@ -32,9 +32,9 @@ #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/ElementDocument.h" #include "../../Include/RmlUi/Core/FileInterface.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/PropertyIdSet.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/SystemInterface.h" #include #include @@ -88,7 +88,7 @@ void ElementLottie::OnRender() GenerateGeometry(); UpdateTexture(); - geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round()); + geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round(), texture); } } @@ -121,26 +121,15 @@ void ElementLottie::OnPropertyChange(const PropertyIdSet& changed_properties) void ElementLottie::GenerateGeometry() { - geometry.Release(true); - - Vector& vertices = geometry.GetVertices(); - Vector& indices = geometry.GetIndices(); - - vertices.resize(4); - indices.resize(6); - - Vector2f texcoords[2] = { - {0.0f, 0.0f}, - {1.0f, 1.0f}, - }; - const ComputedValues& computed = GetComputedValues(); ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round(); render_dimensions = Vector2i(render_dimensions_f); - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), render_dimensions_f, quad_colour, texcoords[0], texcoords[1]); + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateQuad(mesh, Vector2f(0), render_dimensions_f, quad_colour, Vector2f(0), Vector2f(1)); + geometry = GetRenderManager()->MakeGeometry(std::move(mesh)); geometry_dirty = false; } @@ -149,7 +138,7 @@ bool ElementLottie::LoadAnimation() { animation_dirty = false; intrinsic_dimensions = Vector2f{}; - geometry.SetTexture(nullptr); + texture = {}; animation.reset(); prev_animation_frame = size_t(-1); time_animation_start = -1; @@ -198,6 +187,10 @@ void ElementLottie::UpdateTexture() if (!animation) return; + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + return; + const double t = GetSystemInterface()->GetElapsedTime(); // Find the next animation frame to display. @@ -222,8 +215,7 @@ void ElementLottie::UpdateTexture() } // Callback for generating texture. - auto texture_callback = [this, next_frame](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, - Vector2i& out_dimensions) -> bool { + auto texture_callback = [this, next_frame](const CallbackTextureInterface& texture_interface) -> bool { RMLUI_ASSERT(animation); const size_t bytes_per_line = 4 * render_dimensions.x; @@ -246,15 +238,13 @@ void ElementLottie::UpdateTexture() #endif } - if (!render_interface->GenerateTexture(out_handle, p_data, render_dimensions)) + if (!texture_interface.GenerateTexture(p_data, render_dimensions)) return false; - - out_dimensions = render_dimensions; return true; }; - texture.Set("lottie", std::move(texture_callback)); - geometry.SetTexture(&texture); + texture = render_manager->MakeCallbackTexture(std::move(texture_callback)); + prev_animation_frame = next_frame; texture_size_dirty = false; } diff --git a/Source/SVG/ElementSVG.cpp b/Source/SVG/ElementSVG.cpp index 0b283161e..8e4dc55d3 100644 --- a/Source/SVG/ElementSVG.cpp +++ b/Source/SVG/ElementSVG.cpp @@ -31,10 +31,10 @@ #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/ElementDocument.h" #include "../../Include/RmlUi/Core/FileInterface.h" -#include "../../Include/RmlUi/Core/GeometryUtilities.h" #include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/PropertyIdSet.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/SystemInterface.h" #include #include @@ -76,7 +76,7 @@ void ElementSVG::OnRender() GenerateGeometry(); UpdateTexture(); - geometry.Render(GetAbsoluteOffset(BoxArea::Content)); + geometry.Render(GetAbsoluteOffset(BoxArea::Content), Texture(texture)); } } @@ -114,27 +114,15 @@ void ElementSVG::OnPropertyChange(const PropertyIdSet& changed_properties) void ElementSVG::GenerateGeometry() { - geometry.Release(true); - - Vector& vertices = geometry.GetVertices(); - Vector& indices = geometry.GetIndices(); - - vertices.resize(4); - indices.resize(6); - - Vector2f texcoords[2] = { - {0.0f, 0.0f}, - {1.0f, 1.0f}, - }; - const ComputedValues& computed = GetComputedValues(); ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round(); - render_dimensions.x = int(render_dimensions_f.x); - render_dimensions.y = int(render_dimensions_f.y); + render_dimensions = Vector2i(render_dimensions_f); - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), render_dimensions_f, quad_colour, texcoords[0], texcoords[1]); + Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); + MeshUtilities::GenerateQuad(mesh, Vector2f(0), render_dimensions_f, quad_colour, Vector2f(0), Vector2f(1)); + geometry = GetRenderManager()->MakeGeometry(std::move(mesh)); geometry_dirty = false; } @@ -144,7 +132,7 @@ bool ElementSVG::LoadSource() source_dirty = false; texture_dirty = true; intrinsic_dimensions = Vector2f{}; - geometry.SetTexture(nullptr); + texture = {}; svg_document.reset(); const String attribute_src = GetAttribute("src", ""); @@ -190,9 +178,12 @@ void ElementSVG::UpdateTexture() if (!svg_document || !texture_dirty) return; + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + return; + // Callback for generating texture. - auto texture_callback = [this](RenderInterface* render_interface, const String& /*name*/, TextureHandle& out_handle, - Vector2i& out_dimensions) -> bool { + auto texture_callback = [this](const CallbackTextureInterface& texture_interface) -> bool { RMLUI_ASSERT(svg_document); lunasvg::Bitmap bitmap = svg_document->renderToBitmap(render_dimensions.x, render_dimensions.y); @@ -204,14 +195,12 @@ void ElementSVG::UpdateTexture() if (!bitmap.valid() || !bitmap.data()) return false; - if (!render_interface->GenerateTexture(out_handle, reinterpret_cast(bitmap.data()), render_dimensions)) + if (!texture_interface.GenerateTexture(reinterpret_cast(bitmap.data()), render_dimensions)) return false; - out_dimensions = render_dimensions; return true; }; - texture.Set("svg", std::move(texture_callback)); - geometry.SetTexture(&texture); + texture = render_manager->MakeCallbackTexture(std::move(texture_callback)); texture_dirty = false; } diff --git a/Tests/Source/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index 189945d26..d675ae7d1 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -89,6 +89,26 @@ void TestsRenderInterface::RenderGeometry(Rml::Vertex* /*vertices*/, int /*num_v counters.render_calls += 1; } +Rml::CompiledGeometryHandle TestsRenderInterface::CompileGeometry(Rml::Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, + int /*num_indices*/) +{ + if (!enable_compiled_geometry) + return {}; + counters.compile_geometry_calls += 1; + return Rml::CompiledGeometryHandle(counters.compile_geometry_calls); +} + +void TestsRenderInterface::RenderCompiledGeometry(Rml::CompiledGeometryHandle /*geometry*/, const Rml::Vector2f& /*translation*/, + Rml::TextureHandle /*texture*/) +{ + counters.render_compiled_geometry_calls += 1; +} + +void TestsRenderInterface::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle /*geometry*/) +{ + counters.release_compiled_geometry_calls += 1; +} + void TestsRenderInterface::EnableScissorRegion(bool /*enable*/) { counters.enable_scissor += 1; diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index 828bf434f..563670e3f 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -58,6 +58,9 @@ class TestsRenderInterface : public Rml::RenderInterface { public: struct Counters { size_t render_calls; + size_t compile_geometry_calls; + size_t render_compiled_geometry_calls; + size_t release_compiled_geometry_calls; size_t enable_scissor; size_t set_scissor; size_t load_texture; @@ -69,6 +72,10 @@ class TestsRenderInterface : public Rml::RenderInterface { void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; + void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; + void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; + void EnableScissorRegion(bool enable) override; void SetScissorRegion(int x, int y, int width, int height) override; @@ -78,11 +85,19 @@ class TestsRenderInterface : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; - const Counters& GetCounters() const { return counters; } + void EnableCompiledGeometry(bool enable) { enable_compiled_geometry = enable; } + const Counters& GetCounters() const { return counters; } void ResetCounters() { counters = {}; } + void Reset() + { + ResetCounters(); + EnableCompiledGeometry(false); + } + private: + bool enable_compiled_geometry = false; Counters counters = {}; }; diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index e3787c0ad..20d4625b0 100644 --- a/Tests/Source/UnitTests/Core.cpp +++ b/Tests/Source/UnitTests/Core.cpp @@ -124,14 +124,15 @@ TEST_CASE("core.texture_source_list") TestsShell::ShutdownShell(); } -TEST_CASE("core.release_textures") +TEST_CASE("core.release_resources") { TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface(); // This test only works with the dummy renderer. if (!render_interface) return; - render_interface->ResetCounters(); + render_interface->Reset(); + render_interface->EnableCompiledGeometry(true); const auto& counters = render_interface->GetCounters(); Context* context = TestsShell::GetContext(); @@ -154,6 +155,20 @@ TEST_CASE("core.release_textures") CHECK(counters.generate_texture + counters.load_texture > counters.release_texture); } + SUBCASE("ReleaseFontResources") + { + const auto counter_generate_before = counters.generate_texture; + const auto counter_release_before = counters.release_texture; + + Rml::ReleaseFontResources(); + CHECK(counters.generate_texture == counter_generate_before); + CHECK(counters.release_texture > counter_release_before); + + // Font texture is regenerated when rendered again. + TestsShell::RenderLoop(); + CHECK(counters.generate_texture > counter_generate_before); + } + SUBCASE("FontGlyphCache") { const auto counter_generate_before = counters.generate_texture; @@ -172,10 +187,25 @@ TEST_CASE("core.release_textures") CHECK(counters.release_texture == counter_release_before + 1); } + SUBCASE("ReleaseCompiledGeometry") + { + CHECK(counters.compile_geometry_calls > 0); + CHECK(counters.release_compiled_geometry_calls == 0); + + Rml::ReleaseCompiledGeometry(); + CHECK(counters.compile_geometry_calls == counters.release_compiled_geometry_calls); + + TestsShell::RenderLoop(); + CHECK(counters.compile_geometry_calls > counters.release_compiled_geometry_calls); + } + document->Close(); TestsShell::ShutdownShell(); - // Finally, verify that all generated and loaded textures are released during shutdown. + // Finally, verify that all generated and loaded resources are released during shutdown. CHECK(counters.generate_texture + counters.load_texture == counters.release_texture); + CHECK(counters.compile_geometry_calls == counters.release_compiled_geometry_calls); + + render_interface->Reset(); } diff --git a/Tests/Source/UnitTests/Filter.cpp b/Tests/Source/UnitTests/Filter.cpp index 182a1ab60..d833cd5ca 100644 --- a/Tests/Source/UnitTests/Filter.cpp +++ b/Tests/Source/UnitTests/Filter.cpp @@ -27,6 +27,7 @@ */ #include "../Common/TestsShell.h" +#include #include #include #include @@ -49,21 +50,15 @@ static Vector compiled_test_filters; class FilterTest : public Filter { public: FilterTest(float value, Unit unit) : value(value), unit(unit) {} - ~FilterTest() { CHECK(num_compile == num_release); } - CompiledFilterHandle CompileFilter(Element* element) const override + CompiledFilter CompileFilter(Element* element) const override { compiled_test_filters.push_back({element->GetId(), this}); - num_compile += 1; - return CompiledFilterHandle(num_compile); + return CompiledFilter(); } - void ReleaseCompiledFilter(Element* /*element*/, CompiledFilterHandle /*filter_handle*/) const override { num_release += 1; } - float value = 0.f; Unit unit = Unit::UNKNOWN; - mutable int num_compile = 0; - mutable int num_release = 0; }; class FilterTestInstancer : public FilterInstancer { diff --git a/Tests/Source/UnitTests/GeometryDatabase.cpp b/Tests/Source/UnitTests/StableVector.cpp similarity index 59% rename from Tests/Source/UnitTests/GeometryDatabase.cpp rename to Tests/Source/UnitTests/StableVector.cpp index 029885968..8f44eff28 100644 --- a/Tests/Source/UnitTests/GeometryDatabase.cpp +++ b/Tests/Source/UnitTests/StableVector.cpp @@ -26,43 +26,51 @@ * */ -#include "../../../Source/Core/GeometryDatabase.h" -#include -#include +#include #include using namespace Rml; -TEST_CASE("Geometry database") +TEST_CASE("StableVector") { - REQUIRE(GeometryDatabase::PrepareForTests()); + StableVector v; - using GeometryDatabase::ListMatchesDatabase; + REQUIRE(v.empty() == true); + REQUIRE(v.size() == 0); - Vector geometry_list(10); + const int a = 3; + const auto index_a = v.insert(a); + REQUIRE(!v.empty()); + REQUIRE(v.size() == 1); - int i = 0; - for (auto& geometry : geometry_list) - geometry.GetIndices().push_back(i++); + const int b = 4; + const auto index_b = v.insert(b); + REQUIRE(!v.empty()); + REQUIRE(v.size() == 2); - CHECK(ListMatchesDatabase(geometry_list)); + const int expected_values[] = {a, b}; + v.for_each([&, i = 0](int& value) mutable { + REQUIRE(value == expected_values[i]); + i++; + }); - geometry_list.reserve(2000); - CHECK(ListMatchesDatabase(geometry_list)); + REQUIRE(v[index_a] == a); + REQUIRE(v[index_b] == b); - geometry_list.erase(geometry_list.begin() + 5); - CHECK(ListMatchesDatabase(geometry_list)); + const int a_out = v.erase(index_a); + REQUIRE(a_out == a); + REQUIRE(v.size() == 1); + REQUIRE(v[index_b] == b); - std::swap(geometry_list.front(), geometry_list.back()); - geometry_list.pop_back(); - CHECK(ListMatchesDatabase(geometry_list)); + const int b_out = v.erase(index_b); + REQUIRE(b_out == b); + REQUIRE(v.empty()); + REQUIRE(v.size() == 0); - std::swap(geometry_list.front(), geometry_list.back()); - CHECK(ListMatchesDatabase(geometry_list)); - - geometry_list.emplace_back(); - CHECK(ListMatchesDatabase(geometry_list)); - - geometry_list.clear(); - CHECK(ListMatchesDatabase(geometry_list)); + const int c = 5; + const auto index_c = v.insert(c); + REQUIRE(!v.empty()); + REQUIRE(v.size() == 1); + REQUIRE(v[index_c] == c); + v.for_each([&](int& value) { REQUIRE(value == c); }); } diff --git a/Tests/Source/VisualTests/CaptureScreen.cpp b/Tests/Source/VisualTests/CaptureScreen.cpp index 62ea66d3d..230a5836a 100644 --- a/Tests/Source/VisualTests/CaptureScreen.cpp +++ b/Tests/Source/VisualTests/CaptureScreen.cpp @@ -28,8 +28,8 @@ #include "CaptureScreen.h" #include "TestConfig.h" -#include #include +#include #include #include #include @@ -187,8 +187,8 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int const Rml::ColourbPremultiplied colour = {255, 255, 255, 255}; const Rml::Vector2f uv_top_left = {0, 0}; const Rml::Vector2f uv_bottom_right = {1, 1}; - Rml::GeometryUtilities::GenerateQuad(geometry.vertices, geometry.indices, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref), - colour, uv_top_left, uv_bottom_right, 0); + Rml::MeshUtilities::GenerateQuad(geometry.mesh, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref), colour, uv_top_left, + uv_bottom_right); return true; }; @@ -207,7 +207,8 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int void RenderTextureGeometry(Rml::RenderInterface* render_interface, TextureGeometry& geometry) { if (geometry.texture_handle) - render_interface->RenderGeometry(geometry.vertices, 4, geometry.indices, 6, geometry.texture_handle, Rml::Vector2f(0, 0)); + render_interface->RenderGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), geometry.mesh.indices.data(), + (int)geometry.mesh.indices.size(), geometry.texture_handle, Rml::Vector2f(0, 0)); } void ReleaseTextureGeometry(Rml::RenderInterface* render_interface, TextureGeometry& geometry) diff --git a/Tests/Source/VisualTests/CaptureScreen.h b/Tests/Source/VisualTests/CaptureScreen.h index bcb8c84ee..7c6d67a47 100644 --- a/Tests/Source/VisualTests/CaptureScreen.h +++ b/Tests/Source/VisualTests/CaptureScreen.h @@ -29,9 +29,9 @@ #ifndef RMLUI_TESTS_VISUALTESTS_CAPTURESCREEN_H #define RMLUI_TESTS_VISUALTESTS_CAPTURESCREEN_H +#include #include #include -#include struct ComparisonResult { bool skipped = true; @@ -44,8 +44,7 @@ struct ComparisonResult { struct TextureGeometry { Rml::TextureHandle texture_handle = 0; - Rml::Vertex vertices[4]; - int indices[6] = {}; + Rml::Mesh mesh; }; bool CaptureScreenshot(const Rml::String& filename, int clip_width); diff --git a/Tests/Source/VisualTests/TestNavigator.cpp b/Tests/Source/VisualTests/TestNavigator.cpp index 2fb717d16..7c25a2851 100644 --- a/Tests/Source/VisualTests/TestNavigator.cpp +++ b/Tests/Source/VisualTests/TestNavigator.cpp @@ -118,7 +118,8 @@ void TestNavigator::Render() if (reference_state != ReferenceState::None && source_state == SourceType::None && !viewer->IsHelpVisible()) { TextureGeometry& geometry = (reference_state == ReferenceState::ShowReferenceHighlight ? reference_highlight_geometry : reference_geometry); - render_interface->RenderGeometry(geometry.vertices, 4, geometry.indices, 6, geometry.texture_handle, Rml::Vector2f(0, 0)); + render_interface->RenderGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), geometry.mesh.indices.data(), + (int)geometry.mesh.indices.size(), geometry.texture_handle, Rml::Vector2f(0, 0)); } } From ba355254d3a27cd695efc2b51f3835ceac4c79e6 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 8 Oct 2023 18:43:27 +0200 Subject: [PATCH 56/80] Implement clip masks for the GL2 renderer Implement compiled geometry to enable rendering clip masks. Takes advantage of the new guarantee that the pointers to vertices and indices are stable until the call to release. --- Backends/RmlUi_Backend_SDL_GL2.cpp | 5 +- Backends/RmlUi_Backend_SFML_GL2.cpp | 5 +- Backends/RmlUi_Renderer_GL2.cpp | 135 ++++++++++++++++++---------- Backends/RmlUi_Renderer_GL2.h | 13 +++ 4 files changed, 105 insertions(+), 53 deletions(-) diff --git a/Backends/RmlUi_Backend_SDL_GL2.cpp b/Backends/RmlUi_Backend_SDL_GL2.cpp index 37f916395..8d84125f3 100644 --- a/Backends/RmlUi_Backend_SDL_GL2.cpp +++ b/Backends/RmlUi_Backend_SDL_GL2.cpp @@ -52,8 +52,7 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { public: RenderInterface_GL2_SDL(SDL_Renderer* renderer) : renderer(renderer) {} - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override + void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override { SDL_Texture* sdl_texture = (SDL_Texture*)texture; if (sdl_texture) @@ -62,7 +61,7 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation); + RenderInterface_GL2::RenderCompiledGeometry(handle, translation, texture); if (sdl_texture) SDL_GL_UnbindTexture(sdl_texture); diff --git a/Backends/RmlUi_Backend_SFML_GL2.cpp b/Backends/RmlUi_Backend_SFML_GL2.cpp index 64d22b6f7..3d35fb81c 100644 --- a/Backends/RmlUi_Backend_SFML_GL2.cpp +++ b/Backends/RmlUi_Backend_SFML_GL2.cpp @@ -46,8 +46,7 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { public: // -- Inherited from Rml::RenderInterface -- - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override + void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override { if (texture) { @@ -55,7 +54,7 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation); + RenderInterface_GL2::RenderCompiledGeometry(handle, translation, texture); } bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override diff --git a/Backends/RmlUi_Renderer_GL2.cpp b/Backends/RmlUi_Renderer_GL2.cpp index 2b3d6580d..0eee9b23b 100644 --- a/Backends/RmlUi_Renderer_GL2.cpp +++ b/Backends/RmlUi_Renderer_GL2.cpp @@ -72,6 +72,11 @@ void RenderInterface_GL2::BeginFrame() glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); + glStencilMask(GLuint(-1)); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); glMatrixMode(GL_PROJECTION); glLoadMatrixf(projection.data()); @@ -88,13 +93,40 @@ void RenderInterface_GL2::EndFrame() {} void RenderInterface_GL2::Clear() { glClearStencil(0); - glClearColor(0, 0, 0, 1); + glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } -void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertices*/, int* indices, int num_indices, const Rml::TextureHandle texture, +void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, const Rml::Vector2f& translation) { + Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices); + + if (geometry) + { + RenderCompiledGeometry(geometry, translation, texture); + ReleaseCompiledGeometry(geometry); + } +} + +Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) +{ + GeometryView* data = new GeometryView{vertices, indices, num_vertices, num_indices}; + return reinterpret_cast(data); +} + +void RenderInterface_GL2::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) +{ + delete reinterpret_cast(geometry); +} + +void RenderInterface_GL2::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) +{ + const GeometryView* geometry = reinterpret_cast(handle); + const Rml::Vertex* vertices = geometry->vertices; + const int* indices = geometry->indices; + const int num_indices = geometry->num_indices; + glPushMatrix(); glTranslatef(translation.x, translation.y, 0); @@ -125,62 +157,71 @@ void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertic void RenderInterface_GL2::EnableScissorRegion(bool enable) { if (enable) - { - if (!transform_enabled) - { - glEnable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - } - else - { - glDisable(GL_SCISSOR_TEST); - glEnable(GL_STENCIL_TEST); - } - } + glEnable(GL_SCISSOR_TEST); else - { glDisable(GL_SCISSOR_TEST); - glDisable(GL_STENCIL_TEST); - } } void RenderInterface_GL2::SetScissorRegion(int x, int y, int width, int height) { - if (!transform_enabled) - { - glScissor(x, viewport_height - (y + height), width, height); - } + glScissor(x, viewport_height - (y + height), width, height); +} + +void RenderInterface_GL2::EnableClipMask(bool enable) +{ + if (enable) + glEnable(GL_STENCIL_TEST); else + glDisable(GL_STENCIL_TEST); +} + +void RenderInterface_GL2::RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) +{ + RMLUI_ASSERT(glIsEnabled(GL_STENCIL_TEST)); + using Rml::ClipMaskOperation; + + const bool clear_stencil = (operation == ClipMaskOperation::Set || operation == ClipMaskOperation::SetInverse); + if (clear_stencil) { - // clear the stencil buffer - glStencilMask(GLuint(-1)); + // @performance Increment the reference value instead of clearing each time. glClear(GL_STENCIL_BUFFER_BIT); + } - // fill the stencil buffer - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glDepthMask(GL_FALSE); - glStencilFunc(GL_NEVER, 1, GLuint(-1)); - glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); - - float fx = (float)x; - float fy = (float)y; - float fwidth = (float)width; - float fheight = (float)height; - - // draw transformed quad - GLfloat vertices[] = {fx, fy, 0, fx, fy + fheight, 0, fx + fwidth, fy + fheight, 0, fx + fwidth, fy, 0}; - glDisableClientState(GL_COLOR_ARRAY); - glVertexPointer(3, GL_FLOAT, 0, vertices); - GLushort indices[] = {1, 2, 0, 3}; - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices); - glEnableClientState(GL_COLOR_ARRAY); - - // prepare for drawing the real thing - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_TRUE); - glStencilMask(0); - glStencilFunc(GL_EQUAL, 1, GLuint(-1)); + 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; + } + + RenderCompiledGeometry(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 diff --git a/Backends/RmlUi_Renderer_GL2.h b/Backends/RmlUi_Renderer_GL2.h index 08c403376..b7d49f401 100644 --- a/Backends/RmlUi_Renderer_GL2.h +++ b/Backends/RmlUi_Renderer_GL2.h @@ -49,10 +49,16 @@ class RenderInterface_GL2 : public Rml::RenderInterface { void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; + void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; + void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; void EnableScissorRegion(bool enable) override; void SetScissorRegion(int x, int y, int width, int height) override; + void EnableClipMask(bool enable) override; + void RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; + bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; void ReleaseTexture(Rml::TextureHandle texture_handle) override; @@ -63,6 +69,13 @@ class RenderInterface_GL2 : public Rml::RenderInterface { static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); private: + struct GeometryView { + Rml::Vertex* vertices = nullptr; + int* indices = nullptr; + int num_vertices = 0; + int num_indices = 0; + }; + int viewport_width = 0; int viewport_height = 0; bool transform_enabled = false; From 90d2ef285802198d5b620ce5a9ab8de6e3c554bf Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 25 Dec 2023 21:09:31 +0100 Subject: [PATCH 57/80] Introduce a basic implementation of a Span type --- CMake/FileList.cmake | 1 + Include/RmlUi/Core/Span.h | 71 ++++++++++++++++++++++++++++++++++++++ Include/RmlUi/Core/Types.h | 1 + 3 files changed, 73 insertions(+) create mode 100644 Include/RmlUi/Core/Span.h diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 940cadd8b..5334c0d24 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -202,6 +202,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Span.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Spritesheet.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StableVector.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Stream.h diff --git a/Include/RmlUi/Core/Span.h b/Include/RmlUi/Core/Span.h new file mode 100644 index 000000000..5fdfc61ce --- /dev/null +++ b/Include/RmlUi/Core/Span.h @@ -0,0 +1,71 @@ +/* + * 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_CORE_SPAN_H +#define RMLUI_CORE_SPAN_H + +#include "../Config/Config.h" +#include "Header.h" +#include +#include + +namespace Rml { + +/** + Basic implementation of a span, which refers to a contiguous sequence of objects. + */ + +template +class RMLUICORE_API Span { +public: + Span() = default; + Span(T* data, size_t size) : m_data(data), m_size(size) { RMLUI_ASSERT(data != nullptr || size == 0); } + + Span(const Vector>& container) : Span(container.data(), container.size()) {} + Span(Vector& container) : Span(container.data(), container.size()) {} + + T& operator[](size_t index) const + { + RMLUI_ASSERT(index < m_size); + return m_data[index]; + } + + T* data() const { return m_data; } + size_t size() const { return m_size; } + bool empty() const { return m_size == 0; } + + T* begin() const { return m_data; } + T* end() const { return m_data + m_size; } + +private: + T* m_data = nullptr; + size_t m_size = 0; +}; + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index 4074a4d8d..a45287910 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -51,6 +51,7 @@ enum class BoxArea { Margin, Border, Padding, Content, Auto }; #include "Matrix4.h" #include "ObserverPtr.h" #include "Rectangle.h" +#include "Span.h" #include "Vector2.h" #include "Vector3.h" #include "Vector4.h" From 3176c85846991d5204ed33d58748d7254cdb1b68 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 25 Dec 2023 21:12:01 +0100 Subject: [PATCH 58/80] In demo sample, fix form submit animation not playing smoothly on power saving mode --- Samples/basic/demo/src/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Samples/basic/demo/src/main.cpp b/Samples/basic/demo/src/main.cpp index 79ccf6ce7..9b747eeb4 100644 --- a/Samples/basic/demo/src/main.cpp +++ b/Samples/basic/demo/src/main.cpp @@ -166,6 +166,8 @@ class DemoWindow : public Rml::EventListener { if (auto el_output = document->GetElementById("form_output")) el_output->SetInnerRML(submit_message); } + + document->GetContext()->RequestNextUpdate(.0); } } @@ -465,9 +467,9 @@ int main(int /*argc*/, char** /*argv*/) bool running = true; while (running) { - running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); - demo_window->Update(); + + running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true); context->Update(); Backend::BeginFrame(); From 5faedc427b42c1b2162dd41d60f483bef4af310e Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 25 Dec 2023 21:25:13 +0100 Subject: [PATCH 59/80] Refactor render interface: Require compiled geometry, use modern and safer types [breaking change] All geometry now requires compiling, the immediate geometry rendering call is removed for the following reasons: 1. Compiled geometry is required to use any of the new rendering features, including clip mask and shaders. 2. It simplifies the render interface, by removing a function that essentially provided a duplicate feature set. 3. This change enables us to make a clear distinction between required and optional functions. All required functions are now abstract, while optional functions are not. With recent changes, migrating from the immediate render function to using the compiled geometry functions, should be much less effort. Now the pointers to the geometry data (vertices and indices) are guaranteed to be available and immutable until the same geometry is released. Thus, users can simply store the views to this data, and reuse that during rendering. The signatures of several functions are changed: - The Span type is now used to represent contiguous data for improved usability and safety. - Texture load and generate now return the texture handle directly, to be consistent with the way geometry is compiled. The value zero is used to represent an empty texture, like before. - The scissor region now takes a Rectangle type for improved ergonomics. This is a breaking change for all users, and requires some minor adaptation in existing render interfaces. The changes to the backend renderers in this commit can be used as a reference for the change. --- Backends/RmlUi_Backend_SDL_GL2.cpp | 31 +++---- Backends/RmlUi_Backend_SDL_GL3.cpp | 21 ++--- Backends/RmlUi_Backend_SFML_GL2.cpp | 18 ++-- Backends/RmlUi_Renderer_GL2.cpp | 50 ++++------- Backends/RmlUi_Renderer_GL2.h | 24 +++-- Backends/RmlUi_Renderer_GL3.cpp | 68 ++++++-------- Backends/RmlUi_Renderer_GL3.h | 21 ++--- Backends/RmlUi_Renderer_SDL.cpp | 60 ++++++++----- Backends/RmlUi_Renderer_SDL.h | 20 +++-- Backends/RmlUi_Renderer_VK.cpp | 87 ++++++++---------- Backends/RmlUi_Renderer_VK.h | 29 +++--- Include/RmlUi/Core/CallbackTexture.h | 2 +- Include/RmlUi/Core/Rectangle.h | 6 ++ Include/RmlUi/Core/RenderInterface.h | 88 ++++++++----------- Include/RmlUi/Core/RenderManager.h | 2 +- Source/Core/CallbackTexture.cpp | 10 +-- .../FontFaceHandleDefault.cpp | 2 +- .../FontEngineDefault/FontFaceHandleDefault.h | 2 +- .../Core/FontEngineDefault/FontFaceLayer.cpp | 10 +-- Source/Core/FontEngineDefault/FontFaceLayer.h | 4 +- Source/Core/RenderInterface.cpp | 23 +---- Source/Core/RenderManager.cpp | 16 ++-- Source/Core/TextureDatabase.cpp | 3 +- Source/Core/TextureLayoutTexture.cpp | 9 +- Source/Core/TextureLayoutTexture.h | 2 +- Source/Lottie/ElementLottie.cpp | 2 +- Source/SVG/ElementSVG.cpp | 8 +- Tests/Source/Common/TestsInterface.cpp | 47 +++++----- Tests/Source/Common/TestsInterface.h | 40 ++++----- Tests/Source/Common/TestsShell.cpp | 15 ++-- Tests/Source/UnitTests/Core.cpp | 13 ++- Tests/Source/VisualTests/CaptureScreen.cpp | 29 +++--- Tests/Source/VisualTests/CaptureScreen.h | 1 + Tests/Source/VisualTests/TestNavigator.cpp | 11 ++- 34 files changed, 351 insertions(+), 423 deletions(-) diff --git a/Backends/RmlUi_Backend_SDL_GL2.cpp b/Backends/RmlUi_Backend_SDL_GL2.cpp index 8d84125f3..15277089d 100644 --- a/Backends/RmlUi_Backend_SDL_GL2.cpp +++ b/Backends/RmlUi_Backend_SDL_GL2.cpp @@ -52,7 +52,7 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { public: RenderInterface_GL2_SDL(SDL_Renderer* renderer) : renderer(renderer) {} - void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override { SDL_Texture* sdl_texture = (SDL_Texture*)texture; if (sdl_texture) @@ -61,18 +61,18 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderCompiledGeometry(handle, translation, texture); + RenderInterface_GL2::RenderGeometry(handle, translation, texture); if (sdl_texture) SDL_GL_UnbindTexture(sdl_texture); } - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); if (!file_handle) - return false; + return {}; file_interface->Seek(file_handle, 0, SEEK_END); const size_t buffer_size = file_interface->Tell(file_handle); @@ -88,7 +88,9 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); if (!surface) - return false; + return {}; + + texture_dimensions = {surface->w, surface->h}; if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32) { @@ -98,7 +100,7 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { SDL_FreeSurface(surface); if (!converted_surface) - return false; + return {}; surface = converted_surface; } @@ -113,18 +115,12 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { } SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); - - texture_dimensions = Rml::Vector2i(surface->w, surface->h); - texture_handle = (Rml::TextureHandle)texture; SDL_FreeSurface(surface); - if (!texture) - return false; - - return true; + return (Rml::TextureHandle)texture; } - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override + Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override { #if SDL_BYTEORDER == SDL_BIG_ENDIAN Uint32 rmask = 0xff000000; @@ -138,13 +134,12 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { Uint32 amask = 0xff000000; #endif - SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, rmask, - gmask, bmask, amask); + SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)source.data(), source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, + rmask, gmask, bmask, amask); SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); SDL_FreeSurface(surface); - texture_handle = (Rml::TextureHandle)texture; - return true; + return (Rml::TextureHandle)texture; } void ReleaseTexture(Rml::TextureHandle texture_handle) override { SDL_DestroyTexture((SDL_Texture*)texture_handle); } diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index aaf2bf6ce..dfc6d9f90 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -52,12 +52,12 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { public: RenderInterface_GL3_SDL() {} - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); if (!file_handle) - return false; + return {}; file_interface->Seek(file_handle, 0, SEEK_END); const size_t buffer_size = file_interface->Tell(file_handle); @@ -73,10 +73,9 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); if (!surface) - return false; + return {}; - texture_dimensions.x = surface->w; - texture_dimensions.y = surface->h; + texture_dimensions = {surface->w, surface->h}; if (surface->format->format != SDL_PIXELFORMAT_RGBA32) { @@ -85,24 +84,26 @@ class RenderInterface_GL3_SDL : public RenderInterface_GL3 { SDL_FreeSurface(surface); if (!converted_surface) - return false; + return {}; surface = converted_surface; } // Convert colors to premultiplied alpha, which is necessary for correct alpha compositing. + const size_t pixels_byte_size = surface->w * surface->h * 4; byte* pixels = static_cast(surface->pixels); - for (int i = 0; i < surface->w * surface->h * 4; i += 4) + for (size_t i = 0; i < pixels_byte_size; i += 4) { const byte alpha = pixels[i + 3]; - for (int j = 0; j < 3; ++j) + for (size_t j = 0; j < 3; ++j) pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); } - bool success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions); + Rml::TextureHandle texture_handle = RenderInterface_GL3::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions); + SDL_FreeSurface(surface); - return success; + return texture_handle; } }; diff --git a/Backends/RmlUi_Backend_SFML_GL2.cpp b/Backends/RmlUi_Backend_SFML_GL2.cpp index 3d35fb81c..5d9dbb0ae 100644 --- a/Backends/RmlUi_Backend_SFML_GL2.cpp +++ b/Backends/RmlUi_Backend_SFML_GL2.cpp @@ -46,7 +46,7 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { public: // -- Inherited from Rml::RenderInterface -- - void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override { if (texture) { @@ -54,10 +54,10 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderCompiledGeometry(handle, translation, texture); + RenderInterface_GL2::RenderGeometry(handle, translation, texture); } - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); @@ -99,13 +99,11 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { return false; } - texture_handle = (Rml::TextureHandle)texture; texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y); - - return true; + return (Rml::TextureHandle)texture; } - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override + Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override { sf::Texture* texture = new sf::Texture(); texture->setSmooth(true); @@ -116,11 +114,9 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { return false; } - texture->update(source, source_dimensions.x, source_dimensions.y, 0, 0); - - texture_handle = (Rml::TextureHandle)texture; + texture->update(source.data(), source_dimensions.x, source_dimensions.y, 0, 0); - return true; + return (Rml::TextureHandle)texture; } void ReleaseTexture(Rml::TextureHandle texture_handle) override { delete (sf::Texture*)texture_handle; } diff --git a/Backends/RmlUi_Renderer_GL2.cpp b/Backends/RmlUi_Renderer_GL2.cpp index 0eee9b23b..85308d44b 100644 --- a/Backends/RmlUi_Renderer_GL2.cpp +++ b/Backends/RmlUi_Renderer_GL2.cpp @@ -97,35 +97,23 @@ void RenderInterface_GL2::Clear() glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } -void RenderInterface_GL2::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, - const Rml::Vector2f& translation) +Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Span vertices, Rml::Span indices) { - Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices); - - if (geometry) - { - RenderCompiledGeometry(geometry, translation, texture); - ReleaseCompiledGeometry(geometry); - } -} - -Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) -{ - GeometryView* data = new GeometryView{vertices, indices, num_vertices, num_indices}; + GeometryView* data = new GeometryView{vertices, indices}; return reinterpret_cast(data); } -void RenderInterface_GL2::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) +void RenderInterface_GL2::ReleaseGeometry(Rml::CompiledGeometryHandle geometry) { delete reinterpret_cast(geometry); } -void RenderInterface_GL2::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) +void RenderInterface_GL2::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) { const GeometryView* geometry = reinterpret_cast(handle); - const Rml::Vertex* vertices = geometry->vertices; - const int* indices = geometry->indices; - const int num_indices = geometry->num_indices; + const Rml::Vertex* vertices = geometry->vertices.data(); + const int* indices = geometry->indices.data(); + const int num_indices = (int)geometry->indices.size(); glPushMatrix(); glTranslatef(translation.x, translation.y, 0); @@ -162,9 +150,9 @@ void RenderInterface_GL2::EnableScissorRegion(bool enable) glDisable(GL_SCISSOR_TEST); } -void RenderInterface_GL2::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_GL2::SetScissorRegion(Rml::Rectanglei region) { - glScissor(x, viewport_height - (y + height), width, height); + glScissor(region.Left(), viewport_height - region.Bottom(), region.Width(), region.Height()); } void RenderInterface_GL2::EnableClipMask(bool enable) @@ -215,7 +203,7 @@ void RenderInterface_GL2::RenderToClipMask(Rml::ClipMaskOperation operation, Rml break; } - RenderCompiledGeometry(geometry, translation, {}); + RenderGeometry(geometry, translation, {}); // Restore state // @performance Cache state so we don't toggle it unnecessarily. @@ -243,7 +231,7 @@ struct TGAHeader { // Restore packing #pragma pack() -bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) +Rml::TextureHandle RenderInterface_GL2::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); @@ -272,7 +260,7 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; - int image_size = header.width * header.height * 4; // We always make 32bit textures + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { @@ -319,33 +307,29 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V texture_dimensions.x = header.width; texture_dimensions.y = header.height; - bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - - return success; + return GenerateTexture({image_dest, image_size}, texture_dimensions); } -bool RenderInterface_GL2::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) +Rml::TextureHandle RenderInterface_GL2::GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) { GLuint texture_id = 0; glGenTextures(1, &texture_id); if (texture_id == 0) { Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture."); - return false; + return {}; } glBindTexture(GL_TEXTURE_2D, texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - texture_handle = (Rml::TextureHandle)texture_id; - - return true; + return (Rml::TextureHandle)texture_id; } void RenderInterface_GL2::ReleaseTexture(Rml::TextureHandle texture_handle) diff --git a/Backends/RmlUi_Renderer_GL2.h b/Backends/RmlUi_Renderer_GL2.h index b7d49f401..c26e4c4c0 100644 --- a/Backends/RmlUi_Renderer_GL2.h +++ b/Backends/RmlUi_Renderer_GL2.h @@ -47,22 +47,20 @@ class RenderInterface_GL2 : public Rml::RenderInterface { // -- Inherited from Rml::RenderInterface -- - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override; - Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; - void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; - void RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; + void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override; + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override; + + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override; + void ReleaseTexture(Rml::TextureHandle texture_handle) override; void EnableScissorRegion(bool enable) override; - void SetScissorRegion(int x, int y, int width, int height) override; + void SetScissorRegion(Rml::Rectanglei region) override; void EnableClipMask(bool enable) override; void RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; - void ReleaseTexture(Rml::TextureHandle texture_handle) override; - void SetTransform(const Rml::Matrix4f* transform) override; // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. @@ -70,10 +68,8 @@ class RenderInterface_GL2 : public Rml::RenderInterface { private: struct GeometryView { - Rml::Vertex* vertices = nullptr; - int* indices = nullptr; - int num_vertices = 0; - int num_indices = 0; + Rml::Span vertices; + Rml::Span indices; }; int viewport_width = 0; diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 66113ffd0..f10455fd4 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -31,8 +31,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -783,8 +783,7 @@ RenderInterface_GL3::RenderInterface_GL3() 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.data(), (int)mesh.vertices.size(), mesh.indices.data(), (int)mesh.indices.size()); + fullscreen_quad_geometry = RenderInterface_GL3::CompileGeometry(mesh.vertices, mesh.indices); } } @@ -792,7 +791,7 @@ RenderInterface_GL3::~RenderInterface_GL3() { if (fullscreen_quad_geometry) { - RenderInterface_GL3::ReleaseCompiledGeometry(fullscreen_quad_geometry); + RenderInterface_GL3::ReleaseGeometry(fullscreen_quad_geometry); fullscreen_quad_geometry = {}; } @@ -965,19 +964,7 @@ void RenderInterface_GL3::Clear() glClear(GL_COLOR_BUFFER_BIT); } -void RenderInterface_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, - const Rml::Vector2f& translation) -{ - Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices); - - if (geometry) - { - RenderCompiledGeometry(geometry, translation, texture); - ReleaseCompiledGeometry(geometry); - } -} - -Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) +Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Span vertices, Rml::Span indices) { constexpr GLenum draw_usage = GL_STATIC_DRAW; @@ -991,7 +978,7 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(Rml::Vertex) * num_vertices, (const void*)vertices, draw_usage); + 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), @@ -1006,7 +993,7 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve (const GLvoid*)(offsetof(Rml::Vertex, tex_coord))); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, (const void*)indices, draw_usage); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * indices.size(), (const void*)indices.data(), draw_usage); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); @@ -1017,12 +1004,12 @@ Rml::CompiledGeometryHandle RenderInterface_GL3::CompileGeometry(Rml::Vertex* ve geometry->vao = vao; geometry->vbo = vbo; geometry->ibo = ibo; - geometry->draw_count = num_indices; + geometry->draw_count = (GLsizei)indices.size(); return (Rml::CompiledGeometryHandle)geometry; } -void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation, Rml::TextureHandle texture) +void RenderInterface_GL3::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; @@ -1053,7 +1040,7 @@ void RenderInterface_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle han Gfx::CheckGLError("RenderCompiledGeometry"); } -void RenderInterface_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle handle) +void RenderInterface_GL3::ReleaseGeometry(Rml::CompiledGeometryHandle handle) { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; @@ -1103,9 +1090,9 @@ void RenderInterface_GL3::EnableScissorRegion(bool enable) SetScissor(Rml::Rectanglei::MakeInvalid(), false); } -void RenderInterface_GL3::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_GL3::SetScissorRegion(Rml::Rectanglei region) { - SetScissor(Rml::Rectanglei::FromPositionSize({x, y}, {width, height})); + SetScissor(region); } void RenderInterface_GL3::EnableClipMask(bool enable) @@ -1156,7 +1143,7 @@ void RenderInterface_GL3::RenderToClipMask(Rml::ClipMaskOperation operation, Rml break; } - RenderCompiledGeometry(geometry, translation, {}); + RenderGeometry(geometry, translation, {}); // Restore state // @performance Cache state so we don't toggle it unnecessarily. @@ -1184,7 +1171,7 @@ struct TGAHeader { // Restore packing #pragma pack() -bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) +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); @@ -1213,7 +1200,7 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; - int image_size = header.width * header.height * 4; // We always make 32bit textures + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { @@ -1260,12 +1247,10 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V texture_dimensions.x = header.width; texture_dimensions.y = header.height; - bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - - return success; + return GenerateTexture({image_dest, image_size}, texture_dimensions); } -bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) +Rml::TextureHandle RenderInterface_GL3::GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) { GLuint texture_id = 0; glGenTextures(1, &texture_id); @@ -1277,23 +1262,21 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co 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); + 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - texture_handle = (Rml::TextureHandle)texture_id; - glBindTexture(GL_TEXTURE_2D, 0); - return true; + return (Rml::TextureHandle)texture_id; } void RenderInterface_GL3::DrawFullscreenQuad() { - RenderCompiledGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); + RenderGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); } void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) @@ -1305,8 +1288,9 @@ void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vecto for (Rml::Vertex& vertex : mesh.vertices) vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; } - RenderGeometry(mesh.vertices.data(), (int)mesh.vertices.size(), mesh.indices.data(), (int)mesh.indices.size(), - RenderInterface_GL3::TexturePostprocess, {}); + const Rml::CompiledGeometryHandle geometry = CompileGeometry(mesh.vertices, mesh.indices); + RenderGeometry(geometry, {}, RenderInterface_GL3::TexturePostprocess); + ReleaseGeometry(geometry); } static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) @@ -1758,7 +1742,7 @@ void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); } -void RenderInterface_GL3::RenderFilters(const Rml::FilterHandleList& filter_handles) +void RenderInterface_GL3::RenderFilters(Rml::Span filter_handles) { for (const Rml::CompiledFilterHandle filter_handle : filter_handles) { @@ -1896,7 +1880,7 @@ void RenderInterface_GL3::PushLayer(Rml::LayerFill layer_fill) glClear(GL_COLOR_BUFFER_BIT); } -void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, const Rml::FilterHandleList& filters) +void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, Rml::Span filters) { using Rml::BlendMode; @@ -1938,8 +1922,8 @@ void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, const Rml::FilterH Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture(Rml::Vector2i dimensions) { - Rml::TextureHandle render_texture = {}; - if (!GenerateTexture(render_texture, nullptr, dimensions)) + Rml::TextureHandle render_texture = GenerateTexture({}, dimensions); + if (!render_texture) return {}; BlitTopLayerToPostprocessPrimary(); diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index f145685e3..40297e059 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -62,27 +62,24 @@ class RenderInterface_GL3 : public Rml::RenderInterface { // -- Inherited from Rml::RenderInterface -- - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::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::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; - void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) 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(int x, int y, int width, int height) 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; - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; - void ReleaseTexture(Rml::TextureHandle texture_handle) override; - void SetTransform(const Rml::Matrix4f* transform) override; void PushLayer(Rml::LayerFill layer_fill) override; - void PopLayer(Rml::BlendMode blend_mode, const Rml::FilterHandleList& filters) override; + void PopLayer(Rml::BlendMode blend_mode, Rml::Span filters) override; Rml::TextureHandle SaveLayerAsTexture(Rml::Vector2i dimensions) override; @@ -107,7 +104,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void SubmitTransformUniform(Rml::Vector2f translation); void BlitTopLayerToPostprocessPrimary(); - void RenderFilters(const Rml::FilterHandleList& filter_handles); + void RenderFilters(Rml::Span filter_handles); void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); diff --git a/Backends/RmlUi_Renderer_SDL.cpp b/Backends/RmlUi_Renderer_SDL.cpp index 1a0bdcb04..636ae2ba4 100644 --- a/Backends/RmlUi_Renderer_SDL.cpp +++ b/Backends/RmlUi_Renderer_SDL.cpp @@ -51,12 +51,28 @@ void RenderInterface_SDL::BeginFrame() void RenderInterface_SDL::EndFrame() {} -void RenderInterface_SDL::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rml::TextureHandle texture, - const Rml::Vector2f& translation) +Rml::CompiledGeometryHandle RenderInterface_SDL::CompileGeometry(Rml::Span vertices, Rml::Span indices) { + GeometryView* data = new GeometryView{vertices, indices}; + return reinterpret_cast(data); +} + +void RenderInterface_SDL::ReleaseGeometry(Rml::CompiledGeometryHandle geometry) +{ + delete reinterpret_cast(geometry); +} + +void RenderInterface_SDL::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + const GeometryView* geometry = reinterpret_cast(handle); + const Rml::Vertex* vertices = geometry->vertices.data(); + const size_t num_vertices = geometry->vertices.size(); + const int* indices = geometry->indices.data(); + const size_t num_indices = geometry->indices.size(); + SDL_FPoint* positions = new SDL_FPoint[num_vertices]; - for (int i = 0; i < num_vertices; i++) + for (size_t i = 0; i < num_vertices; i++) { positions[i].x = vertices[i].position.x + translation.x; positions[i].y = vertices[i].position.y + translation.y; @@ -65,7 +81,7 @@ void RenderInterface_SDL::RenderGeometry(Rml::Vertex* vertices, int num_vertices SDL_Texture* sdl_texture = (SDL_Texture*)texture; SDL_RenderGeometryRaw(renderer, sdl_texture, &positions[0].x, sizeof(SDL_FPoint), (const SDL_Color*)&vertices->colour, sizeof(Rml::Vertex), - &vertices->tex_coord.x, sizeof(Rml::Vertex), num_vertices, indices, num_indices, 4); + &vertices->tex_coord.x, sizeof(Rml::Vertex), (int)num_vertices, indices, (int)num_indices, 4); delete[] positions; } @@ -80,23 +96,23 @@ void RenderInterface_SDL::EnableScissorRegion(bool enable) scissor_region_enabled = enable; } -void RenderInterface_SDL::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_SDL::SetScissorRegion(Rml::Rectanglei region) { - rect_scissor.x = x; - rect_scissor.y = y; - rect_scissor.w = width; - rect_scissor.h = height; + rect_scissor.x = region.Left(); + rect_scissor.y = region.Top(); + rect_scissor.w = region.Width(); + rect_scissor.h = region.Height(); if (scissor_region_enabled) SDL_RenderSetClipRect(renderer, &rect_scissor); } -bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) +Rml::TextureHandle RenderInterface_SDL::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); if (!file_handle) - return false; + return {}; file_interface->Seek(file_handle, 0, SEEK_END); size_t buffer_size = file_interface->Tell(file_handle); @@ -112,7 +128,7 @@ bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); if (!surface) - return false; + return {}; if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32) { @@ -120,7 +136,7 @@ bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V SDL_FreeSurface(surface); if (!converted_surface) - return false; + return {}; surface = converted_surface; } @@ -137,28 +153,24 @@ bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); texture_dimensions = Rml::Vector2i(surface->w, surface->h); - texture_handle = (Rml::TextureHandle)texture; SDL_FreeSurface(surface); - if (!texture) - return false; - - SDL_SetTextureBlendMode(texture, blend_mode); + if (texture) + SDL_SetTextureBlendMode(texture, blend_mode); - return true; + return (Rml::TextureHandle)texture; } -bool RenderInterface_SDL::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) +Rml::TextureHandle RenderInterface_SDL::GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) { - SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, - SDL_PIXELFORMAT_RGBA32); + SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)source.data(), source_dimensions.x, source_dimensions.y, 32, + source_dimensions.x * 4, SDL_PIXELFORMAT_RGBA32); SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetTextureBlendMode(texture, blend_mode); SDL_FreeSurface(surface); - texture_handle = (Rml::TextureHandle)texture; - return true; + return (Rml::TextureHandle)texture; } void RenderInterface_SDL::ReleaseTexture(Rml::TextureHandle texture_handle) diff --git a/Backends/RmlUi_Renderer_SDL.h b/Backends/RmlUi_Renderer_SDL.h index 4ee1c93f2..7c0af76e9 100644 --- a/Backends/RmlUi_Renderer_SDL.h +++ b/Backends/RmlUi_Renderer_SDL.h @@ -42,17 +42,23 @@ class RenderInterface_SDL : public Rml::RenderInterface { // -- Inherited from Rml::RenderInterface -- - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; + void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override; + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override; - void EnableScissorRegion(bool enable) override; - void SetScissorRegion(int x, int y, int width, int height) override; - - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override; void ReleaseTexture(Rml::TextureHandle texture_handle) override; + void EnableScissorRegion(bool enable) override; + void SetScissorRegion(Rml::Rectanglei region) override; + private: + struct GeometryView { + Rml::Span vertices; + Rml::Span indices; + }; + SDL_Renderer* renderer; SDL_BlendMode blend_mode = {}; SDL_Rect rect_scissor = {}; diff --git a/Backends/RmlUi_Renderer_VK.cpp b/Backends/RmlUi_Renderer_VK.cpp index 151b4b889..33fb04e8e 100644 --- a/Backends/RmlUi_Renderer_VK.cpp +++ b/Backends/RmlUi_Renderer_VK.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -101,19 +102,7 @@ RenderInterface_VK::RenderInterface_VK() : RenderInterface_VK::~RenderInterface_VK() {} -void RenderInterface_VK::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) -{ - Rml::CompiledGeometryHandle handle = CompileGeometry(vertices, num_vertices, indices, num_indices); - - if (handle) - { - RenderCompiledGeometry(handle, translation, texture); - ReleaseCompiledGeometry(handle); - } -} - -Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) +Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Span vertices, Rml::Span indices) { RMLUI_ZoneScopedN("Vulkan - CompileGeometry"); @@ -127,26 +116,26 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver auto* p_geometry_handle = new geometry_handle_t{}; uint32_t* pCopyDataToBuffer = nullptr; - const void* pData = reinterpret_cast(vertices); + const void* pData = reinterpret_cast(vertices.data()); - bool status = m_memory_pool.Alloc_VertexBuffer(num_vertices, sizeof(Rml::Vertex), reinterpret_cast(&pCopyDataToBuffer), + bool status = m_memory_pool.Alloc_VertexBuffer((uint32_t)vertices.size(), sizeof(Rml::Vertex), reinterpret_cast(&pCopyDataToBuffer), &p_geometry_handle->m_p_vertex, &p_geometry_handle->m_p_vertex_allocation); RMLUI_VK_ASSERTMSG(status, "failed to AllocVertexBuffer"); - memcpy(pCopyDataToBuffer, pData, sizeof(Rml::Vertex) * num_vertices); + memcpy(pCopyDataToBuffer, pData, sizeof(Rml::Vertex) * vertices.size()); - status = m_memory_pool.Alloc_IndexBuffer(num_indices, sizeof(int), reinterpret_cast(&pCopyDataToBuffer), &p_geometry_handle->m_p_index, - &p_geometry_handle->m_p_index_allocation); + status = m_memory_pool.Alloc_IndexBuffer((uint32_t)indices.size(), sizeof(int), reinterpret_cast(&pCopyDataToBuffer), + &p_geometry_handle->m_p_index, &p_geometry_handle->m_p_index_allocation); RMLUI_VK_ASSERTMSG(status, "failed to AllocIndexBuffer"); - memcpy(pCopyDataToBuffer, indices, sizeof(int) * num_indices); + memcpy(pCopyDataToBuffer, indices.data(), sizeof(int) * indices.size()); - p_geometry_handle->m_num_indices = num_indices; + p_geometry_handle->m_num_indices = (int)indices.size(); return Rml::CompiledGeometryHandle(p_geometry_handle); } -void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) +void RenderInterface_VK::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) { RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry"); @@ -288,7 +277,7 @@ void RenderInterface_VK::RenderCompiledGeometry(Rml::CompiledGeometryHandle geom vkCmdDrawIndexed(m_p_current_command_buffer, p_casted_compiled_geometry->m_num_indices, 1, 0, 0, 0); } -void RenderInterface_VK::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) +void RenderInterface_VK::ReleaseGeometry(Rml::CompiledGeometryHandle geometry) { RMLUI_ZoneScopedN("Vulkan - ReleaseCompiledGeometry"); @@ -316,23 +305,18 @@ void RenderInterface_VK::EnableScissorRegion(bool enable) } } -void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_VK::SetScissorRegion(Rml::Rectanglei region) { if (m_is_use_scissor_specified) { if (m_is_transform_enabled) { - float left = static_cast(x); - float right = static_cast(x + width); - float top = static_cast(y); - float bottom = static_cast(y + height); - Rml::Vertex vertices[4]; - vertices[0].position = {left, top}; - vertices[1].position = {right, top}; - vertices[2].position = {right, bottom}; - vertices[3].position = {left, bottom}; + vertices[0].position = Rml::Vector2f(region.TopLeft()); + vertices[1].position = Rml::Vector2f(region.TopRight()); + vertices[2].position = Rml::Vector2f(region.BottomRight()); + vertices[3].position = Rml::Vector2f(region.BottomLeft()); int indices[6] = {0, 2, 1, 0, 3, 2}; @@ -365,7 +349,11 @@ void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height) vkCmdClearDepthStencilImage(m_p_current_command_buffer, m_texture_depthstencil.m_p_vk_image, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, &info_clear_color, 1, &info_range); - RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0.0f, 0.0f)); + if (Rml::CompiledGeometryHandle handle = CompileGeometry({vertices, 4}, {indices, 6})) + { + RenderGeometry(handle, {}, {}); + ReleaseGeometry(handle); + } m_is_use_stencil_pipeline = false; @@ -373,10 +361,10 @@ void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height) } else { - m_scissor.extent.width = width; - m_scissor.extent.height = height; - m_scissor.offset.x = static_cast(std::abs(x)); - m_scissor.offset.y = static_cast(std::abs(y)); + m_scissor.extent.width = region.Width(); + m_scissor.extent.height = region.Height(); + m_scissor.offset.x = Rml::Math::Max(region.Left(), 0); + m_scissor.offset.y = Rml::Math::Max(region.Top(), 0); #ifdef RMLUI_DEBUG VkDebugUtilsLabelEXT info{}; @@ -414,7 +402,7 @@ struct TGAHeader { // Restore packing #pragma pack() -bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) +Rml::TextureHandle RenderInterface_VK::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) { Rml::FileInterface* file_interface = Rml::GetFileInterface(); Rml::FileHandle file_handle = file_interface->Open(source); @@ -443,7 +431,7 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve memcpy(&header, buffer.get(), sizeof(TGAHeader)); int color_mode = header.bitsPerPixel / 8; - int image_size = header.width * header.height * 4; // We always make 32bit textures + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { @@ -490,15 +478,13 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve texture_dimensions.x = header.width; texture_dimensions.y = header.height; - bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); - - return success; + return GenerateTexture({image_dest, image_size}, texture_dimensions); } -bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) +Rml::TextureHandle RenderInterface_VK::GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) { Rml::String source_name = "generated-texture"; - return CreateTexture(texture_handle, source, source_dimensions, source_name); + return CreateTexture(source_data, source_dimensions, source_name); } /* @@ -517,12 +503,11 @@ bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, con efficient handling otherwise it is cpu_to_gpu visibility and it means you create only ONE buffer that is accessible for CPU and for GPU, but it will cause the worst performance... */ -bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& dimensions, - const Rml::String& name) +Rml::TextureHandle RenderInterface_VK::CreateTexture(Rml::Span source, Rml::Vector2i dimensions, const Rml::String& name) { RMLUI_ZoneScopedN("Vulkan - GenerateTexture"); - RMLUI_VK_ASSERTMSG(source, "you pushed not valid data for copying to buffer"); + RMLUI_VK_ASSERTMSG(!source.empty(), "you pushed not valid data for copying to buffer"); RMLUI_VK_ASSERTMSG(m_p_allocator, "you have to initialize Vma Allocator for this method"); (void)name; @@ -532,14 +517,14 @@ bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const RMLUI_VK_ASSERTMSG(width, "invalid width"); RMLUI_VK_ASSERTMSG(height, "invalid height"); - VkDeviceSize image_size = width * height * 4; + VkDeviceSize image_size = source.size(); VkFormat format = VkFormat::VK_FORMAT_R8G8B8A8_UNORM; buffer_data_t cpu_buffer = CreateResource_StagingBuffer(image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); void* data; vmaMapMemory(m_p_allocator, cpu_buffer.m_p_vma_allocation, &data); - memcpy(data, source, static_cast(image_size)); + memcpy(data, source.data(), static_cast(image_size)); vmaUnmapMemory(m_p_allocator, cpu_buffer.m_p_vma_allocation); VkExtent3D extent_image = {}; @@ -678,9 +663,7 @@ bool RenderInterface_VK::CreateTexture(Rml::TextureHandle& texture_handle, const p_texture->m_p_vk_image_view = p_image_view; p_texture->m_p_vk_sampler = m_p_sampler_linear; - texture_handle = reinterpret_cast(p_texture); - - return true; + return reinterpret_cast(p_texture); } void RenderInterface_VK::ReleaseTexture(Rml::TextureHandle texture_handle) diff --git a/Backends/RmlUi_Renderer_VK.h b/Backends/RmlUi_Renderer_VK.h index c4d377ef4..f8afb5d4b 100644 --- a/Backends/RmlUi_Renderer_VK.h +++ b/Backends/RmlUi_Renderer_VK.h @@ -126,32 +126,25 @@ class RenderInterface_VK : public Rml::RenderInterface { // -- Inherited from Rml::RenderInterface -- - /// Called by RmlUi when it wants to render geometry that it does not wish to optimise. - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override; - /// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future. - Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; - + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; /// Called by RmlUi when it wants to render application-compiled geometry. - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; - + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override; /// Called by RmlUi when it wants to release application-compiled geometry. - void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; - - /// Called by RmlUi when it wants to enable or disable scissoring to clip content. - void EnableScissorRegion(bool enable) override; - /// Called by RmlUi when it wants to change the scissor region. - void SetScissorRegion(int x, int y, int width, int height) override; + void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override; /// Called by RmlUi when a texture is required by the library. - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override; /// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels. - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; - + Rml::TextureHandle GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) override; /// Called by RmlUi when a loaded texture is no longer required. void ReleaseTexture(Rml::TextureHandle texture_handle) override; + /// Called by RmlUi when it wants to enable or disable scissoring to clip content. + void EnableScissorRegion(bool enable) override; + /// Called by RmlUi when it wants to change the scissor region. + void SetScissorRegion(Rml::Rectanglei region) override; + /// Called by RmlUi when it wants to set the current transform matrix to a new matrix. void SetTransform(const Rml::Matrix4f* transform) override; @@ -479,7 +472,7 @@ class RenderInterface_VK : public Rml::RenderInterface { using ExtensionPropertiesList = Rml::Vector; private: - bool CreateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& dimensions, const Rml::String& name); + Rml::TextureHandle CreateTexture(Rml::Span source, Rml::Vector2i dimensions, const Rml::String& name); void Initialize_Instance(Rml::Vector required_extensions) noexcept; void Initialize_Device() noexcept; diff --git a/Include/RmlUi/Core/CallbackTexture.h b/Include/RmlUi/Core/CallbackTexture.h index ef1aa41f6..5584bc1d8 100644 --- a/Include/RmlUi/Core/CallbackTexture.h +++ b/Include/RmlUi/Core/CallbackTexture.h @@ -76,7 +76,7 @@ class RMLUICORE_API CallbackTextureInterface { /// @param[in] source Texture data in 8-bit RGBA (premultiplied) format. /// @param[in] dimensions The width and height of the texture. /// @return True on success. - bool GenerateTexture(const byte* source, Vector2i dimensions) const; + bool GenerateTexture(Span source, Vector2i dimensions) const; /// Store the current layer as a texture, so that it can be rendered with geometry later. /// @param[in] dimensions The dimensions of the resulting texture, which will be copied from the top-left part of the active layer. diff --git a/Include/RmlUi/Core/Rectangle.h b/Include/RmlUi/Core/Rectangle.h index e85370c97..58ba9356a 100644 --- a/Include/RmlUi/Core/Rectangle.h +++ b/Include/RmlUi/Core/Rectangle.h @@ -76,6 +76,12 @@ class Rectangle { void ExtendTopLeft(Vector2Type v) { p0 -= v; } void ExtendBottomRight(Vector2Type v) { p1 += v; } + void Translate(Vector2Type v) + { + p0 += v; + p1 += v; + } + void Join(Vector2Type p) { p0 = Math::Min(p0, p); diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index b931d6b77..81c4c8f68 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -64,80 +64,68 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { RenderInterface(); virtual ~RenderInterface(); - /// Called by RmlUi when it wants to render geometry that the application does not wish to optimise. Note that - /// RmlUi renders everything as triangles. - /// @param[in] vertices The geometry's vertex data. - /// @param[in] num_vertices The number of vertices passed to the function. - /// @param[in] indices The geometry's index data. - /// @param[in] num_indices The number of indices passed to the function. This will always be a multiple of three. - /// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured. - /// @param[in] translation The translation to apply to the geometry. - virtual void RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture, - const Vector2f& translation) = 0; + /** + @name Required functions for basic rendering. + */ - /// Called by RmlUi when it wants to compile geometry it believes will be static for the forseeable future. - /// If supported, this should return a handle to an optimised, application-specific version of the data. If - /// not, do not override the function or return zero; the simpler RenderGeometry() will be called instead. + /// Called by RmlUi when it wants to compile geometry to be rendered later. /// @param[in] vertices The geometry's vertex data. - /// @param[in] num_vertices The number of vertices passed to the function. /// @param[in] indices The geometry's index data. - /// @param[in] num_indices The number of indices passed to the function. This will always be a multiple of three. - /// @return The application-specific compiled geometry. Compiled geometry will be stored and rendered using RenderCompiledGeometry() in future - /// calls, and released with ReleaseCompiledGeometry() when it is no longer needed. - virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices); - /// Called by RmlUi when it wants to render application-compiled geometry. - /// @param[in] geometry The application-specific compiled geometry to render. + /// @return An application-specified handle to the geometry, or zero if it could not be compiled. + /// @lifetime The pointed-to vertex and index data are guaranteed to be valid and immutable until ReleaseGeometry() + /// is called with the geometry handle returned here. + virtual CompiledGeometryHandle CompileGeometry(Span vertices, Span indices) = 0; + /// Called by RmlUi when it wants to render geometry. + /// @param[in] geometry The geometry to render. /// @param[in] translation The translation to apply to the geometry. - /// @param[in] texture The texture to be applied to the geometry. This may be nullptr, in which case the geometry is untextured. - virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation, TextureHandle texture); - /// Called by RmlUi when it wants to release application-compiled geometry. - /// @param[in] geometry The application-specific compiled geometry to release. - virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry); + /// @param[in] texture The texture to be applied to the geometry, or zero if the geometry is untextured. + virtual void RenderGeometry(CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture) = 0; + /// Called by RmlUi when it wants to release geometry. + /// @param[in] geometry The geometry to release. + virtual void ReleaseGeometry(CompiledGeometryHandle geometry) = 0; /// Called by RmlUi when a texture is required by the library. - /// @param[out] texture_handle The handle to write the texture handle for the loaded texture to. - /// @param[out] texture_dimensions The variable to write the dimensions of the loaded texture. + /// @param[out] texture_dimensions The dimensions of the loaded texture, which must be set by the application. /// @param[in] source The application-defined image source, joined with the path of the referencing document. - /// @return True if the load attempt succeeded and the handle and dimensions are valid, false if not. - virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source); - /// Called by RmlUi when a texture is required to be built from an internally-generated sequence of pixels. - /// @param[out] texture_handle The handle to write the texture handle for the generated texture to. + /// @return An application-specified handle identifying the texture, or zero if it could not be loaded. + virtual TextureHandle LoadTexture(Vector2i& texture_dimensions, const String& source) = 0; + /// Called by RmlUi when a texture is required to be generated from a sequence of pixels in memory. /// @param[in] source The raw texture data. Each pixel is made up of four 8-bit values, red, green, blue, and premultiplied alpha, in that order. /// @param[in] source_dimensions The dimensions, in pixels, of the source data. - /// @return True if the texture generation succeeded and the handle is valid, false if not. - virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); - /// Called by RmlUi when a loaded texture is no longer required. + /// @return An application-specified handle identifying the texture, or zero if it could not be generated. + virtual TextureHandle GenerateTexture(Span source, Vector2i source_dimensions) = 0; + /// Called by RmlUi when a loaded or generated texture is no longer required. /// @param[in] texture The texture handle to release. - virtual void ReleaseTexture(TextureHandle texture); + virtual void ReleaseTexture(TextureHandle texture) = 0; /// Called by RmlUi when it wants to enable or disable scissoring to clip content. /// @param[in] enable True if scissoring is to enabled, false if it is to be disabled. virtual void EnableScissorRegion(bool enable) = 0; /// Called by RmlUi when it wants to change the scissor region. - /// @param[in] x The left-most pixel to be rendered. All pixels to the left of this should be clipped. - /// @param[in] y The top-most pixel to be rendered. All pixels to the top of this should be clipped. - /// @param[in] width The width of the scissored region. All pixels to the right of (x + width) should be clipped. - /// @param[in] height The height of the scissored region. All pixels to below (y + height) should be clipped. - virtual void SetScissorRegion(int x, int y, int width, int height) = 0; + /// @param[in] region The region to be rendered. All pixels outside this region should be clipped. + /// @note The region should be applied in window coordinates regardless of any active transform. + virtual void SetScissorRegion(Rectanglei region) = 0; /** - @name Remaining functions are optional to implement for advanced effects. + @name Optional functions for advanced rendering features. */ /// Called by RmlUi when it wants to enable or disable the clip mask. /// @param[in] enable True if the clip mask is to be enabled, false if it is to be disabled. - /// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask. virtual void EnableClipMask(bool enable); /// Called by RmlUi when it wants to set or modify the contents of the clip mask. /// @param[in] operation Describes how the geometry should affect the clip mask. /// @param[in] geometry The compiled geometry to render. /// @param[in] translation The translation to apply to the geometry. + /// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask. + /// @note The clip mask applies to all other functions that render with a geometry handle, and only those. virtual void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation); /// Called by RmlUi when it wants the renderer to use a new transform matrix. - /// This will only be called if 'transform' properties are encountered. If no transform applies to the current element, nullptr - /// is submitted. Then it expects the renderer to use an identity matrix or otherwise omit the multiplication with the transform. /// @param[in] transform The new transform to apply, or nullptr if no transform applies to the current element. + /// @note When nullptr is submitted, the renderer should use an identity transform matrix or otherwise omit the + /// multiplication with the transform. + /// @note The transform applies to all functions that render with a geometry handle, and only those. virtual void SetTransform(const Matrix4f* transform); /// Called by RmlUi when it wants to push a new layer onto the render stack. @@ -146,21 +134,21 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// Called by RmlUi when it wants to pop the render layer stack, after applying filters to the top layer and blending it into the layer below. /// @param[in] blend_mode The mode used to blend the top layer into the one below. /// @param[in] filters A list of compiled filters which should be applied to the top layer before blending. - virtual void PopLayer(BlendMode blend_mode, const FilterHandleList& filters); + virtual void PopLayer(BlendMode blend_mode, Span filters); /// Called by RmlUi when it wants to store the current layer as a new texture to be rendered later with geometry. /// @param[in] dimensions The dimensions of the texture, to be copied from the top-left part of the viewport. - /// @return The handle to the new texture. + /// @return An application-specified handle to the new texture. virtual TextureHandle SaveLayerAsTexture(Vector2i dimensions); /// Called by RmlUi when it wants to store the current layer as a mask image, to be applied later as a filter. - /// @return The handle to a new filter representng the stored mask image. + /// @return An application-specified handle to a new filter representng the stored mask image. virtual CompiledFilterHandle SaveLayerAsMaskImage(); /// Called by RmlUi when it wants to compile a new filter. /// @param[in] name The name of the filter. /// @param[in] parameters The list of name-value parameters specified for the filter. - /// @return The handle representing the compiled filter. + /// @return An application-specified handle representing the compiled filter. virtual CompiledFilterHandle CompileFilter(const String& name, const Dictionary& parameters); /// Called by RmlUi when it no longer needs a previously compiled filter. /// @param[in] filter The handle to a previously compiled filter. @@ -169,13 +157,13 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// Called by RmlUi when it wants to compile a new shader. /// @param[in] name The name of the shader. /// @param[in] parameters The list of name-value parameters specified for the filter. - /// @return The handle representing the compiled shader. + /// @return An application-specified handle representing the shader. virtual CompiledShaderHandle CompileShader(const String& name, const Dictionary& parameters); /// Called by RmlUi when it wants to render geometry using the given shader. /// @param[in] shader The handle to a previously compiled shader. /// @param[in] geometry The handle to a previously compiled geometry. /// @param[in] translation The translation to apply to the geometry. - /// @param[in] texture The texture to use when rendering the geometry, or nullptr if no texture is required. + /// @param[in] texture The texture to use when rendering the geometry, or zero for no texture. virtual void RenderShader(CompiledShaderHandle shader, CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture); /// Called by RmlUi when it no longer needs a previously compiled shader. /// @param[in] shader The handle to a previously compiled shader. diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 808a63110..594b50007 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -104,7 +104,7 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { CompiledShader CompileShader(const String& name, const Dictionary& parameters); void PushLayer(LayerFill layer_fill); - void PopLayer(BlendMode blend_mode, const FilterHandleList& filters); + void PopLayer(BlendMode blend_mode, Span filters); CompiledFilter SaveLayerAsMaskImage(); diff --git a/Source/Core/CallbackTexture.cpp b/Source/Core/CallbackTexture.cpp index 286211448..c86c5db20 100644 --- a/Source/Core/CallbackTexture.cpp +++ b/Source/Core/CallbackTexture.cpp @@ -52,19 +52,17 @@ CallbackTextureInterface::CallbackTextureInterface(RenderManager& render_manager render_interface(render_interface), texture_handle(texture_handle), dimensions(dimensions) {} -bool CallbackTextureInterface::GenerateTexture(const byte* source, Vector2i new_dimensions) const +bool CallbackTextureInterface::GenerateTexture(Span source, Vector2i new_dimensions) const { if (texture_handle) { RMLUI_ERRORMSG("Texture already set"); return false; } - const bool result = render_interface.GenerateTexture(texture_handle, source, new_dimensions); - if (result) + texture_handle = render_interface.GenerateTexture(source, new_dimensions); + if (texture_handle) dimensions = new_dimensions; - else - texture_handle = {}; - return result; + return texture_handle != TextureHandle{}; } void CallbackTextureInterface::SaveLayerAsTexture(Vector2i new_dimensions) const diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp index 6225ed66f..044a81f11 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp @@ -169,7 +169,7 @@ int FontFaceHandleDefault::GenerateLayerConfiguration(const FontEffectList& font return (int)(layer_configurations.size() - 1); } -bool FontFaceHandleDefault::GenerateLayerTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, +bool FontFaceHandleDefault::GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, int handle_version) const { if (handle_version != version) diff --git a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h index 0b30d43d9..d94ebcceb 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h @@ -73,7 +73,7 @@ class FontFaceHandleDefault final : public NonCopyMoveable { /// @param[in] font_effect The font effect used for the layer. /// @param[in] texture_id The index of the texture within the layer to generate. /// @param[in] handle_version The version of the handle data. Function returns false if out of date. - bool GenerateLayerTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, + bool GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, int texture_id, int handle_version) const; /// Generates the geometry required to render a single line of text. diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.cpp b/Source/Core/FontEngineDefault/FontFaceLayer.cpp index c81947168..10364faf5 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.cpp +++ b/Source/Core/FontEngineDefault/FontFaceLayer.cpp @@ -161,12 +161,12 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace const int texture_id = i; CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( - const CallbackTextureInterface& texture_interface) -> bool { + const CallbackTextureInterface& texture_interface) -> bool { Vector2i dimensions; - UniquePtr data; - if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || !data) + Vector data; + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty()) return false; - if (!texture_interface.GenerateTexture(data.get(), dimensions)) + if (!texture_interface.GenerateTexture(data, dimensions)) return false; return true; }; @@ -181,7 +181,7 @@ bool FontFaceLayer::Generate(const FontFaceHandleDefault* handle, const FontFace return true; } -bool FontFaceLayer::GenerateTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs) +bool FontFaceLayer::GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs) { if (texture_id < 0 || texture_id > texture_layout.GetNumTextures()) return false; diff --git a/Source/Core/FontEngineDefault/FontFaceLayer.h b/Source/Core/FontEngineDefault/FontFaceLayer.h index e19cc2938..909015487 100644 --- a/Source/Core/FontEngineDefault/FontFaceLayer.h +++ b/Source/Core/FontEngineDefault/FontFaceLayer.h @@ -60,11 +60,11 @@ class FontFaceLayer { bool Generate(const FontFaceHandleDefault* handle, const FontFaceLayer* clone = nullptr, bool clone_glyph_origins = false); /// Generates the texture data for a layer (for the texture database). - /// @param[out] texture_data The pointer to be set to the generated texture data. + /// @param[out] texture_data The generated texture data. /// @param[out] texture_dimensions The dimensions of the texture. /// @param[in] texture_id The index of the texture within the layer to generate. /// @param[in] glyphs The glyphs required by the font face handle. - bool GenerateTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs); + bool GenerateTexture(Vector& texture_data, Vector2i& texture_dimensions, int texture_id, const FontGlyphMap& glyphs); /// Generates the geometry required to render a single character. /// @param[out] mesh_list An array of meshes this layer will write to. It must be at least as big as the number of textures in this layer. diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 3fd9e69a2..414cd20fa 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -45,36 +45,15 @@ RenderInterface::~RenderInterface() "or nullptr dereference when releasing render resources. Ensure that the render interface is destroyed *after* the call to Rml::Shutdown."); } -CompiledGeometryHandle RenderInterface::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/) -{ - return 0; -} - -void RenderInterface::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/, TextureHandle /*texture*/) {} - -void RenderInterface::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} - void RenderInterface::EnableClipMask(bool /*enable*/) {} void RenderInterface::RenderToClipMask(ClipMaskOperation /*operation*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/) {} -bool RenderInterface::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/) -{ - return false; -} - -bool RenderInterface::GenerateTexture(TextureHandle& /*texture_handle*/, const byte* /*source*/, const Vector2i& /*source_dimensions*/) -{ - return false; -} - -void RenderInterface::ReleaseTexture(TextureHandle /*texture*/) {} - void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} void RenderInterface::PushLayer(LayerFill /*layer_fill*/) {} -void RenderInterface::PopLayer(BlendMode /*blend_mode*/, const FilterHandleList& /*filters*/) {} +void RenderInterface::PopLayer(BlendMode /*blend_mode*/, Span /*filters*/) {} TextureHandle RenderInterface::SaveLayerAsTexture(Vector2i /*dimensions*/) { diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index 253d907a3..7c6685e46 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -127,7 +127,7 @@ void RenderManager::SetScissorRegion(Rectanglei new_region) new_region.Intersect(Rectanglei::FromSize(viewport_dimensions)); if (new_region != state.scissor_region) - render_interface->SetScissorRegion(new_region.Left(), new_region.Top(), new_region.Width(), new_region.Height()); + render_interface->SetScissorRegion(new_region); } state.scissor_region = new_region; @@ -219,8 +219,10 @@ CompiledGeometryHandle RenderManager::GetCompiledGeometryHandle(StableVectorInde GeometryData& geometry = geometry_list[index]; if (!geometry.handle && !geometry.mesh.indices.empty()) { - geometry.handle = render_interface->CompileGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), - geometry.mesh.indices.data(), (int)geometry.mesh.indices.size()); + geometry.handle = render_interface->CompileGeometry(geometry.mesh.vertices, geometry.mesh.indices); + + if (!geometry.handle) + Log::Message(Log::LT_ERROR, "Got empty compiled geometry."); } return geometry.handle; } @@ -245,7 +247,7 @@ void RenderManager::Render(const Geometry& geometry, Vector2f translation, Textu if (shader) render_interface->RenderShader(shader.resource_handle, geometry_handle, translation, texture_handle); else - render_interface->RenderCompiledGeometry(geometry_handle, translation, texture_handle); + render_interface->RenderGeometry(geometry_handle, translation, texture_handle); } } @@ -265,7 +267,7 @@ void RenderManager::ReleaseAllCompiledGeometry() geometry_list.for_each([this](GeometryData& data) { if (data.handle) { - render_interface->ReleaseCompiledGeometry(data.handle); + render_interface->ReleaseGeometry(data.handle); data.handle = {}; } }); @@ -298,7 +300,7 @@ void RenderManager::PushLayer(LayerFill layer_fill) render_interface->PushLayer(layer_fill); } -void RenderManager::PopLayer(BlendMode blend_mode, const FilterHandleList& filters) +void RenderManager::PopLayer(BlendMode blend_mode, Span filters) { render_interface->PopLayer(blend_mode, filters); } @@ -327,7 +329,7 @@ Mesh RenderManager::ReleaseResource(const Geometry& geometry) GeometryData& data = geometry_list[geometry.resource_handle]; if (data.handle) { - render_interface->ReleaseCompiledGeometry(data.handle); + render_interface->ReleaseGeometry(data.handle); data.handle = {}; } Mesh result = std::exchange(data.mesh, Mesh()); diff --git a/Source/Core/TextureDatabase.cpp b/Source/Core/TextureDatabase.cpp index 13c762ab8..aca8ae6a4 100644 --- a/Source/Core/TextureDatabase.cpp +++ b/Source/Core/TextureDatabase.cpp @@ -122,9 +122,8 @@ TextureFileIndex FileTextureDatabase::LoadTexture(RenderInterface* render_interf if (it != texture_map.end()) return it->second; - TextureHandle handle = {}; Vector2i dimensions; - if (render_interface->LoadTexture(handle, dimensions, source) && handle) + if (TextureHandle handle = render_interface->LoadTexture(dimensions, source)) { TextureFileIndex result = TextureFileIndex(texture_list.size()); texture_map[source] = result; diff --git a/Source/Core/TextureLayoutTexture.cpp b/Source/Core/TextureLayoutTexture.cpp index 7fdf65f27..2194fafe6 100644 --- a/Source/Core/TextureLayoutTexture.cpp +++ b/Source/Core/TextureLayoutTexture.cpp @@ -132,18 +132,17 @@ int TextureLayoutTexture::Generate(TextureLayout& layout, int maximum_dimensions } } -UniquePtr TextureLayoutTexture::AllocateTexture() +Vector TextureLayoutTexture::AllocateTexture() { - // Note: this object does not free this texture data. It is freed in the font texture loader. - UniquePtr texture_data; + Vector texture_data; if (dimensions.x > 0 && dimensions.y > 0) { // Set the texture to transparent black. - texture_data.reset(new byte[dimensions.x * dimensions.y * 4]()); + texture_data.resize(dimensions.x * dimensions.y * 4, 0); for (size_t i = 0; i < rows.size(); ++i) - rows[i].Allocate(texture_data.get(), dimensions.x * 4); + rows[i].Allocate(texture_data.data(), dimensions.x * 4); } return texture_data; diff --git a/Source/Core/TextureLayoutTexture.h b/Source/Core/TextureLayoutTexture.h index 4d9c43bb2..c8ff5adf6 100644 --- a/Source/Core/TextureLayoutTexture.h +++ b/Source/Core/TextureLayoutTexture.h @@ -62,7 +62,7 @@ class TextureLayoutTexture { /// Allocates the texture. /// @return The allocated texture data. - UniquePtr AllocateTexture(); + Vector AllocateTexture(); private: using RowList = Vector; diff --git a/Source/Lottie/ElementLottie.cpp b/Source/Lottie/ElementLottie.cpp index e59ae910b..ab74f9c8f 100644 --- a/Source/Lottie/ElementLottie.cpp +++ b/Source/Lottie/ElementLottie.cpp @@ -238,7 +238,7 @@ void ElementLottie::UpdateTexture() #endif } - if (!texture_interface.GenerateTexture(p_data, render_dimensions)) + if (!texture_interface.GenerateTexture({p_data, total_bytes}, render_dimensions)) return false; return true; }; diff --git a/Source/SVG/ElementSVG.cpp b/Source/SVG/ElementSVG.cpp index 8e4dc55d3..0e8d834a3 100644 --- a/Source/SVG/ElementSVG.cpp +++ b/Source/SVG/ElementSVG.cpp @@ -188,14 +188,14 @@ void ElementSVG::UpdateTexture() lunasvg::Bitmap bitmap = svg_document->renderToBitmap(render_dimensions.x, render_dimensions.y); // Swap red and blue channels, assuming LunaSVG v2.3.2 or newer, to convert to RmlUi's expected RGBA-ordering. - const size_t bitmap_size = bitmap.width() * bitmap.height(); + const size_t bitmap_byte_size = bitmap.width() * bitmap.height() * 4; uint8_t* bitmap_data = bitmap.data(); - for (size_t i = 0; i < bitmap_size; i++) - std::swap(bitmap_data[i * 4], bitmap_data[i * 4 + 2]); + for (size_t i = 0; i < bitmap_byte_size; i += 4) + std::swap(bitmap_data[i], bitmap_data[i + 2]); if (!bitmap.valid() || !bitmap.data()) return false; - if (!texture_interface.GenerateTexture(reinterpret_cast(bitmap.data()), render_dimensions)) + if (!texture_interface.GenerateTexture({reinterpret_cast(bitmap.data()), bitmap_byte_size}, render_dimensions)) return false; return true; }; diff --git a/Tests/Source/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index d675ae7d1..0fec9f35d 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -83,30 +83,20 @@ void TestsSystemInterface::SetTime(double t) elapsed_time = t; } -void TestsRenderInterface::RenderGeometry(Rml::Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, int /*num_indices*/, - const Rml::TextureHandle /*texture*/, const Rml::Vector2f& /*translation*/) +Rml::CompiledGeometryHandle TestsRenderInterface::CompileGeometry(Rml::Span /*vertices*/, Rml::Span /*indices*/) { - counters.render_calls += 1; + counters.compile_geometry += 1; + return Rml::CompiledGeometryHandle(counters.compile_geometry); } -Rml::CompiledGeometryHandle TestsRenderInterface::CompileGeometry(Rml::Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, - int /*num_indices*/) +void TestsRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle /*geometry*/, Rml::Vector2f /*translation*/, Rml::TextureHandle /*texture*/) { - if (!enable_compiled_geometry) - return {}; - counters.compile_geometry_calls += 1; - return Rml::CompiledGeometryHandle(counters.compile_geometry_calls); + counters.render_geometry += 1; } -void TestsRenderInterface::RenderCompiledGeometry(Rml::CompiledGeometryHandle /*geometry*/, const Rml::Vector2f& /*translation*/, - Rml::TextureHandle /*texture*/) +void TestsRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle /*geometry*/) { - counters.render_compiled_geometry_calls += 1; -} - -void TestsRenderInterface::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle /*geometry*/) -{ - counters.release_compiled_geometry_calls += 1; + counters.release_geometry += 1; } void TestsRenderInterface::EnableScissorRegion(bool /*enable*/) @@ -114,26 +104,33 @@ void TestsRenderInterface::EnableScissorRegion(bool /*enable*/) counters.enable_scissor += 1; } -void TestsRenderInterface::SetScissorRegion(int /*x*/, int /*y*/, int /*width*/, int /*height*/) +void TestsRenderInterface::SetScissorRegion(Rml::Rectanglei /*region*/) { counters.set_scissor += 1; } -bool TestsRenderInterface::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& /*source*/) +void TestsRenderInterface::EnableClipMask(bool /*enable*/) +{ + counters.enable_clip_mask += 1; +} + +void TestsRenderInterface::RenderToClipMask(Rml::ClipMaskOperation /*mask_operation*/, Rml::CompiledGeometryHandle /*geometry*/, Rml::Vector2f /*translation*/) +{ + counters.render_to_clip_mask += 1; +} + +Rml::TextureHandle TestsRenderInterface::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& /*source*/) { counters.load_texture += 1; - texture_handle = 1; texture_dimensions.x = 512; texture_dimensions.y = 256; - return true; + return 1; } -bool TestsRenderInterface::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* /*source*/, - const Rml::Vector2i& /*source_dimensions*/) +Rml::TextureHandle TestsRenderInterface::GenerateTexture(Rml::Span /*source*/, Rml::Vector2i /*source_dimensions*/) { counters.generate_texture += 1; - texture_handle = 1; - return true; + return 1; } void TestsRenderInterface::ReleaseTexture(Rml::TextureHandle /*texture_handle*/) diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index 563670e3f..68fde0c64 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -57,47 +57,41 @@ class TestsSystemInterface : public Rml::SystemInterface { class TestsRenderInterface : public Rml::RenderInterface { public: struct Counters { - size_t render_calls; - size_t compile_geometry_calls; - size_t render_compiled_geometry_calls; - size_t release_compiled_geometry_calls; - size_t enable_scissor; - size_t set_scissor; + size_t compile_geometry; + size_t render_geometry; + size_t release_geometry; size_t load_texture; size_t generate_texture; size_t release_texture; + size_t enable_scissor; + size_t set_scissor; + size_t enable_clip_mask; + size_t render_to_clip_mask; size_t set_transform; }; - void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, - const Rml::Vector2f& translation) override; + Rml::CompiledGeometryHandle CompileGeometry(Rml::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::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices) override; - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation, Rml::TextureHandle texture) override; - void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) 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(int x, int y, int width, int height) override; + void SetScissorRegion(Rml::Rectanglei region) override; - bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; - bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; - void ReleaseTexture(Rml::TextureHandle texture_handle) override; + void EnableClipMask(bool enable) override; + void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; void SetTransform(const Rml::Matrix4f* transform) override; - void EnableCompiledGeometry(bool enable) { enable_compiled_geometry = enable; } - const Counters& GetCounters() const { return counters; } void ResetCounters() { counters = {}; } - void Reset() - { - ResetCounters(); - EnableCompiledGeometry(false); - } + void Reset() { ResetCounters(); } private: - bool enable_compiled_geometry = false; Counters counters = {}; }; diff --git a/Tests/Source/Common/TestsShell.cpp b/Tests/Source/Common/TestsShell.cpp index 49b4b49a1..acf91fc97 100644 --- a/Tests/Source/Common/TestsShell.cpp +++ b/Tests/Source/Common/TestsShell.cpp @@ -196,15 +196,20 @@ Rml::String TestsShell::GetRenderStats() result = Rml::CreateString(256, "Context::Render() stats:\n" - " Render calls: %zu\n" - " Scissor enable: %zu\n" - " Scissor set: %zu\n" + " Compile geometry: %zu\n" + " Render geometry: %zu\n" + " Release geometry: %zu\n" " Texture load: %zu\n" " Texture generate: %zu\n" " Texture release: %zu\n" + " Scissor enable: %zu\n" + " Scissor set: %zu\n" + " Clip mask enable: %zu\n" + " Clip mask render: %zu\n" " Transform set: %zu", - counters.render_calls, counters.enable_scissor, counters.set_scissor, counters.load_texture, counters.generate_texture, - counters.release_texture, counters.set_transform); + counters.compile_geometry, counters.render_geometry, counters.release_geometry, counters.load_texture, counters.generate_texture, + counters.release_texture, counters.enable_scissor, counters.set_scissor, counters.enable_clip_mask, counters.render_to_clip_mask, + counters.set_transform); #endif diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index 20d4625b0..e786798df 100644 --- a/Tests/Source/UnitTests/Core.cpp +++ b/Tests/Source/UnitTests/Core.cpp @@ -132,7 +132,6 @@ TEST_CASE("core.release_resources") return; render_interface->Reset(); - render_interface->EnableCompiledGeometry(true); const auto& counters = render_interface->GetCounters(); Context* context = TestsShell::GetContext(); @@ -187,16 +186,16 @@ TEST_CASE("core.release_resources") CHECK(counters.release_texture == counter_release_before + 1); } - SUBCASE("ReleaseCompiledGeometry") + SUBCASE("ReleaseGeometry") { - CHECK(counters.compile_geometry_calls > 0); - CHECK(counters.release_compiled_geometry_calls == 0); + CHECK(counters.compile_geometry > 0); + CHECK(counters.release_geometry == 0); Rml::ReleaseCompiledGeometry(); - CHECK(counters.compile_geometry_calls == counters.release_compiled_geometry_calls); + CHECK(counters.compile_geometry == counters.release_geometry); TestsShell::RenderLoop(); - CHECK(counters.compile_geometry_calls > counters.release_compiled_geometry_calls); + CHECK(counters.compile_geometry > counters.release_geometry); } document->Close(); @@ -205,7 +204,7 @@ TEST_CASE("core.release_resources") // Finally, verify that all generated and loaded resources are released during shutdown. CHECK(counters.generate_texture + counters.load_texture == counters.release_texture); - CHECK(counters.compile_geometry_calls == counters.release_compiled_geometry_calls); + CHECK(counters.compile_geometry == counters.release_geometry); render_interface->Reset(); } diff --git a/Tests/Source/VisualTests/CaptureScreen.cpp b/Tests/Source/VisualTests/CaptureScreen.cpp index 230a5836a..868013b00 100644 --- a/Tests/Source/VisualTests/CaptureScreen.cpp +++ b/Tests/Source/VisualTests/CaptureScreen.cpp @@ -125,11 +125,13 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int } RMLUI_ASSERT(screen.num_components == 3); + const size_t image_ref_diff_byte_size = w_ref * h_ref * 4; + Image diff; diff.width = w_ref; diff.height = h_ref; diff.num_components = 4; - diff.data = Rml::UniquePtr(new Rml::byte[diff.width * diff.height * diff.num_components]); + diff.data = Rml::UniquePtr(new Rml::byte[image_ref_diff_byte_size]); // So we have both images now, compare them! Also create a diff image. // In case they are not the same size, we require that the reference image size is smaller or equal to the screen @@ -181,22 +183,23 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int result.similarity_score = (sum_diff == 0 ? 1.0 : 1.0 - std::log(double(sum_diff)) / std::log(double(max_diff))); // Optionally render the screen capture or diff to a texture. - auto GenerateGeometry = [&](TextureGeometry& geometry, Rml::byte* data, Rml::Vector2i dimensions) -> bool { - if (!render_interface->GenerateTexture(geometry.texture_handle, data, dimensions)) - return false; + auto GenerateGeometry = [&](TextureGeometry& geometry, Rml::Span data, Rml::Vector2i dimensions) -> bool { + ReleaseTextureGeometry(render_interface, geometry); const Rml::ColourbPremultiplied colour = {255, 255, 255, 255}; const Rml::Vector2f uv_top_left = {0, 0}; const Rml::Vector2f uv_bottom_right = {1, 1}; Rml::MeshUtilities::GenerateQuad(geometry.mesh, Rml::Vector2f(0, 0), Rml::Vector2f((float)w_ref, (float)h_ref), colour, uv_top_left, uv_bottom_right); - return true; + geometry.texture_handle = render_interface->GenerateTexture(data, dimensions); + geometry.geometry_handle = render_interface->CompileGeometry(geometry.mesh.vertices, geometry.mesh.indices); + return geometry.texture_handle && geometry.geometry_handle; }; if (out_reference && result.success) - result.success = GenerateGeometry(*out_reference, data_ref, {(int)w_ref, (int)h_ref}); + result.success = GenerateGeometry(*out_reference, {data_ref, image_ref_diff_byte_size}, {(int)w_ref, (int)h_ref}); if (out_highlight && result.success) - result.success = GenerateGeometry(*out_highlight, diff.data.get(), {diff.width, diff.height}); + result.success = GenerateGeometry(*out_highlight, {diff.data.get(), image_ref_diff_byte_size}, {diff.width, diff.height}); if (!result.success) result.error_msg = Rml::CreateString(1024, "Could not generate texture from file %s", input_path.c_str()); @@ -206,13 +209,19 @@ ComparisonResult CompareScreenToPreviousCapture(Rml::RenderInterface* render_int void RenderTextureGeometry(Rml::RenderInterface* render_interface, TextureGeometry& geometry) { - if (geometry.texture_handle) - render_interface->RenderGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), geometry.mesh.indices.data(), - (int)geometry.mesh.indices.size(), geometry.texture_handle, Rml::Vector2f(0, 0)); + if (geometry.geometry_handle && geometry.texture_handle) + { + render_interface->RenderGeometry(geometry.geometry_handle, Rml::Vector2f(0, 0), geometry.texture_handle); + } } void ReleaseTextureGeometry(Rml::RenderInterface* render_interface, TextureGeometry& geometry) { + if (geometry.geometry_handle) + { + render_interface->ReleaseGeometry(geometry.geometry_handle); + geometry.geometry_handle = 0; + } if (geometry.texture_handle) { render_interface->ReleaseTexture(geometry.texture_handle); diff --git a/Tests/Source/VisualTests/CaptureScreen.h b/Tests/Source/VisualTests/CaptureScreen.h index 7c6d67a47..41150bfca 100644 --- a/Tests/Source/VisualTests/CaptureScreen.h +++ b/Tests/Source/VisualTests/CaptureScreen.h @@ -44,6 +44,7 @@ struct ComparisonResult { struct TextureGeometry { Rml::TextureHandle texture_handle = 0; + Rml::CompiledGeometryHandle geometry_handle = 0; Rml::Mesh mesh; }; diff --git a/Tests/Source/VisualTests/TestNavigator.cpp b/Tests/Source/VisualTests/TestNavigator.cpp index 7c25a2851..8ded7edea 100644 --- a/Tests/Source/VisualTests/TestNavigator.cpp +++ b/Tests/Source/VisualTests/TestNavigator.cpp @@ -117,9 +117,14 @@ void TestNavigator::Render() { if (reference_state != ReferenceState::None && source_state == SourceType::None && !viewer->IsHelpVisible()) { - TextureGeometry& geometry = (reference_state == ReferenceState::ShowReferenceHighlight ? reference_highlight_geometry : reference_geometry); - render_interface->RenderGeometry(geometry.mesh.vertices.data(), (int)geometry.mesh.vertices.size(), geometry.mesh.indices.data(), - (int)geometry.mesh.indices.size(), geometry.texture_handle, Rml::Vector2f(0, 0)); + const TextureGeometry& geometry = + (reference_state == ReferenceState::ShowReferenceHighlight ? reference_highlight_geometry : reference_geometry); + + if (const Rml::CompiledGeometryHandle handle = render_interface->CompileGeometry(geometry.mesh.vertices, geometry.mesh.indices)) + { + render_interface->RenderGeometry(handle, Rml::Vector2f(0, 0), geometry.texture_handle); + render_interface->ReleaseGeometry(handle); + } } } From 8b292e1ee7fed236bbaca3a8bbbd707c9565b1a1 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 25 Dec 2023 23:37:09 +0100 Subject: [PATCH 60/80] Add a render interface adapter from the old interface to the newly refactored interface This adapter is implemented to ease the transition to the new render interface, so that users can quickly adopt the new version of RmlUi. Then, users can iteratively migrate to the new interface as they see fit. There are also some comments that can help guide the migration. To use the adapter, simply derive from `Rml::RenderInterfaceCompatibility` instead of `Rml::RenderInterface`. Then submit the adapted interface to RmlUi, as demonstrated in the following: ``` #include class MyRenderInterface : public Rml::RenderInterfaceCompatibility { ... }; // During initialization of RmlUi, set the adapted render interface as follows. Rml::SetRenderInterface(my_render_interface.GetAdaptedInterface()); ``` See `Include/RmlUi/Core/RenderInterfaceCompatibility.h` for more details. This commit further adds the GL2 and GL3 renderers from RmlUi 5, using the compatibility adapter, for demonstrating and testing of the adapter. These can be selected by setting the CMake variable `SAMPLES_BACKEND` to `BackwardCompatible_GLFW_GL2` or `BackwardCompatible_GLFW_GL3`. --- ...Ui_Backend_BackwardCompatible_GLFW_GL2.cpp | 254 ++++++ ...Ui_Backend_BackwardCompatible_GLFW_GL3.cpp | 268 ++++++ .../RmlUi_Renderer_BackwardCompatible_GL2.cpp | 328 ++++++++ .../RmlUi_Renderer_BackwardCompatible_GL2.h | 77 ++ .../RmlUi_Renderer_BackwardCompatible_GL3.cpp | 793 ++++++++++++++++++ .../RmlUi_Renderer_BackwardCompatible_GL3.h | 148 ++++ CMake/BackendFileList.cmake | 21 + CMake/FileList.cmake | 2 + CMakeLists.txt | 4 +- .../RmlUi/Core/RenderInterfaceCompatibility.h | 121 +++ Source/Core/RenderInterfaceCompatibility.cpp | 218 +++++ 11 files changed, 2232 insertions(+), 2 deletions(-) create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp create mode 100644 Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h create mode 100644 Include/RmlUi/Core/RenderInterfaceCompatibility.h create mode 100644 Source/Core/RenderInterfaceCompatibility.cpp diff --git a/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp b/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp new file mode 100644 index 000000000..cdbad37eb --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp @@ -0,0 +1,254 @@ +/* + * 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_Backend.h" +#include "../RmlUi_Platform_GLFW.h" +#include "RmlUi_Renderer_BackwardCompatible_GL2.h" +#include +#include +#include +#include + +static void SetupCallbacks(GLFWwindow* window); + +static void LogErrorFromGLFW(int error, const char* description) +{ + Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description); +} + +/** + Global data used by this backend. + + Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). + */ +struct BackendData { + SystemInterface_GLFW system_interface; + RenderInterface_BackwardCompatible_GL2 render_interface; + GLFWwindow* window = nullptr; + int glfw_active_modifiers = 0; + bool context_dimensions_dirty = true; + + // Arguments set during event processing and nulled otherwise. + Rml::Context* context = nullptr; + KeyDownCallback key_down_callback = nullptr; +}; +static Rml::UniquePtr data; + +bool Backend::Initialize(const char* name, int width, int height, bool allow_resize) +{ + RMLUI_ASSERT(!data); + + glfwSetErrorCallback(LogErrorFromGLFW); + + if (!glfwInit()) + return false; + + // Set window hints for OpenGL 2 context creation. + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); + + // Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements. + glfwWindowHint(GLFW_STENCIL_BITS, 8); + + // Enable MSAA for better-looking visuals, especially when transforms are applied. + glfwWindowHint(GLFW_SAMPLES, 2); + + // Apply window properties and create it. + glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + + GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr); + if (!window) + return false; + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + data = Rml::MakeUnique(); + data->window = window; + data->system_interface.SetWindow(window); + + // The window size may have been scaled by DPI settings, get the actual pixel size. + glfwGetFramebufferSize(window, &width, &height); + data->render_interface.SetViewport(width, height); + + // Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields. + glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE); + + // Setup the input and window event callback functions. + SetupCallbacks(window); + + return true; +} + +void Backend::Shutdown() +{ + RMLUI_ASSERT(data); + glfwDestroyWindow(data->window); + data.reset(); + glfwTerminate(); +} + +Rml::SystemInterface* Backend::GetSystemInterface() +{ + RMLUI_ASSERT(data); + return &data->system_interface; +} + +Rml::RenderInterface* Backend::GetRenderInterface() +{ + RMLUI_ASSERT(data); + return data->render_interface.GetAdaptedInterface(); +} + +bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save) +{ + RMLUI_ASSERT(data && context); + + // The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context. + if (data->context_dimensions_dirty) + { + data->context_dimensions_dirty = false; + + Rml::Vector2i window_size; + float dp_ratio = 1.f; + glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y); + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + context->SetDimensions(window_size); + context->SetDensityIndependentPixelRatio(dp_ratio); + } + + data->context = context; + data->key_down_callback = key_down_callback; + + if (power_save) + glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)); + else + glfwPollEvents(); + + data->context = nullptr; + data->key_down_callback = nullptr; + + const bool result = !glfwWindowShouldClose(data->window); + glfwSetWindowShouldClose(data->window, GLFW_FALSE); + return result; +} + +void Backend::RequestExit() +{ + RMLUI_ASSERT(data); + glfwSetWindowShouldClose(data->window, GLFW_TRUE); +} + +void Backend::BeginFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.BeginFrame(); + data->render_interface.Clear(); +} + +void Backend::PresentFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.EndFrame(); + glfwSwapBuffers(data->window); + + // Optional, used to mark frames during performance profiling. + RMLUI_FrameMark; +} + +static void SetupCallbacks(GLFWwindow* window) +{ + RMLUI_ASSERT(data); + + // Key input + glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) { + if (!data->context) + return; + + // Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events. + data->glfw_active_modifiers = glfw_mods; + + // Override the default key event callback to add global shortcuts for the samples. + Rml::Context* context = data->context; + KeyDownCallback key_down_callback = data->key_down_callback; + + switch (glfw_action) + { + case GLFW_PRESS: + case GLFW_REPEAT: + { + const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key); + const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods); + float dp_ratio = 1.f; + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + // See if we have any global shortcuts that take priority over the context. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true)) + break; + // Otherwise, hand the event over to the context by calling the input handler as normal. + if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods)) + break; + // The key was not consumed by the context either, try keyboard shortcuts of lower priority. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false)) + break; + } + break; + case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break; + } + }); + + glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); }); + + glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); }); + + // Mouse input + glfwSetCursorPosCallback(window, [](GLFWwindow* /*window*/, double xpos, double ypos) { + RmlGLFW::ProcessCursorPosCallback(data->context, xpos, ypos, data->glfw_active_modifiers); + }); + + glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { + data->glfw_active_modifiers = mods; + RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); + }); + + glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { + RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); + }); + + // Window events + glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { + data->render_interface.SetViewport(width, height); + RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); + }); + + glfwSetWindowContentScaleCallback(window, + [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); +} diff --git a/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp b/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp new file mode 100644 index 000000000..92465ab3b --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp @@ -0,0 +1,268 @@ +/* + * 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_Backend.h" +#include "../RmlUi_Platform_GLFW.h" +#include "RmlUi_Renderer_BackwardCompatible_GL3.h" +#include +#include +#include +#include + +static void SetupCallbacks(GLFWwindow* window); + +static void LogErrorFromGLFW(int error, const char* description) +{ + Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description); +} + +/** + Global data used by this backend. + + Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). + */ +struct BackendData { + SystemInterface_GLFW system_interface; + RenderInterface_BackwardCompatible_GL3 render_interface; + GLFWwindow* window = nullptr; + int glfw_active_modifiers = 0; + bool context_dimensions_dirty = true; + + // Arguments set during event processing and nulled otherwise. + Rml::Context* context = nullptr; + KeyDownCallback key_down_callback = nullptr; +}; +static Rml::UniquePtr data; + +bool Backend::Initialize(const char* name, int width, int height, bool allow_resize) +{ + RMLUI_ASSERT(!data); + + glfwSetErrorCallback(LogErrorFromGLFW); + + if (!glfwInit()) + return false; + + // Set window hints for OpenGL 3.3 Core context creation. + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); + + // Request stencil buffer of at least 8-bit size to supporting clipping on transformed elements. + glfwWindowHint(GLFW_STENCIL_BITS, 8); + + // Enable MSAA for better-looking visuals, especially when transforms are applied. + glfwWindowHint(GLFW_SAMPLES, 2); + + // Apply window properties and create it. + glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + + GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr); + if (!window) + return false; + + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + + // Load the OpenGL functions. + Rml::String renderer_message; + if (!RmlGL3::Initialize(&renderer_message)) + return false; + + // Construct the system and render interface, this includes compiling all the shaders. If this fails, it is likely an error in the shader code. + data = Rml::MakeUnique(); + if (!data || !data->render_interface) + return false; + + data->window = window; + data->system_interface.SetWindow(window); + data->system_interface.LogMessage(Rml::Log::LT_INFO, renderer_message); + + // The window size may have been scaled by DPI settings, get the actual pixel size. + glfwGetFramebufferSize(window, &width, &height); + data->render_interface.SetViewport(width, height); + + // Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields. + glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE); + + // Setup the input and window event callback functions. + SetupCallbacks(window); + + return true; +} + +void Backend::Shutdown() +{ + RMLUI_ASSERT(data); + glfwDestroyWindow(data->window); + data.reset(); + RmlGL3::Shutdown(); + glfwTerminate(); +} + +Rml::SystemInterface* Backend::GetSystemInterface() +{ + RMLUI_ASSERT(data); + return &data->system_interface; +} + +Rml::RenderInterface* Backend::GetRenderInterface() +{ + RMLUI_ASSERT(data); + return data->render_interface.GetAdaptedInterface(); +} + +bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save) +{ + RMLUI_ASSERT(data && context); + + // The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context. + if (data->context_dimensions_dirty) + { + data->context_dimensions_dirty = false; + + Rml::Vector2i window_size; + float dp_ratio = 1.f; + glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y); + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + context->SetDimensions(window_size); + context->SetDensityIndependentPixelRatio(dp_ratio); + } + + data->context = context; + data->key_down_callback = key_down_callback; + + if (power_save) + glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)); + else + glfwPollEvents(); + + data->context = nullptr; + data->key_down_callback = nullptr; + + const bool result = !glfwWindowShouldClose(data->window); + glfwSetWindowShouldClose(data->window, GLFW_FALSE); + return result; +} + +void Backend::RequestExit() +{ + RMLUI_ASSERT(data); + glfwSetWindowShouldClose(data->window, GLFW_TRUE); +} + +void Backend::BeginFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.BeginFrame(); + data->render_interface.Clear(); +} + +void Backend::PresentFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.EndFrame(); + glfwSwapBuffers(data->window); + + // Optional, used to mark frames during performance profiling. + RMLUI_FrameMark; +} + +static void SetupCallbacks(GLFWwindow* window) +{ + RMLUI_ASSERT(data); + + // Key input + glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) { + if (!data->context) + return; + + // Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events. + data->glfw_active_modifiers = glfw_mods; + + // Override the default key event callback to add global shortcuts for the samples. + Rml::Context* context = data->context; + KeyDownCallback key_down_callback = data->key_down_callback; + + switch (glfw_action) + { + case GLFW_PRESS: + case GLFW_REPEAT: + { + const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key); + const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods); + float dp_ratio = 1.f; + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + // See if we have any global shortcuts that take priority over the context. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true)) + break; + // Otherwise, hand the event over to the context by calling the input handler as normal. + if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods)) + break; + // The key was not consumed by the context either, try keyboard shortcuts of lower priority. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false)) + break; + } + break; + case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break; + } + }); + + glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); }); + + glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); }); + + // Mouse input + glfwSetCursorPosCallback(window, [](GLFWwindow* /*window*/, double xpos, double ypos) { + RmlGLFW::ProcessCursorPosCallback(data->context, xpos, ypos, data->glfw_active_modifiers); + }); + + glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { + data->glfw_active_modifiers = mods; + RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); + }); + + glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { + RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); + }); + + // Window events + glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { + data->render_interface.SetViewport(width, height); + RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); + }); + + glfwSetWindowContentScaleCallback(window, + [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); +} diff --git a/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp new file mode 100644 index 000000000..a85662751 --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp @@ -0,0 +1,328 @@ +/* + * 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_BackwardCompatible_GL2.h" +#include +#include +#include +#include +#include + +#if defined RMLUI_PLATFORM_WIN32 + #include "RmlUi_Include_Windows.h" + #include + #include +#elif defined RMLUI_PLATFORM_MACOSX + #include + #include + #include + #include +#elif defined RMLUI_PLATFORM_UNIX + #include "RmlUi_Include_Xlib.h" + #include + #include + #include + #include +#endif + +#define GL_CLAMP_TO_EDGE 0x812F + +RenderInterface_BackwardCompatible_GL2::RenderInterface_BackwardCompatible_GL2() {} + +void RenderInterface_BackwardCompatible_GL2::SetViewport(int in_viewport_width, int in_viewport_height) +{ + viewport_width = in_viewport_width; + viewport_height = in_viewport_height; +} + +void RenderInterface_BackwardCompatible_GL2::BeginFrame() +{ + RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0); + glViewport(0, 0, viewport_width, viewport_height); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(projection.data()); + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + transform_enabled = false; +} + +void RenderInterface_BackwardCompatible_GL2::EndFrame() {} + +void RenderInterface_BackwardCompatible_GL2::Clear() +{ + glClearStencil(0); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +void RenderInterface_BackwardCompatible_GL2::RenderGeometry(Rml::Vertex* vertices, int /*num_vertices*/, int* indices, int num_indices, + const Rml::TextureHandle texture, const Rml::Vector2f& translation) +{ + glPushMatrix(); + glTranslatef(translation.x, translation.y, 0); + + glVertexPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].position); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(Rml::Vertex), &vertices[0].colour); + + if (!texture) + { + glDisable(GL_TEXTURE_2D); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + else + { + glEnable(GL_TEXTURE_2D); + + if (texture != TextureEnableWithoutBinding) + glBindTexture(GL_TEXTURE_2D, (GLuint)texture); + + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, sizeof(Rml::Vertex), &vertices[0].tex_coord); + } + + glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, indices); + + glPopMatrix(); +} + +void RenderInterface_BackwardCompatible_GL2::EnableScissorRegion(bool enable) +{ + if (enable) + { + if (!transform_enabled) + { + glEnable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + } + else + { + glDisable(GL_SCISSOR_TEST); + glEnable(GL_STENCIL_TEST); + } + } + else + { + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + } +} + +void RenderInterface_BackwardCompatible_GL2::SetScissorRegion(int x, int y, int width, int height) +{ + if (!transform_enabled) + { + glScissor(x, viewport_height - (y + height), width, height); + } + else + { + // clear the stencil buffer + glStencilMask(GLuint(-1)); + glClear(GL_STENCIL_BUFFER_BIT); + + // fill the stencil buffer + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glDepthMask(GL_FALSE); + glStencilFunc(GL_NEVER, 1, GLuint(-1)); + glStencilOp(GL_REPLACE, GL_KEEP, GL_KEEP); + + float fx = (float)x; + float fy = (float)y; + float fwidth = (float)width; + float fheight = (float)height; + + // draw transformed quad + GLfloat vertices[] = {fx, fy, 0, fx, fy + fheight, 0, fx + fwidth, fy + fheight, 0, fx + fwidth, fy, 0}; + glDisableClientState(GL_COLOR_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, vertices); + GLushort indices[] = {1, 2, 0, 3}; + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices); + glEnableClientState(GL_COLOR_ARRAY); + + // prepare for drawing the real thing + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + glStencilMask(0); + glStencilFunc(GL_EQUAL, 1, GLuint(-1)); + } +} + +// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file +#pragma pack(1) +struct TGAHeader { + char idLength; + char colourMapType; + char dataType; + short int colourMapOrigin; + short int colourMapLength; + char colourMapDepth; + short int xOrigin; + short int yOrigin; + short int width; + short int height; + char bitsPerPixel; + char imageDescriptor; +}; +// Restore packing +#pragma pack() + +bool RenderInterface_BackwardCompatible_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, + const Rml::String& source) +{ + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle file_handle = file_interface->Open(source); + if (!file_handle) + { + return false; + } + + file_interface->Seek(file_handle, 0, SEEK_END); + size_t buffer_size = file_interface->Tell(file_handle); + file_interface->Seek(file_handle, 0, SEEK_SET); + + if (buffer_size <= sizeof(TGAHeader)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image."); + file_interface->Close(file_handle); + return false; + } + + char* buffer = new char[buffer_size]; + file_interface->Read(buffer, buffer_size, file_handle); + file_interface->Close(file_handle); + + TGAHeader header; + memcpy(&header, buffer, sizeof(TGAHeader)); + + int color_mode = header.bitsPerPixel / 8; + int image_size = header.width * header.height * 4; // We always make 32bit textures + + if (header.dataType != 2) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); + delete[] buffer; + return false; + } + + // Ensure we have at least 3 colors + if (color_mode < 3) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); + delete[] buffer; + return false; + } + + const char* image_src = buffer + sizeof(TGAHeader); + unsigned char* image_dest = new unsigned char[image_size]; + + // Targa is BGR, swap to RGB and flip Y axis + for (long y = 0; y < header.height; y++) + { + long read_index = y * header.width * color_mode; + long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * color_mode; + for (long x = 0; x < header.width; x++) + { + image_dest[write_index] = image_src[read_index + 2]; + image_dest[write_index + 1] = image_src[read_index + 1]; + image_dest[write_index + 2] = image_src[read_index]; + if (color_mode == 4) + image_dest[write_index + 3] = image_src[read_index + 3]; + else + image_dest[write_index + 3] = 255; + + write_index += 4; + read_index += color_mode; + } + } + + texture_dimensions.x = header.width; + texture_dimensions.y = header.height; + + bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); + + delete[] image_dest; + delete[] buffer; + + return success; +} + +bool RenderInterface_BackwardCompatible_GL2::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, + const Rml::Vector2i& source_dimensions) +{ + GLuint texture_id = 0; + glGenTextures(1, &texture_id); + if (texture_id == 0) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture."); + return false; + } + + glBindTexture(GL_TEXTURE_2D, texture_id); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + texture_handle = (Rml::TextureHandle)texture_id; + + return true; +} + +void RenderInterface_BackwardCompatible_GL2::ReleaseTexture(Rml::TextureHandle texture_handle) +{ + glDeleteTextures(1, (GLuint*)&texture_handle); +} + +void RenderInterface_BackwardCompatible_GL2::SetTransform(const Rml::Matrix4f* transform) +{ + transform_enabled = (transform != nullptr); + + if (transform) + { + if (std::is_same::value) + glLoadMatrixf(transform->data()); + else if (std::is_same::value) + glLoadMatrixf(transform->Transpose().data()); + } + else + glLoadIdentity(); +} diff --git a/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h new file mode 100644 index 000000000..d31ae089a --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h @@ -0,0 +1,77 @@ +/* + * 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_BACKWARDCOMPATIBLE_GL2_H +#define RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL2_H + +#include + +/* + The GL2 renderer from RmlUi 5, only modified to derive from the compatibility interface. + + Implemented for testing and demonstration purposes, not recommended for production use. +*/ + +class RenderInterface_BackwardCompatible_GL2 : public Rml::RenderInterfaceCompatibility { +public: + RenderInterface_BackwardCompatible_GL2(); + + // The viewport should be updated whenever the window size changes. + void SetViewport(int viewport_width, int viewport_height); + + // Sets up OpenGL states for taking rendering commands from RmlUi. + void BeginFrame(); + void EndFrame(); + + // Optional, can be used to clear the framebuffer. + void Clear(); + + // -- Inherited from Rml::RenderInterface -- + + void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, + const Rml::Vector2f& translation) override; + + void EnableScissorRegion(bool enable) override; + void SetScissorRegion(int x, int y, int width, int height) override; + + bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; + void ReleaseTexture(Rml::TextureHandle texture_handle) override; + + void SetTransform(const Rml::Matrix4f* transform) override; + + // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. + static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); + +private: + int viewport_width = 0; + int viewport_height = 0; + bool transform_enabled = false; +}; + +#endif diff --git a/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp new file mode 100644 index 000000000..e3a7a0a57 --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp @@ -0,0 +1,793 @@ +/* + * 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_BackwardCompatible_GL3.h" +#include +#include +#include +#include +#include + +#if defined(RMLUI_PLATFORM_WIN32) && !defined(__MINGW32__) + // function call missing argument list + #pragma warning(disable : 4551) + // unreferenced local function has been removed + #pragma warning(disable : 4505) +#endif + +#if defined RMLUI_PLATFORM_EMSCRIPTEN + #define RMLUI_SHADER_HEADER "#version 300 es\nprecision highp float;\n" + #include +#elif defined RMLUI_GL3_CUSTOM_LOADER + #define RMLUI_SHADER_HEADER "#version 330\n" + #include RMLUI_GL3_CUSTOM_LOADER +#else + #define RMLUI_SHADER_HEADER "#version 330\n" + #define GLAD_GL_IMPLEMENTATION + #include "../RmlUi_Include_GL3.h" +#endif + +static const char* shader_main_vertex = RMLUI_SHADER_HEADER R"( +uniform vec2 _translate; +uniform mat4 _transform; + +in vec2 inPosition; +in vec4 inColor0; +in vec2 inTexCoord0; + +out vec2 fragTexCoord; +out vec4 fragColor; + +void main() { + fragTexCoord = inTexCoord0; + fragColor = inColor0; + + vec2 translatedPos = inPosition + _translate.xy; + vec4 outPos = _transform * vec4(translatedPos, 0, 1); + + gl_Position = outPos; +} +)"; + +static const char* shader_main_fragment_texture = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 finalColor; + +void main() { + vec4 texColor = texture(_tex, fragTexCoord); + finalColor = fragColor * texColor; +} +)"; +static const char* shader_main_fragment_color = RMLUI_SHADER_HEADER R"( +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 finalColor; + +void main() { + finalColor = fragColor; +} +)"; + +namespace Gfx { + +enum class ProgramUniform { Translate, Transform, Tex, Count }; +static const char* const program_uniform_names[(size_t)ProgramUniform::Count] = {"_translate", "_transform", "_tex"}; + +enum class VertexAttribute { Position, Color0, TexCoord0, Count }; +static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; + +struct CompiledGeometryData { + Rml::TextureHandle texture; + GLuint vao; + GLuint vbo; + GLuint ibo; + GLsizei draw_count; +}; + +struct ProgramData { + GLuint id; + GLint uniform_locations[(size_t)ProgramUniform::Count]; +}; + +struct ShadersData { + ProgramData program_color; + ProgramData program_texture; + GLuint shader_main_vertex; + GLuint shader_main_fragment_color; + GLuint shader_main_fragment_texture; +}; + +static void CheckGLError(const char* operation_name) +{ +#ifdef RMLUI_DEBUG + GLenum error_code = glGetError(); + if (error_code != GL_NO_ERROR) + { + static const Rml::Pair error_names[] = {{GL_INVALID_ENUM, "GL_INVALID_ENUM"}, {GL_INVALID_VALUE, "GL_INVALID_VALUE"}, + {GL_INVALID_OPERATION, "GL_INVALID_OPERATION"}, {GL_OUT_OF_MEMORY, "GL_OUT_OF_MEMORY"}}; + const char* error_str = "''"; + for (auto& err : error_names) + { + if (err.first == error_code) + { + error_str = err.second; + break; + } + } + Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL error during %s. Error code 0x%x (%s).", operation_name, error_code, error_str); + } +#endif + (void)operation_name; +} + +// Create the shader, 'shader_type' is either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. +static GLuint CreateShader(GLenum shader_type, const char* code_string) +{ + GLuint id = glCreateShader(shader_type); + + glShaderSource(id, 1, (const GLchar**)&code_string, NULL); + glCompileShader(id); + + GLint status = 0; + glGetShaderiv(id, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) + { + GLint info_log_length = 0; + glGetShaderiv(id, GL_INFO_LOG_LENGTH, &info_log_length); + char* info_log_string = new char[info_log_length + 1]; + glGetShaderInfoLog(id, info_log_length, NULL, info_log_string); + + Rml::Log::Message(Rml::Log::LT_ERROR, "Compile failure in OpenGL shader: %s", info_log_string); + delete[] info_log_string; + glDeleteShader(id); + return 0; + } + + CheckGLError("CreateShader"); + + return id; +} + +static void BindAttribLocations(GLuint program) +{ + for (GLuint i = 0; i < (GLuint)VertexAttribute::Count; i++) + { + glBindAttribLocation(program, i, vertex_attribute_names[i]); + } + CheckGLError("BindAttribLocations"); +} + +static bool CreateProgram(GLuint vertex_shader, GLuint fragment_shader, ProgramData& out_program) +{ + GLuint id = glCreateProgram(); + RMLUI_ASSERT(id); + + BindAttribLocations(id); + + glAttachShader(id, vertex_shader); + glAttachShader(id, fragment_shader); + + glLinkProgram(id); + + glDetachShader(id, vertex_shader); + glDetachShader(id, fragment_shader); + + GLint status = 0; + glGetProgramiv(id, GL_LINK_STATUS, &status); + if (status == GL_FALSE) + { + GLint info_log_length = 0; + glGetProgramiv(id, GL_INFO_LOG_LENGTH, &info_log_length); + char* info_log_string = new char[info_log_length + 1]; + glGetProgramInfoLog(id, info_log_length, NULL, info_log_string); + + Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program linking failure: %s", info_log_string); + delete[] info_log_string; + glDeleteProgram(id); + return false; + } + + out_program = {}; + out_program.id = id; + + // Make a lookup table for the uniform locations. + GLint num_active_uniforms = 0; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &num_active_uniforms); + + constexpr size_t name_size = 64; + GLchar name_buf[name_size] = ""; + for (int unif = 0; unif < num_active_uniforms; ++unif) + { + GLint array_size = 0; + GLenum type = 0; + GLsizei actual_length = 0; + glGetActiveUniform(id, unif, name_size, &actual_length, &array_size, &type, name_buf); + GLint location = glGetUniformLocation(id, name_buf); + + // See if we have the name in our pre-defined name list. + ProgramUniform program_uniform = ProgramUniform::Count; + for (int i = 0; i < (int)ProgramUniform::Count; i++) + { + const char* uniform_name = program_uniform_names[i]; + if (strcmp(name_buf, uniform_name) == 0) + { + program_uniform = (ProgramUniform)i; + break; + } + } + + if ((size_t)program_uniform < (size_t)ProgramUniform::Count) + { + out_program.uniform_locations[(size_t)program_uniform] = location; + } + else + { + Rml::Log::Message(Rml::Log::LT_ERROR, "OpenGL program uses unknown uniform '%s'.", name_buf); + return false; + } + } + + CheckGLError("CreateProgram"); + + return true; +} + +static bool CreateShaders(ShadersData& out_shaders) +{ + out_shaders = {}; + GLuint& main_vertex = out_shaders.shader_main_vertex; + GLuint& main_fragment_color = out_shaders.shader_main_fragment_color; + GLuint& main_fragment_texture = out_shaders.shader_main_fragment_texture; + + main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex); + if (!main_vertex) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_vertex'."); + return false; + } + main_fragment_color = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color); + if (!main_fragment_color) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_color'."); + return false; + } + main_fragment_texture = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture); + if (!main_fragment_texture) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_texture'."); + return false; + } + + if (!CreateProgram(main_vertex, main_fragment_color, out_shaders.program_color)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_color'."); + return false; + } + if (!CreateProgram(main_vertex, main_fragment_texture, out_shaders.program_texture)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_texture'."); + return false; + } + + return true; +} + +static void DestroyShaders(ShadersData& shaders) +{ + glDeleteProgram(shaders.program_color.id); + glDeleteProgram(shaders.program_texture.id); + + glDeleteShader(shaders.shader_main_vertex); + glDeleteShader(shaders.shader_main_fragment_color); + glDeleteShader(shaders.shader_main_fragment_texture); + + shaders = {}; +} + +} // namespace Gfx + +RenderInterface_BackwardCompatible_GL3::RenderInterface_BackwardCompatible_GL3() +{ + shaders = Rml::MakeUnique(); + + if (!Gfx::CreateShaders(*shaders)) + shaders.reset(); +} + +RenderInterface_BackwardCompatible_GL3::~RenderInterface_BackwardCompatible_GL3() +{ + if (shaders) + Gfx::DestroyShaders(*shaders); +} + +void RenderInterface_BackwardCompatible_GL3::SetViewport(int width, int height) +{ + viewport_width = width; + viewport_height = height; +} + +void RenderInterface_BackwardCompatible_GL3::BeginFrame() +{ + RMLUI_ASSERT(viewport_width >= 0 && viewport_height >= 0); + + // Backup GL state. + glstate_backup.enable_cull_face = glIsEnabled(GL_CULL_FACE); + glstate_backup.enable_blend = glIsEnabled(GL_BLEND); + glstate_backup.enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); + glstate_backup.enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); + + glGetIntegerv(GL_VIEWPORT, glstate_backup.viewport); + glGetIntegerv(GL_SCISSOR_BOX, glstate_backup.scissor); + + glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &glstate_backup.stencil_clear_value); + glGetFloatv(GL_COLOR_CLEAR_VALUE, glstate_backup.color_clear_value); + + glGetIntegerv(GL_BLEND_EQUATION_RGB, &glstate_backup.blend_equation_rgb); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &glstate_backup.blend_equation_alpha); + glGetIntegerv(GL_BLEND_SRC_RGB, &glstate_backup.blend_src_rgb); + glGetIntegerv(GL_BLEND_DST_RGB, &glstate_backup.blend_dst_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &glstate_backup.blend_src_alpha); + glGetIntegerv(GL_BLEND_DST_ALPHA, &glstate_backup.blend_dst_alpha); + + glGetIntegerv(GL_STENCIL_FUNC, &glstate_backup.stencil_front.func); + glGetIntegerv(GL_STENCIL_REF, &glstate_backup.stencil_front.ref); + glGetIntegerv(GL_STENCIL_VALUE_MASK, &glstate_backup.stencil_front.value_mask); + glGetIntegerv(GL_STENCIL_WRITEMASK, &glstate_backup.stencil_front.writemask); + glGetIntegerv(GL_STENCIL_FAIL, &glstate_backup.stencil_front.fail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_FAIL, &glstate_backup.stencil_front.pass_depth_fail); + glGetIntegerv(GL_STENCIL_PASS_DEPTH_PASS, &glstate_backup.stencil_front.pass_depth_pass); + + glGetIntegerv(GL_STENCIL_BACK_FUNC, &glstate_backup.stencil_back.func); + glGetIntegerv(GL_STENCIL_BACK_REF, &glstate_backup.stencil_back.ref); + glGetIntegerv(GL_STENCIL_BACK_VALUE_MASK, &glstate_backup.stencil_back.value_mask); + glGetIntegerv(GL_STENCIL_BACK_WRITEMASK, &glstate_backup.stencil_back.writemask); + glGetIntegerv(GL_STENCIL_BACK_FAIL, &glstate_backup.stencil_back.fail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_FAIL, &glstate_backup.stencil_back.pass_depth_fail); + glGetIntegerv(GL_STENCIL_BACK_PASS_DEPTH_PASS, &glstate_backup.stencil_back.pass_depth_pass); + + // Setup expected GL state. + glViewport(0, 0, viewport_width, viewport_height); + + glClearStencil(0); + glClearColor(0, 0, 0, 1); + + glDisable(GL_CULL_FACE); + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_STENCIL_TEST); + glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); + glStencilMask(GLuint(-1)); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + + projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000); + SetTransform(nullptr); +} + +void RenderInterface_BackwardCompatible_GL3::EndFrame() +{ + // Restore GL state. + if (glstate_backup.enable_cull_face) + glEnable(GL_CULL_FACE); + else + glDisable(GL_CULL_FACE); + + if (glstate_backup.enable_blend) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + + if (glstate_backup.enable_stencil_test) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); + + if (glstate_backup.enable_scissor_test) + glEnable(GL_SCISSOR_TEST); + else + glDisable(GL_SCISSOR_TEST); + + glViewport(glstate_backup.viewport[0], glstate_backup.viewport[1], glstate_backup.viewport[2], glstate_backup.viewport[3]); + glScissor(glstate_backup.scissor[0], glstate_backup.scissor[1], glstate_backup.scissor[2], glstate_backup.scissor[3]); + + glClearStencil(glstate_backup.stencil_clear_value); + glClearColor(glstate_backup.color_clear_value[0], glstate_backup.color_clear_value[1], glstate_backup.color_clear_value[2], + glstate_backup.color_clear_value[3]); + + glBlendEquationSeparate(glstate_backup.blend_equation_rgb, glstate_backup.blend_equation_alpha); + glBlendFuncSeparate(glstate_backup.blend_src_rgb, glstate_backup.blend_dst_rgb, glstate_backup.blend_src_alpha, glstate_backup.blend_dst_alpha); + + glStencilFuncSeparate(GL_FRONT, glstate_backup.stencil_front.func, glstate_backup.stencil_front.ref, glstate_backup.stencil_front.value_mask); + glStencilMaskSeparate(GL_FRONT, glstate_backup.stencil_front.writemask); + glStencilOpSeparate(GL_FRONT, glstate_backup.stencil_front.fail, glstate_backup.stencil_front.pass_depth_fail, + glstate_backup.stencil_front.pass_depth_pass); + + glStencilFuncSeparate(GL_BACK, glstate_backup.stencil_back.func, glstate_backup.stencil_back.ref, glstate_backup.stencil_back.value_mask); + glStencilMaskSeparate(GL_BACK, glstate_backup.stencil_back.writemask); + glStencilOpSeparate(GL_BACK, glstate_backup.stencil_back.fail, glstate_backup.stencil_back.pass_depth_fail, + glstate_backup.stencil_back.pass_depth_pass); +} + +void RenderInterface_BackwardCompatible_GL3::Clear() +{ + glClearStencil(0); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +void RenderInterface_BackwardCompatible_GL3::RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, + const Rml::TextureHandle texture, const Rml::Vector2f& translation) +{ + Rml::CompiledGeometryHandle geometry = CompileGeometry(vertices, num_vertices, indices, num_indices, texture); + + if (geometry) + { + RenderCompiledGeometry(geometry, translation); + ReleaseCompiledGeometry(geometry); + } +} + +Rml::CompiledGeometryHandle RenderInterface_BackwardCompatible_GL3::CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, + int num_indices, Rml::TextureHandle texture) +{ + constexpr GLenum draw_usage = GL_STATIC_DRAW; + + GLuint vao = 0; + GLuint vbo = 0; + GLuint ibo = 0; + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glGenBuffers(1, &ibo); + glBindVertexArray(vao); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(Rml::Vertex) * num_vertices, (const void*)vertices, draw_usage); + + glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Position); + glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Position, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex), + (const GLvoid*)(offsetof(Rml::Vertex, position))); + + glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::Color0); + glVertexAttribPointer((GLuint)Gfx::VertexAttribute::Color0, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Rml::Vertex), + (const GLvoid*)(offsetof(Rml::Vertex, colour))); + + glEnableVertexAttribArray((GLuint)Gfx::VertexAttribute::TexCoord0); + glVertexAttribPointer((GLuint)Gfx::VertexAttribute::TexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(Rml::Vertex), + (const GLvoid*)(offsetof(Rml::Vertex, tex_coord))); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, (const void*)indices, draw_usage); + + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + Gfx::CheckGLError("CompileGeometry"); + + Gfx::CompiledGeometryData* geometry = new Gfx::CompiledGeometryData; + geometry->texture = texture; + geometry->vao = vao; + geometry->vbo = vbo; + geometry->ibo = ibo; + geometry->draw_count = num_indices; + + return (Rml::CompiledGeometryHandle)geometry; +} + +void RenderInterface_BackwardCompatible_GL3::RenderCompiledGeometry(Rml::CompiledGeometryHandle handle, const Rml::Vector2f& translation) +{ + Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; + + if (geometry->texture) + { + glUseProgram(shaders->program_texture.id); + if (geometry->texture != TextureEnableWithoutBinding) + glBindTexture(GL_TEXTURE_2D, (GLuint)geometry->texture); + SubmitTransformUniform(ProgramId::Texture, shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]); + glUniform2fv(shaders->program_texture.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x); + } + else + { + glUseProgram(shaders->program_color.id); + glBindTexture(GL_TEXTURE_2D, 0); + SubmitTransformUniform(ProgramId::Color, shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Transform]); + glUniform2fv(shaders->program_color.uniform_locations[(size_t)Gfx::ProgramUniform::Translate], 1, &translation.x); + } + + glBindVertexArray(geometry->vao); + glDrawElements(GL_TRIANGLES, geometry->draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + + glBindVertexArray(0); + glUseProgram(0); + glBindTexture(GL_TEXTURE_2D, 0); + + Gfx::CheckGLError("RenderCompiledGeometry"); +} + +void RenderInterface_BackwardCompatible_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle handle) +{ + Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; + + glDeleteVertexArrays(1, &geometry->vao); + glDeleteBuffers(1, &geometry->vbo); + glDeleteBuffers(1, &geometry->ibo); + + delete geometry; +} + +void RenderInterface_BackwardCompatible_GL3::EnableScissorRegion(bool enable) +{ + ScissoringState new_state = ScissoringState::Disable; + + if (enable) + new_state = (transform_active ? ScissoringState::Stencil : ScissoringState::Scissor); + + if (new_state != scissoring_state) + { + // Disable old + if (scissoring_state == ScissoringState::Scissor) + glDisable(GL_SCISSOR_TEST); + else if (scissoring_state == ScissoringState::Stencil) + glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); + + // Enable new + if (new_state == ScissoringState::Scissor) + glEnable(GL_SCISSOR_TEST); + else if (new_state == ScissoringState::Stencil) + glStencilFunc(GL_EQUAL, 1, GLuint(-1)); + + scissoring_state = new_state; + } +} + +void RenderInterface_BackwardCompatible_GL3::SetScissorRegion(int x, int y, int width, int height) +{ + if (transform_active) + { + const float left = float(x); + const float right = float(x + width); + const float top = float(y); + const float bottom = float(y + height); + + Rml::Vertex vertices[4]; + vertices[0].position = {left, top}; + vertices[1].position = {right, top}; + vertices[2].position = {right, bottom}; + vertices[3].position = {left, bottom}; + + int indices[6] = {0, 2, 1, 0, 3, 2}; + + glClear(GL_STENCIL_BUFFER_BIT); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0)); + + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_EQUAL, 1, GLuint(-1)); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + else + { + glScissor(x, viewport_height - (y + height), width, height); + } +} + +// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file +#pragma pack(1) +struct TGAHeader { + char idLength; + char colourMapType; + char dataType; + short int colourMapOrigin; + short int colourMapLength; + char colourMapDepth; + short int xOrigin; + short int yOrigin; + short int width; + short int height; + char bitsPerPixel; + char imageDescriptor; +}; +// Restore packing +#pragma pack() + +bool RenderInterface_BackwardCompatible_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, + const Rml::String& source) +{ + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle file_handle = file_interface->Open(source); + if (!file_handle) + { + return false; + } + + file_interface->Seek(file_handle, 0, SEEK_END); + size_t buffer_size = file_interface->Tell(file_handle); + file_interface->Seek(file_handle, 0, SEEK_SET); + + if (buffer_size <= sizeof(TGAHeader)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image."); + file_interface->Close(file_handle); + return false; + } + + using Rml::byte; + byte* buffer = new byte[buffer_size]; + file_interface->Read(buffer, buffer_size, file_handle); + file_interface->Close(file_handle); + + TGAHeader header; + memcpy(&header, buffer, sizeof(TGAHeader)); + + int color_mode = header.bitsPerPixel / 8; + int image_size = header.width * header.height * 4; // We always make 32bit textures + + if (header.dataType != 2) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); + delete[] buffer; + return false; + } + + // Ensure we have at least 3 colors + if (color_mode < 3) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); + delete[] buffer; + return false; + } + + const byte* image_src = buffer + sizeof(TGAHeader); + byte* image_dest = new byte[image_size]; + + // Targa is BGR, swap to RGB and flip Y axis + for (long y = 0; y < header.height; y++) + { + long read_index = y * header.width * color_mode; + long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4; + for (long x = 0; x < header.width; x++) + { + image_dest[write_index] = image_src[read_index + 2]; + image_dest[write_index + 1] = image_src[read_index + 1]; + image_dest[write_index + 2] = image_src[read_index]; + if (color_mode == 4) + { + const int alpha = image_src[read_index + 3]; +#ifdef RMLUI_SRGB_PREMULTIPLIED_ALPHA + image_dest[write_index + 0] = (image_dest[write_index + 0] * alpha) / 255; + image_dest[write_index + 1] = (image_dest[write_index + 1] * alpha) / 255; + image_dest[write_index + 2] = (image_dest[write_index + 2] * alpha) / 255; +#endif + image_dest[write_index + 3] = (byte)alpha; + } + else + { + image_dest[write_index + 3] = 255; + } + + write_index += 4; + read_index += color_mode; + } + } + + texture_dimensions.x = header.width; + texture_dimensions.y = header.height; + + bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions); + + delete[] image_dest; + delete[] buffer; + + return success; +} + +bool RenderInterface_BackwardCompatible_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, + const Rml::Vector2i& source_dimensions) +{ + GLuint texture_id = 0; + glGenTextures(1, &texture_id); + if (texture_id == 0) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to generate texture."); + return false; + } + + glBindTexture(GL_TEXTURE_2D, texture_id); + + GLint internal_format = GL_RGBA8; + glTexImage2D(GL_TEXTURE_2D, 0, internal_format, source_dimensions.x, source_dimensions.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, source); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + texture_handle = (Rml::TextureHandle)texture_id; + + glBindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +void RenderInterface_BackwardCompatible_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) +{ + glDeleteTextures(1, (GLuint*)&texture_handle); +} + +void RenderInterface_BackwardCompatible_GL3::SetTransform(const Rml::Matrix4f* new_transform) +{ + transform_active = (new_transform != nullptr); + transform = projection * (new_transform ? *new_transform : Rml::Matrix4f::Identity()); + transform_dirty_state = ProgramId::All; +} + +void RenderInterface_BackwardCompatible_GL3::SubmitTransformUniform(ProgramId program_id, int uniform_location) +{ + if ((int)program_id & (int)transform_dirty_state) + { + glUniformMatrix4fv(uniform_location, 1, false, transform.data()); + transform_dirty_state = ProgramId((int)transform_dirty_state & ~(int)program_id); + } +} + +bool RmlGL3::Initialize(Rml::String* out_message) +{ +#if defined RMLUI_PLATFORM_EMSCRIPTEN + if (out_message) + *out_message = "Started Emscripten WebGL renderer."; +#elif !defined RMLUI_GL3_CUSTOM_LOADER + const int gl_version = gladLoaderLoadGL(); + if (gl_version == 0) + { + if (out_message) + *out_message = "Failed to initialize OpenGL context."; + return false; + } + + if (out_message) + *out_message = Rml::CreateString(128, "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/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h new file mode 100644 index 000000000..dae939ea6 --- /dev/null +++ b/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h @@ -0,0 +1,148 @@ +/* + * 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_BACKWARDCOMPATIBLE_GL3_H +#define RMLUI_BACKENDS_RENDERER_BACKWARDCOMPATIBLE_GL3_H + +#include +#include + +namespace Gfx { +struct ShadersData; +} + +/* + The GL3 renderer from RmlUi 5, only modified to derive from the compatibility interface. + + Implemented for testing and demonstration purposes, not recommended for production use. +*/ + +class RenderInterface_BackwardCompatible_GL3 : public Rml::RenderInterfaceCompatibility { +public: + RenderInterface_BackwardCompatible_GL3(); + ~RenderInterface_BackwardCompatible_GL3(); + + // Returns true if the renderer was successfully constructed. + explicit operator bool() const { return static_cast(shaders); } + + // The viewport should be updated whenever the window size changes. + void SetViewport(int viewport_width, int viewport_height); + + // Sets up OpenGL states for taking rendering commands from RmlUi. + void BeginFrame(); + void EndFrame(); + + // Optional, can be used to clear the framebuffer. + void Clear(); + + // -- Inherited from Rml::RenderInterface -- + + void RenderGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rml::TextureHandle texture, + const Rml::Vector2f& translation) override; + + Rml::CompiledGeometryHandle CompileGeometry(Rml::Vertex* vertices, int num_vertices, int* indices, int num_indices, + Rml::TextureHandle texture) override; + void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) override; + void ReleaseCompiledGeometry(Rml::CompiledGeometryHandle geometry) override; + + void EnableScissorRegion(bool enable) override; + void SetScissorRegion(int x, int y, int width, int height) override; + + bool LoadTexture(Rml::TextureHandle& texture_handle, Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override; + void ReleaseTexture(Rml::TextureHandle texture_handle) override; + + void SetTransform(const Rml::Matrix4f* transform) override; + + // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. + static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); + +private: + enum class ProgramId { None, Texture = 1, Color = 2, All = (Texture | Color) }; + void SubmitTransformUniform(ProgramId program_id, int uniform_location); + + Rml::Matrix4f transform, projection; + ProgramId transform_dirty_state = ProgramId::All; + bool transform_active = false; + + enum class ScissoringState { Disable, Scissor, Stencil }; + ScissoringState scissoring_state = ScissoringState::Disable; + + int viewport_width = 0; + int viewport_height = 0; + + Rml::UniquePtr shaders; + + struct GLStateBackup { + bool enable_cull_face; + bool enable_blend; + bool enable_stencil_test; + bool enable_scissor_test; + + int viewport[4]; + int scissor[4]; + + int stencil_clear_value; + float color_clear_value[4]; + + int blend_equation_rgb; + int blend_equation_alpha; + int blend_src_rgb; + int blend_dst_rgb; + int blend_src_alpha; + int blend_dst_alpha; + + struct Stencil { + int func; + int ref; + int value_mask; + int writemask; + int fail; + int pass_depth_fail; + int pass_depth_pass; + }; + Stencil stencil_front; + Stencil stencil_back; + }; + GLStateBackup glstate_backup = {}; +}; + +/** + Helper functions for the OpenGL 3 renderer. + */ +namespace RmlGL3 { + +// Loads OpenGL functions. Optionally, the out message describes the loaded GL version or an error message on failure. +bool Initialize(Rml::String* out_message = nullptr); + +// Unloads OpenGL functions. +void Shutdown(); + +} // namespace RmlGL3 + +#endif diff --git a/CMake/BackendFileList.cmake b/CMake/BackendFileList.cmake index 50b45bb9a..13afccaaf 100644 --- a/CMake/BackendFileList.cmake +++ b/CMake/BackendFileList.cmake @@ -119,3 +119,24 @@ set(GLFW_VK_HDR_FILES ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_VK.h ) + +set(BackwardCompatible_GLFW_GL2_SRC_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL2.cpp +) +set(BackwardCompatible_GLFW_GL2_HDR_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL2.h +) + +set(BackwardCompatible_GLFW_GL3_SRC_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Backend_BackwardCompatible_GLFW_GL3.cpp +) +set(BackwardCompatible_GLFW_GL3_HDR_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_GLFW.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_BackwardCompatible/RmlUi_Renderer_BackwardCompatible_GL3.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Include_GL3.h +) \ No newline at end of file diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 5334c0d24..8289273c7 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -199,6 +199,7 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/PropertySpecification.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Rectangle.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterface.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderInterfaceCompatibility.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/RenderManager.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScriptInterface.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ScrollTypes.h @@ -376,6 +377,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterface.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/RenderInterfaceCompatibility.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderManager.cpp ${PROJECT_SOURCE_DIR}/Source/Core/RenderManagerAccess.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ScrollController.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c5b11d1a..9e48586b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,7 +168,7 @@ endif() option(BUILD_SAMPLES "Build samples" OFF) set(SAMPLES_BACKEND "auto" CACHE STRING "Backend platform and renderer used for the samples.") -set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK) +set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK BackwardCompatible_GLFW_GL2 BackwardCompatible_GLFW_GL3) if(SAMPLES_BACKEND STREQUAL "auto") if(EMSCRIPTEN) @@ -765,7 +765,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING) target_link_libraries(shell PRIVATE ${SFML_LIBRARIES}) endif() - if(SAMPLES_BACKEND MATCHES "^GLFW") + if(SAMPLES_BACKEND MATCHES "GLFW") message("-- Looking for GLFW3 library for samples backend.") find_package(glfw3 3.3 CONFIG REQUIRED) target_link_libraries(shell PRIVATE glfw) diff --git a/Include/RmlUi/Core/RenderInterfaceCompatibility.h b/Include/RmlUi/Core/RenderInterfaceCompatibility.h new file mode 100644 index 000000000..0f77262f5 --- /dev/null +++ b/Include/RmlUi/Core/RenderInterfaceCompatibility.h @@ -0,0 +1,121 @@ +/* + * 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_CORE_RENDERINTERFACECOMPATIBILITY_H +#define RMLUI_CORE_RENDERINTERFACECOMPATIBILITY_H + +#include "RenderInterface.h" + +namespace Rml { + +class RenderInterfaceAdapter; + +/** + Provides a backward-compatible adapter for render interfaces written for RmlUi 5 and lower. The compatibility adapter + should be used as follows. + + 1. In your legacy RenderInterface implementation, derive from Rml::RenderInterfaceCompatibility instead of + Rml::RenderInterface. + + #include + class MyRenderInterface : public Rml::RenderInterfaceCompatibility { ... }; + + 2. Use the adapted interface when setting the RmlUi render interface. + + Rml::SetRenderInterface(my_render_interface.GetAdaptedInterface()); + + New rendering features are not supported when using the compatibility adapter. +*/ + +class RMLUICORE_API RenderInterfaceCompatibility : public NonCopyMoveable { +public: + RenderInterfaceCompatibility(); + virtual ~RenderInterfaceCompatibility(); + + virtual void RenderGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture, + const Vector2f& translation) = 0; + + virtual CompiledGeometryHandle CompileGeometry(Vertex* vertices, int num_vertices, int* indices, int num_indices, TextureHandle texture); + virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation); + virtual void ReleaseCompiledGeometry(CompiledGeometryHandle geometry); + + virtual void EnableScissorRegion(bool enable) = 0; + virtual void SetScissorRegion(int x, int y, int width, int height) = 0; + + virtual bool LoadTexture(TextureHandle& texture_handle, Vector2i& texture_dimensions, const String& source); + virtual bool GenerateTexture(TextureHandle& texture_handle, const byte* source, const Vector2i& source_dimensions); + virtual void ReleaseTexture(TextureHandle texture); + + virtual void SetTransform(const Matrix4f* transform); + + RenderInterface* GetAdaptedInterface(); + +private: + UniquePtr adapter; +}; + +/* + The render interface adapter takes calls from the render interface, makes any necessary conversions, and passes the + calls on to the legacy render interface. +*/ +class RMLUICORE_API RenderInterfaceAdapter : public RenderInterface { +public: + CompiledGeometryHandle CompileGeometry(Span vertices, Span indices) override; + void RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture) override; + void ReleaseGeometry(CompiledGeometryHandle handle) override; + + void EnableScissorRegion(bool enable) override; + void SetScissorRegion(Rectanglei region) override; + + TextureHandle LoadTexture(Vector2i& texture_dimensions, const String& source) override; + TextureHandle GenerateTexture(Span source_data, Vector2i source_dimensions) override; + void ReleaseTexture(TextureHandle texture_handle) override; + + void EnableClipMask(bool enable) override; + void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation) override; + + void SetTransform(const Matrix4f* transform) override; + +private: + using LegacyCompiledGeometryHandle = CompiledGeometryHandle; + + struct AdaptedGeometry { + Vector vertices; + Vector indices; + SmallUnorderedMap textures; + }; + + RenderInterfaceAdapter(RenderInterfaceCompatibility& legacy); + + RenderInterfaceCompatibility& legacy; + + friend Rml::RenderInterfaceCompatibility::RenderInterfaceCompatibility(); +}; + +} // namespace Rml +#endif diff --git a/Source/Core/RenderInterfaceCompatibility.cpp b/Source/Core/RenderInterfaceCompatibility.cpp new file mode 100644 index 000000000..3db7b2149 --- /dev/null +++ b/Source/Core/RenderInterfaceCompatibility.cpp @@ -0,0 +1,218 @@ +/* + * 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 "../../Include/RmlUi/Core/RenderInterfaceCompatibility.h" +#include "../../Include/RmlUi/Core/Math.h" + +namespace Rml { + +static void UnPremultiplyAlpha(const byte* source, byte* destination) +{ + const byte alpha = source[3]; + destination[0] = (alpha > 0 ? (source[0] * 255) / alpha : 255); + destination[1] = (alpha > 0 ? (source[1] * 255) / alpha : 255); + destination[2] = (alpha > 0 ? (source[2] * 255) / alpha : 255); + destination[3] = alpha; +} + +RenderInterfaceCompatibility::RenderInterfaceCompatibility() : adapter(new RenderInterfaceAdapter(*this)) {} + +RenderInterfaceCompatibility::~RenderInterfaceCompatibility() {} + +CompiledGeometryHandle RenderInterfaceCompatibility::CompileGeometry(Vertex* /*vertices*/, int /*num_vertices*/, int* /*indices*/, + int /*num_indices*/, TextureHandle /*texture*/) +{ + return 0; +} + +void RenderInterfaceCompatibility::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/) {} + +void RenderInterfaceCompatibility::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} + +bool RenderInterfaceCompatibility::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/) +{ + return false; +} + +bool RenderInterfaceCompatibility::GenerateTexture(TextureHandle& /*texture_handle*/, const byte* /*source*/, const Vector2i& /*source_dimensions*/) +{ + return false; +} + +void RenderInterfaceCompatibility::ReleaseTexture(TextureHandle /*texture*/) {} + +void RenderInterfaceCompatibility::SetTransform(const Matrix4f* /*transform*/) {} + +RenderInterface* RenderInterfaceCompatibility::GetAdaptedInterface() +{ + return static_cast(adapter.get()); +} + +RenderInterfaceAdapter::RenderInterfaceAdapter(RenderInterfaceCompatibility& legacy) : legacy(legacy) {} + +CompiledGeometryHandle RenderInterfaceAdapter::CompileGeometry(Span vertices, Span indices) +{ + // Previously, vertex colors were given in unpremultipled alpha, while now they are given in premultiplied alpha. If + // not corrected for, transparent colors may look darker than they should with the legacy renderer. Thus, here we + // make such a conversion. + // + // When upgrading your renderer, it is strongly recommended to convert your pipeline to use premultiplied alpha, + // both to avoid copying vertex data like here and to achieve correct blending results. + // + // Note that, the vertices and indices are now guaranteed to be valid and immutable until the call to + // ReleaseGeometry. Thus, it is possible to avoid copying the data even if you need access to it during the render + // call. However, (1) due to the need to modify the vertices, we need to make a copy of them here. And (2), due to a + // limitation in the legacy render interface, vertices and indices were previously submitted as pointers to mutable + // vertices and indices. They were never intended to be mutable, but to avoid a const_cast we need to copy both of + // them for that reason too. + + Vector vertices_unpremultiplied(vertices.begin(), vertices.end()); + for (size_t i = 0; i < vertices.size(); i++) + { + UnPremultiplyAlpha(vertices[i].colour, vertices_unpremultiplied[i].colour); + } + + Vector indices_copy(indices.begin(), indices.end()); + + AdaptedGeometry* data = new AdaptedGeometry{std::move(vertices_unpremultiplied), std::move(indices_copy), {}}; + return reinterpret_cast(data); +} + +void RenderInterfaceAdapter::RenderGeometry(CompiledGeometryHandle handle, Vector2f translation, TextureHandle texture) +{ + AdaptedGeometry* geometry = reinterpret_cast(handle); + + // Textures were previously stored with the compiled geometry, but is now instead submitted during rendering. + LegacyCompiledGeometryHandle& legacy_geometry = geometry->textures[texture]; + if (!legacy_geometry) + { + legacy_geometry = legacy.CompileGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(), + (int)geometry->indices.size(), texture); + } + + // If the legacy renderer supports compiling, use that, otherwise render the geometry in immediate mode. + if (legacy_geometry) + { + legacy.RenderCompiledGeometry(legacy_geometry, translation); + } + else + { + legacy.RenderGeometry(geometry->vertices.data(), (int)geometry->vertices.size(), geometry->indices.data(), (int)geometry->indices.size(), + texture, translation); + } +} + +void RenderInterfaceAdapter::ReleaseGeometry(CompiledGeometryHandle handle) +{ + AdaptedGeometry* geometry = reinterpret_cast(handle); + for (auto& pair : geometry->textures) + legacy.ReleaseCompiledGeometry(pair.second); + + delete reinterpret_cast(geometry); +} + +void RenderInterfaceAdapter::EnableScissorRegion(bool enable) +{ + legacy.EnableScissorRegion(enable); +} + +void RenderInterfaceAdapter::SetScissorRegion(Rectanglei region) +{ + legacy.SetScissorRegion(region.Left(), region.Top(), region.Width(), region.Height()); +} + +void RenderInterfaceAdapter::EnableClipMask(bool enable) +{ + legacy.EnableScissorRegion(enable); +} + +void RenderInterfaceAdapter::RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle handle, Vector2f translation) +{ + switch (operation) + { + case ClipMaskOperation::Set: + case ClipMaskOperation::Intersect: + // Intersect is considered like Set. This typically occurs in nested clipping situations, which never worked + // correctly in legacy. + break; + case ClipMaskOperation::SetInverse: + // Using features not supported in legacy, bail out. + return; + } + + // New features can render more complex clip masks, while legacy only supported rectangle scissoring. Find the + // geometry's rectangular coverage. + const AdaptedGeometry* geometry = reinterpret_cast(handle); + + Rectanglef rectangle = Rectanglef::FromPosition(geometry->vertices[0].position); + for (const Vertex& vertex : geometry->vertices) + rectangle.Join(vertex.position); + rectangle.Translate(translation); + + const Rectanglei scissor = Rectanglei(rectangle); + legacy.SetScissorRegion(scissor.Left(), scissor.Top(), scissor.Width(), scissor.Height()); +} + +TextureHandle RenderInterfaceAdapter::LoadTexture(Vector2i& texture_dimensions, const String& source) +{ + TextureHandle texture_handle = {}; + if (!legacy.LoadTexture(texture_handle, texture_dimensions, source)) + texture_handle = {}; + return texture_handle; +} + +TextureHandle RenderInterfaceAdapter::GenerateTexture(Span source_data, Vector2i source_dimensions) +{ + // Previously, textures were given in unpremultiplied alpha format. Since RmlUi 6, they are given in premultiplied + // alpha. For compatibility, convert the texture to unpremultiplied alpha which is expected by legacy render + // interfaces. + const int num_bytes = source_dimensions.x * source_dimensions.y * 4; + std::unique_ptr unpremultiplied_copy(new byte[num_bytes]); + + for (int i = 0; i < num_bytes; i += 4) + { + UnPremultiplyAlpha(&source_data[i], unpremultiplied_copy.get() + i); + } + + TextureHandle texture_handle = {}; + if (!legacy.GenerateTexture(texture_handle, unpremultiplied_copy.get(), source_dimensions)) + texture_handle = {}; + return texture_handle; +} + +void RenderInterfaceAdapter::ReleaseTexture(TextureHandle texture_handle) +{ + legacy.ReleaseTexture(texture_handle); +} + +void RenderInterfaceAdapter::SetTransform(const Matrix4f* transform) +{ + legacy.SetTransform(transform); +} + +} // namespace Rml From 94fda352761434236fb4c1384f28d9f2dda57610 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 26 Dec 2023 22:56:13 +0100 Subject: [PATCH 61/80] GL3 renderer: Fix shader compilation on GLES, add GLSL line numbers --- Backends/RmlUi_Renderer_GL3.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index f10455fd4..6e164be64 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -66,7 +66,8 @@ static constexpr int NUM_MSAA_SAMPLES = 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" +#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; @@ -113,6 +114,7 @@ void main() { )"; 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 @@ -143,7 +145,7 @@ vec4 mix_stop_colors(float t) { } void main() { - float t = 0; + float t = 0.0; if (_func == LINEAR || _func == REPEATING_LINEAR) { From cbeea4380446c058f651d93d803e7d9983e3d565 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 30 Dec 2023 22:55:59 +0100 Subject: [PATCH 62/80] Remove unused Log procedures for initialise and shutdown --- Include/RmlUi/Core/Log.h | 7 ------- Source/Core/Core.cpp | 5 ----- Source/Core/Log.cpp | 7 ------- 3 files changed, 19 deletions(-) diff --git a/Include/RmlUi/Core/Log.h b/Include/RmlUi/Core/Log.h index d1d0cd2b8..a0299775d 100644 --- a/Include/RmlUi/Core/Log.h +++ b/Include/RmlUi/Core/Log.h @@ -52,13 +52,6 @@ class RMLUICORE_API Log { LT_MAX, }; -public: - /// Initialises the logging interface. - /// @return True if the logging interface was successful, false if not. - static bool Initialise(); - /// Shutdown the log interface. - static void Shutdown(); - /// Log the specified message via the registered log interface /// @param[in] type Type of message. /// @param[in] format The message, with sprintf-style parameters. diff --git a/Source/Core/Core.cpp b/Source/Core/Core.cpp index 7836b5ada..a9de844fa 100644 --- a/Source/Core/Core.cpp +++ b/Source/Core/Core.cpp @@ -94,8 +94,6 @@ bool Initialise() { RMLUI_ASSERTMSG(!initialised, "Rml::Initialise() called, but RmlUi is already initialised!"); - Log::Initialise(); - // Check for valid interfaces, or install default interfaces as appropriate. if (!system_interface) { @@ -184,9 +182,6 @@ void Shutdown() default_file_interface.reset(); - Log::Shutdown(); - - // Release any memory pools ReleaseMemoryPools(); } diff --git a/Source/Core/Log.cpp b/Source/Core/Log.cpp index 937c2bf11..f943701d4 100644 --- a/Source/Core/Log.cpp +++ b/Source/Core/Log.cpp @@ -35,13 +35,6 @@ namespace Rml { -bool Log::Initialise() -{ - return true; -} - -void Log::Shutdown() {} - void Log::Message(Log::Type type, const char* fmt, ...) { const int buffer_size = 1024; From ac8e1f2752e835efdafc097f28b8d3979a328e71 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 30 Dec 2023 23:28:14 +0100 Subject: [PATCH 63/80] Use default log output also when there is no system interface installed This ensures that log messages are submitted to the same stream output before and after installing the default provided system interface. In particular, the output from MSVC is given in its debug output. --- CMake/FileList.cmake | 2 + Source/Core/Log.cpp | 5 ++- Source/Core/LogDefault.cpp | 71 +++++++++++++++++++++++++++++++++ Source/Core/LogDefault.h | 48 ++++++++++++++++++++++ Source/Core/SystemInterface.cpp | 38 ++---------------- 5 files changed, 127 insertions(+), 37 deletions(-) create mode 100644 Source/Core/LogDefault.cpp create mode 100644 Source/Core/LogDefault.h diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index 8289273c7..cfb5c5c1a 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -76,6 +76,7 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Layout/ReplacedFormattingContext.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.h + ${PROJECT_SOURCE_DIR}/Source/Core/LogDefault.h ${PROJECT_SOURCE_DIR}/Source/Core/Memory.h ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.h ${PROJECT_SOURCE_DIR}/Source/Core/Pool.h @@ -352,6 +353,7 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingContext.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Layout/TableFormattingDetails.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Log.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/LogDefault.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Math.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Memory.cpp ${PROJECT_SOURCE_DIR}/Source/Core/MeshUtilities.cpp diff --git a/Source/Core/Log.cpp b/Source/Core/Log.cpp index f943701d4..fa8e5d491 100644 --- a/Source/Core/Log.cpp +++ b/Source/Core/Log.cpp @@ -30,6 +30,7 @@ #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/StringUtilities.h" #include "../../Include/RmlUi/Core/SystemInterface.h" +#include "LogDefault.h" #include #include @@ -54,7 +55,7 @@ void Log::Message(Log::Type type, const char* fmt, ...) if (SystemInterface* system_interface = GetSystemInterface()) system_interface->LogMessage(type, buffer); else - puts(buffer); + LogDefault::LogMessage(type, buffer); } void Log::ParseError(const String& filename, int line_number, const char* fmt, ...) @@ -87,7 +88,7 @@ bool Assert(const char* msg, const char* file, int line) if (SystemInterface* system_interface = GetSystemInterface()) result = system_interface->LogMessage(Log::LT_ASSERT, message); else - puts(message.c_str()); + result = LogDefault::LogMessage(Log::LT_ASSERT, message); return result; } diff --git a/Source/Core/LogDefault.cpp b/Source/Core/LogDefault.cpp new file mode 100644 index 000000000..3da501712 --- /dev/null +++ b/Source/Core/LogDefault.cpp @@ -0,0 +1,71 @@ +/* + * 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 "LogDefault.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +#ifdef RMLUI_PLATFORM_WIN32 + #include +#else + #include +#endif + +namespace Rml { + +#ifdef RMLUI_PLATFORM_WIN32 +bool LogDefault::LogMessage(Log::Type type, const String& message) +{ + #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) + if (type == Log::LT_ASSERT) + { + String message_user = CreateString(1024, "%s\nWould you like to interrupt execution?", message.c_str()); + + // Return TRUE if the user presses NO (continue execution) + return (IDNO == MessageBoxA(nullptr, message_user.c_str(), "Assertion Failure", MB_YESNO | MB_ICONSTOP | MB_DEFBUTTON2 | MB_TASKMODAL)); + } + else + #endif + { + OutputDebugStringA(message.c_str()); + OutputDebugStringA("\r\n"); + } + return true; +} +#else +bool LogDefault::LogMessage(Log::Type /*type*/, const String& message) +{ + #ifdef RMLUI_PLATFORM_EMSCRIPTEN + puts(message.c_str()); + #else + fprintf(stderr, "%s\n", message.c_str()); + #endif + return true; +} +#endif + +} // namespace Rml diff --git a/Source/Core/LogDefault.h b/Source/Core/LogDefault.h new file mode 100644 index 000000000..7d3f962e8 --- /dev/null +++ b/Source/Core/LogDefault.h @@ -0,0 +1,48 @@ +/* + * 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_CORE_LOGDEFAULT_H +#define RMLUI_CORE_LOGDEFAULT_H + +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +/** + Provides a platform-dependent default implementation for message logging. + */ + +class LogDefault { +public: + static bool LogMessage(Log::Type type, const String& message); +}; + +} // namespace Rml + +#endif diff --git a/Source/Core/SystemInterface.cpp b/Source/Core/SystemInterface.cpp index 72456adb8..dd8d2f910 100644 --- a/Source/Core/SystemInterface.cpp +++ b/Source/Core/SystemInterface.cpp @@ -30,12 +30,7 @@ #include "../../Include/RmlUi/Core/Log.h" #include "../../Include/RmlUi/Core/StringUtilities.h" #include "../../Include/RmlUi/Core/URL.h" - -#ifdef RMLUI_PLATFORM_WIN32 - #include -#else - #include -#endif +#include "LogDefault.h" namespace Rml { @@ -45,37 +40,10 @@ SystemInterface::SystemInterface() {} SystemInterface::~SystemInterface() {} -#ifdef RMLUI_PLATFORM_WIN32 -bool SystemInterface::LogMessage(Log::Type logtype, const String& message) -{ - // By default we just send a platform message - #if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) - if (logtype == Log::LT_ASSERT) - { - String message_user = CreateString(1024, "%s\nWould you like to interrupt execution?", message.c_str()); - - // Return TRUE if the user presses NO (continue execution) - return (IDNO == MessageBoxA(nullptr, message_user.c_str(), "Assertion Failure", MB_YESNO | MB_ICONSTOP | MB_DEFBUTTON2 | MB_TASKMODAL)); - } - else - #endif - { - OutputDebugStringA(message.c_str()); - OutputDebugStringA("\r\n"); - } - return true; -} -#else -bool SystemInterface::LogMessage(Log::Type /*logtype*/, const String& message) +bool SystemInterface::LogMessage(Log::Type type, const String& message) { - #ifdef RMLUI_PLATFORM_EMSCRIPTEN - puts(message.c_str()); - #else - fprintf(stderr, "%s\n", message.c_str()); - #endif - return true; + return LogDefault::LogMessage(type, message); } -#endif void SystemInterface::SetMouseCursor(const String& /*cursor_name*/) {} From 5a27590067dca44fd149325b21aff39d8c1136d3 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 31 Dec 2023 00:36:35 +0100 Subject: [PATCH 64/80] Redirect all print-like calls to our built-in logger --- Backends/RmlUi_Backend_GLFW_VK.cpp | 9 +++++---- Backends/RmlUi_Backend_SDL_GL2.cpp | 5 +++-- Backends/RmlUi_Backend_SDL_GL3.cpp | 7 ++++--- Backends/RmlUi_Backend_SDL_VK.cpp | 9 +++++---- Backends/RmlUi_Backend_Win32_GL2.cpp | 20 ++++++++------------ Backends/RmlUi_Backend_Win32_VK.cpp | 14 +++++--------- Backends/RmlUi_Backend_X11_GL2.cpp | 7 ++++--- Samples/shell/src/PlatformExtensions.cpp | 5 +++-- 8 files changed, 37 insertions(+), 39 deletions(-) diff --git a/Backends/RmlUi_Backend_GLFW_VK.cpp b/Backends/RmlUi_Backend_GLFW_VK.cpp index 5adc83ca0..8294a4af8 100644 --- a/Backends/RmlUi_Backend_GLFW_VK.cpp +++ b/Backends/RmlUi_Backend_GLFW_VK.cpp @@ -26,14 +26,15 @@ * */ -#include "RmlUi/Config/Config.h" #include "RmlUi_Backend.h" #include "RmlUi_Renderer_VK.h" // This space is intentional to prevent autoformat from reordering RmlUi_Renderer_VK behind RmlUi_Platform_GLFW #include "RmlUi_Platform_GLFW.h" +#include #include #include #include +#include #include #include #include @@ -84,7 +85,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al if (!window) { - fprintf(stderr, "[GLFW] error can't create window!"); + Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW failed to create window"); return false; } @@ -93,7 +94,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al uint32_t count; const char** extensions = glfwGetRequiredInstanceExtensions(&count); - RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query glfw vulkan extensions"); + RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query GLFW Vulkan extensions"); if (!data->render_interface.Initialize(Rml::Vector(extensions, extensions + count), [](VkInstance instance, VkSurfaceKHR* out_surface) { return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS; @@ -101,7 +102,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al })) { data.reset(); - fprintf(stderr, "Could not initialize Vulkan render interface."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface"); return false; } diff --git a/Backends/RmlUi_Backend_SDL_GL2.cpp b/Backends/RmlUi_Backend_SDL_GL2.cpp index 15277089d..690285bd7 100644 --- a/Backends/RmlUi_Backend_SDL_GL2.cpp +++ b/Backends/RmlUi_Backend_SDL_GL2.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -195,7 +196,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags); if (!window) { - fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError()); + Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError()); return false; } } @@ -220,7 +221,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al GLenum err = glewInit(); if (err != GLEW_OK) { - fprintf(stderr, "GLEW error: %s\n", glewGetErrorString(err)); + Rml::Log::Message(Rml::Log::LT_ERROR, "GLEW error: %s", glewGetErrorString(err)); return false; } diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index dfc6d9f90..a5eec38bd 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -154,7 +155,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags); if (!window) { - fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError()); + Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError()); return false; } @@ -164,7 +165,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al if (!RmlGL3::Initialize()) { - fprintf(stderr, "Could not initialize OpenGL"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL renderer"); return false; } @@ -173,7 +174,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al if (!data->render_interface) { data.reset(); - fprintf(stderr, "Could not initialize OpenGL3 render interface."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize OpenGL3 render interface"); return false; } diff --git a/Backends/RmlUi_Backend_SDL_VK.cpp b/Backends/RmlUi_Backend_SDL_VK.cpp index d90ae4d39..1bc3375b5 100644 --- a/Backends/RmlUi_Backend_SDL_VK.cpp +++ b/Backends/RmlUi_Backend_SDL_VK.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al SDL_Window* window = SDL_CreateWindow(window_name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, window_flags); if (!window) { - fprintf(stderr, "SDL error on create window: %s\n", SDL_GetError()); + Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError()); return false; } @@ -82,14 +83,14 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al if (!SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr)) { data.reset(); - fprintf(stderr, "Could not get required vulkan extensions"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions"); return false; } extensions.resize(count); if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data())) { data.reset(); - fprintf(stderr, "Could not get required vulkan extensions"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get required vulkan extensions"); return false; } } @@ -98,7 +99,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al [](VkInstance instance, VkSurfaceKHR* out_surface) { return (bool)SDL_Vulkan_CreateSurface(data->window, instance, out_surface); })) { data.reset(); - fprintf(stderr, "Could not initialize Vulkan render interface."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface"); return false; } diff --git a/Backends/RmlUi_Backend_Win32_GL2.cpp b/Backends/RmlUi_Backend_Win32_GL2.cpp index dae4cd697..f08bd3a3e 100644 --- a/Backends/RmlUi_Backend_Win32_GL2.cpp +++ b/Backends/RmlUi_Backend_Win32_GL2.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include /** @@ -91,11 +92,6 @@ static float GetDensityIndependentPixelRatio(HWND window_handle) return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI); } -static void DisplayError(HWND window_handle, const Rml::String& msg) -{ - MessageBoxW(window_handle, RmlWin32::ConvertToUTF16(msg).c_str(), L"Backend Error", MB_OK); -} - // Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings. static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize); // Attach the OpenGL context. @@ -349,7 +345,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name if (!RegisterClassW(&window_class)) { - DisplayError(NULL, "Could not register window class."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class"); return nullptr; } @@ -361,7 +357,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name if (!window_handle) { - DisplayError(NULL, "Could not create window."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window"); return nullptr; } @@ -398,7 +394,7 @@ static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& o if (!device_context) { - DisplayError(window_handle, "Could not get device context."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to get device context"); return false; } @@ -418,27 +414,27 @@ static bool AttachToNative(HWND window_handle, HDC& out_device_context, HGLRC& o int pixel_format = ChoosePixelFormat(device_context, &pixel_format_descriptor); if (!pixel_format) { - DisplayError(window_handle, "Could not choose 32-bit pixel format."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to choose 32-bit pixel format"); return false; } if (!SetPixelFormat(device_context, pixel_format, &pixel_format_descriptor)) { - DisplayError(window_handle, "Could not set pixel format."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to set pixel format"); return false; } HGLRC render_context = wglCreateContext(device_context); if (!render_context) { - DisplayError(window_handle, "Could not create OpenGL rendering context."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create OpenGL rendering context"); return false; } // Activate the rendering context. if (!wglMakeCurrent(device_context, render_context)) { - DisplayError(window_handle, "Unable to make rendering context current."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to make rendering context current"); return false; } diff --git a/Backends/RmlUi_Backend_Win32_VK.cpp b/Backends/RmlUi_Backend_Win32_VK.cpp index 65d9badfc..c3cbcb46d 100644 --- a/Backends/RmlUi_Backend_Win32_VK.cpp +++ b/Backends/RmlUi_Backend_Win32_VK.cpp @@ -26,14 +26,15 @@ * */ -#include "RmlUi/Config/Config.h" #include "RmlUi_Backend.h" #include "RmlUi_Include_Windows.h" #include "RmlUi_Platform_Win32.h" #include "RmlUi_Renderer_VK.h" +#include #include #include #include +#include #include /** @@ -92,11 +93,6 @@ static float GetDensityIndependentPixelRatio(HWND window_handle) return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI); } -static void DisplayError(HWND window_handle, const Rml::String& msg) -{ - MessageBoxW(window_handle, RmlWin32::ConvertToUTF16(msg).c_str(), L"Backend Error", MB_OK); -} - // Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings. static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize); // Create the Win32 Vulkan surface. @@ -150,7 +146,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); if (!data->render_interface.Initialize(std::move(extensions), CreateVulkanSurface)) { - DisplayError(window_handle, "Could not initialize Vulkan render interface."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to initialize Vulkan render interface"); ::CloseWindow(window_handle); data.reset(); return false; @@ -357,7 +353,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name if (!RegisterClassW(&window_class)) { - DisplayError(NULL, "Could not register window class."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to register window class"); return nullptr; } @@ -369,7 +365,7 @@ static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name if (!window_handle) { - DisplayError(NULL, "Could not create window."); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to create window"); return nullptr; } diff --git a/Backends/RmlUi_Backend_X11_GL2.cpp b/Backends/RmlUi_Backend_X11_GL2.cpp index bc4ebe72a..0a0bb2558 100644 --- a/Backends/RmlUi_Backend_X11_GL2.cpp +++ b/Backends/RmlUi_Backend_X11_GL2.cpp @@ -26,12 +26,13 @@ * */ -#include "RmlUi/Core/Debug.h" #include "RmlUi_Backend.h" #include "RmlUi_Include_Xlib.h" #include "RmlUi_Platform_X11.h" #include "RmlUi_Renderer_GL2.h" #include +#include +#include #include #include #include @@ -61,7 +62,7 @@ static bool AttachToNative(GLXContext& out_gl_context, Display* display, Window return false; if (!glXIsDirect(display, gl_context)) - puts("OpenGL context does not support direct rendering; performance is likely to be poor."); + Rml::Log::Message(Rml::Log::LT_INFO, "OpenGL context does not support direct rendering; performance is likely to be poor."); out_gl_context = gl_context; @@ -142,7 +143,7 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al XSizeHints* win_size_hints = XAllocSizeHints(); // Allocate a size hint structure if (!win_size_hints) { - fprintf(stderr, "XAllocSizeHints - out of memory\n"); + Rml::Log::Message(Rml::Log::LT_ERROR, "XAllocSizeHints - out of memory"); } else { diff --git a/Samples/shell/src/PlatformExtensions.cpp b/Samples/shell/src/PlatformExtensions.cpp index 320ba1fc1..3333b9e25 100644 --- a/Samples/shell/src/PlatformExtensions.cpp +++ b/Samples/shell/src/PlatformExtensions.cpp @@ -27,6 +27,7 @@ */ #include "../include/PlatformExtensions.h" +#include #include #if defined RMLUI_PLATFORM_WIN32 @@ -118,7 +119,7 @@ Rml::String PlatformExtensions::FindSamplesRoot() ssize_t len = readlink("/proc/self/exe", executable_file_name, PATH_MAX); if (len == -1) { - printf("Unable to determine the executable path!\n"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to determine the executable path"); executable_file_name[0] = 0; } else @@ -153,7 +154,7 @@ Rml::String PlatformExtensions::FindSamplesRoot() } } - printf("Unable to find the path to the samples root!\n"); + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to find the path to the samples root"); return Rml::String(); From 6e1d4874f121b3be82a2dbf085bc60aa7624d32b Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 3 Feb 2024 21:13:55 +0100 Subject: [PATCH 65/80] Fix some situations where units were not shown in properties --- Source/Core/Property.cpp | 2 +- Source/Core/TransformUtilities.cpp | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Source/Core/Property.cpp b/Source/Core/Property.cpp index 0e4963327..15926d443 100644 --- a/Source/Core/Property.cpp +++ b/Source/Core/Property.cpp @@ -40,7 +40,7 @@ Property::Property() : unit(Unit::UNKNOWN), specificity(-1) String Property::ToString() const { if (!definition) - return value.Get(); + return value.Get() + Rml::ToString(unit); String string; definition->GetValue(string, *this); diff --git a/Source/Core/TransformUtilities.cpp b/Source/Core/TransformUtilities.cpp index f4c524134..7ab96c4ea 100644 --- a/Source/Core/TransformUtilities.cpp +++ b/Source/Core/TransformUtilities.cpp @@ -94,14 +94,6 @@ static inline float ResolveDepth(NumericValue value, Element& e) noexcept return e.ResolveNumericValue(value, Math::Max(size.x, size.y)); } -static inline String ToString(NumericValue value) noexcept -{ - Property prop; - prop.value = Variant(value.number); - prop.unit = value.unit; - return prop.ToString(); -} - struct SetIdentityVisitor { template void operator()(Transforms::ResolvedPrimitive& p) @@ -609,6 +601,11 @@ static String ToString(const Transforms::ResolvedPrimitive& p, const String& return result; } +static inline String ToString(NumericValue value) noexcept +{ + return ToString(value.number) + ToString(value.unit); +} + template static inline String ToString(const Transforms::UnresolvedPrimitive& p) noexcept { From ff651070d1f51878c4c5f608debfd60b51d29549 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 3 Feb 2024 22:20:41 +0100 Subject: [PATCH 66/80] Fix string conversion of ColorStopList and BoxShadowList, fixes #590 --- Include/RmlUi/Core/Colour.h | 17 +++++++- Source/Core/TypeConverter.cpp | 7 ++-- Tests/Source/UnitTests/Properties.cpp | 60 +++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/Include/RmlUi/Core/Colour.h b/Include/RmlUi/Core/Colour.h index 7c7abbbf8..27a3cb8ca 100644 --- a/Include/RmlUi/Core/Colour.h +++ b/Include/RmlUi/Core/Colour.h @@ -106,7 +106,7 @@ class Colour { typename = typename std::enable_if_t::value>> inline Colour ToPremultiplied() const { - return Colour{ + return { ColourType((red * alpha) / 255), ColourType((green * alpha) / 255), ColourType((blue * alpha) / 255), @@ -119,7 +119,7 @@ class Colour { inline Colour ToPremultiplied(float opacity) const { const float new_alpha = alpha * opacity; - return Colour{ + return { ColourType(red * (new_alpha / 255.f)), ColourType(green * (new_alpha / 255.f)), ColourType(blue * (new_alpha / 255.f)), @@ -127,6 +127,19 @@ class Colour { }; } + // Convert color to non-premultiplied alpha. + template , + typename = typename std::enable_if_t::value>> + inline Colour ToNonPremultiplied() const + { + return { + ColourType(alpha > 0 ? (red * 255) / alpha : 0), + ColourType(alpha > 0 ? (green * 255) / alpha : 0), + ColourType(alpha > 0 ? (blue * 255) / alpha : 0), + ColourType(alpha), + }; + } + ColourType red, green, blue, alpha; #if defined(RMLUI_COLOUR_USER_EXTRA) diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 9250fc93d..edae60e88 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -46,7 +46,7 @@ bool TypeConverter::Convert(const Unit& src, String& dest) { switch (src) { - // clang-format off + // clang-format off case Unit::NUMBER: dest = ""; return true; case Unit::PERCENT: dest = "%"; return true; @@ -247,7 +247,8 @@ bool TypeConverter::Convert(const ColorStopList& src, Str for (size_t i = 0; i < src.size(); i++) { const ColorStop& stop = src[i]; - dest += ToString(stop.color); + const Colourb color = stop.color.ToNonPremultiplied(); + dest += CreateString(32, "rgba(%d,%d,%d,%d)", color.red, color.green, color.blue, color.alpha); if (Any(stop.position.unit & Unit::NUMBER_LENGTH_PERCENT)) dest += " " + ToString(stop.position.number) + ToString(stop.position.unit); @@ -280,7 +281,7 @@ bool TypeConverter::Convert(const BoxShadowList& src, Str if (shadow.inset) temp += " inset"; - dest += "rgba(" + ToString(shadow.color) + ')' + temp; + dest += "rgba(" + ToString(shadow.color.ToNonPremultiplied()) + ')' + temp; if (i < src.size() - 1) { diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index 3e1a7ca67..cbb825ba2 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -95,7 +97,7 @@ TEST_CASE("Properties") SUBCASE("gradient") { - auto ParseGradient = [&](const String& value) -> ColorStopList { + auto ParseGradient = [&](const String& value) -> Property { document->SetProperty("decorator", "linear-gradient(" + value + ")"); auto decorators = document->GetProperty("decorator"); if (!decorators || decorators->list.size() != 1) @@ -103,19 +105,21 @@ TEST_CASE("Properties") for (auto& id_property : decorators->list.front().properties.GetProperties()) { if (id_property.second.unit == Unit::COLORSTOPLIST) - return id_property.second.Get(); + return id_property.second; } return {}; }; struct GradientTestCase { String value; + String expected_parsed_string; ColorStopList expected_color_stops; }; GradientTestCase test_cases[] = { { "red, blue", + "rgba(255,0,0,255), rgba(0,0,255,255)", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{}}, @@ -123,6 +127,7 @@ TEST_CASE("Properties") }, { "red 5px, blue 50%", + "rgba(255,0,0,255) 5px, rgba(0,0,255,255) 50%", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{5.f, Unit::PX}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, @@ -130,6 +135,7 @@ TEST_CASE("Properties") }, { "red, #00f 50%, rgba(0, 255,0, 150) 10dp", + "rgba(255,0,0,255), rgba(0,0,255,255) 50%, rgba(0,255,0,150) 10dp", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, @@ -138,6 +144,7 @@ TEST_CASE("Properties") }, { "red 50px 20%, blue 10in", + "rgba(255,0,0,255) 50px, rgba(255,0,0,255) 20%, rgba(0,0,255,255) 10in", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{50.f, Unit::PX}}, ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{20.f, Unit::PERCENT}}, @@ -148,10 +155,55 @@ TEST_CASE("Properties") for (const GradientTestCase& test_case : test_cases) { - const ColorStopList result = ParseGradient(test_case.value); - CHECK(result == test_case.expected_color_stops); + const Property result = ParseGradient(test_case.value); + CHECK(result.ToString() == test_case.expected_parsed_string); + CHECK(result.Get() == test_case.expected_color_stops); } } Rml::Shutdown(); } + +TEST_CASE("Property.ToString") +{ + TestsSystemInterface system_interface; + TestsRenderInterface render_interface; + SetRenderInterface(&render_interface); + SetSystemInterface(&system_interface); + + Rml::Initialise(); + + CHECK(Property(5.2f, Unit::CM).ToString() == "5.2cm"); + CHECK(Property(150, Unit::PERCENT).ToString() == "150%"); + CHECK(Property(Colourb{170, 187, 204, 255}, Unit::COLOUR).ToString() == "170, 187, 204, 255"); + + auto ParsedValue = [](const String& name, const String& value) -> String { + PropertyDictionary properties; + StyleSheetSpecification::ParsePropertyDeclaration(properties, name, value); + REQUIRE(properties.GetNumProperties() == 1); + return properties.GetProperties().begin()->second.ToString(); + }; + + CHECK(ParsedValue("width", "10px") == "10px"); + CHECK(ParsedValue("width", "10.00em") == "10em"); + CHECK(ParsedValue("width", "auto") == "auto"); + + CHECK(ParsedValue("background-color", "#abc") == "rgba(170,187,204,255)"); + CHECK(ParsedValue("background-color", "red") == "rgba(255,0,0,255)"); + + CHECK(ParsedValue("transform", "translateX(10px)") == "translateX(10px)"); + CHECK(ParsedValue("transform", "translate(20in, 50em)") == "translate(20in, 50em)"); + + CHECK(ParsedValue("box-shadow", "2px 2px 0px, #00ff 4px 4px 2em") == "rgba(0, 0, 0, 255) 2px 2px 0px, rgba(0, 0, 255, 255) 4px 4px 2em"); + + // Due to conversion to and from premultiplied alpha, some color information is lost. + CHECK(ParsedValue("box-shadow", "#fff0 2px 2px 0px") == "rgba(0, 0, 0, 0) 2px 2px 0px"); + + CHECK(ParsedValue("decorator", "linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box") == + "linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box"); + + CHECK(ParsedValue("filter", "drop-shadow(#000 30px 20px 5px) opacity(0.2) sepia(0.2)") == + "drop-shadow(#000 30px 20px 5px) opacity(0.2) sepia(0.2)"); + + Rml::Shutdown(); +} From 7335eed838c45226729eea0f15e26bb5157b30fe Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 3 Feb 2024 22:29:43 +0100 Subject: [PATCH 67/80] Ensure all invoked types define a string converter --- Include/RmlUi/Core/TypeConverter.h | 1 + Include/RmlUi/Core/TypeConverter.inl | 33 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Include/RmlUi/Core/TypeConverter.h b/Include/RmlUi/Core/TypeConverter.h index 6583b295e..066312e12 100644 --- a/Include/RmlUi/Core/TypeConverter.h +++ b/Include/RmlUi/Core/TypeConverter.h @@ -35,6 +35,7 @@ #include "Types.h" #include #include +#include namespace Rml { diff --git a/Include/RmlUi/Core/TypeConverter.inl b/Include/RmlUi/Core/TypeConverter.inl index 53349265c..6f64affb8 100644 --- a/Include/RmlUi/Core/TypeConverter.inl +++ b/Include/RmlUi/Core/TypeConverter.inl @@ -385,6 +385,24 @@ public: } }; +template <> +class TypeConverter { +public: + static bool Convert(void* const& src, String& dest) { return FormatString(dest, 32, "%p", src) > 0; } +}; + +template <> +class TypeConverter { +public: + static bool Convert(ScriptInterface* const& src, String& dest) { return FormatString(dest, 32, "%p", static_cast(src)) > 0; } +}; + +template <> +class TypeConverter { +public: + static bool Convert(const char& src, String& dest) { return FormatString(dest, 32, "%c", src) > 0; } +}; + template class TypeConverterVectorString { public: @@ -423,6 +441,21 @@ VECTOR_STRING_CONVERTER(Vector4i, int, 4); VECTOR_STRING_CONVERTER(Vector4f, float, 4); VECTOR_STRING_CONVERTER(Colourf, float, 4); VECTOR_STRING_CONVERTER(Colourb, byte, 4); + +template +class TypeConverter { +public: + template + struct AlwaysFalse : std::integral_constant {}; + + static bool Convert(const SourceType& /*src*/, String& /*dest*/) + { + static_assert(AlwaysFalse{}, + "The type converter was invoked on a type without a string converter, please define a converter from SourceType to String."); + return false; + } +}; + #undef PASS_THROUGH #undef BASIC_CONVERTER #undef BASIC_CONVERTER_BOOL From 7b359d5d7c63373f2878a9e8c7c7f8e481b70de8 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sat, 3 Feb 2024 23:35:21 +0100 Subject: [PATCH 68/80] Use RCSS syntax for color-string conversion, format colors using hexadecimal notation --- Include/RmlUi/Core/TypeConverter.h | 10 ++++ Include/RmlUi/Core/TypeConverter.inl | 2 - Samples/basic/databinding/src/main.cpp | 2 +- Samples/invaders/src/HighScores.h | 2 +- Samples/luainvaders/src/HighScores.h | 2 +- Source/Core/PropertyDefinition.cpp | 7 --- Source/Core/PropertyParserColour.cpp | 62 +++++++++++++---------- Source/Core/PropertyParserColour.h | 5 +- Source/Core/TypeConverter.cpp | 19 +++++-- Tests/Source/UnitTests/Animation.cpp | 32 +++++------- Tests/Source/UnitTests/DataExpression.cpp | 2 +- Tests/Source/UnitTests/Element.cpp | 10 ++-- Tests/Source/UnitTests/Properties.cpp | 18 +++---- 13 files changed, 98 insertions(+), 75 deletions(-) diff --git a/Include/RmlUi/Core/TypeConverter.h b/Include/RmlUi/Core/TypeConverter.h index 066312e12..c588ed456 100644 --- a/Include/RmlUi/Core/TypeConverter.h +++ b/Include/RmlUi/Core/TypeConverter.h @@ -79,6 +79,16 @@ class TypeConverter { public: RMLUICORE_API static bool Convert(const Unit& src, String& dest); }; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const Colourb& src, String& dest); +}; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const String& src, Colourb& dest); +}; template <> class TypeConverter { diff --git a/Include/RmlUi/Core/TypeConverter.inl b/Include/RmlUi/Core/TypeConverter.inl index 6f64affb8..6f0848148 100644 --- a/Include/RmlUi/Core/TypeConverter.inl +++ b/Include/RmlUi/Core/TypeConverter.inl @@ -302,7 +302,6 @@ STRING_VECTOR_CONVERTER(Vector3f, float, 3); STRING_VECTOR_CONVERTER(Vector4i, int, 4); STRING_VECTOR_CONVERTER(Vector4f, float, 4); STRING_VECTOR_CONVERTER(Colourf, float, 4); -STRING_VECTOR_CONVERTER(Colourb, byte, 4); ///////////////////////////////////////////////// // To String Converters @@ -440,7 +439,6 @@ VECTOR_STRING_CONVERTER(Vector3f, float, 3); VECTOR_STRING_CONVERTER(Vector4i, int, 4); VECTOR_STRING_CONVERTER(Vector4f, float, 4); VECTOR_STRING_CONVERTER(Colourf, float, 4); -VECTOR_STRING_CONVERTER(Colourb, byte, 4); template class TypeConverter { diff --git a/Samples/basic/databinding/src/main.cpp b/Samples/basic/databinding/src/main.cpp index bcde2b5e2..9eb04106e 100644 --- a/Samples/basic/databinding/src/main.cpp +++ b/Samples/basic/databinding/src/main.cpp @@ -230,7 +230,7 @@ namespace InvadersExample { // Register a custom getter for the Colourb type. constructor.RegisterScalar( - [](const Rml::Colourb& color, Rml::Variant& variant) { variant = "rgba(" + Rml::ToString(color) + ')'; }); + [](const Rml::Colourb& color, Rml::Variant& variant) { variant = Rml::ToString(color); }); // Register a transform function for formatting time constructor.RegisterTransformFunc("format_time", [](const Rml::VariantList& arguments) -> Rml::Variant { if (arguments.empty()) diff --git a/Samples/invaders/src/HighScores.h b/Samples/invaders/src/HighScores.h index 0ae5e8085..a02651417 100644 --- a/Samples/invaders/src/HighScores.h +++ b/Samples/invaders/src/HighScores.h @@ -64,7 +64,7 @@ class HighScores { int score; int wave; - Rml::String GetColour() { return "rgba(" + Rml::ToString(colour) + ')'; } + Rml::String GetColour() { return Rml::ToString(colour); } }; using ScoreList = Rml::Vector; ScoreList scores; diff --git a/Samples/luainvaders/src/HighScores.h b/Samples/luainvaders/src/HighScores.h index 42fb56561..1113e10b2 100644 --- a/Samples/luainvaders/src/HighScores.h +++ b/Samples/luainvaders/src/HighScores.h @@ -64,7 +64,7 @@ class HighScores { int score; int wave; - Rml::String GetColour() { return "rgba(" + Rml::ToString(colour) + ')'; } + Rml::String GetColour() { return Rml::ToString(colour); } }; using ScoreList = Rml::Vector; ScoreList scores; diff --git a/Source/Core/PropertyDefinition.cpp b/Source/Core/PropertyDefinition.cpp index d3ea2c85a..24109c32e 100644 --- a/Source/Core/PropertyDefinition.cpp +++ b/Source/Core/PropertyDefinition.cpp @@ -156,13 +156,6 @@ bool PropertyDefinition::GetValue(String& value, const Property& property) const } break; - case Unit::COLOUR: - { - Colourb colour = property.value.Get(); - value = CreateString(32, "rgba(%d,%d,%d,%d)", colour.red, colour.green, colour.blue, colour.alpha); - } - break; - default: value += ToString(property.unit); break; } diff --git a/Source/Core/PropertyParserColour.cpp b/Source/Core/PropertyParserColour.cpp index 71f2f79ca..c0bc8f7d8 100644 --- a/Source/Core/PropertyParserColour.cpp +++ b/Source/Core/PropertyParserColour.cpp @@ -31,37 +31,50 @@ namespace Rml { -PropertyParserColour::PropertyParserColour() -{ - html_colours["black"] = Colourb(0, 0, 0); - html_colours["silver"] = Colourb(192, 192, 192); - html_colours["gray"] = Colourb(128, 128, 128); - html_colours["grey"] = Colourb(128, 128, 128); - html_colours["white"] = Colourb(255, 255, 255); - html_colours["maroon"] = Colourb(128, 0, 0); - html_colours["red"] = Colourb(255, 0, 0); - html_colours["orange"] = Colourb(255, 165, 0); - html_colours["purple"] = Colourb(128, 0, 128); - html_colours["fuchsia"] = Colourb(255, 0, 255); - html_colours["green"] = Colourb(0, 128, 0); - html_colours["lime"] = Colourb(0, 255, 0); - html_colours["olive"] = Colourb(128, 128, 0); - html_colours["yellow"] = Colourb(255, 255, 0); - html_colours["navy"] = Colourb(0, 0, 128); - html_colours["blue"] = Colourb(0, 0, 255); - html_colours["teal"] = Colourb(0, 128, 128); - html_colours["aqua"] = Colourb(0, 255, 255); - html_colours["transparent"] = Colourb(0, 0, 0, 0); -} +const PropertyParserColour::ColourMap PropertyParserColour::html_colours = { + {"black", Colourb(0, 0, 0)}, + {"silver", Colourb(192, 192, 192)}, + {"gray", Colourb(128, 128, 128)}, + {"grey", Colourb(128, 128, 128)}, + {"white", Colourb(255, 255, 255)}, + {"maroon", Colourb(128, 0, 0)}, + {"red", Colourb(255, 0, 0)}, + {"orange", Colourb(255, 165, 0)}, + {"purple", Colourb(128, 0, 128)}, + {"fuchsia", Colourb(255, 0, 255)}, + {"green", Colourb(0, 128, 0)}, + {"lime", Colourb(0, 255, 0)}, + {"olive", Colourb(128, 128, 0)}, + {"yellow", Colourb(255, 255, 0)}, + {"navy", Colourb(0, 0, 128)}, + {"blue", Colourb(0, 0, 255)}, + {"teal", Colourb(0, 128, 128)}, + {"aqua", Colourb(0, 255, 255)}, + {"transparent", Colourb(0, 0, 0, 0)}, +}; + +PropertyParserColour::PropertyParserColour() {} PropertyParserColour::~PropertyParserColour() {} bool PropertyParserColour::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const +{ + Colourb colour; + if (!ParseColour(colour, value)) + return false; + + property.value = Variant(colour); + property.unit = Unit::COLOUR; + + return true; +} + +bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) { if (value.empty()) return false; - Colourb colour; + colour = {}; // Check for a hex colour. if (value[0] == '#') @@ -155,9 +168,6 @@ bool PropertyParserColour::ParseValue(Property& property, const String& value, c colour = (*iterator).second; } - property.value = Variant(colour); - property.unit = Unit::COLOUR; - return true; } diff --git a/Source/Core/PropertyParserColour.h b/Source/Core/PropertyParserColour.h index 621f2133b..4450d7dfa 100644 --- a/Source/Core/PropertyParserColour.h +++ b/Source/Core/PropertyParserColour.h @@ -52,9 +52,12 @@ class PropertyParserColour : public PropertyParser { /// @return True if the value was parsed successfully, false otherwise. bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; + /// Parse a colour directly. + static bool ParseColour(Colourb& colour, const String& value); + private: using ColourMap = UnorderedMap; - ColourMap html_colours; + static const ColourMap html_colours; }; } // namespace Rml diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index edae60e88..24d1b0267 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -37,6 +37,7 @@ #include "../../Include/RmlUi/Core/StyleSheetTypes.h" #include "../../Include/RmlUi/Core/Transform.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" +#include "PropertyParserColour.h" #include "PropertyParserDecorator.h" #include "TransformUtilities.h" @@ -247,8 +248,7 @@ bool TypeConverter::Convert(const ColorStopList& src, Str for (size_t i = 0; i < src.size(); i++) { const ColorStop& stop = src[i]; - const Colourb color = stop.color.ToNonPremultiplied(); - dest += CreateString(32, "rgba(%d,%d,%d,%d)", color.red, color.green, color.blue, color.alpha); + dest += ToString(stop.color.ToNonPremultiplied()); if (Any(stop.position.unit & Unit::NUMBER_LENGTH_PERCENT)) dest += " " + ToString(stop.position.number) + ToString(stop.position.unit); @@ -281,7 +281,7 @@ bool TypeConverter::Convert(const BoxShadowList& src, Str if (shadow.inset) temp += " inset"; - dest += "rgba(" + ToString(shadow.color.ToNonPremultiplied()) + ')' + temp; + dest += ToString(shadow.color.ToNonPremultiplied()) + temp; if (i < src.size() - 1) { @@ -292,4 +292,17 @@ bool TypeConverter::Convert(const BoxShadowList& src, Str return true; } +bool TypeConverter::Convert(const Colourb& src, String& dest) +{ + if (src.alpha == 255) + return FormatString(dest, 32, "#%02hhx%02hhx%02hhx", src.red, src.green, src.blue) > 0; + else + return FormatString(dest, 32, "#%02hhx%02hhx%02hhx%02hhx", src.red, src.green, src.blue, src.alpha) > 0; +} + +bool TypeConverter::Convert(const String& src, Colourb& dest) +{ + return PropertyParserColour::ParseColour(dest, src); +} + } // namespace Rml diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index ce6b8aba7..565e04023 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -89,7 +89,7 @@ TEST_CASE("animation.decorator") "horizontal-gradient(transparent transparent)", "horizontal-gradient(white white)", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, { "", @@ -98,7 +98,7 @@ TEST_CASE("animation.decorator") "horizontal-gradient(transparent transparent) border-box", "horizontal-gradient(white white) border-box", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)) border-box", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f) border-box", }, { "", @@ -107,7 +107,7 @@ TEST_CASE("animation.decorator") "none", "horizontal-gradient(transparent transparent)", - "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", @@ -116,8 +116,7 @@ TEST_CASE("animation.decorator") "none", "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", - "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), horizontal-gradient(horizontal rgba(220,220,220,191) " - "rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf), horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", @@ -126,8 +125,7 @@ TEST_CASE("animation.decorator") "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", "none", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), horizontal-gradient(horizontal rgba(127,127,127,63) " - "rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f), horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, /// Only rule declaration @@ -138,7 +136,7 @@ TEST_CASE("animation.decorator") "from_rule", "to_rule", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, { "", @@ -147,7 +145,7 @@ TEST_CASE("animation.decorator") "from_rule", "to_rule", - "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "start-color: transparent; stop-color: transparent;", @@ -156,7 +154,7 @@ TEST_CASE("animation.decorator") "from_rule", "to_rule", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, /// Mix rule and standard declaration @@ -167,7 +165,7 @@ TEST_CASE("animation.decorator") "from_rule", "horizontal-gradient(white white)", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, { "", @@ -176,7 +174,7 @@ TEST_CASE("animation.decorator") "none", "to_rule", - "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "start-color: transparent; stop-color: transparent;", @@ -185,7 +183,7 @@ TEST_CASE("animation.decorator") "from_rule", "none", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, { "", @@ -194,8 +192,7 @@ TEST_CASE("animation.decorator") "from_rule, to_rule", "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", - "horizontal-gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191)), horizontal-gradient(horizontal rgba(220,220,220,191) " - "rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf), horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", @@ -204,8 +201,7 @@ TEST_CASE("animation.decorator") "horizontal-gradient(transparent transparent), horizontal-gradient(transparent transparent)", "from_rule, to_rule", - "horizontal-gradient(horizontal rgba(127,127,127,63) rgba(127,127,127,63)), horizontal-gradient(horizontal rgba(127,127,127,63) " - "rgba(127,127,127,63))", + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f), horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, }; @@ -305,7 +301,7 @@ TEST_CASE("animation.filter") { "drop-shadow(#000 30px 20px 0px)", "drop-shadow(#f00 30px 20px 4px)", // colors interpolated in linear space - "drop-shadow(rgba(127,0,0,255) 30px 20px 1px)", + "drop-shadow(#7f0000 30px 20px 1px)", }, { "opacity(0) brightness(2)", diff --git a/Tests/Source/UnitTests/DataExpression.cpp b/Tests/Source/UnitTests/DataExpression.cpp index 3d381fa8b..1777a94e3 100644 --- a/Tests/Source/UnitTests/DataExpression.cpp +++ b/Tests/Source/UnitTests/DataExpression.cpp @@ -121,7 +121,7 @@ TEST_CASE("Data expressions") CHECK(TestExpression("'a' | to_upper") == "A"); CHECK(TestExpression("!!10 - 1 ? 'hello' : 'world' | to_upper") == "WORLD"); - CHECK(TestExpression("(color_name) + (': rgba(' + color_value + ')')") == "color: rgba(180, 100, 255, 255)"); + CHECK(TestExpression("(color_name) + (': ' + color_value)") == "color: #b464ff"); CHECK(TestExpression("'hello world' | to_upper | concatenate(5 + 12 == 17 ? 'yes' : 'no', 9*2)") == "HELLO WORLD,yes,18"); CHECK(TestExpression("true == false") == "0"); CHECK(TestExpression("true != false") == "1"); diff --git a/Tests/Source/UnitTests/Element.cpp b/Tests/Source/UnitTests/Element.cpp index 7ed8d15fe..4f5522eeb 100644 --- a/Tests/Source/UnitTests/Element.cpp +++ b/Tests/Source/UnitTests/Element.cpp @@ -258,21 +258,21 @@ TEST_CASE("Element") SUBCASE("CloneManual") { Element* element = document->GetFirstChild(); - REQUIRE(element->GetProperty("background-color") == "255, 0, 0, 255"); - CHECK(element->Clone()->GetProperty("background-color") == "255, 0, 0, 255"); + REQUIRE(element->GetProperty("background-color") == "#ff0000"); + CHECK(element->Clone()->GetProperty("background-color") == "#ff0000"); element->SetProperty("background-color", "#0f0"); - CHECK(element->Clone()->GetProperty("background-color") == "0, 255, 0, 255"); + CHECK(element->Clone()->GetProperty("background-color") == "#00ff00"); element->RemoveProperty("background-color"); Element* clone = document->AppendChild(element->Clone()); context->Update(); - CHECK(clone->GetProperty("background-color") == "255, 255, 255, 255"); + CHECK(clone->GetProperty("background-color") == "#ffffff"); element->SetClass("blue", true); clone = document->AppendChild(element->Clone()); context->Update(); - CHECK(clone->GetProperty("background-color") == "0, 0, 255, 255"); + CHECK(clone->GetProperty("background-color") == "#0000ff"); } SUBCASE("SetInnerRML") diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index cbb825ba2..b8f4429e7 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -119,7 +119,7 @@ TEST_CASE("Properties") GradientTestCase test_cases[] = { { "red, blue", - "rgba(255,0,0,255), rgba(0,0,255,255)", + "#ff0000, #0000ff", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{}}, @@ -127,7 +127,7 @@ TEST_CASE("Properties") }, { "red 5px, blue 50%", - "rgba(255,0,0,255) 5px, rgba(0,0,255,255) 50%", + "#ff0000 5px, #0000ff 50%", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{5.f, Unit::PX}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, @@ -135,7 +135,7 @@ TEST_CASE("Properties") }, { "red, #00f 50%, rgba(0, 255,0, 150) 10dp", - "rgba(255,0,0,255), rgba(0,0,255,255) 50%, rgba(0,255,0,150) 10dp", + "#ff0000, #0000ff 50%, #00ff0096 10dp", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, @@ -144,7 +144,7 @@ TEST_CASE("Properties") }, { "red 50px 20%, blue 10in", - "rgba(255,0,0,255) 50px, rgba(255,0,0,255) 20%, rgba(0,0,255,255) 10in", + "#ff0000 50px, #ff0000 20%, #0000ff 10in", { ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{50.f, Unit::PX}}, ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{20.f, Unit::PERCENT}}, @@ -175,7 +175,7 @@ TEST_CASE("Property.ToString") CHECK(Property(5.2f, Unit::CM).ToString() == "5.2cm"); CHECK(Property(150, Unit::PERCENT).ToString() == "150%"); - CHECK(Property(Colourb{170, 187, 204, 255}, Unit::COLOUR).ToString() == "170, 187, 204, 255"); + CHECK(Property(Colourb{170, 187, 204, 255}, Unit::COLOUR).ToString() == "#aabbcc"); auto ParsedValue = [](const String& name, const String& value) -> String { PropertyDictionary properties; @@ -188,16 +188,16 @@ TEST_CASE("Property.ToString") CHECK(ParsedValue("width", "10.00em") == "10em"); CHECK(ParsedValue("width", "auto") == "auto"); - CHECK(ParsedValue("background-color", "#abc") == "rgba(170,187,204,255)"); - CHECK(ParsedValue("background-color", "red") == "rgba(255,0,0,255)"); + CHECK(ParsedValue("background-color", "#abc") == "#aabbcc"); + CHECK(ParsedValue("background-color", "red") == "#ff0000"); CHECK(ParsedValue("transform", "translateX(10px)") == "translateX(10px)"); CHECK(ParsedValue("transform", "translate(20in, 50em)") == "translate(20in, 50em)"); - CHECK(ParsedValue("box-shadow", "2px 2px 0px, #00ff 4px 4px 2em") == "rgba(0, 0, 0, 255) 2px 2px 0px, rgba(0, 0, 255, 255) 4px 4px 2em"); + CHECK(ParsedValue("box-shadow", "2px 2px 0px, #00ff 4px 4px 2em") == "#000000 2px 2px 0px, #0000ff 4px 4px 2em"); // Due to conversion to and from premultiplied alpha, some color information is lost. - CHECK(ParsedValue("box-shadow", "#fff0 2px 2px 0px") == "rgba(0, 0, 0, 0) 2px 2px 0px"); + CHECK(ParsedValue("box-shadow", "#fff0 2px 2px 0px") == "#00000000 2px 2px 0px"); CHECK(ParsedValue("decorator", "linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box") == "linear-gradient(110deg, #fff3, #fff 10%, #c33 250dp, #3c3, #33c, #000 90%, #0003) border-box"); From 144212b67732e18549890d84ed7a99093877120c Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Feb 2024 00:46:06 +0100 Subject: [PATCH 69/80] Fix defender decorator from being rendered in invader samples Add warning message when decorators generate invalid data handles --- Include/RmlUi/Core/Decorator.h | 1 + Samples/invaders/data/high_score.rml | 2 +- Samples/invaders/src/DecoratorDefender.cpp | 48 ++++++++++++------ Samples/luainvaders/data/high_score.rml | 2 +- Samples/luainvaders/src/DecoratorDefender.cpp | 50 ++++++++++++------- Source/Core/ElementDecoration.cpp | 3 ++ 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index d56e79a44..eb9973a70 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -71,6 +71,7 @@ class RMLUICORE_API Decorator { virtual void RenderElement(Element* element, DecoratorDataHandle element_data) const = 0; /// Value specifying an invalid or non-existent Decorator data handle. + /// @note This value will prevent the decorator from being rendered on the given element. static const DecoratorDataHandle INVALID_DECORATORDATAHANDLE = 0; protected: diff --git a/Samples/invaders/data/high_score.rml b/Samples/invaders/data/high_score.rml index 21b95f578..9d6129d79 100644 --- a/Samples/invaders/data/high_score.rml +++ b/Samples/invaders/data/high_score.rml @@ -63,7 +63,7 @@ {{score.name}} - + {{score.wave}} diff --git a/Samples/invaders/src/DecoratorDefender.cpp b/Samples/invaders/src/DecoratorDefender.cpp index 2f2996793..6d979dc52 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -37,6 +37,11 @@ #include #include +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; + DecoratorDefender::~DecoratorDefender() {} bool DecoratorDefender::Initialise(const Rml::Texture& texture) @@ -50,30 +55,41 @@ bool DecoratorDefender::Initialise(const Rml::Texture& texture) return true; } -Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/, Rml::BoxArea /*paint_area*/) const +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const { - return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -} + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_data*/) const {} - -void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle /*element_data*/) const -{ Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding); Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); Rml::Math::SnapToPixelGrid(position, size); - if (Rml::RenderManager* render_manager = element->GetRenderManager()) - { - Rml::Texture texture = GetTexture(image_index); - Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - Rml::Mesh mesh; - Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; - Rml::Geometry geometry = render_manager->MakeGeometry(std::move(mesh)); - geometry.Render(position, texture); - } + if (!element_data->texture || !element_data->geometry) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + return reinterpret_cast(element_data); +} + +void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle element_data_handle) const +{ + delete reinterpret_cast(element_data_handle); +} + +void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data_handle) const +{ + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding).Round(); + DecoratorDefenderElementData* element_data = reinterpret_cast(element_data_handle); + element_data->geometry.Render(position, element_data->texture); } DecoratorInstancerDefender::DecoratorInstancerDefender() diff --git a/Samples/luainvaders/data/high_score.rml b/Samples/luainvaders/data/high_score.rml index 7bb5b43e3..ff18a76f3 100644 --- a/Samples/luainvaders/data/high_score.rml +++ b/Samples/luainvaders/data/high_score.rml @@ -72,7 +72,7 @@ end {{score.name}} - + {{score.wave}} diff --git a/Samples/luainvaders/src/DecoratorDefender.cpp b/Samples/luainvaders/src/DecoratorDefender.cpp index 1214592a2..6d979dc52 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -30,13 +30,18 @@ #include #include #include -#include #include +#include #include #include #include #include +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; + DecoratorDefender::~DecoratorDefender() {} bool DecoratorDefender::Initialise(const Rml::Texture& texture) @@ -50,30 +55,41 @@ bool DecoratorDefender::Initialise(const Rml::Texture& texture) return true; } -Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* /*element*/, Rml::BoxArea /*paint_area*/) const +Rml::DecoratorDataHandle DecoratorDefender::GenerateElementData(Rml::Element* element, Rml::BoxArea /*paint_area*/) const { - return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -} + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_data*/) const {} - -void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle /*element_data*/) const -{ Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding); Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding); Rml::Math::SnapToPixelGrid(position, size); - if (Rml::RenderManager* render_manager = element->GetRenderManager()) - { - Rml::Texture texture = GetTexture(image_index); - Rml::ColourbPremultiplied color = element->GetProperty("color").ToPremultiplied(); + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - Rml::Mesh mesh; - Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; - Rml::Geometry geometry = render_manager->MakeGeometry(std::move(mesh)); - geometry.Render(position, texture); - } + if (!element_data->texture || !element_data->geometry) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; + + return reinterpret_cast(element_data); +} + +void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle element_data_handle) const +{ + delete reinterpret_cast(element_data_handle); +} + +void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle element_data_handle) const +{ + Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding).Round(); + DecoratorDefenderElementData* element_data = reinterpret_cast(element_data_handle); + element_data->geometry.Render(position, element_data->texture); } DecoratorInstancerDefender::DecoratorInstancerDefender() diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index de2a35dce..378487e6a 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -165,6 +165,9 @@ void ElementDecoration::ReloadDecoratorsData() decorator.decorator->ReleaseElementData(decorator.decorator_data); decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); + + if (!decorator.decorator_data) + Log::Message(Log::LT_WARNING, "Could not load decorator data on element: %s", element->GetAddress().c_str()); } } From b301702ed4a7db5e7a40aa5cf0f246f58a157718 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Feb 2024 16:09:29 +0100 Subject: [PATCH 70/80] Update CMake minimum to 3.5 --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e48586b9..fde29a27f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # Build script for RmlUi =========== #=================================== -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.5) if(APPLE) # This has to be before most other options so CMake properly handles the @@ -52,6 +52,9 @@ endif(POLICY CMP0072) if(POLICY CMP0074) cmake_policy(SET CMP0074 NEW) endif(POLICY CMP0074) +if(POLICY CMP0074) + cmake_policy(SET CMP0092 NEW) +endif(POLICY CMP0074) project(RmlUi LANGUAGES C CXX VERSION 6.0) From 13b60d13b2ade3f1e065debef841313e1cf91cb7 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 5 Feb 2024 22:32:29 +0100 Subject: [PATCH 71/80] UnitTests: Add filter support to tests interface, test filter generate and release --- Tests/Source/Common/TestsInterface.cpp | 31 +++++++++++++++++++++++++- Tests/Source/Common/TestsInterface.h | 13 +++++++++++ Tests/Source/UnitTests/Filter.cpp | 17 ++++++++++++-- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/Tests/Source/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index 0fec9f35d..5053f9ff8 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -114,7 +114,8 @@ void TestsRenderInterface::EnableClipMask(bool /*enable*/) counters.enable_clip_mask += 1; } -void TestsRenderInterface::RenderToClipMask(Rml::ClipMaskOperation /*mask_operation*/, Rml::CompiledGeometryHandle /*geometry*/, Rml::Vector2f /*translation*/) +void TestsRenderInterface::RenderToClipMask(Rml::ClipMaskOperation /*mask_operation*/, Rml::CompiledGeometryHandle /*geometry*/, + Rml::Vector2f /*translation*/) { counters.render_to_clip_mask += 1; } @@ -142,3 +143,31 @@ void TestsRenderInterface::SetTransform(const Rml::Matrix4f* /*transform*/) { counters.set_transform += 1; } + +Rml::CompiledFilterHandle TestsRenderInterface::CompileFilter(const Rml::String& /*name*/, const Rml::Dictionary& /*parameters*/) +{ + counters.compile_filter += 1; + return 1; +} + +void TestsRenderInterface::ReleaseCompiledFilter(Rml::CompiledFilterHandle /*filter*/) +{ + counters.release_filter += 1; +} + +Rml::CompiledShaderHandle TestsRenderInterface::CompileShader(const Rml::String& /*name*/, const Rml::Dictionary& /*parameters*/) +{ + counters.compile_shader += 1; + return 1; +} + +void TestsRenderInterface::RenderShader(Rml::CompiledShaderHandle /*shader*/, Rml::CompiledGeometryHandle /*geometry*/, Rml::Vector2f /*translation*/, + Rml::TextureHandle /*texture*/) +{ + counters.render_shader += 1; +} + +void TestsRenderInterface::ReleaseCompiledShader(Rml::CompiledShaderHandle /*shader*/) +{ + counters.release_shader += 1; +} diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index 68fde0c64..67f7d2532 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -68,6 +68,11 @@ class TestsRenderInterface : public Rml::RenderInterface { size_t enable_clip_mask; size_t render_to_clip_mask; size_t set_transform; + size_t compile_filter; + size_t release_filter; + size_t compile_shader; + size_t render_shader; + size_t release_shader; }; Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; @@ -86,6 +91,14 @@ class TestsRenderInterface : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; + void ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) override; + + Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override; + void RenderShader(Rml::CompiledShaderHandle shader, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, + Rml::TextureHandle texture) override; + void ReleaseCompiledShader(Rml::CompiledShaderHandle shader) override; + const Counters& GetCounters() const { return counters; } void ResetCounters() { counters = {}; } diff --git a/Tests/Source/UnitTests/Filter.cpp b/Tests/Source/UnitTests/Filter.cpp index d833cd5ca..f6cabe1f0 100644 --- a/Tests/Source/UnitTests/Filter.cpp +++ b/Tests/Source/UnitTests/Filter.cpp @@ -26,6 +26,7 @@ * */ +#include "../Common/TestsInterface.h" #include "../Common/TestsShell.h" #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include @@ -54,7 +56,7 @@ class FilterTest : public Filter { CompiledFilter CompileFilter(Element* element) const override { compiled_test_filters.push_back({element->GetId(), this}); - return CompiledFilter(); + return element->GetRenderManager()->CompileFilter("FilterTest", {}); } float value = 0.f; @@ -125,6 +127,8 @@ TEST_CASE("filter") Context* context = TestsShell::GetContext(); REQUIRE(context); + TestsShell::GetTestsRenderInterface()->ResetCounters(); + FilterTestInstancer instancer; Rml::Factory::RegisterFilterInstancer("test", &instancer); @@ -165,8 +169,17 @@ TEST_CASE("filter") // Filters aren't cached like decorators are, so each element will instance a new decorator even if they refer to // the same style rule. Thus, here producing 4 instead of 3 unique instances. - CHECK(instancer.num_instances == 4); + CHECK(instancer.num_instances == expected_compiled_filters.size()); + + auto& counters = TestsShell::GetTestsRenderInterface()->GetCounters(); + CHECK(counters.compile_filter == expected_compiled_filters.size()); + CHECK(counters.release_filter == 0); document->Close(); + context->Update(); + + CHECK(counters.compile_filter == expected_compiled_filters.size()); + CHECK(counters.release_filter == expected_compiled_filters.size()); + TestsShell::ShutdownShell(); } From 7b9d86574a7b6c8b7e219f24cf7c052d71031352 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Feb 2024 16:35:47 +0100 Subject: [PATCH 72/80] Log warning if filter could not be compiled --- Source/Core/ElementDecoration.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 378487e6a..0a1d9ebe4 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -157,6 +157,7 @@ void ElementDecoration::ReloadDecoratorsData() { decorators_data_dirty = false; + bool decorator_data_failed = false; for (DecoratorEntryList* list : {&decorators, &mask_images}) { for (DecoratorEntry& decorator : *list) @@ -165,17 +166,27 @@ void ElementDecoration::ReloadDecoratorsData() decorator.decorator->ReleaseElementData(decorator.decorator_data); decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); - if (!decorator.decorator_data) - Log::Message(Log::LT_WARNING, "Could not load decorator data on element: %s", element->GetAddress().c_str()); + decorator_data_failed = true; } } + if (decorator_data_failed) + Log::Message(Log::LT_WARNING, "Could not load decorator data on element: %s", element->GetAddress().c_str()); + + bool filter_compile_failed = false; for (FilterEntryList* list : {&filters, &backdrop_filters}) { for (FilterEntry& filter : *list) + { filter.compiled = filter.filter->CompileFilter(element); + if (!filter.compiled) + filter_compile_failed = true; + } } + + if (filter_compile_failed) + Log::Message(Log::LT_WARNING, "Could not compile filter on element: %s", element->GetAddress().c_str()); } } From 8dfba0597a907a3dc2bb38cdc6ab33d95e740adf Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 4 Feb 2024 21:41:04 +0100 Subject: [PATCH 73/80] GL3 renderer: Add color write mask to OpenGL state backup --- Backends/RmlUi_Renderer_GL3.cpp | 3 +++ Backends/RmlUi_Renderer_GL3.h | 1 + 2 files changed, 4 insertions(+) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 6e164be64..d7c0c9e03 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -828,6 +828,7 @@ void RenderInterface_GL3::BeginFrame() 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); @@ -943,6 +944,8 @@ void RenderInterface_GL3::EndFrame() 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); diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 40297e059..2c591a102 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -193,6 +193,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { int stencil_clear_value; float color_clear_value[4]; + unsigned char color_writemask[4]; int blend_equation_rgb; int blend_equation_alpha; From 742bbbafdaa9285abc0e720ad925eaa0f109f0e7 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 5 Feb 2024 21:16:18 +0100 Subject: [PATCH 74/80] Add mask-image to absolute positioning containing block --- Source/Core/Layout/ContainerBox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Layout/ContainerBox.cpp b/Source/Core/Layout/ContainerBox.cpp index 02a4bca5e..e1aed3722 100644 --- a/Source/Core/Layout/ContainerBox.cpp +++ b/Source/Core/Layout/ContainerBox.cpp @@ -132,7 +132,7 @@ ContainerBox::ContainerBox(Type type, Element* element, ContainerBox* parent_con overflow_x = computed.overflow_x(); overflow_y = computed.overflow_y(); is_absolute_positioning_containing_block = (computed.position() != Style::Position::Static || computed.has_local_transform() || - computed.has_local_perspective() || computed.has_filter() || computed.has_backdrop_filter()); + computed.has_local_perspective() || computed.has_filter() || computed.has_backdrop_filter() || computed.has_mask_image()); } } From c42d7298d742342806a5048054a3f5efd3be269e Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 5 Feb 2024 22:55:28 +0100 Subject: [PATCH 75/80] Render interface naming: Remove compiled from release functions --- Backends/RmlUi_Renderer_GL3.cpp | 4 ++-- Backends/RmlUi_Renderer_GL3.h | 4 ++-- Include/RmlUi/Core/RenderInterface.h | 4 ++-- Source/Core/RenderInterface.cpp | 4 ++-- Source/Core/RenderManager.cpp | 4 ++-- Tests/Source/Common/TestsInterface.cpp | 4 ++-- Tests/Source/Common/TestsInterface.h | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index d7c0c9e03..bae2c2726 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1592,7 +1592,7 @@ Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& return {}; } -void RenderInterface_GL3::ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) +void RenderInterface_GL3::ReleaseFilter(Rml::CompiledFilterHandle filter) { delete reinterpret_cast(filter); } @@ -1731,7 +1731,7 @@ void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Gfx::CheckGLError("RenderShader"); } -void RenderInterface_GL3::ReleaseCompiledShader(Rml::CompiledShaderHandle shader_handle) +void RenderInterface_GL3::ReleaseShader(Rml::CompiledShaderHandle shader_handle) { delete reinterpret_cast(shader_handle); } diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 2c591a102..98adbc231 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -86,12 +86,12 @@ class RenderInterface_GL3 : public Rml::RenderInterface { Rml::CompiledFilterHandle SaveLayerAsMaskImage() override; Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; - void ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) 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 ReleaseCompiledShader(Rml::CompiledShaderHandle effect_handle) 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); diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 81c4c8f68..207684462 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -152,7 +152,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { virtual CompiledFilterHandle CompileFilter(const String& name, const Dictionary& parameters); /// Called by RmlUi when it no longer needs a previously compiled filter. /// @param[in] filter The handle to a previously compiled filter. - virtual void ReleaseCompiledFilter(CompiledFilterHandle filter); + virtual void ReleaseFilter(CompiledFilterHandle filter); /// Called by RmlUi when it wants to compile a new shader. /// @param[in] name The name of the shader. @@ -167,7 +167,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { virtual void RenderShader(CompiledShaderHandle shader, CompiledGeometryHandle geometry, Vector2f translation, TextureHandle texture); /// Called by RmlUi when it no longer needs a previously compiled shader. /// @param[in] shader The handle to a previously compiled shader. - virtual void ReleaseCompiledShader(CompiledShaderHandle shader); + virtual void ReleaseShader(CompiledShaderHandle shader); }; } // namespace Rml diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index 414cd20fa..abd1a8793 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -70,7 +70,7 @@ CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, cons return CompiledFilterHandle{}; } -void RenderInterface::ReleaseCompiledFilter(CompiledFilterHandle /*filter*/) {} +void RenderInterface::ReleaseFilter(CompiledFilterHandle /*filter*/) {} CompiledShaderHandle RenderInterface::CompileShader(const String& /*name*/, const Dictionary& /*parameters*/) { @@ -81,6 +81,6 @@ void RenderInterface::RenderShader(CompiledShaderHandle /*shader*/, CompiledGeom TextureHandle /*texture*/) {} -void RenderInterface::ReleaseCompiledShader(CompiledShaderHandle /*shader*/) {} +void RenderInterface::ReleaseShader(CompiledShaderHandle /*shader*/) {} } // namespace Rml diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index 7c6685e46..f8a6f10e9 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -341,7 +341,7 @@ void RenderManager::ReleaseResource(const CompiledFilter& filter) { RMLUI_ASSERT(filter.render_manager == this && filter.resource_handle != filter.InvalidHandle()); - render_interface->ReleaseCompiledFilter(filter.resource_handle); + render_interface->ReleaseFilter(filter.resource_handle); compiled_filter_count -= 1; } @@ -349,7 +349,7 @@ void RenderManager::ReleaseResource(const CompiledShader& shader) { RMLUI_ASSERT(shader.render_manager == this && shader.resource_handle != shader.InvalidHandle()); - render_interface->ReleaseCompiledShader(shader.resource_handle); + render_interface->ReleaseShader(shader.resource_handle); compiled_shader_count -= 1; } diff --git a/Tests/Source/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index 5053f9ff8..0d017259d 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -150,7 +150,7 @@ Rml::CompiledFilterHandle TestsRenderInterface::CompileFilter(const Rml::String& return 1; } -void TestsRenderInterface::ReleaseCompiledFilter(Rml::CompiledFilterHandle /*filter*/) +void TestsRenderInterface::ReleaseFilter(Rml::CompiledFilterHandle /*filter*/) { counters.release_filter += 1; } @@ -167,7 +167,7 @@ void TestsRenderInterface::RenderShader(Rml::CompiledShaderHandle /*shader*/, Rm counters.render_shader += 1; } -void TestsRenderInterface::ReleaseCompiledShader(Rml::CompiledShaderHandle /*shader*/) +void TestsRenderInterface::ReleaseShader(Rml::CompiledShaderHandle /*shader*/) { counters.release_shader += 1; } diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index 67f7d2532..83b9d86d6 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -92,12 +92,12 @@ class TestsRenderInterface : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; - void ReleaseCompiledFilter(Rml::CompiledFilterHandle filter) override; + void ReleaseFilter(Rml::CompiledFilterHandle filter) override; Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override; void RenderShader(Rml::CompiledShaderHandle shader, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override; - void ReleaseCompiledShader(Rml::CompiledShaderHandle shader) override; + void ReleaseShader(Rml::CompiledShaderHandle shader) override; const Counters& GetCounters() const { return counters; } void ResetCounters() { counters = {}; } From 011b3c17efeaff5a6bcda4547dd7fd4cb5590451 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 4 Mar 2024 01:28:44 +0100 Subject: [PATCH 76/80] Fix file textures not being reloaded after release --- Source/Core/RenderManager.cpp | 2 +- Source/Core/RenderManagerAccess.cpp | 2 +- Source/Core/TextureDatabase.cpp | 43 +++++++++++++++++++++-------- Source/Core/TextureDatabase.h | 8 ++++-- Tests/Source/UnitTests/Core.cpp | 22 +++++++++++++-- 5 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index f8a6f10e9..0844a6069 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -240,7 +240,7 @@ void RenderManager::Render(const Geometry& geometry, Vector2f translation, Textu { TextureHandle texture_handle = {}; if (texture.file_index != TextureFileIndex::Invalid) - texture_handle = texture_database->file_database.GetHandle(texture.file_index); + texture_handle = texture_database->file_database.GetHandle(render_interface, texture.file_index); else if (texture.callback_index != StableVectorIndex::Invalid) texture_handle = texture_database->callback_database.GetHandle(this, render_interface, texture.callback_index); diff --git a/Source/Core/RenderManagerAccess.cpp b/Source/Core/RenderManagerAccess.cpp index 57bbf0b5b..b157f8637 100644 --- a/Source/Core/RenderManagerAccess.cpp +++ b/Source/Core/RenderManagerAccess.cpp @@ -34,7 +34,7 @@ namespace Rml { Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, TextureFileIndex texture) { - return render_manager->texture_database->file_database.GetDimensions(texture); + return render_manager->texture_database->file_database.GetDimensions(render_manager->render_interface, texture); } Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture) diff --git a/Source/Core/TextureDatabase.cpp b/Source/Core/TextureDatabase.cpp index aca8ae6a4..fb5bf0875 100644 --- a/Source/Core/TextureDatabase.cpp +++ b/Source/Core/TextureDatabase.cpp @@ -122,28 +122,47 @@ TextureFileIndex FileTextureDatabase::LoadTexture(RenderInterface* render_interf if (it != texture_map.end()) return it->second; - Vector2i dimensions; - if (TextureHandle handle = render_interface->LoadTexture(dimensions, source)) + FileTextureEntry entry = LoadTextureEntry(render_interface, source); + if (!entry.texture_handle) + return TextureFileIndex::Invalid; + + const auto index = TextureFileIndex(texture_list.size()); + texture_map[source] = index; + texture_list.push_back(std::move(entry)); + + return index; +} + +FileTextureDatabase::FileTextureEntry FileTextureDatabase::LoadTextureEntry(RenderInterface* render_interface, const String& source) +{ + FileTextureEntry result = {}; + result.texture_handle = render_interface->LoadTexture(result.dimensions, source); + return result; +} + +FileTextureDatabase::FileTextureEntry& FileTextureDatabase::EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index) +{ + FileTextureEntry& entry = texture_list[size_t(index)]; + if (!entry.texture_handle) { - TextureFileIndex result = TextureFileIndex(texture_list.size()); - texture_map[source] = result; - texture_list.push_back(FileTextureEntry{handle, dimensions}); - return result; + auto it = std::find_if(texture_map.begin(), texture_map.end(), [index](const auto& pair) { return pair.second == index; }); + RMLUI_ASSERT(it != texture_map.end()); + const String& source = it->first; + entry = LoadTextureEntry(render_interface, source); } - - return TextureFileIndex::Invalid; + return entry; } -TextureHandle FileTextureDatabase::GetHandle(TextureFileIndex index) const +TextureHandle FileTextureDatabase::GetHandle(RenderInterface* render_interface, TextureFileIndex index) { RMLUI_ASSERT(size_t(index) < texture_list.size()); - return texture_list[size_t(index)].texture_handle; + return EnsureLoaded(render_interface, index).texture_handle; } -Vector2i FileTextureDatabase::GetDimensions(TextureFileIndex index) const +Vector2i FileTextureDatabase::GetDimensions(RenderInterface* render_interface, TextureFileIndex index) { RMLUI_ASSERT(size_t(index) < texture_list.size()); - return texture_list[size_t(index)].dimensions; + return EnsureLoaded(render_interface, index).dimensions; } void FileTextureDatabase::GetSourceList(StringList& source_list) const diff --git a/Source/Core/TextureDatabase.h b/Source/Core/TextureDatabase.h index f9ca38b1c..bba3b7cbb 100644 --- a/Source/Core/TextureDatabase.h +++ b/Source/Core/TextureDatabase.h @@ -71,8 +71,8 @@ class FileTextureDatabase : NonCopyMoveable { TextureFileIndex LoadTexture(RenderInterface* render_interface, const String& source); - TextureHandle GetHandle(TextureFileIndex index) const; - Vector2i GetDimensions(TextureFileIndex index) const; + TextureHandle GetHandle(RenderInterface* render_interface, TextureFileIndex index); + Vector2i GetDimensions(RenderInterface* render_interface, TextureFileIndex index); void GetSourceList(StringList& source_list) const; @@ -83,6 +83,10 @@ class FileTextureDatabase : NonCopyMoveable { TextureHandle texture_handle = {}; Vector2i dimensions; }; + + FileTextureEntry LoadTextureEntry(RenderInterface* render_interface, const String& source); + FileTextureEntry& EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index); + Vector texture_list; UnorderedMap texture_map; // key: source, value: index into 'texture_list' }; diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index e786798df..157a7519c 100644 --- a/Tests/Source/UnitTests/Core.cpp +++ b/Tests/Source/UnitTests/Core.cpp @@ -61,7 +61,7 @@ static const String document_textures_rml = R"( height: 100px; decorator: image(alien3); } - progress { + progress { display: block; width: 50px; height: 50px; @@ -145,13 +145,29 @@ TEST_CASE("core.release_resources") SUBCASE("ReleaseTextures") { + const auto startup_counters = counters; + REQUIRE(counters.load_texture > 0); + REQUIRE(counters.generate_texture > 0); + REQUIRE(counters.release_texture == 0); + // Release all textures and verify that the render interface received the release call. Rml::ReleaseTextures(); - CHECK(counters.generate_texture + counters.load_texture == counters.release_texture); + CHECK(counters.load_texture == startup_counters.load_texture); + CHECK(counters.generate_texture == startup_counters.generate_texture); + CHECK(counters.release_texture == startup_counters.generate_texture + startup_counters.load_texture); + const int num_released_textures = counters.release_texture; // By doing a new context Update+Render the textures should be loaded again. TestsShell::RenderLoop(); - CHECK(counters.generate_texture + counters.load_texture > counters.release_texture); + CHECK(counters.load_texture == 2 * startup_counters.load_texture); + CHECK(counters.generate_texture == 2 * startup_counters.generate_texture); + CHECK(counters.release_texture == num_released_textures); + + // Another loop should not affect the texture calls. + TestsShell::RenderLoop(); + CHECK(counters.load_texture == 2 * startup_counters.load_texture); + CHECK(counters.generate_texture == 2 * startup_counters.generate_texture); + CHECK(counters.release_texture == num_released_textures); } SUBCASE("ReleaseFontResources") From b470b2b0e84c51b413c0622d6cb82c3e77605b56 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Sun, 24 Mar 2024 23:18:12 +0100 Subject: [PATCH 77/80] Make backdrop-filter be affected by any filter and mask-image applied to the same element --- Backends/RmlUi_Renderer_GL3.cpp | 26 +++- Include/RmlUi/Core/RenderInterface.h | 3 +- Samples/basic/effect/data/effect.rml | 38 +++--- Source/Core/ElementDecoration.cpp | 49 +++++--- Tests/Data/VisualTests/filter_blur.rml | 41 +++++++ Tests/Data/VisualTests/filter_overflow.rml | 34 ++++++ .../Data/VisualTests/filter_with_backdrop.rml | 112 ++++++++++++++++++ 7 files changed, 258 insertions(+), 45 deletions(-) create mode 100644 Tests/Data/VisualTests/filter_blur.rml create mode 100644 Tests/Data/VisualTests/filter_overflow.rml create mode 100644 Tests/Data/VisualTests/filter_with_backdrop.rml diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index bae2c2726..5e5382df0 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1432,10 +1432,10 @@ void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& so 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 noticable when moving an element with + // 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 enlargen the window to the + // 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); @@ -1875,14 +1875,28 @@ void RenderInterface_GL3::RenderFilters(Rml::Span filters) diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 207684462..0538983e3 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -44,7 +44,8 @@ enum class ClipMaskOperation { enum class LayerFill { None, // No operation necessary, does not care about the layer color. Clear, // Clear the layer to transparent black. - Clone, // Copy the color data from the previous layer. + Copy, // Copy the color data from the previous layer. + Link, // Link the color data with the previous layer. }; enum class BlendMode { Blend, // Normal alpha blending. diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml index 5511fb8f3..5e60e28e9 100644 --- a/Samples/basic/effect/data/effect.rml +++ b/Samples/basic/effect/data/effect.rml @@ -163,32 +163,32 @@ data-style-filter="'opacity(' + opacity + ') sepia(' + sepia + ') grayscale(' + grayscale + ') saturate(' + saturate + ') brightness(' + brightness + ') contrast(' + contrast + ') hue-rotate(' + hue_rotate + 'deg) invert(' + invert + ') blur(' + blur + 'px)' + (drop_shadow ? ' drop-shadow(#f11b 10px 10px 8px)' : '')" data-class-transform_all="transform_all" > +
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
"Creation" (Danilo Guanabara)
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
"Creation" (Danilo Guanabara)
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
+
Hello, do you feel the funk?
-
Hello, do you feel the funk?
- -
Hello, do you feel the funk?
-
Hello, do you feel the funk?
+
Hello, do you feel the funk?
diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index 0a1d9ebe4..d8c7a9a53 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -258,30 +258,22 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) scissor_region.IntersectIfValid(render_manager->GetState().scissor_region); render_manager->SetScissorRegion(scissor_region); }; - - if (!backdrop_filters.empty()) - { - if (render_stage == RenderStage::Enter) - { - ApplyClippingRegion(PropertyId::BackdropFilter); - - render_manager->PushLayer(LayerFill::Clone); - - FilterHandleList filter_handles; - for (auto& filter : backdrop_filters) - filter.compiled.AddHandleTo(filter_handles); - - render_manager->PopLayer(BlendMode::Replace, filter_handles); - - render_manager->SetScissorRegion(initial_scissor_region); - } - } + auto ApplyScissorRegionForBackdrop = [this, &render_manager]() { + // Set the scissor region for backdrop drawing, which covers the element's border box plus any area we may need + // to read from, such as any blur radius. + Rectanglef filter_region = Rectanglef::MakeInvalid(); + ElementUtilities::GetBoundingBox(filter_region, element, BoxArea::Border); + for (const auto& filter : backdrop_filters) + filter.filter->ExtendInkOverflow(element, filter_region); + Math::ExpandToPixelGrid(filter_region); + render_manager->SetScissorRegion(Rectanglei(filter_region)); + }; if (!filters.empty() || !mask_images.empty()) { if (render_stage == RenderStage::Enter) { - render_manager->PushLayer(LayerFill::Clear); + render_manager->PushLayer(backdrop_filters.empty() ? LayerFill::Clear : LayerFill::Copy); } else if (render_stage == RenderStage::Exit) { @@ -313,6 +305,25 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) render_manager->SetScissorRegion(initial_scissor_region); } } + + if (!backdrop_filters.empty()) + { + if (render_stage == RenderStage::Enter) + { + ApplyScissorRegionForBackdrop(); + render_manager->PushLayer(LayerFill::Copy); + render_manager->PushLayer(LayerFill::Link); + + FilterHandleList filter_handles; + for (auto& filter : backdrop_filters) + filter.compiled.AddHandleTo(filter_handles); + + render_manager->PopLayer(BlendMode::Replace, filter_handles); + ApplyClippingRegion(PropertyId::BackdropFilter); + render_manager->PopLayer(BlendMode::Blend, {}); + render_manager->SetScissorRegion(initial_scissor_region); + } + } } void ElementDecoration::DirtyDecorators() diff --git a/Tests/Data/VisualTests/filter_blur.rml b/Tests/Data/VisualTests/filter_blur.rml new file mode 100644 index 000000000..97f63331d --- /dev/null +++ b/Tests/Data/VisualTests/filter_blur.rml @@ -0,0 +1,41 @@ + + + Filter: blur + + + + + + + + + + + diff --git a/Tests/Data/VisualTests/filter_overflow.rml b/Tests/Data/VisualTests/filter_overflow.rml new file mode 100644 index 000000000..76122e739 --- /dev/null +++ b/Tests/Data/VisualTests/filter_overflow.rml @@ -0,0 +1,34 @@ + + + Filter: overflowing content + + + + + + + +
+
+
+ + +
diff --git a/Tests/Data/VisualTests/filter_with_backdrop.rml b/Tests/Data/VisualTests/filter_with_backdrop.rml new file mode 100644 index 000000000..b70c9b6e9 --- /dev/null +++ b/Tests/Data/VisualTests/filter_with_backdrop.rml @@ -0,0 +1,112 @@ + + + Filter: backdrop + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + +
From e96b950373f5cfd47f478ad48be7b82e7d3eb3a1 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Mon, 25 Mar 2024 23:00:06 +0100 Subject: [PATCH 78/80] Render interface: Make layer compositing a separate function instead of being a part of layer pop The purpose of each function should now be more clear. With this change, we can now composite two arbitrary layers which simplifies some use cases of compositing. This commit also uses the changed interface to fix some artifacts when rendering back-drop filter blur combined with plain filter blur. --- Backends/RmlUi_Renderer_GL3.cpp | 101 +++++++----------- Backends/RmlUi_Renderer_GL3.h | 16 +-- Include/RmlUi/Core/RenderInterface.h | 32 +++--- Include/RmlUi/Core/RenderManager.h | 10 +- Include/RmlUi/Core/StableVector.h | 5 +- Include/RmlUi/Core/Types.h | 1 + Source/Core/ElementDecoration.cpp | 66 +++++++----- Source/Core/GeometryBoxShadow.cpp | 9 +- Source/Core/RenderInterface.cpp | 11 +- Source/Core/RenderManager.cpp | 31 +++++- .../{filter_blur.rml => filter_blur_area.rml} | 5 +- Tests/Data/VisualTests/filter_blur_orb.rml | 44 ++++++++ Tests/Source/UnitTests/Core.cpp | 2 +- 13 files changed, 204 insertions(+), 129 deletions(-) rename Tests/Data/VisualTests/{filter_blur.rml => filter_blur_area.rml} (92%) create mode 100644 Tests/Data/VisualTests/filter_blur_orb.rml diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 5e5382df0..50967fe8a 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1736,9 +1736,9 @@ void RenderInterface_GL3::ReleaseShader(Rml::CompiledShaderHandle shader_handle) delete reinterpret_cast(shader_handle); } -void RenderInterface_GL3::BlitTopLayerToPostprocessPrimary() +void RenderInterface_GL3::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle) { - const Gfx::FramebufferData& source = render_layers.GetTopLayer(); + 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); @@ -1873,57 +1873,31 @@ void RenderInterface_GL3::RenderFilters(Rml::Span filters) +void RenderInterface_GL3::CompositeLayers(Rml::LayerHandle source_handle, Rml::LayerHandle destination_handle, Rml::BlendMode blend_mode, + Rml::Span filters) { using Rml::BlendMode; - if (blend_mode == BlendMode::Discard) - { - RMLUI_ASSERT(filters.empty()); - render_layers.PopLayer(); - glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); - return; - } - - // Blit stack to filter rendering 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. + // 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. - BlitTopLayerToPostprocessPrimary(); + BlitLayerToPostprocessPrimary(source_handle); // Render the filters, the PostprocessPrimary framebuffer is used for both input and output. RenderFilters(filters); - // Pop the active layer, thereby activating the beneath layer. - render_layers.PopLayer(); - - // Render to the activated layer. Apply any mask if active. - glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + // Render to the destination layer. + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetLayer(destination_handle).framebuffer); Gfx::BindTexture(render_layers.GetPostprocessPrimary()); UseProgram(ProgramId::Passthrough); @@ -1936,7 +1910,16 @@ void RenderInterface_GL3::PopLayer(Rml::BlendMode blend_mode, Rml::Span 0); - fb_layers.insert(fb_layers.begin() + layers_size, Gfx::FramebufferData{fb_layers[layers_size - 1]}); - layers_size += 1; + return GetTopLayerHandle(); } void RenderInterface_GL3::RenderLayerStack::PopLayer() { RMLUI_ASSERT(layers_size > 0); layers_size -= 1; +} - // Only cloned framebuffers are removed. Other framebuffers remain for later re-use. - if (IsCloneOfBelow(layers_size)) - fb_layers.erase(fb_layers.begin() + layers_size); +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 fb_layers[layers_size - 1]; + return static_cast(layers_size - 1); } void RenderInterface_GL3::RenderLayerStack::SwapPostprocessPrimarySecondary() @@ -2123,13 +2107,6 @@ void RenderInterface_GL3::RenderLayerStack::DestroyFramebuffers() Gfx::DestroyFramebuffer(fb); } -bool RenderInterface_GL3::RenderLayerStack::IsCloneOfBelow(int layer_index) const -{ - const bool result = - (layer_index >= 1 && layer_index < (int)fb_layers.size() && fb_layers[layer_index].framebuffer == fb_layers[layer_index - 1].framebuffer); - return result; -} - const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::EnsureFramebufferPostprocess(int index) { RMLUI_ASSERT(index < (int)fb_postprocess.size()) diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 98adbc231..51f118ee4 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -78,8 +78,10 @@ class RenderInterface_GL3 : public Rml::RenderInterface { void SetTransform(const Rml::Matrix4f* transform) override; - void PushLayer(Rml::LayerFill layer_fill) override; - void PopLayer(Rml::BlendMode blend_mode, Rml::Span filters) 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(Rml::Vector2i dimensions) override; @@ -103,7 +105,7 @@ class RenderInterface_GL3 : public Rml::RenderInterface { int GetUniformLocation(UniformId uniform_id) const; void SubmitTransformUniform(Rml::Vector2f translation); - void BlitTopLayerToPostprocessPrimary(); + void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle); void RenderFilters(Rml::Span filter_handles); void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); @@ -144,15 +146,14 @@ class RenderInterface_GL3 : public Rml::RenderInterface { ~RenderLayerStack(); // Push a new layer. All references to previously retrieved layers are invalidated. - void PushLayer(); - - // Push a clone of the active layer. All references to previously retrieved layers are invalidated. - void PushLayerClone(); + 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); } @@ -166,7 +167,6 @@ class RenderInterface_GL3 : public Rml::RenderInterface { private: void DestroyFramebuffers(); - bool IsCloneOfBelow(int layer_index) const; const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index); int width = 0, height = 0; diff --git a/Include/RmlUi/Core/RenderInterface.h b/Include/RmlUi/Core/RenderInterface.h index 0538983e3..21482ade8 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -41,16 +41,9 @@ enum class ClipMaskOperation { SetInverse, // Set the clip mask to the area *outside* the rendered geometry, clearing any existing clip mask. Intersect, // Intersect the clip mask with the area of the rendered geometry. }; -enum class LayerFill { - None, // No operation necessary, does not care about the layer color. - Clear, // Clear the layer to transparent black. - Copy, // Copy the color data from the previous layer. - Link, // Link the color data with the previous layer. -}; enum class BlendMode { Blend, // Normal alpha blending. Replace, // Replace the destination colors from the source. - Discard, // Leave the destination colors unaltered. }; /** @@ -119,7 +112,8 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @param[in] geometry The compiled geometry to render. /// @param[in] translation The translation to apply to the geometry. /// @note When enabled, the clip mask should hide any rendered contents outside the area of the mask. - /// @note The clip mask applies to all other functions that render with a geometry handle, and only those. + /// @note The clip mask applies exclusively to all other functions that render with a geometry handle, in addition + /// to the layer compositing function while rendering to its destination. virtual void RenderToClipMask(ClipMaskOperation operation, CompiledGeometryHandle geometry, Vector2f translation); /// Called by RmlUi when it wants the renderer to use a new transform matrix. @@ -129,13 +123,19 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { /// @note The transform applies to all functions that render with a geometry handle, and only those. virtual void SetTransform(const Matrix4f* transform); - /// Called by RmlUi when it wants to push a new layer onto the render stack. - /// @param[in] layer_fill Specifies how the color data of the new layer should be filled. - virtual void PushLayer(LayerFill layer_fill); - /// Called by RmlUi when it wants to pop the render layer stack, after applying filters to the top layer and blending it into the layer below. - /// @param[in] blend_mode The mode used to blend the top layer into the one below. - /// @param[in] filters A list of compiled filters which should be applied to the top layer before blending. - virtual void PopLayer(BlendMode blend_mode, Span filters); + /// Called by RmlUi when it wants to push a new layer onto the render stack, setting it as the new render target. + /// @return An application-specified handle representing the new layer. The value 'zero' is reserved for the initial base layer. + /// @note The new layer should be initialized to transparent black within the current scissor region. + virtual LayerHandle PushLayer(); + /// Composite two layers with the given blend mode and apply filters. + /// @param[in] source The source layer. + /// @param[in] destination The destination layer. + /// @param[in] blend_mode The mode used to blend the source layer onto the destination layer. + /// @param[in] filters A list of compiled filters which should be applied before blending. + /// @note Source and destination can reference the same layer. + virtual void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + /// Called by RmlUi when it wants to pop the render layer stack, setting the new top layer as the render target. + virtual void PopLayer(); /// Called by RmlUi when it wants to store the current layer as a new texture to be rendered later with geometry. /// @param[in] dimensions The dimensions of the texture, to be copied from the top-left part of the viewport. @@ -143,7 +143,7 @@ class RMLUICORE_API RenderInterface : public NonCopyMoveable { virtual TextureHandle SaveLayerAsTexture(Vector2i dimensions); /// Called by RmlUi when it wants to store the current layer as a mask image, to be applied later as a filter. - /// @return An application-specified handle to a new filter representng the stored mask image. + /// @return An application-specified handle to a new filter representing the stored mask image. virtual CompiledFilterHandle SaveLayerAsMaskImage(); /// Called by RmlUi when it wants to compile a new filter. diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 594b50007..25ea3b1a9 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -103,8 +103,12 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); CompiledShader CompileShader(const String& name, const Dictionary& parameters); - void PushLayer(LayerFill layer_fill); - void PopLayer(BlendMode blend_mode, Span filters); + LayerHandle PushLayer(); + void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + void PopLayer(); + + LayerHandle GetTopLayer() const; + LayerHandle GetNextLayer() const; CompiledFilter SaveLayerAsMaskImage(); @@ -142,6 +146,8 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { RenderState state; Vector2i viewport_dimensions; + Vector render_stack; + friend class RenderManagerAccess; }; diff --git a/Include/RmlUi/Core/StableVector.h b/Include/RmlUi/Core/StableVector.h index 1e463932d..92b1d7855 100644 --- a/Include/RmlUi/Core/StableVector.h +++ b/Include/RmlUi/Core/StableVector.h @@ -40,8 +40,9 @@ namespace Rml { A vector-like container that returns stable indices to refer to entries. The indices are only invalidated when the element is erased. Pointers on the other hand are invalidated just like for a - vector. The container is implemented as a vector with a separate bit mask to track free slots. For simplicity, freed - slots are simply replaced with value-initialized elements. + vector. The container is implemented as a vector with a separate bit mask to track free slots. + + @note For simplicity, freed slots are simply replaced with value-initialized elements instead of being destroyed. */ template class StableVector { diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index a45287910..a310249cb 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -110,6 +110,7 @@ using CompiledShaderHandle = uintptr_t; using DecoratorDataHandle = uintptr_t; using FontFaceHandle = uintptr_t; using FontEffectsHandle = uintptr_t; +using LayerHandle = uintptr_t; using ElementPtr = UniqueReleaserPtr; using ContextPtr = UniqueReleaserPtr; diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementDecoration.cpp index d8c7a9a53..fd62e88fe 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementDecoration.cpp @@ -269,13 +269,45 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) render_manager->SetScissorRegion(Rectanglei(filter_region)); }; - if (!filters.empty() || !mask_images.empty()) + if (render_stage == RenderStage::Enter) { - if (render_stage == RenderStage::Enter) + const LayerHandle backdrop_source_layer = render_manager->GetTopLayer(); + + if (!filters.empty() || !mask_images.empty()) { - render_manager->PushLayer(backdrop_filters.empty() ? LayerFill::Clear : LayerFill::Copy); + render_manager->PushLayer(); } - else if (render_stage == RenderStage::Exit) + + if (!backdrop_filters.empty()) + { + const LayerHandle backdrop_destination_layer = render_manager->GetTopLayer(); + + // @performance We strictly only need this temporary buffer when having to read from outside the element + // boundaries, which currently only applies to blur and drop-shadow. Alternatively, we could avoid this + // completely if we introduced a render interface API concept of different input and output clipping. That + // is, we set a large input scissor to cover all input data, which can be used e.g. during blurring, and use + // our small border-area-only clipping region for the layers composite output. + ApplyScissorRegionForBackdrop(); + render_manager->PushLayer(); + const LayerHandle backdrop_temp_layer = render_manager->GetTopLayer(); + + FilterHandleList filter_handles; + for (auto& filter : backdrop_filters) + filter.compiled.AddHandleTo(filter_handles); + + // Render the backdrop filters in the extended scissor region including any ink overflow. + render_manager->CompositeLayers(backdrop_source_layer, backdrop_temp_layer, BlendMode::Blend, filter_handles); + + // Then composite the filter output to our destination while applying our clipping region, including any border-radius. + ApplyClippingRegion(PropertyId::BackdropFilter); + render_manager->CompositeLayers(backdrop_temp_layer, backdrop_destination_layer, BlendMode::Blend, {}); + render_manager->PopLayer(); + render_manager->SetScissorRegion(initial_scissor_region); + } + } + else if (render_stage == RenderStage::Exit) + { + if (!filters.empty() || !mask_images.empty()) { ApplyClippingRegion(PropertyId::Filter); @@ -288,7 +320,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) if (!mask_images.empty()) { - render_manager->PushLayer(LayerFill::Clear); + render_manager->PushLayer(); for (int i = (int)mask_images.size() - 1; i >= 0; i--) { @@ -298,29 +330,11 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) } mask_image_filter = render_manager->SaveLayerAsMaskImage(); mask_image_filter.AddHandleTo(filter_handles); - render_manager->PopLayer(BlendMode::Discard, {}); + render_manager->PopLayer(); } - render_manager->PopLayer(BlendMode::Blend, filter_handles); - render_manager->SetScissorRegion(initial_scissor_region); - } - } - - if (!backdrop_filters.empty()) - { - if (render_stage == RenderStage::Enter) - { - ApplyScissorRegionForBackdrop(); - render_manager->PushLayer(LayerFill::Copy); - render_manager->PushLayer(LayerFill::Link); - - FilterHandleList filter_handles; - for (auto& filter : backdrop_filters) - filter.compiled.AddHandleTo(filter_handles); - - render_manager->PopLayer(BlendMode::Replace, filter_handles); - ApplyClippingRegion(PropertyId::BackdropFilter); - render_manager->PopLayer(BlendMode::Blend, {}); + render_manager->CompositeLayers(render_manager->GetTopLayer(), render_manager->GetNextLayer(), BlendMode::Blend, filter_handles); + render_manager->PopLayer(); render_manager->SetScissorRegion(initial_scissor_region); } } diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index c83b5de54..7d9bc4dbc 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -124,7 +124,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& render_manager.ResetState(); render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); - render_manager.PushLayer(LayerFill::Clear); + render_manager.PushLayer(); background_border_geometry.Render(element_offset_in_texture); @@ -174,7 +174,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& { blur = render_manager.CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); if (blur) - render_manager.PushLayer(LayerFill::Clear); + render_manager.PushLayer(); } Geometry geometry_shadow = render_manager.MakeGeometry(std::move(mesh_shadow)); @@ -204,14 +204,15 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& { FilterHandleList filters; blur.AddHandleTo(filters); - render_manager.PopLayer(BlendMode::Blend, filters); + render_manager.CompositeLayers(render_manager.GetTopLayer(), render_manager.GetNextLayer(), BlendMode::Blend, filters); + render_manager.PopLayer(); blur.Release(); } } texture_interface.SaveLayerAsTexture(texture_dimensions); - render_manager.PopLayer(BlendMode::Discard, {}); + render_manager.PopLayer(); render_manager.SetState(initial_render_state); return true; diff --git a/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index abd1a8793..5c12f4c58 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -51,9 +51,16 @@ void RenderInterface::RenderToClipMask(ClipMaskOperation /*operation*/, Compiled void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} -void RenderInterface::PushLayer(LayerFill /*layer_fill*/) {} +LayerHandle RenderInterface::PushLayer() +{ + return {}; +} + +void RenderInterface::CompositeLayers(LayerHandle /*source*/, LayerHandle /*destination*/, BlendMode /*blend_mode*/, + Span /*filters*/) +{} -void RenderInterface::PopLayer(BlendMode /*blend_mode*/, Span /*filters*/) {} +void RenderInterface::PopLayer() {} TextureHandle RenderInterface::SaveLayerAsTexture(Vector2i /*dimensions*/) { diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index 0844a6069..832bf1546 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -75,6 +75,7 @@ void RenderManager::PrepareRender() RMLUI_ASSERT(state.clip_mask_list == default_state.clip_mask_list); RMLUI_ASSERT(state.scissor_region == default_state.scissor_region); RMLUI_ASSERT(state.transform == default_state.transform); + RMLUI_ASSERTMSG(render_stack.empty(), "Unbalanced render stack detected, ensure every PushLayer call has a corresponding call to PopLayer."); #endif } @@ -295,14 +296,36 @@ CompiledShader RenderManager::CompileShader(const String& name, const Dictionary return CompiledShader(); } -void RenderManager::PushLayer(LayerFill layer_fill) +LayerHandle RenderManager::PushLayer() { - render_interface->PushLayer(layer_fill); + const LayerHandle layer = render_interface->PushLayer(); + render_stack.push_back(layer); + return layer; } -void RenderManager::PopLayer(BlendMode blend_mode, Span filters) +void RenderManager::CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters) { - render_interface->PopLayer(blend_mode, filters); + RMLUI_ASSERT(source == 0 || std::find(render_stack.begin(), render_stack.end(), source) != render_stack.end()); + RMLUI_ASSERT(destination == 0 || std::find(render_stack.begin(), render_stack.end(), destination) != render_stack.end()); + render_interface->CompositeLayers(source, destination, blend_mode, filters); +} + +void RenderManager::PopLayer() +{ + RMLUI_ASSERT(!render_stack.empty()); + render_interface->PopLayer(); + render_stack.pop_back(); +} + +LayerHandle RenderManager::GetTopLayer() const +{ + return render_stack.empty() ? LayerHandle{} : render_stack.back(); +} + +LayerHandle RenderManager::GetNextLayer() const +{ + RMLUI_ASSERT(!render_stack.empty()); + return render_stack.size() < 2 ? LayerHandle{} : render_stack[render_stack.size() - 2]; } CompiledFilter RenderManager::SaveLayerAsMaskImage() diff --git a/Tests/Data/VisualTests/filter_blur.rml b/Tests/Data/VisualTests/filter_blur_area.rml similarity index 92% rename from Tests/Data/VisualTests/filter_blur.rml rename to Tests/Data/VisualTests/filter_blur_area.rml index 97f63331d..4d53b8261 100644 --- a/Tests/Data/VisualTests/filter_blur.rml +++ b/Tests/Data/VisualTests/filter_blur_area.rml @@ -1,6 +1,6 @@ - Filter: blur + Filter: blur area @@ -16,6 +16,7 @@ display: block; cursor: move; background: transparent; + box-sizing: border-box; } .background { width: 512dp; @@ -27,7 +28,7 @@ .blur { width: 300dp; height: 300dp; - border-radius: 50dp; + border-radius: 30dp; margin: auto; backdrop-filter: blur(50dp); } diff --git a/Tests/Data/VisualTests/filter_blur_orb.rml b/Tests/Data/VisualTests/filter_blur_orb.rml new file mode 100644 index 000000000..467d54971 --- /dev/null +++ b/Tests/Data/VisualTests/filter_blur_orb.rml @@ -0,0 +1,44 @@ + + + Filter: blur orb + + + + + + + + + + diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index 157a7519c..15ac04abb 100644 --- a/Tests/Source/UnitTests/Core.cpp +++ b/Tests/Source/UnitTests/Core.cpp @@ -155,7 +155,7 @@ TEST_CASE("core.release_resources") CHECK(counters.load_texture == startup_counters.load_texture); CHECK(counters.generate_texture == startup_counters.generate_texture); CHECK(counters.release_texture == startup_counters.generate_texture + startup_counters.load_texture); - const int num_released_textures = counters.release_texture; + const size_t num_released_textures = counters.release_texture; // By doing a new context Update+Render the textures should be loaded again. TestsShell::RenderLoop(); From c93dbc0696de37f4c449a81f3fa3237171ae280e Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Tue, 26 Mar 2024 18:42:26 +0100 Subject: [PATCH 79/80] Fix bad rendering in Emscripten, add sample assets as link dependencies --- Backends/RmlUi_Renderer_GL3.cpp | 8 +++++++- CMakeLists.txt | 15 +++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 50967fe8a..9184b0629 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1082,7 +1082,13 @@ void RenderInterface_GL3::SetScissor(Rml::Rectanglei region, bool vertically_fli region = VerticallyFlipped(region, viewport_height); if (region.Valid() && region != scissor_state) - glScissor(region.Left(), viewport_height - region.Bottom(), region.Width(), region.Height()); + { + // 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; diff --git a/CMakeLists.txt b/CMakeLists.txt index fde29a27f..9af1e26e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -802,7 +802,7 @@ if(BUILD_SAMPLES OR BUILD_TESTING) if(SAMPLES_BACKEND MATCHES "GL3$") message("-- Adding OpenGL 3 renderer backend.") if(EMSCRIPTEN) - set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} -sMAX_WEBGL_VERSION=2") + set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2") else() find_package(OpenGL REQUIRED) target_include_directories(shell PRIVATE ${OPENGL_INCLUDE_DIR}) @@ -867,10 +867,11 @@ if(BUILD_SAMPLES) # Add assets to emscripten binaries if(EMSCRIPTEN) - message("-- Preloading emscipten sample assets") + message("-- Preloading emscripten sample assets") set(COMMON_ASSET_FOLDER "Samples/assets/") set(EMSCRIPTEN_EXE_FLAGS "${EMSCRIPTEN_EXE_FLAGS} --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${COMMON_ASSET_FOLDER}@/${COMMON_ASSET_FOLDER}") + file(GLOB COMMON_ASSET_FILES "${COMMON_ASSET_FOLDER}*") foreach(sample ${samples}) set(SAMPLE_DATA_FOLDER "Samples/basic/${sample}/data/") @@ -878,6 +879,8 @@ if(BUILD_SAMPLES) if(EXISTS ${ABS_SAMPLE_DATA_FOLDER}) target_link_libraries(${sample} "--preload-file ${ABS_SAMPLE_DATA_FOLDER}@/${SAMPLE_DATA_FOLDER}") endif() + file(GLOB SAMPLE_DATA_FILES "${SAMPLE_DATA_FOLDER}*") + set_target_properties(${sample} PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${SAMPLE_DATA_FILES}") endforeach() foreach(tutorial ${tutorials}) @@ -886,10 +889,14 @@ if(BUILD_SAMPLES) if(EXISTS ${ABS_TUTORIAL_DATA_FOLDER}) target_link_libraries("tutorial_${tutorial}" "--preload-file ${ABS_TUTORIAL_DATA_FOLDER}@/${TUTORIAL_DATA_FOLDER}") endif() + file(GLOB TUTORIAL_DATA_FILES "${TUTORIAL_DATA_FOLDER}*") + set_target_properties(${sample} PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${TUTORIAL_DATA_FILES}") endforeach() - set(INVADER_DATA_FOLDER "Samples/invaders/data/") - target_link_libraries(invaders "-sALLOW_MEMORY_GROWTH --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${INVADER_DATA_FOLDER}@/${INVADER_DATA_FOLDER}") + set(INVADERS_DATA_FOLDER "Samples/invaders/data/") + target_link_libraries(invaders "-sALLOW_MEMORY_GROWTH --preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${INVADERS_DATA_FOLDER}@/${INVADERS_DATA_FOLDER}") + file(GLOB INVADERS_DATA_FILES "${INVADERS_DATA_FOLDER}*") + set_target_properties(invaders PROPERTIES LINK_DEPENDS "${COMMON_ASSET_FILES};${INVADERS_DATA_FILES}") endif() endif() From 37cf4d3e7f8b338b33e693d65fa57e21d8b92f90 Mon Sep 17 00:00:00 2001 From: Michael Ragazzon Date: Wed, 27 Mar 2024 00:18:49 +0100 Subject: [PATCH 80/80] Refactor ElementDecoration to ElementEffects --- CMake/FileList.cmake | 4 +- Include/RmlUi/Core/EffectSpecification.h | 11 ++--- Include/RmlUi/Core/Element.h | 3 -- Include/RmlUi/Core/PropertySpecification.h | 2 +- Source/Core/Element.cpp | 35 +++++++-------- ...ementDecoration.cpp => ElementEffects.cpp} | 42 +++++++++--------- .../{ElementDecoration.h => ElementEffects.h} | 44 ++++++++----------- Source/Core/ElementStyle.cpp | 1 - Source/Core/ElementUtilities.cpp | 2 +- 9 files changed, 65 insertions(+), 79 deletions(-) rename Source/Core/{ElementDecoration.cpp => ElementEffects.cpp} (93%) rename Source/Core/{ElementDecoration.h => ElementEffects.h} (69%) diff --git a/CMake/FileList.cmake b/CMake/FileList.cmake index cfb5c5c1a..96253b93e 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -21,8 +21,8 @@ set(Core_HDR_FILES ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.h - ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.h + ${PROJECT_SOURCE_DIR}/Source/Core/ElementEffects.h ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.h ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementImage.h ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementLabel.h @@ -277,9 +277,9 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementDocument.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/ElementEffects.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.cpp ${PROJECT_SOURCE_DIR}/Source/Core/ElementInstancer.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Elements/ElementForm.cpp diff --git a/Include/RmlUi/Core/EffectSpecification.h b/Include/RmlUi/Core/EffectSpecification.h index fde117e66..84b8340f0 100644 --- a/Include/RmlUi/Core/EffectSpecification.h +++ b/Include/RmlUi/Core/EffectSpecification.h @@ -47,18 +47,19 @@ class RMLUICORE_API EffectSpecification { protected: ~EffectSpecification(); - /// Registers a property for the decorator. + /// Registers a property for the effect. /// @param[in] property_name The name of the new property (how it is specified through RCSS). /// @param[in] default_value The default value to be used. /// @return The new property definition, ready to have parsers attached. PropertyDefinition& RegisterProperty(const String& property_name, const String& default_value); - /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' to parse anonymous decorators. + /// Registers a shorthand property definition. Specify a shorthand name of 'decorator' or 'filter' to parse + /// anonymous decorators or filters, respectively. /// @param[in] shorthand_name The name to register the new shorthand property under. - /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in which they are specified here is - /// the order in which the values will be processed. + /// @param[in] properties A comma-separated list of the properties this definition is shorthand for. The order in + /// which they are specified here is the order in which the values will be processed. /// @param[in] type The type of shorthand to declare. - /// @param True if all the property names exist, false otherwise. + /// @return An ID for the new shorthand, or 'Invalid' if the shorthand declaration is invalid. ShorthandId RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type); private: diff --git a/Include/RmlUi/Core/Element.h b/Include/RmlUi/Core/Element.h index 42830a03b..0f60fed35 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -51,7 +51,6 @@ class ElementInstancer; class EventDispatcher; class EventListener; class ElementBackgroundBorder; -class ElementDecoration; class ElementDefinition; class ElementDocument; class ElementScroll; @@ -578,8 +577,6 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr attribute_event_listeners; EventDispatcher event_dispatcher; ElementStyle style; ElementBackgroundBorder background_border; - ElementDecoration decoration; + ElementEffects effects; ElementScroll scroll; Style::ComputedValues computed_values; }; @@ -177,7 +177,7 @@ void Element::Update(float dp_ratio, Vector2f vp_dimensions) UpdateProperties(dp_ratio, vp_dimensions); } - meta->decoration.InstanceDecorators(); + meta->effects.InstanceEffects(); for (size_t i = 0; i < children.size(); i++) children[i]->Update(dp_ratio, vp_dimensions); @@ -233,13 +233,13 @@ void Element::Render() // Apply our transform ElementUtilities::ApplyTransform(*this); - meta->decoration.RenderDecorators(RenderStage::Enter); + meta->effects.RenderEffects(RenderStage::Enter); // Set up the clipping region for this element. if (ElementUtilities::SetClippingRegion(this)) { meta->background_border.Render(this); - meta->decoration.RenderDecorators(RenderStage::Decoration); + meta->effects.RenderEffects(RenderStage::Decoration); { RMLUI_ZoneScopedNC("OnRender", 0x228B22); @@ -252,7 +252,7 @@ void Element::Render() for (Element* element : stacking_context) element->Render(); - meta->decoration.RenderDecorators(RenderStage::Exit); + meta->effects.RenderEffects(RenderStage::Exit); } ElementPtr Element::Clone() const @@ -452,7 +452,7 @@ void Element::SetBox(const Box& box) meta->background_border.DirtyBackground(); meta->background_border.DirtyBorder(); - meta->decoration.DirtyDecoratorsData(); + meta->effects.DirtyEffectsData(); } } @@ -464,7 +464,7 @@ void Element::AddBox(const Box& box, Vector2f offset) meta->background_border.DirtyBackground(); meta->background_border.DirtyBorder(); - meta->decoration.DirtyDecoratorsData(); + meta->effects.DirtyEffectsData(); } const Box& Element::GetBox() @@ -1553,11 +1553,6 @@ ElementBackgroundBorder* Element::GetElementBackgroundBorder() const return &meta->background_border; } -ElementDecoration* Element::GetElementDecoration() const -{ - return &meta->decoration; -} - ElementScroll* Element::GetElementScroll() const { return &meta->scroll; @@ -1795,18 +1790,18 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) meta->background_border.DirtyBorder(); } - // Dirty the decoration if it's changed. + // Dirty the effects if they've changed. if (border_radius_changed || filter_or_mask_changed || changed_properties.Contains(PropertyId::Decorator)) { - meta->decoration.DirtyDecorators(); + meta->effects.DirtyEffects(); } - // Dirty the decoration data when its visual looks may have changed. + // Dirty the effects data when their visual looks may have changed. if (border_radius_changed || // changed_properties.Contains(PropertyId::Opacity) || // changed_properties.Contains(PropertyId::ImageColor)) { - meta->decoration.DirtyDecoratorsData(); + meta->effects.DirtyEffectsData(); } // Check for `perspective' and `perspective-origin' changes @@ -2813,7 +2808,7 @@ void Element::UpdateTransformState() void Element::OnStyleSheetChangeRecursive() { - GetElementDecoration()->DirtyDecorators(); + meta->effects.DirtyEffects(); OnStyleSheetChange(); @@ -2825,7 +2820,7 @@ void Element::OnStyleSheetChangeRecursive() void Element::OnDpRatioChangeRecursive() { - GetElementDecoration()->DirtyDecorators(); + meta->effects.DirtyEffects(); GetStyle()->DirtyPropertiesWithUnits(Unit::DP_SCALABLE_LENGTH); OnDpRatioChange(); diff --git a/Source/Core/ElementDecoration.cpp b/Source/Core/ElementEffects.cpp similarity index 93% rename from Source/Core/ElementDecoration.cpp rename to Source/Core/ElementEffects.cpp index fd62e88fe..3c47d86ce 100644 --- a/Source/Core/ElementDecoration.cpp +++ b/Source/Core/ElementEffects.cpp @@ -26,7 +26,7 @@ * */ -#include "ElementDecoration.h" +#include "ElementEffects.h" #include "../../Include/RmlUi/Core/ComputedValues.h" #include "../../Include/RmlUi/Core/Decorator.h" #include "../../Include/RmlUi/Core/Element.h" @@ -38,23 +38,23 @@ namespace Rml { -ElementDecoration::ElementDecoration(Element* _element) : element(_element) {} +ElementEffects::ElementEffects(Element* _element) : element(_element) {} -ElementDecoration::~ElementDecoration() +ElementEffects::~ElementEffects() { - ReleaseDecorators(); + ReleaseEffects(); } -void ElementDecoration::InstanceDecorators() +void ElementEffects::InstanceEffects() { - if (!decorators_dirty) + if (!effects_dirty) return; - decorators_dirty = false; - decorators_data_dirty = true; + effects_dirty = false; + effects_data_dirty = true; RMLUI_ZoneScopedC(0xB22222); - ReleaseDecorators(); + ReleaseEffects(); RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) @@ -151,11 +151,11 @@ void ElementDecoration::InstanceDecorators() } } -void ElementDecoration::ReloadDecoratorsData() +void ElementEffects::ReloadEffectsData() { - if (decorators_data_dirty) + if (effects_data_dirty) { - decorators_data_dirty = false; + effects_data_dirty = false; bool decorator_data_failed = false; for (DecoratorEntryList* list : {&decorators, &mask_images}) @@ -190,7 +190,7 @@ void ElementDecoration::ReloadDecoratorsData() } } -void ElementDecoration::ReleaseDecorators() +void ElementEffects::ReleaseEffects() { for (DecoratorEntryList* list : {&decorators, &mask_images}) { @@ -206,10 +206,10 @@ void ElementDecoration::ReleaseDecorators() backdrop_filters.clear(); } -void ElementDecoration::RenderDecorators(RenderStage render_stage) +void ElementEffects::RenderEffects(RenderStage render_stage) { - InstanceDecorators(); - ReloadDecoratorsData(); + InstanceEffects(); + ReloadEffectsData(); if (!decorators.empty()) { @@ -286,7 +286,7 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) // boundaries, which currently only applies to blur and drop-shadow. Alternatively, we could avoid this // completely if we introduced a render interface API concept of different input and output clipping. That // is, we set a large input scissor to cover all input data, which can be used e.g. during blurring, and use - // our small border-area-only clipping region for the layers composite output. + // our small border-area-only clipping region for the composite layers output. ApplyScissorRegionForBackdrop(); render_manager->PushLayer(); const LayerHandle backdrop_temp_layer = render_manager->GetTopLayer(); @@ -340,14 +340,14 @@ void ElementDecoration::RenderDecorators(RenderStage render_stage) } } -void ElementDecoration::DirtyDecorators() +void ElementEffects::DirtyEffects() { - decorators_dirty = true; + effects_dirty = true; } -void ElementDecoration::DirtyDecoratorsData() +void ElementEffects::DirtyEffectsData() { - decorators_data_dirty = true; + effects_data_dirty = true; } } // namespace Rml diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementEffects.h similarity index 69% rename from Source/Core/ElementDecoration.h rename to Source/Core/ElementEffects.h index 636a23144..5420cdb06 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementEffects.h @@ -26,11 +26,10 @@ * */ -#ifndef RMLUI_CORE_ELEMENTDECORATION_H -#define RMLUI_CORE_ELEMENTDECORATION_H +#ifndef RMLUI_CORE_ELEMENTEFFECTS_H +#define RMLUI_CORE_ELEMENTEFFECTS_H #include "../../Include/RmlUi/Core/CompiledFilterShader.h" -#include "../../Include/RmlUi/Core/Filter.h" #include "../../Include/RmlUi/Core/Types.h" namespace Rml { @@ -38,37 +37,32 @@ namespace Rml { class Decorator; class Element; class Filter; -class CompiledFilter; enum class RenderStage { Enter, Decoration, Exit }; /** - Manages an elements decorator state - - @author Lloyd Weehuizen + Manages and renders an element's effects: decorators, filters, backdrop filters, and mask images. */ -class ElementDecoration { +class ElementEffects { public: - ElementDecoration(Element* element); - ~ElementDecoration(); + ElementEffects(Element* element); + ~ElementEffects(); - /// Instances decorators if necessary. - void InstanceDecorators(); + void InstanceEffects(); - /// Renders all appropriate decorators. - void RenderDecorators(RenderStage render_stage); + void RenderEffects(RenderStage render_stage); - /// Mark decorators as dirty and force them to reset themselves. - void DirtyDecorators(); - /// Mark the element data of decorators as dirty. - void DirtyDecoratorsData(); + // Mark effects as dirty and force them to reset themselves. + void DirtyEffects(); + // Mark the element data of effects as dirty. + void DirtyEffectsData(); private: - // Releases existing element data of decorators, and regenerates it. - void ReloadDecoratorsData(); - // Releases all existing decorators and frees their data. - void ReleaseDecorators(); + // Releases existing element data of effects, and regenerates it. + void ReloadEffectsData(); + // Releases all existing effects and their element data. + void ReleaseEffects(); struct DecoratorEntry { SharedPtr decorator; @@ -85,16 +79,16 @@ class ElementDecoration { Element* element; - // The list of decorators and filters used by this element from all style rules. + // The list of decorators and filters used by this element. DecoratorEntryList decorators; DecoratorEntryList mask_images; FilterEntryList filters; FilterEntryList backdrop_filters; // If set, a full reload is necessary. - bool decorators_dirty = false; + bool effects_dirty = false; // If set, element data of all decorators need to be regenerated. - bool decorators_data_dirty = false; + bool effects_data_dirty = false; }; } // namespace Rml diff --git a/Source/Core/ElementStyle.cpp b/Source/Core/ElementStyle.cpp index e24197296..492c3202d 100644 --- a/Source/Core/ElementStyle.cpp +++ b/Source/Core/ElementStyle.cpp @@ -44,7 +44,6 @@ #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #include "../../Include/RmlUi/Core/TransformPrimitive.h" #include "ComputeProperty.h" -#include "ElementDecoration.h" #include "ElementDefinition.h" #include "PropertiesIterator.h" #include diff --git a/Source/Core/ElementUtilities.cpp b/Source/Core/ElementUtilities.cpp index f38f605ba..3126fa210 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -290,7 +290,7 @@ bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* elemen if (box_area == BoxArea::Auto) { // 'Auto' acts like border box extended to encompass any ink overflow, including the element's box-shadow. - // Note: Does not currently include ink overflow due to filters, as that is handled manually in ElementDecoration. + // Note: Does not currently include ink overflow due to filters, as that is handled manually in ElementEffects. box_area = BoxArea::Border; if (const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow))