diff --git a/.clang-format b/.clang-format index b2ebc88df..e0516a8ae 100644 --- a/.clang-format +++ b/.clang-format @@ -64,3 +64,4 @@ StatementMacros: - RMLUI_RTTI_DefineWithParent TabWidth: 4 UseTab: AlignWithSpaces +WhitespaceSensitiveMacros: ['RMLUI_STRINGIFY'] diff --git a/Backends/RmlUi_Backend_GLFW_GL3.cpp b/Backends/RmlUi_Backend_GLFW_GL3.cpp index e21f879c7..75745df63 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); @@ -184,8 +178,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_GLFW_VK.cpp b/Backends/RmlUi_Backend_GLFW_VK.cpp index 9af61a700..73cae3f69 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 85204f6d7..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 @@ -52,8 +53,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 RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override { SDL_Texture* sdl_texture = (SDL_Texture*)texture; if (sdl_texture) @@ -62,59 +62,66 @@ class RenderInterface_GL2_SDL : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation); + 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); - 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; + return {}; + + texture_dimensions = {surface->w, surface->h}; - 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); if (!converted_surface) - return false; + return {}; 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); SDL_FreeSurface(surface); - 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; @@ -128,13 +135,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); } @@ -190,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; } } @@ -215,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 e0fed6c1d..a67204506 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 #include @@ -53,12 +54,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); @@ -69,38 +70,42 @@ 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()); + if (!surface) + return {}; - bool success = false; - if (surface) + texture_dimensions = {surface->w, surface->h}; + + if (surface->format->format != SDL_PIXELFORMAT_RGBA32) { - texture_dimensions.x = surface->w; - texture_dimensions.y = surface->h; + // 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); - if (surface->format->format != SDL_PIXELFORMAT_RGBA32) - { - SDL_SetSurfaceAlphaMod(surface, SDL_ALPHA_OPAQUE); - SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE); + if (!converted_surface) + return {}; - SDL_Surface* new_surface = SDL_CreateRGBSurfaceWithFormat(0, surface->w, surface->h, 32, SDL_PIXELFORMAT_RGBA32); - if (!new_surface) - return false; + surface = converted_surface; + } - if (SDL_BlitSurface(surface, 0, new_surface, 0) != 0) - return false; + // 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 (size_t i = 0; i < pixels_byte_size; i += 4) + { + const byte alpha = pixels[i + 3]; + for (size_t j = 0; j < 3; ++j) + pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255); + } - SDL_FreeSurface(surface); - surface = new_surface; - } + Rml::TextureHandle texture_handle = RenderInterface_GL3::GenerateTexture({pixels, pixels_byte_size}, texture_dimensions); - success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions); - SDL_FreeSurface(surface); - } + SDL_FreeSurface(surface); - return success; + return texture_handle; } }; @@ -144,30 +149,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; - } + Rml::Log::Message(Rml::Log::LT_ERROR, "SDL error on create window: %s", SDL_GetError()); + return false; } SDL_GLContext glcontext = SDL_GL_CreateContext(window); @@ -176,7 +166,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; } @@ -185,7 +175,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_SFML_GL2.cpp b/Backends/RmlUi_Backend_SFML_GL2.cpp index 08060370e..5d9dbb0ae 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 RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override { if (texture) { @@ -55,10 +54,10 @@ class RenderInterface_GL2_SFML : public RenderInterface_GL2 { texture = RenderInterface_GL2::TextureEnableWithoutBinding; } - RenderInterface_GL2::RenderGeometry(vertices, num_vertices, indices, num_indices, texture, translation); + 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); @@ -69,31 +68,42 @@ 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_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y); + 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); @@ -104,10 +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_Backend_Win32_GL2.cpp b/Backends/RmlUi_Backend_Win32_GL2.cpp index 246dafee9..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. @@ -193,7 +189,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) @@ -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 cdad95db7..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; @@ -195,7 +191,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) @@ -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 c23dba10b..795deff30 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 @@ -60,7 +61,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; @@ -141,7 +142,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/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..0cd2f45d4 --- /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, window, xpos, ypos, data->glfw_active_modifiers); + }); + + glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { + data->glfw_active_modifiers = mods; + RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); + }); + + glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { + RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); + }); + + // Window events + glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { + data->render_interface.SetViewport(width, height); + RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); + }); + + glfwSetWindowContentScaleCallback(window, + [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); +} 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..6aad6776c --- /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, window, xpos, ypos, data->glfw_active_modifiers); + }); + + glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { + data->glfw_active_modifiers = mods; + RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); + }); + + glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { + RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); + }); + + // Window events + glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { + data->render_interface.SetViewport(width, height); + RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); + }); + + glfwSetWindowContentScaleCallback(window, + [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); +} 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/Backends/RmlUi_Renderer_GL2.cpp b/Backends/RmlUi_Renderer_GL2.cpp index 437da970e..c06bd383f 100644 --- a/Backends/RmlUi_Renderer_GL2.cpp +++ b/Backends/RmlUi_Renderer_GL2.cpp @@ -67,7 +67,12 @@ 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); + + 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); @@ -85,13 +90,28 @@ 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, - const Rml::Vector2f& translation) +Rml::CompiledGeometryHandle RenderInterface_GL2::CompileGeometry(Rml::Span vertices, Rml::Span indices) +{ + GeometryView* data = new GeometryView{vertices, indices}; + return reinterpret_cast(data); +} + +void RenderInterface_GL2::ReleaseGeometry(Rml::CompiledGeometryHandle geometry) { + delete reinterpret_cast(geometry); +} + +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.data(); + const int* indices = geometry->indices.data(); + const int num_indices = (int)geometry->indices.size(); + glPushMatrix(); glTranslatef(translation.x, translation.y, 0); @@ -122,62 +142,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) +void RenderInterface_GL2::SetScissorRegion(Rml::Rectanglei region) { - if (!transform_enabled) - { - 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) +{ + 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); + } + + 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)); - // 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)); + switch (operation) + { + case ClipMaskOperation::Set: + { + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + stencil_test_value = 1; } + break; + case ClipMaskOperation::SetInverse: + { + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + stencil_test_value = 0; + } + break; + case ClipMaskOperation::Intersect: + { + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + stencil_test_value += 1; + } + break; + } + + RenderGeometry(geometry, translation, {}); + + // Restore state + // @performance Cache state so we don't toggle it unnecessarily. + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_EQUAL, stencil_test_value, GLuint(-1)); } // Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file @@ -199,7 +228,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); @@ -219,20 +248,20 @@ 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 + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); - delete[] buffer; return false; } @@ -240,25 +269,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; @@ -270,36 +304,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); - - delete[] image_dest; - delete[] buffer; - - 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_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - 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 08c403376..c26e4c4c0 100644 --- a/Backends/RmlUi_Renderer_GL2.h +++ b/Backends/RmlUi_Renderer_GL2.h @@ -47,15 +47,19 @@ 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::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; - 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 operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; void SetTransform(const Rml::Matrix4f* transform) override; @@ -63,6 +67,11 @@ class RenderInterface_GL2 : public Rml::RenderInterface { static const Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); private: + struct GeometryView { + Rml::Span vertices; + Rml::Span indices; + }; + int viewport_width = 0; int viewport_height = 0; bool transform_enabled = false; diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index b143fe3d4..12f015f9e 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -28,9 +28,13 @@ #include "RmlUi_Renderer_GL3.h" #include +#include #include +#include #include +#include #include +#include #include #if defined(RMLUI_PLATFORM_WIN32) && !defined(__MINGW32__) @@ -41,18 +45,31 @@ #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"( +// 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 MAX_NUM_STOPS 16 +#define BLUR_SIZE 7 +#define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2) + +#define RMLUI_STRINGIFY_IMPL(x) #x +#define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x) + +#define RMLUI_SHADER_HEADER \ + RMLUI_SHADER_HEADER_VERSION "#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n#line " RMLUI_STRINGIFY(__LINE__) "\n" + +static const char* shader_vert_main = RMLUI_SHADER_HEADER R"( uniform vec2 _translate; uniform mat4 _transform; @@ -67,14 +84,13 @@ void main() { fragTexCoord = inTexCoord0; fragColor = inColor0; - vec2 translatedPos = inPosition + _translate.xy; - vec4 outPos = _transform * vec4(translatedPos, 0, 1); + 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 +102,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,34 +113,374 @@ 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 +#define CONIC 2 +#define REPEATING_LINEAR 3 +#define REPEATING_RADIAL 4 +#define REPEATING_CONIC 5 +#define PI 3.14159265 + +uniform int _func; // one of the above definitions +uniform vec2 _p; // linear: starting point, radial: center, conic: center +uniform vec2 _v; // linear: vector to ending point, radial: 2d curvature (inverse radius), conic: angled unit vector +uniform vec4 _stop_colors[MAX_NUM_STOPS]; +uniform float _stop_positions[MAX_NUM_STOPS]; // normalized, 0 -> starting point, 1 -> ending point +uniform int _num_stops; + +in vec2 fragTexCoord; +in vec4 fragColor; +out vec4 finalColor; + +vec4 mix_stop_colors(float t) { + vec4 color = _stop_colors[0]; + + for (int i = 1; i < _num_stops; i++) + color = mix(color, _stop_colors[i], smoothstep(_stop_positions[i-1], _stop_positions[i], t)); + + return color; +} + +void main() { + float t = 0.0; + + if (_func == LINEAR || _func == REPEATING_LINEAR) + { + float dist_square = dot(_v, _v); + vec2 V = fragTexCoord - _p; + t = dot(_v, V) / dist_square; + } + else if (_func == RADIAL || _func == REPEATING_RADIAL) + { + vec2 V = fragTexCoord - _p; + t = length(_v * V); + } + else if (_func == CONIC || _func == REPEATING_CONIC) + { + mat2 R = mat2(_v.x, -_v.y, _v.y, _v.x); + vec2 V = R * (fragTexCoord - _p); + t = 0.5 + atan(-V.x, V.y) / (2.0 * PI); + } + + if (_func == REPEATING_LINEAR || _func == REPEATING_RADIAL || _func == REPEATING_CONIC) + { + float t0 = _stop_positions[0]; + float t1 = _stop_positions[_num_stops - 1]; + t = t0 + mod(t - t0, t1 - t0); + } + + finalColor = fragColor * mix_stop_colors(t); +} +)"; + +// "Creation" by Danilo Guanabara, based on: https://www.shadertoy.com/view/XsXXDn +static const char* shader_frag_creation = RMLUI_SHADER_HEADER R"( +uniform float _value; +uniform vec2 _dimensions; + +in vec2 fragTexCoord; +in vec4 fragColor; +out vec4 finalColor; + +void main() { + float t = _value; + vec3 c; + float l; + for (int i = 0; i < 3; i++) { + vec2 p = fragTexCoord; + vec2 uv = p; + p -= .5; + p.x *= _dimensions.x / _dimensions.y; + float z = t + float(i) * .07; + l = length(p); + uv += p / l * (sin(z) + 1.) * abs(sin(l * 9. - z - z)); + c[i] = .01 / length(mod(uv, 1.) - .5); + } + finalColor = vec4(c / l, fragColor.a); +} +)"; + +static const char* shader_vert_passthrough = RMLUI_SHADER_HEADER R"( +in vec2 inPosition; +in vec2 inTexCoord0; + +out vec2 fragTexCoord; + +void main() { + fragTexCoord = inTexCoord0; + gl_Position = vec4(inPosition, 0.0, 1.0); +} +)"; +static const char* shader_frag_passthrough = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + finalColor = texture(_tex, fragTexCoord); +} +)"; +static const char* shader_frag_color_matrix = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform mat4 _color_matrix; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + // The general case uses a 4x5 color matrix for full rgba transformation, plus a constant term with the last column. + // However, we only consider the case of rgb transformations. Thus, we could in principle use a 3x4 matrix, but we + // keep the alpha row for simplicity. + // In the general case we should do the matrix transformation in non-premultiplied space. However, without alpha + // transformations, we can do it directly in premultiplied space to avoid the extra division and multiplication + // steps. In this space, the constant term needs to be multiplied by the alpha value, instead of unity. + vec4 texColor = texture(_tex, fragTexCoord); + vec3 transformedColor = vec3(_color_matrix * texColor); + finalColor = vec4(transformedColor, texColor.a); +} +)"; +static const char* shader_frag_blend_mask = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform sampler2D _texMask; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + vec4 texColor = texture(_tex, fragTexCoord); + float maskAlpha = texture(_texMask, fragTexCoord).a; + finalColor = texColor * maskAlpha; +} +)"; + +#define RMLUI_SHADER_BLUR_HEADER \ + RMLUI_SHADER_HEADER "\n#define BLUR_SIZE " RMLUI_STRINGIFY(BLUR_SIZE) "\n#define BLUR_NUM_WEIGHTS " RMLUI_STRINGIFY(BLUR_NUM_WEIGHTS) + +static const char* shader_vert_blur = RMLUI_SHADER_BLUR_HEADER R"( +uniform vec2 _texelOffset; + +in vec3 inPosition; +in vec2 inTexCoord0; + +out vec2 fragTexCoord[BLUR_SIZE]; + +void main() { + for(int i = 0; i < BLUR_SIZE; i++) + fragTexCoord[i] = inTexCoord0 - float(i - BLUR_NUM_WEIGHTS + 1) * _texelOffset; + gl_Position = vec4(inPosition, 1.0); +} +)"; +static const char* shader_frag_blur = RMLUI_SHADER_BLUR_HEADER R"( +uniform sampler2D _tex; +uniform float _weights[BLUR_NUM_WEIGHTS]; +uniform vec2 _texCoordMin; +uniform vec2 _texCoordMax; + +in vec2 fragTexCoord[BLUR_SIZE]; +out vec4 finalColor; + +void main() { + vec4 color = vec4(0.0, 0.0, 0.0, 0.0); + for(int i = 0; i < BLUR_SIZE; i++) + { + vec2 in_region = step(_texCoordMin, fragTexCoord[i]) * step(fragTexCoord[i], _texCoordMax); + color += texture(_tex, fragTexCoord[i]) * in_region.x * in_region.y * _weights[abs(i - BLUR_NUM_WEIGHTS + 1)]; + } + finalColor = color; +} +)"; +static const char* shader_frag_drop_shadow = RMLUI_SHADER_HEADER R"( +uniform sampler2D _tex; +uniform vec2 _texCoordMin; +uniform vec2 _texCoordMax; +uniform vec4 _color; + +in vec2 fragTexCoord; +out vec4 finalColor; + +void main() { + vec2 in_region = step(_texCoordMin, fragTexCoord) * step(fragTexCoord, _texCoordMax); + finalColor = texture(_tex, fragTexCoord).a * in_region.x * in_region.y * _color; +} +)"; + +enum class ProgramId { + None, + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class VertShaderId { + Main, + Passthrough, + Blur, + Count, +}; +enum class FragShaderId { + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class UniformId { + Translate, + Transform, + Tex, + Color, + ColorMatrix, + TexelOffset, + TexCoordMin, + TexCoordMax, + TexMask, + Weights, + Func, + P, + V, + StopColors, + StopPositions, + NumStops, + Value, + Dimensions, + Count, +}; + namespace Gfx { -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", "_color_matrix", + "_texelOffset", "_texCoordMin", "_texCoordMax", "_texMask", "_weights[0]", "_func", "_p", "_v", "_stop_colors[0]", "_stop_positions[0]", + "_num_stops", "_value", "_dimensions"}; enum class VertexAttribute { Position, Color0, TexCoord0, Count }; static const char* const vertex_attribute_names[(size_t)VertexAttribute::Count] = {"inPosition", "inColor0", "inTexCoord0"}; +struct VertShaderDefinition { + VertShaderId id; + const char* name_str; + const char* code_str; +}; +struct FragShaderDefinition { + FragShaderId id; + const char* name_str; + const char* code_str; +}; +struct ProgramDefinition { + ProgramId id; + const char* name_str; + VertShaderId vert_shader; + FragShaderId frag_shader; +}; + +// clang-format off +static const VertShaderDefinition vert_shader_definitions[] = { + {VertShaderId::Main, "main", shader_vert_main}, + {VertShaderId::Passthrough, "passthrough", shader_vert_passthrough}, + {VertShaderId::Blur, "blur", shader_vert_blur}, +}; +static const FragShaderDefinition frag_shader_definitions[] = { + {FragShaderId::Color, "color", shader_frag_color}, + {FragShaderId::Texture, "texture", shader_frag_texture}, + {FragShaderId::Gradient, "gradient", shader_frag_gradient}, + {FragShaderId::Creation, "creation", shader_frag_creation}, + {FragShaderId::Passthrough, "passthrough", shader_frag_passthrough}, + {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix}, + {FragShaderId::BlendMask, "blend_mask", shader_frag_blend_mask}, + {FragShaderId::Blur, "blur", shader_frag_blur}, + {FragShaderId::DropShadow, "drop_shadow", shader_frag_drop_shadow}, +}; +static const ProgramDefinition program_definitions[] = { + {ProgramId::Color, "color", VertShaderId::Main, FragShaderId::Color}, + {ProgramId::Texture, "texture", VertShaderId::Main, FragShaderId::Texture}, + {ProgramId::Gradient, "gradient", VertShaderId::Main, FragShaderId::Gradient}, + {ProgramId::Creation, "creation", VertShaderId::Main, FragShaderId::Creation}, + {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, + {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, + {ProgramId::BlendMask, "blend_mask", VertShaderId::Passthrough, FragShaderId::BlendMask}, + {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, + {ProgramId::DropShadow, "drop_shadow", VertShaderId::Passthrough, FragShaderId::DropShadow}, +}; +// clang-format on + +template +class EnumArray { +public: + const T& operator[](Enum id) const + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + T& operator[](Enum id) + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + auto begin() const { return ids.begin(); } + auto end() const { return ids.end(); } + +private: + Rml::Array ids = {}; +}; + +using Programs = EnumArray; +using VertShaders = EnumArray; +using FragShaders = EnumArray; + +class Uniforms { +public: + GLint Get(ProgramId id, UniformId uniform) const + { + auto it = map.find(ToKey(id, uniform)); + if (it != map.end()) + return it->second; + return -1; + } + void Insert(ProgramId id, UniformId uniform, GLint location) { map[ToKey(id, uniform)] = location; } + +private: + using Key = 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 { - Rml::TextureHandle texture; GLuint vao; GLuint vbo; GLuint ibo; 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) { @@ -150,10 +506,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); @@ -169,29 +526,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); @@ -216,8 +568,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; @@ -234,20 +585,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 { @@ -261,83 +612,208 @@ 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); - main_vertex = CreateShader(GL_VERTEX_SHADER, shader_main_vertex); - if (!main_vertex) + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + 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); } - main_fragment_color = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_color); - if (!main_fragment_color) + else { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL shader: 'shader_main_fragment_color'."); - return false; + 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_texture = CreateShader(GL_FRAGMENT_SHADER, shader_main_fragment_texture); - if (!main_fragment_texture) + + // 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_texture'."); - 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); } - if (!CreateProgram(main_vertex, main_fragment_color, out_shaders.program_color)) + const GLuint framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) { - Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL program: 'program_color'."); + 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_texture, out_shaders.program_texture)) + + 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_texture'."); + RMLUI_ERRORMSG("Only framebuffers with color textures can be bound as textures. This framebuffer probably uses multisampling which needs a " + "blit step first."); + } + + glBindTexture(GL_TEXTURE_2D, fb.color_tex_buffer); +} + +static bool CreateShaders(ProgramData& data) +{ + RMLUI_ASSERT(std::all_of(data.vert_shaders.begin(), data.vert_shaders.end(), [](auto&& value) { return value == 0; })); + RMLUI_ASSERT(std::all_of(data.frag_shaders.begin(), data.frag_shaders.end(), [](auto&& value) { return value == 0; })); + RMLUI_ASSERT(std::all_of(data.programs.begin(), data.programs.end(), [](auto&& value) { return value == 0; })); + auto ReportError = [](const char* type, const char* name) { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create OpenGL %s: '%s'.", type, name); return false; + }; + + for (const VertShaderDefinition& def : vert_shader_definitions) + { + if (!CreateShader(data.vert_shaders[def.id], GL_VERTEX_SHADER, def.code_str)) + return ReportError("vertex shader", def.name_str); } + for (const FragShaderDefinition& def : frag_shader_definitions) + { + if (!CreateShader(data.frag_shaders[def.id], GL_FRAGMENT_SHADER, def.code_str)) + return ReportError("fragment shader", def.name_str); + } + + for (const ProgramDefinition& def : program_definitions) + { + if (!CreateProgram(data.programs[def.id], data.uniforms, def.id, data.vert_shaders[def.vert_shader], data.frag_shaders[def.frag_shader])) + return ReportError("program", def.name_str); + } + + glUseProgram(data.programs[ProgramId::BlendMask]); + glUniform1i(data.uniforms.Get(ProgramId::BlendMask, UniformId::TexMask), 1); + + glUseProgram(0); + return true; } -static void DestroyShaders(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(); - - if (!Gfx::CreateShaders(*shaders)) - shaders.reset(); + auto mut_program_data = Rml::MakeUnique(); + if (Gfx::CreateShaders(*mut_program_data)) + { + program_data = std::move(mut_program_data); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + fullscreen_quad_geometry = RenderInterface_GL3::CompileGeometry(mesh.vertices, mesh.indices); + } } RenderInterface_GL3::~RenderInterface_GL3() { - if (shaders) - Gfx::DestroyShaders(*shaders); + if (fullscreen_quad_geometry) + { + RenderInterface_GL3::ReleaseGeometry(fullscreen_quad_geometry); + fullscreen_quad_geometry = {}; + } + + if (program_data) + { + Gfx::DestroyShaders(*program_data); + program_data.reset(); + } } void RenderInterface_GL3::SetViewport(int width, int height) { - 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); @@ -348,8 +824,11 @@ 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); + 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); @@ -378,25 +857,64 @@ 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); + // Set blending function for premultiplied alpha. glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + +#ifndef RMLUI_PLATFORM_EMSCRIPTEN + // We do blending in nonlinear sRGB space because that is the common practice and gives results that we are used to. + glDisable(GL_FRAMEBUFFER_SRGB); +#endif glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 1, GLuint(-1)); glStencilMask(GLuint(-1)); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - 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 premultiplied alpha, we would need to perform a manual un-premultiplication step. + glActiveTexture(GL_TEXTURE0); + Gfx::BindTexture(fb_postprocess); + UseProgram(ProgramId::Passthrough); + DrawFullscreenQuad(); + + render_layers.EndFrame(); + // Restore GL state. if (glstate_backup.enable_cull_face) glEnable(GL_CULL_FACE); @@ -421,9 +939,13 @@ 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]); + 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); @@ -437,29 +959,17 @@ 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); -} - -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); - - if (geometry) - { - RenderCompiledGeometry(geometry, translation); - ReleaseCompiledGeometry(geometry); - } + glClear(GL_COLOR_BUFFER_BIT); } -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::Span vertices, Rml::Span indices) { constexpr GLenum draw_usage = GL_STATIC_DRAW; @@ -473,7 +983,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), @@ -488,7 +998,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); @@ -496,46 +1006,46 @@ 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; - 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) +void RenderInterface_GL3::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; - if (geometry->texture) + if (texture == TexturePostprocess) + { + // Do nothing. + } + else if (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); + UseProgram(ProgramId::Texture); + SubmitTransformUniform(translation); + if (texture != TextureEnableWithoutBinding) + glBindTexture(GL_TEXTURE_2D, (GLuint)texture); } 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"); } -void RenderInterface_GL3::ReleaseCompiledGeometry(Rml::CompiledGeometryHandle handle) +void RenderInterface_GL3::ReleaseGeometry(Rml::CompiledGeometryHandle handle) { Gfx::CompiledGeometryData* geometry = (Gfx::CompiledGeometryData*)handle; @@ -546,63 +1056,111 @@ 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); + 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; +} - if (new_state != scissoring_state) +void RenderInterface_GL3::SetScissor(Rml::Rectanglei region, bool vertically_flip) +{ + if (region.Valid() != scissor_state.Valid()) { - // Disable old - if (scissoring_state == ScissoringState::Scissor) + if (region.Valid()) + glEnable(GL_SCISSOR_TEST); + else 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)); + if (region.Valid() && vertically_flip) + region = VerticallyFlipped(region, viewport_height); + + if (region.Valid() && region != scissor_state) + { + // Some render APIs don't like offscreen positions (WebGL in particular), so clamp them to the viewport. + const int x = Rml::Math::Clamp(region.Left(), 0, viewport_width); + const int y = Rml::Math::Clamp(viewport_height - region.Bottom(), 0, viewport_height); - scissoring_state = new_state; + glScissor(x, y, region.Width(), region.Height()); } + + Gfx::CheckGLError("SetScissorRegion"); + scissor_state = region; } -void RenderInterface_GL3::SetScissorRegion(int x, int y, int width, int height) +void RenderInterface_GL3::EnableScissorRegion(bool enable) { - 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); + // Assume enable is immediately followed by a SetScissorRegion() call, and ignore it here. + if (!enable) + SetScissor(Rml::Rectanglei::MakeInvalid(), false); +} - Rml::Vertex vertices[4]; - vertices[0].position = {left, top}; - vertices[1].position = {right, top}; - vertices[2].position = {right, bottom}; - vertices[3].position = {left, bottom}; +void RenderInterface_GL3::SetScissorRegion(Rml::Rectanglei region) +{ + SetScissor(region); +} - int indices[6] = {0, 2, 1, 0, 3, 2}; +void RenderInterface_GL3::EnableClipMask(bool enable) +{ + if (enable) + glEnable(GL_STENCIL_TEST); + else + glDisable(GL_STENCIL_TEST); +} - 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); +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); - RenderGeometry(vertices, 4, indices, 6, 0, Rml::Vector2f(0, 0)); + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + glStencilFunc(GL_ALWAYS, GLint(1), GLuint(-1)); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, 1, GLuint(-1)); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + switch (operation) + { + case ClipMaskOperation::Set: + { + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + stencil_test_value = 1; } - else + break; + case ClipMaskOperation::SetInverse: { - glScissor(x, viewport_height - (y + height), width, height); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + stencil_test_value = 0; + } + break; + case ClipMaskOperation::Intersect: + { + glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); + stencil_test_value += 1; + } + break; } + + RenderGeometry(geometry, translation, {}); + + // Restore state + // @performance Cache state so we don't toggle it unnecessarily. + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_EQUAL, stencil_test_value, GLuint(-1)); } // Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file @@ -624,7 +1182,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); @@ -645,20 +1203,19 @@ 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 + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); - delete[] buffer; return false; } @@ -666,14 +1223,14 @@ 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; @@ -685,18 +1242,13 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V 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; + const byte alpha = image_src[read_index + 3]; + for (size_t j = 0; j < 3; j++) + image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255); + image_dest[write_index + 3] = alpha; } else - { image_dest[write_index + 3] = 255; - } write_index += 4; read_index += color_mode; @@ -706,15 +1258,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); - - delete[] image_dest; - delete[] buffer; - - 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); @@ -726,19 +1273,188 @@ bool RenderInterface_GL3::GenerateTexture(Rml::TextureHandle& texture_handle, co 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_data.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - texture_handle = (Rml::TextureHandle)texture_id; - glBindTexture(GL_TEXTURE_2D, 0); - return true; + return (Rml::TextureHandle)texture_id; +} + +void RenderInterface_GL3::DrawFullscreenQuad() +{ + RenderGeometry(fullscreen_quad_geometry, {}, RenderInterface_GL3::TexturePostprocess); +} + +void RenderInterface_GL3::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) +{ + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + if (uv_offset != Rml::Vector2f() || uv_scaling != Rml::Vector2f(1.f)) + { + for (Rml::Vertex& vertex : mesh.vertices) + vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; + } + const Rml::CompiledGeometryHandle geometry = CompileGeometry(mesh.vertices, mesh.indices); + RenderGeometry(geometry, {}, RenderInterface_GL3::TexturePostprocess); + ReleaseGeometry(geometry); +} + +static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) +{ + Rml::Colourf result; + for (int i = 0; i < 4; i++) + result[i] = (1.f / 255.f) * float(c0[i]); + return result; +} + +static void SigmaToParameters(const float desired_sigma, int& out_pass_level, float& out_sigma) +{ + constexpr int max_num_passes = 10; + static_assert(max_num_passes < 31, ""); + constexpr float max_single_pass_sigma = 3.0f; + out_pass_level = Rml::Math::Clamp(Rml::Math::Log2(int(desired_sigma * (2.f / max_single_pass_sigma))), 0, max_num_passes); + out_sigma = Rml::Math::Clamp(desired_sigma / float(1 << out_pass_level), 0.0f, max_single_pass_sigma); +} + +static void SetTexCoordLimits(GLint tex_coord_min_location, GLint tex_coord_max_location, Rml::Rectanglei rectangle_flipped, + Rml::Vector2i framebuffer_size) +{ + // Offset by half-texel values so that texture lookups are clamped to fragment centers, thereby avoiding color + // bleeding from neighboring texels due to bilinear interpolation. + const Rml::Vector2f min = (Rml::Vector2f(rectangle_flipped.p0) + Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + const Rml::Vector2f max = (Rml::Vector2f(rectangle_flipped.p1) - Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + + glUniform2f(tex_coord_min_location, min.x, min.y); + glUniform2f(tex_coord_max_location, max.x, max.y); +} + +static void SetBlurWeights(GLint weights_location, float sigma) +{ + constexpr int num_weights = BLUR_NUM_WEIGHTS; + float weights[num_weights]; + float normalization = 0.0f; + for (int i = 0; i < num_weights; i++) + { + if (Rml::Math::Absolute(sigma) < 0.1f) + weights[i] = float(i == 0); + else + weights[i] = Rml::Math::Exp(-float(i * i) / (2.0f * sigma * sigma)) / (Rml::Math::SquareRoot(2.f * Rml::Math::RMLUI_PI) * sigma); + + normalization += (i == 0 ? 1.f : 2.0f) * weights[i]; + } + for (int i = 0; i < num_weights; i++) + weights[i] /= normalization; + + glUniform1fv(weights_location, (GLsizei)num_weights, &weights[0]); +} + +void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, + const Rml::Rectanglei window_flipped) +{ + RMLUI_ASSERT(&source_destination != &temp && source_destination.width == temp.width && source_destination.height == temp.height); + RMLUI_ASSERT(window_flipped.Valid()); + + int pass_level = 0; + SigmaToParameters(sigma, pass_level, sigma); + + const Rml::Rectanglei original_scissor = scissor_state; + + // Begin by downscaling so that the blur pass can be done at a reduced resolution for large sigma. + Rml::Rectanglei scissor = window_flipped; + + UseProgram(ProgramId::Passthrough); + SetScissor(scissor, true); + + // Downscale by iterative half-scaling with bilinear filtering, to reduce aliasing. + glViewport(0, 0, source_destination.width / 2, source_destination.height / 2); + + // Scale UVs if we have even dimensions, such that texture fetches align perfectly between texels, thereby producing a 50% blend of + // neighboring texels. + const Rml::Vector2f uv_scaling = {(source_destination.width % 2 == 1) ? (1.f - 1.f / float(source_destination.width)) : 1.f, + (source_destination.height % 2 == 1) ? (1.f - 1.f / float(source_destination.height)) : 1.f}; + + for (int i = 0; i < pass_level; i++) + { + scissor.p0 = (scissor.p0 + Rml::Vector2i(1)) / 2; + scissor.p1 = Rml::Math::Max(scissor.p1 / 2, scissor.p0); + const bool from_source = (i % 2 == 0); + Gfx::BindTexture(from_source ? source_destination : temp); + glBindFramebuffer(GL_FRAMEBUFFER, (from_source ? temp : source_destination).framebuffer); + SetScissor(scissor, true); + + DrawFullscreenQuad({}, uv_scaling); + } + + glViewport(0, 0, source_destination.width, source_destination.height); + + // Ensure texture data end up in the temp buffer. Depending on the last downscaling, we might need to move it from the source_destination buffer. + const bool transfer_to_temp_buffer = (pass_level % 2 == 0); + if (transfer_to_temp_buffer) + { + Gfx::BindTexture(source_destination); + glBindFramebuffer(GL_FRAMEBUFFER, temp.framebuffer); + DrawFullscreenQuad(); + } + + // Set up uniforms. + UseProgram(ProgramId::Blur); + SetBlurWeights(GetUniformLocation(UniformId::Weights), sigma); + SetTexCoordLimits(GetUniformLocation(UniformId::TexCoordMin), GetUniformLocation(UniformId::TexCoordMax), scissor, + {source_destination.width, source_destination.height}); + + const GLint texel_offset_location = GetUniformLocation(UniformId::TexelOffset); + auto SetTexelOffset = [texel_offset_location](Rml::Vector2f blur_direction, int texture_dimension) { + const Rml::Vector2f texel_offset = blur_direction * (1.0f / float(texture_dimension)); + glUniform2f(texel_offset_location, texel_offset.x, texel_offset.y); + }; + + // Blur render pass - vertical. + Gfx::BindTexture(temp); + glBindFramebuffer(GL_FRAMEBUFFER, source_destination.framebuffer); + + SetTexelOffset({0.f, 1.f}, temp.height); + DrawFullscreenQuad(); + + // Blur render pass - horizontal. + Gfx::BindTexture(source_destination); + glBindFramebuffer(GL_FRAMEBUFFER, temp.framebuffer); + + SetTexelOffset({1.f, 0.f}, source_destination.width); + DrawFullscreenQuad(); + + // Blit the blurred image to the scissor region with upscaling. + SetScissor(window_flipped, true); + glBindFramebuffer(GL_READ_FRAMEBUFFER, temp.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, source_destination.framebuffer); + + const Rml::Vector2i src_min = scissor.p0; + const Rml::Vector2i src_max = scissor.p1; + const Rml::Vector2i dst_min = window_flipped.p0; + const Rml::Vector2i dst_max = window_flipped.p1; + glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, dst_min.x, dst_min.y, dst_max.x, dst_max.y, GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // The above upscale blit might be jittery at low resolutions (large pass levels). This is especially noticeable when moving an element with + // backdrop blur around or when trying to click/hover an element within a blurred region since it may be rendered at an offset. For more stable + // and accurate rendering we next upscale the blur image by an exact power-of-two. However, this may not fill the edges completely so we need to + // do the above first. Note that this strategy may sometimes result in visible seams. Alternatively, we could try to enlarge the window to the + // next power-of-two size and then downsample and blur that. + const Rml::Vector2i target_min = src_min * (1 << pass_level); + const Rml::Vector2i target_max = src_max * (1 << pass_level); + if (target_min != dst_min || target_max != dst_max) + { + glBlitFramebuffer(src_min.x, src_min.y, src_max.x, src_max.y, target_min.x, target_min.y, target_max.x, target_max.y, GL_COLOR_BUFFER_BIT, + GL_LINEAR); + } + + // Restore render state. + SetScissor(original_scissor); + + Gfx::CheckGLError("Blur"); } void RenderInterface_GL3::ReleaseTexture(Rml::TextureHandle texture_handle) @@ -748,18 +1464,662 @@ 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; + transform = (new_transform ? (projection * (*new_transform)) : projection); + program_transform_dirty.set(); +} + +enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix, MaskImage }; +struct CompiledFilter { + FilterType type; + + // Passthrough + float blend_factor; + + // Blur + float sigma; + + // Drop shadow + Rml::Vector2f offset; + Rml::ColourbPremultiplied color; + + // ColorMatrix + Rml::Matrix4f color_matrix; +}; + +Rml::CompiledFilterHandle RenderInterface_GL3::CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) +{ + CompiledFilter filter = {}; + + if (name == "opacity") + { + filter.type = FilterType::Passthrough; + filter.blend_factor = Rml::Get(parameters, "value", 1.0f); + } + else if (name == "blur") + { + filter.type = FilterType::Blur; + filter.sigma = 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()).ToPremultiplied(); + filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f)); + } + else if (name == "brightness") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + } + else if (name == "contrast") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float grayness = 0.5f - 0.5f * value; + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(grayness, grayness, grayness, 1.f)); + } + else if (name == "invert") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Math::Clamp(Rml::Get(parameters, "value", 1.0f), 0.f, 1.f); + const float inverted = 1.f - 2.f * value; + filter.color_matrix = Rml::Matrix4f::Diag(inverted, inverted, inverted, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(value, value, value, 1.f)); + } + else if (name == "grayscale") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f gray = value * Rml::Vector3f(0.2126f, 0.7152f, 0.0722f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {gray.x + rev_value, gray.y, gray.z, 0.f}, + {gray.x, gray.y + rev_value, gray.z, 0.f}, + {gray.x, gray.y, gray.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "sepia") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f r_mix = value * Rml::Vector3f(0.393f, 0.769f, 0.189f); + const Rml::Vector3f g_mix = value * Rml::Vector3f(0.349f, 0.686f, 0.168f); + const Rml::Vector3f b_mix = value * Rml::Vector3f(0.272f, 0.534f, 0.131f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {r_mix.x + rev_value, r_mix.y, r_mix.z, 0.f}, + {g_mix.x, g_mix.y + rev_value, g_mix.z, 0.f}, + {b_mix.x, b_mix.y, b_mix.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "hue-rotate") + { + // Hue-rotation and saturation values based on: https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-huerotate + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float s = Rml::Math::Sin(value); + const float c = Rml::Math::Cos(value); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * c - 0.213f * s, 0.715f - 0.715f * c - 0.715f * s, 0.072f - 0.072f * c + 0.928f * s, 0.f}, + {0.213f - 0.213f * c + 0.143f * s, 0.715f + 0.285f * c + 0.140f * s, 0.072f - 0.072f * c - 0.283f * s, 0.f}, + {0.213f - 0.213f * c - 0.787f * s, 0.715f - 0.715f * c + 0.715f * s, 0.072f + 0.928f * c + 0.072f * s, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "saturate") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * value, 0.715f - 0.715f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f + 0.285f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f - 0.715f * value, 0.072f + 0.928f * value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + + if (filter.type != FilterType::Invalid) + return reinterpret_cast(new CompiledFilter(std::move(filter))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported filter type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_GL3::ReleaseFilter(Rml::CompiledFilterHandle filter) +{ + delete reinterpret_cast(filter); +} + +enum class CompiledShaderType { Invalid = 0, Gradient, Creation }; +struct CompiledShader { + CompiledShaderType type; + + // Gradient + ShaderGradientFunction gradient_function; + Rml::Vector2f p; + Rml::Vector2f v; + Rml::Vector stop_positions; + Rml::Vector stop_colors; + + // Shader + Rml::Vector2f dimensions; +}; + +Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) +{ + auto ApplyColorStopList = [](CompiledShader& shader, const Rml::Dictionary& shader_parameters) { + auto it = shader_parameters.find("color_stop_list"); + RMLUI_ASSERT(it != shader_parameters.end() && it->second.GetType() == Rml::Variant::COLORSTOPLIST); + const Rml::ColorStopList& color_stop_list = it->second.GetReference(); + const int num_stops = Rml::Math::Min((int)color_stop_list.size(), MAX_NUM_STOPS); + + shader.stop_positions.resize(num_stops); + shader.stop_colors.resize(num_stops); + for (int i = 0; i < num_stops; i++) + { + const Rml::ColorStop& stop = color_stop_list[i]; + RMLUI_ASSERT(stop.position.unit == Rml::Unit::NUMBER); + shader.stop_positions[i] = stop.position.number; + shader.stop_colors[i] = ConvertToColorf(stop.color); + } + }; + + CompiledShader shader = {}; + + if (name == "linear-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingLinear : ShaderGradientFunction::Linear); + shader.p = Rml::Get(parameters, "p0", Rml::Vector2f(0.f)); + shader.v = Rml::Get(parameters, "p1", Rml::Vector2f(0.f)) - shader.p; + ApplyColorStopList(shader, parameters); + } + else if (name == "radial-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingRadial : ShaderGradientFunction::Radial); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + shader.v = Rml::Vector2f(1.f) / Rml::Get(parameters, "radius", Rml::Vector2f(1.f)); + ApplyColorStopList(shader, parameters); + } + else if (name == "conic-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingConic : ShaderGradientFunction::Conic); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + const float angle = Rml::Get(parameters, "angle", 0.f); + shader.v = {Rml::Math::Cos(angle), Rml::Math::Sin(angle)}; + ApplyColorStopList(shader, parameters); + } + else if (name == "shader") + { + const Rml::String value = Rml::Get(parameters, "value", Rml::String()); + if (value == "creation") + { + shader.type = CompiledShaderType::Creation; + shader.dimensions = Rml::Get(parameters, "dimensions", Rml::Vector2f(0.f)); + } + } + + if (shader.type != CompiledShaderType::Invalid) + return reinterpret_cast(new CompiledShader(std::move(shader))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported shader type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, + Rml::Vector2f translation, Rml::TextureHandle /*texture*/) +{ + RMLUI_ASSERT(shader_handle && geometry_handle); + const CompiledShader& shader = *reinterpret_cast(shader_handle); + const CompiledShaderType type = shader.type; + const Gfx::CompiledGeometryData& geometry = *reinterpret_cast(geometry_handle); + + switch (type) + { + case CompiledShaderType::Gradient: + { + RMLUI_ASSERT(shader.stop_positions.size() == shader.stop_colors.size()); + const int num_stops = (int)shader.stop_positions.size(); + + UseProgram(ProgramId::Gradient); + glUniform1i(GetUniformLocation(UniformId::Func), static_cast(shader.gradient_function)); + glUniform2f(GetUniformLocation(UniformId::P), shader.p.x, shader.p.y); + glUniform2f(GetUniformLocation(UniformId::V), shader.v.x, shader.v.y); + glUniform1i(GetUniformLocation(UniformId::NumStops), num_stops); + glUniform1fv(GetUniformLocation(UniformId::StopPositions), num_stops, shader.stop_positions.data()); + glUniform4fv(GetUniformLocation(UniformId::StopColors), num_stops, shader.stop_colors[0]); + + SubmitTransformUniform(translation); + glBindVertexArray(geometry.vao); + glDrawElements(GL_TRIANGLES, geometry.draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + glBindVertexArray(0); + } + break; + case CompiledShaderType::Creation: + { + const double time = Rml::GetSystemInterface()->GetElapsedTime(); + + UseProgram(ProgramId::Creation); + glUniform1f(GetUniformLocation(UniformId::Value), (float)time); + glUniform2f(GetUniformLocation(UniformId::Dimensions), shader.dimensions.x, shader.dimensions.y); + + SubmitTransformUniform(translation); + glBindVertexArray(geometry.vao); + glDrawElements(GL_TRIANGLES, geometry.draw_count, GL_UNSIGNED_INT, (const GLvoid*)0); + glBindVertexArray(0); + } + break; + case CompiledShaderType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render shader %d.", (int)type); + } + break; + } + + Gfx::CheckGLError("RenderShader"); +} + +void RenderInterface_GL3::ReleaseShader(Rml::CompiledShaderHandle shader_handle) +{ + delete reinterpret_cast(shader_handle); +} + +void RenderInterface_GL3::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle) +{ + const Gfx::FramebufferData& source = render_layers.GetLayer(layer_handle); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessPrimary(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.framebuffer); + + // Blit and resolve MSAA. Any active scissor state will restrict the size of the blit region. + glBlitFramebuffer(0, 0, source.width, source.height, 0, 0, destination.width, destination.height, GL_COLOR_BUFFER_BIT, GL_NEAREST); +} + +void RenderInterface_GL3::RenderFilters(Rml::Span filter_handles) +{ + for (const Rml::CompiledFilterHandle filter_handle : filter_handles) + { + const CompiledFilter& filter = *reinterpret_cast(filter_handle); + const FilterType type = filter.type; + + switch (type) + { + case FilterType::Passthrough: + { + UseProgram(ProgramId::Passthrough); + glBlendFunc(GL_CONSTANT_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::Blur: + { + glDisable(GL_BLEND); + + const Gfx::FramebufferData& source_destination = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& temp = render_layers.GetPostprocessSecondary(); + + const Rml::Rectanglei window_flipped = VerticallyFlipped(scissor_state, viewport_height); + RenderBlur(filter.sigma, source_destination, temp, window_flipped); + + glEnable(GL_BLEND); + } + break; + case FilterType::DropShadow: + { + UseProgram(ProgramId::DropShadow); + glDisable(GL_BLEND); + + Rml::Colourf color = ConvertToColorf(filter.color); + glUniform4fv(GetUniformLocation(UniformId::Color), 1, &color[0]); + + const Gfx::FramebufferData& primary = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& secondary = render_layers.GetPostprocessSecondary(); + Gfx::BindTexture(primary); + glBindFramebuffer(GL_FRAMEBUFFER, secondary.framebuffer); + + const Rml::Rectanglei window_flipped = VerticallyFlipped(scissor_state, viewport_height); + SetTexCoordLimits(GetUniformLocation(UniformId::TexCoordMin), GetUniformLocation(UniformId::TexCoordMax), window_flipped, + {primary.width, primary.height}); + + const Rml::Vector2f uv_offset = filter.offset / Rml::Vector2f(-(float)viewport_width, (float)viewport_height); + DrawFullscreenQuad(uv_offset); + + if (filter.sigma >= 0.5f) + { + const Gfx::FramebufferData& tertiary = render_layers.GetPostprocessTertiary(); + RenderBlur(filter.sigma, secondary, tertiary, window_flipped); + } + + UseProgram(ProgramId::Passthrough); + BindTexture(primary); + glEnable(GL_BLEND); + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + } + break; + case FilterType::ColorMatrix: + { + UseProgram(ProgramId::ColorMatrix); + glDisable(GL_BLEND); + + const GLint uniform_location = program_data->uniforms.Get(ProgramId::ColorMatrix, UniformId::ColorMatrix); + constexpr bool transpose = std::is_same::value; + glUniformMatrix4fv(uniform_location, 1, transpose, filter.color_matrix.data()); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + Gfx::BindTexture(source); + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + glEnable(GL_BLEND); + } + break; + case FilterType::MaskImage: + { + UseProgram(ProgramId::BlendMask); + glDisable(GL_BLEND); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& blend_mask = render_layers.GetBlendMask(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + + Gfx::BindTexture(source); + glActiveTexture(GL_TEXTURE1); + Gfx::BindTexture(blend_mask); + glActiveTexture(GL_TEXTURE0); + + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + + DrawFullscreenQuad(); + + render_layers.SwapPostprocessPrimarySecondary(); + glEnable(GL_BLEND); + } + break; + case FilterType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render filter %d.", (int)type); + } + break; + } + } + + Gfx::CheckGLError("RenderFilter"); +} + +Rml::LayerHandle RenderInterface_GL3::PushLayer() +{ + const Rml::LayerHandle layer_handle = render_layers.PushLayer(); + + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetLayer(layer_handle).framebuffer); + glClear(GL_COLOR_BUFFER_BIT); + + return layer_handle; +} + +void RenderInterface_GL3::CompositeLayers(Rml::LayerHandle source_handle, Rml::LayerHandle destination_handle, Rml::BlendMode blend_mode, + Rml::Span filters) +{ + using Rml::BlendMode; + + // Blit source layer to postprocessing buffer. Do this regardless of whether we actually have any filters to be + // applied, because we need to resolve the multi-sampled framebuffer in any case. + // @performance If we have BlendMode::Replace and no filters or mask then we can just blit directly to the destination. + BlitLayerToPostprocessPrimary(source_handle); + + // Render the filters, the PostprocessPrimary framebuffer is used for both input and output. + RenderFilters(filters); + + // Render to the destination layer. + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetLayer(destination_handle).framebuffer); + Gfx::BindTexture(render_layers.GetPostprocessPrimary()); + + UseProgram(ProgramId::Passthrough); + + if (blend_mode == BlendMode::Replace) + glDisable(GL_BLEND); + + DrawFullscreenQuad(); + + if (blend_mode == BlendMode::Replace) + glEnable(GL_BLEND); + + if (destination_handle != render_layers.GetTopLayerHandle()) + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + + Gfx::CheckGLError("CompositeLayers"); +} + +void RenderInterface_GL3::PopLayer() +{ + render_layers.PopLayer(); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); +} + +Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture(Rml::Vector2i dimensions) +{ + Rml::TextureHandle render_texture = GenerateTexture({}, dimensions); + if (!render_texture) + return {}; + + BlitLayerToPostprocessPrimary(render_layers.GetTopLayerHandle()); + + RMLUI_ASSERT(scissor_state.Valid() && render_texture); + const Rml::Rectanglei initial_scissor_state = scissor_state; + EnableScissorRegion(false); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetPostprocessSecondary(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destination.framebuffer); + + Rml::Rectanglei bounds = initial_scissor_state; + + // Flip the image vertically, as that convention is used for textures, and move to origin. + glBlitFramebuffer( // + bounds.Left(), source.height - bounds.Bottom(), // src0 + bounds.Right(), source.height - bounds.Top(), // src1 + 0, bounds.Height(), // dst0 + bounds.Width(), 0, // dst1 + GL_COLOR_BUFFER_BIT, GL_NEAREST // + ); + + glBindTexture(GL_TEXTURE_2D, (GLuint)render_texture); + + const Gfx::FramebufferData& texture_source = destination; + glBindFramebuffer(GL_READ_FRAMEBUFFER, texture_source.framebuffer); + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, bounds.Width(), bounds.Height()); + + SetScissor(initial_scissor_state); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + Gfx::CheckGLError("SaveLayerAsTexture"); + + return render_texture; +} + +Rml::CompiledFilterHandle RenderInterface_GL3::SaveLayerAsMaskImage() +{ + BlitLayerToPostprocessPrimary(render_layers.GetTopLayerHandle()); + + const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = render_layers.GetBlendMask(); + + glBindFramebuffer(GL_FRAMEBUFFER, destination.framebuffer); + BindTexture(source); + UseProgram(ProgramId::Passthrough); + glDisable(GL_BLEND); + + DrawFullscreenQuad(); + + glEnable(GL_BLEND); + glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); + Gfx::CheckGLError("SaveLayerAsMaskImage"); + + CompiledFilter filter = {}; + filter.type = FilterType::MaskImage; + return reinterpret_cast(new CompiledFilter(std::move(filter))); +} + +void RenderInterface_GL3::UseProgram(ProgramId program_id) +{ + RMLUI_ASSERT(program_data); + if (active_program != program_id) + { + if (program_id != ProgramId::None) + glUseProgram(program_data->programs[program_id]); + active_program = program_id; + } +} + +int RenderInterface_GL3::GetUniformLocation(UniformId uniform_id) const +{ + return program_data->uniforms.Get(active_program, uniform_id); } -void RenderInterface_GL3::SubmitTransformUniform(ProgramId program_id, int uniform_location) +void RenderInterface_GL3::SubmitTransformUniform(Rml::Vector2f translation) { - if ((int)program_id & (int)transform_dirty_state) + 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(uniform_location, 1, false, transform.data()); - transform_dirty_state = ProgramId((int)transform_dirty_state & ~(int)program_id); + glUniformMatrix4fv(GetUniformLocation(UniformId::Transform), 1, false, transform.data()); + program_transform_dirty.set(program_index, false); } + + glUniform2fv(GetUniformLocation(UniformId::Translate), 1, &translation.x); + + Gfx::CheckGLError("SubmitTransformUniform"); +} + +RenderInterface_GL3::RenderLayerStack::RenderLayerStack() +{ + fb_postprocess.resize(4); +} + +RenderInterface_GL3::RenderLayerStack::~RenderLayerStack() +{ + DestroyFramebuffers(); +} + +Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::PushLayer() +{ + RMLUI_ASSERT(layers_size <= (int)fb_layers.size()); + + if (layers_size == (int)fb_layers.size()) + { + // All framebuffers should share a single stencil buffer. + GLuint shared_depth_stencil = (fb_layers.empty() ? 0 : fb_layers.front().depth_stencil_buffer); + + fb_layers.push_back(Gfx::FramebufferData{}); + Gfx::CreateFramebuffer(fb_layers.back(), width, height, NUM_MSAA_SAMPLES, Gfx::FramebufferAttachment::DepthStencil, shared_depth_stencil); + } + + layers_size += 1; + return GetTopLayerHandle(); +} + +void RenderInterface_GL3::RenderLayerStack::PopLayer() +{ + RMLUI_ASSERT(layers_size > 0); + layers_size -= 1; +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetLayer(Rml::LayerHandle layer) const +{ + RMLUI_ASSERT((size_t)layer < (size_t)layers_size); + return fb_layers[layer]; +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetTopLayer() const +{ + return GetLayer(GetTopLayerHandle()); +} + +Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::GetTopLayerHandle() const +{ + RMLUI_ASSERT(layers_size > 0); + return static_cast(layers_size - 1); +} + +void RenderInterface_GL3::RenderLayerStack::SwapPostprocessPrimarySecondary() +{ + std::swap(fb_postprocess[0], fb_postprocess[1]); +} + +void RenderInterface_GL3::RenderLayerStack::BeginFrame(int new_width, int new_height) +{ + RMLUI_ASSERT(layers_size == 0); + + if (new_width != width || new_height != height) + { + width = new_width; + height = new_height; + + DestroyFramebuffers(); + } + + PushLayer(); +} + +void RenderInterface_GL3::RenderLayerStack::EndFrame() +{ + RMLUI_ASSERT(layers_size == 1); + PopLayer(); +} + +void RenderInterface_GL3::RenderLayerStack::DestroyFramebuffers() +{ + RMLUI_ASSERTMSG(layers_size == 0, "Do not call this during frame rendering, that is, between BeginFrame() and EndFrame()."); + + for (Gfx::FramebufferData& fb : fb_layers) + Gfx::DestroyFramebuffer(fb); + + fb_layers.clear(); + + for (Gfx::FramebufferData& fb : fb_postprocess) + Gfx::DestroyFramebuffer(fb); +} + +const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::EnsureFramebufferPostprocess(int index) +{ + RMLUI_ASSERT(index < (int)fb_postprocess.size()) + Gfx::FramebufferData& fb = fb_postprocess[index]; + if (!fb.framebuffer) + Gfx::CreateFramebuffer(fb, width, height, 0, Gfx::FramebufferAttachment::None, 0); + return fb; } bool RmlGL3::Initialize(Rml::String* out_message) diff --git a/Backends/RmlUi_Renderer_GL3.h b/Backends/RmlUi_Renderer_GL3.h index 537b61519..51f118ee4 100644 --- a/Backends/RmlUi_Renderer_GL3.h +++ b/Backends/RmlUi_Renderer_GL3.h @@ -31,10 +31,15 @@ #include #include +#include +enum class ProgramId; +enum class UniformId; +class RenderLayerStack; namespace Gfx { -struct ShadersData; -} +struct ProgramData; +struct FramebufferData; +} // namespace Gfx class RenderInterface_GL3 : public Rml::RenderInterface { public: @@ -42,55 +47,138 @@ 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 -- - 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, - Rml::TextureHandle texture) override; - void RenderCompiledGeometry(Rml::CompiledGeometryHandle geometry, const Rml::Vector2f& translation) 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; + 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; + + Rml::CompiledFilterHandle SaveLayerAsMaskImage() override; + + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; + void ReleaseFilter(Rml::CompiledFilterHandle filter) override; + + Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override; + void RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation, + Rml::TextureHandle texture) override; + void ReleaseShader(Rml::CompiledShaderHandle effect_handle) override; + // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. - static 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); + int GetUniformLocation(UniformId uniform_id) const; + void SubmitTransformUniform(Rml::Vector2f translation); + + void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle); + void RenderFilters(Rml::Span filter_handles); + + void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); + + void DrawFullscreenQuad(); + void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f)); - Rml::Matrix4f transform, projection; - ProgramId transform_dirty_state = ProgramId::All; - bool transform_active = false; + void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, Rml::Rectanglei window_flipped); - enum class ScissoringState { Disable, Scissor, Stencil }; - ScissoringState scissoring_state = ScissoringState::Disable; + static constexpr size_t MaxNumPrograms = 32; + std::bitset program_transform_dirty; + + Rml::Matrix4f transform; + Rml::Matrix4f projection; + + ProgramId active_program = {}; + Rml::Rectanglei scissor_state; int viewport_width = 0; int viewport_height = 0; - 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. + Rml::LayerHandle PushLayer(); + + // Pop the top layer. All references to previously retrieved layers are invalidated. + void PopLayer(); + + const Gfx::FramebufferData& GetLayer(Rml::LayerHandle layer) const; + const Gfx::FramebufferData& GetTopLayer() const; + Rml::LayerHandle GetTopLayerHandle() const; + + const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); } + const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); } + const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); } + const Gfx::FramebufferData& GetBlendMask() { return EnsureFramebufferPostprocess(3); } + + void SwapPostprocessPrimarySecondary(); + + void BeginFrame(int new_width, int new_height); + void EndFrame(); + + private: + void DestroyFramebuffers(); + const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index); + + int width = 0, height = 0; + + // The number of active layers is manually tracked since we re-use the framebuffers stored in the fb_layers stack. + int layers_size = 0; + + Rml::Vector fb_layers; + Rml::Vector fb_postprocess; + }; + + RenderLayerStack render_layers; struct GLStateBackup { bool enable_cull_face; @@ -101,8 +189,11 @@ class RenderInterface_GL3 : public Rml::RenderInterface { int viewport[4]; int scissor[4]; + int active_texture; + int stencil_clear_value; float color_clear_value[4]; + unsigned char color_writemask[4]; int blend_equation_rgb; int blend_equation_alpha; diff --git a/Backends/RmlUi_Renderer_SDL.cpp b/Backends/RmlUi_Renderer_SDL.cpp index 742d6395e..636ae2ba4 100644 --- a/Backends/RmlUi_Renderer_SDL.cpp +++ b/Backends/RmlUi_Renderer_SDL.cpp @@ -33,24 +33,46 @@ #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() {} -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; @@ -59,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; } @@ -74,79 +96,81 @@ 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); 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)); + 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, int(buffer_size)), 1, extension.c_str()); + SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str()); + if (!surface) + return {}; - bool success = 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 {}; - 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); + + texture_dimensions = Rml::Vector2i(surface->w, surface->h); + SDL_FreeSurface(surface); + + if (texture) + SDL_SetTextureBlendMode(texture, blend_mode); - return success; + 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) { -#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.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, SDL_BLENDMODE_BLEND); + 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 2f4031eb7..7c0af76e9 100644 --- a/Backends/RmlUi_Renderer_SDL.h +++ b/Backends/RmlUi_Renderer_SDL.h @@ -42,18 +42,25 @@ 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 = {}; bool scissor_region_enabled = false; }; diff --git a/Backends/RmlUi_Renderer_VK.cpp b/Backends/RmlUi_Renderer_VK.cpp index c00749605..85d38fb7a 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,25 +102,10 @@ 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, texture); - - if (handle) - { - RenderCompiledGeometry(handle, translation); - 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::Span vertices, Rml::Span 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,6 +115,37 @@ 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.data()); + + 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) * vertices.size()); + + 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.data(), sizeof(int) * indices.size()); + + p_geometry_handle->m_num_indices = (int)indices.size(); + + return Rml::CompiledGeometryHandle(p_geometry_handle); +} + +void RenderInterface_VK::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry"); + + if (m_p_current_command_buffer == nullptr) + return; + + 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) { @@ -152,37 +169,6 @@ Rml::CompiledGeometryHandle RenderInterface_VK::CompileGeometry(Rml::Vertex* ver p_texture->m_p_vk_descriptor_set = p_texture_set; } - uint32_t* pCopyDataToBuffer = nullptr; - const void* pData = reinterpret_cast(vertices); - - bool status = m_memory_pool.Alloc_VertexBuffer(num_vertices, 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); - - 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); - RMLUI_VK_ASSERTMSG(status, "failed to AllocIndexBuffer"); - - 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) -{ - RMLUI_ZoneScopedN("Vulkan - RenderCompiledGeometry"); - - if (m_p_current_command_buffer == nullptr) - return; - - RMLUI_VK_ASSERTMSG(m_p_current_command_buffer, "must be valid otherwise you can't render now!!! (can't be)"); - geometry_handle_t* p_casted_compiled_geometry = reinterpret_cast(geometry); m_user_data_for_vertex_shader.m_translate = translation; @@ -236,15 +222,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 +242,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) { @@ -291,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"); @@ -319,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}; @@ -370,7 +351,11 @@ void RenderInterface_VK::SetScissorRegion(int x, int y, int width, int height) vkCmdClearAttachments(m_p_current_command_buffer, 1, &clear_attachment, 1, &clear_rect); - 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; @@ -378,10 +363,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::Clamp(region.Left(), 0, m_width); + m_scissor.offset.y = Rml::Math::Clamp(region.Top(), 0, m_height); #ifdef RMLUI_DEBUG VkDebugUtilsLabelEXT info{}; @@ -419,7 +404,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); @@ -432,22 +417,23 @@ 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 + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures if (header.dataType != 2) { @@ -458,25 +444,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; @@ -488,18 +480,13 @@ 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; - - return status; + 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); } /* @@ -518,12 +505,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; @@ -533,14 +519,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 = {}; @@ -679,9 +665,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) @@ -1980,10 +1964,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; @@ -3020,8 +3004,14 @@ void RenderInterface_VK::MemoryPool::Free_GeometryHandle(geometry_handle_t* p_va "you must pass a VALID pointer to geometry_handle_t, otherwise something is wrong and debug your code"); RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_vertex_allocation, "you must have a VALID pointer of VmaAllocation for vertex buffer"); RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_index_allocation, "you must have a VALID pointer of VmaAllocation for index buffer"); - RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_shader_allocation, - "you must have a VALID pointer of VmaAllocation for shader operations (like uniforms and etc)"); + + // TODO: The following assertion is disabled for now. The shader allocation pointer is only set once the geometry + // handle is rendered with. However, currently the Vulkan renderer does not handle all draw calls from RmlUi, so + // this pointer may never be set if the geometry was only used in a unsupported draw calls. This can then trigger + // the following assertion. The free call below gracefully handles zero pointers so this should be safe regardless. + // RMLUI_VK_ASSERTMSG(p_valid_geometry_handle->m_p_shader_allocation, + // "you must have a VALID pointer of VmaAllocation for shader operations (like uniforms and etc)"); + RMLUI_VK_ASSERTMSG(m_p_block, "you have to allocate the virtual block before do this operation..."); vmaVirtualFree(m_p_block, p_valid_geometry_handle->m_p_vertex_allocation); @@ -3031,8 +3021,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 06fd7c2ff..d1f7f916b 100644 --- a/Backends/RmlUi_Renderer_VK.h +++ b/Backends/RmlUi_Renderer_VK.h @@ -129,33 +129,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, - Rml::TextureHandle texture) 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) 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; @@ -178,11 +170,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; @@ -486,7 +475,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/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 ab1265968..4c59c3d85 100644 --- a/CMake/FileList.cmake +++ b/CMake/FileList.cmake @@ -12,21 +12,17 @@ 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/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 - ${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 @@ -52,12 +48,15 @@ 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/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 ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h - ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h + ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.h ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockContainer.h ${PROJECT_SOURCE_DIR}/Source/Core/Layout/BlockFormattingContext.h @@ -77,14 +76,18 @@ 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 ${PROJECT_SOURCE_DIR}/Source/Core/precompiled.h ${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 ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h @@ -92,6 +95,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 @@ -105,7 +109,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 @@ -125,8 +128,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 @@ -141,9 +146,10 @@ set(Core_PUB_HDR_FILES ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataTypes.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DataVariable.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Debug.h + ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/DecorationTypes.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/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 @@ -164,13 +170,13 @@ 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 ${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 @@ -178,6 +184,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 @@ -192,9 +200,13 @@ 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 + ${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 ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h @@ -213,6 +225,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 @@ -232,7 +245,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 @@ -251,25 +266,21 @@ 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/DecoratorShader.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/EffectSpecification.cpp ${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 @@ -311,6 +322,10 @@ 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/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 @@ -320,8 +335,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/GeometryDatabase.cpp - ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp + ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBoxShadow.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 @@ -340,8 +354,10 @@ 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 ${PROJECT_SOURCE_DIR}/Source/Core/ObserverPtr.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Plugin.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PluginRegistry.cpp @@ -351,8 +367,11 @@ set(Core_SRC_FILES ${PROJECT_SOURCE_DIR}/Source/Core/PropertyDefinition.cpp ${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 ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserFontEffect.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp @@ -361,6 +380,9 @@ 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 ${PROJECT_SOURCE_DIR}/Source/Core/Spritesheet.cpp ${PROJECT_SOURCE_DIR}/Source/Core/Stream.cpp @@ -383,7 +405,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/CMake/SampleFileList.cmake b/CMake/SampleFileList.cmake index 627253898..dbcbddd4b 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 ) @@ -151,8 +158,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 @@ -174,8 +179,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 @@ -198,8 +201,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 @@ -216,8 +217,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/CMake/gen_samplelists.sh b/CMake/gen_samplelists.sh index b51d4beb9..b47ea3781 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/harfbuzzshaping' 'basic/lottie' 'basic/svg' 'tutorial/template' 'tutorial/drag' 'invaders' 'luainvaders' diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ab6a6fe3..efcc515a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -170,7 +173,7 @@ option(BUILD_SAMPLES "Build samples" OFF) option(ENABLE_HARFBUZZ "Enable HarfBuzz for text-shaping sample. Requires the HarfBuzz library." 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) @@ -842,7 +845,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) @@ -876,7 +879,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}) @@ -891,7 +894,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) @@ -950,10 +953,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/") @@ -961,6 +965,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}) @@ -969,10 +975,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() @@ -1049,6 +1059,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/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 525802c94..50e8d309e 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" @@ -40,8 +42,9 @@ #include "Core/DataTypeRegister.h" #include "Core/DataTypes.h" #include "Core/DataVariable.h" +#include "Core/DecorationTypes.h" #include "Core/Decorator.h" -#include "Core/DecoratorInstancer.h" +#include "Core/EffectSpecification.h" #include "Core/Element.h" #include "Core/ElementDocument.h" #include "Core/ElementInstancer.h" @@ -54,17 +57,19 @@ #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" #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" @@ -75,6 +80,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" @@ -89,6 +95,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..5584bc1d8 --- /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(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. + 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/Colour.h b/Include/RmlUi/Core/Colour.h index 9fabe43ff..27a3cb8ca 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,45 @@ 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 { + 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 { + ColourType(red * (new_alpha / 255.f)), + ColourType(green * (new_alpha / 255.f)), + ColourType(blue * (new_alpha / 255.f)), + ColourType(new_alpha), + }; + } + + // 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/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/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/ComputedValues.h b/Include/RmlUi/Core/ComputedValues.h index ec9220c7a..b5cc9295b 100644 --- a/Include/RmlUi/Core/ComputedValues.h +++ b/Include/RmlUi/Core/ComputedValues.h @@ -161,7 +161,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_mask_image(false), has_filter(false), has_backdrop_filter(false), has_box_shadow(false) {} LengthPercentage::Type min_width_type : 1, max_width_type : 1; @@ -179,6 +181,11 @@ 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; + Clip clip; float min_width = 0, max_width = FLT_MAX; @@ -294,6 +301,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; } @@ -302,6 +311,10 @@ 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; } // -- Assignment -- // Common @@ -384,6 +397,10 @@ 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; } // clang-format on diff --git a/Include/RmlUi/Core/Context.h b/Include/RmlUi/Core/Context.h index 98b3c3709..8f0759c3e 100644 --- a/Include/RmlUi/Core/Context.h +++ b/Include/RmlUi/Core/Context.h @@ -46,6 +46,7 @@ class DataModel; class DataModelConstructor; class DataTypeRegister; class ScrollController; +class RenderManager; enum class EventId : uint16_t; /** @@ -56,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(); @@ -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. @@ -305,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; @@ -369,9 +367,6 @@ class RMLUICORE_API Context : public ScriptInterface { // itself can't be part of it. ElementSet drag_hover_chain; - Vector2i clip_origin; - Vector2i clip_dimensions; - using DataModels = UnorderedMap>; DataModels data_models; @@ -379,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 77948f0fc..773289778 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,11 +154,16 @@ 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); /// Releases a specified texture by name from memory, returning 'true' if successful and 'false' if not found. -RMLUICORE_API bool ReleaseTexture(const String& name); +/// @param[in] source The texture source to match. +/// @param[in] render_interface Release any matching texture belonging to the given interface, or nullptr to look in all interfaces. +/// @return True if any texture was released. +RMLUICORE_API bool ReleaseTexture(const String& source, 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/DecorationTypes.h b/Include/RmlUi/Core/DecorationTypes.h new file mode 100644 index 000000000..3098db192 --- /dev/null +++ b/Include/RmlUi/Core/DecorationTypes.h @@ -0,0 +1,68 @@ +/* + * 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_DECORATIONTYPES_H +#define RMLUI_CORE_DECORATIONTYPES_H + +#include "NumericValue.h" +#include "Types.h" + +namespace Rml { + +struct ColorStop { + ColourbPremultiplied 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 { + ColourbPremultiplied color; + NumericValue offset_x, offset_y; + NumericValue blur_radius; + NumericValue spread_distance; + bool inset = false; +}; +inline bool operator==(const BoxShadow& a, const BoxShadow& b) +{ + return a.color == b.color && a.offset_x == b.offset_x && a.offset_y == b.offset_y && a.blur_radius == b.blur_radius && + a.spread_distance == b.spread_distance && a.inset == b.inset; +} +inline bool operator!=(const BoxShadow& a, const BoxShadow& b) +{ + return !(a == b); +} + +} // namespace Rml +#endif diff --git a/Include/RmlUi/Core/Decorator.h b/Include/RmlUi/Core/Decorator.h index 2e81bc9dd..eb9973a70 100644 --- a/Include/RmlUi/Core/Decorator.h +++ b/Include/RmlUi/Core/Decorator.h @@ -29,24 +29,28 @@ #ifndef RMLUI_CORE_DECORATOR_H #define RMLUI_CORE_DECORATOR_H +#include "EffectSpecification.h" #include "Header.h" +#include "PropertyDictionary.h" +#include "PropertySpecification.h" #include "Texture.h" #include "Types.h" namespace Rml { -class DecoratorInstancer; class Element; class PropertyDictionary; -class Property; -struct Texture; +struct Sprite; +class Texture; +class StyleSheet; +class RenderManager; +class DecoratorInstancerInterface; /** The abstract base class for any visual object that can be attached to any element. @author Peter Curry */ - class RMLUICORE_API Decorator { public: Decorator(); @@ -54,8 +58,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; @@ -66,19 +71,20 @@ 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: /// 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. @@ -87,5 +93,44 @@ class RMLUICORE_API Decorator { Vector additional_textures; }; +/** + A decorator instancer, which can be inherited from to instance new decorators when encountered in the style sheet. + */ +class RMLUICORE_API DecoratorInstancer : public EffectSpecification { +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; +}; + +class RMLUICORE_API DecoratorInstancerInterface { +public: + 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. + 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; + + /// Get the render manager for the decorator being instanced. + RenderManager& GetRenderManager() const; + +private: + RenderManager& render_manager; + const StyleSheet& style_sheet; + const PropertySource* property_source; +}; + } // namespace Rml #endif diff --git a/Include/RmlUi/Core/DecoratorInstancer.h b/Include/RmlUi/Core/EffectSpecification.h similarity index 52% rename from Include/RmlUi/Core/DecoratorInstancer.h rename to Include/RmlUi/Core/EffectSpecification.h index 6db5121dc..84b8340f0 100644 --- a/Include/RmlUi/Core/DecoratorInstancer.h +++ b/Include/RmlUi/Core/EffectSpecification.h @@ -26,82 +26,45 @@ * */ -#ifndef RMLUI_CORE_DECORATORINSTANCER_H -#define RMLUI_CORE_DECORATORINSTANCER_H +#ifndef RMLUI_CORE_EFFECTSPECIFICATION_H +#define RMLUI_CORE_EFFECTSPECIFICATION_H #include "Header.h" -#include "PropertyDictionary.h" #include "PropertySpecification.h" +#include "Types.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 { +class RMLUICORE_API EffectSpecification { 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; + EffectSpecification(); /// Returns the property specification associated with the instancer. const PropertySpecification& GetPropertySpecification() const; protected: - /// Registers a property for the decorator. + ~EffectSpecification(); + + /// 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: 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/Element.h b/Include/RmlUi/Core/Element.h index bb76f3ac0..0d0499aab 100644 --- a/Include/RmlUi/Core/Element.h +++ b/Include/RmlUi/Core/Element.h @@ -50,7 +50,7 @@ class Decorator; class ElementInstancer; class EventDispatcher; class EventListener; -class ElementDecoration; +class ElementBackgroundBorder; class ElementDefinition; class ElementDocument; class ElementScroll; @@ -60,6 +60,7 @@ class InlineLevelBox; class ReplacedBox; class PropertiesIteratorView; class PropertyDictionary; +class RenderManager; class StyleSheet; class StyleSheetContainer; class TransformState; @@ -336,6 +337,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; - Colourb colour; + ColourbPremultiplied colour; float opacity; int font_handle_version; diff --git a/Include/RmlUi/Core/ElementUtilities.h b/Include/RmlUi/Core/ElementUtilities.h index 7e5f13096..d35e93b24 100644 --- a/Include/RmlUi/Core/ElementUtilities.h +++ b/Include/RmlUi/Core/ElementUtilities.h @@ -30,6 +30,7 @@ #define RMLUI_CORE_ELEMENTUTILITIES_H #include "Header.h" +#include "RenderManager.h" #include "Types.h" namespace Rml { @@ -87,19 +88,25 @@ class RMLUICORE_API ElementUtilities { static int GetStringWidth(Element* element, const String& string, Character prior_character = Character::Null); /// Generates the clipping region for an element. - /// @param[out] clip_origin The origin, in context coordinates, of the origin of the element's clipping window. - /// @param[out] clip_dimensions The size, in context coordinates, of the element's clipping window. /// @param[in] element The element to generate the clipping region for. + /// @param[out] clip_region The element's clipping region in window coordinates. + /// @param[out] clip_mask_list Optional, returns a list of geometry that defines the element's clip mask. + /// @param[in] force_clip_self If true, also clips to the border area of the provided element regardless. /// @return True if a clipping region exists for the element and clip_origin and clip_window were set, false if not. - static bool GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_dimensions, Element* element); + static bool GetClippingRegion(Element* element, Rectanglei& clip_region, ClipMaskGeometryList* clip_mask_list = nullptr, + bool force_clip_self = false); /// Sets the clipping region from an element and its ancestors. /// @param[in] element The element to generate the clipping region from. - /// @param[in] context The context of the element; if this is not supplied, it will be derived from the element. + /// @param[in] force_clip_self If true, also clips to the border area of the provided element regardless. /// @return The visibility of the given element within its clipping region. - static bool SetClippingRegion(Element* element, Context* context = nullptr); - /// Applies the clip region from the render interface to the renderer - /// @param[in] context The context to read the clip region from - static void ApplyActiveClipRegion(Context* context); + static bool SetClippingRegion(Element* element, bool force_clip_self = false); + + /// 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, 'Auto' means the border box in addition to any ink overflow. + /// @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. @@ -122,9 +129,8 @@ class RMLUICORE_API ElementUtilities { static bool PositionElement(Element* element, Vector2f offset, PositionAnchor anchor); /// Applies an element's accumulated transform matrix, determined from its and ancestor's `perspective' and `transform' properties. - /// @param[in] element The element whose transform to apply. - /// @return true if a render interface is available to set the transform. - /// @note All calls to RenderInterface::SetTransform must go through here. + /// @param[in] element The element whose transform to apply, or nullptr for identity transform. + /// @return True if the transform could be submitted to the render interface. static bool ApplyTransform(Element& element); /// Creates data views and data controllers if a data model applies to the element. diff --git a/Include/RmlUi/Core/Factory.h b/Include/RmlUi/Core/Factory.h index 7e39e8c57..72be6ae90 100644 --- a/Include/RmlUi/Core/Factory.h +++ b/Include/RmlUi/Core/Factory.h @@ -49,10 +49,13 @@ class EventListener; class EventListenerInstancer; class FontEffect; class FontEffectInstancer; +class Filter; +class FilterInstancer; class StyleSheetContainer; class PropertyDictionary; class PropertySpecification; class DecoratorInstancerInterface; +class RenderManager; enum class EventId : uint16_t; /** @@ -77,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. @@ -126,6 +130,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..ffb04ce1f --- /dev/null +++ b/Include/RmlUi/Core/Filter.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_FILTER_H +#define RMLUI_CORE_FILTER_H + +#include "EffectSpecification.h" +#include "Header.h" +#include "Types.h" + +namespace Rml { + +class Element; +class PropertyDictionary; +class CompiledFilter; + +/** + 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 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. + /// @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; +}; + +/** + 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/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 7332f77fb..6b0acb55e 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 "TextShapingContext.h" #include "Types.h" @@ -104,18 +104,20 @@ class RMLUICORE_API FontEngineInterface { virtual int GetStringWidth(FontFaceHandle handle, const String& string, const TextShapingContext& text_shaping_context, 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. /// @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] text_shaping_context Additional parameters that provide context for text shaping. - /// @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, const TextShapingContext& text_shaping_context, 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, const TextShapingContext& text_shaping_context, + 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 3f698c84a..92a433e47 100644 --- a/Include/RmlUi/Core/Geometry.h +++ b/Include/RmlUi/Core/Geometry.h @@ -29,74 +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; -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); + enum class ReleaseMode { ReturnMesh, ClearMesh }; - /// 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(); + Geometry() = default; - /// Gets the geometry's texture. - /// @return The geometry's texture. - const Texture* GetTexture() const; - /// Sets the geometry's texture. - void SetTexture(const Texture* texture); + void Render(Vector2f translation, Texture texture = {}, const CompiledShader& shader = {}) const; - /// 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/Include/RmlUi/Core/ID.h b/Include/RmlUi/Core/ID.h index db4b34089..853e54f44 100644 --- a/Include/RmlUi/Core/ID.h +++ b/Include/RmlUi/Core/ID.h @@ -156,8 +156,13 @@ enum class PropertyId : uint8_t { Focus, Decorator, + MaskImage, FontEffect, + Filter, + BackdropFilter, + BoxShadow, + FillImage, AlignContent, 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/Include/RmlUi/Core/Math.h b/Include/RmlUi/Core/Math.h index 528169334..856a55cad 100644 --- a/Include/RmlUi/Core/Math.h +++ b/Include/RmlUi/Core/Math.h @@ -35,13 +35,17 @@ 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; using Vector2i = Vector2; +template +class Rectangle; +using Rectanglef = Rectangle; namespace Math { @@ -89,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. @@ -151,9 +155,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. @@ -208,6 +212,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/Source/Core/DecoratorTiledVerticalInstancer.h b/Include/RmlUi/Core/Mesh.h similarity index 72% rename from Source/Core/DecoratorTiledVerticalInstancer.h rename to Include/RmlUi/Core/Mesh.h index 4c6d31f29..147d78733 100644 --- a/Source/Core/DecoratorTiledVerticalInstancer.h +++ b/Include/RmlUi/Core/Mesh.h @@ -26,26 +26,28 @@ * */ -#ifndef RMLUI_CORE_DECORATORTILEDVERTICALINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDVERTICALINSTANCER_H +#ifndef RMLUI_CORE_MESH_H +#define RMLUI_CORE_MESH_H -#include "DecoratorTiledInstancer.h" +#include "Header.h" +#include "Texture.h" +#include "Vertex.h" namespace Rml { -/** - @author Peter Curry - */ +struct Mesh { + Vector vertices; + Vector indices; -class DecoratorTiledVerticalInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledVerticalInstancer(); - ~DecoratorTiledVerticalInstancer(); + explicit operator bool() const { return !indices.empty(); } +}; - /// Instances a vertical decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; +struct TexturedMesh { + Mesh mesh; + Texture texture; }; +using TexturedMeshList = Vector; + } // namespace Rml #endif diff --git a/Include/RmlUi/Core/GeometryUtilities.h b/Include/RmlUi/Core/MeshUtilities.h similarity index 65% rename from Include/RmlUi/Core/GeometryUtilities.h rename to Include/RmlUi/Core/MeshUtilities.h index f5eb43e12..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, Colourb 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,30 +60,40 @@ 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(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, Colourb color); + static void GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied 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. - static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour, - const Colourb* border_colours = nullptr); + /// @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(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. + /// @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(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/PropertySpecification.h b/Include/RmlUi/Core/PropertySpecification.h index d0ef42cf0..8391bb5c5 100644 --- a/Include/RmlUi/Core/PropertySpecification.h +++ b/Include/RmlUi/Core/PropertySpecification.h @@ -98,7 +98,7 @@ class RMLUICORE_API PropertySpecification { /// the order in which the values will be processed. /// @param[in] type The type of shorthand to declare. /// @param[in] id If 'Invalid' then automatically assigns a new id, otherwise assigns the given id. - /// @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, ShorthandId id = ShorthandId::Invalid); /// Returns a shorthand definition. @@ -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/Include/RmlUi/Core/Rectangle.h b/Include/RmlUi/Core/Rectangle.h index d6c0a1862..f1e9fe1d0 100644 --- a/Include/RmlUi/Core/Rectangle.h +++ b/Include/RmlUi/Core/Rectangle.h @@ -35,7 +35,7 @@ namespace Rml { /** - Templated class for a generic rectangle. + Templated class for a generic axis-aligned rectangle. */ template class Rectangle { @@ -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); } @@ -74,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 6c8cac955..21482ade8 100644 --- a/Include/RmlUi/Core/RenderInterface.h +++ b/Include/RmlUi/Core/RenderInterface.h @@ -30,13 +30,22 @@ #define RMLUI_CORE_RENDERINTERFACE_H #include "Header.h" -#include "Texture.h" #include "Traits.h" #include "Types.h" #include "Vertex.h" 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 BlendMode { + Blend, // Normal alpha blending. + Replace, // Replace the destination colors from the source. +}; + /** 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. @@ -49,67 +58,117 @@ 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. - /// @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); - /// 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. - virtual void RenderCompiledGeometry(CompiledGeometryHandle geometry, const Vector2f& translation); - /// 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_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 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 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) = 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; - /// 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 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_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 texture The texture handle to release. - virtual void ReleaseTexture(TextureHandle texture); + /** + @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. + 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 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. - /// 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, 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. + /// @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 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. + /// @param[in] name The name of the filter. + /// @param[in] parameters The list of name-value parameters specified for the 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. + virtual void ReleaseFilter(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. + /// @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 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. + virtual void ReleaseShader(CompiledShaderHandle shader); }; } // namespace Rml 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/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h new file mode 100644 index 000000000..0f0ed513c --- /dev/null +++ b/Include/RmlUi/Core/RenderManager.h @@ -0,0 +1,157 @@ +/* + * 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 "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; + 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 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(RenderInterface* render_interface); + ~RenderManager(); + + void PrepareRender(); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; + + 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); + + // 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(); + + 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); + + LayerHandle PushLayer(); + void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + void PopLayer(); + + LayerHandle GetTopLayer() const; + LayerHandle GetNextLayer() const; + + CompiledFilter SaveLayerAsMaskImage(); + +private: + void ApplyClipMask(const ClipMaskGeometryList& clip_elements); + + 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; + + bool ReleaseTexture(const String& texture_source); + 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; + + Vector render_stack; + + friend class RenderManagerAccess; +}; + +} // namespace Rml + +#endif diff --git a/Source/Core/GeometryDatabase.h b/Include/RmlUi/Core/Span.h similarity index 60% rename from Source/Core/GeometryDatabase.h rename to Include/RmlUi/Core/Span.h index ebedf6a6d..cd46c0cda 100644 --- a/Source/Core/GeometryDatabase.h +++ b/Include/RmlUi/Core/Span.h @@ -26,38 +26,46 @@ * */ -#ifndef RMLUI_CORE_GEOMETRYDATABASE_H -#define RMLUI_CORE_GEOMETRYDATABASE_H +#ifndef RMLUI_CORE_SPAN_H +#define RMLUI_CORE_SPAN_H -#include "../../Include/RmlUi/Core/Types.h" +#include "../Config/Config.h" +#include "Header.h" #include +#include namespace Rml { -class Geometry; -using GeometryDatabaseHandle = uint32_t; - /** - The geometry database stores a reference to all active geometry. + Basic implementation of a span, which refers to a contiguous sequence of objects. + */ - The intention is for the user to be able to re-compile all geometry in use. +template +class Span { +public: + Span() = default; + Span(T* data, size_t size) : m_data(data), m_size(size) { RMLUI_ASSERT(data != nullptr || size == 0); } - It is expected that every Insert() call is followed (at some later time) by - exactly one Erase() call with the same handle value. -*/ + Span(const Vector>& container) : Span(container.data(), container.size()) {} + Span(Vector& container) : Span(container.data(), container.size()) {} -namespace GeometryDatabase { + T& operator[](size_t index) const + { + RMLUI_ASSERT(index < m_size); + return m_data[index]; + } - GeometryDatabaseHandle Insert(Geometry* geometry); - void Erase(GeometryDatabaseHandle handle); + T* data() const { return m_data; } + size_t size() const { return m_size; } + bool empty() const { return m_size == 0; } - void ReleaseAll(); + T* begin() const { return m_data; } + T* end() const { return m_data + m_size; } -#ifdef RMLUI_TESTS_ENABLED - bool PrepareForTests(); - bool ListMatchesDatabase(const Vector& geometry_list); -#endif -} // namespace GeometryDatabase +private: + T* m_data = nullptr; + size_t m_size = 0; +}; } // namespace Rml #endif 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..92b1d7855 --- /dev/null +++ b/Include/RmlUi/Core/StableVector.h @@ -0,0 +1,125 @@ +/* + * 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. + + @note For simplicity, freed slots are simply replaced with value-initialized elements instead of being destroyed. + */ +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 6525bd016..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; @@ -67,8 +68,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 @@ -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(); @@ -103,7 +105,7 @@ class RMLUICORE_API StyleSheet final : public NonCopyMoveable { KeyframesMap keyframes; // Name of every @decorator mapped to their specification - DecoratorSpecificationMap 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 f86cf3c37..3d00d92d8 100644 --- a/Include/RmlUi/Core/StyleSheetTypes.h +++ b/Include/RmlUi/Core/StyleSheetTypes.h @@ -52,37 +52,32 @@ struct Keyframes { }; using KeyframesMap = UnorderedMap; -struct DecoratorSpecification { - String decorator_type; +struct NamedDecorator { + String type; + DecoratorInstancer* instancer; PropertyDictionary properties; - SharedPtr decorator; }; -using DecoratorSpecificationMap = UnorderedMap; +using NamedDecoratorMap = UnorderedMap; struct DecoratorDeclaration { String type; DecoratorInstancer* instancer; PropertyDictionary properties; + BoxArea paint_area; }; - -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; }; +struct FilterDeclaration { + String type; + FilterInstancer* instancer; + PropertyDictionary properties; +}; +struct FilterDeclarationList { + Vector list; + String value; +}; enum class MediaQueryModifier { None, diff --git a/Include/RmlUi/Core/Texture.h b/Include/RmlUi/Core/Texture.h index 533874f4b..8f1de2912 100644 --- a/Include/RmlUi/Core/Texture.h +++ b/Include/RmlUi/Core/Texture.h @@ -34,55 +34,53 @@ 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, const 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& GetSource() const; + const String& GetDefinitionSource() const; private: - SharedPtr resource; + String source; + String document_path; + mutable SmallUnorderedMap textures; }; } // namespace Rml diff --git a/Include/RmlUi/Core/TypeConverter.h b/Include/RmlUi/Core/TypeConverter.h index 19c874421..c588ed456 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 { @@ -78,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 { @@ -123,6 +134,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 { @@ -135,6 +156,28 @@ 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: + RMLUICORE_API static bool Convert(const BoxShadowList& src, BoxShadowList& dest); +}; +template <> +class TypeConverter { +public: + RMLUICORE_API static bool Convert(const BoxShadowList& src, String& dest); +}; + } // namespace Rml #include "TypeConverter.inl" diff --git a/Include/RmlUi/Core/TypeConverter.inl b/Include/RmlUi/Core/TypeConverter.inl index 53349265c..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 @@ -385,6 +384,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: @@ -422,7 +439,21 @@ 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 { +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 diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index 3aac5c4cf..a310249cb 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 { @@ -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" @@ -59,8 +60,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; @@ -77,6 +79,8 @@ using Matrix4f = RMLUI_MATRIX4_TYPE; class Element; class ElementInstancer; class ElementAnimation; +class RenderManager; +class Texture; class Context; class Event; class Property; @@ -89,6 +93,9 @@ struct Animation; struct Transition; struct TransitionList; struct DecoratorDeclarationList; +struct FilterDeclarationList; +struct ColorStop; +struct BoxShadow; enum class EventId : uint16_t; enum class PropertyId : uint8_t; enum class MediaQueryId : uint8_t; @@ -98,14 +105,20 @@ enum class FamilyId : int; 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; +using LayerHandle = uintptr_t; 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; @@ -125,10 +138,14 @@ struct FontEffects { FontEffectList list; String value; }; +using ColorStopList = Vector; +using BoxShadowList = Vector; +using FilterHandleList = Vector; // Additional smart pointers using TransformPtr = SharedPtr; using DecoratorsPtr = SharedPtr; +using FiltersPtr = SharedPtr; using FontEffectsPtr = SharedPtr; // Data binding types 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/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 542f2f73b..18132046b 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -75,12 +75,14 @@ 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 + BOXSHADOWLIST = 1 << 27, // shadow list; fetch as 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/Include/RmlUi/Core/Variant.h b/Include/RmlUi/Core/Variant.h index b10761b89..c9b1903cb 100644 --- a/Include/RmlUi/Core/Variant.h +++ b/Include/RmlUi/Core/Variant.h @@ -70,7 +70,10 @@ class RMLUICORE_API Variant { TRANSITIONLIST = 'T', ANIMATIONLIST = 'A', DECORATORSPTR = 'D', - FONTEFFECTSPTR = 'F', + FILTERSPTR = 'F', + FONTEFFECTSPTR = 'E', + COLORSTOPLIST = 'C', + BOXSHADOWLIST = 'S', VOIDPTR = '*', }; @@ -153,8 +156,14 @@ 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); + void Set(const ColorStopList& value); + void Set(ColorStopList&& value); + void Set(const BoxShadowList& value); + void Set(BoxShadowList&& value); template ::value>> void Set(const T value); diff --git a/Include/RmlUi/Core/Variant.inl b/Include/RmlUi/Core/Variant.inl index b80b7ec82..c5422af29 100644 --- a/Include/RmlUi/Core/Variant.inl +++ b/Include/RmlUi/Core/Variant.inl @@ -73,7 +73,10 @@ 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 COLORSTOPLIST: return TypeConverter::Convert(*(ColorStopList*)data, value); break; + case BOXSHADOWLIST: return TypeConverter::Convert(*reinterpret_cast(data), value); case NONE: break; } 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/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 3a7cb8a6a..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, const Colourb& 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 12591746d..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, const Colourb& 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 fbb6bba58..831be3140 100644 --- a/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp +++ b/Samples/basic/bitmapfont/src/FontEngineInterfaceBitmap.cpp @@ -81,11 +81,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, const Colourb& colour, float /*opacity*/, const TextShapingContext& /*text_shaping_context*/, GeometryList& geometry) +int FontEngineInterfaceBitmap::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle /*font_effects_handle*/, + const String& string, const Vector2f& position, ColourbPremultiplied colour, float /*opacity*/, + const TextShapingContext& /*text_shaping_context*/, 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 ef4b56be4..fbc1a2589 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; @@ -48,8 +48,9 @@ using Rml::Style::FontWeight; using Rml::FontEffectList; using Rml::FontMetrics; -using Rml::GeometryList; +using Rml::RenderManager; using Rml::TextShapingContext; +using Rml::TexturedMeshList; class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { public: @@ -81,8 +82,9 @@ class FontEngineInterfaceBitmap : public Rml::FontEngineInterface { 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, - const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, 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, const TextShapingContext& text_shaping_context, + 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/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/basic/demo/data/demo.rml b/Samples/basic/demo/data/demo.rml index 8b0a112dd..74c2b975d 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 @@ -360,7 +360,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/Samples/basic/demo/src/main.cpp b/Samples/basic/demo/src/main.cpp index fb6491838..fa2dafb49 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(); diff --git a/Samples/basic/effect/data/effect.rml b/Samples/basic/effect/data/effect.rml new file mode 100644 index 000000000..5e60e28e9 --- /dev/null +++ b/Samples/basic/effect/data/effect.rml @@ -0,0 +1,194 @@ + + + + +Effect Sample + + + +

