diff --git a/src/platforms/common/server/CMakeLists.txt b/src/platforms/common/server/CMakeLists.txt index 466ea7f6e03..3c1053f9070 100644 --- a/src/platforms/common/server/CMakeLists.txt +++ b/src/platforms/common/server/CMakeLists.txt @@ -10,6 +10,8 @@ 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..9d126d66e23 --- /dev/null +++ b/src/platforms/common/server/cpu_copy_output_surface.cpp @@ -0,0 +1,281 @@ +/* + * 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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#include +#include +#include + +#include "mir/graphics/egl_error.h" +#include "mir/graphics/platform.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 ensure_context_current(EGLDisplay dpy, EGLContext ctx) + -> EGLContext +{ + 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 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 ctx, + std::shared_ptr allocator, + geom::Size size) + : impl{std::make_unique(dpy, 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->make_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 ctx, + std::shared_ptr allocator, + geom::Size size) + : allocator{std::move(allocator)}, + dpy{dpy}, + ctx{ensure_context_current(dpy, 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() +{ + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx); +} + +void mgc::CPUCopyOutputSurface::Impl::release_current() +{ + eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_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..7a24c3369a7 --- /dev/null +++ b/src/platforms/common/server/cpu_copy_output_surface.h @@ -0,0 +1,63 @@ +/* + * 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 . + * + * Authored by: Christopher James Halse Rogers + */ + +#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 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/buffer_allocator.cpp b/src/platforms/eglstream-kms/server/buffer_allocator.cpp index c5bc5813845..7a803cb118e 100644 --- a/src/platforms/eglstream-kms/server/buffer_allocator.cpp +++ b/src/platforms/eglstream-kms/server/buffer_allocator.cpp @@ -18,6 +18,7 @@ #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" @@ -583,152 +584,8 @@ class GLHandle GLuint id; }; -using RenderbufferHandle = GLHandle<&glGenRenderbuffers, &glDeleteRenderbuffers>; -using FramebufferHandle = GLHandle<&glGenFramebuffers, &glDeleteFramebuffers>; using TextureHandle = GLHandle<&glGenTextures, &glDeleteTextures>; -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 CPUCopyOutputSurface : public mg::gl::OutputSurface -{ -public: - CPUCopyOutputSurface( - std::unique_ptr ctx, - std::shared_ptr allocator, - geom::Size size) - : allocator{std::move(allocator)}, - ctx{std::move(ctx)}, - size_{std::move(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 bind() override - { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - } - - void make_current() override - { - ctx->make_current(); - } - - void release_current() override - { - ctx->release_current(); - } - - auto commit() -> std::unique_ptr override - { - 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_RGBA; - } - else if (format == DRM_FORMAT_RGBA8888 || format == DRM_FORMAT_RGBX8888) - { - pixel_layout = GL_BGRA_EXT; - } - 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 size() const -> geom::Size override - { - return size_; - } - - auto layout() const -> Layout override - { - return Layout::TopRowFirst; - } - -private: - std::shared_ptr const allocator; - std::unique_ptr const ctx; - geom::Size const size_; - mg::DRMFormat const format; - RenderbufferHandle const colour_buffer; - FramebufferHandle const fbo; -}; - auto make_stream_ctx(EGLDisplay dpy, EGLConfig cfg, EGLContext share_with) -> EGLContext { eglBindAPI(EGL_OPENGL_ES_API); @@ -892,8 +749,9 @@ auto mge::GLRenderingProvider::surface_for_output( auto fb_context = ctx->make_share_context(); fb_context->make_current(); - return std::make_unique( - std::move(fb_context), + return std::make_unique( + dpy, + static_cast(*ctx), cpu_provider, size); } diff --git a/src/platforms/gbm-kms/server/buffer_allocator.cpp b/src/platforms/gbm-kms/server/buffer_allocator.cpp index 6aa595aeeb7..112c2f75980 100644 --- a/src/platforms/gbm-kms/server/buffer_allocator.cpp +++ b/src/platforms/gbm-kms/server/buffer_allocator.cpp @@ -37,6 +37,7 @@ #include "mir/graphics/drm_formats.h" #include "display_helpers.h" #include "mir/graphics/egl_error.h" +#include "cpu_copy_output_surface.h" #include #include @@ -323,197 +324,6 @@ auto mgg::GLRenderingProvider::as_texture(std::shared_ptr buffer) -> std 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 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"})); -} - -auto ensure_context_current(EGLDisplay dpy, EGLContext ctx) - -> EGLContext -{ - if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make share context current")); - } - return ctx; -} - -class CPUCopyOutputSurface : public mg::gl::OutputSurface -{ -public: - CPUCopyOutputSurface( - EGLDisplay dpy, - EGLContext ctx, - std::shared_ptr allocator, - geom::Size size) - : allocator{std::move(allocator)}, - dpy{dpy}, - ctx{ensure_context_current(dpy, ctx)}, - size_{std::move(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 bind() override - { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - } - - void make_current() override - { - eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx); - } - - void release_current() override - { - eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - - auto commit() -> std::unique_ptr override - { - 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 size() const -> geom::Size override - { - return size_; - } - - auto layout() const -> Layout override - { - return Layout::TopRowFirst; - } - -private: - std::shared_ptr const allocator; - EGLDisplay const dpy; - EGLContext const ctx; - geom::Size const size_; - mg::DRMFormat const format; - RenderbufferHandle const colour_buffer; - FramebufferHandle const fbo; -}; - class GBMOutputSurface : public mg::gl::OutputSurface { public: @@ -778,7 +588,7 @@ auto mgg::GLRenderingProvider::surface_for_output( } auto cpu_provider = target->acquire_interface(); - return std::make_unique( + return std::make_unique( dpy, ctx, std::move(cpu_provider), diff --git a/src/platforms/renderer-generic-egl/buffer_allocator.cpp b/src/platforms/renderer-generic-egl/buffer_allocator.cpp index dfa84353f32..a6d4e78a90c 100644 --- a/src/platforms/renderer-generic-egl/buffer_allocator.cpp +++ b/src/platforms/renderer-generic-egl/buffer_allocator.cpp @@ -35,6 +35,7 @@ #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 @@ -325,201 +326,6 @@ auto mge::GLRenderingProvider::as_texture(std::shared_ptr buffer) -> std 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 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"})); -} - -auto ensure_context_current(EGLDisplay dpy, EGLContext ctx) - -> EGLContext -{ - if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make share context current")); - } - return ctx; -} - -class CPUCopyOutputSurface : public mg::gl::OutputSurface -{ -public: - CPUCopyOutputSurface( - EGLDisplay dpy, - EGLContext ctx, - std::shared_ptr allocator, - geom::Size size) - : allocator{std::move(allocator)}, - dpy{dpy}, - ctx{ensure_context_current(dpy, ctx)}, - size_{size}, - format{select_format_from(*this->allocator)} - { - if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) != EGL_TRUE) - { - BOOST_THROW_EXCEPTION(mg::egl_error("Failed to make share context current")); - } - - 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 bind() override - { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - } - - void make_current() override - { - eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx); - } - - void release_current() override - { - eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - } - - auto commit() -> std::unique_ptr override - { - 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 size() const -> geom::Size override - { - return size_; - } - - auto layout() const -> Layout override - { - return Layout::TopRowFirst; - } - -private: - std::shared_ptr const allocator; - EGLDisplay const dpy; - EGLContext const ctx; - geom::Size const size_; - mg::DRMFormat const format; - RenderbufferHandle const colour_buffer; - FramebufferHandle const fbo; -}; class EGLOutputSurface : public mg::gl::OutputSurface { @@ -600,7 +406,7 @@ auto mge::GLRenderingProvider::surface_for_output( } auto cpu_provider = framebuffer_provider->acquire_interface(); - return std::make_unique( + return std::make_unique( dpy, ctx, std::move(cpu_provider),