ソースを参照

On-demand rendering (power saving mode) (#436)

Dominik Thalhammer 2 年 前
コミット
801b23945d
36 ファイル変更266 行追加75 行削除
  1. 1 1
      Backends/RmlUi_Backend.h
  2. 4 2
      Backends/RmlUi_Backend_GLFW_GL2.cpp
  3. 4 2
      Backends/RmlUi_Backend_GLFW_GL3.cpp
  4. 18 9
      Backends/RmlUi_Backend_GLFW_VK.cpp
  5. 7 2
      Backends/RmlUi_Backend_SDL_GL2.cpp
  6. 7 2
      Backends/RmlUi_Backend_SDL_GL3.cpp
  7. 8 3
      Backends/RmlUi_Backend_SDL_SDLrenderer.cpp
  8. 26 3
      Backends/RmlUi_Backend_SDL_VK.cpp
  9. 4 1
      Backends/RmlUi_Backend_SFML_GL2.cpp
  10. 18 4
      Backends/RmlUi_Backend_Win32_GL2.cpp
  11. 29 9
      Backends/RmlUi_Backend_Win32_VK.cpp
  12. 23 1
      Backends/RmlUi_Backend_X11_GL2.cpp
  13. 5 13
      Backends/RmlUi_Renderer_VK.cpp
  14. 2 2
      Backends/RmlUi_Renderer_VK.h
  15. 19 0
      Include/RmlUi/Core/Context.h
  16. 2 1
      Include/RmlUi/Core/Element.h
  17. 3 0
      Include/RmlUi/Lottie/ElementLottie.h
  18. 1 1
      Samples/basic/bitmapfont/src/main.cpp
  19. 1 1
      Samples/basic/demo/src/main.cpp
  20. 1 1
      Samples/basic/drag/src/main.cpp
  21. 1 1
      Samples/basic/loaddocument/src/main.cpp
  22. 1 1
      Samples/basic/lottie/src/main.cpp
  23. 1 1
      Samples/basic/svg/src/main.cpp
  24. 1 1
      Samples/basic/treeview/src/main.cpp
  25. 1 1
      Samples/tutorial/datagrid/src/main.cpp
  26. 1 1
      Samples/tutorial/datagrid_tree/src/main.cpp
  27. 1 1
      Samples/tutorial/drag/src/main.cpp
  28. 1 1
      Samples/tutorial/template/src/main.cpp
  29. 18 2
      Source/Core/Context.cpp
  30. 16 2
      Source/Core/Element.cpp
  31. 4 0
      Source/Core/Elements/WidgetSlider.cpp
  32. 6 0
      Source/Core/Elements/WidgetTextInput.cpp
  33. 3 1
      Source/Core/ScrollController.cpp
  34. 1 1
      Source/Core/ScrollController.h
  35. 4 0
      Source/Core/WidgetScroll.cpp
  36. 23 3
      Source/Lottie/ElementLottie.cpp

+ 1 - 1
Backends/RmlUi_Backend.h

@@ -58,7 +58,7 @@ Rml::RenderInterface* GetRenderInterface();
 
 // Polls and processes events from the current platform, and applies any relevant events to the provided RmlUi context and the key down callback.
 // @return False to indicate that the application should be closed.
-bool ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback = nullptr);
+bool ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback = nullptr, bool power_save = false);
 // Request application closure during the next event processing call.
 void RequestExit();
 

+ 4 - 2
Backends/RmlUi_Backend_GLFW_GL2.cpp

@@ -127,7 +127,7 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -148,7 +148,9 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->context = context;
 	data->key_down_callback = key_down_callback;
 
-	glfwPollEvents();
+	if(power_save)
+		glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
+	else glfwPollEvents();
 
 	data->context = nullptr;
 	data->key_down_callback = nullptr;

+ 4 - 2
Backends/RmlUi_Backend_GLFW_GL3.cpp

