diff --git a/Libraries/LibGfx/PaintingSurface.cpp b/Libraries/LibGfx/PaintingSurface.cpp index 68029c98204..f645dbeaa03 100644 --- a/Libraries/LibGfx/PaintingSurface.cpp +++ b/Libraries/LibGfx/PaintingSurface.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -58,7 +58,7 @@ NonnullRefPtr PaintingSurface::wrap_bitmap(Bitmap& bitmap) } #ifdef AK_OS_MACOS -NonnullRefPtr PaintingSurface::wrap_iosurface(Core::IOSurfaceHandle const& iosurface_handle, RefPtr context, Origin origin) +NonnullRefPtr PaintingSurface::create_from_iosurface(Core::IOSurfaceHandle&& iosurface_handle, NonnullRefPtr context, Origin origin) { context->lock(); ScopeGuard unlock_guard([&context] { diff --git a/Libraries/LibGfx/PaintingSurface.h b/Libraries/LibGfx/PaintingSurface.h index fab6f6f65ed..a2890d74694 100644 --- a/Libraries/LibGfx/PaintingSurface.h +++ b/Libraries/LibGfx/PaintingSurface.h @@ -36,7 +36,7 @@ public: static NonnullRefPtr wrap_bitmap(Bitmap&); #ifdef AK_OS_MACOS - static NonnullRefPtr wrap_iosurface(Core::IOSurfaceHandle const&, RefPtr, Origin = Origin::TopLeft); + static NonnullRefPtr create_from_iosurface(Core::IOSurfaceHandle&&, NonnullRefPtr, Origin = Origin::TopLeft); #endif void read_into_bitmap(Bitmap&); diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 7443ce17285..3393d65d4d4 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -683,7 +683,6 @@ set(SOURCES Page/Page.cpp Painting/AudioPaintable.cpp Painting/BackgroundPainting.cpp - Painting/BackingStore.cpp Painting/BackingStoreManager.cpp Painting/BorderPainting.cpp Painting/BorderRadiiData.cpp diff --git a/Libraries/LibWeb/HTML/Navigable.cpp b/Libraries/LibWeb/HTML/Navigable.cpp index 8a53da774b8..82ebc0c2561 100644 --- a/Libraries/LibWeb/HTML/Navigable.cpp +++ b/Libraries/LibWeb/HTML/Navigable.cpp @@ -2327,8 +2327,6 @@ void Navigable::set_viewport_size(CSSPixelSize size) if (m_viewport_size == size) return; - m_rendering_thread.clear_bitmap_to_surface_cache(); - if (!m_is_svg_page) { m_backing_store_manager->restart_resize_timer(); m_backing_store_manager->resize_backing_stores_if_needed(Web::Painting::BackingStoreManager::WindowResizingInProgress::Yes); @@ -2557,8 +2555,8 @@ void Navigable::ready_to_paint() void Navigable::paint_next_frame() { - auto [backing_store_id, back_store] = m_backing_store_manager->acquire_store_for_next_frame(); - if (!back_store) + auto [backing_store_id, painting_surface] = m_backing_store_manager->acquire_store_for_next_frame(); + if (!painting_surface) return; VERIFY(m_number_of_queued_rasterization_tasks <= 1); @@ -2566,7 +2564,7 @@ void Navigable::paint_next_frame() auto viewport_rect = page().css_to_device_rect(this->viewport_rect()); PaintConfig paint_config { .paint_overlay = true, .should_show_line_box_borders = m_should_show_line_box_borders, .canvas_fill_rect = Gfx::IntRect { {}, viewport_rect.size().to_type() } }; - start_display_list_rendering(*back_store, paint_config, [this, viewport_rect, backing_store_id] { + start_display_list_rendering(*painting_surface, paint_config, [this, viewport_rect, backing_store_id] { if (!is_top_level_traversable()) return; auto& traversable = *page().top_level_traversable(); @@ -2574,7 +2572,7 @@ void Navigable::paint_next_frame() }); } -void Navigable::start_display_list_rendering(Painting::BackingStore& target, PaintConfig paint_config, Function&& callback) +void Navigable::start_display_list_rendering(Gfx::PaintingSurface& painting_surface, PaintConfig paint_config, Function&& callback) { m_needs_repaint = false; auto document = active_document(); @@ -2589,7 +2587,7 @@ void Navigable::start_display_list_rendering(Painting::BackingStore& target, Pai return; } auto scroll_state_snapshot = document->paintable()->scroll_state().snapshot(); - m_rendering_thread.enqueue_rendering_task(*display_list, move(scroll_state_snapshot), target, move(callback)); + m_rendering_thread.enqueue_rendering_task(*display_list, move(scroll_state_snapshot), painting_surface, move(callback)); } } diff --git a/Libraries/LibWeb/HTML/Navigable.h b/Libraries/LibWeb/HTML/Navigable.h index 7e79fe3aba0..3e02da7b1f2 100644 --- a/Libraries/LibWeb/HTML/Navigable.h +++ b/Libraries/LibWeb/HTML/Navigable.h @@ -208,7 +208,7 @@ public: bool is_ready_to_paint() const; void ready_to_paint(); void paint_next_frame(); - void start_display_list_rendering(Painting::BackingStore&, PaintConfig, Function&& callback); + void start_display_list_rendering(Gfx::PaintingSurface&, PaintConfig, Function&& callback); bool needs_repaint() const { return m_needs_repaint; } void set_needs_repaint() { m_needs_repaint = true; } diff --git a/Libraries/LibWeb/HTML/RenderingThread.cpp b/Libraries/LibWeb/HTML/RenderingThread.cpp index d118faab5df..55b3b3a05cb 100644 --- a/Libraries/LibWeb/HTML/RenderingThread.cpp +++ b/Libraries/LibWeb/HTML/RenderingThread.cpp @@ -7,7 +7,6 @@ #include #include #include -#include namespace Web::HTML { @@ -49,10 +48,6 @@ void RenderingThread::rendering_thread_loop() while (true) { auto task = [this]() -> Optional { Threading::MutexLocker const locker { m_rendering_task_mutex }; - if (m_needs_to_clear_bitmap_to_surface_cache) { - m_bitmap_to_surface.clear(); - m_needs_to_clear_bitmap_to_surface_cache = false; - } while (m_rendering_tasks.is_empty() && !m_exit) { m_rendering_task_ready_wake_condition.wait(); } @@ -66,8 +61,7 @@ void RenderingThread::rendering_thread_loop() break; } - auto painting_surface = painting_surface_for_backing_store(task->backing_store); - m_skia_player->execute(*task->display_list, task->scroll_state_snapshot, painting_surface); + m_skia_player->execute(*task->display_list, task->scroll_state_snapshot, task->painting_surface); if (m_exit) break; m_main_thread_event_loop.deferred_invoke([callback = move(task->callback)] { @@ -76,48 +70,11 @@ void RenderingThread::rendering_thread_loop() } } -void RenderingThread::enqueue_rendering_task(NonnullRefPtr display_list, Painting::ScrollStateSnapshot&& scroll_state_snapshot, NonnullRefPtr backing_store, Function&& callback) +void RenderingThread::enqueue_rendering_task(NonnullRefPtr display_list, Painting::ScrollStateSnapshot&& scroll_state_snapshot, NonnullRefPtr painting_surface, Function&& callback) { Threading::MutexLocker const locker { m_rendering_task_mutex }; - m_rendering_tasks.enqueue(Task { move(display_list), move(scroll_state_snapshot), move(backing_store), move(callback) }); + m_rendering_tasks.enqueue(Task { move(display_list), move(scroll_state_snapshot), move(painting_surface), move(callback) }); m_rendering_task_ready_wake_condition.signal(); } -NonnullRefPtr RenderingThread::painting_surface_for_backing_store(Painting::BackingStore& backing_store) -{ - auto& bitmap = backing_store.bitmap(); - auto cached_surface = m_bitmap_to_surface.find(&bitmap); - if (cached_surface != m_bitmap_to_surface.end()) - return cached_surface->value; - - RefPtr new_surface; - if (m_display_list_player_type == DisplayListPlayerType::SkiaGPUIfAvailable && m_skia_backend_context) { -#ifdef USE_VULKAN - // Vulkan: Try to create an accelerated surface. - new_surface = Gfx::PaintingSurface::create_with_size(m_skia_backend_context, backing_store.size(), Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); - new_surface->on_flush = [backing_store = static_cast>(backing_store)](auto& surface) { surface.read_into_bitmap(backing_store->bitmap()); }; -#endif -#ifdef AK_OS_MACOS - // macOS: Wrap an IOSurface if available. - if (is(backing_store)) { - auto& iosurface_backing_store = static_cast(backing_store); - new_surface = Gfx::PaintingSurface::wrap_iosurface(iosurface_backing_store.iosurface_handle(), *m_skia_backend_context); - } -#endif - } - - // CPU and fallback: wrap the backing store bitmap directly. - if (!new_surface) - new_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap); - - m_bitmap_to_surface.set(&bitmap, *new_surface); - return *new_surface; -} - -void RenderingThread::clear_bitmap_to_surface_cache() -{ - Threading::MutexLocker const locker { m_rendering_task_mutex }; - m_needs_to_clear_bitmap_to_surface_cache = true; -} - } diff --git a/Libraries/LibWeb/HTML/RenderingThread.h b/Libraries/LibWeb/HTML/RenderingThread.h index 7cb92cc61fc..e9b27fbef11 100644 --- a/Libraries/LibWeb/HTML/RenderingThread.h +++ b/Libraries/LibWeb/HTML/RenderingThread.h @@ -29,12 +29,10 @@ public: void start(DisplayListPlayerType); void set_skia_player(OwnPtr&& player) { m_skia_player = move(player); } void set_skia_backend_context(RefPtr context) { m_skia_backend_context = move(context); } - void enqueue_rendering_task(NonnullRefPtr, Painting::ScrollStateSnapshot&&, NonnullRefPtr, Function&& callback); - void clear_bitmap_to_surface_cache(); + void enqueue_rendering_task(NonnullRefPtr, Painting::ScrollStateSnapshot&&, NonnullRefPtr, Function&& callback); private: void rendering_thread_loop(); - NonnullRefPtr painting_surface_for_backing_store(Painting::BackingStore& backing_store); Core::EventLoop& m_main_thread_event_loop; DisplayListPlayerType m_display_list_player_type; @@ -49,7 +47,7 @@ private: struct Task { NonnullRefPtr display_list; Painting::ScrollStateSnapshot scroll_state_snapshot; - NonnullRefPtr backing_store; + NonnullRefPtr painting_surface; Function callback; }; // NOTE: Queue will only contain multiple items in case tasks were scheduled by screenshot requests. @@ -57,9 +55,6 @@ private: Queue m_rendering_tasks; Threading::Mutex m_rendering_task_mutex; Threading::ConditionVariable m_rendering_task_ready_wake_condition { m_rendering_task_mutex }; - - HashMap> m_bitmap_to_surface; - bool m_needs_to_clear_bitmap_to_surface_cache { false }; }; } diff --git a/Libraries/LibWeb/HTML/TraversableNavigable.cpp b/Libraries/LibWeb/HTML/TraversableNavigable.cpp index 83d94d36216..c83b19e1267 100644 --- a/Libraries/LibWeb/HTML/TraversableNavigable.cpp +++ b/Libraries/LibWeb/HTML/TraversableNavigable.cpp @@ -21,8 +21,7 @@ #include #include #include -#include -#include +#include #include namespace Web::HTML { @@ -1413,19 +1412,19 @@ void TraversableNavigable::process_screenshot_requests() } auto rect = page().enclosing_device_rect(dom_node->paintable_box()->absolute_border_box_rect()); auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type()).release_value_but_fixme_should_propagate_errors(); - auto backing_store = Painting::BitmapBackingStore::create(*bitmap); + auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*bitmap); PaintConfig paint_config { .canvas_fill_rect = rect.to_type() }; - start_display_list_rendering(backing_store, paint_config, [backing_store, &client] { - client.page_did_take_screenshot(backing_store->bitmap().to_shareable_bitmap()); + start_display_list_rendering(painting_surface, paint_config, [bitmap, &client] { + client.page_did_take_screenshot(bitmap->to_shareable_bitmap()); }); } else { auto scrollable_overflow_rect = active_document()->layout_node()->paintable_box()->scrollable_overflow_rect(); auto rect = page().enclosing_device_rect(scrollable_overflow_rect.value()); auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size().to_type()).release_value_but_fixme_should_propagate_errors(); - auto backing_store = Painting::BitmapBackingStore::create(*bitmap); + auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(*bitmap); PaintConfig paint_config { .paint_overlay = true, .canvas_fill_rect = rect.to_type() }; - start_display_list_rendering(backing_store, paint_config, [backing_store, &client] { - client.page_did_take_screenshot(backing_store->bitmap().to_shareable_bitmap()); + start_display_list_rendering(painting_surface, paint_config, [bitmap, &client] { + client.page_did_take_screenshot(bitmap->to_shareable_bitmap()); }); } } diff --git a/Libraries/LibWeb/Painting/BackingStore.cpp b/Libraries/LibWeb/Painting/BackingStore.cpp deleted file mode 100644 index ccea6c21a60..00000000000 --- a/Libraries/LibWeb/Painting/BackingStore.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2024, Aliaksandr Kalenik - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -namespace Web::Painting { - -BitmapBackingStore::BitmapBackingStore(RefPtr bitmap) - : m_bitmap(move(bitmap)) -{ -} - -#ifdef AK_OS_MACOS -IOSurfaceBackingStore::IOSurfaceBackingStore(Core::IOSurfaceHandle&& iosurface_handle) - : m_iosurface_handle(move(iosurface_handle)) -{ - auto bytes_per_row = m_iosurface_handle.bytes_per_row(); - auto bitmap = Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size(), bytes_per_row, m_iosurface_handle.data()); - m_bitmap_wrapper = bitmap.release_value(); -} - -Gfx::IntSize IOSurfaceBackingStore::size() const -{ - return { m_iosurface_handle.width(), m_iosurface_handle.height() }; -} -#endif - -}; diff --git a/Libraries/LibWeb/Painting/BackingStore.h b/Libraries/LibWeb/Painting/BackingStore.h deleted file mode 100644 index 74d8e74faae..00000000000 --- a/Libraries/LibWeb/Painting/BackingStore.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2024, Aliaksandr Kalenik - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -#ifdef AK_OS_MACOS -# include -#endif - -namespace Web::Painting { - -class BackingStore : public AtomicRefCounted { - AK_MAKE_NONCOPYABLE(BackingStore); - -public: - virtual Gfx::IntSize size() const = 0; - virtual Gfx::Bitmap& bitmap() const = 0; - - BackingStore() { } - virtual ~BackingStore() { } -}; - -class BitmapBackingStore final : public BackingStore { -public: - static NonnullRefPtr create(RefPtr bitmap) - { - return adopt_ref(*new BitmapBackingStore(move(bitmap))); - } - - Gfx::IntSize size() const override { return m_bitmap->size(); } - Gfx::Bitmap& bitmap() const override { return *m_bitmap; } - -private: - BitmapBackingStore(RefPtr); - - RefPtr m_bitmap; -}; - -#ifdef AK_OS_MACOS -class IOSurfaceBackingStore final : public BackingStore { -public: - static NonnullRefPtr create(Core::IOSurfaceHandle&& iosurface_handle) - { - return adopt_ref(*new IOSurfaceBackingStore(move(iosurface_handle))); - } - - Gfx::IntSize size() const override; - - Core::IOSurfaceHandle& iosurface_handle() { return m_iosurface_handle; } - Gfx::Bitmap& bitmap() const override { return *m_bitmap_wrapper; } - -private: - IOSurfaceBackingStore(Core::IOSurfaceHandle&&); - - Core::IOSurfaceHandle m_iosurface_handle; - RefPtr m_bitmap_wrapper; -}; -#endif - -} diff --git a/Libraries/LibWeb/Painting/BackingStoreManager.cpp b/Libraries/LibWeb/Painting/BackingStoreManager.cpp index cbc5ba17dc6..0d294e34c2c 100644 --- a/Libraries/LibWeb/Painting/BackingStoreManager.cpp +++ b/Libraries/LibWeb/Painting/BackingStoreManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -48,8 +48,9 @@ void BackingStoreManager::restart_resize_timer() void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size) { + auto skia_backend_context = m_navigable->skia_backend_context(); #ifdef AK_OS_MACOS - if (m_navigable->is_top_level_traversable() && s_browser_mach_port.has_value()) { + if (skia_backend_context && s_browser_mach_port.has_value()) { auto back_iosurface = Core::IOSurfaceHandle::create(size.width(), size.height()); auto back_iosurface_port = back_iosurface.create_mach_port(); @@ -93,8 +94,8 @@ void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size) VERIFY_NOT_REACHED(); } - m_front_store = IOSurfaceBackingStore::create(move(front_iosurface)); - m_back_store = IOSurfaceBackingStore::create(move(back_iosurface)); + m_front_store = Gfx::PaintingSurface::create_from_iosurface(move(front_iosurface), *skia_backend_context); + m_back_store = Gfx::PaintingSurface::create_from_iosurface(move(back_iosurface), *skia_backend_context); return; } @@ -106,8 +107,23 @@ void BackingStoreManager::reallocate_backing_stores(Gfx::IntSize size) auto front_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value(); auto back_bitmap = Gfx::Bitmap::create_shareable(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value(); - m_front_store = BitmapBackingStore::create(front_bitmap); - m_back_store = BitmapBackingStore::create(back_bitmap); +#ifdef USE_VULKAN + if (skia_backend_context) { + m_front_store = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); + m_front_store->on_flush = [front_bitmap](auto& surface) { + surface.read_into_bitmap(*front_bitmap); + }; + m_back_store = Gfx::PaintingSurface::create_with_size(skia_backend_context, size, Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied); + m_back_store->on_flush = [back_bitmap](auto& surface) { + surface.read_into_bitmap(*back_bitmap); + }; + } +#endif + + if (!m_front_store) + m_front_store = Gfx::PaintingSurface::wrap_bitmap(*front_bitmap); + if (!m_back_store) + m_back_store = Gfx::PaintingSurface::wrap_bitmap(*back_bitmap); if (m_navigable->is_top_level_traversable()) { auto& page_client = m_navigable->top_level_traversable()->page().client(); diff --git a/Libraries/LibWeb/Painting/BackingStoreManager.h b/Libraries/LibWeb/Painting/BackingStoreManager.h index 9c270048fb0..ca72057371e 100644 --- a/Libraries/LibWeb/Painting/BackingStoreManager.h +++ b/Libraries/LibWeb/Painting/BackingStoreManager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Aliaksandr Kalenik + * Copyright (c) 2024-2025, Aliaksandr Kalenik * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,7 +7,6 @@ #pragma once #include -#include namespace Web::Painting { @@ -30,14 +29,14 @@ public: struct BackingStore { i32 bitmap_id { -1 }; - Web::Painting::BackingStore* store { nullptr }; + RefPtr store; }; BackingStore acquire_store_for_next_frame() { BackingStore backing_store; backing_store.bitmap_id = m_back_bitmap_id; - backing_store.store = m_back_store.ptr(); + backing_store.store = m_back_store; swap_back_and_front(); return backing_store; } @@ -53,8 +52,8 @@ private: i32 m_front_bitmap_id { -1 }; i32 m_back_bitmap_id { -1 }; - RefPtr m_front_store; - RefPtr m_back_store; + RefPtr m_front_store; + RefPtr m_back_store; int m_next_bitmap_id { 0 }; RefPtr m_backing_store_shrink_timer; diff --git a/Libraries/LibWeb/WebDriver/Screenshot.cpp b/Libraries/LibWeb/WebDriver/Screenshot.cpp index d26b44c32f1..63faad79c56 100644 --- a/Libraries/LibWeb/WebDriver/Screenshot.cpp +++ b/Libraries/LibWeb/WebDriver/Screenshot.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include namespace Web::WebDriver { @@ -56,10 +55,10 @@ ErrorOr, WebDriver::Error> draw_bounding_box_fr Gfx::IntRect paint_rect { rect.x(), rect.y(), paint_width, paint_height }; auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, canvas.surface()->size())); - auto backing_store = Painting::BitmapBackingStore::create(bitmap); + auto painting_surface = Gfx::PaintingSurface::wrap_bitmap(bitmap); IGNORE_USE_IN_ESCAPING_LAMBDA bool did_paint = false; HTML::PaintConfig paint_config { .canvas_fill_rect = paint_rect }; - browsing_context.active_document()->navigable()->start_display_list_rendering(backing_store, paint_config, [&did_paint] { + browsing_context.active_document()->navigable()->start_display_list_rendering(painting_surface, paint_config, [&did_paint] { did_paint = true; }); HTML::main_thread_event_loop().spin_until(GC::create_function(HTML::main_thread_event_loop().heap(), [&] { diff --git a/Libraries/LibWeb/WebGL/OpenGLContext.cpp b/Libraries/LibWeb/WebGL/OpenGLContext.cpp index d61745f7bb9..d354c5afa58 100644 --- a/Libraries/LibWeb/WebGL/OpenGLContext.cpp +++ b/Libraries/LibWeb/WebGL/OpenGLContext.cpp @@ -176,7 +176,6 @@ void OpenGLContext::allocate_painting_surface_if_needed() VERIFY(!m_size.is_empty()); auto iosurface = Core::IOSurfaceHandle::create(m_size.width(), m_size.height()); - m_painting_surface = Gfx::PaintingSurface::wrap_iosurface(iosurface, m_skia_backend_context, Gfx::PaintingSurface::Origin::BottomLeft); auto width = m_size.width(); auto height = m_size.height(); @@ -229,6 +228,8 @@ void OpenGLContext::allocate_painting_surface_if_needed() glBindRenderbuffer(GL_RENDERBUFFER, m_impl->depth_buffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_impl->depth_buffer); + + m_painting_surface = Gfx::PaintingSurface::create_from_iosurface(move(iosurface), m_skia_backend_context, Gfx::PaintingSurface::Origin::BottomLeft); #endif }