Atlas - SDL_asyncio_windows_ioring.c
Home / ext / SDL / src / io / windows Lines: 1 | Size: 21574 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2026 Sam Lantinga <[email protected]> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20*/ 21 22// The Windows backend uses IoRing for asynchronous i/o, and falls back to 23// the "generic" threadpool implementation if it isn't available or 24// fails for some other reason. IoRing was introduced in Windows 11. 25 26#include "SDL_internal.h" 27#include "../SDL_sysasyncio.h" 28 29#ifdef HAVE_IORINGAPI_H 30 31#include "../../core/windows/SDL_windows.h" 32#include <ioringapi.h> 33 34// Don't know what the lowest usable version is, but this seems safe. 35#define SDL_REQUIRED_IORING_VERSION IORING_VERSION_3 36 37static SDL_InitState ioring_init; 38 39// We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now. 40static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue); 41static void (*QuitAsyncIO)(void); 42static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio); 43 44// we never link directly to ioring. 45static const char *ioring_library = "KernelBase.dll"; 46static void *ioring_handle = NULL; 47 48#define SDL_IORING_FUNCS \ 49 SDL_IORING_FUNC(HRESULT, QueryIoRingCapabilities, (IORING_CAPABILITIES *capabilities)) \ 50 SDL_IORING_FUNC(BOOL, IsIoRingOpSupported, (HIORING ioRing, IORING_OP_CODE op)) \ 51 SDL_IORING_FUNC(HRESULT, CreateIoRing, (IORING_VERSION ioringVersion, IORING_CREATE_FLAGS flags, UINT32 submissionQueueSize, UINT32 completionQueueSize, HIORING* h)) \ 52 SDL_IORING_FUNC(HRESULT, GetIoRingInfo, (HIORING ioRing, IORING_INFO* info)) \ 53 SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32 * submittedEntries)) \ 54 SDL_IORING_FUNC(HRESULT, CloseIoRing, (HIORING ioRing)) \ 55 SDL_IORING_FUNC(HRESULT, PopIoRingCompletion, (HIORING ioRing, IORING_CQE* cqe)) \ 56 SDL_IORING_FUNC(HRESULT, SetIoRingCompletionEvent, (HIORING ioRing, HANDLE hEvent)) \ 57 SDL_IORING_FUNC(HRESULT, BuildIoRingCancelRequest, (HIORING ioRing, IORING_HANDLE_REF file, UINT_PTR opToCancel, UINT_PTR userData)) \ 58 SDL_IORING_FUNC(HRESULT, BuildIoRingReadFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF dataRef, UINT32 numberOfBytesToRead, UINT64 fileOffset, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ 59 SDL_IORING_FUNC(HRESULT, BuildIoRingWriteFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF bufferRef, UINT32 numberOfBytesToWrite, UINT64 fileOffset, FILE_WRITE_FLAGS writeFlags, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ 60 SDL_IORING_FUNC(HRESULT, BuildIoRingFlushFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, FILE_FLUSH_MODE flushMode, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ 61 62#define SDL_IORING_FUNC(ret, fn, args) typedef ret (WINAPI *SDL_fntype_##fn) args; 63SDL_IORING_FUNCS 64#undef SDL_IORING_FUNC 65 66typedef struct SDL_WinIoRingFunctions 67{ 68 #define SDL_IORING_FUNC(ret, fn, args) SDL_fntype_##fn fn; 69 SDL_IORING_FUNCS 70 #undef SDL_IORING_FUNC 71} SDL_WinIoRingFunctions; 72 73static SDL_WinIoRingFunctions ioring; 74 75 76typedef struct WinIoRingAsyncIOQueueData 77{ 78 SDL_Mutex *sqe_lock; 79 SDL_Mutex *cqe_lock; 80 HANDLE event; 81 HIORING ring; 82 SDL_AtomicInt num_waiting; 83} WinIoRingAsyncIOQueueData; 84 85 86static void UnloadWinIoRingLibrary(void) 87{ 88 if (ioring_library) { 89 SDL_UnloadObject(ioring_handle); 90 ioring_library = NULL; 91 } 92 SDL_zero(ioring); 93} 94 95static bool LoadWinIoRingSyms(void) 96{ 97 #define SDL_IORING_FUNC(ret, fn, args) { \ 98 ioring.fn = (SDL_fntype_##fn) SDL_LoadFunction(ioring_handle, #fn); \ 99 if (!ioring.fn) { \ 100 return false; \ 101 } \ 102 } 103 SDL_IORING_FUNCS 104 #undef SDL_IORING_FUNC 105 return true; 106} 107 108static bool LoadWinIoRing(void) 109{ 110 bool result = true; 111 112 if (!ioring_handle) { 113 ioring_handle = SDL_LoadObject(ioring_library); 114 if (!ioring_handle) { 115 result = false; 116 // Don't call SDL_SetError(): SDL_LoadObject already did. 117 } else { 118 result = LoadWinIoRingSyms(); 119 if (result) { 120 IORING_CAPABILITIES caps; 121 HRESULT hr = ioring.QueryIoRingCapabilities(&caps); 122 if (FAILED(hr)) { 123 result = false; 124 } else if (caps.MaxVersion < SDL_REQUIRED_IORING_VERSION) { 125 result = false; 126 } 127 } 128 129 if (!result) { 130 UnloadWinIoRingLibrary(); 131 } 132 } 133 } 134 return result; 135} 136 137static Sint64 ioring_asyncio_size(void *userdata) 138{ 139 HANDLE handle = (HANDLE) userdata; 140 LARGE_INTEGER size; 141 if (!GetFileSizeEx(handle, &size)) { 142 WIN_SetError("GetFileSizeEx"); 143 return -1; 144 } 145 return (Sint64) size.QuadPart; 146} 147 148// you must hold sqe_lock when calling this! 149static bool ioring_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task) 150{ 151 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 152 const HRESULT hr = ioring.SubmitIoRing(queuedata->ring, 0, 0, NULL); 153 return (FAILED(hr) ? WIN_SetErrorFromHRESULT("SubmitIoRing", hr) : true); 154} 155 156static void ioring_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task) 157{ 158 if (!task->asyncio || !task->asyncio->userdata) { 159 return; // Windows IoRing needs the file handle in question, so we'll just have to let it complete if unknown. 160 } 161 162 SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task)); 163 if (!cancel_task) { 164 return; // oh well, the task can just finish on its own. 165 } 166 167 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 168 HANDLE handle = (HANDLE) task->asyncio->userdata; 169 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); 170 171 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. 172 SDL_LockMutex(queuedata->sqe_lock); 173 const HRESULT hr = ioring.BuildIoRingCancelRequest(queuedata->ring, href, (UINT_PTR) task, (UINT_PTR) cancel_task); 174 if (FAILED(hr)) { 175 SDL_UnlockMutex(queuedata->sqe_lock); 176 SDL_free(cancel_task); // oh well, the task can just finish on its own. 177 return; 178 } 179 180 cancel_task->app_userdata = task; 181 ioring_asyncioqueue_queue_task(userdata, task); 182 SDL_UnlockMutex(queuedata->sqe_lock); 183} 184 185static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_CQE *cqe) 186{ 187 if (!cqe) { 188 return NULL; 189 } 190 191 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) cqe->UserData; 192 if (task) { // can be NULL if this was just a wakeup message, a NOP, etc. 193 if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation. 194 SDL_AsyncIOTask *cancel_task = task; 195 task = (SDL_AsyncIOTask *) cancel_task->app_userdata; 196 SDL_free(cancel_task); 197 if (SUCCEEDED(cqe->ResultCode)) { // cancel was successful? 198 task->result = SDL_ASYNCIO_CANCELED; 199 } else { 200 task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. 201 } 202 } else if (FAILED(cqe->ResultCode)) { 203 if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && (cqe->ResultCode == E_ACCESSDENIED) && task->asyncio->readonly) { 204 // we push all close requests through as flushes, as there is currently no async close operation and flushing writes to disk is the time-consuming part. 205 // However, flushing a read-only handle generates an error, so we catch this specific situation and ignore it. This approach still makes the task go 206 // through the IoRing so we can handle this all in the same place otherwise. The actual close happens below. 207 } else { 208 task->result = SDL_ASYNCIO_FAILURE; 209 // !!! FIXME: fill in task->error. 210 } 211 } else { 212 if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) { 213 task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. 214 } 215 216 // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it. 217 218 if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) { 219 task->result_size = (Uint64) cqe->Information; 220 } 221 } 222 223 // we currently send all close operations through as flushes, requested or not, so the actually closing is (in theory) fast. We do that here. 224 // if a later IoRing interface version offers an asynchronous close operation, revisit this to only flush if requested, like we do in the Linux io_uring code. 225 if (task->type == SDL_ASYNCIO_TASK_CLOSE) { 226 SDL_assert(task->asyncio != NULL); 227 SDL_assert(task->asyncio->userdata != NULL); 228 HANDLE handle = (HANDLE) task->asyncio->userdata; 229 if (!CloseHandle(handle)) { 230 task->result = SDL_ASYNCIO_FAILURE; // shrug. 231 } 232 } 233 } 234 235 return task; 236} 237 238static SDL_AsyncIOTask *ioring_asyncioqueue_get_results(void *userdata) 239{ 240 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 241 242 // unlike liburing's io_uring_peek_cqe(), it's possible PopIoRingCompletion() is thread safe, but for now we wrap it in a mutex just in case. 243 SDL_LockMutex(queuedata->cqe_lock); 244 IORING_CQE cqe; 245 const HRESULT hr = ioring.PopIoRingCompletion(queuedata->ring, &cqe); 246 SDL_UnlockMutex(queuedata->cqe_lock); 247 248 if ((hr == S_FALSE) || FAILED(hr)) { 249 return NULL; // nothing available at the moment. 250 } 251 252 return ProcessCQE(queuedata, &cqe); 253} 254 255static SDL_AsyncIOTask *ioring_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS) 256{ 257 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 258 259 // the event only signals when the IoRing moves from empty to non-empty, so you have to try a (non-blocking) get_results first or risk eternal hangs. 260 SDL_AsyncIOTask *task = ioring_asyncioqueue_get_results(userdata); 261 if (!task) { 262 SDL_AddAtomicInt(&queuedata->num_waiting, 1); 263 WaitForSingleObject(queuedata->event, (timeoutMS < 0) ? INFINITE : (DWORD) timeoutMS); 264 SDL_AddAtomicInt(&queuedata->num_waiting, -1); 265 266 // (we don't care if the wait failed for any reason, as the upcoming get_results will report valid information. We just wanted the wait operation to block.) 267 task = ioring_asyncioqueue_get_results(userdata); 268 } 269 270 return task; 271} 272 273static void ioring_asyncioqueue_signal(void *userdata) 274{ 275 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 276 const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting); 277 for (int i = 0; i < num_waiting; i++) { 278 SetEvent(queuedata->event); 279 } 280} 281 282static void ioring_asyncioqueue_destroy(void *userdata) 283{ 284 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; 285 ioring.CloseIoRing(queuedata->ring); 286 CloseHandle(queuedata->event); 287 SDL_DestroyMutex(queuedata->sqe_lock); 288 SDL_DestroyMutex(queuedata->cqe_lock); 289 SDL_free(queuedata); 290} 291 292static bool SDL_SYS_CreateAsyncIOQueue_ioring(SDL_AsyncIOQueue *queue) 293{ 294 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata)); 295 if (!queuedata) { 296 return false; 297 } 298 299 HRESULT hr; 300 IORING_CREATE_FLAGS flags; 301 302 SDL_SetAtomicInt(&queuedata->num_waiting, 0); 303 304 queuedata->sqe_lock = SDL_CreateMutex(); 305 if (!queuedata->sqe_lock) { 306 goto failed; 307 } 308 309 queuedata->cqe_lock = SDL_CreateMutex(); 310 if (!queuedata->cqe_lock) { 311 goto failed; 312 } 313 314 queuedata->event = CreateEventW(NULL, FALSE, FALSE, NULL); 315 if (!queuedata->event) { 316 WIN_SetError("CreateEventW"); 317 goto failed; 318 } 319 320 // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small? 321 flags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE; 322 flags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE; 323 hr = ioring.CreateIoRing(SDL_REQUIRED_IORING_VERSION, flags, 128, 128, &queuedata->ring); 324 if (FAILED(hr)) { 325 WIN_SetErrorFromHRESULT("CreateIoRing", hr); 326 goto failed; 327 } 328 329 hr = ioring.SetIoRingCompletionEvent(queuedata->ring, queuedata->event); 330 if (FAILED(hr)) { 331 WIN_SetErrorFromHRESULT("SetIoRingCompletionEvent", hr); 332 goto failed; 333 } 334 335 static const IORING_OP_CODE needed_ops[] = { 336 IORING_OP_NOP, 337 IORING_OP_FLUSH, 338 IORING_OP_READ, 339 IORING_OP_WRITE, 340 IORING_OP_CANCEL 341 }; 342 343 for (int i = 0; i < SDL_arraysize(needed_ops); i++) { 344 if (!ioring.IsIoRingOpSupported(queuedata->ring, needed_ops[i])) { 345 SDL_SetError("Created IoRing doesn't support op %u", (unsigned int) needed_ops[i]); 346 goto failed; 347 } 348 } 349 350 static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_ioring = { 351 ioring_asyncioqueue_queue_task, 352 ioring_asyncioqueue_cancel_task, 353 ioring_asyncioqueue_get_results, 354 ioring_asyncioqueue_wait_results, 355 ioring_asyncioqueue_signal, 356 ioring_asyncioqueue_destroy 357 }; 358 359 SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_ioring); 360 queue->userdata = queuedata; 361 return true; 362 363failed: 364 if (queuedata->ring) { 365 ioring.CloseIoRing(queuedata->ring); 366 } 367 if (queuedata->event) { 368 CloseHandle(queuedata->event); 369 } 370 if (queuedata->sqe_lock) { 371 SDL_DestroyMutex(queuedata->sqe_lock); 372 } 373 if (queuedata->cqe_lock) { 374 SDL_DestroyMutex(queuedata->cqe_lock); 375 } 376 SDL_free(queuedata); 377 return false; 378} 379 380static bool ioring_asyncio_read(void *userdata, SDL_AsyncIOTask *task) 381{ 382 // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs 383 // !!! FIXME: and make a note in the task that there are several in sequence. 384 if (task->requested_size > 0xFFFFFFFF) { 385 return SDL_SetError("ioring: i/o task is too large"); 386 } 387 388 HANDLE handle = (HANDLE) userdata; 389 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata; 390 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); 391 IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer); 392 393 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. 394 SDL_LockMutex(queuedata->sqe_lock); 395 bool retval; 396 const HRESULT hr = ioring.BuildIoRingReadFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, (UINT_PTR) task, IOSQE_FLAGS_NONE); 397 if (FAILED(hr)) { 398 retval = WIN_SetErrorFromHRESULT("BuildIoRingReadFile", hr); 399 } else { 400 retval = task->queue->iface.queue_task(task->queue->userdata, task); 401 } 402 SDL_UnlockMutex(queuedata->sqe_lock); 403 return retval; 404} 405 406static bool ioring_asyncio_write(void *userdata, SDL_AsyncIOTask *task) 407{ 408 // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs 409 // !!! FIXME: and make a note in the task that there are several in sequence. 410 if (task->requested_size > 0xFFFFFFFF) { 411 return SDL_SetError("ioring: i/o task is too large"); 412 } 413 414 HANDLE handle = (HANDLE) userdata; 415 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata; 416 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); 417 IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer); 418 419 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. 420 SDL_LockMutex(queuedata->sqe_lock); 421 bool retval; 422 const HRESULT hr = ioring.BuildIoRingWriteFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, 0 /*FILE_WRITE_FLAGS_NONE*/, (UINT_PTR) task, IOSQE_FLAGS_NONE); 423 if (FAILED(hr)) { 424 retval = WIN_SetErrorFromHRESULT("BuildIoRingWriteFile", hr); 425 } else { 426 retval = task->queue->iface.queue_task(task->queue->userdata, task); 427 } 428 SDL_UnlockMutex(queuedata->sqe_lock); 429 return retval; 430} 431 432static bool ioring_asyncio_close(void *userdata, SDL_AsyncIOTask *task) 433{ 434 // current IoRing operations don't offer asynchronous closing, but let's assume most of the potential work is flushing to disk, so just do it for everything, explicit flush or not. We'll close when it finishes. 435 HANDLE handle = (HANDLE) userdata; 436 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *)task->queue->userdata; 437 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); 438 439 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. 440 SDL_LockMutex(queuedata->sqe_lock); 441 bool retval; 442 const HRESULT hr = ioring.BuildIoRingFlushFile(queuedata->ring, href, FILE_FLUSH_DEFAULT, (UINT_PTR) task, IOSQE_FLAGS_NONE); 443 if (FAILED(hr)) { 444 retval = WIN_SetErrorFromHRESULT("BuildIoRingFlushFile", hr); 445 } else { 446 retval = task->queue->iface.queue_task(task->queue->userdata, task); 447 } 448 SDL_UnlockMutex(queuedata->sqe_lock); 449 return retval; 450} 451 452static void ioring_asyncio_destroy(void *userdata) 453{ 454 // this is only a Win32 file HANDLE, should have been closed elsewhere. 455} 456 457static bool Win32OpenModeFromString(const char *mode, DWORD *access_mode, DWORD *create_mode) 458{ 459 // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work. 460 static const struct { const char *str; DWORD amode; WORD cmode; } mappings[] = { 461 { "rb", GENERIC_READ, OPEN_EXISTING }, 462 { "wb", GENERIC_WRITE, CREATE_ALWAYS }, 463 { "r+b", GENERIC_READ | GENERIC_WRITE, OPEN_EXISTING }, 464 { "w+b", GENERIC_READ | GENERIC_WRITE, CREATE_ALWAYS } 465 }; 466 467 for (int i = 0; i < SDL_arraysize(mappings); i++) { 468 if (SDL_strcmp(mappings[i].str, mode) == 0) { 469 *access_mode = mappings[i].amode; 470 *create_mode = mappings[i].cmode; 471 return true; 472 } 473 } 474 475 SDL_assert(!"Shouldn't have reached this code"); 476 return SDL_SetError("Invalid file open mode"); 477} 478 479static bool SDL_SYS_AsyncIOFromFile_ioring(const char *file, const char *mode, SDL_AsyncIO *asyncio) 480{ 481 DWORD access_mode, create_mode; 482 if (!Win32OpenModeFromString(mode, &access_mode, &create_mode)) { 483 return false; 484 } 485 486 LPWSTR wstr = WIN_UTF8ToStringW(file); 487 if (!wstr) { 488 return false; 489 } 490 491 HANDLE handle = CreateFileW(wstr, access_mode, FILE_SHARE_READ, NULL, create_mode, FILE_ATTRIBUTE_NORMAL, NULL); 492 SDL_free(wstr); 493 if (!handle) { 494 return WIN_SetError("CreateFileW"); 495 } 496 497 static const SDL_AsyncIOInterface SDL_AsyncIOFile_ioring = { 498 ioring_asyncio_size, 499 ioring_asyncio_read, 500 ioring_asyncio_write, 501 ioring_asyncio_close, 502 ioring_asyncio_destroy 503 }; 504 505 SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_ioring); 506 507 asyncio->userdata = (void *) handle; 508 return true; 509} 510 511static void SDL_SYS_QuitAsyncIO_ioring(void) 512{ 513 UnloadWinIoRingLibrary(); 514} 515 516static void MaybeInitializeWinIoRing(void) 517{ 518 if (SDL_ShouldInit(&ioring_init)) { 519 if (LoadWinIoRing()) { 520 SDL_DebugLogBackend("asyncio", "ioring"); 521 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring; 522 QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring; 523 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring; 524 } else { // can't use ioring? Use the "generic" threadpool implementation instead. 525 SDL_DebugLogBackend("asyncio", "generic"); 526 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; 527 QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; 528 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; 529 } 530 SDL_SetInitialized(&ioring_init, true); 531 } 532} 533 534bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue) 535{ 536 MaybeInitializeWinIoRing(); 537 return CreateAsyncIOQueue(queue); 538} 539 540bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio) 541{ 542 MaybeInitializeWinIoRing(); 543 return AsyncIOFromFile(file, mode, asyncio); 544} 545 546void SDL_SYS_QuitAsyncIO(void) 547{ 548 if (SDL_ShouldQuit(&ioring_init)) { 549 QuitAsyncIO(); 550 CreateAsyncIOQueue = NULL; 551 QuitAsyncIO = NULL; 552 AsyncIOFromFile = NULL; 553 SDL_SetInitialized(&ioring_init, false); 554 } 555} 556 557#endif // defined HAVE_IORINGAPI_H 558 559[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.