@@ -139,7 +139,7 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -160,7 +160,9 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->context = context;
 	data->key_down_callback = key_down_callback;
 
-	glfwPollEvents();
+	if(power_save)
+		glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
+	else glfwPollEvents();
 
 	data->context = nullptr;
 	data->key_down_callback = nullptr;

+ 18 - 9
Backends/RmlUi_Backend_GLFW_VK.cpp

@@ -26,15 +26,17 @@
  *
  */
 
+#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 <RmlUi/Core/Context.h>
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/FileInterface.h>
-
+#include <GLFW/glfw3.h>
+#include <cstdint>
 #include <thread>
-#include <chrono>
 
 static void SetupCallbacks(GLFWwindow* window);
 
@@ -89,10 +91,14 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	data = Rml::MakeUnique<BackendData>();
 	data->window = window;
 
-	if (!data->render_interface.Initialize([](VkInstance instance, VkSurfaceKHR* out_surface) {
-			return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS;
-			; 
-		}))
+	uint32_t count;
+	const char** extensions = glfwGetRequiredInstanceExtensions(&count);
+	RMLUI_VK_ASSERTMSG(extensions != nullptr, "Failed to query glfw vulkan extensions");
+	if (!data->render_interface.Initialize(Rml::Vector<const char*>(extensions, extensions + count),
+			[](VkInstance instance, VkSurfaceKHR* out_surface) {
+				return glfwCreateWindowSurface(instance, data->window, nullptr, out_surface) == VkResult::VK_SUCCESS;
+				;
+			}))
 	{
 		data.reset();
 		fprintf(stderr, "Could not initialize Vulkan render interface.");
@@ -160,7 +166,7 @@ static bool WaitForValidSwapchain()
 	return result;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -178,11 +184,14 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		context->SetDimensions(window_size);
 		context->SetDensityIndependentPixelRatio(dp_ratio);
 	}
-	
+
 	data->context = context;
 	data->key_down_callback = key_down_callback;
 
-	glfwPollEvents();
+	if (power_save)
+		glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0));
+	else
+		glfwPollEvents();
 
 	if (!WaitForValidSwapchain())
 		result = false;

+ 7 - 2
Backends/RmlUi_Backend_SDL_GL2.cpp

@@ -253,7 +253,7 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -261,7 +261,11 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->running = true;
 
 	SDL_Event ev;
-	while (SDL_PollEvent(&ev))
+	int has_event = 0;
+	if(power_save)
+		has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000));
+	else has_event = SDL_PollEvent(&ev);
+	while (has_event)
 	{
 		switch (ev.type)
 		{
@@ -307,6 +311,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 		break;
 		}
+		has_event = SDL_PollEvent(&ev);
 	}
 
 	return result;

+ 7 - 2
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -218,7 +218,7 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -240,7 +240,11 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->running = true;
 
 	SDL_Event ev;
-	while (SDL_PollEvent(&ev))
+	int has_event = 0;
+	if(power_save)
+		has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000));
+	else has_event = SDL_PollEvent(&ev);
+	while (has_event)
 	{
 		switch (ev.type)
 		{
@@ -286,6 +290,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 		break;
 		}
+		has_event = SDL_PollEvent(&ev);
 	}
 
 	return result;

+ 8 - 3
Backends/RmlUi_Backend_SDL_SDLrenderer.cpp

@@ -120,14 +120,18 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
 	bool result = data->running;
 	SDL_Event ev;
-
-	while (SDL_PollEvent(&ev))
+	
+	int has_event = 0;
+	if(power_save)
+		has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000));
+	else has_event = SDL_PollEvent(&ev);
+	while (has_event)
 	{
 		switch (ev.type)
 		{
@@ -159,6 +163,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 		break;
 		}
+		has_event = SDL_PollEvent(&ev);
 	}
 
 	return result;

+ 26 - 3
Backends/RmlUi_Backend_SDL_VK.cpp

