Sunshine v2026.319.132152
Self-hosted game stream host for Moonlight.
graphics.h
Go to the documentation of this file.
1
5#pragma once
6
7// standard includes
8#include <optional>
9#include <string_view>
10
11// lib includes
12#include <glad/egl.h>
13#include <glad/gl.h>
14
15// local includes
16#include "misc.h"
17#include "src/logging.h"
18#include "src/platform/common.h"
19#include "src/utility.h"
21
22#define SUNSHINE_STRINGIFY_HELPER(x) #x
23#define SUNSHINE_STRINGIFY(x) SUNSHINE_STRINGIFY_HELPER(x)
24#define gl_drain_errors_helper(x) gl::drain_errors(x)
25#define gl_drain_errors gl_drain_errors_helper(__FILE__ ":" SUNSHINE_STRINGIFY(__LINE__))
26
27extern "C" int close(int __fd);
28
29// X11 Display
30extern "C" struct _XDisplay;
31
32struct AVFrame;
33void free_frame(AVFrame *frame);
34
36
37namespace gl {
38 extern GladGLContext ctx;
39
40 // glEGLImageTargetTexture2DOES (GL_OES_EGL_image) is not part of desktop GL —
41 // it is a GLES extension that must be loaded manually via eglGetProcAddress.
42 // GLeglImageOES is typedef void* per the Khronos spec (gl.xml).
43 using PFNGLEGLIMAGETARGETTEXTURE2DOESPROC = void (*)(GLenum target, void *image);
44 PFNGLEGLIMAGETARGETTEXTURE2DOESPROC egl_image_target_texture_2d();
45
46 void drain_errors(const std::string_view &prefix);
47
48 class tex_t: public util::buffer_t<GLuint> {
49 using util::buffer_t<GLuint>::buffer_t;
50
51 public:
52 tex_t(tex_t &&) = default;
53 tex_t &operator=(tex_t &&) = default;
54
55 ~tex_t();
56
57 static tex_t make(std::size_t count);
58 };
59
60 class frame_buf_t: public util::buffer_t<GLuint> {
61 using util::buffer_t<GLuint>::buffer_t;
62
63 public:
64 frame_buf_t(frame_buf_t &&) = default;
65 frame_buf_t &operator=(frame_buf_t &&) = default;
66
68
69 static frame_buf_t make(std::size_t count);
70
71 inline void bind(std::nullptr_t, std::nullptr_t) {
72 int x = 0;
73 for (auto fb : (*this)) {
74 ctx.BindFramebuffer(GL_FRAMEBUFFER, fb);
75 ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, 0, 0);
76
77 ++x;
78 }
79 return;
80 }
81
82 template<class It>
83 void bind(It it_begin, It it_end) {
84 using namespace std::literals;
85 if (std::distance(it_begin, it_end) > size()) {
86 BOOST_LOG(warning) << "To many elements to bind"sv;
87 return;
88 }
89
90 int x = 0;
91 std::for_each(it_begin, it_end, [&](auto tex) {
92 ctx.BindFramebuffer(GL_FRAMEBUFFER, (*this)[x]);
93 ctx.BindTexture(GL_TEXTURE_2D, tex);
94
95 ctx.FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, tex, 0);
96
97 ++x;
98 });
99 }
100
104 void copy(int id, int texture, int offset_x, int offset_y, int width, int height);
105 };
106
107 class shader_t {
108 KITTY_USING_MOVE_T(shader_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
109 if (el != std::numeric_limits<GLuint>::max()) {
110 ctx.DeleteShader(el);
111 }
112 });
113
114 public:
115 std::string err_str();
116
117 static util::Either<shader_t, std::string> compile(const std::string_view &source, GLenum type);
118
119 GLuint handle() const;
120
121 private:
122 shader_internal_t _shader;
123 };
124
125 class buffer_t {
126 KITTY_USING_MOVE_T(buffer_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
127 if (el != std::numeric_limits<GLuint>::max()) {
128 ctx.DeleteBuffers(1, &el);
129 }
130 });
131
132 public:
133 static buffer_t make(util::buffer_t<GLint> &&offsets, const char *block, const std::string_view &data);
134
135 GLuint handle() const;
136
137 const char *block() const;
138
139 void update(const std::string_view &view, std::size_t offset = 0);
140 void update(std::string_view *members, std::size_t count, std::size_t offset = 0);
141
142 private:
143 const char *_block;
144
145 std::size_t _size;
146
147 util::buffer_t<GLint> _offsets;
148
149 buffer_internal_t _buffer;
150 };
151
152 class program_t {
153 KITTY_USING_MOVE_T(program_internal_t, GLuint, std::numeric_limits<GLuint>::max(), {
154 if (el != std::numeric_limits<GLuint>::max()) {
155 ctx.DeleteProgram(el);
156 }
157 });
158
159 public:
160 std::string err_str();
161
162 static util::Either<program_t, std::string> link(const shader_t &vert, const shader_t &frag);
163
164 void bind(const buffer_t &buffer);
165
166 std::optional<buffer_t> uniform(const char *block, std::pair<const char *, std::string_view> *members, std::size_t count);
167
168 GLuint handle() const;
169
170 private:
171 program_internal_t _program;
172 };
173} // namespace gl
174
175namespace gbm {
176 struct device;
177 typedef void (*device_destroy_fn)(device *gbm);
178 typedef device *(*create_device_fn)(int fd);
179
180 extern device_destroy_fn device_destroy;
181 extern create_device_fn create_device;
182
184
185 int init();
186
187} // namespace gbm
188
189namespace egl {
191
192 struct rgb_img_t {
193 display_t::pointer display;
194 EGLImage xrgb8;
195
196 gl::tex_t tex;
197 };
198
199 struct nv12_img_t {
200 display_t::pointer display;
201 EGLImage r8;
202 EGLImage bg88;
203
204 gl::tex_t tex;
205 gl::frame_buf_t buf;
206
207 // sizeof(va::DRMPRIMESurfaceDescriptor::objects) / sizeof(va::DRMPRIMESurfaceDescriptor::objects[0]);
208 static constexpr std::size_t num_fds = 4;
209
210 std::array<file_t, num_fds> fds;
211 };
212
213 KITTY_USING_MOVE_T(rgb_t, rgb_img_t, , {
214 if (el.xrgb8) {
215 eglDestroyImage(el.display, el.xrgb8);
216 }
217 });
218
219 KITTY_USING_MOVE_T(nv12_t, nv12_img_t, , {
220 if (el.r8) {
221 eglDestroyImage(el.display, el.r8);
222 }
223
224 if (el.bg88) {
225 eglDestroyImage(el.display, el.bg88);
226 }
227 });
228
229 KITTY_USING_MOVE_T(ctx_t, (std::tuple<display_t::pointer, EGLContext>), , {
230 TUPLE_2D_REF(disp, ctx, el);
231 if (ctx) {
232 eglMakeCurrent(disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
233 eglDestroyContext(disp, ctx);
234 }
235 });
236
238 int width;
239 int height;
240 int fds[4];
241 std::uint32_t fourcc;
242 std::uint64_t modifier;
243 std::uint32_t pitches[4];
244 std::uint32_t offsets[4];
245 };
246
247 display_t make_display(std::variant<gbm::gbm_t::pointer, wl_display *, _XDisplay *> native_display);
248 std::optional<ctx_t> make_ctx(display_t::pointer display);
249
250 std::optional<rgb_t>
251 import_source(
252 display_t::pointer egl_display,
253 const surface_descriptor_t &xrgb
254 );
255
256 rgb_t create_blank(platf::img_t &img);
257
258 std::optional<nv12_t> import_target(
259 display_t::pointer egl_display,
260 std::array<file_t, nv12_img_t::num_fds> &&fds,
261 const surface_descriptor_t &y,
262 const surface_descriptor_t &uv
263 );
264
272 std::optional<nv12_t> create_target(int width, int height, AVPixelFormat format);
273
274 class cursor_t: public platf::img_t {
275 public:
276 int x;
277 int y;
278 int src_w;
279 int src_h;
280
281 unsigned long serial;
282
283 std::vector<std::uint8_t> buffer;
284 };
285
286 // Allow cursor and the underlying image to be kept together
288 public:
290 reset();
291 }
292
293 void reset() {
294 for (auto x = 0; x < 4; ++x) {
295 if (sd.fds[x] >= 0) {
296 close(sd.fds[x]);
297
298 sd.fds[x] = -1;
299 }
300 }
301 }
302
304
305 // Increment sequence when new rgb_t needs to be created
306 std::uint64_t sequence;
307
308 // PipeWire metadata
309 std::optional<uint64_t> pts;
310 std::optional<uint64_t> seq;
311 std::optional<bool> pw_damage;
312 std::optional<uint32_t> pw_flags;
313 };
314
315 class sws_t {
316 public:
317 static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, gl::tex_t &&tex);
318 static std::optional<sws_t> make(int in_width, int in_height, int out_width, int out_height, AVPixelFormat format);
319
320 // Convert the loaded image into the first two framebuffers
321 int convert(gl::frame_buf_t &fb);
322
323 // Make an area of the image black
324 int blank(gl::frame_buf_t &fb, int offsetX, int offsetY, int width, int height);
325
326 void load_ram(platf::img_t &img);
327 void load_vram(img_descriptor_t &img, int offset_x, int offset_y, int texture);
328
329 void apply_colorspace(const video::sunshine_colorspace_t &colorspace);
330
331 // The first texture is the monitor image.
332 // The second texture is the cursor image
333 gl::tex_t tex;
334
335 // The cursor image will be blended into this framebuffer
336 gl::frame_buf_t cursor_framebuffer;
337 gl::frame_buf_t copy_framebuffer;
338
339 // Y - shader, UV - shader, Cursor - shader
340 gl::program_t program[3];
341 gl::buffer_t color_matrix;
342
343 int out_width;
344 int out_height;
345 int in_width;
346 int in_height;
347 int offsetX;
348 int offsetY;
349
350 // Pointer to the texture to be converted to nv12
351 int loaded_texture;
352
353 // Store latest cursor for load_vram
354 std::uint64_t serial;
355 };
356
357 bool fail();
358} // namespace egl
Definition graphics.h:274
Definition graphics.h:287
Definition graphics.h:315
Definition graphics.h:125
Definition graphics.h:60
void copy(int id, int texture, int offset_x, int offset_y, int width, int height)
Definition graphics.cpp:87
Definition graphics.h:152
Definition graphics.h:107
Definition graphics.h:48
Definition utility.h:499
Definition utility.h:835
Definition utility.h:530
Declarations for common platform specific utilities.
rgb_t create_blank(platf::img_t &img)
Create a black RGB texture of the specified image size.
Definition graphics.cpp:583
std::optional< nv12_t > create_target(int width, int height, AVPixelFormat format)
Create biplanar YUV textures to render into.
Definition graphics.cpp:666
Miscellaneous declarations for Linux.
Declarations for logging related functions.
Definition graphics.h:199
Definition graphics.h:192
Definition graphics.h:237
Definition common.h:369
Definition video_colorspace.h:20
Declarations for utility functions.
Declarations for colorspace functions.