summaryrefslogtreecommitdiff
path: root/tests/wayland/test_reconnect.c
blob: 737afb2fb9ee98d72bada1e65c073ad5c0c8d9fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* Compositor restart: run by helpers/run_reconnect.py, which kills the compositor
 * after "ready" and starts a new one after "lost". Windows must survive, keep
 * their pixels, and accept new GL and Vulkan objects after DISPLAY_RESTORED. */
#include <yasl/yasl.h>
#include <yasl/cpu.h>
#include <yasl/gl.h>
#include <yasl/native/wayland.h>
#if TEST_VULKAN
#include <yasl/vulkan.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CHECK(cond, what) do { if (!(cond)) { const char *why; why = strerror(errno); \
    fprintf(stderr, "reconnect: %s failed at line %d (%s)\n", what, __LINE__, why); exit(1); } } while (0)

typedef void (*clear_color_fn)(float, float, float, float);
typedef void (*clear_fn)(unsigned);

static yasl_surface *cpu, *gl;

/* Waits up to timeout_ms for an event of the given type, failing on any CLOSE. */
static yasl_event expect(enum yasl_event_type type, yasl_surface *surface, int timeout_ms)
{
    yasl_event e;
    for (int waited = 0; waited < timeout_ms; waited += 50) {
        CHECK(yasl_wait_event(&e, 50) >= 0, "wait");
        CHECK(e.type != YASL_EVENT_CLOSE, "keeping windows open");
        if (e.type == type && (!surface || e.surface == surface)) return e;
    }
    fprintf(stderr, "reconnect: timed out waiting for event %d\n", type);
    exit(1);
}

static void draw_gl(float red)
{
    CHECK((yasl_gl_make_current(gl) == 0), "binding GL");
    clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor");
    clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear");
    CHECK(clear_color && clear, "loading GL procedures");
    clear_color(red, 0.2f, 0.3f, 1);
    clear(0x00004000u);
    CHECK((yasl_gl_present(gl) == 0), "swapping GL buffers");
}

#if TEST_VULKAN
static VkInstance instance;
static yasl_surface *vk;

static VkSurfaceKHR vulkan_surface(void)
{
    VkSurfaceKHR out = VK_NULL_HANDLE;
    CHECK(yasl_vulkan_create_surface(vk, instance, NULL, &out) == VK_SUCCESS, "creating Vulkan surface");
    return out;
}
#endif

int main(void)
{
    CHECK((yasl_init(&(struct yasl_options){.backend=YASL_BACKEND_WAYLAND}) == 0), "selecting Wayland");
    cpu = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect CPU", .width=160, .height=120, .type=YASL_SURFACE_CPU, .flags=0});
    CHECK(cpu, "creating CPU window");
    gl = yasl_gl_surface_create(&(struct yasl_surface_options){.title="reconnect GL", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0}, NULL);
    CHECK(gl, "creating GL window");

    expect(YASL_EVENT_REFRESH, cpu, 5000);
    int w, h;
    struct yasl_cpu_buffer buffer;
    CHECK(yasl_cpu_acquire(cpu, &buffer) == 0, "acquire CPU buffer");
    w=buffer.width; h=buffer.height;
    uint32_t *pixels = buffer.pixels;
    CHECK(pixels, "getting pixels");
    for (size_t i = 0; i < (size_t)w * h; i++) pixels[i] = 0xff336699u;
    CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting");
    draw_gl(0.8f);

#if TEST_VULKAN
    CHECK((yasl_vulkan_init(vkGetInstanceProcAddr) == 0), "loading Vulkan");
    vk = yasl_surface_create(&(struct yasl_surface_options){.title="reconnect Vulkan", .width=160, .height=120, .type=YASL_SURFACE_NATIVE, .flags=0});
    CHECK(vk, "creating Vulkan window");
    struct yasl_vulkan_requirements requirements;
    CHECK(yasl_vulkan_get_requirements(vk, &requirements) == 0, "Vulkan requirements");
    VkInstanceCreateInfo info = {.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
        .flags = requirements.flags, .enabledExtensionCount = requirements.extension_count,
        .ppEnabledExtensionNames = requirements.extensions};
    CHECK(vkCreateInstance(&info, NULL, &instance) == VK_SUCCESS, "creating Vulkan instance");
    CHECK(vulkan_surface() != VK_NULL_HANDLE, "Vulkan handle");
#endif

    puts("ready");
    fflush(stdout);

    expect(YASL_EVENT_DISPLAY_LOST, NULL, 10000);
    struct yasl_wayland_handles native;
    CHECK(yasl_wayland_get_native(cpu, &native) == -ENOTCONN && !native.display && !native.surface,
        "rejecting native handles while offline");
    CHECK(yasl_cpu_acquire(cpu, &buffer) == -ENOTCONN, "rejecting CPU acquisition while offline");
    CHECK(yasl_gl_make_current(gl) == -ENOTCONN, "rejecting GL binding while offline");
#if TEST_VULKAN
    CHECK(yasl_vulkan_destroy_surface(vk) == 0, "destroying Vulkan surface");
#endif
    CHECK(!yasl_surface_create(&(struct yasl_surface_options){.title="offline", .width=64, .height=64, .type=YASL_SURFACE_CPU, .flags=0}), "refusing windows while offline");
    CHECK(errno == ENOTCONN, "reporting the disconnect");
    puts("lost");
    fflush(stdout);

    expect(YASL_EVENT_DISPLAY_RESTORED, NULL, 15000);
    CHECK(yasl_wayland_get_native(cpu, &native) == 0 && native.display && native.surface,
        "querying restored native handles");
    expect(YASL_EVENT_REFRESH, cpu, 5000);
    int rw, rh;
    CHECK(yasl_cpu_acquire(cpu, &buffer) == 0, "reacquire CPU buffer");
    rw=buffer.width; rh=buffer.height; pixels=buffer.pixels;
    CHECK(pixels && rw == w && rh == h, "keeping the window size");
    CHECK(pixels[0] == 0xff336699u && pixels[(size_t)w * h - 1] == 0xff336699u, "keeping the pixels");
    CHECK((yasl_cpu_present(cpu, &buffer) == 0), "presenting after reconnect");
    draw_gl(0.1f);
#if TEST_VULKAN
    CHECK(vulkan_surface() != VK_NULL_HANDLE, "restored Vulkan handle");
    CHECK(yasl_vulkan_destroy_surface(vk) == 0, "destroying Vulkan surface");
    vkDestroyInstance(instance, NULL);
#endif

    /* A second window proves the new connection accepts creation. */
    CHECK(yasl_surface_create(&(struct yasl_surface_options){.title="after reconnect", .width=64, .height=64, .type=YASL_SURFACE_CPU, .flags=0}), "creating a window after reconnect");
    yasl_gl_make_current(NULL);
    yasl_shutdown();
    puts("restored");
    return 0;
}