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