From 4cbf35c95b82cef18b774e44ea0c910b05372106 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 15 Jan 2026 16:32:35 +1100 Subject: [PATCH 1/8] GLRenderingProvider: Add a `debug()` method to describe --- include/platform/mir/graphics/platform.h | 2 ++ src/platforms/eglstream-kms/server/buffer_allocator.cpp | 8 ++++++++ src/platforms/eglstream-kms/server/buffer_allocator.h | 2 ++ src/platforms/gbm-kms/server/buffer_allocator.cpp | 8 ++++++++ src/platforms/gbm-kms/server/buffer_allocator.h | 2 ++ src/platforms/renderer-generic-egl/buffer_allocator.cpp | 7 +++++++ src/platforms/renderer-generic-egl/buffer_allocator.h | 2 ++ .../include/mir/test/doubles/mock_gl_rendering_provider.h | 1 + .../include/mir/test/doubles/stub_gl_rendering_provider.h | 5 +++++ 9 files changed, 37 insertions(+) diff --git a/include/platform/mir/graphics/platform.h b/include/platform/mir/graphics/platform.h index 2b1b6b8a383..749f1ea2368 100644 --- a/include/platform/mir/graphics/platform.h +++ b/include/platform/mir/graphics/platform.h @@ -152,6 +152,8 @@ class RenderingProvider -> std::unique_ptr = 0; }; + virtual auto debug() const -> std::string = 0; + /** * Check how well this Renderer can support a particular display sink */ diff --git a/src/platforms/eglstream-kms/server/buffer_allocator.cpp b/src/platforms/eglstream-kms/server/buffer_allocator.cpp index 475190984d5..687d524998b 100644 --- a/src/platforms/eglstream-kms/server/buffer_allocator.cpp +++ b/src/platforms/eglstream-kms/server/buffer_allocator.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include "buffer_allocator.h" @@ -828,6 +829,13 @@ mge::GLRenderingProvider::GLRenderingProvider( mge::GLRenderingProvider::~GLRenderingProvider() = default; +auto mge::GLRenderingProvider::debug() const -> std::string +{ + return std::format("EGLStream GLRenderingProvider: {} {}", + eglQueryString(dpy, EGL_VENDOR), + eglQueryString(dpy, EGL_VERSION)); +} + auto mir::graphics::eglstream::GLRenderingProvider::as_texture(std::shared_ptr buffer) -> std::shared_ptr { diff --git a/src/platforms/eglstream-kms/server/buffer_allocator.h b/src/platforms/eglstream-kms/server/buffer_allocator.h index 58632990cc9..ead59c9760f 100644 --- a/src/platforms/eglstream-kms/server/buffer_allocator.h +++ b/src/platforms/eglstream-kms/server/buffer_allocator.h @@ -98,6 +98,8 @@ class GLRenderingProvider : public graphics::GLRenderingProvider GLRenderingProvider(EGLDisplay dpy, std::unique_ptr ctx); ~GLRenderingProvider(); + auto debug() const -> std::string override; + auto as_texture(std::shared_ptr buffer) -> std::shared_ptr override; auto suitability_for_allocator(std::shared_ptr const& target) -> probe::Result override; diff --git a/src/platforms/gbm-kms/server/buffer_allocator.cpp b/src/platforms/gbm-kms/server/buffer_allocator.cpp index abea0cb5cd9..fc4dbfca861 100644 --- a/src/platforms/gbm-kms/server/buffer_allocator.cpp +++ b/src/platforms/gbm-kms/server/buffer_allocator.cpp @@ -563,6 +563,14 @@ auto mgg::GLRenderingProvider::import_syncobj(Fd const& syncobj_fd) return std::make_unique(drm_fd, handle); } +auto mgg::GLRenderingProvider::debug() const -> std::string +{ + return std::format("GBM GLRenderingProvider: {} {} ({})", + eglQueryString(dpy, EGL_VENDOR), + eglQueryString(dpy, EGL_VERSION), + drmGetPrimaryDeviceNameFromFd(drm_fd)); +} + auto mgg::GLRenderingProvider::make_framebuffer_provider(DisplaySink& sink) -> std::unique_ptr { diff --git a/src/platforms/gbm-kms/server/buffer_allocator.h b/src/platforms/gbm-kms/server/buffer_allocator.h index baf5ac8b1c2..8db495f604d 100644 --- a/src/platforms/gbm-kms/server/buffer_allocator.h +++ b/src/platforms/gbm-kms/server/buffer_allocator.h @@ -99,6 +99,8 @@ class GLRenderingProvider : public graphics::DRMRenderingProvider EGLContext ctx, std::shared_ptr const& quirks); + auto debug() const -> std::string override; + auto make_framebuffer_provider(DisplaySink& sink) -> std::unique_ptr override; diff --git a/src/platforms/renderer-generic-egl/buffer_allocator.cpp b/src/platforms/renderer-generic-egl/buffer_allocator.cpp index 770a291bdb0..3052e1ca2f1 100644 --- a/src/platforms/renderer-generic-egl/buffer_allocator.cpp +++ b/src/platforms/renderer-generic-egl/buffer_allocator.cpp @@ -426,6 +426,13 @@ auto mge::GLRenderingProvider::surface_for_sink( config); } +auto mge::GLRenderingProvider::debug() const -> std::string +{ + return std::format("Generic EGL/GLRenderingProvider: {} {}", + eglQueryString(dpy, EGL_VENDOR), + eglQueryString(dpy, EGL_VERSION)); +} + auto mge::GLRenderingProvider::make_framebuffer_provider(DisplaySink& /*sink*/) -> std::unique_ptr { diff --git a/src/platforms/renderer-generic-egl/buffer_allocator.h b/src/platforms/renderer-generic-egl/buffer_allocator.h index d36ca55db32..71cd51a3710 100644 --- a/src/platforms/renderer-generic-egl/buffer_allocator.h +++ b/src/platforms/renderer-generic-egl/buffer_allocator.h @@ -91,6 +91,8 @@ class GLRenderingProvider : public graphics::GLRenderingProvider std::shared_ptr dmabuf_provider, std::shared_ptr egl_delegate); + auto debug() const -> std::string override; + auto make_framebuffer_provider(DisplaySink& sink) -> std::unique_ptr override; diff --git a/tests/include/mir/test/doubles/mock_gl_rendering_provider.h b/tests/include/mir/test/doubles/mock_gl_rendering_provider.h index af25bb6e624..d8ce77d44d9 100644 --- a/tests/include/mir/test/doubles/mock_gl_rendering_provider.h +++ b/tests/include/mir/test/doubles/mock_gl_rendering_provider.h @@ -28,6 +28,7 @@ namespace mir::test::doubles class MockGlRenderingProvider : public graphics::GLRenderingProvider { public: + MOCK_METHOD(std::string, debug, (), (const override)); MOCK_METHOD(std::shared_ptr, as_texture, (std::shared_ptr), (override)); MOCK_METHOD( std::unique_ptr, diff --git a/tests/include/mir/test/doubles/stub_gl_rendering_provider.h b/tests/include/mir/test/doubles/stub_gl_rendering_provider.h index 96c7073d810..1aa22662f56 100644 --- a/tests/include/mir/test/doubles/stub_gl_rendering_provider.h +++ b/tests/include/mir/test/doubles/stub_gl_rendering_provider.h @@ -58,6 +58,11 @@ class StubGlRenderingProvider : public graphics::GLRenderingProvider return std::make_unique>(); } + auto debug() const -> std::string override + { + return "StubGLRenderingProvider"; + } + auto suitability_for_allocator(std::shared_ptr const& /*sink*/) -> graphics::probe::Result override { From 49e20c485840d179a5f3e7f3235ede27c6d5ff45 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 15 Jan 2026 16:32:35 +1100 Subject: [PATCH 2/8] BasicScreenShooter: Log selection of GLRenderingProvider --- src/server/compositor/basic_screen_shooter.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/server/compositor/basic_screen_shooter.cpp b/src/server/compositor/basic_screen_shooter.cpp index 97b86ad3056..a076fbaf9a0 100644 --- a/src/server/compositor/basic_screen_shooter.cpp +++ b/src/server/compositor/basic_screen_shooter.cpp @@ -252,10 +252,15 @@ auto mc::BasicScreenShooter::select_provider( * For now, just use the first that claims to work. */ auto suitability = provider->suitability_for_display(temp_db); + mir::log_debug( + "Querying GLRenderingProvider: %s. Suitability for display %i", + provider->debug().c_str(), + suitability); // We also need to make sure that the GLRenderingProvider can access client buffers... if (provider->suitability_for_allocator(buffer_allocator) > mg::probe::unsupported && suitability > best_provider.first) { + mir::log_debug("...Also suitable for allocator"); best_provider = std::make_pair(suitability, provider); } } @@ -264,6 +269,8 @@ auto mc::BasicScreenShooter::select_provider( BOOST_THROW_EXCEPTION((std::runtime_error{"No rendering provider claims to support a CPU addressable target"})); } + mir::log_debug("Selected provider: %s", best_provider.second->debug().c_str()); + return best_provider.second; } From 66d68f38497695d72dd8560d2856b1f9058c7bb9 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 15 Jan 2026 16:56:28 +1100 Subject: [PATCH 3/8] linux_dmabuf: Log some stuff --- src/platform/graphics/linux_dmabuf.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index 2468dbe6354..ae5a2cb9bb2 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #define MIR_LOG_COMPONENT "linux-dmabuf-import" #include @@ -1413,6 +1414,12 @@ mg::LinuxDmaBuf::LinuxDmaBuf( : mir::wayland::LinuxDmabufV1::Global(display, Version<5>{}), provider{std::move(provider)} { + drmDevicePtr d; + drmGetDeviceFromDevId(this->provider->devnum(), DRM_DEVICE_GET_PCI_REVISION, &d); + mir::log_debug( + "Initialised linux_dmabuf on %s", + d->nodes[0]); + drmFreeDevice(&d); } auto mg::LinuxDmaBuf::buffer_from_resource( From 22fa2f0b416f9781ea43e376f13c28fb5a9aaff1 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 15 Jan 2026 17:05:07 +1100 Subject: [PATCH 4/8] linux_dmabuf: Log which import codepath we go down --- src/platform/graphics/linux_dmabuf.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index ae5a2cb9bb2..af03ad25b8b 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -1552,6 +1552,7 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) { if (dmabuf_tex->on_same_egl_display(dpy)) { + mir::log_debug("DMA-Buf: On same GPU, using as-is"); auto tex = dmabuf_tex->as_texture(); return std::shared_ptr(std::move(dmabuf_tex), tex); } @@ -1563,6 +1564,7 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) // Cross-GPU import requires explicit modifiers; MOD_INVALID will not work if (dmabuf_tex->modifier().value_or(DRM_FORMAT_MOD_INVALID) != DRM_FORMAT_MOD_INVALID) { + mir::log_debug("DMA-Buf: Compatible cross-GPU buffer detected; importing directly"); /* We're being naughty here and using the fact that `as_texture()` has a side-effect * of invoking the buffer's `on_consumed()` callback. */ @@ -1580,6 +1582,8 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) * In this case we'll need to get the *importing* GPU to blit to a format * we *can* handle. */ + mir::log_debug("DMA-Buf: Buffer cannot be imported directly, bouncing throug EGL copy"); + auto importing_provider = dmabuf_tex->provider(); if (!importing_provider->dmabuf_export_ext) From aec7c414b2863254a699a8afd3dab040e80b4715 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Thu, 15 Jan 2026 17:05:07 +1100 Subject: [PATCH 5/8] linux_dmabuf: HACK --- src/platform/graphics/linux_dmabuf.cpp | 531 +++++++++++++++++-------- 1 file changed, 372 insertions(+), 159 deletions(-) diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index af03ad25b8b..ad062751a62 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -358,83 +358,83 @@ class DMABuf : public mg::DMABufBuffer geom::Size const size_; }; -auto export_egl_image( - mg::EGLExtensions::MESADmaBufExport const& ext, - EGLDisplay dpy, - EGLImage image, - geom::Size size) -> std::unique_ptr -{ - constexpr int const max_planes = 4; - - int fourcc; - int num_planes; - std::array modifiers; - if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); - } - - /* There's only a single modifier for a logical buffer. For some reason the EGL interface - * decided to return one modifier per plane, but they are always the same. - * - * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. - */ - auto modifier = - [](uint64_t egl_modifier) -> std::optional - { - if (egl_modifier == DRM_FORMAT_MOD_INVALID) - { - return std::nullopt; - } - return egl_modifier; - }(modifiers[0]); - - std::array fds; - std::array strides; - std::array offsets; - - if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); - } - - std::vector planes; - planes.reserve(num_planes); - for (int i = 0; i < num_planes; ++i) - { - mir::Fd fd; - // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent - // planes. - if (fds[i] == -1) - { - // Paranoia - if (i == 0) - { - BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); - } - fds[i] = fds[i - 1]; - fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; - } - else - { - // We own these FDs now. - fd = mir::Fd{fds[i]}; - } - planes.push_back( - PlaneInfo { - .dma_buf = std::move(fd), - .stride = static_cast(strides[i]), - .offset = static_cast(offsets[i]) - }); - } - - return std::make_unique( - mg::DRMFormat{static_cast(fourcc)}, - modifier, - std::move(planes), - mg::gl::Texture::Layout::TopRowFirst, - size); -} +// auto export_egl_image( +// mg::EGLExtensions::MESADmaBufExport const& ext, +// EGLDisplay dpy, +// EGLImage image, +// geom::Size size) -> std::unique_ptr +// { +// constexpr int const max_planes = 4; + +// int fourcc; +// int num_planes; +// std::array modifiers; +// if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) +// { +// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); +// } + +// /* There's only a single modifier for a logical buffer. For some reason the EGL interface +// * decided to return one modifier per plane, but they are always the same. +// * +// * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. +// */ +// auto modifier = +// [](uint64_t egl_modifier) -> std::optional +// { +// if (egl_modifier == DRM_FORMAT_MOD_INVALID) +// { +// return std::nullopt; +// } +// return egl_modifier; +// }(modifiers[0]); + +// std::array fds; +// std::array strides; +// std::array offsets; + +// if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) +// { +// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); +// } + +// std::vector planes; +// planes.reserve(num_planes); +// for (int i = 0; i < num_planes; ++i) +// { +// mir::Fd fd; +// // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent +// // planes. +// if (fds[i] == -1) +// { +// // Paranoia +// if (i == 0) +// { +// BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); +// } +// fds[i] = fds[i - 1]; +// fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; +// } +// else +// { +// // We own these FDs now. +// fd = mir::Fd{fds[i]}; +// } +// planes.push_back( +// PlaneInfo { +// .dma_buf = std::move(fd), +// .stride = static_cast(strides[i]), +// .offset = static_cast(offsets[i]) +// }); +// } + +// return std::make_unique( +// mg::DRMFormat{static_cast(fourcc)}, +// modifier, +// std::move(planes), +// mg::gl::Texture::Layout::TopRowFirst, +// size); +// } /** * Reimport dmabufs into EGL @@ -1545,6 +1545,197 @@ void mg::DMABufEGLProvider::validate_import(DMABufBuffer const& dma_buf) } } +namespace +{ +GLuint new_texture() +{ + GLuint tex; + glGenTextures(1, &tex); + + glBindTexture(GL_TEXTURE_2D, tex); + // The ShmBuffer *should* be immutable, so we can just set up the properties once + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + return tex; +} + +bool get_gl_pixel_format(MirPixelFormat mir_format, + GLenum& gl_format, GLenum& gl_type) +{ +#if __BYTE_ORDER == __LITTLE_ENDIAN + GLenum const argb = GL_BGRA_EXT; + GLenum const abgr = GL_RGBA; +#elif __BYTE_ORDER == __BIG_ENDIAN + // TODO: Big endian support + GLenum const argb = GL_INVALID_ENUM; + GLenum const abgr = GL_INVALID_ENUM; + //GLenum const rgba = GL_RGBA; + //GLenum const bgra = GL_BGRA_EXT; +#endif + + static const struct + { + MirPixelFormat mir_format; + GLenum gl_format, gl_type; + } mapping[mir_pixel_formats] = + { + {mir_pixel_format_invalid, GL_INVALID_ENUM, GL_INVALID_ENUM}, + {mir_pixel_format_abgr_8888, abgr, GL_UNSIGNED_BYTE}, + {mir_pixel_format_xbgr_8888, abgr, GL_UNSIGNED_BYTE}, + {mir_pixel_format_argb_8888, argb, GL_UNSIGNED_BYTE}, + {mir_pixel_format_xrgb_8888, argb, GL_UNSIGNED_BYTE}, + {mir_pixel_format_bgr_888, GL_INVALID_ENUM, GL_INVALID_ENUM}, + {mir_pixel_format_rgb_888, GL_RGB, GL_UNSIGNED_BYTE}, + {mir_pixel_format_rgb_565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, + {mir_pixel_format_rgba_5551, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, + {mir_pixel_format_rgba_4444, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, + }; + + if (mir_format > mir_pixel_format_invalid && + mir_format < mir_pixel_formats && + mapping[mir_format].mir_format == mir_format) // just a sanity check + { + gl_format = mapping[mir_format].gl_format; + gl_type = mapping[mir_format].gl_type; + } + else + { + gl_format = GL_INVALID_ENUM; + gl_type = GL_INVALID_ENUM; + } + + return gl_format != GL_INVALID_ENUM && gl_type != GL_INVALID_ENUM; +} + +class ShmBufferTexture : public mg::gl::Texture +{ +public: + explicit ShmBufferTexture(std::shared_ptr const& egl_delegate) + : egl_delegate(egl_delegate), + tex_id_(new_texture()) + { + } + ~ShmBufferTexture() override + { + egl_delegate->spawn( + [id=tex_id()] + { + glDeleteTextures(1, &id); + }); + } + + void bind() override + { + glBindTexture(GL_TEXTURE_2D, tex_id()); + } + + auto tex_id() const -> GLuint override + { + return tex_id_; + } + + mg::gl::Program const& shader(mg::gl::ProgramFactory& cache) const override + { + static int argb_shader{0}; + return cache.compile_fragment_shader( + &argb_shader, + "", + "uniform sampler2D tex;\n" + "vec4 sample_to_rgba(in vec2 texcoord)\n" + "{\n" + " return texture2D(tex, texcoord);\n" + "}\n"); + } + + Layout layout() const override + { + return Layout::GL; + } + void add_syncpoint() override + { + } + + void try_upload_to_texture( + mg::BufferID id, void const* pixels, geom::Size const& size, + geom::Stride const& stride, MirPixelFormat pixel_format) + { + std::lock_guard lock{uploaded_mutex}; + if (uploaded) + return; + + bind(); + GLenum format, type; + + if (get_gl_pixel_format(pixel_format, format, type)) + { + auto const stride_in_px = + stride.as_int() / MIR_BYTES_PER_PIXEL(pixel_format); + /* + * We assume (as does Weston, AFAICT) that stride is + * a multiple of whole pixels, but it need not be. + * + * TODO: Handle non-pixel-multiple strides. + * This should be possible by calculating GL_UNPACK_ALIGNMENT + * to match the size of the partial-pixel-stride(). + */ + + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride_in_px); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glTexImage2D( + GL_TEXTURE_2D, + 0, + format, + size.width.as_int(), size.height.as_int(), + 0, + format, + type, + pixels); + // Be nice to other users of the GL context by reverting our changes to shared state + glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0); // 0 is default, meaning “use width” + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is default; word alignment. + glFinish(); + } + else + { + mir::log_error( + "Buffer %i has non-GL-compatible pixel format %i; rendering will be incomplete", + id.as_value(), + pixel_format); + } + + uploaded = true; + } + + void mark_dirty() + { + std::lock_guard lock{uploaded_mutex}; + uploaded = false; + } + +private: + std::shared_ptr egl_delegate; + GLuint tex_id_; + std::mutex uploaded_mutex; + bool uploaded = false; +}; + +auto drm_node_for_device(dev_t device) -> std::string +{ + drmDevicePtr d; + + drmGetDeviceFromDevId(device, DRM_DEVICE_GET_PCI_REVISION, &d); + std::string node = d->nodes[0]; + + drmFreeDevice(&d); + + return node; +} + +} + auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) -> std::shared_ptr { @@ -1586,88 +1777,110 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) auto importing_provider = dmabuf_tex->provider(); - if (!importing_provider->dmabuf_export_ext) - { - mir::log_warning("EGL implementation does not handle cross-GPU buffer export"); - return nullptr; - } - - /* TODO: Be smarter about finding a shared pixel format; everything *should* do - * ARGB8888, but if the buffer is in a higher bitdepth this will lose colour information - */ - auto const& supported_formats = *formats; - auto const& modifiers = - [&supported_formats]() -> std::vector const& - { - for (size_t i = 0; i < supported_formats.num_formats(); ++i) - { - if (supported_formats[i].format == DRM_FORMAT_ARGB8888) - { - return supported_formats[i].modifiers; - } - } - BOOST_THROW_EXCEPTION((std::runtime_error{"Platform doesn't support ARGB8888?!"})); - }(); - - auto importable_buf = importing_provider->allocate_importable_image( - mg::DRMFormat{DRM_FORMAT_ARGB8888}, - std::span{modifiers.data(), modifiers.size()}, - dmabuf_tex->size()); - - if (!importable_buf) - { - mir::log_warning("Failed to allocate common-format buffer for cross-GPU buffer import"); - return nullptr; - } - - auto src_image = import_egl_image( - dmabuf_tex->size().width.as_int(), dmabuf_tex->size().height.as_int(), - dmabuf_tex->format(), - dmabuf_tex->modifier(), - dmabuf_tex->planes(), - importing_provider->dpy, - *importing_provider->egl_extensions); - auto importable_image = import_egl_image( - importable_buf->size().width.as_int(), importable_buf->size().height.as_int(), - importable_buf->format(), - importable_buf->modifier(), - importable_buf->planes(), - importing_provider->dpy, - *importing_provider->egl_extensions); - auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); - if (sync) - { - BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); - } - auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); - - auto base_extension = importing_provider->egl_extensions->base(importing_provider->dpy); - base_extension.eglDestroyImageKHR(importing_provider->dpy, src_image); - base_extension.eglDestroyImageKHR(importing_provider->dpy, importable_image); - - if (auto descriptor = descriptor_for_format_and_modifiers( - importable_dmabuf->format(), - importable_dmabuf->modifier().value_or(DRM_FORMAT_MOD_INVALID), - *this)) - { - /* We're being naughty here and using the fact that `as_texture()` has a side-effect - * of invoking the buffer's `on_consumed()` callback. - */ - dmabuf_tex->as_texture(); - return std::make_shared( - dpy, - *egl_extensions, - *importable_dmabuf, - *descriptor, - egl_delegate); - } - - /* To get here we have to have failed to find the format/modifier descriptor for a - * buffer that we've explicitly allocated to be importable by us. - * - * This is a logic bug, so go noisily. - */ - BOOST_THROW_EXCEPTION((std::logic_error{"Failed to find import parameterns for buffer we explicitly allocated for import"})); + mir::log_debug( + "Bouncing from %s to %s", + drm_node_for_device(importing_provider->devnum()).c_str(), + drm_node_for_device(devnum()).c_str()); + mir::log_debug( + "Source format is %s:%s", + dmabuf_tex->format().name(), + mg::drm_modifier_to_string(dmabuf_tex->modifier().value_or(DRM_FORMAT_MOD_INVALID)).c_str()); + + mir::log_debug("HACK: Doing CPU download -> CPU upload"); + + auto buf = std::make_shared(egl_delegate); + auto mapping = dmabuf_tex->map_readable(); + + buf->try_upload_to_texture( + BufferID{0xfade}, + mapping->data(), + mapping->size(), + mapping->stride(), + mapping->format()); + + return buf; + // if (!importing_provider->dmabuf_export_ext) + // { + // mir::log_warning("EGL implementation does not handle cross-GPU buffer export"); + // return nullptr; + // } + + // /* TODO: Be smarter about finding a shared pixel format; everything *should* do + // * ARGB8888, but if the buffer is in a higher bitdepth this will lose colour information + // */ + // auto const& supported_formats = *formats; + // auto const& modifiers = + // [&supported_formats]() -> std::vector const& + // { + // for (size_t i = 0; i < supported_formats.num_formats(); ++i) + // { + // if (supported_formats[i].format == DRM_FORMAT_ARGB8888) + // { + // return supported_formats[i].modifiers; + // } + // } + // BOOST_THROW_EXCEPTION((std::runtime_error{"Platform doesn't support ARGB8888?!"})); + // }(); + + // auto importable_buf = importing_provider->allocate_importable_image( + // mg::DRMFormat{DRM_FORMAT_ARGB8888}, + // std::span{modifiers.data(), modifiers.size()}, + // dmabuf_tex->size()); + + // if (!importable_buf) + // { + // mir::log_warning("Failed to allocate common-format buffer for cross-GPU buffer import"); + // return nullptr; + // } + + // auto src_image = import_egl_image( + // dmabuf_tex->size().width.as_int(), dmabuf_tex->size().height.as_int(), + // dmabuf_tex->format(), + // dmabuf_tex->modifier(), + // dmabuf_tex->planes(), + // importing_provider->dpy, + // *importing_provider->egl_extensions); + // auto importable_image = import_egl_image( + // importable_buf->size().width.as_int(), importable_buf->size().height.as_int(), + // importable_buf->format(), + // importable_buf->modifier(), + // importable_buf->planes(), + // importing_provider->dpy, + // *importing_provider->egl_extensions); + // auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); + // if (sync) + // { + // BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); + // } + // auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); + + // auto base_extension = importing_provider->egl_extensions->base(importing_provider->dpy); + // base_extension.eglDestroyImageKHR(importing_provider->dpy, src_image); + // base_extension.eglDestroyImageKHR(importing_provider->dpy, importable_image); + + // if (auto descriptor = descriptor_for_format_and_modifiers( + // importable_dmabuf->format(), + // importable_dmabuf->modifier().value_or(DRM_FORMAT_MOD_INVALID), + // *this)) + // { + // /* We're being naughty here and using the fact that `as_texture()` has a side-effect + // * of invoking the buffer's `on_consumed()` callback. + // */ + // dmabuf_tex->as_texture(); + // return std::make_shared( + // dpy, + // *egl_extensions, + // *importable_dmabuf, + // *descriptor, + // egl_delegate); + // } + + // /* To get here we have to have failed to find the format/modifier descriptor for a + // * buffer that we've explicitly allocated to be importable by us. + // * + // * This is a logic bug, so go noisily. + // */ + // BOOST_THROW_EXCEPTION((std::logic_error{"Failed to find import parameterns for buffer we explicitly allocated for import"})); } return nullptr; } From d98bb7a85fc5e6af685c28a9d2714f3016a26efa Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 16 Jan 2026 01:11:49 +0000 Subject: [PATCH 6/8] Hello, yes --- src/platform/graphics/egl_buffer_copy.cpp | 29 ++ src/platform/graphics/linux_dmabuf.cpp | 353 +++++++++--------- src/platforms/gbm-kms/server/kms/platform.cpp | 16 +- src/server/graphics/platform_probe.cpp | 2 +- 4 files changed, 226 insertions(+), 174 deletions(-) diff --git a/src/platform/graphics/egl_buffer_copy.cpp b/src/platform/graphics/egl_buffer_copy.cpp index ec08bcbe050..5f687e792d2 100644 --- a/src/platform/graphics/egl_buffer_copy.cpp +++ b/src/platform/graphics/egl_buffer_copy.cpp @@ -258,6 +258,11 @@ class mg::EGLBufferCopier::Impl egl_delegate->spawn([state = std::move(state)]() mutable { state = nullptr; }); } +#define LOG_IF_GL_ERROR() \ + if (auto err = glGetError()) {\ + mir::log_debug("GL error at %i: %s", __LINE__, mg::gl_category().message(err).c_str()); } + + auto blit(EGLImage from, EGLImage to, geometry::Size size) -> std::optional { /* TODO: It *must* be possible to create the fence FD first and *then* @@ -270,41 +275,65 @@ class mg::EGLBufferCopier::Impl [sync = std::move(sync_promise), from, to, state = state, size]() { glUseProgram(state->prog); + LOG_IF_GL_ERROR(); TextureHandle tex; + LOG_IF_GL_ERROR(); RenderbufferHandle renderbuffer; + LOG_IF_GL_ERROR(); FramebufferHandle fbo; + LOG_IF_GL_ERROR(); glActiveTexture(GL_TEXTURE0); + LOG_IF_GL_ERROR(); glBindTexture(GL_TEXTURE_2D, tex); + LOG_IF_GL_ERROR(); state->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, from); + LOG_IF_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + LOG_IF_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + LOG_IF_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + LOG_IF_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + LOG_IF_GL_ERROR(); glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + LOG_IF_GL_ERROR(); state->glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, to); + LOG_IF_GL_ERROR(); glBindFramebuffer(GL_FRAMEBUFFER, fbo); + LOG_IF_GL_ERROR(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + LOG_IF_GL_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, state->vert_data); + LOG_IF_GL_ERROR(); glVertexAttribPointer (state->attrpos, 2, GL_FLOAT, GL_FALSE, 0, 0); + LOG_IF_GL_ERROR(); glBindBuffer(GL_ARRAY_BUFFER, state->tex_data); + LOG_IF_GL_ERROR(); glVertexAttribPointer (state->attrtex, 2, GL_FLOAT, GL_FALSE, 0, 0); + LOG_IF_GL_ERROR(); glEnableVertexAttribArray(state->attrpos); + LOG_IF_GL_ERROR(); glEnableVertexAttribArray(state->attrtex); + LOG_IF_GL_ERROR(); glViewport(0, 0, size.width.as_int(), size.height.as_int()); + LOG_IF_GL_ERROR(); GLubyte const idx[] = { 0, 1, 3, 2 }; glDrawElements (GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx); + LOG_IF_GL_ERROR(); // TODO: Actually use the sync glFinish(); + LOG_IF_GL_ERROR(); sync->set_value({}); // Unbind all our resources diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index ad062751a62..7ee87ea1ac8 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -358,83 +358,83 @@ class DMABuf : public mg::DMABufBuffer geom::Size const size_; }; -// auto export_egl_image( -// mg::EGLExtensions::MESADmaBufExport const& ext, -// EGLDisplay dpy, -// EGLImage image, -// geom::Size size) -> std::unique_ptr -// { -// constexpr int const max_planes = 4; - -// int fourcc; -// int num_planes; -// std::array modifiers; -// if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) -// { -// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); -// } - -// /* There's only a single modifier for a logical buffer. For some reason the EGL interface -// * decided to return one modifier per plane, but they are always the same. -// * -// * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. -// */ -// auto modifier = -// [](uint64_t egl_modifier) -> std::optional -// { -// if (egl_modifier == DRM_FORMAT_MOD_INVALID) -// { -// return std::nullopt; -// } -// return egl_modifier; -// }(modifiers[0]); - -// std::array fds; -// std::array strides; -// std::array offsets; - -// if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) -// { -// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); -// } - -// std::vector planes; -// planes.reserve(num_planes); -// for (int i = 0; i < num_planes; ++i) -// { -// mir::Fd fd; -// // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent -// // planes. -// if (fds[i] == -1) -// { -// // Paranoia -// if (i == 0) -// { -// BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); -// } -// fds[i] = fds[i - 1]; -// fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; -// } -// else -// { -// // We own these FDs now. -// fd = mir::Fd{fds[i]}; -// } -// planes.push_back( -// PlaneInfo { -// .dma_buf = std::move(fd), -// .stride = static_cast(strides[i]), -// .offset = static_cast(offsets[i]) -// }); -// } - -// return std::make_unique( -// mg::DRMFormat{static_cast(fourcc)}, -// modifier, -// std::move(planes), -// mg::gl::Texture::Layout::TopRowFirst, -// size); -// } +auto export_egl_image( + mg::EGLExtensions::MESADmaBufExport const& ext, + EGLDisplay dpy, + EGLImage image, + geom::Size size) -> std::unique_ptr +{ + constexpr int const max_planes = 4; + + int fourcc; + int num_planes; + std::array modifiers; + if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); + } + + /* There's only a single modifier for a logical buffer. For some reason the EGL interface + * decided to return one modifier per plane, but they are always the same. + * + * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. + */ + auto modifier = + [](uint64_t egl_modifier) -> std::optional + { + if (egl_modifier == DRM_FORMAT_MOD_INVALID) + { + return std::nullopt; + } + return egl_modifier; + }(modifiers[0]); + + std::array fds; + std::array strides; + std::array offsets; + + if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); + } + + std::vector planes; + planes.reserve(num_planes); + for (int i = 0; i < num_planes; ++i) + { + mir::Fd fd; + // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent + // planes. + if (fds[i] == -1) + { + // Paranoia + if (i == 0) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); + } + fds[i] = fds[i - 1]; + fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; + } + else + { + // We own these FDs now. + fd = mir::Fd{fds[i]}; + } + planes.push_back( + PlaneInfo { + .dma_buf = std::move(fd), + .stride = static_cast(strides[i]), + .offset = static_cast(offsets[i]) + }); + } + + return std::make_unique( + mg::DRMFormat{static_cast(fourcc)}, + modifier, + std::move(planes), + mg::gl::Texture::Layout::TopRowFirst, + size); +} /** * Reimport dmabufs into EGL @@ -456,6 +456,8 @@ auto import_egl_image( { std::vector attributes; + mir::log_debug("Importing EGL image:"); + attributes.push_back(EGL_WIDTH); attributes.push_back(width); attributes.push_back(EGL_HEIGHT); @@ -463,17 +465,24 @@ auto import_egl_image( attributes.push_back(EGL_LINUX_DRM_FOURCC_EXT); attributes.push_back(format); + mir::log_debug("\tsize: (%i × %i)", width, height); + mir::log_debug("\tformat: %i", static_cast(format)); + for(auto i = 0u; i < planes.size(); ++i) { auto const& attrib_names = egl_attribs[i]; auto const& plane = planes[i]; + mir::log_debug("\tPlane %i:", i); attributes.push_back(attrib_names.fd); attributes.push_back(static_cast(plane.dma_buf)); + mir::log_debug("\t\tfd: %i", attributes.back()); attributes.push_back(attrib_names.offset); attributes.push_back(plane.offset); + mir::log_debug("\t\toffset: %i", attributes.back()); attributes.push_back(attrib_names.pitch); attributes.push_back(plane.stride); + mir::log_debug("\t\tpitch: %i", attributes.back()); if (auto modifier_present = modifier) { attributes.push_back(attrib_names.modifier_lo); @@ -1786,101 +1795,101 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) dmabuf_tex->format().name(), mg::drm_modifier_to_string(dmabuf_tex->modifier().value_or(DRM_FORMAT_MOD_INVALID)).c_str()); - mir::log_debug("HACK: Doing CPU download -> CPU upload"); - - auto buf = std::make_shared(egl_delegate); - auto mapping = dmabuf_tex->map_readable(); - - buf->try_upload_to_texture( - BufferID{0xfade}, - mapping->data(), - mapping->size(), - mapping->stride(), - mapping->format()); - - return buf; - // if (!importing_provider->dmabuf_export_ext) - // { - // mir::log_warning("EGL implementation does not handle cross-GPU buffer export"); - // return nullptr; - // } - - // /* TODO: Be smarter about finding a shared pixel format; everything *should* do - // * ARGB8888, but if the buffer is in a higher bitdepth this will lose colour information - // */ - // auto const& supported_formats = *formats; - // auto const& modifiers = - // [&supported_formats]() -> std::vector const& - // { - // for (size_t i = 0; i < supported_formats.num_formats(); ++i) - // { - // if (supported_formats[i].format == DRM_FORMAT_ARGB8888) - // { - // return supported_formats[i].modifiers; - // } - // } - // BOOST_THROW_EXCEPTION((std::runtime_error{"Platform doesn't support ARGB8888?!"})); - // }(); - - // auto importable_buf = importing_provider->allocate_importable_image( - // mg::DRMFormat{DRM_FORMAT_ARGB8888}, - // std::span{modifiers.data(), modifiers.size()}, - // dmabuf_tex->size()); - - // if (!importable_buf) - // { - // mir::log_warning("Failed to allocate common-format buffer for cross-GPU buffer import"); - // return nullptr; - // } - - // auto src_image = import_egl_image( - // dmabuf_tex->size().width.as_int(), dmabuf_tex->size().height.as_int(), - // dmabuf_tex->format(), - // dmabuf_tex->modifier(), - // dmabuf_tex->planes(), - // importing_provider->dpy, - // *importing_provider->egl_extensions); - // auto importable_image = import_egl_image( - // importable_buf->size().width.as_int(), importable_buf->size().height.as_int(), - // importable_buf->format(), - // importable_buf->modifier(), - // importable_buf->planes(), - // importing_provider->dpy, - // *importing_provider->egl_extensions); - // auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); - // if (sync) - // { - // BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); - // } - // auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); - - // auto base_extension = importing_provider->egl_extensions->base(importing_provider->dpy); - // base_extension.eglDestroyImageKHR(importing_provider->dpy, src_image); - // base_extension.eglDestroyImageKHR(importing_provider->dpy, importable_image); - - // if (auto descriptor = descriptor_for_format_and_modifiers( - // importable_dmabuf->format(), - // importable_dmabuf->modifier().value_or(DRM_FORMAT_MOD_INVALID), - // *this)) - // { - // /* We're being naughty here and using the fact that `as_texture()` has a side-effect - // * of invoking the buffer's `on_consumed()` callback. - // */ - // dmabuf_tex->as_texture(); - // return std::make_shared( - // dpy, - // *egl_extensions, - // *importable_dmabuf, - // *descriptor, - // egl_delegate); - // } - - // /* To get here we have to have failed to find the format/modifier descriptor for a - // * buffer that we've explicitly allocated to be importable by us. - // * - // * This is a logic bug, so go noisily. - // */ - // BOOST_THROW_EXCEPTION((std::logic_error{"Failed to find import parameterns for buffer we explicitly allocated for import"})); + if (!importing_provider->dmabuf_export_ext) + { + mir::log_warning("EGL implementation does not handle cross-GPU buffer export"); + return nullptr; + } + + /* TODO: Be smarter about finding a shared pixel format; everything *should* do + * ARGB8888, but if the buffer is in a higher bitdepth this will lose colour information + */ + auto const& supported_formats = *formats; + auto const& modifiers = + [&supported_formats]() -> std::vector const& + { + for (size_t i = 0; i < supported_formats.num_formats(); ++i) + { + if (supported_formats[i].format == DRM_FORMAT_ARGB8888) + { + return supported_formats[i].modifiers; + } + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Platform doesn't support ARGB8888?!"})); + }(); + + mir::log_debug("Candidate modifiers for bounce buffer: "); + for (auto mod : modifiers) + { + mir::log_debug("\t%s", mg::drm_modifier_to_string(mod).c_str()); + } + + auto importable_buf = importing_provider->allocate_importable_image( + mg::DRMFormat{DRM_FORMAT_ARGB8888}, + std::span{modifiers.data(), modifiers.size()}, + dmabuf_tex->size()); + + if (!importable_buf) + { + mir::log_warning("Failed to allocate common-format buffer for cross-GPU buffer import"); + return nullptr; + } + + mir::log_debug("Bounce buffer has format %s:%s", importable_buf->format().name(), mg::drm_modifier_to_string(importable_buf->modifier().value_or(DRM_FORMAT_MOD_INVALID)).c_str()); + + mir::log_debug("Importing source image on %s", drm_node_for_device(importing_provider->devnum()).c_str() ); + auto src_image = import_egl_image( + dmabuf_tex->size().width.as_int(), dmabuf_tex->size().height.as_int(), + dmabuf_tex->format(), + dmabuf_tex->modifier(), + dmabuf_tex->planes(), + importing_provider->dpy, + *importing_provider->egl_extensions); + mir::log_debug("Importing bounce buffer (on same device)"); + auto importable_image = import_egl_image( + importable_buf->size().width.as_int(), importable_buf->size().height.as_int(), + importable_buf->format(), + importable_buf->modifier(), + importable_buf->planes(), + importing_provider->dpy, + *importing_provider->egl_extensions); + mir::log_debug("Blitting to bounce buffer"); + auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); + if (sync) + { + BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); + } + mir::log_debug("Exporting bounce buffer to DMA-buf"); + auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); + + auto base_extension = importing_provider->egl_extensions->base(importing_provider->dpy); + base_extension.eglDestroyImageKHR(importing_provider->dpy, src_image); + base_extension.eglDestroyImageKHR(importing_provider->dpy, importable_image); + + if (auto descriptor = descriptor_for_format_and_modifiers( + importable_dmabuf->format(), + importable_dmabuf->modifier().value_or(DRM_FORMAT_MOD_INVALID), + *this)) + { + /* We're being naughty here and using the fact that `as_texture()` has a side-effect + * of invoking the buffer's `on_consumed()` callback. + */ + dmabuf_tex->as_texture(); + mir::log_debug("Constructing DMABufTex on %s", drm_node_for_device(devnum()).c_str()); + return std::make_shared( + dpy, + *egl_extensions, + *importable_dmabuf, + *descriptor, + egl_delegate); + } + + /* To get here we have to have failed to find the format/modifier descriptor for a + * buffer that we've explicitly allocated to be importable by us. + * + * This is a logic bug, so go noisily. + */ + BOOST_THROW_EXCEPTION((std::logic_error{"Failed to find import parameterns for buffer we explicitly allocated for import"})); } return nullptr; } diff --git a/src/platforms/gbm-kms/server/kms/platform.cpp b/src/platforms/gbm-kms/server/kms/platform.cpp index 642d6701ef7..a90d0f60877 100644 --- a/src/platforms/gbm-kms/server/kms/platform.cpp +++ b/src/platforms/gbm-kms/server/kms/platform.cpp @@ -288,13 +288,27 @@ auto gbm_bo_with_modifiers_or_linear( format, modifiers.data(), modifiers.size(), GBM_BO_USE_RENDERING); - if (!gbm_bo && errno != ENOSYS) + if (!gbm_bo && (errno != ENOSYS || errno == EINVAL)) { + // Stupid NVIDIA stupid flags stupid stupid + if (errno == EINVAL) + { + gbm_bo = gbm_bo_create_with_modifiers2( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + modifiers.data(), modifiers.size(), + 0); + } + if (!gbm_bo && errno != ENOSYS) + { BOOST_THROW_EXCEPTION(( std::system_error{ errno, std::system_category(), "Failed to allocate GBM bo"})); + } + mir::log_debug("Using NVIDIA no-flags path"); } else if (!gbm_bo) { diff --git a/src/server/graphics/platform_probe.cpp b/src/server/graphics/platform_probe.cpp index 4e730348311..716eee9de43 100644 --- a/src/server/graphics/platform_probe.cpp +++ b/src/server/graphics/platform_probe.cpp @@ -607,7 +607,7 @@ auto mg::select_buffer_allocating_renderer( // Now, find the best platform out of the ones that support the most Sinks auto const best_provider = std::max_element( providers.begin(), last_good_provider, - [](auto const& a, auto const&b) { return a.best_count < b.best_count; }); + [](auto const& a, auto const&b) { return a.best_count <= b.best_count; }); return best_provider->platform; } From 199091c5f63931d71108e48705b029e250661646 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Fri, 16 Jan 2026 03:25:10 +0000 Subject: [PATCH 7/8] Do all the weird stuff --- src/platform/graphics/linux_dmabuf.cpp | 316 +++++++++++++----- src/platforms/gbm-kms/server/kms/platform.cpp | 2 +- 2 files changed, 230 insertions(+), 88 deletions(-) diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index 7ee87ea1ac8..95d36902beb 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -358,83 +358,83 @@ class DMABuf : public mg::DMABufBuffer geom::Size const size_; }; -auto export_egl_image( - mg::EGLExtensions::MESADmaBufExport const& ext, - EGLDisplay dpy, - EGLImage image, - geom::Size size) -> std::unique_ptr -{ - constexpr int const max_planes = 4; - - int fourcc; - int num_planes; - std::array modifiers; - if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); - } - - /* There's only a single modifier for a logical buffer. For some reason the EGL interface - * decided to return one modifier per plane, but they are always the same. - * - * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. - */ - auto modifier = - [](uint64_t egl_modifier) -> std::optional - { - if (egl_modifier == DRM_FORMAT_MOD_INVALID) - { - return std::nullopt; - } - return egl_modifier; - }(modifiers[0]); - - std::array fds; - std::array strides; - std::array offsets; - - if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); - } - - std::vector planes; - planes.reserve(num_planes); - for (int i = 0; i < num_planes; ++i) - { - mir::Fd fd; - // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent - // planes. - if (fds[i] == -1) - { - // Paranoia - if (i == 0) - { - BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); - } - fds[i] = fds[i - 1]; - fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; - } - else - { - // We own these FDs now. - fd = mir::Fd{fds[i]}; - } - planes.push_back( - PlaneInfo { - .dma_buf = std::move(fd), - .stride = static_cast(strides[i]), - .offset = static_cast(offsets[i]) - }); - } - - return std::make_unique( - mg::DRMFormat{static_cast(fourcc)}, - modifier, - std::move(planes), - mg::gl::Texture::Layout::TopRowFirst, - size); -} +// auto export_egl_image( +// mg::EGLExtensions::MESADmaBufExport const& ext, +// EGLDisplay dpy, +// EGLImage image, +// geom::Size size) -> std::unique_ptr +// { +// constexpr int const max_planes = 4; + +// int fourcc; +// int num_planes; +// std::array modifiers; +// if (ext.eglExportDMABUFImageQueryMESA(dpy, image, &fourcc, &num_planes, modifiers.data()) != EGL_TRUE) +// { +// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to query EGLImage for dma-buf export"))); +// } + +// /* There's only a single modifier for a logical buffer. For some reason the EGL interface +// * decided to return one modifier per plane, but they are always the same. +// * +// * We handle DRM_FORMAT_MOD_INVALID as an empty modifier; fix that up here if we get it. +// */ +// auto modifier = +// [](uint64_t egl_modifier) -> std::optional +// { +// if (egl_modifier == DRM_FORMAT_MOD_INVALID) +// { +// return std::nullopt; +// } +// return egl_modifier; +// }(modifiers[0]); + +// std::array fds; +// std::array strides; +// std::array offsets; + +// if (ext.eglExportDMABUFImageMESA(dpy, image, fds.data(), strides.data(), offsets.data()) != EGL_TRUE) +// { +// BOOST_THROW_EXCEPTION((mg::egl_error("Failed to export EGLImage to dma-buf(s)"))); +// } + +// std::vector planes; +// planes.reserve(num_planes); +// for (int i = 0; i < num_planes; ++i) +// { +// mir::Fd fd; +// // If multiple planes use the same buffer, the fds array will be filled with -1 for subsequent +// // planes. +// if (fds[i] == -1) +// { +// // Paranoia +// if (i == 0) +// { +// BOOST_THROW_EXCEPTION((std::runtime_error{"Driver has a broken EGL_MESA_image_dma_buf_export extension"})); +// } +// fds[i] = fds[i - 1]; +// fd = mir::Fd{mir::IntOwnedFd{fds[i]}}; +// } +// else +// { +// // We own these FDs now. +// fd = mir::Fd{fds[i]}; +// } +// planes.push_back( +// PlaneInfo { +// .dma_buf = std::move(fd), +// .stride = static_cast(strides[i]), +// .offset = static_cast(offsets[i]) +// }); +// } + +// return std::make_unique( +// mg::DRMFormat{static_cast(fourcc)}, +// modifier, +// std::move(planes), +// mg::gl::Texture::Layout::TopRowFirst, +// size); +// } /** * Reimport dmabufs into EGL @@ -1283,6 +1283,7 @@ class DmabufTexBuffer : void* data_; }; + friend class GLMapping; class GLMapping : public mrs::Mapping { @@ -1743,6 +1744,133 @@ auto drm_node_for_device(dev_t device) -> std::string return node; } +class DmaBufWriteMapping : public mrs::Mapping +{ +public: + DmaBufWriteMapping(mg::DMABufBuffer const& parent) + : parent{parent} + { + if (!parent.format().as_mir_format().has_value()) + { + BOOST_THROW_EXCEPTION((mg::UnmappableBuffer{"Format unrepresentable in mir_pixel_format"})); + } + if (parent.planes().size() != 1) + { + BOOST_THROW_EXCEPTION((mg::UnmappableBuffer{"Multiplanar formats not supported"})); + } + auto& info = parent.planes().front(); + data_ = ::mmap( + nullptr, + info.stride * parent.size().height.as(), + PROT_WRITE, + MAP_SHARED_VALIDATE, + info.dma_buf, + info.offset); + if (data_ == MAP_FAILED) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to map DMABuf"})); + } + struct dma_buf_sync sync{.flags = DMA_BUF_SYNC_WRITE | DMA_BUF_SYNC_START}; + if (ioctl(info.dma_buf, DMA_BUF_IOCTL_SYNC, &sync) == -1) + { + // TODO: ioctl documentation says we should retry on EAGAIN and EINTR + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to notify kernel of dma-buf CPU read" + } + )); + } + } + + ~DmaBufWriteMapping() + { + struct dma_buf_sync sync{.flags = DMA_BUF_SYNC_END}; + ioctl(parent.planes().front().dma_buf, DMA_BUF_IOCTL_SYNC, &sync); + + munmap(data_, len()); + } + + auto data() const -> std::byte* override + { + return static_cast(data_); + } + + auto len() const -> size_t override + { + return size().height.as() * stride().as(); + } + + auto format() const -> MirPixelFormat override + { + return parent.format().as_mir_format().value_or(mir_pixel_format_invalid); + } + + auto stride() const -> geom::Stride override + { + return geom::Stride{parent.planes().front().stride}; + } + + auto size() const -> geom::Size override + { + return parent.size(); + } +private: + mg::DMABufBuffer const& parent; + void* data_; +}; + +std::byte fill_colour_for(int x, int y) +{ + + if ((x / 20) % 2 == 1) + { + if ((y / 20) % 2 == 1) + { + return std::byte{0xff}; + } + else + { + return std::byte{0}; + } + } + else + { + if ((y / 20) % 2 == 1) + { + return std::byte{0x0}; + } + else + { + return std::byte{0xff}; + } + } +} + +auto fill_checkerboard(geom::Size size) -> std::unique_ptr +{ + auto checkerboard = std::make_unique(size.width.as() * size.height.as() * 4); + + for (int x = 0; x < size.width.as(); ++x) + { + for (int y = 0; y < size.height.as(); ++y) + { + auto const fill = fill_colour_for(x, y); + + auto pixel_start = checkerboard.get() + ((y * size.width.as()) + x) * 4; + *pixel_start = fill; + *(pixel_start + 1) = fill; + *(pixel_start + 2) = fill; + *(pixel_start + 3) = fill; + } + } + return checkerboard; +} } auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) @@ -1853,22 +1981,36 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) importable_buf->planes(), importing_provider->dpy, *importing_provider->egl_extensions); - mir::log_debug("Blitting to bounce buffer"); - auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); - if (sync) + mir::log_debug("Filling bounce buffer with checkerboard"); + { - BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); - } - mir::log_debug("Exporting bounce buffer to DMA-buf"); - auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); + DmaBufWriteMapping mapping{*importable_buf}; + auto line_base = mapping.data(); + auto width = importable_buf->size().width.as(); + auto checkerboard = fill_checkerboard(importable_buf->size()); + + for (auto line = 0; line < importable_buf->size().height.as(); ++line) + { + memcpy(line_base, checkerboard.get() + (line * width * 4), width * 4); + line_base += mapping.stride().as(); + } + } + + // auto sync = importing_provider->blitter->blit(src_image, importable_image, dmabuf_tex->size()); + // if (sync) + // { + // BOOST_THROW_EXCEPTION((std::logic_error{"EGL_ANDROID_native_fence_sync support not implemented yet"})); + // } + // mir::log_debug("Exporting bounce buffer to DMA-buf"); + // auto importable_dmabuf = export_egl_image(*importing_provider->dmabuf_export_ext, importing_provider->dpy, importable_image, dmabuf_tex->size()); auto base_extension = importing_provider->egl_extensions->base(importing_provider->dpy); base_extension.eglDestroyImageKHR(importing_provider->dpy, src_image); base_extension.eglDestroyImageKHR(importing_provider->dpy, importable_image); if (auto descriptor = descriptor_for_format_and_modifiers( - importable_dmabuf->format(), - importable_dmabuf->modifier().value_or(DRM_FORMAT_MOD_INVALID), + importable_buf->format(), + importable_buf->modifier().value_or(DRM_FORMAT_MOD_INVALID), *this)) { /* We're being naughty here and using the fact that `as_texture()` has a side-effect @@ -1879,7 +2021,7 @@ auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) return std::make_shared( dpy, *egl_extensions, - *importable_dmabuf, + *importable_buf, *descriptor, egl_delegate); } diff --git a/src/platforms/gbm-kms/server/kms/platform.cpp b/src/platforms/gbm-kms/server/kms/platform.cpp index a90d0f60877..aa7ab41c17f 100644 --- a/src/platforms/gbm-kms/server/kms/platform.cpp +++ b/src/platforms/gbm-kms/server/kms/platform.cpp @@ -374,7 +374,7 @@ auto alloc_dma_buf( format, modifier, std::move(planes), - mg::gl::Texture::Layout::GL, + mg::gl::Texture::Layout::TopRowFirst, size); } From 4f7549861d06ca7c875251defa5c8656cec07c00 Mon Sep 17 00:00:00 2001 From: Christopher James Halse Rogers Date: Wed, 21 Jan 2026 17:57:51 +1100 Subject: [PATCH 8/8] EGLBufferCopier: Import the source image as EXTERNAL_OES --- src/platform/graphics/egl_buffer_copy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/graphics/egl_buffer_copy.cpp b/src/platform/graphics/egl_buffer_copy.cpp index 5f687e792d2..bcf9bd6fc0f 100644 --- a/src/platform/graphics/egl_buffer_copy.cpp +++ b/src/platform/graphics/egl_buffer_copy.cpp @@ -286,9 +286,9 @@ class mg::EGLBufferCopier::Impl glActiveTexture(GL_TEXTURE0); LOG_IF_GL_ERROR(); - glBindTexture(GL_TEXTURE_2D, tex); + glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex); LOG_IF_GL_ERROR(); - state->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, from); + state->glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, from); LOG_IF_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);