Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.16: [mir:wayland] Fix handling of display unplug/replug (#3433) #3534

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 17 additions & 23 deletions src/platforms/wayland/displayclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ class mgw::DisplayClient::Output :
xdg_surface* shell_surface{nullptr};
xdg_toplevel* shell_toplevel{nullptr};

EGLContext eglctx{EGL_NO_CONTEXT};
wl_egl_window* egl_window{nullptr};
EGLSurface eglsurface{EGL_NO_SURFACE};

std::optional<geometry::Size> pending_toplevel_size;
bool has_initialized{false};

Expand Down Expand Up @@ -104,6 +100,7 @@ class mgw::DisplayClient::Output :
void set_next_image(std::unique_ptr<Framebuffer> content) override;

private:
std::mutex mutex;
std::unique_ptr<WlDisplayAllocator::Framebuffer> next_frame;
std::shared_ptr<WlDisplayAllocator> provider;
};
Expand Down Expand Up @@ -159,11 +156,6 @@ mgw::DisplayClient::Output::~Output()
}

wl_surface_destroy(surface);

if (egl_window != nullptr)
{
wl_egl_window_destroy(egl_window);
}
}

void mgw::DisplayClient::Output::geometry(
Expand Down Expand Up @@ -305,20 +297,17 @@ void mgw::DisplayClient::Output::surface_configure(uint32_t serial)
dcout.custom_logical_size = pending_toplevel_size.value();
pending_toplevel_size.reset();
output_size = dcout.extents().size;
if (!has_initialized)
if (!has_initialized || size_is_changed)
{
has_initialized = true;
provider = std::make_shared<WlDisplayAllocator>(
owner_->provider->get_egl_display(),
surface,
output_size);
}
else if (size_is_changed)
{
/* TODO: We should, again, handle this by storing the pending size, raising a hardware-changed
* notification, and then letting the `configure()` system tear down everything and bring it back
* up at the new size.
*/
{
std::lock_guard lock{mutex};
provider = std::make_shared<WlDisplayAllocator>(
owner_->provider->get_egl_display(),
surface,
output_size);
}
owner_->on_display_config_changed();
}
}
}
Expand Down Expand Up @@ -383,8 +372,10 @@ void mgw::DisplayClient::Output::post()
});

// The Framebuffer ensures that this swap_buffers call doesn't block...
next_frame->swap_buffers();

