Update to v094r23 release.

byuu says:

The library window is gone, and replaced with
hiro::BrowserWindow::openFolder(). This gives navigation capabilities to
game loading, and it also completes our slotted cart selection code. As
an added bonus, it's less code this way, too.

I also set the window size to consistent sizes between all emulated
systems, so that switching between SFC and GB don't cause the window
size to keep changing, and so that the scaling size is consistent (eg at
normal scale, GB @ 3x is closer to SNES @ 2x.) This means black borders
in GB/GBA mode, but it doesn't look that bad, and it's not like many
people ever use these modes anyway.

Finally, added the placeholder tabs for video, audio and timing. I don't
intend to add the timing calculator code to v095 (it might be better as
a separate tool), but I'll add the ability to set video/audio rates, at
least.

Glitch 1: despite selecting the first item in the BrowserDialog list, if
you press enter when the window appears, it doesn't activate the item
until you press an arrow key first.

Glitch 2: in Game Boy mode, if you set the 4x window size, it's not
honoring the full requested height because the viewport is smaller than
the window. 8+ years of trying to get GTK+ and Qt to simply set the god
damned window size I ask for, and I still can't get them to do it
reliably.

Remaining issues:
- finish configuration panels (video, audio, timing)
- fix ruby driver compilation on Windows
- add DIP switch selection window (NSS) [I may end up punting this one
  to v096]
This commit is contained in:
Tim Allen 2015-05-30 21:39:09 +10:00
parent 7bf4cff946
commit 314aee8c5c
40 changed files with 500 additions and 545 deletions

View File

