Skip to content

Commit

Permalink
Added render threads mode to options.
Browse files Browse the repository at this point in the history
This lets the user choose how many CPU threads they want to use with
software rendering. By default it uses whatever the max thread count is
for their CPU (the value returned by Platform::getThreadCount()).
  • Loading branch information
afritz1 committed May 15, 2018
1 parent 1779021 commit da00122
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 22 deletions.
15 changes: 14 additions & 1 deletion OpenTESArena/src/Game/Options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace
{ "VerticalFOV", OptionType::Double },
{ "LetterboxMode", OptionType::Int },
{ "CursorScale", OptionType::Double },
{ "ModernInterface", OptionType::Bool }
{ "ModernInterface", OptionType::Bool },
{ "RenderThreadsMode", OptionType::Int }
};

const std::vector<std::pair<std::string, OptionType>> AudioMappings =
Expand Down Expand Up @@ -76,6 +77,8 @@ const double Options::MIN_CURSOR_SCALE = 0.50;
const double Options::MAX_CURSOR_SCALE = 8.0;
const int Options::MIN_LETTERBOX_MODE = 0;
const int Options::MAX_LETTERBOX_MODE = 2;
const int Options::MIN_RENDER_THREADS_MODE = 0;
const int Options::MAX_RENDER_THREADS_MODE = 3;
const double Options::MIN_HORIZONTAL_SENSITIVITY = 0.50;
const double Options::MAX_HORIZONTAL_SENSITIVITY = 50.0;
const double Options::MIN_VERTICAL_SENSITIVITY = 0.50;
Expand Down Expand Up @@ -484,6 +487,16 @@ void Options::checkGraphics_CursorScale(double value) const
String::fixedPrecision(Options::MAX_CURSOR_SCALE, 1) + ".");
}

void Options::checkGraphics_RenderThreadsMode(int value) const
{
DebugAssert(value >= Options::MIN_RENDER_THREADS_MODE,
"Render threads mode cannot be less than " +
std::to_string(Options::MIN_RENDER_THREADS_MODE) + ".");
DebugAssert(value <= Options::MAX_RENDER_THREADS_MODE,
"Render threads mode cannot be greater than " +
std::to_string(Options::MAX_RENDER_THREADS_MODE) + ".");
}

