Skip to content

Commit d1fe241

Browse files
committed
Merge pull request #106614 from stuartcarnie/macos_embedded_gl_vsync
macOS: Support vsync when embedding OpenGL processes
2 parents 8bd4285 + aae3370 commit d1fe241

10 files changed

+216
-63
lines changed

platform/macos/display_server_embedded.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,15 @@ struct DisplayServerEmbeddedState {
4343
/// Default to a scale of 2.0, which is the most common.
4444
float screen_max_scale = 2.0f;
4545
float screen_dpi = 96.0f;
46+
/// The display ID of the window which is displaying the the embedded process content.
47+
uint32_t display_id = -1;
4648

4749
void serialize(PackedByteArray &r_data);
4850
Error deserialize(const PackedByteArray &p_data);
51+
52+
_FORCE_INLINE_ bool operator==(const DisplayServerEmbeddedState &p_other) const {
53+
return screen_max_scale == p_other.screen_max_scale && screen_dpi == p_other.screen_dpi && display_id == p_other.display_id;
54+
}
4955
};
5056

5157
class DisplayServerEmbedded : public DisplayServer {

platform/macos/display_server_embedded.mm

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
if (err != OK) {
136136
ERR_FAIL_MSG("Could not create OpenGL context.");
137137
}
138+
gl_manager->set_vsync_enabled(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
138139
}
139140
#endif
140141

@@ -708,15 +709,44 @@
708709
}
709710

710711
void DisplayServerEmbedded::set_state(const DisplayServerEmbeddedState &p_state) {
712+
if (state == p_state) {
713+
return;
714+
}
715+
716+
uint32_t old_display_id = state.display_id;
717+
711718
state = p_state;
719+
720+
if (state.display_id != old_display_id) {
721+
#if defined(GLES3_ENABLED)
722+
if (gl_manager) {
723+
gl_manager->set_display_id(state.display_id);
724+
}
725+
#endif
726+
}
712727
}
713728