@@ -73,7 +73,25 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 	data = Rml::MakeUnique<BackendData>();
 	data->window = window;
 
-	if (!data->render_interface.Initialize(
+	Rml::Vector<const char*> extensions;
+	{
+		unsigned int count;
+		if(!SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr))
+		{
+			data.reset();
+			fprintf(stderr, "Could not 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");
+			return false;
+		}
+	}
+
+	if (!data->render_interface.Initialize(std::move(extensions),
 			[](VkInstance instance, VkSurfaceKHR* out_surface) { return (bool)SDL_Vulkan_CreateSurface(data->window, instance, out_surface); }))
 	{
 		data.reset();
@@ -138,14 +156,18 @@ static bool WaitForValidSwapchain()
 	return result;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
 	bool result = data->running;
 	SDL_Event ev;
 
-	while (SDL_PollEvent(&ev))
+	int has_event = 0;
+	if(power_save)
+		has_event = SDL_WaitEventTimeout(&ev, static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000));
+	else has_event = SDL_PollEvent(&ev);
+	while (has_event)
 	{
 		switch (ev.type)
 		{
@@ -188,6 +210,7 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 		}
 		break;
 		}
+		has_event = SDL_PollEvent(&ev);
 	}
 
 	if (!WaitForValidSwapchain())

+ 4 - 1
Backends/RmlUi_Backend_SFML_GL2.cpp

@@ -189,10 +189,13 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
+	// SFML does not seem to provide a way to wait for events with a timeout.
+	(void)power_save;
+
 	// The contents of this function is intended to be copied directly into your main loop.
 	bool result = data->running;
 	data->running = true;

+ 18 - 4
Backends/RmlUi_Backend_Win32_GL2.cpp

@@ -189,7 +189,20 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+static bool NextEvent(MSG& message, UINT timeout)
+{
+	if(timeout != 0)
+	{
+		UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL);
+		BOOL res = GetMessage(&message, NULL, 0, 0);
+		KillTimer(NULL, timer_id);
+		if(message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
+			return res;
+	}
+	return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE);
+}
+
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -206,13 +219,14 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->key_down_callback = key_down_callback;
 
 	MSG message;
-	while (PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE))
+	bool has_message = NextEvent(message, power_save ? static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000.0) : 0);
+	while (has_message)
 	{
-		GetMessage(&message, nullptr, 0, 0);
-
 		// Dispatch the message to our local event handler below.
 		TranslateMessage(&message);
 		DispatchMessage(&message);
+
+		has_message = NextEvent(message, 0);
 	}
 
 	data->context = nullptr;

+ 29 - 9
Backends/RmlUi_Backend_Win32_VK.cpp

@@ -26,6 +26,7 @@
  *
  */
 
+#include "RmlUi/Config/Config.h"
 #include "RmlUi_Backend.h"
 #include "RmlUi_Include_Windows.h"
 #include "RmlUi_Platform_Win32.h"
@@ -144,7 +145,10 @@ bool Backend::Initialize(const char* window_name, int width, int height, bool al
 
 	data->window_handle = window_handle;
 
-	if (!data->render_interface.Initialize(CreateVulkanSurface))
+	Rml::Vector<const char*> extensions;
+	extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
+	extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
+	if (!data->render_interface.Initialize(std::move(extensions), CreateVulkanSurface))
 	{
 		DisplayError(window_handle, "Could not initialize Vulkan render interface.");
 		::CloseWindow(window_handle);
@@ -187,7 +191,20 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+static bool NextEvent(MSG& message, UINT timeout)
+{
+	if(timeout != 0)
+	{
+		UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL);
+		BOOL res = GetMessage(&message, NULL, 0, 0);
+		KillTimer(NULL, timer_id);
+		if(message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id)
+			return res;
+	}
+	return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE);
+}
+
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -204,21 +221,24 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	data->key_down_callback = key_down_callback;
 
 	MSG message;
-
 	// Process events.