void Options::checkAudio_MusicVolume(double value) const
{
DebugAssert(value >= Options::MIN_VOLUME, "Music volume cannot be negative.");
Expand Down
3 changes: 3 additions & 0 deletions OpenTESArena/src/Game/Options.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class Options
static const double MAX_CURSOR_SCALE;
static const int MIN_LETTERBOX_MODE;
static const int MAX_LETTERBOX_MODE;
static const int MIN_RENDER_THREADS_MODE;
static const int MAX_RENDER_THREADS_MODE;
static const double MIN_HORIZONTAL_SENSITIVITY;
static const double MAX_HORIZONTAL_SENSITIVITY;
static const double MIN_VERTICAL_SENSITIVITY;
Expand Down Expand Up @@ -134,6 +136,7 @@ void set##section##_##name(const std::string &value) \
OPTION_INT(Graphics, LetterboxMode)
OPTION_DOUBLE(Graphics, CursorScale)
OPTION_BOOL(Graphics, ModernInterface)
OPTION_INT(Graphics, RenderThreadsMode)

OPTION_DOUBLE(Audio, MusicVolume)
OPTION_DOUBLE(Audio, SoundVolume)
Expand Down
8 changes: 5 additions & 3 deletions OpenTESArena/src/Interface/ChooseAttributesPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,13 @@ ChooseAttributesPanel::ChooseAttributesPanel(Game &game,
{
// Initialize 3D renderer.
auto &renderer = game.getRenderer();
const auto &options = game.getOptions();
const auto &miscAssets = game.getMiscAssets();
const bool fullGameWindow =
game.getOptions().getGraphics_ModernInterface();
const bool fullGameWindow = options.getGraphics_ModernInterface();
renderer.initializeWorldRendering(
game.getOptions().getGraphics_ResolutionScale(), fullGameWindow);
options.getGraphics_ResolutionScale(),
fullGameWindow,
options.getGraphics_RenderThreadsMode());

std::unique_ptr<GameData> gameData = [this, &name, gender, raceID,
&charClass, &miscAssets]()
Expand Down
4 changes: 2 additions & 2 deletions OpenTESArena/src/Interface/MainMenuPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ MainMenuPanel::MainMenuPanel(Game &game)
auto &renderer = game.getRenderer();
const auto &options = game.getOptions();
const bool fullGameWindow = options.getGraphics_ModernInterface();
renderer.initializeWorldRendering(
options.getGraphics_ResolutionScale(), fullGameWindow);
renderer.initializeWorldRendering(options.getGraphics_ResolutionScale(),
fullGameWindow, options.getGraphics_RenderThreadsMode());

// Game data instance, to be initialized further by one of the loading methods below.
// Create a player with random data for testing.
Expand Down
24 changes: 22 additions & 2 deletions OpenTESArena/src/Interface/OptionsPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ const std::string OptionsPanel::FPS_LIMIT_NAME = "FPS Limit";
const std::string OptionsPanel::FULLSCREEN_NAME = "Fullscreen";
const std::string OptionsPanel::LETTERBOX_MODE_NAME = "Letterbox Mode";
const std::string OptionsPanel::MODERN_INTERFACE_NAME = "Modern Interface";
const std::string OptionsPanel::RENDER_THREADS_MODE_NAME = "Render Threads Mode";
const std::string OptionsPanel::RESOLUTION_SCALE_NAME = "Resolution Scale";
const std::string OptionsPanel::VERTICAL_FOV_NAME = "Vertical FOV";

Expand Down Expand Up @@ -439,7 +440,7 @@ OptionsPanel::OptionsPanel(Game &game)

this->graphicsOptions.push_back(std::make_unique<BoolOption>(
OptionsPanel::MODERN_INTERFACE_NAME,
"Modern mode uses a new minimal interface with free-look.",
"Modern mode uses a minimal interface with free-look.",
options.getGraphics_ModernInterface(),
[this](bool value)
{
Expand Down Expand Up @@ -467,10 +468,29 @@ OptionsPanel::OptionsPanel(Game &game)
options.getGraphics_ResolutionScale(), fullGameWindow);
}));

auto renderThreadsModeOption = std::make_unique<IntOption>(
OptionsPanel::RENDER_THREADS_MODE_NAME,
"Determines the number of CPU threads to use for rendering.\nThis has a significant impact on performance.",
options.getGraphics_RenderThreadsMode(),
1,
Options::MIN_RENDER_THREADS_MODE,
Options::MAX_RENDER_THREADS_MODE,
[this](int value)
{
auto &game = this->getGame();
auto &options = game.getOptions();
auto &renderer = game.getRenderer();
options.setGraphics_RenderThreadsMode(value);
renderer.setRenderThreadsMode(value);
});

renderThreadsModeOption->setDisplayOverrides({ "Low", "Medium", "High", "Max" });
this->graphicsOptions.push_back(std::move(renderThreadsModeOption));

// Create audio options.
auto soundResamplingOption = std::make_unique<IntOption>(
OptionsPanel::SOUND_RESAMPLING_NAME,
"Affects quality of sounds. Results may vary depending on\nOpenAL Soft version.",
"Affects quality of sounds. Results may vary depending on\nOpenAL version.",
options.getAudio_SoundResampling(),
1,
0,
Expand Down
1 change: 1 addition & 0 deletions OpenTESArena/src/Interface/OptionsPanel.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class OptionsPanel : public Panel
static const std::string FULLSCREEN_NAME;
static const std::string LETTERBOX_MODE_NAME;
static const std::string MODERN_INTERFACE_NAME;
static const std::string RENDER_THREADS_MODE_NAME;
static const std::string RESOLUTION_SCALE_NAME;
static const std::string VERTICAL_FOV_NAME;

Expand Down
11 changes: 9 additions & 2 deletions OpenTESArena/src/Rendering/Renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,8 @@ void Renderer::setClipRect(const SDL_Rect *rect)
SDL_RenderSetClipRect(this->renderer, rect);
}

void Renderer::initializeWorldRendering(double resolutionScale, bool fullGameWindow)
void Renderer::initializeWorldRendering(double resolutionScale, bool fullGameWindow,
int renderThreadsMode)
{
this->fullGameWindow = fullGameWindow;

Expand All @@ -435,7 +436,13 @@ void Renderer::initializeWorldRendering(double resolutionScale, bool fullGameWin
"Couldn't create game world texture, " + std::string(SDL_GetError()));

// Initialize 3D rendering.
this->softwareRenderer.init(renderWidth, renderHeight);
this->softwareRenderer.init(renderWidth, renderHeight, renderThreadsMode);
}

void Renderer::setRenderThreadsMode(int mode)
{
assert(this->softwareRenderer.isInited());
this->softwareRenderer.setRenderThreadsMode(mode);
}

void Renderer::addFlat(int id, const Double3 &position, double width,
Expand Down
6 changes: 5 additions & 1 deletion OpenTESArena/src/Rendering/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,11 @@ class Renderer
// determines whether to render a "fullscreen" 3D image or just the part above
// the game interface. If there is an existing renderer in memory, it will be
// overwritten with the new one.
void initializeWorldRendering(double resolutionScale, bool fullGameWindow);
void initializeWorldRendering(double resolutionScale, bool fullGameWindow,
int renderThreadsMode);

// Sets which mode to use for software render threads (low, medium, high, etc.).
void setRenderThreadsMode(int mode);

// Helper methods for changing data in the 3D renderer. Some data, like the voxel
// grid, are passed each frame by reference.
Expand Down
52 changes: 43 additions & 9 deletions OpenTESArena/src/Rendering/SoftwareRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ SoftwareRenderer::SoftwareRenderer()
// Initialize values to empty.
this->width = 0;
this->height = 0;
this->renderThreadCount = 0;
this->renderThreadsMode = 0;
this->fogDistance = 0.0;
}

Expand All @@ -226,7 +226,7 @@ bool SoftwareRenderer::isInited() const
return (this->width > 0) && (this->height > 0);
}

