Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

frontend/wayland: wp_viewporter support #3445

Merged
merged 12 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/core/mir/geometry/dimensions.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ template<typename T, typename Scalar>
inline constexpr DeltaX<T> operator/(DeltaX<T> const& dx, Scalar scale) { return DeltaX<T>{dx.as_value() / scale}; }
template<typename T, typename Scalar>
inline constexpr DeltaY<T> operator/(DeltaY<T> const& dy, Scalar scale) { return DeltaY<T>{dy.as_value() / scale}; }

// Can divide a coördinate by a distance to get a scalar
template<typename T, typename U>
inline constexpr auto operator/(X<T> const& x, Width<U> const& width) { return x.as_value() / width.as_value(); }
template<typename T, typename U>
inline constexpr auto operator/(Y<T> const& y, Height<U> const& height) { return y.as_value() / height.as_value(); }
} // namespace

// Converting between types is fine, as long as they are along the same axis
Expand Down
18 changes: 18 additions & 0 deletions include/core/mir/geometry/size.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#include "forward.h"
#include "dimensions.h"
#include <ostream>
#include <limits>
#include <concepts>
#include <type_traits>

namespace mir
{
Expand All @@ -32,6 +35,13 @@ struct Point;
template<typename T>
struct Displacement;

template<typename T, typename U>
concept is_exactly_representable = requires
{
typename std::enable_if<std::numeric_limits<T>::digits >= std::numeric_limits<U>::digits, U>::type;
requires std::floating_point<T>;
};

template<typename T>
struct Size
{
Expand All @@ -41,6 +51,14 @@ struct Size
constexpr Size(Size const&) noexcept = default;
Size& operator=(Size const&) noexcept = default;

template<typename U>
requires is_exactly_representable<T, U>
constexpr Size(Size<U> const& other) noexcept
: width{Width<T>{other.width}},
height{Height<T>{other.height}}
{
}

template<typename U>
explicit constexpr Size(Size<U> const& other) noexcept
: width{Width<T>{other.width}},
Expand Down
7 changes: 7 additions & 0 deletions include/platform/mir/graphics/renderable.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ class Renderable
virtual std::shared_ptr<Buffer> buffer() const = 0;

virtual geometry::Rectangle screen_position() const = 0;
/**
* The region of \ref buffer that should be sampled from.
*
* This is in buffer coordinates, so {{0, 0}, {buffer->size().width, buffer->size().height} means
* “use the whole buffer”.
*/
virtual geometry::RectangleD src_bounds() const = 0;
mattkae marked this conversation as resolved.
Show resolved Hide resolved
virtual std::optional<geometry::Rectangle> clip_area() const = 0;

// These are from the old CompositingCriteria. There is a little bit
Expand Down
43 changes: 34 additions & 9 deletions src/gl/tessellation_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,51 @@
namespace mg = mir::graphics;
namespace mgl = mir::gl;
namespace geom = mir::geometry;

namespace
{
struct SrcTexCoords
{
GLfloat top;
GLfloat bottom;
GLfloat left;
GLfloat right;
};

auto tex_coords_from_rect(geom::Size buffer_size, geom::RectangleD sample_rect) -> SrcTexCoords
{
/* GL Texture coordinates are normalised to the size of the buffer, so (0.0, 0.0) is the top-left
* and (1.0, 1.0) is the bottom-right
*/
SrcTexCoords coords;
coords.top = sample_rect.top() / buffer_size.height;
coords.bottom = sample_rect.bottom() / buffer_size.height;
coords.left = sample_rect.left() / buffer_size.width;
coords.right = sample_rect.right() / buffer_size.width;
return coords;
}
}

mgl::Primitive mgl::tessellate_renderable_into_rectangle(
mg::Renderable const& renderable, geom::Displacement const& offset)
{
auto rect = renderable.screen_position();
rect.top_left = rect.top_left - offset;
GLfloat left = rect.top_left.x.as_int();
GLfloat right = left + rect.size.width.as_int();
GLfloat top = rect.top_left.y.as_int();
GLfloat bottom = top + rect.size.height.as_int();
GLfloat const left = rect.top_left.x.as_int();
GLfloat const right = left + rect.size.width.as_int();
GLfloat const top = rect.top_left.y.as_int();
GLfloat const bottom = top + rect.size.height.as_int();

mgl::Primitive rectangle;
rectangle.type = GL_TRIANGLE_STRIP;

GLfloat const tex_right = 1.0f;
GLfloat const tex_bottom = 1.0f;
auto const [tex_top, tex_bottom, tex_left, tex_right] =
tex_coords_from_rect(renderable.buffer()->size(), renderable.src_bounds());

auto& vertices = rectangle.vertices;
vertices[0] = {{left, top, 0.0f}, {0.0f, 0.0f}};
vertices[1] = {{left, bottom, 0.0f}, {0.0f, tex_bottom}};
vertices[2] = {{right, top, 0.0f}, {tex_right, 0.0f}};
vertices[0] = {{left, top, 0.0f}, {tex_left, tex_top}};
vertices[1] = {{left, bottom, 0.0f}, {tex_left, tex_bottom}};
vertices[2] = {{right, top, 0.0f}, {tex_right, tex_top}};
vertices[3] = {{right, bottom, 0.0f}, {tex_right, tex_bottom}};
return rectangle;
}
1 change: 1 addition & 0 deletions src/server/frontend_wayland/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ set(
${PROJECT_SOURCE_DIR}/src/include/server/mir/frontend/pointer_input_dispatcher.h
session_credentials.cpp
${PROJECT_SOURCE_DIR}/src/include/server/mir/frontend/buffer_stream.h
wp_viewporter.cpp wp_viewporter.h
)

add_custom_command(
Expand Down
3 changes: 3 additions & 0 deletions src/server/frontend_wayland/wayland_connector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "wayland_executor.h"
#include "desktop_file_manager.h"
#include "foreign_toplevel_manager_v1.h"
#include "wp_viewporter.h"

#include "mir/main_loop.h"
#include "mir/thread_name.h"
Expand Down Expand Up @@ -331,6 +332,8 @@ mf::WaylandConnector::WaylandConnector(

shm_global = std::make_unique<WlShm>(display.get(), executor);

viewporter = std::make_unique<WpViewporter>(display.get());

char const* wayland_display = nullptr;

if (auto const display_name = getenv("WAYLAND_DISPLAY"))
Expand Down
2 changes: 2 additions & 0 deletions src/server/frontend_wayland/wayland_connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ class WlSeat;
class WlShm;
class WlSubcompositor;
class WlSurface;
class WpViewporter;
class DesktopFileManager;

class WaylandExtensions
Expand Down Expand Up @@ -210,6 +211,7 @@ class WaylandConnector : public Connector
std::shared_ptr<DesktopFileManager> desktop_file_manager;
std::unique_ptr<WlDataDeviceManager> data_device_manager_global;
std::unique_ptr<WlShm> shm_global;
std::unique_ptr<WpViewporter> viewporter;
std::shared_ptr<Executor> const executor;
std::shared_ptr<graphics::GraphicBufferAllocator> const allocator;
std::shared_ptr<shell::Shell> const shell;
Expand Down
75 changes: 59 additions & 16 deletions src/server/frontend_wayland/wl_surface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

#include "wl_surface.h"

#include "mir/geometry/forward.h"
#include "viewporter_wrapper.h"
#include "wayland_utils.h"
#include "wl_surface_role.h"
#include "wl_subcompositor.h"
Expand All @@ -39,9 +39,12 @@
#include "mir/scene/surface.h"
#include "mir/shell/surface_specification.h"
#include "mir/log.h"
#include "wp_viewporter.h"

#include <chrono>
#include <boost/throw_exception.hpp>
#include <cmath>
#include <limits>
#include <wayland-server-protocol.h>

namespace mf = mir::frontend;
Expand Down Expand Up @@ -72,6 +75,9 @@ void mf::WlSurfaceState::update_from(WlSurfaceState const& source)
begin(source.frame_callbacks),
end(source.frame_callbacks));

if (source.viewport)
viewport = source.viewport;

if (source.surface_data_invalidated)
surface_data_invalidated = true;
}
Expand Down Expand Up @@ -331,7 +337,19 @@ void mf::WlSurface::commit(WlSurfaceState const& state)
input_shape = state.input_shape.value();

if (state.scale)
inv_scale = 1.0f / state.scale.value();
scale = state.scale.value();

if (state.viewport)
{
viewport = std::move(state.viewport);
}

bool needs_buffer_submission =
state.scale || // If the scale has changed, or...
state.viewport || // ...we've added a viewport, or...
(viewport && viewport.value().changed_since_last_resolve()); // ...the viewport has changed...
// ...then we'll need to submit a new frame, even if the client hasn't
// attached a new buffer.

auto const executor_send_frame_callbacks = [executor = wayland_executor, weak_self = mw::make_weak(this)]()
{
Expand Down Expand Up @@ -366,49 +384,65 @@ void mf::WlSurface::commit(WlSurfaceState const& state)
}
});
};
std::shared_ptr<graphics::Buffer> mir_buffer;