-	while (PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE) || !data->render_interface.IsSwapchainValid())
+	bool has_message = NextEvent(message, power_save ? static_cast<int>(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)*1000.0) : 0);
+	while (has_message || !data->render_interface.IsSwapchainValid())
 	{
-		GetMessage(&message, nullptr, 0, 0);
-
-		// Dispatch the message to our local event handler below.
-		TranslateMessage(&message);
-		DispatchMessage(&message);
+		if(has_message)
+		{
+			// Dispatch the message to our local event handler below.
+			TranslateMessage(&message);
+			DispatchMessage(&message);
+		}
 
 		// In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any
 		// render calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead,
 		// we trap the application inside this loop until we are able to recreate the swapchain and render again.
 		if (!data->render_interface.IsSwapchainValid())
 			data->render_interface.RecreateSwapchain();
+		
+		has_message = NextEvent(message, 0);
 	}
 
 	data->context = nullptr;

+ 23 - 1
Backends/RmlUi_Backend_X11_GL2.cpp

@@ -26,6 +26,7 @@
  *
  */
 
+#include "RmlUi/Core/Debug.h"
 #include "RmlUi_Backend.h"
 #include "RmlUi_Include_Xlib.h"
 #include "RmlUi_Platform_X11.h"
@@ -47,6 +48,7 @@
 #include <sys/types.h>
 #include <time.h>
 #include <unistd.h>
+#include <cmath>
 
 // Attach the OpenGL context to the window.
 static bool AttachToNative(GLXContext& out_gl_context, Display* display, Window window, XVisualInfo* visual_info)
@@ -205,7 +207,7 @@ Rml::RenderInterface* Backend::GetRenderInterface()
 	return &data->render_interface;
 }
 
-bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback)
+bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save)
 {
 	RMLUI_ASSERT(data && context);
 
@@ -213,6 +215,26 @@ bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_call
 	bool result = data->running;
 	data->running = true;
 
+	if(power_save && XPending(display) == 0) {
+		int display_fd = ConnectionNumber(display);
+		fd_set fds{};
+		FD_ZERO(&fds);
+        FD_SET(display_fd, &fds);
+
+		double timeout = Rml::Math::Min(context->GetNextUpdateDelay(), 10.0);
+		struct timeval tv{};
+		double seconds;
+        tv.tv_usec = std::modf(timeout, &seconds)*1000000.0;
+        tv.tv_sec = seconds;
+
+		int ready_fd_count;
+		do {
+			ready_fd_count = select(display_fd + 1, &fds, NULL, NULL, &tv);
+			// We don't care about the return value as long as select didn't error out
+			RMLUI_ASSERT(ready_fd_count >= 0);
+		} while(XPending(display) == 0 && ready_fd_count != 0);
+	}
+
 	while (XPending(display) > 0)
 	{
 		XEvent ev;

+ 5 - 13
Backends/RmlUi_Renderer_VK.cpp

@@ -798,7 +798,7 @@ void RenderInterface_VK::RecreateSwapchain()
 	SetViewport(m_width, m_height);
 }
 
-bool RenderInterface_VK::Initialize(CreateSurfaceCallback create_surface_callback)
+bool RenderInterface_VK::Initialize(Rml::Vector<const char*> required_extensions, CreateSurfaceCallback create_surface_callback)
 {
 	RMLUI_ZoneScopedN("Vulkan - Initialize");
 
@@ -806,7 +806,7 @@ bool RenderInterface_VK::Initialize(CreateSurfaceCallback create_surface_callbac
 	glad_result = gladLoaderLoadVulkan(VK_NULL_HANDLE, VK_NULL_HANDLE, VK_NULL_HANDLE);
 	RMLUI_VK_ASSERTMSG(glad_result != 0, "Vulkan loader failed - Global functions");
 
-	Initialize_Instance();
+	Initialize_Instance(std::move(required_extensions));
 
 	VkPhysicalDeviceProperties physical_device_properties = {};
 	Initialize_PhysicalDevice(physical_device_properties);
@@ -850,7 +850,7 @@ void RenderInterface_VK::Shutdown()
 	gladLoaderUnloadVulkan();
 }
 
-void RenderInterface_VK::Initialize_Instance() noexcept
+void RenderInterface_VK::Initialize_Instance(Rml::Vector<const char*> required_extensions) noexcept
 {
 	uint32_t required_version = GetRequiredVersionAndValidateMachine();
 
@@ -863,7 +863,7 @@ void RenderInterface_VK::Initialize_Instance() noexcept
 	info.apiVersion = required_version;
 
 	Rml::Vector<const char*> instance_layer_names;
-	Rml::Vector<const char*> instance_extension_names;
+	Rml::Vector<const char*> instance_extension_names = std::move(required_extensions);
 	CreatePropertiesFor_Instance(instance_layer_names, instance_extension_names);
 
 	VkInstanceCreateInfo info_instance = {};
@@ -1023,7 +1023,7 @@ void RenderInterface_VK::Initialize_Surface(CreateSurfaceCallback create_surface
 	RMLUI_VK_ASSERTMSG(m_p_instance, "you must initialize your VkInstance");
 
 	bool result = create_surface_callback(m_p_instance, &m_p_surface);
-	RMLUI_VK_ASSERTMSG(result && m_p_surface, "failed to vkCreateWin32SurfaceKHR");
+	RMLUI_VK_ASSERTMSG(result && m_p_surface, "failed to call create_surface_callback");
 }
 
 void RenderInterface_VK::Initialize_QueueIndecies() noexcept
@@ -1409,14 +1409,6 @@ void RenderInterface_VK::CreatePropertiesFor_Instance(Rml::Vector<const char*>&
 	AddExtensionToInstance(instance_extension_names, instance_extension_properties, "VK_EXT_debug_utils");
 	AddExtensionToInstance(instance_extension_names, instance_extension_properties, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
 
-#if defined(RMLUI_PLATFORM_UNIX)
-	AddExtensionToInstance(instance_extension_names, instance_extension_properties, VK_KHR_XCB_SURFACE_EXTENSION_NAME);
-#elif defined(RMLUI_PLATFORM_WIN32)
-	AddExtensionToInstance(instance_extension_names, instance_extension_properties, VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
-#endif
-
-	AddExtensionToInstance(instance_extension_names, instance_extension_properties, VK_KHR_SURFACE_EXTENSION_NAME);
-
 #ifdef RMLUI_VK_DEBUG
 	AddLayerToInstance(instance_layer_names, instance_layer_properties, "VK_LAYER_LUNARG_monitor");
 

+ 2 - 2
Backends/RmlUi_Renderer_VK.h

@@ -114,7 +114,7 @@ public:
 
 	using CreateSurfaceCallback = bool (*)(VkInstance instance, VkSurfaceKHR* out_surface);
 
-	bool Initialize(CreateSurfaceCallback create_surface_callback);
+	bool Initialize(Rml::Vector<const char*> required_extensions, CreateSurfaceCallback create_surface_callback);
 	void Shutdown();
 
 	void BeginFrame();
@@ -485,7 +485,7 @@ private:
 private:
 	bool CreateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& dimensions, const Rml::String& name);
 
-	void Initialize_Instance() noexcept;
+	void Initialize_Instance(Rml::Vector<const char*> required_extensions) noexcept;
 	void Initialize_Device() noexcept;
 	void Initialize_PhysicalDevice(VkPhysicalDeviceProperties& out_physical_device_properties) noexcept;
 	void Initialize_Swapchain(VkExtent2D window_extent) noexcept;

+ 19 - 0
Include/RmlUi/Core/Context.h

@@ -280,6 +280,21 @@ public:
 	/// @return The current documents base tag name.
 	const String& GetDocumentsBaseTag();
 
+	/// Updates the time until Update should get called again. This can be used by elements
+	/// and the app to implement on demand rendering and thus drastically save CPU/GPU and
+	/// reduce power consumption during inactivity. The context stores the lowest requested
+	/// timestamp, which can later retrieved using GetNextUpdateDelay().
+	/// @param[in] delay Maximum time until next update
+	void RequestNextUpdate(double delay);
+
+	/// Get the max delay until update and render should get called again. An application can choose
+	/// to only call update and render once the time has elapsed, but theres no harm in doing so
+	/// more often. The returned value can be infinity, in which case update should be invoked after
+	/// user input was received. A value of 0 means "render as fast as possible", for example if
+	/// an animation is playing.
+	/// @return Time until next update is expected.
+	double GetNextUpdateDelay() const;
+
 protected:
 	void Release() override;
 
@@ -360,6 +375,10 @@ private:
 
 	UniquePtr<DataTypeRegister> default_data_type_register;
 
+	// 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;
+
 	// Internal callback for when an element is detached or removed from the hierarchy.
 	void OnElementDetach(Element* element);
 	// Internal callback for when a new element gains focus.

+ 2 - 1
Include/RmlUi/Core/Element.h

@@ -177,8 +177,9 @@ public:
 	virtual bool IsPointWithinElement(Vector2f point);
 
 	/// Returns the visibility of the element.
+	/// @param[in] include_ancestors Check parent elements for visibility
 	/// @return True if the element is visible, false otherwise.
-	bool IsVisible() const;
+	bool IsVisible(bool include_ancestors = false) const;
 	/// Returns the z-index of the element.
 	/// @return The element's z-index.
 	float GetZIndex() const;

+ 3 - 0
Include/RmlUi/Lottie/ElementLottie.h

@@ -50,6 +50,9 @@ public:
 	bool GetIntrinsicDimensions(Vector2f& dimensions, float& ratio) override;
 
 protected:
+	/// Updates the animation.
+	void OnUpdate() override;
+
 	/// Renders the animation.
 	void OnRender() override;
 

+ 1 - 1
Samples/basic/bitmapfont/src/main.cpp

@@ -106,7 +106,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/basic/demo/src/main.cpp

@@ -476,7 +476,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		demo_window->Update();
 		context->Update();

+ 1 - 1
Samples/basic/drag/src/main.cpp

@@ -86,7 +86,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/basic/loaddocument/src/main.cpp

@@ -83,7 +83,7 @@ int main(int /*argc*/, char** /*argv*/)
 	while (running)
 	{
 		// Handle input and window events.
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		// This is a good place to update your game or application.
 

+ 1 - 1
Samples/basic/lottie/src/main.cpp

@@ -82,7 +82,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/basic/svg/src/main.cpp

@@ -82,7 +82,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/basic/treeview/src/main.cpp

@@ -89,7 +89,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/tutorial/datagrid/src/main.cpp

@@ -75,7 +75,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/tutorial/datagrid_tree/src/main.cpp

@@ -79,7 +79,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/tutorial/drag/src/main.cpp

@@ -69,7 +69,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 1 - 1
Samples/tutorial/template/src/main.cpp

@@ -62,7 +62,7 @@ int main(int /*argc*/, char** /*argv*/)
 	bool running = true;
 	while (running)
 	{
-		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts);
+		running = Backend::ProcessEvents(context, &Shell::ProcessKeyDownShortcuts, true);
 
 		context->Update();
 

+ 18 - 2
Source/Core/Context.cpp

@@ -41,10 +41,12 @@
 #include "DataModel.h"
 #include "EventDispatcher.h"
 #include "PluginRegistry.h"
+#include "RmlUi/Core/Debug.h"
 #include "ScrollController.h"
 #include "StreamFile.h"
 #include <algorithm>
 #include <iterator>
+#include <limits>
 
 
 namespace Rml {
@@ -53,7 +55,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)
+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)
 {
 	instancer = nullptr;
 
@@ -183,7 +185,10 @@ bool Context::Update()
 {
 	RMLUI_ZoneScoped;
 
-	scroll_controller->Update(mouse_position, density_independent_pixel_ratio);
+	next_update_timeout = std::numeric_limits<double>::infinity();
+
+	if(scroll_controller->Update(mouse_position, density_independent_pixel_ratio))
+		RequestNextUpdate(0);
 
 	// Update the hover chain to detect any new or moved elements under the mouse.
 	if (mouse_active)
@@ -202,11 +207,13 @@ bool Context::Update()
 	root->Update(density_independent_pixel_ratio, Vector2f(dimensions));
 
 	for (int i = 0; i < root->GetNumChildren(); ++i)
+	{
 		if (auto doc = root->GetChild(i)->GetOwnerDocument())
 		{
 			doc->UpdateLayout();
 			doc->UpdatePosition();
 		}
+	}
 
 	// Release any documents that were unloaded during the update.
 	ReleaseUnloadedDocuments();
@@ -1462,4 +1469,13 @@ const String& Context::GetDocumentsBaseTag()
 	return documents_base_tag;
 }
 
+void Context::RequestNextUpdate(double delay) {
+	RMLUI_ASSERT(delay >= 0.0);
+	next_update_timeout = Math::Min(next_update_timeout, delay);
+}
+
+double Context::GetNextUpdateDelay() const {
+	return next_update_timeout;
+}
+
 } // namespace Rml

+ 16 - 2
Source/Core/Element.cpp

@@ -185,6 +185,11 @@ void Element::Update(float dp_ratio, Vector2f vp_dimensions)
 
 	for (size_t i = 0; i < children.size(); i++)
 		children[i]->Update(dp_ratio, vp_dimensions);
+
+	if(!animations.empty() && IsVisible(true)) {
+		if(Context* ctx = GetContext())
+			ctx->RequestNextUpdate(0);
+	}
 }
 
 void Element::UpdateProperties(const float dp_ratio, const Vector2f vp_dimensions)
@@ -560,9 +565,18 @@ bool Element::IsPointWithinElement(const Vector2f point)
 }
 
 // Returns the visibility of the element.
-bool Element::IsVisible() const
+bool Element::IsVisible(bool include_ancestors) const
 {
-	return visible;
+	if (!include_ancestors)
+		return visible;
+	const Element* element = this;
+	while (element)
+	{
+		if (!element->visible)
+			return false;
+		element = element->parent;
+	}
+	return true;
 }
 
 // Returns the z-index of the element.

+ 4 - 0
Source/Core/Elements/WidgetSlider.cpp

@@ -34,6 +34,7 @@
 #include "../../../Include/RmlUi/Core/Input.h"
 #include "../../../Include/RmlUi/Core/Profiling.h"
 #include "../Clock.h"
+#include "../../../Include/RmlUi/Core/Context.h"
 
 namespace Rml {
 
@@ -148,6 +149,9 @@ void WidgetSlider::Update()
 				arrow_timers[i] += DEFAULT_REPEAT_PERIOD;
 				SetBarPosition(i == 0 ? OnLineDecrement() : OnLineIncrement());
 			}
+
+			if(Context* ctx = parent->GetContext())
+				ctx->RequestNextUpdate(arrow_timers[i]);
 		}
 	}
 }