@ -3,7 +3,7 @@
namespace Emulator {
static const char Name[] = "higan";
static const char Version[] = "094.22";
static const char Version[] = "094.23";
static const char Author[] = "byuu";
static const char License[] = "GPLv3";
static const char Website[] = "http://byuu.org/";

View File

@ -6,6 +6,7 @@ struct BrowserDialogWindow {
auto activate() -> void;
auto change() -> void;
auto isFolder(const string& name) -> bool;
auto isMatch(const string& name) -> bool;
auto run() -> lstring;
auto setPath(string path) -> void;
@ -25,6 +26,7 @@ private:
Button cancelButton{&controlLayout, Size{80, 0}, 8};
BrowserDialog::State& state;
vector<lstring> filters;
};
//accept button clicked, or enter pressed on file name line edit
@ -45,12 +47,18 @@ auto BrowserDialogWindow::accept() -> void {
}
}
if(state.action == "openFolder" && selectedItems) {
string name = selectedItems.first()->text(0);
if(!isMatch(name)) return setPath({state.path, name});
state.response.append(string{state.path, name, "/"});
}
if(state.action == "saveFile") {
string name = fileName.text();
if(!name && selectedItems) name = selectedItems.first()->text(0);
if(!name || isFolder(name)) return;
if(file::exists({state.path, name})) {
if(MessageDialog("File already exists; overwrite it?").question() != 0) return;
if(MessageDialog("File already exists; overwrite it?").question() != "Yes") return;
}
state.response.append(string{state.path, name});
}
@ -66,15 +74,18 @@ auto BrowserDialogWindow::accept() -> void {
//list view item double-clicked, or enter pressed on selected list view item
auto BrowserDialogWindow::activate() -> void {
auto selectedItem = view.selected();
if(state.action == "saveFile" && selectedItem) {
string name = selectedItem->text(0);
if(isFolder(name)) return setPath({state.path, name});
fileName.setText(isFolder(name) ? "" : name);
fileName.setText(name);
}
if(state.action == "selectFolder" && selectedItem) {
string name = selectedItem->text(0);
if(isFolder(name)) return setPath({state.path, name});
}
accept();
}
@ -93,34 +104,41 @@ auto BrowserDialogWindow::isFolder(const string& name) -> bool {
return directory::exists({state.path, name});
}
auto BrowserDialogWindow::isMatch(const string& name) -> bool {
if(auto selectedItem = filterList.selected()) {
for(auto& filter : filters[selectedItem->offset()]) {
if(name.match(filter)) return true;
}
}
return false;
}
auto BrowserDialogWindow::run() -> lstring {
state.response.reset();
layout.setMargin(8);
pathName.onActivate([&] { setPath(pathName.text()); });
pathHome.onActivate([&] { setPath(userpath()); });
pathHome.setBordered(false).setIcon(Icon::Go::Home);
pathRefresh.onActivate([&] { setPath(state.path); });
pathRefresh.setBordered(false).setIcon(Icon::Action::Refresh);
pathUp.onActivate([&] { setPath(state.path.dirname()); });
pathUp.setBordered(false).setIcon(Icon::Go::Up);
view.onActivate([&] { activate(); });
view.onChange([&] { change(); });
view.setMultiSelect(state.action == "openFiles");
filterList.onChange([&] { setPath(state.path); });
pathHome.setBordered(false).setIcon(Icon::Go::Home).onActivate([&] { setPath(userpath()); });
pathRefresh.setBordered(false).setIcon(Icon::Action::Refresh).onActivate([&] { setPath(state.path); });
pathUp.setBordered(false).setIcon(Icon::Go::Up).onActivate([&] { setPath(state.path.dirname()); });
view.setMultiSelect(state.action == "openFiles").onActivate([&] { activate(); }).onChange([&] { change(); });
filterList.setVisible(state.action != "selectFolder").onChange([&] { setPath(state.path); });
for(auto& filter : state.filters) {
auto part = filter.split<1>("|");
filterList.append(ComboButtonItem().setText(part.first()));
}
filterList.setVisible(state.action != "selectFolder");
fileName.onActivate([&] { accept(); });
fileName.setVisible(state.action == "saveFile");
fileName.setVisible(state.action == "saveFile").onActivate([&] { accept(); });
acceptButton.onActivate([&] { accept(); });
if(state.action == "openFile" || state.action == "openFiles") acceptButton.setText("Open");
if(state.action == "openFile" || state.action == "openFiles" || state.action == "openFolder") acceptButton.setText("Open");
if(state.action == "saveFile") acceptButton.setText("Save");
if(state.action == "selectFolder") acceptButton.setText("Select");
cancelButton.onActivate([&] { window.setModal(false); });
cancelButton.setText("Cancel");
cancelButton.setText("Cancel").onActivate([&] { window.setModal(false); });
if(!state.filters) state.filters.append("All|*");
for(auto& filter : state.filters) {
auto part = filter.split<1>("|");
filters.append(part.last().split(":"));
}
setPath(state.path);
@ -146,30 +164,30 @@ auto BrowserDialogWindow::setPath(string path) -> void {
view.append(ListViewColumn().setWidth(~0));
view.append(ListViewColumn().setWidth( 0).setForegroundColor({192, 128, 128}));
for(auto& folder : directory::folders(path)) {
auto contents = directory::contents(path);
bool folderMode = state.action == "openFolder";
for(auto content : contents) {
if(!content.endsWith("/")) continue;
if(folderMode && isMatch(content.rtrim("/"))) continue;
ListViewItem item{&view};
item.setIcon(0, Icon::Emblem::Folder);
item.setText(0, folder.rtrim("/"));
item.setText(1, octal<3>(storage::mode({path, folder}) & 0777));
item.setText(0, content.rtrim("/"));
item.setText(1, octal<3>(storage::mode({path, content}) & 0777));
}
if(state.action != "selectFolder") { //don't show files during folder selection
string filter = "*";
if(auto selected = filterList.selected()) {
auto part = state.filters[selected->offset()].split<1>("|");
filter = part.last();
}
for(auto content : contents) {
if(content.endsWith("/") && !folderMode) continue;
if(!isMatch(content.rtrim("/"))) continue;
for(auto& file : directory::files(path)) {
if(!file.match(filter)) continue;
ListViewItem item{&view};
item.setIcon(0, Icon::Emblem::File);
item.setText(0, file);
item.setText(1, octal<3>(storage::mode({path, file}) & 0777));
}
item.setIcon(0, folderMode ? Icon::Action::Open : Icon::Emblem::File);
item.setText(0, content.rtrim("/"));
item.setText(1, octal<3>(storage::mode({path, content}) & 0777));
}
if(view.items()) view.setSelected({0});
if(view.items()) view.item(0)->setSelected();
Application::processEvents();
view.resizeColumns().setFocused().doChange();
}
@ -191,7 +209,14 @@ auto BrowserDialog::openFiles() -> lstring {
if(!state.title) state.title = "Open Files";
if(auto result = _run()) return result;
return {};
};
}
auto BrowserDialog::openFolder() -> string {
state.action = "openFolder";
if(!state.title) state.title = "Open Folder";
if(auto result = _run()) return result.first();
return {};
}
auto BrowserDialog::saveFile() -> string {
state.action = "saveFile";
@ -212,7 +237,7 @@ auto BrowserDialog::setFilters(const lstring& filters) -> type& {
return *this;
}
auto BrowserDialog::setParent(const shared_pointer<mWindow>& parent) -> type& {
auto BrowserDialog::setParent(const sWindow& parent) -> type& {
state.parent = parent;
return *this;
}

View File

@ -6,26 +6,27 @@ struct BrowserDialog {
using type = BrowserDialog;
BrowserDialog();
auto openFile() -> nall::string; //one existing file
auto openFiles() -> nall::lstring; //any existing files or folders
auto saveFile() -> nall::string; //one file
auto selectFolder() -> nall::string; //one existing folder
auto setFilters(const nall::lstring& filters = {"All|*"}) -> type&;
auto setParent(const nall::shared_pointer<mWindow>& parent) -> type&;
auto setPath(const nall::string& path = "") -> type&;
auto setTitle(const nall::string& title = "") -> type&;
auto openFile() -> string; //one existing file
auto openFiles() -> lstring; //any existing files or folders
auto openFolder() -> string; //one existing folder
auto saveFile() -> string; //one file
auto selectFolder() -> string; //one existing folder
auto setFilters(const lstring& filters = {}) -> type&;
auto setParent(const sWindow& parent) -> type&;
auto setPath(const string& path = "") -> type&;
auto setTitle(const string& title = "") -> type&;
private:
struct State {
nall::string action;
nall::lstring filters = {"*"};
nall::shared_pointer<mWindow> parent;
nall::string path;
nall::lstring response;
nall::string title;
string action;
lstring filters = {"*"};
sWindow parent;
string path;
lstring response;
string title;
} state;
auto _run() -> nall::lstring;
auto _run() -> lstring;
friend class BrowserDialogWindow;
};

View File

@ -4,19 +4,19 @@ MessageDialog::MessageDialog(const string& text) {
state.text = text;
}
auto MessageDialog::error(const lstring& buttons) -> signed {
auto MessageDialog::error(const lstring& buttons) -> string {
state.buttons = buttons;
state.icon = Icon::Prompt::Error;
return _run();
}
auto MessageDialog::information(const lstring& buttons) -> signed {
auto MessageDialog::information(const lstring& buttons) -> string {
state.buttons = buttons;
state.icon = Icon::Prompt::Information;
return _run();
}
auto MessageDialog::question(const lstring& buttons) -> signed {
auto MessageDialog::question(const lstring& buttons) -> string {
state.buttons = buttons;
state.icon = Icon::Prompt::Question;
return _run();
@ -37,13 +37,13 @@ auto MessageDialog::setTitle(const string& title) -> type& {
return *this;
}
auto MessageDialog::warning(const lstring& buttons) -> signed {
auto MessageDialog::warning(const lstring& buttons) -> string {
state.buttons = buttons;
state.icon = Icon::Prompt::Warning;
return _run();
}
auto MessageDialog::_run() -> signed {
auto MessageDialog::_run() -> string {
Window window;
VerticalLayout layout{&window};
HorizontalLayout messageLayout{&layout, Size{~0, 0}, 8};
@ -57,7 +57,7 @@ auto MessageDialog::_run() -> signed {
messageText.setText(state.text);
for(auto n : range(state.buttons)) {
Button button{&controlLayout, Size{80, 0}, 8};
button.onActivate([&, n] { state.response = n; window.setModal(false); });
button.onActivate([&, n] { state.response = state.buttons[n]; window.setModal(false); });
button.setText(state.buttons[n]);
button.setFocused(); //the last button will have effective focus
}
@ -66,7 +66,7 @@ auto MessageDialog::_run() -> signed {
signed widthButtons = 8 + state.buttons.size() * 88;
signed width = max(320, widthMessage, widthButtons);
window.onClose([&] { state.response = -1; window.setModal(false); });
window.onClose([&] { window.setModal(false); });
window.setTitle(state.title);
window.setResizable(false);
window.setSize({width, layout.minimumSize().height()});

View File

@ -3,26 +3,26 @@
struct MessageDialog {
using type = MessageDialog;
MessageDialog(const nall::string& text = "");
auto error(const nall::lstring& buttons = {"Ok"}) -> signed;
auto information(const nall::lstring& buttons = {"Ok"}) -> signed;
auto question(const nall::lstring& buttons = {"Yes", "No"}) -> signed;
auto setParent(nall::shared_pointer<mWindow> parent = {}) -> type&;
auto setText(const nall::string& text = "") -> type&;
auto setTitle(const nall::string& title = "") -> type&;
auto warning(const nall::lstring& buttons = {"Ok"}) -> signed;
MessageDialog(const string& text = "");
auto error(const lstring& buttons = {"Ok"}) -> string;
auto information(const lstring& buttons = {"Ok"}) -> string;
auto question(const lstring& buttons = {"Yes", "No"}) -> string;
auto setParent(sWindow parent = {}) -> type&;
auto setText(const string& text = "") -> type&;
auto setTitle(const string& title = "") -> type&;
auto warning(const lstring& buttons = {"Ok"}) -> string;
private:
struct State {
nall::lstring buttons;
nall::vector<uint8_t> icon;
nall::shared_pointer<mWindow> parent;
signed response = -1;
nall::string text;
nall::string title;
lstring buttons;
vector<uint8_t> icon;
sWindow parent;
string response;
string text;
string title;
} state;
auto _run() -> signed;
auto _run() -> string;
};
#endif

View File

@ -7,15 +7,36 @@
namespace nall {
struct any {
bool empty() const { return container; }
void reset() { if(container) { delete container; container = nullptr; } }
any() = default;
any(const any& source) { operator=(source); }
any(any&& source) { operator=(move(source)); }
template<typename T> any(const T& value) { operator=(value); }
~any() { reset(); }
const std::type_info& type() const {
explicit operator bool() const { return container; }
auto empty() const -> bool { return !container; }
auto reset() -> void { if(container) { delete container; container = nullptr; } }
auto type() const -> const std::type_info& {
return container ? container->type() : typeid(void);
}
template<typename T> any& operator=(const T& value) {
using auto_t = type_if<is_array<T>, typename std::remove_extent<typename std::add_const<T>::type>::type*, T>;
template<typename T> auto is() const -> bool {
return type() == typeid(typename remove_reference<T>::type);
}
template<typename T> auto get() -> T& {
if(!is<T>()) throw;
return static_cast<holder<typename remove_reference<T>::type>*>(container)->value;
}
template<typename T> auto get() const -> const T& {
if(!is<T>()) throw;
return static_cast<holder<typename remove_reference<T>::type>*>(container)->value;
}
template<typename T> auto operator=(const T& value) -> any& {
using auto_t = type_if<is_array<T>, typename remove_extent<typename add_const<T>::type>::type*, T>;
if(type() == typeid(auto_t)) {
static_cast<holder<auto_t>*>(container)->value = (auto_t)value;
@ -27,68 +48,35 @@ struct any {
return *this;
}
any& operator=(const any& source) {
auto operator=(const any& source) -> any& {
if(container) { delete container; container = nullptr; }
if(source.container) container = source.container->copy();
return *this;
}
any& operator=(any&& source) {
auto operator=(any&& source) -> any& {
if(container) delete container;
container = source.container;
source.container = nullptr;
return *this;
}
any() = default;
any(const any& source) { operator=(source); }
any(any&& source) { operator=(move(source)); }
template<typename T> any(const T& value) { operator=(value); }
~any() { reset(); }
private:
struct placeholder {
virtual const std::type_info& type() const = 0;
virtual placeholder* copy() const = 0;
virtual ~placeholder() {}
virtual ~placeholder() = default;
virtual auto type() const -> const std::type_info& = 0;
virtual auto copy() const -> placeholder* = 0;
};
placeholder* container = nullptr;
template<typename T> struct holder : placeholder {
T value;
const std::type_info& type() const { return typeid(T); }
placeholder* copy() const { return new holder(value); }
holder(const T& value) : value(value) {}
auto type() const -> const std::type_info& { return typeid(T); }
auto copy() const -> placeholder* { return new holder(value); }
T value;
};
template<typename T> friend T any_cast(any&);
template<typename T> friend T any_cast(const any&);
template<typename T> friend T* any_cast(any*);
template<typename T> friend const T* any_cast(const any*);
};
template<typename T> T any_cast(any& value) {
typedef typename std::remove_reference<T>::type nonref;
if(value.type() != typeid(nonref)) throw;
return static_cast<any::holder<nonref>*>(value.container)->value;
}
template<typename T> T any_cast(const any& value) {
typedef const typename std::remove_reference<T>::type nonref;
if(value.type() != typeid(nonref)) throw;
return static_cast<any::holder<nonref>*>(value.container)->value;
}
template<typename T> T* any_cast(any* value) {
if(!value || value->type() != typeid(T)) return nullptr;
return &static_cast<any::holder<T>*>(value->container)->value;
}
template<typename T> const T* any_cast(const any* value) {
if(!value || value->type() != typeid(T)) return nullptr;
return &static_cast<any::holder<T>*>(value->container)->value;
}
}
#endif

View File

@ -19,6 +19,9 @@ namespace nall {
template<typename T> using is_array = std::is_array<T>;
template<typename T> using is_function = std::is_function<T>;
template<typename T> using is_integral = std::is_integral<T>;
template<typename T> using add_const = std::add_const<T>;
template<typename T> using remove_extent = std::remove_extent<T>;
template<typename T> using remove_reference = std::remove_reference<T>;
}
namespace nall {

View File

@ -10,26 +10,30 @@ namespace ruby {
struct pAudioOpenAL {
struct {
ALCdevice* handle;
ALCcontext* context;
ALuint source;
ALenum format;
unsigned latency;
unsigned queueLength;
ALCdevice* handle = nullptr;
ALCcontext* context = nullptr;
ALuint source = 0;
ALenum format = AL_FORMAT_STEREO16;
unsigned latency = 0;
unsigned queueLength = 0;
} device;
struct {
uint32_t* data;
unsigned length;
unsigned size;
uint32_t* data = nullptr;
unsigned length = 0;
unsigned size = 0;
} buffer;
struct {
bool synchronize;
unsigned frequency;
unsigned latency;
bool synchronize = true;
unsigned frequency = 22050;
unsigned latency = 40;
} settings;
~pAudioOpenAL() {
term();
}
auto cap(const string& name) -> bool {
if(name == Audio::Synchronize) return true;
if(name == Audio::Frequency) return true;
@ -41,23 +45,23 @@ struct pAudioOpenAL {
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
if(name == Audio::Latency) return settings.latency;
return false;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Synchronize) {
settings.synchronize = any_cast<bool>(value);
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
return true;
}
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(name == Audio::Frequency && value.is<unsigned>()) {
settings.frequency = value.get<unsigned>();
return true;
}
if(name == Audio::Latency) {
if(settings.latency != any_cast<unsigned>(value)) {
settings.latency = any_cast<unsigned>(value);
if(name == Audio::Latency && value.is<unsigned>()) {
if(settings.latency != value.get<unsigned>()) {
settings.latency = value.get<unsigned>();
updateLatency();
}
return true;
@ -66,8 +70,8 @@ struct pAudioOpenAL {
return false;
}
auto sample(uint16_t sl, uint16_t sr) -> void {
buffer.data[buffer.length++] = sl + (sr << 16);
auto sample(uint16_t left, uint16_t right) -> void {
buffer.data[buffer.length++] = left << 0 | right << 16;
if(buffer.length < buffer.size) return;
ALuint albuffer = 0;
@ -171,26 +175,6 @@ struct pAudioOpenAL {
}
}
pAudioOpenAL() {
device.source = 0;
device.handle = 0;
device.context = 0;
device.format = AL_FORMAT_STEREO16;
device.queueLength = 0;
buffer.data = 0;
buffer.length = 0;
buffer.size = 0;
settings.synchronize = true;
settings.frequency = 22050;
settings.latency = 40;
}
~pAudioOpenAL() {
term();
}
private:
auto queryDevices() -> lstring {
lstring result;

View File

@ -48,24 +48,24 @@ struct pAudioOSS {
if(name == Audio::Device) return settings.device;
if(name == Audio::Synchronize) return settings.synchronize;
if(name == Audio::Frequency) return settings.frequency;
return false;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Audio::Device) {
settings.device = any_cast<string>(value);
if(name == Audio::Device && value.is<string>()) {
settings.device = value.get<string>();
if(!settings.device) settings.device = "/dev/dsp";
return true;
}
if(name == Audio::Synchronize) {
settings.synchronize = any_cast<bool>(value);
if(name == Audio::Synchronize && value.is<bool>()) {
settings.synchronize = value.get<bool>();
updateSynchronization();
return true;
}
if(name == Audio::Frequency) {
settings.frequency = any_cast<unsigned>(value);
if(name == Audio::Frequency && value.is<unsigned>()) {
settings.frequency = value.get<unsigned>();
if(device.fd >= 0) init();
return true;
}

View File

@ -27,12 +27,12 @@ struct pInputSDL {
auto get(const string& name) -> any {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return false;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Input::Handle) {
settings.handle = any_cast<uintptr_t>(value);
if(name == Input::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}

View File

@ -25,12 +25,12 @@ struct pInputXlib {
auto get(const string& name) -> any {
if(name == Input::Handle) return (uintptr_t)settings.handle;
return false;
return {};
}
auto set(const string& name, const any& value) -> bool {
if(name == Input::Handle) {
settings.handle = any_cast<uintptr_t>(value);
if(name == Input::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}

View File

@ -6,31 +6,36 @@
namespace ruby {
struct pVideoGLX : OpenGL {
GLXContext (*glXCreateContextAttribs)(Display*, GLXFBConfig, GLXContext, int, const int*) = nullptr;
int (*glXSwapInterval)(int) = nullptr;
auto (*glXCreateContextAttribs)(Display*, GLXFBConfig, GLXContext, signed, const signed*) -> GLXContext = nullptr;
auto (*glXSwapInterval)(signed) -> signed = nullptr;
Display* display;
int screen;
Window xwindow;
Colormap colormap;
GLXContext glxcontext;
GLXWindow glxwindow;
Display* display = nullptr;
signed screen = 0;
Window xwindow = 0;
Colormap colormap = 0;
GLXContext glxcontext = nullptr;
GLXWindow glxwindow = 0;
struct {
int version_major, version_minor;
bool double_buffer;
bool is_direct;
signed version_major = 0;
signed version_minor = 0;
bool doubleBuffer = false;
bool isDirect = false;
} glx;
struct {
Window handle;
bool synchronize;
unsigned depth;
unsigned filter;
Window handle = 0;
bool synchronize = false;
unsigned depth = 24;
unsigned filter = 1; //linear
string shader;
} settings;
bool cap(const string& name) {
~pVideoGLX() {
term();
}
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) return true;
if(name == Video::Depth) return true;
@ -39,30 +44,31 @@ struct pVideoGLX : OpenGL {
return false;
}
any get(const string& name) {
auto get(const string& name) -> any {
if(name == Video::Handle) return (uintptr_t)settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
if(name == Video::Depth) return settings.depth;
if(name == Video::Filter) return settings.filter;
return false;
if(name == Video::Shader) return settings.shader;
return {};
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Synchronize) {
if(settings.synchronize != any_cast<bool>(value)) {
settings.synchronize = any_cast<bool>(value);
if(name == Video::Synchronize && value.is<bool>()) {
if(settings.synchronize != value.get<bool>()) {
settings.synchronize = value.get<bool>();
if(glXSwapInterval) glXSwapInterval(settings.synchronize);
return true;
}
}
if(name == Video::Depth) {
unsigned depth = any_cast<unsigned>(value);
if(name == Video::Depth && value.is<unsigned>()) {
unsigned depth = value.get<unsigned>();
if(depth > DefaultDepth(display, screen)) return false;
switch(depth) {
@ -75,14 +81,14 @@ struct pVideoGLX : OpenGL {
return true;
}
if(name == Video::Filter) {
settings.filter = any_cast<unsigned>(value);
if(name == Video::Filter && value.is<unsigned>()) {
settings.filter = value.get<unsigned>();
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
return true;
}
if(name == Video::Shader) {
settings.shader = any_cast<const char*>(value);
if(name == Video::Shader && value.is<string>()) {
settings.shader = value.get<string>();
OpenGL::shader(settings.shader);
if(settings.shader.empty()) OpenGL::filter = settings.filter ? GL_LINEAR : GL_NEAREST;
return true;
@ -91,20 +97,20 @@ struct pVideoGLX : OpenGL {
return false;
}
bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) {
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
OpenGL::size(width, height);
return OpenGL::lock(data, pitch);
}
void unlock() {
auto unlock() -> void {
}
void clear() {
auto clear() -> void {
OpenGL::clear();
if(glx.double_buffer) glXSwapBuffers(display, glxwindow);
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
}
void refresh() {
auto refresh() -> void {
//we must ensure that the child window is the same size as the parent window.
//unfortunately, we cannot hook the parent window resize event notification,
//as we did not create the parent window, nor have any knowledge of the toolkit used.
@ -118,12 +124,15 @@ struct pVideoGLX : OpenGL {
outputWidth = parent.width, outputHeight = parent.height;
OpenGL::refresh();
if(glx.double_buffer) glXSwapBuffers(display, glxwindow);
if(glx.doubleBuffer) glXSwapBuffers(display, glxwindow);
}
bool init() {
auto init() -> bool {
term();
display = XOpenDisplay(0);
screen = DefaultScreen(display);
glXQueryVersion(display, &glx.version_major, &glx.version_minor);
//require GLX 1.2+ API
if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false;
@ -133,7 +142,7 @@ struct pVideoGLX : OpenGL {
//let GLX determine the best Visual to use for GL output; provide a few hints
//note: some video drivers will override double buffering attribute
int attributeList[] = {
signed attributeList[] = {
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DOUBLEBUFFER, True,
@ -143,7 +152,7 @@ struct pVideoGLX : OpenGL {
None
};
int fbCount;
signed fbCount;
GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
if(fbCount == 0) return false;
@ -174,12 +183,12 @@ struct pVideoGLX : OpenGL {
glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE);
glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
glXCreateContextAttribs = (GLXContext (*)(Display*, GLXFBConfig, GLXContext, int, const int*))glGetProcAddress("glXCreateContextAttribsARB");
glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalSGI");
if(!glXSwapInterval) glXSwapInterval = (int (*)(int))glGetProcAddress("glXSwapIntervalMESA");
glXCreateContextAttribs = (GLXContext (*)(Display*, GLXFBConfig, GLXContext, signed, const signed*))glGetProcAddress("glXCreateContextAttribsARB");
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
if(glXCreateContextAttribs) {
int attributes[] = {
signed attributes[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,
None
@ -197,16 +206,16 @@ struct pVideoGLX : OpenGL {
}
//read attributes of frame buffer for later use, as requested attributes from above are not always granted
int value = 0;
signed value = 0;
glXGetConfig(display, vi, GLX_DOUBLEBUFFER, &value);
glx.double_buffer = value;
glx.is_direct = glXIsDirect(display, glxcontext);
glx.doubleBuffer = value;
glx.isDirect = glXIsDirect(display, glxcontext);
OpenGL::init();
return true;
}
void term() {
auto term() -> void {
OpenGL::term();
if(glxcontext) {
@ -223,26 +232,11 @@ struct pVideoGLX : OpenGL {
XFreeColormap(display, colormap);
colormap = 0;
}
}
pVideoGLX() {
display = XOpenDisplay(0);
screen = DefaultScreen(display);
settings.handle = 0;
settings.synchronize = false;
settings.depth = 24;
settings.filter = 1; //linear
xwindow = 0;
colormap = 0;
glxcontext = nullptr;
glxwindow = 0;
}
~pVideoGLX() {
term();
if(display) {
XCloseDisplay(display);
display = nullptr;
}
}
};

View File

@ -1,4 +1,4 @@
void OpenGL::shader(const char* pathname) {
auto OpenGL::shader(const string& pathname) -> void {
for(auto& program : programs) program.release();
programs.reset();
@ -29,11 +29,11 @@ void OpenGL::shader(const char* pathname) {
string text = node.text();
if(node.name() == "width") {
if(text.endsWith("%")) relativeWidth = real(text.rtrim("%")) / 100.0;
else absoluteWidth = decimal(text);
else absoluteWidth = text.decimal();
}
if(node.name() == "height") {
if(text.endsWith("%")) relativeHeight = real(text.rtrim("%")) / 100.0;
else absoluteHeight = decimal(text);
else absoluteHeight = text.decimal();
}
}
@ -51,7 +51,7 @@ void OpenGL::shader(const char* pathname) {
allocateHistory(historySize);
}
void OpenGL::allocateHistory(unsigned size) {
auto OpenGL::allocateHistory(unsigned size) -> void {
for(auto& frame : history) glDeleteTextures(1, &frame.texture);
history.reset();
while(size--) {
@ -65,12 +65,12 @@ void OpenGL::allocateHistory(unsigned size) {
}
}
bool OpenGL::lock(uint32_t*& data, unsigned& pitch) {
auto OpenGL::lock(uint32_t*& data, unsigned& pitch) -> bool {
pitch = width * sizeof(uint32_t);
return data = buffer;
}
void OpenGL::clear() {
auto OpenGL::clear() -> void {
for(auto& p : programs) {
glUseProgram(p.program);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, p.framebuffer);
@ -83,7 +83,7 @@ void OpenGL::clear() {
glClear(GL_COLOR_BUFFER_BIT);
}
void OpenGL::refresh() {
auto OpenGL::refresh() -> void {
clear();
glActiveTexture(GL_TEXTURE0);
@ -180,7 +180,7 @@ void OpenGL::refresh() {
}
}
bool OpenGL::init() {
auto OpenGL::init() -> bool {
if(!OpenGLBind()) return false;
glDisable(GL_ALPHA_TEST);
@ -199,13 +199,13 @@ bool OpenGL::init() {
OpenGLSurface::allocate();
glrLinkProgram(program);
shader(nullptr);
shader("");
return initialized = true;
}
void OpenGL::term() {
auto OpenGL::term() -> void {
if(initialized == false) return;
shader(nullptr); //release shader resources (eg frame[] history)
shader(""); //release shader resources (eg frame[] history)
OpenGLSurface::release();
if(buffer) { delete[] buffer; buffer = nullptr; }
initialized = false;

View File

@ -22,18 +22,23 @@ namespace ruby {
struct OpenGL;
struct OpenGLTexture {
auto getFormat() const -> GLuint;
auto getType() const -> GLuint;
GLuint texture = 0;
unsigned width = 0;
unsigned height = 0;
GLuint format = GL_RGBA8;
GLuint filter = GL_LINEAR;
GLuint wrap = GL_CLAMP_TO_BORDER;
GLuint getFormat() const;
GLuint getType() const;
};
struct OpenGLSurface : OpenGLTexture {
auto allocate() -> void;
auto size(unsigned width, unsigned height) -> void;
auto release() -> void;
auto render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) -> void;
GLuint program = 0;
GLuint framebuffer = 0;
GLuint vao = 0;
@ -42,14 +47,13 @@ struct OpenGLSurface : OpenGLTexture {
GLuint geometry = 0;
GLuint fragment = 0;
uint32_t* buffer = nullptr;
void allocate();
void size(unsigned width, unsigned height);
void release();
void render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight);
};
struct OpenGLProgram : OpenGLSurface {
auto bind(OpenGL* instance, const Markup::Node& node, const string& pathname) -> void;
auto parse(OpenGL* instance, string& source) -> void;
auto release() -> void;
unsigned phase = 0; //frame counter
unsigned modulo = 0; //frame counter modulus
unsigned absoluteWidth = 0;
@ -57,13 +61,17 @@ struct OpenGLProgram : OpenGLSurface {
double relativeWidth = 0;
double relativeHeight = 0;
vector<OpenGLTexture> pixmaps;
void bind(OpenGL* instance, const Markup::Node& node, const string& pathname);
void parse(OpenGL* instance, string& source);
void release();
};
struct OpenGL : OpenGLProgram {
auto shader(const string& pathname) -> void;
auto allocateHistory(unsigned size) -> void;
auto lock(uint32_t*& data, unsigned& pitch) -> bool;
auto clear() -> void;
auto refresh() -> void;
auto init() -> bool;
auto term() -> void;
vector<OpenGLProgram> programs;
vector<OpenGLTexture> history;
GLuint inputFormat = GL_RGBA8;
@ -72,22 +80,14 @@ struct OpenGL : OpenGLProgram {
struct Setting {
string name;
string value;
bool operator< (const Setting& source) { return name < source.name; }
bool operator==(const Setting& source) { return name == source.name; }
Setting() {}
bool operator< (const Setting& source) const { return name < source.name; }
bool operator==(const Setting& source) const { return name == source.name; }
Setting() = default;
Setting(const string& name) : name(name) {}
Setting(const string& name, const string& value) : name(name), value(value) {}
};
set<Setting> settings;
bool initialized = false;
void shader(const char* pathname);
void allocateHistory(unsigned size);
bool lock(uint32_t*& data, unsigned& pitch);
void clear();
void refresh();
bool init();
void term();
};
#include "texture.hpp"

View File

@ -1,13 +1,13 @@
void OpenGLProgram::bind(OpenGL* instance, const Markup::Node& node, const string& pathname) {
auto OpenGLProgram::bind(OpenGL* instance, const Markup::Node& node, const string& pathname) -> void {
filter = glrFilter(node["filter"].text());
wrap = glrWrap(node["wrap"].text());
modulo = glrModulo(node["modulo"].integer());
string w = node["width"].text(), h = node["height"].text();
if(w.endsWith("%")) relativeWidth = real(w.rtrim("%")) / 100.0;
else absoluteWidth = decimal(w);
else absoluteWidth = w.decimal();
if(h.endsWith("%")) relativeHeight = real(h.rtrim("%")) / 100.0;
else absoluteHeight = decimal(h);
else absoluteHeight = h.decimal();
format = glrFormat(node["format"].text());
@ -71,7 +71,7 @@ void OpenGLProgram::bind(OpenGL* instance, const Markup::Node& node, const strin
}
//apply manifest settings to shader source #in tags
void OpenGLProgram::parse(OpenGL* instance, string& source) {
auto OpenGLProgram::parse(OpenGL* instance, string& source) -> void {
lstring lines = source.split("\n");
for(auto& line : lines) {
string s = line;
@ -89,7 +89,7 @@ void OpenGLProgram::parse(OpenGL* instance, string& source) {
source = lines.merge("\n");
}
void OpenGLProgram::release() {
auto OpenGLProgram::release() -> void {
OpenGLSurface::release();
for(auto& pixmap : pixmaps) glDeleteTextures(1, &pixmap.texture);
pixmaps.reset();

View File

@ -1,10 +1,10 @@
void OpenGLSurface::allocate() {
auto OpenGLSurface::allocate() -> void {
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glGenBuffers(3, &vbo[0]);
}
void OpenGLSurface::size(unsigned w, unsigned h) {
auto OpenGLSurface::size(unsigned w, unsigned h) -> void {
if(width == w && height == h) return;
width = w, height = h;
w = glrSize(w), h = glrSize(h);
@ -25,7 +25,7 @@ void OpenGLSurface::size(unsigned w, unsigned h) {
}
}
void OpenGLSurface::release() {
auto OpenGLSurface::release() -> void {
if(vbo[0]) { glDeleteBuffers(3, &vbo[0]); for(auto &o : vbo) o = 0; }
if(vao) { glDeleteVertexArrays(1, &vao); vao = 0; }
if(vertex) { glDetachShader(program, vertex); glDeleteShader(vertex); vertex = 0; }
@ -37,7 +37,7 @@ void OpenGLSurface::release() {
width = 0, height = 0;
}
void OpenGLSurface::render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) {
auto OpenGLSurface::render(unsigned sourceWidth, unsigned sourceHeight, unsigned targetWidth, unsigned targetHeight) -> void {
glViewport(0, 0, targetWidth, targetHeight);
float w = (float)sourceWidth / (float)glrSize(sourceWidth);

View File

@ -1,10 +1,10 @@
GLuint OpenGLTexture::getFormat() const {
auto OpenGLTexture::getFormat() const -> GLuint {
if(format == GL_R32I) return GL_RED_INTEGER;
if(format == GL_R32UI) return GL_RED_INTEGER;
return GL_BGRA;
}
GLuint OpenGLTexture::getType() const {
auto OpenGLTexture::getType() const -> GLuint {
if(format == GL_R32I) return GL_UNSIGNED_INT;
if(format == GL_R32UI) return GL_UNSIGNED_INT;
if(format == GL_RGB10_A2) return GL_UNSIGNED_INT_2_10_10_10_REV;

View File

@ -1,9 +1,9 @@
static unsigned glrSize(unsigned size) {
static auto glrSize(unsigned size) -> unsigned {
return size;
//return bit::round(size); //return nearest power of two
}
static GLuint glrFormat(const string& format) {
static auto glrFormat(const string& format) -> GLuint {
if(format == "r32i" ) return GL_R32I;
if(format == "r32ui" ) return GL_R32UI;
if(format == "rgba8" ) return GL_RGBA8;
@ -15,53 +15,53 @@ static GLuint glrFormat(const string& format) {
return GL_RGBA8;
}
static GLuint glrFilter(const string& filter) {
static auto glrFilter(const string& filter) -> GLuint {
if(filter == "nearest") return GL_NEAREST;
if(filter == "linear" ) return GL_LINEAR;
return GL_LINEAR;
}
static GLuint glrWrap(const string& wrap) {
static auto glrWrap(const string& wrap) -> GLuint {
if(wrap == "border") return GL_CLAMP_TO_BORDER;
if(wrap == "edge" ) return GL_CLAMP_TO_EDGE;
if(wrap == "repeat") return GL_REPEAT;
return GL_CLAMP_TO_BORDER;
}
static unsigned glrModulo(unsigned modulo) {
static auto glrModulo(unsigned modulo) -> unsigned {
if(modulo) return modulo;
return 300; //divisible by 2, 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 100, 150
}
static GLuint glrProgram() {
static auto glrProgram() -> GLuint {
GLuint program = 0;
glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)&program);
return program;
}
static void glrUniform1i(const string& name, GLint value) {
static auto glrUniform1i(const string& name, GLint value) -> void {
GLint location = glGetUniformLocation(glrProgram(), name);
glUniform1i(location, value);
}
static void glrUniform4f(const string& name, GLfloat value0, GLfloat value1, GLfloat value2, GLfloat value3) {
static auto glrUniform4f(const string& name, GLfloat value0, GLfloat value1, GLfloat value2, GLfloat value3) -> void {
GLint location = glGetUniformLocation(glrProgram(), name);
glUniform4f(location, value0, value1, value2, value3);
}
static void glrUniformMatrix4fv(const string& name, GLfloat *values) {
static auto glrUniformMatrix4fv(const string& name, GLfloat* values) -> void {
GLint location = glGetUniformLocation(glrProgram(), name);
glUniformMatrix4fv(location, 1, GL_FALSE, values);
}
static void glrParameters(GLuint filter, GLuint wrap) {
static auto glrParameters(GLuint filter, GLuint wrap) -> void {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
}
static GLuint glrCreateShader(GLuint program, GLuint type, const char* source) {
static auto glrCreateShader(GLuint program, GLuint type, const char* source) -> GLuint {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, 0);
glCompileShader(shader);
@ -80,7 +80,7 @@ static GLuint glrCreateShader(GLuint program, GLuint type, const char* source) {
return shader;
}
static void glrLinkProgram(GLuint program) {
static auto glrLinkProgram(GLuint program) -> void {
glLinkProgram(program);
GLint result = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &result);

View File

@ -8,38 +8,43 @@
namespace ruby {
struct pVideoSDL {
Display* display;
SDL_Surface* screen;
SDL_Surface* buffer;
unsigned iwidth, iheight;
Display* display = nullptr;
SDL_Surface* screen = nullptr;
SDL_Surface* buffer = nullptr;
unsigned iwidth = 0;
unsigned iheight = 0;
struct {
uintptr_t handle;
uintptr_t handle = 0;
unsigned width;
unsigned height;
unsigned width = 0;
unsigned height = 0;
} settings;
bool cap(const string& name) {
~pVideoSDL() {
term();
}
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
return false;
}
any get(const string& name) {
auto get(const string& name) -> any {
if(name == Video::Handle) return settings.handle;
return false;
return {};
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
return false;
}
void resize(unsigned width, unsigned height) {
auto resize(unsigned width, unsigned height) -> void {
if(iwidth >= width && iheight >= height) return;
iwidth = max(width, iwidth);
@ -52,7 +57,7 @@ struct pVideoSDL {
);
}
bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) {
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
if(width != settings.width || height != settings.height) {
resize(settings.width = width, settings.height = height);
}
@ -62,11 +67,11 @@ struct pVideoSDL {
return data = (uint32_t*)buffer->pixels;
}
void unlock() {
auto unlock() -> void {
if(SDL_MUSTLOCK(buffer)) SDL_UnlockSurface(buffer);
}
void clear() {
auto clear() -> void {
if(SDL_MUSTLOCK(buffer)) SDL_LockSurface(buffer);
for(unsigned y = 0; y < iheight; y++) {
uint32_t* data = (uint32_t*)buffer->pixels + y * (buffer->pitch >> 2);
@ -76,7 +81,7 @@ struct pVideoSDL {
refresh();
}
void refresh() {
auto refresh() -> void {
//ruby input is X8R8G8B8, top 8-bits are ignored.
//as SDL forces us to use a 32-bit buffer, we must set alpha to 255 (full opacity)
//to prevent blending against the window beneath when X window visual is 32-bits.
@ -106,12 +111,12 @@ struct pVideoSDL {
SDL_UpdateRect(screen, dest.x, dest.y, dest.w, dest.h);
}
bool init() {
auto init() -> bool {
display = XOpenDisplay(0);
//todo: this causes a segfault inside SDL_SetVideoMode on FreeBSD (works under Linux)
char env[512];
sprintf(env, "SDL_WINDOWID=%ld", (long int)settings.handle);
sprintf(env, "SDL_WINDOWID=%ld", (long)settings.handle);
putenv(env);
SDL_InitSubSystem(SDL_INIT_VIDEO);
@ -126,15 +131,11 @@ struct pVideoSDL {
return true;
}
void term() {
auto term() -> void {
XCloseDisplay(display);
SDL_FreeSurface(buffer);
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
pVideoSDL() {
settings.handle = 0;
}
};
DeclareVideo(SDL)

View File

@ -13,15 +13,16 @@ namespace ruby {
struct pVideoXShm {
struct Device {
Display* display = nullptr;
int screen;
int depth;
signed screen = 0;
signed depth = 0;
Visual* visual = nullptr;
Window window;
Window window = 0;
XShmSegmentInfo shmInfo;
XImage* image = nullptr;
uint32_t* buffer = nullptr;
unsigned width, height;
unsigned width = 0;
unsigned height = 0;
} device;
struct Settings {
@ -29,34 +30,39 @@ struct pVideoXShm {
unsigned filter = Video::FilterLinear;
uint32_t* buffer = nullptr;
unsigned width, height;
unsigned width = 0;
unsigned height = 0;
} settings;
bool cap(const string& name) {
~pVideoXShm() {
term();
}
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Filter) return true;
return false;
}
any get(const string& name) {
auto get(const string& name) -> any {
if(name == Video::Handle) return settings.handle;
if(name == Video::Filter) return settings.filter;
return false;
return {};
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Filter) {
settings.filter = any_cast<unsigned>(value);
if(name == Video::Filter && value.is<unsigned>()) {
settings.filter = value.get<unsigned>();
return true;
}
return false;
}
bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) {
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
if(settings.buffer == nullptr || settings.width != width || settings.height != height) {
if(settings.buffer) delete[] settings.buffer;
settings.width = width, settings.height = height;
@ -68,18 +74,18 @@ struct pVideoXShm {
return true;
}
void unlock() {
auto unlock() -> void {
}
void clear() {
auto clear() -> void {
if(settings.buffer == nullptr) return;
uint32_t* dp = settings.buffer;
unsigned length = settings.width * settings.height;
while(length--) *dp++ = (255u << 24);
while(length--) *dp++ = 255u << 24;
refresh();
}
void refresh() {
auto refresh() -> void {
if(settings.buffer == nullptr) return;
size();
@ -96,12 +102,12 @@ struct pVideoXShm {
if(settings.filter == Video::FilterNearest) {
for(unsigned x = 0; x < device.width; x++) {
*dp++ = (255u << 24) | sp[(unsigned)xstep];
*dp++ = 255u << 24 | sp[(unsigned)xstep];
xstep += xratio;
}
} else { //settings.filter == Video::FilterLinear
for(unsigned x = 0; x < device.width; x++) {
*dp++ = (255u << 24) | interpolate(xstep - (unsigned)xstep, sp[(unsigned)xstep], sp[(unsigned)xstep + 1]);
*dp++ = 255u << 24 | interpolate(xstep - (unsigned)xstep, sp[(unsigned)xstep], sp[(unsigned)xstep + 1]);
xstep += xratio;
}
}
@ -116,7 +122,7 @@ struct pVideoXShm {
XFlush(device.display);
}
bool init() {
auto init() -> bool {
device.display = XOpenDisplay(0);
device.screen = DefaultScreen(device.display);
@ -151,18 +157,16 @@ struct pVideoXShm {
return true;
}
void term() {
auto term() -> void {
free();
if(device.display) { XCloseDisplay(device.display); device.display = nullptr; }
if(device.display) {
XCloseDisplay(device.display);
device.display = nullptr;
}
}
~pVideoXShm() {
term();
}
//internal:
bool size() {
private:
auto size() -> bool {
XWindowAttributes windowAttributes;
XGetWindowAttributes(device.display, settings.handle, &windowAttributes);
@ -185,7 +189,7 @@ struct pVideoXShm {
return true;
}
void free() {
auto free() -> void {
if(device.buffer == nullptr) return;
device.buffer = nullptr;
XShmDetach(device.display, &device.shmInfo);
@ -194,13 +198,13 @@ struct pVideoXShm {
shmctl(device.shmInfo.shmid, IPC_RMID, 0);
}
alwaysinline uint32_t interpolate(float mu, uint32_t a, uint32_t b) {
uint8_t ar = (a >> 16), ag = (a >> 8), ab = (a >> 0);
uint8_t br = (b >> 16), bg = (b >> 8), bb = (b >> 0);
alwaysinline auto interpolate(float mu, uint32_t a, uint32_t b) -> uint32_t {
uint8_t ar = a >> 16, ag = a >> 8, ab = a >> 0;
uint8_t br = b >> 16, bg = b >> 8, bb = b >> 0;
uint8_t cr = ar * (1.0 - mu) + br * mu;
uint8_t cg = ag * (1.0 - mu) + bg * mu;
uint8_t cb = ab * (1.0 - mu) + bb * mu;
return (cr << 16) | (cg << 8) | (cb << 0);
return cr << 16 | cg << 8 | cb << 0;
}
};

View File

@ -4,54 +4,58 @@
#include <X11/extensions/Xv.h>
#include <X11/extensions/Xvlib.h>
extern "C" XvImage* XvShmCreateImage(Display*, XvPortID, int, char*, int, int, XShmSegmentInfo*);
extern "C" auto XvShmCreateImage(Display*, XvPortID, signed, char*, signed, signed, XShmSegmentInfo*) -> XvImage*;
namespace ruby {
struct pVideoXv {
uint32_t* buffer;
uint8_t* ytable;
uint8_t* utable;
uint8_t* vtable;
uint32_t* buffer = nullptr;
uint8_t* ytable = nullptr;
uint8_t* utable = nullptr;
uint8_t* vtable = nullptr;
enum XvFormat {
enum XvFormat : unsigned {
XvFormatRGB32,
XvFormatRGB24,
XvFormatRGB16,
XvFormatRGB15,
XvFormatYUY2,
XvFormatUYVY,
XvFormatUnknown
XvFormatUnknown,
};
struct {
Display* display;
GC gc;
Window window;
Colormap colormap;
Display* display = nullptr;
GC gc = 0;
Window window = 0;
Colormap colormap = 0;
XShmSegmentInfo shminfo;
int port;
int depth;
int visualid;
signed port = -1;
signed depth = 0;
signed visualid = 0;
XvImage* image;
XvFormat format;
uint32_t fourcc;
XvImage* image = nullptr;
XvFormat format = XvFormatUnknown;
uint32_t fourcc = 0;
unsigned width;
unsigned height;
unsigned width = 0;
unsigned height = 0;
} device;
struct {
Window handle;
bool synchronize;
Window handle = 0;
bool synchronize = false;
unsigned width;
unsigned height;
unsigned width = 0;
unsigned height = 0;
} settings;
bool cap(const string& name) {
~pVideoXv() {
term();
}
auto cap(const string& name) -> bool {
if(name == Video::Handle) return true;
if(name == Video::Synchronize) {
return XInternAtom(XOpenDisplay(0), "XV_SYNC_TO_VBLANK", true) != None;
@ -59,23 +63,23 @@ struct pVideoXv {
return false;
}
any get(const string& name) {
auto get(const string& name) -> any {
if(name == Video::Handle) return settings.handle;
if(name == Video::Synchronize) return settings.synchronize;
return false;
return {};
}
bool set(const string& name, const any& value) {
if(name == Video::Handle) {
settings.handle = any_cast<uintptr_t>(value);
auto set(const string& name, const any& value) -> bool {
if(name == Video::Handle && value.is<uintptr_t>()) {
settings.handle = value.get<uintptr_t>();
return true;
}
if(name == Video::Synchronize) {
if(name == Video::Synchronize && value.is<bool>()) {
Display* display = XOpenDisplay(0);
Atom atom = XInternAtom(display, "XV_SYNC_TO_VBLANK", true);
if(atom != None && device.port >= 0) {
settings.synchronize = any_cast<bool>(value);
settings.synchronize = value.get<bool>();
XvSetPortAttribute(display, device.port, atom, settings.synchronize);
return true;
}
@ -85,7 +89,7 @@ struct pVideoXv {
return false;
}
void resize(unsigned width, unsigned height) {
auto resize(unsigned width, unsigned height) -> void {
if(device.width >= width && device.height >= height) return;
device.width = max(width, device.width);
device.height = max(height, device.height);
@ -106,7 +110,7 @@ struct pVideoXv {
buffer = new uint32_t[device.width * device.height];
}
bool lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) {
auto lock(uint32_t*& data, unsigned& pitch, unsigned width, unsigned height) -> bool {
if(width != settings.width || height != settings.height) {
resize(settings.width = width, settings.height = height);
}
@ -115,17 +119,17 @@ struct pVideoXv {
return data = buffer;
}
void unlock() {
auto unlock() -> void {
}
void clear() {
memset(buffer, 0, device.width * device.height * sizeof(uint32_t));
auto clear() -> void {
memory::fill(buffer, device.width * device.height * sizeof(uint32_t));
//clear twice in case video is double buffered ...
refresh();
refresh();
}
void refresh() {
auto refresh() -> void {
unsigned width = settings.width;
unsigned height = settings.height;
@ -146,12 +150,12 @@ struct pVideoXv {
XGetWindowAttributes(device.display, device.window, &target);
switch(device.format) {
case XvFormatRGB32: render_rgb32(width, height); break;
case XvFormatRGB24: render_rgb24(width, height); break;
case XvFormatRGB16: render_rgb16(width, height); break;
case XvFormatRGB15: render_rgb15(width, height); break;
case XvFormatYUY2: render_yuy2 (width, height); break;
case XvFormatUYVY: render_uyvy (width, height); break;
case XvFormatRGB32: renderRGB32(width, height); break;
case XvFormatRGB24: renderRGB24(width, height); break;
case XvFormatRGB16: renderRGB16(width, height); break;
case XvFormatRGB15: renderRGB15(width, height); break;
case XvFormatYUY2: renderYUY2 (width, height); break;
case XvFormatUYVY: renderUYVY (width, height); break;
}
XvShmPutImage(device.display, device.port, device.window, device.gc, device.image,
@ -160,7 +164,7 @@ struct pVideoXv {
true);
}
bool init() {
auto init() -> bool {
device.display = XOpenDisplay(0);
if(!XShmQueryExtension(device.display)) {
@ -201,7 +205,7 @@ struct pVideoXv {
visualtemplate.screen = DefaultScreen(device.display);
visualtemplate.depth = device.depth;
visualtemplate.visual = 0;
int visualmatches = 0;
signed visualmatches = 0;
XVisualInfo *visualinfo = XGetVisualInfo(device.display, VisualIDMask | VisualScreenMask | VisualDepthMask, &visualtemplate, &visualmatches);
if(visualmatches < 1 || !visualinfo->visual) {
if(visualinfo) XFree(visualinfo);
@ -315,12 +319,12 @@ struct pVideoXv {
buffer = new uint32_t[device.width * device.height];
settings.width = 256;
settings.height = 256;
init_yuv_tables();
initTables();
clear();
return true;
}
void term() {
auto term() -> void {
XShmDetach(device.display, &device.shminfo);
shmdt(device.shminfo.shmaddr);
shmctl(device.shminfo.shmid, IPC_RMID, NULL);
@ -336,13 +340,14 @@ struct pVideoXv {
device.colormap = 0;
}
if(buffer) { delete[] buffer; buffer = 0; }
if(ytable) { delete[] ytable; ytable = 0; }
if(utable) { delete[] utable; utable = 0; }
if(vtable) { delete[] vtable; vtable = 0; }
if(buffer) { delete[] buffer; buffer = nullptr; }
if(ytable) { delete[] ytable; ytable = nullptr; }
if(utable) { delete[] utable; utable = nullptr; }
if(vtable) { delete[] vtable; vtable = nullptr; }
}
void render_rgb32(unsigned width, unsigned height) {
private:
auto renderRGB32(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint32_t* output = (uint32_t*)device.image->data;
@ -353,7 +358,7 @@ struct pVideoXv {
}
}
void render_rgb24(unsigned width, unsigned height) {
auto renderRGB24(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint8_t* output = (uint8_t*)device.image->data;
@ -370,7 +375,7 @@ struct pVideoXv {
}
}
void render_rgb16(unsigned width, unsigned height) {
auto renderRGB16(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
@ -385,7 +390,7 @@ struct pVideoXv {
}
}
void render_rgb15(unsigned width, unsigned height) {
auto renderRGB15(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
@ -400,7 +405,7 @@ struct pVideoXv {
}
}
void render_yuy2(unsigned width, unsigned height) {
auto renderYUY2(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
@ -423,7 +428,7 @@ struct pVideoXv {
}
}
void render_uyvy(unsigned width, unsigned height) {
auto renderUYVY(unsigned width, unsigned height) -> void {
uint32_t* input = (uint32_t*)buffer;
uint16_t* output = (uint16_t*)device.image->data;
@ -446,7 +451,7 @@ struct pVideoXv {
}
}
void init_yuv_tables() {
auto initTables() -> void {
ytable = new uint8_t[65536];
utable = new uint8_t[65536];
vtable = new uint8_t[65536];
@ -475,23 +480,6 @@ struct pVideoXv {
vtable[i] = v < 0 ? 0 : v > 255 ? 255 : v;
}
}
pVideoXv() {
device.window = 0;
device.colormap = 0;
device.port = -1;
ytable = 0;
utable = 0;
vtable = 0;
settings.handle = 0;
settings.synchronize = false;
}
~pVideoXv() {
term();
}
};
DeclareVideo(Xv)

View File

@ -9,7 +9,7 @@ include gb/GNUmakefile
include gba/GNUmakefile
ui_objects := ui-tomoko ui-program ui-configuration ui-input
ui_objects += ui-library ui-settings ui-tools ui-presentation
ui_objects += ui-settings ui-tools ui-presentation
ui_objects += ruby hiro
# platform

View File

@ -1,35 +0,0 @@
LibraryBrowser::LibraryBrowser(TabFrame& parent, Emulator::Interface::Media& media) : TabFrameItem{&parent} {
this->media = media;
setText(media.name);
layout.setMargin(5);
gameList.onActivate([&] {
libraryManager->setVisible(false);
program->loadMedia({config().library.location, this->media.name, "/", gameList.selected()->text(), ".", this->media.type, "/"});
});
}
auto LibraryBrowser::reload() -> void {
string path = {config().library.location, media.name};
directory::create(path);
gameList.reset();
gameList.append(ListViewColumn());
bool first = true;
auto folders = directory::folders(path, {"*.", media.type});
for(auto& folder : folders) {
ListViewItem item;
item.setIcon(0, Icon::Emblem::Program);
item.setText(folder.rtrim({".", media.type, "/"}));
gameList.append(item);
if(first) {
first = false;
item.setFocused();
}
}
}
auto LibraryBrowser::select() -> void {
reload();
setSelected();
gameList.setFocused();
}

View File

@ -1,3 +0,0 @@
#include "../tomoko.hpp"
#include "browser.cpp"
#include "manager.cpp"

View File

@ -1,21 +0,0 @@
struct LibraryBrowser : TabFrameItem {
LibraryBrowser(TabFrame& parent, Emulator::Interface::Media& media);
auto reload() -> void;
auto select() -> void;
Emulator::Interface::Media media;
VerticalLayout layout{this};
ListView gameList{&layout, Size{~0, ~0}};
};
struct LibraryManager : Window {
LibraryManager();
auto show(const string& type) -> void;
VerticalLayout layout{this};
TabFrame libraryFrame{&layout, Size{~0, ~0}};
vector<LibraryBrowser*> libraryBrowsers;
};
extern LibraryManager* libraryManager;

View File

@ -1,29 +0,0 @@
LibraryManager* libraryManager = nullptr;
LibraryManager::LibraryManager() {
libraryManager = this;
layout.setMargin(5);
for(auto& emulator : program->emulators) {
for(auto& media : emulator->media) {
if(media.bootable == false) continue;
auto browser = new LibraryBrowser(libraryFrame, media);
libraryBrowsers.append(browser);
}
}
setSize({640, 800});
setPlacement(0.0, 0.0);
}
auto LibraryManager::show(const string& type) -> void {
for(auto& browser : libraryBrowsers) {
if(type != browser->media.type) continue;
browser->select();
}
setTitle({"Library (", config().library.location, ")"});
setVisible();
setFocused();
}

View File

@ -7,10 +7,18 @@ Presentation::Presentation() {
libraryMenu.setText("Library");
for(auto& emulator : program->emulators) {
for(auto& media : emulator->media) {
if(media.bootable == false) continue;
if(!media.bootable) continue;
auto item = new MenuItem{&libraryMenu};
item->setText({media.name, " ..."}).onActivate([=] {
libraryManager->show(media.type);
directory::create({config().library.location, media.name});
auto location = BrowserDialog()
.setTitle({"Load ", media.name})
.setPath({config().library.location, media.name})
.setFilters(string{media.name, "|*.", media.type})
.openFolder();
if(directory::exists(location)) {
program->loadMedia(location);
}
});
loadBootableMedia.append(item);
}
@ -73,7 +81,7 @@ Presentation::Presentation() {
statusBar.setVisible(config().userInterface.showStatusBar);
if(visible()) resizeViewport();
});
showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(0); });
showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(2); });
toolsMenu.setText("Tools").setVisible(false);
saveStateMenu.setText("Save State");
@ -98,7 +106,7 @@ Presentation::Presentation() {
setTitle({"tomoko v", Emulator::Version});
setResizable(false);
setBackgroundColor({16, 16, 16});
setBackgroundColor({0, 0, 0});
resizeViewport();
}
@ -130,24 +138,32 @@ auto Presentation::updateEmulator() -> void {
}
auto Presentation::resizeViewport() -> void {
signed scale = 1;
if(config().video.scale == "Small" ) scale = 1;
if(config().video.scale == "Normal") scale = 2;
if(config().video.scale == "Large" ) scale = 4;
signed width = 256;
signed height = 240;
if(emulator) {
width = emulator->information.width;
height = emulator->information.height;
}
bool arc = config().video.aspectCorrection;
if(fullScreen() == false) {
bool arc = config().video.aspectCorrection && emulator && emulator->information.aspectRatio != 1.0;
signed windowWidth = 256 * scale;
signed windowHeight = 240 * scale;
if(arc) windowWidth = windowWidth * 8 / 7;
if(config().video.scale == "Small" ) width *= 1, height *= 1;
if(config().video.scale == "Normal") width *= 2, height *= 2;
if(config().video.scale == "Large" ) width *= 4, height *= 4;
if(arc) width = width * 8 / 7;
double stretch = (arc && emulator && emulator->information.aspectRatio != 1.0) ? 8.0 / 7.0 : 1.0;
signed multiplier = min(windowWidth / (signed)(width * stretch), windowHeight / height);
width = width * multiplier * stretch;
height = height * multiplier;
setSize({width, height});
viewport.setGeometry({0, 0, width, height});
setSize({windowWidth, windowHeight});
viewport.setGeometry({(windowWidth - width) / 2, (windowHeight - height) / 2, width, height});
setPlacement(0.5, 0.5);
} else {
auto desktop = Desktop::size();

View File

@ -1,11 +1,10 @@
//request from emulation core to load non-volatile media folder
auto Program::loadRequest(unsigned id, string name, string type) -> void {
string location = BrowserDialog()
.setParent(*presentation)
.setTitle({"Load ", name})
.setPath({config().library.location, name})
.setFilters({string{name, "|*.", type}})
.selectFolder();
.openFolder();
if(!directory::exists(location)) return;
mediaPaths(id) = location;

View File

@ -22,7 +22,6 @@ Program::Program() {
new ConfigurationManager;
new InputManager;
new LibraryManager;
new SettingsManager;
new CheatDatabase;
new ToolsManager;

View File

@ -34,7 +34,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) {
libraryPrefix.setText("Location:");
libraryLocation.setEditable(false).setText(config().library.location);
libraryChange.setText("Change ...").onActivate([&] {
if(auto location = BrowserDialog().setParent(*presentation).selectFolder()) {
if(auto location = BrowserDialog().setTitle("Select Library Location").selectFolder()) {
libraryLocation.setText(config().library.location = location);
}
});

View File

@ -0,0 +1,6 @@
AudioSettings::AudioSettings(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Device::Speaker);
setText("Audio");
layout.setMargin(5);
}

View File

@ -9,7 +9,7 @@ HotkeySettings::HotkeySettings(TabFrame* parent) : TabFrameItem(parent) {
eraseButton.setEnabled((bool)mappingList.selected());
});
resetButton.setText("Reset").onActivate([&] {
if(MessageDialog("Are you sure you want to erase all hotkey mappings?").setParent(*settingsManager).question() == 0) {
if(MessageDialog("Are you sure you want to erase all hotkey mappings?").setParent(*settingsManager).question() == "Yes") {
for(auto& mapping : inputManager->hotkeys) mapping->unbind();
refreshMappings();
}

View File

@ -16,7 +16,7 @@ InputSettings::InputSettings(TabFrame* parent) : TabFrameItem(parent) {
assignMouse2.setVisible(false).onActivate([&] { assignMouseInput(1); });
assignMouse3.setVisible(false).onActivate([&] { assignMouseInput(2); });
resetButton.setText("Reset").onActivate([&] {
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == 0) {
if(MessageDialog("Are you sure you want to erase all mappings for this device?").setParent(*settingsManager).question() == "Yes") {
for(auto& mapping : activeDevice().mappings) mapping->unbind();
refreshMappings();
}

View File

@ -1,6 +1,9 @@
#include "../tomoko.hpp"
#include "video.cpp"
#include "audio.cpp"
#include "input.cpp"
#include "hotkeys.cpp"
#include "timing.cpp"
#include "advanced.cpp"
SettingsManager* settingsManager = nullptr;

View File

@ -1,3 +1,15 @@
struct VideoSettings : TabFrameItem {
VideoSettings(TabFrame*);
VerticalLayout layout{this};
};
struct AudioSettings : TabFrameItem {
AudioSettings(TabFrame*);
VerticalLayout layout{this};
};
struct InputSettings : TabFrameItem {
InputSettings(TabFrame*);
auto updateControls() -> void;
@ -46,6 +58,12 @@ struct HotkeySettings : TabFrameItem {
Button eraseButton{&controlLayout, Size{80, 0}};
};
struct TimingSettings : TabFrameItem {
TimingSettings(TabFrame*);
VerticalLayout layout{this};
};
struct AdvancedSettings : TabFrameItem {
AdvancedSettings(TabFrame*);
@ -72,8 +90,11 @@ struct SettingsManager : Window {
VerticalLayout layout{this};
TabFrame panel{&layout, Size{~0, ~0}};
VideoSettings video{&panel};
AudioSettings audio{&panel};
InputSettings input{&panel};
HotkeySettings hotkeys{&panel};
TimingSettings timing{&panel};
AdvancedSettings advanced{&panel};
StatusBar statusBar{this};

View File

@ -0,0 +1,6 @@
TimingSettings::TimingSettings(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Device::Clock);
setText("Timing");
layout.setMargin(5);
}

View File

@ -0,0 +1,6 @@
VideoSettings::VideoSettings(TabFrame* parent) : TabFrameItem(parent) {
setIcon(Icon::Device::Display);
setText("Video");
layout.setMargin(5);
}

View File

@ -11,7 +11,6 @@ using namespace hiro;
#include "program/program.hpp"
#include "configuration/configuration.hpp"
#include "input/input.hpp"
#include "library/library.hpp"
#include "settings/settings.hpp"
#include "tools/tools.hpp"
#include "presentation/presentation.hpp"

View File

@ -62,7 +62,7 @@ auto CheatEditor::doRefresh() -> void {
}
auto CheatEditor::doReset(bool force) -> void {
if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == "Yes") {
for(auto& cheat : cheats) {
cheat.enabled = false;
cheat.code = "";

View File

@ -84,7 +84,7 @@ auto StateManager::doSave() -> void {
}
auto StateManager::doReset() -> void {
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) {
if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == "Yes") {
for(auto slot : range(Slots)) file::remove(program->stateName(1 + slot, true));
doRefresh();
}