| 1 |
#include "RenderSettings.hh" |
| 2 |
|
| 3 |
#include "CommandController.hh" |
| 4 |
#include "CommandException.hh" |
| 5 |
#include "Version.hh" |
| 6 |
|
| 7 |
#include "stl.hh" |
| 8 |
#include "unreachable.hh" |
| 9 |
|
| 10 |
#include "build-info.hh" |
| 11 |
|
| 12 |
#include <algorithm> |
| 13 |
#include <cmath> |
| 14 |
#include <iostream> |
| 15 |
|
| 16 |
using namespace gl; |
| 17 |
|
| 18 |
namespace openmsx { |
| 19 |
|
| 20 |
EnumSetting<RenderSettings::ScaleAlgorithm>::Map RenderSettings::getScalerMap() |
| 21 |
{ |
| 22 |
using enum ScaleAlgorithm; |
| 23 |
EnumSetting<ScaleAlgorithm>::Map scalerMap = { |
| 24 |
{"simple", SIMPLE}, |
| 25 |
{"ScaleNx", SCALE}, |
| 26 |
{"hq", HQ}, |
| 27 |
{"RGBtriplet", RGBTRIPLET}, |
| 28 |
{"TV", TV} |
| 29 |
}; |
| 30 |
return scalerMap; |
| 31 |
} |
| 32 |
|
| 33 |
EnumSetting<RenderSettings::RendererID>::Map RenderSettings::getRendererMap() |
| 34 |
{ |
| 35 |
using enum RendererID; |
| 36 |
EnumSetting<RendererID>::Map rendererMap = { |
| 37 |
{"uninitialized", UNINITIALIZED}, |
| 38 |
{"none", DUMMY}, |
| 39 |
{"SDLGL-PP", SDLGL_PP} |
| 40 |
}; |
| 41 |
return rendererMap; |
| 42 |
} |
| 43 |
|
| 44 |
RenderSettings::RenderSettings(CommandController& commandController) |
| 45 |
: accuracySetting(commandController, |
| 46 |
"accuracy", "rendering accuracy", Accuracy::PIXEL, |
| 47 |
EnumSetting<Accuracy>::Map{ |
| 48 |
{"screen", Accuracy::SCREEN}, |
| 49 |
{"line", Accuracy::LINE}, |
| 50 |
{"pixel", Accuracy::PIXEL}}) |
| 51 |
|
| 52 |
, deinterlaceSetting(commandController, |
| 53 |
"deinterlace", "deinterlacing on/off", true) |
| 54 |
|
| 55 |
, deflickerSetting(commandController, |
| 56 |
"deflicker", "deflicker on/off", false) |
| 57 |
|
| 58 |
, maxFrameSkipSetting(commandController, |
| 59 |
"maxframeskip", "set the max amount of frameskip", 3, 0, 100) |
| 60 |
|
| 61 |
, minFrameSkipSetting(commandController, |
| 62 |
"minframeskip", "set the min amount of frameskip", 0, 0, 100) |
| 63 |
|
| 64 |
, fullScreenSetting(commandController, |
| 65 |
"fullscreen", "full screen display on/off", false) |
| 66 |
|
| 67 |
, gammaSetting(commandController, "gamma", |
| 68 |
"amount of gamma correction: low is dark, high is bright", |
| 69 |
1.1, 0.1, 5.0) |
| 70 |
|
| 71 |
, brightnessSetting(commandController, "brightness", |
| 72 |
"brightness video setting: " |
| 73 |
"0 is normal, lower is darker, higher is brighter", |
| 74 |
0.0, -100.0, 100.0) |
| 75 |
, contrastSetting(commandController, "contrast", |
| 76 |
"contrast video setting: " |
| 77 |
"0 is normal, lower is less contrast, higher is more contrast", |
| 78 |
0.0, -100.0, 100.0) |
| 79 |
|
| 80 |
, colorMatrixSetting(commandController, |
| 81 |
"color_matrix", |
| 82 |
"3x3 matrix to transform MSX RGB to host RGB, see manual for details", |
| 83 |
"{ 1 0 0 } { 0 1 0 } { 0 0 1 }") |
| 84 |
|
| 85 |
, glowSetting(commandController, |
| 86 |
"glow", "amount of afterglow effect: 0 = none, 100 = lots", |
| 87 |
0, 0, 100) |
| 88 |
|
| 89 |
, noiseSetting(commandController, |
| 90 |
"noise", "amount of noise to add to the frame", |
| 91 |
0.0, 0.0, 100.0) |
| 92 |
|
| 93 |
, rendererSetting(commandController, |
| 94 |
"renderer", "rendering back-end used to display the MSX screen", |
| 95 |
RendererID::SDLGL_PP, getRendererMap(), Setting::Save::NO) |
| 96 |
|
| 97 |
, horizontalBlurSetting(commandController, |
| 98 |
"blur", "amount of horizontal blur effect: 0 = none, 100 = full", |
| 99 |
50, 0, 100) |
| 100 |
|
| 101 |
, scaleAlgorithmSetting( |
| 102 |
commandController, "scale_algorithm", "scale algorithm", |
| 103 |
ScaleAlgorithm::SIMPLE, getScalerMap()) |
| 104 |
|
| 105 |
, scaleFactorSetting(commandController, |
| 106 |
"scale_factor", "scale factor", |
| 107 |
std::clamp(2, MIN_SCALE_FACTOR, MAX_SCALE_FACTOR), MIN_SCALE_FACTOR, MAX_SCALE_FACTOR) |
| 108 |
|
| 109 |
, scanlineAlphaSetting(commandController, |
| 110 |
"scanline", "amount of scanline effect: 0 = none, 100 = full", |
| 111 |
20, 0, 100) |
| 112 |
|
| 113 |
, limitSpritesSetting(commandController, |
| 114 |
"limitsprites", "limit number of sprites per line " |
| 115 |
"(on for realism, off to reduce sprite flashing)", true) |
| 116 |
|
| 117 |
, disableSpritesSetting(commandController, |
| 118 |
"disablesprites", "disable sprite rendering", |
| 119 |
false, Setting::Save::NO) |
| 120 |
|
| 121 |
, cmdTimingSetting(commandController, |
| 122 |
"cmdtiming", "VDP command timing", false, |
| 123 |
EnumSetting<bool>::Map{{"real", false}, {"broken", true}}, |
| 124 |
Setting::Save::NO) |
| 125 |
|
| 126 |
, tooFastAccessSetting(commandController, |
| 127 |
"too_fast_vram_access", |
| 128 |
"Should too fast VDP VRAM access be correctly emulated.\n" |
| 129 |
"Possible values are:\n" |
| 130 |
" real -> too fast accesses are dropped\n" |
| 131 |
" ignore -> access speed is ignored, all accesses are executed", |
| 132 |
false, |
| 133 |
EnumSetting<bool>::Map{{"real", false }, {"ignore", true}}, |
| 134 |
Setting::Save::NO) |
| 135 |
|
| 136 |
, displayDeformSetting( |
| 137 |
commandController, |
| 138 |
"display_deform", "Display deform", DisplayDeform::NORMAL, |
| 139 |
EnumSetting<DisplayDeform>::Map{ |
| 140 |
{"normal", DisplayDeform::NORMAL}, |
| 141 |
{"3d", DisplayDeform::_3D}}) |
| 142 |
|
| 143 |
, vSyncSetting(commandController, |
| 144 |
"vsync", "Synchronize page flip with the host screen vertical sync:\n" |
| 145 |
" on -> flip on host vsync: avoids tearing\n" |
| 146 |
" off -> immediate flip: might be more fluent when host framerate" |
| 147 |
" (typically 60Hz) differs from MSX framerate (50 or 60Hz).\n", |
| 148 |
true) |
| 149 |
|
| 150 |
, fullStretchSetting(commandController, |
| 151 |
"full_stretch", "Stretch the image to fill the entire screen in fullscreen mode", false) |
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
, horizontalStretchSetting(commandController, |
| 157 |
"horizontal_stretch", |
| 158 |
"Amount of horizontal stretch: this many MSX pixels will be " |
| 159 |
"stretched over the complete width of the output screen.\n" |
| 160 |
" 320 = no stretch (large borders)\n" |
| 161 |
" 288 = a bit more than all border pixels\n" |
| 162 |
" 284 = all border pixels\n" |
| 163 |
" 280 = a bit less than all border pixels\n" |
| 164 |
" 272 = realistic\n" |
| 165 |
" 256 = max stretch (no border visible at all)\n" |
| 166 |
" good values are 272 or 280\n", |
| 167 |
PLATFORM_ANDROID ? 320.0 : 280.0, 256.0, 320.0) |
| 168 |
|
| 169 |
, pointerHideDelaySetting(commandController, |
| 170 |
"pointer_hide_delay", |
| 171 |
"number of seconds after which the mouse pointer is hidden in the openMSX " |
| 172 |
"window; negative = no hiding, 0 = immediately", |
| 173 |
2.0, -1.0, 60.0) |
| 174 |
|
| 175 |
, interleaveBlackFrameSetting(commandController, |
| 176 |
"interleave_black_frame", |
| 177 |
"Insert a black frame in between each normal MSX frame. " |
| 178 |
"Useful on (100Hz+) lightboost enabled monitors to reduce " |
| 179 |
"motion blur and double frame artifacts.", |
| 180 |
false) |
| 181 |
{ |
| 182 |
brightnessSetting.attach(*this); |
| 183 |
contrastSetting .attach(*this); |
| 184 |
updateBrightnessAndContrast(); |
| 185 |
|
| 186 |
auto& interp = commandController.getInterpreter(); |
| 187 |
colorMatrixSetting.setChecker([this, &interp](const TclObject& newValue) { |
| 188 |
try { |
| 189 |
parseColorMatrix(interp, newValue); |
| 190 |
} catch (CommandException& e) { |
| 191 |
throw CommandException( |
| 192 |
"Invalid color matrix: ", e.getMessage()); |
| 193 |
} |
| 194 |
}); |
| 195 |
try { |
| 196 |
parseColorMatrix(interp, colorMatrixSetting.getValue()); |
| 197 |
} catch (MSXException& e) { |
| 198 |
std::cerr << e.getMessage() << '\n'; |
| 199 |
cmIdentity = true; |
| 200 |
} |
| 201 |
|
| 202 |
rendererSetting.setEnum(RendererID::UNINITIALIZED); |
| 203 |
rendererSetting.setChecker([this](const TclObject& newValue) { |
| 204 |
if (newValue.getString() == "uninitialized") { |
| 205 |
throw CommandException("can't set special value 'uninitialized'"); |
| 206 |
} |
| 207 |
|
| 208 |
(void)rendererSetting.fromStringBase(newValue.getString()); |
| 209 |
}); |
| 210 |
} |
| 211 |
|
| 212 |
RenderSettings::~RenderSettings() |
| 213 |
{ |
| 214 |
brightnessSetting.detach(*this); |
| 215 |
contrastSetting .detach(*this); |
| 216 |
} |
| 217 |
|
| 218 |
void RenderSettings::update(const Setting& setting) noexcept |
| 219 |
{ |
| 220 |
if (&setting == &brightnessSetting) { |
| 221 |
updateBrightnessAndContrast(); |
| 222 |
} else if (&setting == &contrastSetting) { |
| 223 |
updateBrightnessAndContrast(); |
| 224 |
} else { |
| 225 |
UNREACHABLE; |
| 226 |
} |
| 227 |
} |
| 228 |
|
| 229 |
void RenderSettings::updateBrightnessAndContrast() |
| 230 |
{ |
| 231 |
float contrastValue = getContrast(); |
| 232 |
contrast = (contrastValue >= 0.0f) ? (1.0f + contrastValue * (1.0f / 25.0f)) |
| 233 |
: (1.0f + contrastValue * (1.0f / 125.0f)); |
| 234 |
brightness = (getBrightness() * (1.0f / 100.0f) - 0.5f) * contrast + 0.5f; |
| 235 |
} |
| 236 |
|
| 237 |
static float conv2(float x, float gamma) |
| 238 |
{ |
| 239 |
return ::powf(std::clamp(x, 0.0f, 1.0f), gamma); |
| 240 |
} |
| 241 |
|
| 242 |
float RenderSettings::transformComponent(float c) const |
| 243 |
{ |
| 244 |
float c2 = c * contrast + brightness; |
| 245 |
return conv2(c2, 1.0f / getGamma()); |
| 246 |
} |
| 247 |
|
| 248 |
vec3 RenderSettings::transformRGB(vec3 rgb) const |
| 249 |
{ |
| 250 |
auto [r, g, b] = colorMatrix * (rgb * contrast + vec3(brightness)); |
| 251 |
float gamma = 1.0f / getGamma(); |
| 252 |
return {conv2(r, gamma), |
| 253 |
conv2(g, gamma), |
| 254 |
conv2(b, gamma)}; |
| 255 |
} |
| 256 |
|
| 257 |
void RenderSettings::parseColorMatrix(Interpreter& interp, const TclObject& value) |
| 258 |
{ |
| 259 |
if (value.getListLength(interp) != 3) { |
| 260 |
throw CommandException("must have 3 rows"); |
| 261 |
} |
| 262 |
bool identity = true; |
| 263 |
for (auto i : xrange(3)) { |
| 264 |
TclObject row = value.getListIndex(interp, i); |
| 265 |
if (row.getListLength(interp) != 3) { |
| 266 |
throw CommandException("each row must have 3 elements"); |
| 267 |
} |
| 268 |
for (auto j : xrange(3)) { |
| 269 |
TclObject element = row.getListIndex(interp, j); |
| 270 |
float val = element.getFloat(interp); |
| 271 |
colorMatrix[j][i] = val; |
| 272 |
identity &= (val == (i == j ? 1.0f : 0.0f)); |
| 273 |
} |
| 274 |
} |
| 275 |
cmIdentity = identity; |
| 276 |
} |
| 277 |
|
| 278 |
} |