+ 6 - 0
Source/Core/Elements/WidgetTextInput.cpp

@@ -39,6 +39,7 @@
 #include "../../../Include/RmlUi/Core/Math.h"
 #include "../../../Include/RmlUi/Core/StringUtilities.h"
 #include "../../../Include/RmlUi/Core/SystemInterface.h"
+#include "../../../Include/RmlUi/Core/Context.h"
 #include "../Clock.h"
 #include "ElementTextSelection.h"
 #include <algorithm>
@@ -328,6 +329,11 @@ void WidgetTextInput::OnUpdate()
 			cursor_timer += CURSOR_BLINK_TIME;
 			cursor_visible = !cursor_visible;
 		}
+
+		if(parent->IsVisible(true)) {
+			if(Context* ctx = parent->GetContext())
+				ctx->RequestNextUpdate(cursor_timer);
+		}
 	}
 }
 

+ 3 - 1
Source/Core/ScrollController.cpp

@@ -122,12 +122,14 @@ void ScrollController::ActivateSmoothscroll(Element* in_target, Vector2f delta_d
 		Reset();
 }
 
-void ScrollController::Update(Vector2i mouse_position, float dp_ratio)
+bool ScrollController::Update(Vector2i mouse_position, float dp_ratio)
 {
 	if (mode == Mode::Autoscroll)
 		UpdateAutoscroll(mouse_position, dp_ratio);
 	else if (mode == Mode::Smoothscroll)
 		UpdateSmoothscroll(dp_ratio);
+	
+	return mode != Mode::None;
 }
 
 void ScrollController::UpdateAutoscroll(Vector2i mouse_position, float dp_ratio)