if (auto const shm_buffer = ShmBuffer::from(weak_buffer.value()))
{
mir_buffer = allocator->buffer_from_shm(
current_buffer = allocator->buffer_from_shm(
shm_buffer->data(),
std::move(executor_send_frame_callbacks),
std::move(release_buffer));
tracepoint(
mir_server_wayland,
sw_buffer_committed,
wl_resource_get_client(resource),
mir_buffer->id().as_value());
current_buffer->id().as_value());
}
else
{
mir_buffer = allocator->buffer_from_resource(
current_buffer = allocator->buffer_from_resource(
weak_buffer.value(),
std::move(executor_send_frame_callbacks),
std::move(release_buffer));
tracepoint(
mir_server_wayland,
hw_buffer_committed,
wl_resource_get_client(resource),
mir_buffer->id().as_value());
}

stream->submit_buffer(mir_buffer, mir_buffer->size() * inv_scale, {{0, 0}, geom::SizeD{mir_buffer->size()}});
auto const new_buffer_size = mir_buffer->size() * inv_scale;

if (std::make_optional(new_buffer_size) != buffer_size_)
{
state.invalidate_surface_data(); // input shape needs to be recalculated for the new size
current_buffer->id().as_value());
}

buffer_size_ = new_buffer_size;
needs_buffer_submission = true;
}
}
else
{
frame_callback_executor->spawn(std::move(executor_send_frame_callbacks));
}