{
std::lock_guard lock{mutex};
next_frame->swap_buffers();
}
// ...so we need external synchronisation to throttle rendering.
// Wait for the host compositor to tell us to render.
frame_sync->wait_for_done();
Expand Down Expand Up @@ -418,6 +409,8 @@ auto mgw::DisplayClient::Output::maybe_create_allocator(DisplayAllocator::Tag co
{
BOOST_THROW_EXCEPTION((std::runtime_error{"Attempted to create allocator before Output is fully initialised"}));
}

std::lock_guard lock{mutex};
return provider.get();
}
return nullptr;
Expand All @@ -443,6 +436,7 @@ void mgw::DisplayClient::Output::set_next_image(std::unique_ptr<Framebuffer> con
{
if (auto wl_content = unique_ptr_cast<WlDisplayAllocator::Framebuffer>(std::move(content)))
{
std::lock_guard lock{mutex};
next_frame = std::move(wl_content);
}
else
Expand Down
160 changes: 115 additions & 45 deletions src/platforms/wayland/wl_egl_display_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,133 @@ namespace mg = mir::graphics;
namespace mgw = mir::graphics::wayland;
namespace geom = mir::geometry;

namespace
{
// Utility to restore EGL state on scope exit
class CacheEglState
{
public:
CacheEglState() = default;

~CacheEglState()
{
eglMakeCurrent(dpy, draw_surf, read_surf, ctx);
}
private:
CacheEglState(CacheEglState const&) = delete;
EGLDisplay const dpy = eglGetCurrentDisplay();
EGLContext const ctx = eglGetCurrentContext();
EGLSurface const draw_surf = eglGetCurrentSurface(EGL_DRAW);
EGLSurface const read_surf = eglGetCurrentSurface(EGL_READ);
};
}

class mgw::WlDisplayAllocator::SurfaceState
{
public:
SurfaceState(EGLDisplay dpy, struct ::wl_egl_window* wl_window) :
dpy{dpy}, wl_window{wl_window} {}

~SurfaceState()
{
if (egl_surface != EGL_NO_SURFACE) eglDestroySurface(dpy, egl_surface);
wl_egl_window_destroy(wl_window);
}

auto surface(EGLConfig egl_config) -> EGLSurface
{
std::lock_guard lock{mutex};
if (egl_surface == EGL_NO_SURFACE)
{
egl_surface = eglCreatePlatformWindowSurface(dpy, egl_config, wl_window, nullptr);
}

if (egl_surface == EGL_NO_SURFACE)
{
BOOST_THROW_EXCEPTION(egl_error("Failed to create EGL surface"));
}

return egl_surface;
}

private:
EGLDisplay const dpy;
struct ::wl_egl_window* const wl_window;

std::mutex mutex;
EGLSurface egl_surface{EGL_NO_SURFACE};
};

class mgw::WlDisplayAllocator::Framebuffer::EGLState
{
public:
EGLState(EGLDisplay dpy, EGLContext ctx, EGLSurface surf)
EGLState(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, std::shared_ptr<SurfaceState> ss)
: dpy{dpy},
ctx{ctx},
surf{surf}
surf{surf},
ss{std::move(ss)}
{
CacheEglState stash;

make_current();
// Don't block in eglSwapBuffers; we rely on external synchronisation to throttle rendering
eglSwapInterval(dpy, 0);
release_current();
}

~EGLState()
{
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (ctx == eglGetCurrentContext())
{
eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
eglDestroyContext(dpy, ctx);
eglDestroySurface(dpy, surf);
}


void make_current() const
{
if (eglMakeCurrent(dpy, surf, surf, ctx) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION((mg::egl_error("eglMakeCurrent failed")));
}
}

void release_current() const
{
if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release EGL context"));
}
}

void swap_buffers() const
{
if (eglSwapBuffers(dpy, surf) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION((mg::egl_error("eglSwapBuffers failed")));
}
}

EGLDisplay const dpy;
EGLContext const ctx;
EGLSurface const surf;
std::shared_ptr<SurfaceState> const ss;
};

mgw::WlDisplayAllocator::Framebuffer::Framebuffer(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, geom::Size size)
: Framebuffer(std::make_shared<EGLState>(dpy, ctx, surf), size)
mgw::WlDisplayAllocator::Framebuffer::Framebuffer(
EGLDisplay dpy, EGLContext ctx, EGLSurface surf, std::shared_ptr<SurfaceState> ss, mir::geometry::Size size) :
Framebuffer{std::make_shared<EGLState>(dpy, ctx, surf, ss), size}
{
auto current_ctx = eglGetCurrentContext();
auto current_draw_surf = eglGetCurrentSurface(EGL_DRAW);
auto current_read_surf = eglGetCurrentSurface(EGL_READ);

make_current();
// Don't block in eglSwapBuffers; we rely on external synchronisation to throttle rendering
eglSwapInterval(dpy, 0);
release_current();

// Paranoia: Restore the previous EGL context state, just in case
eglMakeCurrent(dpy, current_draw_surf, current_read_surf, current_ctx);
}

mgw::WlDisplayAllocator::Framebuffer::Framebuffer(std::shared_ptr<EGLState const> state, geom::Size size)
: state{std::move(state)},
size_{size}
: state{std::move(state)}, size_{size}
{
}

mgw::WlDisplayAllocator::Framebuffer::Framebuffer(Framebuffer const& that)
: state{that.state},
size_{that.size_}
{
}

Expand All @@ -63,46 +149,36 @@ auto mgw::WlDisplayAllocator::Framebuffer::size() const -> geom::Size

void mgw::WlDisplayAllocator::Framebuffer::make_current()
{
if (eglMakeCurrent(state->dpy, state->surf, state->surf, state->ctx) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION((mg::egl_error("eglMakeCurrent failed")));
}
state->make_current();
}

void mgw::WlDisplayAllocator::Framebuffer::release_current()
{
if (eglMakeCurrent(state->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release EGL context"));
}
state->release_current();
}

void mgw::WlDisplayAllocator::Framebuffer::swap_buffers()
{
if (eglSwapBuffers(state->dpy, state->surf) != EGL_TRUE)
{
BOOST_THROW_EXCEPTION((mg::egl_error("eglSwapBuffers failed")));
}
state->swap_buffers();
}

auto mgw::WlDisplayAllocator::Framebuffer::clone_handle() -> std::unique_ptr<mg::GenericEGLDisplayAllocator::EGLFramebuffer>
{
return std::unique_ptr<mg::GenericEGLDisplayAllocator::EGLFramebuffer>{new Framebuffer(state, size_)};
return std::unique_ptr<mg::GenericEGLDisplayAllocator::EGLFramebuffer>{new Framebuffer(*this)};
}

mgw::WlDisplayAllocator::WlDisplayAllocator(
EGLDisplay dpy,
struct wl_surface* surface,
geometry::Size size)
: dpy{dpy},
wl_window{wl_egl_window_create(surface, size.width.as_int(), size.height.as_int())},
surface_state{std::make_shared<SurfaceState>(dpy, wl_egl_window_create(surface, size.width.as_int(), size.height.as_int()))},
size{size}
{
}

mgw::WlDisplayAllocator::~WlDisplayAllocator()
{
wl_egl_window_destroy(wl_window);
}

auto mgw::WlDisplayAllocator::alloc_framebuffer(
Expand Down Expand Up @@ -138,19 +214,13 @@ auto mgw::WlDisplayAllocator::alloc_framebuffer(

auto egl_context = eglCreateContext(dpy, egl_config, share_context, context_attr);
if (egl_context == EGL_NO_CONTEXT)
BOOST_THROW_EXCEPTION(egl_error("Failed to create EGL context"));

auto surf = eglCreatePlatformWindowSurface(dpy, egl_config, wl_window, nullptr);
if (surf == EGL_NO_SURFACE)
{
BOOST_THROW_EXCEPTION(egl_error("Failed to create EGL surface"));
BOOST_THROW_EXCEPTION(egl_error("Failed to create EGL context"));
}

return std::make_unique<Framebuffer>(
dpy,
egl_context,
surf,
size);
auto surface = surface_state->surface(egl_config);

return std::make_unique<Framebuffer>(dpy, egl_context, surface, surface_state, size);
}

mgw::WlDisplayProvider::WlDisplayProvider(EGLDisplay dpy)
Expand Down
13 changes: 5 additions & 8 deletions src/platforms/wayland/wl_egl_display_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ class WlDisplayProvider : public GenericEGLDisplayProvider
public:
WlDisplayProvider(EGLDisplay dpy);

WlDisplayProvider(
WlDisplayProvider const& from,
struct wl_surface* surface,
geometry::Size size);

auto get_egl_display() -> EGLDisplay override;
private:
EGLDisplay const dpy;
Expand All @@ -50,6 +45,7 @@ class WlDisplayAllocator : public GenericEGLDisplayAllocator
auto alloc_framebuffer(GLConfig const& config, EGLContext share_context)
-> std::unique_ptr<EGLFramebuffer> override;

struct SurfaceState;
class Framebuffer : public GenericEGLDisplayAllocator::EGLFramebuffer
{
public:
Expand All @@ -60,7 +56,7 @@ class WlDisplayAllocator : public GenericEGLDisplayAllocator
* final handle generated from this Framebuffer is released,
* the EGL resources \param ctx and \param surff will be freed.
*/
Framebuffer(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, geometry::Size size);
Framebuffer(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, std::shared_ptr<SurfaceState> ss, geometry::Size size);

auto size() const -> geometry::Size override;

Expand All @@ -71,14 +67,15 @@ class WlDisplayAllocator : public GenericEGLDisplayAllocator
void swap_buffers();
private:
class EGLState;
Framebuffer(std::shared_ptr<EGLState const> surf, geometry::Size size);
Framebuffer(std::shared_ptr<EGLState const> state, geometry::Size size);
Framebuffer(Framebuffer const& that);

std::shared_ptr<EGLState const> const state;
geometry::Size const size_;
};
private:
EGLDisplay const dpy;
struct ::wl_egl_window* const wl_window;
std::shared_ptr<SurfaceState> const surface_state;
geometry::Size const size;
};
}
Expand Down
Loading