+ 1 - 1
Source/Core/ScrollController.h

@@ -53,7 +53,7 @@ public:
 
 	void ActivateSmoothscroll(Element* target, Vector2f delta_distance, ScrollBehavior scroll_behavior);
 
-	void Update(Vector2i mouse_position, float dp_ratio);
+	bool Update(Vector2i mouse_position, float dp_ratio);
 
 	void IncrementSmoothscrollTarget(Vector2f delta_distance);
 

+ 4 - 0
Source/Core/WidgetScroll.cpp

@@ -33,6 +33,7 @@
 #include "../../Include/RmlUi/Core/Event.h"
 #include "../../Include/RmlUi/Core/Factory.h"
 #include "../../Include/RmlUi/Core/Property.h"
+#include "../../Include/RmlUi/Core/Context.h"
 #include "Clock.h"
 #include "LayoutDetails.h"
 
@@ -169,6 +170,9 @@ void WidgetScroll::Update()
 				else
 					ScrollLineDown();
 			}
+
+			if(Context* ctx = parent->GetContext())
+				ctx->RequestNextUpdate(arrow_timers[i]);
 		}
 	}
 }

+ 23 - 3
Source/Lottie/ElementLottie.cpp

@@ -29,6 +29,7 @@
 #include "../../Include/RmlUi/Lottie/ElementLottie.h"
 #include "../../Include/RmlUi/Core/ComputedValues.h"
 #include "../../Include/RmlUi/Core/Core.h"