void SoftwareRenderer::init(int width, int height)
void SoftwareRenderer::init(int width, int height, int renderThreadsMode)
{
// Initialize 2D frame buffer.
const int pixelCount = width * height;
Expand All @@ -242,14 +242,17 @@ void SoftwareRenderer::init(int width, int height)

this->width = width;
this->height = height;

// Obtain the number of threads to use.
this->renderThreadCount = Platform::getThreadCount();
this->renderThreadsMode = renderThreadsMode;

// Fog distance is zero by default.
this->fogDistance = 0.0;
}

void SoftwareRenderer::setRenderThreadsMode(int mode)
{
this->renderThreadsMode = mode;
}

void SoftwareRenderer::addFlat(int id, const Double3 &position, double width,
double height, int textureID)
{
Expand Down Expand Up @@ -869,6 +872,35 @@ Double3 SoftwareRenderer::getSunDirection(double daytimePercent) const
return Double3(0.0, -std::cos(radians), std::sin(radians)).normalized();
}

int SoftwareRenderer::getRenderThreadsFromMode(int mode)
{
if (mode == 0)
{
// Low.
return 1;
}
else if (mode == 1)
{
// Medium.
return std::max(Platform::getThreadCount() / 2, 1);
}
else if (mode == 2)
{
// High.
return std::max(Platform::getThreadCount() - 1, 1);
}
else if (mode == 3)
{
// Max.
return Platform::getThreadCount();
}
else
{
throw DebugException("Invalid render threads mode \"" +
std::to_string(mode) + "\".");
}
}

