58 if (
auto handle = CreateEventW(NULL, TRUE, TRUE, NULL)) {
59 _use_vsync_handle = handle;
61 hi_log_fatal(
"Could not create an use-vsync handle. {}", get_last_error_message());
65 if (
auto handle = CreateEventW(NULL, FALSE, FALSE, NULL)) {
66 _handles.push_back(handle);
67 _sockets.push_back(-1);
68 _socket_functions.emplace_back();
70 hi_log_fatal(
"Could not create an vsync-event handle. {}", get_last_error_message());
74 if (
auto handle = CreateEventW(NULL, FALSE, FALSE, NULL)) {
75 _handles.push_back(handle);
76 _sockets.push_back(-1);
77 _socket_functions.emplace_back();
79 hi_log_fatal(
"Could not create an async-event handle. {}", get_last_error_message());
86 while (_handles.size() > _socket_handle_idx) {
87 if (not WSACloseEvent(_handles.back())) {
88 hi_log_error(
"Could not clock socket event handle for socket {}. {}", _sockets.back(), get_last_error_message());
95 if (_vsync_thread.joinable()) {
96 _vsync_thread.request_stop();
100 if (not CloseHandle(_handles[_function_handle_idx])) {
101 hi_log_error(
"Could not close async-event handle. {}", get_last_error_message());
103 if (not CloseHandle(_handles[_vsync_handle_idx])) {
104 hi_log_error(
"Could not close vsync-event handle. {}", get_last_error_message());
106 if (not CloseHandle(_use_vsync_handle)) {
107 hi_log_error(
"Could not close use-vsync handle. {}", get_last_error_message());
111 void set_maximum_frame_rate(
double frame_rate)
noexcept override
113 hi_axiom(on_thread());
116 void set_vsync_monitor_id(uintptr_t
id)
noexcept override
118 _selected_monitor_id.store(
id, std::memory_order::relaxed);
123 hi_axiom(on_thread());
124 _render_functions.push_back(
std::move(f));
127 if (not _vsync_thread.joinable()) {
128 _vsync_thread = std::jthread{[
this](std::stop_token token) {
129 return vsync_thread_proc(
std::move(token));
136 hi_axiom(on_thread());
137 hi_not_implemented();
140 void remove_socket(
int fd)
override
142 hi_axiom(on_thread());
143 hi_not_implemented();
146 int resume(std::stop_token stop_token)
noexcept override
149 hilet thread_handle = GetCurrentThread();
151 int original_thread_priority = GetThreadPriority(thread_handle);
152 if (original_thread_priority == THREAD_PRIORITY_ERROR_RETURN) {
153 original_thread_priority = THREAD_PRIORITY_NORMAL;
154 hi_log_error(
"GetThreadPriority() for loop failed {}", get_last_error_message());
157 if (is_main and original_thread_priority < THREAD_PRIORITY_ABOVE_NORMAL) {
158 if (not SetThreadPriority(thread_handle, THREAD_PRIORITY_ABOVE_NORMAL)) {
159 hi_log_error(
"SetThreadPriority() for loop failed {}", get_last_error_message());
164 while (not _exit_code) {
167 if (stop_token.stop_possible()) {
168 if (stop_token.stop_requested()) {
173 if (_render_functions.empty() and _function_fifo.empty() and _function_timer.empty() and
174 _handles.size() <= _socket_handle_idx) {
182 if (is_main and original_thread_priority < THREAD_PRIORITY_ABOVE_NORMAL) {
183 if (not SetThreadPriority(thread_handle, original_thread_priority)) {
184 hi_log_error(
"SetThreadPriority() for loop failed {}", get_last_error_message());
191 void resume_once(
bool block)
noexcept override
193 using namespace std::chrono_literals;
195 hi_axiom(on_thread());
197 auto current_time = std::chrono::utc_clock::now();
198 auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(_function_timer.current_deadline() - current_time);
200 timeout = std::clamp(timeout, 0ms, 100ms);
201 hilet timeout_ms = narrow_cast<DWORD>(timeout / 1ms);
205 hilet message_mask = is_main and block ? QS_ALLINPUT : 0;
208 MsgWaitForMultipleObjects(narrow_cast<DWORD>(_handles.size()), _handles.data(), FALSE, timeout_ms, message_mask);
210 if (wait_r == WAIT_FAILED) {
211 hi_log_fatal(
"Failed on MsgWaitForMultipleObjects(), {}", get_last_error_message());
213 }
else if (wait_r == WAIT_TIMEOUT) {
217 }
else if (wait_r == WAIT_OBJECT_0 + _vsync_handle_idx) {
222 }
else if (wait_r == WAIT_OBJECT_0 + _function_handle_idx) {
226 }
else if (wait_r >= WAIT_OBJECT_0 + _socket_handle_idx and wait_r < WAIT_OBJECT_0 + _handles.size()) {
227 hilet index = wait_r - WAIT_OBJECT_0;
229 WSANETWORKEVENTS events;
230 if (WSAEnumNetworkEvents(_sockets[index], _handles[index], &events) != 0) {
231 switch (WSAGetLastError()) {
232 case WSANOTINITIALISED:
233 hi_log_fatal(
"WSAStartup was not called.");
235 hi_log_fatal(
"The network subsystem has failed.");
237 hi_log_fatal(
"One of the specified parameters was invalid.");
240 "A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a "
245 hi_log_fatal(
"The lpNetworkEvents parameter is not a valid part of the user address space.");
248 hi_log_error(
"Error during WSAEnumNetworkEvents on socket {}: {}", _sockets[index], get_last_error_message());
249 _handles.erase(_handles.begin() + index);
250 _sockets.erase(_sockets.begin() + index);
251 _socket_functions.erase(_socket_functions.begin() + index);
259 _socket_functions[index](_sockets[index], socket_events_from_win32(events));
262 }
else if (wait_r == WAIT_OBJECT_0 + _handles.size()) {
265 }
else if (wait_r >= WAIT_ABANDONED_0 and wait_r < WAIT_ABANDONED_0 + _handles.size()) {
266 hilet index = wait_r - WAIT_ABANDONED_0;
267 if (index == _vsync_handle_idx) {
268 hi_log_fatal(
"The vsync-handle has been abandoned.");
270 }
else if (index == _function_handle_idx) {
271 hi_log_fatal(
"The async-handle has been abandoned.");
275 hi_log_error(
"The socket-handle for socket {} has been abandoned.", _sockets[index]);
276 _handles.erase(_handles.begin() + index);
277 _sockets.erase(_sockets.begin() + index);
278 _socket_functions.erase(_socket_functions.begin() + index);
300 constexpr static size_t _vsync_handle_idx = 0;
301 constexpr static size_t _function_handle_idx = 1;
302 constexpr static size_t _socket_handle_idx = 2;
311 HANDLE _use_vsync_handle;
319 bool _vsync_time_from_sleep =
true;
331 uint64_t _sub_frame_count = 0;
337 uint64_t _frame_count = 0;
363 std::jthread _vsync_thread;
367 HANDLE _vsync_thread_handle;
371 int _vsync_thread_priority = THREAD_PRIORITY_NORMAL;
384 IDXGIOutput *_vsync_monitor_output =
nullptr;
386 void notify_has_send()
noexcept override
388 if (not SetEvent(_handles[_function_handle_idx])) {
389 hi_log_error(
"Could not trigger async-event. {}", get_last_error_message());
397 void handle_vsync()
noexcept
403 if (not _vsync_thread.joinable()) {
405 _vsync_time.
store(std::chrono::utc_clock::now());
410 for (
auto& render_function : _render_functions) {
411 if (
auto render_function_ = render_function.lock()) {
412 (*render_function_)(display_time);
416 std::erase_if(_render_functions, [](
auto& render_function) {
417 return render_function.expired();
420 if (_render_functions.empty()) {
422 if (_vsync_thread.joinable()) {
423 _vsync_thread.request_stop();
432 void handle_functions()
noexcept
434 _function_fifo.run_all();
437 void handle_timers()
noexcept
439 _function_timer.run_all(std::chrono::utc_clock::now());
446 void handle_gui_events()
noexcept
449 hilet t1 =
trace<
"loop:gui-events">();
450 while (PeekMessageW(&msg,
nullptr, 0, 0, PM_REMOVE | PM_NOYIELD)) {
451 hilet t2 =
trace<
"loop:gui-event">();
453 if (msg.message == WM_QUIT) {
454 _exit_code = narrow_cast<int>(msg.wParam);
458 TranslateMessage(&msg);
459 DispatchMessageW(&msg);
467 void vsync_thread_update_dxgi_output()
noexcept
469 if (not compare_store(_vsync_monitor_id, _selected_monitor_id.
load(std::memory_order::relaxed))) {
473 if (_vsync_monitor_output) {
474 _vsync_monitor_output->Release();
475 _vsync_monitor_output =
nullptr;
478 IDXGIFactory *factory =
nullptr;
479 if (FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (
void **)&factory))) {
480 hi_log_error_once(
"vsync:error:CreateDXGIFactory",
"Could not IDXGIFactory. {}", get_last_error_message());
483 hi_assert_not_null(factory);
484 auto d1 = defer([&] {
488 IDXGIAdapter *adapter =
nullptr;
489 if (FAILED(factory->EnumAdapters(0, &adapter))) {
490 hi_log_error_once(
"vsync:error:EnumAdapters",
"Could not get IDXGIAdapter. {}", get_last_error_message());
493 hi_assert_not_null(adapter);
494 auto d2 = defer([&] {
498 if (FAILED(adapter->EnumOutputs(0, &_vsync_monitor_output))) {
499 hi_log_error_once(
"vsync:error:EnumOutputs",
"Could not get IDXGIOutput. {}", get_last_error_message());
503 DXGI_OUTPUT_DESC description;
504 if (FAILED(_vsync_monitor_output->GetDesc(&description))) {
505 hi_log_error_once(
"vsync:error:GetDesc",
"Could not get IDXGIOutput description. {}", get_last_error_message());
506 _vsync_monitor_output->Release();
507 _vsync_monitor_output =
nullptr;
511 if (description.Monitor != std::bit_cast<HMONITOR>(_vsync_monitor_id)) {
512 hi_log_error_once(
"vsync:error:not-primary-monitor",
"DXGI primary monitor does not match desktop primary monitor");
513 _vsync_monitor_output->Release();
514 _vsync_monitor_output =
nullptr;
534 hilet new_time = time_stamp_utc::make(ts);
536 hilet was_sleeping = std::exchange(_vsync_time_from_sleep, on_sleep);
537 hilet old_time = _vsync_time.
exchange(new_time, std::memory_order::acquire);
543 void vsync_thread_wait_for_vblank()
noexcept
545 using namespace std::chrono_literals;
547 vsync_thread_update_dxgi_output();
549 if (_vsync_monitor_output and FAILED(_vsync_monitor_output->WaitForVBlank())) {
550 hi_log_error_once(
"vsync:error:WaitForVBlank",
"WaitForVBlank() failed. {}", get_last_error_message());
553 if (vsync_thread_update_time(
false) < 1ms) {
554 hi_log_info_once(
"vsync:monitor-off",
"WaitForVBlank() did not block; is the monitor turned off?");
558 vsync_thread_update_time(
true);
560 ++global_counter<
"vsync:vertical-blank">;
571 [[nodiscard]]
bool vsync_thread_pull_down()
noexcept
573 _sub_frame_count += _pull_down.
load(std::memory_order::relaxed);
574 return compare_store(_frame_count, _sub_frame_count >> 8);
582 void vsync_thread_update_priority(
int new_priority)
noexcept
584 if (std::exchange(_vsync_thread_priority, new_priority) != new_priority) {
585 if (not SetThreadPriority(_vsync_thread_handle, new_priority)) {
586 hi_log_error_once(
"vsync:error:SetThreadPriority",
"Could not set the vsync thread priority to {}", new_priority);
591 void vsync_thread_proc(std::stop_token stop_token)
noexcept
593 _vsync_thread_handle = GetCurrentThread();
594 set_thread_name(
"vsync");
596 while (not stop_token.stop_requested()) {
597 switch (WaitForSingleObject(_use_vsync_handle, 30)) {
600 vsync_thread_update_time(
true);
602 vsync_thread_update_priority(THREAD_PRIORITY_NORMAL);
604 ++global_counter<
"vsync:low-priority">;
605 ++global_counter<
"vsync:frame">;
606 SetEvent(_handles[_vsync_handle_idx]);
611 vsync_thread_update_priority(THREAD_PRIORITY_TIME_CRITICAL);
613 vsync_thread_wait_for_vblank();
615 if (vsync_thread_pull_down()) {
616 ++global_counter<
"vsync:frame">;
617 SetEvent(_handles[_vsync_handle_idx]);
623 hi_log_error_once(
"vsync:error:WAIT_ABANDONED",
"use_vsync_handle has been abandoned.");
624 ResetEvent(_use_vsync_handle);
628 hi_log_error_once(
"vsync:error:WAIT_FAILED",
"WaitForSingleObject failed. {}", get_last_error_message());
629 ResetEvent(_use_vsync_handle);