LibGfx+LibWeb+Compositor: Remove sync wait on GPU flush
The compositor present path submitted Skia work with a synchronous GPU wait, so each present_frame call stayed blocked until the backend finished the submitted work. That serialized compositor processing behind GPU completion even though the presented-bitmap acknowledgement already controls backing-store reuse. Add an async Skia flush-and-submit entrypoint and have the compositor track pending presents until the Skia finished callback runs back on the compositor event loop. did_present_frame now fires from that completion path, while the existing presented_bitmap_ready_to_paint acknowledgement remains the reuse gate for client-presented bitmaps. Ganesh does not reliably run the finished proc without explicit async work polling on this backend, so pending presents keep a completion timer alive to call checkAsyncWorkCompletion().
This commit is contained in:
parent
d6b6efa27a
commit
8950da276d
7 changed files with 213 additions and 27 deletions
|
|
@ -39,10 +39,47 @@ static constexpr auto skia_resource_cache_critical_watermark = 512 * MiB;
|
|||
|
||||
static RefPtr<SkiaBackendContext> s_main_thread_context;
|
||||
|
||||
#if defined(AK_OS_MACOS) || USE_VULKAN
|
||||
static void invoke_async_flush_callback(void* context)
|
||||
{
|
||||
auto* callback = static_cast<Function<void()>*>(context);
|
||||
auto callback_to_invoke = move(*callback);
|
||||
delete callback;
|
||||
callback_to_invoke();
|
||||
}
|
||||
|
||||
static void flush_and_submit_async_to_context(GrDirectContext& context, SkSurface* surface, Function<void()>&& callback)
|
||||
{
|
||||
GrFlushInfo flush_info {};
|
||||
flush_info.fFinishedProc = invoke_async_flush_callback;
|
||||
flush_info.fFinishedContext = new Function<void()>(move(callback));
|
||||
context.flush(surface, SkSurfaces::BackendSurfaceAccess::kPresent, flush_info);
|
||||
VERIFY(context.submit(GrSyncCpu::kNo));
|
||||
}
|
||||
#endif
|
||||
|
||||
void SkiaBackendContext::check_async_work_completion()
|
||||
{
|
||||
if (auto* context = sk_context())
|
||||
context->checkAsyncWorkCompletion();
|
||||
}
|
||||
|
||||
void SkiaBackendContext::flush_and_submit(SkSurface* surface)
|
||||
{
|
||||
flush_and_submit_impl(surface);
|
||||
|
||||
perform_post_flush_cleanup();
|
||||
}
|
||||
|
||||
void SkiaBackendContext::flush_and_submit_async(SkSurface* surface, Function<void()>&& callback)
|
||||
{
|
||||
flush_and_submit_async_impl(surface, move(callback));
|
||||
|
||||
perform_post_flush_cleanup();
|
||||
}
|
||||
|
||||
void SkiaBackendContext::perform_post_flush_cleanup()
|
||||
{
|
||||
auto* context = sk_context();
|
||||
if (!context)
|
||||
return;
|
||||
|
|
@ -136,6 +173,11 @@ public:
|
|||
m_context->submit(GrSyncCpu::kYes);
|
||||
}
|
||||
|
||||
void flush_and_submit_async_impl(SkSurface* surface, Function<void()>&& callback) override
|
||||
{
|
||||
flush_and_submit_async_to_context(*m_context, surface, move(callback));
|
||||
}
|
||||
|
||||
skgpu::VulkanExtensions const* extensions() const { return m_extensions.ptr(); }
|
||||
|
||||
GrDirectContext* sk_context() const override { return m_context.get(); }
|
||||
|
|
@ -201,6 +243,11 @@ public:
|
|||
m_context->submit(GrSyncCpu::kYes);
|
||||
}
|
||||
|
||||
void flush_and_submit_async_impl(SkSurface* surface, Function<void()>&& callback) override
|
||||
{
|
||||
flush_and_submit_async_to_context(*m_context, surface, move(callback));
|
||||
}
|
||||
|
||||
GrDirectContext* sk_context() const override { return m_context.get(); }
|
||||
|
||||
VulkanContext const& vulkan_context() override { VERIFY_NOT_REACHED(); }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AtomicRefCounted.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
|
||||
#ifdef USE_VULKAN
|
||||
|
|
@ -46,6 +47,8 @@ public:
|
|||
virtual ~SkiaBackendContext() { }
|
||||
|
||||
void flush_and_submit(SkSurface*);
|
||||
void flush_and_submit_async(SkSurface*, Function<void()>&&);
|
||||
void check_async_work_completion();
|
||||
virtual GrDirectContext* sk_context() const = 0;
|
||||
|
||||
virtual MetalContext& metal_context() = 0;
|
||||
|
|
@ -53,6 +56,10 @@ public:
|
|||
|
||||
protected:
|
||||
virtual void flush_and_submit_impl(SkSurface*) = 0;
|
||||
virtual void flush_and_submit_async_impl(SkSurface*, Function<void()>&&) = 0;
|
||||
|
||||
private:
|
||||
void perform_post_flush_cleanup();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,16 @@ void DisplayListPlayerSkia::flush(Gfx::PaintingSurface& surface)
|
|||
m_image_cache.prune();
|
||||
}
|
||||
|
||||
void DisplayListPlayerSkia::flush_async(Gfx::PaintingSurface& surface, Function<void()>&& callback)
|
||||
{
|
||||
if (auto context = surface.skia_backend_context())
|
||||
context->flush_and_submit_async(&surface.sk_surface(), move(callback));
|
||||
else
|
||||
callback();
|
||||
surface.flush();
|
||||
m_image_cache.prune();
|
||||
}
|
||||
|
||||
void DisplayListPlayerSkia::draw_glyph_run(DrawGlyphRun const& command)
|
||||
{
|
||||
auto const& font = resource_storage().font(command.font_id);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibGfx/DecodedImageFrameSkiaImageCache.h>
|
||||
#include <LibWeb/Painting/DisplayList.h>
|
||||
#include <LibWeb/Painting/DisplayListCommand.h>
|
||||
|
|
@ -23,6 +24,7 @@ public:
|
|||
~DisplayListPlayerSkia();
|
||||
|
||||
void flush(Gfx::PaintingSurface&) override;
|
||||
void flush_async(Gfx::PaintingSurface&, Function<void()>&&);
|
||||
|
||||
private:
|
||||
void draw_glyph_run(DrawGlyphRun const&) override;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ target_include_directories(compositorservice PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
|
|||
target_include_directories(compositorservice PRIVATE ${LADYBIRD_SOURCE_DIR}/Services/)
|
||||
|
||||
target_link_libraries(Compositor PRIVATE compositorservice LibCore LibMain LibWebView)
|
||||
target_link_libraries(compositorservice PRIVATE LibCore LibGfx LibIPC LibMedia LibWeb)
|
||||
target_link_libraries(compositorservice PRIVATE LibCore LibGfx LibIPC LibMedia LibSync LibWeb)
|
||||
|
||||
if (WIN32)
|
||||
target_include_directories(Compositor PRIVATE $<BUILD_INTERFACE:${PTHREAD_INCLUDE_DIR}>)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <Compositor/CompositorState.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Color.h>
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
|
||||
namespace Compositor {
|
||||
|
||||
static constexpr int gpu_completion_check_interval_ms = 1;
|
||||
|
||||
static void set_or_append_pending_scroll_offset(Vector<Web::Compositor::AsyncScrollOffset>& pending_scroll_offsets, Web::Compositor::AsyncScrollOffset const& scroll_offset)
|
||||
{
|
||||
for (auto& existing : pending_scroll_offsets) {
|
||||
|
|
@ -151,6 +154,14 @@ CompositorState::CompositorState(RefPtr<Gfx::SkiaBackendContext> skia_backend_co
|
|||
{
|
||||
}
|
||||
|
||||
CompositorState::~CompositorState()
|
||||
{
|
||||
if (!m_gpu_completion_timer)
|
||||
return;
|
||||
m_gpu_completion_timer->on_timeout = {};
|
||||
m_gpu_completion_timer->stop();
|
||||
}
|
||||
|
||||
CompositorState::ContextState::~ContextState()
|
||||
{
|
||||
stop_backing_store_shrink_timer();
|
||||
|
|
@ -220,6 +231,7 @@ void CompositorState::destroy_context(Web::Compositor::CompositorContextId conte
|
|||
auto* context = context_if_present(context_id);
|
||||
VERIFY(context);
|
||||
|
||||
cancel_pending_async_presents_for_context(context_id);
|
||||
detach_from_parent_surface(context_id, *context);
|
||||
for (auto& child_context_entry : context->child_contexts_by_surface_id) {
|
||||
auto* child_context = context_if_present(child_context_entry.value);
|
||||
|
|
@ -258,6 +270,9 @@ void CompositorState::stop_presenting_to_client(Web::Compositor::CompositorConte
|
|||
{
|
||||
auto* context = context_if_present(context_id);
|
||||
VERIFY(context);
|
||||
if (context->gpu_present_bitmap_id_awaiting_completion.has_value()
|
||||
&& context->presented_bitmap_id_awaiting_ack == context->gpu_present_bitmap_id_awaiting_completion)
|
||||
context->presented_bitmap_id_awaiting_ack.clear();
|
||||
context->presents_to_client = false;
|
||||
}
|
||||
|
||||
|
|
@ -506,6 +521,7 @@ bool CompositorState::should_defer_main_thread_present_for_async_scroll(Web::Com
|
|||
|
||||
return context->has_deferred_async_scroll_present
|
||||
|| context->pending_present_frame.has_value()
|
||||
|| context->gpu_present_bitmap_id_awaiting_completion.has_value()
|
||||
|| context->presented_bitmap_id_awaiting_ack.has_value();
|
||||
}
|
||||
|
||||
|
|
@ -543,7 +559,7 @@ void CompositorState::present_frame(Web::Compositor::CompositorContextId context
|
|||
|
||||
void CompositorState::present_frame(Web::Compositor::CompositorContextId context_id, ContextState& context, Gfx::IntRect viewport_rect)
|
||||
{
|
||||
if (context.presented_bitmap_id_awaiting_ack.has_value()) {
|
||||
if (context.gpu_present_bitmap_id_awaiting_completion.has_value() || context.presented_bitmap_id_awaiting_ack.has_value()) {
|
||||
context.pending_present_frame = viewport_rect;
|
||||
return;
|
||||
}
|
||||
|
|
@ -562,20 +578,37 @@ void CompositorState::present_frame(Web::Compositor::CompositorContextId context
|
|||
});
|
||||
m_display_list_player->execute(*context.display_list, context.display_list_resource_storage, context.scroll_state_snapshot, back_store);
|
||||
paint_viewport_scrollbar_overlay(context, back_store);
|
||||
m_display_list_player->flush(back_store);
|
||||
auto rendered_bitmap_id = context.backing_store_manager.back_bitmap_id();
|
||||
context.backing_store_manager.swap();
|
||||
context.gpu_present_bitmap_id_awaiting_completion = rendered_bitmap_id;
|
||||
if (context.presents_to_client)
|
||||
context.presented_bitmap_id_awaiting_ack = rendered_bitmap_id;
|
||||
m_pending_async_presents.append(context_id, viewport_rect, rendered_bitmap_id);
|
||||
auto* pending_present = &m_pending_async_presents.last();
|
||||
|
||||
context.presentation_mode.visit(
|
||||
[&](Empty const&) {
|
||||
if (present_frame_to_client(context_id, context, viewport_rect, rendered_bitmap_id))
|
||||
context.presented_bitmap_id_awaiting_ack = rendered_bitmap_id;
|
||||
context.presented_frame = viewport_rect;
|
||||
},
|
||||
[&](Web::Compositor::PublishToCompositorSurface const& mode) {
|
||||
publish_to_parent_surface(context, mode);
|
||||
context.presented_frame = viewport_rect;
|
||||
auto event_loop_reference = Core::EventLoop::current_weak();
|
||||
auto self = NonnullRefPtr { *this };
|
||||
m_display_list_player->flush_async(back_store, [self = move(self), event_loop_reference = move(event_loop_reference), pending_present] {
|
||||
auto event_loop = event_loop_reference->take();
|
||||
if (!event_loop.is_alive())
|
||||
return;
|
||||
event_loop->deferred_invoke([self = move(self), pending_present] {
|
||||
self->did_finish_async_present(*pending_present);
|
||||
});
|
||||
});
|
||||
context.backing_store_manager.swap();
|
||||
context.presented_frame = viewport_rect;
|
||||
schedule_gpu_completion_check();
|
||||
}
|
||||
|
||||
void CompositorState::drain_pending_present_frame_if_unblocked(Web::Compositor::CompositorContextId context_id, ContextState& context)
|
||||
{
|
||||
if (context.gpu_present_bitmap_id_awaiting_completion.has_value() || context.presented_bitmap_id_awaiting_ack.has_value())
|
||||
return;
|
||||
if (!context.pending_present_frame.has_value())
|
||||
return;
|
||||
|
||||
auto pending_present_frame = context.pending_present_frame.release_value();
|
||||
present_frame(context_id, context, pending_present_frame);
|
||||
}
|
||||
|
||||
bool CompositorState::request_screenshot(Web::Compositor::CompositorContextId context_id, Gfx::ShareableBitmap& target_bitmap)
|
||||
|
|
@ -603,10 +636,84 @@ void CompositorState::presented_bitmap_ready_to_paint(Web::Compositor::Composito
|
|||
return;
|
||||
|
||||
context->presented_bitmap_id_awaiting_ack.clear();
|
||||
if (context->pending_present_frame.has_value()) {
|
||||
auto pending_present_frame = context->pending_present_frame.release_value();
|
||||
present_frame(context_id, *context, pending_present_frame);
|
||||
drain_pending_present_frame_if_unblocked(context_id, *context);
|
||||
}
|
||||
|
||||
void CompositorState::did_finish_async_present(PendingAsyncPresent& pending_present)
|
||||
{
|
||||
auto pending_present_iterator = m_pending_async_presents.begin();
|
||||
for (; pending_present_iterator != m_pending_async_presents.end(); ++pending_present_iterator) {
|
||||
if (&*pending_present_iterator == &pending_present)
|
||||
break;
|
||||
}
|
||||
VERIFY(pending_present_iterator != m_pending_async_presents.end());
|
||||
|
||||
auto context_id = pending_present.context_id;
|
||||
auto viewport_rect = pending_present.viewport_rect;
|
||||
auto bitmap_id = pending_present.bitmap_id;
|
||||
auto was_cancelled = pending_present.was_cancelled;
|
||||
(void)m_pending_async_presents.remove(pending_present_iterator);
|
||||
if (m_pending_async_presents.is_empty() && m_gpu_completion_timer)
|
||||
m_gpu_completion_timer->stop();
|
||||
|
||||
if (was_cancelled)
|
||||
return;
|
||||
|
||||
auto* context = context_if_present(context_id);
|
||||
VERIFY(context);
|
||||
VERIFY(context->gpu_present_bitmap_id_awaiting_completion == bitmap_id);
|
||||
|
||||
context->gpu_present_bitmap_id_awaiting_completion.clear();
|
||||
if (context->presents_to_client) {
|
||||
VERIFY(m_client);
|
||||
VERIFY(context->presented_bitmap_id_awaiting_ack == bitmap_id);
|
||||
m_client->did_present_frame(context_id, viewport_rect, bitmap_id);
|
||||
} else {
|
||||
context->presentation_mode.visit(
|
||||
[](Empty const&) {},
|
||||
[&](Web::Compositor::PublishToCompositorSurface const& mode) {
|
||||
publish_to_parent_surface(*context, mode);
|
||||
});
|
||||
}
|
||||
|
||||
drain_pending_present_frame_if_unblocked(context_id, *context);
|
||||
}
|
||||
|
||||
void CompositorState::cancel_pending_async_presents_for_context(Web::Compositor::CompositorContextId context_id)
|
||||
{
|
||||
for (auto& pending_present : m_pending_async_presents) {
|
||||
if (pending_present.context_id == context_id)
|
||||
pending_present.was_cancelled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorState::schedule_gpu_completion_check()
|
||||
{
|
||||
if (!m_skia_backend_context || m_pending_async_presents.is_empty())
|
||||
return;
|
||||
|
||||
if (!m_gpu_completion_timer) {
|
||||
m_gpu_completion_timer = Core::Timer::create_repeating(gpu_completion_check_interval_ms, [this] {
|
||||
check_gpu_completions();
|
||||
});
|
||||
}
|
||||
if (!m_gpu_completion_timer->is_active())
|
||||
m_gpu_completion_timer->start();
|
||||
}
|
||||
|
||||
void CompositorState::check_gpu_completions()
|
||||
{
|
||||
if (m_pending_async_presents.is_empty()) {
|
||||
if (m_gpu_completion_timer)
|
||||
m_gpu_completion_timer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_skia_backend_context)
|
||||
m_skia_backend_context->check_async_work_completion();
|
||||
|
||||
if (m_pending_async_presents.is_empty() && m_gpu_completion_timer)
|
||||
m_gpu_completion_timer->stop();
|
||||
}
|
||||
|
||||
CompositorState::ContextState* CompositorState::context_if_present(Web::Compositor::CompositorContextId context_id)
|
||||
|
|
@ -955,14 +1062,4 @@ void CompositorState::publish_backing_stores(Web::Compositor::CompositorContextI
|
|||
m_client->did_allocate_backing_stores(context_id, publication.front_bitmap_id, move(publication.front_shared_image), publication.back_bitmap_id, move(publication.back_shared_image));
|
||||
}
|
||||
|
||||
bool CompositorState::present_frame_to_client(Web::Compositor::CompositorContextId context_id, ContextState& context, Gfx::IntRect const& viewport_rect, i32 bitmap_id)
|
||||
{
|
||||
VERIFY(m_client);
|
||||
if (!context.presents_to_client)
|
||||
return false;
|
||||
|
||||
m_client->did_present_frame(context_id, viewport_rect, bitmap_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/DoublyLinkedList.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/NonnullRefPtr.h>
|
||||
#include <AK/Optional.h>
|
||||
|
|
@ -58,6 +59,7 @@ public:
|
|||
class CompositorState final : public RefCounted<CompositorState> {
|
||||
public:
|
||||
static NonnullRefPtr<CompositorState> create(RefPtr<Gfx::SkiaBackendContext>, bool async_scrolling_enabled);
|
||||
~CompositorState();
|
||||
|
||||
enum class ContextOwnerCheckResult {
|
||||
OwnedByClient,
|
||||
|
|
@ -147,6 +149,7 @@ private:
|
|||
|
||||
Optional<Gfx::IntRect> pending_present_frame;
|
||||
Optional<Gfx::IntRect> presented_frame;
|
||||
Optional<i32> gpu_present_bitmap_id_awaiting_completion;
|
||||
Optional<i32> presented_bitmap_id_awaiting_ack;
|
||||
bool has_deferred_async_scroll_present { false };
|
||||
Gfx::IntRect deferred_async_scroll_present_viewport_rect;
|
||||
|
|
@ -154,6 +157,20 @@ private:
|
|||
void stop_backing_store_shrink_timer();
|
||||
};
|
||||
|
||||
struct PendingAsyncPresent {
|
||||
PendingAsyncPresent(Web::Compositor::CompositorContextId context_id, Gfx::IntRect viewport_rect, i32 bitmap_id)
|
||||
: context_id(context_id)
|
||||
, viewport_rect(viewport_rect)
|
||||
, bitmap_id(bitmap_id)
|
||||
{
|
||||
}
|
||||
|
||||
Web::Compositor::CompositorContextId context_id;
|
||||
Gfx::IntRect viewport_rect;
|
||||
i32 bitmap_id { 0 };
|
||||
bool was_cancelled { false };
|
||||
};
|
||||
|
||||
ContextState* context_if_present(Web::Compositor::CompositorContextId);
|
||||
ContextState const* context_if_present(Web::Compositor::CompositorContextId) const;
|
||||
void detach_from_parent_surface(Web::Compositor::CompositorContextId, ContextState&);
|
||||
|
|
@ -177,11 +194,17 @@ private:
|
|||
void publish_to_parent_surface(ContextState&, Web::Compositor::PublishToCompositorSurface const&);
|
||||
void present_frame(Web::Compositor::CompositorContextId, ContextState&, Gfx::IntRect);
|
||||
void publish_backing_stores(Web::Compositor::CompositorContextId, ContextState&, BackingStoreManager::Publication&&);
|
||||
bool present_frame_to_client(Web::Compositor::CompositorContextId, ContextState&, Gfx::IntRect const&, i32 bitmap_id);
|
||||
void did_finish_async_present(PendingAsyncPresent&);
|
||||
void drain_pending_present_frame_if_unblocked(Web::Compositor::CompositorContextId, ContextState&);
|
||||
void cancel_pending_async_presents_for_context(Web::Compositor::CompositorContextId);
|
||||
void schedule_gpu_completion_check();
|
||||
void check_gpu_completions();
|
||||
|
||||
HashMap<Web::Compositor::CompositorContextId, OwnPtr<ContextState>> m_contexts;
|
||||
DoublyLinkedList<PendingAsyncPresent> m_pending_async_presents;
|
||||
RefPtr<Gfx::SkiaBackendContext> m_skia_backend_context;
|
||||
OwnPtr<Web::Painting::DisplayListPlayerSkia> m_display_list_player;
|
||||
RefPtr<Core::Timer> m_gpu_completion_timer;
|
||||
CompositorStateClient* m_client { nullptr };
|
||||
bool m_async_scrolling_enabled { true };
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue