diff --git a/CMakeLists.txt b/CMakeLists.txt index eb9a6b40850..09d68b397ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "${build_types}" FORCE) # Enable cmake-gui to display a drop down list for CMAKE_BUILD_TYPE set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${build_types}") -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -233,6 +233,7 @@ if(NOT GLM_FOUND) endif() pkg_check_modules(DRM REQUIRED IMPORTED_TARGET libdrm) pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl) +pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy) pkg_check_modules(GFlags REQUIRED IMPORTED_TARGET gflags) pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0 gio-unix-2.0) pkg_check_modules(GLESv2 REQUIRED IMPORTED_TARGET glesv2) @@ -271,14 +272,6 @@ if (MIR_BUILD_PLATFORM_GBM_KMS) pkg_check_modules(GBM REQUIRED IMPORTED_TARGET gbm>=11.0.0) endif() -if (MIR_BUILD_PLATFORM_EGLSTREAM_KMS) - pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy) -endif() - -if (MIR_BUILD_PLATFORM_WAYLAND) - pkg_check_modules(EPOXY REQUIRED IMPORTED_TARGET epoxy) -endif() - set(MIR_TRACEPOINT_LIB_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/mir/tools) mir_make_pkgconfig_variable(PKGCONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") diff --git a/debian/control b/debian/control index 4fa423e237a..29d8af348b8 100644 --- a/debian/control +++ b/debian/control @@ -76,7 +76,7 @@ Description: Display server for Ubuntu - server library . Contains the shared library needed by server applications for Mir. -Package: libmirplatform26 +Package: libmirplatform27 Section: libs Architecture: linux-any Multi-Arch: same @@ -124,7 +124,7 @@ Section: libdevel Architecture: linux-any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} -Depends: libmirplatform26 (= ${binary:Version}), +Depends: libmirplatform27 (= ${binary:Version}), libmircommon-dev (= ${binary:Version}), libboost-program-options-dev, ${misc:Depends}, @@ -520,7 +520,7 @@ Description: Developer files for the Mir ABI-stable abstraction layer Contains header files required for development using the MirAL abstraction layer. -Package: libmiroil3 +Package: libmiroil4 Section: libs Architecture: linux-any Multi-Arch: same @@ -537,7 +537,7 @@ Section: libdevel Architecture: linux-any Multi-Arch: same Pre-Depends: ${misc:Pre-Depends} -Depends: libmiroil3 (= ${binary:Version}), +Depends: libmiroil4 (= ${binary:Version}), ${misc:Depends}, Description: Developer files for the Mir Lomiri compatibility library MirOil provides the Lomiri compatibility API. diff --git a/debian/libmiroil3.install b/debian/libmiroil3.install deleted file mode 100644 index f8f1a08d2d4..00000000000 --- a/debian/libmiroil3.install +++ /dev/null @@ -1 +0,0 @@ -usr/lib/*/libmiroil.so.3 diff --git a/debian/libmiroil4.install b/debian/libmiroil4.install new file mode 100644 index 00000000000..00634524443 --- /dev/null +++ b/debian/libmiroil4.install @@ -0,0 +1 @@ +usr/lib/*/libmiroil.so.4 diff --git a/debian/libmirplatform26.install b/debian/libmirplatform26.install deleted file mode 100644 index b5b49cd0c74..00000000000 --- a/debian/libmirplatform26.install +++ /dev/null @@ -1 +0,0 @@ -usr/lib/*/libmirplatform.so.26 diff --git a/debian/libmirplatform27.install b/debian/libmirplatform27.install new file mode 100644 index 00000000000..21e6f7cea33 --- /dev/null +++ b/debian/libmirplatform27.install @@ -0,0 +1 @@ +usr/lib/*/libmirplatform.so.27 diff --git a/doc/sphinx/.readthedocs.yaml b/doc/sphinx/.readthedocs.yaml index cb0bd3d2f8f..f6798b5ca54 100644 --- a/doc/sphinx/.readthedocs.yaml +++ b/doc/sphinx/.readthedocs.yaml @@ -20,6 +20,7 @@ build: - libboost-system-dev - libdrm-dev - libegl-dev + - libepoxy-dev - libfreetype-dev - libglib2.0-dev - libgles2-mesa-dev diff --git a/include/platform/mir/graphics/buffer.h b/include/platform/mir/graphics/buffer.h index 0aed4e83074..cfb3f7a9127 100644 --- a/include/platform/mir/graphics/buffer.h +++ b/include/platform/mir/graphics/buffer.h @@ -21,9 +21,6 @@ #include "mir/geometry/size.h" #include "mir_toolkit/common.h" -#include -#include - namespace mir { namespace graphics diff --git a/include/platform/mir/graphics/display.h b/include/platform/mir/graphics/display.h index 7131d4b3517..1d837cccde1 100644 --- a/include/platform/mir/graphics/display.h +++ b/include/platform/mir/graphics/display.h @@ -85,7 +85,7 @@ class DisplaySyncGroup /** * Interface to the display subsystem. */ -class Display : public renderer::gl::ContextSource +class Display { public: /** diff --git a/include/platform/mir/graphics/display_buffer.h b/include/platform/mir/graphics/display_buffer.h index 0af246568d6..eda8095e65c 100644 --- a/include/platform/mir/graphics/display_buffer.h +++ b/include/platform/mir/graphics/display_buffer.h @@ -29,17 +29,24 @@ namespace mir namespace graphics { -class Buffer; +class Framebuffer; +class DisplayInterfaceProvider; -class NativeDisplayBuffer +struct DisplayElement { -protected: - NativeDisplayBuffer() = default; - virtual ~NativeDisplayBuffer() = default; - NativeDisplayBuffer(NativeDisplayBuffer const&) = delete; - NativeDisplayBuffer operator=(NativeDisplayBuffer const&) = delete; + /// Position and size of this element on-screen + geometry::Rectangle screen_positon; + /** Position and size of region of the buffer to sample from + * + * This will typically be (0,0) ->(buffer width, buffer height) + * to use the whole buffer (with screen_position.size = (buffer width, buffer height)). + * + * Scaling can be specified by having screen_position.size != source_position.size, + * clipping can be specified by having source_position.size != buffer.size + */ + geometry::RectangleF source_position; + std::shared_ptr buffer; }; - /** * Interface to an output framebuffer. */ @@ -64,7 +71,20 @@ class DisplayBuffer * caller should then render the list another way using a graphics * library such as OpenGL. **/ - virtual bool overlay(RenderableList const& renderlist) = 0; + virtual bool overlay(std::vector const& renderlist) = 0; + + /** + * Set the content for the next submission of this display + * + * This is basically a specialisation of overlay(), above, with extra constraints + * and guarantees. Namely: + * * The Framebuffer must be exactly view_area().size big, and + * * The DisplayPlatform guarantees that this call will succeed with a framebuffer + * allocated for this DisplayBuffer + * + * \param content + */ + virtual void set_next_image(std::unique_ptr content) = 0; /** * Returns a transformation that the renderer must apply to all rendering. @@ -75,14 +95,7 @@ class DisplayBuffer */ virtual glm::mat2 transformation() const = 0; - /** Returns a pointer to the native display buffer object backing this - * display buffer. - * - * The pointer to the native display buffer remains valid as long as the - * display buffer object is valid. - */ - virtual NativeDisplayBuffer* native_display_buffer() = 0; - + virtual auto display_provider() const -> std::shared_ptr = 0; protected: DisplayBuffer() = default; DisplayBuffer(DisplayBuffer const& c) = delete; diff --git a/include/platform/mir/graphics/display_configuration_policy.h b/include/platform/mir/graphics/display_configuration_policy.h index 3caa300e5d7..261894f6f64 100644 --- a/include/platform/mir/graphics/display_configuration_policy.h +++ b/include/platform/mir/graphics/display_configuration_policy.h @@ -30,6 +30,7 @@ class DisplayConfigurationPolicy virtual ~DisplayConfigurationPolicy() = default; virtual void apply_to(DisplayConfiguration& conf) = 0; + virtual void confirm(DisplayConfiguration const& conf) = 0; protected: DisplayConfigurationPolicy() = default; diff --git a/include/platform/mir/graphics/dmabuf_buffer.h b/include/platform/mir/graphics/dmabuf_buffer.h index 8ef979f3d3d..347f64aa859 100644 --- a/include/platform/mir/graphics/dmabuf_buffer.h +++ b/include/platform/mir/graphics/dmabuf_buffer.h @@ -21,12 +21,14 @@ #include #include "mir/graphics/buffer.h" +#include "mir/graphics/texture.h" #include "mir/fd.h" namespace mir { namespace graphics { +class DRMFormat; /** * A logical buffer backed by one-or-more dmabuf buffers */ @@ -42,9 +44,9 @@ class DMABufBuffer : public NativeBufferBase virtual ~DMABufBuffer() = default; /** - * The format of this logical buffer, as in + * The format of this logical buffer */ - virtual auto drm_fourcc() const -> uint32_t = 0; + virtual auto format() const -> DRMFormat = 0; /** * The DRM modifier of this logical buffer, if specified. @@ -57,6 +59,8 @@ class DMABufBuffer : public NativeBufferBase virtual auto planes() const -> std::vector const& = 0; + virtual auto layout() const -> gl::Texture::Layout = 0; + virtual auto size() const -> geometry::Size = 0; }; } diff --git a/include/platform/mir/graphics/drm_formats.h b/include/platform/mir/graphics/drm_formats.h index b8a50ee7133..851c5271087 100644 --- a/include/platform/mir/graphics/drm_formats.h +++ b/include/platform/mir/graphics/drm_formats.h @@ -50,6 +50,8 @@ class DRMFormat operator uint32_t() const; auto as_mir_format() const -> std::optional; + static auto from_mir_format(MirPixelFormat format) -> DRMFormat; + struct FormatInfo; private: FormatInfo const* info; diff --git a/include/platform/mir/graphics/egl_extensions.h b/include/platform/mir/graphics/egl_extensions.h index 7f9f2b9b81c..8b4a72f4e06 100644 --- a/include/platform/mir/graphics/egl_extensions.h +++ b/include/platform/mir/graphics/egl_extensions.h @@ -313,8 +313,17 @@ struct EGLExtensions PFNEGLQUERYDMABUFFORMATSEXTPROC const eglQueryDmaBufFormatsExt; PFNEGLQUERYDMABUFMODIFIERSEXTPROC const eglQueryDmaBufModifiersExt; }; -}; + struct MESADmaBufExport + { + MESADmaBufExport(EGLDisplay dpy); + + static auto extension_if_supported(EGLDisplay dpy) -> std::optional; + + PFNEGLEXPORTDMABUFIMAGEMESAPROC const eglExportDMABUFImageMESA; + PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC const eglExportDMABUFImageQueryMESA; + }; +}; } } diff --git a/include/platform/mir/graphics/graphic_buffer_allocator.h b/include/platform/mir/graphics/graphic_buffer_allocator.h index 3c5c4694f55..85831b6ac93 100644 --- a/include/platform/mir/graphics/graphic_buffer_allocator.h +++ b/include/platform/mir/graphics/graphic_buffer_allocator.h @@ -75,7 +75,7 @@ class GraphicBufferAllocator * Deinitialise the BufferAllocator for this Wayland display * * This should do whatever is required to clean up before the Wayland loop is stopped. For - * example, calling eglUnindWaylandDisplayWL. + * example, calling eglUnbindWaylandDisplayWL. * * \param display [in] The Wayland display to unbind from */ diff --git a/include/platform/mir/graphics/linux_dmabuf.h b/include/platform/mir/graphics/linux_dmabuf.h index d3a99000d81..9f435e81853 100644 --- a/include/platform/mir/graphics/linux_dmabuf.h +++ b/include/platform/mir/graphics/linux_dmabuf.h @@ -21,11 +21,13 @@ #include "linux-dmabuf-unstable-v1_wrapper.h" #include +#include +#include #include "mir/graphics/buffer.h" +#include "mir/graphics/drm_formats.h" #include "mir/graphics/egl_extensions.h" - namespace mir { namespace renderer @@ -38,35 +40,81 @@ class Context; namespace graphics { +namespace gl +{ +class Texture; +} + namespace common { class EGLContextExecutor; } class DmaBufFormatDescriptors; +class DMABufBuffer; +class EGLBufferCopier; + +class DMABufEGLProvider : public std::enable_shared_from_this +{ +public: + using EGLImageAllocator = + std::function(DRMFormat, std::span, geometry::Size)>; + DMABufEGLProvider( + EGLDisplay dpy, + std::shared_ptr egl_extensions, + EGLExtensions::EXTImageDmaBufImportModifiers const& dmabuf_ext, + std::shared_ptr egl_delegate, + EGLImageAllocator allocate_importable_image); + + ~DMABufEGLProvider(); + + auto import_dma_buf( + DMABufBuffer const& dma_buf, + std::function&& on_consumed, + std::function&& on_release) + -> std::shared_ptr; + + /** + * Validate that this provider *can* import this DMA-BUF + * + * @param dma_buf + * \throws EGL exception on failure + */ + void validate_import(DMABufBuffer const& dma_buf); + + auto as_texture( + std::shared_ptr buffer) + -> std::shared_ptr; + + auto supported_formats() const -> DmaBufFormatDescriptors const&; +private: + EGLDisplay const dpy; + std::shared_ptr const egl_extensions; + std::optional const dmabuf_export_ext; + std::unique_ptr const formats; + std::shared_ptr const egl_delegate; + EGLImageAllocator allocate_importable_image; + std::unique_ptr const blitter; +}; class LinuxDmaBufUnstable : public mir::wayland::LinuxDmabufV1::Global { public: LinuxDmaBufUnstable( wl_display* display, - EGLDisplay dpy, - std::shared_ptr egl_extensions, - EGLExtensions::EXTImageDmaBufImportModifiers const& dmabuf_ext); + std::shared_ptr provider); - std::shared_ptr buffer_from_resource( + auto buffer_from_resource( wl_resource* buffer, std::function&& on_consumed, std::function&& on_release, - std::shared_ptr egl_delegate); - + std::shared_ptr egl_delegate) + -> std::shared_ptr; private: class Instance; void bind(wl_resource* new_resource) override; - EGLDisplay const dpy; - std::shared_ptr const egl_extensions; - std::shared_ptr const formats; + std::shared_ptr const provider; }; } diff --git a/include/platform/mir/graphics/platform.h b/include/platform/mir/graphics/platform.h index c976c80fdf0..a0fe6450981 100644 --- a/include/platform/mir/graphics/platform.h +++ b/include/platform/mir/graphics/platform.h @@ -19,10 +19,16 @@ #include #include +#include +#include +#include "mir/graphics/drm_formats.h" #include "mir/module_properties.h" #include "mir/module_deleter.h" #include "mir/udev/wrapper.h" +#include "mir/renderer/sw/pixel_source.h" + +#include namespace mir { @@ -38,18 +44,128 @@ namespace options class Option; class ProgramOption; } +namespace renderer::software +{ +class WriteMappableBuffer; +} /// Graphics subsystem. Mediates interaction between core system and /// the graphics environment. namespace graphics { class Buffer; +class Framebuffer; class Display; +class DisplayBuffer; class DisplayReport; class DisplayConfigurationPolicy; class GraphicBufferAllocator; class GLConfig; +class DisplayInterfaceProvider; + +namespace probe +{ + /** + * A measure of how well a platform supports a device + * + * \note This is compared as an integer; best + 1 is a valid Result that + * will be used in preference to a module that reports best. + * Platform modules distributed with Mir will never use a priority higher + * than best. + */ + using Result = uint32_t; + Result const unsupported = 0; /**< Unable to function at all on this device */ + Result const dummy = 1; /**< Used only for dummy or stub platforms. + */ + Result const supported = 128; /**< Capable of providing a functionality on this device; + * possibly with degraded performance or features. + */ + Result const hosted = 192; /**< Capable of providing fully-featured functionality on this device; + * running nested under some other display server rather than with + * exclusive hardware access. + */ + Result const best = 256; /**< Capable of providing the best features and performance this device + * is capable of. + */ +} + +class RenderingProvider +{ +public: + class Tag + { + public: + Tag() = default; + virtual ~Tag() = default; + }; + + virtual ~RenderingProvider() = default; + + class FramebufferProvider + { + public: + FramebufferProvider() = default; + virtual ~FramebufferProvider() = default; + + FramebufferProvider(FramebufferProvider const&) = delete; + auto operator=(FramebufferProvider const&) = delete; + + /** + * Get a directly-displayable handle for a buffer, if cheaply possible + * + * Some buffer types can be passed as-is to the display hardware. If this + * buffer can be used in this way (on the DisplayInterfaceProvider associated + * with this FramebufferProvider), this method creates a handle that can be + * passed to the overlay method of an associated DisplayBuffer. + * + * \note The returned Framebuffer may share ownership of the provided Buffer. + * It is not necessary for calling code to retain a reference to the Buffer. + * \param buffer + * \return A handle to a directly submittable buffer, or nullptr if this buffer + is not pasable as-is to the display hardware. + */ + virtual auto buffer_to_framebuffer(std::shared_ptr buffer) + -> std::unique_ptr = 0; + }; + + /** + * Check how well this Renderer can support a particular display target + */ + virtual auto suitability_for_display(std::shared_ptr const& target) + -> probe::Result = 0; + + /** + * Check how well this Renderer can support a particular BufferAllocator + */ + virtual auto suitability_for_allocator(std::shared_ptr const& target) + -> probe::Result = 0; + + virtual auto make_framebuffer_provider(std::shared_ptr target) + -> std::unique_ptr = 0; +}; + +namespace gl +{ +class Texture; +class OutputSurface; +} + +class GLRenderingProvider : public RenderingProvider +{ +public: + class Tag : public RenderingProvider::Tag + { + }; + + virtual auto as_texture(std::shared_ptr buffer) -> std::shared_ptr = 0; + + virtual auto surface_for_output( + std::shared_ptr framebuffer_provider, + geometry::Size size, + GLConfig const& config) -> std::unique_ptr = 0; +}; + /** * \defgroup platform_enablement Mir platform enablement * @@ -71,11 +187,280 @@ class RenderingPlatform /** * Creates the buffer allocator subsystem. */ - virtual UniqueModulePtr create_buffer_allocator( - Display const& output) = 0; + virtual UniqueModulePtr create_buffer_allocator(Display const& output) = 0; + + /** + * Attempt to acquire a platform-specific interface from this RenderingPlatform + * + * Any given platform is not guaranteed to implement any specific interface, + * and the set of supported interfaces may depend on the runtime environment. + * + * Since this may result in a runtime probe the call may be costly, and the + * result should be saved rather than re-acquiring an interface each time. + * + * \tparam Provider + * \return On success: an occupied std::shared_ptr + * On failure: std::shared_ptr{nullptr} + */ + template + static auto acquire_provider(std::shared_ptr platform) -> std::shared_ptr + { + static_assert( + std::is_convertible_v, + "Can only acquire a Renderer interface; Provider must implement RenderingProvider"); + + if (auto const provider = platform->maybe_create_provider(typename Provider::Tag{})) + { + if (auto const requested = std::dynamic_pointer_cast(provider)) + { + return requested; + } + BOOST_THROW_EXCEPTION(( + std::logic_error{ + "Implementation error! Platform returned object that does not support requested interface"})); + } + return nullptr; + } + +protected: + /** + * Acquire a specific rendering interface + * + * This should perform any runtime checks necessary to verify the requested interface is + * expected to work and return a pointer to an implementation of that interface. + * + * This function is guaranteed to be called with `this` managed by a `shared_ptr`; if + * the returned value needs to share ownership with `this`, calls to std::shared_from_this + * can be expected to work. + * + * \param type_tag [in] An instance of the Tag type for the requested interface. + * Implementations are expected to dynamic_cast<> this to + * discover the specific interface being requested. + * \return On success: A pointer to an implementation of the RenderingProvider-derived + * interface that corresponds to the most-derived type of tag_type + * On failure: std::shared_ptr{nullptr} + */ + virtual auto maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr = 0; }; -class DisplayPlatform +class DisplayProvider +{ +public: + class Tag + { + public: + Tag() = default; + virtual ~Tag() = default; + }; + + virtual ~DisplayProvider() = default; +}; + + +class Framebuffer +{ +public: + Framebuffer() = default; + virtual ~Framebuffer() = default; + + /** + * The size of this framebuffer, in pixels + */ + virtual auto size() const -> geometry::Size = 0; +}; + + +class CPUAddressableDisplayProvider : public DisplayProvider +{ +public: + class Tag : public DisplayProvider::Tag + { + }; + + class MappableFB : + public Framebuffer, + public mir::renderer::software::WriteMappableBuffer + { + public: + MappableFB() = default; + virtual ~MappableFB() override = default; + }; + + virtual auto supported_formats() const + -> std::vector = 0; + + virtual auto alloc_fb(geometry::Size pixel_size, DRMFormat format) + -> std::unique_ptr = 0; +}; + +class GBMDisplayProvider : public DisplayProvider +{ +public: + class Tag : public DisplayProvider::Tag + { + }; + + /** + * Check if the provided UDev device is the same hardware device as this display + * + * This can be either because they point to the same device node, or because + * the provided device is a Rendernode associated with the display hardware + */ + virtual auto is_same_device(mir::udev::Device const& render_device) const -> bool = 0; + + /** + * Get the GBM device for this display + */ + virtual auto gbm_device() const -> std::shared_ptr = 0; + + /** + * Formats supported for output + */ + virtual auto supported_formats() const -> std::vector = 0; + + /** + * Modifiers supported + */ + virtual auto modifiers_for_format(DRMFormat format) const -> std::vector = 0; + + class GBMSurface + { + public: + GBMSurface() = default; + virtual ~GBMSurface() = default; + + virtual operator gbm_surface*() const = 0; + + /** + * Commit the current EGL front buffer as a KMS-displayable Framebuffer + * + * Like the underlying gbm_sufrace_lock_front_buffer GBM API, it is a this + * must be called after at least one call to eglSwapBuffers, and at most + * once per eglSwapBuffers call. + * + * The Framebuffer should not be retained; a GBMSurface has a limited number + * of buffers available and attempting to claim a framebuffer when no buffers + * are free will result in an EBUSY std::system_error being raised. + */ + virtual auto claim_framebuffer() -> std::unique_ptr = 0; + }; + + virtual auto make_surface(geometry::Size size, DRMFormat format, std::span modifiers) -> std::unique_ptr = 0; +}; + +class DmaBufBuffer; + +class DmaBufDisplayProvider : public DisplayProvider +{ +public: + class Tag : public DisplayProvider::Tag + { + }; + + virtual auto framebuffer_for(std::shared_ptr buffer) + -> std::unique_ptr = 0; +}; + +#ifndef EGLStreamKHR +typedef void* EGLStreamKHR; +#endif + +class EGLStreamDisplayProvider : public DisplayProvider +{ +public: + class Tag : public DisplayProvider::Tag + { + }; + + virtual auto get_egl_display() const -> EGLDisplay = 0; + + virtual auto claim_stream() -> EGLStreamKHR = 0; +}; + +class GenericEGLDisplayProvider : public DisplayProvider +{ +public: + class Tag : public DisplayProvider::Tag + { + }; + + virtual auto get_egl_display() -> EGLDisplay = 0; + + class EGLFramebuffer : public graphics::Framebuffer + { + public: + virtual void make_current() = 0; + virtual void release_current() = 0; + virtual auto clone_handle() -> std::unique_ptr = 0; + }; + + virtual auto alloc_framebuffer(GLConfig const& config, EGLContext share_context) -> std::unique_ptr = 0; +}; + +class DisplayInterfaceProvider : public std::enable_shared_from_this +{ +public: + DisplayInterfaceProvider() = default; + virtual ~DisplayInterfaceProvider() = default; + + DisplayInterfaceProvider(DisplayInterfaceProvider const&) = delete; + auto operator=(DisplayInterfaceProvider const&) -> DisplayInterfaceProvider& = delete; + + /** + * Attempt to acquire a platform-specific interface from this DisplayPlatform + * + * Any given platform is not guaranteed to implement any specific interface, + * and the set of supported interfaces may depend on the runtime environment. + * + * Since this may result in a runtime probe the call may be costly, and the + * result should be saved rather than re-acquiring an interface each time. + * + * \tparam Interface + * \return On success: an occupied std::shared_ptr + * On failure: std::shared_ptr{nullptr} + */ + template + auto acquire_interface() -> std::shared_ptr + { + static_assert( + std::is_convertible_v, + "Can only acquire a Display interface; Interface must implement DisplayProvider"); + + if (auto const base_interface = maybe_create_interface(typename Interface::Tag{})) + { + if (auto const requested_interface = std::dynamic_pointer_cast(base_interface)) + { + return requested_interface; + } + BOOST_THROW_EXCEPTION((std::logic_error{ + "Implementation error! Platform returned object that does not support requested interface"})); + } + return nullptr; + } + +protected: + /** + * Acquire a specific hardware interface + * + * This should perform any runtime checks necessary to verify the requested interface is + * expected to work and return a pointer to an implementation of that interface. + * + * This function is guaranteed to be called with `this` managed by a `shared_ptr`; if + * the returned value needs to share ownership with `this`, calls to `std::shared_from_this` + * can be expected to work. + * + * \param type_tag [in] An instance of the Tag type for the requested interface. + * Implementations are expected to dynamic_cast<> this to + * discover the specific interface being requested. + * \return A pointer to an implementation of the DisplayProvider-derived + * interface that corresponds to the most-derived type of tag_type. + */ + virtual auto maybe_create_interface(DisplayProvider::Tag const& type_tag) + -> std::shared_ptr = 0; +}; + +class DisplayPlatform : public std::enable_shared_from_this { public: DisplayPlatform() = default; @@ -90,31 +475,14 @@ class DisplayPlatform virtual UniqueModulePtr create_display( std::shared_ptr const& initial_conf_policy, std::shared_ptr const& gl_config) = 0; -}; -/** - * A measure of how well a platform supports a device - * - * \note This is compared as an integer; best + 1 is a valid PlatformPriority that - * will be used in preference to a module that reports best. - * Platform modules distributed with Mir will never use a priority higher - * than best. - */ -enum PlatformPriority : uint32_t -{ - unsupported = 0, /**< Unable to function at all on this device */ - dummy = 1, /**< Used only for dummy or stub platforms. - */ - supported = 128, /**< Capable of providing a functioning Platform on this device, - * possibly with degraded performance or features. - */ - hosted = 192, /**< Capable of providing a fully-featured Platform on this device, - * running nested under some other display server rather than with - * exclusive hardware access. - */ - best = 256 /**< Capable of providing a Platform with the best features and - * performance this device is capable of. - */ + static auto interface_for(std::shared_ptr platform) + -> std::shared_ptr + { + return platform->interface_for(); + } +protected: + virtual auto interface_for() -> std::shared_ptr = 0; }; struct SupportedDevice @@ -132,7 +500,7 @@ struct SupportedDevice * particular hardware. */ std::unique_ptr device; - PlatformPriority support_level; /**< How well the platform can support this device */ + probe::Result support_level; /**< How well the platform can support this device */ /** * Platform-private data from probing @@ -152,7 +520,7 @@ typedef mir::UniqueModulePtr(*CreateDisplayPlatf typedef mir::UniqueModulePtr(*CreateRenderPlatform)( mir::graphics::SupportedDevice const& device, - std::vector> const& displays, + std::vector> const& displays, mir::options::Option const& options, mir::EmergencyCleanupRegistry& emergency_cleanup_registry); @@ -164,6 +532,12 @@ typedef std::vector(*PlatformProbe)( std::shared_ptr const&, mir::options::ProgramOption const& options); +typedef std::vector(*RenderProbe)( + std::span> const&, + mir::ConsoleServices&, + std::shared_ptr const&, + mir::options::ProgramOption const&); + typedef mir::ModuleProperties const*(*DescribeModule)(); } } @@ -200,7 +574,7 @@ mir::UniqueModulePtr create_display_platform( mir::UniqueModulePtr create_rendering_platform( mir::graphics::SupportedDevice const& device, - std::vector> const& displays, + std::vector> const& displays, mir::options::Option const& options, mir::EmergencyCleanupRegistry& emergency_cleanup_registry); @@ -223,7 +597,8 @@ auto probe_display_platform( mir::options::ProgramOption const& options) -> std::vector; auto probe_rendering_platform( - std::shared_ptr const& console, + std::span> const& display_providers, + mir::ConsoleServices& console, std::shared_ptr const& udev, mir::options::ProgramOption const& options) -> std::vector; diff --git a/include/platform/mir/renderer/gl/gl_surface.h b/include/platform/mir/renderer/gl/gl_surface.h new file mode 100644 index 00000000000..852c3639611 --- /dev/null +++ b/include/platform/mir/renderer/gl/gl_surface.h @@ -0,0 +1,59 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_RENDERER_GL_SURFACE_H_ +#define MIR_RENDERER_GL_SURFACE_H_ + +#include "mir/geometry/size.h" +#include + +namespace mir +{ +namespace graphics +{ +class Framebuffer; + +namespace gl +{ +class OutputSurface +{ +public: + virtual ~OutputSurface() = default; + + virtual void bind() = 0; + + virtual void make_current() = 0; + virtual void release_current() = 0; + + // Naming: SwapBuffers? Commit? Claim current buffer? + virtual auto commit() -> std::unique_ptr = 0; + + /// Size, in pixels, of the underlying surface + virtual auto size() const -> mir::geometry::Size = 0; + + enum class Layout + { + TopRowFirst, //< First row has y-coördinate 0, y increases with each row. + BottomRowFirst, //< First row has y-coördinate $height, y decreases with each row + GL = BottomRowFirst //< GL texture layout is in decreasing-y order. + }; + virtual auto layout() const -> Layout = 0; +}; +} +} +} + +#endif //MIR_RENDERER_GL_SURFACE_H_ diff --git a/include/renderer/mir/renderer/renderer.h b/include/renderer/mir/renderer/renderer.h index 1c1ac7ef298..022995e5024 100644 --- a/include/renderer/mir/renderer/renderer.h +++ b/include/renderer/mir/renderer/renderer.h @@ -24,6 +24,11 @@ namespace mir { +namespace graphics +{ +class Framebuffer; +} + namespace renderer { @@ -34,7 +39,7 @@ class Renderer virtual void set_viewport(geometry::Rectangle const& rect) = 0; virtual void set_output_transform(glm::mat2 const&) = 0; - virtual void render(graphics::RenderableList const&) const = 0; + virtual auto render(graphics::RenderableList const&) const -> std::unique_ptr = 0; virtual void suspend() = 0; // called when render() is skipped protected: diff --git a/include/renderer/mir/renderer/renderer_factory.h b/include/renderer/mir/renderer/renderer_factory.h index df663f2ef43..bd0f30371ef 100644 --- a/include/renderer/mir/renderer/renderer_factory.h +++ b/include/renderer/mir/renderer/renderer_factory.h @@ -23,7 +23,11 @@ namespace mir { namespace graphics { -struct DisplayBuffer; +class GLRenderingProvider; +namespace gl +{ +class OutputSurface; +} } namespace renderer { @@ -39,7 +43,9 @@ class RendererFactory public: virtual ~RendererFactory() = default; - virtual std::unique_ptr create_renderer_for(gl::RenderTarget& render_target) = 0; + virtual auto create_renderer_for( + std::unique_ptr output_surface, + std::shared_ptr gl_provider) const -> std::unique_ptr = 0; protected: RendererFactory() = default; diff --git a/include/renderers/gl/mir/renderer/gl/basic_buffer_render_target.h b/include/renderers/gl/mir/renderer/gl/basic_buffer_render_target.h deleted file mode 100644 index ac351f68810..00000000000 --- a/include/renderers/gl/mir/renderer/gl/basic_buffer_render_target.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 or 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef MIR_RENDERER_GL_BASIC_BUFFER_RENDER_TARGET_H_ -#define MIR_RENDERER_GL_BASIC_BUFFER_RENDER_TARGET_H_ - -#include "buffer_render_target.h" - -#include -#include - -namespace mir -{ -namespace renderer -{ -namespace gl -{ -class Context; - -/// Not threadsafe, do not use concurrently -class BasicBufferRenderTarget: public BufferRenderTarget -{ -public: - BasicBufferRenderTarget(std::shared_ptr const& ctx); - - void set_buffer(std::shared_ptr const& buffer) override; - - auto size() const -> geometry::Size override; - void make_current() override; - void release_current() override; - void swap_buffers() override; - void bind() override; - -private: - class Framebuffer - { - public: - Framebuffer(geometry::Size const& size); - ~Framebuffer(); - void copy_to(software::WriteMappableBuffer& buffer); - void bind(); - - geometry::Size const size; - - private: - Framebuffer(Framebuffer const&) = delete; - Framebuffer& operator=(Framebuffer const&) = delete; - - GLuint colour_buffer; - GLuint fbo; - }; - - std::shared_ptr const ctx; - - std::shared_ptr buffer{nullptr}; - std::optional framebuffer; -}; - -} -} -} - -#endif // MIR_RENDERER_GL_BASIC_BUFFER_RENDER_TARGET_H_ diff --git a/include/renderers/gl/mir/renderer/gl/buffer_render_target.h b/include/renderers/gl/mir/renderer/gl/buffer_render_target.h deleted file mode 100644 index 3d71b013a66..00000000000 --- a/include/renderers/gl/mir/renderer/gl/buffer_render_target.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 or 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef MIR_RENDERER_GL_BUFFER_RENDER_TARGET_H_ -#define MIR_RENDERER_GL_BUFFER_RENDER_TARGET_H_ - -#include "render_target.h" -#include "mir/geometry/size.h" - -#include - -namespace mir -{ -namespace renderer -{ -namespace software -{ -class WriteMappableBuffer; -} -namespace gl -{ - -/// Not threadsafe, do not use concurrently -class BufferRenderTarget: public RenderTarget -{ -public: - virtual void set_buffer(std::shared_ptr const& buffer) = 0; -}; - -} -} -} - -#endif // MIR_RENDERER_GL_BUFFER_RENDER_TARGET_H_ diff --git a/include/renderers/gl/mir/renderer/gl/context.h b/include/renderers/gl/mir/renderer/gl/context.h index 7c4513c909f..a1fa970947c 100644 --- a/include/renderers/gl/mir/renderer/gl/context.h +++ b/include/renderers/gl/mir/renderer/gl/context.h @@ -17,6 +17,9 @@ #ifndef MIR_RENDERER_GL_CONTEXT_H_ #define MIR_RENDERER_GL_CONTEXT_H_ +#include +#include + namespace mir { namespace renderer @@ -32,6 +35,13 @@ class Context virtual void make_current() const = 0; virtual void release_current() const = 0; + /** + * Create an EGL context that shares this context's sharable-data + */ + virtual auto make_share_context() const -> std::unique_ptr = 0; + + explicit virtual operator EGLContext() = 0; + protected: Context() = default; Context(Context const&) = delete; diff --git a/include/test/mir/test/doubles/null_gl_context.h b/include/test/mir/test/doubles/null_gl_context.h index e602313cdc3..bfa4df2c24a 100644 --- a/include/test/mir/test/doubles/null_gl_context.h +++ b/include/test/mir/test/doubles/null_gl_context.h @@ -29,8 +29,10 @@ namespace doubles class NullGLContext : public renderer::gl::Context { public: - void make_current() const {} - void release_current() const {} + void make_current() const override {} + void release_current() const override {} + auto make_share_context() const -> std::unique_ptr override { return {}; } + explicit operator EGLContext() override { return EGL_NO_DISPLAY; } }; } diff --git a/include/test/mir_test_framework/headless_display_buffer_compositor_factory.h b/include/test/mir_test_framework/headless_display_buffer_compositor_factory.h index 654c89390f9..12b3276a206 100644 --- a/include/test/mir_test_framework/headless_display_buffer_compositor_factory.h +++ b/include/test/mir_test_framework/headless_display_buffer_compositor_factory.h @@ -18,16 +18,30 @@ #define MIR_TEST_FRAMEWORK_HEADLESS_DISPLAY_BUFFER_COMPOSITOR_FACTORY_H_ #include "mir/compositor/display_buffer_compositor_factory.h" +namespace mir::graphics +{ +class GLRenderingProvider; +class GLConfig; +} + namespace mir_test_framework { class PassthroughTracker; struct HeadlessDisplayBufferCompositorFactory : mir::compositor::DisplayBufferCompositorFactory { - HeadlessDisplayBufferCompositorFactory(); - HeadlessDisplayBufferCompositorFactory(std::shared_ptr const& tracker); + HeadlessDisplayBufferCompositorFactory( + std::shared_ptr render_platform, + std::shared_ptr gl_config); + HeadlessDisplayBufferCompositorFactory( + std::shared_ptr render_platform, + std::shared_ptr gl_config, + std::shared_ptr const& tracker); + std::unique_ptr create_compositor_for( mir::graphics::DisplayBuffer& display_buffer) override; private: + std::shared_ptr const render_platform; + std::shared_ptr const gl_config; std::shared_ptr const tracker; }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd32449ba72..4425ea9101d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ # We need MIRPLATFORM_ABI in both libmirplatform and the platform implementations. -set(MIRPLATFORM_ABI 26) +set(MIRPLATFORM_ABI 27) set(MIRAL_VERSION_MAJOR 4) set(MIRAL_VERSION_MINOR 1) diff --git a/src/include/server/mir/graphics/default_display_configuration_policy.h b/src/include/server/mir/graphics/default_display_configuration_policy.h index fca7812ff09..c81ed612523 100644 --- a/src/include/server/mir/graphics/default_display_configuration_policy.h +++ b/src/include/server/mir/graphics/default_display_configuration_policy.h @@ -29,21 +29,24 @@ namespace graphics class CloneDisplayConfigurationPolicy : public DisplayConfigurationPolicy { public: - void apply_to(DisplayConfiguration& conf); + void apply_to(DisplayConfiguration& conf) override; + void confirm(DisplayConfiguration const& conf) override; }; /// Each screen placed to the right of the previous one class SideBySideDisplayConfigurationPolicy : public DisplayConfigurationPolicy { public: - void apply_to(graphics::DisplayConfiguration& conf); + void apply_to(graphics::DisplayConfiguration& conf) override; + void confirm(DisplayConfiguration const& conf) override; }; /// Just use the first screen class SingleDisplayConfigurationPolicy : public DisplayConfigurationPolicy { public: - void apply_to(graphics::DisplayConfiguration& conf); + void apply_to(graphics::DisplayConfiguration& conf) override; + void confirm(DisplayConfiguration const& conf) override; }; /** @} */ } diff --git a/src/include/server/mir/input/vt_filter.h b/src/include/server/mir/input/vt_filter.h index 2970de0396f..f42a3b6a4ef 100644 --- a/src/include/server/mir/input/vt_filter.h +++ b/src/include/server/mir/input/vt_filter.h @@ -32,6 +32,7 @@ class VTFilter : public EventFilter { public: VTFilter(std::unique_ptr switcher); + ~VTFilter(); bool handle(MirEvent const& event) override; diff --git a/src/miral/display_configuration_option.cpp b/src/miral/display_configuration_option.cpp index 842caf674b0..47f6dba523a 100644 --- a/src/miral/display_configuration_option.cpp +++ b/src/miral/display_configuration_option.cpp @@ -55,6 +55,7 @@ class PixelFormatSelector : public mg::DisplayConfigurationPolicy public: PixelFormatSelector(std::shared_ptr const& base_policy, bool with_alpha); virtual void apply_to(mg::DisplayConfiguration& conf); + virtual void confirm(mg::DisplayConfiguration const& conf); private: std::shared_ptr const base_policy; bool const with_alpha; @@ -98,6 +99,11 @@ void PixelFormatSelector::apply_to(mg::DisplayConfiguration& conf) }); } +void PixelFormatSelector::confirm(mg::DisplayConfiguration const& conf) +{ + base_policy->confirm(conf); +} + class ScaleSetter : public mg::DisplayConfigurationPolicy { public: @@ -108,6 +114,7 @@ class ScaleSetter : public mg::DisplayConfigurationPolicy } void apply_to(mg::DisplayConfiguration& conf) override; + void confirm(mg::DisplayConfiguration const& conf) override; private: std::shared_ptr const base_policy; float const with_scale; @@ -123,6 +130,11 @@ void ScaleSetter::apply_to(mg::DisplayConfiguration& conf) }); } +void ScaleSetter::confirm(mg::DisplayConfiguration const& conf) +{ + base_policy->confirm(conf); +} + class AutoscaleSetter : public mg::DisplayConfigurationPolicy { public: @@ -133,6 +145,7 @@ class AutoscaleSetter : public mg::DisplayConfigurationPolicy } void apply_to(mg::DisplayConfiguration& conf) override; + void confirm(mg::DisplayConfiguration const& conf) override; private: void apply_to(mg::UserDisplayConfigurationOutput& output); std::shared_ptr const base_policy; @@ -146,6 +159,11 @@ void AutoscaleSetter::apply_to(mg::DisplayConfiguration& conf) [this](mg::UserDisplayConfigurationOutput& output) { if (output.connected) apply_to(output); }); } +void AutoscaleSetter::confirm(mg::DisplayConfiguration const& conf) +{ + base_policy->confirm(conf); +} + void AutoscaleSetter::apply_to(mg::UserDisplayConfigurationOutput& output) { auto const output_height = diff --git a/src/miral/static_display_config.cpp b/src/miral/static_display_config.cpp index 73be9049f50..0e2ebdc2f45 100644 --- a/src/miral/static_display_config.cpp +++ b/src/miral/static_display_config.cpp @@ -328,6 +328,10 @@ void miral::YamlFileDisplayConfig::apply_to(mg::DisplayConfiguration& conf) mir::log_info(out.str()); } +void miral::YamlFileDisplayConfig::confirm(mir::graphics::DisplayConfiguration const&) +{ +} + void miral::YamlFileDisplayConfig::apply_default_configuration(mg::DisplayConfiguration& conf) { conf.for_each_output([config=Config{}](mg::UserDisplayConfigurationOutput& conf_output) @@ -549,7 +553,7 @@ void miral::ReloadingYamlFileDisplayConfig::config_path(std::string newpath) check_for_layout_override(); } -void miral::ReloadingYamlFileDisplayConfig::apply_to(mir::graphics::DisplayConfiguration& conf) +void miral::ReloadingYamlFileDisplayConfig::confirm(mir::graphics::DisplayConfiguration const& conf) { std::lock_guard lock{mutex}; if (!config_path_) @@ -584,8 +588,6 @@ void miral::ReloadingYamlFileDisplayConfig::apply_to(mir::graphics::DisplayConfi filename.c_str()); } } - - YamlFileDisplayConfig::apply_to(conf); } auto miral::ReloadingYamlFileDisplayConfig::the_main_loop() const -> std::shared_ptr diff --git a/src/miral/static_display_config.h b/src/miral/static_display_config.h index b01527b7f4a..1df3a1cd984 100644 --- a/src/miral/static_display_config.h +++ b/src/miral/static_display_config.h @@ -45,6 +45,7 @@ class YamlFileDisplayConfig : public mir::graphics::DisplayConfigurationPolicy void load_config(std::istream& config_file, std::string const& filename); void apply_to(mir::graphics::DisplayConfiguration& conf) override; + virtual void confirm(mir::graphics::DisplayConfiguration const& conf) override; void select_layout(std::string const& layout); @@ -94,7 +95,7 @@ class ReloadingYamlFileDisplayConfig : public YamlFileDisplayConfig void config_path(std::string newpath); - void apply_to(mir::graphics::DisplayConfiguration& conf) override; + void confirm(mir::graphics::DisplayConfiguration const& conf) override; void check_for_layout_override(); diff --git a/src/miroil/CMakeLists.txt b/src/miroil/CMakeLists.txt index 6142a8e8b88..26baa539b82 100644 --- a/src/miroil/CMakeLists.txt +++ b/src/miroil/CMakeLists.txt @@ -1,4 +1,4 @@ -set(MIROIL_ABI 3) +set(MIROIL_ABI 4) set(MIROIL_VERSION_MAJOR ${MIROIL_ABI}) set(MIROIL_VERSION_MINOR 0) set(MIROIL_VERSION_PATCH 0) diff --git a/src/miroil/persist_display_config.cpp b/src/miroil/persist_display_config.cpp index 4426163f4f1..404e008d976 100644 --- a/src/miroil/persist_display_config.cpp +++ b/src/miroil/persist_display_config.cpp @@ -77,6 +77,10 @@ struct DisplayConfigurationPolicyAdapter : mg::DisplayConfigurationPolicy self->apply_to(conf, *wrapped_policy, *custom_policy); } + void confirm(mg::DisplayConfiguration const&) override + { + } + std::shared_ptr const self; std::shared_ptr const wrapped_policy; std::shared_ptr const custom_policy; diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index de41bed4e98..53188ca559a 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -71,6 +71,8 @@ target_link_libraries(mirplatform PRIVATE mircommon ${MIR_PLATFORM_REFERENCES} + PUBLIC + PkgConfig::EPOXY ) set_target_properties( diff --git a/src/platform/graphics/CMakeLists.txt b/src/platform/graphics/CMakeLists.txt index 9135e722bf9..3f29e523e90 100644 --- a/src/platform/graphics/CMakeLists.txt +++ b/src/platform/graphics/CMakeLists.txt @@ -32,6 +32,8 @@ add_library(mirplatformgraphicscommon OBJECT ${PROJECT_SOURCE_DIR}/include/platform/mir/graphics/drm_formats.h ${PROJECT_SOURCE_DIR}/include/platform/mir/graphics/egl_context_executor.h egl_context_executor.cpp + egl_buffer_copy.h + egl_buffer_copy.cpp ) mir_generate_protocol_wrapper(mirplatformgraphicscommon "zwp_" linux-dmabuf-unstable-v1.xml) diff --git a/src/platform/graphics/drm_formats.cpp b/src/platform/graphics/drm_formats.cpp index 257c26dc623..d1923a2b383 100644 --- a/src/platform/graphics/drm_formats.cpp +++ b/src/platform/graphics/drm_formats.cpp @@ -15,6 +15,7 @@ */ #include "mir/graphics/drm_formats.h" +#include "mir_toolkit/common.h" #include #include @@ -528,7 +529,49 @@ auto mg::DRMFormat::as_mir_format() const -> std::optional default: return std::nullopt; } -} +} + +auto mg::DRMFormat::from_mir_format(MirPixelFormat format) + -> DRMFormat +{ + switch (format) + { + case mir_pixel_format_argb_8888: + return DRMFormat{DRM_FORMAT_ARGB8888}; + case mir_pixel_format_xrgb_8888: + return DRMFormat{DRM_FORMAT_XRGB8888}; + case mir_pixel_format_abgr_8888: + return DRMFormat{DRM_FORMAT_ABGR8888}; + case mir_pixel_format_xbgr_8888: + return DRMFormat{DRM_FORMAT_XBGR8888}; + case mir_pixel_format_bgr_888: + return DRMFormat{DRM_FORMAT_BGR888}; + case mir_pixel_format_rgb_888: + return DRMFormat{DRM_FORMAT_RGB888}; + case mir_pixel_format_rgb_565: + return DRMFormat{DRM_FORMAT_RGB565}; + case mir_pixel_format_rgba_5551: + return DRMFormat{DRM_FORMAT_RGBA5551}; + case mir_pixel_format_rgba_4444: + return DRMFormat{DRM_FORMAT_RGBA4444}; + case mir_pixel_format_invalid: + /* We *could* do something with DRM_FORMAT_INVALID here, but + * let's not. Let's maintain that DRMFormat is always valid + */ + BOOST_THROW_EXCEPTION((std::runtime_error{"Attempt to look up DRM format info of invalid pixel format"})); + case mir_pixel_formats: + BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to look up format info of sentinel pixel format"})); +#ifndef __clang__ + /* Clang accepts that the above cases are exhaustive, gcc does not + * Have a default clause here to pacify gcc, but omit it for clang + * so that we fail to build if the above is *not* exhaustive. + */ + default: + BOOST_THROW_EXCEPTION((std::logic_error{"Exhaustive switch needs updating"})); +#endif + } +} + auto mg::drm_modifier_to_string(uint64_t modifier) -> std::string { diff --git a/src/platform/graphics/egl_buffer_copy.cpp b/src/platform/graphics/egl_buffer_copy.cpp new file mode 100644 index 00000000000..f5e5e781e33 --- /dev/null +++ b/src/platform/graphics/egl_buffer_copy.cpp @@ -0,0 +1,391 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "egl_buffer_copy.h" +#include "mir/graphics/egl_context_executor.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/egl_extensions.h" + +#include +#include +#include +#include +#include +#include + +#define MIR_LOG_COMPONENT "egl-buffer-copy" +#include "mir/log.h" + +namespace mg = mir::graphics; + +namespace +{ +const GLchar* const vshader = + { + "attribute vec4 position;\n" + "attribute vec2 texcoord;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_Position = position;\n" + " v_texcoord = texcoord;\n" + "}\n" + }; + +const GLchar* const fshader = + { + "#ifdef GL_ES\n" + "precision mediump float;\n" + "#endif\n" + "uniform sampler2D tex;" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(tex, v_texcoord);\n" + "}\n" + }; + +template +class GLBulkHandle +{ +public: + GLBulkHandle() + { + (**allocator)(1, &id); + } + + ~GLBulkHandle() + { + if (id) + (**deleter)(1, &id); + } + + GLBulkHandle(GLBulkHandle const&) = delete; + GLBulkHandle& operator=(GLBulkHandle const&) = delete; + GLBulkHandle& operator=(GLBulkHandle&& rhs) + { + std::swap(id, rhs.id); + return *this; + } + + GLBulkHandle(GLBulkHandle&& from) + : id{std::exchange(from.id, 0)} + { + } + + operator GLuint() const + { + return id; + } + +private: + GLuint id; +}; + +template +class GLTypedHandle +{ +public: + GLTypedHandle(GLenum type) + : id{(**allocator)(type)} + { + } + + ~GLTypedHandle() + { + if (id) + (**deleter)(id); + } + + GLTypedHandle(GLTypedHandle const&) = delete; + GLTypedHandle& operator=(GLTypedHandle const&) = delete; + GLTypedHandle& operator=(GLTypedHandle&& rhs) + { + std::swap(id, rhs.id); + return *this; + } + + GLTypedHandle(GLTypedHandle&& from) + : id{std::exchange(from.id, 0)} + { + } + + operator GLuint() const + { + return id; + } + +private: + GLuint id; +}; + +template +class GLHandle +{ +public: + GLHandle() + : id{(**allocator)()} + { + } + + ~GLHandle() + { + if (id) + (**deleter)(id); + } + + GLHandle(GLHandle const&) = delete; + GLHandle& operator=(GLHandle const&) = delete; + GLHandle& operator=(GLHandle&& rhs) + { + std::swap(id, rhs.id); + return *this; + } + + GLHandle(GLHandle&& from) + : id{std::exchange(from.id, 0)} + { + } + + operator GLuint() const + { + return id; + } + +private: + GLuint id; +}; + +using TextureHandle = GLBulkHandle<&glGenTextures, &glDeleteTextures>; +using GLBufferHandle = GLBulkHandle<&glGenBuffers, &glDeleteBuffers>; + +using RenderbufferHandle = GLBulkHandle<&glGenRenderbuffers, &glDeleteRenderbuffers>; +using FramebufferHandle = GLBulkHandle<&glGenFramebuffers, &glDeleteFramebuffers>; + +using ProgramHandle = GLHandle<&glCreateProgram, &glDeleteProgram>; +using ShaderHandle = GLTypedHandle<&glCreateShader, &glDeleteShader>; + +ShaderHandle compile_shader(GLenum type, GLchar const* src) +{ + auto id = ShaderHandle(type); + if (!id) + { + BOOST_THROW_EXCEPTION(mg::gl_error("Failed to create shader")); + } + + glShaderSource(id, 1, &src, NULL); + glCompileShader(id); + GLint ok; + glGetShaderiv(id, GL_COMPILE_STATUS, &ok); + if (!ok) + { + GLchar log[1024] = "(No log info)"; + glGetShaderInfoLog(id, sizeof log, NULL, log); + glDeleteShader(id); + BOOST_THROW_EXCEPTION( + std::runtime_error( + std::string("Compile failed: ") + log + " for:\n" + src)); + } + return id; +} + +ProgramHandle link_shader( + ShaderHandle const& vertex_shader, + ShaderHandle const& fragment_shader) +{ + ProgramHandle program; + glAttachShader(program, fragment_shader); + glAttachShader(program, vertex_shader); + glLinkProgram(program); + GLint ok; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (!ok) + { + GLchar log[1024]; + glGetProgramInfoLog(program, sizeof log - 1, NULL, log); + log[sizeof log - 1] = '\0'; + BOOST_THROW_EXCEPTION( + std::runtime_error( + std::string("Linking GL shader failed: ") + log)); + } + + return program; +} +} + +class mg::EGLBufferCopier::Impl +{ +public: + Impl(std::shared_ptr egl_delegate) + : egl_delegate{std::move(egl_delegate)} + { + auto exception_catcher = std::make_shared>(); + auto initialised = exception_catcher->get_future(); + this->egl_delegate->spawn( + [this, exception_catcher = std::move(exception_catcher)]() + { + try + { + initialise(); + exception_catcher->set_value(); + } + catch(std::exception const& err) + { + exception_catcher->set_exception(std::make_exception_ptr(err)); + } + }); + initialised.get(); + } + + ~Impl() + { + // Ensure EGL resources are cleaned up in the right context + egl_delegate->spawn([state = state]() mutable { state = nullptr; }); + } + + auto blit(EGLImage from, EGLImage to, geometry::Size size) -> std::optional + { + /* TODO: It *must* be possible to create the fence FD first and *then* + * insert it into the command stream! That would let us immediately return + * the fd without waiting for the EGL thread. + */ + auto sync_promise = std::make_shared>>(); + auto sync = sync_promise->get_future(); + egl_delegate->spawn( + [sync = std::move(sync_promise), from, to, state = state, size]() + { + glUseProgram(state->prog); + + TextureHandle tex; + RenderbufferHandle renderbuffer; + FramebufferHandle fbo; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex); + state->glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, from); + + 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_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + state->glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, to); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + + glBindBuffer(GL_ARRAY_BUFFER, state->vert_data); + glVertexAttribPointer (state->attrpos, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glBindBuffer(GL_ARRAY_BUFFER, state->tex_data); + glVertexAttribPointer (state->attrtex, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glEnableVertexAttribArray(state->attrpos); + glEnableVertexAttribArray(state->attrtex); + + glViewport(0, 0, size.width.as_int(), size.height.as_int()); + GLubyte const idx[] = { 0, 1, 3, 2 }; + glDrawElements (GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx); + + // TODO: Actually use the sync + glFinish(); + sync->set_value({}); + + // Unbind all our resources + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + }); + return sync.get(); + } +private: + std::shared_ptr const egl_delegate; + // State accessed only on the EGL thread + std::shared_ptr const egl_extensions; + struct State + { + ProgramHandle prog; + GLint attrtex; + GLint attrpos; + GLBufferHandle vert_data; + GLBufferHandle tex_data; + PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES; + PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES; + }; + /* This has to be initialised on the EGL thread, but it contains non-default-initialisable state. + * + * We use a shared_ptr to avoid worrying about making sure we outlive any invocations on the EGL thread. + * We don't need any synchronisation because state is only ever accessed on EGLContextExecutor, which + * is single-threaded. + */ + std::shared_ptr state; + + // Called once, from the EGL thread + void initialise() + { + state = std::make_shared(); + + // Check necessary EGL extensions + if (!strstr(reinterpret_cast(glGetString(GL_EXTENSIONS)), "GL_OES_EGL_image")) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Missing required GL_OES_EGL_image extension"})); + } + state->glEGLImageTargetRenderbufferStorageOES = + reinterpret_cast(eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES")); + state->glEGLImageTargetTexture2DOES = + reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES")); + + state->prog = link_shader(compile_shader(GL_VERTEX_SHADER, vshader), compile_shader(GL_FRAGMENT_SHADER, fshader)); + + glUseProgram(state->prog); + + state->attrpos = glGetAttribLocation(state->prog, "position"); + state->attrtex = glGetAttribLocation(state->prog, "texcoord"); + + auto unitex = glGetUniformLocation(state->prog, "tex"); + + glUniform1i(unitex, 0); + + static GLfloat const dest_vert[4][2] = + { { -1.f, 1.f }, { 1.f, 1.f }, { 1.f, -1.f }, { -1.f, -1.f } }; + glBindBuffer(GL_ARRAY_BUFFER, state->vert_data); + glBufferData(GL_ARRAY_BUFFER, sizeof(dest_vert), dest_vert, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + static GLfloat const tex_vert[4][2] = + { + { 0.f, 0.f }, { 1.f, 0.f }, { 1.f, 1.f }, { 0.f, 1.f }, + }; + glBindBuffer(GL_ARRAY_BUFFER, state->tex_data); + glBufferData(GL_ARRAY_BUFFER, sizeof(tex_vert), tex_vert, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } +}; + +mg::EGLBufferCopier::EGLBufferCopier(std::shared_ptr egl_delegate) + : impl{std::make_unique(std::move(egl_delegate))} +{ +} + +mg::EGLBufferCopier::~EGLBufferCopier() = default; + +auto mg::EGLBufferCopier::blit(EGLImage src, EGLImage dest, geometry::Size size) -> std::optional +{ + return impl->blit(src, dest, size); +} diff --git a/src/platform/graphics/egl_buffer_copy.h b/src/platform/graphics/egl_buffer_copy.h new file mode 100644 index 00000000000..ce65c01d02f --- /dev/null +++ b/src/platform/graphics/egl_buffer_copy.h @@ -0,0 +1,49 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "mir/graphics/egl_context_executor.h" +#include "mir/fd.h" +#include "mir/geometry/size.h" + +#include +#include + +#include + +namespace mir::graphics +{ +class EGLBufferCopier +{ +public: + EGLBufferCopier(std::shared_ptr egl_delegate); + + ~EGLBufferCopier(); + /** + * Copy whole image from src to dest + * + * Both src and dest must be EGLImages sharing an EGLDisplay with + * the EGLContext in egl_delegate + * + * \returns An optional native fence object, as per EGL_ANDROID_native_fence_sync + * If the optional is empty, the copy has been completed before blit returns. + * Otherwise, the fence must be used for synchronisation. + */ + auto blit(EGLImage src, EGLImage dest, geometry::Size size) -> std::optional; +private: + class Impl; + std::unique_ptr const impl; +}; +} diff --git a/src/platform/graphics/egl_extensions.cpp b/src/platform/graphics/egl_extensions.cpp index 3b3f0289b4b..bfc077c4491 100644 --- a/src/platform/graphics/egl_extensions.cpp +++ b/src/platform/graphics/egl_extensions.cpp @@ -201,3 +201,30 @@ mg::EGLExtensions::EXTImageDmaBufImportModifiers::EXTImageDmaBufImportModifiers( std::runtime_error{"EGL_EXT_image_dma_buf_import_modifiers not supported"})); } } + +mg::EGLExtensions::MESADmaBufExport::MESADmaBufExport(EGLDisplay dpy) + : eglExportDMABUFImageMESA{ + reinterpret_cast( + eglGetProcAddress("eglExportDMABUFImageMESA"))}, + eglExportDMABUFImageQueryMESA{ + reinterpret_cast( + eglGetProcAddress("eglExportDMABUFImageQueryMESA"))} + { + if (!strstr(eglQueryString(dpy, EGL_EXTENSIONS), "EGL_MESA_image_dma_buf_export")) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Missing required EGL_MESA_image_dma_buf_export extension"})); + } + } + +auto mg::EGLExtensions::MESADmaBufExport::extension_if_supported(EGLDisplay dpy) -> std::optional +{ + try + { + return MESADmaBufExport{dpy}; + } + catch (std::runtime_error const&) + { + return std::nullopt; + } +} + diff --git a/src/platform/graphics/linux_dmabuf.cpp b/src/platform/graphics/linux_dmabuf.cpp index f5a750f711a..bd27f9f3f1d 100644 --- a/src/platform/graphics/linux_dmabuf.cpp +++ b/src/platform/graphics/linux_dmabuf.cpp @@ -14,16 +14,16 @@ * along with this program. If not, see . */ - #include "mir/graphics/linux_dmabuf.h" +#include "mir/fd.h" #include "mir/graphics/drm_formats.h" +#include "egl_buffer_copy.h" #include "wayland_wrapper.h" #include "mir/wayland/protocol_error.h" #include "mir/wayland/client.h" #include "mir/graphics/egl_extensions.h" #include "mir/graphics/egl_error.h" -#include "mir/renderer/gl/context.h" #include "mir/graphics/texture.h" #include "mir/graphics/program_factory.h" #include "mir/graphics/buffer.h" @@ -31,16 +31,18 @@ #include "mir/graphics/dmabuf_buffer.h" #include "mir/graphics/egl_context_executor.h" +#include +#include +#include +#include + #define MIR_LOG_COMPONENT "linux-dmabuf-import" #include "mir/log.h" - -#include -#include - #include #include #include +#include #include #include @@ -227,18 +229,295 @@ BufferGLDescription const ExternalOES = { "}\n" }; +namespace +{ +struct EGLPlaneAttribs +{ + EGLint fd; + EGLint offset; + EGLint pitch; + EGLint modifier_lo; + EGLint modifier_hi; +}; + +static constexpr std::array egl_attribs = { + EGLPlaneAttribs { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT + }, + EGLPlaneAttribs { + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT + }, + EGLPlaneAttribs { + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT + }, + EGLPlaneAttribs { + EGL_DMA_BUF_PLANE3_FD_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT + } +}; + +class DMABuf : public mg::DMABufBuffer +{ +public: + DMABuf( + mg::DRMFormat format, + std::optional modifier, + std::vector planes, + mg::gl::Texture::Layout layout, + geom::Size size) + : format_{format}, + modifier_{std::move(modifier)}, + planes_{std::move(planes)}, + layout_{layout}, + size_{size} + { + } + + auto format() const -> mg::DRMFormat override + { + return format_; + } + auto modifier() const -> std::optional override + { + return modifier_; + } + auto planes() const -> std::vector const& override + { + return planes_; + } + auto layout() const -> mg::gl::Texture::Layout override + { + return layout_; + } + auto size() const -> geom::Size override + { + return size_; + } +private: + mg::DRMFormat const format_; + std::optional const modifier_; + std::vector const planes_; + mg::gl::Texture::Layout const layout_; + 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); +} + +/** + * Reimport dmabufs into EGL + * + * This is necessary to call each time the buffer is re-submitted by the client, + * to ensure any state is properly synchronised. + * + * \return An EGLImageKHR handle to the imported + * \throws A std::system_error containing the EGL error on failure. + */ +auto import_egl_image( + int32_t width, + int32_t height, + mg::DRMFormat format, + std::optional modifier, + std::vector const& planes, + EGLDisplay dpy, + mg::EGLExtensions const& egl_extensions) -> EGLImageKHR +{ + std::vector attributes; + + attributes.push_back(EGL_WIDTH); + attributes.push_back(width); + attributes.push_back(EGL_HEIGHT); + attributes.push_back(height); + attributes.push_back(EGL_LINUX_DRM_FOURCC_EXT); + attributes.push_back(format); + + for(auto i = 0u; i < planes.size(); ++i) + { + auto const& attrib_names = egl_attribs[i]; + auto const& plane = planes[i]; + + attributes.push_back(attrib_names.fd); + attributes.push_back(static_cast(plane.dma_buf)); + attributes.push_back(attrib_names.offset); + attributes.push_back(plane.offset); + attributes.push_back(attrib_names.pitch); + attributes.push_back(plane.stride); + if (auto modifier_present = modifier) + { + attributes.push_back(attrib_names.modifier_lo); + attributes.push_back(modifier_present.value() & 0xFFFFFFFF); + attributes.push_back(attrib_names.modifier_hi); + attributes.push_back(modifier_present.value() >> 32); + } + } + attributes.push_back(EGL_NONE); + EGLImage image = egl_extensions.base(dpy).eglCreateImageKHR( + dpy, + EGL_NO_CONTEXT, + EGL_LINUX_DMA_BUF_EXT, + nullptr, + attributes.data()); + + if (image == EGL_NO_IMAGE_KHR) + { + auto const msg = planes.size() > 1 ? + "Failed to import supplied dmabufs" : + "Failed to import supplied dmabuf"; + BOOST_THROW_EXCEPTION((mg::egl_error(msg))); + } + + return image; +} + +/** + * Get a description of how to use a specified format/modifier pair in GL + * + * \return A pointer to a statically-allocated descriptor, or nullptr if + * this format/modifier pair is invalid. + */ +BufferGLDescription const* descriptor_for_format_and_modifiers( + mg::DRMFormat format, + uint64_t modifier, + mg::DMABufEGLProvider const& provider) +{ + auto const& formats = provider.supported_formats(); + for (auto i = 0u; i < formats.num_formats(); ++i) + { + auto const& [supported_format, modifiers, external_only] = formats[i]; + + for (auto j = 0u ; j < modifiers.size(); ++j) + { + auto const supported_modifier = modifiers[j]; + + if (static_cast(supported_format) == format && + modifier == supported_modifier) + { + if (external_only[j]) + { + return &ExternalOES; + } + return &Tex2D; + } + } + } + + /* The specification of zwp_linux_buffer_params_v1.add says: + * + * Warning: It should be an error if the format/modifier pair was not + * advertised with the modifier event. This is not enforced yet because + * some implementations always accept `DRM_FORMAT_MOD_INVALID`. Also + * version 2 of this protocol does not have the modifier event. + * + * So don't enforce the error for this case + */ + if (modifier == DRM_FORMAT_MOD_INVALID) + { + return &Tex2D; + } + return nullptr; +} + +} + /** * Holds on to all imported dmabuf buffers, and allows looking up by wl_buffer * * \note This is not threadsafe, and should only be accessed on the Wayland thread */ -class WlDmaBufBuffer : public mir::wayland::Buffer +class WlDmaBufBuffer : public mir::wayland::Buffer, public mir::graphics::DMABufBuffer { public: WlDmaBufBuffer( - EGLDisplay dpy, - std::shared_ptr egl_extensions, - BufferGLDescription const& desc, wl_resource* wl_buffer, int32_t width, int32_t height, @@ -247,39 +526,28 @@ class WlDmaBufBuffer : public mir::wayland::Buffer uint64_t modifier, std::vector plane_params) : Buffer(wl_buffer, Version<1>{}), - dpy{dpy}, - egl_extensions{std::move(egl_extensions)}, - desc{desc}, width{width}, height{height}, format_{format}, flags{flags}, modifier_{modifier}, - planes_{std::move(plane_params)}, - image{EGL_NO_IMAGE_KHR} + planes_{std::move(plane_params)} { - reimport_egl_image(); } - ~WlDmaBufBuffer() - { - if (image != EGL_NO_IMAGE_KHR) - { - egl_extensions->base(dpy).eglDestroyImageKHR(dpy, image); - } - } + ~WlDmaBufBuffer() = default; static auto maybe_dmabuf_from_wl_buffer(wl_resource* buffer) -> WlDmaBufBuffer* { return dynamic_cast(Buffer::from(buffer)); } - auto size() -> geom::Size + auto size() const -> geom::Size override { return {width, height}; } - auto layout() -> mg::gl::Texture::Layout + auto layout() const -> mg::gl::Texture::Layout override { if (flags & mw::LinuxBufferParamsV1::Flags::y_invert) { @@ -291,135 +559,26 @@ class WlDmaBufBuffer : public mir::wayland::Buffer } } - auto format() -> uint32_t + auto format() const -> mg::DRMFormat override { return format_; } - auto descriptor() const -> BufferGLDescription const& - { - return desc; - } - /** - * Reimport dmabufs into EGL - * - * This is necessary to call each time the buffer is re-submitted by the client, - * to ensure any state is properly synchronised. - * - * \return An EGLImageKHR handle to the imported - * \throws A std::system_error containing the EGL error on failure. - */ - auto reimport_egl_image() -> EGLImageKHR - { - std::vector attributes; - - attributes.push_back(EGL_WIDTH); - attributes.push_back(width); - attributes.push_back(EGL_HEIGHT); - attributes.push_back(height); - attributes.push_back(EGL_LINUX_DRM_FOURCC_EXT); - attributes.push_back(format()); - - for(auto i = 0u; i < planes_.size(); ++i) - { - auto const& attrib_names = egl_attribs[i]; - auto const& plane = planes()[i]; - - attributes.push_back(attrib_names.fd); - attributes.push_back(static_cast(plane.dma_buf)); - attributes.push_back(attrib_names.offset); - attributes.push_back(plane.offset); - attributes.push_back(attrib_names.pitch); - attributes.push_back(plane.stride); - if (modifier() != DRM_FORMAT_MOD_INVALID) - { - attributes.push_back(attrib_names.modifier_lo); - attributes.push_back(modifier() & 0xFFFFFFFF); - attributes.push_back(attrib_names.modifier_hi); - attributes.push_back(modifier() >> 32); - } - } - attributes.push_back(EGL_NONE); - if (image != EGL_NO_IMAGE_KHR) - { - egl_extensions->base(dpy).eglDestroyImageKHR(dpy, image); - } - image = egl_extensions->base(dpy).eglCreateImageKHR( - dpy, - EGL_NO_CONTEXT, - EGL_LINUX_DMA_BUF_EXT, - nullptr, - attributes.data()); - - if (image == EGL_NO_IMAGE_KHR) - { - auto const msg = planes_.size() > 1 ? - "Failed to import supplied dmabufs" : - "Failed to import supplied dmabuf"; - BOOST_THROW_EXCEPTION((mg::egl_error(msg))); - } - - return image; - } - - auto modifier() -> uint64_t + auto modifier() const -> std::optional override { return modifier_; } - auto planes() -> std::vector const& + auto planes() const -> std::vector const& override { return planes_; } private: - EGLDisplay const dpy; - std::shared_ptr const egl_extensions; - BufferGLDescription const& desc; int32_t const width, height; mg::DRMFormat const format_; uint32_t const flags; - uint64_t const modifier_; + std::optional const modifier_; std::vector const planes_; - EGLImageKHR image; - - struct EGLPlaneAttribs - { - EGLint fd; - EGLint offset; - EGLint pitch; - EGLint modifier_lo; - EGLint modifier_hi; - }; - static constexpr std::array egl_attribs = { - EGLPlaneAttribs { - EGL_DMA_BUF_PLANE0_FD_EXT, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, - EGL_DMA_BUF_PLANE0_PITCH_EXT, - EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT - }, - EGLPlaneAttribs { - EGL_DMA_BUF_PLANE1_FD_EXT, - EGL_DMA_BUF_PLANE1_OFFSET_EXT, - EGL_DMA_BUF_PLANE1_PITCH_EXT, - EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT - }, - EGLPlaneAttribs { - EGL_DMA_BUF_PLANE2_FD_EXT, - EGL_DMA_BUF_PLANE2_OFFSET_EXT, - EGL_DMA_BUF_PLANE2_PITCH_EXT, - EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT - }, - EGLPlaneAttribs { - EGL_DMA_BUF_PLANE3_FD_EXT, - EGL_DMA_BUF_PLANE3_OFFSET_EXT, - EGL_DMA_BUF_PLANE3_PITCH_EXT, - EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, - EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT - } - }; }; class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 @@ -427,14 +586,10 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 public: LinuxDmaBufParams( wl_resource* new_resource, - EGLDisplay dpy, - std::shared_ptr egl_extensions, - std::shared_ptr formats) + std::shared_ptr provider) : mir::wayland::LinuxBufferParamsV1(new_resource, Version<3>{}), consumed{false}, - dpy{dpy}, - egl_extensions{std::move(egl_extensions)}, - formats{std::move(formats)} + provider{std::move(provider)} { } @@ -445,9 +600,12 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 std::array planes; std::optional modifier; bool consumed; - EGLDisplay dpy; - std::shared_ptr egl_extensions; - std::shared_ptr const formats; + /* We don't *really* need to create an mg::Buffer from here, but we *do* need to validate + * that the dma-buf(s) can be succesfully imported to an EGLImage. + * + * The only way to ensure that is to actually import them. + */ + std::shared_ptr const provider; void add( mir::Fd fd, @@ -555,8 +713,9 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 return planes.cbegin() + plane_count; } - void validate_params(int32_t width, int32_t height, uint32_t /*format*/, uint32_t /*flags*/) + void validate_params(int32_t width, int32_t height, uint32_t format, uint32_t /*flags*/) { + // TODO: Validate flags if (width < 1 || height < 1) { BOOST_THROW_EXCEPTION(( @@ -565,61 +724,20 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 Error::invalid_dimensions, "Width %i or height %i invalid; both must be >= 1!", width, height})); - - // TODO: Validate format & flags - } - } - - BufferGLDescription const& descriptor_for_format_and_modifiers(mg::DRMFormat format) - { - /* The optional modifier is guaranteed to be engaged here, - * as the add() call fills it if it is unset, and validate_and_count_planes() - * has already checked that add() has been called at least once. - */ - auto const requested_modifier = modifier.value(); - for (auto i = 0u; i < formats->num_formats(); ++i) - { - auto const& [supported_format, modifiers, external_only] = (*formats)[i]; - - for (auto j = 0u ; j < modifiers.size(); ++j) - { - auto const supported_modifier = modifiers[j]; - - if (static_cast(supported_format) == format && - requested_modifier == supported_modifier) - { - if (external_only[j]) - { - return ExternalOES; - } - return Tex2D; - } - } } - - // The specification of zwp_linux_buffer_params_v1.add says: - // - // Warning: It should be an error if the format/modifier pair was not - // advertised with the modifier event. This is not enforced yet because - // some implementations always accept `DRM_FORMAT_MOD_INVALID`. Also - // version 2 of this protocol does not have the modifier event. - // - // So don't enforce the error for this case - if (requested_modifier == DRM_FORMAT_MOD_INVALID) + if (!descriptor_for_format_and_modifiers(mg::DRMFormat{format}, modifier.value(), *provider)) { - return Tex2D; + BOOST_THROW_EXCEPTION(( + mw::ProtocolError{ + resource, + Error::invalid_format, + "Client requested unsupported format/modifier combination %s/%s (%u/%u,%u)", + mg::DRMFormat{format}.name(), + mg::drm_modifier_to_string(*modifier).c_str(), + static_cast(format), + static_cast(*modifier >> 32), + static_cast(*modifier & 0xFFFFFFFF)})); } - - BOOST_THROW_EXCEPTION(( - mw::ProtocolError{ - resource, - Error::invalid_format, - "Client requested unsupported format/modifier combination %s/%s (%u/%u,%u)", - format.name(), - mg::drm_modifier_to_string(requested_modifier).c_str(), - static_cast(format), - static_cast(requested_modifier >> 32), - static_cast(requested_modifier & 0xFFFFFFFF)})); } void create(int32_t width, int32_t height, uint32_t format, uint32_t flags) override @@ -638,10 +756,7 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 auto const last_valid_plane = validate_and_count_planes(); mg::DRMFormat const drm_format{format}; - new WlDmaBufBuffer{ - dpy, - egl_extensions, - descriptor_for_format_and_modifiers(drm_format), + auto dma_buf = new WlDmaBufBuffer{ buffer_resource, width, height, @@ -649,6 +764,10 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 flags, modifier.value(), {planes.cbegin(), last_valid_plane}}; + + // We don't need to keep it around, but we do need to ensure that we *can* create a Buffer + // from this dma-buf + provider->validate_import(*dma_buf); send_created_event(buffer_resource); } catch (std::system_error const& err) @@ -681,10 +800,7 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 auto const last_valid_plane = validate_and_count_planes(); mg::DRMFormat const drm_format{format}; - new WlDmaBufBuffer{ - dpy, - egl_extensions, - descriptor_for_format_and_modifiers(drm_format), + auto dma_buf = new WlDmaBufBuffer{ buffer_id, width, height, @@ -692,6 +808,9 @@ class LinuxDmaBufParams : public mir::wayland::LinuxBufferParamsV1 flags, modifier.value(), {planes.cbegin(), last_valid_plane}}; + // We don't need to keep it around, but we do need to ensure that we *can* create a Buffer + // from this dma-buf + provider->validate_import(*dma_buf); } catch (std::system_error const& err) { @@ -723,66 +842,39 @@ GLuint get_tex_id() return tex; } -bool drm_format_has_alpha(uint32_t format) -{ - /* TODO: We should really have something like libweston/pixel-formats.h - * We've said this multiple times before, so 🤷 - */ - - switch (format) - { - /* This is only an optimisation, so pick a bunch of formats and hope that - * covers it… - */ - case DRM_FORMAT_XBGR2101010: - case DRM_FORMAT_BGRX1010102: - case DRM_FORMAT_XBGR8888: - case DRM_FORMAT_BGRX8888: - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_RGBX1010102: - case DRM_FORMAT_RGBX8888: - case DRM_FORMAT_XRGB8888: - return false; - default: - // TODO: I *think* true is a safe default, as it'll just mean we're blending something without alpha? - return true; - } -} - -class WaylandDmabufTexBuffer : - public mg::BufferBasic, - public mg::gl::Texture, - public mg::DMABufBuffer +class DMABufTex : public mg::gl::Texture { public: - // Note: Must be called with a current EGL context - WaylandDmabufTexBuffer( - WlDmaBufBuffer& source, - mg::EGLExtensions const& extensions, + DMABufTex( EGLDisplay dpy, - std::shared_ptr egl_delegate, - std::function&& on_consumed, - std::function&& on_release) + mg::EGLExtensions const& extensions, + mg::DMABufBuffer const& dma_buf, + BufferGLDescription const& descriptor, + std::shared_ptr egl_delegate) : tex{get_tex_id()}, - desc{source.descriptor()}, - on_consumed{std::move(on_consumed)}, - on_release{std::move(on_release)}, - size_{source.size()}, - layout_{source.layout()}, - has_alpha{drm_format_has_alpha(source.format())}, - planes_{source.planes()}, - modifier_{source.modifier()}, - fourcc{source.format()}, + desc{descriptor}, + layout_{dma_buf.layout()}, egl_delegate{std::move(egl_delegate)} { eglBindAPI(EGL_OPENGL_ES_API); - auto const target = source.descriptor().target; + auto const target = descriptor.target; + + EGLImage image = import_egl_image( + dma_buf.size().width.as_int(), + dma_buf.size().height.as_int(), + dma_buf.format(), + dma_buf.modifier(), + dma_buf.planes(), + dpy, + extensions); glBindTexture(target, tex); - extensions.base(dpy).glEGLImageTargetTexture2DOES(target, source.reimport_egl_image()); + extensions.base(dpy).glEGLImageTargetTexture2DOES(target, image); + // tex is now an EGLImage sibling, so we can free the EGLImage without // freeing the backing data. + extensions.base(dpy).eglDestroyImageKHR(dpy, image); glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -790,17 +882,85 @@ class WaylandDmabufTexBuffer : glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - ~WaylandDmabufTexBuffer() override + ~DMABufTex() override { egl_delegate->spawn( [tex = tex]() { - glDeleteTextures(1, &tex); + glDeleteTextures(1, &tex); }); + } + + mg::gl::Program const& shader(mg::gl::ProgramFactory& cache) const override + { + /* We rely on the fact that `desc` is a reference to a statically-allocated namespaced + * variable, and so taking the address will give us the address of the static instance, + * making the cache compile only once for each `desc`. + */ + return cache.compile_fragment_shader( + &desc, + desc.extension_fragment, + desc.fragment_fragment); + } + + Layout layout() const override + { + return layout_; + } + + void bind() override + { + glBindTexture(desc.target, tex); + } + void add_syncpoint() override + { + } +private: + GLuint const tex; + BufferGLDescription const& desc; + Layout const layout_; + std::shared_ptr const egl_delegate; +}; + +class DmabufTexBuffer : + public mg::BufferBasic, + public mg::DMABufBuffer +{ +public: + // Note: Must be called with a current EGL context + DmabufTexBuffer( + EGLDisplay dpy, + mg::EGLExtensions const& extensions, + mg::DMABufBuffer const& dma_buf, + BufferGLDescription const& descriptor, + std::shared_ptr provider, + std::shared_ptr egl_delegate, + std::function&& on_consumed, + std::function&& on_release) + : dpy{dpy}, + tex{dpy, extensions, dma_buf, descriptor, std::move(egl_delegate)}, + provider_{std::move(provider)}, + on_consumed{std::move(on_consumed)}, + on_release{std::move(on_release)}, + size_{dma_buf.size()}, + has_alpha{dma_buf.format().has_alpha()}, + planes_{dma_buf.planes()}, + modifier_{dma_buf.modifier()}, + format_{dma_buf.format()} + { + } + + ~DmabufTexBuffer() override + { on_release(); } + auto on_same_egl_display(EGLDisplay dpy) -> bool + { + return this->dpy == dpy; + } + mir::geometry::Size size() const override { return size_; @@ -808,6 +968,10 @@ class WaylandDmabufTexBuffer : MirPixelFormat pixel_format() const override { + if (auto mir_format = format_.as_mir_format()) + { + return mir_format.value(); + } // There's no way to implement this corectly… if (has_alpha) { @@ -824,39 +988,22 @@ class WaylandDmabufTexBuffer : return this; } - mir::graphics::gl::Program const& shader(mir::graphics::gl::ProgramFactory& cache) const override + auto as_texture() -> DMABufTex* { - /* We rely on the fact that `desc` is a reference to a statically-allocated namespaced - * variable, and so taking the address will give us the address of the static instance, - * making the cache compile only once for each `desc`. + /* We only get asked for a texture when the Renderer is about to + * texture from this buffer; it's a good indication that the buffer + * has been consumed. */ - return cache.compile_fragment_shader( - &desc, - desc.extension_fragment, - desc.fragment_fragment); - } - - Layout layout() const override - { - return layout_; - } - - void bind() override - { - glBindTexture(desc.target, tex); - - std::lock_guard lock(consumed_mutex); + std::lock_guard lock{consumed_mutex}; on_consumed(); on_consumed = [](){}; - } - void add_syncpoint() override - { + return &tex; } - auto drm_fourcc() const -> uint32_t override + auto format() const -> mg::DRMFormat override { - return fourcc; + return format_; } auto modifier() const -> std::optional override @@ -869,23 +1016,31 @@ class WaylandDmabufTexBuffer : return planes_; } + auto layout() const -> mg::gl::Texture::Layout override + { + return tex.layout(); + } + + auto provider() const -> std::shared_ptr + { + return provider_; + } private: - GLuint const tex; - BufferGLDescription const& desc; + EGLDisplay const dpy; + DMABufTex tex; + + std::shared_ptr const provider_; std::mutex consumed_mutex; std::function on_consumed; std::function const on_release; geom::Size const size_; - Layout const layout_; bool const has_alpha; std::vector const planes_; std::optional const modifier_; - uint32_t const fourcc; - - std::shared_ptr const egl_delegate; + mg::DRMFormat const format_; }; @@ -896,17 +1051,14 @@ class mg::LinuxDmaBufUnstable::Instance : public mir::wayland::LinuxDmabufV1 public: Instance( wl_resource* new_resource, - EGLDisplay dpy, - std::shared_ptr egl_extensions, - std::shared_ptr formats) + std::shared_ptr provider) : mir::wayland::LinuxDmabufV1(new_resource, Version<3>{}), - dpy{dpy}, - egl_extensions{std::move(egl_extensions)}, - formats{std::move(formats)} + provider{std::move(provider)} { - for (auto i = 0u; i < this->formats->num_formats(); ++i) + auto const& formats = this->provider->supported_formats(); + for (auto i = 0u; i < formats.num_formats(); ++i) { - auto [format, modifiers, external_only] = (*(this->formats))[i]; + auto [format, modifiers, external_only] = formats[i]; send_format_event(format); for (auto j = 0u; j < modifiers.size(); ++j) @@ -922,23 +1074,17 @@ class mg::LinuxDmaBufUnstable::Instance : public mir::wayland::LinuxDmabufV1 private: void create_params(struct wl_resource* params_id) override { - new LinuxDmaBufParams{params_id, dpy, egl_extensions, formats}; + new LinuxDmaBufParams{params_id, provider}; } - EGLDisplay const dpy; - std::shared_ptr const egl_extensions; - std::shared_ptr const formats; + std::shared_ptr const provider; }; mg::LinuxDmaBufUnstable::LinuxDmaBufUnstable( wl_display* display, - EGLDisplay dpy, - std::shared_ptr egl_extensions, - EGLExtensions::EXTImageDmaBufImportModifiers const& dmabuf_ext) + std::shared_ptr provider) : mir::wayland::LinuxDmabufV1::Global(display, Version<3>{}), - dpy{dpy}, - egl_extensions{std::move(egl_extensions)}, - formats{std::make_shared(dpy, dmabuf_ext)} + provider{std::move(provider)} { } @@ -946,16 +1092,13 @@ auto mg::LinuxDmaBufUnstable::buffer_from_resource( wl_resource* buffer, std::function&& on_consumed, std::function&& on_release, - std::shared_ptr egl_delegate) + std::shared_ptr /*egl_delegate*/) -> std::shared_ptr { if (auto dmabuf = WlDmaBufBuffer::maybe_dmabuf_from_wl_buffer(buffer)) { - return std::make_shared( + return provider->import_dma_buf( *dmabuf, - *egl_extensions, - dpy, - std::move(egl_delegate), std::move(on_consumed), std::move(on_release)); } @@ -964,5 +1107,187 @@ auto mg::LinuxDmaBufUnstable::buffer_from_resource( void mg::LinuxDmaBufUnstable::bind(wl_resource* new_resource) { - new LinuxDmaBufUnstable::Instance{new_resource, dpy, egl_extensions, formats}; + new LinuxDmaBufUnstable::Instance{new_resource, provider}; +} + +mg::DMABufEGLProvider::DMABufEGLProvider( + EGLDisplay dpy, + std::shared_ptr egl_extensions, + mg::EGLExtensions::EXTImageDmaBufImportModifiers const& dmabuf_ext, + std::shared_ptr egl_delegate, + EGLImageAllocator allocate_importable_image) + : dpy{dpy}, + egl_extensions{std::move(egl_extensions)}, + dmabuf_export_ext{mg::EGLExtensions::MESADmaBufExport::extension_if_supported(dpy)}, + formats{std::make_unique(dpy, dmabuf_ext)}, + egl_delegate{std::move(egl_delegate)}, + allocate_importable_image{std::move(allocate_importable_image)}, + blitter{std::make_unique(this->egl_delegate)} +{ +} + +mg::DMABufEGLProvider::~DMABufEGLProvider() = default; + +auto mg::DMABufEGLProvider::supported_formats() const -> mg::DmaBufFormatDescriptors const& +{ + return *formats; +} + +auto mg::DMABufEGLProvider::import_dma_buf( + mg::DMABufBuffer const& dma_buf, + std::function&& on_consumed, + std::function&& on_release) -> std::shared_ptr +{ + // This will have been pre-validated at point the Wayland client created the buffer + auto descriptor = descriptor_for_format_and_modifiers( + dma_buf.format(), + dma_buf.modifier().value_or(DRM_FORMAT_MOD_INVALID), + *this); + return std::make_shared( + dpy, + *egl_extensions, + dma_buf, + *descriptor, + shared_from_this(), + egl_delegate, + std::move(on_consumed), + std::move(on_release)); +} + +void mg::DMABufEGLProvider::validate_import(DMABufBuffer const& dma_buf) +{ + auto image = import_egl_image( + dma_buf.size().width.as_int(), dma_buf.size().height.as_int(), + dma_buf.format(), + dma_buf.modifier(), + dma_buf.planes(), + dpy, + *egl_extensions); + if (image != EGL_NO_IMAGE_KHR) + { + // We can throw this image away immediately + egl_extensions->base(dpy).eglDestroyImageKHR(dpy, image); + } +} + +auto mg::DMABufEGLProvider::as_texture(std::shared_ptr buffer) + -> std::shared_ptr +{ + if (auto dmabuf_tex = std::dynamic_pointer_cast(buffer)) + { + if (dmabuf_tex->on_same_egl_display(dpy)) + { + auto tex = dmabuf_tex->as_texture(); + return std::shared_ptr(std::move(dmabuf_tex), tex); + } + else if (auto descriptor = descriptor_for_format_and_modifiers( + dmabuf_tex->format(), + dmabuf_tex->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, + *dmabuf_tex, + *descriptor, + egl_delegate); + } + else + { + /* Oh, no. We've got a dma-buf in a format that our rendering GPU can't handle. + * + * In this case we'll need to get the *importing* GPU to blit to a format + * we *can* handle. + */ + 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"})); + } + } + return nullptr; } diff --git a/src/platform/symbols.map b/src/platform/symbols.map index 0187aed2796..d6a0d145878 100644 --- a/src/platform/symbols.map +++ b/src/platform/symbols.map @@ -1,4 +1,4 @@ -MIRPLATFORM_2.5 { +MIR_PLATFORM_2.16 { global: extern "C++" { mir::graphics::AtomicFrame::increment*; @@ -6,6 +6,18 @@ MIRPLATFORM_2.5 { mir::graphics::AtomicFrame::store*; mir::graphics::Buffer::Buffer*; mir::graphics::BufferBasic::BufferBasic*; + mir::graphics::DMABufEGLProvider::?DMABufEGLProvider*; + mir::graphics::DMABufEGLProvider::DMABufEGLProvider*; + mir::graphics::DMABufEGLProvider::as_texture*; + mir::graphics::DRMFormat::DRMFormat*; + mir::graphics::DRMFormat::alpha_equivalent*; + mir::graphics::DRMFormat::as_mir_format*; + mir::graphics::DRMFormat::components*; + mir::graphics::DRMFormat::from_mir_format*; + mir::graphics::DRMFormat::has_alpha*; + mir::graphics::DRMFormat::name*; + mir::graphics::DRMFormat::opaque_equivalent*; + mir::graphics::DRMFormat::operator?unsigned?int*; /* Is actually operator uint32_t(), but 🤷 */ mir::graphics::DisplayConfiguration::operator*; mir::graphics::DisplayConfiguration::valid*; mir::graphics::DisplayConfigurationOutput::extents*; @@ -51,7 +63,11 @@ MIRPLATFORM_2.5 { mir::graphics::UserDisplayConfigurationOutput::extents*; mir::graphics::alpha_channel_depth*; mir::graphics::blue_channel_depth*; + mir::graphics::common::EGLContextExecutor::?EGLContextExecutor*; + mir::graphics::common::EGLContextExecutor::EGLContextExecutor*; + mir::graphics::common::EGLContextExecutor::spawn*; mir::graphics::contains_alpha*; + mir::graphics::drm_modifier_to_string*; mir::graphics::egl_category*; mir::graphics::gl::Program::?Program*; mir::graphics::gl::ProgramFactory::?ProgramFactory*; @@ -108,6 +124,7 @@ MIRPLATFORM_2.5 { mir::options::glog_log_dir*; mir::options::glog_minloglevel*; mir::options::glog_stderrthreshold*; + mir::options::idle_timeout_opt; mir::options::input_report_opt*; mir::options::log_opt_value*; mir::options::logind_console; @@ -135,8 +152,10 @@ MIRPLATFORM_2.5 { mir::renderer::software::as_read_mappable_buffer*; mir::udev::Context::?Context*; mir::udev::Context::Context*; + mir::udev::Context::char_device_from_devnum*; mir::udev::Context::ctx*; mir::udev::Context::device_from_syspath*; + mir::udev::Device::clone*; mir::udev::Device::initialised*; mir::udev::Device::sysname*; mir::udev::Device::syspath*; @@ -161,6 +180,7 @@ MIRPLATFORM_2.5 { typeinfo?for?mir::graphics::Buffer; typeinfo?for?mir::graphics::BufferBasic; typeinfo?for?mir::graphics::DisplayConfiguration; + typeinfo?for?mir::graphics::common::EGLContextExecutor; typeinfo?for?mir::graphics::gl::Program; typeinfo?for?mir::graphics::gl::ProgramFactory; typeinfo?for?mir::graphics::gl::Texture; @@ -172,6 +192,7 @@ MIRPLATFORM_2.5 { vtable?for?mir::graphics::Buffer; vtable?for?mir::graphics::BufferBasic; vtable?for?mir::graphics::DisplayConfiguration; + vtable?for?mir::graphics::common::EGLContextExecutor; vtable?for?mir::graphics::gl::Program; vtable?for?mir::graphics::gl::ProgramFactory; vtable?for?mir::graphics::gl::Texture; @@ -183,40 +204,3 @@ MIRPLATFORM_2.5 { local: *; }; -MIRPLATFORM_2.7 { - global: - extern "C++" { - mir::options::idle_timeout_opt; - }; -} MIRPLATFORM_2.5; - -MIR_PLATFORM_2.8 { - global: - extern "C++" { - mir::graphics::DRMFormat::DRMFormat*; - mir::graphics::DRMFormat::name*; - mir::graphics::DRMFormat::alpha_equivalent*; - mir::graphics::DRMFormat::opaque_equivalent*; - mir::graphics::DRMFormat::has_alpha*; - mir::graphics::DRMFormat::components*; - mir::graphics::DRMFormat::operator?unsigned?int*; /* Is actually operator uint32_t(), but 🤷 */ - - mir::graphics::drm_modifier_to_string*; - - mir::udev::Device::clone*; - mir::udev::Context::char_device_from_devnum*; - - mir::graphics::common::EGLContextExecutor::EGLContextExecutor*; - mir::graphics::common::EGLContextExecutor::?EGLContextExecutor*; - mir::graphics::common::EGLContextExecutor::spawn*; - typeinfo?for?mir::graphics::common::EGLContextExecutor; - vtable?for?mir::graphics::common::EGLContextExecutor; - }; -} MIRPLATFORM_2.7; - -MIR_PLATFORM_2.11 { - global: - extern "C++" { - mir::graphics::DRMFormat::as_mir_format*; - }; -} MIR_PLATFORM_2.8; diff --git a/src/platforms/common/server/CMakeLists.txt b/src/platforms/common/server/CMakeLists.txt index 466ea7f6e03..ba2fd013204 100644 --- a/src/platforms/common/server/CMakeLists.txt +++ b/src/platforms/common/server/CMakeLists.txt @@ -6,10 +6,14 @@ include_directories( ${server_common_include_dirs} ) +add_compile_definitions(MIR_LOG_COMPONENT_FALLBACK="server_platform_common") + add_library(server_platform_common STATIC shm_buffer.cpp one_shot_device_observer.h one_shot_device_observer.cpp + cpu_copy_output_surface.cpp + cpu_copy_output_surface.h ) target_include_directories( diff --git a/src/platforms/common/server/cpu_copy_output_surface.cpp b/src/platforms/common/server/cpu_copy_output_surface.cpp new file mode 100644 index 00000000000..5001f282304 --- /dev/null +++ b/src/platforms/common/server/cpu_copy_output_surface.cpp @@ -0,0 +1,305 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include +#include + +#include + +#include "mir/graphics/egl_error.h" +#include "mir/graphics/platform.h" +#include "mir/log.h" + +#include "cpu_copy_output_surface.h" + +namespace mg = mir::graphics; +namespace mgc = mg::common; +namespace geom = mir::geometry; + +namespace +{ +template +class GLHandle +{ +public: + GLHandle() + { + (*allocator)(1, &id); + } + + ~GLHandle() + { + if (id) + (*deleter)(1, &id); + } + + GLHandle(GLHandle const&) = delete; + GLHandle& operator=(GLHandle const&) = delete; + + GLHandle(GLHandle&& from) + : id{from.id} + { + from.id = 0; + } + + operator GLuint() const + { + return id; + } + +private: + GLuint id; +}; + +using RenderbufferHandle = GLHandle<&glGenRenderbuffers, &glDeleteRenderbuffers>; +using FramebufferHandle = GLHandle<&glGenFramebuffers, &glDeleteFramebuffers>; + +auto create_current_context(EGLDisplay dpy, EGLContext share_ctx) + -> EGLContext +{ + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + auto egl_extensions = eglQueryString(dpy, EGL_EXTENSIONS); + if (strstr(egl_extensions, "EGL_KHR_no_config_context") == nullptr) + { + // We do not *strictly* need this, but it means I don't need to thread a GLConfig all the way through to here. + BOOST_THROW_EXCEPTION((std::runtime_error{"EGL implementation missing necessary EGL_KHR_no_config_context extension"})); + } + + eglBindAPI(EGL_OPENGL_ES_API); + auto ctx = eglCreateContext(dpy, EGL_NO_CONFIG_KHR, share_ctx, context_attr); + + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); + } + return ctx; +} + +auto select_format_from(mg::CPUAddressableDisplayProvider const& provider) -> mg::DRMFormat +{ + std::optional best_format; + for (auto const format : provider.supported_formats()) + { + switch(static_cast(format)) + { + case DRM_FORMAT_ARGB8888: + case DRM_FORMAT_XRGB8888: + // ?RGB8888 is the easiest for us + return format; + case DRM_FORMAT_RGBA8888: + case DRM_FORMAT_RGBX8888: + // RGB?8888 requires an EGL extension, but is OK + best_format = format; + break; + } + } + if (best_format) + { + return *best_format; + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Non-?RGB8888 formats not yet supported for display"})); +} +} + +class mgc::CPUCopyOutputSurface::Impl +{ +public: + Impl( + EGLDisplay dpy, + EGLContext share_ctx, + std::shared_ptr allocator, + geom::Size size); + + void bind(); + + void make_current(); + void release_current(); + + auto commit() -> std::unique_ptr; + + auto size() const -> geom::Size; + auto layout() const -> Layout; + +private: + std::shared_ptr const allocator; + EGLDisplay const dpy; + EGLContext const ctx; + geometry::Size const size_; + DRMFormat const format; + RenderbufferHandle const colour_buffer; + FramebufferHandle const fbo; +}; + +mgc::CPUCopyOutputSurface::CPUCopyOutputSurface( + EGLDisplay dpy, + EGLContext share_ctx, + std::shared_ptr allocator, + geom::Size size) + : impl{std::make_unique(dpy, share_ctx, std::move(allocator), size)} +{ +} + +mgc::CPUCopyOutputSurface::~CPUCopyOutputSurface() = default; + +void mgc::CPUCopyOutputSurface::bind() +{ + impl->bind(); +} + +void mgc::CPUCopyOutputSurface::make_current() +{ + impl->make_current(); +} + +void mgc::CPUCopyOutputSurface::release_current() +{ + impl->release_current(); +} + +auto mgc::CPUCopyOutputSurface::commit() -> std::unique_ptr +{ + return impl->commit(); +} + +auto mgc::CPUCopyOutputSurface::size() const -> geom::Size +{ + return impl->size(); +} + +auto mgc::CPUCopyOutputSurface::layout() const -> Layout +{ + return impl->layout(); +} + +mgc::CPUCopyOutputSurface::Impl::Impl( + EGLDisplay dpy, + EGLContext share_ctx, + std::shared_ptr allocator, + geom::Size size) + : allocator{std::move(allocator)}, + dpy{dpy}, + ctx{create_current_context(dpy, share_ctx)}, + size_{size}, + format{select_format_from(*this->allocator)} +{ + glBindRenderbuffer(GL_RENDERBUFFER, colour_buffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, size_.width.as_int(), size_.height.as_int()); + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colour_buffer); + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + BOOST_THROW_EXCEPTION(( + std::runtime_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"} + )); + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + // Somehow we've managed to attach buffers with mismatched sizes? + BOOST_THROW_EXCEPTION(( + std::logic_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"} + )); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + BOOST_THROW_EXCEPTION(( + std::logic_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"} + )); + case GL_FRAMEBUFFER_UNSUPPORTED: + // This is the only one that isn't necessarily a programming error + BOOST_THROW_EXCEPTION(( + std::runtime_error{"FBO is incomplete: formats selected are not supported by this GL driver"} + )); + case 0: + BOOST_THROW_EXCEPTION(( + mg::gl_error("Failed to verify GL Framebuffer completeness"))); + } + BOOST_THROW_EXCEPTION(( + std::runtime_error{ + std::string{"Unknown GL framebuffer error code: "} + std::to_string(status)})); + } +} + +void mgc::CPUCopyOutputSurface::Impl::bind() +{ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); +} + +void mgc::CPUCopyOutputSurface::Impl::make_current() +{ + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + mir::log_debug("Failed to make EGL context current"); + } +} + +void mgc::CPUCopyOutputSurface::Impl::release_current() +{ + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + { + mir::log_debug("Failed to release current EGL context"); + } +} + +auto mgc::CPUCopyOutputSurface::Impl::commit() -> std::unique_ptr +{ + auto fb = allocator->alloc_fb(size_, format); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + { + /* TODO: We can usefully put this *into* DRMFormat */ + GLenum pixel_layout = GL_INVALID_ENUM; + if (format == DRM_FORMAT_ARGB8888 || format == DRM_FORMAT_XRGB8888) + { + pixel_layout = GL_BGRA_EXT; + } + else if (format == DRM_FORMAT_RGBA8888 || format == DRM_FORMAT_RGBX8888) + { + pixel_layout = GL_RGBA; + } + auto mapping = fb->map_writeable(); + /* + * TODO: This introduces a pipeline stall; GL must wait for all previous rendering commands + * to complete before glReadPixels returns. We could instead do something fancy with + * pixel buffer objects to defer this cost. + */ + /* + * TODO: We are assuming that the framebuffer pixel format is RGBX + */ + glReadPixels( + 0, 0, + size_.width.as_int(), size_.height.as_int(), + pixel_layout, GL_UNSIGNED_BYTE, mapping->data()); + } + return fb; +} + +auto mgc::CPUCopyOutputSurface::Impl::size() const -> geom::Size +{ + return size_; +} + +auto mgc::CPUCopyOutputSurface::Impl::layout() const -> Layout +{ + return Layout::TopRowFirst; +} diff --git a/src/platforms/common/server/cpu_copy_output_surface.h b/src/platforms/common/server/cpu_copy_output_surface.h new file mode 100644 index 00000000000..fc38036b8af --- /dev/null +++ b/src/platforms/common/server/cpu_copy_output_surface.h @@ -0,0 +1,61 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include + +#include "mir/graphics/drm_formats.h" +#include "mir/geometry/size.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/gl/gl_surface.h" + +namespace mir::graphics +{ + +namespace common +{ +class CPUCopyOutputSurface : public gl::OutputSurface +{ +public: + CPUCopyOutputSurface( + EGLDisplay dpy, + EGLContext share_ctx, + std::shared_ptr allocator, + geometry::Size size); + + ~CPUCopyOutputSurface() override; + + void bind() override; + + void make_current() override; + + void release_current() override; + + auto commit() -> std::unique_ptr override; + + auto size() const -> geometry::Size override; + + auto layout() const -> Layout override; + +private: + class Impl; + std::unique_ptr const impl; + std::shared_ptr const allocator; +}; +} +} \ No newline at end of file diff --git a/src/platforms/eglstream-kms/server/CMakeLists.txt b/src/platforms/eglstream-kms/server/CMakeLists.txt index 3e5604e0cae..346f11c9775 100644 --- a/src/platforms/eglstream-kms/server/CMakeLists.txt +++ b/src/platforms/eglstream-kms/server/CMakeLists.txt @@ -24,6 +24,8 @@ add_library(mirplatformgraphicseglstreamkmsobjects OBJECT drm_event_handler.h threaded_drm_event_handler.h threaded_drm_event_handler.cpp + eglstream_interface_provider.h + eglstream_interface_provider.cpp ) target_link_libraries(mirplatformgraphicseglstreamkmsobjects diff --git a/src/platforms/eglstream-kms/server/buffer_allocator.cpp b/src/platforms/eglstream-kms/server/buffer_allocator.cpp index ff58a4ff8a5..f0f158356fa 100644 --- a/src/platforms/eglstream-kms/server/buffer_allocator.cpp +++ b/src/platforms/eglstream-kms/server/buffer_allocator.cpp @@ -15,9 +15,16 @@ */ #include +#include #include "buffer_allocator.h" +#include "cpu_copy_output_surface.h" #include "mir/anonymous_shm_file.h" +#include "mir/graphics/display_buffer.h" +#include "mir/graphics/drm_formats.h" +#include "mir/graphics/egl_resources.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/platform.h" #include "shm_buffer.h" #include "mir/graphics/buffer_properties.h" #include "mir/renderer/gl/context_source.h" @@ -33,6 +40,8 @@ #include "mir/renderer/gl/context.h" #include "mir/raii.h" #include "mir/wayland/protocol_error.h" +#include "mir/wayland/wayland_base.h" +#include "mir/renderer/gl/gl_surface.h" #define MIR_LOG_COMPONENT "platform-eglstream-kms" #include "mir/log.h" @@ -40,6 +49,7 @@ #include #include +#include #include #include @@ -58,42 +68,10 @@ namespace geom = mir::geometry; #define EGL_WAYLAND_EGLSTREAM_WL 0x334B #endif /* EGL_WL_wayland_eglstream */ -namespace -{ -std::unique_ptr context_for_output(mg::Display const& output) -{ - try - { - auto& context_source = dynamic_cast(output); - - /* - * We care about no part of this context's config; we will do no rendering with it. - * All we care is that we can allocate texture IDs and bind a texture, which is - * config independent. - * - * That's not *entirely* true; we also need it to be on the same device as we want - * to do the rendering on, and that GL must support all the extensions we care about, - * but since we don't yet support heterogeneous hybrid and implementing that will require - * broader interface changes it's a safe enough requirement for now. - */ - return context_source.create_gl_context(); - } - catch (std::bad_cast const& err) - { - std::throw_with_nested( - boost::enable_error_info( - std::runtime_error{"Output platform cannot provide a GL context"}) - << boost::throw_function(__PRETTY_FUNCTION__) - << boost::throw_line(__LINE__) - << boost::throw_file(__FILE__)); - } -} -} - -mge::BufferAllocator::BufferAllocator(mg::Display const& output) - : wayland_ctx{context_for_output(output)}, +mge::BufferAllocator::BufferAllocator(std::unique_ptr ctx) + : wayland_ctx{ctx->make_share_context()}, egl_delegate{ - std::make_shared(context_for_output(output))} + std::make_shared(std::move(ctx))} { } @@ -569,3 +547,263 @@ auto mge::BufferAllocator::buffer_from_shm( std::move(on_consumed), std::move(on_release)); } + +namespace +{ +// libepoxy replaces the GL symbols with resolved-on-first-use function pointers +template +class GLHandle +{ +public: + GLHandle() + { + (**allocator)(1, &id); + } + + ~GLHandle() + { + if (id) + (**deleter)(1, &id); + } + + GLHandle(GLHandle const&) = delete; + GLHandle& operator=(GLHandle const&) = delete; + + GLHandle(GLHandle&& from) + : id{from.id} + { + from.id = 0; + } + + operator GLuint() const + { + return id; + } + +private: + GLuint id; +}; + +using TextureHandle = GLHandle<&glGenTextures, &glDeleteTextures>; + +auto make_stream_ctx(EGLDisplay dpy, EGLConfig cfg, EGLContext share_with) -> EGLContext +{ + eglBindAPI(EGL_OPENGL_ES_API); + + EGLint const context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLContext context = eglCreateContext(dpy, cfg, share_with, context_attr); + if (context == EGL_NO_CONTEXT) + { + // One of the ways this will happen is if we try to create one of these + // on a different device to the display. + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); + } + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make EGL context current")); + } + + return context; +} + +auto make_output_surface(EGLDisplay dpy, EGLConfig cfg, EGLStreamKHR output_stream, geom::Size size) + -> EGLSurface +{ + EGLint const surface_attribs[] = { + EGL_WIDTH, size.width.as_int(), + EGL_HEIGHT, size.height.as_int(), + EGL_NONE, + }; + auto surface = eglCreateStreamProducerSurfaceKHR(dpy, cfg, output_stream, surface_attribs); + if (surface == EGL_NO_SURFACE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create StreamProducerSurface")); + } + return surface; +} + +class EGLStreamOutputSurface : public mg::gl::OutputSurface +{ +public: + EGLStreamOutputSurface( + EGLDisplay dpy, + EGLConfig config, + EGLContext display_share_context, + EGLStreamKHR output_stream, + geom::Size size) + : dpy{dpy}, + ctx{make_stream_ctx(dpy, config, display_share_context)}, + surface{make_output_surface(dpy, config, output_stream, size)}, + size_{std::move(size)} + { + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + + void bind() override + { + } + + void make_current() override + { + if (eglMakeCurrent(dpy, surface, surface, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); + } + } + + void release_current() override + { + 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")); + } + } + + auto commit() -> std::unique_ptr override + { + if (eglSwapBuffers(dpy, surface) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("eglSwapBuffers failed")); + } + return {}; + } + + auto size() const -> geom::Size override + { + return size_; + } + + auto layout() const -> Layout override + { + return Layout::GL; + } + +private: + EGLDisplay const dpy; + EGLContext const ctx; + EGLSurface const surface; + geom::Size const size_; +}; + +auto pick_stream_surface_config(EGLDisplay dpy, mg::GLConfig const& gl_config) -> EGLConfig +{ + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, gl_config.depth_buffer_bits(), + EGL_STENCIL_SIZE, gl_config.stencil_buffer_bits(), + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLConfig egl_config; + EGLint num_egl_configs{0}; + + if (eglChooseConfig(dpy, config_attr, &egl_config, 1, &num_egl_configs) == EGL_FALSE || + num_egl_configs != 1) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to choose ARGB EGL config")); + } + return egl_config; +} +} + +mge::GLRenderingProvider::GLRenderingProvider(EGLDisplay dpy, std::unique_ptr ctx) + : dpy{dpy}, + ctx{std::move(ctx)} +{ +} + +mge::GLRenderingProvider::~GLRenderingProvider() = default; + +auto mir::graphics::eglstream::GLRenderingProvider::as_texture(std::shared_ptr buffer) + -> std::shared_ptr +{ + return std::dynamic_pointer_cast(std::move(buffer)); +} + +auto mge::GLRenderingProvider::surface_for_output( + std::shared_ptr target, + geom::Size size, + mg::GLConfig const& gl_config) -> std::unique_ptr +{ + if (auto stream_platform = target->acquire_interface()) + { + try + { + return std::make_unique( + dpy, + pick_stream_surface_config(dpy, gl_config), + static_cast(*ctx), + stream_platform->claim_stream(), + size); + } + catch (std::exception const& err) + { + mir::log_info( + "Failed to create EGLStream-backed output surface: %s", + err.what()); + } + } + if (auto cpu_provider = target->acquire_interface()) + { + auto fb_context = ctx->make_share_context(); + fb_context->make_current(); + return std::make_unique( + dpy, + static_cast(*ctx), + cpu_provider, + size); + } + BOOST_THROW_EXCEPTION((std::runtime_error{"DisplayInterfaceProvider does not support any viable output interface"})); +} + +auto mge::GLRenderingProvider::suitability_for_allocator(std::shared_ptr const& target) + -> probe::Result +{ + // TODO: We *can* import from other allocators, maybe (anything with dma-buf is probably possible) + // For now, the simplest thing is to bind hard to own own allocator. + if (dynamic_cast(target.get())) + { + return probe::best; + } + return probe::unsupported; +} + +auto mge::GLRenderingProvider::suitability_for_display( + std::shared_ptr const& target) -> probe::Result +{ + if (target->acquire_interface()) + { + return probe::best; + } + if (target->acquire_interface()) + { + return probe::supported; + } + return probe::unsupported; +} + +auto mge::GLRenderingProvider::make_framebuffer_provider(std::shared_ptr /*target*/) + -> std::unique_ptr +{ + // TODO: *Can* we provide overlay support? + class NullFramebufferProvider : public FramebufferProvider + { + public: + auto buffer_to_framebuffer(std::shared_ptr) -> std::unique_ptr override + { + // It is safe to return nullptr; this will be treated as “this buffer cannot be used as + // a framebuffer”. + return {}; + } + }; + return std::make_unique(); +} diff --git a/src/platforms/eglstream-kms/server/buffer_allocator.h b/src/platforms/eglstream-kms/server/buffer_allocator.h index ec4afe1b087..9eb80b2a311 100644 --- a/src/platforms/eglstream-kms/server/buffer_allocator.h +++ b/src/platforms/eglstream-kms/server/buffer_allocator.h @@ -21,6 +21,7 @@ #include "mir/graphics/buffer_id.h" #include "mir/graphics/egl_extensions.h" #include "mir/graphics/egl_context_executor.h" +#include "mir/graphics/platform.h" #include "wayland-eglstream-controller.h" @@ -52,7 +53,7 @@ class BufferAllocator: public graphics::GraphicBufferAllocator { public: - BufferAllocator(graphics::Display const& output); + BufferAllocator(std::unique_ptr ctx); ~BufferAllocator(); std::shared_ptr alloc_software_buffer(geometry::Size size, MirPixelFormat format) override; @@ -91,6 +92,29 @@ class BufferAllocator: static struct wl_eglstream_controller_interface const impl; }; +class GLRenderingProvider : public graphics::GLRenderingProvider +{ +public: + GLRenderingProvider(EGLDisplay dpy, std::unique_ptr ctx); + ~GLRenderingProvider(); + + auto as_texture(std::shared_ptr buffer) -> std::shared_ptr override; + + auto suitability_for_allocator(std::shared_ptr const& target) -> probe::Result override; + + auto suitability_for_display(std::shared_ptr const& target) -> probe::Result override; + + auto make_framebuffer_provider(std::shared_ptr target) -> std::unique_ptr override; + + auto surface_for_output( + std::shared_ptr provider, + geometry::Size size, + GLConfig const& gl_config) -> std::unique_ptr override; +private: + EGLDisplay dpy; + std::unique_ptr const ctx; +}; + } } } diff --git a/src/platforms/eglstream-kms/server/display.cpp b/src/platforms/eglstream-kms/server/display.cpp index ad2ff336498..effe77c4b7e 100644 --- a/src/platforms/eglstream-kms/server/display.cpp +++ b/src/platforms/eglstream-kms/server/display.cpp @@ -20,6 +20,7 @@ #include "egl_output.h" #include "kms-utils/drm_mode_resources.h" +#include "mir/graphics/platform.h" #include "threaded_drm_event_handler.h" #include "mir/graphics/display_configuration.h" @@ -29,8 +30,6 @@ #include "mir/graphics/egl_error.h" #include "mir/graphics/display_buffer.h" #include "mir/graphics/transformation.h" -#include "mir/renderer/gl/render_target.h" -#include "mir/renderer/gl/context.h" #include "mir/graphics/egl_extensions.h" #include "mir/graphics/display_report.h" #include "mir/graphics/event_handler_register.h" @@ -46,7 +45,7 @@ namespace mg = mir::graphics; namespace mge = mir::graphics::eglstream; -namespace mgk = mir::graphics::kms; +namespace geom = mir::geometry; #ifndef EGL_NV_output_drm_flip_event #define EGL_NV_output_drm_flip_event 1 @@ -121,12 +120,11 @@ EGLContext create_context(EGLDisplay display, EGLConfig config, EGLContext share class DisplayBuffer : public mg::DisplaySyncGroup, - public mg::DisplayBuffer, - public mg::NativeDisplayBuffer, - public mir::renderer::gl::RenderTarget + public mg::DisplayBuffer { public: DisplayBuffer( + std::shared_ptr owner, mir::Fd drm_node, EGLDisplay dpy, EGLContext ctx, @@ -134,7 +132,8 @@ class DisplayBuffer std::shared_ptr event_handler, mge::kms::EGLOutput const& output, std::shared_ptr display_report) - : dpy{dpy}, + : owner{std::move(owner)}, + dpy{dpy}, ctx{create_context(dpy, config, ctx)}, layer{output.output_layer()}, crtc_id{output.crtc_id()}, @@ -172,17 +171,6 @@ class DisplayBuffer BOOST_THROW_EXCEPTION(mg::egl_error("Failed to attach EGLStream to output")); }; - EGLint const surface_attribs[] = { - EGL_WIDTH, output_size.width.as_int(), - EGL_HEIGHT, output_size.height.as_int(), - EGL_NONE, - }; - surface = eglCreateStreamProducerSurfaceKHR(dpy, config, output_stream, surface_attribs); - if (surface == EGL_NO_SURFACE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create StreamProducerSurface")); - } - this->display_report->report_successful_setup_of_native_resources(); this->display_report->report_successful_display_construction(); this->display_report->report_egl_configuration(dpy, config); @@ -193,33 +181,18 @@ class DisplayBuffer pending_flip = satisfied_promise.get_future(); } - /* gl::RenderTarget */ - auto size() const -> mir::geometry::Size override - { - return output_size; - } - - void make_current() override - { - if (eglMakeCurrent(dpy, surface, surface, ctx) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); - } - } - - void release_current() override + ~DisplayBuffer() { - if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + if (output_stream != EGL_NO_STREAM_KHR) { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release context")); + eglDestroyStreamKHR(dpy, output_stream); + output_stream = nullptr; } - } - void swap_buffers() override - { - if (eglSwapBuffers(dpy, surface) != EGL_TRUE) + if (ctx != EGL_NO_CONTEXT) { - BOOST_THROW_EXCEPTION(mg::egl_error("eglSwapBuffers failed")); + eglDestroyContext(dpy, ctx); + ctx = nullptr; } } @@ -228,21 +201,11 @@ class DisplayBuffer return view_area_; } - bool overlay(const mir::graphics::RenderableList& /*renderlist*/) override - { - return false; - } - glm::mat2 transformation() const override { return transform; } - mir::graphics::NativeDisplayBuffer* native_display_buffer() override - { - return this; - } - void for_each_display_buffer(const std::function& f) override { f(*this); @@ -271,21 +234,95 @@ class DisplayBuffer }; if (nv_stream(dpy).eglStreamConsumerAcquireAttribNV(dpy, output_stream, acquire_attribs) != EGL_TRUE) { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to submit frame from EGLStream for display")); + auto error = eglGetError(); + EGLAttrib stream_state{0}; + eglQueryStreamAttribKHR(dpy, output_stream, EGL_STREAM_STATE_KHR, &stream_state); + std::string state; + switch (stream_state) + { + case EGL_STREAM_STATE_CREATED_KHR: + state = "EGL_STREAM_STATE_CREATED_KHR"; + break; + case EGL_STREAM_STATE_CONNECTING_KHR: + state = "EGL_STREAM_STATE_CONNECTING_KHR"; + break; + case EGL_STREAM_STATE_EMPTY_KHR: + state = "EGL_STREAM_STATE_EMPTY_KHR"; + break; + case EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR: + state = "EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR"; + break; + case EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR: + state = "EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR"; + break; + case EGL_STREAM_STATE_DISCONNECTED_KHR: + state = "EGL_STREAM_STATE_DISCONNECTED_KHR"; + break; + default: + state = ""; + } + BOOST_THROW_EXCEPTION(( + std::system_error{ + error, + mg::egl_category(), + std::string{"Failed to submit frame from EGLStream for display. (Stream in state: "} + state + ")"})); } } - void bind() override + std::chrono::milliseconds recommended_sleep() const override { + return std::chrono::milliseconds{0}; } - std::chrono::milliseconds recommended_sleep() const override + auto display_provider() const -> std::shared_ptr override { - return std::chrono::milliseconds{0}; + return std::make_shared(*owner, output_stream); } + void set_next_image(std::unique_ptr content) override + { + std::vector const single_buffer = { + mg::DisplayElement { + view_area(), + mir::geometry::RectangleF{ + {0, 0}, + {view_area().size.width.as_value(), view_area().size.height.as_value()}}, + std::move(content) + } + }; + if (!overlay(single_buffer)) + { + // Oh, oh! We should be *guaranteed* to “overlay” a single Framebuffer; this is likely a programming error + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to post buffer to display"})); + } + } + + bool overlay(std::vector const& renderable_list) override + { + // TODO: implement more than the most basic case. + if (renderable_list.size() != 1) + { + return false; + } + + if (renderable_list[0].screen_positon != view_area()) + { + return false; + } + + if (renderable_list[0].source_position.top_left != geom::PointF {0,0} || + renderable_list[0].source_position.size.width.as_value() != view_area().size.width.as_int() || + renderable_list[0].source_position.size.height.as_value() != view_area().size.height.as_int()) + { + return false; + } + + // TODO: Validate that the submitted "framebuffer" is *actually* our EGLStream + return true; + } private: + std::shared_ptr const owner; EGLDisplay dpy; EGLContext ctx; EGLOutputLayerEXT layer; @@ -294,7 +331,6 @@ class DisplayBuffer mir::geometry::Size const output_size; glm::mat2 const transform; EGLStreamKHR output_stream; - EGLSurface surface; mir::Fd const drm_node; std::shared_ptr const event_handler; std::future pending_flip; @@ -317,12 +353,14 @@ mge::KMSDisplayConfiguration create_display_configuration( } mge::Display::Display( + std::shared_ptr provider, mir::Fd drm_node, EGLDisplay display, std::shared_ptr const& configuration_policy, GLConfig const& gl_conf, std::shared_ptr display_report) - : drm_node{drm_node}, + : provider{std::move(provider)}, + drm_node{drm_node}, display{display}, config{choose_config(display, gl_conf)}, context{create_context(display, config)}, @@ -372,13 +410,14 @@ void mge::Display::configure(DisplayConfiguration const& conf) const_cast(output).configure(output.current_mode_index); active_sync_groups.emplace_back( std::make_unique<::DisplayBuffer>( - drm_node, - display, - context, - config, - event_handler, - output, - display_report)); + provider, + drm_node, + display, + context, + config, + event_handler, + output, + display_report)); } }); } @@ -432,78 +471,6 @@ std::shared_ptr mge::Display::create_hardware_cursor() return nullptr; } -std::unique_ptr mge::Display::create_gl_context() const -{ - class GLContext : public renderer::gl::Context - { - public: - GLContext(EGLDisplay display, EGLContext shared_context) - : display{display}, - context{make_context(display, shared_context)} - { - } - - void make_current() const override - { - if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); - } - } - - void release_current() const override - { - if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release context")); - } - } - - private: - static EGLContext make_context(EGLDisplay dpy, EGLContext shared_context) - { - eglBindAPI(EGL_OPENGL_ES_API); - - static const EGLint context_attr[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - EGLint const config_attr[] = { - EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 0, - EGL_DEPTH_SIZE, 0, - EGL_STENCIL_SIZE, 0, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - EGLint num_egl_configs; - EGLConfig egl_config; - if (eglChooseConfig(dpy, config_attr, &egl_config, 1, &num_egl_configs) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to chose EGL config")); - } else if (num_egl_configs != 1) - { - BOOST_THROW_EXCEPTION(std::runtime_error{"Failed to find compatible EGL config"}); - } - - auto egl_context = eglCreateContext(dpy, egl_config, shared_context, context_attr); - if (egl_context == EGL_NO_CONTEXT) - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); - - return egl_context; - } - - EGLDisplay const display; - EGLContext const context; - }; - return std::make_unique(display, context); -} - bool mge::Display::apply_if_configuration_preserves_display_buffers( mg::DisplayConfiguration const& /*conf*/) { diff --git a/src/platforms/eglstream-kms/server/display.h b/src/platforms/eglstream-kms/server/display.h index 1b1e75c8a23..b8ec32579d9 100644 --- a/src/platforms/eglstream-kms/server/display.h +++ b/src/platforms/eglstream-kms/server/display.h @@ -17,10 +17,12 @@ #ifndef MIR_PLATFORMS_EGLSTREAM_KMS_DISPLAY_H_ #define MIR_PLATFORMS_EGLSTREAM_KMS_DISPLAY_H_ +#include "eglstream_interface_provider.h" + #include "mir/graphics/display.h" #include "kms_display_configuration.h" #include "mir/fd.h" -#include "mir/renderer/gl/context_source.h" +#include "mir/graphics/platform.h" #include @@ -40,6 +42,7 @@ class Display : public mir::graphics::Display { public: Display( + std::shared_ptr provider, mir::Fd drm_node, EGLDisplay display, std::shared_ptr const& configuration_policy, @@ -63,9 +66,8 @@ class Display : public mir::graphics::Display std::shared_ptr create_hardware_cursor() override; - std::unique_ptr create_gl_context() const override; - private: + std::shared_ptr const provider; mir::Fd const drm_node; EGLDisplay display; EGLConfig config; diff --git a/src/platforms/eglstream-kms/server/egl_output.cpp b/src/platforms/eglstream-kms/server/egl_output.cpp index e263ef151b6..e9b9b3100ce 100644 --- a/src/platforms/eglstream-kms/server/egl_output.cpp +++ b/src/platforms/eglstream-kms/server/egl_output.cpp @@ -39,10 +39,10 @@ namespace { namespace { -class DumbFb +class CPUAddressableFb { public: - DumbFb(int drm_fd, uint32_t width, uint32_t height) + CPUAddressableFb(int drm_fd, uint32_t width, uint32_t height) : drm_fd{drm_fd} { struct drm_mode_create_dumb params = {}; @@ -53,7 +53,7 @@ class DumbFb if (ioctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, ¶ms) != 0) { - BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to create dumb buffer"})); + BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to create KMS dumb buffer"})); } gem_handle = params.handle; @@ -62,7 +62,7 @@ class DumbFb auto ret = drmModeAddFB(drm_fd, width, height, 24, 32, params.pitch, params.handle, &fb_id); if (ret) { - BOOST_THROW_EXCEPTION((std::system_error{-ret, std::system_category(), "Failed to attach dumb buffer to FB"})); + BOOST_THROW_EXCEPTION((std::system_error{-ret, std::system_category(), "Failed to attach KMS dumb buffer to FB"})); } struct drm_mode_map_dumb map_request = {}; @@ -72,7 +72,7 @@ class DumbFb ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_request); if (ret) { - BOOST_THROW_EXCEPTION((std::system_error{-ret, std::system_category(), "Failed to map dumb buffer"})); + BOOST_THROW_EXCEPTION((std::system_error{-ret, std::system_category(), "Failed to map KMS dumb buffer"})); } auto map = mmap(0, params.size, PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd, map_request.offset); @@ -83,7 +83,7 @@ class DumbFb ::memset(map, 0, pitch_ * height); } - ~DumbFb() noexcept(false) + ~CPUAddressableFb() noexcept(false) { struct drm_mode_destroy_dumb params = { gem_handle }; @@ -91,7 +91,7 @@ class DumbFb { if (!std::uncaught_exceptions()) { - BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to destroy dumb buffer"})); + BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to destroy KMS dumb buffer"})); } } } @@ -217,6 +217,8 @@ void mgek::EGLOutput::configure(size_t kms_mode_index) mgk::DRMModeCrtcUPtr crtc; mgk::DRMModePlaneUPtr plane; + refresh_connector(drm_fd, connector); + std::tie(crtc, plane) = mgk::find_crtc_with_primary_plane(drm_fd, connector); auto const crtc_id = crtc->crtc_id; @@ -233,7 +235,7 @@ void mgek::EGLOutput::configure(size_t kms_mode_index) std::system_error(-ret, std::system_category(), "Failed to create DRM Mode property blob")); } - DumbFb dummy{drm_fd, width, height}; + CPUAddressableFb dummy{drm_fd, width, height}; mgk::ObjectProperties crtc_props{drm_fd, crtc_id, DRM_MODE_OBJECT_CRTC}; diff --git a/src/platforms/eglstream-kms/server/eglstream_interface_provider.cpp b/src/platforms/eglstream-kms/server/eglstream_interface_provider.cpp new file mode 100644 index 00000000000..9123ed603df --- /dev/null +++ b/src/platforms/eglstream-kms/server/eglstream_interface_provider.cpp @@ -0,0 +1,82 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "eglstream_interface_provider.h" +#include "mir/graphics/platform.h" + +#include +#include + +#include + +namespace mg = mir::graphics; + +namespace +{ +class DisplayProviderImpl : public mg::EGLStreamDisplayProvider +{ +public: + DisplayProviderImpl(EGLDisplay dpy, std::optional stream); + + auto get_egl_display() const -> EGLDisplay override; + + auto claim_stream() -> EGLStreamKHR override; +private: + EGLDisplay dpy; + std::optional stream; +}; + +DisplayProviderImpl::DisplayProviderImpl(EGLDisplay dpy, std::optional stream) + : dpy{dpy}, + stream{stream} +{ +} + +auto DisplayProviderImpl::get_egl_display() const -> EGLDisplay +{ + return dpy; +} + +auto DisplayProviderImpl::claim_stream() -> EGLStreamKHR +{ + if (stream) + { + return *std::exchange(stream, std::nullopt); + } + BOOST_THROW_EXCEPTION((std::logic_error{"No EGLStream to claim (either incorrect object, or stream already claimed)"})); +} +} + +mg::eglstream::InterfaceProvider::InterfaceProvider(EGLDisplay dpy) + : dpy{dpy} +{ +} + +mg::eglstream::InterfaceProvider::InterfaceProvider(InterfaceProvider const& from, EGLStreamKHR with_stream) + : dpy{from.dpy}, + stream{with_stream} +{ +} + +auto mg::eglstream::InterfaceProvider::maybe_create_interface(DisplayProvider::Tag const& tag) + -> std::shared_ptr +{ + if (dynamic_cast(&tag)) + { + return std::make_shared(dpy, stream); + } + return nullptr; +} diff --git a/src/platforms/eglstream-kms/server/eglstream_interface_provider.h b/src/platforms/eglstream-kms/server/eglstream_interface_provider.h new file mode 100644 index 00000000000..313fdf09058 --- /dev/null +++ b/src/platforms/eglstream-kms/server/eglstream_interface_provider.h @@ -0,0 +1,44 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_EGLSTREAM_INTERFACE_PROVIDER_H_ +#define MIR_GRAPHICS_EGLSTREAM_INTERFACE_PROVIDER_H_ + +#include "mir/graphics/platform.h" + +#include + +namespace mir::graphics::eglstream +{ +class InterfaceProvider : public DisplayInterfaceProvider +{ +public: + InterfaceProvider(EGLDisplay dpy); + InterfaceProvider(InterfaceProvider const& from, EGLStreamKHR with_stream); + +protected: + auto maybe_create_interface(DisplayProvider::Tag const& tag) + -> std::shared_ptr override; + +private: + EGLDisplay dpy; + std::optional stream; +}; + +; +} + +#endif // MIR_GRAPHICS_EGLSTREAM_INTERFACE_PROVIDER_H_ diff --git a/src/platforms/eglstream-kms/server/platform.cpp b/src/platforms/eglstream-kms/server/platform.cpp index da3febfd4d1..777b0561a34 100644 --- a/src/platforms/eglstream-kms/server/platform.cpp +++ b/src/platforms/eglstream-kms/server/platform.cpp @@ -19,25 +19,195 @@ #include "platform.h" #include "buffer_allocator.h" #include "display.h" +#include "mir/graphics/platform.h" #include "utils.h" +#include "eglstream_interface_provider.h" +#include "one_shot_device_observer.h" + +#include "mir/console_services.h" #include "mir/graphics/egl_error.h" +#include "mir/renderer/gl/context.h" +#include #include #include -#include -#include namespace mg = mir::graphics; -namespace mge = mir::graphics::eglstream; namespace mgc = mir::graphics::common; +namespace mge = mir::graphics::eglstream; + +namespace +{ +const auto mir_xwayland_option = "MIR_XWAYLAND_OPTION"; + +auto make_egl_context(EGLDisplay dpy) -> EGLContext +{ + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_STREAM_BIT_KHR, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLConfig egl_config; + EGLint num_egl_configs{0}; + + if (eglChooseConfig(dpy, config_attr, &egl_config, 1, &num_egl_configs) == EGL_FALSE || + num_egl_configs != 1) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to choose ARGB EGL config")); + } + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + eglBindAPI(EGL_OPENGL_ES_API); + + EGLContext const ctx = eglCreateContext(dpy, egl_config, EGL_NO_CONTEXT, context_attr); + if (ctx == EGL_NO_CONTEXT) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to create EGL context"))); + } + return ctx; +} + +class BasicEGLContext : public mir::renderer::gl::Context +{ +public: + BasicEGLContext(EGLDisplay dpy) + : dpy{dpy}, + ctx{make_egl_context(dpy)} + { + } + + BasicEGLContext(EGLDisplay dpy, EGLContext copy_from) + : dpy{dpy}, + ctx{duplicate_context(dpy, copy_from)} + { + } + + void make_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make EGL context current")); + } + } + + void release_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release current EGL context")); + } + } + + auto make_share_context() const -> std::unique_ptr override + { + return std::make_unique(dpy, ctx); + } + + explicit operator EGLContext() override + { + return ctx; + } + +private: + static auto get_context_attrib(EGLDisplay dpy, EGLContext ctx, EGLenum attrib) -> EGLint + { + EGLint result; + if (eglQueryContext(dpy, ctx, attrib, &result) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to query attribute of EGLContext")); + } + return result; + } + + static auto duplicate_context(EGLDisplay dpy, EGLContext copy_from) -> EGLContext + { + EGLint const api = get_context_attrib(dpy, copy_from, EGL_CONTEXT_CLIENT_TYPE); + std::optional client_version; + if (api == EGL_OPENGL_ES_API) + { + // Client version only exists for GLES contexts + client_version = get_context_attrib(dpy, copy_from, EGL_CONTEXT_CLIENT_VERSION); + } + + EGLint const config_id = get_context_attrib(dpy, copy_from, EGL_CONFIG_ID); + EGLConfig config; + int num_configs; + EGLint const attributes[] = { + EGL_CONFIG_ID, config_id, + EGL_NONE + }; + if (eglChooseConfig(dpy, attributes, &config, 1, &num_configs) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to find existing EGLContext's EGLConfig"))); + } + + std::vector ctx_attribs; + if (client_version) + { + ctx_attribs.push_back(EGL_CONTEXT_CLIENT_VERSION); + ctx_attribs.push_back(client_version.value()); + } + ctx_attribs.push_back(EGL_NONE); + eglBindAPI(api); + auto ctx = eglCreateContext(dpy, config, copy_from, ctx_attribs.data()); + if (ctx == EGL_NO_CONTEXT) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create duplicate context")); + } + return ctx; + } + + EGLDisplay const dpy; + EGLContext const ctx; +}; +} + +mge::RenderingPlatform::RenderingPlatform(EGLDisplay dpy) + : dpy{dpy}, + ctx{std::make_unique(dpy)} +{ + // XWayland eglstream has always been kinda flaky, now it's somehow worse. + // Disable it until we've had a chance to look at what's wrong. + // setenv(mir_xwayland_option, "-eglstream", 1); +} + +mge::RenderingPlatform::~RenderingPlatform() +{ + unsetenv(mir_xwayland_option); +} + +mir::UniqueModulePtr mge::RenderingPlatform::create_buffer_allocator( + mg::Display const&) +{ + return mir::make_module_ptr(ctx->make_share_context()); +} + +auto mge::RenderingPlatform::maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr +{ + if (dynamic_cast(&type_tag)) + { + return std::make_shared(dpy, ctx->make_share_context()); + } + return nullptr; +} mge::DisplayPlatform::DisplayPlatform( ConsoleServices& console, EGLDeviceEXT device, std::shared_ptr display_report) - : display_report{std::move(display_report)}, - display{EGL_NO_DISPLAY} + : display_report{std::move(display_report)} { using namespace std::literals; @@ -49,8 +219,7 @@ mge::DisplayPlatform::DisplayPlatform( if (drm_node == mir::Fd::invalid) { - BOOST_THROW_EXCEPTION(( - std::runtime_error{"Failed to acquire DRM device node for device"})); + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire DRM device node for device"})); } int const drm_node_attrib[] = { @@ -60,57 +229,48 @@ mge::DisplayPlatform::DisplayPlatform( if (display == EGL_NO_DISPLAY) { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGLDisplay on the EGLDeviceEXT")); + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to create EGLDisplay on EGLDeviceEXT"))); } - EGLint major{1}; - EGLint minor{4}; - auto const required_egl_version_major = major; - auto const required_egl_version_minor = minor; - if (eglInitialize(display, &major, &minor) != EGL_TRUE) + std::tuple egl_version = std::make_tuple(1, 4); + if (eglInitialize(display, &std::get<0>(egl_version), &std::get<1>(egl_version)) != EGL_TRUE) { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to initialise EGL")); + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to initialize EGL"))); } - if ((major < required_egl_version_major) || - (major == required_egl_version_major && minor < required_egl_version_minor)) + if (egl_version < std::make_tuple(1, 4)) { BOOST_THROW_EXCEPTION((std::runtime_error{ "Incompatible EGL version"s + - "Wanted 1.4, got " + std::to_string(major) + "." + std::to_string(minor)})); + "Wanted 1.4, got " + + std::to_string(std::get<0>(egl_version)) + "." + std::to_string(std::get<1>(egl_version))})); } -} -mir::UniqueModulePtr mge::DisplayPlatform::create_display( - std::shared_ptr const& configuration_policy, - std::shared_ptr const& gl_config) -{ - auto retval = - mir::make_module_ptr( - drm_node, - display, - configuration_policy, - *gl_config, - display_report); - return retval; + provider = std::make_shared(display); } -namespace +mge::DisplayPlatform::~DisplayPlatform() { -const auto mir_xwayland_option = "MIR_XWAYLAND_OPTION"; -} - -mge::RenderingPlatform::RenderingPlatform() -{ - setenv(mir_xwayland_option, "-eglstream", 1); + if (display != EGL_NO_DISPLAY) + { + eglTerminate(display); + } } -mge::RenderingPlatform::~RenderingPlatform() +auto mge::DisplayPlatform::create_display( + std::shared_ptr const& configuration_policy, + std::shared_ptr const& gl_config) + -> UniqueModulePtr { - unsetenv(mir_xwayland_option); + return mir::make_module_ptr( + provider, + drm_node, + display, + configuration_policy, + *gl_config, + display_report); } -mir::UniqueModulePtr mge::RenderingPlatform::create_buffer_allocator( - mg::Display const& output) +auto mge::DisplayPlatform::interface_for() -> std::shared_ptr { - return mir::make_module_ptr(output); + return provider; } diff --git a/src/platforms/eglstream-kms/server/platform.h b/src/platforms/eglstream-kms/server/platform.h index dce1403c3a3..1eee5f51cf1 100644 --- a/src/platforms/eglstream-kms/server/platform.h +++ b/src/platforms/eglstream-kms/server/platform.h @@ -18,7 +18,6 @@ #define MIR_PLATFORMS_EGLSTREAM_KMS_PLATFORM_H_ #include "mir/graphics/platform.h" -#include "mir/options/option.h" #include "mir/graphics/graphic_buffer_allocator.h" #include "mir/graphics/display.h" #include "mir/fd.h" @@ -39,15 +38,23 @@ namespace graphics { namespace eglstream { +class InterfaceProvider; class RenderingPlatform : public graphics::RenderingPlatform { public: - RenderingPlatform(); + RenderingPlatform(EGLDisplay dpy); ~RenderingPlatform() override; UniqueModulePtr create_buffer_allocator(Display const& output) override; + +protected: + auto maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr override; +private: + EGLDisplay const dpy; + std::unique_ptr const ctx; }; class DisplayPlatform : public graphics::DisplayPlatform @@ -58,18 +65,23 @@ class DisplayPlatform : public graphics::DisplayPlatform EGLDeviceEXT device, std::shared_ptr display_report); - UniqueModulePtr create_display( - std::shared_ptr const& /*initial_conf_policy*/, - std::shared_ptr const& /*gl_config*/) override; + ~DisplayPlatform(); + + auto create_display( + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& gl_config) + ->UniqueModulePtr override; private: - std::shared_ptr const display_report; + auto interface_for() -> std::shared_ptr override; + + std::unique_ptr drm_device; EGLDisplay display; + std::shared_ptr provider; mir::Fd drm_node; - std::unique_ptr drm_device; + std::shared_ptr const display_report; }; } } } - #endif // MIR_PLATFORMS_EGLSTREAM_KMS_PLATFORM_H_ diff --git a/src/platforms/eglstream-kms/server/platform_symbols.cpp b/src/platforms/eglstream-kms/server/platform_symbols.cpp index 0504b58959b..c9ac198e100 100644 --- a/src/platforms/eglstream-kms/server/platform_symbols.cpp +++ b/src/platforms/eglstream-kms/server/platform_symbols.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -42,18 +43,124 @@ #include namespace mg = mir::graphics; -namespace mgc = mir::graphics::common; namespace mo = mir::options; +namespace mgc = mir::graphics::common; namespace mge = mir::graphics::eglstream; -namespace +auto create_rendering_platform( + mg::SupportedDevice const& device, + std::vector> const& /*displays*/, + mo::Option const&, + mir::EmergencyCleanupRegistry&) -> mir::UniqueModulePtr { -EGLDeviceEXT find_device() + mir::assert_entry_point_signature(&create_rendering_platform); + + auto platform_data = std::any_cast>(device.platform_data); + + EGLDisplay display{EGL_NO_DISPLAY}; + + if (auto dpy = std::get_if<0>(&platform_data)) + { + display = *dpy; + } + else if (auto device = std::get_if<1>(&platform_data)) + { + display = eglGetPlatformDisplayEXT( + EGL_PLATFORM_DEVICE_EXT, + *device, + nullptr); + + if (display == EGL_NO_DISPLAY) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to create EGL Display"))); + } + + EGLint major_ver{1}, minor_ver{4}; + if (!eglInitialize(display, &major_ver, &minor_ver)) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to initialise EGL"))); + } + } + + return mir::make_module_ptr(display); +} + +void add_graphics_platform_options(boost::program_options::options_description& /*config*/) { + mir::assert_entry_point_signature(&add_graphics_platform_options); +} + +auto probe_rendering_platform( + std::span> const& displays, + mir::ConsoleServices& /*console*/, + std::shared_ptr const& udev, + mo::ProgramOption const& /*options*/) -> std::vector +{ + mir::assert_entry_point_signature(&probe_rendering_platform); + + mg::probe::Result maximum_suitability = mg::probe::unsupported; + // First check if there are any displays we can possibly drive + std::vector> eglstream_providers; + for (auto const& display_provider : displays) + { + if (auto provider = display_provider->acquire_interface()) + { + // We can optimally drive an EGLStream display + mir::log_debug("EGLStream-capable display found"); + maximum_suitability = mg::probe::best; + eglstream_providers.push_back(provider); + } + if (display_provider->acquire_interface()) + { + /* We *can* support this output, but with slower buffer copies + * If another platform supports this device better, let it. + */ + maximum_suitability = mg::probe::supported; + } + } + + if (maximum_suitability == mg::probe::unsupported) + { + mir::log_debug("No outputs capable of accepting EGLStream input detected"); + mir::log_debug("Probing will be skipped"); + return {}; + } + + std::vector missing_extensions; + for (char const* extension : { + "EGL_EXT_platform_base", + "EGL_EXT_platform_device", + "EGL_EXT_device_base", + "EGL_EXT_device_enumeration", + "EGL_EXT_device_query"}) + { + if (!epoxy_has_egl_extension(EGL_NO_DISPLAY, extension)) + { + missing_extensions.push_back(extension); + } + } + + if (!missing_extensions.empty()) + { + std::stringstream message; + message << "Missing required extension" << (missing_extensions.size() > 1 ? "s:" : ":"); + for (auto missing_extension : missing_extensions) + { + message << " " << missing_extension; + } + + mir::log_debug("EGLStream platform is unsupported: %s", + message.str().c_str()); + return {}; + } + int device_count{0}; if (eglQueryDevicesEXT(0, nullptr, &device_count) != EGL_TRUE) { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to query device count with eglQueryDevicesEXT")); + mir::log_info("Platform claims to support EGL_EXT_device_base, but " + "eglQueryDevicesEXT falied: %s", + mg::egl_category().message(eglGetError()).c_str()); + return {}; } auto devices = std::make_unique(device_count); @@ -62,27 +169,128 @@ EGLDeviceEXT find_device() BOOST_THROW_EXCEPTION(mg::egl_error("Failed to get device list with eglQueryDevicesEXT")); } - auto device = std::find_if(devices.get(), devices.get() + device_count, - [](EGLDeviceEXT device) + std::vector supported_devices; + for (auto i = 0; i != device_count; ++i) + { + auto const& device = devices[i]; + try { - auto device_extensions = eglQueryDeviceStringEXT(device, EGL_EXTENSIONS); - if (device_extensions) + supported_devices.emplace_back(mg::SupportedDevice{ + udev->char_device_from_devnum(mge::devnum_for_device(device)), + mg::probe::unsupported, + nullptr + }); + } + catch (std::exception const& e) + { + mir::log_debug("Failed to find kernel device for EGLDevice: %s", e.what()); + continue; + } + + EGLDisplay display{EGL_NO_DISPLAY}; + bool using_display_platform_dpy = false; + for (auto const& display_provider : eglstream_providers) + { + auto display_dpy = display_provider->get_egl_display(); + EGLAttrib display_device; + if (eglQueryDisplayAttribEXT(display_dpy, EGL_DEVICE_EXT, &display_device) == EGL_TRUE) { - return strstr(device_extensions, "EGL_EXT_device_drm") != NULL; + if (reinterpret_cast(display_device) == device) + { + mir::log_debug("Rendering platform using EGLDeviceEXT matching Display platform"); + display = display_dpy; + using_display_platform_dpy = true; + } } - return false; - }); + else + { + mir::log_info("Failed to query EGLDeviceEXT from display platform"); + } + } - if (device == (devices.get() + device_count)) + if (display == EGL_NO_DISPLAY) + { + display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, nullptr); + + if (display == EGL_NO_DISPLAY) + { + mir::log_debug("Failed to create EGLDisplay: %s", mg::egl_category().message(eglGetError()).c_str()); + continue; + } + } + auto egl_init = mir::raii::paired_calls( + [&display, using_display_platform_dpy]() + { + if (!using_display_platform_dpy) + { + EGLint major_ver{1}, minor_ver{4}; + if (!eglInitialize(display, &major_ver, &minor_ver)) + { + mir::log_debug("Failed to initialise EGL: %s", mg::egl_category().message(eglGetError()).c_str()); + display = EGL_NO_DISPLAY; + } + } + }, + [&display, using_display_platform_dpy]() + { + if (display != EGL_NO_DISPLAY && ! using_display_platform_dpy) + { + eglTerminate(display); + } + }); + + if (display != EGL_NO_DISPLAY) + { + std::vector missing_extensions; + for (char const* extension : { + "EGL_KHR_stream_consumer_gltexture", + "EGL_NV_stream_attrib"}) + { + if (!epoxy_has_egl_extension(display, extension)) + { + missing_extensions.push_back(extension); + } + } + + for (auto const missing_extension: missing_extensions) + { + mir::log_info("EGLDevice found but unsuitable. Missing extension %s", missing_extension); + } + + if (missing_extensions.empty()) + { + // We've got EGL, and we've got the necessary EGL extensions. We're good. + supported_devices.back().support_level = maximum_suitability; + if (using_display_platform_dpy) + { + supported_devices.back().platform_data = std::variant{ + std::in_place_index<0>, display}; + } + else + { + supported_devices.back().platform_data = std::variant{ + std::in_place_index<1>, device}; + } + } + } + } + if (!std::any_of( + supported_devices.begin(), + supported_devices.end(), + [](auto const& device) + { + return device.support_level > mg::probe::unsupported; + })) { - BOOST_THROW_EXCEPTION(std::runtime_error("Couldn't find EGLDeviceEXT supporting EGL_EXT_device_drm?")); + mir::log_debug( + "EGLDeviceEXTs found, but none are suitable for Mir"); } - return *device; -} + + return supported_devices; } mir::UniqueModulePtr create_display_platform( - mg::SupportedDevice const&, + mg::SupportedDevice const& device, std::shared_ptr const& options, std::shared_ptr const&, std::shared_ptr const& console, @@ -95,23 +303,10 @@ mir::UniqueModulePtr create_display_platform( mg::initialise_egl_logger(); } - return mir::make_module_ptr(*console, find_device(), display_report); -} - -auto create_rendering_platform( - mg::SupportedDevice const& /*device*/, - std::vector> const& /*displays*/, - mo::Option const&, - mir::EmergencyCleanupRegistry&) -> mir::UniqueModulePtr -{ - mir::assert_entry_point_signature(&create_rendering_platform); - - return mir::make_module_ptr(); -} - -void add_graphics_platform_options(boost::program_options::options_description& /*config*/) -{ - mir::assert_entry_point_signature(&add_graphics_platform_options); + return mir::make_module_ptr( + *console, + std::any_cast(device.platform_data), + display_report); } auto probe_display_platform( @@ -189,8 +384,8 @@ auto probe_display_platform( supported_devices.emplace_back( mg::SupportedDevice{ udev->char_device_from_devnum(devnum), - mg::PlatformPriority::unsupported, - nullptr + mg::probe::unsupported, + device }); } catch (std::exception const& e) @@ -198,7 +393,6 @@ auto probe_display_platform( mir::log_info("Failed to query DRM node for EGLDevice: %s", e.what()); continue; } - if (drm_fd == mir::Fd::invalid) { mir::log_debug( @@ -350,7 +544,7 @@ auto probe_display_platform( if (epoxy_has_egl_extension(display, "EGL_EXT_output_base")) { - supported_devices.back().support_level = mg::PlatformPriority::best; + supported_devices.back().support_level = mg::probe::best; } else { @@ -373,132 +567,6 @@ auto probe_display_platform( return supported_devices; } -auto probe_rendering_platform( - std::shared_ptr const& /*console*/, - std::shared_ptr const& udev, - mo::ProgramOption const& /*options*/) -> std::vector -{ - mir::assert_entry_point_signature(&probe_rendering_platform); - - std::vector missing_extensions; - for (char const* extension : { - "EGL_EXT_platform_base", - "EGL_EXT_platform_device", - "EGL_EXT_device_base",}) - { - if (!epoxy_has_egl_extension(EGL_NO_DISPLAY, extension)) - { - missing_extensions.push_back(extension); - } - } - - if (!missing_extensions.empty()) - { - std::stringstream message; - message << "Missing required extension" << (missing_extensions.size() > 1 ? "s:" : ":"); - for (auto missing_extension : missing_extensions) - { - message << " " << missing_extension; - } - - mir::log_debug("EGLStream platform is unsupported: %s", - message.str().c_str()); - return {}; - } - - int device_count{0}; - if (eglQueryDevicesEXT(0, nullptr, &device_count) != EGL_TRUE) - { - mir::log_info("Platform claims to support EGL_EXT_device_base, but " - "eglQueryDevicesEXT falied: %s", - mg::egl_category().message(eglGetError()).c_str()); - return {}; - } - - auto devices = std::make_unique(device_count); - if (eglQueryDevicesEXT(device_count, devices.get(), &device_count) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to get device list with eglQueryDevicesEXT")); - } - - std::vector supported_devices; - for (auto i = 0; i != device_count; ++i) - { - auto const& device = devices[i]; - try - { - supported_devices.emplace_back(mg::SupportedDevice{ - udev->char_device_from_devnum(mge::devnum_for_device(device)), - mg::PlatformPriority::unsupported, - nullptr - }); - } - catch (std::exception const& e) - { - mir::log_debug("Failed to find kernel device for EGLDevice: %s", e.what()); - continue; - } - - EGLDisplay display = eglGetPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, device, nullptr); - - if (display == EGL_NO_DISPLAY) - { - mir::log_debug("Failed to create EGLDisplay: %s", mg::egl_category().message(eglGetError()).c_str()); - continue; - } - - auto egl_init = mir::raii::paired_calls( - [&display]() - { - EGLint major_ver{1}, minor_ver{4}; - if (!eglInitialize(display, &major_ver, &minor_ver)) - { - mir::log_debug("Failed to initialise EGL: %s", mg::egl_category().message(eglGetError()).c_str()); - display = EGL_NO_DISPLAY; - } - }, - [&display]() - { - if (display != EGL_NO_DISPLAY) - { - eglTerminate(display); - } - }); - - if (display != EGL_NO_DISPLAY) - { - std::vector missing_extensions; - for (char const* extension : { - "EGL_KHR_stream_consumer_gltexture", - "EGL_NV_stream_attrib"}) - { - if (!epoxy_has_egl_extension(display, extension)) - { - missing_extensions.push_back(extension); - } - } - - for (auto const missing_extension: missing_extensions) - { - mir::log_info("EGLDevice found but unsuitable. Missing extension %s", missing_extension); - } - - if (missing_extensions.empty()) - { - // We've got EGL, and we've got the necessary EGL extensions. We're good. - supported_devices.back().support_level = mg::PlatformPriority::best; - } - } - } - if (supported_devices.empty()) - { - mir::log_debug( - "EGLDeviceEXTs found, but none are suitable for Mir"); - } - - return supported_devices; -} - namespace { mir::ModuleProperties const description = { diff --git a/src/platforms/gbm-kms/server/CMakeLists.txt b/src/platforms/gbm-kms/server/CMakeLists.txt index c97843c0180..3122c903661 100644 --- a/src/platforms/gbm-kms/server/CMakeLists.txt +++ b/src/platforms/gbm-kms/server/CMakeLists.txt @@ -4,6 +4,10 @@ add_library( mirsharedgbmservercommon-static STATIC display_helpers.cpp + buffer_allocator.cpp + buffer_allocator.h + surfaceless_egl_context.h + surfaceless_egl_context.cpp ) target_include_directories( diff --git a/src/platforms/gbm-kms/server/buffer_allocator.cpp b/src/platforms/gbm-kms/server/buffer_allocator.cpp new file mode 100644 index 00000000000..ee6b2b59e25 --- /dev/null +++ b/src/platforms/gbm-kms/server/buffer_allocator.cpp @@ -0,0 +1,576 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "buffer_allocator.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/graphic_buffer_allocator.h" +#include "mir/graphics/linux_dmabuf.h" +#include "mir/graphics/dmabuf_buffer.h" +#include "mir/anonymous_shm_file.h" +#include "mir/renderer/sw/pixel_source.h" +#include "mir/graphics/platform.h" +#include "shm_buffer.h" +#include "mir/graphics/egl_context_executor.h" +#include "mir/graphics/egl_extensions.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/buffer_properties.h" +#include "mir/raii.h" +#include "mir/graphics/display.h" +#include "mir/renderer/gl/context.h" +#include "mir/renderer/gl/context_source.h" +#include "mir/graphics/egl_wayland_allocator.h" +#include "mir/executor.h" +#include "mir/renderer/gl/gl_surface.h" +#include "mir/graphics/display_buffer.h" +#include "kms/egl_helper.h" +#include "mir/graphics/drm_formats.h" +#include "display_helpers.h" +#include "mir/graphics/egl_error.h" +#include "cpu_copy_output_surface.h" +#include "surfaceless_egl_context.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIR_LOG_COMPONENT "gbm-kms-buffer-allocator" +#include +#include + +namespace mg = mir::graphics; +namespace mgg = mg::gbm; +namespace mgc = mg::common; +namespace geom = mir::geometry; + +mgg::BufferAllocator::BufferAllocator( + std::unique_ptr context, + std::shared_ptr egl_delegate, + std::shared_ptr dmabuf_provider) + : ctx{std::move(context)}, + egl_delegate{std::move(egl_delegate)}, + egl_extensions(std::make_shared()), + dmabuf_provider{std::move(dmabuf_provider)} +{ +} + +std::shared_ptr mgg::BufferAllocator::alloc_software_buffer( + geom::Size size, MirPixelFormat format) +{ + if (!mgc::MemoryBackedShmBuffer::supports(format)) + { + BOOST_THROW_EXCEPTION( + std::runtime_error( + "Trying to create SHM buffer with unsupported pixel format")); + } + + return std::make_shared(size, format, egl_delegate); +} + +std::vector mgg::BufferAllocator::supported_pixel_formats() +{ + /* + * supported_pixel_formats() is kind of a kludge. The right answer depends + * on whether you're using hardware or software, and it depends on + * the usage type (e.g. scanout). In the future it's also expected to + * depend on the GPU model in use at runtime. + * To be precise, ShmBuffer now supports OpenGL compositing of all + * but one MirPixelFormat (bgr_888). But GBM only supports [AX]RGB. + * So since we don't yet have an adequate API in place to query what the + * intended usage will be, we need to be conservative and report the + * intersection of ShmBuffer and GBM's pixel format support. That is + * just these two. Be aware however you can create a software surface + * with almost any pixel format and it will also work... + * TODO: Convert this to a loop that just queries the intersection of + * gbm_device_is_format_supported and ShmBuffer::supports(), however not + * yet while the former is buggy. (FIXME: LP: #1473901) + */ + static std::vector const pixel_formats{ + mir_pixel_format_argb_8888, + mir_pixel_format_xrgb_8888 + }; + + return pixel_formats; +} + +void mgg::BufferAllocator::bind_display(wl_display* display, std::shared_ptr wayland_executor) +{ + auto context_guard = mir::raii::paired_calls( + [this]() { ctx->make_current(); }, + [this]() { ctx->release_current(); }); + auto dpy = eglGetCurrentDisplay(); + + try + { + mg::wayland::bind_display(dpy, display, *egl_extensions); + egl_display_bound = true; + } + catch (...) + { + log( + logging::Severity::warning, + MIR_LOG_COMPONENT, + std::current_exception(), + "Failed to bind EGL Display to Wayland display, falling back to software buffers"); + } + + try + { + if (dmabuf_provider) + { + mg::EGLExtensions::EXTImageDmaBufImportModifiers modifier_ext{dpy}; + dmabuf_extension = + std::unique_ptr>( + new LinuxDmaBufUnstable{ + display, + dmabuf_provider, + }, + [wayland_executor](LinuxDmaBufUnstable* global) + { + // The global must be destroyed on the Wayland thread + wayland_executor->spawn( + [global]() + { + /* This is safe against double-frees, as the WaylandExecutor + * guarantees that work scheduled will only run while the Wayland + * event loop is running, and the main loop is stopped before + * wl_display_destroy() frees any globals + * + * This will, however, leak the global if the main loop is destroyed + * before the buffer allocator. Fixing that requires work in the + * wrapper generator. + */ + delete global; + }); + }); + mir::log_info("Enabled linux-dmabuf import support"); + } + } + catch (std::runtime_error const& error) + { + mir::log_info( + "Cannot enable linux-dmabuf import support: %s", error.what()); + mir::log( + mir::logging::Severity::debug, + MIR_LOG_COMPONENT, + std::current_exception(), + "Detailed error: "); + } + + this->wayland_executor = std::move(wayland_executor); +} + +void mgg::BufferAllocator::unbind_display(wl_display* display) +{ + if (egl_display_bound) + { + auto context_guard = mir::raii::paired_calls( + [this]() { ctx->make_current(); }, + [this]() { ctx->release_current(); }); + auto dpy = eglGetCurrentDisplay(); + + mg::wayland::unbind_display(dpy, display, *egl_extensions); + } +} + +std::shared_ptr mgg::BufferAllocator::buffer_from_resource( + wl_resource* buffer, + std::function&& on_consumed, + std::function&& on_release) +{ + auto context_guard = mir::raii::paired_calls( + [this]() { ctx->make_current(); }, + [this]() { ctx->release_current(); }); + + if (auto dmabuf = dmabuf_extension->buffer_from_resource( + buffer, + std::function{on_consumed}, + std::function{on_release}, + egl_delegate)) + { + return dmabuf; + } + return mg::wayland::buffer_from_resource( + buffer, + std::move(on_consumed), + std::move(on_release), + *egl_extensions, + egl_delegate); +} + +auto mgg::BufferAllocator::buffer_from_shm( + std::shared_ptr data, + std::function&& on_consumed, + std::function&& on_release) -> std::shared_ptr +{ + return std::make_shared( + std::move(data), + egl_delegate, + std::move(on_consumed), + std::move(on_release)); +} + +auto mgg::BufferAllocator::shared_egl_context() -> EGLContext +{ + return static_cast(*ctx); +} + +auto mgg::GLRenderingProvider::as_texture(std::shared_ptr buffer) -> std::shared_ptr +{ + if (auto dmabuf_texture = dmabuf_provider->as_texture(buffer)) + { + return dmabuf_texture; + } + else if (auto tex = std::dynamic_pointer_cast(buffer)) + { + return tex; + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to import buffer as texture; rendering will be incomplete"})); +} + +namespace +{ +class GBMOutputSurface : public mg::gl::OutputSurface +{ +public: + GBMOutputSurface( + EGLDisplay dpy, + EGLContext share_context, + mg::GLConfig const& config, + mg::GBMDisplayProvider& display, + mg::DRMFormat format, + mir::geometry::Size size) + : GBMOutputSurface( + size, + dpy, + create_renderable(dpy, share_context, format, config, display, size)) + { + } + + ~GBMOutputSurface() + { + eglDestroySurface(dpy, egl_surf); + eglDestroyContext(dpy, ctx); + } + + void bind() override + { + if (!gbm_surface_has_free_buffers(*surface)) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to render to GBM surface before releasing previous front buffer"})); + } + } + + void make_current() override + { + if (eglMakeCurrent(dpy, egl_surf, egl_surf, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make EGL context current")); + } + } + + void release_current() override + { + 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")); + } + } + + auto commit() -> std::unique_ptr override + { + if (eglSwapBuffers(dpy, egl_surf) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("eglSwapBuffers failed")); + } + return surface->claim_framebuffer(); + } + + auto size() const -> geom::Size override + { + return size_; + } + + auto layout() const -> Layout override + { + return Layout::GL; + } + +private: + static auto get_matching_configs(EGLDisplay dpy, EGLint const attr[]) -> std::vector + { + EGLint num_egl_configs; + + // First query the number of matching configs… + if ((eglChooseConfig(dpy, attr, nullptr, 0, &num_egl_configs) == EGL_FALSE) || + (num_egl_configs == 0)) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to enumerate any matching EGL configs")); + } + + std::vector matching_configs(static_cast(num_egl_configs)); + if ((eglChooseConfig(dpy, attr, matching_configs.data(), static_cast(matching_configs.size()), &num_egl_configs) == EGL_FALSE) || + (num_egl_configs == 0)) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to acquire matching EGL configs")); + } + + matching_configs.resize(static_cast(num_egl_configs)); + return matching_configs; + } + + + static auto egl_config_for_format(EGLDisplay dpy, mg::GLConfig const& config, mg::DRMFormat format) -> std::optional + { + mg::DRMFormat::RGBComponentInfo const default_components = { 8, 8, 8, 0}; + + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, static_cast(format.components().value_or(default_components).red_bits), + EGL_GREEN_SIZE, static_cast(format.components().value_or(default_components).green_bits), + EGL_BLUE_SIZE, static_cast(format.components().value_or(default_components).blue_bits), + EGL_ALPHA_SIZE, static_cast(format.components().value_or(default_components).alpha_bits.value_or(0)), + EGL_DEPTH_SIZE, config.depth_buffer_bits(), + EGL_STENCIL_SIZE, config.stencil_buffer_bits(), + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + for (auto const& config : get_matching_configs(dpy, config_attr)) + { + EGLint id; + if (eglGetConfigAttrib(dpy, config, EGL_NATIVE_VISUAL_ID, &id) == EGL_FALSE) + { + mir::log_warning( + "Failed to query GBM format of EGLConfig: %s", + mg::egl_category().message(eglGetError()).c_str()); + continue; + } + + if (id == static_cast(format)) + { + // We've found our matching format, so we're done here. + return config; + } + } + return std::nullopt; + } + + static auto create_renderable( + EGLDisplay dpy, + EGLContext share_context, + mg::DRMFormat format, + mg::GLConfig const& config, + mg::GBMDisplayProvider& display, + mir::geometry::Size size) + -> std::tuple, EGLContext, EGLSurface> + { + mg::EGLExtensions::PlatformBaseEXT egl_ext; + + auto const [egl_cfg, resolved_format] = + [&]() -> std::pair + { + if (auto eglconfig = egl_config_for_format(dpy, config, format)) + { + return std::make_pair(eglconfig.value(), format); + } + + auto alternate_format = + [&]() -> std::optional + { + if (format.has_alpha()) + { + return format.opaque_equivalent(); + } + return format.alpha_equivalent(); + }(); + + if (alternate_format) + { + if (auto eglconfig = egl_config_for_format(dpy, config, *alternate_format)) + { + return std::make_pair(eglconfig.value(), *alternate_format); + } + } + + BOOST_THROW_EXCEPTION(( + std::runtime_error{ + std::string{"Failed to find EGL config matching DRM format: "} + + format.name()})); + }(); + + auto modifiers = display.modifiers_for_format(resolved_format); + + auto surf = display.make_surface(size, resolved_format, modifiers); + + auto egl_surf = egl_ext.eglCreatePlatformWindowSurface( + dpy, + egl_cfg, + *surf, + nullptr); + + if (egl_surf == EGL_NO_SURFACE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL window surface")); + } + + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + auto egl_ctx = eglCreateContext(dpy, egl_cfg, share_context, context_attr); + if (egl_ctx == EGL_NO_CONTEXT) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); + } + + + return std::make_tuple(std::move(surf), egl_ctx, egl_surf); + } + + GBMOutputSurface( + geom::Size size, + EGLDisplay dpy, + std::tuple, EGLContext, EGLSurface> renderables) + : size_{size}, + surface{std::move(std::get<0>(renderables))}, + egl_surf{std::get<2>(renderables)}, + dpy{dpy}, + ctx{std::get<1>(renderables)} + { + } + + geom::Size const size_; + std::unique_ptr const surface; + EGLSurface const egl_surf; + EGLDisplay const dpy; + EGLContext const ctx; +}; +} + +auto mgg::GLRenderingProvider::suitability_for_allocator( + std::shared_ptr const& target) -> probe::Result +{ + // TODO: We *can* import from other allocators, maybe (anything with dma-buf is probably possible) + // For now, the simplest thing is to bind hard to own own allocator. + if (dynamic_cast(target.get())) + { + return probe::best; + } + return probe::unsupported; +} + +auto mgg::GLRenderingProvider::suitability_for_display( + std::shared_ptr const& target) -> probe::Result +{ + if (bound_display) + { + if (auto gbm_provider = target->acquire_interface()) + { + if (bound_display->gbm_device() == gbm_provider->gbm_device()) + { + /* We're rendering on the same device as display; + * it doesn't get better than this! + */ + return probe::best; + } + } + } + + if (target->acquire_interface()) + { + // We *can* render to CPU buffers, but if anyone can do better, let them. + return probe::supported; + } + + return probe::unsupported; +} + +auto mgg::GLRenderingProvider::surface_for_output( + std::shared_ptr target, + geom::Size size, + GLConfig const& config) + -> std::unique_ptr +{ + if (bound_display) + { + if (auto gbm_provider = target->acquire_interface()) + { + if (bound_display->gbm_device() == gbm_provider->gbm_device()) + { + return std::make_unique( + dpy, + ctx, + config, + *bound_display, + DRMFormat{DRM_FORMAT_XRGB8888}, + size); + } + } + } + auto cpu_provider = target->acquire_interface(); + + return std::make_unique( + dpy, + ctx, + std::move(cpu_provider), + size); +} + +auto mgg::GLRenderingProvider::make_framebuffer_provider(std::shared_ptr /*target*/) + -> std::unique_ptr +{ + // TODO: Make this not a null implementation, so bypass/overlays can work again + class NullFramebufferProvider : public FramebufferProvider + { + public: + auto buffer_to_framebuffer(std::shared_ptr) -> std::unique_ptr override + { + // It is safe to return nullptr; this will be treated as “this buffer cannot be used as + // a framebuffer”. + return {}; + } + }; + return std::make_unique(); +} + +mgg::GLRenderingProvider::GLRenderingProvider( + std::shared_ptr associated_display, + std::shared_ptr egl_delegate, + std::shared_ptr dmabuf_provider, + EGLDisplay dpy, + EGLContext ctx) + : bound_display{std::move(associated_display)}, + dpy{dpy}, + ctx{ctx}, + dmabuf_provider{std::move(dmabuf_provider)}, + egl_delegate{std::move(egl_delegate)} +{ +} diff --git a/src/platforms/gbm-kms/server/buffer_allocator.h b/src/platforms/gbm-kms/server/buffer_allocator.h new file mode 100644 index 00000000000..9303e7b2c22 --- /dev/null +++ b/src/platforms/gbm-kms/server/buffer_allocator.h @@ -0,0 +1,123 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_EGL_GENERIC_BUFFER_ALLOCATOR_H_ +#define MIR_GRAPHICS_EGL_GENERIC_BUFFER_ALLOCATOR_H_ + +#include "mir/graphics/graphic_buffer_allocator.h" +#include "mir/graphics/linux_dmabuf.h" +#include "mir/graphics/platform.h" + +#include +#include + +#include + +namespace mir +{ +namespace renderer +{ +namespace gl +{ +class Context; +} +} +namespace graphics +{ +class Display; +struct EGLExtensions; + +namespace common +{ +class EGLContextExecutor; +} + +namespace gbm +{ + +class GLRenderingProvider; +class SurfacelessEGLContext; + +class BufferAllocator: + public graphics::GraphicBufferAllocator +{ +public: + BufferAllocator( + std::unique_ptr ctx, + std::shared_ptr egl_delegate, + std::shared_ptr dmabuf_provider); + + std::shared_ptr alloc_software_buffer(geometry::Size size, MirPixelFormat) override; + std::vector supported_pixel_formats() override; + + void bind_display(wl_display* display, std::shared_ptr wayland_executor) override; + void unbind_display(wl_display* display) override; + auto buffer_from_resource( + wl_resource* buffer, + std::function&& on_consumed, + std::function&& on_release) -> std::shared_ptr override; + auto buffer_from_shm( + std::shared_ptr data, + std::function&& on_consumed, + std::function&& on_release) -> std::shared_ptr override; + + auto shared_egl_context() -> EGLContext; +private: + std::unique_ptr const ctx; + std::shared_ptr const egl_delegate; + std::shared_ptr wayland_executor; + std::unique_ptr> dmabuf_extension; + std::shared_ptr const egl_extensions; + std::shared_ptr const dmabuf_provider; + bool egl_display_bound{false}; +}; + +class GLRenderingProvider : public graphics::GLRenderingProvider +{ +public: + GLRenderingProvider( + std::shared_ptr associated_display, + std::shared_ptr egl_delegate, + std::shared_ptr dmabuf_provider, + EGLDisplay dpy, + EGLContext ctx); + + auto make_framebuffer_provider(std::shared_ptr target) + -> std::unique_ptr override; + + auto as_texture(std::shared_ptr buffer) -> std::shared_ptr override; + + auto suitability_for_allocator(std::shared_ptr const& target) -> probe::Result override; + + auto suitability_for_display(std::shared_ptr const& target) -> probe::Result override; + + auto surface_for_output( + std::shared_ptr framebuffer_provider, + geometry::Size size, + GLConfig const& config) -> std::unique_ptr override; + +private: + std::shared_ptr const bound_display; ///< Associated Display provider (if any - null is valid) + EGLDisplay const dpy; + EGLContext const ctx; + std::shared_ptr const dmabuf_provider; + std::shared_ptr const egl_delegate; +}; +} +} +} + +#endif // MIR_GRAPHICS_EGL_GENERIC_BUFFER_ALLOCATOR_H_ diff --git a/src/platforms/gbm-kms/server/display_helpers.cpp b/src/platforms/gbm-kms/server/display_helpers.cpp index 1f0fa74978c..2ad592eb741 100644 --- a/src/platforms/gbm-kms/server/display_helpers.cpp +++ b/src/platforms/gbm-kms/server/display_helpers.cpp @@ -234,30 +234,6 @@ mgmh::GBMHelper::GBMHelper(mir::Fd const& drm_fd) std::runtime_error("Failed to create GBM device")); } -mgg::GBMSurfaceUPtr mgmh::GBMHelper::create_scanout_surface( - uint32_t width, - uint32_t height, - uint32_t gbm_format, - bool sharable) const -{ - auto format_flags = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; - - if (sharable) - { - format_flags |= GBM_BO_USE_LINEAR; - } - - auto surface_raw = gbm_surface_create(device, width, height, gbm_format, format_flags); - - auto gbm_surface_deleter = [](gbm_surface *p) { if (p) gbm_surface_destroy(p); }; - GBMSurfaceUPtr surface{surface_raw, gbm_surface_deleter}; - - if (!surface) - BOOST_THROW_EXCEPTION(std::runtime_error("Failed to create GBM scanout surface")); - - return surface; -} - mgmh::GBMHelper::~GBMHelper() { if (device) diff --git a/src/platforms/gbm-kms/server/display_helpers.h b/src/platforms/gbm-kms/server/display_helpers.h index 4438846d710..27c789474d0 100644 --- a/src/platforms/gbm-kms/server/display_helpers.h +++ b/src/platforms/gbm-kms/server/display_helpers.h @@ -79,8 +79,6 @@ class GBMHelper GBMHelper(const GBMHelper&) = delete; GBMHelper& operator=(const GBMHelper&) = delete; - GBMSurfaceUPtr create_scanout_surface(uint32_t width, uint32_t height, uint32_t gbm_format, bool sharable) const; - gbm_device* const device; }; diff --git a/src/platforms/gbm-kms/server/kms/CMakeLists.txt b/src/platforms/gbm-kms/server/kms/CMakeLists.txt index a6670a5b29b..fac0b24655f 100644 --- a/src/platforms/gbm-kms/server/kms/CMakeLists.txt +++ b/src/platforms/gbm-kms/server/kms/CMakeLists.txt @@ -37,12 +37,17 @@ add_library( egl_helper.cpp quirks.cpp quirks.h + kms_framebuffer.h + cpu_addressable_fb.cpp + cpu_addressable_fb.h ) target_link_libraries( mirplatformgraphicsgbmkmsobjects - mirsharedgbmservercommon-static + PRIVATE + mirsharedgbmservercommon-static + ${GBM_LDFLAGS} ${GBM_LIBRARIES} ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/symbols.map.in diff --git a/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.cpp b/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.cpp new file mode 100644 index 00000000000..dacf1932a60 --- /dev/null +++ b/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.cpp @@ -0,0 +1,354 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "cpu_addressable_fb.h" + +#include "mir/geometry/forward.h" +#include "mir/log.h" +#include "mir_toolkit/common.h" + +#include +#include +#include +#include + +namespace mg = mir::graphics; +namespace mgg = mg::gbm; + +class mgg::CPUAddressableFB::Buffer : public mir::renderer::software::RWMappableBuffer +{ + template + class Mapping : public mir::renderer::software::Mapping + { + public: + Mapping( + uint32_t width, uint32_t height, + uint32_t pitch, + MirPixelFormat format, + T* data, + size_t len) + : size_{width, height}, + stride_{pitch}, + format_{format}, + data_{data}, + len_{len} + { + } + + ~Mapping() + { + if (::munmap(const_cast::type *>(data_), len_) == -1) + { + // It's unclear how this could happen, but tell *someone* about it if it does! + mir::log_error("Failed to unmap CPU buffer: %s (%i)", strerror(errno), errno); + } + } + + [[nodiscard]] + auto format() const -> MirPixelFormat + { + return format_; + } + + [[nodiscard]] + auto stride() const -> mir::geometry::Stride + { + return stride_; + } + + [[nodiscard]] + auto size() const -> mir::geometry::Size + { + return size_; + } + + [[nodiscard]] + auto data() -> T* + { + return data_; + } + + [[nodiscard]] + auto len() const -> size_t + { + return len_; + } + + private: + mir::geometry::Size const size_; + mir::geometry::Stride const stride_; + MirPixelFormat const format_; + T* const data_; + size_t const len_; + }; + +public: + ~Buffer() + { + struct drm_mode_destroy_dumb params = { gem_handle }; + + if (auto const err = drmIoctl(drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, ¶ms)) + { + mir::log_error("Failed destroy CPU-accessible buffer: %s (%i)", strerror(-err), -err); + } + } + + static auto create_kms_dumb_buffer(mir::Fd drm_fd, DRMFormat format, mir::geometry::Size const& size) -> + std::unique_ptr + { + struct drm_mode_create_dumb params = {}; + + params.bpp = 32; + params.width = size.width.as_uint32_t(); + params.height = size.height.as_uint32_t(); + + if (auto const err = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, ¶ms)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + "Failed to allocate CPU-accessible buffer"})); + } + + return std::unique_ptr{ + new Buffer{std::move(drm_fd), params, format}}; + } + + auto map_writeable() -> std::unique_ptr> override + { + auto const data = mmap_buffer(PROT_WRITE); + return std::make_unique>( + width(), height(), + pitch(), + format(), + static_cast(data), + size_); + } + auto map_readable() -> std::unique_ptr> override + { + auto const data = mmap_buffer(PROT_READ); + return std::make_unique>( + width(), height(), pitch(), format(), + static_cast(data), + size_); + } + + auto map_rw() -> std::unique_ptr> override + { + auto const data = mmap_buffer(PROT_READ | PROT_WRITE); + return std::make_unique>( + width(), height(), + pitch(), + format(), + static_cast(data), + size_); + } + + [[nodiscard]] + auto handle() const -> uint32_t + { + return gem_handle; + } + [[nodiscard]] + auto pitch() const -> uint32_t + { + return pitch_; + } + [[nodiscard]] + auto width() const -> uint32_t + { + return width_; + } + [[nodiscard]] + auto height() const -> uint32_t + { + return height_; + } + + auto format() const -> MirPixelFormat override + { + return format_.as_mir_format().value_or(mir_pixel_format_invalid); + } + + auto stride() const -> geometry::Stride override + { + return geometry::Stride{pitch_}; + } + + auto size() const -> geometry::Size override + { + return geometry::Size{width_, height_}; + } +private: + Buffer( + mir::Fd fd, + struct drm_mode_create_dumb const& params, + DRMFormat format) + : drm_fd{std::move(fd)}, + width_{params.width}, + height_{params.height}, + pitch_{params.pitch}, + format_{format}, + gem_handle{params.handle}, + size_{static_cast(params.size)} /* params.size is a u64, but the kernel cannot possibly return a value outside the range of size_t + * On 64bit systems this is trivial, as sizeof(size_t) == sizeof(u64) + * On 32bit systems sizeof(size_t) < sizeof(u64), so this cast is necessary. + */ + { + } + + auto mmap_buffer(int access_mode) -> void* + { + struct drm_mode_map_dumb map_request = {}; + + map_request.handle = gem_handle; + + if (auto err = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_request)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + -err, + std::system_category(), + "Failed to map buffer for CPU-access"})); + } + + auto map = mmap(0, size_, access_mode, MAP_SHARED, drm_fd, map_request.offset); + if (map == MAP_FAILED) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to mmap() buffer"})); + } + + return map; + } + + mir::Fd const drm_fd; + uint32_t const width_; + uint32_t const height_; + uint32_t const pitch_; + DRMFormat const format_; + uint32_t const gem_handle; + size_t const size_; +}; + +mgg::CPUAddressableFB::CPUAddressableFB( + mir::Fd const& drm_fd, + bool supports_modifiers, + DRMFormat format, + mir::geometry::Size const& size) + : CPUAddressableFB(drm_fd, supports_modifiers, format, Buffer::create_kms_dumb_buffer(drm_fd, format, size)) +{ +} + +mgg::CPUAddressableFB::~CPUAddressableFB() +{ + drmModeRmFB(drm_fd, fb_id); +} + +mgg::CPUAddressableFB::CPUAddressableFB( + mir::Fd drm_fd, + bool supports_modifiers, + DRMFormat format, + std::unique_ptr buffer) + : drm_fd{std::move(drm_fd)}, + fb_id{fb_id_for_buffer(this->drm_fd, supports_modifiers, format, *buffer)}, + buffer{std::move(buffer)} +{ +} + +auto mgg::CPUAddressableFB::map_writeable() -> std::unique_ptr> +{ + return buffer->map_writeable(); +} + +auto mgg::CPUAddressableFB::format() const -> MirPixelFormat +{ + return buffer->format(); +} + +auto mgg::CPUAddressableFB::stride() const -> geometry::Stride +{ + return buffer->stride(); +} + +auto mgg::CPUAddressableFB::size() const -> geometry::Size +{ + return buffer->size(); +} + +mgg::CPUAddressableFB::operator uint32_t() const +{ + return fb_id; +} + +auto mgg::CPUAddressableFB::fb_id_for_buffer( + mir::Fd const &drm_fd, + bool supports_modifiers, + DRMFormat format, + Buffer const& buf) -> uint32_t +{ + uint32_t fb_id; + uint32_t const pitches[4] = { buf.pitch(), 0, 0, 0 }; + uint32_t const handles[4] = { buf.handle(), 0, 0, 0 }; + uint32_t const offsets[4] = { 0, 0, 0, 0 }; + uint64_t const modifiers[4] = { DRM_FORMAT_MOD_LINEAR, 0, 0, 0 }; + if (supports_modifiers) + { + if (auto err = drmModeAddFB2WithModifiers( + drm_fd, + buf.width(), + buf.height(), + format, + handles, + pitches, + offsets, + modifiers, + &fb_id, + DRM_MODE_FB_MODIFIERS)) + { + BOOST_THROW_EXCEPTION((std::system_error{ + -err, + std::system_category(), + "Failed to create DRM framebuffer from CPU-accessible buffer"})); + } + } + else + { + if (auto err = drmModeAddFB2( + drm_fd, + buf.width(), + buf.height(), + format, + handles, + pitches, + offsets, + &fb_id, + 0)) + { + BOOST_THROW_EXCEPTION((std::system_error{ + -err, + std::system_category(), + "Failed to create DRM framebuffer from CPU-accessible buffer"})); + } + + + } + return fb_id; +} diff --git a/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.h b/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.h new file mode 100644 index 00000000000..9d2b3494168 --- /dev/null +++ b/src/platforms/gbm-kms/server/kms/cpu_addressable_fb.h @@ -0,0 +1,68 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_FB_H_ +#define MIR_GRAPHICS_GBM_FB_H_ + +#include "mir/fd.h" +#include "mir/graphics/platform.h" + +#include "kms_framebuffer.h" + +namespace mir::graphics::gbm +{ +class CPUAddressableFB : public FBHandle, public CPUAddressableDisplayProvider::MappableFB +{ +public: + CPUAddressableFB( + mir::Fd const& drm_fd, + bool supports_modifiers, + DRMFormat format, + mir::geometry::Size const& size); + ~CPUAddressableFB() override; + + auto map_writeable() -> std::unique_ptr> override; + + auto format() const -> MirPixelFormat override; + auto stride() const -> geometry::Stride override; + auto size() const -> geometry::Size override; + + operator uint32_t() const override; + + CPUAddressableFB(CPUAddressableFB const&) = delete; + CPUAddressableFB& operator=(CPUAddressableFB const&) = delete; +private: + class Buffer; + + CPUAddressableFB( + mir::Fd drm_fd, + bool supports_modifiers, + DRMFormat format, + std::unique_ptr buffer); + static auto fb_id_for_buffer( + mir::Fd const& drm_fd, + bool supports_modifiers, + DRMFormat format, + Buffer const& buf) -> uint32_t; + + mir::Fd const drm_fd; + uint32_t const fb_id; + std::unique_ptr const buffer; +}; + +} + +#endif //MIR_GRAPHICS_GBM_FB_H_ diff --git a/src/platforms/gbm-kms/server/kms/display.cpp b/src/platforms/gbm-kms/server/kms/display.cpp index cdba44dda5f..e250fb1b522 100644 --- a/src/platforms/gbm-kms/server/kms/display.cpp +++ b/src/platforms/gbm-kms/server/kms/display.cpp @@ -17,11 +17,14 @@ #include "display.h" #include "cursor.h" #include "kms/egl_helper.h" +#include "mir/graphics/platform.h" #include "platform.h" #include "display_buffer.h" #include "kms_display_configuration.h" #include "kms_output.h" #include "kms_page_flipper.h" +#include "kms_framebuffer.h" +#include "cpu_addressable_fb.h" #include "mir/console_services.h" #include "mir/graphics/overlapping_output_grouping.h" #include "mir/graphics/event_handler_register.h" @@ -32,16 +35,25 @@ #include "mir/geometry/rectangle.h" #include "mir/renderer/gl/context.h" #include "mir/graphics/drm_formats.h" +#include "mir/graphics/egl_error.h" #include #include -#include +#include +#include +#include +#include #define MIR_LOG_COMPONENT "gbm-kms" #include "mir/log.h" #include "kms-utils/drm_mode_resources.h" #include "kms-utils/kms_connector.h" +#include +#include +#include +#include + #include #include #include @@ -52,43 +64,6 @@ namespace geom = mir::geometry; namespace { - -class GBMGLContext : public mir::renderer::gl::Context -{ -public: - GBMGLContext(mgg::helpers::GBMHelper const& gbm, - mg::GLConfig const& gl_config, - EGLContext shared_context) - : egl{gl_config} - { - egl.setup(gbm, shared_context); - } - - void make_current() const override - { - egl.make_current(); - } - - void release_current() const override - { - egl.release_current(); - } - -private: - mgg::helpers::EGLHelper egl; -}; - -std::vector drm_fds_from_drm_helpers( - std::vector> const& helpers) -{ - std::vector fds; - for (auto const& helper: helpers) - { - fds.push_back(helper->fd); - } - return fds; -} - double calculate_vrefresh_hz(drmModeModeInfo const& mode) { if (mode.htotal == 0 || mode.vtotal == 0) @@ -117,97 +92,79 @@ char const* describe_connection_status(drmModeConnector const& connection) } } -void log_drm_details(std::vector> const& drm) +void log_drm_details(mir::Fd const& drm_fd) { mir::log_info("DRM device details:"); - for (auto const& device : drm) - { - auto version = std::unique_ptr{ - drmGetVersion(device->fd), - &drmFreeVersion}; + auto version = std::unique_ptr{ + drmGetVersion(drm_fd), + &drmFreeVersion}; + + auto device_name = std::unique_ptr{ + drmGetDeviceNameFromFd(drm_fd), + &free + }; + + mir::log_info( + "%s: using driver %s [%s] (version: %i.%i.%i driver date: %s)", + device_name.get(), + version->name, + version->desc, + version->version_major, + version->version_minor, + version->version_patchlevel, + version->date); - auto device_name = std::unique_ptr{ - drmGetDeviceNameFromFd(device->fd), - &free - }; - - mir::log_info( - "%s: using driver %s [%s] (version: %i.%i.%i driver date: %s)", - device_name.get(), - version->name, - version->desc, - version->version_major, - version->version_minor, - version->version_patchlevel, - version->date); - - try + try + { + mg::kms::DRMModeResources resources{drm_fd}; + for (auto const& connector : resources.connectors()) { - mg::kms::DRMModeResources resources{device->fd}; - for (auto const& connector : resources.connectors()) + mir::log_info( + "\tOutput: %s (%s)", + mg::kms::connector_name(connector).c_str(), + describe_connection_status(*connector)); + for (auto i = 0; i < connector->count_modes; ++i) { mir::log_info( - "\tOutput: %s (%s)", - mg::kms::connector_name(connector).c_str(), - describe_connection_status(*connector)); - for (auto i = 0; i < connector->count_modes; ++i) - { - mir::log_info( - "\t\tMode: %i×%i@%.2f", - connector->modes[i].hdisplay, - connector->modes[i].vdisplay, - calculate_vrefresh_hz(connector->modes[i])); - } + "\t\tMode: %i×%i@%.2f", + connector->modes[i].hdisplay, + connector->modes[i].vdisplay, + calculate_vrefresh_hz(connector->modes[i])); } } - catch (std::exception const& error) - { - mir::log_info( - "\tKMS not supported (%s)", - error.what()); - } + } + catch (std::exception const& error) + { + mir::log_info( + "\tKMS not supported (%s)", + error.what()); } } } -mgg::Display::Display(std::vector> const& drm, - std::shared_ptr const& gbm, - mgg::BypassOption bypass_option, - std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config, - std::shared_ptr const& listener) - : drm{drm}, - gbm(gbm), +mgg::Display::Display( + std::shared_ptr parent, + mir::Fd drm_fd, + mgg::BypassOption bypass_option, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& listener) + : owner{std::move(parent)}, + drm_fd{std::move(drm_fd)}, listener(listener), monitor(mir::udev::Context()), - shared_egl{*gl_config}, output_container{ std::make_shared( - drm_fds_from_drm_helpers(drm), - [ - listener, - flippers = std::unordered_map>{} - ](int drm_fd) mutable - { - auto& flipper = flippers[drm_fd]; - if (!flipper) - { - flipper = std::make_shared(drm_fd, listener); - } - return flipper; - })}, + this->drm_fd, + std::make_shared(this->drm_fd, listener))}, current_display_configuration{output_container}, dirty_configuration{false}, - bypass_option(bypass_option), - gl_config{gl_config} + bypass_option(bypass_option) { - shared_egl.setup(*gbm); - monitor.filter_by_subsystem_and_type("drm", "drm_minor"); monitor.enable(); - log_drm_details(drm); + log_drm_details(this->drm_fd); initial_conf_policy->apply_to(current_display_configuration); @@ -368,11 +325,6 @@ void mgg::Display::clear_connected_unused_outputs() }); } -std::unique_ptr mgg::Display::create_gl_context() const -{ - return std::make_unique(*gbm, *gl_config, shared_egl.context()); -} - bool mgg::Display::apply_if_configuration_preserves_display_buffers( mg::DisplayConfiguration const& conf) { @@ -392,101 +344,6 @@ bool mgg::Display::apply_if_configuration_preserves_display_buffers( return result; } -namespace -{ -/* - * Add output to the grouping, maintaining the invariant that each vector of outputs - * is a single GPU memory domain. - */ -void add_to_drm_device_group( - std::vector>>& grouping, - std::shared_ptr&& output) -{ - for (auto &group : grouping) - { - /* - * We could be smarter about this, but being on the same DRM device is guaranteed - * to be in the same GPU memory domain :). - */ - if (group.front()->drm_fd() == output->drm_fd()) - { - group.push_back(std::move(output)); - break; - } - } - if (output) - { - grouping.push_back(std::vector>{std::move(output)}); - } -} - -auto get_equivalent_other_alphaness_format(mg::DRMFormat const format) -> std::optional -{ - if (format.has_alpha()) - { - return format.opaque_equivalent(); - } - else - { - return format.alpha_equivalent(); - } -} - -auto make_surface_with_egl_context( - geom::Size size, - mg::DRMFormat format, - mgg::helpers::GBMHelper const& gbm, - mg::GLConfig const& config, - EGLContext shared_context, - bool cross_gpu) - -> std::tuple -{ - auto surface = gbm.create_scanout_surface(size.width.as_uint32_t(), size.height.as_uint32_t(), format, cross_gpu); - auto raw_surface = surface.get(); - - try - { - return std::make_tuple( - std::move(surface), - mgg::helpers::EGLHelper{ - config, - gbm, - raw_surface, - format, - shared_context - }); - } - catch (mgg::helpers::EGLHelper::NoMatchingEGLConfig const&) - { - // If the format has an opaque/alpha equivalent, try that - auto equivalent_format = get_equivalent_other_alphaness_format(format); - if (!equivalent_format) - { - // No equivalent format to try, so bail - throw; - } - - surface = gbm.create_scanout_surface( - size.width.as_uint32_t(), - size.height.as_uint32_t(), - *equivalent_format, - cross_gpu); - raw_surface = surface.get(); - return std::make_tuple( - std::move(surface), - - mgg::helpers::EGLHelper{ - config, - gbm, - raw_surface, - *equivalent_format, - shared_context - }); - } -} - -} - void mgg::Display::configure_locked( mgg::RealKMSDisplayConfiguration const& kms_conf, std::lock_guard const&) @@ -529,8 +386,7 @@ void mgg::Display::configure_locked( [&](OverlappingOutputGroup const& group) { auto bounding_rect = group.bounding_rectangle(); - // Each vector is a single GPU memory domain - std::vector>> kms_output_groups; + std::vector> kms_outputs; glm::mat2 transformation; geom::Size current_mode_resolution; @@ -546,7 +402,7 @@ void mgg::Display::configure_locked( { kms_output->set_power_mode(conf_output.power_mode); kms_output->set_gamma(conf_output.gamma); - add_to_drm_device_group(kms_output_groups, std::move(kms_output)); + kms_outputs.push_back(std::move(kms_output)); } /* @@ -565,43 +421,16 @@ void mgg::Display::configure_locked( } else { - uint32_t const width = current_mode_resolution.width.as_uint32_t(); - uint32_t const height = current_mode_resolution.height.as_uint32_t(); - - for (auto const& group : kms_output_groups) - { - // TODO: Pull this out of the configuration - // TODO: Actually query available formats! - mg::DRMFormat format{GBM_FORMAT_XRGB8888}; - /* - * In a hybrid setup a scanout surface needs to be allocated differently if it - * needs to be able to be shared across GPUs. This likely reduces performance. - * - * As a first cut, assume every scanout buffer in a hybrid setup might need - * to be shared. - */ - auto [surface, egl] = make_surface_with_egl_context( - current_mode_resolution, - format, - *gbm, - *gl_config, - shared_egl.context(), - drm.size() != 1); - auto db = std::make_unique( - bypass_option, - listener, - group, - GBMOutputSurface{ - group.front()->drm_fd(), - std::move(surface), - width, height, - std::move(egl) - }, - bounding_rect, - transformation); - - display_buffers_new.push_back(std::move(db)); - } + auto db = std::make_unique( + owner, + drm_fd, + bypass_option, + listener, + kms_outputs, + bounding_rect, + transformation); + + display_buffers_new.push_back(std::move(db)); } }); @@ -615,3 +444,313 @@ void mgg::Display::configure_locked( /* Clear connected but unused outputs */ clear_connected_unused_outputs(); } + +namespace +{ +auto drm_get_cap_checked(mir::Fd const& drm_fd, uint64_t cap) -> uint64_t +{ + uint64_t value; + if (drmGetCap(drm_fd, cap, &value)) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to query DRM capabilities"})); + } + return value; +} +} + +mgg::CPUAddressableDisplayProvider::CPUAddressableDisplayProvider(mir::Fd drm_fd) + : drm_fd{std::move(drm_fd)}, + supports_modifiers{drm_get_cap_checked(this->drm_fd, DRM_CAP_ADDFB2_MODIFIERS) == 1} +{ +} + +auto mgg::CPUAddressableDisplayProvider::supported_formats() const + -> std::vector +{ + // TODO: Pull out of DRM info + return {mg::DRMFormat{DRM_FORMAT_XRGB8888}, mg::DRMFormat{DRM_FORMAT_ARGB8888}}; +} + +auto mgg::CPUAddressableDisplayProvider::alloc_fb( + geom::Size size, DRMFormat format) -> std::unique_ptr +{ + return std::make_unique(drm_fd, supports_modifiers, format, size); +} + +namespace +{ +auto gbm_create_device_checked(mir::Fd fd) -> std::shared_ptr +{ + errno = 0; + auto device = gbm_create_device(fd); + if (!device) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to create GBM device"})); + } + return { + device, + [fd](struct gbm_device* device) // Capture shared ownership of fd to keep gdm_device functional + { + if (device) + { + gbm_device_destroy(device); + } + } + }; +} +} + +mgg::GBMDisplayProvider::GBMDisplayProvider( + mir::Fd drm_fd) + : fd{std::move(drm_fd)}, + gbm{gbm_create_device_checked(fd)} +{ +} + +auto mgg::GBMDisplayProvider::gbm_device() const -> std::shared_ptr +{ + return gbm; +} + +auto mgg::GBMDisplayProvider::is_same_device(mir::udev::Device const& render_device) const -> bool +{ +#ifndef MIR_DRM_HAS_GET_DEVICE_FROM_DEVID + class CStrFree + { + public: + void operator()(char* str) + { + if (str) + { + free(str); + } + } + }; + + std::unique_ptr primary_node{drmGetPrimaryDeviceNameFromFd(fd)}; + std::unique_ptr render_node{drmGetRenderDeviceNameFromFd(fd)}; + + mir::log_debug("Checking whether %s is the same device as (%s, %s)...", render_device.devnode(), primary_node.get(), render_node.get()); + + if (primary_node) + { + if (strcmp(primary_node.get(), render_device.devnode()) == 0) + { + mir::log_debug("\t...yup."); + return true; + } + } + if (render_node) + { + if (strcmp(render_node.get(), render_device.devnode()) == 0) + { + mir::log_debug("\t...yup."); + return true; + } + } + + mir::log_debug("\t...nope."); + + return false; +#else + drmDevicePtr us{nullptr}, them{nullptr}; + + drmGetDeviceFromDevId(render_device.devno(), 0, &them); + drmGetDevice2(fd, 0, &us); + + bool result = drmDevicesEqual(us, them); + + drmDeviceFree(us); + drmDeviceFree(them); + + return result; +#endif +} + +auto mgg::GBMDisplayProvider::supported_formats() const -> std::vector +{ + // TODO: Pull out of KMS plane info + return { DRMFormat{DRM_FORMAT_XRGB8888}, DRMFormat{DRM_FORMAT_ARGB8888}}; +} + +auto mgg::GBMDisplayProvider::modifiers_for_format(DRMFormat /*format*/) const -> std::vector +{ + // TODO: Pull out off KMS plane info + return {}; +} + +namespace +{ +using LockedFrontBuffer = std::unique_ptr>; + +class GBMBoFramebuffer : public mgg::FBHandle +{ +public: + static auto framebuffer_for_frontbuffer(mir::Fd const& drm_fd, LockedFrontBuffer bo) -> std::unique_ptr + { + if (auto cached_fb = static_cast*>(gbm_bo_get_user_data(bo.get()))) + { + return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *cached_fb}}; + } + + auto fb_id = new std::shared_ptr{ + new uint32_t{0}, + [drm_fd](uint32_t* fb_id) + { + if (*fb_id) + { + drmModeRmFB(drm_fd, *fb_id); + } + delete fb_id; + }}; + uint32_t handles[4] = {gbm_bo_get_handle(bo.get()).u32, 0, 0, 0}; + uint32_t strides[4] = {gbm_bo_get_stride(bo.get()), 0, 0, 0}; + uint32_t offsets[4] = {gbm_bo_get_offset(bo.get(), 0), 0, 0, 0}; + + auto format = gbm_bo_get_format(bo.get()); + + auto const width = gbm_bo_get_width(bo.get()); + auto const height = gbm_bo_get_height(bo.get()); + + /* Create a KMS FB object with the gbm_bo attached to it. */ + auto ret = drmModeAddFB2(drm_fd, width, height, format, + handles, strides, offsets, fb_id->get(), 0); + if (ret) + return nullptr; + + gbm_bo_set_user_data(bo.get(), fb_id, [](gbm_bo*, void* fb_ptr) { delete static_cast*>(fb_ptr); }); + + return std::unique_ptr{new GBMBoFramebuffer{std::move(bo), *fb_id}}; + } + + operator uint32_t() const override + { + return *fb_id; + } + + auto size() const -> geom::Size override + { + return + geom::Size{ + gbm_bo_get_width(bo.get()), + gbm_bo_get_height(bo.get())}; + } +private: + GBMBoFramebuffer(LockedFrontBuffer bo, std::shared_ptr fb) + : bo{std::move(bo)}, + fb_id{std::move(fb)} + { + } + + LockedFrontBuffer const bo; + std::shared_ptr const fb_id; +}; + +namespace +{ +auto create_gbm_surface(gbm_device* gbm, geom::Size size, mg::DRMFormat format, std::span modifiers) + -> std::shared_ptr +{ + auto const surface = + [&]() + { + if (modifiers.empty()) + { + // If we have no no modifiers don't use the with-modifiers creation path. + auto foo = GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT; + return gbm_surface_create( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + foo); + } + else + { + return gbm_surface_create_with_modifiers2( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + modifiers.data(), + modifiers.size(), + GBM_BO_USE_RENDERING | GBM_BO_USE_SCANOUT); + } + }(); + + if (!surface) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to create GBM surface"})); + + } + return std::shared_ptr{ + surface, + [](auto surface) { gbm_surface_destroy(surface); }}; +} +} + +class GBMSurfaceImpl : public mgg::GBMDisplayProvider::GBMSurface +{ +public: + GBMSurfaceImpl(mir::Fd drm_fd, gbm_device* gbm, geom::Size size, mg::DRMFormat const format, std::span modifiers) + : drm_fd{std::move(drm_fd)}, + surface{create_gbm_surface(gbm, size, format, modifiers)} + { + } + + GBMSurfaceImpl(GBMSurfaceImpl const&) = delete; + auto operator=(GBMSurfaceImpl const&) -> GBMSurfaceImpl const& = delete; + + operator gbm_surface*() const override + { + return surface.get(); + } + + auto claim_framebuffer() -> std::unique_ptr override + { + if (!gbm_surface_has_free_buffers(surface.get())) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + EBUSY, + std::system_category(), + "Too many buffers consumed from GBM surface"})); + } + + LockedFrontBuffer bo{ + gbm_surface_lock_front_buffer(surface.get()), + [shared_surface = surface](gbm_bo* bo) { gbm_surface_release_buffer(shared_surface.get(), bo); }}; + + if (!bo) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire GBM front buffer"})); + } + + auto fb = GBMBoFramebuffer::framebuffer_for_frontbuffer(drm_fd, std::move(bo)); + if (!fb) + { + BOOST_THROW_EXCEPTION((std::system_error{errno, std::system_category(), "Failed to make DRM FB"})); + } + return fb; + } +private: + mir::Fd const drm_fd; + std::shared_ptr const surface; +}; +} + +auto mgg::GBMDisplayProvider::make_surface(geom::Size size, DRMFormat format, std::span modifiers) + -> std::unique_ptr +{ + return std::make_unique(fd, gbm.get(), std::move(size), format, modifiers); +} diff --git a/src/platforms/gbm-kms/server/kms/display.h b/src/platforms/gbm-kms/server/kms/display.h index 983b165d5be..be6cf74ac86 100644 --- a/src/platforms/gbm-kms/server/kms/display.h +++ b/src/platforms/gbm-kms/server/kms/display.h @@ -24,6 +24,7 @@ #include "display_helpers.h" #include "egl_helper.h" #include "platform_common.h" +#include "mir/graphics/platform.h" #include #include @@ -34,6 +35,7 @@ namespace mir namespace graphics { +class DisplayInterfaceProvider; class DisplayReport; class DisplayBuffer; class DisplayConfigurationPolicy; @@ -56,12 +58,12 @@ class Cursor; class Display : public graphics::Display { public: - Display(std::vector> const& drm, - std::shared_ptr const& gbm, - BypassOption bypass_option, - std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config, - std::shared_ptr const& listener); + Display( + std::shared_ptr parent, + mir::Fd drm_fd, + BypassOption bypass_option, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& listener); ~Display(); geometry::Rectangle view_area() const; @@ -81,19 +83,16 @@ class Display : public graphics::Display std::shared_ptr create_hardware_cursor() override; - std::unique_ptr create_gl_context() const override; - private: void clear_connected_unused_outputs(); + std::shared_ptr const owner; mutable std::mutex configuration_mutex; - std::vector> const drm; - std::shared_ptr const gbm; + mir::Fd const drm_fd; std::shared_ptr const listener; mir::udev::Monitor monitor; - helpers::EGLHelper shared_egl; - std::vector> display_buffers; std::shared_ptr const output_container; + std::vector> display_buffers; mutable RealKMSDisplayConfiguration current_display_configuration; mutable std::atomic dirty_configuration; @@ -103,9 +102,42 @@ class Display : public graphics::Display BypassOption bypass_option; std::weak_ptr cursor; - std::shared_ptr const gl_config; }; +class CPUAddressableDisplayProvider : public graphics::CPUAddressableDisplayProvider +{ +public: + explicit CPUAddressableDisplayProvider(mir::Fd drm_fd); + + auto supported_formats() const + -> std::vector override; + + auto alloc_fb(geometry::Size pixel_size, DRMFormat format) + -> std::unique_ptr override; + +private: + mir::Fd const drm_fd; + bool const supports_modifiers; +}; + +class GBMDisplayProvider : public graphics::GBMDisplayProvider +{ +public: + GBMDisplayProvider(mir::Fd drm_fd); + + auto is_same_device(mir::udev::Device const& render_device) const -> bool override; + + auto gbm_device() const -> std::shared_ptr override; + + auto supported_formats() const -> std::vector override; + + auto modifiers_for_format(DRMFormat format) const -> std::vector override; + + auto make_surface(geometry::Size size, DRMFormat format, std::span modifier) -> std::unique_ptr override; +private: + mir::Fd const fd; + std::shared_ptr const gbm; +}; } } } diff --git a/src/platforms/gbm-kms/server/kms/display_buffer.cpp b/src/platforms/gbm-kms/server/kms/display_buffer.cpp index 296dfb52c7a..130ae5f523f 100644 --- a/src/platforms/gbm-kms/server/kms/display_buffer.cpp +++ b/src/platforms/gbm-kms/server/kms/display_buffer.cpp @@ -16,6 +16,8 @@ #include "display_buffer.h" #include "kms_output.h" +#include "cpu_addressable_fb.h" +#include "mir/fd.h" #include "mir/graphics/display_report.h" #include "mir/graphics/transformation.h" #include "bypass.h" @@ -39,399 +41,20 @@ #include #include -namespace mg = mir::graphics; namespace mgg = mir::graphics::gbm; namespace geom = mir::geometry; -namespace mgmh = mir::graphics::gbm::helpers; - -mgg::GBMOutputSurface::FrontBuffer::FrontBuffer() - : surf{nullptr}, - bo{nullptr} -{ -} - -mgg::GBMOutputSurface::FrontBuffer::FrontBuffer(gbm_surface* surface) - : surf{surface}, - bo{gbm_surface_lock_front_buffer(surface)} -{ - if (!bo) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to acquire front buffer of gbm_surface")); - } -} - -mgg::GBMOutputSurface::FrontBuffer::~FrontBuffer() -{ - if (surf) - { - gbm_surface_release_buffer(surf, bo); - } -} - -mgg::GBMOutputSurface::FrontBuffer::FrontBuffer(FrontBuffer&& from) - : surf{from.surf}, - bo{from.bo} -{ - const_cast(from.surf) = nullptr; - const_cast(from.bo) = nullptr; -} - -auto mgg::GBMOutputSurface::FrontBuffer::operator=(FrontBuffer&& from) -> FrontBuffer& -{ - if (surf) - { - gbm_surface_release_buffer(surf, bo); - } - - const_cast(surf) = from.surf; - const_cast(bo) = from.bo; - - const_cast(from.surf) = nullptr; - const_cast(from.bo) = nullptr; - - return *this; -} - -auto mgg::GBMOutputSurface::FrontBuffer::operator=(std::nullptr_t) -> FrontBuffer& -{ - return *this = FrontBuffer{}; -} - -mgg::GBMOutputSurface::FrontBuffer::operator gbm_bo*() -{ - return bo; -} - -mgg::GBMOutputSurface::FrontBuffer::operator bool() const -{ - return (surf != nullptr) && (bo != nullptr); -} - -namespace -{ -void require_extensions( - std::initializer_list extensions, - std::function const& extension_getter) -{ - std::stringstream missing_extensions; - - std::string const ext_string = extension_getter(); - - for (auto extension : extensions) - { - if (ext_string.find(extension) == std::string::npos) - { - missing_extensions << "Missing " << extension << std::endl; - } - } - - if (!missing_extensions.str().empty()) - { - BOOST_THROW_EXCEPTION(std::runtime_error( - std::string("Missing required extensions:\n") + missing_extensions.str())); - } -} - -void require_egl_extensions(EGLDisplay dpy, std::initializer_list extensions) -{ - require_extensions( - extensions, - [dpy]() -> std::string - { - char const* maybe_exts = eglQueryString(dpy, EGL_EXTENSIONS); - if (maybe_exts) - return maybe_exts; - return {}; - }); -} - -void require_gl_extensions(std::initializer_list extensions) -{ - require_extensions( - extensions, - []() -> std::string - { - char const *maybe_exts = - reinterpret_cast(glGetString(GL_EXTENSIONS)); - if (maybe_exts) - return maybe_exts; - return {}; - }); -} - -bool needs_bounce_buffer(mgg::KMSOutput const& destination, gbm_bo* source) -{ - return destination.buffer_requires_migration(source); -} - -const GLchar* const vshader = - { - "attribute vec4 position;\n" - "attribute vec2 texcoord;\n" - "varying vec2 v_texcoord;\n" - "void main() {\n" - " gl_Position = position;\n" - " v_texcoord = texcoord;\n" - "}\n" - }; - -const GLchar* const fshader = - { - "#ifdef GL_ES\n" - "precision mediump float;\n" - "#endif\n" - "uniform sampler2D tex;" - "varying vec2 v_texcoord;\n" - "void main() {\n" - " gl_FragColor = texture2D(tex, v_texcoord);\n" - "}\n" - }; - -class VBO -{ -public: - VBO(void const* data, size_t size) - { - glGenBuffers(1, &buf_id); - glBindBuffer(GL_ARRAY_BUFFER, buf_id); - glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - ~VBO() - { - glDeleteBuffers(1, &buf_id); - } - - void bind() - { - glBindBuffer(GL_ARRAY_BUFFER, buf_id); - } - -private: - GLuint buf_id; -}; - -class NoAuxGlConfig : public mg::GLConfig -{ -public: - int depth_buffer_bits() const override - { - return 0; - } - int stencil_buffer_bits() const override - { - return 0; - } -}; - -class EGLBufferCopier -{ -public: - EGLBufferCopier( - mir::Fd const& drm_fd, - uint32_t width, - uint32_t height, - uint32_t format) - : eglCreateImageKHR{ - reinterpret_cast(eglGetProcAddress("eglCreateImageKHR"))}, - eglDestroyImageKHR{ - reinterpret_cast(eglGetProcAddress("eglDestroyImageKHR"))}, - glEGLImageTargetTexture2DOES{ - reinterpret_cast(eglGetProcAddress("glEGLImageTargetTexture2DOES"))}, - device{drm_fd}, - width{width}, - height{height}, - surface{device.create_scanout_surface(width, height, format, false)}, - egl{NoAuxGlConfig{}} - { - egl.setup(device, surface.get(), format, EGL_NO_CONTEXT, true); - - require_gl_extensions({ - "GL_OES_EGL_image" - }); - - require_egl_extensions( - eglGetCurrentDisplay(), - { - "EGL_KHR_image_base", - "EGL_EXT_image_dma_buf_import" - }); - - egl.make_current(); - - auto vertex = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertex, 1, &vshader, nullptr); - glCompileShader(vertex); - - int compiled; - glGetShaderiv (vertex, GL_COMPILE_STATUS, &compiled); - - if (!compiled) { - GLchar log[1024]; - - glGetShaderInfoLog (vertex, sizeof log - 1, NULL, log); - log[sizeof log - 1] = '\0'; - glDeleteShader (vertex); - - BOOST_THROW_EXCEPTION( - std::runtime_error(std::string{"Failed to compile vertex shader:\n"} + log)); - } - - - auto fragment = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragment, 1, &fshader, nullptr); - glCompileShader(fragment); - - glGetShaderiv (fragment, GL_COMPILE_STATUS, &compiled); - if (!compiled) { - GLchar log[1024]; - - glGetShaderInfoLog (fragment, sizeof log - 1, NULL, log); - log[sizeof log - 1] = '\0'; - glDeleteShader (fragment); - - BOOST_THROW_EXCEPTION( - std::runtime_error(std::string{"Failed to compile fragment shader:\n"} + log)); - } - - prog = glCreateProgram(); - glAttachShader(prog, vertex); - glAttachShader(prog, fragment); - glLinkProgram(prog); - glGetProgramiv (prog, GL_LINK_STATUS, &compiled); - if (!compiled) { - GLchar log[1024]; - - glGetProgramInfoLog (prog, sizeof log - 1, NULL, log); - log[sizeof log - 1] = '\0'; - - BOOST_THROW_EXCEPTION( - std::runtime_error(std::string{"Failed to link shader prog:\n"} + log)); - } - - glUseProgram(prog); - - attrpos = glGetAttribLocation(prog, "position"); - attrtex = glGetAttribLocation(prog, "texcoord"); - auto unitex = glGetUniformLocation(prog, "tex"); - - glGenTextures(1, &tex); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, tex); - - glUniform1i(unitex, 0); - - 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_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - static GLfloat const dest_vert[4][2] = - { { -1.f, 1.f }, { 1.f, 1.f }, { 1.f, -1.f }, { -1.f, -1.f } }; - vert_data = std::make_unique(dest_vert, sizeof(dest_vert)); - - static GLfloat const tex_vert[4][2] = - { - { 0.f, 0.f }, { 1.f, 0.f }, { 1.f, 1.f }, { 0.f, 1.f }, - }; - tex_data = std::make_unique(tex_vert, sizeof(tex_vert)); - } - - EGLBufferCopier(EGLBufferCopier const&) = delete; - EGLBufferCopier& operator==(EGLBufferCopier const&) = delete; - - ~EGLBufferCopier() - { - egl.make_current(); - vert_data = nullptr; - tex_data = nullptr; - } - - mgg::GBMOutputSurface::FrontBuffer copy_front_buffer_from(mgg::GBMOutputSurface::FrontBuffer&& from) - { - egl.make_current(); - mir::Fd const dma_buf{gbm_bo_get_fd(from)}; - - glUseProgram(prog); - - EGLint const image_attrs[] = { - EGL_WIDTH, static_cast(width), - EGL_HEIGHT, static_cast(height), - EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_XRGB8888, - EGL_DMA_BUF_PLANE0_FD_EXT, static_cast(dma_buf), - EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, - EGL_DMA_BUF_PLANE0_PITCH_EXT, static_cast(gbm_bo_get_stride(from)), - EGL_NONE - }; - - auto image = eglCreateImageKHR( - eglGetCurrentDisplay(), - EGL_NO_CONTEXT, - EGL_LINUX_DMA_BUF_EXT, - nullptr, - image_attrs); - - if (image == EGL_NO_IMAGE_KHR) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGLImage from dma_buf")); - } - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, tex); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); - - vert_data->bind(); - glVertexAttribPointer (attrpos, 2, GL_FLOAT, GL_FALSE, 0, 0); - - tex_data->bind(); - glVertexAttribPointer (attrtex, 2, GL_FLOAT, GL_FALSE, 0, 0); - - glEnableVertexAttribArray(attrpos); - glEnableVertexAttribArray(attrtex); - - GLubyte const idx[] = { 0, 1, 3, 2 }; - glDrawElements (GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx); - - egl.swap_buffers(); - - eglDestroyImageKHR(eglGetCurrentDisplay(), image); - - egl.release_current(); - return mgg::GBMOutputSurface::FrontBuffer(surface.get()); - } - - private: - - PFNEGLCREATEIMAGEKHRPROC const eglCreateImageKHR; - PFNEGLDESTROYIMAGEKHRPROC const eglDestroyImageKHR; - PFNGLEGLIMAGETARGETTEXTURE2DOESPROC const glEGLImageTargetTexture2DOES; - - mgmh::GBMHelper const device; - uint32_t const width; - uint32_t const height; - mgg::GBMSurfaceUPtr const surface; - mgmh::EGLHelper egl; - GLuint prog; - GLuint tex; - GLint attrtex; - GLint attrpos; - std::unique_ptr vert_data; - std::unique_ptr tex_data; -}; -} mgg::DisplayBuffer::DisplayBuffer( - mgg::BypassOption option, + std::shared_ptr provider, + mir::Fd drm_fd, + mgg::BypassOption, std::shared_ptr const& listener, std::vector> const& outputs, - GBMOutputSurface&& surface_gbm, geom::Rectangle const& area, glm::mat2 const& transformation) - : listener(listener), - bypass_option(option), + : provider{std::move(provider)}, + listener(listener), outputs(outputs), - surface{std::move(surface_gbm)}, area(area), transform{transformation}, needs_set_crtc{false}, @@ -439,69 +62,39 @@ mgg::DisplayBuffer::DisplayBuffer( { listener->report_successful_setup_of_native_resources(); - make_current(); - - listener->report_successful_egl_make_current_on_construction(); - - glClear(GL_COLOR_BUFFER_BIT); - - surface.swap_buffers(); - - listener->report_successful_egl_buffer_swap_on_construction(); - - auto temporary_front = surface.lock_front(); - if (!temporary_front) - fatal_error("Failed to get frontbuffer"); - - if (needs_bounce_buffer(*outputs.front(), temporary_front)) - { - mir::log_info("Hybrid GPU setup detected; DisplayBuffer using EGL buffer copies for migration"); - get_front_buffer = std::bind( - std::mem_fn(&EGLBufferCopier::copy_front_buffer_from), - std::make_shared( - mir::Fd{mir::IntOwnedFd{outputs.front()->drm_fd()}}, - surface.size().width.as_int(), - surface.size().height.as_int(), - GBM_FORMAT_XRGB8888), - std::placeholders::_1); - } - else + // If any of the outputs have a CRTC mismatch, we will want to set all of them + // so that they're all showing the same buffer. + bool has_crtc_mismatch = false; + for (auto& output : outputs) { - mir::log_info("Detected single-GPU DisplayBuffer. Rendering will be sent directly to output"); - get_front_buffer = [](auto&& fb) { return std::move(fb); }; + has_crtc_mismatch = output->has_crtc_mismatch(); + if (has_crtc_mismatch) + break; } - visible_composite_frame = get_front_buffer(std::move(temporary_front)); - - /* - * Check that our (possibly bounced) front buffer is usable on *all* the - * outputs we've been asked to output on. - */ - for (auto const& output : outputs) + if (has_crtc_mismatch) { - if (output->buffer_requires_migration(visible_composite_frame)) - { - BOOST_THROW_EXCEPTION(std::invalid_argument( - "Attempted to create a DisplayBuffer spanning multiple GPU memory domains")); + mir::log_info("Clearing screen due to differing encountered and target modes"); + // TODO: Pull a supported format out of KMS rather than assuming XRGB8888 + auto initial_fb = std::make_shared( + std::move(drm_fd), + false, + DRMFormat{DRM_FORMAT_XRGB8888}, + area.size); + + auto mapping = initial_fb->map_writeable(); + ::memset(mapping->data(), 24, mapping->len()); + + visible_fb = std::move(initial_fb); + for (auto &output: outputs) { + output->set_crtc(*visible_fb); } + listener->report_successful_drm_mode_set_crtc_on_construction(); } - - set_crtc(*outputs.front()->fb_for(visible_composite_frame)); - - release_current(); - - listener->report_successful_drm_mode_set_crtc_on_construction(); listener->report_successful_display_construction(); - surface.report_egl_configuration( - [&listener] (EGLDisplay disp, EGLConfig cfg) - { - listener->report_egl_configuration(disp, cfg); - }); } -mgg::DisplayBuffer::~DisplayBuffer() -{ -} +mgg::DisplayBuffer::~DisplayBuffer() = default; geom::Rectangle mgg::DisplayBuffer::view_area() const { @@ -519,33 +112,31 @@ void mgg::DisplayBuffer::set_transformation(glm::mat2 const& t, geometry::Rectan area = a; } -bool mgg::DisplayBuffer::overlay(RenderableList const& renderable_list) +bool mgg::DisplayBuffer::overlay(std::vector const& renderable_list) { - glm::mat2 static const no_transformation(1); - if (transform == no_transformation && - (bypass_option == mgg::BypassOption::allowed)) + // TODO: implement more than the most basic case. + if (renderable_list.size() != 1) { - mgg::BypassMatch bypass_match(area); - auto bypass_it = std::find_if(renderable_list.rbegin(), renderable_list.rend(), bypass_match); - if (bypass_it != renderable_list.rend()) - { - auto bypass_buffer = (*bypass_it)->buffer(); - auto dmabuf_image = dynamic_cast(bypass_buffer->native_buffer_base()); - if (dmabuf_image && - bypass_buffer->size() == surface.size()) - { - if (auto bufobj = outputs.front()->fb_for(*dmabuf_image)) - { - bypass_buf = bypass_buffer; - bypass_bufobj = bufobj; - return true; - } - } - } + return false; + } + + if (renderable_list[0].screen_positon != view_area()) + { + return false; + } + + if (renderable_list[0].source_position.top_left != geom::PointF {0,0} || + renderable_list[0].source_position.size.width.as_value() != view_area().size.width.as_int() || + renderable_list[0].source_position.size.height.as_value() != view_area().size.height.as_int()) + { + return false; } - bypass_buf = nullptr; - bypass_bufobj = nullptr; + if (auto fb = std::dynamic_pointer_cast(renderable_list[0].buffer)) + { + next_swap = std::move(fb); + return true; + } return false; } @@ -555,13 +146,6 @@ void mgg::DisplayBuffer::for_each_display_buffer( f(*this); } -void mgg::DisplayBuffer::swap_buffers() -{ - surface.swap_buffers(); - bypass_buf = nullptr; - bypass_bufobj = nullptr; -} - void mgg::DisplayBuffer::set_crtc(FBHandle const& forced_frame) { for (auto& output : outputs) @@ -590,20 +174,18 @@ void mgg::DisplayBuffer::post() */ wait_for_page_flip(); - std::shared_ptr bufobj; - if (bypass_buf) + if (!next_swap) { - bufobj = bypass_bufobj; - } - else - { - scheduled_composite_frame = get_front_buffer(surface.lock_front()); - bufobj = outputs.front()->fb_for(scheduled_composite_frame); - if (!bufobj) - fatal_error("Failed to get front buffer object"); + // Hey! No one has given us a next frame yet, so we don't have to change what's onscreen. + // Sweet! We can just bail. + return; } + /* + * Otherwise, pull the next frame into the pending slot + */ + scheduled_fb = std::move(next_swap); + next_swap = nullptr; - scheduled_fb = std::move(bufobj); /* * Try to schedule a page flip as first preference to avoid tearing. * [will complete in a background thread] @@ -630,7 +212,7 @@ void mgg::DisplayBuffer::post() // Predicted worst case render time for the next frame... auto predicted_render_time = 50ms; - if (bypass_buf) + if (holding_client_buffers) { /* * For composited frames we defer wait_for_page_flip till just before @@ -641,7 +223,6 @@ void mgg::DisplayBuffer::post() * Also, bypass does not need the deferred page flip because it has * no compositing/rendering step for which to save time for. */ - scheduled_bypass_frame = bypass_buf; wait_for_page_flip(); // It's very likely the next frame will be bypassed like this one so @@ -667,10 +248,6 @@ void mgg::DisplayBuffer::post() */ } - // Buffer lifetimes are managed exclusively by scheduled*/visible* now - bypass_buf = nullptr; - bypass_bufobj = nullptr; - recommend_sleep = 0ms; if (outputs.size() == 1) { @@ -714,38 +291,6 @@ void mgg::DisplayBuffer::wait_for_page_flip() page_flips_pending = false; } - - if (scheduled_bypass_frame || scheduled_composite_frame) - { - // Why are both of these grouped into a single statement? - // Because in either case both types of frame need releasing each time. - - visible_bypass_frame = scheduled_bypass_frame; - scheduled_bypass_frame = nullptr; - - visible_composite_frame = std::move(scheduled_composite_frame); - scheduled_composite_frame = nullptr; - } -} - -auto mgg::DisplayBuffer::size() const -> geometry::Size -{ - return surface.size(); -} - -void mgg::DisplayBuffer::make_current() -{ - surface.make_current(); -} - -void mgg::DisplayBuffer::bind() -{ - surface.bind(); -} - -void mgg::DisplayBuffer::release_current() -{ - surface.release_current(); } void mgg::DisplayBuffer::schedule_set_crtc() @@ -753,70 +298,30 @@ void mgg::DisplayBuffer::schedule_set_crtc() needs_set_crtc = true; } -mg::NativeDisplayBuffer* mgg::DisplayBuffer::native_display_buffer() -{ - return this; -} - -mgg::GBMOutputSurface::GBMOutputSurface( - int drm_fd, - GBMSurfaceUPtr&& surface, - uint32_t width, - uint32_t height, - helpers::EGLHelper&& egl) - : drm_fd{drm_fd}, - width{width}, - height{height}, - egl{std::move(egl)}, - surface{std::move(surface)} -{ -} - -mgg::GBMOutputSurface::GBMOutputSurface(GBMOutputSurface&& from) - : drm_fd{from.drm_fd}, - width{from.width}, - height{from.height}, - egl{std::move(from.egl)}, - surface{std::move(from.surface)} +auto mir::graphics::gbm::DisplayBuffer::display_provider() const -> std::shared_ptr { + return provider; } -auto mgg::GBMOutputSurface::GBMOutputSurface::size() const -> geometry::Size +auto mgg::DisplayBuffer::drm_fd() const -> mir::Fd { - return {width, height}; + return mir::Fd{mir::IntOwnedFd{outputs.front()->drm_fd()}}; } -void mgg::GBMOutputSurface::make_current() +void mir::graphics::gbm::DisplayBuffer::set_next_image(std::unique_ptr content) { - if (!egl.make_current()) + std::vector const single_buffer = { + DisplayElement { + view_area(), + geom::RectangleF{ + {0, 0}, + {view_area().size.width.as_value(), view_area().size.height.as_value()}}, + std::move(content) + } + }; + if (!overlay(single_buffer)) { - fatal_error("Failed to make EGL surface current"); + // Oh, oh! We should be *guaranteed* to “overlay” a single Framebuffer; this is likely a programming error + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to post buffer to display"})); } } - -void mgg::GBMOutputSurface::release_current() -{ - egl.release_current(); -} - -void mgg::GBMOutputSurface::swap_buffers() -{ - if (!egl.swap_buffers()) - fatal_error("Failed to perform buffer swap"); -} - -void mgg::GBMOutputSurface::bind() -{ - -} - -auto mgg::GBMOutputSurface::lock_front() -> FrontBuffer -{ - return FrontBuffer{surface.get()}; -} - -void mgg::GBMOutputSurface::report_egl_configuration( - std::function const& to) -{ - egl.report_egl_configuration(to); -} diff --git a/src/platforms/gbm-kms/server/kms/display_buffer.h b/src/platforms/gbm-kms/server/kms/display_buffer.h index 94634e3c823..f30523dd728 100644 --- a/src/platforms/gbm-kms/server/kms/display_buffer.h +++ b/src/platforms/gbm-kms/server/kms/display_buffer.h @@ -19,10 +19,10 @@ #include "mir/graphics/display_buffer.h" #include "mir/graphics/display.h" -#include "mir/renderer/gl/render_target.h" #include "display_helpers.h" #include "egl_helper.h" #include "platform_common.h" +#include "kms_framebuffer.h" #include #include @@ -44,74 +44,25 @@ class FBHandle; class KMSOutput; class NativeBuffer; -class GBMOutputSurface : public renderer::gl::RenderTarget -{ -public: - class FrontBuffer - { - public: - FrontBuffer(); - FrontBuffer(gbm_surface* surface); - FrontBuffer(FrontBuffer&& from); - - ~FrontBuffer(); - - FrontBuffer& operator=(FrontBuffer&& from); - FrontBuffer& operator=(std::nullptr_t); - - operator gbm_bo*(); - operator bool() const; - - private: - gbm_surface* const surf; - gbm_bo* const bo; - }; - - GBMOutputSurface( - int drm_fd, - GBMSurfaceUPtr&& surface, - uint32_t width, - uint32_t height, - helpers::EGLHelper&& egl); - GBMOutputSurface(GBMOutputSurface&& from); - - // gl::RenderTarget - auto size() const -> geometry::Size override; - void make_current() override; - void release_current() override; - void swap_buffers() override; - void bind() override; - - FrontBuffer lock_front(); - void report_egl_configuration(std::function const& to); -private: - int const drm_fd; - uint32_t width, height; - helpers::EGLHelper egl; - GBMSurfaceUPtr surface; -}; - class DisplayBuffer : public graphics::DisplayBuffer, - public graphics::DisplaySyncGroup, - public graphics::NativeDisplayBuffer, - public renderer::gl::RenderTarget + public graphics::DisplaySyncGroup { public: - DisplayBuffer(BypassOption bypass_options, - std::shared_ptr const& listener, - std::vector> const& outputs, - GBMOutputSurface&& surface_gbm, - geometry::Rectangle const& area, - glm::mat2 const& transformation); + DisplayBuffer( + std::shared_ptr provider, + mir::Fd drm_fd, + BypassOption bypass_options, + std::shared_ptr const& listener, + std::vector> const& outputs, + geometry::Rectangle const& area, + glm::mat2 const& transformation); ~DisplayBuffer(); geometry::Rectangle view_area() const override; - auto size() const -> geometry::Size override; - void make_current() override; - void release_current() override; - void swap_buffers() override; - bool overlay(RenderableList const& renderlist) override; - void bind() override; + + void set_next_image(std::unique_ptr content) override; + + bool overlay(std::vector const& renderlist) override; void for_each_display_buffer( std::function const& f) override; @@ -119,38 +70,31 @@ class DisplayBuffer : public graphics::DisplayBuffer, std::chrono::milliseconds recommended_sleep() const override; glm::mat2 transformation() const override; - NativeDisplayBuffer* native_display_buffer() override; void set_transformation(glm::mat2 const& t, geometry::Rectangle const& a); void schedule_set_crtc(); void wait_for_page_flip(); + auto display_provider() const -> std::shared_ptr override; + + auto drm_fd() const -> mir::Fd; private: bool schedule_page_flip(FBHandle const& bufobj); void set_crtc(FBHandle const&); - std::shared_ptr visible_bypass_frame, scheduled_bypass_frame; - std::shared_ptr bypass_buf{nullptr}; + std::shared_ptr const provider; + bool holding_client_buffers{false}; std::shared_ptr bypass_bufobj{nullptr}; std::shared_ptr const listener; - BypassOption bypass_option; std::vector> outputs; - /* - * Destruction order is important here: - * - The GBMFrontBuffers depend on *either*: - * i) The GBMOutputSurface, or - * ii) The EGLBufferCopier hidden inside get_front_buffer - */ - std::function get_front_buffer; - GBMOutputSurface surface; - - GBMOutputSurface::FrontBuffer visible_composite_frame; - GBMOutputSurface::FrontBuffer scheduled_composite_frame; - - std::shared_ptr scheduled_fb{nullptr}; - std::shared_ptr visible_fb{nullptr}; + // Framebuffer handling + // KMS does not take a reference to submitted framebuffers; if you destroy a framebuffer while + // it's in use, KMS treat that as submitting a null framebuffer and turn off the display. + std::shared_ptr next_swap{nullptr}; //< Next frame to submit to the hardware + std::shared_ptr scheduled_fb{nullptr}; //< Frame currently submitted to the hardware, not yet on-screen + std::shared_ptr visible_fb{nullptr}; //< Frame currently onscreen geometry::Rectangle area; glm::mat2 transform; diff --git a/src/platforms/gbm-kms/server/kms/egl_helper.cpp b/src/platforms/gbm-kms/server/kms/egl_helper.cpp index 01dc4ef3a43..dba583827eb 100644 --- a/src/platforms/gbm-kms/server/kms/egl_helper.cpp +++ b/src/platforms/gbm-kms/server/kms/egl_helper.cpp @@ -44,7 +44,7 @@ mgmh::EGLHelper::EGLHelper( EGLContext shared_context) : EGLHelper(gl_config) { - setup(gbm, surface, gbm_format, shared_context, false); + setup(gbm.device, surface, gbm_format, shared_context, false); } mgmh::EGLHelper::EGLHelper(EGLHelper&& from) @@ -156,7 +156,7 @@ void mgmh::EGLHelper::setup(GBMHelper const& gbm, EGLContext shared_context) } void mgmh::EGLHelper::setup( - GBMHelper const& gbm, + gbm_device* const device, gbm_surface* surface_gbm, uint32_t gbm_format, EGLContext shared_context, @@ -169,14 +169,14 @@ void mgmh::EGLHelper::setup( EGL_NONE }; - egl_display = egl_display_for_gbm_device(gbm.device); + egl_display = egl_display_for_gbm_device(device); if (owns_egl) { initialise_egl(egl_display, 1, 4); should_terminate_egl = owns_egl; } - egl_config = egl_config_for_format(gbm_format); + egl_config = egl_config_for_format(static_cast(gbm_format)); egl_surface = platform_base.eglCreatePlatformWindowSurface( egl_display, diff --git a/src/platforms/gbm-kms/server/kms/egl_helper.h b/src/platforms/gbm-kms/server/kms/egl_helper.h index daa6c54b6b5..24f24bb3def 100644 --- a/src/platforms/gbm-kms/server/kms/egl_helper.h +++ b/src/platforms/gbm-kms/server/kms/egl_helper.h @@ -17,7 +17,7 @@ #ifndef MIR_GRAPHICS_GBM_EGL_HELPER_H_ #define MIR_GRAPHICS_GBM_EGL_HELPER_H_ -#include "display_helpers.h" +#include "../display_helpers.h" #include "mir/graphics/egl_extensions.h" #include #include @@ -49,13 +49,14 @@ class EGLHelper void setup(GBMHelper const& gbm); void setup(GBMHelper const& gbm, EGLContext shared_context); - void setup(GBMHelper const& gbm, gbm_surface* surface_gbm, uint32_t gbm_format, EGLContext shared_context, bool owns_egl); + void setup(gbm_device* const device, gbm_surface* surface_gbm, uint32_t gbm_format, EGLContext shared_context, bool owns_egl); bool swap_buffers(); bool make_current() const; bool release_current() const; EGLContext context() const { return egl_context; } + auto display() const -> EGLDisplay { return egl_display; } void report_egl_configuration(std::function); diff --git a/src/platforms/gbm-kms/server/kms/kms_framebuffer.h b/src/platforms/gbm-kms/server/kms/kms_framebuffer.h new file mode 100644 index 00000000000..32b16c2ebf6 --- /dev/null +++ b/src/platforms/gbm-kms/server/kms/kms_framebuffer.h @@ -0,0 +1,41 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_GRAPHICS_GBM_KMS_FRAMEBUFFER_H_ +#define MIR_GRAPHICS_GBM_KMS_FRAMEBUFFER_H_ + +#include "mir/graphics/platform.h" + +namespace mir +{ +namespace graphics +{ +namespace gbm +{ +class FBHandle : public Framebuffer +{ +public: + virtual ~FBHandle() = default; + + virtual operator uint32_t() const = 0; +}; + +} +} +} + + +#endif //MIR_GRAPHICS_GBM_KMS_FRAMEBUFFER_H_ diff --git a/src/platforms/gbm-kms/server/kms/kms_output.h b/src/platforms/gbm-kms/server/kms/kms_output.h index 0404fc2e5ea..f68a59c6b6a 100644 --- a/src/platforms/gbm-kms/server/kms/kms_output.h +++ b/src/platforms/gbm-kms/server/kms/kms_output.h @@ -24,7 +24,6 @@ #include "mir/graphics/frame.h" #include "mir/graphics/dmabuf_buffer.h" #include "mir_toolkit/common.h" - #include "kms-utils/drm_mode_resources.h" #include @@ -64,6 +63,12 @@ class KMSOutput virtual int max_refresh_rate() const = 0; virtual bool set_crtc(FBHandle const& fb) = 0; + + /** + * Check if the pending call to set_crtc is compatible with the current state of the CRTC. + * @returns true if a set_crtc is required, otherwise false + */ + virtual bool has_crtc_mismatch() = 0; virtual void clear_crtc() = 0; virtual bool schedule_page_flip(FBHandle const& fb) = 0; virtual void wait_for_page_flip() = 0; @@ -91,30 +96,6 @@ class KMSOutput */ virtual void update_from_hardware_state(DisplayConfigurationOutput& to_update) const = 0; - // TODO: Move these to a DRM-device level object - /** - * Get a DRM FB backed by the buffer referred to by a gbm_bo. - * - * \param [in] bo The GBM bo containing the image - * \return An opaque handle to a DRM FB, usable in other KMSOutput calls. - * - * \note As suggested by the shared_ptr return value, returned FB handle may be - * a reference to an existing FB rather than a new import. - */ - virtual auto fb_for(gbm_bo* bo) const -> std::shared_ptr = 0; - virtual auto fb_for(DMABufBuffer const& buffer) const -> std::shared_ptr = 0; - - /** - * Check whether buffer need to be migrated to GPU-private memory for display. - * - * \param [in] bo GBM buffer to test - * \return True if buffer must be migrated to display-private memory in order to be displayed. - * If this method returns true the caller should probably copy it to a new buffer before - * calling fb_for(buffer), as acquiring a FBHandle to the buffer will likely make it - * unusable for rendering on the original GPU. - */ - virtual bool buffer_requires_migration(gbm_bo* bo) const = 0; - virtual int drm_fd() const = 0; protected: KMSOutput() = default; diff --git a/src/platforms/gbm-kms/server/kms/platform.cpp b/src/platforms/gbm-kms/server/kms/platform.cpp index 365ea56d36a..940f587e71f 100644 --- a/src/platforms/gbm-kms/server/kms/platform.cpp +++ b/src/platforms/gbm-kms/server/kms/platform.cpp @@ -15,49 +15,415 @@ */ #include "platform.h" +#include "buffer_allocator.h" #include "display.h" #include "mir/console_services.h" #include "mir/emergency_cleanup_registry.h" +#include "mir/graphics/dmabuf_buffer.h" +#include "mir/graphics/drm_formats.h" +#include "mir/graphics/egl_context_executor.h" +#include "mir/graphics/platform.h" +#include "mir/graphics/texture.h" #include "mir/udev/wrapper.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/egl_extensions.h" +#include "one_shot_device_observer.h" +#include "mir/graphics/linux_dmabuf.h" +#include "mir/graphics/egl_context_executor.h" +#include "surfaceless_egl_context.h" +#include +#include +#include #define MIR_LOG_COMPONENT "platform-graphics-gbm-kms" #include "mir/log.h" -#include +#include +#include namespace mg = mir::graphics; namespace mgg = mg::gbm; -namespace mgmh = mgg::helpers; -mgg::Platform::Platform(std::shared_ptr const& listener, - ConsoleServices& vt, - EmergencyCleanupRegistry&, - BypassOption bypass_option, - std::unique_ptr quirks) +namespace +{ +auto master_fd_for_device(mir::udev::Device const& device, mir::ConsoleServices& vt) -> std::tuple, mir::Fd> +{ + mir::Fd drm_fd; + auto device_handle = vt.acquire_device( + major(device.devnum()), minor(device.devnum()), + std::make_unique(drm_fd)) + .get(); + + if (drm_fd == mir::Fd::invalid) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to acquire DRM fd"})); + } + + return std::make_tuple(std::move(device_handle), std::move(drm_fd)); +} +} + +mgg::Platform::Platform( + udev::Device const& device, + std::shared_ptr const& listener, + ConsoleServices& vt, + EmergencyCleanupRegistry& registry, + BypassOption bypass_option) + : Platform(master_fd_for_device(device, vt), listener, registry, bypass_option) +{ +} + +mgg::Platform::Platform( + std::tuple, mir::Fd> drm, + std::shared_ptr const& listener, + EmergencyCleanupRegistry&, + BypassOption bypass_option) : udev{std::make_shared()}, - drm{helpers::DRMHelper::open_all_devices(udev, vt, *quirks)}, - // We assume the first DRM device is the boot GPU, and arbitrarily pick it as our - // shell renderer. - // - // TODO: expose multiple rendering GPUs to the shell. - gbm{std::make_shared(drm.front()->fd)}, listener{listener}, + device_handle{std::move(std::get<0>(drm))}, + drm_fd{std::move(std::get<1>(drm))}, + provider{std::make_shared(drm_fd)}, bypass_option_{bypass_option} { + if (drm_fd == mir::Fd::invalid) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Invalid DRM device FD"})); + } } +namespace +{ +auto gbm_device_for_udev_device( + mir::udev::Device const& device, + std::vector> const& displays) + -> std::variant, std::shared_ptr> +{ + /* First check to see whether our device exactly matches a display device. + * If so, we should use its GBM device + */ + for(auto const& display_device : displays) + { + if (auto gbm_display = display_device->acquire_interface()) + { + if (gbm_display->is_same_device(device)) + { + return gbm_display; + } + } + } + + // We don't match any display HW, create our own GBM device + if (auto node = device.devnode()) + { + auto fd = mir::Fd{open(node, O_RDWR | O_CLOEXEC)}; + if (fd < 0) + { + BOOST_THROW_EXCEPTION(( + std::system_error{ + errno, + std::system_category(), + "Failed to open DRM device"})); + } + std::shared_ptr gbm{ + gbm_create_device(fd), + [fd = std::move(fd)](gbm_device* device) + { + if (device) + { + gbm_device_destroy(device); + } + }}; + if (!gbm) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to create GBM device"})); + } + return gbm; + } + + BOOST_THROW_EXCEPTION(( + std::runtime_error{"Attempt to create GBM device from UDev device with no device node?!"})); +} + +/** + * Initialise an EGLDisplay and return the initialised display + */ +auto initialise_egl(EGLDisplay dpy, int minimum_major_version, int minimum_minor_version) -> EGLDisplay +{ + EGLint major, minor; + + if (eglInitialize(dpy, &major, &minor) == EGL_FALSE) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to initialize EGL display")); + + if ((major < minimum_major_version) || + (major == minimum_major_version && minor < minimum_minor_version)) + { + std::stringstream msg; + msg << "Incompatible EGL version. Requested: " + << minimum_major_version << "." << minimum_minor_version + << " got: " << major << "." << minor; + BOOST_THROW_EXCEPTION((std::runtime_error{msg.str()})); + } + + return dpy; +} + +auto dpy_for_gbm_device(gbm_device* device) -> EGLDisplay +{ + mg::EGLExtensions::PlatformBaseEXT platform_ext; + + auto const egl_display = platform_ext.eglGetPlatformDisplay( + EGL_PLATFORM_GBM_KHR, // EGL_PLATFORM_GBM_MESA has the same value. + static_cast(device), + nullptr); + if (egl_display == EGL_NO_DISPLAY) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to get EGL display")); + + return egl_display; +} + +struct display_provider_or_nothing +{ + auto operator()(std::shared_ptr provider) { return provider; } + auto operator()(std::shared_ptr) { return std::shared_ptr{}; } +}; + +struct gbm_device_from_hw +{ + auto operator()(std::shared_ptr const& provider) { return provider->gbm_device(); } + auto operator()(std::shared_ptr device) { return device; } +}; + +class DMABufBuffer : public mg::DMABufBuffer +{ +public: + DMABufBuffer( + mg::DRMFormat format, + std::optional modifier, + std::vector planes, + mg::gl::Texture::Layout layout, + mir::geometry::Size size) + : format_{format}, + modifier_{modifier}, + planes_{std::move(planes)}, + layout_{layout}, + size_{size} + { + } + + auto format() const -> mg::DRMFormat override + { + return format_; + } + auto modifier() const -> std::optional override + { + return modifier_; + } + auto planes() const -> std::vector const& override + { + return planes_; + } + auto layout() const -> mg::gl::Texture::Layout override + { + return layout_; + } + auto size() const -> mir::geometry::Size override + { + return size_; + } +private: + mg::DRMFormat format_; + std::optional modifier_; + std::vector planes_; + mg::gl::Texture::Layout layout_; + mir::geometry::Size size_; +}; + +auto alloc_dma_buf( + gbm_device* gbm, + mg::DRMFormat format, + std::span modifiers, + mir::geometry::Size size) -> std::shared_ptr +{ + auto gbm_bo = std::unique_ptr{ + gbm_bo_create_with_modifiers2( + gbm, + size.width.as_uint32_t(), size.height.as_uint32_t(), + format, + modifiers.data(), modifiers.size(), + GBM_BO_USE_RENDERING), + &gbm_bo_destroy}; + + auto plane_count = gbm_bo_get_plane_count(gbm_bo.get()); + std::vector planes; + planes.reserve(plane_count); + + for (int i = 0; i < plane_count; ++i) + { + planes.push_back( + mg::DMABufBuffer::PlaneDescriptor { + .dma_buf = mir::Fd{gbm_bo_get_fd_for_plane(gbm_bo.get(), i)}, + .stride = gbm_bo_get_stride_for_plane(gbm_bo.get(), i), + .offset = gbm_bo_get_offset(gbm_bo.get(), i) + }); + } + + std::optional modifier; + auto raw_modifier = gbm_bo_get_modifier(gbm_bo.get()); + if (raw_modifier == DRM_FORMAT_MOD_INVALID) + { + modifier = std::nullopt; + } + else + { + modifier = raw_modifier; + } + + return std::make_shared( + format, + modifier, + std::move(planes), + mg::gl::Texture::Layout::GL, + size); +} + +auto maybe_make_dmabuf_provider( + std::shared_ptr gbm, + EGLDisplay dpy, + std::shared_ptr egl_extensions, + std::shared_ptr egl_delegate) + -> std::shared_ptr +{ + try + { + mg::EGLExtensions::EXTImageDmaBufImportModifiers modifier_ext{dpy}; + return std::make_shared( + dpy, + std::move(egl_extensions), + modifier_ext, + std::move(egl_delegate), + [gbm](mg::DRMFormat format, std::span modifiers, mir::geometry::Size size) + -> std::shared_ptr + { + return alloc_dma_buf(gbm.get(), format, modifiers, size); + }); + } + catch (std::runtime_error const& error) + { + mir::log_info( + "Cannot enable linux-dmabuf import support: %s", error.what()); + mir::log( + mir::logging::Severity::debug, + MIR_LOG_COMPONENT, + std::current_exception(), + "Detailed error: "); + } + return nullptr; +} +} + +mgg::RenderingPlatform::RenderingPlatform( + mir::udev::Device const& device, + std::vector> const& displays) + : RenderingPlatform(gbm_device_for_udev_device(device, displays)) +{ +} + +mgg::RenderingPlatform::RenderingPlatform( + std::variant, std::shared_ptr> hw) + : device{std::visit(gbm_device_from_hw{}, hw)}, + bound_display{std::visit(display_provider_or_nothing{}, hw)}, + share_ctx{std::make_unique(initialise_egl(dpy_for_gbm_device(device.get()), 1, 4))}, + egl_delegate{std::make_shared(share_ctx->make_share_context())}, + dmabuf_provider{maybe_make_dmabuf_provider(device, share_ctx->egl_display(), std::make_shared(), egl_delegate)} +{ +} + +mgg::RenderingPlatform::~RenderingPlatform() = default; + +mir::UniqueModulePtr mgg::RenderingPlatform::create_buffer_allocator( + mg::Display const&) +{ + return make_module_ptr( + std::make_unique(share_ctx->egl_display(), static_cast(*share_ctx)), + egl_delegate, + dmabuf_provider); +} + +auto mgg::RenderingPlatform::maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr +{ + if (dynamic_cast(&type_tag)) + { + return std::make_shared( + bound_display, + egl_delegate, + dmabuf_provider, + share_ctx->egl_display(), + static_cast(*share_ctx)); + } + return nullptr; +} + +class mgg::Platform::KMSDisplayInterfaceProvider : public mg::DisplayInterfaceProvider +{ +public: + explicit KMSDisplayInterfaceProvider(mir::Fd drm_fd) + : drm_fd{std::move(drm_fd)}, + gbm_provider{maybe_make_gbm_provider(this->drm_fd)} + { + } + +protected: + auto maybe_create_interface(mg::DisplayProvider::Tag const& type_tag) + -> std::shared_ptr + { + if (dynamic_cast(&type_tag)) + { + return gbm_provider; + } + if (dynamic_cast(&type_tag)) + { + return std::make_shared(drm_fd); + } + return {}; + } +private: + static auto maybe_make_gbm_provider(mir::Fd drm_fd) -> std::shared_ptr + { + try + { + return std::make_shared(std::move(drm_fd)); + } + catch (std::exception const& err) + { + mir::log_info("Failed to create GBM device for direct buffer submission"); + mir::log_info("Output will use CPU buffer copies"); + return {}; + } + } + + mir::Fd const drm_fd; + // We rely on the GBM provider being stable over probe, so we construct one at startup + // and reuse it. + std::shared_ptr const gbm_provider; +}; + mir::UniqueModulePtr mgg::Platform::create_display( - std::shared_ptr const& initial_conf_policy, std::shared_ptr const& gl_config) + std::shared_ptr const& initial_conf_policy, std::shared_ptr const&) { return make_module_ptr( - drm, - gbm, + provider, + drm_fd, bypass_option_, initial_conf_policy, - gl_config, listener); } +auto mgg::Platform::interface_for() -> std::shared_ptr +{ + return provider; +} + mgg::BypassOption mgg::Platform::bypass_option() const { return bypass_option_; diff --git a/src/platforms/gbm-kms/server/kms/platform.h b/src/platforms/gbm-kms/server/kms/platform.h index c667e458681..abc7f777282 100644 --- a/src/platforms/gbm-kms/server/kms/platform.h +++ b/src/platforms/gbm-kms/server/kms/platform.h @@ -21,6 +21,9 @@ #include "platform_common.h" #include "display_helpers.h" +#include +#include + namespace mir { class EmergencyCleanupRegistry; @@ -28,19 +31,28 @@ class ConsoleServices; namespace graphics { +class DMABufEGLProvider; + +namespace common +{ +class EGLContextExecutor; +} + namespace gbm { class Quirks; +class SurfacelessEGLContext; class Platform : public graphics::DisplayPlatform { public: - explicit Platform(std::shared_ptr const& reporter, - ConsoleServices& vt, - EmergencyCleanupRegistry& emergency_cleanup_registry, - BypassOption bypass_option, - std::unique_ptr quirks); + Platform( + udev::Device const& device, + std::shared_ptr const& reporter, + ConsoleServices& vt, + EmergencyCleanupRegistry& emergency_cleanup_registry, + BypassOption bypass_option); /* From Platform */ UniqueModulePtr create_display( @@ -48,15 +60,56 @@ class Platform : public graphics::DisplayPlatform std::shared_ptr const& gl_config) override; std::shared_ptr udev; - std::vector> const drm; - std::shared_ptr const gbm; - + std::shared_ptr const listener; +protected: + auto interface_for() -> std::shared_ptr override; + +public: BypassOption bypass_option() const; private: + Platform( + std::tuple, mir::Fd> drm, + std::shared_ptr const& reporter, + EmergencyCleanupRegistry& emergency_cleanup_registry, + BypassOption bypass_option); + + std::unique_ptr const device_handle; + mir::Fd const drm_fd; + + class KMSDisplayInterfaceProvider; + std::shared_ptr const provider; + BypassOption const bypass_option_; }; + +class RenderingPlatform : public graphics::RenderingPlatform +{ +public: + RenderingPlatform( + udev::Device const& device, + std::vector> const& displays); + + ~RenderingPlatform() override; + + auto create_buffer_allocator( + graphics::Display const&) -> UniqueModulePtr override; + +protected: + auto maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr override; + +private: + RenderingPlatform( + std::variant, std::shared_ptr> hw); + + std::shared_ptr const device; ///< gbm_device this platform is created on, always valid. + std::shared_ptr const bound_display; ///< Associated Display, if any (nullptr is valid) + std::unique_ptr const share_ctx; + std::shared_ptr const egl_delegate; + std::shared_ptr const dmabuf_provider; +}; } } } diff --git a/src/platforms/gbm-kms/server/kms/platform_symbols.cpp b/src/platforms/gbm-kms/server/kms/platform_symbols.cpp index 4204007fce4..ad5526c9328 100644 --- a/src/platforms/gbm-kms/server/kms/platform_symbols.cpp +++ b/src/platforms/gbm-kms/server/kms/platform_symbols.cpp @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +#include "mir/graphics/platform.h" #define MIR_LOG_COMPONENT "gbm-kms" #include "mir/log.h" @@ -52,7 +53,7 @@ char const* bypass_option_name{"bypass"}; } mir::UniqueModulePtr create_display_platform( - mg::SupportedDevice const&, + mg::SupportedDevice const& device, std::shared_ptr const& options, std::shared_ptr const& emergency_cleanup_registry, std::shared_ptr const& console, @@ -70,10 +71,19 @@ mir::UniqueModulePtr create_display_platform( if (!options->get(bypass_option_name)) bypass_option = mgg::BypassOption::prohibited; - auto quirks = std::make_unique(*options); - return mir::make_module_ptr( - report, *console, *emergency_cleanup_registry, bypass_option, std::move(quirks)); + *device.device, report, *console, *emergency_cleanup_registry, bypass_option); +} + +auto create_rendering_platform( + mg::SupportedDevice const& device, + std::vector> const& displays, + mo::Option const&, + mir::EmergencyCleanupRegistry&) -> mir::UniqueModulePtr +{ + mir::assert_entry_point_signature(&create_rendering_platform); + + return mir::make_module_ptr(*device.device, displays); } void add_graphics_platform_options(boost::program_options::options_description& config) @@ -178,7 +188,7 @@ auto probe_display_platform( supported_devices.emplace_back( mg::SupportedDevice{ device.clone(), - mg::PlatformPriority::unsupported, + mg::probe::unsupported, nullptr }); @@ -218,7 +228,7 @@ auto probe_display_platform( if ("llvmpipe"s == renderer_string) { mir::log_info("KMS device only has associated software renderer: %s, device unsuitable", renderer_string); - supported_devices.back().support_level = mg::PlatformPriority::unsupported; + supported_devices.back().support_level = mg::probe::unsupported; continue; } @@ -236,7 +246,7 @@ auto probe_display_platform( mir::log_warning( "Failed to query BusID for device %s; cannot check if KMS is available", device.devnode()); - supported_devices.back().support_level = mg::PlatformPriority::supported; + supported_devices.back().support_level = mg::probe::supported; } else { @@ -250,7 +260,7 @@ auto probe_display_platform( (kms_resources.num_encoders() > 0)) { // It supports KMS *and* can drive at least one physical output! Top hole! - supported_devices.back().support_level = mg::PlatformPriority::best; + supported_devices.back().support_level = mg::probe::best; } else { @@ -270,7 +280,7 @@ auto probe_display_platform( mir::log_warning( "Failed to detect whether device %s supports KMS, continuing with lower confidence", device.devnode()); - supported_devices.back().support_level = mg::PlatformPriority::supported; + supported_devices.back().support_level = mg::probe::supported; break; default: @@ -278,7 +288,7 @@ auto probe_display_platform( "but continuing anyway", strerror(err), err); mir::log_warning("Please file a bug at " "https://github.com/MirServer/mir/issues containing this message"); - supported_devices.back().support_level = mg::PlatformPriority::supported; + supported_devices.back().support_level = mg::probe::supported; } } } @@ -296,6 +306,168 @@ auto probe_display_platform( return supported_devices; } +auto probe_rendering_platform( + std::span> const& displays, + mir::ConsoleServices&, + std::shared_ptr const& udev, + mir::options::ProgramOption const& options) -> std::vector +{ + mir::assert_entry_point_signature(&probe_rendering_platform); + + mg::probe::Result maximum_suitability = mg::probe::unsupported; + // First check if there are any displays we can possibly drive + for (auto const& display_provider : displays) + { + if (display_provider->acquire_interface()) + { + // We can optimally drive a GBM-backed display + mir::log_debug("GBM-capable display found"); + maximum_suitability = mg::probe::best; + break; + } + if (display_provider->acquire_interface()) + { + /* We *can* support this output, but with slower buffer copies + * If another platform supports this device better, let it. + */ + maximum_suitability = mg::probe::supported; + } + } + + if (maximum_suitability == mg::probe::unsupported) + { + mir::log_debug("No outputs capable of accepting GBM input detected"); + mir::log_debug("Probing will be skipped"); + return {}; + } + + mgg::Quirks quirks{options}; + + mir::udev::Enumerator drm_devices{udev}; + drm_devices.match_subsystem("drm"); + drm_devices.match_sysname("card[0-9]*"); + drm_devices.match_sysname("renderD[0-9]*"); + drm_devices.scan_devices(); + + if (drm_devices.begin() == drm_devices.end()) + { + mir::log_info("Unsupported: No DRM devices detected"); + return {}; + } + + // We also require GBM EGL platform + auto const* client_extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + if (!client_extensions) + { + // Doesn't support EGL client extensions; Mesa does, so this is unlikely to be gbm-kms. + mir::log_info("Unsupported: EGL platform does not support client extensions."); + return {}; + } + if (strstr(client_extensions, "EGL_KHR_platform_gbm") == nullptr) + { + // Doesn't support the Khronos-standardised GBM platform… + mir::log_info("EGL platform does not support EGL_KHR_platform_gbm extension"); + // …maybe we support the old pre-standardised Mesa GBM platform? + if (strstr(client_extensions, "EGL_MESA_platform_gbm") == nullptr) + { + mir::log_info( + "Unsupported: EGL platform supports neither EGL_KHR_platform_gbm nor EGL_MESA_platform_gbm"); + return {}; + } + } + + // Check for suitability + std::vector supported_devices; + for (auto& device : drm_devices) + { + if (quirks.should_skip(device)) + { + mir::log_info("Not probing device %s due to specified quirk", device.devnode()); + continue; + } + auto const device_node = device.devnode(); + if (device_node == nullptr) + { + /* The display connectors attached to the card appear as subdevices + * of the card[0-9] node. + * These won't have a device node, so pass on anything that doesn't have + * a /dev/dri/card* node + */ + continue; + } + + try + { + // Open the device node directly; we don't need DRM master + mir::Fd tmp_fd{::open(device_node, O_RDWR | O_CLOEXEC)}; + + /* We prefer render nodes for the RenderingPlatform. + * Check if this device has an associated render node, + * and skip it if it does. + */ + std::unique_ptr> const render_node{ + drmGetRenderDeviceNameFromFd(tmp_fd), + &free + }; + if (render_node) + { + // We might already be probing a render node + if (strcmp(device_node, render_node.get())) + { + // The render node of this device is not *this* device, + // so let's pass and let us open the render node later. + continue; + } + } + + // We know we've got a device that we *might* be able to use + supported_devices.emplace_back(mg::SupportedDevice{device.clone(), mg::probe::unsupported, nullptr}); + if (tmp_fd != mir::Fd::invalid) + { + mgg::helpers::GBMHelper gbm_device{tmp_fd}; + mgg::helpers::EGLHelper egl{MinimalGLConfig()}; + + egl.setup(gbm_device); + + egl.make_current(); + + auto const renderer_string = reinterpret_cast(glGetString(GL_RENDERER)); + if (!renderer_string) + { + throw mg::gl_error( + "Probe failed to query GL renderer"); + } + + // TODO: Probe for necessary EGL extensions (ie: at least one of WL_bind_display / dmabuf_import + + if (strncmp( + "llvmpipe", + renderer_string, + strlen("llvmpipe")) == 0) + { + mir::log_info("Detected software renderer: %s", renderer_string); + // Leave the priority at ::unsupported; if we've got a software renderer then + // we're not successfully using *this* rendernode. + } + else + { + // We've got a non-software renderer. That's our cue! + supported_devices.back().support_level = maximum_suitability; + } + } + } + catch (std::exception const& e) + { + mir::log( + mir::logging::Severity::informational, + MIR_LOG_COMPONENT, + std::current_exception(), + "Failed to probe DRM device"); + } + } + return supported_devices; +} + namespace { mir::ModuleProperties const description = { @@ -312,3 +484,4 @@ mir::ModuleProperties const* describe_graphics_module() mir::assert_entry_point_signature(&describe_graphics_module); return &description; } + diff --git a/src/platforms/gbm-kms/server/kms/quirks.cpp b/src/platforms/gbm-kms/server/kms/quirks.cpp index 48f7d659e07..44fdbf5a380 100644 --- a/src/platforms/gbm-kms/server/kms/quirks.cpp +++ b/src/platforms/gbm-kms/server/kms/quirks.cpp @@ -154,8 +154,7 @@ class mgg::Quirks::Impl private: // Mir's gbm-kms support isn't (yet) working with nvidia - // Mir's gbm-kms support isn't (yet) working with evdi - std::unordered_set drivers_to_skip = { "nvidia", "evdi", "ast" }; + std::unordered_set drivers_to_skip = { "nvidia", "ast" }; std::unordered_set devnodes_to_skip; // We know this is currently useful for virtio_gpu, vc4-drm and v3d std::unordered_set skip_modesetting_support = { "virtio_gpu", "vc4-drm", "v3d" }; diff --git a/src/platforms/gbm-kms/server/kms/real_kms_output.cpp b/src/platforms/gbm-kms/server/kms/real_kms_output.cpp index 8e67325df51..214a0da8a7a 100644 --- a/src/platforms/gbm-kms/server/kms/real_kms_output.cpp +++ b/src/platforms/gbm-kms/server/kms/real_kms_output.cpp @@ -15,6 +15,7 @@ */ #include "real_kms_output.h" +#include "kms_framebuffer.h" #include "mir/graphics/display_configuration.h" #include "page_flipper.h" #include "kms-utils/kms_connector.h" @@ -25,36 +26,30 @@ #include #include #include +#include namespace mg = mir::graphics; namespace mgg = mg::gbm; namespace mgk = mg::kms; namespace geom = mir::geometry; -class mgg::FBHandle -{ -public: - FBHandle(int drm_fd, uint32_t fb_id) - : drm_fd{drm_fd}, - fb_id{fb_id} - { - } - - ~FBHandle() - { - // TODO: Some sort of logging on failure? - drmModeRmFB(drm_fd, fb_id); - } - - auto get_drm_fb_id() const -> uint32_t - { - return fb_id; - } -private: - int const drm_fd; - uint32_t const fb_id; -}; +namespace +{ +bool kms_modes_are_equal(drmModeModeInfo const& info1, drmModeModeInfo const& info2) +{ + return (info1.clock == info2.clock && + info1.hdisplay == info2.hdisplay && + info1.hsync_start == info2.hsync_start && + info1.hsync_end == info2.hsync_end && + info1.htotal == info2.htotal && + info1.hskew == info2.hskew && + info1.vdisplay == info2.vdisplay && + info1.vsync_start == info2.vsync_start && + info1.vsync_end == info2.vsync_end && + info1.vtotal == info2.vtotal); +} +} mgg::RealKMSOutput::RealKMSOutput( int drm_fd, @@ -162,11 +157,12 @@ bool mgg::RealKMSOutput::set_crtc(FBHandle const& fb) } auto ret = drmModeSetCrtc(drm_fd_, current_crtc->crtc_id, - fb.get_drm_fb_id(), fb_offset.dx.as_int(), fb_offset.dy.as_int(), + fb, fb_offset.dx.as_int(), fb_offset.dy.as_int(), &connector->connector_id, 1, &connector->modes[mode_index]); if (ret) { + mir::log_error("Failed to set CRTC: %s (%i)", strerror(-ret), -ret); current_crtc = nullptr; return false; } @@ -175,6 +171,17 @@ bool mgg::RealKMSOutput::set_crtc(FBHandle const& fb) return true; } +bool mgg::RealKMSOutput::has_crtc_mismatch() +{ + if (!ensure_crtc()) + { + mir::log_error("Output %s has no associated CRTC to get ", mgk::connector_name(connector).c_str()); + return true; + } + + return !kms_modes_are_equal(current_crtc->mode, connector->modes[mode_index]); +} + void mgg::RealKMSOutput::clear_crtc() { try @@ -233,7 +240,7 @@ bool mgg::RealKMSOutput::schedule_page_flip(FBHandle const& fb) } return page_flipper->schedule_flip( current_crtc->crtc_id, - fb.get_drm_fb_id(), + fb, connector->connector_id); } @@ -401,21 +408,6 @@ void mgg::RealKMSOutput::refresh_hardware_state() namespace { - -bool kms_modes_are_equal(drmModeModeInfo const& info1, drmModeModeInfo const& info2) -{ - return (info1.clock == info2.clock && - info1.hdisplay == info2.hdisplay && - info1.hsync_start == info2.hsync_start && - info1.hsync_end == info2.hsync_end && - info1.htotal == info2.htotal && - info1.hskew == info2.hskew && - info1.vdisplay == info2.vdisplay && - info1.vsync_start == info2.vsync_start && - info1.vsync_end == info2.vsync_end && - info1.vtotal == info2.vtotal); -} - double calculate_vrefresh_hz(drmModeModeInfo const& mode) { if (mode.htotal == 0 || mode.vtotal == 0) @@ -611,258 +603,6 @@ void mgg::RealKMSOutput::update_from_hardware_state( output.edid = edid; } -namespace -{ -void bo_user_data_destroy(gbm_bo* /*bo*/, void *data) -{ - auto bufobj = static_cast*>(data); - delete bufobj; -} -} - -auto mgg::RealKMSOutput::FBRegistry::lookup_or_create(int const drm_fd, gbm_bo* bo) - -> std::shared_ptr -{ - if (!bo) - return nullptr; - - /* - * Check if we have already set up this gbm_bo (the gbm-kms implementation is - * free to reuse gbm_bos). If so, return the associated FBHandle. - */ - auto bufobj = static_cast*>(gbm_bo_get_user_data(bo)); - if (bufobj) - { - return *bufobj; - } - - uint32_t fb_id{0}; - uint32_t handles[4] = {gbm_bo_get_handle(bo).u32, 0, 0, 0}; - uint32_t strides[4] = {gbm_bo_get_stride(bo), 0, 0, 0}; - uint32_t offsets[4] = {0, 0, 0, 0}; - - auto format = gbm_bo_get_format(bo); - /* - * Mir might use the old GBM_BO_ enum formats, but KMS and the rest of - * the world need fourcc formats, so convert... - */ - if (format == GBM_BO_FORMAT_XRGB8888) - format = GBM_FORMAT_XRGB8888; - else if (format == GBM_BO_FORMAT_ARGB8888) - format = GBM_FORMAT_ARGB8888; - - auto const width = gbm_bo_get_width(bo); - auto const height = gbm_bo_get_height(bo); - - /* Create a KMS FB object with the gbm_bo attached to it. */ - auto ret = drmModeAddFB2(drm_fd, width, height, format, - handles, strides, offsets, &fb_id, 0); - if (ret) - return nullptr; - - /* Create a FBHandle and associate it with the gbm_bo */ - - bufobj = new std::shared_ptr(new FBHandle{drm_fd, fb_id}); - gbm_bo_set_user_data(bo, bufobj, bo_user_data_destroy); - - return *bufobj; -} - -auto mgg::RealKMSOutput::fb_for(gbm_bo* bo) const -> std::shared_ptr -{ - return framebuffers.lookup_or_create(drm_fd(), bo); -} - -struct mgg::RealKMSOutput::FBRegistry::DMABufFB -{ - std::array const fds; - std::array const bo_handles; - std::weak_ptr handle; - - DMABufFB( - std::array fds, - std::array bo_handles, - std::weak_ptr handle) - : fds{std::move(fds)}, - bo_handles{bo_handles}, - handle{std::move(handle)} - { - } -}; - -auto mgg::RealKMSOutput::FBRegistry::lookup_or_create( - const int drm_fd, - const DMABufBuffer& image) -> std::shared_ptr -{ - // The DRM API expects a bunch of 4-element arrays, with unused elements set to 0 - std::array handles = {0, 0, 0, 0}; - std::array strides = {0, 0, 0, 0}; - std::array offsets = {0, 0, 0, 0}; - std::array modifiers = {0, 0, 0, 0}; - - std::array dma_bufs; - - auto const& planes = image.planes(); - for (auto i = 0u; i < planes.size() ; ++i) - { - strides[i] = planes[i].stride; - offsets[i] = planes[i].offset; - dma_bufs[i] = planes[i].dma_buf; - } - - // If we've got this FB already imported, we can just return that… - auto const existing_fb = std::find_if( - dmabuf_fbs.begin(), - dmabuf_fbs.end(), - [&dma_bufs](auto const& candidate) - { - for (auto i = 0u; i < dma_bufs.size(); ++i) - { - /* We can do a numerical compare here because the DMAbufFB structs keep a - * reference to their fds open, so any newly imported buffer will have - * a different integer fd handle; they can't accidentally alias. - * - * And the Wayland protocol only imports the FDs once, and then the client - * references the wl_buffer, so we can expect the FDs of a particular buffer - * to be numerically stable. - */ - if (static_cast(dma_bufs[i]) != static_cast(candidate->fds[i])) - { - return false; - } - } - return true; - }); - - uint32_t const* imported_handles; - if (existing_fb != dmabuf_fbs.end()) - { - if (auto const handle = (*existing_fb)->handle.lock()) - { - /* We have already imported the DMA-bufs *and* have a current FB for them. - * We can just return that. - */ - return handle; - } - /* We've imported the DMA-bufs, but have already deleted the FB associated with - * them. Grab the previously-imported handles, then re-create a FB. - */ - imported_handles = (*existing_fb)->bo_handles.data(); - } - else - { - // Otherwise, we need to import the DMA-bufs - for (auto i = 0u; i < planes.size(); ++i) - { - if (auto const error = drmPrimeFDToHandle(drm_fd, dma_bufs[i], &handles[i])) - { - BOOST_THROW_EXCEPTION(( - std::system_error{ - error, - std::system_category(), - "Failed to acquire GEM handle for DMA-BUF"})); - } - } - imported_handles = handles.data(); - } - - - // If there's a modifier set, propagate it to all components. - if (image.modifier().has_value()) - { - for (auto i = 0u; i < planes.size(); ++i) - { - modifiers[i] = image.modifier().value(); - } - } - - uint32_t drm_fb_id; - auto const ret = drmModeAddFB2WithModifiers( - drm_fd, - image.size().width.as_uint32_t(), - image.size().height.as_uint32_t(), - image.drm_fourcc(), - imported_handles, - strides.data(), - offsets.data(), - image.modifier().has_value() ? modifiers.data() : nullptr, - &drm_fb_id, - image.modifier().has_value() ? DRM_MODE_FB_MODIFIERS : 0); - - if (ret) - { - mir::log_debug( - "Failed to import dmabuf-based image as FB: %s", - std::system_category().message(ret).c_str()); - return nullptr; - } - - auto fb_handle = std::make_shared(drm_fd, drm_fb_id); - - if (existing_fb != dmabuf_fbs.end()) - { - // We already have the buffer data; we just need to re-create a handle - (*existing_fb)->handle = fb_handle; - } - else - { - dmabuf_fbs.push_back(std::make_shared(std::move(dma_bufs), handles, fb_handle)); - } - - return fb_handle; -} - -auto mgg::RealKMSOutput::fb_for(mg::DMABufBuffer const& image) const -> std::shared_ptr -{ - return framebuffers.lookup_or_create(drm_fd(), image); -} - -bool mgg::RealKMSOutput::buffer_requires_migration(gbm_bo* bo) const -{ - /* - * It's possible that some devices will not require migration - - * Intel GPUs can obviously scanout from main memory, as can USB outputs such as - * DisplayLink. - * - * For a first go, just say that *every* device scans out of GPU-private memory. - * - * To complicate matters, Mali's gbm-kms implementation does *not* return the same - * integer fd from gbm_device_get_fd() as the drm fd that the device was created - * from, and GBM may choose to internally open the DRM render node associated - * with the DRM node passed to gbm_create_device. - */ - - errno = 0; - auto const gbm_device_node = std::unique_ptr{ - drmGetPrimaryDeviceNameFromFd(gbm_device_get_fd(gbm_bo_get_device(bo))), - &free - }; - if (!gbm_device_node) - { - BOOST_THROW_EXCEPTION(( - std::system_error{ - errno, - std::system_category(), - "Failed to query DRM device backing GBM buffer"})); - } - - auto const drm_device_node = std::unique_ptr{ - drmGetPrimaryDeviceNameFromFd(drm_fd_), - &free - }; - if (!drm_device_node) - { - BOOST_THROW_EXCEPTION(( - std::system_error{ - errno, - std::system_category(), - "Failed to query DRM device node of display device"})); - } - - // These *should* match if we're on the same device - return strcmp(gbm_device_node.get(), drm_device_node.get()) != 0; -} - int mgg::RealKMSOutput::drm_fd() const { return drm_fd_; diff --git a/src/platforms/gbm-kms/server/kms/real_kms_output.h b/src/platforms/gbm-kms/server/kms/real_kms_output.h index fa6f25fc81e..ee9286d39ab 100644 --- a/src/platforms/gbm-kms/server/kms/real_kms_output.h +++ b/src/platforms/gbm-kms/server/kms/real_kms_output.h @@ -49,6 +49,7 @@ class RealKMSOutput : public KMSOutput int max_refresh_rate() const override; bool set_crtc(FBHandle const& fb) override; + bool has_crtc_mismatch() override; void clear_crtc() override; bool schedule_page_flip(FBHandle const& fb) override; void wait_for_page_flip() override; @@ -64,10 +65,6 @@ class RealKMSOutput : public KMSOutput void refresh_hardware_state() override; void update_from_hardware_state(DisplayConfigurationOutput& output) const override; - auto fb_for(gbm_bo* bo) const -> std::shared_ptr override; - auto fb_for(DMABufBuffer const& image) const -> std::shared_ptr override; - - bool buffer_requires_migration(gbm_bo* bo) const override; int drm_fd() const override; private: @@ -77,21 +74,6 @@ class RealKMSOutput : public KMSOutput int const drm_fd_; std::shared_ptr const page_flipper; - /* TODO: This should really be owned by a DRM-device-level object, - * not per-output. We don't have one of those at the moment, so here'll do. - */ - class FBRegistry - { - public: - auto lookup_or_create(int const drm_fd, gbm_bo* bo) -> std::shared_ptr; - auto lookup_or_create(int const drm_fd, DMABufBuffer const& image) -> std::shared_ptr; - - struct DMABufFB; - private: - std::vector> dmabuf_fbs; - }; - FBRegistry mutable framebuffers; - kms::DRMModeConnectorUPtr connector; size_t mode_index; geometry::Displacement fb_offset; diff --git a/src/platforms/gbm-kms/server/kms/real_kms_output_container.cpp b/src/platforms/gbm-kms/server/kms/real_kms_output_container.cpp index dae9bf51c95..dfe9e5cce8b 100644 --- a/src/platforms/gbm-kms/server/kms/real_kms_output_container.cpp +++ b/src/platforms/gbm-kms/server/kms/real_kms_output_container.cpp @@ -22,10 +22,10 @@ namespace mgg = mir::graphics::gbm; mgg::RealKMSOutputContainer::RealKMSOutputContainer( - std::vector const& drm_fds, - std::function(int)> const& construct_page_flipper) - : drm_fds{drm_fds}, - construct_page_flipper{construct_page_flipper} + mir::Fd drm_fd, + std::shared_ptr page_flipper) + : drm_fd{std::move(drm_fd)}, + page_flipper{std::move(page_flipper)} { } @@ -39,58 +39,38 @@ void mgg::RealKMSOutputContainer::update_from_hardware_state() { decltype(outputs) new_outputs; - // TODO: Accumulate errors and present them all. - std::exception_ptr last_error; + auto const resources = std::make_unique(drm_fd); - for (auto drm_fd : drm_fds) + for (auto &&connector : resources->connectors()) { - std::unique_ptr resources; - try + // Caution: O(n²) here, but n is the number of outputs, so should + // conservatively be << 100. + auto existing_output = std::find_if( + outputs.begin(), + outputs.end(), + [&connector, this](auto const &candidate) + { + return + connector->connector_id == candidate->id() && + drm_fd == candidate->drm_fd(); + }); + + if (existing_output != outputs.end()) { - resources = std::make_unique(drm_fd); + // We could drop this down to O(n) by being smarter about moving out + // of the outputs vector. + // + // That's a bit of a faff, so just do the simple thing for now. + new_outputs.push_back(*existing_output); + new_outputs.back()->refresh_hardware_state(); } - catch (std::exception const&) + else { - last_error = std::current_exception(); - continue; + new_outputs.push_back(std::make_shared( + drm_fd, + std::move(connector), + page_flipper)); } - - for (auto &&connector : resources->connectors()) - { - // Caution: O(n²) here, but n is the number of outputs, so should - // conservatively be << 100. - auto existing_output = std::find_if( - outputs.begin(), - outputs.end(), - [&connector, drm_fd](auto const &candidate) - { - return - connector->connector_id == candidate->id() && - drm_fd == candidate->drm_fd(); - }); - - if (existing_output != outputs.end()) - { - // We could drop this down to O(n) by being smarter about moving out - // of the outputs vector. - // - // That's a bit of a faff, so just do the simple thing for now. - new_outputs.push_back(*existing_output); - new_outputs.back()->refresh_hardware_state(); - } - else - { - new_outputs.push_back(std::make_shared( - drm_fd, - std::move(connector), - construct_page_flipper(drm_fd))); - } - } - - } - if (new_outputs.empty() && last_error) - { - std::rethrow_exception(last_error); } outputs = new_outputs; diff --git a/src/platforms/gbm-kms/server/kms/real_kms_output_container.h b/src/platforms/gbm-kms/server/kms/real_kms_output_container.h index 661695ab4e4..942c4e4822a 100644 --- a/src/platforms/gbm-kms/server/kms/real_kms_output_container.h +++ b/src/platforms/gbm-kms/server/kms/real_kms_output_container.h @@ -18,6 +18,7 @@ #define MIR_GRAPHICS_GBM_REAL_KMS_OUTPUT_CONTAINER_H_ #include "kms_output_container.h" +#include "mir/fd.h" #include namespace mir @@ -33,16 +34,16 @@ class RealKMSOutputContainer : public KMSOutputContainer { public: RealKMSOutputContainer( - std::vector const& drm_fds, - std::function(int drm_fd)> const& construct_page_flipper); + mir::Fd drm_fd, + std::shared_ptr page_flipper); void for_each_output(std::function const&)> functor) const override; void update_from_hardware_state() override; private: - std::vector const drm_fds; + mir::Fd const drm_fd; std::vector> outputs; - std::function(int drm_fd)> const construct_page_flipper; + std::shared_ptr const page_flipper; }; } diff --git a/src/platforms/gbm-kms/server/kms/symbols.map.in b/src/platforms/gbm-kms/server/kms/symbols.map.in index 30dad63f1b7..59fbd10490d 100644 --- a/src/platforms/gbm-kms/server/kms/symbols.map.in +++ b/src/platforms/gbm-kms/server/kms/symbols.map.in @@ -4,6 +4,8 @@ probe_display_platform; describe_graphics_module; create_display_platform; + probe_rendering_platform; + create_rendering_platform; local: *; }; diff --git a/src/platforms/gbm-kms/server/surfaceless_egl_context.cpp b/src/platforms/gbm-kms/server/surfaceless_egl_context.cpp new file mode 100644 index 00000000000..869df4beb62 --- /dev/null +++ b/src/platforms/gbm-kms/server/surfaceless_egl_context.cpp @@ -0,0 +1,106 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "surfaceless_egl_context.h" +#include "mir/graphics/egl_error.h" + +#include +#include +#include + +namespace mg = mir::graphics; + +namespace +{ +auto make_share_only_context(EGLDisplay dpy, std::optional share_with) -> EGLContext +{ + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLConfig cfg; + EGLint num_configs; + + if (eglChooseConfig(dpy, config_attr, &cfg, 1, &num_configs) != EGL_TRUE || num_configs != 1) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to find any matching EGL config"))); + } + + auto ctx = eglCreateContext(dpy, cfg, share_with.value_or(EGL_NO_CONTEXT), context_attr); + if (ctx == EGL_NO_CONTEXT) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); + } + return ctx; +} +} + +mg::gbm::SurfacelessEGLContext::SurfacelessEGLContext(EGLDisplay dpy) + : dpy{dpy}, + ctx{make_share_only_context(dpy, {})} + { + } + +mg::gbm::SurfacelessEGLContext::SurfacelessEGLContext(EGLDisplay dpy, EGLContext share_with) + : dpy{dpy}, + ctx{make_share_only_context(dpy, share_with)} +{ +} + +mg::gbm::SurfacelessEGLContext::~SurfacelessEGLContext() +{ + eglDestroyContext(dpy, ctx); +} + +void mg::gbm::SurfacelessEGLContext::make_current() const +{ + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); + } +} + +void mg::gbm::SurfacelessEGLContext::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 current EGL context")); + } +} + +auto mg::gbm::SurfacelessEGLContext::make_share_context() const -> std::unique_ptr +{ + return std::unique_ptr{new SurfacelessEGLContext{dpy, ctx}}; +} + +mg::gbm::SurfacelessEGLContext::operator EGLContext() +{ + return ctx; +} + +auto mg::gbm::SurfacelessEGLContext::egl_display() const -> EGLDisplay +{ + return dpy; +} diff --git a/src/platforms/gbm-kms/server/surfaceless_egl_context.h b/src/platforms/gbm-kms/server/surfaceless_egl_context.h new file mode 100644 index 00000000000..1b21e252913 --- /dev/null +++ b/src/platforms/gbm-kms/server/surfaceless_egl_context.h @@ -0,0 +1,43 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include "mir/renderer/gl/context.h" + +#include + +namespace mir::graphics::gbm +{ +class SurfacelessEGLContext : public mir::renderer::gl::Context +{ +public: + SurfacelessEGLContext(EGLDisplay dpy); + SurfacelessEGLContext(EGLDisplay dpy, EGLContext share_with); + + ~SurfacelessEGLContext() override; + + void make_current() const override; + + void release_current() const override; + + auto make_share_context() const -> std::unique_ptr override; + explicit operator EGLContext() override; + + auto egl_display() const -> EGLDisplay; +private: + EGLDisplay const dpy; + EGLContext const ctx; +}; +} \ No newline at end of file diff --git a/src/platforms/renderer-generic-egl/buffer_allocator.cpp b/src/platforms/renderer-generic-egl/buffer_allocator.cpp index d8de60fa0ef..356e6dc5bcc 100644 --- a/src/platforms/renderer-generic-egl/buffer_allocator.cpp +++ b/src/platforms/renderer-generic-egl/buffer_allocator.cpp @@ -15,9 +15,11 @@ */ #include "buffer_allocator.h" +#include "mir/graphics/gl_config.h" #include "mir/graphics/linux_dmabuf.h" #include "mir/anonymous_shm_file.h" #include "mir/renderer/sw/pixel_source.h" +#include "mir/graphics/platform.h" #include "shm_buffer.h" #include "mir/graphics/egl_context_executor.h" #include "mir/graphics/egl_extensions.h" @@ -29,6 +31,11 @@ #include "mir/renderer/gl/context_source.h" #include "mir/graphics/egl_wayland_allocator.h" #include "mir/executor.h" +#include "mir/renderer/gl/gl_surface.h" +#include "mir/graphics/display_buffer.h" +#include "mir/graphics/drm_formats.h" +#include "mir/graphics/egl_error.h" +#include "cpu_copy_output_surface.h" #include #include @@ -38,7 +45,10 @@ #include #include +#include + #include +#include #include #include #include @@ -57,44 +67,102 @@ namespace geom = mir::geometry; namespace { -auto context_for_output(mg::Display const& output) -> std::unique_ptr +auto make_share_only_context(EGLDisplay dpy, std::optional share_with) -> EGLContext { - try - { - auto& context_source = dynamic_cast(output); + eglBindAPI(EGL_OPENGL_ES_API); - /* - * We care about no part of this context's config; we will do no rendering with it. - * All we care is that we can allocate texture IDs and bind a texture, which is - * config independent. - * - * That's not *entirely* true; we also need it to be on the same device as we want - * to do the rendering on, and that GL must support all the extensions we care about, - * but since we don't yet support heterogeneous hybrid and implementing that will require - * broader interface changes it's a safe enough requirement for now. - */ - return context_source.create_gl_context(); + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLConfig cfg; + EGLint num_configs; + + if (eglChooseConfig(dpy, config_attr, &cfg, 1, &num_configs) != EGL_TRUE || num_configs != 1) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to find any matching EGL config"))); } - catch (std::bad_cast const& err) + + auto ctx = eglCreateContext(dpy, cfg, share_with.value_or(EGL_NO_CONTEXT), context_attr); + if (ctx == EGL_NO_CONTEXT) { - std::throw_with_nested( - boost::enable_error_info( - std::runtime_error{"Output platform cannot provide a GL context"}) - << boost::throw_function(__PRETTY_FUNCTION__) - << boost::throw_line(__LINE__) - << boost::throw_file(__FILE__)); + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); } + return ctx; } + +class SurfacelessEGLContext : public mir::renderer::gl::Context +{ +public: + explicit SurfacelessEGLContext(EGLDisplay dpy) + : dpy{dpy}, + ctx{make_share_only_context(dpy, {})} + { + } + + SurfacelessEGLContext(EGLDisplay dpy, EGLContext share_with) + : dpy{dpy}, + ctx{make_share_only_context(dpy, share_with)} + { + } + + ~SurfacelessEGLContext() override + { + eglDestroyContext(dpy, ctx); + } + + void make_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); + } + } + + void release_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release current EGL context")); + } + } + + auto make_share_context() const -> std::unique_ptr override + { + return std::unique_ptr{new SurfacelessEGLContext{dpy, ctx}}; + } + + explicit operator EGLContext() override + { + return ctx; + } +private: + EGLDisplay const dpy; + EGLContext const ctx; +}; } -mge::BufferAllocator::BufferAllocator(mg::Display const& output) - : ctx{context_for_output(output)}, +mge::BufferAllocator::BufferAllocator( + EGLDisplay dpy, + EGLContext share_with, + std::shared_ptr dmabuf_provider) + : ctx{std::make_unique(dpy, share_with)}, egl_delegate{ - std::make_shared(context_for_output(output))}, - egl_extensions(std::make_shared()) + std::make_shared(ctx->make_share_context())}, + egl_extensions(std::make_shared()), + dmabuf_provider{std::move(dmabuf_provider)} { } +mge::BufferAllocator::~BufferAllocator() = default; + std::shared_ptr mge::BufferAllocator::alloc_software_buffer( geom::Size size, MirPixelFormat format) { @@ -157,34 +225,34 @@ void mge::BufferAllocator::bind_display(wl_display* display, std::shared_ptr>( - new LinuxDmaBufUnstable{ - display, - dpy, - egl_extensions, - modifier_ext, - }, - [wayland_executor](LinuxDmaBufUnstable* global) - { - // The global must be destroyed on the Wayland thread - wayland_executor->spawn( - [global]() - { - /* This is safe against double-frees, as the WaylandExecutor - * guarantees that work scheduled will only run while the Wayland - * event loop is running, and the main loop is stopped before - * wl_display_destroy() frees any globals - * - * This will, however, leak the global if the main loop is destroyed - * before the buffer allocator. Fixing that requires work in the - * wrapper generator. - */ - delete global; - }); - }); - mir::log_info("Enabled linux-dmabuf import support"); + if (dmabuf_provider) + { + dmabuf_extension = + std::unique_ptr>( + new LinuxDmaBufUnstable{ + display, + dmabuf_provider + }, + [wayland_executor](LinuxDmaBufUnstable* global) + { + // The global must be destroyed on the Wayland thread + wayland_executor->spawn( + [global]() + { + /* This is safe against double-frees, as the WaylandExecutor + * guarantees that work scheduled will only run while the Wayland + * event loop is running, and the main loop is stopped before + * wl_display_destroy() frees any globals + * + * This will, however, leak the global if the main loop is destroyed + * before the buffer allocator. Fixing that requires work in the + * wrapper generator. + */ + delete global; + }); + }); + mir::log_info("Enabled linux-dmabuf import support"); + } } catch (std::runtime_error const& error) { @@ -249,3 +317,149 @@ auto mge::BufferAllocator::buffer_from_shm( std::move(on_consumed), std::move(on_release)); } + +auto mge::BufferAllocator::shared_egl_context() -> EGLContext +{ + return static_cast(*ctx); +} + +auto mge::GLRenderingProvider::as_texture(std::shared_ptr buffer) -> std::shared_ptr +{ + if (dmabuf_provider) + { + if (auto tex = dmabuf_provider->as_texture(buffer)) + { + return tex; + } + } + // TODO: Should this be abstracted, like dmabuf_provider above? + return std::dynamic_pointer_cast(buffer); +} + +namespace +{ + +class EGLOutputSurface : public mg::gl::OutputSurface +{ +public: + EGLOutputSurface( + std::unique_ptr fb) + : fb{std::move(fb)} + { + } + + void bind() override + { + } + + void make_current() override + { + fb->make_current(); + } + + void release_current() override + { + fb->release_current(); + } + + auto commit() -> std::unique_ptr override + { + return fb->clone_handle(); + } + + auto size() const -> geom::Size override + { + return fb->size(); + } + + auto layout() const -> Layout override + { + return Layout::GL; + } + +private: + std::unique_ptr const fb; +}; +} + +auto mge::GLRenderingProvider::suitability_for_allocator(std::shared_ptr const& target) + -> probe::Result +{ + // TODO: We *can* import from other allocators, maybe (anything with dma-buf is probably possible) + // For now, the simplest thing is to bind hard to own own allocator. + if (dynamic_cast(target.get())) + { + return probe::best; + } + return probe::unsupported; +} + +auto mge::GLRenderingProvider::suitability_for_display( + std::shared_ptr const& target) -> probe::Result +{ + if (target->acquire_interface()) + { + /* We're effectively hosted on an underlying EGL platform. + * + * We'll work fine, but if there's a hardware-specific platform + * let it take over. + */ + return probe::hosted; + } + + if (target->acquire_interface()) + { + /* We can *work* on a CPU-backed surface, but if anything's better + * we should use something else! + */ + return probe::supported; + } + + return probe::unsupported; +} + +auto mge::GLRenderingProvider::surface_for_output( + std::shared_ptr framebuffer_provider, + geometry::Size size, + GLConfig const& config) + -> std::unique_ptr +{ + if (auto egl_display = framebuffer_provider->acquire_interface()) + { + return std::make_unique(egl_display->alloc_framebuffer(config, ctx)); + } + auto cpu_provider = framebuffer_provider->acquire_interface(); + + return std::make_unique( + dpy, + ctx, + std::move(cpu_provider), + size); +} + +auto mge::GLRenderingProvider::make_framebuffer_provider(std::shared_ptr /*target*/) + -> std::unique_ptr +{ + // TODO: Work out under what circumstances the EGL renderer *can* provide overlayable framebuffers + class NullFramebufferProvider : public FramebufferProvider + { + public: + auto buffer_to_framebuffer(std::shared_ptr) -> std::unique_ptr override + { + // It is safe to return nullptr; this will be treated as “this buffer cannot be used as + // a framebuffer”. + return {}; + } + }; + return std::make_unique(); +} + +mge::GLRenderingProvider::GLRenderingProvider( + EGLDisplay dpy, + EGLContext ctx, + std::shared_ptr dmabuf_provider) + : dpy{dpy}, + ctx{ctx}, + dmabuf_provider{std::move(dmabuf_provider)} +{ +} diff --git a/src/platforms/renderer-generic-egl/buffer_allocator.h b/src/platforms/renderer-generic-egl/buffer_allocator.h index 2c5b4b255e6..f29754f12cc 100644 --- a/src/platforms/renderer-generic-egl/buffer_allocator.h +++ b/src/platforms/renderer-generic-egl/buffer_allocator.h @@ -19,6 +19,7 @@ #include "mir/graphics/graphic_buffer_allocator.h" #include "mir/graphics/linux_dmabuf.h" +#include "mir/graphics/platform.h" #include #include @@ -47,12 +48,15 @@ class EGLContextExecutor; namespace egl::generic { +class GLRenderingProvider; + class BufferAllocator: public graphics::GraphicBufferAllocator { public: - explicit BufferAllocator(Display const& output); - + BufferAllocator(EGLDisplay dpy, EGLContext share_with, std::shared_ptr dmabuf_provider); + ~BufferAllocator() override; + std::shared_ptr alloc_software_buffer(geometry::Size size, MirPixelFormat) override; std::vector supported_pixel_formats() override; @@ -66,15 +70,45 @@ class BufferAllocator: std::shared_ptr data, std::function&& on_consumed, std::function&& on_release) -> std::shared_ptr override; + + auto shared_egl_context() -> EGLContext; private: - std::shared_ptr const ctx; + std::unique_ptr const ctx; std::shared_ptr const egl_delegate; std::shared_ptr wayland_executor; std::unique_ptr> dmabuf_extension; std::shared_ptr const egl_extensions; + std::shared_ptr const dmabuf_provider; bool egl_display_bound{false}; }; +class GLRenderingProvider : public graphics::GLRenderingProvider +{ +public: + GLRenderingProvider( + EGLDisplay dpy, + EGLContext ctx, + std::shared_ptr dmabuf_provider); + + auto make_framebuffer_provider(std::shared_ptr target) + -> std::unique_ptr override; + + auto as_texture(std::shared_ptr buffer) -> std::shared_ptr override; + + auto suitability_for_allocator(std::shared_ptr const& target) -> probe::Result override; + + auto suitability_for_display(std::shared_ptr const& target) -> probe::Result override; + + auto surface_for_output( + std::shared_ptr framebuffer_provider, + geometry::Size size, + GLConfig const& config) -> std::unique_ptr override; + +private: + EGLDisplay const dpy; + EGLContext const ctx; + std::shared_ptr const dmabuf_provider; +}; } } } diff --git a/src/platforms/renderer-generic-egl/platform_symbols.cpp b/src/platforms/renderer-generic-egl/platform_symbols.cpp index 5e237589ab7..f8b4a6e2c33 100644 --- a/src/platforms/renderer-generic-egl/platform_symbols.cpp +++ b/src/platforms/renderer-generic-egl/platform_symbols.cpp @@ -35,13 +35,13 @@ namespace mge = mg::egl::generic; auto create_rendering_platform( mg::SupportedDevice const&, - std::vector> const&, + std::vector> const& displays, mo::Option const&, mir::EmergencyCleanupRegistry&) -> mir::UniqueModulePtr { - mir::assert_entry_point_signature(&create_rendering_platform); + mir::assert_entry_point_signature(&create_rendering_platform); - return mir::make_module_ptr(); + return mir::make_module_ptr(displays); } void add_graphics_platform_options(boost::program_options::options_description&) @@ -50,18 +50,34 @@ void add_graphics_platform_options(boost::program_options::options_description&) } auto probe_rendering_platform( - std::shared_ptr const&, + std::span> const& displays, + mir::ConsoleServices&, std::shared_ptr const&, mo::ProgramOption const&) -> std::vector { - mir::assert_entry_point_signature(&probe_rendering_platform); + mir::assert_entry_point_signature(&probe_rendering_platform); + + mg::probe::Result maximum_suitability = mg::probe::unsupported; + // First check if there are any displays we can possibly drive + for (auto const& display_provider : displays) + { + if (display_provider->acquire_interface()) + { + maximum_suitability = mg::probe::hosted; + break; + } + /* TODO: We *can* drive a CPUAddressableDisplayProvider, too, but without + * an EGLDisplay from the GenericEGLDisplayProvider we'd need to check that + * the EGLDisplay we get from eglGetDisplay(EGL_DEFAULT_DISPLAY) is functional. + */ + } std::vector supported_devices; supported_devices.push_back( mg::SupportedDevice { nullptr, // We aren't associated with any particular device - mg::PlatformPriority::supported, // We should be fully-functional, but let any hardware-specific + maximum_suitability, // We should be fully-functional, but let any hardware-specific // platform claim a higher priority, if it exists. nullptr // No platform-specific data diff --git a/src/platforms/renderer-generic-egl/rendering_platform.cpp b/src/platforms/renderer-generic-egl/rendering_platform.cpp index 827fb738619..2115cc86e98 100644 --- a/src/platforms/renderer-generic-egl/rendering_platform.cpp +++ b/src/platforms/renderer-generic-egl/rendering_platform.cpp @@ -16,16 +16,182 @@ #include "rendering_platform.h" #include "buffer_allocator.h" +#include "mir/graphics/egl_extensions.h" +#include "mir/graphics/platform.h" +#include "mir/graphics/egl_error.h" +#include "mir/renderer/gl/context.h" + +#define MIR_LOG_COMPONENT "platform-generic-egl" +#include "mir/log.h" + +#include +#include namespace mg = mir::graphics; +namespace mgc = mir::graphics::common; namespace mge = mg::egl::generic; +namespace geom = mir::geometry; + +namespace +{ +auto egl_display_from_platforms(std::vector> const& displays) -> EGLDisplay +{ + for (auto const& display : displays) + { + if (auto egl_provider = display->acquire_interface()) + { + return egl_provider->get_egl_display(); + } + } + // No Displays provide an EGL display + // We can still work, falling back to CPU-copy output, as long as we can get *any* EGL display + auto dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (dpy == EGL_NO_DISPLAY) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to create any EGL display"})); + } + return dpy; +} + +auto make_share_only_context(EGLDisplay dpy, std::optional share_context) -> EGLContext +{ + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + EGLConfig cfg; + EGLint num_configs; + + if (eglChooseConfig(dpy, config_attr, &cfg, 1, &num_configs) != EGL_TRUE || num_configs != 1) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to find any matching EGL config"))); + } + + auto ctx = eglCreateContext(dpy, cfg, share_context.value_or(EGL_NO_CONTEXT), context_attr); + if (ctx == EGL_NO_CONTEXT) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); + } + return ctx; +} + +class SurfacelessEGLContext : public mir::renderer::gl::Context +{ +public: + explicit SurfacelessEGLContext(EGLDisplay dpy) + : dpy{dpy}, + ctx{make_share_only_context(dpy, {})} + { + } + + SurfacelessEGLContext(EGLDisplay dpy, EGLContext share_with) + : dpy{dpy}, + ctx{make_share_only_context(dpy, share_with)} + { + } + + ~SurfacelessEGLContext() override + { + eglDestroyContext(dpy, ctx); + } + + void make_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make context current")); + } + } -mge::RenderingPlatform::RenderingPlatform() + void release_current() const override + { + if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to release current EGL context")); + } + } + + auto make_share_context() const -> std::unique_ptr override + { + return std::unique_ptr{new SurfacelessEGLContext{dpy, ctx}}; + } + + explicit operator EGLContext() override + { + return ctx; + } +private: + EGLDisplay const dpy; + EGLContext const ctx; +}; + +auto maybe_make_dmabuf_provider( + EGLDisplay dpy, + std::shared_ptr egl_extensions, + std::shared_ptr egl_delegate) + -> std::shared_ptr { + try + { + mg::EGLExtensions::EXTImageDmaBufImportModifiers modifier_ext{dpy}; + return std::make_shared( + dpy, + std::move(egl_extensions), + modifier_ext, + std::move(egl_delegate), + [](mg::DRMFormat, std::span, geom::Size) -> std::shared_ptr + { + return nullptr; // We can't (portably) allocate dmabufs, but we also shouldn't need to + }); + } + catch (std::runtime_error const& error) + { + mir::log_info( + "Cannot enable linux-dmabuf import support: %s", error.what()); + mir::log( + mir::logging::Severity::debug, + MIR_LOG_COMPONENT, + std::current_exception(), + "Detailed error: "); + } + return nullptr; +} } +mge::RenderingPlatform::RenderingPlatform(std::vector> const& displays) + : dpy{egl_display_from_platforms(displays)}, + ctx{std::make_unique(dpy)}, + dmabuf_provider{ + maybe_make_dmabuf_provider( + dpy, + std::make_shared(), + std::make_shared(ctx->make_share_context()))} +{ +} + +mge::RenderingPlatform::~RenderingPlatform() = default; + auto mge::RenderingPlatform::create_buffer_allocator( - mg::Display const& output) -> mir::UniqueModulePtr + mg::Display const& /*output*/) -> mir::UniqueModulePtr +{ + return make_module_ptr(dpy, static_cast(*ctx), dmabuf_provider); +} + +auto mge::RenderingPlatform::maybe_create_provider(RenderingProvider::Tag const& tag) + -> std::shared_ptr { - return make_module_ptr(output); + if (dynamic_cast(&tag)) + { + return std::make_shared(dpy, static_cast(*ctx), dmabuf_provider); + } + return nullptr; } diff --git a/src/platforms/renderer-generic-egl/rendering_platform.h b/src/platforms/renderer-generic-egl/rendering_platform.h index f58ab223639..dd6ebccbfd5 100644 --- a/src/platforms/renderer-generic-egl/rendering_platform.h +++ b/src/platforms/renderer-generic-egl/rendering_platform.h @@ -17,12 +17,17 @@ #ifndef MIR_GRAPHICS_RENDERING_EGL_GENERIC_H_ #define MIR_GRAPHICS_RENDERING_EGL_GENERIC_H_ +#include "mir/graphics/linux_dmabuf.h" #include "mir/graphics/platform.h" +#include + namespace mir { -class EmergencyCleanupRegistry; -class ConsoleServices; +namespace renderer::gl +{ +class Context; +} namespace graphics::egl::generic { @@ -30,10 +35,21 @@ namespace graphics::egl::generic class RenderingPlatform : public graphics::RenderingPlatform { public: - explicit RenderingPlatform(); + explicit RenderingPlatform(std::vector> const& displays); + + ~RenderingPlatform(); auto create_buffer_allocator( graphics::Display const& output) -> UniqueModulePtr override; + +protected: + auto maybe_create_provider( + RenderingProvider::Tag const& type_tag) -> std::shared_ptr override; + +private: + EGLDisplay const dpy; + std::unique_ptr const ctx; + std::shared_ptr const dmabuf_provider; }; } diff --git a/src/platforms/wayland/CMakeLists.txt b/src/platforms/wayland/CMakeLists.txt index 16b8ce31ba7..5dd1ce55852 100644 --- a/src/platforms/wayland/CMakeLists.txt +++ b/src/platforms/wayland/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(mirplatformwayland-graphics STATIC displayclient.cpp displayclient.h wayland_display.cpp wayland_display.h cursor.cpp cursor.h + wl_egl_display_provider.cpp wl_egl_display_provider.h ) target_include_directories(mirplatformwayland-graphics diff --git a/src/platforms/wayland/display.cpp b/src/platforms/wayland/display.cpp index eb70f9bbe3e..520c17d40b2 100644 --- a/src/platforms/wayland/display.cpp +++ b/src/platforms/wayland/display.cpp @@ -93,9 +93,10 @@ mgw::Display* the_display = nullptr; mgw::Display::Display( wl_display* const wl_display, - std::shared_ptr const& gl_config, + std::shared_ptr provider, + std::shared_ptr const&, std::shared_ptr const& report) : - DisplayClient{wl_display, gl_config}, + DisplayClient{wl_display, std::move(provider)}, report{report}, shutdown_signal{::eventfd(0, EFD_CLOEXEC)}, flush_signal{::eventfd(0, EFD_SEMAPHORE)}, @@ -150,47 +151,6 @@ bool mgw::Display::apply_if_configuration_preserves_display_buffers(DisplayConfi return false; } -auto mgw::Display::create_gl_context() const -> std::unique_ptr -{ - EGLint const static context_attr[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - class GlContext : public mir::renderer::gl::Context - { - public: - GlContext(EGLDisplay egldisplay, EGLConfig eglconfig, EGLContext eglctx) : - egldisplay{egldisplay}, - eglctx{eglCreateContext(egldisplay, eglconfig, eglctx, context_attr)} {} - - ~GlContext() - { - eglDestroyContext(egldisplay, eglctx); - } - - void make_current() const override - { - if (eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, eglctx) != EGL_TRUE) - { - log_warning( - "%s FAILED: %s", - __PRETTY_FUNCTION__, - egl_category().message(eglGetError()).c_str()); - } - } - - void release_current() const override - { eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } - - private: - EGLDisplay const egldisplay; - EGLContext const eglctx; - }; - - return std::make_unique(egldisplay, eglconfig, eglctx); -} - void mgw::Display::for_each_display_sync_group(const std::function& f) { DisplayClient::for_each_display_sync_group(f); diff --git a/src/platforms/wayland/display.h b/src/platforms/wayland/display.h index fc57fa8ef8a..b0c2eb58d6e 100644 --- a/src/platforms/wayland/display.h +++ b/src/platforms/wayland/display.h @@ -18,6 +18,7 @@ #define MIR_PLATFORMS_WAYLAND_DISPLAY_H_ #include "displayclient.h" +#include "platform.h" #include #include @@ -58,6 +59,7 @@ class Display : public mir::graphics::Display, public: Display( wl_display* const wl_display, + std::shared_ptr provider, std::shared_ptr const& gl_config, std::shared_ptr const& report); @@ -86,8 +88,6 @@ class Display : public mir::graphics::Display, auto create_hardware_cursor() -> std::shared_ptroverride; - auto create_gl_context() const -> std::unique_ptr override; - private: void keyboard_key(wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) override; diff --git a/src/platforms/wayland/displayclient.cpp b/src/platforms/wayland/displayclient.cpp index d3163c6e7aa..ccf96dbaa54 100644 --- a/src/platforms/wayland/displayclient.cpp +++ b/src/platforms/wayland/displayclient.cpp @@ -16,7 +16,8 @@ */ #include "displayclient.h" -#include "mir/graphics/egl_error.h" +#include "wl_egl_display_provider.h" +#include "mir/graphics/platform.h" #include #include @@ -32,22 +33,19 @@ #include #include #include -#include +#include namespace mgw = mir::graphics::wayland; namespace geom = mir::geometry; class mgw::DisplayClient::Output : public DisplaySyncGroup, - public renderer::gl::RenderTarget, - public NativeDisplayBuffer, public DisplayBuffer { public: Output( wl_output* output, - DisplayClient* owner, - std::function on_change); + DisplayClient* owner); ~Output(); @@ -61,7 +59,7 @@ class mgw::DisplayClient::Output : float host_scale{1.0f}; wl_output* const output; - DisplayClient* const owner; + DisplayClient* const owner_; wl_surface* const surface; xdg_surface* shell_surface{nullptr}; @@ -73,7 +71,6 @@ class mgw::DisplayClient::Output : std::optional pending_toplevel_size; bool has_initialized{false}; - std::function on_change; // wl_output events void geometry( @@ -100,35 +97,22 @@ class mgw::DisplayClient::Output : // DisplayBuffer implementation auto view_area() const -> geometry::Rectangle override; - bool overlay(RenderableList const& renderlist) override; + bool overlay(std::vector const& renderlist) override; auto transformation() const -> glm::mat2 override; - auto native_display_buffer() -> NativeDisplayBuffer* override; - - // RenderTarget implementation - auto size() const -> geom::Size override; - void make_current() override; - void release_current() override; - void swap_buffers() override; - void bind() override; -}; + auto display_provider() const -> std::shared_ptr override; + void set_next_image(std::unique_ptr content) override; -namespace -{ -static EGLint const ctxattribs[] = - { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; -} +private: + std::unique_ptr next_frame; + std::shared_ptr provider; +}; mgw::DisplayClient::Output::Output( wl_output* output, - DisplayClient* owner, - std::function on_change) : + DisplayClient* owner) : output{output}, - owner{owner}, - surface{wl_compositor_create_surface(owner->compositor)}, - on_change{std::move(on_change)} + owner_{owner}, + surface{wl_compositor_create_surface(owner->compositor)} { // If building against newer Wayland protocol definitions we may miss trailing fields #pragma GCC diagnostic push @@ -175,20 +159,10 @@ mgw::DisplayClient::Output::~Output() wl_surface_destroy(surface); - if (eglsurface != EGL_NO_SURFACE) - { - eglDestroySurface(owner->egldisplay, eglsurface); - } - if (egl_window != nullptr) { wl_egl_window_destroy(egl_window); } - - if (eglctx != EGL_NO_CONTEXT) - { - eglDestroyContext(owner->egldisplay, eglctx); - } } void mgw::DisplayClient::Output::geometry( @@ -291,7 +265,7 @@ void mgw::DisplayClient::Output::done() static xdg_surface_listener const shell_surface_listener{ [](void* self, auto, auto... args) { static_cast(self)->surface_configure(args...); }, }; - shell_surface = xdg_wm_base_get_xdg_surface(owner->shell, surface); + shell_surface = xdg_wm_base_get_xdg_surface(owner_->shell, surface); xdg_surface_add_listener(shell_surface, &shell_surface_listener, this); static xdg_toplevel_listener const shell_toplevel_listener{ @@ -309,7 +283,9 @@ void mgw::DisplayClient::Output::done() } else { - on_change(); + /* TODO: We should handle this by raising a hardware-changed notification and reconfiguring in + * the subsequent `configure()` call. + */ } } @@ -331,21 +307,15 @@ void mgw::DisplayClient::Output::surface_configure(uint32_t serial) output_size = dcout.extents().size; if (!has_initialized) { - egl_window = wl_egl_window_create(surface, output_size.width.as_int(), output_size.height.as_int()); - eglsurface = eglCreateWindowSurface( - owner->egldisplay, - owner->eglconfig, - reinterpret_cast(egl_window), - nullptr); has_initialized = true; + provider = std::make_shared(*owner_->provider, surface, output_size); } else if (size_is_changed) { - if (egl_window) - { - wl_egl_window_resize(egl_window, output_size.width.as_int(), output_size.height.as_int(), 0, 0); - } - on_change(); + /* 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. + */ } } @@ -355,54 +325,6 @@ void mgw::DisplayClient::Output::for_each_display_buffer(std::function std::chrono::milliseconds -{ - return std::chrono::milliseconds{0}; -} - -auto mgw::DisplayClient::Output::view_area() const -> geometry::Rectangle -{ - return dcout.extents(); -} - -bool mgw::DisplayClient::Output::overlay(mir::graphics::RenderableList const&) -{ - return false; -} - -auto mgw::DisplayClient::Output::transformation() const -> glm::mat2 -{ - return glm::mat2{1}; -} - -auto mgw::DisplayClient::Output::native_display_buffer() -> NativeDisplayBuffer* -{ - return this; -} - -auto mgw::DisplayClient::Output::size() const -> geom::Size -{ - return output_size; -} - -void mgw::DisplayClient::Output::make_current() -{ - if (eglctx == EGL_NO_CONTEXT) - eglctx = eglCreateContext(owner->egldisplay, owner->eglconfig, owner->eglctx, ctxattribs); - - if (!eglMakeCurrent(owner->egldisplay, eglsurface, eglsurface, eglctx)) - BOOST_THROW_EXCEPTION(egl_error("Can't eglMakeCurrent")); -} - -void mgw::DisplayClient::Output::release_current() -{ - eglMakeCurrent(owner->egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); -} - -void mgw::DisplayClient::Output::swap_buffers() { struct FrameSync { @@ -417,7 +339,7 @@ void mgw::DisplayClient::Output::swap_buffers() static struct wl_callback_listener const frame_listener = { [](void* data, auto... args) - { static_cast(data)->frame_done(args...); }, + { static_cast(data)->frame_done(args...); }, }; wl_callback_add_listener(callback, &frame_listener, this); } @@ -451,29 +373,79 @@ void mgw::DisplayClient::Output::swap_buffers() }; auto const frame_sync = std::make_shared(surface); - owner->spawn([frame_sync]() - { - frame_sync->init(); - }); + owner_->spawn([frame_sync]() + { + frame_sync->init(); + }); // Avoid throttling compositing by blocking in eglSwapBuffers(). // Instead we use the frame "done" notification. - eglSwapInterval(owner->egldisplay, 0); + // TODO: We probably don't need to do this every frame! + eglSwapInterval(provider->get_egl_display(), 0); - if (eglSwapBuffers(owner->egldisplay, eglsurface) != EGL_TRUE) - BOOST_THROW_EXCEPTION(egl_error("Failed to perform buffer swap")); + next_frame->swap_buffers(); frame_sync->wait_for_done(); } -void mgw::DisplayClient::Output::bind() +auto mgw::DisplayClient::Output::recommended_sleep() const -> std::chrono::milliseconds +{ + return std::chrono::milliseconds{0}; +} + +auto mgw::DisplayClient::Output::view_area() const -> geometry::Rectangle +{ + return dcout.extents(); +} + +bool mgw::DisplayClient::Output::overlay(std::vector const&) +{ + return false; +} + +auto mgw::DisplayClient::Output::transformation() const -> glm::mat2 +{ + return glm::mat2{1}; +} + +auto mgw::DisplayClient::Output::display_provider() const -> std::shared_ptr +{ + return provider; +} + +namespace +{ +template +auto unique_ptr_cast(std::unique_ptr ptr) -> std::unique_ptr +{ + From* unowned_src = ptr.release(); + if (auto to_src = dynamic_cast(unowned_src)) + { + return std::unique_ptr{to_src}; + } + delete unowned_src; + BOOST_THROW_EXCEPTION(( + std::bad_cast())); +} +} + +void mgw::DisplayClient::Output::set_next_image(std::unique_ptr content) { + if (auto wl_content = unique_ptr_cast(std::move(content))) + { + next_frame = std::move(wl_content); + } + else + { + BOOST_THROW_EXCEPTION((std::logic_error{"set_next_image called with a Framebuffer from a different platform"})); + } } mgw::DisplayClient::DisplayClient( wl_display* display, - std::shared_ptr const& gl_config) : + std::shared_ptr provider) : display{display}, + provider{std::move(provider)}, keyboard_context_{xkb_context_new(XKB_CONTEXT_NO_FLAGS)}, registry{nullptr, [](auto){}} { @@ -491,46 +463,6 @@ mgw::DisplayClient::DisplayClient( wl_registry_add_listener(registry.get(), ®istry_listener, this); - auto format = shm_pixel_format; - - EGLint const cfgattribs[] = - { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, red_channel_depth(format), - EGL_GREEN_SIZE, green_channel_depth(format), - EGL_BLUE_SIZE, blue_channel_depth(format), - EGL_ALPHA_SIZE, alpha_channel_depth(format), - EGL_DEPTH_SIZE, gl_config->depth_buffer_bits(), - EGL_STENCIL_SIZE, gl_config->stencil_buffer_bits(), - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - eglBindAPI(EGL_OPENGL_ES_API); - - egldisplay = eglGetDisplay((EGLNativeDisplayType)(display)); - if (egldisplay == EGL_NO_DISPLAY) - BOOST_THROW_EXCEPTION(egl_error("Can't eglGetDisplay")); - - EGLint major; - EGLint minor; - if (!eglInitialize(egldisplay, &major, &minor)) - BOOST_THROW_EXCEPTION(egl_error("Can't eglInitialize")); - - if (std::tuple{major, minor} < std::tuple{1, 4}) - BOOST_THROW_EXCEPTION(egl_error("EGL version is not at least 1.4")); - - EGLint neglconfigs; - if (!eglChooseConfig(egldisplay, cfgattribs, &eglconfig, 1, &neglconfigs)) - BOOST_THROW_EXCEPTION(egl_error("Could not eglChooseConfig")); - - if (neglconfigs == 0) - BOOST_THROW_EXCEPTION(egl_error("No EGL config available")); - - eglctx = eglCreateContext(egldisplay, eglconfig, EGL_NO_CONTEXT, ctxattribs); - if (eglctx == EGL_NO_CONTEXT) - BOOST_THROW_EXCEPTION(egl_error("eglCreateContext failed")); - auto const has_uninitialized_output = [&]() { for (auto const& pair : bound_outputs) @@ -570,16 +502,11 @@ void mgw::DisplayClient::delete_outputs_to_be_deleted() mgw::DisplayClient::~DisplayClient() { - eglMakeCurrent(egldisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - { std::lock_guard lock{outputs_mutex}; bound_outputs.clear(); } registry.reset(); - - eglDestroyContext(egldisplay, eglctx); - eglTerminate(egldisplay); } void mgw::DisplayClient::new_global( @@ -621,8 +548,7 @@ void mgw::DisplayClient::new_global( id, std::make_unique( output, - self, - [self]() { self->on_display_config_changed(); }))); + self))); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { diff --git a/src/platforms/wayland/displayclient.h b/src/platforms/wayland/displayclient.h index ca73bb3dd6b..b1ad98a999e 100644 --- a/src/platforms/wayland/displayclient.h +++ b/src/platforms/wayland/displayclient.h @@ -46,19 +46,22 @@ namespace graphics { namespace wayland { +class WlDisplayProvider; class DisplayClient : public Executor { public: - DisplayClient(wl_display* display, - std::shared_ptr const& gl_config); + DisplayClient( + wl_display* display, + std::shared_ptr provider); virtual ~DisplayClient(); protected: wl_display* const display; + std::shared_ptr const provider; auto display_configuration() const -> std::unique_ptr; void for_each_display_sync_group(const std::function& f); @@ -171,10 +174,6 @@ class DisplayClient std::unordered_map> bound_outputs; std::mutex mutable config_change_handlers_mutex; std::vector config_change_handlers; - - EGLDisplay egldisplay; - EGLConfig eglconfig; - EGLContext eglctx; }; } } diff --git a/src/platforms/wayland/platform.cpp b/src/platforms/wayland/platform.cpp index c2a9f359971..6b428841e3f 100644 --- a/src/platforms/wayland/platform.cpp +++ b/src/platforms/wayland/platform.cpp @@ -14,16 +14,64 @@ * along with this program. If not, see . */ +#include + +#include "wl_egl_display_provider.h" #include "platform.h" #include "display.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/platform.h" namespace mg = mir::graphics; namespace mgw = mir::graphics::wayland; using namespace std::literals; +namespace +{ +auto make_initialised_egl_display(struct wl_display* wl_display) -> EGLDisplay +{ + EGLDisplay dpy; + // TODO: When we require EGL 1.5 support this check can go away; the EXT_platform_base is core in EGL 1.5 + if (epoxy_has_egl_extension(EGL_NO_DISPLAY, "EGL_EXT_platform_base")) + { + if (!epoxy_has_egl_extension(EGL_NO_DISPLAY, "EGL_EXT_platform_wayland")) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"EGL implementation does not have Wayland support"})); + } + dpy = eglGetPlatformDisplayEXT(EGL_PLATFORM_WAYLAND_KHR, wl_display, nullptr); + } + else + { + dpy = eglGetDisplay(wl_display); + } + if (dpy == EGL_NO_DISPLAY) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to get EGLDisplay"))); + } + + std::tuple version; + if (eglInitialize(dpy, &std::get<0>(version), &std::get<1>(version)) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("Failed to initialise EGLDisplay"))); + } + if (version < std::make_tuple(1, 4)) + { + BOOST_THROW_EXCEPTION(( + std::runtime_error{ + "EGL version unsupported. Require at least 1.4, got " + + std::to_string(std::get<0>(version)) + + "." + + std::to_string(std::get<1>(version)) + })); + } + return dpy; +} +} + mgw::Platform::Platform(struct wl_display* const wl_display, std::shared_ptr const& report) : wl_display{wl_display}, - report{report} + report{report}, + provider{std::make_shared(make_initialised_egl_display(wl_display))} { } @@ -31,5 +79,10 @@ mir::UniqueModulePtr mgw::Platform::create_display( std::shared_ptr const&, std::shared_ptr const& gl_config) { - return mir::make_module_ptr(wl_display, gl_config, report); + return mir::make_module_ptr(wl_display, provider, gl_config, report); +} + +auto mgw::Platform::interface_for() -> std::shared_ptr +{ + return provider; } diff --git a/src/platforms/wayland/platform.h b/src/platforms/wayland/platform.h index 79da7280d5a..3bbc3a3347d 100644 --- a/src/platforms/wayland/platform.h +++ b/src/platforms/wayland/platform.h @@ -18,14 +18,8 @@ #define MIR_GRAPHICS_WAYLAND_PLATFORM_H_ #include "mir/graphics/platform.h" -#include "mir/options/option.h" #include "mir/graphics/display.h" -#include "mir/fd.h" -#include "displayclient.h" - -#include -#include #include #include @@ -35,20 +29,26 @@ namespace graphics { namespace wayland { +class WlDisplayProvider; + class Platform : public graphics::DisplayPlatform { public: Platform(struct wl_display* const wl_display, std::shared_ptr const& report); ~Platform() = default; - UniqueModulePtr create_display( std::shared_ptr const& initial_conf_policy, std::shared_ptr const& gl_config) override; +protected: + auto interface_for() -> std::shared_ptr override; private: + struct wl_display* const wl_display; std::shared_ptr const report; + + std::shared_ptr const provider; }; } } diff --git a/src/platforms/wayland/platform_symbols.cpp b/src/platforms/wayland/platform_symbols.cpp index 312108ba103..cc72c10fe4b 100644 --- a/src/platforms/wayland/platform_symbols.cpp +++ b/src/platforms/wayland/platform_symbols.cpp @@ -64,7 +64,7 @@ auto probe_graphics_platform( { return mg::SupportedDevice { nullptr, - mg::PlatformPriority::hosted, + mg::probe::hosted, nullptr }; } diff --git a/src/platforms/wayland/wl_egl_display_provider.cpp b/src/platforms/wayland/wl_egl_display_provider.cpp new file mode 100644 index 00000000000..ed022738214 --- /dev/null +++ b/src/platforms/wayland/wl_egl_display_provider.cpp @@ -0,0 +1,220 @@ +#include "wl_egl_display_provider.h" + +#include "mir/graphics/egl_error.h" +#include "mir/graphics/gl_config.h" +#include "mir/graphics/platform.h" +#include +#include + +#include + +namespace mg = mir::graphics; +namespace mgw = mir::graphics::wayland; +namespace geom = mir::geometry; + +class mgw::WlDisplayProvider::Framebuffer::EGLState +{ +public: + EGLState(EGLDisplay dpy, EGLContext ctx, EGLSurface surf) + : dpy{dpy}, + ctx{ctx}, + surf{surf} + { + } + + ~EGLState() + { + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(dpy, ctx); + eglDestroySurface(dpy, surf); + } + + EGLDisplay const dpy; + EGLContext const ctx; + EGLSurface const surf; +}; + +mgw::WlDisplayProvider::Framebuffer::Framebuffer(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, geom::Size size) + : Framebuffer(std::make_shared(dpy, ctx, surf), size) +{ +} + +mgw::WlDisplayProvider::Framebuffer::Framebuffer(std::shared_ptr state, geom::Size size) + : state{std::move(state)}, + size_{size} +{ +} + +auto mgw::WlDisplayProvider::Framebuffer::size() const -> geom::Size +{ + return size_; +} + +void mgw::WlDisplayProvider::Framebuffer::make_current() +{ + if (eglMakeCurrent(state->dpy, state->surf, state->surf, state->ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("eglMakeCurrent failed"))); + } +} + +void mgw::WlDisplayProvider::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")); + } +} + +void mgw::WlDisplayProvider::Framebuffer::swap_buffers() +{ + if (eglSwapBuffers(state->dpy, state->surf) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("eglSwapBuffers failed"))); + } +} + +auto mgw::WlDisplayProvider::Framebuffer::clone_handle() -> std::unique_ptr +{ + return std::unique_ptr{new Framebuffer(state, size_)}; +} + +class mgw::WlDisplayProvider::EGLDisplayProvider : public mg::GenericEGLDisplayProvider +{ +public: + EGLDisplayProvider(EGLDisplay dpy); + + EGLDisplayProvider( + EGLDisplayProvider const& from, + struct wl_surface* surface, + geometry::Size size); + + ~EGLDisplayProvider(); + + auto get_egl_display() -> EGLDisplay override; + + auto alloc_framebuffer(GLConfig const& config, EGLContext share_context) -> std::unique_ptr override; + +private: + EGLDisplay const dpy; + + struct OutputContext + { + struct wl_egl_window* wl_window; + geometry::Size size; + }; + std::optional const output; +}; + +mgw::WlDisplayProvider::EGLDisplayProvider::EGLDisplayProvider(EGLDisplay dpy) + : dpy{dpy} +{ +} + +mgw::WlDisplayProvider::EGLDisplayProvider::EGLDisplayProvider( + EGLDisplayProvider const& from, + struct wl_surface* surface, + geometry::Size size) + : dpy{from.dpy}, + output{ + OutputContext { + wl_egl_window_create(surface, size.width.as_int(), size.height.as_int()), + size + }} +{ +} + +mgw::WlDisplayProvider::EGLDisplayProvider::~EGLDisplayProvider() +{ + if (output) + { + wl_egl_window_destroy(output->wl_window); + } +} + +auto mgw::WlDisplayProvider::EGLDisplayProvider::alloc_framebuffer( + GLConfig const& config, + EGLContext share_context) -> std::unique_ptr +{ + if (!output) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Ooops"})); + } + auto const [wl_window, size] = *output; + + EGLint const config_attr[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, config.depth_buffer_bits(), + EGL_STENCIL_SIZE, config.stencil_buffer_bits(), + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + EGLint num_egl_configs; + EGLConfig egl_config; + if (eglChooseConfig(dpy, config_attr, &egl_config, 1, &num_egl_configs) == EGL_FALSE || + num_egl_configs != 1) + { + BOOST_THROW_EXCEPTION(egl_error("Failed to choose ARGB EGL config")); + } + + 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")); + } + + return std::make_unique( + dpy, + egl_context, + surf, + size); +} + +auto mgw::WlDisplayProvider::EGLDisplayProvider::get_egl_display() -> EGLDisplay +{ + return dpy; +} + +mgw::WlDisplayProvider::WlDisplayProvider(EGLDisplay dpy) + : egl_provider{std::make_shared(dpy)} +{ +} + +mgw::WlDisplayProvider::WlDisplayProvider( + WlDisplayProvider const& from, + struct wl_surface* surface, + geometry::Size size) + : egl_provider{std::make_shared(*from.egl_provider, surface, size)} +{ +} + +auto mgw::WlDisplayProvider::get_egl_display() const -> EGLDisplay +{ + return egl_provider->get_egl_display(); +} + +auto mgw::WlDisplayProvider::maybe_create_interface(DisplayProvider::Tag const& type_tag) + -> std::shared_ptr +{ + if (dynamic_cast(&type_tag)) + { + return egl_provider; + } + return nullptr; +} diff --git a/src/platforms/wayland/wl_egl_display_provider.h b/src/platforms/wayland/wl_egl_display_provider.h new file mode 100644 index 00000000000..c0cdc0af0ce --- /dev/null +++ b/src/platforms/wayland/wl_egl_display_provider.h @@ -0,0 +1,75 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_PLATFORM_WAYLAND_DISPLAY_PROVIDER_H_ +#define MIR_PLATFORM_WAYLAND_DISPLAY_PROVIDER_H_ + +#include "mir/graphics/platform.h" + +#include + +namespace mir::graphics::wayland +{ + +class WlDisplayProvider : public DisplayInterfaceProvider +{ +public: + WlDisplayProvider(EGLDisplay dpy); + + WlDisplayProvider( + WlDisplayProvider const& from, + struct wl_surface* surface, + geometry::Size size); + + auto get_egl_display() const -> EGLDisplay; + + auto maybe_create_interface(DisplayProvider::Tag const& type_tag) + -> std::shared_ptr override; + + class Framebuffer : public GenericEGLDisplayProvider::EGLFramebuffer + { + public: + /** + * Handle for an EGL output surface + * + * \note This takes ownership of \param ctx and \param surf; when the + * 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); + + auto size() const -> geometry::Size override; + + void make_current() override; + void release_current() override; + auto clone_handle() -> std::unique_ptr override; + + void swap_buffers(); + private: + class EGLState; + Framebuffer(std::shared_ptr surf, geometry::Size size); + + std::shared_ptr const state; + geometry::Size const size_; + }; +private: + class EGLDisplayProvider; + + std::shared_ptr const egl_provider; +}; +} + +#endif // MIR_PLATFORM_WAYLAND_DISPLAY_PROVIDER_H_ \ No newline at end of file diff --git a/src/platforms/x11/graphics/display.cpp b/src/platforms/x11/graphics/display.cpp index 5f738854bc1..cb80d393975 100644 --- a/src/platforms/x11/graphics/display.cpp +++ b/src/platforms/x11/graphics/display.cpp @@ -46,47 +46,15 @@ auto get_pixel_size_mm(mx::XCBConnection* conn) -> geom::SizeF float(screen->width_in_millimeters) / screen->width_in_pixels, float(screen->height_in_millimeters) / screen->height_in_pixels}; } - -class XGLContext : public mir::renderer::gl::Context -{ -public: - XGLContext(::Display* const x_dpy, - std::shared_ptr const& gl_config, - EGLContext const shared_ctx) - : egl{*gl_config, x_dpy, shared_ctx} - { - } - - ~XGLContext() = default; - - void make_current() const override - { - egl.make_current(); - } - - void release_current() const override - { - egl.release_current(); - } - -private: - mgx::helpers::EGLHelper const egl; -}; } mgx::X11Window::X11Window(mx::X11Resources* x11_resources, std::string title, - EGLDisplay egl_dpy, - geom::Size const size, - EGLConfig const egl_cfg) + geom::Size const size) : x11_resources{x11_resources} { auto const conn = x11_resources->conn.get(); - EGLint vid; - if (!eglGetConfigAttrib(egl_dpy, egl_cfg, EGL_NATIVE_VISUAL_ID, &vid)) - BOOST_THROW_EXCEPTION(mg::egl_error("Cannot get config attrib")); - uint32_t const value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK; uint32_t value_list[32]; value_list[0] = 0; // background_pixel @@ -134,15 +102,15 @@ mgx::X11Window::operator xcb_window_t() const return win; } -mgx::Display::Display(std::shared_ptr const& x11_resources, - std::string const title, - std::vector const& requested_sizes, - std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config, - std::shared_ptr const& report) - : shared_egl{*gl_config, x11_resources->xlib_dpy}, +mgx::Display::Display( + std::shared_ptr parent, + std::shared_ptr const& x11_resources, + std::string const title, + std::vector const& requested_sizes, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& report) + : parent{std::move(parent)}, x11_resources{x11_resources}, - gl_config{gl_config}, pixel_size_mm{get_pixel_size_mm(x11_resources->conn.get())}, report{report} { @@ -154,9 +122,7 @@ mgx::Display::Display(std::shared_ptr const& x11_resources auto window = std::make_unique( x11_resources.get(), title, - shared_egl.display(), - actual_size, - shared_egl.config()); + actual_size); auto pf = x11_resources->conn->default_pixel_format(); auto configuration = DisplayConfiguration::build_output( pf, @@ -168,14 +134,9 @@ mgx::Display::Display(std::shared_ptr const& x11_resources requested_size.scale, mir_orientation_normal); auto display_buffer = std::make_unique( - x11_resources->xlib_dpy, - configuration->id, + this->parent, *window, - configuration->extents(), - actual_size, - shared_egl.context(), - report, - *gl_config); + configuration->extents()); top_left.x += as_delta(configuration->extents().size.width); outputs.push_back(std::make_unique( this, @@ -184,8 +145,6 @@ mgx::Display::Display(std::shared_ptr const& x11_resources std::move(configuration))); } - shared_egl.make_current(); - auto const display_config = configuration(); initial_conf_policy->apply_to(*display_config); configure(*display_config); @@ -283,11 +242,6 @@ auto mgx::Display::create_hardware_cursor() -> std::shared_ptr return nullptr; } -std::unique_ptr mgx::Display::create_gl_context() const -{ - return std::make_unique(x11_resources->xlib_dpy, gl_config, shared_egl.context()); -} - bool mgx::Display::apply_if_configuration_preserves_display_buffers( mg::DisplayConfiguration const& /*conf*/) { @@ -320,7 +274,6 @@ void mgx::Display::OutputInfo::set_size(geometry::Size const& size) return; } config->modes[0].size = size; - display_buffer->set_size(size); display_buffer->set_view_area(config->extents()); auto const handlers = owner->config_change_handlers; diff --git a/src/platforms/x11/graphics/display.h b/src/platforms/x11/graphics/display.h index 2f9e0abbf7f..502e755913b 100644 --- a/src/platforms/x11/graphics/display.h +++ b/src/platforms/x11/graphics/display.h @@ -34,8 +34,6 @@ namespace mir namespace graphics { -class AtomicFrame; -class GLConfig; class DisplayReport; struct DisplayConfigurationOutput; class DisplayConfigurationPolicy; @@ -45,15 +43,14 @@ namespace X class DisplayBuffer; class X11OutputConfig; +class Platform; class X11Window { public: X11Window(mir::X::X11Resources* x11_resources, std::string title, - EGLDisplay egl_dpy, - geometry::Size const size, - EGLConfig const egl_cfg); + geometry::Size const size); ~X11Window(); operator xcb_window_t() const; @@ -66,12 +63,13 @@ class X11Window class Display : public graphics::Display { public: - explicit Display(std::shared_ptr const& x11_resources, - std::string const title, - std::vector const& requested_size, - std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config, - std::shared_ptr const& report); + Display( + std::shared_ptr parent, + std::shared_ptr const& x11_resources, + std::string const title, + std::vector const& requested_size, + std::shared_ptr const& initial_conf_policy, + std::shared_ptr const& report); ~Display() noexcept; void for_each_display_sync_group(std::function const& f) override; @@ -91,8 +89,6 @@ class Display : public graphics::Display std::shared_ptr create_hardware_cursor() override; - std::unique_ptr create_gl_context() const override; - private: struct OutputInfo : ::mir::X::X11Resources::VirtualOutput { @@ -112,12 +108,10 @@ class Display : public graphics::Display std::shared_ptr const config; }; - helpers::EGLHelper const shared_egl; + std::shared_ptr const parent; std::shared_ptr const x11_resources; - std::shared_ptr const gl_config; geometry::SizeF pixel_size_mm; std::shared_ptr const report; - std::shared_ptr const last_frame; std::mutex mutable mutex; std::vector> outputs; diff --git a/src/platforms/x11/graphics/display_buffer.cpp b/src/platforms/x11/graphics/display_buffer.cpp index 07da8fa9368..6813d013439 100644 --- a/src/platforms/x11/graphics/display_buffer.cpp +++ b/src/platforms/x11/graphics/display_buffer.cpp @@ -15,6 +15,7 @@ */ #include "mir/fatal.h" +#include "platform.h" #include "display_buffer.h" #include "display_configuration.h" #include "mir/graphics/display_report.h" @@ -25,26 +26,14 @@ namespace mg=mir::graphics; namespace mgx=mg::X; namespace geom=mir::geometry; -mgx::DisplayBuffer::DisplayBuffer(::Display* const x_dpy, - DisplayConfigurationOutputId output_id, +mgx::DisplayBuffer::DisplayBuffer(std::shared_ptr parent, xcb_window_t win, - geometry::Rectangle const& view_area, - geometry::Size const& window_size, - EGLContext const shared_context, - std::shared_ptr const& r, - GLConfig const& gl_config) - : report{r}, + geometry::Rectangle const& view_area) + : parent{std::move(parent)}, area{view_area}, - window_size{window_size}, transform(1), - egl{gl_config, x_dpy, win, shared_context}, - output_id{output_id} + x_win{win} { - egl.report_egl_configuration( - [&r] (EGLDisplay disp, EGLConfig cfg) - { - r->report_egl_configuration(disp, cfg); - }); } geom::Rectangle mgx::DisplayBuffer::view_area() const @@ -52,35 +41,32 @@ geom::Rectangle mgx::DisplayBuffer::view_area() const return area; } -auto mgx::DisplayBuffer::size() const -> geom::Size +auto mgx::DisplayBuffer::overlay(std::vector const& /*renderlist*/) -> bool { - return window_size; + // We could, with a lot of effort, make something like overlay support work, but + // there's little point. + return false; } -void mgx::DisplayBuffer::make_current() +namespace { - if (!egl.make_current()) - fatal_error("Failed to make EGL surface current"); -} - -void mgx::DisplayBuffer::release_current() +template +auto unique_ptr_cast(std::unique_ptr ptr) -> std::unique_ptr { - egl.release_current(); -} - -bool mgx::DisplayBuffer::overlay(RenderableList const& /*renderlist*/) -{ - return false; + From* unowned_src = ptr.release(); + if (auto to_src = dynamic_cast(unowned_src)) + { + return std::unique_ptr{to_src}; + } + delete unowned_src; + BOOST_THROW_EXCEPTION(( + std::bad_cast())); } - -void mgx::DisplayBuffer::swap_buffers() -{ - if (!egl.swap_buffers()) - fatal_error("Failed to perform buffer swap"); } -void mgx::DisplayBuffer::bind() +void mgx::DisplayBuffer::set_next_image(std::unique_ptr content) { + next_frame = unique_ptr_cast(std::move(content)); } glm::mat2 mgx::DisplayBuffer::transformation() const @@ -93,9 +79,9 @@ void mgx::DisplayBuffer::set_view_area(geom::Rectangle const& a) area = a; } -void mgx::DisplayBuffer::set_size(geom::Size const& size) +auto mgx::DisplayBuffer::display_provider() const -> std::shared_ptr { - window_size = size; + return parent->provider_for_window(x_win); } void mgx::DisplayBuffer::set_transformation(glm::mat2 const& t) @@ -103,11 +89,6 @@ void mgx::DisplayBuffer::set_transformation(glm::mat2 const& t) transform = t; } -mg::NativeDisplayBuffer* mgx::DisplayBuffer::native_display_buffer() -{ - return this; -} - void mgx::DisplayBuffer::for_each_display_buffer( std::function const& f) { @@ -116,9 +97,16 @@ void mgx::DisplayBuffer::for_each_display_buffer( void mgx::DisplayBuffer::post() { + next_frame->swap_buffers(); + next_frame.reset(); } std::chrono::milliseconds mgx::DisplayBuffer::recommended_sleep() const { return std::chrono::milliseconds::zero(); } + +auto mgx::DisplayBuffer::x11_window() const -> xcb_window_t +{ + return x_win; +} \ No newline at end of file diff --git a/src/platforms/x11/graphics/display_buffer.h b/src/platforms/x11/graphics/display_buffer.h index 6dcf9e11072..44118a0bd7a 100644 --- a/src/platforms/x11/graphics/display_buffer.h +++ b/src/platforms/x11/graphics/display_buffer.h @@ -20,6 +20,7 @@ #include "mir/graphics/display_buffer.h" #include "mir/graphics/display_configuration.h" #include "mir/graphics/display.h" +#include "mir/graphics/platform.h" #include "mir/renderer/gl/render_target.h" #include "egl_helper.h" @@ -31,38 +32,32 @@ namespace mir namespace graphics { -class AtomicFrame; class GLConfig; class DisplayReport; namespace X { +class Platform; class DisplayBuffer : public graphics::DisplayBuffer, - public graphics::DisplaySyncGroup, - public graphics::NativeDisplayBuffer, - public renderer::gl::RenderTarget + public graphics::DisplaySyncGroup { public: DisplayBuffer( - ::Display* const x_dpy, - DisplayConfigurationOutputId output_id, + std::shared_ptr parent, xcb_window_t win, - geometry::Rectangle const& view_area, - geometry::Size const& window_size, - EGLContext const shared_context, - std::shared_ptr const& r, - GLConfig const& gl_config); - - geometry::Rectangle view_area() const override; - auto size() const -> geometry::Size override; - void make_current() override; - void release_current() override; - void swap_buffers() override; - void bind() override; - bool overlay(RenderableList const& renderlist) override; + geometry::Rectangle const& view_area); + + auto view_area() const -> geometry::Rectangle override; + + auto overlay(std::vector const& renderlist) -> bool override; + void set_next_image(std::unique_ptr content) override; + + glm::mat2 transformation() const override; + + auto display_provider() const -> std::shared_ptr override; + void set_view_area(geometry::Rectangle const& a); - void set_size(geometry::Size const& size); void set_transformation(glm::mat2 const& t); void for_each_display_buffer( @@ -70,16 +65,13 @@ class DisplayBuffer : public graphics::DisplayBuffer, void post() override; std::chrono::milliseconds recommended_sleep() const override; - glm::mat2 transformation() const override; - NativeDisplayBuffer* native_display_buffer() override; - + auto x11_window() const -> xcb_window_t; private: - std::shared_ptr const report; + std::shared_ptr const parent; + std::shared_ptr next_frame; geometry::Rectangle area; - geometry::Size window_size; glm::mat2 transform; - helpers::EGLHelper const egl; - DisplayConfigurationOutputId const output_id; + xcb_window_t const x_win; }; } diff --git a/src/platforms/x11/graphics/egl_helper.cpp b/src/platforms/x11/graphics/egl_helper.cpp index 13e57d0a574..df7536309df 100644 --- a/src/platforms/x11/graphics/egl_helper.cpp +++ b/src/platforms/x11/graphics/egl_helper.cpp @@ -19,31 +19,88 @@ #include "mir/graphics/gl_config.h" #include "mir/graphics/egl_error.h" +#include #include namespace mg = mir::graphics; namespace mgx = mg::X; namespace mgxh = mgx::helpers; -mgxh::EGLHelper::EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy) - : EGLHelper{gl_config} +class mgxh::Framebuffer::EGLState { - eglBindAPI(EGL_OPENGL_ES_API); +public: + EGLState(EGLDisplay dpy, EGLContext ctx, EGLSurface surf) + : dpy{dpy}, + ctx{ctx}, + surf{surf} + { + } - static const EGLint context_attr[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; + ~EGLState() + { + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroyContext(dpy, ctx); + eglDestroySurface(dpy, surf); + } + + EGLDisplay const dpy; + EGLContext const ctx; + EGLSurface const surf; +}; + +mgxh::Framebuffer::Framebuffer(EGLDisplay dpy, EGLContext ctx, EGLSurface surf, geometry::Size size) + : Framebuffer(std::make_shared(dpy, ctx, surf), size) +{ +} - setup_internal(x_dpy, true); +mgxh::Framebuffer::Framebuffer(std::shared_ptr state, geometry::Size size) + : state{std::move(state)}, + size_{size} +{ +} - egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attr); - if (egl_context == EGL_NO_CONTEXT) - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); +auto mgxh::Framebuffer::size() const -> geometry::Size +{ + return size_; +} + +void mgxh::Framebuffer::make_current() +{ + if (eglMakeCurrent(state->dpy, state->surf, state->surf, state->ctx) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("eglMakeCurrent failed"))); + } } -mgxh::EGLHelper::EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy, EGLContext shared_context) - : EGLHelper{gl_config} +void mgxh::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")); + } +} + +void mgxh::Framebuffer::swap_buffers() +{ + if (eglSwapBuffers(state->dpy, state->surf) != EGL_TRUE) + { + BOOST_THROW_EXCEPTION((mg::egl_error("eglSwapBuffers failed"))); + } +} + +auto mgxh::Framebuffer::clone_handle() -> std::unique_ptr +{ + return std::unique_ptr{new Framebuffer(state, size_)}; +} + +mgxh::EGLHelper::EGLHelper(::Display* const x_dpy) + : EGLHelper{0, 0} +{ + setup_internal(x_dpy, true); +} + +mgxh::EGLHelper::EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy) + : EGLHelper{gl_config.stencil_buffer_bits(), gl_config.depth_buffer_bits()} { eglBindAPI(EGL_OPENGL_ES_API); @@ -52,9 +109,9 @@ mgxh::EGLHelper::EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy, EG EGL_NONE }; - setup_internal(x_dpy, false); + setup_internal(x_dpy, true); - egl_context = eglCreateContext(egl_display, egl_config, shared_context, context_attr); + egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attr); if (egl_context == EGL_NO_CONTEXT) BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); } @@ -64,7 +121,7 @@ mgxh::EGLHelper::EGLHelper( ::Display* const x_dpy, xcb_window_t win, EGLContext shared_context) - : EGLHelper{gl_config} + : EGLHelper{gl_config.stencil_buffer_bits(), gl_config.depth_buffer_bits()} { eglBindAPI(EGL_OPENGL_ES_API); @@ -101,35 +158,35 @@ mgxh::EGLHelper::~EGLHelper() noexcept } } -bool mgxh::EGLHelper::swap_buffers() const +mgxh::EGLHelper::EGLHelper(int stencil_bits, int depth_bits) + : depth_buffer_bits{depth_bits}, + stencil_buffer_bits{stencil_bits}, + egl_display{EGL_NO_DISPLAY}, egl_config{0}, + egl_context{EGL_NO_CONTEXT}, egl_surface{EGL_NO_SURFACE}, + should_terminate_egl{false} { - auto ret = eglSwapBuffers(egl_display, egl_surface); - return (ret == EGL_TRUE); } -bool mgxh::EGLHelper::make_current() const +namespace { - auto ret = eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); - eglBindAPI(EGL_OPENGL_ES_API); - return (ret == EGL_TRUE); -} - -bool mgxh::EGLHelper::release_current() const +auto size_for_x_win(xcb_connection_t* xcb_conn, xcb_window_t win) -> mir::geometry::Size { - auto ret = eglMakeCurrent(egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - return (ret == EGL_TRUE); + auto cookie = xcb_get_geometry(xcb_conn, win); + if (auto reply = xcb_get_geometry_reply(xcb_conn, cookie, nullptr)) + { + mir::geometry::Size const window_size{reply->width, reply->height}; + free(reply); + return window_size; + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Failed to get X11 window size"})); } - -mgxh::EGLHelper::EGLHelper(GLConfig const& gl_config) - : depth_buffer_bits{gl_config.depth_buffer_bits()}, - stencil_buffer_bits{gl_config.stencil_buffer_bits()}, - egl_display{EGL_NO_DISPLAY}, egl_config{0}, - egl_context{EGL_NO_CONTEXT}, egl_surface{EGL_NO_SURFACE}, - should_terminate_egl{false} -{ } -void mgxh::EGLHelper::setup_internal(::Display* x_dpy, bool initialize) +auto mgxh::EGLHelper::framebuffer_for_window( + GLConfig const& conf, + xcb_connection_t* xcb_conn, + xcb_window_t win, + EGLContext shared_context) -> std::unique_ptr { EGLint const config_attr[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, @@ -137,16 +194,46 @@ void mgxh::EGLHelper::setup_internal(::Display* x_dpy, bool initialize) EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, depth_buffer_bits, - EGL_STENCIL_SIZE, stencil_buffer_bits, + EGL_DEPTH_SIZE, conf.depth_buffer_bits(), + EGL_STENCIL_SIZE, conf.stencil_buffer_bits(), EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; - static const EGLint required_egl_version_major = 1; - static const EGLint required_egl_version_minor = 4; + eglBindAPI(EGL_OPENGL_ES_API); + + static const EGLint context_attr[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; EGLint num_egl_configs; + if (eglChooseConfig(egl_display, config_attr, &egl_config, 1, &num_egl_configs) == EGL_FALSE || + num_egl_configs != 1) + { + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to choose ARGB EGL config")); + } + + auto egl_surface = eglCreateWindowSurface(egl_display, egl_config, win, nullptr); + if(egl_surface == EGL_NO_SURFACE) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL window surface")); + + auto egl_context = eglCreateContext(egl_display, egl_config, shared_context, context_attr); + if (egl_context == EGL_NO_CONTEXT) + BOOST_THROW_EXCEPTION(mg::egl_error("Failed to create EGL context")); + + return std::make_unique( + egl_display, + egl_context, + egl_surface, + size_for_x_win(xcb_conn, win)); +} + +void mgxh::EGLHelper::setup_internal(::Display* x_dpy, bool initialize) +{ + + static const EGLint required_egl_version_major = 1; + static const EGLint required_egl_version_minor = 4; egl_display = eglGetDisplay(static_cast(x_dpy)); if (egl_display == EGL_NO_DISPLAY) @@ -169,12 +256,6 @@ void mgxh::EGLHelper::setup_internal(::Display* x_dpy, bool initialize) should_terminate_egl = true; } - - if (eglChooseConfig(egl_display, config_attr, &egl_config, 1, &num_egl_configs) == EGL_FALSE || - num_egl_configs != 1) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to choose ARGB EGL config")); - } } void mgxh::EGLHelper::report_egl_configuration(std::function f) const diff --git a/src/platforms/x11/graphics/egl_helper.h b/src/platforms/x11/graphics/egl_helper.h index 7bc6e12eb62..80e5a7e1bce 100644 --- a/src/platforms/x11/graphics/egl_helper.h +++ b/src/platforms/x11/graphics/egl_helper.h @@ -23,6 +23,8 @@ #include #include +#include "mir/graphics/platform.h" + typedef struct _XDisplay Display; namespace mir @@ -36,6 +38,32 @@ namespace X namespace helpers { +class Framebuffer : public GenericEGLDisplayProvider::EGLFramebuffer +{ +public: + /** + * Handle for an EGL output surface + * + * \note This takes ownership of \param ctx and \param surf; when the + * 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); + + auto size() const -> geometry::Size override; + + void make_current() override; + void release_current() override; + auto clone_handle() -> std::unique_ptr override; + + void swap_buffers(); +private: + class EGLState; + Framebuffer(std::shared_ptr surf, geometry::Size size); + + std::shared_ptr const state; + geometry::Size const size_; +}; class EGLHelper { @@ -43,24 +71,28 @@ class EGLHelper EGLHelper(const EGLHelper&) = delete; EGLHelper& operator=(const EGLHelper&) = delete; + explicit EGLHelper(::Display* const x_dpy); EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy); EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy, EGLContext shared_context); // Passing an XLib Display and an XCB window is, in fact, not a mistake EGLHelper(GLConfig const& gl_config, ::Display* const x_dpy, xcb_window_t win, EGLContext shared_context); ~EGLHelper() noexcept; - bool swap_buffers() const; - bool make_current() const; - bool release_current() const; + auto framebuffer_for_window( + GLConfig const& conf, + xcb_connection_t* xcb_conn, + xcb_window_t win, + EGLContext shared_context) + -> std::unique_ptr; - EGLContext context() const { return egl_context; } EGLDisplay display() const { return egl_display; } EGLConfig config() const { return egl_config; } EGLSurface surface() const { return egl_surface; } + EGLContext context() const { return egl_context; } void report_egl_configuration(std::function) const; private: - EGLHelper(GLConfig const& gl_config); + EGLHelper(int stencil_bits, int depth_bits); void setup_internal(::Display* const x_dpy, bool initialize); EGLint const depth_buffer_bits; @@ -71,7 +103,6 @@ class EGLHelper EGLSurface egl_surface; bool should_terminate_egl; }; - } } } diff --git a/src/platforms/x11/graphics/graphics.cpp b/src/platforms/x11/graphics/graphics.cpp index eaceb8e91ea..85f8760f5a7 100644 --- a/src/platforms/x11/graphics/graphics.cpp +++ b/src/platforms/x11/graphics/graphics.cpp @@ -89,7 +89,7 @@ auto probe_graphics_platform() -> std::optional { return mg::SupportedDevice { nullptr, - mg::PlatformPriority::hosted, + mg::probe::hosted, std::move(resources) }; } diff --git a/src/platforms/x11/graphics/platform.cpp b/src/platforms/x11/graphics/platform.cpp index 18948dc2b75..8a2adabb2f2 100644 --- a/src/platforms/x11/graphics/platform.cpp +++ b/src/platforms/x11/graphics/platform.cpp @@ -16,10 +16,14 @@ #include "platform.h" #include "display.h" +#include "display_buffer.h" +#include "egl_helper.h" +#include "mir/graphics/egl_error.h" +#include "mir/graphics/platform.h" #include "mir/options/option.h" +#include -namespace mo = mir::options; namespace mg = mir::graphics; namespace mgx = mg::X; namespace geom = mir::geometry; @@ -108,18 +112,98 @@ mgx::Platform::Platform(std::shared_ptr const& x11_resourc std::string title, std::vector output_sizes, std::shared_ptr const& report) - : x11_resources{x11_resources}, + : egl_provider{std::make_shared(x11_resources->xlib_dpy)}, + x11_resources{x11_resources}, title{std::move(title)}, report{report}, output_sizes{std::move(output_sizes)} { - if (!x11_resources) - BOOST_THROW_EXCEPTION(std::runtime_error("Need valid x11 display")); } mir::UniqueModulePtr mgx::Platform::create_display( std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config) + std::shared_ptr const& /*gl_config*/) { - return make_module_ptr(x11_resources, title, output_sizes, initial_conf_policy, gl_config, report); + return make_module_ptr( + std::dynamic_pointer_cast(shared_from_this()), + x11_resources, + title, + output_sizes, + initial_conf_policy, + report); } + +class mgx::Platform::InterfaceProvider : public mg::DisplayInterfaceProvider +{ +public: + InterfaceProvider(::Display* x_dpy) + : egl{std::make_shared(x_dpy)}, + connection{nullptr}, + win{std::nullopt} + { + } + + InterfaceProvider( + InterfaceProvider const& from, + std::shared_ptr connection, + xcb_window_t win) + : egl{from.egl}, + connection{std::move(connection)}, + win{win} + { + } + + class EGLDisplayProvider : public mg::GenericEGLDisplayProvider + { + public: + EGLDisplayProvider( + std::shared_ptr egl, + std::shared_ptr connection, + std::optional x_win) + : egl{std::move(egl)}, + connection{std::move(connection)}, + x_win{std::move(x_win)} + { + } + + auto get_egl_display() -> EGLDisplay + { + return egl->display(); + } + + auto alloc_framebuffer(mg::GLConfig const& config, EGLContext share_context) + -> std::unique_ptr + { + return egl->framebuffer_for_window(config, connection->conn->connection(), x_win.value(), share_context); + } + private: + std::shared_ptr const egl; + std::shared_ptr const connection; + std::optional const x_win; + }; + + auto maybe_create_interface(mir::graphics::DisplayProvider::Tag const& type_tag) + -> std::shared_ptr + { + if (dynamic_cast(&type_tag)) + { + return std::make_shared(egl, connection, win); + } + return {}; + } + +private: + std::shared_ptr const egl; + std::shared_ptr const connection; + std::optional const win; +}; + +auto mgx::Platform::interface_for() -> std::shared_ptr +{ + return egl_provider; +} + +auto mgx::Platform::provider_for_window(xcb_window_t x_win) -> std::shared_ptr +{ + return std::make_shared(*egl_provider, x11_resources, x_win); +} \ No newline at end of file diff --git a/src/platforms/x11/graphics/platform.h b/src/platforms/x11/graphics/platform.h index 6d26864f364..abbebba8206 100644 --- a/src/platforms/x11/graphics/platform.h +++ b/src/platforms/x11/graphics/platform.h @@ -21,6 +21,9 @@ #include "mir/graphics/platform.h" #include "mir/geometry/size.h" +#include +#include + namespace mir { namespace X @@ -58,17 +61,25 @@ class Platform : public graphics::DisplayPlatform // Parses colon separated list of sizes in the form WIDTHxHEIGHT^SCALE (^SCALE is optional) static auto parse_output_sizes(std::string output_sizes) -> std::vector; - explicit Platform(std::shared_ptr const& x11_resources, - std::string const title, - std::vector output_sizes, - std::shared_ptr const& report); + Platform( + std::shared_ptr const& x11_resources, + std::string const title, + std::vector output_sizes, + std::shared_ptr const& report); ~Platform() = default; /* From Platform */ - UniqueModulePtr create_display( + auto create_display( std::shared_ptr const& initial_conf_policy, - std::shared_ptr const& gl_config) override; + std::shared_ptr const& gl_config) -> UniqueModulePtr override; + + auto provider_for_window(xcb_window_t x_win) -> std::shared_ptr; +protected: + auto interface_for() -> std::shared_ptr override; + private: + class InterfaceProvider; + std::shared_ptr const egl_provider; std::shared_ptr const x11_resources; std::string const title; std::shared_ptr const report; diff --git a/src/renderers/gl/CMakeLists.txt b/src/renderers/gl/CMakeLists.txt index f3acd38c141..5b50becf9df 100644 --- a/src/renderers/gl/CMakeLists.txt +++ b/src/renderers/gl/CMakeLists.txt @@ -19,7 +19,6 @@ ADD_LIBRARY( renderer.cpp renderer_factory.cpp - basic_buffer_render_target.cpp ) target_include_directories( diff --git a/src/renderers/gl/basic_buffer_render_target.cpp b/src/renderers/gl/basic_buffer_render_target.cpp deleted file mode 100644 index 1b58ec578df..00000000000 --- a/src/renderers/gl/basic_buffer_render_target.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 or 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "mir/renderer/gl/basic_buffer_render_target.h" -#include "mir/renderer/gl/context.h" -#include "mir/renderer/sw/pixel_source.h" -#include "mir/graphics/egl_error.h" - -#include -#include - -namespace mg = mir::graphics; -namespace mrg = mir::renderer::gl; -namespace mrs = mir::renderer::software; - -mrg::BasicBufferRenderTarget::Framebuffer::Framebuffer(geometry::Size const& size) - : size{size} -{ - glGenRenderbuffers(1, &colour_buffer); - glGenFramebuffers(1, &fbo); - - glBindRenderbuffer(GL_RENDERBUFFER, colour_buffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8_OES, size.width.as_int(), size.height.as_int()); - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colour_buffer); - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - if (status != GL_FRAMEBUFFER_COMPLETE) - { - switch (status) - { - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - BOOST_THROW_EXCEPTION(( - std::runtime_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"} - )); - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: - // Somehow we've managed to attach buffers with mismatched sizes? - BOOST_THROW_EXCEPTION(( - std::logic_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"} - )); - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - BOOST_THROW_EXCEPTION(( - std::logic_error{"FBO is incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"} - )); - case GL_FRAMEBUFFER_UNSUPPORTED: - // This is the only one that isn't necessarily a programming error - BOOST_THROW_EXCEPTION(( - std::runtime_error{"FBO is incomplete: formats selected are not supported by this GL driver"} - )); - case 0: - BOOST_THROW_EXCEPTION(( - mg::gl_error("Failed to verify GL Framebuffer completeness"))); - } - BOOST_THROW_EXCEPTION(( - std::runtime_error{ - std::string{"Unknown GL framebuffer error code: "} + std::to_string(status)})); - } - - // gl::Renderer can only set glViewport if there is a current EGL surface to get the size from. Since we don't bind - // an EGL surface when rendering to a buffer, we have to set the viewport ourselves. - glViewport(0, 0, size.width.as_int(), size.height.as_int()); -} - -mrg::BasicBufferRenderTarget::Framebuffer::~Framebuffer() -{ - glDeleteFramebuffers(1, &fbo); - glDeleteRenderbuffers(1, &colour_buffer); -} - -void mrg::BasicBufferRenderTarget::Framebuffer::copy_to(software::WriteMappableBuffer& buffer) -{ - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - auto mapping = buffer.map_writeable(); - if (mapping->size() != size) - { - BOOST_THROW_EXCEPTION(std::logic_error("given size does not match buffer size")); - } - if (mapping->stride() != geometry::Stride{size.width.as_int() * 4}) - { - BOOST_THROW_EXCEPTION(std::logic_error("invalid buffer stride " + std::to_string(mapping->stride().as_int()))); - } - if (mapping->format() != mir_pixel_format_argb_8888) - { - BOOST_THROW_EXCEPTION(std::logic_error("invalid pixel format " + std::to_string(mapping->format()))); - } - glReadPixels( - 0, 0, - size.width.as_int(), size.height.as_int(), - GL_BGRA_EXT, GL_UNSIGNED_BYTE, mapping->data()); -} - -void mrg::BasicBufferRenderTarget::Framebuffer::bind() -{ - glBindFramebuffer(GL_FRAMEBUFFER, fbo); -} - -mrg::BasicBufferRenderTarget::BasicBufferRenderTarget(std::shared_ptr const& ctx) - : ctx{ctx} -{ -} - -void mrg::BasicBufferRenderTarget::set_buffer(std::shared_ptr const& buffer) -{ - this->buffer = buffer; - if (framebuffer && framebuffer->size == buffer->size()) - { - return; - } - framebuffer.reset(); - framebuffer.emplace(buffer->size()); -} - -auto mrg::BasicBufferRenderTarget::size() const -> geometry::Size -{ - if (framebuffer) - { - return framebuffer.value().size; - } - else - { - return {}; - } -} - -void mrg::BasicBufferRenderTarget::make_current() -{ - ctx->make_current(); -} - -void mrg::BasicBufferRenderTarget::release_current() -{ - ctx->release_current(); -} - -void mrg::BasicBufferRenderTarget::swap_buffers() -{ - if (!framebuffer || !buffer) - { - BOOST_THROW_EXCEPTION(std::logic_error("swap_buffers() called when buffer unset")); - } - framebuffer->copy_to(*buffer); -} - -void mrg::BasicBufferRenderTarget::bind() -{ - if (!framebuffer) - { - BOOST_THROW_EXCEPTION(std::logic_error("bind() called without framebuffer")); - } - framebuffer->bind(); -} diff --git a/src/renderers/gl/renderer.cpp b/src/renderers/gl/renderer.cpp index 533b4d97f44..160e7824e29 100644 --- a/src/renderers/gl/renderer.cpp +++ b/src/renderers/gl/renderer.cpp @@ -24,9 +24,11 @@ #include "mir/log.h" #include "mir/report_exception.h" #include "mir/graphics/egl_error.h" +#include "mir/graphics/platform.h" #include "mir/graphics/texture.h" #include "mir/graphics/program_factory.h" #include "mir/graphics/program.h" +#include "mir/renderer/gl/gl_surface.h" #define GLM_FORCE_RADIANS #include @@ -44,38 +46,6 @@ namespace mgl = mir::gl; namespace mrg = mir::renderer::gl; namespace geom = mir::geometry; -mrg::CurrentRenderTarget::CurrentRenderTarget(RenderTarget& render_target) - : render_target{&render_target} -{ - ensure_current(); -} - -mrg::CurrentRenderTarget::~CurrentRenderTarget() -{ - render_target->release_current(); -} - -auto mrg::CurrentRenderTarget::size() const -> geom::Size -{ - return render_target->size(); -} - -void mrg::CurrentRenderTarget::ensure_current() -{ - render_target->make_current(); -} - -void mrg::CurrentRenderTarget::bind() -{ - ensure_current(); - render_target->bind(); -} - -void mrg::CurrentRenderTarget::swap_buffers() -{ - render_target->swap_buffers(); -} - namespace { template @@ -302,11 +272,23 @@ mrg::Renderer::Program::Program(GLuint program_id) alpha_uniform = glGetUniformLocation(id, "alpha"); } -mrg::Renderer::Renderer(RenderTarget& render_target) - : render_target(render_target), +namespace +{ +auto make_output_current(std::unique_ptr output) -> std::unique_ptr +{ + output->make_current(); + return output; +} +} + +mrg::Renderer::Renderer( + std::shared_ptr gl_interface, + std::unique_ptr output) + : output_surface{make_output_current(std::move(output))}, clear_color{0.0f, 0.0f, 0.0f, 1.0f}, program_factory{std::make_unique()}, - display_transform(1) + display_transform(1), + gl_interface{std::move(gl_interface)} { eglBindAPI(EGL_OPENGL_ES_API); EGLDisplay disp = eglGetCurrentDisplay(); @@ -360,7 +342,6 @@ mrg::Renderer::Renderer(RenderTarget& render_target) mrg::Renderer::~Renderer() { - render_target.ensure_current(); } void mrg::Renderer::tessellate(std::vector& primitives, @@ -370,9 +351,10 @@ void mrg::Renderer::tessellate(std::vector& primitives, primitives[0] = mgl::tessellate_renderable_into_rectangle(renderable, geom::Displacement{0,0}); } -void mrg::Renderer::render(mg::RenderableList const& renderables) const +auto mrg::Renderer::render(mg::RenderableList const& renderables) const -> std::unique_ptr { - render_target.bind(); + output_surface->make_current(); + output_surface->bind(); glClearColor(clear_color[0], clear_color[1], clear_color[2], clear_color[3]); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); @@ -384,10 +366,13 @@ void mrg::Renderer::render(mg::RenderableList const& renderables) const draw(*r); } - render_target.swap_buffers(); + auto output = output_surface->commit(); + // Report any GL errors after commit, to catch any *during* commit while (auto const gl_error = glGetError()) mir::log_debug("GL error: %d", gl_error); + + return output; } void mrg::Renderer::draw(mg::Renderable const& renderable) const @@ -408,12 +393,7 @@ void mrg::Renderer::draw(mg::Renderable const& renderable) const ); } - auto const texture = std::dynamic_pointer_cast(renderable.buffer()); - if (!texture) - { - mir::log_error("Buffer does not support GL rendering!"); - return; - } + auto const texture = gl_interface->as_texture(renderable.buffer()); // All the programs are held by program_factory through its lifetime. Using pointers avoids // -Wdangling-reference. @@ -464,7 +444,7 @@ void mrg::Renderer::draw(mg::Renderable const& renderable) const 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - -1.0, 1.0, 0.0, 1.0 + 0.0, 0.0, 0.0, 1.0 }; } @@ -604,36 +584,53 @@ void mrg::Renderer::update_gl_viewport() * the logical viewport aspect ratio doesn't match the display aspect. * This keeps pixels square. Note "black"-bars are really glClearColor. */ - - auto const buf_size = render_target.size(); - GLint const buf_width = buf_size.width.as_value(), buf_height = buf_size.height.as_value(); - if (!buf_width || !buf_height) - { - return; - } - auto transformed_viewport = display_transform * glm::vec4(viewport.size.width.as_int(), viewport.size.height.as_int(), 0, 1); auto viewport_width = fabs(transformed_viewport[0]); auto viewport_height = fabs(transformed_viewport[1]); - GLint reduced_width = buf_width, reduced_height = buf_height; - // if viewport_aspect_ratio >= buf_aspect_ratio - if (viewport_width * buf_height >= buf_width * viewport_height) - reduced_height = buf_width * viewport_height / viewport_width; - else - reduced_width = buf_height * viewport_width / viewport_height; + auto const output_size = output_surface->size(); + auto const output_width = output_size.width.as_value(); + auto const output_height = output_size.height.as_value(); + + if (viewport_width > 0.0f && viewport_height > 0.0f && + output_width > 0 && output_height > 0) + { + GLint reduced_width = output_width, reduced_height = output_height; + // if viewport_aspect_ratio >= output_aspect_ratio + if (viewport_width * output_height >= output_width * viewport_height) + reduced_height = output_width * viewport_height / viewport_width; + else + reduced_width = output_height * viewport_width / viewport_height; - GLint offset_x = (buf_width - reduced_width) / 2; - GLint offset_y = (buf_height - reduced_height) / 2; + GLint offset_x = (output_width - reduced_width) / 2; + GLint offset_y = (output_height - reduced_height) / 2; - glViewport(offset_x, offset_y, reduced_width, reduced_height); + glViewport(offset_x, offset_y, reduced_width, reduced_height); + } } void mrg::Renderer::set_output_transform(glm::mat2 const& t) { - auto const new_display_transform = glm::mat4(t); + auto new_display_transform = glm::mat4(t); + + switch (output_surface->layout()) + { + case graphics::gl::OutputSurface::Layout::GL: + break; + case graphics::gl::OutputSurface::Layout::TopRowFirst: + // GL is going to render in its own coordinate system, but the OutputSurface + // wants the output to be the other way up. Get GL to render upside-down instead. + new_display_transform *= glm::mat4{ + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + }; + break; + } + if (new_display_transform != display_transform) { display_transform = new_display_transform; @@ -643,4 +640,5 @@ void mrg::Renderer::set_output_transform(glm::mat2 const& t) void mrg::Renderer::suspend() { + output_surface->release_current(); } diff --git a/src/renderers/gl/renderer.h b/src/renderers/gl/renderer.h index 9d4b12455ca..0caf2c3dec6 100644 --- a/src/renderers/gl/renderer.h +++ b/src/renderers/gl/renderer.h @@ -22,7 +22,6 @@ #include #include #include -#include "mir/renderer/gl/render_target.h" #include #include @@ -31,38 +30,23 @@ namespace mir { -namespace graphics { class DisplayBuffer; } +namespace graphics { class DisplayBuffer; class GLRenderingProvider; } +namespace graphics::gl { class OutputSurface; } namespace renderer { namespace gl { -class CurrentRenderTarget -{ -public: - CurrentRenderTarget(RenderTarget& render_target); - ~CurrentRenderTarget(); - - auto size() const -> geometry::Size; - void ensure_current(); - void bind(); - void swap_buffers(); - -private: - renderer::gl::RenderTarget* const render_target; -}; - class Renderer : public renderer::Renderer { public: - /// render_target is owned externally, and must be kept alive as long as this object. - Renderer(RenderTarget& render_target); + Renderer(std::shared_ptr gl_interface, std::unique_ptr output); virtual ~Renderer(); // These are called with a valid GL context: void set_viewport(geometry::Rectangle const& rect) override; void set_output_transform(glm::mat2 const&) override; - void render(graphics::RenderableList const&) const override; + auto render(graphics::RenderableList const&) const -> std::unique_ptr override; // This is called _without_ a GL context: void suspend() override; @@ -87,7 +71,7 @@ class Renderer : public renderer::Renderer Program(GLuint program_id); }; private: - mutable CurrentRenderTarget render_target; + std::unique_ptr const output_surface; protected: /** @@ -124,6 +108,7 @@ class Renderer : public renderer::Renderer glm::mat4 screen_to_gl_coords; glm::mat4 display_transform; std::vector mutable primitives; + std::shared_ptr const gl_interface; }; } diff --git a/src/renderers/gl/renderer_factory.cpp b/src/renderers/gl/renderer_factory.cpp index f22fb7c7514..55003077cc6 100644 --- a/src/renderers/gl/renderer_factory.cpp +++ b/src/renderers/gl/renderer_factory.cpp @@ -16,11 +16,14 @@ #include "renderer_factory.h" #include "renderer.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/gl/gl_surface.h" namespace mrg = mir::renderer::gl; -std::unique_ptr -mrg::RendererFactory::create_renderer_for(RenderTarget& render_target) +auto mrg::RendererFactory::create_renderer_for( + std::unique_ptr output_surface, + std::shared_ptr gl_provider) const -> std::unique_ptr { - return std::make_unique(render_target); + return std::make_unique(std::move(gl_provider), std::move(output_surface)); } diff --git a/src/renderers/gl/renderer_factory.h b/src/renderers/gl/renderer_factory.h index 82eed4f15b9..24f3def5204 100644 --- a/src/renderers/gl/renderer_factory.h +++ b/src/renderers/gl/renderer_factory.h @@ -21,6 +21,10 @@ namespace mir { +namespace graphics +{ +class GLRenderingProvider; +} namespace renderer { namespace gl @@ -29,7 +33,9 @@ namespace gl class RendererFactory : public renderer::RendererFactory { public: - std::unique_ptr create_renderer_for(RenderTarget& render_target) override; + auto create_renderer_for( + std::unique_ptr output_surface, + std::shared_ptr gl_provider) const -> std::unique_ptr override; }; } diff --git a/src/server/compositor/basic_screen_shooter.cpp b/src/server/compositor/basic_screen_shooter.cpp index c9e113c99bb..8bffc25d439 100644 --- a/src/server/compositor/basic_screen_shooter.cpp +++ b/src/server/compositor/basic_screen_shooter.cpp @@ -15,30 +15,122 @@ */ #include "basic_screen_shooter.h" -#include "mir/renderer/gl/buffer_render_target.h" +#include "mir/graphics/drm_formats.h" +#include "mir/graphics/gl_config.h" #include "mir/renderer/renderer.h" -#include "mir/renderer/gl/context.h" +#include "mir/renderer/gl/gl_surface.h" #include "mir/compositor/scene_element.h" #include "mir/compositor/scene.h" #include "mir/log.h" #include "mir/executor.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/renderer_factory.h" +#include "mir/renderer/sw/pixel_source.h" namespace mc = mir::compositor; namespace mr = mir::renderer; namespace mg = mir::graphics; -namespace mrg = mir::renderer::gl; namespace mrs = mir::renderer::software; namespace geom = mir::geometry; +class mc::BasicScreenShooter::Self::OneShotBufferDisplayProvider : public mg::CPUAddressableDisplayProvider +{ +public: + OneShotBufferDisplayProvider() = default; + + class FB : public mg::CPUAddressableDisplayProvider::MappableFB + { + public: + FB(std::shared_ptr buffer) + : buffer{std::move(buffer)} + { + } + + auto map_writeable() -> std::unique_ptr> override + { + return buffer->map_writeable(); + } + auto size() const -> geom::Size override + { + return buffer->size(); + } + auto format() const -> MirPixelFormat override + { + return buffer->format(); + } + auto stride() const -> geom::Stride override + { + return buffer->stride(); + } + private: + std::shared_ptr const buffer; + }; + + auto supported_formats() const -> std::vector override + { + if (!next_buffer) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Attempted to query supported_formats before assigning a buffer"})); + } + return {mg::DRMFormat::from_mir_format(next_buffer->format())}; + } + + auto alloc_fb(geom::Size pixel_size, mg::DRMFormat format) -> std::unique_ptr override + { + if (pixel_size != next_buffer->size()) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Mismatched buffer sizes?"})); + } + if (format.as_mir_format().value_or(mir_pixel_format_invalid) != next_buffer->format()) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Mismatched pixel formats"})); + } + return std::make_unique(std::exchange(next_buffer, nullptr)); + } + + void set_next_buffer(std::shared_ptr buffer) + { + if (next_buffer) + { + BOOST_THROW_EXCEPTION((std::logic_error{"Attempt to set next buffer with a buffer already pending"})); + } + next_buffer = std::move(buffer); + } +private: + std::shared_ptr next_buffer; +}; + +class InterfaceProvider : public mg::DisplayInterfaceProvider +{ +public: + InterfaceProvider(std::shared_ptr provider) + : provider {std::move(provider)} + { + } +protected: + auto maybe_create_interface(mg::DisplayProvider::Tag const& type_tag) + -> std::shared_ptr override + { + if (dynamic_cast(&type_tag)) + { + return provider; + } + return nullptr; + } +private: + std::shared_ptr const provider; +}; + mc::BasicScreenShooter::Self::Self( std::shared_ptr const& scene, std::shared_ptr const& clock, - std::unique_ptr&& render_target, - std::unique_ptr&& renderer) + std::shared_ptr render_provider, + std::shared_ptr renderer_factory) : scene{scene}, - render_target{std::move(render_target)}, - renderer{std::move(renderer)}, - clock{clock} + clock{clock}, + render_provider{std::move(render_provider)}, + renderer_factory{std::move(renderer_factory)}, + output{std::make_shared()} { } @@ -58,26 +150,81 @@ auto mc::BasicScreenShooter::Self::render( } scene_elements.clear(); - render_target->make_current(); - render_target->set_buffer(buffer); + auto& renderer = renderer_for_buffer(buffer); + renderer.set_viewport(area); + /* We don't need the result of this `render` call, as we know it's + * going into the buffer we just set + */ + renderer.render(renderable_list); + + // Because we might be called on a different thread next time we need to + // ensure the renderer doesn't keep the EGL context current + renderer.suspend(); + return captured_time; +} - render_target->bind(); - renderer->set_viewport(area); - renderer->render(renderable_list); +auto mc::BasicScreenShooter::Self::renderer_for_buffer(std::shared_ptr buffer) + -> mr::Renderer& +{ + auto const buffer_size = buffer->size(); + if (buffer_size.height == geom::Height{0} || buffer_size.width == geom::Width{0}) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Attempt to capture to a zero-sized buffer"})); + } + output->set_next_buffer(std::move(buffer)); + if (buffer_size != last_rendered_size) + { + // We need to build a new Renderer, at the new size + class NoAuxConfig : public graphics::GLConfig + { + public: + auto depth_buffer_bits() const -> int override + { + return 0; + } + auto stencil_buffer_bits() const -> int override + { + return 0; + } + }; + auto interface_provider = std::make_shared(output); + auto gl_surface = render_provider->surface_for_output(interface_provider, buffer_size, NoAuxConfig{}); + current_renderer = renderer_factory->create_renderer_for(std::move(gl_surface), render_provider); + last_rendered_size = buffer_size; + } + return *current_renderer; +} - render_target->release_current(); - renderable_list.clear(); +auto mc::BasicScreenShooter::select_provider( + std::span> const& providers) + -> std::shared_ptr +{ + auto display_provider = std::make_shared(); + auto interface_provider = std::make_shared(display_provider); - return captured_time; + for (auto const& render_provider : providers) + { + /* TODO: There might be more sensible ways to select a provider + * (one in use by at least one DisplayBuffer, the only one in use, the lowest-powered one,...) + * That will be a job for a policy object, later. + * + * For now, just use the first that claims to work. + */ + if (render_provider->suitability_for_display(interface_provider) >= mg::probe::supported) + { + return render_provider; + } + } + BOOST_THROW_EXCEPTION((std::runtime_error{"No rendering provider claims to support a CPU addressable target"})); } mc::BasicScreenShooter::BasicScreenShooter( std::shared_ptr const& scene, std::shared_ptr const& clock, Executor& executor, - std::unique_ptr&& render_target, - std::unique_ptr&& renderer) - : self{std::make_shared(scene, clock, std::move(render_target), std::move(renderer))}, + std::span> const& providers, + std::shared_ptr render_factory) + : self{std::make_shared(scene, clock, select_provider(providers), std::move(render_factory))}, executor{executor} { } @@ -89,7 +236,7 @@ void mc::BasicScreenShooter::capture( { // TODO: use an atomic to keep track of number of in-flight captures, and error if it's too many - executor.spawn([weak_self=std::weak_ptr{self}, buffer, area, callback=std::move(callback)] + executor.spawn([weak_self=std::weak_ptr{self}, buffer, area, callback=std::move(callback)] { if (auto const self = weak_self.lock()) { diff --git a/src/server/compositor/basic_screen_shooter.h b/src/server/compositor/basic_screen_shooter.h index 7763f6c9d9c..3caeaa80779 100644 --- a/src/server/compositor/basic_screen_shooter.h +++ b/src/server/compositor/basic_screen_shooter.h @@ -18,6 +18,9 @@ #define MIR_COMPOSITOR_BASIC_SCREEN_SHOOTER_H_ #include "mir/compositor/screen_shooter.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/renderer_factory.h" +#include "mir/renderer/sw/pixel_source.h" #include "mir/time/clock.h" #include @@ -28,10 +31,7 @@ class Executor; namespace renderer { class Renderer; -namespace gl -{ -class BufferRenderTarget; -} +class RendererFactory; } namespace compositor { @@ -44,8 +44,8 @@ class BasicScreenShooter: public ScreenShooter std::shared_ptr const& scene, std::shared_ptr const& clock, Executor& executor, - std::unique_ptr&& render_target, - std::unique_ptr&& renderer); + std::span> const& providers, + std::shared_ptr render_factory); void capture( std::shared_ptr const& buffer, @@ -55,24 +55,41 @@ class BasicScreenShooter: public ScreenShooter private: struct Self { + class OneShotBufferDisplayProvider; + Self( std::shared_ptr const& scene, std::shared_ptr const& clock, - std::unique_ptr&& render_target, - std::unique_ptr&& renderer); + std::shared_ptr provider, + std::shared_ptr render_factory); auto render( std::shared_ptr const& buffer, geometry::Rectangle const& area) -> time::Timestamp; + auto renderer_for_buffer(std::shared_ptr buffer) + -> renderer::Renderer&; + std::mutex mutex; std::shared_ptr const scene; - std::unique_ptr const render_target; - std::unique_ptr const renderer; std::shared_ptr const clock; + std::shared_ptr const render_provider; + std::shared_ptr const renderer_factory; + + /* The Renderer instantiation is tied to a particular output size, and + * and requires enough setup to make it worth keeping around as a consumer + * is likely to be taking screenshots of consistent size + */ + std::unique_ptr current_renderer; + geometry::Size last_rendered_size; + + std::shared_ptr const output; }; std::shared_ptr const self; Executor& executor; + + static auto select_provider(std::span> const& providers) + -> std::shared_ptr; }; } } diff --git a/src/server/compositor/default_configuration.cpp b/src/server/compositor/default_configuration.cpp index 85e5cd7614f..00a8a0f9a6d 100644 --- a/src/server/compositor/default_configuration.cpp +++ b/src/server/compositor/default_configuration.cpp @@ -16,27 +16,22 @@ #include "mir/default_server_configuration.h" +#include "mir/log.h" #include "mir/shell/shell.h" #include "buffer_stream_factory.h" #include "default_display_buffer_compositor_factory.h" +#include "mir/executor.h" #include "multi_threaded_compositor.h" #include "gl/renderer_factory.h" #include "basic_screen_shooter.h" #include "null_screen_shooter.h" #include "mir/main_loop.h" -#include "mir/graphics/display.h" -#include "mir/executor.h" -#include "mir/renderer/gl/basic_buffer_render_target.h" -#include "mir/renderer/gl/context.h" -#include "mir/renderer/renderer.h" -#include "mir/log.h" - +#include "mir/graphics/platform.h" #include "mir/options/configuration.h" namespace mc = mir::compositor; namespace ms = mir::scene; -namespace mf = mir::frontend; -namespace mrg = mir::renderer::gl; +namespace mg = mir::graphics; std::shared_ptr mir::DefaultServerConfiguration::the_buffer_stream_factory() @@ -54,8 +49,22 @@ mir::DefaultServerConfiguration::the_display_buffer_compositor_factory() return display_buffer_compositor_factory( [this]() { - return wrap_display_buffer_compositor_factory(std::make_shared( - the_renderer_factory(), the_compositor_report())); + std::vector> providers; + providers.reserve(the_rendering_platforms().size()); + for (auto const& platform : the_rendering_platforms()) + { + if (auto gl_provider = mg::RenderingPlatform::acquire_provider(platform)) + { + providers.push_back(gl_provider); + } + } + if (providers.empty()) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"Selected rendering platform does not support GL"})); + } + return wrap_display_buffer_compositor_factory( + std::make_shared( + std::move(providers), the_gl_config(), the_renderer_factory(), the_buffer_allocator(), the_compositor_report())); }); } @@ -102,14 +111,25 @@ auto mir::DefaultServerConfiguration::the_screen_shooter() -> std::shared_ptr(the_display()->create_gl_context()); - auto renderer = the_renderer_factory()->create_renderer_for(*render_target); + std::vector> providers; + providers.reserve(the_rendering_platforms().size()); + for (auto& platform : the_rendering_platforms()) + { + if (auto gl_provider = mg::RenderingPlatform::acquire_provider(platform)) + { + providers.push_back(gl_provider); + } + } + if (providers.empty()) + { + BOOST_THROW_EXCEPTION((std::runtime_error{"No platform provides GL rendering support"})); + } return std::make_shared( the_scene(), the_clock(), thread_pool_executor, - std::move(render_target), - std::move(renderer)); + providers, + the_renderer_factory()); } catch (...) { diff --git a/src/server/compositor/default_display_buffer_compositor.cpp b/src/server/compositor/default_display_buffer_compositor.cpp index aa356d8ccc5..6d0cb241b52 100644 --- a/src/server/compositor/default_display_buffer_compositor.cpp +++ b/src/server/compositor/default_display_buffer_compositor.cpp @@ -16,11 +16,14 @@ #include "default_display_buffer_compositor.h" +#include "mir/compositor/compositor_report.h" #include "mir/compositor/scene.h" #include "mir/compositor/scene_element.h" #include "mir/graphics/renderable.h" #include "mir/graphics/display_buffer.h" #include "mir/graphics/buffer.h" +#include "mir/graphics/platform.h" +#include "mir/compositor/buffer_stream.h" #include "mir/renderer/renderer.h" #include "occlusion.h" @@ -29,10 +32,12 @@ namespace mg = mir::graphics; mc::DefaultDisplayBufferCompositor::DefaultDisplayBufferCompositor( mg::DisplayBuffer& display_buffer, + graphics::GLRenderingProvider& gl_provider, std::shared_ptr const& renderer, - std::shared_ptr const& report) : + std::shared_ptr const& report) : display_buffer(display_buffer), renderer(renderer), + fb_adaptor{gl_provider.make_framebuffer_provider(display_buffer.display_provider())}, report(report) { } @@ -58,14 +63,48 @@ void mc::DefaultDisplayBufferCompositor::composite(mc::SceneElementSequence&& sc /* * Note: Buffer lifetimes are ensured by the two objects holding * references to them; scene_elements and renderable_list. - * So no buffer is going to be released back to the client till + * So no buffer is going to be releaswayland_executored back to the client till * both of those containers get destroyed (end of the function). * Actually, there's a third reference held by the texture cache * in GLRenderer, but that gets released earlier in render(). */ scene_elements.clear(); // Those in use are still in renderable_list - if (display_buffer.overlay(renderable_list)) + std::vector framebuffers; + framebuffers.reserve(renderable_list.size()); + + for (auto const& renderable : renderable_list) + { + auto fb = fb_adaptor->buffer_to_framebuffer(renderable->buffer()); + if (!fb) + { + break; + } + geometry::Rectangle clipped_dest; + if (renderable->clip_area()) + { + clipped_dest = intersection_of(renderable->screen_position(), *renderable->clip_area()); + } + else + { + clipped_dest = renderable->screen_position(); + } + geometry::SizeF const source_size{ + clipped_dest.size.width.as_value(), + clipped_dest.size.height.as_value()}; + geometry::PointF const source_origin{ + clipped_dest.top_left.x.as_value() - renderable->screen_position().top_left.x.as_value(), + clipped_dest.top_left.y.as_value() - renderable->screen_position().top_left.y.as_value() + }; + + framebuffers.emplace_back(mg::DisplayElement{ + renderable->screen_position(), + geometry::RectangleF{source_origin, source_size}, + std::move(fb) + }); + } + + if (framebuffers.size() == renderable_list.size() && display_buffer.overlay(framebuffers)) { report->renderables_in_frame(this, renderable_list); renderer->suspend(); @@ -74,7 +113,8 @@ void mc::DefaultDisplayBufferCompositor::composite(mc::SceneElementSequence&& sc { renderer->set_output_transform(display_buffer.transformation()); renderer->set_viewport(view_area); - renderer->render(renderable_list); + + display_buffer.set_next_image(renderer->render(renderable_list)); report->renderables_in_frame(this, renderable_list); report->rendered_frame(this); diff --git a/src/server/compositor/default_display_buffer_compositor.h b/src/server/compositor/default_display_buffer_compositor.h index ed4afd4d020..4bf13a3ca3b 100644 --- a/src/server/compositor/default_display_buffer_compositor.h +++ b/src/server/compositor/default_display_buffer_compositor.h @@ -18,11 +18,15 @@ #define MIR_COMPOSITOR_DEFAULT_DISPLAY_BUFFER_COMPOSITOR_H_ #include "mir/compositor/display_buffer_compositor.h" -#include "mir/compositor/compositor_report.h" +#include "mir/graphics/platform.h" #include namespace mir { +namespace compositor +{ +class CompositorReport; +} namespace graphics { class DisplayBuffer; @@ -41,15 +45,17 @@ class DefaultDisplayBufferCompositor : public DisplayBufferCompositor public: DefaultDisplayBufferCompositor( graphics::DisplayBuffer& display_buffer, + graphics::GLRenderingProvider& gl_provider, std::shared_ptr const& renderer, - std::shared_ptr const& report); + std::shared_ptr const& report); void composite(SceneElementSequence&& scene_sequence) override; private: graphics::DisplayBuffer& display_buffer; std::shared_ptr const renderer; - std::shared_ptr const report; + std::unique_ptr const fb_adaptor; + std::shared_ptr const report; }; } diff --git a/src/server/compositor/default_display_buffer_compositor_factory.cpp b/src/server/compositor/default_display_buffer_compositor_factory.cpp index cfb9cf2f569..85c39de0244 100644 --- a/src/server/compositor/default_display_buffer_compositor_factory.cpp +++ b/src/server/compositor/default_display_buffer_compositor_factory.cpp @@ -19,6 +19,9 @@ #include "mir/renderer/renderer.h" #include "mir/graphics/display_buffer.h" #include "mir/renderer/gl/render_target.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/gl/gl_surface.h" +#include "mir/graphics/gl_config.h" #include "default_display_buffer_compositor.h" @@ -28,10 +31,16 @@ namespace mc = mir::compositor; namespace mg = mir::graphics; mc::DefaultDisplayBufferCompositorFactory::DefaultDisplayBufferCompositorFactory( + std::vector> render_platforms, + std::shared_ptr gl_config, std::shared_ptr const& renderer_factory, + std::shared_ptr const& buffer_allocator, std::shared_ptr const& report) : - renderer_factory{renderer_factory}, - report{report} + platforms{std::move(render_platforms)}, + gl_config{std::move(gl_config)}, + renderer_factory{renderer_factory}, + buffer_allocator{buffer_allocator}, + report{report} { } @@ -39,13 +48,44 @@ std::unique_ptr mc::DefaultDisplayBufferCompositorFactory::create_compositor_for( mg::DisplayBuffer& display_buffer) { - auto const render_target = dynamic_cast(display_buffer.native_display_buffer()); - if (!render_target) + /* TODO: There's scope for (GPU) memory optimisation here: + * We unconditionally allocate a GL rendering surface for the renderer, + * but with a different interface the DisplayBufferCompositor could choose + * not to allocate a GL surface if everything is working with overlays. + * + * For simple cases, such as those targetted by Ubuntu Frame, not needing the + * GL surface could be the common case, and not allocating it would save a + * potentially-significant amount of GPU memory. + */ + + auto const display_provider = display_buffer.display_provider(); + + /* In a heterogeneous system, different providers may be better at driving a specific + * display. Select the best one. + */ + std::pair> best_provider = std::make_pair(mg::probe::unsupported, nullptr); + for (auto const& provider : platforms) { - BOOST_THROW_EXCEPTION(std::logic_error("DisplayBuffer does not support GL rendering")); + auto suitability = provider->suitability_for_display(display_provider); + // 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) + { + best_provider = std::make_pair(suitability, provider); + } } - auto renderer = renderer_factory->create_renderer_for(*render_target); + if (best_provider.first == mg::probe::unsupported) + { + // We should not get here; the rendering platforms have already had + // an opportunity to claim they don't support any present hardware + BOOST_THROW_EXCEPTION((std::logic_error{"No rendering platform claims to support this output"})); + } + + auto const chosen_allocator = best_provider.second; + + auto output_surface = chosen_allocator->surface_for_output( + display_provider, display_buffer.view_area().size, *gl_config); + auto renderer = renderer_factory->create_renderer_for(std::move(output_surface), chosen_allocator); renderer->set_viewport(display_buffer.view_area()); return std::make_unique( - display_buffer, std::move(renderer), report); + display_buffer, *chosen_allocator, std::move(renderer), report); } diff --git a/src/server/compositor/default_display_buffer_compositor_factory.h b/src/server/compositor/default_display_buffer_compositor_factory.h index 1659293be1c..efee53b1a33 100644 --- a/src/server/compositor/default_display_buffer_compositor_factory.h +++ b/src/server/compositor/default_display_buffer_compositor_factory.h @@ -22,10 +22,21 @@ namespace mir { +namespace graphics +{ +class RenderingPlatform; +} namespace renderer { class RendererFactory; } + +namespace graphics +{ +class GLRenderingProvider; +class GLConfig; +class GraphicBufferAllocator; +} /// Compositing. Combining renderables into a display image. namespace compositor { @@ -34,17 +45,24 @@ class DefaultDisplayBufferCompositorFactory : public DisplayBufferCompositorFact { public: DefaultDisplayBufferCompositorFactory( + std::vector> render_platforms, + std::shared_ptr gl_config, std::shared_ptr const& renderer_factory, + std::shared_ptr const& buffer_allocator, std::shared_ptr const& report); - std::unique_ptr create_compositor_for(graphics::DisplayBuffer& display_buffer); + std::unique_ptr create_compositor_for(graphics::DisplayBuffer& display_buffer) override; private: + std::vector> const platforms; + std::shared_ptr const gl_config; std::shared_ptr const renderer_factory; + std::shared_ptr const buffer_allocator; std::shared_ptr const report; }; } } + #endif /* MIR_COMPOSITOR_DEFAULT_DISPLAY_BUFFER_COMPOSITOR_FACTORY_H_ */ diff --git a/src/server/graphics/default_configuration.cpp b/src/server/graphics/default_configuration.cpp index 1942db6df3f..2cd37f0efb8 100644 --- a/src/server/graphics/default_configuration.cpp +++ b/src/server/graphics/default_configuration.cpp @@ -20,6 +20,7 @@ #include "mir/graphics/default_display_configuration_policy.h" #include "mir/graphics/graphic_buffer_allocator.h" #include "mir/graphics/display.h" +#include "multiplexing_display.h" #include "null_cursor.h" #include "software_cursor.h" #include "platform_probe.h" @@ -133,6 +134,25 @@ auto select_platforms_from_list(std::string const& selection, std::vector>>& platform_modules) +{ + static bool const experimental_hybrid_graphics = getenv("MIR_EXPERIMENTAL_HYBRID_GRAPHICS"); + + if (!experimental_hybrid_graphics) + { + std::stable_sort(std::begin(platform_modules), std::end(platform_modules), + [](auto const& l, auto const& r) { return l.first.support_level > r.first.support_level; }); + + if (!platform_modules.empty()) + { + auto const erase_begin = std::remove_if(platform_modules.begin(), platform_modules.end(), + [platform=platform_modules.front().second](auto const& pm) { return pm.second != platform; }); + + platform_modules.erase(erase_begin, platform_modules.end()); + } + } +} } auto mir::DefaultServerConfiguration::the_display_platforms() -> std::vector> const& @@ -169,11 +189,12 @@ auto mir::DefaultServerConfiguration::the_display_platforms() -> std::vector= mg::PlatformPriority::supported) + // Add any devices that the platform claims are supported + if (device.support_level >= mg::probe::supported) { found_supported_device = true; + platform_modules.emplace_back(std::move(device), platform); } - platform_modules.emplace_back(std::move(device), platform); } if (!found_supported_device) @@ -184,12 +205,21 @@ auto mir::DefaultServerConfiguration::the_display_platforms() -> std::vectorname); + + // We're here only if the platform doesn't claim to support *any* of the detected devices + // Add *all* the found devices into our platform list, and hope. + for (auto& device : supported_devices) + { + platform_modules.emplace_back(std::move(device), platform); + } } } } else { platform_modules = mir::graphics::display_modules_for_device(platforms, dynamic_cast(*the_options()), the_console_services()); + + hybrid_check(platform_modules); } for (auto const& [device, platform]: platform_modules) @@ -201,13 +231,28 @@ auto mir::DefaultServerConfiguration::the_display_platforms() -> std::vectorload_function( "describe_graphics_module", MIR_SERVER_GRAPHICS_PLATFORM_VERSION); - + auto description = describe_module(); - mir::log_info("Selected display driver: %s (version %d.%d.%d)", - description->name, - description->major_version, - description->minor_version, - description->micro_version); + if (!device.device) + { + mir::log_info( + "Selected display driver: %s (version %d.%d.%d) for platform", + description->name, + description->major_version, + description->minor_version, + description->micro_version); + } + else + { + mir::log_info( + "Selected display driver: %s (version %d.%d.%d) for device (%s: %s)", + description->name, + description->major_version, + description->minor_version, + description->micro_version, + device.device->driver(), + device.device->devnode()); + } // TODO: Do we want to be able to continue on partial failure here? display_platforms.push_back( @@ -256,6 +301,14 @@ auto mir::DefaultServerConfiguration::the_rendering_platforms() -> throw std::runtime_error(msg.c_str()); } + std::vector> display_interfaces; + display_interfaces.reserve(the_display_platforms().size()); + + for (auto& display : the_display_platforms()) + { + display_interfaces.push_back(mg::DisplayPlatform::interface_for(display)); + } + if (the_options()->is_set(options::platform_rendering_libs)) { auto const manually_selected_platforms = @@ -265,6 +318,7 @@ auto mir::DefaultServerConfiguration::the_rendering_platforms() -> { auto supported_devices = graphics::probe_rendering_module( + display_interfaces, *platform, dynamic_cast(*the_options()), the_console_services()); @@ -272,7 +326,7 @@ auto mir::DefaultServerConfiguration::the_rendering_platforms() -> bool found_supported_device{false}; for (auto& device : supported_devices) { - if (device.support_level >= mg::PlatformPriority::supported) + if (device.support_level >= mg::probe::supported) { found_supported_device = true; } @@ -292,7 +346,9 @@ auto mir::DefaultServerConfiguration::the_rendering_platforms() -> } else { - platform_modules = mir::graphics::rendering_modules_for_device(platforms, dynamic_cast(*the_options()), the_console_services()); + platform_modules = mir::graphics::rendering_modules_for_device(platforms, display_interfaces, dynamic_cast(*the_options()), the_console_services()); + + hybrid_check(platform_modules); } for (auto const& [device, platform]: platform_modules) @@ -306,17 +362,32 @@ auto mir::DefaultServerConfiguration::the_rendering_platforms() -> MIR_SERVER_GRAPHICS_PLATFORM_VERSION); auto description = describe_module(); - mir::log_info("Selected rendering driver: %s (version %d.%d.%d)", - description->name, - description->major_version, - description->minor_version, - description->micro_version); + if (!device.device) + { + mir::log_info( + "Selected rendering driver: %s (version %d.%d.%d) for platform", + description->name, + description->major_version, + description->minor_version, + description->micro_version); + } + else + { + mir::log_info( + "Selected rendering driver: %s (version %d.%d.%d) for device (%s: %s)", + description->name, + description->major_version, + description->minor_version, + description->micro_version, + device.device->driver(), + device.device->devnode()); + } // TODO: Do we want to be able to continue on partial failure here? rendering_platforms.push_back( create_rendering_platform( device, - the_display_platforms(), + display_interfaces, *the_options(), *the_emergency_cleanup())); // Add this module to the list searched by the input stack later @@ -352,7 +423,7 @@ mir::DefaultServerConfiguration::the_buffer_allocator() [&]() -> std::shared_ptr { // TODO: More than one BufferAllocator - return the_rendering_platforms().front()->create_buffer_allocator(*the_display()); + return the_rendering_platforms().back()->create_buffer_allocator(*the_display()); }); } @@ -362,9 +433,18 @@ mir::DefaultServerConfiguration::the_display() return display( [this]() -> std::shared_ptr { - return the_display_platforms().front()->create_display( - the_display_configuration_policy(), - the_gl_config()); + std::vector> displays; + displays.reserve(the_display_platforms().size()); + for (auto const& platform : the_display_platforms()) + { + displays.push_back( + platform->create_display( + the_display_configuration_policy(), + the_gl_config())); + } + return std::make_shared( + std::move(displays), + *the_display_configuration_policy()); }); } diff --git a/src/server/graphics/default_display_configuration_policy.cpp b/src/server/graphics/default_display_configuration_policy.cpp index 19ca41c12a4..7f333a1f9db 100644 --- a/src/server/graphics/default_display_configuration_policy.cpp +++ b/src/server/graphics/default_display_configuration_policy.cpp @@ -88,6 +88,10 @@ void mg::CloneDisplayConfigurationPolicy::apply_to(DisplayConfiguration& conf) }); } +void mg::CloneDisplayConfigurationPolicy::confirm(mir::graphics::DisplayConfiguration const&) +{ +} + void mg::SideBySideDisplayConfigurationPolicy::apply_to(graphics::DisplayConfiguration& conf) { int max_x = 0; @@ -113,6 +117,9 @@ void mg::SideBySideDisplayConfigurationPolicy::apply_to(graphics::DisplayConfigu }); } +void mg::SideBySideDisplayConfigurationPolicy::confirm(mir::graphics::DisplayConfiguration const&) +{ +} void mg::SingleDisplayConfigurationPolicy::apply_to(graphics::DisplayConfiguration& conf) { @@ -137,3 +144,7 @@ void mg::SingleDisplayConfigurationPolicy::apply_to(graphics::DisplayConfigurati } }); } + +void mg::SingleDisplayConfigurationPolicy::confirm(mir::graphics::DisplayConfiguration const&) +{ +} \ No newline at end of file diff --git a/src/server/graphics/multiplexing_display.cpp b/src/server/graphics/multiplexing_display.cpp index 80510ea5edf..0981f5ccf83 100644 --- a/src/server/graphics/multiplexing_display.cpp +++ b/src/server/graphics/multiplexing_display.cpp @@ -33,6 +33,7 @@ mg::MultiplexingDisplay::MultiplexingDisplay( { auto conf = configuration(); initial_configuration_policy.apply_to(*conf); + initial_configuration_policy.confirm(*conf); configure(*conf); } @@ -316,9 +317,3 @@ auto mg::MultiplexingDisplay::create_hardware_cursor() -> std::shared_ptr std::unique_ptr -{ - // This will disappear in the New Platform API™; just return the first underlying Display's context for now. - return displays[0]->create_gl_context(); -} diff --git a/src/server/graphics/multiplexing_display.h b/src/server/graphics/multiplexing_display.h index 7fce7a9ef03..eb648f234b0 100644 --- a/src/server/graphics/multiplexing_display.h +++ b/src/server/graphics/multiplexing_display.h @@ -47,8 +47,6 @@ class MultiplexingDisplay : public Display void resume() override; auto create_hardware_cursor() -> std::shared_ptr override; - - auto create_gl_context() const -> std::unique_ptr override; private: std::vector> const displays; }; diff --git a/src/server/graphics/platform_probe.cpp b/src/server/graphics/platform_probe.cpp index d484147abee..8651f1994c8 100644 --- a/src/server/graphics/platform_probe.cpp +++ b/src/server/graphics/platform_probe.cpp @@ -16,6 +16,7 @@ #include "mir/log.h" #include "mir/graphics/platform.h" +#include "mir/shared_library.h" #include "platform_probe.h" #include @@ -25,11 +26,9 @@ namespace mg = mir::graphics; namespace { auto probe_module( - mir::graphics::PlatformProbe const& probe, - mir::SharedLibrary& module, - char const* platform_type_name, - mir::options::ProgramOption const& options, - std::shared_ptr const& console) -> std::vector + std::function()> const& probe, + mir::SharedLibrary const& module, + char const* platform_type_name) -> std::vector { auto describe = module.load_function( "describe_graphics_module", @@ -43,7 +42,7 @@ auto probe_module( desc->minor_version, desc->micro_version); - auto supported_devices = probe(console, std::make_shared(), options); + auto supported_devices = probe(); if (supported_devices.empty()) { mir::log_info("(Unsupported by system environment)"); @@ -75,33 +74,38 @@ auto probe_module( } auto mir::graphics::probe_display_module( - SharedLibrary& module, + SharedLibrary const& module, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector { return probe_module( - module.load_function( - "probe_display_platform", - MIR_SERVER_GRAPHICS_PLATFORM_VERSION), + [&console, &options, &module]() -> std::vector + { + auto probe = module.load_function( + "probe_display_platform", + MIR_SERVER_GRAPHICS_PLATFORM_VERSION); + return probe(console, std::make_shared(), options); + }, module, - "display", - options, - console); + "display"); } auto mir::graphics::probe_rendering_module( - SharedLibrary& module, + std::span> const& displays, + SharedLibrary const& module, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector { return probe_module( - module.load_function( - "probe_rendering_platform", - MIR_SERVER_GRAPHICS_PLATFORM_VERSION), + [&console, &options, &module, &displays]() -> std::vector + { + auto probe = module.load_function( + "probe_rendering_platform", + MIR_SERVER_GRAPHICS_PLATFORM_VERSION); + return probe(displays, *console, std::make_shared(), options); + }, module, - "rendering", - options, - console); + "rendering"); } namespace @@ -128,27 +132,16 @@ enum class ModuleType }; auto modules_for_device( - ModuleType type, - std::vector> const& modules, - mir::options::ProgramOption const& options, - std::shared_ptr const& console) --> std::vector>> + std::function(mir::SharedLibrary const&)> const& probe, + std::vector> const& modules) + -> std::vector>> { std::vector>> best_modules_so_far; for (auto& module : modules) { try { - std::vector supported_devices; - switch (type) - { - case ModuleType::Rendering: - supported_devices = mg::probe_rendering_module(*module, options, console); - break; - case ModuleType::Display: - supported_devices = mg::probe_display_module(*module, options, console); - break; - } + auto supported_devices = probe(*module); for (auto& device : supported_devices) { if (device.device) @@ -168,13 +161,13 @@ auto modules_for_device( *existing_device = std::make_pair(std::move(device), module); } } - else if (device.support_level > mg::PlatformPriority::unsupported) + else if (device.support_level > mg::probe::unsupported) { // Not-seen-before device, which this platform supports in some fashion best_modules_so_far.emplace_back(std::move(device), module); } } - else if (device.support_level > mg::PlatformPriority::unsupported) + else if (device.support_level > mg::probe::unsupported) { // Devices with null associated udev device are not combined with any others best_modules_so_far.emplace_back(std::move(device), module); @@ -199,7 +192,7 @@ auto modules_for_device( best_modules_so_far.end(), [](auto const& module) { - return module.first.support_level > mg::PlatformPriority::dummy; + return module.first.support_level > mg::probe::dummy; }); // …then, if there are any platforms before the start of the dummy platforms… @@ -219,20 +212,23 @@ auto mir::graphics::display_modules_for_device( std::shared_ptr const& console) -> std::vector>> { return modules_for_device( - ModuleType::Display, - modules, - options, - console); + [&options, &console](mir::SharedLibrary const& module) -> std::vector + { + return mg::probe_display_module(module, options, console); + }, + modules); } auto mir::graphics::rendering_modules_for_device( std::vector> const& modules, + std::span> const& displays, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector>> { return modules_for_device( - ModuleType::Rendering, - modules, - options, - console); + [&displays, &options, &console](SharedLibrary const& module) -> std::vector + { + return probe_rendering_module(displays, module, options, console); + }, + modules); } diff --git a/src/server/graphics/platform_probe.h b/src/server/graphics/platform_probe.h index 8a9aef20182..88b177fb623 100644 --- a/src/server/graphics/platform_probe.h +++ b/src/server/graphics/platform_probe.h @@ -31,12 +31,13 @@ class ConsoleServices; namespace graphics { auto probe_display_module( - SharedLibrary& module, + SharedLibrary const& module, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector; auto probe_rendering_module( - SharedLibrary& module, + std::span> const& displays, + SharedLibrary const& module, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector; @@ -48,6 +49,7 @@ auto display_modules_for_device( auto rendering_modules_for_device( std::vector> const& modules, + std::span> const& displays, options::ProgramOption const& options, std::shared_ptr const& console) -> std::vector>>; diff --git a/src/server/input/vt_filter.cpp b/src/server/input/vt_filter.cpp index b5112b112cc..ad969536d9d 100644 --- a/src/server/input/vt_filter.cpp +++ b/src/server/input/vt_filter.cpp @@ -26,6 +26,8 @@ mir::input::VTFilter::VTFilter(std::unique_ptr switcher) { } +mir::input::VTFilter::~VTFilter() = default; + bool mir::input::VTFilter::handle(MirEvent const& event) { if (mir_event_get_type(&event) != mir_event_type_input) diff --git a/src/server/server.cpp b/src/server/server.cpp index 1f0ff102a80..f3d1f6f99c6 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -24,7 +24,6 @@ #include "mir/input/composite_event_filter.h" #include "mir/input/event_filter.h" #include "mir/options/default_configuration.h" -#include "mir/renderer/gl/render_target.h" #include "mir/default_server_configuration.h" #include "mir/logging/logger.h" #include "mir/log.h" diff --git a/tests/acceptance-tests/platforms/CMakeLists.txt b/tests/acceptance-tests/platforms/CMakeLists.txt index c7cf1a9fc12..2e910a3f242 100644 --- a/tests/acceptance-tests/platforms/CMakeLists.txt +++ b/tests/acceptance-tests/platforms/CMakeLists.txt @@ -36,8 +36,20 @@ target_compile_definitions( PRIVATE MIR_SERVER_GRAPHICS_PLATFORM_VERSION="${MIR_SERVER_GRAPHICS_PLATFORM_VERSION}") +target_include_directories( + mirplatformtest + PRIVATE + ${PROJECT_SOURCE_DIR}/tests/include +) + target_link_libraries(mirplatformtest PUBLIC mirplatform mircommon + mir-test-doubles-static ) + +target_include_directories(mirplatformtest + PRIVATE + ${PROJECT_SOURCE_DIR}/tests/include/ +) \ No newline at end of file diff --git a/tests/acceptance-tests/platforms/test_rendering_platform.cpp b/tests/acceptance-tests/platforms/test_rendering_platform.cpp index 4948c1cf89e..93fcdc9268e 100644 --- a/tests/acceptance-tests/platforms/test_rendering_platform.cpp +++ b/tests/acceptance-tests/platforms/test_rendering_platform.cpp @@ -21,8 +21,14 @@ #include #include "mir/graphics/platform.h" +#include "mir/graphics/graphic_buffer_allocator.h" +#include "mir/options/program_option.h" +#include "mir/emergency_cleanup_registry.h" +#include "mir/udev/wrapper.h" +#include "mir/test/doubles/null_console_services.h" namespace mg = mir::graphics; +namespace mtd = mir::test::doubles; RenderingPlatformTest::RenderingPlatformTest() { @@ -36,11 +42,11 @@ RenderingPlatformTest::~RenderingPlatformTest() TEST_P(RenderingPlatformTest, has_render_platform_entrypoints) { - auto const module = GetParam()->platform_module(); + platform_module = GetParam()->platform_module(); try { - module->load_function( + platform_module->load_function( "describe_graphics_module", MIR_SERVER_GRAPHICS_PLATFORM_VERSION); } @@ -53,7 +59,7 @@ TEST_P(RenderingPlatformTest, has_render_platform_entrypoints) try { - module->load_function( + platform_module->load_function( "create_rendering_platform", MIR_SERVER_GRAPHICS_PLATFORM_VERSION); } @@ -66,7 +72,7 @@ TEST_P(RenderingPlatformTest, has_render_platform_entrypoints) try { - module->load_function( + platform_module->load_function( "probe_rendering_platform", MIR_SERVER_GRAPHICS_PLATFORM_VERSION); } @@ -78,3 +84,57 @@ TEST_P(RenderingPlatformTest, has_render_platform_entrypoints) } } +namespace +{ +class NullEmergencyCleanup : public mir::EmergencyCleanupRegistry +{ +public: + void add(mir::EmergencyCleanupHandler const&) override + { + } + + void add(mir::ModuleEmergencyCleanupHandler) override + { + } +}; +} + +/* + * DISABLED, as this requires more thought + */ +TEST_P(RenderingPlatformTest, DISABLED_supports_gl_rendering) +{ + using namespace testing; + + platform_module = GetParam()->platform_module(); + + auto const platform_loader = platform_module->load_function( + "create_rendering_platform", + MIR_SERVER_GRAPHICS_PLATFORM_VERSION); + auto const platform_probe = platform_module->load_function( + "probe_rendering_platform", + MIR_SERVER_GRAPHICS_PLATFORM_VERSION); + + mir::options::ProgramOption empty_options{}; + NullEmergencyCleanup emergency_cleanup{}; + auto const console_services = std::make_shared(); + + auto supported_devices = platform_probe( + console_services, + std::make_shared(), + empty_options); + + ASSERT_THAT(supported_devices, Not(IsEmpty())); + + for (auto const& device : supported_devices) + { + std::shared_ptr const platform = platform_loader( + device, + {}, // Hopefully the platform can handle not pre-linking with a DisplayPlatform + empty_options, + emergency_cleanup); + + auto const provider = platform->acquire_provider(nullptr); + EXPECT_THAT(provider, testing::NotNull()); + } +} diff --git a/tests/acceptance-tests/platforms/test_rendering_platform.h b/tests/acceptance-tests/platforms/test_rendering_platform.h index c4771f0ef06..80f628985a1 100644 --- a/tests/acceptance-tests/platforms/test_rendering_platform.h +++ b/tests/acceptance-tests/platforms/test_rendering_platform.h @@ -21,11 +21,19 @@ #include "platform_test_harness.h" +namespace mir +{ +class SharedLibrary; +} + class RenderingPlatformTest : public testing::TestWithParam { public: RenderingPlatformTest(); virtual ~RenderingPlatformTest() override; + +protected: + std::shared_ptr platform_module; }; #endif //MIR_TEST_RENDERING_PLATFORM_H diff --git a/tests/include/mir/test/as_render_target.h b/tests/include/mir/test/as_render_target.h deleted file mode 100644 index ad1d260d7bc..00000000000 --- a/tests/include/mir/test/as_render_target.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef MIR_TEST_AS_RENDER_TARGET_H_ -#define MIR_TEST_AS_RENDER_TARGET_H_ - -#include "mir/graphics/display_buffer.h" -#include "mir/renderer/gl/render_target.h" - -#include - -namespace mir -{ -namespace test -{ - -inline auto as_render_target(graphics::DisplayBuffer& display_buffer) -{ - auto const render_target = - dynamic_cast(display_buffer.native_display_buffer()); - if (!render_target) - throw std::logic_error{"DisplayBuffer doesn't support GL rendering"}; - return render_target; -} - -} -} - -#endif diff --git a/tests/include/mir/test/doubles/mock_display.h b/tests/include/mir/test/doubles/mock_display.h index 99fa95d1512..6b2ce95b05a 100644 --- a/tests/include/mir/test/doubles/mock_display.h +++ b/tests/include/mir/test/doubles/mock_display.h @@ -40,7 +40,6 @@ struct MockDisplay : public graphics::Display MOCK_METHOD(void, register_configuration_change_handler, (graphics::EventHandlerRegister&, graphics::DisplayConfigurationChangeHandler const&), (override)); MOCK_METHOD(void, pause, (), (override)); MOCK_METHOD(void, resume, (), (override)); - MOCK_METHOD(std::unique_ptr, create_gl_context, (), (const override)); MOCK_METHOD(std::shared_ptr, create_hardware_cursor, (), (override)); MOCK_METHOD(graphics::Frame, last_frame_on, (unsigned), (const override)); }; diff --git a/tests/include/mir/test/doubles/mock_display_buffer.h b/tests/include/mir/test/doubles/mock_display_buffer.h index 2702e830706..5803580fb9c 100644 --- a/tests/include/mir/test/doubles/mock_display_buffer.h +++ b/tests/include/mir/test/doubles/mock_display_buffer.h @@ -18,6 +18,7 @@ #define MIR_TEST_DOUBLES_MOCK_DISPLAY_BUFFER_H_ #include +#include #include @@ -28,8 +29,7 @@ namespace test namespace doubles { -class MockDisplayBuffer : public graphics::DisplayBuffer, - public graphics::NativeDisplayBuffer +class MockDisplayBuffer : public graphics::DisplayBuffer { public: MockDisplayBuffer() @@ -37,13 +37,12 @@ class MockDisplayBuffer : public graphics::DisplayBuffer, using namespace testing; ON_CALL(*this, view_area()) .WillByDefault(Return(geometry::Rectangle{{0,0},{0,0}})); - ON_CALL(*this, native_display_buffer()) - .WillByDefault(Return(this)); } - MOCK_CONST_METHOD0(view_area, geometry::Rectangle()); - MOCK_METHOD1(overlay, bool(graphics::RenderableList const&)); - MOCK_CONST_METHOD0(transformation, glm::mat2()); - MOCK_METHOD0(native_display_buffer, graphics::NativeDisplayBuffer*()); + MOCK_METHOD(geometry::Rectangle, view_area, (), (const override)); + MOCK_METHOD(bool, overlay, (std::vector const&), (override)); + MOCK_METHOD(void, set_next_image, (std::unique_ptr), (override)); + MOCK_METHOD(glm::mat2, transformation, (), (const override)); + MOCK_METHOD(std::shared_ptr, display_provider, (), (const override)); }; } diff --git a/tests/include/mir/test/doubles/mock_drm.h b/tests/include/mir/test/doubles/mock_drm.h index d3c44f30c20..6e41f9b2048 100644 --- a/tests/include/mir/test/doubles/mock_drm.h +++ b/tests/include/mir/test/doubles/mock_drm.h @@ -17,6 +17,7 @@ #ifndef MIR_TEST_DOUBLES_MOCK_DRM_H_ #define MIR_TEST_DOUBLES_MOCK_DRM_H_ +#include "mir_test_framework/mmap_wrapper.h" #include "mir_test_framework/open_wrapper.h" #include "mir/geometry/forward.h" @@ -162,6 +163,9 @@ class MockDRM MOCK_METHOD1(drmCheckModesettingSupported, int(char const*)); + MOCK_METHOD(void*, mmap, (void* addr, size_t length, int prot, int flags, int fd, off_t offset)); + MOCK_METHOD(int, munmap, (void* addr, size_t length)); + void add_crtc( char const* device, uint32_t id, @@ -194,8 +198,30 @@ class MockDRM private: std::unordered_map fake_drms; std::unordered_map fd_to_drm; + + class TransparentUPtrComparator + { + public: + auto operator()(std::unique_ptr const& a, void* b) const -> bool + { + return a.get() < b; + } + auto operator()(void*a, std::unique_ptr const& b) const -> bool + { + return a < b.get(); + } + auto operator()(std::unique_ptr const& a, std::unique_ptr const& b) const -> bool + { + return a.get() < b.get(); + } + struct is_transparent; + }; + + std::map, size_t, TransparentUPtrComparator> mmapings; drmModeObjectProperties empty_object_props; - mir_test_framework::OpenHandlerHandle open_interposer; + mir_test_framework::OpenHandlerHandle const open_interposer; + mir_test_framework::MmapHandlerHandle const mmap_interposer; + mir_test_framework::MunmapHandlerHandle const munmap_interposer; }; testing::Matcher IsFdOfDevice(char const* device); diff --git a/tests/include/mir/test/doubles/mock_gl_rendering_provider.h b/tests/include/mir/test/doubles/mock_gl_rendering_provider.h new file mode 100644 index 00000000000..31a859d030b --- /dev/null +++ b/tests/include/mir/test/doubles/mock_gl_rendering_provider.h @@ -0,0 +1,54 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_TEST_DOUBLES_MOCK_GL_RENDERING_PROVIDER_H_ +#define MIR_TEST_DOUBLES_MOCK_GL_RENDERING_PROVIDER_H_ + +#include + +#include "mir/graphics/gl_config.h" +#include "mir/graphics/platform.h" + +namespace mir::test::doubles +{ +class MockGlRenderingProvider : public graphics::GLRenderingProvider +{ +public: + MOCK_METHOD(std::shared_ptr, as_texture, (std::shared_ptr), (override)); + MOCK_METHOD( + std::unique_ptr, + surface_for_output, + (std::shared_ptr, geometry::Size, graphics::GLConfig const&), + (override)); + MOCK_METHOD( + graphics::probe::Result, + suitability_for_allocator, + (std::shared_ptr const&), + (override)); + MOCK_METHOD( + graphics::probe::Result, + suitability_for_display, + (std::shared_ptr const&), + (override)); + MOCK_METHOD( + std::unique_ptr, + make_framebuffer_provider, + (std::shared_ptr), + (override)); +}; +} + +#endif //MIR_TEST_DOUBLES_MOCK_GL_RENDERING_PROVIDER_H_ diff --git a/tests/include/mir/test/doubles/mock_output_surface.h b/tests/include/mir/test/doubles/mock_output_surface.h new file mode 100644 index 00000000000..88e3e5f88ea --- /dev/null +++ b/tests/include/mir/test/doubles/mock_output_surface.h @@ -0,0 +1,38 @@ +/* + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_TEST_DOUBLES_MOCK_OUTPUT_SURFACE_H_ +#define MIR_TEST_DOUBLES_MOCK_OUTPUT_SURFACE_H_ + +#include + +#include "mir/renderer/gl/gl_surface.h" + +namespace mir::test::doubles +{ +class MockOutputSurface : public mir::graphics::gl::OutputSurface +{ +public: + MOCK_METHOD(void, bind, (), (override)); + MOCK_METHOD(void, make_current, (), (override)); + MOCK_METHOD(void, release_current, (), (override)); + MOCK_METHOD(std::unique_ptr, commit, (), (override)); + MOCK_METHOD(mir::geometry::Size, size, (), (const override)); + MOCK_METHOD(Layout, layout, (), (const override)); +}; +} + +#endif //MIR_TEST_DOUBLES_MOCK_OUTPUT_SURFACE_H_ diff --git a/tests/include/mir/test/doubles/mock_renderer.h b/tests/include/mir/test/doubles/mock_renderer.h index 201ae907d31..8e34db92529 100644 --- a/tests/include/mir/test/doubles/mock_renderer.h +++ b/tests/include/mir/test/doubles/mock_renderer.h @@ -17,6 +17,7 @@ #define MIR_TEST_DOUBLES_MOCK_RENDERER_H_ #include "mir/renderer/renderer.h" +#include "mir/graphics/platform.h" #include @@ -29,10 +30,10 @@ namespace doubles struct MockRenderer : public renderer::Renderer { - MOCK_METHOD1(set_viewport, void(geometry::Rectangle const&)); - MOCK_METHOD1(set_output_transform, void(glm::mat2 const&)); - MOCK_CONST_METHOD1(render, void(graphics::RenderableList const&)); - MOCK_METHOD0(suspend, void()); + MOCK_METHOD(void, set_viewport, (geometry::Rectangle const&)); + MOCK_METHOD(void, set_output_transform, (glm::mat2 const&)); + MOCK_METHOD(std::unique_ptr, render, (graphics::RenderableList const&), (const override)); + MOCK_METHOD(void, suspend, ()); ~MockRenderer() noexcept {} }; diff --git a/tests/include/mir/test/doubles/null_display.h b/tests/include/mir/test/doubles/null_display.h index 116f4ee5711..ca5f49542b7 100644 --- a/tests/include/mir/test/doubles/null_display.h +++ b/tests/include/mir/test/doubles/null_display.h @@ -60,11 +60,6 @@ class NullDisplay : public graphics::Display { return {}; } - std::unique_ptr create_gl_context() const override - { - return std::unique_ptr{new NullGLContext()}; - } - NullDisplaySyncGroup group; }; diff --git a/tests/include/mir/test/doubles/null_display_buffer.h b/tests/include/mir/test/doubles/null_display_buffer.h index 39a4af171eb..ee61d69ccc2 100644 --- a/tests/include/mir/test/doubles/null_display_buffer.h +++ b/tests/include/mir/test/doubles/null_display_buffer.h @@ -26,13 +26,14 @@ namespace test namespace doubles { -class NullDisplayBuffer : public graphics::DisplayBuffer, public graphics::NativeDisplayBuffer +class NullDisplayBuffer : public graphics::DisplayBuffer { public: geometry::Rectangle view_area() const override { return geometry::Rectangle(); } - bool overlay(graphics::RenderableList const&) override { return false; } + bool overlay(std::vector const&) override { return false; } + void set_next_image(std::unique_ptr) override { } glm::mat2 transformation() const override { return glm::mat2(1); } - NativeDisplayBuffer* native_display_buffer() override { return this; } + auto display_provider() const -> std::shared_ptr override { return {};} }; } diff --git a/tests/include/mir/test/doubles/null_display_configuration_policy.h b/tests/include/mir/test/doubles/null_display_configuration_policy.h index 54907ffd1b8..d62f905791d 100644 --- a/tests/include/mir/test/doubles/null_display_configuration_policy.h +++ b/tests/include/mir/test/doubles/null_display_configuration_policy.h @@ -29,6 +29,7 @@ class NullDisplayConfigurationPolicy : public graphics::DisplayConfigurationPoli { public: void apply_to(graphics::DisplayConfiguration&) override {} + void confirm(graphics::DisplayConfiguration const&) override {} }; } } diff --git a/tests/include/mir/test/doubles/null_display_sync_group.h b/tests/include/mir/test/doubles/null_display_sync_group.h index a7072477bab..944d9319758 100644 --- a/tests/include/mir/test/doubles/null_display_sync_group.h +++ b/tests/include/mir/test/doubles/null_display_sync_group.h @@ -19,7 +19,7 @@ #include "mir/graphics/display.h" #include "mir/geometry/size.h" -#include "mir/test/doubles/stub_gl_display_buffer.h" +#include "mir/test/doubles/stub_display_buffer.h" #include namespace mir @@ -59,7 +59,7 @@ struct StubDisplaySyncGroup : graphics::DisplaySyncGroup private: std::vector const output_rects; - std::vector display_buffers; + std::vector display_buffers; }; struct NullDisplaySyncGroup : graphics::DisplaySyncGroup @@ -79,7 +79,7 @@ struct NullDisplaySyncGroup : graphics::DisplaySyncGroup return std::chrono::milliseconds::zero(); } - StubGLDisplayBuffer db{{{}, {1, 1}}}; + StubDisplayBuffer db{{{}, {1, 1}}}; }; } diff --git a/tests/include/mir/test/doubles/null_platform.h b/tests/include/mir/test/doubles/null_platform.h index b2e9c4d7697..919358ed1f2 100644 --- a/tests/include/mir/test/doubles/null_platform.h +++ b/tests/include/mir/test/doubles/null_platform.h @@ -36,6 +36,13 @@ class NullDisplayPlatform : public graphics::DisplayPlatform { return mir::make_module_ptr(); } + +protected: + auto interface_for() + -> std::shared_ptr override + { + return nullptr; + } }; class NullRenderingPlatform : public graphics::RenderingPlatform @@ -46,6 +53,13 @@ class NullRenderingPlatform : public graphics::RenderingPlatform { return nullptr; } + +protected: + auto maybe_create_provider( + graphics::RenderingProvider::Tag const&) -> std::shared_ptr override + { + return nullptr; + } }; } } diff --git a/tests/include/mir/test/doubles/stub_gl_rendering_provider.h b/tests/include/mir/test/doubles/stub_gl_rendering_provider.h new file mode 100644 index 00000000000..9950ab3803c --- /dev/null +++ b/tests/include/mir/test/doubles/stub_gl_rendering_provider.h @@ -0,0 +1,90 @@ +/* + * Copyright © Canonical Ltd. + * + * Copyright © Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef MIR_TEST_DOUBLES_STUB_GL_RENDERING_PROVIDER_H_ +#define MIR_TEST_DOUBLES_STUB_GL_RENDERING_PROVIDER_H_ + +#include "mir/graphics/platform.h" +#include "mir/test/doubles/mock_gl_buffer.h" +#include "mir/test/doubles/mock_output_surface.h" + +namespace mir::test::doubles +{ +class StubGlRenderingProvider : public graphics::GLRenderingProvider +{ +public: + auto as_texture(std::shared_ptr buffer) -> std::shared_ptr override + { + if (auto existing_buf = std::dynamic_pointer_cast(std::move(buffer))) + { + return existing_buf; + } + auto tex_buf = std::make_shared>( + geometry::Size{800, 500}, + geometry::Stride{-1}, + mir_pixel_format_argb_8888); + + ON_CALL(*tex_buf, shader(testing::_)) + .WillByDefault(testing::Invoke( + [](auto& factory) -> mir::graphics::gl::Program& + { + static int unused = 1; + return factory.compile_fragment_shader(&unused, "extension code", "fragment code"); + })); + + return tex_buf; + } + + auto surface_for_output( + std::shared_ptr, + geometry::Size, + graphics::GLConfig const&) -> std::unique_ptr override + { + return std::make_unique>(); + } + + auto suitability_for_allocator(std::shared_ptr const& /*target*/) + -> graphics::probe::Result override + { + return graphics::probe::supported; + } + + auto suitability_for_display(std::shared_ptr const& /*target*/) + -> graphics::probe::Result override + { + return graphics::probe::dummy; + } + + auto make_framebuffer_provider(std::shared_ptr /*target*/) + -> std::unique_ptr override + { + class NullFramebufferProvider : public FramebufferProvider + { + public: + auto buffer_to_framebuffer(std::shared_ptr) + -> std::unique_ptr override + { + return {}; + } + }; + return std::make_unique(); + } +}; +} + +#endif //MIR_TEST_DOUBLES_STUB_GL_RENDERING_PROVIDER_H_ diff --git a/tests/include/mir/test/doubles/stub_renderer.h b/tests/include/mir/test/doubles/stub_renderer.h index ad7d0d34f67..54ec64b7cb0 100644 --- a/tests/include/mir/test/doubles/stub_renderer.h +++ b/tests/include/mir/test/doubles/stub_renderer.h @@ -19,6 +19,7 @@ #include "mir/renderer/renderer.h" #include "mir/graphics/renderable.h" +#include "mir/graphics/buffer.h" #include namespace mir @@ -35,12 +36,14 @@ class StubRenderer : public renderer::Renderer void set_output_transform(glm::mat2 const&) override {} void suspend() override {} - void render(graphics::RenderableList const& renderables) const override + auto render(graphics::RenderableList const& renderables) const -> std::unique_ptr override { for (auto const& r : renderables) r->buffer(); // We need to consume a buffer to unblock client tests // Yield to reduce runtime under valgrind std::this_thread::yield(); + + return {}; } }; diff --git a/tests/integration-tests/test_surface_stack_with_compositor.cpp b/tests/integration-tests/test_surface_stack_with_compositor.cpp index fabee098056..9818bc3dbac 100644 --- a/tests/integration-tests/test_surface_stack_with_compositor.cpp +++ b/tests/integration-tests/test_surface_stack_with_compositor.cpp @@ -15,6 +15,7 @@ */ #include "mir/compositor/display_listener.h" +#include "mir/graphics/platform.h" #include "mir/renderer/renderer_factory.h" #include "src/server/report/null_report_factory.h" #include "src/server/scene/surface_stack.h" @@ -31,6 +32,11 @@ #include "mir/test/doubles/null_display_sync_group.h" #include "mir/test/doubles/mock_event_sink.h" #include "mir/test/doubles/stub_buffer_allocator.h" +#include "mir/test/doubles/mock_gl_buffer.h" +#include "mir/test/doubles/mock_output_surface.h" +#include "mir/test/doubles/stub_gl_rendering_provider.h" +#include "mir/test/doubles/null_gl_config.h" +#include "mir/test/doubles/stub_buffer_allocator.h" #include #include @@ -54,8 +60,9 @@ namespace class StubRendererFactory : public mir::renderer::RendererFactory { public: - std::unique_ptr - create_renderer_for(mir::renderer::gl::RenderTarget&) override + auto create_renderer_for( + std::unique_ptr, + std::shared_ptr) const -> std::unique_ptr override { return std::unique_ptr(new mtd::StubRenderer); } @@ -120,6 +127,27 @@ struct StubDisplayListener : mc::DisplayListener virtual void remove_display(geom::Rectangle const& /*area*/) override {} }; +class StubTexture : public testing::NiceMock +{ +public: + StubTexture() + { + ON_CALL(*this, shader(_)) + .WillByDefault( + Invoke( + [](auto& factory) -> mg::gl::Program& + { + static int yo; + return factory.compile_fragment_shader( + &yo, + "extension fragment", + "shader code"); + })); + ON_CALL(*this, layout) + .WillByDefault(Return(mg::gl::Texture::Layout::GL)); + } +}; + struct SurfaceStackCompositor : public Test { SurfaceStackCompositor() : @@ -155,8 +183,12 @@ struct SurfaceStackCompositor : public Test CountingDisplaySyncGroup stub_secondary_db; StubDisplay stub_display{stub_primary_db, stub_secondary_db}; StubDisplayListener stub_display_listener; + mc::DefaultDisplayBufferCompositorFactory dbc_factory{ + std::vector>{std::make_shared()}, + std::make_shared(), mt::fake_shared(renderer_factory), + std::make_shared(), null_comp_report}; }; diff --git a/tests/mir_test_doubles/CMakeLists.txt b/tests/mir_test_doubles/CMakeLists.txt index 41d4f828fd2..285c2f07181 100644 --- a/tests/mir_test_doubles/CMakeLists.txt +++ b/tests/mir_test_doubles/CMakeLists.txt @@ -27,6 +27,9 @@ set( ${PROJECT_SOURCE_DIR}/tests/include/mir/test/doubles/null_display_configuration_policy.h ${PROJECT_SOURCE_DIR}/tests/include/mir/test/doubles/stub_buffer_allocator.h stub_buffer_allocator.cpp + ${PROJECT_SOURCE_DIR}/tests/include/mir/test/doubles/stub_gl_rendering_provider.h + ${PROJECT_SOURCE_DIR}/tests/include/mir/test/doubles/mock_gl_rendering_provider.h + ${PROJECT_SOURCE_DIR}/tests/include/mir/test/doubles/mock_output_surface.h ) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -106,6 +109,12 @@ add_library(mir-test-doubles-static STATIC ${TEST_UTILS_SRCS} ) +target_include_directories( + mir-test-doubles-static + PUBLIC + ${PROJECT_SOURCE_DIR}/tests/include +) + add_dependencies(mir-test-doubles-static GMock) target_link_libraries(mir-test-doubles-static diff --git a/tests/mir_test_doubles/mock_drm.cpp b/tests/mir_test_doubles/mock_drm.cpp index bd51466cbac..a96b05d04fd 100644 --- a/tests/mir_test_doubles/mock_drm.cpp +++ b/tests/mir_test_doubles/mock_drm.cpp @@ -15,6 +15,7 @@ */ #include "mir/test/doubles/mock_drm.h" +#include "mir_test_framework/mmap_wrapper.h" #include "mir_test_framework/open_wrapper.h" #include "mir/geometry/size.h" #include @@ -241,6 +242,34 @@ mtd::MockDRM::MockDRM() return this->open(path, flags); } return std::nullopt; + })}, + mmap_interposer{mir_test_framework::add_mmap_handler( + [this](void* addr, size_t length, int prot, int flags, int fd, off_t offset) -> std::optional + { + // The dumb buffer API uses a call of mmap() on the DRM fd, so we want to handle that. + if (this->fd_to_drm.contains(fd)) + { + return this->mmap(addr, length, prot, flags, fd, offset); + } + return std::nullopt; + })}, + munmap_interposer{mir_test_framework::add_munmap_handler( + [this](void* addr, size_t length) -> std::optional + { + auto maybe_mapping = mmapings.find(addr); + if (maybe_mapping != mmapings.end()) + { + if (maybe_mapping->second != length) + { + /* It's technically possible to unmap only *part* of the mapping + * A more fancy mock would check whether addr + length overlaps + * any existing mappings and punch holes, but 🤷‍♀️ + */ + BOOST_THROW_EXCEPTION((std::runtime_error{"munmap mock does not support partial unmappings"})); + } + return this->munmap(addr, length); + } + return std::nullopt; })} { using namespace testing; @@ -358,6 +387,27 @@ mtd::MockDRM::MockDRM() .WillByDefault(Return(0)); ON_CALL(*this, drmCheckModesettingSupported(IsNull())) .WillByDefault(Return(-EINVAL)); + + ON_CALL(*this, mmap(_, _, _, _, _, _)) + .WillByDefault( + WithArg<1>(Invoke( + [this](auto size) -> void* + { + auto allocation = std::make_unique(size); + void* const address = allocation.get(); + mmapings.insert(std::make_pair(std::move(allocation), size)); + return address; + }))); + + ON_CALL(*this, munmap(_, _)) + .WillByDefault( + WithArg<0>(Invoke( + [this](void* addr) -> int + { + auto iter = mmapings.find(addr); + mmapings.erase(iter); + return 0; + }))); } mtd::MockDRM::~MockDRM() noexcept diff --git a/tests/mir_test_framework/headless_display_buffer_compositor_factory.cpp b/tests/mir_test_framework/headless_display_buffer_compositor_factory.cpp index ed46d7426e7..93c92f18967 100644 --- a/tests/mir_test_framework/headless_display_buffer_compositor_factory.cpp +++ b/tests/mir_test_framework/headless_display_buffer_compositor_factory.cpp @@ -16,9 +16,10 @@ #include "mir_test_framework/headless_display_buffer_compositor_factory.h" #include "mir_test_framework/passthrough_tracker.h" -#include "mir/renderer/gl/render_target.h" #include "mir/graphics/display_buffer.h" #include "mir/graphics/texture.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/gl/gl_surface.h" #include "mir/compositor/display_buffer_compositor.h" #include "mir/compositor/scene_element.h" #include "mir/graphics/buffer.h" @@ -26,18 +27,25 @@ namespace mtf = mir_test_framework; namespace mg = mir::graphics; -namespace mrg = mir::renderer::gl; namespace mc = mir::compositor; namespace geom = mir::geometry; -mtf::HeadlessDisplayBufferCompositorFactory::HeadlessDisplayBufferCompositorFactory() : - tracker(nullptr) +mtf::HeadlessDisplayBufferCompositorFactory::HeadlessDisplayBufferCompositorFactory( + std::shared_ptr render_platform, + std::shared_ptr gl_config) + : render_platform{std::move(render_platform)}, + gl_config{std::move(gl_config)}, + tracker(nullptr) { } mtf::HeadlessDisplayBufferCompositorFactory::HeadlessDisplayBufferCompositorFactory( - std::shared_ptr const& tracker) : - tracker(tracker) + std::shared_ptr render_platform, + std::shared_ptr gl_config, + std::shared_ptr const& tracker) + : render_platform{std::move(render_platform)}, + gl_config{std::move(gl_config)}, + tracker(tracker) { } @@ -48,11 +56,13 @@ mtf::HeadlessDisplayBufferCompositorFactory::create_compositor_for(mg::DisplayBu { HeadlessDBC( mg::DisplayBuffer& db, + std::unique_ptr output, + std::shared_ptr render_platform, std::shared_ptr const& tracker) : - db(db), - tracker(tracker), - render_target(dynamic_cast( - db.native_display_buffer())) + db{db}, + output{std::move(output)}, + render_platform{std::move(render_platform)}, + tracker(tracker) { } @@ -86,34 +96,30 @@ mtf::HeadlessDisplayBufferCompositorFactory::create_compositor_for(mg::DisplayBu void composite(mir::compositor::SceneElementSequence&& seq) override { auto renderlist = filter(seq, db.view_area()); - if (db.overlay(renderlist)) - { - if (tracker) - tracker->note_passthrough(); - return; - } - // Invoke GL renderer specific functions if the DisplayBuffer supports them - if (render_target) - render_target->make_current(); + // TODO: Might need to attempt to overlay in order to test those codepaths? + + output->bind(); // We need to consume a buffer to unblock client tests for (auto const& renderable : renderlist) { auto buf = renderable->buffer(); - if (auto tex = dynamic_cast(buf->native_buffer_base())) + if (auto tex = render_platform->as_texture(buf)) { // bind() is what drives the Wayland frame event. tex->bind(); } } - if (render_target) - render_target->swap_buffers(); + output->commit(); } mg::DisplayBuffer& db; + std::unique_ptr const output; + std::shared_ptr const render_platform; std::shared_ptr const tracker; - mrg::RenderTarget* const render_target; }; - return std::make_unique(db, tracker); + auto output_surface = + render_platform->surface_for_output(db.display_provider(), db.view_area().size, *gl_config); + return std::make_unique(db, std::move(output_surface), render_platform, tracker); } diff --git a/tests/mir_test_framework/headless_test.cpp b/tests/mir_test_framework/headless_test.cpp index 178d88291d6..5b80751c043 100644 --- a/tests/mir_test_framework/headless_test.cpp +++ b/tests/mir_test_framework/headless_test.cpp @@ -27,6 +27,7 @@ namespace geom = mir::geometry; namespace mtf = mir_test_framework; +namespace mg = mir::graphics; namespace { @@ -42,11 +43,19 @@ mtf::HeadlessTest::HeadlessTest() add_to_environment("MIR_SERVER_PLATFORM_INPUT_LIB", mtf::server_platform("input-stub.so").c_str()); add_to_environment("MIR_SERVER_ENABLE_KEY_REPEAT", "false"); add_to_environment("MIR_SERVER_CONSOLE_PROVIDER", "none"); - server.override_the_display_buffer_compositor_factory([] + + server.override_the_display_buffer_compositor_factory( + [this]() -> std::shared_ptr { - return std::make_shared(); + auto first_platform = server.the_rendering_platforms().front(); + if (auto gl_provider = mg::RenderingPlatform::acquire_provider(std::move(first_platform))) + { + return std::make_shared(std::move(gl_provider), server.the_gl_config()); + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Test RenderingPlatform does not support GL interface"})); }); + server.add_init_callback( [server = &server] { diff --git a/tests/mir_test_framework/mmap_wrapper.cpp b/tests/mir_test_framework/mmap_wrapper.cpp index ad3f8672fd6..1301ee2f6c2 100644 --- a/tests/mir_test_framework/mmap_wrapper.cpp +++ b/tests/mir_test_framework/mmap_wrapper.cpp @@ -31,7 +31,7 @@ namespace mtf = mir_test_framework; namespace { using MmapInterposer = mtf::InterposerHandlers; -using MunmapInterposer = mtf::InterposerHandlers; +using MunmapInterposer = mtf::InterposerHandlers; } mtf::MmapHandlerHandle mtf::add_mmap_handler(MmapHandler handler) @@ -92,4 +92,3 @@ int munmap(void *addr, size_t length) } return (*real_munmap)(addr, length); } - diff --git a/tests/mir_test_framework/platform_graphics_dummy.cpp b/tests/mir_test_framework/platform_graphics_dummy.cpp index cf034d28de4..e376d9aa0db 100644 --- a/tests/mir_test_framework/platform_graphics_dummy.cpp +++ b/tests/mir_test_framework/platform_graphics_dummy.cpp @@ -38,23 +38,24 @@ auto probe_display_platform( result.emplace_back( mg::SupportedDevice { nullptr, - mg::PlatformPriority::dummy, + mg::probe::dummy, nullptr }); return result; } auto probe_rendering_platform( - std::shared_ptr const&, + std::span> const&, + mir::ConsoleServices&, std::shared_ptr const&, mir::options::ProgramOption const&) -> std::vector { - mir::assert_entry_point_signature(&probe_rendering_platform); + mir::assert_entry_point_signature(&probe_rendering_platform); std::vector result; result.emplace_back( mg::SupportedDevice { nullptr, - mg::PlatformPriority::dummy, + mg::probe::dummy, nullptr }); return result; diff --git a/tests/mir_test_framework/platform_graphics_throw.cpp b/tests/mir_test_framework/platform_graphics_throw.cpp index db18b4ada50..c1af57c1d7a 100644 --- a/tests/mir_test_framework/platform_graphics_throw.cpp +++ b/tests/mir_test_framework/platform_graphics_throw.cpp @@ -53,13 +53,27 @@ class ExceptionThrowingPlatform : public mg::DisplayPlatform, public mg::Renderi mtd::NullEmergencyCleanup null_cleanup; mg::SupportedDevice device = { nullptr, - mg::PlatformPriority::unsupported, + mg::probe::unsupported, nullptr }; stub_render_platform = create_stub_render_platform(device, {}, mo::ProgramOption{}, null_cleanup); stub_display_platform = create_stub_display_platform(device, nullptr, nullptr, nullptr, nullptr); } +protected: + auto maybe_create_provider( + mir::graphics::RenderingProvider::Tag const&) -> std::shared_ptr override + { + return nullptr; + } + + auto interface_for() -> std::shared_ptr override + { + return mg::DisplayPlatform::interface_for(stub_display_platform); + } + +public: + mir::UniqueModulePtr create_buffer_allocator(mg::Display const& output) override { @@ -108,7 +122,7 @@ class ExceptionThrowingPlatform : public mg::DisplayPlatform, public mg::Renderi std::unordered_map> const should_throw; mir::UniqueModulePtr stub_render_platform; - mir::UniqueModulePtr stub_display_platform; + std::shared_ptr stub_display_platform; }; } @@ -123,23 +137,24 @@ auto probe_display_platform( result.emplace_back( mg::SupportedDevice { nullptr, - mg::PlatformPriority::unsupported, + mg::probe::unsupported, nullptr }); return result; } auto probe_rendering_platform( - std::shared_ptr const&, + std::span> const&, + mir::ConsoleServices&, std::shared_ptr const&, mir::options::ProgramOption const&) -> std::vector { - mir::assert_entry_point_signature(&probe_rendering_platform); + mir::assert_entry_point_signature(&probe_rendering_platform); std::vector result; result.emplace_back( mg::SupportedDevice { nullptr, - mg::PlatformPriority::unsupported, + mg::probe::unsupported, nullptr }); return result; @@ -166,7 +181,7 @@ void add_graphics_platform_options(boost::program_options::options_description&) mir::UniqueModulePtr create_rendering_platform( mg::SupportedDevice const&, - std::vector> const&, + std::vector> const&, mo::Option const&, mir::EmergencyCleanupRegistry&) { diff --git a/tests/mir_test_framework/stubbed_graphics_platform.cpp b/tests/mir_test_framework/stubbed_graphics_platform.cpp index 30f23f28eca..fbef382dad2 100644 --- a/tests/mir_test_framework/stubbed_graphics_platform.cpp +++ b/tests/mir_test_framework/stubbed_graphics_platform.cpp @@ -20,6 +20,7 @@ #include "mir_toolkit/common.h" #include "mir/test/doubles/stub_buffer_allocator.h" +#include "mir/test/doubles/stub_gl_rendering_provider.h" #include "mir/test/doubles/fake_display.h" #include "mir/assert_module_entry_point.h" @@ -88,6 +89,32 @@ mir::UniqueModulePtr mtf::StubGraphicPlatform::create_display( return mir::make_module_ptr(display_rects); } +auto mtf::StubGraphicPlatform::maybe_create_provider( + mir::graphics::RenderingProvider::Tag const& tag) + -> std::shared_ptr +{ + if (dynamic_cast(&tag)) + { + return std::make_shared(); + } + return nullptr; +} + +auto mtf::StubGraphicPlatform::interface_for() + -> std::shared_ptr +{ + class NullInterfaceProvider : public mg::DisplayInterfaceProvider + { + protected: + auto maybe_create_interface(mg::DisplayProvider::Tag const&) + -> std::shared_ptr + { + return nullptr; + } + }; + return std::make_shared(); +} + namespace { std::unique_ptr> chosen_display_rects; @@ -135,7 +162,7 @@ mir::UniqueModulePtr create_display_platform( mir::UniqueModulePtr create_rendering_platform( mg::SupportedDevice const&, - std::vector> const&, + std::vector> const&, mo::Option const&, mir::EmergencyCleanupRegistry&) { diff --git a/tests/mir_test_framework/stubbed_graphics_platform.h b/tests/mir_test_framework/stubbed_graphics_platform.h index 354f84724da..f045c176b33 100644 --- a/tests/mir_test_framework/stubbed_graphics_platform.h +++ b/tests/mir_test_framework/stubbed_graphics_platform.h @@ -35,6 +35,13 @@ class StubGraphicPlatform : std::shared_ptr const&, std::shared_ptr const&) override; +protected: + auto maybe_create_provider( + mir::graphics::RenderingProvider::Tag const& tag) + -> std::shared_ptr override; + + auto interface_for() + -> std::shared_ptr override; private: std::vector const display_rects; }; diff --git a/tests/mir_test_framework/stubbed_server_configuration.cpp b/tests/mir_test_framework/stubbed_server_configuration.cpp index ee8579c62b2..a4e9d2b1589 100644 --- a/tests/mir_test_framework/stubbed_server_configuration.cpp +++ b/tests/mir_test_framework/stubbed_server_configuration.cpp @@ -46,7 +46,10 @@ namespace class StubRendererFactory : public mir::renderer::RendererFactory { public: - std::unique_ptr create_renderer_for(mir::renderer::gl::RenderTarget&) + auto create_renderer_for( + std::unique_ptr, + std::shared_ptr /*platform*/) + const -> std::unique_ptr override { return std::unique_ptr(new mtd::StubRenderer()); } @@ -80,7 +83,7 @@ mtf::StubbedServerConfiguration::StubbedServerConfiguration( mtf::StubbedServerConfiguration::~StubbedServerConfiguration() = default; auto mtf::StubbedServerConfiguration::the_display_platforms() --> std::vector> const& + -> std::vector> const& { if (display_platform.empty()) { diff --git a/tests/mir_test_framework/test_display_server.cpp b/tests/mir_test_framework/test_display_server.cpp index eb21f371621..55f61d58234 100644 --- a/tests/mir_test_framework/test_display_server.cpp +++ b/tests/mir_test_framework/test_display_server.cpp @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +#include "src/server/compositor/default_display_buffer_compositor_factory.h" #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include @@ -39,6 +41,7 @@ namespace msh = mir::shell; namespace ml = mir::logging; namespace mo = mir::options; namespace mtd = mir::test::doubles; +namespace mg = mir::graphics; namespace { @@ -101,11 +104,24 @@ void miral::TestDisplayServer::start_server() }); }); - server.override_the_display_buffer_compositor_factory([] + + server.override_the_display_buffer_compositor_factory( + [&server]() -> std::shared_ptr { - return std::make_shared(); + auto first_rendering_platform = server.the_rendering_platforms().front(); + auto gl_provider = + mg::RenderingPlatform::acquire_provider( + std::move(first_rendering_platform)); + if (gl_provider) + { + return std::make_shared( + std::move(gl_provider), + server.the_gl_config()); + } + BOOST_THROW_EXCEPTION((std::runtime_error{"Platform does not support GL interface"})); }); + server.override_the_logger([&]() { std::shared_ptr result{}; diff --git a/tests/performance-tests/test_glmark2-es2.cpp b/tests/performance-tests/test_glmark2-es2.cpp index 74bfea04d0d..9004bd25277 100644 --- a/tests/performance-tests/test_glmark2-es2.cpp +++ b/tests/performance-tests/test_glmark2-es2.cpp @@ -205,6 +205,7 @@ struct HostedGLMark2Wayland : GLMark2Wayland EXPECT_TRUE(spin_wait_for_condition_or_timeout(wait_for_host, 10s)); add_to_environment("MIR_SERVER_WAYLAND_HOST", host_socket); + add_to_environment("MIR_SERVER_PLATFORM_DISPLAY_LIBS", "mir:wayland"); auto args = get_nested_args(); server.set_command_line(args.size(), args.data()); diff --git a/tests/platform_test_harness/graphics_platform_test_harness.cpp b/tests/platform_test_harness/graphics_platform_test_harness.cpp index 804df868873..7da4fe12bb2 100644 --- a/tests/platform_test_harness/graphics_platform_test_harness.cpp +++ b/tests/platform_test_harness/graphics_platform_test_harness.cpp @@ -26,6 +26,7 @@ #include "mir/renderer/renderer_factory.h" #include "mir/renderer/renderer.h" #include "mir/main_loop.h" +#include "mir/renderer/gl/gl_surface.h" #include "../../src/include/platform/mir/graphics/pixel_format_utils.h" #include "mir/default_server_configuration.h" @@ -37,7 +38,6 @@ #include #include #include -#include #include #include @@ -109,33 +109,33 @@ class MinimalServerEnvironment : private mir::DefaultServerConfiguration }; char const* MinimalServerEnvironment::argv[] = {"graphics_platform_test_harness", nullptr}; -std::string describe_probe_result(mg::PlatformPriority priority) +std::string describe_probe_result(mg::probe::Result priority) { - if (priority == mg::PlatformPriority::unsupported) + if (priority == mg::probe::unsupported) { return "UNSUPPORTED"; } - else if (priority == mg::PlatformPriority::dummy) + else if (priority == mg::probe::dummy) { return "DUMMY"; } - else if (priority < mg::PlatformPriority::supported) + else if (priority < mg::probe::supported) { - return std::string{"SUPPORTED - "} + std::to_string(mg::PlatformPriority::supported - priority); + return std::string{"SUPPORTED - "} + std::to_string(mg::probe::supported - priority); } - else if (priority == mg::PlatformPriority::supported) + else if (priority == mg::probe::supported) { return "SUPPORTED"; } - else if (priority < mg::PlatformPriority::best) + else if (priority < mg::probe::best) { - return std::string{"SUPPORTED + "} + std::to_string(priority - mg::PlatformPriority::supported); + return std::string{"SUPPORTED + "} + std::to_string(priority - mg::probe::supported); } - else if (priority == mg::PlatformPriority::best) + else if (priority == mg::probe::best) { return "BEST"; } - return std::string{"BEST + "} + std::to_string(priority - mg::PlatformPriority::best); + return std::string{"BEST + "} + std::to_string(priority - mg::probe::best); } auto test_probe(mir::SharedLibrary const& dso, MinimalServerEnvironment& config) -> std::vector @@ -173,7 +173,7 @@ auto test_platform_construction(mir::SharedLibrary const& dso, std::vector= mg::PlatformPriority::supported) + if (device.support_level >= mg::probe::supported) { auto display = create_display_platform( device, @@ -240,29 +240,6 @@ auto test_display_has_at_least_one_enabled_output(mg::Display& display) -> bool return output_count > 0; } -auto test_display_buffers_support_gl(mg::Display& display) -> bool -{ - bool all_support_gl{true}; - for_each_display_buffer( - display, - [&all_support_gl](mg::DisplayBuffer& db) - { - all_support_gl &= - dynamic_cast(db.native_display_buffer()) != nullptr; - }); - - if (all_support_gl) - { - std::cout << "DisplayBuffers support GL rendering" << std::endl; - } - else - { - std::cout << "DisplayBuffers do *not* support GL rendering" << std::endl; - } - - return all_support_gl; -} - auto dump_egl_config(mg::Display& display) -> bool { auto& context_source = dynamic_cast(display); @@ -279,7 +256,7 @@ auto dump_egl_config(mg::Display& display) -> bool return true; } - +/* auto hex_to_gl(unsigned char colour) -> GLclampf { return static_cast(colour) / 255; @@ -322,7 +299,7 @@ void basic_display_swapping(mg::Display& display) } }); } - +*/ std::shared_ptr alloc_and_fill_sw_buffer( mg::GraphicBufferAllocator& allocator, mir::geometry::Size size, @@ -391,8 +368,9 @@ auto bounce_within( [[maybe_unused]] void basic_software_buffer_drawing( mg::Display& display, + std::shared_ptr platform, mg::GraphicBufferAllocator& allocator, - mir::renderer::RendererFactory& factory) + mir::renderer::RendererFactory& /*factory*/) { uint32_t const transparent_orange = 0x002054e9; uint32_t const translucent_aubergine = 0xaa77216f; @@ -407,17 +385,19 @@ void basic_software_buffer_drawing( int min_height{std::numeric_limits::max()}, min_width{std::numeric_limits::max()}; for_each_display_buffer( display, - [&renderers, &factory, &min_height, &min_width](mg::DisplayBuffer& db) + [platform, /*&renderers, &factory,*/ &min_height, &min_width](mg::DisplayBuffer& db) { - auto const render_target = dynamic_cast(db.native_display_buffer()); - if (!render_target) + if (auto gl_provider = mg::RenderingPlatform::acquire_provider(platform)) + { +// auto output_surface = gl_provider->surface_for_output(db, ); +// renderers.push_back(factory.create_renderer_for(std::move(output_surface), gl_provider)); + min_height = std::min(min_height, db.view_area().bottom().as_int()); + min_width = std::min(min_width, db.view_area().right().as_int()); + } + else { - BOOST_THROW_EXCEPTION(std::logic_error("DisplayBuffer does not support GL rendering")); + BOOST_THROW_EXCEPTION((std::runtime_error{"Platform does not support GL"})); } - renderers.push_back(factory.create_renderer_for(*render_target)); - renderers.back()->set_viewport(db.view_area()); - min_height = std::min(min_height, db.view_area().bottom().as_int()); - min_width = std::min(min_width, db.view_area().right().as_int()); }); class DummyRenderable : public mg::Renderable @@ -564,8 +544,8 @@ int main(int argc, char const** argv) { success &= dump_egl_config(*display); success &= test_display_has_at_least_one_enabled_output(*display); - success &= test_display_buffers_support_gl(*display); - basic_display_swapping(*display); +// success &= test_display_buffers_support_gl(*display); +// basic_display_swapping(*display); // TODO: Update for display/render platform split // auto buffer_allocator = platform->create_buffer_allocator(*display); diff --git a/tests/unit-tests/compositor/test_basic_screen_shooter.cpp b/tests/unit-tests/compositor/test_basic_screen_shooter.cpp index f5d3b169970..0a521bb313a 100644 --- a/tests/unit-tests/compositor/test_basic_screen_shooter.cpp +++ b/tests/unit-tests/compositor/test_basic_screen_shooter.cpp @@ -14,12 +14,17 @@ * along with this program. If not, see . */ +#include "mir/executor.h" +#include "mir/graphics/platform.h" +#include "mir/renderer/gl/gl_surface.h" +#include "mir/test/doubles/stub_gl_rendering_provider.h" #include "src/server/compositor/basic_screen_shooter.h" -#include "mir/renderer/gl/buffer_render_target.h" -#include "mir/test/fake_shared.h" +#include "mir/renderer/renderer_factory.h" #include "mir/test/doubles/mock_scene.h" #include "mir/test/doubles/mock_renderer.h" +#include "mir/test/doubles/mock_gl_rendering_provider.h" +#include "mir/test/doubles/stub_gl_rendering_provider.h" #include "mir/test/doubles/advanceable_clock.h" #include "mir/test/doubles/explicit_executor.h" #include "mir/test/doubles/stub_buffer.h" @@ -30,11 +35,8 @@ namespace mc = mir::compositor; namespace mr = mir::renderer; -namespace mrg = mir::renderer::gl; -namespace mrs = mir::renderer::software; namespace mg = mir::graphics; namespace geom = mir::geometry; -namespace mt = mir::test; namespace mtd = mir::test::doubles; using namespace testing; @@ -42,26 +44,72 @@ using namespace std::chrono_literals; namespace { - -class MockBufferRenderTarget: public mrg::BufferRenderTarget +class MockRendererFactory : public mr::RendererFactory { public: - MOCK_METHOD(void, set_buffer, (std::shared_ptr const& buffer), (override)); - MOCK_METHOD(geom::Size, size, (), (const, override)); - MOCK_METHOD(void, make_current, (), (override)); - MOCK_METHOD(void, release_current, (), (override)); - MOCK_METHOD(void, swap_buffers, (), (override)); - MOCK_METHOD(void, bind, (), (override)); + MOCK_METHOD( + std::unique_ptr, + create_renderer_for, + (std::unique_ptr, std::shared_ptr), + (const override)); }; struct BasicScreenShooter : Test { BasicScreenShooter() { - ON_CALL(scene, scene_elements_for(_)).WillByDefault(Return(scene_elements)); + ON_CALL(*scene, scene_elements_for(_)).WillByDefault(Return(scene_elements)); + ON_CALL(*renderer_factory, create_renderer_for(_,_)).WillByDefault( + [this](auto output_surface, auto) + { + ON_CALL(*next_renderer, render(_)) + .WillByDefault( + [surface = std::shared_ptr(std::move(output_surface))]() + { + return surface->commit(); + }); + + return std::move(next_renderer); + }); + ON_CALL(*gl_provider, as_texture(_)) + .WillByDefault( + [this](auto buffer) + { + return default_gl_behaviour_provider.as_texture(std::move(buffer)); + }); + ON_CALL(*gl_provider, surface_for_output(_, _, _)) + .WillByDefault( + [](std::shared_ptr provider, auto size, auto const&) + -> std::unique_ptr + { + if (auto cpu_provider = provider->acquire_interface()) + { + auto surface = std::make_unique>(); + auto format = cpu_provider->supported_formats().front(); + ON_CALL(*surface, commit()) + .WillByDefault( + [cpu_provider, size, format]() + { + return cpu_provider->alloc_fb(size, format); + }); + return surface; + } + BOOST_THROW_EXCEPTION((std::runtime_error{"CPU output support not available?!"})); + }); + ON_CALL(*gl_provider, suitability_for_display(_)) + .WillByDefault(Return(mg::probe::supported)); + + shooter = std::make_unique( + scene, + clock, + executor, + gl_providers, + renderer_factory); } - NiceMock scene; + std::unique_ptr next_renderer{std::make_unique>()}; + + std::shared_ptr scene{std::make_shared>()}; mg::RenderableList renderables{[]() { mg::RenderableList renderables; @@ -80,17 +128,14 @@ struct BasicScreenShooter : Test } return elements; }()}; - mtd::MockRenderer& renderer{*new NiceMock}; - MockBufferRenderTarget& render_target{*new NiceMock}; - mtd::AdvanceableClock clock; + mtd::StubGlRenderingProvider default_gl_behaviour_provider; + std::shared_ptr gl_provider{std::make_shared>()}; + std::vector> gl_providers{gl_provider}; + std::shared_ptr renderer_factory{std::make_shared>()}; + std::shared_ptr clock{std::make_shared()}; mtd::ExplicitExecutor executor; - mc::BasicScreenShooter shooter{ - mt::fake_shared(scene), - mt::fake_shared(clock), - executor, - std::unique_ptr{&render_target}, - std::unique_ptr{&renderer}}; - mtd::StubBuffer buffer; + std::unique_ptr shooter; + std::shared_ptr buffer{std::make_shared(geom::Size{800, 600})}; geom::Rectangle const viewport_rect{{20, 30}, {40, 50}}; StrictMock)>> callback; std::optional const nullopt_time{}; @@ -100,65 +145,60 @@ struct BasicScreenShooter : Test TEST_F(BasicScreenShooter, calls_callback_from_executor) { - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); - clock.advance_by(1s); - EXPECT_CALL(callback, Call(std::make_optional(clock.now()))); + clock->advance_by(1s); + EXPECT_CALL(callback, Call(std::make_optional(clock->now()))); executor.execute(); } TEST_F(BasicScreenShooter, renders_scene_elements) { - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); InSequence seq; - EXPECT_CALL(scene, scene_elements_for(_)).WillOnce(Return(scene_elements)); + EXPECT_CALL(*scene, scene_elements_for(_)).WillOnce(Return(scene_elements)); EXPECT_THAT(renderables.size(), Gt(0)); - EXPECT_CALL(renderer, render(Eq(renderables))); - EXPECT_CALL(callback, Call(std::make_optional(clock.now()))); + EXPECT_CALL(*next_renderer, render(Eq(renderables))); + EXPECT_CALL(callback, Call(std::make_optional(clock->now()))); executor.execute(); } TEST_F(BasicScreenShooter, sets_viewport_correctly_before_render) { - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); Sequence a, b; - EXPECT_CALL(renderer, set_viewport(Eq(viewport_rect))).InSequence(b); - EXPECT_CALL(renderer, render(_)).InSequence(a, b); - EXPECT_CALL(callback, Call(std::make_optional(clock.now()))).InSequence(a, b); + EXPECT_CALL(*next_renderer, set_viewport(Eq(viewport_rect))).InSequence(b); + EXPECT_CALL(*next_renderer, render(_)).InSequence(a, b); + EXPECT_CALL(callback, Call(std::make_optional(clock->now()))).InSequence(a, b); executor.execute(); } -TEST_F(BasicScreenShooter, sets_buffer_before_render) +TEST_F(BasicScreenShooter, graceful_failure_on_zero_sized_buffer) { - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + auto broken_buffer = std::make_shared(geom::Size{0, 0}); + shooter->capture(broken_buffer, viewport_rect, [&](auto time) { callback.Call(time); }); - InSequence seq; - EXPECT_CALL(render_target, make_current()); - EXPECT_CALL(render_target, set_buffer(Eq(mt::fake_shared(buffer)))); - EXPECT_CALL(render_target, bind()); - EXPECT_CALL(renderer, render(_)); - EXPECT_CALL(render_target, release_current()); - EXPECT_CALL(callback, Call(std::make_optional(clock.now()))); + EXPECT_CALL(callback, Call(nullopt_time)); executor.execute(); } TEST_F(BasicScreenShooter, throw_in_scene_elements_for_causes_graceful_failure) { - ON_CALL(scene, scene_elements_for(_)).WillByDefault(Invoke([](auto) -> mc::SceneElementSequence + ON_CALL(*scene, scene_elements_for(_)).WillByDefault(Invoke([](auto) -> mc::SceneElementSequence { throw std::runtime_error{"throw in scene_elements_for()!"}; })); - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); @@ -166,13 +206,14 @@ TEST_F(BasicScreenShooter, throw_in_scene_elements_for_causes_graceful_failure) executor.execute(); } -TEST_F(BasicScreenShooter, throw_in_make_current_causes_graceful_failure) +TEST_F(BasicScreenShooter, throw_in_surface_for_output_handled_gracefully) { - ON_CALL(render_target, make_current()).WillByDefault(Invoke([]() + ON_CALL(*gl_provider, surface_for_output).WillByDefault( + [](auto, auto, auto&) -> std::unique_ptr { - throw std::runtime_error{"throw in make_current()!"}; - })); - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + BOOST_THROW_EXCEPTION((std::runtime_error{"Throw in surface_for_output"})); + }); + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); @@ -182,14 +223,67 @@ TEST_F(BasicScreenShooter, throw_in_make_current_causes_graceful_failure) TEST_F(BasicScreenShooter, throw_in_render_causes_graceful_failure) { - ON_CALL(renderer, render(_)).WillByDefault(Invoke([](auto) + EXPECT_CALL(*next_renderer, render(_)).WillOnce([](auto) -> std::unique_ptr { throw std::runtime_error{"throw in render()!"}; - })); - shooter.capture(mt::fake_shared(buffer), viewport_rect, [&](auto time) + }); + shooter->capture(buffer, viewport_rect, [&](auto time) { callback.Call(time); }); EXPECT_CALL(callback, Call(nullopt_time)); executor.execute(); } + +TEST_F(BasicScreenShooter, ensures_renderer_is_current_on_only_one_thread) +{ + thread_local bool is_current_here = false; + std::atomic is_current = false; + + auto shooter = std::make_unique( + scene, + clock, + mir::thread_pool_executor, + gl_providers, + renderer_factory); + + ON_CALL(*next_renderer, render(_)) + .WillByDefault( + [&](auto) -> std::unique_ptr + { + /* It's OK if we're being made current again on the same thread + * without having been released previously. + * We just need to ensure that, if we are current *anywhere* then + * it's *this* thread that we're current on. + */ + EXPECT_THAT(is_current_here, Eq(is_current.load())); + is_current = true; + is_current_here = true; + return {}; + }); + ON_CALL(*next_renderer, suspend()) + .WillByDefault( + [&]() + { + EXPECT_THAT(is_current_here, Eq(is_current.load())); + is_current = false; + is_current_here = false; + }); + + // This doesn't actually have to be atomic + std::atomic call_count = 0; + auto const spawn_a_capture = + [&]() + { + shooter->capture(buffer, viewport_rect, [&](auto) { call_count++; }); + }; + + auto const expected_call_count = 20; + for (auto i = 0; i < expected_call_count; ++i) + { + mir::thread_pool_executor.spawn(spawn_a_capture); + } + + mir::ThreadPoolExecutor::quiesce(); + EXPECT_THAT(call_count, Eq(expected_call_count)); +} diff --git a/tests/unit-tests/compositor/test_default_display_buffer_compositor.cpp b/tests/unit-tests/compositor/test_default_display_buffer_compositor.cpp index 2bfc5351b1c..5e937aa3517 100644 --- a/tests/unit-tests/compositor/test_default_display_buffer_compositor.cpp +++ b/tests/unit-tests/compositor/test_default_display_buffer_compositor.cpp @@ -25,6 +25,7 @@ #include "mir/test/doubles/fake_renderable.h" #include "mir/test/doubles/mock_compositor_report.h" #include "mir/test/doubles/stub_scene_element.h" +#include "mir/test/doubles/stub_gl_rendering_provider.h" #include #include @@ -95,6 +96,7 @@ struct DefaultDisplayBufferCompositor : public testing::Test testing::NiceMock mock_renderer; geom::Rectangle screen{{0, 0}, {1366, 768}}; testing::NiceMock display_buffer; + mtd::StubGlRenderingProvider gl_provider; std::shared_ptr small; std::shared_ptr big; std::shared_ptr fullscreen; @@ -111,6 +113,7 @@ TEST_F(DefaultDisplayBufferCompositor, render) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); compositor.composite(make_scene_elements({})); @@ -141,6 +144,7 @@ TEST_F(DefaultDisplayBufferCompositor, optimization_skips_composition) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), report); compositor.composite(make_scene_elements({})); @@ -169,6 +173,7 @@ TEST_F(DefaultDisplayBufferCompositor, rendering_reports_everything) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), report); compositor.composite(make_scene_elements({})); @@ -191,6 +196,7 @@ TEST_F(DefaultDisplayBufferCompositor, calls_renderer_in_sequence) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); @@ -228,6 +234,7 @@ TEST_F(DefaultDisplayBufferCompositor, rotates_viewport) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); @@ -278,6 +285,7 @@ TEST_F(DefaultDisplayBufferCompositor, optimization_toggles_seamlessly) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); @@ -309,6 +317,7 @@ TEST_F(DefaultDisplayBufferCompositor, occluded_surfaces_are_not_rendered) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); compositor.composite(make_scene_elements({ @@ -349,6 +358,7 @@ TEST_F(DefaultDisplayBufferCompositor, marks_rendered_scene_elements) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); @@ -372,6 +382,7 @@ TEST_F(DefaultDisplayBufferCompositor, marks_occluded_scene_elements) mc::DefaultDisplayBufferCompositor compositor( display_buffer, + gl_provider, mt::fake_shared(mock_renderer), mr::null_compositor_report()); diff --git a/tests/unit-tests/graphics/test_multiplexing_display.cpp b/tests/unit-tests/graphics/test_multiplexing_display.cpp index a1c472c32d3..34321b7b32c 100644 --- a/tests/unit-tests/graphics/test_multiplexing_display.cpp +++ b/tests/unit-tests/graphics/test_multiplexing_display.cpp @@ -123,12 +123,6 @@ auto make_safe_mock_display() -> std::unique_ptr { return std::make_unique(); }); - ON_CALL(*display, create_gl_context()) - .WillByDefault( - []() - { - return std::make_unique(); - }); return display; } } @@ -685,6 +679,10 @@ TEST(MultiplexingDisplay, applies_initial_display_configuration) }); } + void confirm(mg::DisplayConfiguration const&) override + { + } + auto expected_location_for_display(mg::DisplayConfigurationOutput const& display) -> geom::Point { auto it = std::find_if( diff --git a/tests/unit-tests/graphics/test_platform_prober.cpp b/tests/unit-tests/graphics/test_platform_prober.cpp index 9ff44a65506..2d08f51cc49 100644 --- a/tests/unit-tests/graphics/test_platform_prober.cpp +++ b/tests/unit-tests/graphics/test_platform_prober.cpp @@ -216,7 +216,7 @@ TEST_F(ServerPlatformProbeMockDRM, DoesNotLoadDummyPlatformWhenBetterPlatformExi EXPECT_THAT(selection_result, Not(IsEmpty())); for (auto& [device, module] : selection_result) { - EXPECT_THAT(device.support_level, Gt(mir::graphics::PlatformPriority::dummy)); + EXPECT_THAT(device.support_level, Gt(mir::graphics::probe::dummy)); } } #endif diff --git a/tests/unit-tests/graphics/test_shm_buffer.cpp b/tests/unit-tests/graphics/test_shm_buffer.cpp index daa7001c986..ff46e573f32 100644 --- a/tests/unit-tests/graphics/test_shm_buffer.cpp +++ b/tests/unit-tests/graphics/test_shm_buffer.cpp @@ -55,6 +55,16 @@ class DumbGLContext : public mir::renderer::gl::Context eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } + auto make_share_context() const -> std::unique_ptr override + { + return std::make_unique(ctx); + } + + explicit operator EGLContext() override + { + return ctx; + } + private: EGLDisplay const dpy{reinterpret_cast(0xdeebbeed)}; EGLContext const ctx; diff --git a/tests/unit-tests/platforms/eglstream-kms/CMakeLists.txt b/tests/unit-tests/platforms/eglstream-kms/CMakeLists.txt index 1516e4b6797..c1073395a58 100644 --- a/tests/unit-tests/platforms/eglstream-kms/CMakeLists.txt +++ b/tests/unit-tests/platforms/eglstream-kms/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory("server") -add_subdirectory("client") mir_add_wrapped_executable(mir_unit_tests_eglstream-kms NOINSTALL ${EGLSTREAM_KMS_UNIT_TEST_SOURCES} diff --git a/tests/unit-tests/platforms/gbm-kms/kms/CMakeLists.txt b/tests/unit-tests/platforms/gbm-kms/kms/CMakeLists.txt index 3d03cf55049..b1bef7f8ab7 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/CMakeLists.txt +++ b/tests/unit-tests/platforms/gbm-kms/kms/CMakeLists.txt @@ -1,7 +1,5 @@ mir_add_wrapped_executable(mir_unit_tests_gbm-kms NOINSTALL ${CMAKE_CURRENT_SOURCE_DIR}/test_platform.cpp -# ${CMAKE_CURRENT_SOURCE_DIR}/test_graphics_platform.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_display.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_display_generic.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_display_buffer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_display_multi_monitor.cpp @@ -40,9 +38,8 @@ target_link_libraries( mir-test-doubles-platform-static mirsharedgbmservercommon-static - server_platform_common - PkgConfig::DRM + PkgConfig::GBM ) if (MIR_RUN_UNIT_TESTS) diff --git a/tests/unit-tests/platforms/gbm-kms/kms/mock_kms_output.h b/tests/unit-tests/platforms/gbm-kms/kms/mock_kms_output.h index 020b4b56573..d457d465b36 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/mock_kms_output.h +++ b/tests/unit-tests/platforms/gbm-kms/kms/mock_kms_output.h @@ -39,6 +39,7 @@ struct MockKMSOutput : public graphics::gbm::KMSOutput return set_crtc_thunk(&fb); } + MOCK_METHOD0(has_crtc_mismatch, bool()); MOCK_METHOD1(set_crtc_thunk, bool(graphics::gbm::FBHandle const*)); MOCK_METHOD0(clear_crtc, void()); diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_buffer_allocator.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_buffer_allocator.cpp new file mode 100644 index 00000000000..6782e5935d9 --- /dev/null +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_buffer_allocator.cpp @@ -0,0 +1,137 @@ +/* + * Copyright © 2012 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 or 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "mir/test/doubles/null_emergency_cleanup.h" +#include "src/server/report/null_report_factory.h" +#include "mir/test/doubles/stub_console_services.h" +#include "src/platforms/gbm-kms/server/kms/platform.h" +#include "src/platforms/gbm-kms/server/kms/quirks.h" +#include "src/platforms/gbm-kms/server/buffer_allocator.h" +#include "mir/graphics/buffer_properties.h" +#include "mir/graphics/display.h" +#include "mir/options/program_option.h" +#include "mir/renderer/gl/context.h" + +#include "mir/test/doubles/mock_drm.h" +#include "mir/test/doubles/mock_gbm.h" +#include "mir/test/doubles/mock_egl.h" +#include "mir/test/doubles/mock_gl.h" +#include "mir/test/doubles/null_gl_config.h" +#include "mir/test/doubles/null_display_configuration_policy.h" +#include "mir_test_framework/udev_environment.h" + +#include +#include +#include +#include + +#include + +namespace mg = mir::graphics; +namespace mgg = mir::graphics::gbm; +namespace geom = mir::geometry; +namespace mtd = mir::test::doubles; +namespace mtf = mir_test_framework; +namespace mo = mir::options; + +class MesaBufferAllocatorTest : public ::testing::Test +{ +protected: + virtual void SetUp() + { + using namespace testing; + + fake_devices.add_standard_device("standard-drm-devices"); + + size = geom::Size{300, 200}; + pf = mir_pixel_format_argb_8888; + + ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_)) + .WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), + SetArgPointee<4>(1), + Return(EGL_TRUE))); + + ON_CALL(mock_egl, eglGetConfigAttrib(_, mock_egl.fake_configs[0], EGL_NATIVE_VISUAL_ID, _)) + .WillByDefault( + DoAll( + SetArgPointee<3>(GBM_FORMAT_XRGB8888), + Return(EGL_TRUE))); + + // We sometimes want to copy the config of an existing context. + // Since our mocks don't check what config attribs we're asking for, + // we can just make something up. + ON_CALL(mock_egl, eglQueryContext(_,_,EGL_CONFIG_ID,_)) + .WillByDefault( + DoAll( + SetArgPointee<3>(0xabadbaab), + Return(EGL_TRUE))); + + mock_egl.provide_egl_extensions(); + mock_gl.provide_gles_extensions(); + + ON_CALL(mock_gbm, gbm_bo_get_handle(_)) + .WillByDefault(Return(mock_gbm.fake_gbm.bo_handle)); + + EGLDisplay dpy; + EGLContext ctx; + allocator = std::make_unique(dpy, ctx); + } + + // Defaults + geom::Size size; + MirPixelFormat pf; + + ::testing::NiceMock mock_drm; + ::testing::NiceMock mock_gbm; + ::testing::NiceMock mock_egl; + ::testing::NiceMock mock_gl; + std::shared_ptr platform; + std::unique_ptr display; + std::unique_ptr allocator; + mtf::UdevEnvironment fake_devices; +}; + +TEST_F(MesaBufferAllocatorTest, creates_software_buffer_without_utilizing_gbm) +{ + using namespace testing; + EXPECT_CALL(mock_gbm, gbm_bo_create(_,_,_,_,_)).Times(0); + allocator->alloc_software_buffer( { 1000, 1000}, mir_pixel_format_abgr_8888); +} + +TEST_F(MesaBufferAllocatorTest, supported_pixel_formats_contain_common_formats) +{ + auto supported_pixel_formats = allocator->supported_pixel_formats(); + + auto argb_8888_count = std::count(supported_pixel_formats.begin(), + supported_pixel_formats.end(), + mir_pixel_format_argb_8888); + + auto xrgb_8888_count = std::count(supported_pixel_formats.begin(), + supported_pixel_formats.end(), + mir_pixel_format_xrgb_8888); + + EXPECT_EQ(1, argb_8888_count); + EXPECT_EQ(1, xrgb_8888_count); +} + +TEST_F(MesaBufferAllocatorTest, supported_pixel_formats_have_sane_default_in_first_position) +{ + auto supported_pixel_formats = allocator->supported_pixel_formats(); + + ASSERT_FALSE(supported_pixel_formats.empty()); + EXPECT_EQ(mir_pixel_format_argb_8888, supported_pixel_formats[0]); +} + diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_display.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_display.cpp deleted file mode 100644 index c54b016fd95..00000000000 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_display.cpp +++ /dev/null @@ -1,956 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include -#include "src/platforms/gbm-kms/server/kms/platform.h" -#include "src/platforms/gbm-kms/server/kms/display.h" -#include "src/platforms/gbm-kms/server/kms/quirks.h" -#include "mir/console_services.h" -#include "src/server/report/logging/display_report.h" -#include "mir/logging/logger.h" -#include "mir/graphics/display_buffer.h" -#include "mir/graphics/default_display_configuration_policy.h" -#include "mir/renderer/gl/render_target.h" -#include "mir/time/steady_clock.h" -#include "mir/glib_main_loop.h" -#include "mir/fatal.h" -#include "mir/options/program_option.h" -#include "src/platforms/common/server/kms-utils/drm_mode_resources.h" - -#include "mir/test/doubles/mock_egl.h" -#include "mir/test/doubles/mock_gl.h" -#include "src/server/report/null_report_factory.h" -#include "mir/test/doubles/mock_display_report.h" -#include "mir/test/doubles/stub_console_services.h" -#include "mir/test/doubles/stub_gl_config.h" -#include "mir/test/doubles/mock_gl_config.h" -#include "mir/test/doubles/null_emergency_cleanup.h" -#include "mir/test/doubles/mock_event_handler_register.h" - -#include "mir/test/doubles/mock_drm.h" -#include "mir/test/doubles/mock_gbm.h" - -#include "mir_test_framework/udev_environment.h" -#include "mir/test/fake_shared.h" -#include "mir/test/auto_unblock_thread.h" -#include "mir/test/signal.h" -#include "mir/test/as_render_target.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace mg=mir::graphics; -namespace mgg=mir::graphics::gbm; -namespace ml=mir::logging; -namespace mrl=mir::report::logging; -namespace mtd=mir::test::doubles; -namespace mtf=mir_test_framework; -namespace mr=mir::report; -namespace mt=mir::test; - -namespace -{ -struct MockLogger : public ml::Logger -{ - MOCK_METHOD3(log, - void(ml::Severity, const std::string&, const std::string&)); - - ~MockLogger() noexcept(true) {} -}; - -class MesaDisplayTest : public ::testing::Test -{ -public: - MesaDisplayTest() : - mock_report{std::make_shared>()}, - null_report{mr::null_display_report()}, - drm_fd{open(drm_device, 0, 0)} - { - using namespace testing; - ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_)) - .WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), - SetArgPointee<4>(1), - Return(EGL_TRUE))); - - ON_CALL(mock_egl, eglGetConfigAttrib(_, mock_egl.fake_configs[0], EGL_NATIVE_VISUAL_ID, _)) - .WillByDefault( - DoAll( - SetArgPointee<3>(GBM_FORMAT_XRGB8888), - Return(EGL_TRUE))); - - mock_egl.provide_egl_extensions(); - mock_gl.provide_gles_extensions(); - /* - * Silence uninteresting calls called when cleaning up resources in - * the MockGBM destructor, and which are not handled by NiceMock<>. - */ - EXPECT_CALL(mock_gbm, gbm_bo_get_device(_)) - .Times(AtLeast(0)); - EXPECT_CALL(mock_gbm, gbm_device_get_fd(_)) - .Times(AtLeast(0)) - .WillRepeatedly(Return(drm_fd)); - - fake_devices.add_standard_device("standard-drm-devices"); - - // Our standard mock devices have 2 DRM devices; kill all the outputs on - // the second one, so we don't try to test hybrid (for now) - mock_drm.reset("/dev/dri/card1"); - } - - std::shared_ptr create_platform() - { - return std::make_shared( - mir::report::null_display_report(), - *std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mir::options::ProgramOption{})); - } - - std::shared_ptr create_display( - std::shared_ptr const& platform) - { - return std::make_shared( - platform->drm, - platform->gbm, - platform->bypass_option(), - std::make_shared(), - std::make_shared(), - null_report); - } - - void setup_post_update_expectations() - { - using namespace testing; - - EXPECT_CALL(mock_egl, eglSwapBuffers(mock_egl.fake_egl_display, - mock_egl.fake_egl_surface)) - .Times(Exactly(2)); - - EXPECT_CALL(mock_gbm, gbm_surface_lock_front_buffer(mock_gbm.fake_gbm.surface)) - .Times(Exactly(2)) - .WillOnce(Return(fake.bo1)) - .WillOnce(Return(fake.bo2)); - - EXPECT_CALL(mock_gbm, gbm_bo_get_handle(fake.bo1)) - .Times(Exactly(1)) - .WillOnce(Return(fake.bo_handle1)); - - EXPECT_CALL(mock_gbm, gbm_bo_get_handle(fake.bo2)) - .Times(Exactly(1)) - .WillOnce(Return(fake.bo_handle2)); - - EXPECT_CALL(mock_drm, drmModeAddFB2(drm_fd, - _, _, _, - Pointee(fake.bo_handle1.u32), - _, _, _, _)) - .Times(Exactly(1)) - .WillOnce(DoAll(SetArgPointee<7>(fake.fb_id1), Return(0))); - - EXPECT_CALL(mock_drm, drmModeAddFB2(drm_fd, - _, _, _, - Pointee(fake.bo_handle2.u32), - _, _, _, _)) - .Times(Exactly(1)) - .WillOnce(DoAll(SetArgPointee<7>(fake.fb_id2), Return(0))); - } - - uint32_t get_connected_connector_id() - { - mg::kms::DRMModeResources resources{drm_fd}; - - int connected_id = 0; - resources.for_each_connector( - [&connected_id](auto const& connector) - { - if (connector->connection == DRM_MODE_CONNECTED) - connected_id = connector->connector_id; - }); - - return connected_id; - } - - uint32_t get_connected_crtc_id() - { - auto connector_id = get_connected_connector_id(); - auto connector = mg::kms::get_connector(drm_fd, connector_id); - - if (connector) - { - auto encoder = mg::kms::get_encoder(drm_fd, connector->encoder_id); - if (encoder) - return encoder->crtc_id; - } - - return 0; - } - - struct FakeData { - FakeData() - : bo1{reinterpret_cast(0xabcd)}, - bo2{reinterpret_cast(0xabce)}, - fb_id1{66}, fb_id2{67}, crtc() - { - bo_handle1.u32 = 0x1234; - bo_handle2.u32 = 0x1235; - crtc.buffer_id = 88; - crtc.crtc_id = 565; - } - - gbm_bo* bo1; - gbm_bo* bo2; - uint32_t fb_id1; - uint32_t fb_id2; - gbm_bo_handle bo_handle1; - gbm_bo_handle bo_handle2; - drmModeCrtc crtc; - } fake; - - ::testing::NiceMock mock_egl; - ::testing::NiceMock mock_gl; - ::testing::NiceMock mock_drm; - ::testing::NiceMock mock_gbm; - std::shared_ptr> const mock_report; - std::shared_ptr const null_report; - mtf::UdevEnvironment fake_devices; - - char const* const drm_device = "/dev/dri/card0"; - int const drm_fd; -}; - -} - -TEST_F(MesaDisplayTest, create_display) -{ - using namespace testing; - - auto const connector_id = get_connected_connector_id(); - auto const crtc_id = get_connected_crtc_id(); - - /* To display a gbm-kms surface, the MesaDisplay should... */ - - /* Create a gbm-kms surface to use as the frame buffer */ - EXPECT_CALL(mock_gbm, gbm_surface_create(mock_gbm.fake_gbm.device,_,_,_,_)) - .Times(Exactly(1)); - - /* Create an EGL window surface backed by the gbm-kms surface */ - EXPECT_CALL(mock_egl, eglCreatePlatformWindowSurfaceEXT( - mock_egl.fake_egl_display, - mock_egl.fake_configs[0], - mock_gbm.fake_gbm.surface, _)) - .Times(Exactly(1)); - - /* Swap the EGL window surface to bring the back buffer to the front */ - EXPECT_CALL(mock_egl, eglSwapBuffers(mock_egl.fake_egl_display, - mock_egl.fake_egl_surface)) - .Times(Exactly(1)); - - /* Get the gbm_bo object corresponding to the front buffer */ - EXPECT_CALL(mock_gbm, gbm_surface_lock_front_buffer(mock_gbm.fake_gbm.surface)) - .Times(Exactly(1)) - .WillOnce(Return(fake.bo1)); - - /* Get the DRM buffer handle associated with the gbm_bo */ - EXPECT_CALL(mock_gbm, gbm_bo_get_handle(fake.bo1)) - .Times(Exactly(1)) - .WillOnce(Return(fake.bo_handle1)); - - /* Create a a DRM FB with the DRM buffer attached */ - EXPECT_CALL(mock_drm, drmModeAddFB2(drm_fd, - _, _, _, - Pointee(fake.bo_handle1.u32), - _, _, _, _)) - .Times(Exactly(1)) - .WillOnce(DoAll(SetArgPointee<7>(fake.fb_id1), Return(0))); - - /* Display the DRM FB (first expectation is for cleanup) */ - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, Ne(fake.fb_id1), - _, _, - Pointee(connector_id), - _, _)) - .Times(AtLeast(0)); - - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, fake.fb_id1, - _, _, - Pointee(connector_id), - _, _)) - .Times(Exactly(1)) - .WillOnce(Return(0)); - - auto display = create_display(create_platform()); -} - -TEST_F(MesaDisplayTest, reset_crtc_on_destruction) -{ - using namespace testing; - - auto const connector_id = get_connected_connector_id(); - auto const crtc_id = get_connected_crtc_id(); - uint32_t const fb_id{66}; - - /* Create DRM FBs */ - EXPECT_CALL(mock_drm, drmModeAddFB2(drm_fd, - _, _, _, _, _, _, _, _)) - .WillRepeatedly(DoAll(SetArgPointee<7>(fb_id), Return(0))); - - - { - InSequence s; - - /* crtc is set */ - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, fb_id, - _, _, - Pointee(connector_id), - _, _)) - .Times(AtLeast(1)); - - /* crtc is reset */ - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, Ne(fb_id), - _, _, - Pointee(connector_id), - _, _)) - .Times(1); - } - - auto display = create_display(create_platform()); -} - -TEST_F(MesaDisplayTest, create_display_drm_failure) -{ - using namespace testing; - - EXPECT_CALL(mock_drm, open(_,_)) - .Times(AtLeast(1)) - .WillRepeatedly( - DoAll( - InvokeWithoutArgs([]() { errno = ENODEV; }), - Return(-1))); - - EXPECT_THROW( - { - auto display = create_display(create_platform()); - }, std::runtime_error); -} - -TEST_F(MesaDisplayTest, create_display_kms_failure) -{ - using namespace testing; - - auto platform = create_platform(); - - Mock::VerifyAndClearExpectations(&mock_drm); - - EXPECT_CALL(mock_drm, drmModeGetResources(_)) - .Times(AtLeast(1)) - .WillRepeatedly(Return(nullptr)); - - EXPECT_CALL(mock_drm, drmModeFreeResources(_)) - .Times(Exactly(0)); - - EXPECT_THROW({ - auto display = create_display(platform); - }, std::runtime_error) << "Expected that c'tor of mgg::Display throws"; -} - -TEST_F(MesaDisplayTest, create_display_gbm_failure) -{ - using namespace testing; - - EXPECT_CALL(mock_gbm, gbm_create_device(_)) - .Times(Exactly(1)) - .WillOnce(Return(reinterpret_cast(0))); - - EXPECT_CALL(mock_gbm, gbm_device_destroy(_)) - .Times(Exactly(0)); - - EXPECT_THROW({ - auto platform = create_platform(); - }, std::runtime_error) << "Expected c'tor of Platform to throw an exception"; -} - -TEST_F(MesaDisplayTest, platform_fails_if_no_modesetting_drm_nodes) -{ - using namespace testing; - - ON_CALL(mock_drm, drmCheckModesettingSupported(_)).WillByDefault(Return(-ENOSYS)); - - EXPECT_THROW({ - auto platform = create_platform(); - }, std::system_error) << "Expected c'tor of Platform to throw an exception"; -} - -TEST_F(MesaDisplayTest, ignores_non_modesetting_nodes) -{ - using namespace testing; - - // The platform should open all DRM nodes. In particular, it should open the second one… - EXPECT_CALL(mock_drm, open(StrEq("/dev/dri/card0"), _)).Times(AtLeast(1)); - EXPECT_CALL(mock_drm, open(StrEq("/dev/dri/card1"), _)).Times(AtLeast(1)); - - // …mark the second DRM node as not supporting modesetting… - char const busid[] = "pci:00:01:02:03"; - ON_CALL(mock_drm, drmGetBusid(mtd::IsFdOfDevice("/dev/dri/card1"))) - .WillByDefault(Return(const_cast(busid))); - ON_CALL(mock_drm, drmFreeBusid(busid)) - .WillByDefault(Invoke([](auto){})); - ON_CALL(mock_drm, drmCheckModesettingSupported(busid)) - .WillByDefault(Return(-ENOSYS)); - - // …and ensure that if we query the modesetting API, we'll fail. - ON_CALL(mock_drm, drmModeGetResources(mtd::IsFdOfDevice("/dev/dri/card1"))) - .WillByDefault(SetErrnoAndReturn(EINVAL, nullptr)); - - EXPECT_NO_THROW({ - auto platform = create_platform(); - auto display = create_display(platform); - }); -} - -TEST_F(MesaDisplayTest, handles_first_card_not_supporting_modeset) -{ - using namespace testing; - - // The platform should open all DRM nodes. - EXPECT_CALL(mock_drm, open(StrEq("/dev/dri/card0"), _)).Times(AtLeast(1)); - EXPECT_CALL(mock_drm, open(StrEq("/dev/dri/card1"), _)).Times(AtLeast(1)); - - // First device is rendering only… - mock_drm.reset("/dev/dri/card0"); - EXPECT_CALL(mock_drm, drmModeGetResources(mtd::IsFdOfDevice("/dev/dri/card0"))) - .Times(AtLeast(1)) - .WillRepeatedly(SetErrnoAndReturn(EOPNOTSUPP, nullptr)); - EXPECT_CALL(mock_drm, drmModeGetResources(mtd::IsFdOfDevice("/dev/dri/card1"))) - .Times(AtLeast(1)); - - // …and set up some connectors on the second card. - std::vector modes; - modes.push_back( - mtd::FakeDRMResources::create_mode( - 1920, 1080, - 138500, 2080, 1111, - mtd::FakeDRMResources::PreferredMode)); - uint32_t const encoder_id{33}; - uint32_t const crtc_id{22}; - uint32_t const connector_id{1}; - std::vector possible_encoder_ids{encoder_id}; - - mock_drm.add_crtc("/dev/dri/card1", crtc_id, modes[0]); - mock_drm.add_encoder("/dev/dri/card1", encoder_id, crtc_id, 0x1); - mock_drm.add_connector( - "/dev/dri/card1", - connector_id, - DRM_MODE_CONNECTOR_HDMIA, - DRM_MODE_CONNECTED, - encoder_id, - modes, - possible_encoder_ids, - mir::geometry::Size{150, 100}); - mock_drm.prepare("/dev/dri/card1"); - - EXPECT_NO_THROW({ - auto platform = create_platform(); - auto display = create_display(platform); - }); -} - -namespace -{ - -ACTION_P(QueuePageFlipEvent, mock_drm) -{ - static_cast(mock_drm).generate_event_on("/dev/dri/card0"); -} - -ACTION_P(InvokePageFlipHandler, param) -{ - int const dont_care{0}; - char dummy; - - arg1->page_flip_handler(dont_care, dont_care, dont_care, dont_care, *param); - ASSERT_EQ(1, read(arg0, &dummy, 1)); -} - -} - -TEST_F(MesaDisplayTest, post_update) -{ - using namespace testing; - - auto const crtc_id = get_connected_crtc_id(); - void* user_data{nullptr}; - - setup_post_update_expectations(); - - { - InSequence s; - - /* Flip the new FB */ - EXPECT_CALL(mock_drm, drmModePageFlip(drm_fd, - crtc_id, - fake.fb_id2, - _, _)) - .Times(Exactly(1)) - .WillOnce(DoAll(QueuePageFlipEvent(std::ref(mock_drm)), - SaveArg<4>(&user_data), - Return(0))); - - /* Handle the flip event */ - EXPECT_CALL(mock_drm, drmHandleEvent(drm_fd, _)) - .Times(1) - .WillOnce(DoAll(InvokePageFlipHandler(&user_data), Return(0))); - - // The initially-visible buffer will be released when the pageflip completes, - // replacing it. - EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo1)) - .Times(Exactly(1)); - - /* Release scheduled_composite_frame (at destruction time) */ - EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo2)) - .Times(Exactly(1)); - } - - - auto display = create_display(create_platform()); - - display->for_each_display_sync_group([](mg::DisplaySyncGroup& group) { - group.for_each_display_buffer([](mg::DisplayBuffer& db) { - mt::as_render_target(db)->swap_buffers(); - }); - group.post(); - }); -} - -TEST_F(MesaDisplayTest, post_update_flip_failure) -{ - mir::FatalErrorStrategy on_error{mir::fatal_error_except}; - using namespace testing; - - auto const crtc_id = get_connected_crtc_id(); - - setup_post_update_expectations(); - - // clear_crtc happens at some stage. Not interesting. - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, 0, - _, _, _, _, _)) - .WillOnce(Return(0)); - - { - InSequence s; - - // DisplayBuffer construction paints an empty screen. - // That's probably less than ideal but we've always had it that way. - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, fake.fb_id1, - _, _, _, _, _)) - .WillOnce(Return(0)); - - // New FB flip failure - EXPECT_CALL(mock_drm, drmModePageFlip(drm_fd, - crtc_id, - fake.fb_id2, - _, _)) - .Times(Exactly(1)) - .WillOnce(Return(-1)); - - // Expect fallback to blitting - EXPECT_CALL(mock_drm, drmModeSetCrtc(drm_fd, - crtc_id, fake.fb_id2, - _, _, _, _, _)) - .WillOnce(Return(0)); - - // Release all buffer objects - EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo1)) - .Times(Exactly(1)); - - EXPECT_CALL(mock_gbm, gbm_surface_release_buffer(mock_gbm.fake_gbm.surface, fake.bo2)) - .Times(Exactly(1)); - } - - // drmModePageFlip is allowed to fail (e.g. on VirtualBox) - EXPECT_NO_THROW( - { - auto display = create_display(create_platform()); - display->for_each_display_sync_group([](mg::DisplaySyncGroup& group) { - group.for_each_display_buffer([](mg::DisplayBuffer& db) { - mt::as_render_target(db)->swap_buffers(); - }); - group.post(); - }); - }); -} - -TEST_F(MesaDisplayTest, successful_creation_of_display_reports_successful_setup_of_native_resources) -{ - using namespace ::testing; - - EXPECT_CALL( - *mock_report, - report_successful_setup_of_native_resources()).Times(Exactly(1)); - EXPECT_CALL( - *mock_report, - report_successful_egl_make_current_on_construction()).Times(Exactly(1)); - - EXPECT_CALL( - *mock_report, - report_successful_egl_buffer_swap_on_construction()).Times(Exactly(1)); - - EXPECT_CALL( - *mock_report, - report_successful_drm_mode_set_crtc_on_construction()).Times(Exactly(1)); - - EXPECT_CALL( - *mock_report, - report_successful_display_construction()).Times(Exactly(1)); - - EXPECT_CALL( - *mock_report, - report_egl_configuration(mock_egl.fake_egl_display,mock_egl.fake_configs[0])).Times(Exactly(1)); - - auto platform = create_platform(); - auto display = std::make_shared( - platform->drm, - platform->gbm, - platform->bypass_option(), - std::make_shared(), - std::make_shared(), - mock_report); -} - -TEST_F(MesaDisplayTest, outputs_correct_string_for_successful_setup_of_native_resources) -{ - using namespace ::testing; - - auto logger = std::make_shared(); - auto reporter = std::make_shared(logger); - - EXPECT_CALL( - *logger, - log(Eq(ml::Severity::informational), - StrEq("Successfully setup native resources."), - StrEq("graphics"))).Times(Exactly(1)); - - reporter->report_successful_setup_of_native_resources(); -} - -TEST_F(MesaDisplayTest, outputs_correct_string_for_successful_egl_make_current_on_construction) -{ - using namespace ::testing; - - auto logger = std::make_shared(); - auto reporter = std::make_shared(logger); - - EXPECT_CALL( - *logger, - log(Eq(ml::Severity::informational), - StrEq("Successfully made egl context current on construction."), - StrEq("graphics"))).Times(Exactly(1)); - - reporter->report_successful_egl_make_current_on_construction(); -} - -TEST_F(MesaDisplayTest, outputs_correct_string_for_successful_egl_buffer_swap_on_construction) -{ - using namespace ::testing; - - auto logger = std::make_shared(); - auto reporter = std::make_shared(logger); - - EXPECT_CALL( - *logger, - log(Eq(ml::Severity::informational), - StrEq("Successfully performed egl buffer swap on construction."), - StrEq("graphics"))).Times(Exactly(1)); - - reporter->report_successful_egl_buffer_swap_on_construction(); -} - -TEST_F(MesaDisplayTest, outputs_correct_string_for_successful_drm_mode_set_crtc_on_construction) -{ - using namespace ::testing; - - auto logger = std::make_shared(); - auto reporter = std::make_shared(logger); - - EXPECT_CALL( - *logger, - log(Eq(ml::Severity::informational), - StrEq("Successfully performed drm mode setup on construction."), - StrEq("graphics"))).Times(Exactly(1)); - - reporter->report_successful_drm_mode_set_crtc_on_construction(); -} - -TEST_F(MesaDisplayTest, constructor_throws_if_egl_khr_image_pixmap_not_supported) -{ - using namespace ::testing; - - const char* egl_exts = "EGL_KHR_image EGL_KHR_image_base"; - - EXPECT_CALL(mock_egl, eglQueryString(_,EGL_EXTENSIONS)) - .WillOnce(Return(egl_exts)); - - EXPECT_THROW( - { - auto display = create_display(create_platform()); - }, std::runtime_error); -} - -TEST_F(MesaDisplayTest, for_each_display_buffer_calls_callback) -{ - using namespace ::testing; - - auto display = create_display(create_platform()); - - int callback_count{0}; - - display->for_each_display_sync_group([&](mg::DisplaySyncGroup& group) { - group.for_each_display_buffer([&](mg::DisplayBuffer&) { - callback_count++; - }); - }); - - EXPECT_NE(0, callback_count); -} - -TEST_F(MesaDisplayTest, configuration_change_registers_video_devices_handler) -{ - using namespace testing; - - auto display = create_display(create_platform()); - mtd::MockEventHandlerRegister mock_register; - - EXPECT_CALL(mock_register, register_fd_handler_module_ptr(_,_,_)); - - display->register_configuration_change_handler(mock_register, []{}); -} - -TEST_F(MesaDisplayTest, drm_device_change_event_triggers_handler) -{ - using namespace testing; - using namespace std::chrono_literals; - - auto display = create_display(create_platform()); - - mir::GLibMainLoop ml{std::make_shared()}; - mir::test::Signal done; - - int const device_add_count{1}; - int const device_change_count{10}; - int const expected_call_count{device_add_count + device_change_count}; - std::atomic call_count{0}; - - display->register_configuration_change_handler( - ml, - [&call_count, &done]() - { - if (++call_count == expected_call_count) - { - done.raise(); - } - }); - - - int const owner{0}; - mt::Signal mainloop_started; - ml.enqueue(&owner, [&] { mainloop_started.raise(); }); - - mt::AutoUnblockThread mainLoopThread([&ml]{ml.stop();}, [&ml]{ml.run();}); - ASSERT_TRUE(mainloop_started.wait_for(10s)); - - auto const syspath = - fake_devices.add_device( - "drm", - "card2", - NULL, - {}, - { - "DEVTYPE", "drm_minor", - "MAJOR", "226", - "MINOR", "42" - }); - - for (int i = 0; i != device_change_count; ++i) - { - // sleeping between calls to fake_devices hides race conditions - std::this_thread::sleep_for(std::chrono::microseconds{500}); - fake_devices.emit_device_changed(syspath); - } - - done.wait_for(20s); - EXPECT_EQ(expected_call_count, call_count); -} - -TEST_F(MesaDisplayTest, respects_gl_config) -{ - using namespace testing; - - mtd::MockGLConfig mock_gl_config; - EGLint const depth_bits{24}; - EGLint const stencil_bits{8}; - - EXPECT_CALL(mock_gl_config, depth_buffer_bits()) - .Times(AtLeast(1)) - .WillRepeatedly(Return(depth_bits)); - EXPECT_CALL(mock_gl_config, stencil_buffer_bits()) - .Times(AtLeast(1)) - .WillRepeatedly(Return(stencil_bits)); - - // We create at least one rendering context, with the requested attributes… - EXPECT_CALL(mock_egl, - eglChooseConfig( - _, - AllOf(mtd::EGLConfigContainsAttrib(EGL_DEPTH_SIZE, depth_bits), - mtd::EGLConfigContainsAttrib(EGL_STENCIL_SIZE, stencil_bits)), - NotNull(),_,_)) - .Times(AtLeast(1)) - .WillRepeatedly(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), - SetArgPointee<4>(1), - Return(EGL_TRUE))); - //…we *also* create zero-or-more non-rendering contexts; we don't care what they ask for - EXPECT_CALL(mock_egl, - eglChooseConfig( - _, - Pointee(EGL_NONE), - NotNull(),_,_)) - .Times(AnyNumber()) - .WillRepeatedly(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), - SetArgPointee<4>(1), - Return(EGL_TRUE))); - /* We actually want the default behaviour here, but because we've made an - * EXPECT_CALL for eglChooseConfig GMock will ignore the ON_CALL behaviour - */ - EXPECT_CALL(mock_egl, eglChooseConfig(_,_,nullptr,_,_)) - .Times(AnyNumber()) - .WillRepeatedly( - DoAll( - SetArgPointee<4>(1), - Return(EGL_TRUE))); - - auto platform = create_platform(); - mgg::Display display{ - platform->drm, - platform->gbm, - platform->bypass_option(), - std::make_shared(), - mir::test::fake_shared(mock_gl_config), - null_report}; -} - -TEST_F(MesaDisplayTest, uses_xrgb8888_framebuffer_when_argb8888_is_not_supported_by_EGL) -{ - using namespace testing; - - EXPECT_CALL(mock_egl, eglGetConfigAttrib(_, mock_egl.fake_configs[0], EGL_NATIVE_VISUAL_ID, _)) - .WillRepeatedly( - DoAll( - SetArgPointee<3>(GBM_FORMAT_XRGB8888), - Return(EGL_TRUE))); - - InSequence s; - // Maybe we first try ARGB8888, then fallback… - EXPECT_CALL(mock_gbm, gbm_surface_create(_, _, _, GBM_FORMAT_ARGB8888, _)) - .Times(AtMost(1)); - // …but we end up creating an XRGB8888 surface. - EXPECT_CALL(mock_gbm, gbm_surface_create(_, _, _, GBM_FORMAT_XRGB8888, _)); - - auto platform = create_platform(); - mgg::Display display{ - platform->drm, - platform->gbm, - platform->bypass_option(), - std::make_shared(), - std::make_shared>(), - null_report}; -} - -TEST_F(MesaDisplayTest, uses_argb8888_framebuffer_when_xrgb8888_is_not_supported_by_EGL) -{ - using namespace testing; - - EXPECT_CALL(mock_egl, eglGetConfigAttrib(_, _, EGL_NATIVE_VISUAL_ID, _)) - .WillRepeatedly( - DoAll( - SetArgPointee<3>(GBM_FORMAT_ARGB8888), - Return(EGL_TRUE))); - - InSequence s; - // Maybe we first try XRGB8888, then fallback… - EXPECT_CALL(mock_gbm, gbm_surface_create(_, _, _, GBM_FORMAT_XRGB8888, _)) - .Times(AtMost(1)); - // …but we end up creating an ARGB8888 surface. - EXPECT_CALL(mock_gbm, gbm_surface_create(_, _, _, GBM_FORMAT_ARGB8888, _)); - - auto platform = create_platform(); - mgg::Display display{ - platform->drm, - platform->gbm, - platform->bypass_option(), - std::make_shared(), - std::make_shared>(), - null_report}; -} - -TEST_F(MesaDisplayTest, can_change_configuration_metadata_without_invalidating_display_buffers) -{ - using namespace testing; - - auto display = create_display(create_platform()); - - auto config = display->configuration(); - - std::vector initial_display_buffer_references; - - display->for_each_display_sync_group( - [&initial_display_buffer_references](auto& group) - { - group.for_each_display_buffer( - [&initial_display_buffer_references](mg::DisplayBuffer& db) - { - initial_display_buffer_references.push_back(&db); - }); - }); - - bool has_active_display{false}; - config->for_each_output( - [&has_active_display](mg::UserDisplayConfigurationOutput& output) - { - has_active_display |= output.used; - - output.form_factor = mir_form_factor_projector; - output.scale = 3.1415f; - output.subpixel_arrangement = mir_subpixel_arrangement_vertical_bgr; - output.orientation = mir_orientation_inverted; - }); - - EXPECT_TRUE(display->apply_if_configuration_preserves_display_buffers(*config)); - - glm::mat2 const rotate_inverted(-1, 0, - 0,-1); - for (auto display_buffer : initial_display_buffer_references) - { - EXPECT_THAT(display_buffer->transformation(), Eq(rotate_inverted)); - } -} diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_display_buffer.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_display_buffer.cpp index 8d677202f82..7b89dd281da 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_display_buffer.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_display_buffer.cpp @@ -13,6 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ +#include "mir/graphics/texture.h" #include "mir/test/doubles/null_emergency_cleanup.h" #include "src/server/report/null_report_factory.h" #include "src/platforms/gbm-kms/server/kms/platform.h" @@ -29,6 +30,7 @@ #include "mir/graphics/transformation.h" #include "mock_kms_output.h" +#include #include #include #include @@ -48,10 +50,25 @@ namespace class MockDMABufBuffer : public DMABufBuffer { public: - MOCK_CONST_METHOD0(drm_fourcc, uint32_t()); - MOCK_CONST_METHOD0(modifier, std::optional()); - MOCK_CONST_METHOD0(planes, std::vector const&()); - MOCK_CONST_METHOD0(size, geometry::Size()); + MOCK_METHOD(uint32_t, drm_fourcc, (), (const override)); + MOCK_METHOD(std::optional, modifier, (), (const override)); + MOCK_METHOD(std::vector const&, planes, (), (const override)); + MOCK_METHOD(geometry::Size, size, (), (const override)); + MOCK_METHOD(gl::Texture::Layout, layout, (), (const override)); + MOCK_METHOD(DRMFormat, format, (), (const override)); +}; + +class MockKMSFramebuffer : public FBHandle +{ +public: + MOCK_METHOD(uint32_t, operator_uint32_thunk, (), (const override)); + + operator uint32_t() const override + { + return operator_uint32_thunk(); + } + + MOCK_METHOD(mir::geometry::Size, size, (), (const override)); }; } @@ -68,7 +85,17 @@ class MesaDisplayBufferTest : public Test std::make_shared(display_area)} , fake_software_renderable{ std::make_shared(display_area)} - , bypassable_list{fake_bypassable_renderable} + , bypass_framebuffer{std::make_shared>()} + , bypassable_list{ + mir::graphics::DisplayElement{ + display_area, + { + {0, 0}, + {display_area.size.width.as_value(), display_area.size.height.as_value()} + }, + bypass_framebuffer} + } + , parent_platform{nullptr} { ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_)) .WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), @@ -88,6 +115,7 @@ class MesaDisplayBufferTest : public Test .WillByDefault(Return(456)); fake_devices.add_standard_device("standard-drm-devices"); + drm_fd = mir::Fd{::open("/dev/dri/card0", O_RDWR | O_CLOEXEC)}; mock_kms_output = std::make_shared>(); ON_CALL(*mock_kms_output, set_crtc_thunk(_)) @@ -124,22 +152,11 @@ class MesaDisplayBufferTest : public Test } protected: - GBMOutputSurface make_output_surface() - { - helpers::EGLHelper egl{gl_config}; - return GBMOutputSurface{ - mir::Fd{} , - GBMSurfaceUPtr{nullptr}, - static_cast(width), - static_cast(height), - std::move(egl) - }; - } - int const width{56}; int const height{78}; mir::geometry::Rectangle const display_area{{12,34}, {width,height}}; glm::mat2 const identity; + mir::Fd drm_fd; NiceMock mock_gbm; NiceMock mock_egl; NiceMock mock_gl; @@ -154,79 +171,41 @@ class MesaDisplayBufferTest : public Test UdevEnvironment fake_devices; std::shared_ptr mock_kms_output; StubGLConfig gl_config; - mir::graphics::RenderableList const bypassable_list; + std::shared_ptr const bypass_framebuffer; + std::vector const bypassable_list; + std::shared_ptr const parent_platform; }; TEST_F(MesaDisplayBufferTest, unrotated_view_area_is_untouched) { graphics::gbm::DisplayBuffer db( + parent_platform, + drm_fd, graphics::gbm::BypassOption::allowed, null_display_report(), {mock_kms_output}, - make_output_surface(), display_area, identity); EXPECT_EQ(display_area, db.view_area()); } -TEST_F(MesaDisplayBufferTest, bypass_buffer_is_held_for_full_frame) -{ - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - auto original_count = mock_bypassable_buffer.use_count(); - - EXPECT_TRUE(db.overlay(bypassable_list)); - EXPECT_EQ(original_count+1, mock_bypassable_buffer.use_count()); - - // Switch back to normal compositing - db.make_current(); - db.swap_buffers(); - db.post(); - - // Bypass buffer should no longer be held by db - EXPECT_EQ(original_count, mock_bypassable_buffer.use_count()); -} - -TEST_F(MesaDisplayBufferTest, predictive_bypass_is_throttled) -{ - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - for (int frame = 0; frame < 5; ++frame) - { - ASSERT_TRUE(db.overlay(bypassable_list)); - db.post(); - - // Cast to a simple int type so that test failures are readable - int milliseconds_per_frame = 1000 / mock_refresh_rate; - ASSERT_THAT(db.recommended_sleep().count(), - Ge(milliseconds_per_frame/2)); - } -} - TEST_F(MesaDisplayBufferTest, frames_requiring_gl_are_not_throttled) { - graphics::RenderableList non_bypassable_list{ - std::make_shared(geometry::Rectangle{{12, 34}, {1, 1}}) + std::vector const non_bypassable_list{ + { + {{12, 34}, {1, 1}}, + {{12, 34}, {1, 1}}, + nullptr + } }; graphics::gbm::DisplayBuffer db( + parent_platform, + drm_fd, graphics::gbm::BypassOption::allowed, null_display_report(), {mock_kms_output}, - make_output_surface(), display_area, identity); @@ -240,34 +219,14 @@ TEST_F(MesaDisplayBufferTest, frames_requiring_gl_are_not_throttled) } } -TEST_F(MesaDisplayBufferTest, bypass_buffer_only_referenced_once_by_db) -{ - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - auto original_count = mock_bypassable_buffer.use_count(); - - EXPECT_TRUE(db.overlay(bypassable_list)); - EXPECT_EQ(original_count+1, mock_bypassable_buffer.use_count()); - - db.post(); - - // Bypass buffer still held by DB only one ref above the original - EXPECT_EQ(original_count+1, mock_bypassable_buffer.use_count()); -} - TEST_F(MesaDisplayBufferTest, untransformed_with_bypassable_list_can_bypass) { graphics::gbm::DisplayBuffer db( + parent_platform, + drm_fd, graphics::gbm::BypassOption::allowed, null_display_report(), {mock_kms_output}, - make_output_surface(), display_area, identity); @@ -283,201 +242,19 @@ auto fake_shared_ptr(intptr_t value) -> std::shared_ptr } } -TEST_F(MesaDisplayBufferTest, failed_bypass_falls_back_gracefully) -{ // Regression test for LP: #1398296 - EXPECT_CALL(*mock_kms_output, fb_for(A())) - .WillOnce(Return(fake_shared_ptr(0xaabb))); // During the DisplayBuffer constructor - EXPECT_CALL(*mock_kms_output, fb_for(A())) - .WillOnce(Return(nullptr)) // Fail first bypass attempt - .WillOnce(Return(fake_shared_ptr(0xbbcc))); // Succeed second bypass attempt - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - EXPECT_FALSE(db.overlay(bypassable_list)); - // And then we recover. DRM finds enough resources to AddFB ... - EXPECT_TRUE(db.overlay(bypassable_list)); -} - -TEST_F(MesaDisplayBufferTest, skips_bypass_because_of_lagging_resize) -{ // Another regression test for LP: #1398296 - auto fullscreen = std::make_shared(display_area); - auto nonbypassable = std::make_shared>(); - ON_CALL(*nonbypassable, native_buffer_base()) - .WillByDefault(Return(&mock_dmabuf_buffer)); - ON_CALL(*nonbypassable, size()) - .WillByDefault(Return(mir::geometry::Size{12,34})); - - fullscreen->set_buffer(nonbypassable); - graphics::RenderableList list{fullscreen}; - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - EXPECT_FALSE(db.overlay(list)); -} - -TEST_F(MesaDisplayBufferTest, rotated_cannot_bypass) -{ - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - transformation(mir_orientation_right)); - - EXPECT_FALSE(db.overlay(bypassable_list)); -} - -TEST_F(MesaDisplayBufferTest, fullscreen_software_buffer_cannot_bypass) -{ - graphics::RenderableList const list{fake_software_renderable}; - - // Passes the bypass candidate test: - EXPECT_EQ(fake_software_renderable->buffer()->size(), display_area.size); - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - EXPECT_FALSE(db.overlay(list)); -} - -TEST_F(MesaDisplayBufferTest, fullscreen_software_buffer_not_used_as_gbm_bo) -{ // Also checks it doesn't crash (LP: #1493721) - graphics::RenderableList const list{fake_software_renderable}; - - // Passes the bypass candidate test: - EXPECT_EQ(fake_software_renderable->buffer()->size(), display_area.size); - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - // If you find yourself using gbm_ functions on a Shm buffer then you're - // asking for a crash (LP: #1493721) ... - EXPECT_CALL(mock_gbm, gbm_bo_get_user_data(_)).Times(0); - db.overlay(list); -} - TEST_F(MesaDisplayBufferTest, transformation_not_implemented_internally) { glm::mat2 const rotate_left = transformation(mir_orientation_left); graphics::gbm::DisplayBuffer db( + parent_platform, + drm_fd, graphics::gbm::BypassOption::allowed, null_display_report(), {mock_kms_output}, - make_output_surface(), display_area, rotate_left); EXPECT_EQ(rotate_left, db.transformation()); } -TEST_F(MesaDisplayBufferTest, clone_mode_first_flip_flips_but_no_wait) -{ - // Ensure clone mode can do multiple page flips in parallel without - // blocking on either (at least till the second post) - EXPECT_CALL(*mock_kms_output, schedule_page_flip_thunk(_)) - .Times(2); - EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) - .Times(0); - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output, mock_kms_output}, - make_output_surface(), - display_area, - identity); - - db.swap_buffers(); - db.post(); -} - -TEST_F(MesaDisplayBufferTest, single_mode_first_post_flips_with_wait) -{ - EXPECT_CALL(*mock_kms_output, schedule_page_flip_thunk(_)) - .Times(1); - EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) - .Times(1); - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - db.swap_buffers(); - db.post(); -} - -TEST_F(MesaDisplayBufferTest, clone_mode_waits_for_page_flip_on_second_flip) -{ - InSequence seq; - - EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) - .Times(0); - EXPECT_CALL(*mock_kms_output, schedule_page_flip_thunk(_)) - .Times(2); - EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) - .Times(2); - EXPECT_CALL(*mock_kms_output, schedule_page_flip_thunk(_)) - .Times(2); - EXPECT_CALL(*mock_kms_output, wait_for_page_flip()) - .Times(0); - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output, mock_kms_output}, - make_output_surface(), - display_area, - identity); - - db.swap_buffers(); - db.post(); - - db.swap_buffers(); - db.post(); -} - -TEST_F(MesaDisplayBufferTest, skips_bypass_because_of_incompatible_list) -{ - graphics::RenderableList list{ - std::make_shared(display_area), - std::make_shared(geometry::Rectangle{{12, 34}, {1, 1}}) - }; - - graphics::gbm::DisplayBuffer db( - graphics::gbm::BypassOption::allowed, - null_display_report(), - {mock_kms_output}, - make_output_surface(), - display_area, - identity); - - EXPECT_FALSE(db.overlay(list)); -} diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_display_configuration.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_display_configuration.cpp index eb3657fea24..52d24f43047 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_display_configuration.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_display_configuration.cpp @@ -124,12 +124,17 @@ class MesaDisplayConfigurationTest : public ::testing::Test std::shared_ptr create_platform() { + mir::udev::Context ctx; + // Caution: non-local state! + // This works because standard-drm-devices contains a udev device with 226:0 and devnode /dev/dri/card0 + auto device = ctx.char_device_from_devnum(makedev(226, 0)); + return std::make_shared( - mir::report::null_display_report(), - *std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mir::options::ProgramOption{})); + *device, + mir::report::null_display_report(), + *std::make_shared(), + *std::make_shared(), + mgg::BypassOption::allowed); } std::shared_ptr create_display( diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_display_generic.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_display_generic.cpp index 184a44c5a23..d611b7aac78 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_display_generic.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_display_generic.cpp @@ -83,12 +83,17 @@ class DisplayTestGeneric : public ::testing::Test std::shared_ptr create_display() { - auto const platform = std::make_shared( - mir::report::null_display_report(), - *std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mir::options::ProgramOption{})); + mir::udev::Context ctx; + // Caution: non-local state! + // This works because standard-drm-devices contains a udev device with 226:0 and devnode /dev/dri/card0 + auto device = ctx.char_device_from_devnum(makedev(226, 0)); + + auto platform = std::make_shared( + *device, + mir::report::null_display_report(), + *std::make_shared(), + *std::make_shared(), + mgg::BypassOption::allowed); return platform->create_display( std::make_shared(), std::make_shared()); diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_display_multi_monitor.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_display_multi_monitor.cpp index 0e08fb535f8..111a1dcc7fa 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_display_multi_monitor.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_display_multi_monitor.cpp @@ -17,6 +17,7 @@ #include "mir/graphics/display.h" #include "mir/graphics/display_buffer.h" #include "mir/graphics/display_configuration.h" +#include "mir/graphics/drm_formats.h" #include "mir/graphics/platform.h" #include "src/platforms/gbm-kms/server/kms/platform.h" @@ -36,6 +37,7 @@ #include "mir/test/doubles/mock_drm.h" #include "mir/test/doubles/mock_gbm.h" +#include #include #include @@ -74,6 +76,10 @@ class ClonedDisplayConfigurationPolicy : public mg::DisplayConfigurationPolicy } }); } + + void confirm(mg::DisplayConfiguration const&) + { + } }; class SideBySideDisplayConfigurationPolicy : public mg::DisplayConfigurationPolicy @@ -103,6 +109,10 @@ class SideBySideDisplayConfigurationPolicy : public mg::DisplayConfigurationPoli } }); } + + void confirm(mg::DisplayConfiguration const&) + { + } }; class MesaDisplayMultiMonitorTest : public ::testing::Test @@ -147,12 +157,17 @@ class MesaDisplayMultiMonitorTest : public ::testing::Test std::shared_ptr create_platform() { + mir::udev::Context ctx; + // Caution: non-local state! + // This works because standard-drm-devices contains a udev device with 226:0 and devnode /dev/dri/card0 + auto device = ctx.char_device_from_devnum(makedev(226, 0)); + return std::make_shared( - mir::report::null_display_report(), - *std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mir::options::ProgramOption{})); + *device, + mir::report::null_display_report(), + *std::make_shared(), + *std::make_shared(), + mgg::BypassOption::allowed); } std::shared_ptr create_display_cloned( @@ -301,39 +316,6 @@ TEST_F(MesaDisplayMultiMonitorTest, create_display_sets_all_connected_crtcs) auto display = create_display_cloned(create_platform()); } -TEST_F(MesaDisplayMultiMonitorTest, create_display_creates_shared_egl_contexts) -{ - using namespace testing; - - int const num_connected_outputs{3}; - int const num_disconnected_outputs{2}; - EGLContext const shared_context{reinterpret_cast(0x77)}; - - setup_outputs(num_connected_outputs, num_disconnected_outputs); - - /* Will create only one shared context */ - EXPECT_CALL(mock_egl, eglCreateContext(_, _, EGL_NO_CONTEXT, _)) - .WillOnce(Return(shared_context)); - - /* Will use the shared context when creating other contexts */ - EXPECT_CALL(mock_egl, eglCreateContext(_, _, shared_context, _)) - .Times(AtLeast(1)); - - { - InSequence s; - - /* Contexts are made current to initialize DisplayBuffers */ - EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,Ne(shared_context))) - .Times(AtLeast(1)); - - /* Contexts are released at teardown */ - EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,EGL_NO_CONTEXT)) - .Times(AtLeast(1)); - } - - auto display = create_display_cloned(create_platform()); -} - namespace { @@ -384,20 +366,36 @@ TEST_F(MesaDisplayMultiMonitorTest, flip_flips_all_connected_crtcs) .WillOnce(DoAll(InvokePageFlipHandler(&user_data[1]), Return(0))) .WillOnce(DoAll(InvokePageFlipHandler(&user_data[2]), Return(0))); - auto display = create_display_cloned(create_platform()); + auto platform = create_platform(); + auto display = create_display_cloned(platform); + auto provider = mg::DisplayPlatform::interface_for(std::move(platform))->acquire_interface(); /* First frame: Page flips are scheduled, but not waited for */ - display->for_each_display_sync_group([](mg::DisplaySyncGroup& group) - { - group.post(); - }); + display->for_each_display_sync_group( + [provider](mg::DisplaySyncGroup& group) + { + group.for_each_display_buffer( + [provider](mg::DisplayBuffer& db) + { + auto fb = provider->alloc_fb(db.view_area().size, mg::DRMFormat{DRM_FORMAT_ABGR8888}); + db.set_next_image(std::move(fb)); + }); + group.post(); + }); /* Second frame: Previous page flips finish (drmHandleEvent) and new ones are scheduled */ - display->for_each_display_sync_group([](mg::DisplaySyncGroup& group) - { - group.post(); - }); + display->for_each_display_sync_group( + [provider](mg::DisplaySyncGroup& group) + { + group.for_each_display_buffer( + [provider](mg::DisplayBuffer& db) + { + auto fb = provider->alloc_fb(db.view_area().size, mg::DRMFormat{DRM_FORMAT_ARGB8888}); + db.set_next_image(std::move(fb)); + }); + group.post(); + }); } namespace diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_graphics_platform.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_graphics_platform.cpp deleted file mode 100644 index 63646011cf7..00000000000 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_graphics_platform.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "mir/graphics/platform.h" -#include "mir/graphics/graphic_buffer_allocator.h" -#include "mir/graphics/buffer_properties.h" -#include "mir/test/doubles/mock_egl.h" -#include "mir/test/doubles/mock_gl.h" -#include "mir/test/doubles/mock_option.h" -#include "mir/test/doubles/null_emergency_cleanup.h" -#include "src/server/report/null_report_factory.h" -#include "mir/test/doubles/stub_console_services.h" -#include "mir/options/program_option.h" -#include "mir/test/doubles/mock_drm.h" -#include "mir/test/doubles/mock_gbm.h" -#include "mir_test_framework/udev_environment.h" -#include "src/platforms/gbm-kms/server/kms/platform.h" -#include "src/platforms/gbm-kms/server/kms/quirks.h" - -#include "mir/logging/dumb_console_logger.h" - -#include - -namespace mg = mir::graphics; -namespace mgg = mg::gbm; -namespace ml = mir::logging; -namespace geom = mir::geometry; -namespace mtd = mir::test::doubles; -namespace mo = mir::options; -namespace mtf = mir_test_framework; - -class GraphicsPlatform : public ::testing::Test -{ -public: - GraphicsPlatform() : logger(std::make_shared()) - { - using namespace testing; - - ON_CALL(mock_gbm, gbm_bo_get_width(_)) - .WillByDefault(Return(320)); - - ON_CALL(mock_gbm, gbm_bo_get_height(_)) - .WillByDefault(Return(240)); - - // FIXME: This format needs to match Mesa's first supported pixel - // format or tests will fail. The coupling is presently loose. - ON_CALL(mock_gbm, gbm_bo_get_format(_)) - .WillByDefault(Return(GBM_FORMAT_ARGB8888)); - - ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_)) - .WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), - SetArgPointee<4>(1), - Return(EGL_TRUE))); - - ON_CALL(mock_egl, eglGetConfigAttrib(_, mock_egl.fake_configs[0], EGL_NATIVE_VISUAL_ID, _)) - .WillByDefault( - DoAll( - SetArgPointee<3>(GBM_FORMAT_XRGB8888), - Return(EGL_TRUE))); - - mock_egl.provide_egl_extensions(); - mock_gl.provide_gles_extensions(); - - - fake_devices.add_standard_device("standard-drm-devices"); - } - - std::shared_ptr create_platform() - { - return std::make_shared( - mir::report::null_display_report(), - std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mtd::MockOption{})); - } - - std::shared_ptr logger; - - ::testing::NiceMock mock_egl; - ::testing::NiceMock mock_gl; - ::testing::NiceMock mock_drm; - ::testing::NiceMock mock_gbm; - mtf::UdevEnvironment fake_devices; -}; - -#include "../../test_graphics_platform.h" diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_platform.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_platform.cpp index c30671a1e10..a157b6c4628 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_platform.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_platform.cpp @@ -15,6 +15,7 @@ */ #include "mir/graphics/event_handler_register.h" +#include "platform_common.h" #include "src/platforms/gbm-kms/server/kms/platform.h" #include "src/platforms/gbm-kms/server/kms/quirks.h" #include "src/server/report/null_report_factory.h" @@ -76,16 +77,6 @@ class MesaGraphicsPlatform : public ::testing::Test fake_devices.add_standard_device("standard-drm-devices"); } - std::shared_ptr create_platform() - { - return std::make_shared( - mir::report::null_display_report(), - *std::make_shared(), - *std::make_shared(), - mgg::BypassOption::allowed, - std::make_unique(mir::options::ProgramOption{})); - } - auto parsed_options_from_args( std::initializer_list const& options, boost::program_options::options_description const& description) const @@ -114,12 +105,33 @@ TEST_F(MesaGraphicsPlatform, a_failure_while_creating_a_platform_results_in_an_e { using namespace ::testing; + mtf::UdevEnvironment udev_environment; + boost::program_options::options_description po; + mir::options::ProgramOption options; + auto const stub_vt = std::make_shared(); + auto const udev = std::make_shared(); + + udev_environment.add_standard_device("standard-drm-devices"); + + mir::SharedLibrary platform_lib{mtf::server_platform("graphics-gbm-kms")}; + auto probe = platform_lib.load_function(display_platform_probe_symbol); + auto supported_devices = probe(stub_vt, udev, options); + EXPECT_CALL(mock_drm, open(_,_)) - .WillRepeatedly(SetErrnoAndReturn(EINVAL, -1)); + .WillRepeatedly(SetErrnoAndReturn(EINVAL, -1)); try { - auto platform = create_platform(); + for (auto& device : supported_devices) + { + mgg::Platform{ + *device.device, + mir::report::null_display_report(), + *std::make_shared(), + *std::make_shared(), + mgg::BypassOption::allowed + }; + } } catch(std::exception const&) { return; @@ -155,7 +167,7 @@ TEST_F(MesaGraphicsPlatform, display_probe_returns_unsupported_when_no_drm_udev_ auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::unsupported))); + Each(SupportLevelIs(mg::probe::unsupported))); } TEST_F(MesaGraphicsPlatform, display_probe_returns_best_when_master) @@ -174,7 +186,7 @@ TEST_F(MesaGraphicsPlatform, display_probe_returns_best_when_master) auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::best))); + Each(SupportLevelIs(mg::probe::best))); } TEST_F(MesaGraphicsPlatform, probe_returns_unsupported_when_modesetting_is_not_supported) @@ -192,7 +204,7 @@ TEST_F(MesaGraphicsPlatform, probe_returns_unsupported_when_modesetting_is_not_s auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::unsupported))); + Each(SupportLevelIs(mg::probe::unsupported))); } TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_cannot_determine_kms_support) @@ -210,7 +222,7 @@ TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_cannot_determine_kms_s auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::supported))); + Each(SupportLevelIs(mg::probe::supported))); } TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_unexpected_error_returned) @@ -228,7 +240,7 @@ TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_unexpected_error_retur auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::supported))); + Each(SupportLevelIs(mg::probe::supported))); } TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_cannot_determine_busid) @@ -246,7 +258,7 @@ TEST_F(MesaGraphicsPlatform, probe_returns_supported_when_cannot_determine_busid auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::supported))); + Each(SupportLevelIs(mg::probe::supported))); } TEST_F(MesaGraphicsPlatform, display_probe_returns_supported_when_KMS_probe_is_overridden) @@ -265,7 +277,7 @@ TEST_F(MesaGraphicsPlatform, display_probe_returns_supported_when_KMS_probe_is_o auto probe = platform_lib.load_function(display_platform_probe_symbol); EXPECT_THAT( probe(stub_vt, udev, options), - Each(SupportLevelIs(mg::PlatformPriority::supported))); + Each(SupportLevelIs(mg::probe::supported))); } TEST_F(MesaGraphicsPlatform, display_probe_does_not_touch_quirked_device) diff --git a/tests/unit-tests/platforms/gbm-kms/kms/test_real_kms_output.cpp b/tests/unit-tests/platforms/gbm-kms/kms/test_real_kms_output.cpp index fdf6abf7886..fe7583f8c69 100644 --- a/tests/unit-tests/platforms/gbm-kms/kms/test_real_kms_output.cpp +++ b/tests/unit-tests/platforms/gbm-kms/kms/test_real_kms_output.cpp @@ -14,6 +14,7 @@ * along with this program. If not, see . */ +#include "kms/kms_framebuffer.h" #include "src/platforms/gbm-kms/server/kms/real_kms_output.h" #include "src/platforms/gbm-kms/server/kms/page_flipper.h" #include "mir/fatal.h" @@ -54,6 +55,27 @@ class MockPageFlipper : public mgg::PageFlipper MOCK_METHOD1(wait_for_flip, mg::Frame(uint32_t)); }; +class MockKMSFramebuffer : public mgg::FBHandle +{ +public: + MockKMSFramebuffer(uint32_t fb_id) + : fb_id{fb_id} + { + } + + operator uint32_t() const override + { + return fb_id; + } + + auto size() const -> geom::Size override + { + return {}; + } +private: + uint32_t const fb_id; +}; + class RealKMSOutputTest : public ::testing::Test { public: @@ -164,7 +186,6 @@ class RealKMSOutputTest : public ::testing::Test char const* const drm_device = "/dev/dri/card0"; int const drm_fd; - gbm_bo* const fake_bo{reinterpret_cast(0x123ba)}; uint32_t const invalid_id; std::vector const crtc_ids; std::vector const encoder_ids; @@ -182,7 +203,7 @@ TEST_F(RealKMSOutputTest, operations_use_existing_crtc) setup_outputs_connected_crtc(); uint32_t const fb_id{42}; - append_fb_id(fb_id); + auto const fb = std::make_shared(fb_id); { InSequence s; @@ -209,8 +230,6 @@ TEST_F(RealKMSOutputTest, operations_use_existing_crtc) mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); - EXPECT_TRUE(output.set_crtc(*fb)); EXPECT_TRUE(output.schedule_page_flip(*fb)); output.wait_for_page_flip(); @@ -221,6 +240,7 @@ TEST_F(RealKMSOutputTest, operations_use_possible_crtc) using namespace testing; uint32_t const fb_id{67}; + auto const fb = std::make_shared(fb_id); setup_outputs_no_connected_crtc(); @@ -244,15 +264,11 @@ TEST_F(RealKMSOutputTest, operations_use_possible_crtc) .Times(1); } - append_fb_id(fb_id); - mgg::RealKMSOutput output{ drm_fd, mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); - EXPECT_TRUE(output.set_crtc(*fb)); EXPECT_TRUE(output.schedule_page_flip(*fb)); output.wait_for_page_flip(); @@ -264,6 +280,7 @@ TEST_F(RealKMSOutputTest, set_crtc_failure_is_handled_gracefully) using namespace testing; uint32_t const fb_id{67}; + auto const fb = std::make_shared(fb_id); setup_outputs_connected_crtc(); @@ -284,15 +301,11 @@ TEST_F(RealKMSOutputTest, set_crtc_failure_is_handled_gracefully) .Times(0); } - append_fb_id(fb_id); - mgg::RealKMSOutput output{ drm_fd, mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); - EXPECT_FALSE(output.set_crtc(*fb)); EXPECT_NO_THROW({ @@ -372,7 +385,7 @@ TEST_F(RealKMSOutputTest, cursor_move_permission_failure_is_non_fatal) mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); + auto const fb = std::make_shared(4); EXPECT_TRUE(output.set_crtc(*fb)); EXPECT_NO_THROW({ @@ -400,7 +413,7 @@ TEST_F(RealKMSOutputTest, cursor_set_permission_failure_is_non_fatal) mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); + auto const fb = std::make_shared(0x42); EXPECT_TRUE(output.set_crtc(*fb)); struct gbm_bo *dummy = reinterpret_cast(0x1234567); @@ -429,7 +442,7 @@ TEST_F(RealKMSOutputTest, has_no_cursor_if_no_hardware_support) mg::kms::get_connector(drm_fd, connector_ids[0]), mt::fake_shared(mock_page_flipper)}; - auto fb = output.fb_for(fake_bo); + auto const fb = std::make_shared(42); EXPECT_TRUE(output.set_crtc(*fb)); struct gbm_bo *dummy = reinterpret_cast(0x1234567); @@ -507,9 +520,7 @@ TEST_F(RealKMSOutputTest, drm_set_gamma) const_cast(gamma.blue.data()))) .Times(1); - append_fb_id(fb_id); - - auto fb = output.fb_for(fake_bo); + auto const fb = std::make_shared(fb_id); EXPECT_TRUE(output.set_crtc(*fb)); @@ -538,9 +549,7 @@ TEST_F(RealKMSOutputTest, drm_set_gamma_failure_does_not_throw) const_cast(gamma.blue.data()))) .WillOnce(Return(-ENOSYS)); - append_fb_id(fb_id); - - auto fb = output.fb_for(fake_bo); + auto const fb = std::make_shared(fb_id); EXPECT_TRUE(output.set_crtc(*fb)); diff --git a/tests/unit-tests/platforms/test_display.h b/tests/unit-tests/platforms/test_display.h index 613a9a95cfa..cf34a330354 100644 --- a/tests/unit-tests/platforms/test_display.h +++ b/tests/unit-tests/platforms/test_display.h @@ -22,17 +22,6 @@ #include "mir/renderer/gl/context_source.h" #include "mir/test/display_config_matchers.h" -namespace -{ -auto as_context_source(mg::Display* display) -{ - auto const ctx = dynamic_cast(display); - if (!ctx) - BOOST_THROW_EXCEPTION(std::logic_error("Display does not support GL rendering")); - return ctx; -} -} - TEST_F(DisplayTestGeneric, configure_disallows_invalid_configuration) { using namespace testing; @@ -48,77 +37,6 @@ TEST_F(DisplayTestGeneric, configure_disallows_invalid_configuration) // platform-dependent exercise, so won't be tested here. } -#ifdef MIR_DISABLE_TESTS_ON_X11 -TEST_F(DisplayTestGeneric, DISABLED_gl_context_make_current_uses_shared_context) -#else -TEST_F(DisplayTestGeneric, gl_context_make_current_uses_shared_context) -#endif -{ - using namespace testing; - EGLContext const shared_context{reinterpret_cast(0x111)}; - EGLContext const display_buffer_context{reinterpret_cast(0x222)}; - EGLContext const new_context{reinterpret_cast(0x333)}; - - EXPECT_CALL(mock_egl, eglCreateContext(_,_,EGL_NO_CONTEXT,_)) - .WillOnce(Return(shared_context)); - EXPECT_CALL(mock_egl, eglCreateContext(_,_,shared_context,_)) - .WillOnce(Return(display_buffer_context)); - - auto display = create_display(); - - Mock::VerifyAndClearExpectations(&mock_egl); - - { - InSequence s; - EXPECT_CALL(mock_egl, eglCreateContext(_,_,shared_context,_)) - .WillOnce(Return(new_context)); - EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,new_context)); - EXPECT_CALL(mock_egl, eglGetCurrentContext()) - .WillOnce(Return(new_context)); - EXPECT_CALL(mock_egl, eglMakeCurrent(_,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)); - - auto const gl_ctx = as_context_source(display.get())->create_gl_context(); - - ASSERT_NE(nullptr, gl_ctx); - - gl_ctx->make_current(); - } - - Mock::VerifyAndClearExpectations(&mock_egl); - - /* Possible display shutdown sequence, depending on the platform */ - EXPECT_CALL(mock_egl, eglGetCurrentContext()) - .Times(AtLeast(0)); - EXPECT_CALL(mock_egl, eglMakeCurrent(_,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)) - .Times(AtLeast(0)); -} - -TEST_F(DisplayTestGeneric, gl_context_releases_context) -{ - using namespace testing; - - auto display = create_display(); - - { - InSequence s; - EXPECT_CALL(mock_egl, eglMakeCurrent(_,_,_,Ne(EGL_NO_CONTEXT))); - EXPECT_CALL(mock_egl, eglMakeCurrent(_,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)); - - auto const gl_ctx = as_context_source(display.get())->create_gl_context(); - - ASSERT_NE(nullptr, gl_ctx); - - gl_ctx->make_current(); - gl_ctx->release_current(); - - Mock::VerifyAndClearExpectations(&mock_egl); - } - - /* Possible display shutdown sequence, depending on the platform */ - EXPECT_CALL(mock_egl, eglMakeCurrent(_,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT)) - .Times(AtLeast(0)); -} - #ifdef MIR_DISABLE_TESTS_ON_X11 TEST_F(DisplayTestGeneric, DISABLED_does_not_expose_display_buffer_for_output_with_power_mode_off) #else diff --git a/tests/unit-tests/platforms/test_graphics_platform.h b/tests/unit-tests/platforms/test_graphics_platform.h deleted file mode 100644 index 1ed3f415216..00000000000 --- a/tests/unit-tests/platforms/test_graphics_platform.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef TEST_GRAPHICS_PLATFORM_H_ -#define TEST_GRAPHICS_PLATFORM_H_ - -#include "mir/graphics/display_configuration_policy.h" -#include "mir/graphics/display.h" - -#include "mir/test/doubles/stub_gl_config.h" -#include "mir/test/doubles/null_display_configuration_policy.h" - -namespace mtd = mir::test::doubles; - -TEST_F(GraphicsPlatform, buffer_allocator_creation) -{ - using namespace testing; - - EXPECT_NO_THROW ( - auto platform = create_platform(); - auto display = platform->create_display( - std::make_shared(), - std::make_shared()); - auto allocator = platform->create_buffer_allocator(*display); - - EXPECT_TRUE(allocator.get()); - ); -} - -TEST_F(GraphicsPlatform, buffer_creation) -{ - auto platform = create_platform(); - auto display = platform->create_display( - std::make_shared(), - std::make_shared()); - auto allocator = platform->create_buffer_allocator(*display); - auto supported_pixel_formats = allocator->supported_pixel_formats(); - - ASSERT_NE(0u, supported_pixel_formats.size()); - - geom::Size size{320, 240}; - MirPixelFormat const pf{supported_pixel_formats[0]}; - mg::BufferUsage usage{mg::BufferUsage::hardware}; - mg::BufferProperties buffer_properties{size, pf, usage}; - - auto buffer = allocator->alloc_software_buffer(size, pf); - - ASSERT_TRUE(buffer.get() != NULL); - - EXPECT_EQ(buffer->size(), size); - EXPECT_EQ(buffer->pixel_format(), pf); -} - -#endif // TEST_GRAPHICS_PLATFORM_H_ diff --git a/tests/unit-tests/platforms/x11/CMakeLists.txt b/tests/unit-tests/platforms/x11/CMakeLists.txt index b5af5ab79d1..a3e727633af 100644 --- a/tests/unit-tests/platforms/x11/CMakeLists.txt +++ b/tests/unit-tests/platforms/x11/CMakeLists.txt @@ -1,15 +1,13 @@ mir_add_wrapped_executable(mir_unit_tests_x11 NOINSTALL ${CMAKE_CURRENT_SOURCE_DIR}/test_platform.cpp -# ${CMAKE_CURRENT_SOURCE_DIR}/test_graphics_platform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_display.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_display_generic.cpp $ $ $ # Sub-optimal. We really want to link a lib ) set_property( - SOURCE test_platform.cpp test_graphics_platform.cpp test_display_generic.cpp + SOURCE test_platform.cpp PROPERTY COMPILE_OPTIONS -Wno-variadic-macros) add_dependencies(mir_unit_tests_x11 GMock) diff --git a/tests/unit-tests/platforms/x11/test_display.cpp b/tests/unit-tests/platforms/x11/test_display.cpp index f115154f08f..5f95e4232bc 100644 --- a/tests/unit-tests/platforms/x11/test_display.cpp +++ b/tests/unit-tests/platforms/x11/test_display.cpp @@ -17,6 +17,7 @@ #include #include +#include "mir/graphics/platform.h" #include "src/platforms/x11/graphics/display.h" #include "src/platforms/x11/graphics/platform.h" #include "src/server/report/null/display_report.h" @@ -100,11 +101,11 @@ class X11DisplayTest : public ::testing::Test std::shared_ptr create_display() { return std::make_shared( + nullptr, mt::fake_shared(x11_resources), "Mir on X", sizes, mt::fake_shared(null_display_configuration_policy), - mt::fake_shared(mock_gl_config), std::make_shared()); } @@ -112,34 +113,11 @@ class X11DisplayTest : public ::testing::Test mtd::NullDisplayConfigurationPolicy null_display_configuration_policy; ::testing::NiceMock mock_egl; ::testing::NiceMock mock_x11; - mtd::MockGLConfig mock_gl_config; + ::testing::NiceMock mock_gl_config; }; } -TEST_F(X11DisplayTest, respects_gl_config) -{ - EGLint const depth_bits{24}; - EGLint const stencil_bits{8}; - - EXPECT_CALL(mock_gl_config, depth_buffer_bits()) - .Times(AtLeast(1)) - .WillRepeatedly(Return(depth_bits)); - EXPECT_CALL(mock_gl_config, stencil_buffer_bits()) - .Times(AtLeast(1)) - .WillRepeatedly(Return(stencil_bits)); - - EXPECT_CALL(mock_egl, - eglChooseConfig( - _, - AllOf(mtd::EGLConfigContainsAttrib(EGL_DEPTH_SIZE, depth_bits), - mtd::EGLConfigContainsAttrib(EGL_STENCIL_SIZE, stencil_bits)), - _,_,_)) - .Times(AtLeast(1)); - - auto display = create_display(); -} - TEST_F(X11DisplayTest, calculates_physical_size_of_display_based_on_default_screen) { auto const pixel = geom::Size{2880, 1800}; diff --git a/tests/unit-tests/platforms/x11/test_display_generic.cpp b/tests/unit-tests/platforms/x11/test_display_generic.cpp deleted file mode 100644 index 8489f318120..00000000000 --- a/tests/unit-tests/platforms/x11/test_display_generic.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "mir/graphics/display.h" -#include "mir/graphics/display_configuration.h" - -#include "mir/test/doubles/mock_egl.h" -#include "mir/test/doubles/mock_gl.h" -#include "mir/test/doubles/stub_gl_config.h" -#include "mir/test/doubles/null_emergency_cleanup.h" -#include "src/server/report/null/display_report.h" -#include "mir/graphics/default_display_configuration_policy.h" -#include "src/platforms/x11/graphics/platform.h" -#include "src/platforms/x11/x11_resources.h" -#include "mir/test/doubles/mock_x11_resources.h" -#include "mir/test/doubles/mock_x11.h" - -#include -#include - -namespace mg = mir::graphics; -namespace mtd = mir::test::doubles; - -class DisplayTestGeneric : public ::testing::Test -{ -public: - DisplayTestGeneric() - { - using namespace testing; - - EGLint const client_version = 2; - - ON_CALL(mock_egl, eglQueryContext(mock_egl.fake_egl_display, - mock_egl.fake_egl_context, - EGL_CONTEXT_CLIENT_VERSION, - _)) - .WillByDefault(DoAll(SetArgPointee<3>(client_version), - Return(EGL_TRUE))); - - ON_CALL(mock_egl, eglQuerySurface(mock_egl.fake_egl_display, - mock_egl.fake_egl_surface, - EGL_WIDTH, - _)) - .WillByDefault(DoAll(SetArgPointee<3>(1280), - Return(EGL_TRUE))); - - ON_CALL(mock_egl, eglQuerySurface(mock_egl.fake_egl_display, - mock_egl.fake_egl_surface, - EGL_HEIGHT, - _)) - .WillByDefault(DoAll(SetArgPointee<3>(1024), - Return(EGL_TRUE))); - - ON_CALL(mock_egl, eglGetConfigAttrib(mock_egl.fake_egl_display, - _, - _, - _)) - .WillByDefault(DoAll(SetArgPointee<3>(EGL_WINDOW_BIT), - Return(EGL_TRUE))); - - ON_CALL(mock_x11, XNextEvent(mock_x11.fake_x11.display, - _)) - .WillByDefault(DoAll(SetArgPointee<1>(mock_x11.fake_x11.expose_event_return), - Return(1))); - - ON_CALL(mock_egl, eglChooseConfig(_,_,_,1,_)) - .WillByDefault(DoAll(SetArgPointee<2>(mock_egl.fake_configs[0]), - SetArgPointee<4>(1), - Return(EGL_TRUE))); - - mock_egl.provide_egl_extensions(); - mock_gl.provide_gles_extensions(); - } - - std::shared_ptr create_display() - { - auto const platform = std::make_shared( - std::make_shared(), - "Mir on X", - std::vector{{{1280, 1024}}}, - std::make_shared()); - return platform->create_display( - std::make_shared(), - std::make_shared()); - } - - ::testing::NiceMock mock_egl; - ::testing::NiceMock mock_gl; - ::testing::NiceMock mock_x11; -}; - -#define MIR_DISABLE_TESTS_ON_X11 -#include "../test_display.h" -#undef MIR_DISABLE_TESTS_ON_X11 diff --git a/tests/unit-tests/platforms/x11/test_graphics_platform.cpp b/tests/unit-tests/platforms/x11/test_graphics_platform.cpp deleted file mode 100644 index 83453ed0438..00000000000 --- a/tests/unit-tests/platforms/x11/test_graphics_platform.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "mir/graphics/platform.h" -#include "mir/graphics/graphic_buffer_allocator.h" -#include "mir/graphics/buffer_properties.h" -#include "mir/test/doubles/mock_egl.h" -#include "mir/test/doubles/mock_gl.h" -#include "mir/test/doubles/null_emergency_cleanup.h" -#include "src/server/report/null/display_report.h" -#include "mir/test/doubles/null_console_services.h" -#include "mir/options/program_option.h" -#include "src/platforms/x11/graphics/platform.h" -#include "src/platforms/x11/x11_resources.h" -#include "mir/test/doubles/mock_x11.h" -#include "mir/test/doubles/mock_x11_resources.h" - -#include "mir/logging/dumb_console_logger.h" - -#include - -namespace mg = mir::graphics; -namespace ml = mir::logging; -namespace geom = mir::geometry; -namespace mtd = mir::test::doubles; -namespace mo = mir::options; - -class GraphicsPlatform : public ::testing::Test -{ -public: - GraphicsPlatform() : logger(std::make_shared()) - { - using namespace testing; - - ON_CALL(mock_x11, XNextEvent(_, _)) - .WillByDefault( - DoAll( - Invoke( - [](auto, XEvent* ev) - { - ev->type = Expose; - }), - Return(true))); - ON_CALL(mock_egl, eglQueryString(_, EGL_EXTENSIONS)) - .WillByDefault(Return("")); - } - - std::shared_ptr create_platform() - { - return std::make_shared( - std::make_shared(), - std::vector{{{1280, 1024}}}, - std::make_shared()); - } - - std::shared_ptr logger; - - ::testing::NiceMock mock_egl; - ::testing::NiceMock mock_gl; - ::testing::NiceMock mock_x11; -}; - -#include "../test_graphics_platform.h" diff --git a/tests/unit-tests/platforms/x11/test_platform.cpp b/tests/unit-tests/platforms/x11/test_platform.cpp index f5158b230be..1da1f562087 100644 --- a/tests/unit-tests/platforms/x11/test_platform.cpp +++ b/tests/unit-tests/platforms/x11/test_platform.cpp @@ -17,6 +17,7 @@ #include #include +#include "mir/graphics/platform.h" #include "mir/options/program_option.h" #include "src/platforms/x11/graphics/platform.h" #include "src/platforms/x11/x11_resources.h" @@ -80,15 +81,19 @@ TEST_F(X11GraphicsPlatformTest, failure_to_open_x11_display_results_in_an_error) { using namespace ::testing; - EXPECT_CALL(mock_x11, XOpenDisplay(_)) + EXPECT_CALL(mock_x11, XOpenDisplay(_)).Times(AtLeast(1)) .WillRepeatedly(Return(nullptr)); + mir::SharedLibrary platform_lib{mtf::server_platform("server-x11")}; + auto create_platform = platform_lib.load_function("create_display_platform"); + EXPECT_THROW( { - std::make_shared( + create_platform( + mg::SupportedDevice{}, + nullptr, + nullptr, nullptr, - "Mir on X", - std::vector{{{1280, 1024}}}, std::make_shared()); }, std::exception); } diff --git a/tests/unit-tests/renderers/gl/CMakeLists.txt b/tests/unit-tests/renderers/gl/CMakeLists.txt index 180ad6ee1cc..ed3b9c14ecf 100644 --- a/tests/unit-tests/renderers/gl/CMakeLists.txt +++ b/tests/unit-tests/renderers/gl/CMakeLists.txt @@ -1,6 +1,5 @@ list(APPEND UNIT_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/test_gl_renderer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/test_basic_buffer_render_target.cpp ) set(UNIT_TEST_SOURCES ${UNIT_TEST_SOURCES} PARENT_SCOPE) diff --git a/tests/unit-tests/renderers/gl/test_basic_buffer_render_target.cpp b/tests/unit-tests/renderers/gl/test_basic_buffer_render_target.cpp deleted file mode 100644 index 09d7fda0ada..00000000000 --- a/tests/unit-tests/renderers/gl/test_basic_buffer_render_target.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright © Canonical Ltd. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 or 3 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "mir/renderer/gl/basic_buffer_render_target.h" - -#include "mir/test/doubles/null_gl_context.h" -#include "mir/test/doubles/mock_gl.h" -#include "mir/test/doubles/stub_buffer.h" -#include "mir/test/fake_shared.h" - -#include -#include - -namespace mr = mir::renderer; -namespace mrg = mir::renderer::gl; -namespace mg = mir::graphics; -namespace geom = mir::geometry; -namespace mt = mir::test; -namespace mtd = mir::test::doubles; - -using namespace testing; - -namespace -{ - -struct BasicBufferRenderTarget : Test -{ - NiceMock mock_gl; - mtd::NullGLContext ctx; - int const reasonable_width = 24, reasonable_height = 32; - geom::Size reasonable_size{reasonable_width, reasonable_height}; - MirPixelFormat const reasonable_pixel_format{mir_pixel_format_argb_8888}; - mtd::StubBuffer reasonable_buffer{{ - reasonable_size, - reasonable_pixel_format, - mg::BufferUsage::software}}; -}; - -} - -TEST_F(BasicBufferRenderTarget, set_buffer_always_allocs_correct_storage) -{ - mrg::BasicBufferRenderTarget render_target{mt::fake_shared(ctx)}; - EXPECT_CALL(mock_gl, glRenderbufferStorage(_, _, reasonable_width, reasonable_height)); - render_target.set_buffer(mt::fake_shared(reasonable_buffer)); - render_target.swap_buffers(); - Mock::VerifyAndClearExpectations(&mock_gl); - int const other_width = 124, other_height = 88; - mtd::StubBuffer other_buffer{{ - {other_width, other_height}, - reasonable_pixel_format, - mg::BufferUsage::software}}; - EXPECT_CALL(mock_gl, glRenderbufferStorage(_, _, other_width, other_height)); - render_target.set_buffer(mt::fake_shared(other_buffer)); - render_target.swap_buffers(); -} - -TEST_F(BasicBufferRenderTarget, sets_gl_viewport_on_buffer_size_change) -{ - mrg::BasicBufferRenderTarget render_target{mt::fake_shared(ctx)}; - EXPECT_CALL(mock_gl, glViewport(0, 0, reasonable_width, reasonable_height)); - render_target.set_buffer(mt::fake_shared(reasonable_buffer)); - Mock::VerifyAndClearExpectations(&mock_gl); - int const other_width = 124, other_height = 88; - mtd::StubBuffer other_buffer{{ - {other_width, other_height}, - reasonable_pixel_format, - mg::BufferUsage::software}}; - EXPECT_CALL(mock_gl, glViewport(0, 0, other_width, other_height)); - render_target.set_buffer(mt::fake_shared(other_buffer)); -} - -TEST_F(BasicBufferRenderTarget, cleans_up_framebuffers_and_renderbuffers) -{ - std::set framebuffers; - GLuint framebuffer_count{0}; - ON_CALL(mock_gl, glGenFramebuffers(1, _)).WillByDefault(Invoke([&](GLsizei, GLuint* result) - { - *result = framebuffer_count++; - framebuffers.insert(*result); - })); - ON_CALL(mock_gl, glDeleteFramebuffers(1, _)).WillByDefault(Invoke([&](GLsizei, GLuint const* id) - { - framebuffers.erase(*id); - })); - std::set renderbuffers; - GLuint renderbuffer_count{0}; - ON_CALL(mock_gl, glGenRenderbuffers(1, _)).WillByDefault(Invoke([&](GLsizei, GLuint* result) - { - *result = renderbuffer_count++; - renderbuffers.insert(*result); - })); - ON_CALL(mock_gl, glDeleteRenderbuffers(1, _)).WillByDefault(Invoke([&](GLsizei, GLuint const* id) - { - renderbuffers.erase(*id); - })); - { - mrg::BasicBufferRenderTarget render_target{mt::fake_shared(ctx)}; - render_target.make_current(); - render_target.set_buffer(mt::fake_shared(reasonable_buffer)); - render_target.bind(); - render_target.swap_buffers(); - int const other_width = 124, other_height = 88; - mtd::StubBuffer other_buffer{{ - {other_width, other_height}, - reasonable_pixel_format, - mg::BufferUsage::software}}; - render_target.set_buffer(mt::fake_shared(other_buffer)); - render_target.bind(); - render_target.swap_buffers(); - } - EXPECT_THAT(framebuffer_count, Gt(0)); - EXPECT_THAT(framebuffers.size(), Eq(0)); - EXPECT_THAT(renderbuffer_count, Gt(0)); - EXPECT_THAT(renderbuffers.size(), Eq(0)); -} - -TEST_F(BasicBufferRenderTarget, reads_pixels) -{ - mrg::BasicBufferRenderTarget render_target{mt::fake_shared(ctx)}; - render_target.set_buffer(mt::fake_shared(reasonable_buffer)); - EXPECT_CALL(mock_gl, glReadPixels(0, 0, reasonable_width, reasonable_height, _, _, _)); - render_target.swap_buffers(); -} - -TEST_F(BasicBufferRenderTarget, throws_on_invalid_buffer) -{ - mrg::BasicBufferRenderTarget render_target{mt::fake_shared(ctx)}; - EXPECT_THROW({ - mtd::StubBuffer buffer({ - reasonable_size, - mir_pixel_format_abgr_8888, // wrong format - mg::BufferUsage::software}); - render_target.set_buffer(mt::fake_shared(buffer)); - render_target.swap_buffers(); - }, std::logic_error); -} diff --git a/tests/unit-tests/renderers/gl/test_gl_renderer.cpp b/tests/unit-tests/renderers/gl/test_gl_renderer.cpp index ae8ae02c2be..015ecd2f312 100644 --- a/tests/unit-tests/renderers/gl/test_gl_renderer.cpp +++ b/tests/unit-tests/renderers/gl/test_gl_renderer.cpp @@ -25,8 +25,8 @@ #include #include #include -#include -#include +#include +#include using testing::SetArgPointee; using testing::InSequence; @@ -154,11 +154,10 @@ class GLRenderer : testing::NiceMock mock_gl; testing::NiceMock mock_egl; std::shared_ptr mock_buffer; - mtd::StubGLDisplayBuffer display_buffer{{{1, 2}, {3, 4}}}; - testing::NiceMock mock_display_buffer; std::shared_ptr> renderable; mg::RenderableList renderable_list; glm::mat4 trans; + std::shared_ptr const gl_platform{std::make_shared()}; class StubProgram : public mg::gl::Program { @@ -166,6 +165,11 @@ class GLRenderer : StubProgram prog; }; +auto make_output_surface() -> std::unique_ptr +{ + return std::make_unique>(); +} + } TEST_F(GLRenderer, disables_blending_for_rgbx_surfaces) @@ -175,7 +179,7 @@ TEST_F(GLRenderer, disables_blending_for_rgbx_surfaces) .WillOnce(Return(false)); EXPECT_CALL(mock_gl, glDisable(GL_BLEND)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } @@ -185,7 +189,7 @@ TEST_F(GLRenderer, enables_blending_for_rgba_surfaces) EXPECT_CALL(mock_gl, glDisable(GL_BLEND)).Times(0); EXPECT_CALL(mock_gl, glEnable(GL_BLEND)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } @@ -196,7 +200,7 @@ TEST_F(GLRenderer, enables_blending_for_rgbx_translucent_surfaces) EXPECT_CALL(mock_gl, glDisable(GL_BLEND)).Times(0); EXPECT_CALL(mock_gl, glEnable(GL_BLEND)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } @@ -208,7 +212,7 @@ TEST_F(GLRenderer, uses_premultiplied_src_alpha_for_rgba_surfaces) EXPECT_CALL(mock_gl, glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } @@ -222,7 +226,7 @@ TEST_F(GLRenderer, avoids_src_alpha_for_rgbx_blending) // LP: #1423462 glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_CONSTANT_ALPHA, GL_ZERO, GL_ONE)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } @@ -233,83 +237,43 @@ TEST_F(GLRenderer, clears_to_opaque_black) EXPECT_CALL(mock_gl, glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)); EXPECT_CALL(mock_gl, glClear(_)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.render(renderable_list); } TEST_F(GLRenderer, makes_display_buffer_current_when_created) { - EXPECT_CALL(mock_display_buffer, make_current()); - - mrg::Renderer renderer(mock_display_buffer); - - testing::Mock::VerifyAndClearExpectations(&mock_display_buffer); -} - -TEST_F(GLRenderer, releases_display_buffer_current_when_destroyed) -{ - mrg::Renderer renderer(mock_display_buffer); - - EXPECT_CALL(mock_display_buffer, release_current()); -} - + auto mock_output_surface = make_output_surface(); -TEST_F(GLRenderer, makes_display_buffer_current_before_deleting_programs) -{ - mrg::Renderer renderer(mock_display_buffer); - - testing::Sequence s1, s2; - // We must call MakeCurrent before anything else. - EXPECT_CALL(mock_display_buffer, make_current()) - .InSequence(s1, s2); - /* As an implementation detail ProgramFactory calls - * glDeleteShader during shader compilation; this has to happen - * before rendering (and so, before SwapBuffers) - */ - EXPECT_CALL(mock_gl, glDeleteShader(_)) - .Times(AtLeast(1)) - .InSequence(s1, s2); - EXPECT_CALL(mock_display_buffer, swap_buffers()) - .InSequence(s1, s2); - EXPECT_CALL(mock_display_buffer, make_current()) - .InSequence(s1, s2); - /* We only care that all glDeleteProgram() and glDeleteShader calls - * happen after make_current() and before the final release_current(); - * we don't care what order they happen in otherwise. - */ - EXPECT_CALL(mock_gl, glDeleteProgram(_)) - .Times(AtLeast(1)) - .InSequence(s1); - EXPECT_CALL(mock_gl, glDeleteShader(_)) - .Times(AtLeast(1)) - .InSequence(s2); - EXPECT_CALL(mock_display_buffer, release_current()).InSequence(s1, s2); + EXPECT_CALL(*mock_output_surface, make_current()); - renderer.render(renderable_list); + mrg::Renderer renderer(gl_platform, std::move(mock_output_surface)); } TEST_F(GLRenderer, makes_display_buffer_current_before_rendering) { - mrg::Renderer renderer(mock_display_buffer); + auto mock_output_surface = make_output_surface(); InSequence seq; - EXPECT_CALL(mock_display_buffer, make_current()); + EXPECT_CALL(*mock_output_surface, make_current()).Times(AnyNumber()); EXPECT_CALL(mock_gl, glClear(_)); - renderer.render(renderable_list); + mrg::Renderer renderer(gl_platform, std::move(mock_output_surface)); - testing::Mock::VerifyAndClearExpectations(&mock_display_buffer); + renderer.render(renderable_list); } TEST_F(GLRenderer, swaps_buffers_after_rendering) { - mrg::Renderer renderer(mock_display_buffer); + auto mock_output_surface = make_output_surface(); InSequence seq; EXPECT_CALL(mock_gl, glDrawArrays(_, _, _)).Times(AnyNumber()); - EXPECT_CALL(mock_display_buffer, swap_buffers()); + EXPECT_CALL(*mock_output_surface, commit()) + .WillRepeatedly(testing::Invoke([]() { return std::unique_ptr(); })); + mrg::Renderer renderer(gl_platform, std::move(mock_output_surface)); renderer.render(renderable_list); } @@ -321,7 +285,7 @@ TEST_F(GLRenderer, sets_scissor_test) EXPECT_CALL(mock_gl, glDisable(GL_SCISSOR_TEST)); EXPECT_CALL(mock_gl, glScissor(-1, 2, 2, 3)); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.set_viewport({{1, 2}, {3, 4}}); renderer.render(renderable_list); @@ -333,7 +297,8 @@ TEST_F(GLRenderer, dont_set_scissor_test_when_unnecessary) EXPECT_CALL(mock_gl, glDisable(GL_SCISSOR_TEST)).Times(0); EXPECT_CALL(mock_gl, glScissor(_, _, _, _)).Times(0); - mrg::Renderer renderer(display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); + renderer.set_viewport(mir::geometry::Rectangle{{0, 0}, {2, 3}}); renderer.render(renderable_list); } @@ -341,14 +306,9 @@ TEST_F(GLRenderer, dont_set_scissor_test_when_unnecessary) TEST_F(GLRenderer, unchanged_viewport_avoids_gl_calls) { - int const screen_width = 1920; - int const screen_height = 1080; mir::geometry::Rectangle const view_area{{0,0}, {1920,1080}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); - - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, make_output_surface()); renderer.set_viewport(view_area); @@ -363,10 +323,14 @@ TEST_F(GLRenderer, unchanged_viewport_updates_gl_if_rotated) int const screen_height = 1080; mir::geometry::Rectangle const view_area{{0,0}, {1920,1080}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); - mrg::Renderer renderer(mock_display_buffer); + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); + + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); @@ -384,12 +348,16 @@ TEST_F(GLRenderer, sets_viewport_unscaled_exact) int const screen_height = 1080; mir::geometry::Rectangle const view_area{{0,0}, {1920,1080}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); + + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); EXPECT_CALL(mock_gl, glViewport(0, 0, screen_width, screen_height)); - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); } @@ -399,12 +367,16 @@ TEST_F(GLRenderer, sets_viewport_upscaled_exact) int const screen_height = 1080; mir::geometry::Rectangle const view_area{{0,0}, {1280,720}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); + + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); EXPECT_CALL(mock_gl, glViewport(0, 0, screen_width, screen_height)); - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); } @@ -414,12 +386,16 @@ TEST_F(GLRenderer, sets_viewport_downscaled_exact) int const screen_height = 720; mir::geometry::Rectangle const view_area{{0,0}, {1920,1080}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); + + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); EXPECT_CALL(mock_gl, glViewport(0, 0, screen_width, screen_height)); - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); } @@ -429,12 +405,16 @@ TEST_F(GLRenderer, sets_viewport_upscaled_narrow) int const screen_height = 1080; mir::geometry::Rectangle const view_area{{0,0}, {640,480}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); + + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); EXPECT_CALL(mock_gl, glViewport(240, 0, 1440, 1080)); - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); } @@ -444,11 +424,15 @@ TEST_F(GLRenderer, sets_viewport_downscaled_wide) int const screen_height = 480; mir::geometry::Rectangle const view_area{{0,0}, {1920,1080}}; - ON_CALL(mock_display_buffer, size()) - .WillByDefault(Return(mir::geometry::Size{screen_width, screen_height})); + auto output_surface = make_output_surface(); + + ON_CALL(*output_surface, size()) + .WillByDefault( + Return( + mir::geometry::Size{screen_width, screen_height})); EXPECT_CALL(mock_gl, glViewport(0, 60, 640, 360)); - mrg::Renderer renderer(mock_display_buffer); + mrg::Renderer renderer(gl_platform, std::move(output_surface)); renderer.set_viewport(view_area); } diff --git a/tests/unit-tests/scene/test_mediating_display_changer.cpp b/tests/unit-tests/scene/test_mediating_display_changer.cpp index e726846f030..671c45a5441 100644 --- a/tests/unit-tests/scene/test_mediating_display_changer.cpp +++ b/tests/unit-tests/scene/test_mediating_display_changer.cpp @@ -55,6 +55,7 @@ class MockDisplayConfigurationPolicy : public mg::DisplayConfigurationPolicy public: ~MockDisplayConfigurationPolicy() noexcept {} MOCK_METHOD(void, apply_to, (mg::DisplayConfiguration&)); + MOCK_METHOD(void, confirm, (mg::DisplayConfiguration const&)); };