+#include "../../Include/RmlUi/Core/Context.h"
 #include "../../Include/RmlUi/Core/ElementDocument.h"
 #include "../../Include/RmlUi/Core/FileInterface.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
@@ -60,6 +61,28 @@ bool ElementLottie::GetIntrinsicDimensions(Vector2f& dimensions, float& ratio)
 	return true;
 }
 
+void ElementLottie::OnUpdate()
+{
+	if (animation_dirty)
+		LoadAnimation();
+
+	if (!animation)
+		return;
+
+	const auto t = GetSystemInterface()->GetElapsedTime();
+
+	if (time_animation_start < 0.0)
+		time_animation_start = t;
+
+	double _unused;
+	const double frame_duration = 1.0 / animation->frameRate();
+	const double delay = std::modf((t - time_animation_start) / frame_duration, &_unused) * frame_duration;
+	if(IsVisible(true)) {
+		if (Context* ctx = GetContext())
+			ctx->RequestNextUpdate(delay);
+	}
+}
+
 void ElementLottie::OnRender()
 {
 	if (animation)
@@ -183,9 +206,6 @@ void ElementLottie::UpdateTexture()
 
 	const double t = GetSystemInterface()->GetElapsedTime();
 
-	if (time_animation_start < 0.0)
-		time_animation_start = t;
-
 	// Find the next animation frame to display.
 	// Here it is possible to add more logic to control playback speed, pause/resume, and more.
 	double _unused;