1 #include <assert.h>
2 #include <GLES2/gl2.h>
3 #include <GLES2/gl2ext.h>
4 #include <stdlib.h>
5 #include <wlr/interfaces/wlr_output.h>
6 #include <wlr/render/wlr_renderer.h>
7 #include <wlr/util/log.h>
8 #include "backend/headless.h"
9 #include "util/signal.h"
10
headless_output_from_output(struct wlr_output * wlr_output)11 static struct wlr_headless_output *headless_output_from_output(
12 struct wlr_output *wlr_output) {
13 assert(wlr_output_is_headless(wlr_output));
14 return (struct wlr_headless_output *)wlr_output;
15 }
16
create_fbo(struct wlr_headless_output * output,unsigned int width,unsigned int height)17 static bool create_fbo(struct wlr_headless_output *output,
18 unsigned int width, unsigned int height) {
19 if (!wlr_egl_make_current(output->backend->egl, EGL_NO_SURFACE, NULL)) {
20 return false;
21 }
22
23 GLuint rbo;
24 glGenRenderbuffers(1, &rbo);
25 glBindRenderbuffer(GL_RENDERBUFFER, rbo);
26 glRenderbufferStorage(GL_RENDERBUFFER, output->backend->internal_format,
27 width, height);
28 glBindRenderbuffer(GL_RENDERBUFFER, 0);
29
30 GLuint fbo;
31 glGenFramebuffers(1, &fbo);
32 glBindFramebuffer(GL_FRAMEBUFFER, fbo);
33 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
34 GL_RENDERBUFFER, rbo);
35 GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
36 glBindFramebuffer(GL_FRAMEBUFFER, 0);
37
38 wlr_egl_unset_current(output->backend->egl);
39
40 if (status != GL_FRAMEBUFFER_COMPLETE) {
41 wlr_log(WLR_ERROR, "Failed to create FBO");
42 return false;
43 }
44
45 output->fbo = fbo;
46 output->rbo = rbo;
47 return true;
48 }
49
destroy_fbo(struct wlr_headless_output * output)50 static void destroy_fbo(struct wlr_headless_output *output) {
51 if (!wlr_egl_make_current(output->backend->egl, EGL_NO_SURFACE, NULL)) {
52 return;
53 }
54
55 glDeleteFramebuffers(1, &output->fbo);
56 glDeleteRenderbuffers(1, &output->rbo);
57
58 wlr_egl_unset_current(output->backend->egl);
59
60 output->fbo = 0;
61 output->rbo = 0;
62 }
63
output_set_custom_mode(struct wlr_output * wlr_output,int32_t width,int32_t height,int32_t refresh)64 static bool output_set_custom_mode(struct wlr_output *wlr_output, int32_t width,
65 int32_t height, int32_t refresh) {
66 struct wlr_headless_output *output =
67 headless_output_from_output(wlr_output);
68
69 if (refresh <= 0) {
70 refresh = HEADLESS_DEFAULT_REFRESH;
71 }
72
73 destroy_fbo(output);
74 if (!create_fbo(output, width, height)) {
75 wlr_output_destroy(wlr_output);
76 return false;
77 }
78
79 output->frame_delay = 1000000 / refresh;
80
81 wlr_output_update_custom_mode(&output->wlr_output, width, height, refresh);
82 return true;
83 }
84
output_attach_render(struct wlr_output * wlr_output,int * buffer_age)85 static bool output_attach_render(struct wlr_output *wlr_output,
86 int *buffer_age) {
87 struct wlr_headless_output *output =
88 headless_output_from_output(wlr_output);
89
90 if (!wlr_egl_make_current(output->backend->egl, EGL_NO_SURFACE, NULL)) {
91 return false;
92 }
93
94 glBindFramebuffer(GL_FRAMEBUFFER, output->fbo);
95
96 if (buffer_age != NULL) {
97 *buffer_age = 0; // We only have one buffer
98 }
99 return true;
100 }
101
output_test(struct wlr_output * wlr_output)102 static bool output_test(struct wlr_output *wlr_output) {
103 if (wlr_output->pending.committed & WLR_OUTPUT_STATE_ENABLED) {
104 wlr_log(WLR_DEBUG, "Cannot disable a headless output");
105 return false;
106 }
107
108 if (wlr_output->pending.committed & WLR_OUTPUT_STATE_MODE) {
109 assert(wlr_output->pending.mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM);
110 }
111
112 return true;
113 }
114
output_commit(struct wlr_output * wlr_output)115 static bool output_commit(struct wlr_output *wlr_output) {
116 struct wlr_headless_output *output =
117 headless_output_from_output(wlr_output);
118
119 if (!output_test(wlr_output)) {
120 return false;
121 }
122
123 if (wlr_output->pending.committed & WLR_OUTPUT_STATE_MODE) {
124 if (!output_set_custom_mode(wlr_output,
125 wlr_output->pending.custom_mode.width,
126 wlr_output->pending.custom_mode.height,
127 wlr_output->pending.custom_mode.refresh)) {
128 return false;
129 }
130 }
131
132 if (wlr_output->pending.committed & WLR_OUTPUT_STATE_BUFFER) {
133 glBindFramebuffer(GL_FRAMEBUFFER, 0);
134 wlr_egl_unset_current(output->backend->egl);
135
136 // Nothing needs to be done for FBOs
137 wlr_output_send_present(wlr_output, NULL);
138 }
139
140 return true;
141 }
142
output_rollback_render(struct wlr_output * wlr_output)143 static void output_rollback_render(struct wlr_output *wlr_output) {
144 struct wlr_headless_output *output =
145 headless_output_from_output(wlr_output);
146 assert(wlr_egl_is_current(output->backend->egl));
147 glBindFramebuffer(GL_FRAMEBUFFER, 0);
148 wlr_egl_unset_current(output->backend->egl);
149 }
150
output_destroy(struct wlr_output * wlr_output)151 static void output_destroy(struct wlr_output *wlr_output) {
152 struct wlr_headless_output *output =
153 headless_output_from_output(wlr_output);
154 wl_list_remove(&output->link);
155 wl_event_source_remove(output->frame_timer);
156 destroy_fbo(output);
157 free(output);
158 }
159
160 static const struct wlr_output_impl output_impl = {
161 .destroy = output_destroy,
162 .attach_render = output_attach_render,
163 .commit = output_commit,
164 .rollback_render = output_rollback_render,
165 };
166
wlr_output_is_headless(struct wlr_output * wlr_output)167 bool wlr_output_is_headless(struct wlr_output *wlr_output) {
168 return wlr_output->impl == &output_impl;
169 }
170
signal_frame(void * data)171 static int signal_frame(void *data) {
172 struct wlr_headless_output *output = data;
173 wlr_output_send_frame(&output->wlr_output);
174 wl_event_source_timer_update(output->frame_timer, output->frame_delay);
175 return 0;
176 }
177
wlr_headless_add_output(struct wlr_backend * wlr_backend,unsigned int width,unsigned int height)178 struct wlr_output *wlr_headless_add_output(struct wlr_backend *wlr_backend,
179 unsigned int width, unsigned int height) {
180 struct wlr_headless_backend *backend =
181 headless_backend_from_backend(wlr_backend);
182
183 struct wlr_headless_output *output =
184 calloc(1, sizeof(struct wlr_headless_output));
185 if (output == NULL) {
186 wlr_log(WLR_ERROR, "Failed to allocate wlr_headless_output");
187 return NULL;
188 }
189 output->backend = backend;
190 wlr_output_init(&output->wlr_output, &backend->backend, &output_impl,
191 backend->display);
192 struct wlr_output *wlr_output = &output->wlr_output;
193
194 if (!create_fbo(output, width, height)) {
195 goto error;
196 }
197
198 output_set_custom_mode(wlr_output, width, height, 0);
199 strncpy(wlr_output->make, "headless", sizeof(wlr_output->make));
200 strncpy(wlr_output->model, "headless", sizeof(wlr_output->model));
201 snprintf(wlr_output->name, sizeof(wlr_output->name), "HEADLESS-%zd",
202 ++backend->last_output_num);
203
204 char description[128];
205 snprintf(description, sizeof(description),
206 "Headless output %zd", backend->last_output_num);
207 wlr_output_set_description(wlr_output, description);
208
209 if (!output_attach_render(wlr_output, NULL)) {
210 goto error;
211 }
212
213 wlr_renderer_begin(backend->renderer, wlr_output->width, wlr_output->height);
214 wlr_renderer_clear(backend->renderer, (float[]){ 1.0, 1.0, 1.0, 1.0 });
215 wlr_renderer_end(backend->renderer);
216
217 struct wl_event_loop *ev = wl_display_get_event_loop(backend->display);
218 output->frame_timer = wl_event_loop_add_timer(ev, signal_frame, output);
219
220 wl_list_insert(&backend->outputs, &output->link);
221
222 if (backend->started) {
223 wl_event_source_timer_update(output->frame_timer, output->frame_delay);
224 wlr_output_update_enabled(wlr_output, true);
225 wlr_signal_emit_safe(&backend->backend.events.new_output, wlr_output);
226 }
227
228 return wlr_output;
229
230 error:
231 wlr_output_destroy(&output->wlr_output);
232 return NULL;
233 }
234