blob: 1fa737444eb7c866d63093e58339c071a54b1a2a [file] [edit]
/*
* Copyright © 2018 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 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 <http://www.gnu.org/licenses/>.
*
* Authored by: Alan Griffiths <alan@octopull.co.uk>
*/
#include "in_process_server.h"
#include <gmock/gmock.h>
using namespace testing;
using namespace wlcs;
namespace
{
struct FrameSubmission : StartedInProcessServer
{
Client client{the_server()};
Surface surface{client.create_visible_surface(200, 200)};
void submit_frame(bool& frame_consumed);
void wait_for_frame(bool const& frame_consumed);
};
void FrameSubmission::submit_frame(bool& consumed_flag)
{
static wl_callback_listener const frame_listener
{
[](void *data, struct wl_callback* callback, uint32_t /*callback_data*/)
{
*static_cast<bool*>(data) = true;
wl_callback_destroy(callback);
}
};
consumed_flag = false;
wl_callback_add_listener(wl_surface_frame(surface), &frame_listener, &consumed_flag);
auto buffer = std::make_shared<wlcs::ShmBuffer>(client, 200, 200);
wl_surface_attach(surface, *buffer, 0, 0);
wl_surface_commit(surface);
}
void FrameSubmission::wait_for_frame(bool const& consumed_flag)
{
// TODO timeout
client.dispatch_until([&consumed_flag]() { return consumed_flag; });
}
}
TEST_F(FrameSubmission, post_one_frame_at_a_time)
{
for (auto i = 0; i != 10; ++i)
{
auto frame_consumed = false;
submit_frame(frame_consumed);
wait_for_frame(frame_consumed);
EXPECT_THAT(frame_consumed, Eq(true));
}
}
// Regression test for https://github.com/MirServer/mir/issues/2960
TEST_F(FrameSubmission, test_buffer_can_be_deleted_after_attached)
{
using namespace testing;
wlcs::Client client{the_server()};
auto surface = client.create_visible_surface(200, 200);
auto buffer = std::make_shared<wlcs::ShmBuffer>(client, 200, 200);
wl_surface_attach(surface, *buffer, 0, 0);
buffer.reset();
/* Check that the destroyed buffer doesn't crash the server
* It's not clear what the *correct* behaviour is in this case:
* Weston treats this as committing a null buffer, wlroots appears to
* treat this as committing the content of the buffer before deletion.
*
* *Whatever* the correct behaviour is, "crash" is *definitely*
* incorrect.
*/
wl_surface_commit(surface);
// Roundtrip to ensure the server has processed our weirdness
client.roundtrip();
}
/* Firefox has a lovely habit of sending an endless stream of
* wl_surface.frame() requests (maybe it's trying to estimate vsync?!)
*
* If a compositor responds immediately to the frame on commit, Firefox
* ends up looping endlessly. If the compositor *doesn't* respond to the frame
* request, Firefox decides not to draw anything. 🤷‍♀️
*/
TEST_F(FrameSubmission, when_client_endlessly_requests_frame_then_callbacks_are_throttled)
{
using namespace testing;
using namespace std::chrono_literals;
wlcs::Client annoying_client{the_server()};
auto surface = client.create_visible_surface(200, 200);
bool frame_callback_called{false};
wl_callback_listener const listener = {
.done = [](void* data, struct wl_callback* callback, [[maybe_unused]]uint32_t timestamp)
{
wl_callback_destroy(callback);
auto callback_called = static_cast<bool*>(data);
*callback_called = true;
}
};
auto const timeout = std::chrono::steady_clock::now() + 10s;
do
{
frame_callback_called = false;
wl_callback_add_listener(wl_surface_frame(surface), &listener, &frame_callback_called);
wl_surface_commit(surface);
client.roundtrip();
/* This roundtrip ensures the server has processed everything prior.
* In particular, if the server sends the frame callback in response to wl_surface.commit, that frame callback
* will have been processed by now.
*/
}
while (frame_callback_called && std::chrono::steady_clock::now() < timeout);
EXPECT_THAT(std::chrono::steady_clock::now(), Lt(timeout)) << "Timed out looping in frame callback storm";
}
/*
* The above test checks that frame callbacks are throttled, but not that
* they are sent at all.
*
* We also need to test that these type of buffer-less frame requests
* *do* get a callback, eventually.
*/
TEST_F(FrameSubmission, frame_requests_without_buffer_are_called_back_eventually)
{
std::array<bool, 5> called{};
wlcs::Client client{the_server()};
auto surface = client.create_visible_surface(640, 480);
for (auto i = 0u; i < called.size(); ++i)
{
surface.add_frame_callback([&called, i](auto) { called[i] = true; });
wl_surface_commit(surface);
}
client.dispatch_until([&called]() { return std::all_of(called.begin(), called.end(), [](bool value) { return value; }); });
}