[config] Add Observers for Options

This adds a generic Observer interface to config::Option. To
demonstrate its intended usage, the Display Configuration dialog and
the options it controls have been updated to be entirely handled via
the config::Option class.

Implementations for wxValidator are used to validate the flow between
the UI and the underlying Option. In turn, modifying the Option value
triggers all of its observers that should then do what they need to
do.

Rather than explicitly calling all of the needed methods after
modifying a global option value, the UI elements that need to be
notified when an Option value is modified will be notified via their
observers. Runtime assert checks are put in place to prevent infinite
recursion if an observer attempts to modify an Option while handling
the observer callback.

Once all uses of Options have been updated, we should get into a state
where the following will be true:
* cmdevents.cpp will no longer rely on the application state.
* All dialogs will have been moved to specific implementations,
  reducing the size of guiinit.cpp
* update_opts() will no longer need to be called and will be removed.

This will then make it easier to update accelerator handling to be
done with config::UserInput.

Bug: #745
This commit is contained in:
Fabrice de Gans 2022-10-16 16:42:28 -07:00 committed by Rafael Kitover
parent da686a4827
commit af4951511e
21 changed files with 1903 additions and 1187 deletions

File diff suppressed because it is too large Load Diff

View File

@ -751,8 +751,11 @@ set(
config/internal/option-internal.cpp
config/option.cpp
config/user-input.cpp
dialogs/display-config.cpp
widgets/keyedit.cpp
widgets/joyedit.cpp
widgets/option-validator.cpp
widgets/render-plugin.cpp
widgets/sdljoy.cpp
widgets/wxmisc.cpp
# probably ought to be in common
@ -792,7 +795,10 @@ set(
config/internal/option-internal.h
config/option.h
config/user-input.h
dialogs/display-config.h
widgets/dpi-support.h
widgets/option-validator.h
widgets/render-plugin.h
widgets/wx/keyedit.h
widgets/wx/joyedit.h
widgets/wx/sdljoy.h

View File

@ -11,10 +11,10 @@
#include <wx/wfstream.h>
#include <wx/msgdlg.h>
#include "strutils.h"
#include "../common/version_cpp.h"
#include "../gb/gbPrinter.h"
#include "../gba/agbprint.h"
#include "config/option.h"
#if (wxMAJOR_VERSION < 3)
#define GetXRCDialog(n) \
@ -2529,40 +2529,35 @@ EVT_HANDLER(GameBoyConfigure, "Game Boy options...")
update_opts();
}
// FIXME: These should be changed so AdjustSize() is called somewhere else.
EVT_HANDLER(SetSize1x, "1x")
{
gopts.video_scale = 1;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(1);
}
EVT_HANDLER(SetSize2x, "2x")
{
gopts.video_scale = 2;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(2);
}
EVT_HANDLER(SetSize3x, "3x")
{
gopts.video_scale = 3;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(3);
}
EVT_HANDLER(SetSize4x, "4x")
{
gopts.video_scale = 4;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(4);
}
EVT_HANDLER(SetSize5x, "5x")
{
gopts.video_scale = 5;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(5);
}
EVT_HANDLER(SetSize6x, "6x")
{
gopts.video_scale = 6;
panel->AdjustSize(true);
config::Option::ByID(config::OptionID::kDisplayScale)->SetDouble(6);
}
EVT_HANDLER(GameBoyAdvanceConfigure, "Game Boy Advance options...")
@ -2756,37 +2751,17 @@ EVT_HANDLER_MASK(DisplayConfigure, "Display options...", CMDEN_NREC_ANY)
EVT_HANDLER_MASK(ChangeFilter, "Change Pixel Filter", CMDEN_NREC_ANY)
{
int filt = gopts.filter;
if ((filt == FF_PLUGIN || ++gopts.filter == FF_PLUGIN) && gopts.filter_plugin.empty()) {
gopts.filter = 0;
}
update_opts();
if (panel->panel) {
panel->panel->Destroy();
panel->panel = nullptr;
}
wxString msg;
msg.Printf(_("Using pixel filter: %s"), pixel_filters_->GetString(gopts.filter));
systemScreenMessage(msg);
const wxString& filter_plugin =
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->GetString();
// Skip the filter plugin if it is not set.
config::Option::ByID(config::OptionID::kDisplayFilter)
->NextFilter(filter_plugin == wxEmptyString);
}
EVT_HANDLER_MASK(ChangeIFB, "Change Interframe Blending", CMDEN_NREC_ANY)
{
gopts.ifb = (gopts.ifb + 1) % 3;
update_opts();
if (panel->panel) {
panel->panel->Destroy();
panel->panel = nullptr;
}
wxString msg;
msg.Printf(_("Using interframe blending: %s"), interframe_blenders_->GetString(gopts.ifb));
systemScreenMessage(msg);
config::Option::ByID(config::OptionID::kDisplayIFB)->NextInterframe();
}
EVT_HANDLER_MASK(SoundConfigure, "Sound options...", CMDEN_NREC_ANY)

View File

@ -20,42 +20,10 @@ namespace config {
namespace {
// clang-format off
// This enum must be kept in sync with the one in wxvbam.h
// TODO: These 2 enums should be unified and a validator created for this enum.
enum class FilterFunction {
kNone,
k2xsai,
kSuper2xsai,
kSupereagle,
kPixelate,
kAdvmame,
kBilinear,
kBilinearplus,
kScanlines,
kTvmode,
kHQ2x,
kLQ2x,
kSimple2x,
kSimple3x,
kHQ3x,
kSimple4x,
kHQ4x,
kXbrz2x,
kXbrz3x,
kXbrz4x,
kXbrz5x,
kXbrz6x,
kPlugin, // This must always be last.
// Do not add anything under here.
kLast,
};
constexpr size_t kNbFilterFunctions = static_cast<size_t>(FilterFunction::kLast);
// These MUST follow the same order as the definitions of the enum above.
// These MUST follow the same order as the definitions of the enum.
// Adding an option without adding to this array will result in a compiler
// error since kNbFilterFunctions is automatically updated.
static const std::array<wxString, kNbFilterFunctions> kFilterStrings = {
// error since kNbFilters is automatically updated.
static const std::array<wxString, kNbFilters> kFilterStrings = {
"none",
"2xsai",
"super2xsai",
@ -81,19 +49,7 @@ static const std::array<wxString, kNbFilterFunctions> kFilterStrings = {
"plugin",
};
// This enum must be kept in sync with the one in wxvbam.h
// TODO: These 2 enums should be unified and a validator created for this enum.
enum class Interframe {
kNone = 0,
kSmart,
kMotionBlur,
// Do not add anything under here.
kLast,
};
constexpr size_t kNbInterframes = static_cast<size_t>(Interframe::kLast);
// These MUST follow the same order as the definitions of the enum above.
// These MUST follow the same order as the definitions of the enum.
// Adding an option without adding to this array will result in a compiler
// error since kNbInterframes is automatically updated.
static const std::array<wxString, kNbInterframes> kInterframeStrings = {
@ -102,29 +58,13 @@ static const std::array<wxString, kNbInterframes> kInterframeStrings = {
"motionblur",
};
// This enum must be kept in sync with the one in wxvbam.h
// TODO: These 2 enums should be unified and a validator created for this enum.
enum class RenderMethod {
kSimple = 0,
kOpenGL,
#ifdef __WXMSW__
kDirect3d,
#elif defined(__WXMAC__)
kQuartz2d,
#endif
// Do not add anything under here.
kLast,
};
constexpr size_t kNbRenderMethods = static_cast<size_t>(RenderMethod::kLast);
// These MUST follow the same order as the definitions of the enum above.
// These MUST follow the same order as the definitions of the enum.
// Adding an option without adding to this array will result in a compiler
// error since kNbRenderMethods is automatically updated.
static const std::array<wxString, kNbRenderMethods> kRenderMethodStrings = {
"simple",
"opengl",
#ifdef __WXMSW__
#if defined(__WXMSW__) && !defined(NO_D3D)
"direct3d",
#elif defined(__WXMAC__)
"quartz2d",
@ -201,7 +141,20 @@ wxString AllEnumValuesForArray(const std::array<wxString, SIZE>& input) {
} // namespace
// static
std::array<Option, kNbOptions>& Option::AllOptions() {
std::array<Option, kNbOptions>& Option::All() {
struct OwnedOptions {
double video_scale = 3;
wxString filter_plugin = wxEmptyString;
Filter filter = Filter::kNone;
Interframe interframe = Interframe::kNone;
#if defined(NO_OGL)
RenderMethod render_method = RenderMethod::kSimple;
#else
RenderMethod render_method = RenderMethod::kOpenGL;
#endif
};
static OwnedOptions g_owned_opts;
// These MUST follow the same order as the definitions in OptionID.
// Adding an option without adding to this array will result in a compiler
// error since kNbOptions is automatically updated.
@ -210,13 +163,13 @@ std::array<Option, kNbOptions>& Option::AllOptions() {
static std::array<Option, kNbOptions> g_all_opts = {
/// Display
Option(OptionID::kDisplayBilinear, &gopts.bilinear),
Option(OptionID::kDisplayFilter, &gopts.filter),
Option(OptionID::kDisplayFilterPlugin, &gopts.filter_plugin),
Option(OptionID::kDisplayIFB, &gopts.ifb),
Option(OptionID::kDisplayFilter, &g_owned_opts.filter),
Option(OptionID::kDisplayFilterPlugin, &g_owned_opts.filter_plugin),
Option(OptionID::kDisplayIFB, &g_owned_opts.interframe),
Option(OptionID::kDisplayKeepOnTop, &gopts.keep_on_top),
Option(OptionID::kDisplayMaxThreads, &gopts.max_threads, 1, 256),
Option(OptionID::kDisplayRenderMethod, &gopts.render_method),
Option(OptionID::kDisplayScale, &gopts.video_scale, 1, 6),
Option(OptionID::kDisplayRenderMethod, &g_owned_opts.render_method),
Option(OptionID::kDisplayScale, &g_owned_opts.video_scale, 1, 6),
Option(OptionID::kDisplayStretch, &gopts.retain_aspect),
/// GB
@ -671,19 +624,22 @@ nonstd::optional<OptionID> StringToOptionId(const wxString& input) {
return iter->second;
}
wxString FilterToString(int value) {
assert(value >= 0 && static_cast<size_t>(value) < kNbFilterFunctions);
return kFilterStrings[value];
wxString FilterToString(const Filter& value) {
const size_t size_value = static_cast<size_t>(value);
assert(size_value >= 0 && size_value < kNbFilters);
return kFilterStrings[size_value];
}
wxString InterframeToString(int value) {
assert(value >= 0 && static_cast<size_t>(value) < kNbInterframes);
return kInterframeStrings[value];
wxString InterframeToString(const Interframe& value) {
const size_t size_value = static_cast<size_t>(value);
assert(size_value >= 0 && size_value < kNbInterframes);
return kInterframeStrings[size_value];
}
wxString RenderMethodToString(int value) {
assert(value >= 0 && static_cast<size_t>(value) < kNbRenderMethods);
return kRenderMethodStrings[value];
wxString RenderMethodToString(const RenderMethod& value) {
const size_t size_value = static_cast<size_t>(value);
assert(size_value >= 0 && size_value < kNbRenderMethods);
return kRenderMethodStrings[size_value];
}
wxString AudioApiToString(int value) {
@ -696,14 +652,14 @@ wxString SoundQualityToString(int value) {
return kSoundQualityStrings[value];
}
int StringToFilter(const wxString& config_name, const wxString& input) {
static std::map<wxString, FilterFunction> kStringToFilter;
Filter StringToFilter(const wxString& config_name, const wxString& input) {
static std::map<wxString, Filter> kStringToFilter;
if (kStringToFilter.empty()) {
for (size_t i = 0; i < kNbFilterFunctions; i++) {
for (size_t i = 0; i < kNbFilters; i++) {
kStringToFilter.emplace(kFilterStrings[i],
static_cast<FilterFunction>(i));
static_cast<Filter>(i));
}
assert(kStringToFilter.size() == kNbFilterFunctions);
assert(kStringToFilter.size() == kNbFilters);
}
const auto iter = kStringToFilter.find(input);
@ -711,12 +667,12 @@ int StringToFilter(const wxString& config_name, const wxString& input) {
wxLogWarning(_("Invalid value %s for option %s; valid values are %s"),
input, config_name,
AllEnumValuesForType(Option::Type::kFilter));
return 0;
return Filter::kNone;
}
return static_cast<int>(iter->second);
return iter->second;
}
int StringToInterframe(const wxString& config_name, const wxString& input) {
Interframe StringToInterframe(const wxString& config_name, const wxString& input) {
static std::map<wxString, Interframe> kStringToInterframe;
if (kStringToInterframe.empty()) {
for (size_t i = 0; i < kNbInterframes; i++) {
@ -731,12 +687,13 @@ int StringToInterframe(const wxString& config_name, const wxString& input) {
wxLogWarning(_("Invalid value %s for option %s; valid values are %s"),
input, config_name,
AllEnumValuesForType(Option::Type::kInterframe));
return 0;
return Interframe::kNone;
}
return static_cast<int>(iter->second);
return iter->second;
}
int StringToRenderMethod(const wxString& config_name, const wxString& input) {
RenderMethod StringToRenderMethod(const wxString& config_name,
const wxString& input) {
static std::map<wxString, RenderMethod> kStringToRenderMethod;
if (kStringToRenderMethod.empty()) {
for (size_t i = 0; i < kNbRenderMethods; i++) {
@ -751,9 +708,9 @@ int StringToRenderMethod(const wxString& config_name, const wxString& input) {
wxLogWarning(_("Invalid value %s for option %s; valid values are %s"),
input, config_name,
AllEnumValuesForType(Option::Type::kRenderMethod));
return 0;
return RenderMethod::kSimple;
}
return static_cast<int>(iter->second);
return iter->second;
}
int StringToAudioApi(const wxString& config_name, const wxString& input) {
@ -843,7 +800,7 @@ wxString AllEnumValuesForType(Option::Type type) {
int MaxForType(Option::Type type) {
switch (type) {
case Option::Type::kFilter:
return kNbFilterFunctions;
return kNbFilters;
case Option::Type::kInterframe:
return kNbInterframes;
case Option::Type::kRenderMethod:

View File

@ -24,14 +24,14 @@ extern const std::array<OptionData, kNbOptions + 1> kAllOptionsData;
// Conversion utilities.
nonstd::optional<OptionID> StringToOptionId(const wxString& input);
wxString FilterToString(int value);
wxString InterframeToString(int value);
wxString RenderMethodToString(int value);
wxString FilterToString(const Filter& value);
wxString InterframeToString(const Interframe& value);
wxString RenderMethodToString(const RenderMethod& value);
wxString AudioApiToString(int value);
wxString SoundQualityToString(int value);
int StringToFilter(const wxString& config_name, const wxString& input);
int StringToInterframe(const wxString& config_name, const wxString& input);
int StringToRenderMethod(const wxString& config_name, const wxString& input);
Filter StringToFilter(const wxString& config_name, const wxString& input);
Interframe StringToInterframe(const wxString& config_name, const wxString& input);
RenderMethod StringToRenderMethod(const wxString& config_name, const wxString& input);
int StringToAudioApi(const wxString& config_name, const wxString& input);
int StringToSoundQuality(const wxString& config_name, const wxString& input);

View File

@ -12,23 +12,47 @@
namespace config {
// static
Option const* Option::FindByName(const wxString& config_name) {
Option* Option::ByName(const wxString& config_name) {
nonstd::optional<OptionID> option_id =
internal::StringToOptionId(config_name);
if (!option_id) {
return nullptr;
}
return &FindByID(option_id.value());
return ByID(option_id.value());
}
// static
Option& Option::FindByID(OptionID id) {
Option* Option::ByID(OptionID id) {
assert(id != OptionID::Last);
return AllOptions()[static_cast<size_t>(id)];
return &All()[static_cast<size_t>(id)];
}
// static
Filter Option::GetFilterValue() {
return Option::ByID(OptionID::kDisplayFilter)->GetFilter();
}
// static
Interframe Option::GetInterframeValue() {
return Option::ByID(OptionID::kDisplayIFB)->GetInterframe();
}
// static
RenderMethod Option::GetRenderMethodValue() {
return Option::ByID(OptionID::kDisplayRenderMethod)->GetRenderMethod();
}
Option::~Option() = default;
Option::Observer::Observer(OptionID option_id)
: option_(Option::ByID(option_id)) {
assert(option_);
option_->AddObserver(this);
}
Option::Observer::~Observer() {
option_->RemoveObserver(this);
}
Option::Option(OptionID id)
: id_(id),
config_name_(
@ -128,6 +152,51 @@ Option::Option(OptionID id, wxString* option)
assert(is_string());
}
Option::Option(OptionID id, Filter* option)
: id_(id),
config_name_(
internal::kAllOptionsData[static_cast<size_t>(id)].config_name),
command_(internal::kAllOptionsData[static_cast<size_t>(id)].command),
ux_helper_(wxGetTranslation(
internal::kAllOptionsData[static_cast<size_t>(id)].ux_helper)),
type_(internal::kAllOptionsData[static_cast<size_t>(id)].type),
value_(option),
min_(0),
max_(internal::MaxForType(type_)) {
assert(id != OptionID::Last);
assert(is_filter());
}
Option::Option(OptionID id, Interframe* option)
: id_(id),
config_name_(
internal::kAllOptionsData[static_cast<size_t>(id)].config_name),
command_(internal::kAllOptionsData[static_cast<size_t>(id)].command),
ux_helper_(wxGetTranslation(
internal::kAllOptionsData[static_cast<size_t>(id)].ux_helper)),
type_(internal::kAllOptionsData[static_cast<size_t>(id)].type),
value_(option),
min_(0),
max_(internal::MaxForType(type_)) {
assert(id != OptionID::Last);
assert(is_interframe());
}
Option::Option(OptionID id, RenderMethod* option)
: id_(id),
config_name_(
internal::kAllOptionsData[static_cast<size_t>(id)].config_name),
command_(internal::kAllOptionsData[static_cast<size_t>(id)].command),
ux_helper_(wxGetTranslation(
internal::kAllOptionsData[static_cast<size_t>(id)].ux_helper)),
type_(internal::kAllOptionsData[static_cast<size_t>(id)].type),
value_(option),
min_(0),
max_(internal::MaxForType(type_)) {
assert(id != OptionID::Last);
assert(is_render_method());
}
Option::Option(OptionID id, int* option)
: id_(id),
config_name_(
@ -140,8 +209,7 @@ Option::Option(OptionID id, int* option)
min_(0),
max_(internal::MaxForType(type_)) {
assert(id != OptionID::Last);
assert(is_filter() || is_interframe() || is_render_method() ||
is_audio_api() || is_sound_quality());
assert(is_audio_api() || is_sound_quality());
// Validate the initial value.
SetEnumInt(*option);
@ -182,21 +250,34 @@ uint32_t Option::GetUnsigned() const {
return *(nonstd::get<uint32_t*>(value_));
}
const wxString Option::GetString() const {
const wxString& Option::GetString() const {
assert(is_string());
return *(nonstd::get<wxString*>(value_));
}
Filter Option::GetFilter() const {
assert(is_filter());
return *(nonstd::get<Filter*>(value_));
}
Interframe Option::GetInterframe() const {
assert(is_interframe());
return *(nonstd::get<Interframe*>(value_));
}
RenderMethod Option::GetRenderMethod() const {
assert(is_render_method());
return *(nonstd::get<RenderMethod*>(value_));
}
wxString Option::GetEnumString() const {
switch (type_) {
case Option::Type::kFilter:
return internal::FilterToString(*(nonstd::get<int32_t*>(value_)));
return internal::FilterToString(GetFilter());
case Option::Type::kInterframe:
return internal::InterframeToString(
*(nonstd::get<int32_t*>(value_)));
return internal::InterframeToString(GetInterframe());
case Option::Type::kRenderMethod:
return internal::RenderMethodToString(
*(nonstd::get<int32_t*>(value_)));
return internal::RenderMethodToString(GetRenderMethod());
case Option::Type::kAudioApi:
return internal::AudioApiToString(*(nonstd::get<int32_t*>(value_)));
case Option::Type::kSoundQuality:
@ -230,72 +311,128 @@ wxString Option::GetGbPaletteString() const {
return palette_string;
}
void Option::SetBool(bool value) const {
bool Option::SetBool(bool value) {
assert(is_bool());
bool old_value = GetBool();
*nonstd::get<bool*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetDouble(double value) const {
bool Option::SetDouble(double value) {
assert(is_double());
double old_value = GetDouble();
if (value < nonstd::get<double>(min_) ||
value > nonstd::get<double>(max_)) {
wxLogWarning(
_("Invalid value %f for option %s; valid values are %f - %f"),
value, config_name_, nonstd::get<double>(min_),
nonstd::get<double>(max_));
return;
return false;
}
*nonstd::get<double*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetInt(int32_t value) const {
bool Option::SetInt(int32_t value) {
assert(is_int());
int old_value = GetInt();
if (value < nonstd::get<int32_t>(min_) ||
value > nonstd::get<int32_t>(max_)) {
wxLogWarning(
_("Invalid value %d for option %s; valid values are %d - %d"),
value, config_name_, nonstd::get<int32_t>(min_),
nonstd::get<int32_t>(max_));
return;
return false;
}
*nonstd::get<int32_t*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetUnsigned(uint32_t value) const {
bool Option::SetUnsigned(uint32_t value) {
assert(is_unsigned());
uint32_t old_value = value;
if (value < nonstd::get<uint32_t>(min_) ||
value > nonstd::get<uint32_t>(max_)) {
wxLogWarning(
_("Invalid value %d for option %s; valid values are %d - %d"),
value, config_name_, nonstd::get<uint32_t>(min_),
nonstd::get<uint32_t>(max_));
return;
return false;
}
*nonstd::get<uint32_t*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetString(const wxString& value) const {
bool Option::SetString(const wxString& value) {
assert(is_string());
const wxString old_value = GetString();
*nonstd::get<wxString*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetEnumString(const wxString& value) const {
bool Option::SetFilter(const Filter& value) {
assert(is_filter());
assert(value != Filter::kLast);
const Filter old_value = GetFilter();
*nonstd::get<Filter*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
bool Option::SetInterframe(const Interframe& value) {
assert(is_interframe());
assert(value != Interframe::kLast);
const Interframe old_value = GetInterframe();
*nonstd::get<Interframe*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
bool Option::SetRenderMethod(const RenderMethod& value) {
assert(is_render_method());
assert(value != RenderMethod::kLast);
const RenderMethod old_value = GetRenderMethod();
*nonstd::get<RenderMethod*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
bool Option::SetEnumString(const wxString& value) {
switch (type_) {
case Option::Type::kFilter:
SetEnumInt(internal::StringToFilter(config_name_, value));
return;
return SetFilter(internal::StringToFilter(config_name_, value));
case Option::Type::kInterframe:
SetEnumInt(internal::StringToInterframe(config_name_, value));
return;
return SetInterframe(
internal::StringToInterframe(config_name_, value));
case Option::Type::kRenderMethod:
SetEnumInt(internal::StringToRenderMethod(config_name_, value));
return;
return SetRenderMethod(
internal::StringToRenderMethod(config_name_, value));
case Option::Type::kAudioApi:
SetEnumInt(internal::StringToAudioApi(config_name_, value));
return;
return SetEnumInt(internal::StringToAudioApi(config_name_, value));
case Option::Type::kSoundQuality:
SetEnumInt(internal::StringToSoundQuality(config_name_, value));
return;
return SetEnumInt(
internal::StringToSoundQuality(config_name_, value));
// We don't use default here to explicitly trigger a compiler warning
// when adding a new value.
@ -307,24 +444,30 @@ void Option::SetEnumString(const wxString& value) const {
case Option::Type::kString:
case Option::Type::kGbPalette:
assert(false);
return;
return false;
}
assert(false);
return false;
}
void Option::SetEnumInt(int value) const {
assert(is_filter() || is_interframe() || is_render_method() ||
is_audio_api() || is_sound_quality());
bool Option::SetEnumInt(int value) {
assert(is_audio_api() || is_sound_quality());
int32_t old_value = *nonstd::get<int32_t*>(value_);
if (value < nonstd::get<int32_t>(min_) ||
value > nonstd::get<int32_t>(max_)) {
wxLogWarning(_("Invalid value %d for option %s; valid values are %s"),
value, config_name_,
internal::AllEnumValuesForType(type_));
return;
return false;
}
*nonstd::get<int32_t*>(value_) = value;
if (old_value != value) {
CallObservers();
}
return true;
}
void Option::SetGbPalette(const wxString& value) const {
bool Option::SetGbPalette(const wxString& value) {
assert(is_gb_palette());
// 8 values of 4 chars and 7 commas.
@ -332,17 +475,44 @@ void Option::SetGbPalette(const wxString& value) const {
if (value.size() != kPaletteStringSize) {
wxLogWarning(_("Invalid value %s for option %s"), value, config_name_);
return;
return false;
}
uint16_t* dest = nonstd::get<uint16_t*>(value_);
uint16_t* dest = nonstd::get<uint16_t*>(value_);
std::array<uint16_t, 8> old_value;
std::copy(dest, dest + 8, old_value.data());
std::array<uint16_t, 8> new_value;
for (size_t i = 0; i < 8; i++) {
wxString number = value.substr(i * 5, 4);
long temp = 0;
if (number.ToLong(&temp, 16)) {
dest[i] = temp;
new_value[i] = temp;
}
}
std::copy(new_value.begin(), new_value.end(), dest);
if (old_value != new_value) {
CallObservers();
}
return true;
}
void Option::NextFilter(bool skip_filter_plugin) {
assert(is_filter());
const int old_value = static_cast<int>(GetFilter());
Filter new_filter = static_cast<Filter>((old_value + 1) % kNbFilters);
if (skip_filter_plugin && new_filter == Filter::kPlugin) {
new_filter = Filter::kNone;
}
SetFilter(new_filter);
}
void Option::NextInterframe() {
assert(is_interframe());
const int old_value = static_cast<int>(GetInterframe());
const int new_value = (old_value + 1) % kNbInterframes;
SetInterframe(static_cast<Interframe>(new_value));
}
wxString Option::ToHelperString() const {
@ -386,4 +556,38 @@ wxString Option::ToHelperString() const {
return helper_string;
}
void Option::AddObserver(Observer* observer) {
assert(observer);
[[maybe_unused]] const auto pair = observers_.emplace(observer);
assert(pair.second);
}
void Option::RemoveObserver(Observer* observer) {
assert(observer);
[[maybe_unused]] const size_t removed = observers_.erase(observer);
assert(removed == 1u);
}
void Option::CallObservers() {
assert(!calling_observers_);
calling_observers_ = true;
for (const auto observer : observers_) {
observer->OnValueChanged();
}
calling_observers_ = false;
}
BasicOptionObserver::BasicOptionObserver(
config::OptionID option_id,
std::function<void(config::Option*)> callback)
: Option::Observer(option_id), callback_(std::move(callback)) {
assert(callback_);
}
BasicOptionObserver::~BasicOptionObserver() = default;
void BasicOptionObserver::OnValueChanged() {
callback_(option());
}
} // namespace config

View File

@ -4,6 +4,8 @@
#include "nonstd/variant.hpp"
#include <array>
#include <functional>
#include <unordered_set>
#include <wx/string.h>
@ -145,8 +147,65 @@ enum class OptionID {
// Do not add anything under here.
Last,
};
static constexpr size_t kNbOptions = static_cast<size_t>(OptionID::Last);
constexpr size_t kNbOptions = static_cast<size_t>(OptionID::Last);
// Values for kDisplayFilter.
enum class Filter {
kNone,
k2xsai,
kSuper2xsai,
kSupereagle,
kPixelate,
kAdvmame,
kBilinear,
kBilinearplus,
kScanlines,
kTvmode,
kHQ2x,
kLQ2x,
kSimple2x,
kSimple3x,
kHQ3x,
kSimple4x,
kHQ4x,
kXbrz2x,
kXbrz3x,
kXbrz4x,
kXbrz5x,
kXbrz6x,
kPlugin, // This must always be last.
// Do not add anything under here.
kLast,
};
static constexpr size_t kNbFilters = static_cast<size_t>(Filter::kLast);
// Values for kDisplayIFB.
enum class Interframe {
kNone = 0,
kSmart,
kMotionBlur,
// Do not add anything under here.
kLast,
};
static constexpr size_t kNbInterframes = static_cast<size_t>(Interframe::kLast);
// Values for kDisplayRenderMethod.
enum class RenderMethod {
kSimple = 0,
kOpenGL,
#if defined(__WXMSW__) && !defined(NO_D3D)
kDirect3d,
#elif defined(__WXMAC__)
kQuartz2d,
#endif
// Do not add anything under here.
kLast,
};
static constexpr size_t kNbRenderMethods =
static_cast<size_t>(RenderMethod::kLast);
// Represents a single option saved in the INI file. Option does not own the
// individual option, but keeps a pointer to where the data is actually saved.
@ -156,8 +215,7 @@ constexpr size_t kNbOptions = static_cast<size_t>(OptionID::Last);
// Option::Set*() is called. This should also handle keyboard and joystick
// configuration so option parsing can be done in a uniform manner. If we ever
// get to that point, we would be able to remove most update_opts() calls and
// have individual UI elements access the option via
// Option::FindByID().
// have individual UI elements access the option via Option::ByID().
//
// The implementation for this class is largely inspired by base::Value in
// Chromium.
@ -179,13 +237,41 @@ public:
kGbPalette,
};
static std::array<Option, kNbOptions>& AllOptions();
// Observer for an option. OnValueChanged() will be called when the value
// has changed. Implementers should take care of not modifying option()
// in the OnValueChanged() handler.
class Observer {
public:
explicit Observer(config::OptionID option_id);
virtual ~Observer();
// Class is move-only.
Observer(const Observer&) = delete;
Observer& operator=(const Observer&) = delete;
Observer(Observer&& other) = default;
Observer& operator=(Observer&& other) = default;
virtual void OnValueChanged() = 0;
protected:
Option* option() const { return option_; }
private:
Option* option_;
};
static std::array<Option, kNbOptions>& All();
// O(log(kNbOptions))
static Option const* FindByName(const wxString& config_name);
static Option* ByName(const wxString& config_name);
// O(1)
static Option& FindByID(OptionID id);
static Option* ByID(OptionID id);
// Convenience direct accessors for some enum options.
static Filter GetFilterValue();
static Interframe GetInterframeValue();
static RenderMethod GetRenderMethodValue();
~Option();
@ -193,6 +279,7 @@ public:
const wxString& config_name() const { return config_name_; }
const wxString& command() const { return command_; }
const wxString& ux_helper() const { return ux_helper_; }
const OptionID& id() const { return id_; }
// Returns the type of the value stored by the current object.
Type type() const { return type_; }
@ -212,25 +299,35 @@ public:
bool is_gb_palette() const { return type() == Type::kGbPalette; }
// Returns a reference to the stored data. Will assert on type mismatch.
// All enum types go through GetEnumString().
// Only enum types can use through GetEnumString().
bool GetBool() const;
double GetDouble() const;
int32_t GetInt() const;
uint32_t GetUnsigned() const;
const wxString GetString() const;
const wxString& GetString() const;
Filter GetFilter() const;
Interframe GetInterframe() const;
RenderMethod GetRenderMethod() const;
wxString GetEnumString() const;
wxString GetGbPaletteString() const;
// Sets the value. Will assert on type mismatch.
// All enum types go through SetEnumString() and SetEnumInt().
void SetBool(bool value) const;
void SetDouble(double value) const;
void SetInt(int32_t value) const;
void SetUnsigned(uint32_t value) const;
void SetString(const wxString& value) const;
void SetEnumString(const wxString& value) const;
void SetEnumInt(int value) const;
void SetGbPalette(const wxString& value) const;
// Only enum types can use SetEnumString().
// Returns true on success. On failure, the value will not be modified.
bool SetBool(bool value);
bool SetDouble(double value);
bool SetInt(int32_t value);
bool SetUnsigned(uint32_t value);
bool SetString(const wxString& value);
bool SetFilter(const Filter& value);
bool SetInterframe(const Interframe& value);
bool SetRenderMethod(const RenderMethod& value);
bool SetEnumString(const wxString& value);
bool SetGbPalette(const wxString& value);
// Special convenience modifiers.
void NextFilter(bool skip_filter_plugin);
void NextInterframe();
// Command-line helper string.
wxString ToHelperString() const;
@ -240,15 +337,32 @@ private:
Option(const Option&) = delete;
Option& operator=(const Option&) = delete;
Option(OptionID id);
explicit Option(OptionID id);
Option(OptionID id, bool* option);
Option(OptionID id, double* option, double min, double max);
Option(OptionID id, int32_t* option, int32_t min, int32_t max);
Option(OptionID id, uint32_t* option, uint32_t min, uint32_t max);
Option(OptionID id, wxString* option);
Option(OptionID id, Filter* option);
Option(OptionID id, Interframe* option);
Option(OptionID id, RenderMethod* option);
Option(OptionID id, int* option);
Option(OptionID id, uint16_t* option);
// Helper method for enums not fully converted yet.
bool SetEnumInt(int value);
// Observer.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
void CallObservers();
std::unordered_set<Observer*> observers_;
// Set to true when the observers are being called. This will fire an assert
// to prevent modifying the object again, which would trigger an infinite
// call stack.
bool calling_observers_ = false;
const OptionID id_;
const wxString config_name_;
@ -262,6 +376,9 @@ private:
int32_t*,
uint32_t*,
wxString*,
Filter*,
Interframe*,
RenderMethod*,
uint16_t*>
value_;
@ -269,6 +386,20 @@ private:
const nonstd::variant<nonstd::monostate, double, int32_t, uint32_t> max_;
};
// A simple Option::Observer that calls a callback when the value has changed.
class BasicOptionObserver : public Option::Observer {
public:
BasicOptionObserver(config::OptionID option_id,
std::function<void(config::Option*)> callback);
~BasicOptionObserver() override;
private:
// Option::Observer implementation.
void OnValueChanged() override;
std::function<void(config::Option*)> callback_;
};
} // namespace config
#endif // VBAM_WX_CONFIG_OPTIONS_H_

View File

@ -0,0 +1,445 @@
#include "dialogs/display-config.h"
#include <wx/arrstr.h>
#include <wx/choice.h>
#include <wx/clntdata.h>
#include <wx/dir.h>
#include <wx/dynlib.h>
#include <wx/log.h>
#include <wx/object.h>
#include <wx/radiobut.h>
#include <wx/spinctrl.h>
#include <wx/stdpaths.h>
#include <wx/textctrl.h>
#include <wx/xrc/xmlres.h>
#include "../../System.h"
#include "../../common/ConfigManager.h"
#include "config/option.h"
#include "rpi.h"
#include "wayland.h"
#include "widgets/option-validator.h"
#include "widgets/render-plugin.h"
#include "widgets/wx/wxmisc.h"
#include "wxvbam.h"
namespace dialogs {
namespace {
// Validator for a wxTextCtrl with a double value.
class ScaleValidator : public widgets::OptionValidator {
public:
ScaleValidator()
: widgets::OptionValidator(config::OptionID::kDisplayScale) {}
~ScaleValidator() override = default;
private:
// OptionValidator implementation.
wxObject* Clone() const override { return new ScaleValidator(); }
bool IsWindowValueValid() override {
double value;
if (!wxDynamicCast(GetWindow(), wxTextCtrl)
->GetValue()
.ToDouble(&value)) {
return false;
}
return true;
}
bool WriteToWindow() override {
wxDynamicCast(GetWindow(), wxTextCtrl)
->SetValue(wxString::Format("%.1f", option()->GetDouble()));
return true;
}
bool WriteToOption() override {
double value;
if (!wxDynamicCast(GetWindow(), wxTextCtrl)
->GetValue()
.ToDouble(&value)) {
return false;
}
return option()->SetDouble(value);
}
};
// Validator for a wxChoice with a Filter value.
class FilterValidator : public widgets::OptionValidator {
public:
FilterValidator() : OptionValidator(config::OptionID::kDisplayFilter) {
Bind(wxEVT_CHOICE, &FilterValidator::OnChoice, this);
}
~FilterValidator() override = default;
private:
// wxChoice event handler.
void OnChoice(wxCommandEvent&) { WriteToOption(); }
// OptionValidator implementation.
wxObject* Clone() const override { return new FilterValidator(); }
bool IsWindowValueValid() override { return true; }
bool WriteToWindow() override {
wxDynamicCast(GetWindow(), wxChoice)
->SetSelection(static_cast<int>(option()->GetFilter()));
return true;
}
bool WriteToOption() override {
const int selection =
wxDynamicCast(GetWindow(), wxChoice)->GetSelection();
if (selection == wxNOT_FOUND) {
return false;
}
if (static_cast<size_t>(selection) > config::kNbFilters) {
return false;
}
return option()->SetFilter(static_cast<config::Filter>(selection));
}
};
// Validator for a wxChoice with an Interframe value.
class InterframeValidator : public widgets::OptionValidator {
public:
InterframeValidator() : OptionValidator(config::OptionID::kDisplayIFB) {
Bind(wxEVT_CHOICE, &InterframeValidator::OnChoice, this);
}
~InterframeValidator() override = default;
private:
// wxChoice event handler.
void OnChoice(wxCommandEvent&) { WriteToOption(); }
// OptionValidator implementation.
wxObject* Clone() const override { return new InterframeValidator(); }
bool IsWindowValueValid() override { return true; }
bool WriteToWindow() override {
wxDynamicCast(GetWindow(), wxChoice)
->SetSelection(static_cast<int>(option()->GetInterframe()));
return true;
}
bool WriteToOption() override {
const int selection =
wxDynamicCast(GetWindow(), wxChoice)->GetSelection();
if (selection == wxNOT_FOUND) {
return false;
}
if (static_cast<size_t>(selection) > config::kNbInterframes) {
return false;
}
return option()->SetInterframe(
static_cast<config::Interframe>(selection));
}
};
// Validator for a wxRadioButton with a RenderMethod value.
class RenderValidator : public widgets::OptionValidator {
public:
explicit RenderValidator(config::RenderMethod render_method)
: OptionValidator(config::OptionID::kDisplayRenderMethod),
render_method_(render_method) {
assert(render_method != config::RenderMethod::kLast);
Bind(wxEVT_RADIOBUTTON, &RenderValidator::OnRadioButton, this);
}
~RenderValidator() override = default;
private:
// wxRadioButton event handler.
void OnRadioButton(wxCommandEvent&) { WriteToOption(); }
// OptionValidator implementation.
wxObject* Clone() const override {
return new RenderValidator(render_method_);
}
bool IsWindowValueValid() override { return true; }
bool WriteToWindow() override {
wxDynamicCast(GetWindow(), wxRadioButton)
->SetValue(option()->GetRenderMethod() == render_method_);
return true;
}
bool WriteToOption() override {
if (wxDynamicCast(GetWindow(), wxRadioButton)->GetValue()) {
return option()->SetRenderMethod(render_method_);
}
return true;
}
const config::RenderMethod render_method_;
};
class PluginSelectorValidator : public widgets::OptionValidator {
public:
PluginSelectorValidator()
: widgets::OptionValidator(config::OptionID::kDisplayFilterPlugin) {}
~PluginSelectorValidator() override = default;
private:
// widgets::OptionValidator implementation.
wxObject* Clone() const override { return new PluginSelectorValidator(); }
bool IsWindowValueValid() override { return true; }
bool WriteToWindow() override {
wxChoice* plugin_selector = wxDynamicCast(GetWindow(), wxChoice);
assert(plugin_selector);
const wxString selected_plugin = option()->GetString();
for (size_t i = 0; i < plugin_selector->GetCount(); i++) {
const wxString& plugin_data =
dynamic_cast<wxStringClientData*>(
plugin_selector->GetClientObject(i))
->GetData();
if (plugin_data == selected_plugin) {
plugin_selector->SetSelection(i);
return true;
}
}
return false;
}
bool WriteToOption() override {
wxChoice* plugin_selector = wxDynamicCast(GetWindow(), wxChoice);
assert(plugin_selector);
const wxString& selected_window_plugin =
dynamic_cast<wxStringClientData*>(
plugin_selector->GetClientObject(
plugin_selector->GetSelection()))
->GetData();
return option()->SetString(selected_window_plugin);
}
};
// Helper functions to assert on the returned value.
wxWindow* GetValidatedChild(const wxWindow* parent, const wxString& name) {
wxWindow* window = parent->FindWindow(name);
assert(window);
return window;
}
template <class T>
T* GetValidatedChild(const wxWindow* parent, const wxString& name) {
T* child = wxDynamicCast(GetValidatedChild(parent, name), T);
assert(child);
return child;
}
} // namespace
// static
DisplayConfig* DisplayConfig::NewInstance(wxWindow* parent) {
assert(parent);
return new DisplayConfig(parent);
}
DisplayConfig::DisplayConfig(wxWindow* parent)
: wxDialog(),
filter_observer_(config::OptionID::kDisplayFilter,
std::bind(&DisplayConfig::OnFilterChanged,
this,
std::placeholders::_1)),
interframe_observer_(config::OptionID::kDisplayIFB,
std::bind(&DisplayConfig::OnInterframeChanged,
this,
std::placeholders::_1)) {
wxXmlResource::Get()->LoadDialog(this, parent, "DisplayConfig");
// Speed
// AutoSkip/FrameSkip are 2 controls for 1 value. Needs post-process
// to ensure checkbox not ignored
GetValidatedChild(this, "FrameSkip")
->SetValidator(wxGenericValidator(&frameSkip));
if (frameSkip >= 0) {
systemFrameSkip = frameSkip;
}
// On-Screen Display
GetValidatedChild(this, "SpeedIndicator")
->SetValidator(wxGenericValidator(&showSpeed));
// Zoom
GetValidatedChild(this, "DefaultScale")->SetValidator(ScaleValidator());
// this was a choice, but I'd rather not have to make an off-by-one
// validator just for this, and spinctrl is good enough.
GetValidatedChild(this, "MaxScale")
->SetValidator(wxGenericValidator(&maxScale));
// Basic
GetValidatedChild(this, "OutputSimple")
->SetValidator(RenderValidator(config::RenderMethod::kSimple));
#if defined(__WXMAC__)
GetValidatedChild(this, "OutputQuartz2D")
->SetValidator(RenderValidator(config::RenderMethod::kQuartz2d));
#else
GetValidatedChild(this, "OutputQuartz2D")->Hide();
#endif
#ifdef NO_OGL
GetValidatedChild(this, "OutputOpenGL")->Hide();
#elif defined(__WXGTK__) && !wxCHECK_VERSION(3, 2, 0)
// wxGLCanvas segfaults on Wayland before wx 3.2.
if (IsItWayland()) {
GetValidatedChild(this, "OutputOpenGL")->Hide();
} else {
GetValidatedChild(this, "OutputOpenGL")
->SetValidator(RenderValidator(config::RenderMethod::kOpenGL));
}
#else
GetValidatedChild(this, "OutputOpenGL")
->SetValidator(RenderValidator(config::RenderMethod::kOpenGL));
#endif // NO_OGL
// Direct3D is not implemented so hide the option on every platform.
GetValidatedChild(this, "OutputDirect3D")->Hide();
filter_selector_ = GetValidatedChild<wxChoice>(this, "Filter");
filter_selector_->SetValidator(FilterValidator());
// These are filled and/or hidden at dialog load time.
plugin_label_ = GetValidatedChild<wxControl>(this, "PluginLab");
plugin_selector_ = GetValidatedChild<wxChoice>(this, "Plugin");
interframe_selector_ = GetValidatedChild<wxChoice>(this, "IFB");
interframe_selector_->SetValidator(InterframeValidator());
Bind(wxEVT_SHOW, &DisplayConfig::OnDialogShown, this, GetId());
Bind(wxEVT_CLOSE_WINDOW, &DisplayConfig::OnDialogClosed, this, GetId());
// Finally, fit everything nicely.
Fit();
}
void DisplayConfig::OnDialogShown(wxShowEvent&) {
// Populate the plugin values, if any.
wxArrayString plugins;
const wxString plugin_path = wxGetApp().GetPluginsDir();
wxDir::GetAllFiles(plugin_path, &plugins, "*.rpi",
wxDIR_FILES | wxDIR_DIRS);
if (plugins.empty()) {
HidePluginOptions();
return;
}
plugin_selector_->Clear();
plugin_selector_->Append(_("None"), new wxStringClientData());
const wxString selected_plugin =
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->GetString();
bool is_plugin_selected = false;
for (const wxString& plugin : plugins) {
wxDynamicLibrary dyn_lib(plugin, wxDL_VERBATIM | wxDL_NOW);
wxDynamicLibrary filter_plugin;
const RENDER_PLUGIN_INFO* plugin_info =
widgets::MaybeLoadFilterPlugin(plugin, &filter_plugin);
if (!plugin_info) {
continue;
}
wxFileName file_name(plugin);
file_name.MakeRelativeTo(plugin_path);
const int added_index = plugin_selector_->Append(
file_name.GetName() + ": " +
wxString(plugin_info->Name, wxConvUTF8),
new wxStringClientData(plugin));
if (plugin == selected_plugin) {
plugin_selector_->SetSelection(added_index);
is_plugin_selected = true;
}
}
if (plugin_selector_->GetCount() == 1u) {
wxLogWarning(wxString::Format(_("No usable rpi plugins found in %s"),
plugin_path));
HidePluginOptions();
return;
}
if (!is_plugin_selected) {
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->SetString(wxEmptyString);
}
plugin_selector_->SetValidator(PluginSelectorValidator());
ShowPluginOptions();
Fit();
}
void DisplayConfig::OnDialogClosed(wxCloseEvent&) {
// Reset the validator to stop handling events while this dialog is not
// shown.
plugin_selector_->SetValidator(wxValidator());
}
void DisplayConfig::OnFilterChanged(config::Option* option) {
const config::Filter option_filter = option->GetFilter();
const bool show_plugin = option_filter == config::Filter::kPlugin;
plugin_label_->Enable(show_plugin);
plugin_selector_->Enable(show_plugin);
systemScreenMessage(wxString::Format(
_("Using pixel filter: %s"),
filter_selector_->GetString(static_cast<size_t>(option_filter))));
}
void DisplayConfig::OnInterframeChanged(config::Option* option) {
const config::Interframe interframe = option->GetInterframe();
systemScreenMessage(wxString::Format(
_("Using interframe blending: %s"),
interframe_selector_->GetString(static_cast<size_t>(interframe))));
}
void DisplayConfig::HidePluginOptions() {
plugin_label_->Hide();
plugin_selector_->Hide();
// Remove the Plugin option, which should be the last.
if (filter_selector_->GetCount() == config::kNbFilters) {
// Make sure we have not selected the plugin option. The validator
// will take care of updating the selector value.
if (config::Option::GetFilterValue() == config::Filter::kPlugin) {
config::Option::ByID(config::OptionID::kDisplayFilter)
->SetFilter(config::Filter::kNone);
}
filter_selector_->Delete(config::kNbFilters - 1);
}
// Also erase the Plugin value to avoid issues down the line.
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->SetString(wxEmptyString);
}
void DisplayConfig::ShowPluginOptions() {
plugin_label_->Show();
plugin_selector_->Show();
// Re-add the Plugin option, if needed.
if (filter_selector_->GetCount() != config::kNbFilters) {
filter_selector_->Append(_("Plugin"));
}
}
} // namespace dialogs

View File

@ -0,0 +1,54 @@
#ifndef VBAM_WX_DIALOGS_DISPLAY_CONFIG_H_
#define VBAM_WX_DIALOGS_DISPLAY_CONFIG_H_
#include <wx/dialog.h>
#include <wx/event.h>
#include "config/option.h"
// Forward declarations.
class wxChoice;
class wxControl;
class wxWindow;
namespace dialogs {
// Manages the display configuration dialog.
class DisplayConfig : public wxDialog {
public:
static DisplayConfig* NewInstance(wxWindow* parent);
~DisplayConfig() override = default;
private:
// The constructor is private so initialization has to be done via the
// static method. This is because this class is destroyed when its
// owner, `parent` is destroyed. This prevents accidental deletion.
DisplayConfig(wxWindow* parent);
// Populates the plugin options.
void OnDialogShown(wxShowEvent&);
//
void OnDialogClosed(wxCloseEvent&);
// Callback called when the render method changes.
void OnFilterChanged(config::Option* option);
// Callback called when the interframe method changes.
void OnInterframeChanged(config::Option* option);
// Hides/Shows the plugin-related filter options.
void HidePluginOptions();
void ShowPluginOptions();
wxControl* plugin_label_;
wxChoice* plugin_selector_;
wxChoice* filter_selector_;
wxChoice* interframe_selector_;
config::BasicOptionObserver filter_observer_;
config::BasicOptionObserver interframe_observer_;
};
} // namespace dialogs
#endif // VBAM_WX_DIALOGS_DISPLAY_CONFIG_H_

View File

@ -27,6 +27,7 @@
#include "config/game-control.h"
#include "config/option.h"
#include "config/user-input.h"
#include "dialogs/display-config.h"
#include "opts.h"
#if defined(__WXGTK__)
@ -1777,180 +1778,6 @@ private:
wxArrayVideoModes vm;
};
// enable plugin-related iff filter choice is plugin
class PluginEnabler : public wxValidator {
public:
PluginEnabler()
: wxValidator()
{
}
PluginEnabler(const PluginEnabler& e)
: wxValidator()
{
(void)e; // unused params
}
wxObject* Clone() const { return new PluginEnabler(*this); }
bool TransferFromWindow() { return true; }
bool Validate(wxWindow* p) {
(void)p; // unused params
return true;
}
bool TransferToWindow()
{
GetWindow()->Enable(gopts.filter == FF_PLUGIN);
return true;
}
};
// The same, but as an event handler
static class PluginEnable_t : public wxEvtHandler {
public:
wxWindow *lab, *ch;
void ToggleChoice(wxCommandEvent& ev)
{
bool en = ev.GetSelection() == FF_PLUGIN;
lab->Enable(en);
ch->Enable(en);
}
} PluginEnableHandler;
// fill in plugin list
class PluginListFiller : public PluginEnabler {
public:
PluginListFiller(wxDialog* parent, wxControl* lab, wxChoice* ch)
: PluginEnabler()
, dlg(parent)
, txt(lab)
, filtch(ch)
, plugins()
{
}
PluginListFiller(const PluginListFiller& e)
: PluginEnabler()
, dlg(e.dlg)
, txt(e.txt)
, filtch(e.filtch)
, plugins(e.plugins)
{
}
wxObject* Clone() const { return new PluginListFiller(*this); }
bool Validate(wxWindow* p) {
(void)p; // unused params
return true;
}
bool TransferToWindow()
{
PluginEnabler::TransferToWindow();
wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);
ch->Clear();
ch->Append(_("None"));
plugins.clear();
const wxString plpath = wxGetApp().GetPluginsDir();
wxDir::GetAllFiles(plpath, &plugins, wxT("*.rpi"), wxDIR_FILES | wxDIR_DIRS);
for (size_t i = 0; i < plugins.size(); i++) {
wxDynamicLibrary dl(plugins[i], wxDL_VERBATIM | wxDL_NOW);
RENDPLUG_GetInfo GetInfo;
const RENDER_PLUGIN_INFO* rpi = NULL;
if (dl.IsLoaded() && (GetInfo = (RENDPLUG_GetInfo)dl.GetSymbol(wxT("RenderPluginGetInfo"))) &&
// note that in actual kega fusion plugins, rpi->Output is
// unused (as is rpi->Handle)
dl.GetSymbol(wxT("RenderPluginOutput")) && (rpi = GetInfo()) &&
// FIXME: maybe this should be >= RPI_VERISON
(rpi->Flags & 0xff) == RPI_VERSION &&
// RPI_565_SUPP is not supported
// although it would be possible
// and it would make Cairo more efficient
(rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP))) {
wxFileName fn(plugins[i]);
wxString s = fn.GetName();
s += wxT(": ");
s += wxString(rpi->Name, wxConvUTF8, sizeof(rpi->Name));
fn.MakeRelativeTo(plpath);
plugins[i] = fn.GetFullPath();
ch->Append(s);
if (plugins[i] == gopts.filter_plugin)
ch->SetSelection(i + 1);
}
else {
plugins.RemoveAt(i--);
}
}
if (ch->GetCount() == 1) {
// this is probably the only place the user can find out where
// to put the plugins... it depends on where program was
// installed, and of course OS
wxString msg;
msg.Printf(_("No usable rpi plugins found in %s"), plpath.c_str());
systemScreenMessage(msg);
ch->Hide();
txt->Hide();
int cursel = filtch->GetSelection();
if (cursel == FF_PLUGIN)
cursel = 0;
if (filtch->GetCount() == FF_PLUGIN + 1) {
filtch->Delete(FF_PLUGIN);
// apparently wxgtk loses selection after this, even
// if selection was not FF_PLUGIN
filtch->SetSelection(cursel);
}
} else {
ch->Show();
txt->Show();
if (filtch->GetCount() < FF_PLUGIN + 1)
filtch->Append(_("Plugin"));
}
// FIXME: this isn't enough. It only resizes 2nd time around
dlg->Fit();
return true;
}
bool TransferFromWindow()
{
wxChoice* ch = wxStaticCast(GetWindow(), wxChoice);
if (ch->GetCount() == 1) {
gopts.filter_plugin = wxEmptyString;
// this happens if "Plugin" was selected and the entry was
// subsequently removed
if (ch->GetSelection() < 0)
ch->SetSelection(0);
if (gopts.filter < 0)
gopts.filter = 0;
} else {
int n = ch->GetSelection();
if (n > 0)
gopts.filter_plugin = plugins[n - 1];
else {
if (filtch->GetSelection() == FF_PLUGIN) {
wxMessageBox(_("Please select a plugin or a different filter"),
_("Plugin selection error"), wxOK | wxICON_ERROR);
return false;
}
gopts.filter_plugin = wxEmptyString;
}
}
return true;
}
private:
wxDialog* dlg;
wxControl* txt;
wxChoice* filtch;
wxArrayString plugins;
};
// this is the cmd table index for the accel tree ctrl
// one of the "benefits" of using TreeItemData is that we have to
// malloc them all, because treectrl destructor will free them all
@ -2976,8 +2803,7 @@ bool MainFrame::BindControls()
checkable_mi_t cmi = { cmdtab[i].cmd_id, mi, 0, 0 };
checkable_mi.push_back(cmi);
for (const config::Option& option :
config::Option::AllOptions()) {
for (const config::Option& option : config::Option::All()) {
if (cmdtab[i].cmd == option.command()) {
if (option.is_int()) {
MenuOptionIntMask(
@ -3233,11 +3059,6 @@ bool MainFrame::BindControls()
tc = SafeXRCCTRL<wxTextCtrl>(d, n); \
tc->SetValidator(wxTextValidator(wxFILTER_NONE, &o)); \
} while (0)
#define getdtc(n, o) \
do { \
tc = SafeXRCCTRL<wxTextCtrl>(d, n); \
tc->SetValidator(wxPositiveDoubleValidator(&o)); \
} while (0)
#define getutc(n, o) \
do { \
tc = SafeXRCCTRL<wxTextCtrl>(d, n); \
@ -3675,73 +3496,9 @@ bool MainFrame::BindControls()
SafeXRCCTRL<wxChoice>(d, "OvMirroring");
d->Fit();
}
d = LoadXRCropertySheetDialog("DisplayConfig");
{
/// Speed
// AutoSkip/FrameSkip are 2 controls for 1 value. Needs post-process
// to ensure checkbox not ignored
getsc("FrameSkip", frameSkip);
getlab("FrameSkipLab");
int fs = frameSkip;
if (fs >= 0)
systemFrameSkip = fs;
dialogs::DisplayConfig::NewInstance(this);
/// On-Screen Display
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "SpeedIndicator", wxGenericValidator(&showSpeed));
/// Zoom
getdtc("DefaultScale", gopts.video_scale);
// this was a choice, but I'd rather not have to make an off-by-one
// validator just for this, and spinctrl is good enough.
getsc("MaxScale", maxScale);
/// Basic
getrbi("OutputSimple", gopts.render_method, RND_SIMPLE);
#if defined(__WXMAC__)
getrbi("OutputQuartz2D", gopts.render_method, RND_QUARTZ2D);
#else
rb = SafeXRCCTRL<wxRadioButton>(d, "OutputQuartz2D");
rb->Hide();
#endif
getrbi("OutputOpenGL", gopts.render_method, RND_OPENGL);
#ifdef NO_OGL
rb->Hide();
#endif
#if defined(__WXGTK__) && !wxCHECK_VERSION(3, 2, 0)
// wxGLCanvas segfaults on Wayland before wx 3.2
if (wxGetApp().UsingWayland()) {
rb->Hide();
}
#endif
// Direct3D is not implemented so hide the option on every platform.
rb = SafeXRCCTRL<wxRadioButton>(d, "OutputDirect3D");
rb->Hide();
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "Filter", wxGenericValidator(&gopts.filter));
// Save the Filters choice control to extract the names from the XRC.
pixel_filters_ = ch;
// these two are filled and/or hidden at dialog load time
wxControl* pll;
wxChoice* pl;
pll = SafeXRCCTRL<wxControl>(d, "PluginLab");
pl = SafeXRCCTRL<wxChoice>(d, "Plugin");
pll->SetValidator(PluginEnabler());
pl->SetValidator(PluginListFiller(d, pll, ch));
PluginEnableHandler.lab = pll;
PluginEnableHandler.ch = pl;
ch->Connect(wxEVT_COMMAND_CHOICE_SELECTED,
wxCommandEventHandler(PluginEnable_t::ToggleChoice),
NULL, &PluginEnableHandler);
ch = GetValidatedChild<wxChoice, wxGenericValidator>(d, "IFB", wxGenericValidator(&gopts.ifb));
d->Fit();
// Save the interframe blender choice control to extract the names from the XRC.
interframe_blenders_ = ch;
}
d = LoadXRCropertySheetDialog("SoundConfig");
wxSlider* sl;
#define getsl(n, o) \

View File

@ -1,7 +1,8 @@
#include "opts.h"
#include <vector>
#include <algorithm>
#include <memory>
#include <unordered_set>
#include <wx/log.h>
#include <wx/display.h>
@ -19,6 +20,58 @@
-p/--profile=hz
*/
namespace {
void SaveOption(config::Option* option) {
wxFileConfig* cfg = wxGetApp().cfg;
switch (option->type()) {
case config::Option::Type::kNone:
// Keyboard and Joypad are handled separately.
break;
case config::Option::Type::kBool:
cfg->Write(option->config_name(), option->GetBool());
break;
case config::Option::Type::kDouble:
cfg->Write(option->config_name(), option->GetDouble());
break;
case config::Option::Type::kInt:
cfg->Write(option->config_name(), option->GetInt());
break;
case config::Option::Type::kUnsigned:
cfg->Write(option->config_name(), option->GetUnsigned());
break;
case config::Option::Type::kString:
cfg->Write(option->config_name(), option->GetString());
break;
case config::Option::Type::kFilter:
case config::Option::Type::kInterframe:
case config::Option::Type::kRenderMethod:
case config::Option::Type::kAudioApi:
case config::Option::Type::kSoundQuality:
cfg->Write(option->config_name(), option->GetEnumString());
break;
case config::Option::Type::kGbPalette:
cfg->Write(option->config_name(), option->GetGbPaletteString());
break;
}
cfg->Flush();
}
// Intitialize global observers to overwrite the configuration option when the
// option has been modified.
void InitializeOptionObservers() {
static std::unordered_set<std::unique_ptr<config::BasicOptionObserver>>
g_observers;
g_observers.reserve(config::kNbOptions);
for (config::Option& option : config::Option::All()) {
g_observers.emplace(std::make_unique<config::BasicOptionObserver>(
option.id(), &SaveOption));
}
}
} // namespace
#define WJKB config::UserInput
opts_t gopts;
@ -257,11 +310,7 @@ opts_t::opts_t()
{
frameSkip = -1;
audio_api = AUD_SDL;
#ifndef NO_OGL
render_method = RND_OPENGL;
#endif
video_scale = 3;
retain_aspect = true;
max_threads = wxThread::GetCPUCount();
@ -301,12 +350,10 @@ opts_t::opts_t()
}
// FIXME: simulate MakeInstanceFilename(vbam.ini) using subkeys (Slave%d/*)
void load_opts()
{
void load_opts() {
// just for sanity...
bool did_init = false;
if (did_init)
return;
static bool did_init = false;
assert(!did_init);
did_init = true;
// enumvals should not be translated, since they would cause config file
@ -403,7 +450,8 @@ void load_opts()
} else {
s.append(wxT('/'));
s.append(e);
if (!config::Option::FindByName(s) && s != wxT("General/LastUpdated") && s != wxT("General/LastUpdatedFileName")) {
if (!config::Option::ByName(s) && s != "General/LastUpdated" &&
s != "General/LastUpdatedFileName") {
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
}
@ -425,7 +473,7 @@ void load_opts()
cfg->SetRecordDefaults();
// First access here will also initialize translations.
for (const config::Option& opt : config::Option::AllOptions()) {
for (config::Option& opt : config::Option::All()) {
switch (opt.type()) {
case config::Option::Type::kNone:
// Keyboard or Joystick. Handled separately for now.
@ -532,6 +580,8 @@ void load_opts()
gopts.recent->Load(*cfg);
cfg->SetPath(wxT("/"));
cfg->Flush();
InitializeOptionObservers();
}
// Note: run load_opts() first to guarantee all config opts exist
@ -539,37 +589,8 @@ void update_opts()
{
wxFileConfig* cfg = wxGetApp().cfg;
for (const config::Option& opt : config::Option::AllOptions()) {
switch (opt.type()) {
case config::Option::Type::kNone:
// Keyboard and Joypad are handled separately.
break;
case config::Option::Type::kBool:
cfg->Write(opt.config_name(), opt.GetBool());
break;
case config::Option::Type::kDouble:
cfg->Write(opt.config_name(), opt.GetDouble());
break;
case config::Option::Type::kInt:
cfg->Write(opt.config_name(), opt.GetInt());
break;
case config::Option::Type::kUnsigned:
cfg->Write(opt.config_name(), opt.GetUnsigned());
break;
case config::Option::Type::kString:
cfg->Write(opt.config_name(), opt.GetString());
break;
case config::Option::Type::kFilter:
case config::Option::Type::kInterframe:
case config::Option::Type::kRenderMethod:
case config::Option::Type::kAudioApi:
case config::Option::Type::kSoundQuality:
cfg->Write(opt.config_name(), opt.GetEnumString());
break;
case config::Option::Type::kGbPalette:
cfg->Write(opt.config_name(), opt.GetGbPaletteString());
break;
}
for (config::Option& opt : config::Option::All()) {
SaveOption(&opt);
}
// For joypad, compare the UserInput sets. Since UserInput guarantees a
@ -656,7 +677,7 @@ void update_opts()
}
void opt_set(const wxString& name, const wxString& val) {
config::Option const* opt = config::Option::FindByName(name);
config::Option* opt = config::Option::ByName(name);
// opt->is_none() means it is Keyboard or Joypad.
if (opt && !opt->is_none()) {

View File

@ -24,13 +24,8 @@ extern struct opts_t {
/// Display
bool bilinear;
int filter;
wxString filter_plugin;
int ifb;
wxVideoMode fs_mode;
int max_threads;
int render_method;
double video_scale;
bool retain_aspect;
bool keep_on_top;

View File

@ -1,5 +1,5 @@
#include <cstdlib>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <vector>
@ -29,10 +29,12 @@
#include "../sdl/text.h"
#include "background-input.h"
#include "config/game-control.h"
#include "config/option.h"
#include "config/user-input.h"
#include "drawing.h"
#include "filters.h"
#include "wayland.h"
#include "widgets/render-plugin.h"
#include "wx/joyedit.h"
#include "wxutil.h"
#include "wxvbam.h"
@ -41,27 +43,82 @@
#include <windows.h>
#endif
namespace {
double GetFilterScale() {
switch (config::Option::GetFilterValue()) {
case config::Filter::kNone:
return 1.0;
case config::Filter::k2xsai:
case config::Filter::kSuper2xsai:
case config::Filter::kSupereagle:
case config::Filter::kPixelate:
case config::Filter::kAdvmame:
case config::Filter::kBilinear:
case config::Filter::kBilinearplus:
case config::Filter::kScanlines:
case config::Filter::kTvmode:
case config::Filter::kSimple2x:
case config::Filter::kLQ2x:
case config::Filter::kHQ2x:
case config::Filter::kXbrz2x:
return 2.0;
case config::Filter::kXbrz3x:
case config::Filter::kSimple3x:
case config::Filter::kHQ3x:
return 3.0;
case config::Filter::kSimple4x:
case config::Filter::kHQ4x:
case config::Filter::kXbrz4x:
return 4.0;
case config::Filter::kXbrz5x:
return 5.0;
case config::Filter::kXbrz6x:
return 6.0;
case config::Filter::kPlugin:
case config::Filter::kLast:
assert(false);
return 1.0;
}
assert(false);
return 1.0;
}
#define out_16 (systemColorDepth == 16)
} // namespace
int emulating;
IMPLEMENT_DYNAMIC_CLASS(GameArea, wxPanel)
GameArea::GameArea()
: wxPanel()
, panel(NULL)
, emusys(NULL)
, was_paused(false)
, rewind_time(0)
, do_rewind(false)
, rewind_mem(0)
, num_rewind_states(0)
, loaded(IMAGE_UNKNOWN)
, basic_width(GBAWidth)
, basic_height(GBAHeight)
, fullscreen(false)
, paused(false)
, pointer_blanked(false)
, mouse_active_time(0)
{
: wxPanel(),
panel(NULL),
emusys(NULL),
was_paused(false),
rewind_time(0),
do_rewind(false),
rewind_mem(0),
num_rewind_states(0),
loaded(IMAGE_UNKNOWN),
basic_width(GBAWidth),
basic_height(GBAHeight),
fullscreen(false),
paused(false),
pointer_blanked(false),
mouse_active_time(0),
filter_observer_(config::OptionID::kDisplayFilter,
std::bind(&GameArea::OnRenderingChanged,
this,
std::placeholders::_1)),
interframe_observer_(config::OptionID::kDisplayIFB,
std::bind(&GameArea::OnRenderingChanged,
this,
std::placeholders::_1)),
scale_observer_(
config::OptionID::kDisplayScale,
std::bind(&GameArea::OnScaleChanged, this, std::placeholders::_1)) {
SetSizer(new wxBoxSizer(wxVERTICAL));
// all renderers prefer 32-bit
// well, "simple" prefers 24-bit, but that's not available for filters
@ -750,11 +807,13 @@ void GameArea::AdjustMinSize()
{
wxWindow* frame = wxGetApp().frame;
double dpi_scale_factor = widgets::DPIScaleFactorForWindow(this);
double display_scale =
config::Option::ByID(config::OptionID::kDisplayScale)->GetDouble();
// note: could safely set min size to 1x or less regardless of video_scale
// but setting it to scaled size makes resizing to default easier
wxSize sz((std::ceil(basic_width * gopts.video_scale) * dpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) * dpi_scale_factor));
wxSize sz((std::ceil(basic_width * display_scale) * dpi_scale_factor),
(std::ceil(basic_height * display_scale) * dpi_scale_factor));
SetMinSize(sz);
#if wxCHECK_VERSION(2, 8, 8)
sz = frame->ClientToWindowSize(sz);
@ -785,9 +844,12 @@ void GameArea::AdjustSize(bool force)
return;
double dpi_scale_factor = widgets::DPIScaleFactorForWindow(this);
double display_scale =
config::Option::ByID(config::OptionID::kDisplayScale)->GetDouble();
const wxSize newsz(
(std::ceil(basic_width * gopts.video_scale) * dpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) * dpi_scale_factor));
(std::ceil(basic_width * display_scale) * dpi_scale_factor),
(std::ceil(basic_height * display_scale) * dpi_scale_factor));
if (!force) {
wxSize sz = GetClientSize();
@ -1045,25 +1107,29 @@ void GameArea::OnIdle(wxIdleEvent& event)
return;
if (!panel) {
switch (gopts.render_method) {
case RND_SIMPLE:
panel = new BasicDrawingPanel(this, basic_width, basic_height);
break;
switch (config::Option::GetRenderMethodValue()) {
case config::RenderMethod::kSimple:
panel = new BasicDrawingPanel(this, basic_width, basic_height);
break;
#ifdef __WXMAC__
case RND_QUARTZ2D:
panel = new Quartz2DDrawingPanel(this, basic_width, basic_height);
break;
case config::RenderMethod::kQuartz2d:
panel =
new Quartz2DDrawingPanel(this, basic_width, basic_height);
break;
#endif
#ifndef NO_OGL
case RND_OPENGL:
panel = new GLDrawingPanel(this, basic_width, basic_height);
break;
case config::RenderMethod::kOpenGL:
panel = new GLDrawingPanel(this, basic_width, basic_height);
break;
#endif
#if defined(__WXMSW__) && !defined(NO_D3D)
case RND_DIRECT3D:
panel = new DXDrawingPanel(this, basic_width, basic_height);
break;
case config::RenderMethod::kDirect3d:
panel = new DXDrawingPanel(this, basic_width, basic_height);
break;
#endif
case config::RenderMethod::kLast:
assert(false);
return;
}
wxWindow* w = panel->GetWindow();
@ -1343,67 +1409,50 @@ EVT_MOUSE_EVENTS(GameArea::MouseEvent)
END_EVENT_TABLE()
DrawingPanelBase::DrawingPanelBase(int _width, int _height)
: width(_width)
, height(_height)
, scale(1)
, did_init(false)
, todraw(0)
, pixbuf1(0)
, pixbuf2(0)
, nthreads(0)
, rpi(0)
{
: width(_width),
height(_height),
scale(1),
did_init(false),
todraw(0),
pixbuf1(0),
pixbuf2(0),
nthreads(0),
rpi_(nullptr) {
memset(delta, 0xff, sizeof(delta));
if (gopts.filter == FF_PLUGIN) {
do // do { } while(0) so break; exits entire block
{
// could've also just used goto & a label, I guess
gopts.filter = FF_NONE; // preemptive in case of errors
systemColorDepth = 32;
if (config::Option::GetFilterValue() == config::Filter::kPlugin) {
rpi_ = widgets::MaybeLoadFilterPlugin(
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->GetString(), &filter_plugin_);
if (rpi_) {
rpi_->Flags &= ~RPI_565_SUPP;
if (gopts.filter_plugin.empty())
break;
wxFileName fpn(gopts.filter_plugin);
fpn.MakeAbsolute(wxGetApp().GetPluginsDir());
if (!filt_plugin.Load(fpn.GetFullPath(), wxDL_VERBATIM | wxDL_NOW))
break;
RENDPLUG_GetInfo gi = (RENDPLUG_GetInfo)filt_plugin.GetSymbol(wxT("RenderPluginGetInfo"));
if (!gi)
break;
// need to be able to write to _rpi to set Output() and Flags
RENDER_PLUGIN_INFO* _rpi = gi();
// FIXME: maybe < RPI_VERSION, assuming future vers. back compat?
if (!_rpi || (_rpi->Flags & 0xff) != RPI_VERSION || !(_rpi->Flags & (RPI_555_SUPP | RPI_888_SUPP)))
break;
_rpi->Flags &= ~RPI_565_SUPP;
if (_rpi->Flags & RPI_888_SUPP) {
_rpi->Flags &= ~RPI_555_SUPP;
if (rpi_->Flags & RPI_888_SUPP) {
rpi_->Flags &= ~RPI_555_SUPP;
// FIXME: should this be 32 or 24? No docs or sample source
systemColorDepth = 32;
} else
systemColorDepth = 16;
if (!_rpi->Output)
// note that in actual kega fusion plugins, rpi->Output is
// unused (as is rpi->Handle)
_rpi->Output = (RENDPLUG_Output)filt_plugin.GetSymbol(wxT("RenderPluginOutput"));
if (!rpi_->Output) {
// note that in actual kega fusion plugins, rpi_->Output is
// unused (as is rpi_->Handle)
rpi_->Output =
(RENDPLUG_Output)filter_plugin_.GetSymbol("RenderPluginOutput");
}
scale *= (rpi_->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
} else {
// This is going to delete the object. Do nothing more here.
config::Option::ByID(config::OptionID::kDisplayFilterPlugin)
->SetString(wxEmptyString);
config::Option::ByID(config::OptionID::kDisplayFilter)
->SetFilter(config::Filter::kNone);
return;
}
}
scale *= (_rpi->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
rpi = _rpi;
gopts.filter = FF_PLUGIN; // now that there is a valid plugin
} while (0);
} else {
scale *= builtin_ff_scale(gopts.filter);
#define out_16 (systemColorDepth == 16)
if (config::Option::GetFilterValue() != config::Filter::kPlugin) {
scale *= GetFilterScale();
systemColorDepth = 32;
}
@ -1485,216 +1534,259 @@ void DrawingPanelBase::EraseBackground(wxEraseEvent& ev)
// interface, I will allow them to be threaded at user's discretion.
class FilterThread : public wxThread {
public:
FilterThread()
: wxThread(wxTHREAD_JOINABLE)
, lock()
, sig(lock)
{
}
FilterThread() : wxThread(wxTHREAD_JOINABLE), lock_(), sig_(lock_) {}
wxMutex lock;
wxCondition sig;
wxSemaphore* done;
wxMutex lock_;
wxCondition sig_;
wxSemaphore* done_;
// Set these params before running
int nthreads, threadno;
int width, height;
double scale;
const RENDER_PLUGIN_INFO* rpi;
uint8_t *dst, *delta;
int nthreads_;
int threadno_;
int width_;
int height_;
double scale_;
const RENDER_PLUGIN_INFO* rpi_;
uint8_t* dst_;
uint8_t* delta_;
// set this param every round
// if NULL, end thread
uint8_t* src;
uint8_t* src_;
ExitCode Entry()
{
ExitCode Entry() override {
// This is the band this thread will process
// threadno == -1 means just do a dummy round on the border line
int procy = height * threadno / nthreads;
height = height * (threadno + 1) / nthreads - procy;
int inbpp = systemColorDepth >> 3;
int inrb = systemColorDepth == 16 ? 2 : systemColorDepth == 24 ? 0 : 1;
int instride = (width + inrb) * inbpp;
int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
int outrb = systemColorDepth == 24 ? 0 : 4;
int outstride = std::ceil(width * outbpp * scale) + outrb;
delta += instride * procy;
const int procy = height_ * threadno_ / nthreads_;
height_ = height_ * (threadno_ + 1) / nthreads_ - procy;
const int inbpp = systemColorDepth >> 3;
const int inrb = systemColorDepth == 16 ? 2
: systemColorDepth == 24 ? 0
: 1;
const int instride = (width_ + inrb) * inbpp;
const int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
const int outrb = systemColorDepth == 24 ? 0 : 4;
const int outstride = std::ceil(width_ * outbpp * scale_) + outrb;
delta_ += instride * procy;
// FIXME: fugly hack
if(gopts.render_method == RND_OPENGL)
dst += (int)std::ceil(outstride * (procy + 1) * scale);
else
dst += (int)std::ceil(outstride * (procy + (1 / scale)) * scale);
if (config::Option::GetRenderMethodValue() ==
config::RenderMethod::kOpenGL) {
dst_ += (int)std::ceil(outstride * (procy + 1) * scale_);
} else {
dst_ += (int)std::ceil(outstride * (procy + (1 / scale_)) * scale_);
}
while (nthreads == 1 || sig.Wait() == wxCOND_NO_ERROR) {
if (!src /* && nthreads > 1 */) {
lock.Unlock();
while (nthreads_ == 1 || sig_.Wait() == wxCOND_NO_ERROR) {
if (!src_ /* && nthreads > 1 */) {
lock_.Unlock();
return 0;
}
src += instride;
src_ += instride;
// interframe blending filter
// definitely not thread safe by default
// added procy param to provide offset into accum buffers
if (gopts.ifb != IFB_NONE) {
switch (gopts.ifb) {
case IFB_SMART:
if (systemColorDepth == 16)
SmartIB(src, instride, width, procy, height);
else
SmartIB32(src, instride, width, procy, height);
ApplyInterframe(instride, procy);
break;
case IFB_MOTION_BLUR:
// FIXME: if(renderer == d3d/gl && filter == NONE) break;
if (systemColorDepth == 16)
MotionBlurIB(src, instride, width, procy, height);
else
MotionBlurIB32(src, instride, width, procy, height);
break;
}
}
if (gopts.filter == FF_NONE) {
if (nthreads == 1)
if (config::Option::GetFilterValue() == config::Filter::kNone) {
if (nthreads_ == 1)
return 0;
done->Post();
done_->Post();
continue;
}
//src += instride * procy;
// src += instride * procy;
// naturally, any of these with accumulation buffers like those of
// the IFB filters will screw up royally as well
switch (gopts.filter) {
case FF_2XSAI:
_2xSaI32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SUPER2XSAI:
Super2xSaI32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SUPEREAGLE:
SuperEagle32(src, instride, delta, dst, outstride, width, height);
break;
case FF_PIXELATE:
Pixelate32(src, instride, delta, dst, outstride, width, height);
break;
case FF_ADVMAME:
AdMame2x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_BILINEAR:
Bilinear32(src, instride, delta, dst, outstride, width, height);
break;
case FF_BILINEARPLUS:
BilinearPlus32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SCANLINES:
Scanlines32(src, instride, delta, dst, outstride, width, height);
break;
case FF_TV:
ScanlinesTV32(src, instride, delta, dst, outstride, width, height);
break;
case FF_LQ2X:
lq2x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SIMPLE2X:
Simple2x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SIMPLE3X:
Simple3x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_SIMPLE4X:
Simple4x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_HQ2X:
hq2x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_HQ3X:
hq3x32_32(src, instride, delta, dst, outstride, width, height);
break;
case FF_HQ4X:
hq4x32_32(src, instride, delta, dst, outstride, width, height);
break;
case FF_XBRZ2X:
xbrz2x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_XBRZ3X:
xbrz3x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_XBRZ4X:
xbrz4x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_XBRZ5X:
xbrz5x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_XBRZ6X:
xbrz6x32(src, instride, delta, dst, outstride, width, height);
break;
case FF_PLUGIN:
// MFC interface did not do plugins in parallel
// Probably because it's almost certain they carry state or do
// other non-thread-safe things
// But the user can always turn mt off of it's not working..
RENDER_PLUGIN_OUTP outdesc;
outdesc.Size = sizeof(outdesc);
outdesc.Flags = rpi->Flags;
outdesc.SrcPtr = src;
outdesc.SrcPitch = instride;
outdesc.SrcW = width;
// FIXME: win32 code adds to H, saying that frame isn't fully
// rendered otherwise
// I need to verify that statement before I go adding stuff that
// may make it crash.
outdesc.SrcH = height; // + scale / 2
outdesc.DstPtr = dst;
outdesc.DstPitch = outstride;
outdesc.DstW = std::ceil(width * scale);
// on the other hand, there is at least 1 line below, so I'll add
// that to dest in case safety checks in plugin use < instead of <=
outdesc.DstH = std::ceil(height * scale); // + scale * (scale / 2)
rpi->Output(&outdesc);
break;
default:
break;
// naturally, any of these with accumulation buffers like those
// of the IFB filters will screw up royally as well
ApplyFilter(instride, outstride);
if (nthreads_ == 1) {
return 0;
}
if (nthreads == 1)
return 0;
done->Post();
done_->Post();
continue;
}
return 0;
}
private:
// interframe blending filter
// definitely not thread safe by default
// added procy param to provide offset into accum buffers
void ApplyInterframe(int instride, int procy) {
switch (config::Option::ByID(config::OptionID::kDisplayIFB)
->GetInterframe()) {
case config::Interframe::kNone:
break;
case config::Interframe::kSmart:
if (systemColorDepth == 16)
SmartIB(src_, instride, width_, procy, height_);
else
SmartIB32(src_, instride, width_, procy, height_);
break;
case config::Interframe::kMotionBlur:
// FIXME: if(renderer == d3d/gl && filter == NONE) break;
if (systemColorDepth == 16)
MotionBlurIB(src_, instride, width_, procy, height_);
else
MotionBlurIB32(src_, instride, width_, procy, height_);
break;
case config::Interframe::kLast:
assert(false);
break;
}
}
// naturally, any of these with accumulation buffers like those
// of the IFB filters will screw up royally as well
void ApplyFilter(int instride, int outstride) {
switch (config::Option::GetFilterValue()) {
case config::Filter::k2xsai:
_2xSaI32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kSuper2xsai:
Super2xSaI32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kSupereagle:
SuperEagle32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kPixelate:
Pixelate32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kAdvmame:
AdMame2x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kBilinear:
Bilinear32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kBilinearplus:
BilinearPlus32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kScanlines:
Scanlines32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kTvmode:
ScanlinesTV32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kLQ2x:
lq2x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kSimple2x:
Simple2x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kSimple3x:
Simple3x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kSimple4x:
Simple4x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kHQ2x:
hq2x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kHQ3x:
hq3x32_32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kHQ4x:
hq4x32_32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kXbrz2x:
xbrz2x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kXbrz3x:
xbrz3x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kXbrz4x:
xbrz4x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kXbrz5x:
xbrz5x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kXbrz6x:
xbrz6x32(src_, instride, delta_, dst_, outstride, width_,
height_);
break;
case config::Filter::kPlugin:
// MFC interface did not do plugins in parallel
// Probably because it's almost certain they carry state
// or do other non-thread-safe things But the user can
// always turn mt off of it's not working..
RENDER_PLUGIN_OUTP outdesc;
outdesc.Size = sizeof(outdesc);
outdesc.Flags = rpi_->Flags;
outdesc.SrcPtr = src_;
outdesc.SrcPitch = instride;
outdesc.SrcW = width_;
// FIXME: win32 code adds to H, saying that frame isn't
// fully rendered otherwise I need to verify that
// statement before I go adding stuff that may make it
// crash.
outdesc.SrcH = height_; // + scale / 2
outdesc.DstPtr = dst_;
outdesc.DstPitch = outstride;
outdesc.DstW = std::ceil(width_ * scale_);
// on the other hand, there is at least 1 line below, so
// I'll add that to dest in case safety checks in plugin
// use < instead of <=
outdesc.DstH =
std::ceil(height_ * scale_); // + scale * (scale / 2)
rpi_->Output(&outdesc);
break;
case config::Filter::kNone:
case config::Filter::kLast:
assert(false);
break;
}
}
};
void DrawingPanelBase::DrawArea(uint8_t** data)
@ -1718,7 +1810,7 @@ void DrawingPanelBase::DrawArea(uint8_t** data)
pixbuf2 = (uint8_t*)calloc(allocstride, std::ceil((alloch + 2) * scale));
}
if (gopts.filter == FF_NONE) {
if (config::Option::GetFilterValue() == config::Filter::kNone) {
todraw = *data;
// *data is assigned below, after old buf has been processed
pixbuf1 = pixbuf2;
@ -1730,15 +1822,17 @@ void DrawingPanelBase::DrawArea(uint8_t** data)
gopts.max_threads = 1;
// First, apply filters, if applicable, in parallel, if enabled
if (gopts.filter != FF_NONE || gopts.ifb != FF_NONE /* FIXME: && (gopts.ifb != FF_MOTION_BLUR || !renderer_can_motion_blur) */) {
// FIXME: && (gopts.ifb != FF_MOTION_BLUR || !renderer_can_motion_blur)
if (config::Option::GetFilterValue() != config::Filter::kNone ||
config::Option::GetInterframeValue() != config::Interframe::kNone) {
if (nthreads != gopts.max_threads) {
if (nthreads) {
if (nthreads > 1)
for (int i = 0; i < nthreads; i++) {
threads[i].lock.Lock();
threads[i].src = NULL;
threads[i].sig.Signal();
threads[i].lock.Unlock();
threads[i].lock_.Lock();
threads[i].src_ = NULL;
threads[i].sig_.Signal();
threads[i].lock_.Unlock();
threads[i].Wait();
}
@ -1749,51 +1843,51 @@ void DrawingPanelBase::DrawArea(uint8_t** data)
threads = new FilterThread[nthreads];
// first time around, no threading in order to avoid
// static initializer conflicts
threads[0].threadno = 0;
threads[0].nthreads = 1;
threads[0].width = width;
threads[0].height = height;
threads[0].scale = scale;
threads[0].src = *data;
threads[0].dst = todraw;
threads[0].delta = delta;
threads[0].rpi = rpi;
threads[0].threadno_ = 0;
threads[0].nthreads_ = 1;
threads[0].width_ = width;
threads[0].height_ = height;
threads[0].scale_ = scale;
threads[0].src_ = *data;
threads[0].dst_ = todraw;
threads[0].delta_ = delta;
threads[0].rpi_ = rpi_;
threads[0].Entry();
// go ahead and start the threads up, though
if (nthreads > 1) {
for (int i = 0; i < nthreads; i++) {
threads[i].threadno = i;
threads[i].nthreads = nthreads;
threads[i].width = width;
threads[i].height = height;
threads[i].scale = scale;
threads[i].dst = todraw;
threads[i].delta = delta;
threads[i].rpi = rpi;
threads[i].done = &filt_done;
threads[i].lock.Lock();
threads[i].threadno_ = i;
threads[i].nthreads_ = nthreads;
threads[i].width_ = width;
threads[i].height_ = height;
threads[i].scale_ = scale;
threads[i].dst_ = todraw;
threads[i].delta_ = delta;
threads[i].rpi_ = rpi_;
threads[i].done_ = &filt_done;
threads[i].lock_.Lock();
threads[i].Create();
threads[i].Run();
}
}
} else if (nthreads == 1) {
threads[0].threadno = 0;
threads[0].nthreads = 1;
threads[0].width = width;
threads[0].height = height;
threads[0].scale = scale;
threads[0].src = *data;
threads[0].dst = todraw;
threads[0].delta = delta;
threads[0].rpi = rpi;
threads[0].threadno_ = 0;
threads[0].nthreads_ = 1;
threads[0].width_ = width;
threads[0].height_ = height;
threads[0].scale_ = scale;
threads[0].src_ = *data;
threads[0].dst_ = todraw;
threads[0].delta_ = delta;
threads[0].rpi_ = rpi_;
threads[0].Entry();
} else {
for (int i = 0; i < nthreads; i++) {
threads[i].lock.Lock();
threads[i].src = *data;
threads[i].sig.Signal();
threads[i].lock.Unlock();
threads[i].lock_.Lock();
threads[i].src_ = *data;
threads[i].sig_.Signal();
threads[i].lock_.Unlock();
}
for (int i = 0; i < nthreads; i++)
@ -1802,8 +1896,9 @@ void DrawingPanelBase::DrawArea(uint8_t** data)
}
// swap buffers now that src has been processed
if (gopts.filter == FF_NONE)
if (config::Option::GetFilterValue() == config::Filter::kNone) {
*data = pixbuf1;
}
// draw OSD text old-style (directly into output buffer), if needed
// new style flickers too much, so we'll stick to this for now
@ -1969,10 +2064,10 @@ DrawingPanelBase::~DrawingPanelBase()
if (nthreads) {
if (nthreads > 1)
for (int i = 0; i < nthreads; i++) {
threads[i].lock.Lock();
threads[i].src = NULL;
threads[i].sig.Signal();
threads[i].lock.Unlock();
threads[i].lock_.Lock();
threads[i].src_ = NULL;
threads[i].sig_.Signal();
threads[i].lock_.Unlock();
threads[i].Wait();
}
@ -1987,10 +2082,14 @@ BasicDrawingPanel::BasicDrawingPanel(wxWindow* parent, int _width, int _height)
{
// wxImage is 24-bit RGB, so 24-bit is preferred. Filters require
// 16 or 32, though
if (gopts.filter == FF_NONE && gopts.ifb == IFB_NONE)
if (config::Option::GetFilterValue() == config::Filter::kNone &&
config::Option::GetInterframeValue() == config::Interframe::kNone) {
// changing from 32 to 24 does not require regenerating color tables
systemColorDepth = 32;
if (!did_init) DrawingPanelInit();
}
if (!did_init) {
DrawingPanelInit();
}
}
void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
@ -2015,7 +2114,7 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
src += 2; // skip rhs border
}
} else if (gopts.filter != FF_NONE) {
} else if (config::Option::GetFilterValue() != config::Filter::kNone) {
// scaled by filters, top/right borders, transform to 24-bit
im = new wxImage(std::ceil(width * scale), std::ceil(height * scale), false);
uint32_t* src = (uint32_t*)todraw + (int)std::ceil(width * scale) + 1; // skip top border
@ -2030,7 +2129,7 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
++src; // skip rhs border
}
} else { // 32 bit
} else { // 32 bit
// not scaled by filters, top/right borders, transform to 24-bit
im = new wxImage(std::ceil(width * scale), std::ceil(height * scale), false);
uint32_t* src = (uint32_t*)todraw + (int)std::ceil((width + 1) * scale); // skip top border
@ -2538,3 +2637,14 @@ void GameArea::ShowMenuBar()
menu_bar_hidden = false;
#endif
}
void GameArea::OnRenderingChanged(config::Option*) {
if (panel) {
panel->Destroy();
panel = nullptr;
}
}
void GameArea::OnScaleChanged(config::Option*) {
AdjustSize(true);
}

View File

@ -0,0 +1,33 @@
#include "widgets/option-validator.h"
namespace widgets {
OptionValidator::OptionValidator(config::OptionID option_id)
: wxValidator(), config::Option::Observer(option_id) {}
bool OptionValidator::TransferFromWindow() {
return WriteToOption();
}
bool OptionValidator::Validate(wxWindow*) {
return IsWindowValueValid();
}
bool OptionValidator::TransferToWindow() {
return WriteToWindow();
}
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
void OptionValidator::SetWindow(wxWindow* window) {
wxValidator::SetWindow(window);
[[maybe_unused]] const bool write_success = WriteToWindow();
assert(write_success);
}
#endif
void OptionValidator::OnValueChanged() {
[[maybe_unused]] const bool write_success = WriteToWindow();
assert(write_success);
}
} // namespace widgets

View File

@ -0,0 +1,90 @@
#ifndef VBAM_WX_WIDGETS_OPTION_VALIDATOR_H_
#define VBAM_WX_WIDGETS_OPTION_VALIDATOR_H_
#include <wx/validate.h>
#include "config/option.h"
#if wxCHECK_VERSION(3, 1, 1)
#define WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE 1
#else
#define WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE 0
#endif // wxCHECK_VERSION(3,1,1)
namespace widgets {
// Generic wxWidgets validator for a config::Option. This base class acts as a
// Controller in a Model-View-Controller pattern. Implementers should only have
// to provide the OptionID and implement the 4 methods indicated below.
//
// Sample usage:
//
// class MyOptionValidator : public widgets::OptionValidator {
// public:
// MyOptionValidator()
// : widgets::OptionValidator(config::OptionID::kOptionID) {}
// ~MyOptionValidator() override = default;
//
// private:
// // widgets::OptionValidator implementation.
// wxObject* Clone() const override {
// return MyOptionValidator();
// }
//
// bool IsWindowValueValid() override {
// wxWindow* window = GetWindow();
// // Validate window value here.
// }
//
// bool WriteToWindow() override {
// wxWindow* window = GetWindow();
// OptionType value = option()->GetType();
// return window->SetValue(value);
// }
//
// bool WriteToOption() override {
// wxWindow* window = GetWindow();
// return option()->SetType(window->GetValue());
// }
//
// };
//
// void DialogInitializer() {
// wxWindow* window = dialog->FindWindow("WindowId");
// window->SetValidator(MyOptionValidator());
// }
class OptionValidator : public wxValidator, public config::Option::Observer {
public:
explicit OptionValidator(config::OptionID option_id);
~OptionValidator() override = default;
// Returns a copy of the object.
wxObject* Clone() const override = 0;
// Returns true if the value held by GetWindow() is valid.
[[nodiscard]] virtual bool IsWindowValueValid() = 0;
// Updates the GetWindow() value, from the option() value.
// Returns true on success, false on failure.
[[nodiscard]] virtual bool WriteToWindow() = 0;
// Updates the option() value, from the GetWindow() value.
// Returns true on success, false on failure.
[[nodiscard]] virtual bool WriteToOption() = 0;
private:
// wxValidator implementation.
bool TransferFromWindow() final;
bool Validate(wxWindow*) final;
bool TransferToWindow() final;
#if WX_HAS_VALIDATOR_SET_WINDOW_OVERRIDE
void SetWindow(wxWindow* window) final;
#endif
// config::Option::Observer implementation.
void OnValueChanged() final;
};
} // namespace widgets
#endif // VBAM_WX_WIDGETS_OPTION_VALIDATOR_H_

View File

@ -0,0 +1,43 @@
#include "widgets/render-plugin.h"
namespace widgets {
RENDER_PLUGIN_INFO* MaybeLoadFilterPlugin(const wxString& path, wxDynamicLibrary* filter_plugin) {
assert(filter_plugin);
if (!filter_plugin->Load(path, wxDL_VERBATIM | wxDL_NOW | wxDL_QUIET)) {
return nullptr;
}
RENDPLUG_GetInfo get_info =
(RENDPLUG_GetInfo)filter_plugin->GetSymbol("RenderPluginGetInfo");
if (!get_info) {
filter_plugin->Unload();
return nullptr;
}
// need to be able to write to plugin_info to set Output() and Flags
RENDER_PLUGIN_INFO* plugin_info = get_info();
if (!plugin_info) {
filter_plugin->Unload();
return nullptr;
}
// TODO: Should this be < RPI_VERISON?
if ((plugin_info->Flags & 0xff) != RPI_VERSION) {
filter_plugin->Unload();
return nullptr;
}
// RPI_565_SUPP is not supported, although it would be possible and it
// would make Cairo more efficient
if ((plugin_info->Flags & (RPI_555_SUPP | RPI_888_SUPP)) == 0) {
filter_plugin->Unload();
return nullptr;
}
return plugin_info;
}
} // namespace widgets

View File

@ -0,0 +1,18 @@
#ifndef VBAM_WX_WIDGETS_RENDER_PLUGIN_H_
#define VBAM_WX_WIDGETS_RENDER_PLUGIN_H_
#include <cstdint>
#include <wx/dynlib.h>
#include <wx/string.h>
#include "rpi.h"
namespace widgets {
// Initializes a RENDER_PLUGIN_INFO, if the plugin at `path` is valid.
// Otherwise, returns nullptr.
RENDER_PLUGIN_INFO* MaybeLoadFilterPlugin(const wxString& path, wxDynamicLibrary* filter_plugin);
} // namespace widgets
#endif // VBAM_WX_WIDGETS_RENDER_PLUGIN_H_

View File

@ -60,19 +60,6 @@ public:
protected:
int val, mask, *vptr;
};
class wxPositiveDoubleValidator : public wxGenericValidator {
public:
wxPositiveDoubleValidator(double* _val);
bool TransferToWindow();
bool TransferFromWindow();
bool Validate(wxWindow* parent);
wxObject* Clone() const;
protected:
double* double_val;
wxString str_val;
};
class wxUIntValidator : public wxValidator {
public:
wxUIntValidator(uint32_t* _val);

View File

@ -384,61 +384,6 @@ static const wxString val_unsdigits_s[] = {
const wxArrayString val_unsdigits(sizeof(val_unsdigits_s) / sizeof(val_unsdigits_s[0]),
val_unsdigits_s);
wxPositiveDoubleValidator::wxPositiveDoubleValidator(double* _val)
: wxGenericValidator(&str_val)
, double_val(_val)
{
if (double_val) {
str_val = wxString::Format(wxT("%.1f"), *double_val);
TransferToWindow();
}
}
bool wxPositiveDoubleValidator::TransferToWindow()
{
if (double_val) {
str_val = wxString::Format(wxT("%.1f"), *double_val);
return wxGenericValidator::TransferToWindow();
}
return true;
}
bool wxPositiveDoubleValidator::TransferFromWindow()
{
if (wxGenericValidator::TransferFromWindow()) {
if (double_val) {
return str_val.ToDouble(double_val);
}
}
return false;
}
bool wxPositiveDoubleValidator::Validate(wxWindow* parent)
{
if (wxGenericValidator::Validate(parent)) {
wxTextCtrl* ctrl = wxDynamicCast(GetWindow(), wxTextCtrl);
if (ctrl) {
wxString cur_txt = ctrl->GetValue();
double val;
if (cur_txt.ToDouble(&val)) {
return val >= 0;
}
return false;
}
return true;
}
return false;
}
wxObject* wxPositiveDoubleValidator::Clone() const
{
return new wxPositiveDoubleValidator(double_val);
}
wxUIntValidator::wxUIntValidator(uint32_t* _val)
: uint_val(_val)
{

View File

@ -360,8 +360,9 @@ bool wxvbamApp::OnInit() {
// wxGLCanvas segfaults under wayland before wx 3.2
#if defined(__WXGTK__) && !wxCHECK_VERSION(3, 2, 0)
if (UsingWayland() && gopts.render_method == RND_OPENGL) {
gopts.render_method = RND_SIMPLE;
if (UsingWayland()) {
config::Option::ByID(config::OptionID::kDisplayRenderMethod)
->SetRenderMethod(config::RenderMethod::kSimple);
}
#endif
@ -671,7 +672,7 @@ bool wxvbamApp::OnCmdLineParsed(wxCmdLineParser& cl)
" configuration changes are made in the user interface.\n\n"
"For flag options, true and false are specified as 1 and 0, respectively.\n\n"));
for (const config::Option& opt : config::Option::AllOptions()) {
for (const config::Option& opt : config::Option::All()) {
wxPrintf("%s\n", opt.ToHelperString());
}

View File

@ -11,6 +11,7 @@
#include <wx/propdlg.h>
#include <wx/datetime.h>
#include "config/option.h"
#include "widgets/dpi-support.h"
#include "wx/joyedit.h"
#include "wx/keyedit.h"
@ -383,9 +384,6 @@ private:
// Load a named wxDialog from the XRC file
wxDialog* LoadXRCropertySheetDialog(const char* name);
wxChoice* pixel_filters_ = nullptr;
wxChoice* interframe_blenders_ = nullptr;
#include "cmdhandlers.h"
};
@ -423,63 +421,6 @@ enum showspeed {
SS_DETAILED
};
// This enum must be kept in sync with the one in vbam-options-static.cpp.
// TODO: These 2 enums should be unified and a validator created for this enum.
enum filtfunc {
// this order must match order of option enum and selector widget
FF_NONE,
FF_2XSAI,
FF_SUPER2XSAI,
FF_SUPEREAGLE,
FF_PIXELATE,
FF_ADVMAME,
FF_BILINEAR,
FF_BILINEARPLUS,
FF_SCANLINES,
FF_TV,
FF_HQ2X,
FF_LQ2X,
FF_SIMPLE2X,
FF_SIMPLE3X,
FF_HQ3X,
FF_SIMPLE4X,
FF_HQ4X,
FF_XBRZ2X,
FF_XBRZ3X,
FF_XBRZ4X,
FF_XBRZ5X,
FF_XBRZ6X,
FF_PLUGIN // plugin must always be last
};
#define builtin_ff_scale(x) \
((x == FF_XBRZ6X) ? 6 : (x == FF_XBRZ5X) \
? 5 \
: (x == FF_XBRZ4X || x == FF_HQ4X || x == FF_SIMPLE4X) \
? 4 \
: (x == FF_XBRZ3X || x == FF_HQ3X || x == FF_SIMPLE3X) \
? 3 \
: x == FF_PLUGIN ? 0 : x == FF_NONE ? 1 : 2)
// This enum must be kept in sync with the one in vbam-options-static.cpp.
// TODO: These 2 enums should be unified and a validator created for this enum.
enum ifbfunc {
IFB_NONE,
IFB_SMART,
IFB_MOTION_BLUR
};
// This enum must be kept in sync with the one in vbam-options-static.cpp.
// TODO: These 2 enums should be unified and a validator created for this enum.
enum renderer {
RND_SIMPLE,
RND_OPENGL,
#if defined(__WXMSW__)
RND_DIRECT3D,
#elif defined(__WXMAC__)
RND_QUARTZ2D,
#endif
};
// This enum must be kept in sync with the one in vbam-options-static.cpp.
// TODO: These 2 enums should be unified and a validator created for this enum.
enum audioapi {
@ -674,6 +615,17 @@ protected:
DECLARE_DYNAMIC_CLASS(GameArea)
DECLARE_EVENT_TABLE()
private:
// Callback for Render and Interframe changed events.
void OnRenderingChanged(config::Option*);
// Callback for VideoScale changed events.
void OnScaleChanged(config::Option*);
config::BasicOptionObserver filter_observer_;
config::BasicOptionObserver interframe_observer_;
config::BasicOptionObserver scale_observer_;
};
// wxString version of OSD message
@ -726,8 +678,8 @@ protected:
FilterThread* threads;
int nthreads;
wxSemaphore filt_done;
wxDynamicLibrary filt_plugin;
const RENDER_PLUGIN_INFO* rpi; // also flag indicating plugin loaded
wxDynamicLibrary filter_plugin_;
RENDER_PLUGIN_INFO* rpi_; // also flag indicating plugin loaded
// largest buffer required is 32-bit * (max width + 1) * (max height + 2)
uint8_t delta[257 * 4 * 226];
};