Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

De-SDL voodoo threading #2568

Merged
merged 1 commit into from Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 66 additions & 0 deletions 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 <condition_variable>
kklobe marked this conversation as resolved.
Show resolved Hide resolved
#include <mutex>

/*
* 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.
};
67 changes: 24 additions & 43 deletions src/hardware/voodoo.cpp
Expand Up @@ -69,6 +69,7 @@
#if C_VOODOO

#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
#include <cmath>
Expand All @@ -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"
Expand Down Expand Up @@ -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<std::thread, TRIANGLE_THREADS> threads;
std::array<Semaphore, TRIANGLE_THREADS> sembegin;
Semaphore semdone;
int done_count;
};

Expand Down Expand Up @@ -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;
}
Expand All @@ -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();
}
}
}

Expand Down Expand Up @@ -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();
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/meson.build
Expand Up @@ -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': []},
Expand Down
44 changes: 44 additions & 0 deletions 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 <gtest/gtest.h>
#include <thread>
#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);
}