Skip to content

Commit

Permalink
platforms/common: Pull out CPUCopyOutputSurface
Browse files Browse the repository at this point in the history
These platforms all have (effectively) a single implementation of
`CPUCopyOutputSurface`.

Pull it into the common platforms code to make it easier to fix
up over time.
  • Loading branch information
RAOF committed Jun 13, 2023
1 parent 388937d commit b461f2e
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 534 deletions.
2 changes: 2 additions & 0 deletions src/platforms/common/server/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
281 changes: 281 additions & 0 deletions src/platforms/common/server/cpu_copy_output_surface.cpp
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* Authored by: Christopher James Halse Rogers
*/

#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <drm_fourcc.h>

#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<void (*allocator)(GLsizei, GLuint*), void (* deleter)(GLsizei, GLuint const*)>
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<mg::DRMFormat> best_format;
for (auto const format : provider.supported_formats())
{
switch(static_cast<uint32_t>(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<mg::CPUAddressableDisplayProvider> allocator,
geom::Size size);

void bind();

void make_current();
void release_current();

auto commit() -> std::unique_ptr<mg::Framebuffer>;

auto size() const -> geom::Size;
auto layout() const -> Layout;

private:
std::shared_ptr<mg::CPUAddressableDisplayProvider> 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<mg::CPUAddressableDisplayProvider> allocator,
geom::Size size)
: impl{std::make_unique<Impl>(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<mg::Framebuffer>
{
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<mg::CPUAddressableDisplayProvider> 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<mg::Framebuffer>
{
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;
}
63 changes: 63 additions & 0 deletions src/platforms/common/server/cpu_copy_output_surface.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* Authored by: Christopher James Halse Rogers
*/

#include <EGL/egl.h>
#include <GLES2/gl2.h>

#include <memory>

#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<CPUAddressableDisplayProvider> allocator,
geometry::Size size);

~CPUCopyOutputSurface() override;

void bind() override;

void make_current() override;

void release_current() override;

auto commit() -> std::unique_ptr<Framebuffer> override;

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

auto layout() const -> Layout override;

private:
class Impl;
std::unique_ptr<Impl> const impl;
std::shared_ptr<CPUAddressableDisplayProvider> const allocator;
};
}
}
Loading

0 comments on commit b461f2e

Please sign in to comment.