if (needs_buffer_submission)
{
geom::Size logical_size;
geom::RectangleD src_sample;

if (viewport)
{
std::tie(src_sample, logical_size) = viewport.value().resolve_viewport(scale, current_buffer->size());
}
else
{
src_sample = geom::RectangleD{{0, 0}, current_buffer->size()};
logical_size = current_buffer->size() / scale;
}

stream->submit_buffer(current_buffer, logical_size, src_sample);

if (std::make_optional(logical_size) != buffer_size_)
{
state.invalidate_surface_data(); // input shape needs to be recalculated for the new size
}

buffer_size_ = logical_size;
}

for (WlSubsurface* child: children)
{
child->parent_has_committed();
Expand Down Expand Up @@ -468,6 +502,15 @@ auto mf::WlSurface::confine_pointer_state() const -> MirPointerConfinementState
return mir_pointer_unconfined;
}

void mf::WlSurface::associate_viewport(wayland::Weak<Viewport> viewport)
{
if (this->viewport || pending.viewport)
{
BOOST_THROW_EXCEPTION((std::logic_error{"Cannot associate a viewport to a window with an existing viewport"}));
}
pending.viewport = viewport;
}

void mir::frontend::WlSurface::update_surface_spec(shell::SurfaceSpecification const& spec)
{
pending.surface_spec.update_from(spec);
Expand Down
22 changes: 21 additions & 1 deletion src/server/frontend_wayland/wl_surface.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Executor;
namespace graphics
{
class GraphicBufferAllocator;
class Buffer;
}
namespace scene
{
Expand All @@ -57,6 +58,7 @@ namespace frontend
class WlSurface;
class WlSubsurface;
class ResourceLifetimeTracker;
class Viewport;

struct WlSurfaceState
{
Expand All @@ -83,6 +85,7 @@ struct WlSurfaceState
std::optional<geometry::Displacement> offset;
std::optional<std::optional<std::vector<geometry::Rectangle>>> input_shape;
std::vector<wayland::Weak<Callback>> frame_callbacks;
wayland::Weak<Viewport> viewport;

private:
// only set to true if invalidate_surface_data() is called
Expand Down Expand Up @@ -142,6 +145,13 @@ class WlSurface : public wayland::Surface
void commit(WlSurfaceState const& state);
auto confine_pointer_state() const -> MirPointerConfinementState;

/**
* Associate a viewport (buffer scale & crop metadata) with this surface
*
* \throws A std::logic_error if the surface already has a viewport associated
*/
void associate_viewport(wayland::Weak<Viewport> viewport);

std::shared_ptr<scene::Session> const session;
std::shared_ptr<compositor::BufferStream> const stream;

Expand All @@ -155,14 +165,24 @@ class WlSurface : public wayland::Surface
NullWlSurfaceRole null_role;
WlSurfaceRole* role;
std::vector<WlSubsurface*> children; // ordering is from bottom to top
/* We might need to resubmit the current buffer, but with different metadata
* For example: if a client commits a wl_surface.buffer_scale() without attaching
* a new buffer, we need to generate a new frame with the same content, but at the
* new scale.
*
* To simplify other interfaces, keep a reference to the current content so we can
* hide this, here, in the frontend.
*/
std::shared_ptr<graphics::Buffer> current_buffer;

WlSurfaceState pending;
geometry::Displacement offset_;
float inv_scale{1.0f};
int32_t scale{1};
std::optional<geometry::Size> buffer_size_;
std::vector<wayland::Weak<WlSurfaceState::Callback>> frame_callbacks;
std::optional<std::vector<mir::geometry::Rectangle>> input_shape;
std::vector<SceneSurfaceCreatedCallback> scene_surface_created_callbacks;
wayland::Weak<Viewport> viewport;

void send_frame_callbacks();

Expand Down
Loading
Loading