Effect Sample

+ + + +
+
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?
+ +
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 new file mode 100644 index 000000000..106faab4b --- /dev/null +++ b/Samples/basic/effect/data/effect_style.rcss @@ -0,0 +1,199 @@ +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; + z-index: 100; + 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: 480dp; + overflow: auto; + overscroll-behavior: contain; + + 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 { + z-index: 100; + 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..cf184aefb --- /dev/null +++ b/Samples/basic/effect/src/main.cpp @@ -0,0 +1,158 @@ +/* + * 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; + 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; + float blur = 0.0f; + bool drop_shadow = false; + } 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("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("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); + 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, false); + + context->Update(); + + Backend::BeginFrame(); + context->Render(); + Backend::PresentFrame(); + } + + Rml::Shutdown(); + + Backend::Shutdown(); + Shell::Shutdown(); + + return 0; +} diff --git a/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp b/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp index 219665bdf..4c2618d15 100644 --- a/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp +++ b/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.cpp @@ -76,11 +76,12 @@ int FontEngineInterfaceHarfBuzz::GetStringWidth(FontFaceHandle handle, const Str return handle_harfbuzz->GetStringWidth(string, text_shaping_context, registered_languages, prior_character); } -int FontEngineInterfaceHarfBuzz::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string, - const Vector2f& position, const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, GeometryList& geometry) +int FontEngineInterfaceHarfBuzz::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle, + const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) { auto handle_harfbuzz = reinterpret_cast(handle); - return handle_harfbuzz->GenerateString(geometry, string, position, colour, opacity, text_shaping_context, registered_languages, + return handle_harfbuzz->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context, registered_languages, (int)font_effects_handle); } diff --git a/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h b/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h index d72aa9854..48befdf19 100644 --- a/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h +++ b/Samples/basic/harfbuzzshaping/src/FontEngineInterfaceHarfBuzz.h @@ -30,18 +30,19 @@ #define FONTENGINEINTERFACEHARFBUZZ_H #include "LanguageData.h" -#include +#include using Rml::byte; using Rml::Character; -using Rml::Colourb; -using Rml::FontFaceHandle; +using Rml::ColourbPremultiplied; using Rml::FontEffectList; using Rml::FontEffectsHandle; +using Rml::FontFaceHandle; using Rml::FontMetrics; -using Rml::GeometryList; +using Rml::RenderManager; using Rml::String; using Rml::TextShapingContext; +using Rml::TexturedMeshList; using Rml::Vector2f; namespace Style = Rml::Style; @@ -63,17 +64,19 @@ class FontEngineInterfaceHarfBuzz : public Rml::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, const TextShapingContext& text_shaping_context, Character prior_character) override; + int GetStringWidth(FontFaceHandle handle, const String& string, const TextShapingContext& text_shaping_context, + 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, - const TextShapingContext& text_shaping_context, GeometryList& geometry) override; + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, const String& string, + const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) override; /// Returns the current version of the font face. int GetVersion(FontFaceHandle handle) override; diff --git a/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp b/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp index bfb660017..017aa5bb6 100644 --- a/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp +++ b/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.cpp @@ -33,9 +33,10 @@ #include "FreeTypeInterface.h" #include #include FT_FREETYPE_H -#include -#include #include +#include +#include +#include static bool IsASCIIControlCharacter(Character c) { @@ -198,8 +199,8 @@ int FontFaceHandleHarfBuzz::GenerateLayerConfiguration(const FontEffectList& fon return (int)(layer_configurations.size() - 1); } -bool FontFaceHandleHarfBuzz::GenerateLayerTexture(UniquePtr& texture_data, Vector2i& texture_dimensions, - const FontEffect* font_effect, int texture_id, int handle_version) const +bool FontFaceHandleHarfBuzz::GenerateLayerTexture(Vector& texture_data, Vector2i& texture_dimensions, const FontEffect* font_effect, + int texture_id, int handle_version) const { if (handle_version != version) { @@ -218,9 +219,9 @@ bool FontFaceHandleHarfBuzz::GenerateLayerTexture(UniquePtr& textu return it->layer->GenerateTexture(texture_data, texture_dimensions, texture_id, glyphs); } -int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String& string, const Vector2f position, const Colourb colour, - const float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, - const int layer_configuration_index) +int FontFaceHandleHarfBuzz::GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, const Vector2f position, + const ColourbPremultiplied colour, const float opacity, const TextShapingContext& text_shaping_context, + const LanguageDataMap& registered_languages, const int layer_configuration_index) { int geometry_index = 0; int line_width = 0; @@ -233,46 +234,38 @@ int FontFaceHandleHarfBuzz::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(); }); + + mesh_list.resize(num_geometries); hb_buffer_t* shaping_buffer = hb_buffer_create(); RMLUI_ASSERT(shaping_buffer != nullptr); - for (size_t i = 0; i < layer_configuration.size(); ++i) + for (size_t layer_index = 0; layer_index < layer_configuration.size(); ++layer_index) { - FontFaceLayer* layer = layer_configuration[i]; + FontFaceLayer* layer = layer_configuration[layer_index]; - 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(); - 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; FontGlyphIndex prior_glyph_codepoint = 0; + // 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); + // Set up and apply text shaping. hb_buffer_clear_contents(shaping_buffer); ConfigureTextShapingBuffer(shaping_buffer, string, text_shaping_context, registered_languages); @@ -282,8 +275,8 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String& unsigned int glyph_count = 0; hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(shaping_buffer, &glyph_count); - geometry[geometry_index].GetIndices().reserve(glyph_count * 6); - geometry[geometry_index].GetVertices().reserve(glyph_count * 4); + mesh_list[geometry_index].mesh.indices.reserve(string.size() * 6); + mesh_list[geometry_index].mesh.vertices.reserve(string.size() * 4); for (int g = 0; g < (int)glyph_count; ++g) { @@ -300,12 +293,12 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String& // Adjust the cursor for the kerning between this character and the previous one. line_width += GetKerning(prior_glyph_codepoint, glyph_codepoint); + ColourbPremultiplied glyph_color = layer_colour; // Use white vertex colors on RGB glyphs. - const Colourb glyph_color = - (layer == base_layer && glyph->color_format == Rml::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], glyph_codepoint, Vector2f(position.x + line_width, position.y), - glyph_color); + layer->GenerateGeometry(&mesh_list[geometry_index], glyph_codepoint, Vector2f(position.x + line_width, position.y), glyph_color); line_width += glyph->advance; line_width += (int)text_shaping_context.letter_spacing; @@ -317,9 +310,6 @@ int FontFaceHandleHarfBuzz::GenerateString(GeometryList& geometry, const String& hb_buffer_destroy(shaping_buffer); - // Cull any excess geometry from a previous generation. - geometry.resize(geometry_index); - return Rml::Math::Max(line_width, 0); } @@ -371,7 +361,7 @@ void FontFaceHandleHarfBuzz::FillKerningPairCache() for (char32_t j = KerningCache_AsciiSubsetBegin; j <= KerningCache_AsciiSubsetLast; j++) { const bool first_iteration = (i == KerningCache_AsciiSubsetBegin && j == KerningCache_AsciiSubsetBegin); - + // Fetch the kerning from the font face. Submit zero font size on subsequent iterations for performance reasons. const int kerning = FreeType::GetKerning(ft_face, first_iteration ? metrics.size : 0, FreeType::GetGlyphIndexFromCharacter(ft_face, Character(i)), FreeType::GetGlyphIndexFromCharacter(ft_face, Character(j))); @@ -390,7 +380,7 @@ int FontFaceHandleHarfBuzz::GetKerning(FontGlyphIndex lhs, FontGlyphIndex rhs) c return 0; // See if the kerning pair has been cached. - const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs))); + const auto it = kerning_pair_cache.find(AsciiPair((int(lhs) << 8) | int(rhs))); if (it != kerning_pair_cache.end()) { return it->second; diff --git a/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h b/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h index 2e6679873..e6e16c157 100644 --- a/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h +++ b/Samples/basic/harfbuzzshaping/src/FontFaceHandleHarfBuzz.h @@ -43,11 +43,12 @@ using Rml::FontEffectList; using Rml::FontFaceHandleFreetype; using Rml::FontGlyph; using Rml::FontMetrics; -using Rml::GeometryList; +using Rml::RenderManager; using Rml::SharedPtr; using Rml::SmallUnorderedMap; using Rml::String; using Rml::TextShapingContext; +using Rml::TexturedMeshList; using Rml::UniquePtr; using Rml::UnorderedMap; using Rml::Vector; @@ -84,16 +85,17 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable { /// @return The index to use when generating geometry using this configuration. int GenerateLayerConfiguration(const FontEffectList& font_effects); /// 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] 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. - /// @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. @@ -102,8 +104,9 @@ class FontFaceHandleHarfBuzz : public Rml::NonCopyMoveable { /// @param[in] registered_languages A list of languages registered in the font engine interface. /// @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, - const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, int layer_configuration = 0); + int GenerateString(RenderManager& render_manager, TexturedMeshList& mesh_list, const String& string, Vector2f position, + ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, const LanguageDataMap& registered_languages, + int layer_configuration = 0); /// Version is changed whenever the layers are dirtied, requiring regeneration of string geometry. int GetVersion() const; diff --git a/Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp b/Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp index ab5c4ca1f..01df3e492 100644 --- a/Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp +++ b/Samples/basic/harfbuzzshaping/src/FontFaceLayer.cpp @@ -47,7 +47,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac // 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(); @@ -58,9 +59,8 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac // 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) @@ -156,26 +156,28 @@ bool FontFaceLayer::Generate(const FontFaceHandleHarfBuzz* handle, const FontFac { const int texture_id = i; - Rml::TextureCallback texture_callback = [handle, effect_ptr, texture_id, handle_version](Rml::RenderInterface* render_interface, - const String& /*name*/, Rml::TextureHandle& out_texture_handle, Vector2i& out_dimensions) -> bool { - UniquePtr data; - if (!handle->GenerateLayerTexture(data, out_dimensions, effect_ptr, texture_id, handle_version) || !data) + CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( + const CallbackTextureInterface& texture_interface) -> bool { + Vector2i dimensions; + Vector data; + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty()) return false; - if (!render_interface->GenerateTexture(out_texture_handle, data.get(), out_dimensions)) + if (!texture_interface.GenerateTexture(data, dimensions)) return false; return true; }; - Texture texture; - texture.Set("font-face-layer", 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)); } } 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; @@ -206,8 +208,6 @@ bool FontFaceLayer::GenerateTexture(UniquePtr& texture_data, Vecto // Copy the glyph's bitmap data into its allocated texture. if (glyph.bitmap_data) { - using Rml::ColorFormat; - byte* destination = rectangle.GetTextureData(); const byte* source = glyph.bitmap_data; const int num_bytes_per_line = glyph.bitmap_dimensions.x * (glyph.color_format == ColorFormat::RGBA8 ? 4 : 1); @@ -218,8 +218,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: @@ -243,47 +245,25 @@ bool FontFaceLayer::GenerateTexture(UniquePtr& texture_data, Vecto return true; } -void FontFaceLayer::GenerateGeometry(Geometry* geometry, const FontGlyphIndex glyph_index, const Vector2f position, const Colourb colour) const -{ - auto it = character_boxes.find(glyph_index); - if (it == character_boxes.end()) - return; - - const TextureBox& box = it->second; - - if (box.texture_index < 0) - 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); - Rml::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); -} - 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(); } -Colourb FontFaceLayer::GetColour() const +ColourbPremultiplied FontFaceLayer::GetColour(float opacity) const { - return colour; + return colour.ToPremultiplied(opacity); } diff --git a/Samples/basic/harfbuzzshaping/src/FontFaceLayer.h b/Samples/basic/harfbuzzshaping/src/FontFaceLayer.h index 6e8f7f1dd..631b01d74 100644 --- a/Samples/basic/harfbuzzshaping/src/FontFaceLayer.h +++ b/Samples/basic/harfbuzzshaping/src/FontFaceLayer.h @@ -29,17 +29,27 @@ #ifndef FONTFACELAYER_H #define FONTFACELAYER_H -#include #include "FontGlyph.h" #include "TextureLayout.h" +#include using Rml::byte; +using Rml::CallbackTextureFunction; +using Rml::CallbackTextureInterface; +using Rml::CallbackTextureSource; +using Rml::Character; +using Rml::ColorFormat; using Rml::Colourb; +using Rml::ColourbPremultiplied; using Rml::FontEffect; using Rml::Geometry; +using Rml::Mesh; +using Rml::RenderManager; using Rml::SharedPtr; using Rml::Texture; +using Rml::TexturedMesh; using Rml::TextureLayout; +using Rml::TextureLayoutRectangle; using Rml::UniquePtr; using Rml::UnorderedMap; using Rml::Vector; @@ -63,40 +73,53 @@ 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 FontFaceHandleHarfBuzz* 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] 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[in] glyph_index The font's glyph index of the character to generate geometry for. + /// @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. - void GenerateGeometry(Geometry* geometry, const FontGlyphIndex glyph_index, const Vector2f position, const Colourb colour) const; + inline void GenerateGeometry(TexturedMesh* mesh_list, const FontGlyphIndex glyph_index, const Vector2f position, + const ColourbPremultiplied colour) const + { + auto it = character_boxes.find(glyph_index); + if (it == character_boxes.end()) + return; + + const TextureBox& box = it->second; + + if (box.texture_index < 0) + return; + + // Generate the geometry for the character. + Mesh& mesh = mesh_list[box.texture_index].mesh; + Rml::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; - /// 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 { - 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. @@ -105,18 +128,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/Samples/basic/harfbuzzshaping/src/main.cpp b/Samples/basic/harfbuzzshaping/src/main.cpp index 23a86f39e..7b17259ff 100644 --- a/Samples/basic/harfbuzzshaping/src/main.cpp +++ b/Samples/basic/harfbuzzshaping/src/main.cpp @@ -37,7 +37,7 @@ */ // Toggle this variable to enable/disable text shaping. -constexpr bool EnableTextShaping = true; +constexpr bool EnableTextShaping = false; class HarfBuzzEventListener : public Rml::EventListener { public: diff --git a/Samples/invaders/data/high_score.rml b/Samples/invaders/data/high_score.rml index e523e2dcb..a5718c719 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 a4a8b56b7..6d979dc52 100644 --- a/Samples/invaders/src/DecoratorDefender.cpp +++ b/Samples/invaders/src/DecoratorDefender.cpp @@ -29,10 +29,18 @@ #include "DecoratorDefender.h" #include #include -#include +#include #include -#include +#include +#include +#include #include +#include + +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; DecoratorDefender::~DecoratorDefender() {} @@ -47,28 +55,61 @@ 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; -} - -void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_data*/) const {} + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -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::RenderInterface* render_interface = ::Rml::GetRenderInterface()) - { - Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); - Rml::Colourb color = element->GetProperty("color"); + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), size, color); + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); - } + 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() +{ + 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..2324db157 100644 --- a/Samples/invaders/src/DecoratorDefender.h +++ b/Samples/invaders/src/DecoratorDefender.h @@ -38,20 +38,28 @@ 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: 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..3a59dd484 100644 --- a/Samples/invaders/src/DecoratorStarfield.cpp +++ b/Samples/invaders/src/DecoratorStarfield.cpp @@ -32,11 +32,9 @@ #include #include #include -#include #include -#include +#include #include -#include DecoratorStarfield::~DecoratorStarfield() {} @@ -54,7 +52,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(); @@ -116,7 +114,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++) { @@ -125,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) @@ -151,3 +150,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..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: @@ -78,4 +73,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/Defender.cpp b/Samples/invaders/src/Defender.cpp index b75a9512a..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::Colourb color = GameDetails::GetDefenderColour(); + 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 d8dda330f..3c1a191c9 100644 --- a/Samples/invaders/src/ElementGame.cpp +++ b/Samples/invaders/src/ElementGame.cpp @@ -85,7 +85,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/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/invaders/src/Invader.cpp b/Samples/invaders/src/Invader.cpp index ceae8f90c..803f7beb1 100644 --- a/Samples/invaders/src/Invader.cpp +++ b/Samples/invaders/src/Invader.cpp @@ -171,21 +171,21 @@ 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::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); 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 741e2637e..9b61b04ab 100644 --- a/Samples/invaders/src/Shield.cpp +++ b/Samples/invaders/src/Shield.cpp @@ -116,14 +116,14 @@ 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) { 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); @@ -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 e5545ae00..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::Colourb 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 9b43d94cd..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::Colourb 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; @@ -48,11 +50,11 @@ class Sprite { }; struct ColoredPoint { - Rml::Colourb color; + Rml::ColourbPremultiplied color; Rml::Vector2f position; }; 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/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/data/high_score.rml b/Samples/luainvaders/data/high_score.rml index 61d48879b..0fe534766 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 a4a8b56b7..6d979dc52 100644 --- a/Samples/luainvaders/src/DecoratorDefender.cpp +++ b/Samples/luainvaders/src/DecoratorDefender.cpp @@ -29,10 +29,18 @@ #include "DecoratorDefender.h" #include #include -#include +#include #include -#include +#include +#include +#include #include +#include + +struct DecoratorDefenderElementData { + Rml::Texture texture; + Rml::Geometry geometry; +}; DecoratorDefender::~DecoratorDefender() {} @@ -47,28 +55,61 @@ 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; -} - -void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_data*/) const {} + Rml::RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return Rml::Decorator::INVALID_DECORATORDATAHANDLE; -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::RenderInterface* render_interface = ::Rml::GetRenderInterface()) - { - Rml::TextureHandle texture = GetTexture(image_index)->GetHandle(); - Rml::Colourb color = element->GetProperty("color"); + Rml::ColourbPremultiplied color = element->GetProperty("image-color").ToPremultiplied(); + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(0.f), size, color); - Rml::Vertex vertices[4]; - int indices[6]; - Rml::GeometryUtilities::GenerateQuad(vertices, indices, Rml::Vector2f(0.f), size, color); + DecoratorDefenderElementData* element_data = new DecoratorDefenderElementData{ + GetTexture(image_index), + render_manager->MakeGeometry(std::move(mesh)), + }; - render_interface->RenderGeometry(vertices, 4, indices, 6, texture, position); - } + 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() +{ + 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..d23cf42c8 100644 --- a/Samples/luainvaders/src/DecoratorDefender.h +++ b/Samples/luainvaders/src/DecoratorDefender.h @@ -38,20 +38,28 @@ 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: 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..3a59dd484 100644 --- a/Samples/luainvaders/src/DecoratorStarfield.cpp +++ b/Samples/luainvaders/src/DecoratorStarfield.cpp @@ -32,11 +32,9 @@ #include #include #include -#include #include -#include +#include #include -#include DecoratorStarfield::~DecoratorStarfield() {} @@ -54,7 +52,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(); @@ -116,7 +114,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++) { @@ -125,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) @@ -151,3 +150,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..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: @@ -78,4 +73,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/Defender.cpp b/Samples/luainvaders/src/Defender.cpp index b75a9512a..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::Colourb color = GameDetails::GetDefenderColour(); + 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/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/Samples/luainvaders/src/Invader.cpp b/Samples/luainvaders/src/Invader.cpp index ceae8f90c..803f7beb1 100644 --- a/Samples/luainvaders/src/Invader.cpp +++ b/Samples/luainvaders/src/Invader.cpp @@ -171,21 +171,21 @@ 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::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); 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 741e2637e..9b61b04ab 100644 --- a/Samples/luainvaders/src/Shield.cpp +++ b/Samples/luainvaders/src/Shield.cpp @@ -116,14 +116,14 @@ 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) { 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); @@ -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 e5545ae00..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::Colourb 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 ae2c7f740..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::Colourb 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; @@ -48,11 +49,11 @@ class Sprite { }; struct ColoredPoint { - Rml::Colourb color; + Rml::ColourbPremultiplied color; Rml::Vector2f position; }; 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/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/Samples/shell/src/PlatformExtensions.cpp b/Samples/shell/src/PlatformExtensions.cpp index 05599e50e..00174c26f 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(); diff --git a/Source/Core/CallbackTexture.cpp b/Source/Core/CallbackTexture.cpp new file mode 100644 index 000000000..c86c5db20 --- /dev/null +++ b/Source/Core/CallbackTexture.cpp @@ -0,0 +1,106 @@ +/* + * 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(Span source, Vector2i new_dimensions) const +{ + if (texture_handle) + { + RMLUI_ERRORMSG("Texture already set"); + return false; + } + texture_handle = render_interface.GenerateTexture(source, new_dimensions); + if (texture_handle) + dimensions = new_dimensions; + return texture_handle != TextureHandle{}; +} + +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 68fd2060f..39345c8cb 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 "../../Include/RmlUi/Core/Debug.h" @@ -54,9 +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), clip_origin(-1, -1), clip_dimensions(-1, -1), - next_update_timeout(0) +Context::Context(const String& name, RenderManager* render_manager) : name(name), render_manager(render_manager) { instancer = nullptr; @@ -126,6 +124,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 +140,6 @@ void Context::SetDimensions(const Vector2i _dimensions) document->DispatchEvent(EventId::Resize, Dictionary()); } } - - clip_dimensions = dimensions; } } @@ -218,12 +215,10 @@ bool Context::Render() { RMLUI_ZoneScoped; - ElementUtilities::ApplyActiveClipRegion(this); + render_manager->PrepareRender(); 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 +228,8 @@ bool Context::Render() cursor_proxy->Render(); } + render_manager->ResetState(); + return true; } @@ -855,21 +852,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) @@ -1163,7 +1148,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; @@ -1177,7 +1162,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; } @@ -1193,10 +1178,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; @@ -1204,12 +1190,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; } } @@ -1225,12 +1211,10 @@ Element* Context::GetElementAtPoint(Vector2f point, const Element* ignore_elemen bool within_element = (projection_result && element->IsPointWithinElement(point)); if (within_element) { - 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); - } + // The element may have been clipped out of view if it overflows an ancestor, so check its clipping region. + Rectanglei clip_region; + if (ElementUtilities::GetClippingRegion(element, clip_region)) + within_element = clip_region.Contains(Vector2i(point)); } if (within_element) 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/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/Core.cpp b/Source/Core/Core.cpp index 0ae9a4237..7d02df9a2 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; @@ -91,19 +94,12 @@ 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) { 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) { @@ -129,7 +125,9 @@ bool Initialise() EventSpecificationInterface::Initialize(); - TextureDatabase::Initialise(); + render_managers = MakeUnique>>(); + if (render_interface) + (*render_managers)[render_interface] = MakeUnique(render_interface); font_interface->Initialize(); @@ -175,7 +173,7 @@ void Shutdown() font_interface->Shutdown(); - TextureDatabase::Shutdown(); + render_managers.reset(); initialised = false; @@ -187,9 +185,6 @@ void Shutdown() default_font_interface.reset(); default_file_interface.reset(); - Log::Shutdown(); - - // Release any memory pools ReleaseMemoryPools(); } @@ -210,12 +205,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; } @@ -244,18 +233,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()); @@ -350,30 +354,51 @@ 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; } -bool ReleaseTexture(const String& source) +void ReleaseTextures(RenderInterface* match_render_interface) { - return TextureDatabase::ReleaseTexture(source); + 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 ReleaseCompiledGeometry() +bool ReleaseTexture(const String& source, RenderInterface* match_render_interface) { - return GeometryDatabase::ReleaseAll(); + if (!render_managers) + return false; + bool result = false; + for (auto& render_manager : *render_managers) + { + if (!match_render_interface || render_manager.first == match_render_interface) + { + if (RenderManagerAccess::ReleaseTexture(render_manager.second.get(), source)) + result = true; + } + } + return result; } -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()); } } @@ -391,4 +416,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 44a188466..074876d56 100644 --- a/Source/Core/Decorator.cpp +++ b/Source/Core/Decorator.cpp @@ -28,8 +28,9 @@ #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 "TextureDatabase.h" #include namespace Rml { @@ -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,41 @@ 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() {} + +DecoratorInstancer::~DecoratorInstancer() {} + +const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const +{ + return style_sheet.GetSprite(name); +} + +Texture DecoratorInstancerInterface::GetTexture(const String& filename) const +{ + 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 {}; + } + + return render_manager.LoadTexture(filename, property_source->path); +} + +RenderManager& DecoratorInstancerInterface::GetRenderManager() const +{ + return render_manager; } } // namespace Rml diff --git a/Source/Core/DecoratorGradient.cpp b/Source/Core/DecoratorGradient.cpp index f1c9f3510..b86c49492 100644 --- a/Source/Core/DecoratorGradient.cpp +++ b/Source/Core/DecoratorGradient.cpp @@ -31,115 +31,723 @@ #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" -/* -Gradient decorator usage in CSS: +namespace Rml { -decorator: gradient( direction start-color stop-color ); +// 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; +} -direction: horizontal|vertical; -start-color: #ff00ff; -stop-color: #00ff00; -*/ +/// 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(); -namespace Rml { + // 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); + } + } -DecoratorGradient::DecoratorGradient() {} + // 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); -DecoratorGradient::~DecoratorGradient() {} + // 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; -bool DecoratorGradient::Initialise(const Direction dir_, const Colourb start_, const Colourb stop_) + 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; +} + +// 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() {} + +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, BoxArea paint_area) const { - Geometry* geometry = new Geometry(); const Box& box = element->GetBox(); 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()); + Mesh mesh; + MeshUtilities::GenerateBackground(mesh, 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 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(); + Vector& vertices = mesh.vertices; - if (dir == Direction::Horizontal) + if (direction == Direction::Horizontal) { 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); } } - else if (dir == Direction::Vertical) + else if (direction == Direction::Vertical) { 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); } } + Geometry* geometry = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + 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; +} + +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, BoxArea paint_area) const +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + RMLUI_ASSERT(!color_stops.empty()); + + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(paint_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); + + CompiledShader shader = render_manager->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 (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), paint_area); + + const Vector2f render_offset = box.GetPosition(paint_area); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + + return reinterpret_cast(element_data); +} + +void DecoratorLinearGradient::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorLinearGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +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; +} + +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 +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + 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); + + 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; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + + const Vector2f render_offset = box.GetPosition(box_area); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + 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); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorRadialGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +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 +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + 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); + + 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; + + Mesh mesh; + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), box_area); + + const Vector2f render_offset = box.GetPosition(box_area); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = vertex.position - render_offset; + + 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); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorConicGradient::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +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; } diff --git a/Source/Core/DecoratorGradient.h b/Source/Core/DecoratorGradient.h index 7906cecf6..cee2f7645 100644 --- a/Source/Core/DecoratorGradient.h +++ b/Source/Core/DecoratorGradient.h @@ -29,43 +29,203 @@ #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/DecoratorInstancer.h" +#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/ID.h" namespace Rml { -class DecoratorGradient : public Decorator { +using Vector2Numeric = Vector2; + +/** + 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; + 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: - Direction dir = {}; + Direction direction = {}; Colourb start, stop; }; -class DecoratorGradientInstancer : public DecoratorInstancer { +class DecoratorStraightGradientInstancer : public DecoratorInstancer { public: - DecoratorGradientInstancer(); - ~DecoratorGradientInstancer(); + DecoratorStraightGradientInstancer(); + virtual ~DecoratorStraightGradientInstancer(); SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, const DecoratorInstancerInterface& instancer_interface) override; private: - struct GradientPropertyIds { + struct PropertyIds { PropertyId direction, start, stop; }; + 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, BoxArea paint_area) 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; + }; + + 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(); + virtual ~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; +}; + +/** + 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; }; diff --git a/Source/Core/DecoratorNinePatch.cpp b/Source/Core/DecoratorNinePatch.cpp index ba5587e59..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; @@ -54,22 +54,17 @@ 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(); - Geometry* data = new Geometry(); + Texture texture = GetTexture(); + const Vector2f texture_dimensions(texture.GetDimensions()); - const Texture* texture = GetTexture(); - data->SetTexture(texture); - 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(); - const Vector2f surface_dimensions = element->GetBox().GetSize(BoxArea::Padding).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. */ @@ -126,14 +121,18 @@ 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(); /* 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); @@ -165,6 +164,8 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co indices[i + 5] = top_left_index + 5; } + Geometry* data = new Geometry(element->GetRenderManager()->MakeGeometry(std::move(mesh))); + return reinterpret_cast(data); } @@ -176,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::Padding)); + data->Render(element->GetAbsoluteOffset(BoxArea::Border), GetTexture()); } DecoratorNinePatchInstancer::DecoratorNinePatchInstancer() @@ -241,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 698309bc0..379650e0c 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" @@ -41,10 +40,10 @@ 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) 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/DecoratorShader.cpp b/Source/Core/DecoratorShader.cpp new file mode 100644 index 000000000..997068362 --- /dev/null +++ b/Source/Core/DecoratorShader.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 "DecoratorShader.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.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 +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return INVALID_DECORATORDATAHANDLE; + + const Box& box = element->GetBox(); + const Vector2f dimensions = box.GetSize(render_area); + CompiledShader shader = render_manager->CompileShader("shader", Dictionary{{"value", Variant(value)}, {"dimensions", Variant(dimensions)}}); + if (!shader) + return INVALID_DECORATORDATAHANDLE; + + Mesh mesh; + + const ComputedValues& computed = element->GetComputedValues(); + const byte alpha = byte(computed.opacity() * 255.f); + MeshUtilities::GenerateBackground(mesh, box, Vector2f(), computed.border_radius(), ColourbPremultiplied(alpha, alpha), render_area); + + const Vector2f offset = box.GetPosition(render_area); + for (Vertex& vertex : mesh.vertices) + vertex.tex_coord = (vertex.position - offset) / dimensions; + + ShaderElementData* element_data = + GetShaderElementDataPool().AllocateAndConstruct(render_manager->MakeGeometry(std::move(mesh)), std::move(shader)); + + return reinterpret_cast(element_data); +} + +void DecoratorShader::ReleaseElementData(DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + GetShaderElementDataPool().DestroyAndDeallocate(element_data); +} + +void DecoratorShader::RenderElement(Element* element, DecoratorDataHandle handle) const +{ + ShaderElementData* element_data = reinterpret_cast(handle); + element_data->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), {}, element_data->shader); +} + +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..2ca3e8cc4 --- /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, CompiledShader&& shader) : geometry(std::move(geometry)), shader(std::move(shader)) {} + Geometry geometry; + CompiledShader shader; +}; +Pool& GetShaderElementDataPool(); + +} // namespace Rml +#endif diff --git a/Source/Core/DecoratorTiled.cpp b/Source/Core/DecoratorTiled.cpp index 1fdc2f5db..9206ee5fa 100644 --- a/Source/Core/DecoratorTiled.cpp +++ b/Source/Core/DecoratorTiled.cpp @@ -29,8 +29,11 @@ #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 namespace Rml { @@ -53,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) { @@ -96,17 +99,13 @@ 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; - float opacity = computed.opacity(); - Colourb quad_colour = computed.image_color(); - - // Apply opacity - quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha); + const ColourbPremultiplied quad_colour = computed.image_color().ToPremultiplied(computed.opacity()); if (!tile_data_calculated) return; @@ -224,22 +223,11 @@ void DecoratorTiled::Tile::GenerateGeometry(Vector& vertices, VectorGet(); + + // 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]; + + const Sprite* sprite = instancer_interface.GetSprite(texture_name); + + // A tile is always either a sprite or an image. + if (sprite) + { + tile.position = sprite->rectangle.Position(); + tile.size = sprite->rectangle.Size(); + tile.display_scale = sprite->sprite_sheet->display_scale; + + texture = sprite->sprite_sheet->texture_source.GetTexture(instancer_interface.GetRenderManager()); + } + 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(); + + if (sprite && + (tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT || tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_X || + tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_Y)) + { + Log::Message(Log::LT_WARNING, "Decorator 'fit' value is '%s', which is incompatible with sprites", fit_property.ToString().c_str()); + return false; + } + + 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 05bc561ba..7bd5e88f9 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. @@ -82,7 +83,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; @@ -94,8 +95,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 @@ -129,5 +130,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..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 { @@ -95,16 +96,17 @@ 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++) { RMLUI_ASSERT(tiles[i].texture_index >= 0); - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); + 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,114 +134,108 @@ 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; } - 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, Vector2f(0, 0), 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, Vector2f(top_left.x, 0), - Vector2f(padded_size.x - (top_left.x + top_right.x), top.y), top); + 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, Vector2f(padded_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, Vector2f(0, top_left.y), - Vector2f(left.x, padded_size.y - (top_left.y + bottom_left.y)), left); + 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, Vector2f((padded_size.x - right.x), top_right.y), - Vector2f(right.x, padded_size.y - (top_right.y + bottom_right.y)), right); + 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, Vector2f(0, padded_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, Vector2f(bottom_left.x, padded_size.y - bottom.y), - Vector2f(padded_size.x - (bottom_left.x + bottom_right.x), bottom.y), bottom); + 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, - Vector2f(padded_size.x - bottom_right.x, padded_size.y - bottom_right.y), bottom_right, bottom_right); + 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(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(mesh[tiles[CENTRE].texture_index], computed, offset + Vector2f(left.x, top.y), centre_surface_dimensions, + centre_dimensions); - 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); + const int num_textures = GetNumTextures(); + DecoratorTiledBoxData* data = new DecoratorTiledBoxData(num_textures); + RenderManager* render_manager = element->GetRenderManager(); - // 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); + // 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); } @@ -251,11 +247,50 @@ 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++) - data->geometry[i].Render(translation); + data->geometry[i].Render(translation, GetTexture(i)); +} + +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..34de15abc 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; @@ -58,18 +58,29 @@ 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 { +public: + DecoratorTiledBoxInstancer(); + ~DecoratorTiledBoxInstancer(); + + /// Instances a box decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; }; } // namespace Rml diff --git a/Source/Core/DecoratorTiledHorizontal.cpp b/Source/Core/DecoratorTiledHorizontal.cpp index 0bdd713cf..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 { @@ -75,56 +76,54 @@ 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++) - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); - 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(); + Mesh mesh[COUNT]; + + tiles[LEFT].GenerateGeometry(mesh[tiles[LEFT].texture_index], computed, offset, left_dimensions, left_dimensions); + + 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 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); - // 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); - // 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); - - // 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); } @@ -136,11 +135,38 @@ 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++) - data->geometry[i].Render(translation); + data->geometry[i].Render(translation, GetTexture(i)); } +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..6d3b92b38 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; @@ -57,9 +57,19 @@ 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 { +public: + DecoratorTiledHorizontalInstancer(); + ~DecoratorTiledHorizontalInstancer(); + + /// Instances a horizontal decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; }; } // namespace Rml 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/DecoratorTiledImage.cpp b/Source/Core/DecoratorTiledImage.cpp index 74fe7cd2e..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,26 +38,28 @@ 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); 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)); - - Geometry* data = new Geometry(); - data->SetTexture(GetTexture()); + tile.CalculateDimensions(GetTexture()); 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)); + 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); } @@ -69,7 +72,32 @@ 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), GetTexture()); +} + +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..de4419c29 100644 --- a/Source/Core/DecoratorTiledImage.h +++ b/Source/Core/DecoratorTiledImage.h @@ -46,10 +46,10 @@ 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) 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; @@ -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/DecoratorTiledInstancer.cpp b/Source/Core/DecoratorTiledInstancer.cpp deleted file mode 100644 index 1d609644c..000000000 --- a/Source/Core/DecoratorTiledInstancer.cpp +++ /dev/null @@ -1,190 +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, repeat, repeat-x, repeat-y").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]; - - const Sprite* sprite = instancer_interface.GetSprite(texture_name); - // A tile is always either a sprite or an image. - if (sprite) - { - 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(); - - if (sprite && (tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT || - tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_X || - tile.fit_mode == DecoratorTiled::TileFitMode::REPEAT_Y)) { - Log::Message(Log::LT_WARNING, "Decorator fit value is '%s', which is incompatible with a spritesheet", - fit_property.ToString().c_str()); - return false; - } - - 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..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 { @@ -76,57 +77,54 @@ 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++) - tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index)); + tiles[i].CalculateDimensions(GetTexture(tiles[i].texture_index)); - 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(); + Mesh mesh[COUNT]; + + tiles[TOP].GenerateGeometry(mesh[tiles[TOP].texture_index], computed, offset, top_dimensions, top_dimensions); + + 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 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); - // 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); - // 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, + 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); } @@ -142,7 +140,35 @@ 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) +{ + 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..17e1177fb 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; @@ -57,9 +57,19 @@ 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 { +public: + DecoratorTiledVerticalInstancer(); + ~DecoratorTiledVerticalInstancer(); + + /// Instances a vertical decorator. + SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, + const DecoratorInstancerInterface& instancer_interface) override; }; } // namespace Rml 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/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 27b610c8b..67b31c2a2 100644 --- a/Source/Core/Element.cpp +++ b/Source/Core/Element.cpp @@ -47,8 +47,8 @@ #include "DataModel.h" #include "ElementAnimation.h" #include "ElementBackgroundBorder.h" -#include "ElementDecoration.h" #include "ElementDefinition.h" +#include "ElementEffects.h" #include "ElementStyle.h" #include "EventDispatcher.h" #include "EventSpecification.h" @@ -92,12 +92,12 @@ static float GetScrollOffsetDelta(ScrollAlignment alignment, float begin_offset, // Meta objects for element collected in a single struct to reduce memory allocations struct ElementMeta { - ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(), decoration(el), scroll(el), computed_values(el) {} + ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(), effects(el), scroll(el), computed_values(el) {} SmallUnorderedMap 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,11 +233,13 @@ void Element::Render() // Apply our transform ElementUtilities::ApplyTransform(*this); + 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(); + meta->effects.RenderEffects(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->effects.RenderEffects(RenderStage::Exit); } ElementPtr Element::Clone() const @@ -448,7 +452,7 @@ void Element::SetBox(const Box& box) meta->background_border.DirtyBackground(); meta->background_border.DirtyBorder(); - meta->decoration.DirtyDecoratorsData(); + meta->effects.DirtyEffectsData(); } } @@ -460,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() @@ -826,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()); @@ -1559,9 +1570,9 @@ String Element::GetEventDispatcherSummary() const return meta->event_dispatcher.ToString(); } -ElementDecoration* Element::GetElementDecoration() const +ElementBackgroundBorder* Element::GetElementBackgroundBorder() const { - return &meta->decoration; + return &meta->background_border; } ElementScroll* Element::GetElementScroll() const @@ -1764,61 +1775,50 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) } } - // Update the z-index. - if (changed_properties.Contains(PropertyId::ZIndex)) + 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_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_or_mask_changed) { - Style::ZIndex z_index_property = meta->computed_values.z_index(); + const Style::ZIndex z_index_property = meta->computed_values.z_index(); - 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; + 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_mask_image()); - stacking_context_dirty = false; - stacking_context.clear(); - } - - // 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) // - ); - // Dirty the background if it's changed. 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(); } @@ -1838,18 +1838,18 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties) meta->background_border.DirtyBorder(); } - // Dirty the decoration if it's changed. - if (border_radius_changed || changed_properties.Contains(PropertyId::Decorator)) + // 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 @@ -2863,7 +2863,7 @@ void Element::UpdateTransformState() void Element::OnStyleSheetChangeRecursive() { - GetElementDecoration()->DirtyDecorators(); + meta->effects.DirtyEffects(); OnStyleSheetChange(); @@ -2875,7 +2875,7 @@ void Element::OnStyleSheetChangeRecursive() void Element::OnDpRatioChangeRecursive() { - GetElementDecoration()->DirtyDecorators(); + meta->effects.DirtyEffects(); GetStyle()->DirtyPropertiesWithUnits(Unit::DP_SCALABLE_LENGTH); OnDpRatioChange(); diff --git a/Source/Core/ElementAnimation.cpp b/Source/Core/ElementAnimation.cpp index 0b0225584..7e138408d 100644 --- a/Source/Core/ElementAnimation.cpp +++ b/Source/Core/ElementAnimation.cpp @@ -27,9 +27,9 @@ */ #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/Filter.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheet.h" @@ -37,11 +37,14 @@ #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" 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(named_decorator->instancer), 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) @@ -111,6 +202,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. @@ -124,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) @@ -171,29 +270,27 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl 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(); - // We construct DecoratorDeclarationView from declaration if it has instancer, otherwise we look for DecoratorSpecification and return - // DecoratorDeclarationView from it - 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) - { - Log::Message(Log::LT_WARNING, "Failed to get element stylesheet for '%s' decorator rule.", declaration.type.c_str()); - return DecoratorDeclarationView{declaration}; - } + return EffectDeclarationView(); - 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()); - return DecoratorDeclarationView{declaration}; + Log::Message(Log::LT_WARNING, "Could not find a named @decorator '%s'.", declaration.type.c_str()); + return EffectDeclarationView(); } - return DecoratorDeclarationView{specification}; + return EffectDeclarationView(named_decorator); }; auto& ptr0 = p0.value.GetReference(); @@ -201,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 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()); - // Build the new, interpolated decorator. - UniquePtr decorator(new DecoratorDeclarationList); - decorator->list.reserve(ptr0->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])}; - - if (!d0_view.instancer || !d1_view.instancer) - return DiscreteInterpolation(); - - if (d0_view.instancer != d1_view.instancer || d0_view.type != d1_view.type || - d0_view.properties.GetNumProperties() != d1_view.properties.GetNumProperties()) - { - // Incompatible decorators, fall back to discrete interpolation. - return DiscreteInterpolation(); - } - - decorator->list.push_back(DecoratorDeclaration{d0_view.type, d0_view.instancer, PropertyDictionary()}); - PropertyDictionary& props = decorator->list.back().properties; + EffectDeclarationView d0 = GetEffectDeclarationView(ptr0->list, i, element); + EffectDeclarationView d1 = GetEffectDeclarationView(ptr1->list, i, element); - const auto& props0 = d0_view.properties.GetProperties(); - const auto& props1 = d1_view.properties.GetProperties(); + const EffectDeclarationView& declaration = (p0_bigger ? d0 : d1); + list.push_back(DecoratorDeclaration{*declaration.type, static_cast(declaration.instancer), PropertyDictionary(), + declaration.paint_area}); - 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()}); - 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 }; @@ -367,13 +441,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; @@ -517,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) : @@ -534,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)) { @@ -543,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/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 23686923d..e2c1beb95 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -29,8 +29,12 @@ #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" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "GeometryBoxShadow.h" namespace Rml { @@ -40,14 +44,23 @@ void ElementBackgroundBorder::Render(Element* element) { if (background_dirty || border_dirty) { + for (auto& background : backgrounds) + { + if (background.first != BackgroundType::BackgroundBorder) + background.second.geometry.Release(); + } + GenerateGeometry(element); background_dirty = false; border_dirty = false; } - if (geometry) - 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() @@ -60,42 +73,106 @@ void ElementBackgroundBorder::DirtyBorder() border_dirty = true; } -void ElementBackgroundBorder::GenerateGeometry(Element* element) +Geometry* ElementBackgroundBorder::GetClipGeometry(Element* element, BoxArea clip_area) { - const ComputedValues& computed = element->GetComputedValues(); + 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; + } - 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(), - }; + 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(); + MeshUtilities::GenerateBackground(mesh, box, {}, border_radius, ColourbPremultiplied(255), clip_area); + if (RenderManager* render_manager = element->GetRenderManager()) + geometry = render_manager->MakeGeometry(std::move(mesh)); + } - // Apply opacity + return &geometry; +} + +ElementBackgroundBorder::Background* ElementBackgroundBorder::GetBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return &it->second; + return nullptr; +} + +ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackground(BackgroundType type) +{ + auto it = backgrounds.find(type); + if (it != backgrounds.end()) + return it->second; + + Background& background = backgrounds[type]; + return background; +} + +void ElementBackgroundBorder::GenerateGeometry(Element* element) +{ + 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(); - background_color.alpha = (byte)(opacity * (float)background_color.alpha); - if (opacity < 1) - { - 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); - geometry.GetVertices().clear(); - geometry.GetIndices().clear(); + auto ConvertColor = [=](Colourb color) { + if (apply_opacity) + return color.ToPremultiplied(opacity); + else + return color.ToPremultiplied(); + }; - const Vector4f radii(computed.border_top_left_radius(), computed.border_top_right_radius(), computed.border_bottom_right_radius(), - computed.border_bottom_left_radius()); + 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; + 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, radii, 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 = geometry; - geometry.Release(); + 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(); + + // Generate the geometry for the box-shadow texture. + Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); + Geometry& shadow_geometry = shadow_background.geometry; + CallbackTexture& shadow_texture = shadow_background.texture; + + GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, *render_manager, element, background_border_geometry, std::move(shadow_list), + border_radius, computed.opacity()); + } } } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index 0b302cdaf..d93038606 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -29,6 +29,7 @@ #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/Types.h" @@ -43,13 +44,24 @@ class ElementBackgroundBorder { void DirtyBackground(); void DirtyBorder(); + Geometry* GetClipGeometry(Element* element, BoxArea clip_area); + private: + enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count }; + struct Background { + Geometry geometry; + CallbackTexture texture; + }; + + Background* GetBackground(BackgroundType type); + Background& GetOrCreateBackground(BackgroundType type); + void GenerateGeometry(Element* element); 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 deleted file mode 100644 index 76623c208..000000000 --- a/Source/Core/ElementDecoration.cpp +++ /dev/null @@ -1,157 +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 "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" -#include "../../Include/RmlUi/Core/StyleSheet.h" - -namespace Rml { - -ElementDecoration::ElementDecoration(Element* _element) : element(_element) {} - -ElementDecoration::~ElementDecoration() -{ - ReleaseDecorators(); -} - -void ElementDecoration::InstanceDecorators() -{ - if (decorators_dirty) - { - decorators_dirty = false; - decorators_data_dirty = true; - ReloadDecorators(); - } -} - -bool ElementDecoration::ReloadDecorators() -{ - RMLUI_ZoneScopedC(0xB22222); - ReleaseDecorators(); - - if (!element->GetComputedValues().has_decorator()) - return true; - - const Property* property = element->GetLocalProperty(PropertyId::Decorator); - if (!property || property->unit != Unit::DECORATOR) - return false; - - DecoratorsPtr decorators_ptr = property->Get(); - if (!decorators_ptr) - return false; - - const StyleSheet* style_sheet = element->GetStyleSheet(); - if (!style_sheet) - return false; - - PropertySource document_source("", 0, ""); - const PropertySource* source = property->source.get(); - - if (!source) - { - if (ElementDocument* document = element->GetOwnerDocument()) - { - document_source.path = document->GetSourceURL(); - source = &document_source; - } - } - - const DecoratorPtrList& decorator_list = style_sheet->InstanceDecorators(*decorators_ptr, source); - - for (const SharedPtr& decorator : decorator_list) - { - if (decorator) - { - DecoratorHandle decorator_handle; - decorator_handle.decorator_data = 0; - decorator_handle.decorator = decorator; - - decorators.push_back(std::move(decorator_handle)); - } - } - - return true; -} - -void ElementDecoration::ReloadDecoratorsData() -{ - if (decorators_data_dirty) - { - decorators_data_dirty = false; - - for (DecoratorHandle& decorator : decorators) - { - if (decorator.decorator_data) - decorator.decorator->ReleaseElementData(decorator.decorator_data); - - decorator.decorator_data = decorator.decorator->GenerateElementData(element); - } - } -} - -void ElementDecoration::ReleaseDecorators() -{ - for (DecoratorHandle& decorator : decorators) - { - if (decorator.decorator_data) - decorator.decorator->ReleaseElementData(decorator.decorator_data); - } - - decorators.clear(); -} - -void ElementDecoration::RenderDecorators() -{ - 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--) - { - DecoratorHandle& decorator = decorators[i]; - decorator.decorator->RenderElement(element, decorator.decorator_data); - } -} - -void ElementDecoration::DirtyDecorators() -{ - decorators_dirty = true; -} - -void ElementDecoration::DirtyDecoratorsData() -{ - decorators_data_dirty = true; -} - -} // namespace Rml diff --git a/Source/Core/ElementEffects.cpp b/Source/Core/ElementEffects.cpp new file mode 100644 index 000000000..f221104ed --- /dev/null +++ b/Source/Core/ElementEffects.cpp @@ -0,0 +1,353 @@ +/* + * 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 "ElementEffects.h" +#include "../../Include/RmlUi/Core/ComputedValues.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/StyleSheet.h" + +namespace Rml { + +ElementEffects::ElementEffects(Element* _element) : element(_element) {} + +ElementEffects::~ElementEffects() +{ + ReleaseEffects(); +} + +void ElementEffects::InstanceEffects() +{ + if (!effects_dirty) + return; + + effects_dirty = false; + effects_data_dirty = true; + + RMLUI_ZoneScopedC(0xB22222); + ReleaseEffects(); + + 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()) + { + const StyleSheet* style_sheet = element->GetStyleSheet(); + if (!style_sheet) + return; + + for (const auto id : {PropertyId::Decorator, PropertyId::MaskImage}) + { + 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) + { + if (ElementDocument* document = element->GetOwnerDocument()) + { + document_source.path = document->GetSourceURL(); + source = &document_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); + decorators_target.reserve(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) + { + 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)); + } + } + } + } + + 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) + continue; + + FiltersPtr filters_ptr = property->Get(); + if (!filters_ptr) + continue; + + 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), CompiledFilter{}}); + } + 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 ElementEffects::ReloadEffectsData() +{ + if (effects_data_dirty) + { + effects_data_dirty = false; + + bool decorator_data_failed = false; + for (DecoratorEntryList* list : {&decorators, &mask_images}) + { + for (DecoratorEntry& decorator : *list) + { + if (decorator.decorator_data) + decorator.decorator->ReleaseElementData(decorator.decorator_data); + + decorator.decorator_data = decorator.decorator->GenerateElementData(element, decorator.paint_area); + if (!decorator.decorator_data) + decorator_data_failed = true; + } + } + + if (decorator_data_failed) + Log::Message(Log::LT_WARNING, "Could not generate decorator element data: %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()); + } +} + +void ElementEffects::ReleaseEffects() +{ + for (DecoratorEntryList* list : {&decorators, &mask_images}) + { + for (DecoratorEntry& decorator : *list) + { + if (decorator.decorator_data) + decorator.decorator->ReleaseElementData(decorator.decorator_data); + } + list->clear(); + } + + filters.clear(); + backdrop_filters.clear(); +} + +void ElementEffects::RenderEffects(RenderStage render_stage) +{ + InstanceEffects(); + ReloadEffectsData(); + + if (!decorators.empty()) + { + 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]; + if (decorator.decorator_data) + decorator.decorator->RenderElement(element, decorator.decorator_data); + } + } + } + + if (filters.empty() && backdrop_filters.empty() && mask_images.empty()) + return; + + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return; + + 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); + + 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, 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) + { + for (const auto& filter : filters) + filter.filter->ExtendInkOverflow(element, filter_region); + } + + Math::ExpandToPixelGrid(filter_region); + + Rectanglei scissor_region = Rectanglei(filter_region); + scissor_region.IntersectIfValid(render_manager->GetState().scissor_region); + render_manager->SetScissorRegion(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 (render_stage == RenderStage::Enter) + { + const LayerHandle backdrop_source_layer = render_manager->GetTopLayer(); + + if (!filters.empty() || !mask_images.empty()) + { + render_manager->PushLayer(); + } + + 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 composite layers 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); + + CompiledFilter mask_image_filter; + FilterHandleList filter_handles; + filter_handles.reserve(filters.size() + (mask_images.empty() ? 0 : 1)); + + for (auto& filter : filters) + filter.compiled.AddHandleTo(filter_handles); + + if (!mask_images.empty()) + { + render_manager->PushLayer(); + + for (int i = (int)mask_images.size() - 1; i >= 0; i--) + { + DecoratorEntry& mask_image = mask_images[i]; + if (mask_image.decorator_data) + mask_image.decorator->RenderElement(element, mask_image.decorator_data); + } + mask_image_filter = render_manager->SaveLayerAsMaskImage(); + mask_image_filter.AddHandleTo(filter_handles); + render_manager->PopLayer(); + } + + render_manager->CompositeLayers(render_manager->GetTopLayer(), render_manager->GetNextLayer(), BlendMode::Blend, filter_handles); + render_manager->PopLayer(); + render_manager->SetScissorRegion(initial_scissor_region); + } + } +} + +void ElementEffects::DirtyEffects() +{ + effects_dirty = true; +} + +void ElementEffects::DirtyEffectsData() +{ + effects_data_dirty = true; +} + +} // namespace Rml diff --git a/Source/Core/ElementDecoration.h b/Source/Core/ElementEffects.h similarity index 57% rename from Source/Core/ElementDecoration.h rename to Source/Core/ElementEffects.h index 5b27f5689..5420cdb06 100644 --- a/Source/Core/ElementDecoration.h +++ b/Source/Core/ElementEffects.h @@ -26,65 +26,69 @@ * */ -#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/Types.h" namespace Rml { class Decorator; class Element; +class Filter; -/** - Manages an elements decorator state +enum class RenderStage { Enter, Decoration, Exit }; - @author Lloyd Weehuizen +/** + Manages and renders an element's effects: decorators, filters, backdrop filters, and mask images. */ -class ElementDecoration { +class ElementEffects { public: - /// Constructor - /// @param element The element this decorator with acting on - ElementDecoration(Element* element); - ~ElementDecoration(); + ElementEffects(Element* element); + ~ElementEffects(); - /// Instances decorators if necessary. - void InstanceDecorators(); + void InstanceEffects(); - /// Renders all appropriate decorators. - void RenderDecorators(); + 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 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 { + // 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; DecoratorDataHandle decorator_data; + BoxArea paint_area; }; + using DecoratorEntryList = Vector; - using DecoratorHandleList = Vector; + struct FilterEntry { + SharedPtr filter; + CompiledFilter compiled; + }; + 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. + 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 eff2a6601..644d17e96 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 @@ -847,11 +846,24 @@ 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 && 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 && p->value.GetType() == Variant::FILTERSPTR && p->value.GetReference()); + break; + case PropertyId::BackdropFilter: + 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 && p->value.GetType() == Variant::BOXSHADOWLIST && !p->value.GetReference().empty()); + break; + case PropertyId::FlexBasis: values.flex_basis(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions)); break; diff --git a/Source/Core/ElementText.cpp b/Source/Core/ElementText.cpp index 5d5285a05..216997849 100644 --- a/Source/Core/ElementText.cpp +++ b/Source/Core/ElementText.cpp @@ -33,13 +33,15 @@ #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" #include "../../Include/RmlUi/Core/TextShapingContext.h" #include "ComputeProperty.h" #include "ElementDefinition.h" #include "ElementStyle.h" +#include "TransformState.h" namespace Rml { @@ -98,6 +100,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; @@ -112,7 +116,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) @@ -123,12 +127,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; @@ -137,37 +143,37 @@ void ElementText::OnRender() const Vector2f translation = GetAbsoluteOffset(); bool render = true; - Vector2i clip_origin; - Vector2i clip_dimensions; - if (GetContext()->GetActiveClipRegion(clip_origin, clip_dimensions)) + + // 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; + } } } 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) @@ -299,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; } @@ -337,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) @@ -397,11 +399,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,33 +446,38 @@ 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 auto& computed = GetComputedValues(); + const TextShapingContext text_shaping_context{computed.language(), computed.direction(), computed.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, text_shaping_context, 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 auto& computed = GetComputedValues(); - const TextShapingContext text_shaping_context{ computed.language(), computed.direction(), computed.letter_spacing() }; - - line.width = GetFontEngineInterface()->GenerateString(font_face_handle, font_effects_handle, line.text, line.position, colour, opacity, - text_shaping_context, 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); @@ -489,7 +497,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 62e0592d7..22682401e 100644 --- a/Source/Core/ElementUtilities.cpp +++ b/Source/Core/ElementUtilities.cpp @@ -27,18 +27,21 @@ */ #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" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementScroll.h" #include "../../Include/RmlUi/Core/Factory.h" #include "../../Include/RmlUi/Core/FontEngineInterface.h" -#include "../../Include/RmlUi/Core/RenderInterface.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/TextShapingContext.h" #include "DataController.h" #include "DataModel.h" #include "DataView.h" -#include "ElementStyle.h" +#include "ElementBackgroundBorder.h" #include "Layout/LayoutDetails.h" #include "Layout/LayoutEngine.h" #include "TransformState.h" @@ -166,14 +169,12 @@ int ElementUtilities::GetStringWidth(Element* element, const String& string, Cha return GetFontEngineInterface()->GetStringWidth(font_face_handle, string, text_shaping_context, 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(); @@ -181,44 +182,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 Vector2i element_origin(element_origin_f); - const Vector2i element_dimensions(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; - 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)); } } @@ -237,48 +253,122 @@ 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); - } + Rectanglei clip_region; + ClipMaskGeometryList clip_mask_list; + + 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(); + + render_manager.SetClipMask(std::move(clip_mask_list)); return true; } -void ElementUtilities::ApplyActiveClipRegion(Context* context) +bool ElementUtilities::GetBoundingBox(Rectanglef& out_rectangle, Element* element, BoxArea box_area) { - RenderInterface* render_interface = ::Rml::GetRenderInterface(); - if (!render_interface) - return; + 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, including the element's box-shadow. + // 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)) + { + 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)); + } + } + } + } - Vector2i origin; - Vector2i dimensions; - bool clip_enabled = context->GetActiveClipRegion(origin, dimensions); + // Element bounds in non-transformed space. + 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); - render_interface->EnableScissorRegion(clip_enabled); - if (clip_enabled) + 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) { - render_interface->SetScissorRegion(origin.x, origin.y, dimensions.x, dimensions.y); + 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) @@ -317,31 +407,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(); + new_transform = 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; - } - - old_transform_ptr = new_transform_ptr; - } + render_manager.SetTransform(new_transform); return true; } diff --git a/Source/Core/Elements/ElementImage.cpp b/Source/Core/Elements/ElementImage.cpp index 843a5dc18..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]; @@ -192,14 +185,13 @@ 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]); + 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; } @@ -210,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. @@ -227,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; } @@ -236,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()); @@ -249,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; } @@ -259,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 91c626125..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) @@ -243,13 +244,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) { @@ -334,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() @@ -350,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; @@ -363,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; } } @@ -373,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; } } @@ -384,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 9679756e2..c7d361985 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" @@ -291,8 +291,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 { @@ -308,11 +308,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(); @@ -1083,12 +1085,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; @@ -1238,6 +1234,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) { @@ -1252,10 +1251,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); } @@ -1263,20 +1259,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; @@ -1288,7 +1277,9 @@ void WidgetTextInput::GenerateCursor() color = property->Get(); } - GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), cursor_size, color); + 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/Elements/WidgetTextInput.h b/Source/Core/Elements/WidgetTextInput.h index 6fdf8d2b3..1e699487e 100644 --- a/Source/Core/Elements/WidgetTextInput.h +++ b/Source/Core/Elements/WidgetTextInput.h @@ -247,7 +247,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/Factory.cpp b/Source/Core/Factory.cpp index d12227249..fdeaef91c 100644 --- a/Source/Core/Factory.cpp +++ b/Source/Core/Factory.cpp @@ -50,10 +50,11 @@ #include "DataViewDefault.h" #include "DecoratorGradient.h" #include "DecoratorNinePatch.h" -#include "DecoratorTiledBoxInstancer.h" -#include "DecoratorTiledHorizontalInstancer.h" -#include "DecoratorTiledImageInstancer.h" -#include "DecoratorTiledVerticalInstancer.h" +#include "DecoratorShader.h" +#include "DecoratorTiledBox.h" +#include "DecoratorTiledHorizontal.h" +#include "DecoratorTiledImage.h" +#include "DecoratorTiledVertical.h" #include "ElementHandle.h" #include "Elements/ElementImage.h" #include "Elements/ElementLabel.h" @@ -62,6 +63,9 @@ #include "Elements/XMLNodeHandlerTabSet.h" #include "Elements/XMLNodeHandlerTextArea.h" #include "EventInstancerDefault.h" +#include "FilterBasic.h" +#include "FilterBlur.h" +#include "FilterDropShadow.h" #include "FontEffectBlur.h" #include "FontEffectGlow.h" #include "FontEffectOutline.h" @@ -87,6 +91,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; @@ -145,7 +153,18 @@ struct DefaultInstancers { DecoratorTiledBoxInstancer decorator_tiled_box; DecoratorTiledImageInstancer decorator_image; DecoratorNinePatchInstancer decorator_ninepatch; - DecoratorGradientInstancer decorator_gradient; + DecoratorShaderInstancer decorator_shader; + 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"}; + 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; @@ -227,7 +246,31 @@ 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("shader", &default_instancers->decorator_shader); + + 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); + 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); + + RegisterFilterInstancer("blur", &default_instancers->filter_blur); + RegisterFilterInstancer("drop-shadow", &default_instancers->filter_drop_shadow); // Font effect instancers RegisterFontEffectInstancer("blur", &default_instancers->font_effect_blur); @@ -299,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; @@ -483,6 +526,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/DecoratorTiledHorizontalInstancer.h b/Source/Core/Filter.cpp similarity index 71% rename from Source/Core/DecoratorTiledHorizontalInstancer.h rename to Source/Core/Filter.cpp index 77c6549fe..ee1ed7b3e 100644 --- a/Source/Core/DecoratorTiledHorizontalInstancer.h +++ b/Source/Core/Filter.cpp @@ -26,26 +26,19 @@ * */ -#ifndef RMLUI_CORE_DECORATORTILEDHORIZONTALINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDHORIZONTALINSTANCER_H - -#include "DecoratorTiledInstancer.h" +#include "../../Include/RmlUi/Core/Filter.h" +#include "../../Include/RmlUi/Core/RenderManager.h" namespace Rml { -/** - @author Peter Curry - */ +Filter::Filter() {} + +Filter::~Filter() {} + +void Filter::ExtendInkOverflow(Element* /*element*/, Rectanglef& /*scissor_region*/) const {} -class DecoratorTiledHorizontalInstancer : public DecoratorTiledInstancer { -public: - DecoratorTiledHorizontalInstancer(); - ~DecoratorTiledHorizontalInstancer(); +FilterInstancer::FilterInstancer() {} - /// Instances a horizontal decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; -}; +FilterInstancer::~FilterInstancer() {} } // namespace Rml -#endif diff --git a/Source/Core/FilterBasic.cpp b/Source/Core/FilterBasic.cpp new file mode 100644 index 000000000..79c534d80 --- /dev/null +++ b/Source/Core/FilterBasic.cpp @@ -0,0 +1,80 @@ +/* + * 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/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +bool FilterBasic::Initialise(const String& in_name, float in_value) +{ + name = in_name; + value = in_value; + return true; +} + +CompiledFilter FilterBasic::CompileFilter(Element* element) const +{ + return element->GetRenderManager()->CompileFilter(name, Dictionary{{"value", Variant(value)}}); +} + +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..e1a546c0c --- /dev/null +++ b/Source/Core/FilterBasic.h @@ -0,0 +1,64 @@ +/* + * 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); + + CompiledFilter CompileFilter(Element* element) 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 diff --git a/Source/Core/DecoratorTiledBoxInstancer.cpp b/Source/Core/FilterBlur.cpp similarity index 50% rename from Source/Core/DecoratorTiledBoxInstancer.cpp rename to Source/Core/FilterBlur.cpp index 92a88dfdb..10372cf98 100644 --- a/Source/Core/DecoratorTiledBoxInstancer.cpp +++ b/Source/Core/FilterBlur.cpp @@ -26,48 +26,51 @@ * */ -#include "DecoratorTiledBoxInstancer.h" -#include "DecoratorTiledBox.h" +#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/RenderManager.h" namespace Rml { -DecoratorTiledBoxInstancer::DecoratorTiledBoxInstancer() : DecoratorTiledInstancer(9) +bool FilterBlur::Initialise(NumericValue in_radius) { - 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); + radius_value = in_radius; + return Any(in_radius.unit & Unit::LENGTH); } -DecoratorTiledBoxInstancer::~DecoratorTiledBoxInstancer() {} +CompiledFilter FilterBlur::CompileFilter(Element* element) const +{ + const float radius = element->ResolveLength(radius_value); + return element->GetRenderManager()->CompileFilter("blur", Dictionary{{"radius", Variant(radius)}}); +} -SharedPtr DecoratorTiledBoxInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) +void FilterBlur::ExtendInkOverflow(Element* element, Rectanglef& scissor_region) const { - constexpr size_t num_tiles = 9; + const float radius = element->ResolveLength(radius_value); + const float blur_extent = 1.5f * Math::Max(radius, 1.f); + scissor_region.Extend(blur_extent); +} - DecoratorTiled::Tile tiles[num_tiles]; - Texture textures[num_tiles]; +FilterBlurInstancer::FilterBlurInstancer() +{ + ids.radius = RegisterProperty("radius", "0px").AddParser("length").GetId(); + RegisterShorthand("filter", "radius", ShorthandType::FallThrough); +} - if (!GetTileProperties(tiles, textures, num_tiles, properties, instancer_interface)) +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(tiles, textures)) - return nullptr; + auto decorator = MakeShared(); + if (decorator->Initialise(p_radius->GetNumericValue())) + return decorator; - return decorator; + return nullptr; } } // namespace Rml diff --git a/Source/Core/FilterBlur.h b/Source/Core/FilterBlur.h new file mode 100644 index 000000000..5531a56ad --- /dev/null +++ b/Source/Core/FilterBlur.h @@ -0,0 +1,64 @@ +/* + * 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); + + CompiledFilter CompileFilter(Element* element) 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 diff --git a/Source/Core/FilterDropShadow.cpp b/Source/Core/FilterDropShadow.cpp new file mode 100644 index 000000000..803f447c8 --- /dev/null +++ b/Source/Core/FilterDropShadow.cpp @@ -0,0 +1,101 @@ +/* + * 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/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/PropertyDictionary.h" +#include "../../Include/RmlUi/Core/RenderManager.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); +} + +CompiledFilter 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), + }; + + CompiledFilter filter = element->GetRenderManager()->CompileFilter("drop-shadow", + Dictionary{{"color", Variant(color)}, {"offset", Variant(offset)}, {"sigma", Variant(sigma)}}); + + return filter; +} + +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..837a44d9a --- /dev/null +++ b/Source/Core/FilterDropShadow.h @@ -0,0 +1,65 @@ +/* + * 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); + + CompiledFilter CompileFilter(Element* element) 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 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 d18759b8c..2ac08bc0a 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.cpp @@ -78,11 +78,13 @@ int FontEngineInterfaceDefault::GetStringWidth(FontFaceHandle handle, const Stri return handle_default->GetStringWidth(string, text_shaping_context.letter_spacing, prior_character); } -int FontEngineInterfaceDefault::GenerateString(FontFaceHandle handle, FontEffectsHandle font_effects_handle, const String& string, - const Vector2f& position, const Colourb& colour, float opacity, const TextShapingContext& text_shaping_context, GeometryList& geometry) +int FontEngineInterfaceDefault::GenerateString(RenderManager& render_manager, FontFaceHandle handle, FontEffectsHandle font_effects_handle, + const String& string, const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + TexturedMeshList& mesh_list) { auto handle_default = reinterpret_cast(handle); - return handle_default->GenerateString(geometry, string, position, colour, opacity, text_shaping_context.letter_spacing, (int)font_effects_handle); + return handle_default->GenerateString(render_manager, mesh_list, string, position, colour, opacity, text_shaping_context.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 28f6bd0d7..41eb6506a 100644 --- a/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h +++ b/Source/Core/FontEngineDefault/FontEngineInterfaceDefault.h @@ -53,17 +53,19 @@ 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, const TextShapingContext& text_shaping_context, Character prior_character) override; + int GetStringWidth(FontFaceHandle handle, const String& string, const TextShapingContext& text_shaping_context, + 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, - const TextShapingContext& text_shaping_context, GeometryList& geometry) override; + int GenerateString(RenderManager& render_manager, FontFaceHandle face_handle, FontEffectsHandle effects_handle, const String& string, + const Vector2f& position, ColourbPremultiplied colour, float opacity, const TextShapingContext& text_shaping_context, + 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 e103fa9e8..4e08723f0 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.cpp @@ -34,6 +34,7 @@ #include "FontProvider.h" #include "FreeTypeInterface.h" #include +#include namespace Rml { @@ -171,7 +172,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) @@ -191,8 +192,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 Colourb 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; @@ -205,45 +206,37 @@ 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]; - 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(); - 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) { @@ -256,11 +249,12 @@ 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); + 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; @@ -270,9 +264,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 f4c9079c1..33b797e42 100644 --- a/Source/Core/FontEngineDefault/FontFaceHandleDefault.h +++ b/Source/Core/FontEngineDefault/FontFaceHandleDefault.h @@ -68,24 +68,26 @@ class FontFaceHandleDefault final : public NonCopyMoveable { /// @return The index to use when generating geometry using this configuration. int GenerateLayerConfiguration(const FontEffectList& font_effects); /// 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] 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. - /// @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, Colourb 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 0c559e041..10364faf5 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,26 +160,28 @@ 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 { - UniquePtr data; - if (!handle->GenerateLayerTexture(data, out_dimensions, effect_ptr, texture_id, handle_version) || !data) + CallbackTextureFunction texture_callback = [handle, effect_ptr, texture_id, handle_version]( + const CallbackTextureInterface& texture_interface) -> bool { + Vector2i dimensions; + Vector data; + if (!handle->GenerateLayerTexture(data, dimensions, effect_ptr, texture_id, handle_version) || data.empty()) return false; - if (!render_interface->GenerateTexture(out_texture_handle, data.get(), out_dimensions)) + if (!texture_interface.GenerateTexture(data, dimensions)) return false; return true; }; - Texture texture; - texture.Set("font-face-layer", 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)); } } 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; @@ -220,8 +222,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: @@ -250,22 +254,22 @@ 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(); } -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..909015487 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,24 +54,25 @@ 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); /// 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] 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 Colourb 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,31 +84,23 @@ 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; - /// 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 { - 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/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/FontEngineInterface.cpp b/Source/Core/FontEngineInterface.cpp index 4b4e33619..082eb2a63 100644 --- a/Source/Core/FontEngineInterface.cpp +++ b/Source/Core/FontEngineInterface.cpp @@ -72,9 +72,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*/, const Colourb& /*colour*/, float /*opacity*/, const TextShapingContext& /*text_shaping_context*/, - 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*/, + const TextShapingContext& /*text_shaping_context*/, TexturedMeshList& /*mesh_list*/) { return 0; } diff --git a/Source/Core/Geometry.cpp b/Source/Core/Geometry.cpp index 4db1e7f91..0be6033c2 100644 --- a/Source/Core/Geometry.cpp +++ b/Source/Core/Geometry.cpp @@ -27,137 +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); +Geometry::Geometry(RenderManager* render_manager, StableVectorIndex resource_handle) : UniqueRenderResource(render_manager, resource_handle) {} - compiled_geometry = std::exchange(other.compiled_geometry, 0); - compile_attempted = std::exchange(other.compile_attempted, false); -} - -Geometry::~Geometry() -{ - GeometryDatabase::Erase(database_handle); - - Release(); -} - -void Geometry::Render(Vector2f translation) +void Geometry::Render(Vector2f translation, Texture texture, const CompiledShader& shader) const { - RenderInterface* const render_interface = ::Rml::GetRenderInterface(); - RMLUI_ASSERT(render_interface); + if (resource_handle == StableVectorIndex::Invalid) + return; translation = translation.Round(); - // Render our compiled geometry if possible. - if (compiled_geometry) - { - RMLUI_ZoneScopedN("RenderCompiled"); - render_interface->RenderCompiledGeometry(compiled_geometry, translation); - } - // 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(), - texture ? texture->GetHandle() : 0); - - // 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); - 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); - } + RenderManagerAccess::Render(render_manager, *this, translation, texture, shader); } -Vector& Geometry::GetVertices() +Mesh Geometry::Release(ReleaseMode mode) { - return vertices; -} + if (resource_handle == StableVectorIndex::Invalid) + return Mesh(); -Vector& Geometry::GetIndices() -{ - return indices; -} - -const Texture* Geometry::GetTexture() const -{ - return texture; -} - -void Geometry::SetTexture(const Texture* _texture) -{ - texture = _texture; - Release(); -} - -void Geometry::Release(bool clear_buffers) -{ - if (compiled_geometry) - { - ::Rml::GetRenderInterface()->ReleaseCompiledGeometry(compiled_geometry); - compiled_geometry = 0; - } - - 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/GeometryBackgroundBorder.cpp b/Source/Core/GeometryBackgroundBorder.cpp index 69f0deb57..3250253d8 100644 --- a/Source/Core/GeometryBackgroundBorder.cpp +++ b/Source/Core/GeometryBackgroundBorder.cpp @@ -36,188 +36,125 @@ 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, ColourbPremultiplied 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 ColourbPremultiplied 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) +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) { @@ -232,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(); @@ -242,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); @@ -281,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; @@ -300,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); @@ -316,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); @@ -334,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; @@ -355,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 647693578..f4bb4cef6 100644 --- a/Source/Core/GeometryBackgroundBorder.h +++ b/Source/Core/GeometryBackgroundBorder.h @@ -44,37 +44,58 @@ 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, 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 ColourbPremultiplied 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. // 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); @@ -89,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 new file mode 100644 index 000000000..7d9bc4dbc --- /dev/null +++ b/Source/Core/GeometryBoxShadow.cpp @@ -0,0 +1,229 @@ +/* + * 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/CompiledFilterShader.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" + +namespace Rml { + +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; + 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)](const CallbackTextureInterface& texture_interface) -> bool { + RenderManager& render_manager = texture_interface.GetRenderManager(); + + 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; + 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); + ColourbPremultiplied white(255); + + if (has_inner_shadow) + MeshUtilities::GenerateBackground(mesh_padding, box, offset, border_radius, white, BoxArea::Padding); + if (has_outer_shadow) + MeshUtilities::GenerateBackground(mesh_padding_border, box, offset, border_radius, white, BoxArea::Border); + } + + const RenderState initial_render_state = render_manager.GetState(); + render_manager.ResetState(); + render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); + + render_manager.PushLayer(); + + 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); + } + + 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++) + { + 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); + } + + MeshUtilities::GenerateBackground(mesh_shadow, box, offset, spread_radii, shadow.color, inset ? BoxArea::Padding : BoxArea::Border); + } + + CompiledFilter blur; + if (blur_radius > 0.5f) + { + blur = render_manager.CompileFilter("blur", Dictionary{{"radius", Variant(blur_radius)}}); + if (blur) + render_manager.PushLayer(); + } + + Geometry geometry_shadow = render_manager.MakeGeometry(std::move(mesh_shadow)); + + if (inset) + { + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_shadow, shadow_offset + element_offset_in_texture); + + for (Rml::Vertex& vertex : mesh_padding.vertices) + vertex.colour = shadow.color; + + // @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); + geometry_shadow.Render(shadow_offset + element_offset_in_texture); + } + + if (blur) + { + FilterHandleList filters; + blur.AddHandleTo(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(); + render_manager.SetState(initial_render_state); + + return true; + }; + + Mesh mesh = out_shadow_geometry.Release(Geometry::ReleaseMode::ClearMesh); + const byte alpha = byte(opacity * 255.f); + MeshUtilities::GenerateQuad(mesh, -element_offset_in_texture, Vector2f(texture_dimensions), ColourbPremultiplied(alpha, alpha)); + + 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 new file mode 100644 index 000000000..3d39a2dce --- /dev/null +++ b/Source/Core/GeometryBoxShadow.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_GEOMETRYBOXSHADOW_H +#define RMLUI_CORE_GEOMETRYBOXSHADOW_H + +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { + +class Geometry; +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, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, + Geometry& background_border_geometry, BoxShadowList shadow_list, Vector4f border_radius, float opacity); +}; + +} // namespace Rml +#endif 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/GeometryUtilities.cpp deleted file mode 100644 index 511868398..000000000 --- a/Source/Core/GeometryUtilities.cpp +++ /dev/null @@ -1,101 +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/GeometryUtilities.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, Colourb 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) -{ - vertices[0].position = origin; - vertices[0].colour = colour; - vertices[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[2].position = origin + dimensions; - vertices[2].colour = colour; - vertices[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); - - indices[0] = index_offset + 0; - indices[1] = index_offset + 3; - indices[2] = index_offset + 1; - - indices[3] = index_offset + 1; - indices[4] = index_offset + 3; - indices[5] = index_offset + 2; -} - -void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb 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); -} - -void GeometryUtilities::GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, - Colourb background_colour, const Colourb* border_colours) -{ - Vector& vertices = geometry->GetVertices(); - Vector& indices = geometry->GetIndices(); - - 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); -} - -} // namespace Rml diff --git a/Source/Core/Layout/ContainerBox.cpp b/Source/Core/Layout/ContainerBox.cpp index 40b5a488f..7687304dc 100644 --- a/Source/Core/Layout/ContainerBox.cpp +++ b/Source/Core/Layout/ContainerBox.cpp @@ -132,8 +132,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() || computed.has_mask_image()); } } diff --git a/Source/Core/Layout/ContainerBox.h b/Source/Core/Layout/ContainerBox.h index 3fe3efd1e..aec17d607 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 6deb34767..84a3f3a87 100644 --- a/Source/Core/Layout/LayoutDetails.cpp +++ b/Source/Core/Layout/LayoutDetails.cpp @@ -186,11 +186,7 @@ ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container { area = BoxArea::Padding; - auto EstablishesAbsoluteContainingBlock = [](const ContainerBox* container) -> bool { - return container->GetPositionProperty() != Position::Static || container->HasLocalTransformOrPerspective(); - }; - - while (!EstablishesAbsoluteContainingBlock(container) && container->GetParent()) + while (container->GetParent() && !container->IsAbsolutePositioningContainingBlock()) container = container->GetParent(); } diff --git a/Source/Core/Log.cpp b/Source/Core/Log.cpp index 937c2bf11..fa8e5d491 100644 --- a/Source/Core/Log.cpp +++ b/Source/Core/Log.cpp @@ -30,18 +30,12 @@ #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/StringUtilities.h" #include "../../Include/RmlUi/Core/SystemInterface.h" +#include "LogDefault.h" #include #include namespace Rml { -bool Log::Initialise() -{ - return true; -} - -void Log::Shutdown() {} - void Log::Message(Log::Type type, const char* fmt, ...) { const int buffer_size = 1024; @@ -61,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, ...) @@ -94,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/DecoratorTiledImageInstancer.cpp b/Source/Core/LogDefault.cpp similarity index 58% rename from Source/Core/DecoratorTiledImageInstancer.cpp rename to Source/Core/LogDefault.cpp index 7fd95cc88..3da501712 100644 --- a/Source/Core/DecoratorTiledImageInstancer.cpp +++ b/Source/Core/LogDefault.cpp @@ -26,34 +26,46 @@ * */ -#include "DecoratorTiledImageInstancer.h" -#include "DecoratorTiledImage.h" +#include "LogDefault.h" +#include "../../Include/RmlUi/Core/StringUtilities.h" + +#ifdef RMLUI_PLATFORM_WIN32 + #include +#else + #include +#endif namespace Rml { -DecoratorTiledImageInstancer::DecoratorTiledImageInstancer() : DecoratorTiledInstancer(1) +#ifdef RMLUI_PLATFORM_WIN32 +bool LogDefault::LogMessage(Log::Type type, const String& message) { - RegisterTileProperty("image", true); - RegisterShorthand("decorator", "image", ShorthandType::RecursiveRepeat); -} + #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()); -DecoratorTiledImageInstancer::~DecoratorTiledImageInstancer() {} - -SharedPtr DecoratorTiledImageInstancer::InstanceDecorator(const String& /*name*/, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) + // 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) { - 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; + #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/DecoratorTiledBoxInstancer.h b/Source/Core/LogDefault.h similarity index 74% rename from Source/Core/DecoratorTiledBoxInstancer.h rename to Source/Core/LogDefault.h index d377660b4..7d3f962e8 100644 --- a/Source/Core/DecoratorTiledBoxInstancer.h +++ b/Source/Core/LogDefault.h @@ -26,26 +26,23 @@ * */ -#ifndef RMLUI_CORE_DECORATORTILEDBOXINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDBOXINSTANCER_H +#ifndef RMLUI_CORE_LOGDEFAULT_H +#define RMLUI_CORE_LOGDEFAULT_H -#include "DecoratorTiledInstancer.h" +#include "../../Include/RmlUi/Core/Log.h" +#include "../../Include/RmlUi/Core/Types.h" namespace Rml { /** - @author Peter Curry + Provides a platform-dependent default implementation for message logging. */ -class DecoratorTiledBoxInstancer : public DecoratorTiledInstancer { +class LogDefault { public: - DecoratorTiledBoxInstancer(); - ~DecoratorTiledBoxInstancer(); - - /// Instances a box decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; + static bool LogMessage(Log::Type type, const String& message); }; } // namespace Rml + #endif diff --git a/Source/Core/Math.cpp b/Source/Core/Math.cpp index a8306c1ed..2ac364712 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) @@ -188,6 +191,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. @@ -269,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/MeshUtilities.cpp b/Source/Core/MeshUtilities.cpp new file mode 100644 index 000000000..5776e5880 --- /dev/null +++ b/Source/Core/MeshUtilities.cpp @@ -0,0 +1,194 @@ +/* + * 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/MeshUtilities.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/FontEngineInterface.h" +#include "../../Include/RmlUi/Core/Types.h" +#include "GeometryBackgroundBorder.h" + +namespace Rml { + +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour) +{ + GenerateQuad(mesh, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1)); +} + +void MeshUtilities::GenerateQuad(Mesh& mesh, Vector2f origin, Vector2f dimensions, ColourbPremultiplied colour, Vector2f top_left_texcoord, + Vector2f bottom_right_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[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[v0 + 2].position = origin + dimensions; + vertices[v0 + 2].colour = colour; + vertices[v0 + 2].tex_coord = bottom_right_texcoord; + + 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[i0 + 0] = v0 + 0; + indices[i0 + 1] = v0 + 3; + indices[i0 + 2] = v0 + 1; + + indices[i0 + 3] = v0 + 1; + indices[i0 + 4] = v0 + 3; + indices[i0 + 5] = v0 + 2; +} + +void MeshUtilities::GenerateLine(Mesh& mesh, Vector2f position, Vector2f size, ColourbPremultiplied color) +{ + Math::SnapToPixelGrid(position, size); + MeshUtilities::GenerateQuad(mesh, position, size, color); +} + +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_mesh.vertices; + Vector& indices = out_mesh.indices; + + 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++) + { + 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); + } + } +#endif + +#ifdef RMLUI_DEBUG + const int num_vertices = (int)vertices.size(); + for (int index : indices) + { + RMLUI_ASSERT(index < num_vertices); + } +#endif +} + +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."); + + 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_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); + indices.reserve((int)indices.size() + 6); + + // Generate the geometry + GeometryBackgroundBorder geometry(vertices, indices); + geometry.DrawBackground(metrics, color); +} + +} // namespace Rml 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/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/PropertyParserBoxShadow.cpp b/Source/Core/PropertyParserBoxShadow.cpp new file mode 100644 index 000000000..06a8eaaab --- /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().ToPremultiplied(); + } + 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/PropertyParserColorStopList.cpp b/Source/Core/PropertyParserColorStopList.cpp new file mode 100644 index 000000000..7942f36cf --- /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().ToPremultiplied(); + + 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/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/PropertyParserDecorator.cpp b/Source/Core/PropertyParserDecorator.cpp index 337ae9573..34a2118c4 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" @@ -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") { @@ -72,10 +80,32 @@ 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); + // 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; + 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 +135,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 +148,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/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/DecoratorTiledImageInstancer.h b/Source/Core/PropertyParserFilter.h similarity index 74% rename from Source/Core/DecoratorTiledImageInstancer.h rename to Source/Core/PropertyParserFilter.h index 433c37d7b..f844bfbf6 100644 --- a/Source/Core/DecoratorTiledImageInstancer.h +++ b/Source/Core/PropertyParserFilter.h @@ -26,25 +26,24 @@ * */ -#ifndef RMLUI_CORE_DECORATORTILEDIMAGEINSTANCER_H -#define RMLUI_CORE_DECORATORTILEDIMAGEINSTANCER_H +#ifndef RMLUI_CORE_PROPERTYPARSERFILTER_H +#define RMLUI_CORE_PROPERTYPARSERFILTER_H -#include "DecoratorTiledInstancer.h" +#include "../../Include/RmlUi/Core/PropertyParser.h" namespace Rml { /** - @author Peter Curry + A property parser for the filter property. */ -class DecoratorTiledImageInstancer : public DecoratorTiledInstancer { +class PropertyParserFilter : public PropertyParser { public: - DecoratorTiledImageInstancer(); - ~DecoratorTiledImageInstancer(); + PropertyParserFilter(); + virtual ~PropertyParserFilter(); - /// Instances an image decorator. - SharedPtr InstanceDecorator(const String& name, const PropertyDictionary& properties, - const DecoratorInstancerInterface& instancer_interface) override; + /// Called to parse a decorator declaration. + bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override; }; } // namespace Rml 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 9de39893c..13e76e44a 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); } } @@ -242,7 +248,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,20 +261,22 @@ 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) + 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"}; } @@ -345,35 +353,46 @@ 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++) + 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, 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; - else if (!item.optional) + else if (item.repeats || !item.optional) return false; + + if (item.repeats) + break; } } else @@ -456,25 +475,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: @@ -488,37 +515,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 == '(') { @@ -532,97 +542,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/Source/Core/RenderInterface.cpp b/Source/Core/RenderInterface.cpp index f70cbb629..5c12f4c58 100644 --- a/Source/Core/RenderInterface.cpp +++ b/Source/Core/RenderInterface.cpp @@ -27,43 +27,67 @@ */ #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*/, - TextureHandle /*texture*/) +void RenderInterface::EnableClipMask(bool /*enable*/) {} + +void RenderInterface::RenderToClipMask(ClipMaskOperation /*operation*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/) {} + +void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} + +LayerHandle RenderInterface::PushLayer() { - return 0; + return {}; } -void RenderInterface::RenderCompiledGeometry(CompiledGeometryHandle /*geometry*/, const Vector2f& /*translation*/) {} +void RenderInterface::CompositeLayers(LayerHandle /*source*/, LayerHandle /*destination*/, BlendMode /*blend_mode*/, + Span /*filters*/) +{} + +void RenderInterface::PopLayer() {} -void RenderInterface::ReleaseCompiledGeometry(CompiledGeometryHandle /*geometry*/) {} +TextureHandle RenderInterface::SaveLayerAsTexture(Vector2i /*dimensions*/) +{ + return TextureHandle{}; +} -bool RenderInterface::LoadTexture(TextureHandle& /*texture_handle*/, Vector2i& /*texture_dimensions*/, const String& /*source*/) +CompiledFilterHandle RenderInterface::SaveLayerAsMaskImage() { - return false; + return CompiledFilterHandle{}; } -bool RenderInterface::GenerateTexture(TextureHandle& /*texture_handle*/, const byte* /*source*/, const Vector2i& /*source_dimensions*/) +CompiledFilterHandle RenderInterface::CompileFilter(const String& /*name*/, const Dictionary& /*parameters*/) { - return false; + return CompiledFilterHandle{}; } -void RenderInterface::ReleaseTexture(TextureHandle /*texture*/) {} +void RenderInterface::ReleaseFilter(CompiledFilterHandle /*filter*/) {} -void RenderInterface::SetTransform(const Matrix4f* /*transform*/) {} +CompiledShaderHandle RenderInterface::CompileShader(const String& /*name*/, const Dictionary& /*parameters*/) +{ + return CompiledShaderHandle{}; +} + +void RenderInterface::RenderShader(CompiledShaderHandle /*shader*/, CompiledGeometryHandle /*geometry*/, Vector2f /*translation*/, + TextureHandle /*texture*/) +{} + +void RenderInterface::ReleaseShader(CompiledShaderHandle /*shader*/) {} } // namespace Rml 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 diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp new file mode 100644 index 000000000..b68fcd8eb --- /dev/null +++ b/Source/Core/RenderManager.cpp @@ -0,0 +1,384 @@ +/* + * 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" +#include "../../Include/RmlUi/Core/SystemInterface.h" +#include "TextureDatabase.h" + +namespace Rml { + +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::PrepareRender() +{ +#ifdef RMLUI_DEBUG + const RenderState default_state; + 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 +} + +void RenderManager::SetViewport(Vector2i dimensions) +{ + viewport_dimensions = dimensions; +} + +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()); +} + +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); + } + + 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 && geometry->render_manager == this); + 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) + { + RMLUI_ASSERT(element_clip.geometry->render_manager == this); + SetTransform(element_clip.transform); + 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. + 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{}); +} + +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, geometry.mesh.indices); + + if (!geometry.handle) + Log::Message(Log::LT_ERROR, "Got empty compiled geometry."); + } + 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(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); + + if (shader) + render_interface->RenderShader(shader.resource_handle, geometry_handle, translation, texture_handle); + else + render_interface->RenderGeometry(geometry_handle, translation, texture_handle); + } +} + +void RenderManager::GetTextureSourceList(StringList& source_list) const +{ + texture_database->file_database.GetSourceList(source_list); +} + +bool RenderManager::ReleaseTexture(const String& texture_source) +{ + return texture_database->file_database.ReleaseTexture(render_interface, texture_source); +} + +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->ReleaseGeometry(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(); +} + +LayerHandle RenderManager::PushLayer() +{ + const LayerHandle layer = render_interface->PushLayer(); + render_stack.push_back(layer); + return layer; +} + +void RenderManager::CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span 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() +{ + 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->ReleaseGeometry(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->ReleaseFilter(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->ReleaseShader(shader.resource_handle); + compiled_shader_count -= 1; +} + +} // namespace Rml diff --git a/Source/Core/DecoratorInstancer.cpp b/Source/Core/RenderManagerAccess.cpp similarity index 50% rename from Source/Core/DecoratorInstancer.cpp rename to Source/Core/RenderManagerAccess.cpp index fc4772dbd..959f2528b 100644 --- a/Source/Core/DecoratorInstancer.cpp +++ b/Source/Core/RenderManagerAccess.cpp @@ -26,48 +26,46 @@ * */ -#include "../../Include/RmlUi/Core/DecoratorInstancer.h" -#include "../../Include/RmlUi/Core/StyleSheet.h" +#include "RenderManagerAccess.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "TextureDatabase.h" namespace Rml { -DecoratorInstancer::DecoratorInstancer() : properties(10, 10) {} - -DecoratorInstancer::~DecoratorInstancer() {} - -const PropertySpecification& DecoratorInstancer::GetPropertySpecification() const +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, TextureFileIndex texture) { - return properties; + return render_manager->texture_database->file_database.GetDimensions(render_manager->render_interface, texture); } -PropertyDefinition& DecoratorInstancer::RegisterProperty(const String& property_name, const String& default_value) +Vector2i RenderManagerAccess::GetDimensions(RenderManager* render_manager, StableVectorIndex callback_texture) { - return properties.RegisterProperty(property_name, default_value, false, false); + return render_manager->texture_database->callback_database.GetDimensions(render_manager, render_manager->render_interface, callback_texture); } -ShorthandId DecoratorInstancer::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type) +void RenderManagerAccess::Render(RenderManager* render_manager, const Geometry& geometry, Vector2f translation, Texture texture, + const CompiledShader& shader) { - return properties.RegisterShorthand(shorthand_name, property_names, type); + render_manager->Render(geometry, translation, texture, shader); } -const Sprite* DecoratorInstancerInterface::GetSprite(const String& name) const +void RenderManagerAccess::GetTextureSourceList(RenderManager* render_manager, StringList& source_list) { - return style_sheet.GetSprite(name); + render_manager->GetTextureSourceList(source_list); } -Texture DecoratorInstancerInterface::GetTexture(const String& filename) const +bool RenderManagerAccess::ReleaseTexture(RenderManager* render_manager, const String& texture_source) { - 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 render_manager->ReleaseTexture(texture_source); +} - texture.Set(filename, property_source->path); +void RenderManagerAccess::ReleaseAllTextures(RenderManager* render_manager) +{ + render_manager->ReleaseAllTextures(); +} - return texture; +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..1e92796bc --- /dev/null +++ b/Source/Core/RenderManagerAccess.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_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 bool ReleaseTexture(RenderManager* render_manager, const String& texture_source); + 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 bool Rml::ReleaseTexture(const String&, RenderInterface*); + 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 a8af7cca5..8f563bcbe 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" @@ -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(), @@ -97,10 +97,10 @@ 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()) + auto it = named_decorator_map.find(name); + if (it != named_decorator_map.end()) return &(it->second); return nullptr; } @@ -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(), @@ -162,9 +163,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(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/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 6f8bd1295..603aed5a5 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" @@ -333,7 +333,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& named_decorator_map, const SharedPtr& source) { StringList name_type; @@ -349,8 +349,8 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci 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); @@ -364,13 +364,13 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci 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.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. @@ -392,16 +392,7 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci 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, DecoratorSpecification{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; } @@ -631,7 +622,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 95e0583d2..df45a0cdf 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, DecoratorSpecificationMap& 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. /// @param[in] rules The rules to parse. diff --git a/Source/Core/StyleSheetSpecification.cpp b/Source/Core/StyleSheetSpecification.cpp index 847446fe8..be53a44c3 100644 --- a/Source/Core/StyleSheetSpecification.cpp +++ b/Source/Core/StyleSheetSpecification.cpp @@ -31,8 +31,11 @@ #include "../../Include/RmlUi/Core/PropertyIdSet.h" #include "IdNameMap.h" #include "PropertyParserAnimation.h" +#include "PropertyParserBoxShadow.h" +#include "PropertyParserColorStopList.h" #include "PropertyParserColour.h" #include "PropertyParserDecorator.h" +#include "PropertyParserFilter.h" #include "PropertyParserFontEffect.h" #include "PropertyParserKeyword.h" #include "PropertyParserNumber.h" @@ -45,10 +48,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(); @@ -56,11 +60,14 @@ struct DefaultStyleSheetParsers { 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(); PropertyParserTransform transform = PropertyParserTransform(); PropertyParserRatio ratio = PropertyParserRatio(); PropertyParserNumber resolution = PropertyParserNumber(Unit::X); + PropertyParserBoxShadow box_shadow = PropertyParserBoxShadow(&color, &length); }; StyleSheetSpecification::StyleSheetSpecification() : @@ -242,6 +249,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); @@ -249,11 +257,14 @@ 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); 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() @@ -407,8 +418,15 @@ 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::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"); + 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/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*/) {} diff --git a/Source/Core/Texture.cpp b/Source/Core/Texture.cpp index 277a96986..f3adbf230 100644 --- a/Source/Core/Texture.cpp +++ b/Source/Core/Texture.cpp @@ -27,55 +27,51 @@ */ #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, const TextureCallback& callback) +Texture::Texture(RenderManager* render_manager, StableVectorIndex callback_index) : render_manager(render_manager), callback_index(callback_index) {} + +Vector2i Texture::GetDimensions() const { - resource = MakeShared(); - resource->Set(name, callback); + 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 {}; } -const String& Texture::GetSource() const +Texture::operator bool() const { - static String empty_string; - if (!resource) - return empty_string; - - return resource->GetSource(); + return callback_index != StableVectorIndex::Invalid || file_index != TextureFileIndex::Invalid; } -TextureHandle Texture::GetHandle() const +bool Texture::operator==(const Texture& other) const { - if (!resource) - return 0; - - return resource->GetHandle(); + return render_manager == other.render_manager && file_index == other.file_index && callback_index == other.callback_index; } -Vector2i Texture::GetDimensions() const -{ - if (!resource) - return {}; +TextureSource::TextureSource(String source, String document_path) : source(std::move(source)), document_path(std::move(document_path)) {} - return resource->GetDimensions(); +Texture TextureSource::GetTexture(RenderManager& render_manager) const +{ + Texture& texture = textures[&render_manager]; + if (!texture) + texture = render_manager.LoadTexture(source, document_path); + return texture; } -bool Texture::operator==(const Texture& other) const +const String& TextureSource::GetSource() const { - return resource == other.resource; + return source; } -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 97d5cda6d..959c1ba1f 100644 --- a/Source/Core/TextureDatabase.cpp +++ b/Source/Core/TextureDatabase.cpp @@ -27,139 +27,178 @@ */ #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; +CallbackTextureDatabase::CallbackTextureDatabase() +{ + constexpr size_t reserve_callback_textures = 30; + texture_list.reserve(reserve_callback_textures); +} -TextureDatabase::TextureDatabase() +CallbackTextureDatabase::~CallbackTextureDatabase() { - RMLUI_ASSERT(texture_database == nullptr); - texture_database = this; + if (!texture_list.empty()) + { + Log::Message(Log::LT_ERROR, "TextureDatabase destroyed with outstanding callback textures. Will likely result in memory corruption."); + RMLUI_ERROR; + } } -TextureDatabase::~TextureDatabase() +StableVectorIndex CallbackTextureDatabase::CreateTexture(CallbackTextureFunction&& callback) { - RMLUI_ASSERT(texture_database == this); + RMLUI_ASSERT(callback); + return texture_list.insert(CallbackTextureEntry{std::move(callback), TextureHandle(), Vector2i()}); +} -#ifdef RMLUI_DEBUG - // All textures not owned by the database should have been released at this point. - int num_leaks_file = 0; +void CallbackTextureDatabase::ReleaseTexture(RenderInterface* render_interface, StableVectorIndex callback_index) +{ + CallbackTextureEntry& data = texture_list[callback_index]; + if (data.texture_handle) + render_interface->ReleaseTexture(data.texture_handle); + texture_list.erase(callback_index); +} - for (auto& texture : textures) - num_leaks_file += (texture.second.use_count() > 1); +Vector2i CallbackTextureDatabase::GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) +{ + return EnsureLoaded(render_manager, render_interface, callback_index).dimensions; +} - const int num_leaks_callback = (int)callback_textures.size(); - const int total_num_leaks = num_leaks_file + num_leaks_callback; +TextureHandle CallbackTextureDatabase::GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) +{ + return EnsureLoaded(render_manager, render_interface, callback_index).texture_handle; +} - if (total_num_leaks > 0) +auto CallbackTextureDatabase::EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index) + -> CallbackTextureEntry& +{ + CallbackTextureEntry& data = texture_list[callback_index]; + if (!data.texture_handle) { - 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); + if (!data.callback(CallbackTextureInterface(*render_manager, *render_interface, data.texture_handle, data.dimensions))) + { + data.texture_handle = {}; + data.dimensions = {}; + } } -#endif - - texture_database = nullptr; + return data; } -void TextureDatabase::Initialise() +size_t CallbackTextureDatabase::size() const { - new TextureDatabase(); + return texture_list.size(); } -void TextureDatabase::Shutdown() +void CallbackTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) { - delete texture_database; + texture_list.for_each([render_interface](CallbackTextureEntry& texture) { + if (texture.texture_handle) + { + render_interface->ReleaseTexture(texture.texture_handle); + texture.texture_handle = {}; + texture.dimensions = {}; + } + }); } -SharedPtr TextureDatabase::Fetch(const String& source, const String& source_directory) +FileTextureDatabase::FileTextureDatabase() {} + +FileTextureDatabase::~FileTextureDatabase() { - String path; - if (source.size() > 0 && source[0] == '?') - path = source; - else - GetSystemInterface()->JoinPath(path, StringUtilities::Replace(source_directory, '|', ':'), source); +#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 +} - auto iterator = texture_database->textures.find(path); - if (iterator != texture_database->textures.end()) - return iterator->second; +TextureFileIndex FileTextureDatabase::LoadTexture(RenderInterface* render_interface, const String& source) +{ + auto it = texture_map.find(source); + if (it != texture_map.end()) + return it->second; - auto resource = MakeShared(); - resource->Set(path); + FileTextureEntry entry = LoadTextureEntry(render_interface, source); + if (!entry.texture_handle) + return TextureFileIndex::Invalid; - texture_database->textures[path] = resource; - return resource; -} + const auto index = TextureFileIndex(texture_list.size()); + texture_map[source] = index; + texture_list.push_back(std::move(entry)); -void TextureDatabase::AddCallbackTexture(TextureResource* texture) -{ - if (texture_database) - texture_database->callback_textures.insert(texture); + return index; } -void TextureDatabase::RemoveCallbackTexture(TextureResource* texture) +FileTextureDatabase::FileTextureEntry FileTextureDatabase::LoadTextureEntry(RenderInterface* render_interface, const String& source) { - if (texture_database) - texture_database->callback_textures.erase(texture); + FileTextureEntry result = {}; + result.texture_handle = render_interface->LoadTexture(result.dimensions, source); + return result; } -StringList TextureDatabase::GetSourceList() +FileTextureDatabase::FileTextureEntry& FileTextureDatabase::EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index) { - StringList result; - - if (texture_database) + FileTextureEntry& entry = texture_list[size_t(index)]; + if (!entry.texture_handle) { - result.reserve(texture_database->textures.size()); - - for (const auto& pair : texture_database->textures) - result.push_back(pair.first); + 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 entry; +} - return result; +TextureHandle FileTextureDatabase::GetHandle(RenderInterface* render_interface, TextureFileIndex index) +{ + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return EnsureLoaded(render_interface, index).texture_handle; } -void TextureDatabase::ReleaseTextures() +Vector2i FileTextureDatabase::GetDimensions(RenderInterface* render_interface, TextureFileIndex index) { - if (texture_database) - { - for (const auto& texture : texture_database->textures) - texture.second->Release(); + RMLUI_ASSERT(size_t(index) < texture_list.size()); + return EnsureLoaded(render_interface, index).dimensions; +} - for (const auto& texture : texture_database->callback_textures) - texture->Release(); - } +void FileTextureDatabase::GetSourceList(StringList& source_list) const +{ + source_list.reserve(source_list.size() + texture_list.size()); + for (const auto& texture : texture_map) + source_list.push_back(texture.first); } -bool TextureDatabase::ReleaseTexture(const String& source) +bool FileTextureDatabase::ReleaseTexture(RenderInterface* render_interface, const String& source) { - auto it = texture_database->textures.find(source); - if (it != texture_database->textures.end()) + auto it = texture_map.find(source); + if (it == texture_map.end()) + return false; + + FileTextureEntry& texture = texture_list[size_t(it->second)]; + if (texture.texture_handle) { - it->second->Release(); + render_interface->ReleaseTexture(texture.texture_handle); + texture.texture_handle = {}; return true; } return false; } -bool TextureDatabase::AllTexturesReleased() +void FileTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface) { - if (texture_database) + for (FileTextureEntry& texture : texture_list) { - for (const auto& texture : texture_database->textures) - if (texture.second->IsLoaded()) - return false; - - for (const auto& texture : texture_database->callback_textures) - if (texture->IsLoaded()) - return false; + 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 2c6c300eb..78147bda0 100644 --- a/Source/Core/TextureDatabase.h +++ b/Source/Core/TextureDatabase.h @@ -29,53 +29,75 @@ #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); + + Vector2i GetDimensions(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + TextureHandle GetHandle(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); + + size_t size() const; + + bool ReleaseTexture(RenderInterface* render_interface, const String& source); + + void ReleaseAllTextures(RenderInterface* render_interface); - /// 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); +private: + struct CallbackTextureEntry { + CallbackTextureFunction callback; + TextureHandle texture_handle = {}; + Vector2i dimensions; + }; + + CallbackTextureEntry& EnsureLoaded(RenderManager* render_manager, RenderInterface* render_interface, StableVectorIndex callback_index); - /// Release all textures in the database. - static void ReleaseTextures(); + StableVector texture_list; +}; - /// Release a given texture from the database. - static bool ReleaseTexture(const String& source); +class FileTextureDatabase : NonCopyMoveable { +public: + FileTextureDatabase(); + ~FileTextureDatabase(); - /// Adds a texture resource with a callback function and stores it as a weak (raw) pointer in the database. - static void AddCallbackTexture(TextureResource* texture); + TextureFileIndex LoadTexture(RenderInterface* render_interface, const String& source); - /// Removes a callback texture from the database. - static void RemoveCallbackTexture(TextureResource* texture); + TextureHandle GetHandle(RenderInterface* render_interface, TextureFileIndex index); + Vector2i GetDimensions(RenderInterface* render_interface, TextureFileIndex index); - /// Return a list of all texture sources currently in the database. - static StringList GetSourceList(); + void GetSourceList(StringList& source_list) const; - /// Returns true if there are no textures in the database yet to be released through the render interface. - static bool AllTexturesReleased(); + bool ReleaseTexture(RenderInterface* render_interface, const String& source); + void ReleaseAllTextures(RenderInterface* render_interface); private: - TextureDatabase(); - ~TextureDatabase(); + struct FileTextureEntry { + TextureHandle texture_handle = {}; + Vector2i dimensions; + }; - using TextureMap = UnorderedMap>; - TextureMap textures; + FileTextureEntry LoadTextureEntry(RenderInterface* render_interface, const String& source); + FileTextureEntry& EnsureLoaded(RenderInterface* render_interface, TextureFileIndex index); - using CallbackTextureMap = UnorderedSet; - CallbackTextureMap callback_textures; + 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.cpp b/Source/Core/TextureLayoutTexture.cpp index 0bf81b396..2194fafe6 100644 --- a/Source/Core/TextureLayoutTexture.cpp +++ b/Source/Core/TextureLayoutTexture.cpp @@ -132,21 +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) { - 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.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 e5c3b9e2a..c8ff5adf6 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 @@ -63,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/Core/TextureResource.cpp b/Source/Core/TextureResource.cpp deleted file mode 100644 index be2399e23..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, const TextureCallback& callback) -{ - Reset(); - source = name; - texture_callback = MakeUnique(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 4fd65ab09..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, const 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/Core/TransformUtilities.cpp b/Source/Core/TransformUtilities.cpp index da41c8ff5..890d31eef 100644 --- a/Source/Core/TransformUtilities.cpp +++ b/Source/Core/TransformUtilities.cpp @@ -93,14 +93,6 @@ static inline float ResolveLength(NumericValue value, Element& e) noexcept return e.ResolveLength(value); } -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) @@ -608,6 +600,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 { diff --git a/Source/Core/TypeConverter.cpp b/Source/Core/TypeConverter.cpp index 76b1a17a9..24d1b0267 100644 --- a/Source/Core/TypeConverter.cpp +++ b/Source/Core/TypeConverter.cpp @@ -28,13 +28,17 @@ #include "../../Include/RmlUi/Core/TypeConverter.h" #include "../../Include/RmlUi/Core/Animation.h" -#include "../../Include/RmlUi/Core/DecoratorInstancer.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" #include "../../Include/RmlUi/Core/PropertySpecification.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #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" namespace Rml { @@ -43,7 +47,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; @@ -161,13 +165,18 @@ bool TypeConverter::Convert(const AnimationList& src, Str return true; } -bool TypeConverter::Convert(const DecoratorsPtr& src, DecoratorsPtr& dest) +template +void AppendPaintArea(const EffectDeclaration& /*declaration*/, String& /*dest*/) +{} +template <> +void AppendPaintArea(const DecoratorDeclaration& declaration, String& dest) { - dest = src; - return true; + if (declaration.paint_area >= BoxArea::Border && declaration.paint_area <= BoxArea::Padding) + dest += " " + PropertyParserDecorator::ConvertAreaToString(declaration.paint_area); } -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 +185,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) - { + if (auto* instancer = declaration.instancer) dest += '(' + instancer->GetPropertySpecification().PropertiesToString(declaration.properties, false, ' ') + ')'; - } - dest += ", "; + + AppendPaintArea(declaration, 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; @@ -206,4 +236,73 @@ 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.ToNonPremultiplied()); + + 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; + 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 += ToString(shadow.color.ToNonPremultiplied()) + temp; + + if (i < src.size() - 1) + { + dest += ", "; + temp.clear(); + } + } + 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/Source/Core/Variant.cpp b/Source/Core/Variant.cpp index f0156c1fc..df5b6c563 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 { @@ -42,7 +43,10 @@ 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"); + 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) @@ -99,12 +103,30 @@ void Variant::Clear() decorators->~DecoratorsPtr(); } break; + case FILTERSPTR: + { + FiltersPtr* decorators = (FiltersPtr*)data; + decorators->~FiltersPtr(); + } + break; case FONTEFFECTSPTR: { FontEffectsPtr* font_effects = (FontEffectsPtr*)data; font_effects->~shared_ptr(); } break; + case COLORSTOPLIST: + { + ColorStopList* value = (ColorStopList*)data; + value->~ColorStopList(); + } + break; + case BOXSHADOWLIST: + { + BoxShadowList* value = (BoxShadowList*)data; + value->~BoxShadowList(); + } + break; default: break; } type = NONE; @@ -121,7 +143,10 @@ 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; + case COLORSTOPLIST: 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; @@ -139,7 +164,10 @@ 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; + 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); type = other.type; @@ -373,6 +401,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) @@ -397,6 +449,54 @@ 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) + { + *(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) { @@ -448,7 +548,10 @@ 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 COLORSTOPLIST: return DEFAULT_VARIANT_COMPARE(ColorStopList); + case BOXSHADOWLIST: return DEFAULT_VARIANT_COMPARE(BoxShadowList); case NONE: return true; } RMLUI_ERRORMSG("Variant comparison not implemented for this type."); diff --git a/Source/Debugger/ElementInfo.cpp b/Source/Debugger/ElementInfo.cpp index 5fb479655..91eff70a9 100644 --- a/Source/Debugger/ElementInfo.cpp +++ b/Source/Debugger/ElementInfo.cpp @@ -35,6 +35,7 @@ #include "../../Include/RmlUi/Core/PropertiesIteratorView.h" #include "../../Include/RmlUi/Core/Property.h" #include "../../Include/RmlUi/Core/PropertyDefinition.h" +#include "../../Include/RmlUi/Core/RenderManager.h" #include "../../Include/RmlUi/Core/StyleSheet.h" #include "../../Include/RmlUi/Core/StyleSheetSpecification.h" #include "../../Include/RmlUi/Core/SystemInterface.h" @@ -175,6 +176,19 @@ void ElementInfo::RenderSourceElement() Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Margin), element_box.GetSize(BoxArea::Margin), border_offset + element_box.GetPosition(BoxArea::Border), element_box.GetSize(BoxArea::Border), Colourb(240, 255, 131, 128)); } + + if (Context* context = source_element->GetContext()) + { + context->GetRenderManager().SetTransform(nullptr); + + Rectanglef bounding_box; + if (ElementUtilities::GetBoundingBox(bounding_box, source_element, BoxArea::Auto)) + { + bounding_box.Extend(1.f); + Math::ExpandToPixelGrid(bounding_box); + Geometry::RenderOutline(bounding_box.Position(), bounding_box.Size(), Colourb(255, 255, 255, 200), 1.f); + } + } } } @@ -205,13 +219,11 @@ void ElementInfo::ProcessEvent(Event& event) else if (id == "show_source") { show_source_element = !target_element->IsClassSet("active"); - ; target_element->SetClass("active", show_source_element); } else if (id == "enable_element_select") { enable_element_select = !target_element->IsClassSet("active"); - ; target_element->SetClass("active", enable_element_select); } else if (target_element->GetTagName() == "pseudo" && source_element) diff --git a/Source/Debugger/Geometry.cpp b/Source/Debugger/Geometry.cpp index 333cd9153..839b38b11 100644 --- a/Source/Debugger/Geometry.cpp +++ b/Source/Debugger/Geometry.cpp @@ -29,50 +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); - 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(); - render_interface->RenderGeometry(vertices, 4 * 4, indices, 6 * 4, 0, origin); + 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); + + 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]; - - GeometryUtilities::GenerateQuad(vertices, indices, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour, 0); + Mesh mesh; + MeshUtilities::GenerateQuad(mesh, Vector2f(0, 0), Vector2f(dimensions.x, dimensions.y), colour.ToPremultiplied()); - 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 8f6c784df..ab74f9c8f 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,29 +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(); - - 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); - 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; } @@ -152,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; @@ -201,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. @@ -225,8 +215,7 @@ 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](const CallbackTextureInterface& texture_interface) -> bool { RMLUI_ASSERT(animation); const size_t bytes_per_line = 4 * render_dimensions.x; @@ -236,31 +225,26 @@ 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)) + if (!texture_interface.GenerateTexture({p_data, total_bytes}, render_dimensions)) return false; - - out_dimensions = render_dimensions; return true; }; - texture.Set("lottie", p_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 31149ab53..0e8d834a3 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,30 +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(); - - 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); - 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; } @@ -147,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", ""); @@ -193,20 +178,29 @@ void ElementSVG::UpdateTexture() if (!svg_document || !texture_dirty) return; + RenderManager* render_manager = GetRenderManager(); + if (!render_manager) + 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](const CallbackTextureInterface& texture_interface) -> 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_byte_size = bitmap.width() * bitmap.height() * 4; + uint8_t* bitmap_data = bitmap.data(); + 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 (!render_interface->GenerateTexture(out_handle, reinterpret_cast(bitmap.data()), render_dimensions)) + if (!texture_interface.GenerateTexture({reinterpret_cast(bitmap.data()), bitmap_byte_size}, render_dimensions)) return false; - out_dimensions = render_dimensions; return true; }; - texture.Set("svg", p_callback); - geometry.SetTexture(&texture); + texture = render_manager->MakeCallbackTexture(std::move(texture_callback)); texture_dirty = false; } 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/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/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/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 + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ + + +
diff --git a/Tests/Data/VisualTests/filter_blur_area.rml b/Tests/Data/VisualTests/filter_blur_area.rml new file mode 100644 index 000000000..4d53b8261 --- /dev/null +++ b/Tests/Data/VisualTests/filter_blur_area.rml @@ -0,0 +1,42 @@ + + + Filter: blur area + + + + + + + + + + + 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/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/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 + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + + +
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?
+ + +
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 @@ + + + + +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 + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+ + diff --git a/Tests/Data/VisualTests/shader_conic_gradient.rml b/Tests/Data/VisualTests/shader_conic_gradient.rml new file mode 100644 index 000000000..e89ad9ffc --- /dev/null +++ b/Tests/Data/VisualTests/shader_conic_gradient.rml @@ -0,0 +1,111 @@ + + + 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_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/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. + +
+
+
+
+
+
+ + + 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
+
+ + + +
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/Common/TestsInterface.cpp b/Tests/Source/Common/TestsInterface.cpp index e6ceadfcc..aab2a4bd7 100644 --- a/Tests/Source/Common/TestsInterface.cpp +++ b/Tests/Source/Common/TestsInterface.cpp @@ -90,10 +90,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); +} + +void TestsRenderInterface::RenderGeometry(Rml::CompiledGeometryHandle /*geometry*/, Rml::Vector2f /*translation*/, Rml::TextureHandle /*texture*/) +{ + counters.render_geometry += 1; +} + +void TestsRenderInterface::ReleaseGeometry(Rml::CompiledGeometryHandle /*geometry*/) +{ + counters.release_geometry += 1; } void TestsRenderInterface::EnableScissorRegion(bool /*enable*/) @@ -101,26 +111,34 @@ 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*/) @@ -132,3 +150,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::ReleaseFilter(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::ReleaseShader(Rml::CompiledShaderHandle /*shader*/) +{ + counters.release_shader += 1; +} diff --git a/Tests/Source/Common/TestsInterface.h b/Tests/Source/Common/TestsInterface.h index c8b6978d5..576c475e3 100644 --- a/Tests/Source/Common/TestsInterface.h +++ b/Tests/Source/Common/TestsInterface.h @@ -59,31 +59,53 @@ class TestsSystemInterface : public Rml::SystemInterface { class TestsRenderInterface : public Rml::RenderInterface { public: struct Counters { - size_t render_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; + size_t compile_filter; + size_t release_filter; + size_t compile_shader; + size_t render_shader; + size_t release_shader; }; - 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::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; - const Counters& GetCounters() const { return counters; } + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; + void ReleaseFilter(Rml::CompiledFilterHandle filter) override; + + Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override; + void RenderShader(Rml::CompiledShaderHandle shader, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, + Rml::TextureHandle texture) override; + void ReleaseShader(Rml::CompiledShaderHandle shader) override; + const Counters& GetCounters() const { return counters; } void ResetCounters() { counters = {}; } + void Reset() { ResetCounters(); } + private: 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/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; diff --git a/Tests/Source/UnitTests/Animation.cpp b/Tests/Source/UnitTests/Animation.cpp index 50dbf5b89..565e04023 100644 --- a/Tests/Source/UnitTests/Animation.cpp +++ b/Tests/Source/UnitTests/Animation.cpp @@ -48,12 +48,12 @@ 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; } - to { decorator: %s; } + from { %s: %s; } + to { %s: %s; } } div { background: #333; @@ -86,113 +86,247 @@ 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 #7f7f7f3f #7f7f7f3f)", + }, + { + "", + "", + + "horizontal-gradient(transparent transparent) border-box", + "horizontal-gradient(white white) border-box", + + "horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f) border-box", }, { "", "", "none", - "gradient(horizontal transparent transparent)", + "horizontal-gradient(transparent transparent)", - "gradient(horizontal rgba(220,220,220,191) rgba(220,220,220,191))", + "horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", "", "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 #dcdcdcbf #dcdcdcbf), horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", "", - "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 #7f7f7f3f #7f7f7f3f), horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", }, /// 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 #7f7f7f3f #7f7f7f3f)", }, { "", - "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 #dcdcdcbf #dcdcdcbf)", }, { - "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 #7f7f7f3f #7f7f7f3f)", }, /// 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 #7f7f7f3f #7f7f7f3f)", }, { "", - "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 #dcdcdcbf #dcdcdcbf)", }, { - "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 #7f7f7f3f #7f7f7f3f)", }, { "", "", "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 #dcdcdcbf #dcdcdcbf), horizontal-gradient(horizontal #dcdcdcbf #dcdcdcbf)", }, { "", "", - "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 #7f7f7f3f #7f7f7f3f), horizontal-gradient(horizontal #7f7f7f3f #7f7f7f3f)", + }, + }; + + TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface(); + Context* context = TestsShell::GetContext(); + + for (const char* property_str : {"decorator", "mask-image"}) + { + 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(), property_str, test.from.c_str(), property_str, test.to.c_str()); + + ElementDocument* document = context->LoadDocumentFromMemory(document_rml, "assets/"); + Element* element = document->GetChild(0); + + document->Show(); + TestsShell::RenderLoop(); + + 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(); + } + } + + system_interface->SetTime(0.0); + + 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(#7f0000 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)", }, }; @@ -200,25 +334,28 @@ TEST_CASE("animation.decorator") 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_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_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(); - TestsShell::RenderLoop(); + document->Show(); - 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(); - document->Close(); + CHECK_MESSAGE(element->GetProperty(property_str) == test.expected_25p, property_str, " from: ", test.from, ", to: ", test.to); + + document->Close(); + } } system_interface->SetTime(0.0); diff --git a/Tests/Source/UnitTests/Core.cpp b/Tests/Source/UnitTests/Core.cpp index e3787c0ad..15ac04abb 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; @@ -124,14 +124,14 @@ 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(); const auto& counters = render_interface->GetCounters(); Context* context = TestsShell::GetContext(); @@ -145,13 +145,43 @@ TEST_CASE("core.release_textures") 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 size_t 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") + { + 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") @@ -172,10 +202,25 @@ TEST_CASE("core.release_textures") CHECK(counters.release_texture == counter_release_before + 1); } + SUBCASE("ReleaseGeometry") + { + CHECK(counters.compile_geometry > 0); + CHECK(counters.release_geometry == 0); + + Rml::ReleaseCompiledGeometry(); + CHECK(counters.compile_geometry == counters.release_geometry); + + TestsShell::RenderLoop(); + CHECK(counters.compile_geometry > counters.release_geometry); + } + 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 == counters.release_geometry); + + render_interface->Reset(); } 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/Filter.cpp b/Tests/Source/UnitTests/Filter.cpp new file mode 100644 index 000000000..f6cabe1f0 --- /dev/null +++ b/Tests/Source/UnitTests/Filter.cpp @@ -0,0 +1,185 @@ +/* + * 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/TestsInterface.h" +#include "../Common/TestsShell.h" +#include +#include +#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) {} + + CompiledFilter CompileFilter(Element* element) const override + { + compiled_test_filters.push_back({element->GetId(), this}); + return element->GetRenderManager()->CompileFilter("FilterTest", {}); + } + + float value = 0.f; + Unit unit = Unit::UNKNOWN; +}; + +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); + + TestsShell::GetTestsRenderInterface()->ResetCounters(); + + 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 == 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(); +} 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 51f4bf7bf..b8f4429e7 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -29,8 +29,12 @@ #include "../Common/TestsInterface.h" #include #include +#include #include #include +#include +#include +#include #include using namespace Rml; @@ -50,43 +54,156 @@ TEST_CASE("Properties") Context* context = Rml::CreateContext("main", window_size); ElementDocument* document = context->CreateDocument(); - struct FlexTestCase { - String flex_value; + SUBCASE("flex") + { + struct FlexTestCase { + String flex_value; - struct ExpectedValues { - float flex_grow; - float flex_shrink; - String flex_basis; - } expected; - }; + 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"}}, - }; + 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) - { - if (!test.flex_value.empty()) + 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); } + } + + SUBCASE("gradient") + { + auto ParseGradient = [&](const String& value) -> Property { + 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; + } + return {}; + }; + + struct GradientTestCase { + String value; + String expected_parsed_string; + ColorStopList expected_color_stops; + }; - 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); + GradientTestCase test_cases[] = { + { + "red, blue", + "#ff0000, #0000ff", + { + ColorStop{ColourbPremultiplied(255, 0, 0), NumericValue{}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{}}, + }, + }, + { + "red 5px, blue 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}}, + }, + }, + { + "red, #00f 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}}, + ColorStop{ColourbPremultiplied(0, 150, 0, 150), NumericValue{10.f, Unit::DP}}, + }, + }, + { + "red 50px 20%, blue 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}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{10.f, Unit::INCH}}, + }, + }, + }; + + for (const GradientTestCase& test_case : test_cases) + { + 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() == "#aabbcc"); + + 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") == "#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") == "#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") == "#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"); + + 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(); +} diff --git a/Tests/Source/UnitTests/PropertySpecification.cpp b/Tests/Source/UnitTests/PropertySpecification.cpp index 00f5d85d3..4114e9634 100644 --- a/Tests/Source/UnitTests/PropertySpecification.cpp +++ b/Tests/Source/UnitTests/PropertySpecification.cpp @@ -37,9 +37,138 @@ #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; @@ -92,13 +221,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(); } @@ -110,6 +232,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) { @@ -203,6 +326,11 @@ TEST_CASE("PropertyParser.InvalidShorthands") {false, "decorator", "gradient(blue vertical red)"}, // Wrong order {false, "decorator", "gradient(vertical blue red green)"}, // Too many values + {true, "filter", "drop-shadow(blue 10px 20px 30px)"}, // + {false, "filter", "drop-shadow(10px 20px 30px blue)"}, // Wrong order + {false, "filter", "drop-shadow(10px blue 20px 30px)"}, // Wrong order + {false, "filter", "drop-shadow(blue 10px 20px 30px 40px)"}, // Too many values + {true, "overflow", "hidden"}, // {true, "overflow", "scroll hidden"}, // {false, "overflow", ""}, // Too few values 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/UnitTests/StyleSheetParser.cpp b/Tests/Source/UnitTests/StyleSheetParser.cpp index 4dd0956fa..798ada564 100644 --- a/Tests/Source/UnitTests/StyleSheetParser.cpp +++ b/Tests/Source/UnitTests/StyleSheetParser.cpp @@ -34,7 +34,7 @@ #include #include -static const char* spritesheet = R"( +static const char spritesheet[] = R"( @spritesheet test_sheet { src: /assets/high_scores_alien_3.tga; test00: 0px 0px 64px 64px; @@ -45,7 +45,7 @@ static const char* spritesheet = R"( } )"; -static const char* spritesheet_with_path_string_encoding = R"( +static const char spritesheet_with_path_string_encoding[] = R"( @spritesheet test_sheet_with_path_string_encoding { src: "/assets/test.tga"; test00: 0px 0px 128px 128px; @@ -64,7 +64,7 @@ TEST_CASE("style_sheet_parser.spritesheet") { StyleSheetContainer style_sheet_container; - StreamMemory spritesheet_stream{reinterpret_cast(spritesheet), strlen(spritesheet)}; + StreamMemory spritesheet_stream{reinterpret_cast(spritesheet), sizeof(spritesheet) - 1}; style_sheet_container.LoadStyleSheetContainer(&spritesheet_stream, 0); style_sheet_container.UpdateCompiledStyleSheet(context); @@ -74,7 +74,7 @@ TEST_CASE("style_sheet_parser.spritesheet") const auto* sprite00 = style_sheet->GetSprite("test00"); CHECK(sprite00 != nullptr); CHECK(sprite00->sprite_sheet->name == "test_sheet"); - CHECK(sprite00->sprite_sheet->image_source == "/assets/high_scores_alien_3.tga"); + CHECK(sprite00->sprite_sheet->texture_source.GetSource() == "/assets/high_scores_alien_3.tga"); CHECK(sprite00->rectangle.TopLeft() == Vector2f(0.f, 0.f)); CHECK(sprite00->rectangle.BottomRight() == Vector2f(64.f, 64.f)); CHECK(sprite00->sprite_sheet->display_scale == 1.f); @@ -82,7 +82,7 @@ TEST_CASE("style_sheet_parser.spritesheet") const auto* sprite01 = style_sheet->GetSprite("test01"); CHECK(sprite01 != nullptr); CHECK(sprite01->sprite_sheet->name == "test_sheet"); - CHECK(sprite01->sprite_sheet->image_source == "/assets/high_scores_alien_3.tga"); + CHECK(sprite01->sprite_sheet->texture_source.GetSource() == "/assets/high_scores_alien_3.tga"); CHECK(sprite01->rectangle.TopLeft() == Vector2f(64.f, 0.f)); CHECK(sprite01->rectangle.BottomRight() == Vector2f(128.f, 64.f)); CHECK(sprite01->sprite_sheet->display_scale == 1.f); @@ -90,7 +90,7 @@ TEST_CASE("style_sheet_parser.spritesheet") const auto* sprite10 = style_sheet->GetSprite("test10"); CHECK(sprite10 != nullptr); CHECK(sprite10->sprite_sheet->name == "test_sheet"); - CHECK(sprite10->sprite_sheet->image_source == "/assets/high_scores_alien_3.tga"); + CHECK(sprite10->sprite_sheet->texture_source.GetSource() == "/assets/high_scores_alien_3.tga"); CHECK(sprite10->rectangle.TopLeft() == Vector2f(0.f, 64.f)); CHECK(sprite10->rectangle.BottomRight() == Vector2f(64.f, 128.f)); CHECK(sprite10->sprite_sheet->display_scale == 1.f); @@ -98,7 +98,7 @@ TEST_CASE("style_sheet_parser.spritesheet") const auto* sprite11 = style_sheet->GetSprite("test11"); CHECK(sprite11 != nullptr); CHECK(sprite11->sprite_sheet->name == "test_sheet"); - CHECK(sprite11->sprite_sheet->image_source == "/assets/high_scores_alien_3.tga"); + CHECK(sprite11->sprite_sheet->texture_source.GetSource() == "/assets/high_scores_alien_3.tga"); CHECK(sprite11->rectangle.TopLeft() == Vector2f(64.f, 64.f)); CHECK(sprite11->rectangle.BottomRight() == Vector2f(128.f, 128.f)); CHECK(sprite11->sprite_sheet->display_scale == 1.f); @@ -114,7 +114,7 @@ TEST_CASE("style_sheet_parser.spritesheet_with_path_string_encoding") { StyleSheetContainer style_sheet_container; StreamMemory spritesheet_stream{reinterpret_cast(spritesheet_with_path_string_encoding), - strlen(spritesheet_with_path_string_encoding)}; + sizeof(spritesheet_with_path_string_encoding) - 1}; style_sheet_container.LoadStyleSheetContainer(&spritesheet_stream, 0); style_sheet_container.UpdateCompiledStyleSheet(context); @@ -124,7 +124,7 @@ TEST_CASE("style_sheet_parser.spritesheet_with_path_string_encoding") const auto* sprite00 = style_sheet->GetSprite("test00"); CHECK(sprite00 != nullptr); CHECK(sprite00->sprite_sheet->name == "test_sheet_with_path_string_encoding"); - CHECK(sprite00->sprite_sheet->image_source == "/assets/test.tga"); + CHECK(sprite00->sprite_sheet->texture_source.GetSource() == "/assets/test.tga"); CHECK(sprite00->rectangle.TopLeft() == Vector2f(0.f, 0.f)); CHECK(sprite00->rectangle.BottomRight() == Vector2f(128.f, 128.f)); CHECK(sprite00->sprite_sheet->display_scale == 0.5f); @@ -132,7 +132,7 @@ TEST_CASE("style_sheet_parser.spritesheet_with_path_string_encoding") const auto* sprite01 = style_sheet->GetSprite("test01"); CHECK(sprite01 != nullptr); CHECK(sprite01->sprite_sheet->name == "test_sheet_with_path_string_encoding"); - CHECK(sprite01->sprite_sheet->image_source == "/assets/test.tga"); + CHECK(sprite01->sprite_sheet->texture_source.GetSource() == "/assets/test.tga"); CHECK(sprite01->rectangle.TopLeft() == Vector2f(128.f, 0.f)); CHECK(sprite01->rectangle.BottomRight() == Vector2f(256.f, 128.f)); CHECK(sprite01->sprite_sheet->display_scale == 0.5f); @@ -140,7 +140,7 @@ TEST_CASE("style_sheet_parser.spritesheet_with_path_string_encoding") const auto* sprite10 = style_sheet->GetSprite("test10"); CHECK(sprite10 != nullptr); CHECK(sprite10->sprite_sheet->name == "test_sheet_with_path_string_encoding"); - CHECK(sprite10->sprite_sheet->image_source == "/assets/test.tga"); + CHECK(sprite10->sprite_sheet->texture_source.GetSource() == "/assets/test.tga"); CHECK(sprite10->rectangle.TopLeft() == Vector2f(0.f, 128.f)); CHECK(sprite10->rectangle.BottomRight() == Vector2f(128.f, 256.f)); CHECK(sprite10->sprite_sheet->display_scale == 0.5f); @@ -148,7 +148,7 @@ TEST_CASE("style_sheet_parser.spritesheet_with_path_string_encoding") const auto* sprite11 = style_sheet->GetSprite("test11"); CHECK(sprite11 != nullptr); CHECK(sprite11->sprite_sheet->name == "test_sheet_with_path_string_encoding"); - CHECK(sprite11->sprite_sheet->image_source == "/assets/test.tga"); + CHECK(sprite11->sprite_sheet->texture_source.GetSource() == "/assets/test.tga"); CHECK(sprite11->rectangle.TopLeft() == Vector2f(128.f, 128.f)); CHECK(sprite11->rectangle.BottomRight() == Vector2f(256.f, 256.f)); CHECK(sprite11->sprite_sheet->display_scale == 0.5f); diff --git a/Tests/Source/VisualTests/CaptureScreen.cpp b/Tests/Source/VisualTests/CaptureScreen.cpp index fd88f3f9a..868013b00 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 @@ -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) { @@ -143,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 = 3; - diff.data = Rml::UniquePtr(new Rml::byte[diff.width * diff.height * diff.num_components]); + diff.num_components = 4; + 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 @@ -162,24 +146,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,20 +179,49 @@ 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::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); + 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, image_ref_diff_byte_size}, {(int)w_ref, (int)h_ref}); + + if (out_highlight && result.success) + 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()); + return result; } 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)); + 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 7cc179845..41150bfca 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,13 +44,14 @@ struct ComparisonResult { struct TextureGeometry { Rml::TextureHandle texture_handle = 0; - Rml::Vertex vertices[4]; - int indices[6] = {}; + Rml::CompiledGeometryHandle geometry_handle = 0; + Rml::Mesh mesh; }; 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 d81c63d03..ccfbd0c10 100644 --- a/Tests/Source/VisualTests/TestNavigator.cpp +++ b/Tests/Source/VisualTests/TestNavigator.cpp @@ -50,6 +50,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); @@ -64,10 +65,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() @@ -113,10 +116,16 @@ 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)); + 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); + } } } @@ -180,6 +189,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) @@ -206,11 +216,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) { @@ -227,6 +245,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(); @@ -256,6 +278,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 && !viewer->IsNavigationLocked()) { @@ -364,7 +396,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(); } @@ -378,7 +410,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; } @@ -507,11 +539,11 @@ 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"; } @@ -526,7 +558,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()); } log += "\nEqual:\n"; for (int i : equal) @@ -567,31 +605,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;