double SoftwareRenderer::fullAtan2(double y, double x)
{
const double angle = std::atan2(y, x);
Expand Down Expand Up @@ -4281,15 +4313,17 @@ void SoftwareRenderer::render(const Double3 &eye, const Double3 &direction, doub
// calculate a new list, and sort it by depth.
std::thread sortThread([this, &camera] { this->updateVisibleFlats(camera); });

// Prepare render threads. These are used for clearing the frame buffer and rendering.
std::vector<std::thread> renderThreads(this->renderThreadCount);
// Prepare render threads, obtaining the number of threads to use from the render threads
// mode. These are used for clearing the frame buffer and rendering.
std::vector<std::thread> renderThreads(
SoftwareRenderer::getRenderThreadsFromMode(this->renderThreadsMode));

// Start clearing the frame buffer with the render threads.
for (size_t i = 0; i < renderThreads.size(); i++)
{
// "blockSize" is the approximate number of rows per thread. Rounding is involved so
// the start and stop coordinates are correct for all resolutions.
const double blockSize = heightReal / static_cast<double>(this->renderThreadCount);
const double blockSize = heightReal / static_cast<double>(renderThreads.size());
const int startY = static_cast<int>(std::round(static_cast<double>(i) * blockSize));
const int endY = static_cast<int>(std::round(static_cast<double>(i + 1) * blockSize));

Expand Down Expand Up @@ -4318,7 +4352,7 @@ void SoftwareRenderer::render(const Double3 &eye, const Double3 &direction, doub
{
// "blockSize" is the approximate number of columns per thread. Rounding is involved so
// the start and stop coordinates are correct for all resolutions.
const double blockSize = widthReal / static_cast<double>(this->renderThreadCount);
const double blockSize = widthReal / static_cast<double>(renderThreads.size());
const int startX = static_cast<int>(std::round(static_cast<double>(i) * blockSize));
const int endX = static_cast<int>(std::round(static_cast<double>(i + 1) * blockSize));

Expand Down
10 changes: 8 additions & 2 deletions OpenTESArena/src/Rendering/SoftwareRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ class SoftwareRenderer
std::vector<Double3> skyPalette; // Colors for each time of day.
double fogDistance; // Distance at which fog is maximum.
int width, height; // Dimensions of frame buffer.
int renderThreadCount; // Number of threads to use for rendering.
int renderThreadsMode; // Determines number of threads to use for rendering.

// Gets the fog color (based on the time of day). It returns a value instead of
// a reference because it interpolates between two colors for a smoother transition.
Expand All @@ -190,6 +190,9 @@ class SoftwareRenderer
// Gets the current sun direction based on the time of day.
Double3 getSunDirection(double daytimePercent) const;

// Gets the number of render threads to use based on the given mode.
static int getRenderThreadsFromMode(int mode);

// A variant of atan2() with a range of [0, 2pi] instead of [-pi, pi].
static double fullAtan2(double y, double x);

Expand Down Expand Up @@ -308,6 +311,9 @@ class SoftwareRenderer

bool isInited() const;

// Sets the render threads mode to use (low, medium, high, etc.).
void setRenderThreadsMode(int mode);

// Adds a flat. Causes an error if the ID exists.
void addFlat(int id, const Double3 &position, double width, double height, int textureID);

Expand Down Expand Up @@ -353,7 +359,7 @@ class SoftwareRenderer

// Initializes software renderer with the given frame buffer dimensions. This can be called
// on first start or to reset the software renderer.
void init(int width, int height);
void init(int width, int height, int renderThreadsMode);

// Resizes the frame buffer and related values.
void resize(int width, int height);
Expand Down
5 changes: 5 additions & 0 deletions options/options-default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ CursorScale=3.60
# similar to Daggerfall's.
ModernInterface=false

# The render threads mode determines how many CPU threads are used for
# rendering. The actual number of threads depends on your CPU.
# 0: low, 1: medium, 2: high, 3: max
RenderThreadsMode=3

[Audio]
MusicVolume=0.30
SoundVolume=0.25
Expand Down

0 comments on commit da00122

Please sign in to comment.