2016-11-01 20:44:33 +00:00
|
|
|
/*
|
|
|
|
** Renderer multithreading framework
|
|
|
|
** Copyright (c) 2016 Magnus Norddahl
|
|
|
|
**
|
|
|
|
** This software is provided 'as-is', without any express or implied
|
|
|
|
** warranty. In no event will the authors be held liable for any damages
|
|
|
|
** arising from the use of this software.
|
|
|
|
**
|
|
|
|
** Permission is granted to anyone to use this software for any purpose,
|
|
|
|
** including commercial applications, and to alter it and redistribute it
|
|
|
|
** freely, subject to the following restrictions:
|
|
|
|
**
|
|
|
|
** 1. The origin of this software must not be misrepresented; you must not
|
|
|
|
** claim that you wrote the original software. If you use this software
|
|
|
|
** in a product, an acknowledgment in the product documentation would be
|
|
|
|
** appreciated but is not required.
|
|
|
|
** 2. Altered source versions must be plainly marked as such, and must not be
|
|
|
|
** misrepresented as being the original software.
|
|
|
|
** 3. This notice may not be removed or altered from any source distribution.
|
|
|
|
**
|
|
|
|
*/
|
2016-10-07 02:01:38 +00:00
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include "templates.h"
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "w_wad.h"
|
|
|
|
#include "v_video.h"
|
|
|
|
#include "doomstat.h"
|
|
|
|
#include "st_stuff.h"
|
|
|
|
#include "g_game.h"
|
|
|
|
#include "g_level.h"
|
|
|
|
#include "r_thread.h"
|
2017-02-04 11:38:05 +00:00
|
|
|
#include "swrenderer/r_memory.h"
|
|
|
|
#include "swrenderer/r_renderthread.h"
|
2016-10-07 02:01:38 +00:00
|
|
|
|
|
|
|
CVAR(Bool, r_multithreaded, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads *DrawerThreads::Instance()
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
static DrawerThreads threads;
|
|
|
|
return &threads;
|
2016-10-07 02:01:38 +00:00
|
|
|
}
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads::DrawerThreads()
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads::~DrawerThreads()
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
|
|
|
StopThreads();
|
|
|
|
}
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
void DrawerThreads::Execute(const std::vector<DrawerCommandQueuePtr> &queues)
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
bool hasWork = false;
|
|
|
|
for (const auto &queue : queues)
|
|
|
|
hasWork = hasWork || !queue->commands.empty();
|
|
|
|
if (!hasWork)
|
2016-10-07 02:01:38 +00:00
|
|
|
return;
|
2017-02-04 11:38:05 +00:00
|
|
|
|
|
|
|
auto queue = Instance();
|
2016-10-07 02:01:38 +00:00
|
|
|
|
|
|
|
// Give worker threads something to do:
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> start_lock(queue->start_mutex);
|
2017-02-04 11:38:05 +00:00
|
|
|
queue->active_commands = queues;
|
2016-10-07 02:01:38 +00:00
|
|
|
queue->run_id++;
|
|
|
|
start_lock.unlock();
|
|
|
|
|
|
|
|
queue->StartThreads();
|
|
|
|
queue->start_condition.notify_all();
|
|
|
|
|
|
|
|
// Do one thread ourselves:
|
|
|
|
|
2017-01-03 03:29:06 +00:00
|
|
|
static DrawerThread thread;
|
2016-10-07 02:01:38 +00:00
|
|
|
thread.core = 0;
|
|
|
|
thread.num_cores = (int)(queue->threads.size() + 1);
|
|
|
|
|
2016-10-12 11:25:05 +00:00
|
|
|
struct TryCatchData
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads *queue;
|
2016-10-12 11:25:05 +00:00
|
|
|
DrawerThread *thread;
|
2017-02-04 11:38:05 +00:00
|
|
|
size_t list_index;
|
2016-10-12 11:25:05 +00:00
|
|
|
size_t command_index;
|
|
|
|
} data;
|
|
|
|
|
|
|
|
data.queue = queue;
|
|
|
|
data.thread = &thread;
|
2017-02-04 11:38:05 +00:00
|
|
|
data.list_index = 0;
|
2016-10-12 11:25:05 +00:00
|
|
|
data.command_index = 0;
|
|
|
|
VectoredTryCatch(&data,
|
|
|
|
[](void *data)
|
|
|
|
{
|
|
|
|
TryCatchData *d = (TryCatchData*)data;
|
2016-10-07 02:01:38 +00:00
|
|
|
|
2016-10-12 11:25:05 +00:00
|
|
|
for (int pass = 0; pass < d->queue->num_passes; pass++)
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2016-10-12 11:25:05 +00:00
|
|
|
d->thread->pass_start_y = pass * d->queue->rows_in_pass;
|
|
|
|
d->thread->pass_end_y = (pass + 1) * d->queue->rows_in_pass;
|
|
|
|
if (pass + 1 == d->queue->num_passes)
|
|
|
|
d->thread->pass_end_y = MAX(d->thread->pass_end_y, MAXHEIGHT);
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
for (auto &list : d->queue->active_commands)
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
size_t size = list->commands.size();
|
|
|
|
for (d->command_index = 0; d->command_index < size; d->command_index++)
|
|
|
|
{
|
|
|
|
auto &command = list->commands[d->command_index];
|
|
|
|
command->Execute(d->thread);
|
|
|
|
}
|
|
|
|
d->list_index++;
|
2016-10-12 11:25:05 +00:00
|
|
|
}
|
2016-10-07 02:01:38 +00:00
|
|
|
}
|
2016-10-12 11:25:05 +00:00
|
|
|
},
|
2016-10-29 04:50:09 +00:00
|
|
|
[](void *data, const char *reason, bool fatal)
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
|
|
|
TryCatchData *d = (TryCatchData*)data;
|
2017-02-04 11:38:05 +00:00
|
|
|
ReportDrawerError(d->queue->active_commands[d->list_index]->commands[d->command_index], true, reason, fatal);
|
2016-10-12 11:25:05 +00:00
|
|
|
});
|
2016-10-07 02:01:38 +00:00
|
|
|
|
|
|
|
// Wait for everyone to finish:
|
|
|
|
|
|
|
|
std::unique_lock<std::mutex> end_lock(queue->end_mutex);
|
|
|
|
queue->end_condition.wait(end_lock, [&]() { return queue->finished_threads == queue->threads.size(); });
|
|
|
|
|
2016-10-12 11:25:05 +00:00
|
|
|
if (!queue->thread_error.IsEmpty())
|
2016-10-29 04:50:09 +00:00
|
|
|
{
|
|
|
|
static bool first = true;
|
|
|
|
if (queue->thread_error_fatal)
|
|
|
|
I_FatalError("%s", queue->thread_error.GetChars());
|
|
|
|
else if (first)
|
|
|
|
Printf("%s\n", queue->thread_error.GetChars());
|
|
|
|
first = false;
|
|
|
|
}
|
2016-10-12 11:25:05 +00:00
|
|
|
|
2016-10-07 02:01:38 +00:00
|
|
|
// Clean up batch:
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
for (auto &list : queue->active_commands)
|
|
|
|
{
|
|
|
|
for (auto &command : list->commands)
|
|
|
|
command->~DrawerCommand();
|
|
|
|
list->Clear();
|
|
|
|
}
|
2016-10-07 02:01:38 +00:00
|
|
|
queue->active_commands.clear();
|
|
|
|
queue->finished_threads = 0;
|
|
|
|
}
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
void DrawerThreads::StartThreads()
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
|
|
|
if (!threads.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
int num_threads = std::thread::hardware_concurrency();
|
|
|
|
if (num_threads == 0)
|
|
|
|
num_threads = 4;
|
|
|
|
|
|
|
|
threads.resize(num_threads - 1);
|
|
|
|
|
|
|
|
for (int i = 0; i < num_threads - 1; i++)
|
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads *queue = this;
|
2016-10-07 02:01:38 +00:00
|
|
|
DrawerThread *thread = &threads[i];
|
|
|
|
thread->core = i + 1;
|
|
|
|
thread->num_cores = num_threads;
|
|
|
|
thread->thread = std::thread([=]()
|
|
|
|
{
|
|
|
|
int run_id = 0;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
// Wait until we are signalled to run:
|
|
|
|
std::unique_lock<std::mutex> start_lock(queue->start_mutex);
|
|
|
|
queue->start_condition.wait(start_lock, [&]() { return queue->run_id != run_id || queue->shutdown_flag; });
|
|
|
|
if (queue->shutdown_flag)
|
|
|
|
break;
|
|
|
|
run_id = queue->run_id;
|
|
|
|
start_lock.unlock();
|
|
|
|
|
|
|
|
// Do the work:
|
2016-10-12 11:25:05 +00:00
|
|
|
|
|
|
|
struct TryCatchData
|
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
DrawerThreads *queue;
|
2016-10-12 11:25:05 +00:00
|
|
|
DrawerThread *thread;
|
2017-02-04 11:38:05 +00:00
|
|
|
size_t list_index;
|
2016-10-12 11:25:05 +00:00
|
|
|
size_t command_index;
|
|
|
|
} data;
|
|
|
|
|
|
|
|
data.queue = queue;
|
|
|
|
data.thread = thread;
|
2017-02-04 11:38:05 +00:00
|
|
|
data.list_index = 0;
|
2016-10-12 11:25:05 +00:00
|
|
|
data.command_index = 0;
|
|
|
|
VectoredTryCatch(&data,
|
|
|
|
[](void *data)
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2016-10-12 11:25:05 +00:00
|
|
|
TryCatchData *d = (TryCatchData*)data;
|
2016-10-07 02:01:38 +00:00
|
|
|
|
2016-10-12 11:25:05 +00:00
|
|
|
for (int pass = 0; pass < d->queue->num_passes; pass++)
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
2016-10-12 11:25:05 +00:00
|
|
|
d->thread->pass_start_y = pass * d->queue->rows_in_pass;
|
|
|
|
d->thread->pass_end_y = (pass + 1) * d->queue->rows_in_pass;
|
|
|
|
if (pass + 1 == d->queue->num_passes)
|
|
|
|
d->thread->pass_end_y = MAX(d->thread->pass_end_y, MAXHEIGHT);
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
for (auto &list : d->queue->active_commands)
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
2017-02-04 11:38:05 +00:00
|
|
|
size_t size = list->commands.size();
|
|
|
|
for (d->command_index = 0; d->command_index < size; d->command_index++)
|
|
|
|
{
|
|
|
|
auto &command = list->commands[d->command_index];
|
|
|
|
command->Execute(d->thread);
|
|
|
|
}
|
|
|
|
d->list_index++;
|
2016-10-12 11:25:05 +00:00
|
|
|
}
|
2016-10-07 02:01:38 +00:00
|
|
|
}
|
2016-10-12 11:25:05 +00:00
|
|
|
},
|
2016-10-29 04:50:09 +00:00
|
|
|
[](void *data, const char *reason, bool fatal)
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
|
|
|
TryCatchData *d = (TryCatchData*)data;
|
2017-02-04 11:38:05 +00:00
|
|
|
ReportDrawerError(d->queue->active_commands[d->list_index]->commands[d->command_index], true, reason, fatal);
|
2016-10-12 11:25:05 +00:00
|
|
|
});
|
2016-10-07 02:01:38 +00:00
|
|
|
|
|
|
|
// Notify main thread that we finished:
|
|
|
|
std::unique_lock<std::mutex> end_lock(queue->end_mutex);
|
|
|
|
queue->finished_threads++;
|
|
|
|
end_lock.unlock();
|
|
|
|
queue->end_condition.notify_all();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
void DrawerThreads::StopThreads()
|
2016-10-07 02:01:38 +00:00
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> lock(start_mutex);
|
|
|
|
shutdown_flag = true;
|
|
|
|
lock.unlock();
|
|
|
|
start_condition.notify_all();
|
|
|
|
for (auto &thread : threads)
|
|
|
|
thread.thread.join();
|
|
|
|
threads.clear();
|
|
|
|
lock.lock();
|
|
|
|
shutdown_flag = false;
|
|
|
|
}
|
2016-10-12 11:25:05 +00:00
|
|
|
|
2017-02-04 11:38:05 +00:00
|
|
|
void DrawerThreads::ReportDrawerError(DrawerCommand *command, bool worker_thread, const char *reason, bool fatal)
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
|
|
|
if (worker_thread)
|
|
|
|
{
|
|
|
|
std::unique_lock<std::mutex> end_lock(Instance()->end_mutex);
|
2016-10-29 04:50:09 +00:00
|
|
|
if (Instance()->thread_error.IsEmpty() || (!Instance()->thread_error_fatal && fatal))
|
|
|
|
{
|
|
|
|
Instance()->thread_error = reason + (FString)": " + command->DebugInfo();
|
|
|
|
Instance()->thread_error_fatal = fatal;
|
|
|
|
}
|
2016-10-12 11:25:05 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-10-29 04:50:09 +00:00
|
|
|
static bool first = true;
|
|
|
|
if (fatal)
|
|
|
|
I_FatalError("%s: %s", reason, command->DebugInfo().GetChars());
|
|
|
|
else if (first)
|
|
|
|
Printf("%s: %s\n", reason, command->DebugInfo().GetChars());
|
|
|
|
first = false;
|
2016-10-12 11:25:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
|
|
|
2016-10-29 04:50:09 +00:00
|
|
|
void VectoredTryCatch(void *data, void(*tryBlock)(void *data), void(*catchBlock)(void *data, const char *reason, bool fatal))
|
2016-10-12 11:25:05 +00:00
|
|
|
{
|
|
|
|
tryBlock(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
2017-02-04 11:38:05 +00:00
|
|
|
|
|
|
|
DrawerCommandQueue::DrawerCommandQueue(swrenderer::RenderThread *renderthread) : renderthread(renderthread)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void *DrawerCommandQueue::AllocMemory(size_t size)
|
|
|
|
{
|
2017-02-04 13:00:21 +00:00
|
|
|
return renderthread->FrameMemory->AllocMemory<uint8_t>((int)size);
|
2017-02-04 11:38:05 +00:00
|
|
|
}
|