diff --git a/include/semaphore.h b/include/semaphore.h new file mode 100644 index 0000000000..66e08861a9 --- /dev/null +++ b/include/semaphore.h @@ -0,0 +1,66 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2021-2023 The DOSBox Staging Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include + +/* + * A Semaphore class for synchronizing threads. + * + * This Semaphore implementation uses a count to represent the number of + * available resources, along with a mutex and a condition variable to + * handle synchronization between threads. + */ +class Semaphore { +public: + /** + * Constructs a new Semaphore. + * + * @param count The initial count. Defaults to 0. + */ + Semaphore(int count = 0) : count(count) {} + + /** + * Decrements (acquires) the semaphore. If the count is 0, this will block + * until another thread calls notify(). + */ + void wait() { + std::unique_lock lock(mtx); + while (count == 0) { + cv.wait(lock); + } + --count; + } + + /** + * Increments (releases) the semaphore, potentially unblocking a thread + * currently waiting on wait(). + */ + void notify() { + std::unique_lock lock(mtx); + ++count; + cv.notify_one(); + } + +private: + std::mutex mtx = {}; // Mutex to protect count. + std::condition_variable cv = {}; // Condition variable for the count. + int count = 0; // Current count. +}; diff --git a/src/hardware/voodoo.cpp b/src/hardware/voodoo.cpp index e760257e9a..22f2a20a18 100644 --- a/src/hardware/voodoo.cpp +++ b/src/hardware/voodoo.cpp @@ -69,6 +69,7 @@ #if C_VOODOO #include +#include #include #include #include @@ -91,6 +92,7 @@ #include "pci_bus.h" #include "pic.h" #include "render.h" +#include "semaphore.h" #include "setup.h" #include "support.h" #include "vga.h" @@ -937,9 +939,9 @@ struct triangle_worker UINT16 *drawbuf; poly_vertex v1, v2, v3; INT32 v1y, v3y, totalpix; - SDL_sem* sembegin[TRIANGLE_THREADS]; - std::condition_variable done_cv; - std::mutex done_mutex; + std::array threads; + std::array sembegin; + Semaphore semdone; int done_count; }; @@ -4269,23 +4271,15 @@ static void triangle_worker_work(triangle_worker& tworker, INT32 worktstart, INT sum_statistics(&v->thread_stats[worktstart], &my_stats); } -static int triangle_worker_thread_func(void* p) +static int triangle_worker_thread_func(INT32 p) { triangle_worker& tworker = v->tworker; - for (INT32 tnum = (INT32)(size_t)p; tworker.threads_active;) - { - SDL_SemWait(tworker.sembegin[tnum]); - if (tworker.threads_active) + for (INT32 tnum = p; tworker.threads_active;) { + tworker.sembegin[tnum].wait(); + if (tworker.threads_active) { triangle_worker_work(tworker, tnum, tnum + 1); - bool done = false; - { - std::lock_guard lock(tworker.done_mutex); - tworker.done_count++; - done = (tworker.done_count == TRIANGLE_THREADS); - } - if (done) { - tworker.done_cv.notify_one(); } + tworker.semdone.notify(); } return 0; } @@ -4294,21 +4288,18 @@ static void triangle_worker_shutdown(triangle_worker& tworker) { if (!tworker.threads_active) return; tworker.threads_active = false; - { - std::lock_guard lock(tworker.done_mutex); - tworker.done_count = 0; - } for (size_t i = 0; i != TRIANGLE_THREADS; i++) { - SDL_SemPost(tworker.sembegin[i]); - } - { - std::unique_lock lock(tworker.done_mutex); - tworker.done_cv.wait(lock, [&] { - return tworker.done_count == TRIANGLE_THREADS; - }); + tworker.sembegin[i].notify(); } + for (size_t i = 0; i != TRIANGLE_THREADS; i++) { - SDL_DestroySemaphore(tworker.sembegin[i]); + tworker.semdone.wait(); + } + + for (auto& thread : tworker.threads) { + if (thread.joinable()) { + thread.join(); + } } } @@ -4355,26 +4346,16 @@ static void triangle_worker_run(triangle_worker& tworker) if (!tworker.threads_active) { tworker.threads_active = true; - for (size_t i = 0; i != TRIANGLE_THREADS; i++) tworker.sembegin[i] = SDL_CreateSemaphore(0); for (size_t i = 0; i != TRIANGLE_THREADS; i++) - SDL_CreateThread(triangle_worker_thread_func, - "voodoo", - (void*)i); - } - - { - std::lock_guard lock(tworker.done_mutex); - tworker.done_count = 0; + tworker.threads[i] = std::thread( + [i] { triangle_worker_thread_func(i); }); } for (size_t i = 0; i != TRIANGLE_THREADS; i++) { - SDL_SemPost(tworker.sembegin[i]); + tworker.sembegin[i].notify(); } triangle_worker_work(tworker, TRIANGLE_THREADS, TRIANGLE_WORKERS); - { - std::unique_lock lock(tworker.done_mutex); - tworker.done_cv.wait(lock, [&] { - return tworker.done_count == TRIANGLE_THREADS; - }); + for (size_t i = 0; i != TRIANGLE_THREADS; i++) { + tworker.semdone.wait(); } } diff --git a/tests/meson.build b/tests/meson.build index 5d845d910f..fe542794c7 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -75,6 +75,7 @@ unit_tests = [ {'name': 'iohandler_containers', 'deps': [libmisc_stubs_dep]}, {'name': 'math_utils', 'deps': [libmisc_stubs_dep]}, {'name': 'rwqueue', 'deps': [libmisc_stubs_dep]}, + {'name': 'semaphore', 'deps': [libmisc_stubs_dep]}, {'name': 'setup', 'deps': [libmisc_stubs_dep]}, {'name': 'shell_cmds', 'deps': [dosbox_dep], 'extra_cpp': []}, {'name': 'shell_redirection', 'deps': [dosbox_dep], 'extra_cpp': []}, diff --git a/tests/semaphore_tests.cpp b/tests/semaphore_tests.cpp new file mode 100644 index 0000000000..1b54f87b5e --- /dev/null +++ b/tests/semaphore_tests.cpp @@ -0,0 +1,44 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2021-2023 The DOSBox Staging Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "semaphore.h" // assuming the semaphore class is defined in this header + +class SemaphoreTest : public ::testing::Test { +protected: + Semaphore semaphore{0}; + bool done = false; +}; + +TEST_F(SemaphoreTest, TestNotify) { + std::thread worker([&]() { + semaphore.wait(); + // At this point, the semaphore should have been notified by the main thread. + done = true; + }); + + // The worker thread should be waiting on the semaphore at this point. + semaphore.notify(); + + worker.join(); + + EXPECT_TRUE(done); +}