Update to v101r06 release.

byuu says:

I reworked the video sizing code. Ended up wasting five fucking hours
fighting GTK. When you call `gtk_widget_set_size_request`, it doesn't
actually happen then. This is kind of a big deal because when I then go
to draw onto the viewport, the actual viewport child window is still the
old size, so the image gets distorted. It recovers in a frame or so with
emulation, but if we were to put a still image on there, it would stay
distorted.

The first thought is, `while(gtk_events_pending())
gtk_main_iteration_do(false);` right after the `set_size_request`. But
nope, it tells you there's no events pending. So then you think, go
deeper, use `XPending()` instead. Same thing, GTK hasn't actually issued
the command to Xlib yet. So then you think, if the widget is realized,
just call a blocking `gtk_main_iteration`. One call does nothing, two
calls results in a deadlock on the second one ... do it before program
startup, and the main window will never appear. Great.

Oh, and it's not just the viewport. It's also the widget container area
of the windows, as well as the window itself, as well as the fullscreen
mode toggle effect. They all do this.

For the latter three, I couldn't find anything that worked, so I just
added 20ms loops of constantly calling `gtk_main_iteration_do(false)`
after each one of those things. The downside here is toggling the status
bar takes 40ms, so you'll see it and it'll feel a tiny bit sluggish.

But I can't have a 20ms wait on each widget resize, that would be
catastrophic to performance on windows with lots of widgets.

I tried hooking configure-event and size-allocate, but they were very
unreliable. So instead I ended up with a loop that waits up to a maximm
of 20ms that inspects the `widget->allocation.(width,height)` values
directly and waits for them to be what we asked for with
`set_size_request`.

There was some extreme ugliness in GTK with calling
`gtk_main_iteration_do` recursively (`hiro::Widget::setGeometry` is
called recursively), so I had to lock it to only happen on the top level
widgets (the child ones should get resized while waiting on the
top-level ones, so it should be fine in practice), and also only run it
on realized widgets.

Even still, I'm getting ~3 timeouts when opening the settings dialog in
higan, but no other windows. But, this is the best I can do for now.

And the reason for all of this pain? Yeah, updated the video code.

So the Emulator::Interface now has this:

    struct VideoSize { uint width, height; };  //or requiem for a tuple
    auto videoSize() -> VideoSize;
    auto videoSize(uint width, uint height, bool arc) -> VideoSize;

The first function, for now, is just returning the literal surface size.
I may remove this ... one thing I want to allow for is cores that send
different texture sizes based on interlace/hires/overscan/etc settings.

The second function is more interesting. Instead of having the UI trying
to figure out sizing, I figure the emulation cores can do a better job
and we can customize it per-core now. So it gets the window's width and
height, and whether the user asked for aspect correction, and then
computes the best width/height ratio possible. For now they're all just
doing multiples of a 1x scale to the UI 2x,3x,4x modes.

We still need a third function, which will probably be what I repurpose
videoSize() for: to return the 'effective' size for pixel shaders, to
then feed into ruby, to then feed into quark, to then feed into our
shaders. Since shaders use normalized coordinates for pixel fetching,
this should work out just fine. The real texture size will be exposed to
quark shaders as well, of course.

Now for the main window ... it's just hard-coded to be 640x480, 960x720,
1280x960 for now. It works nicely for some cores on some modes, not so
much for others. Work in progress I guess.

I also took the opportunity to draw the about dialog box logo on the
main window. Got a bit fancy and used the old spherical gradient and
impose functionality of nall/image on it. Very minor highlight, nothing
garish. Just something nicer than a solid black window.

If you guys want to mess around with sizes, placements, and gradient
styles/colors/shapes ... feel free. If you come up with something nicer,
do share.

That's what led to all the GTK hell ... the logo wasn't drawing right as
you resized the window. But now it is, though I am not at all happy with
the hacking I had to do.

I also had to improve the video update code as a result of this:

  - when you unload a game, it blacks out the screen
      - if you are not quitting the emulator, it'll draw the logo; if
        you are, it won't
  - when you load a game, it black out the logo

These options prevent any unsightliness from resizing the viewport with
image data on it already