714729
void DisplayServerEmbedded::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window) {
715-
// Not supported
730+
#if defined(GLES3_ENABLED)
731+
if (gl_manager) {
732+
gl_manager->set_vsync_enabled(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
733+
}
734+
#endif
735+
736+
#if defined(RD_ENABLED)
737+
if (rendering_context) {
738+
rendering_context->window_set_vsync_mode(p_window, p_vsync_mode);
739+
}
740+
#endif
716741
}
717742

718743
DisplayServer::VSyncMode DisplayServerEmbedded::window_get_vsync_mode(WindowID p_window) const {
719744
_THREAD_SAFE_METHOD_
745+
#if defined(GLES3_ENABLED)
746+
if (gl_manager) {
747+
return (gl_manager->is_vsync_enabled() ? DisplayServer::VSyncMode::VSYNC_ENABLED : DisplayServer::VSyncMode::VSYNC_DISABLED);
748+
}
749+
#endif
720750
#if defined(RD_ENABLED)
721751
if (rendering_context) {
722752
return rendering_context->window_get_vsync_mode(p_window);
@@ -762,14 +792,15 @@
762792
}
763793

764794
void DisplayServerEmbeddedState::serialize(PackedByteArray &r_data) {
765-
r_data.resize(8);
795+
r_data.resize(12);
766796

767797
uint8_t *data = r_data.ptrw();
768798
data += encode_float(screen_max_scale, data);
769799
data += encode_float(screen_dpi, data);
800+
data += encode_uint32(display_id, data);
770801

771802
// Assert we had enough space.
772-
DEV_ASSERT(data - r_data.ptrw() >= r_data.size());
803+
DEV_ASSERT((data - r_data.ptrw()) >= r_data.size());
773804
}
774805

775806
Error DisplayServerEmbeddedState::deserialize(const PackedByteArray &p_data) {
@@ -778,6 +809,8 @@
778809
screen_max_scale = decode_float(data);
779810
data += sizeof(float);
780811
screen_dpi = decode_float(data);
812+
data += sizeof(float);
813+
display_id = decode_uint32(data);
781814

782815
return OK;
783816
}

platform/macos/display_server_macos.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ class DisplayServerMacOS : public DisplayServer {
101101

102102
Vector<Vector2> mpath;
103103

104+
CGDirectDisplayID display_id = -1;
105+
104106
Point2i mouse_pos;
105107
WindowResizeEdge edge = WINDOW_EDGE_MAX;
106108

@@ -253,10 +255,12 @@ class DisplayServerMacOS : public DisplayServer {
253255
void initialize_tts() const;
254256

255257
struct EmbeddedProcessData {
256-
const EmbeddedProcessMacOS *process;
258+
EmbeddedProcessMacOS *process;
259+
WindowData *wd = nullptr;
257260
CALayer *layer_host = nil;
258261
};
259262
HashMap<OS::ProcessID, EmbeddedProcessData> embedded_processes;
263+
void _window_update_display_id(WindowData *p_wd);
260264

261265
public:
262266
void menu_callback(id p_sender);
@@ -294,6 +298,10 @@ class DisplayServerMacOS : public DisplayServer {
294298

295299
bool is_always_on_top_recursive(WindowID p_window) const;
296300

301+
/**
302+
* Get the display ID of a window.
303+
*/
304+
uint32_t window_get_display_id(WindowID p_window) const;
297305
void window_destroy(WindowID p_window);
298306
void window_resize(WindowID p_window, int p_width, int p_height);
299307
void window_set_custom_window_buttons(WindowData &p_wd, bool p_enabled);
@@ -461,7 +469,7 @@ class DisplayServerMacOS : public DisplayServer {
461469

462470
virtual void enable_for_stealing_focus(OS::ProcessID pid) override;
463471
#ifdef TOOLS_ENABLED
464-
Error embed_process_update(WindowID p_window, const EmbeddedProcessMacOS *p_process);
472+
Error embed_process_update(WindowID p_window, EmbeddedProcessMacOS *p_process);
465473
#endif
466474
virtual Error request_close_embedded_process(OS::ProcessID p_pid) override;
467475
virtual Error remove_embedded_process(OS::ProcessID p_pid) override;

platform/macos/display_server_macos.mm

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
#include "scene/resources/image_texture.h"
5555

5656
#ifdef TOOLS_ENABLED
57+
#import "display_server_embedded.h"
5758
#import "editor/embedded_process_macos.h"
5859
#endif
5960

@@ -87,7 +88,7 @@
8788
{
8889
WindowData &wd = windows[id];
8990

90-
wd.window_delegate = [[GodotWindowDelegate alloc] init];
91+
wd.window_delegate = [[GodotWindowDelegate alloc] initWithDisplayServer:this];
9192
ERR_FAIL_NULL_V_MSG(wd.window_delegate, INVALID_WINDOW_ID, "Can't create a window delegate");
9293
[wd.window_delegate setWindowID:id];
9394

@@ -2196,6 +2197,8 @@
21962197
WindowData &wd = windows[p_window];
21972198
NSScreen *screen = [wd.window_object screen];
21982199

2200+
_window_update_display_id(&wd);
2201+
21992202
if (wd.transient_parent != INVALID_WINDOW_ID) {
22002203
WindowData &wd_parent = windows[wd.transient_parent];
22012204
NSScreen *parent_screen = [wd_parent.window_object screen];
@@ -3284,9 +3287,34 @@
32843287
ERR_FAIL_V(m_retval); \
32853288
}
32863289

3290+
uint32_t DisplayServerMacOS::window_get_display_id(WindowID p_window) const {
3291+
const WindowData *wd;
3292+
GET_OR_FAIL_V(wd, windows, p_window, -1);
3293+
return wd->display_id;
3294+
}
3295+
3296+
void DisplayServerMacOS::_window_update_display_id(WindowData *p_wd) {
3297+
NSScreen *screen = [p_wd->window_object screen];
3298+
CGDirectDisplayID display_id = [[screen deviceDescription][@"NSScreenNumber"] unsignedIntValue];
3299+
if (p_wd->display_id == display_id) {
3300+
return;
3301+
}
3302+
3303+
p_wd->display_id = display_id;
3304+
3305+
#ifdef TOOLS_ENABLED
3306+
// Notify any embedded processes of the new display ID, so that they can potentially update their vsync.
3307+
for (KeyValue<OS::ProcessID, EmbeddedProcessData> &E : embedded_processes) {
3308+
if (E.value.wd == p_wd) {
3309+
E.value.process->display_state_changed();
3310+
}
3311+
}
3312+
#endif
3313+
}
3314+
32873315
#ifdef TOOLS_ENABLED
32883316

3289-
Error DisplayServerMacOS::embed_process_update(WindowID p_window, const EmbeddedProcessMacOS *p_process) {
3317+
Error DisplayServerMacOS::embed_process_update(WindowID p_window, EmbeddedProcessMacOS *p_process) {
32903318
_THREAD_SAFE_METHOD_
32913319

32923320
WindowData *wd;
@@ -3303,6 +3331,7 @@
33033331
ed = &embedded_processes.insert(p_pid, EmbeddedProcessData())->value;
33043332

33053333
ed->process = p_process;
3334+
ed->wd = wd;
33063335

33073336
CALayerHost *host = [CALayerHost new];
33083337
uint32_t p_context_id = p_process->get_context_id();

platform/macos/editor/embedded_process_macos.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class EmbeddedProcessMacOS final : public EmbeddedProcessBase {
7979
// Helper functions.
8080

8181
void _try_embed_process();
82-
void update_embedded_process() const;
82+
void update_embedded_process();
8383
void _joy_connection_changed(int p_index, bool p_connected) const;
8484

8585
protected:
@@ -113,6 +113,8 @@ class EmbeddedProcessMacOS final : public EmbeddedProcessBase {
113113

114114
_FORCE_INLINE_ LayerHost *get_layer_host() const { return layer_host; }
115115

116+
void display_state_changed();
117+
116118
// MARK: - Embedded process state
117119
_FORCE_INLINE_ DisplayServer::MouseMode get_mouse_mode() const { return mouse_mode; }
118120

platform/macos/editor/embedded_process_macos.mm

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
}
5454
}
5555

56-
void EmbeddedProcessMacOS::update_embedded_process() const {
56+
void EmbeddedProcessMacOS::update_embedded_process() {
5757
layer_host->set_rect(get_adjusted_embedded_window_rect(get_rect()));
5858
if (is_embedding_completed()) {
5959
ds->embed_process_update(window->get_window_id(), this);
@@ -130,24 +130,28 @@
130130
}
131131
}
132132

133+
void EmbeddedProcessMacOS::display_state_changed() {
134+
DisplayServerEmbeddedState state;
135+
state.screen_max_scale = ds->screen_get_max_scale();
136+
state.screen_dpi = ds->screen_get_dpi();
137+
state.display_id = ds->window_get_display_id(window->get_window_id());
138+
PackedByteArray data;
139+
state.serialize(data);
140+
script_debugger->send_message("embed:ds_state", { data });
141+
}
142+
133143
void EmbeddedProcessMacOS::_try_embed_process() {
134144
if (current_process_id == 0 || script_debugger == nullptr || context_id == 0) {
135145
return;
136146
}
137147

138-
Error err = ds->embed_process_update(window->get_window_id(), this);
148+
DisplayServer::WindowID wid = window->get_window_id();
149+
Error err = ds->embed_process_update(wid, this);
139150
if (err == OK) {
140151
layer_host->set_rect(get_adjusted_embedded_window_rect(get_rect()));
141152

142-
// Replicate some of the DisplayServer state.
143-
{
144-
DisplayServerEmbeddedState state;
145-
state.screen_max_scale = ds->screen_get_max_scale();
146-
state.screen_dpi = ds->screen_get_dpi();
147-
PackedByteArray data;
148-
state.serialize(data);
149-
script_debugger->send_message("embed:ds_state", { data });
150-
}
153+
// Replicate important DisplayServer state.
154+
display_state_changed();
151155

152156
Rect2i rect = get_screen_embedded_window_rect();
153157
script_debugger->send_message("embed:window_size", { rect.size });

platform/macos/embedded_gl_manager.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ class GLManagerEmbedded {
5353
/// Triple-buffering is used to avoid stuttering.
5454
static constexpr uint32_t BUFFER_COUNT = 3;
5555

56+
// The display ID for which vsync is used. If this value is -1, vsync is disabled.
57+
constexpr static uint32_t INVALID_DISPLAY_ID = static_cast<uint32_t>(-1);
58+
5659
struct FrameBuffer {
5760
IOSurfaceRef surface = nullptr;
5861
unsigned int tex = 0;
@@ -86,12 +89,25 @@ class GLManagerEmbedded {
8689
CGLTexImageIOSurface2DPtr CGLTexImageIOSurface2D = nullptr;
8790
CGLErrorStringPtr CGLErrorString = nullptr;
8891

92+
uint32_t display_id = INVALID_DISPLAY_ID;
93+
CVDisplayLinkRef display_link;
94+
bool vsync_enabled = false;
95+
bool display_link_running = false;
96+
dispatch_semaphore_t display_semaphore = nullptr;
97+
98+
void create_display_link();
99+
void release_display_link();
100+
89101
public:
90102
Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_layer, int p_width, int p_height);
91103
void window_destroy(DisplayServer::WindowID p_window_id);
92104
void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
93105
Size2i window_get_size(DisplayServer::WindowID p_window_id) const;
94106

107+
void set_display_id(uint32_t p_display_id);
108+
void set_vsync_enabled(bool p_enabled);
109+
bool is_vsync_enabled() const { return vsync_enabled; }
110+
95111
void release_current();
96112
void swap_buffers();
97113

platform/macos/embedded_gl_manager.mm

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@
237237
}
238238
last_valid = true;
239239

240+
if (display_link_running) {
241+
dispatch_semaphore_wait(display_semaphore, DISPATCH_TIME_FOREVER);
242+
}
243+
240244
[CATransaction begin];
241245
[CATransaction setDisableActions:YES];
242246
win.layer.contents = (__bridge id)win.framebuffers[win.current_fb].surface;
@@ -249,7 +253,65 @@
249253
return framework_loaded ? OK : ERR_CANT_CREATE;
250254
}
251255

256+
void GLManagerEmbedded::create_display_link() {
257+
DEV_ASSERT(display_link == nullptr);
258+
259+
CVReturn err = CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &display_link);
260+
ERR_FAIL_COND_MSG(err != kCVReturnSuccess, "Failed to create display link.");
261+
262+
__block dispatch_semaphore_t local_semaphore = display_semaphore;
263+
264+
CVDisplayLinkSetOutputHandler(display_link, ^CVReturn(CVDisplayLinkRef p_display_link, const CVTimeStamp *p_now, const CVTimeStamp *p_output_time, CVOptionFlags p_flags, CVOptionFlags *p_flags_out) {
265+
dispatch_semaphore_signal(local_semaphore);
266+
return kCVReturnSuccess;
267+
});
268+
}
269+
270+
void GLManagerEmbedded::release_display_link() {
271+
DEV_ASSERT(display_link != nullptr);
272+
if (CVDisplayLinkIsRunning(display_link)) {
273+
CVDisplayLinkStop(display_link);
274+
}
275+
CVDisplayLinkRelease(display_link);
276+
display_link = nullptr;
277+
}
278+
279+
void GLManagerEmbedded::set_display_id(uint32_t p_display_id) {
280+
if (display_id == p_display_id) {
281+
return;
282+
}
283+
284+
CVReturn err = CVDisplayLinkSetCurrentCGDisplay(display_link, static_cast<CGDirectDisplayID>(p_display_id));
285+
ERR_FAIL_COND_MSG(err != kCVReturnSuccess, "Failed to set display ID for display link.");
286+
}
287+
288+
void GLManagerEmbedded::set_vsync_enabled(bool p_enabled) {
289+
if (p_enabled == vsync_enabled) {
290+
return;
291+
}
292+
293+
vsync_enabled = p_enabled;
294+
295+
if (vsync_enabled) {
296+
if (!CVDisplayLinkIsRunning(display_link)) {
297+
CVReturn err = CVDisplayLinkStart(display_link);
298+
ERR_FAIL_COND_MSG(err != kCVReturnSuccess, "Failed to start display link.");
299+
display_link_running = true;
300+
}
301+
} else {
302+
if (CVDisplayLinkIsRunning(display_link)) {
303+
CVReturn err = CVDisplayLinkStop(display_link);
304+
ERR_FAIL_COND_MSG(err != kCVReturnSuccess, "Failed to stop display link.");
305+
display_link_running = false;
306+
}
307+
}
308+
}
309+
252310
GLManagerEmbedded::GLManagerEmbedded() {
311+
display_semaphore = dispatch_semaphore_create(BUFFER_COUNT);
312+
313+
create_display_link();
314+
253315
NSBundle *framework = [NSBundle bundleWithIdentifier:@"com.apple.opengl"];
254316
if ([framework load]) {
255317
void *library_handle = dlopen([framework.executablePath UTF8String], RTLD_NOW);
@@ -263,6 +325,7 @@
263325
}
264326

265327
GLManagerEmbedded::~GLManagerEmbedded() {
328+
release_display_link();
266329
release_current();
267330
}
268331

0 commit comments

Comments
 (0)