I need to redraw the logo when toggling fullscreen with no game loaded
as well for Windows, it seems.
This commit is contained in:
Tim Allen 2016-08-13 23:57:48 +10:00
parent ac2d0ba1cf
commit 427bac3011
23 changed files with 173 additions and 51 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator {
static const string Name = "higan";
static const string Version = "101.05";
static const string Version = "101.06";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "http://byuu.org/";

View File

@ -6,10 +6,7 @@ struct Interface {
struct Information {
string manufacturer;
string name;
uint width;
uint height;
bool overscan;
double aspectRatio;
bool resettable;
struct Capability {
bool states;
@ -70,6 +67,9 @@ struct Interface {
virtual auto title() -> string = 0;
//video information
struct VideoSize { uint width, height; };
virtual auto videoSize() -> VideoSize = 0;
virtual auto videoSize(uint width, uint height, bool arc) -> VideoSize = 0;
virtual auto videoFrequency() -> double = 0;
virtual auto videoColors() -> uint32 = 0;
virtual auto videoColor(uint32 color) -> uint64 = 0;

View File

@ -10,10 +10,7 @@ Interface::Interface() {
information.manufacturer = "Nintendo";
information.name = "Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
@ -54,6 +51,17 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {256, 240};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 256 * (arc ? 8.0 / 7.0 : 1.0);
uint h = 240;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 21477272.0 / (262.0 * 1364.0 - 4.0);
}

View File

@ -25,9 +25,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -11,10 +11,7 @@ Interface::Interface() {
information.manufacturer = "Nintendo";
information.name = "Game Boy";
information.width = 160;
information.height = 144;
information.overscan = false;
information.aspectRatio = 1.0;
information.resettable = false;
information.capability.states = true;
@ -48,6 +45,17 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {160, 144};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 160;
uint h = 144;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 4194304.0 / (154.0 * 456.0);
}

View File

@ -24,9 +24,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -10,10 +10,7 @@ Interface::Interface() {
information.manufacturer = "Nintendo";
information.name = "Game Boy Advance";
information.width = 240;
information.height = 160;
information.overscan = false;
information.aspectRatio = 1.0;
information.resettable = false;
information.capability.states = true;
@ -49,6 +46,17 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {240, 160};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 240;
uint h = 160;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 16777216.0 / (228.0 * 1232.0);
}

View File

@ -22,9 +22,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -10,10 +10,7 @@ Interface::Interface() {
information.manufacturer = "Sega";
information.name = "Mega Drive";
information.width = 320;
information.height = 240;
information.overscan = true;
information.aspectRatio = 1.0;
information.resettable = true;
information.capability.states = false;
@ -52,6 +49,17 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {1280, 480};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 320;
uint h = 240;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 60.0;
}

View File

@ -23,9 +23,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -12,10 +12,7 @@ Interface::Interface() {
information.manufacturer = "Nintendo";
information.name = "Super Famicom";
information.width = 256;
information.height = 240;
information.overscan = true;
information.aspectRatio = 8.0 / 7.0;
information.resettable = true;
information.capability.states = true;
@ -132,6 +129,17 @@ auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {512, 480};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 256 * (arc ? 8.0 / 7.0 : 1.0);
uint h = 240;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
switch(system.region()) { default:
case System::Region::NTSC: return (system.colorburst() * 6.0) / (262.0 * 1364.0 - 4.0);

View File

@ -40,9 +40,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -200,40 +200,37 @@ auto Presentation::updateEmulator() -> void {
}
auto Presentation::resizeViewport() -> void {
int width = emulator ? emulator->information.width : 256;
int height = emulator ? emulator->information.height : 240;
double stretch = emulator ? emulator->information.aspectRatio : 1.0;
if(stretch != 1.0) {
//aspect correction is always enabled in fullscreen mode
if(!fullScreen() && !settings["Video/AspectCorrection"].boolean()) stretch = 1.0;
}
int scale = 2;
uint scale = 2;
if(settings["Video/Scale"].text() == "Small" ) scale = 2;
if(settings["Video/Scale"].text() == "Medium") scale = 3;
if(settings["Video/Scale"].text() == "Large" ) scale = 4;
int windowWidth = 0, windowHeight = 0;
uint windowWidth = 0, windowHeight = 0;
bool aspectCorrection = true;
if(!fullScreen()) {
windowWidth = 320 * scale;
windowHeight = 240 * scale;
aspectCorrection = settings["Video/AspectCorrection"].boolean();
} else {
windowWidth = geometry().width();
windowHeight = geometry().height();
}
int multiplier = min(windowWidth / (int)(width * stretch), windowHeight / height);
width = width * multiplier * stretch;
height = height * multiplier;
if(!fullScreen()) setSize({windowWidth, windowHeight});
viewport.setGeometry({(windowWidth - width) / 2, (windowHeight - height) / 2, width, height});
if(!emulator) drawSplashScreen();
if(!emulator) {
viewport.setGeometry({0, 0, windowWidth, windowHeight});
draw(Resource::Logo::higan);
} else {
auto videoSize = emulator->videoSize(windowWidth, windowHeight, aspectCorrection);
viewport.setGeometry({
(windowWidth - videoSize.width) / 2, (windowHeight - videoSize.height) / 2,
videoSize.width, videoSize.height
});
}
}
auto Presentation::toggleFullScreen() -> void {
if(fullScreen() == false) {
if(!fullScreen()) {
menuBar.setVisible(false);
statusBar.setVisible(false);
setResizable(true);
@ -251,15 +248,33 @@ auto Presentation::toggleFullScreen() -> void {
resizeViewport();
}
auto Presentation::drawSplashScreen() -> void {
auto Presentation::draw(image logo) -> void {
if(!video) return;
uint32_t* output;
uint length;
if(video->lock(output, length, 256, 240)) {
for(auto y : range(240)) {
auto dp = output + y * (length >> 2);
for(auto x : range(256)) *dp++ = 0xff000000;
uint length = 0;
uint width = viewport.geometry().width();
uint height = viewport.geometry().height();
if(video->lock(output, length, width, height)) {
uint cx = (width - logo.width()) - 10;
uint cy = (height - logo.height()) - 10;
image backdrop;
backdrop.allocate(width, height);
if(logo && !program->hasQuit) {
backdrop.sphericalGradient(0xff0000bf, 0xff000000, logo.width(), logo.height() / 2, width, height);
backdrop.impose(image::blend::sourceAlpha, cx, cy, logo, 0, 0, logo.width(), logo.height());
} else {
backdrop.fill(0xff000000);
}
auto data = (uint32_t*)backdrop.data();
for(auto y : range(height)) {
auto dp = output + y * (length >> 2);
auto sp = data + y * width;
for(auto x : range(width)) *dp++ = *sp++;
}
video->unlock();
video->refresh();
}

View File

@ -13,7 +13,7 @@ struct Presentation : Window {
auto updateEmulator() -> void;
auto resizeViewport() -> void;
auto toggleFullScreen() -> void;
auto drawSplashScreen() -> void;
auto draw(image logo = {}) -> void;
auto loadShaders() -> void;
MenuBar menuBar{this};

View File

@ -29,6 +29,7 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
}
updateAudioDriver();
updateAudioEffects();
presentation->draw();
emulator->power();
presentation->resizeViewport();
@ -44,12 +45,14 @@ auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interfa
auto Program::unloadMedium() -> void {
if(!emulator) return;
presentation->draw();
toolsManager->cheatEditor.saveCheats();
emulator->unload();
emulator = nullptr;
mediumPaths.reset();
presentation->drawSplashScreen();
presentation->resizeViewport();
presentation->draw(Resource::Logo::higan);
presentation->setTitle({"higan v", Emulator::Version});
presentation->systemMenu.setVisible(false);
presentation->toolsMenu.setVisible(false);

View File

@ -31,7 +31,7 @@ Program::Program(string_vector args) {
video->set(Video::Synchronize, settings["Video/Synchronize"].boolean());
if(!video->init()) video = Video::create("None");
presentation->drawSplashScreen();
presentation->draw(Resource::Logo::higan);
audio = Audio::create(settings["Audio/Driver"].text());
audio->set(Audio::Device, settings["Audio/Device"].text());
@ -87,6 +87,7 @@ auto Program::main() -> void {
}
auto Program::quit() -> void {
hasQuit = true;
unloadMedium();
settings.quit();
inputManager->quit();

View File

@ -36,6 +36,7 @@ struct Program : Emulator::Interface::Bind {
auto updateAudioDriver() -> void;
auto updateAudioEffects() -> void;
bool hasQuit = false;
bool pause = false;
vector<Emulator::Interface*> emulators;

View File

@ -10,10 +10,7 @@ Interface::Interface() {
information.manufacturer = "Bandai";
information.name = "WonderSwan";
information.width = 224; //note: technically 224x144; but screen can be rotated
information.height = 224; //by using a square size; this can be done in the core
information.overscan = false;
information.aspectRatio = 1.0;
information.resettable = false;
information.capability.states = true;
@ -54,6 +51,17 @@ auto Interface::title() -> string {
return cartridge.information.title;
}
auto Interface::videoSize() -> VideoSize {
return {224, 224};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 224;
uint h = 224;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 3072000.0 / (159.0 * 256.0); //~75.47hz
}

View File

@ -24,9 +24,13 @@ struct Interface : Emulator::Interface {
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double override;
auto loaded() -> bool override;

View File

@ -1,4 +1,5 @@
#include <nall/platform.hpp>
#include <nall/chrono.hpp>
#include <nall/directory.hpp>
#include <nall/function.hpp>
#include <nall/image.hpp>
@ -203,6 +204,8 @@ struct Position {
Position();
Position(signed x, signed y);
template<typename X, typename Y>
Position(X x, Y y) : Position((signed)x, (signed)y) {}
explicit operator bool() const;
auto operator==(const Position& source) const -> bool;
@ -230,6 +233,8 @@ struct Size {
Size();
Size(signed width, signed height);
template<typename W, typename H>
Size(W width, H height) : Size((signed)width, (signed)height) {}
explicit operator bool() const;
auto operator==(const Size& source) const -> bool;
@ -261,6 +266,8 @@ struct Geometry {
Geometry();
Geometry(Position position, Size size);
Geometry(signed x, signed y, signed width, signed height);
template<typename X, typename Y, typename W, typename H>
Geometry(X x, Y y, W width, H height) : Geometry((signed)x, (signed)y, (signed)width, (signed)height) {}
explicit operator bool() const;
auto operator==(const Geometry& source) const -> bool;

View File

@ -41,7 +41,23 @@ auto pWidget::setFont(const Font& font) -> void {
auto pWidget::setGeometry(Geometry geometry) -> void {
if(!gtkWidget) return;
if(gtkParent) gtk_fixed_move(GTK_FIXED(gtkParent), gtkWidget, geometry.x(), geometry.y());
gtk_widget_set_size_request(gtkWidget, max(1, geometry.width()), max(1, geometry.height()));
if(geometry.width() < 1) geometry.setWidth (1);
if(geometry.height() < 1) geometry.setHeight(1);
gtk_widget_set_size_request(gtkWidget, geometry.width(), geometry.height());
if(gtk_widget_get_realized(gtkWidget)) {
static bool locked = false;
if(!locked) {
locked = true;
auto time = chrono::millisecond();
while(chrono::millisecond() - time < 20) {
gtk_main_iteration_do(false);
if(gtkWidget->allocation.width != geometry.width ()) continue;
if(gtkWidget->allocation.height != geometry.height()) continue;
break;
}
locked = false;
}
}
self().doSize();
}

View File

@ -266,6 +266,8 @@ auto pWindow::setFullScreen(bool fullScreen) -> void {
gtk_window_unfullscreen(GTK_WINDOW(widget));
state().geometry = windowedGeometry;
}
auto time = chrono::millisecond();
while(chrono::millisecond() - time < 20) gtk_main_iteration_do(false);
}
auto pWindow::setGeometry(Geometry geometry) -> void {
@ -278,7 +280,12 @@ auto pWindow::setGeometry(Geometry geometry) -> void {
gtk_window_set_geometry_hints(GTK_WINDOW(widget), GTK_WIDGET(widget), &geom, GDK_HINT_MIN_SIZE);
gtk_widget_set_size_request(formContainer, geometry.width(), geometry.height());
auto time1 = chrono::millisecond();
while(chrono::millisecond() - time1 < 20) gtk_main_iteration_do(false);
gtk_window_resize(GTK_WINDOW(widget), geometry.width(), geometry.height() + _menuHeight() + _statusHeight());
auto time2 = chrono::millisecond();
while(chrono::millisecond() - time2 < 20) gtk_main_iteration_do(false);
}
auto pWindow::setModal(bool modal) -> void {

View File

@ -44,8 +44,8 @@ struct pWindow : pObject {
GtkWidget* gtkMenu = nullptr;
GtkWidget* gtkStatus = nullptr;
GtkAllocation lastAllocation = {0};
bool onSizePending = false;
Geometry windowedGeometry{128, 128, 256, 256};
bool onSizePending = false;
};
}