Update to v095r02 release.

byuu says:

Aspect correction is fixed now. Works way better than in v095 official.

It's still force-enabled in fullscreen mode. The idea of disabling it is
that it looks bad at 2x scale. But when you're fullscreen with a minimum
of 4x scale, there's no reason not to enable it.

It won't turn on at all for GB/C/A anymore. And I dropped the cute
attempt at making the aspect prettier on 2560x1600 monitors, so it'll be
the stock 8:7 across the board now for S/NES.

Also, the aspect correction will affect the window even when a game's
not loaded now, so the size won't bounce around as you change games in
windowed mode between GB/C/A and S/NES.

...

I also enhanced the ruby/glx driver. It won't crash if OpenGL 3.2 isn't
available anymore (fails safely ... had to capture the Xlib error
handler to suppress that), and it defaults to the MESA glXSwapInterval
before the SGI version. Because apparently the MESA version defines the
SGI version, but makes it a no-op. What. The. Fuck. right? But whatever,
reordering the enumerations fixes the ability to toggle Vsync on AMD
GPUs now.

...

Video shaders are back again. If you are using the OpenGL driver, you'll
see a "Video Shaders" menu beneath the "Video Filters" menu (couldn't
merge it with the filters due to hiro now constructing menu ordering
inside the header files. This works fine though.)

You want either "higan.exe" + "Video Shaders/" or "~/.local/bin/tomoko"
+ "~/.local/tomoko/Video Shaders/"
This commit is contained in:
Tim Allen 2015-11-08 19:54:42 +11:00
parent 8476a12deb
commit b42ab2fcb3
15 changed files with 190 additions and 72 deletions

View File

@ -8,7 +8,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "095.01"; static const string Version = "095.02";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";
@ -41,9 +41,9 @@ template<typename R, typename... P> struct hook<R (P...)> {
hook() {} hook() {}
hook(const hook& hook) { callback = hook.callback; } hook(const hook& hook) { callback = hook.callback; }
hook(void* function) { callback = function; } hook(void* function) { callback = function; }
hook(R (*function)(P...)) { callback = function; } hook(auto (*function)(P...) -> R) { callback = function; }
template<typename C> hook(R (C::*function)(P...), C* object) { callback = {function, object}; } template<typename C> hook(auto (C::*function)(P...) -> R, C* object) { callback = {function, object}; }
template<typename C> hook(R (C::*function)(P...) const, C* object) { callback = {function, object}; } template<typename C> hook(auto (C::*function)(P...) const -> R, C* object) { callback = {function, object}; }
template<typename L> hook(const L& function) { callback = function; } template<typename L> hook(const L& function) { callback = function; }
auto operator=(const hook& source) -> hook& { callback = source.callback; return *this; } auto operator=(const hook& source) -> hook& { callback = source.callback; return *this; }

View File

@ -30,10 +30,9 @@ auto Archive::create(const string& beatname, const string& pathname, const strin
for(auto& name : contents) { for(auto& name : contents) {
string location{pathname, name}; string location{pathname, name};
bool directory = name.endsWith("/"); bool directory = name.endsWith("/");
bool readable = file_system_object::readable(location);
bool writable = file_system_object::writable(location); bool writable = file_system_object::writable(location);
bool executable = file_system_object::executable(location); bool executable = file_system_object::executable(location);
unsigned info = directory << 0 | readable << 1 | writable << 2 | executable << 3 | (name.rtrim("/").size() - 1) << 4; unsigned info = directory << 0 | writable << 1 | executable << 2 | (name.rtrim("/").size() - 1) << 3;
beat.writevu(info); beat.writevu(info);
beat.writes(name); beat.writes(name);
@ -68,14 +67,13 @@ auto Archive::unpack(const string& beatname, const string& pathname) -> bool {
directory::create(pathname); directory::create(pathname);
while(beat.offset() < beat.size() - 4) { while(beat.offset() < beat.size() - 4) {
auto info = beat.readvu(); auto info = beat.readvu();
auto name = beat.reads((info >> 4) + 1); auto name = beat.reads((info >> 3) + 1);
if(name.find("\\") || name.find("../")) return false; //block path exploits if(name.find("\\") || name.find("../")) return false; //block path exploits
string location{pathname, name}; string location{pathname, name};
bool directory = info & 1; bool directory = info & 1;
bool readable = info & 2; bool writable = info & 2;
bool writable = info & 4; bool executable = info & 4;
bool executable = info & 8;
if(directory) { if(directory) {
if(!nall::directory::create(location)) return false; if(!nall::directory::create(location)) return false;
@ -103,7 +101,7 @@ auto Archive::extract(const string& beatname, const string& filename) -> vector<
while(beat.offset() < beat.size() - 4) { while(beat.offset() < beat.size() - 4) {
auto info = beat.readvu(); auto info = beat.readvu();
auto name = beat.reads((info >> 4) + 1); auto name = beat.reads((info >> 3) + 1);
if(info & 1) continue; //ignore directories if(info & 1) continue; //ignore directories
auto size = beat.readvu(); auto size = beat.readvu();

View File

@ -3,10 +3,13 @@
#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 #define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 #define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
auto VideoGLX_X11ErrorHandler(Display*, XErrorEvent*) -> int {
return 0; //suppress errors
}
struct VideoGLX : Video, OpenGL { struct VideoGLX : Video, OpenGL {
~VideoGLX() { term(); } ~VideoGLX() { term(); }
auto (*glXCreateContextAttribs)(Display*, GLXFBConfig, GLXContext, signed, const signed*) -> GLXContext = nullptr;
auto (*glXSwapInterval)(signed) -> signed = nullptr; auto (*glXSwapInterval)(signed) -> signed = nullptr;
Display* display = nullptr; Display* display = nullptr;
@ -17,8 +20,8 @@ struct VideoGLX : Video, OpenGL {
GLXWindow glxwindow = 0; GLXWindow glxwindow = 0;
struct { struct {
signed version_major = 0; signed versionMajor = 0;
signed version_minor = 0; signed versionMinor = 0;
bool doubleBuffer = false; bool doubleBuffer = false;
bool isDirect = false; bool isDirect = false;
} glx; } glx;
@ -127,12 +130,12 @@ struct VideoGLX : Video, OpenGL {
display = XOpenDisplay(0); display = XOpenDisplay(0);
screen = DefaultScreen(display); screen = DefaultScreen(display);
glXQueryVersion(display, &glx.version_major, &glx.version_minor);
//require GLX 1.2+ API //require GLX 1.2+ API
if(glx.version_major < 1 || (glx.version_major == 1 && glx.version_minor < 2)) return false; glXQueryVersion(display, &glx.versionMajor, &glx.versionMinor);
if(glx.versionMajor < 1 || (glx.versionMajor == 1 && glx.versionMinor < 2)) return false;
XWindowAttributes window_attributes; XWindowAttributes windowAttributes;
XGetWindowAttributes(display, settings.handle, &window_attributes); XGetWindowAttributes(display, settings.handle, &windowAttributes);
//let GLX determine the best Visual to use for GL output; provide a few hints //let GLX determine the best Visual to use for GL output; provide a few hints
//note: some video drivers will override double buffering attribute //note: some video drivers will override double buffering attribute
@ -146,7 +149,7 @@ struct VideoGLX : Video, OpenGL {
None None
}; };
signed fbCount; signed fbCount = 0;
GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount); GLXFBConfig* fbConfig = glXChooseFBConfig(display, screen, attributeList, &fbCount);
if(fbCount == 0) return false; if(fbCount == 0) return false;
@ -161,7 +164,7 @@ struct VideoGLX : Video, OpenGL {
attributes.colormap = colormap; attributes.colormap = colormap;
attributes.border_pixel = 0; attributes.border_pixel = 0;
xwindow = XCreateWindow(display, /* parent = */ settings.handle, xwindow = XCreateWindow(display, /* parent = */ settings.handle,
/* x = */ 0, /* y = */ 0, window_attributes.width, window_attributes.height, /* x = */ 0, /* y = */ 0, windowAttributes.width, windowAttributes.height,
/* border_width = */ 0, vi->depth, InputOutput, vi->visual, /* border_width = */ 0, vi->depth, InputOutput, vi->visual,
CWColormap | CWBorderPixel, &attributes); CWColormap | CWBorderPixel, &attributes);
XSetWindowBackground(display, xwindow, /* color = */ 0); XSetWindowBackground(display, xwindow, /* color = */ 0);
@ -177,27 +180,39 @@ struct VideoGLX : Video, OpenGL {
glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE); glxcontext = glXCreateContext(display, vi, /* sharelist = */ 0, /* direct = */ GL_TRUE);
glXMakeCurrent(display, glxwindow = xwindow, glxcontext); glXMakeCurrent(display, glxwindow = xwindow, glxcontext);
glXCreateContextAttribs = (GLXContext (*)(Display*, GLXFBConfig, GLXContext, signed, const signed*))glGetProcAddress("glXCreateContextAttribsARB"); //glXSwapInterval is used to toggle Vsync
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI"); //note that the ordering is very important! MESA declares SGI, but the SGI function does nothing
glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalEXT");
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA"); if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalMESA");
if(!glXSwapInterval) glXSwapInterval = (signed (*)(signed))glGetProcAddress("glXSwapIntervalSGI");
if(glXCreateContextAttribs) { if(auto glXCreateContextAttribs = (auto (*)(Display*, GLXFBConfig, GLXContext, signed, const signed*) -> GLXContext)glGetProcAddress("glXCreateContextAttribsARB")) {
signed attributes[] = { signed attributes[] = {
GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2, GLX_CONTEXT_MINOR_VERSION_ARB, 2,
None None
}; };
GLXContext context = glXCreateContextAttribs(display, fbConfig[0], nullptr, true, attributes);
//glXCreateContextAttribs tends to throw BadRequest errors instead of simply failing gracefully
auto originalHandler = XSetErrorHandler(VideoGLX_X11ErrorHandler);
auto context = glXCreateContextAttribs(display, fbConfig[0], nullptr, true, attributes);
XSync(display, False);
XSetErrorHandler(originalHandler);
if(context) { if(context) {
glXMakeCurrent(display, 0, nullptr); glXMakeCurrent(display, 0, nullptr);
glXDestroyContext(display, glxcontext); glXDestroyContext(display, glxcontext);
glXMakeCurrent(display, glxwindow, glxcontext = context); glXMakeCurrent(display, glxwindow, glxcontext = context);
} else {
//OpenGL 3.2+ not supported (most likely OpenGL 2.x)
return false;
} }
} else {
//missing required glXCreateContextAtribs function
return false;
} }
if(glXSwapInterval) { if(glXSwapInterval) glXSwapInterval(settings.synchronize);
glXSwapInterval(settings.synchronize);
}
//read attributes of frame buffer for later use, as requested attributes from above are not always granted //read attributes of frame buffer for later use, as requested attributes from above are not always granted
signed value = 0; signed value = 0;

View File

@ -0,0 +1,21 @@
#version 150
#define distortion 0.2
uniform sampler2D source[];
uniform vec4 sourceSize[];
in Vertex {
vec2 texCoord;
};
out vec4 fragColor;
vec2 radialDistortion(vec2 coord) {
vec2 cc = coord - vec2(0.5);
float dist = dot(cc, cc) * distortion;
return coord + cc * (1.0 - dist) * dist;
}
void main() {
fragColor = texture(source[0], radialDistortion(texCoord));
}

View File

@ -0,0 +1,4 @@
program
filter: linear
wrap: border
fragment: curvature.fs

View File

@ -0,0 +1,25 @@
#version 150
uniform sampler2D source[];
uniform vec4 sourceSize[];
in Vertex {
vec2 texCoord;
};
out vec4 fragColor;
vec3 grayscale(vec3 color) {
return vec3(dot(color, vec3(0.3, 0.59, 0.11)));
}
void main() {
vec2 offset = fract(texCoord * sourceSize[0].xy) - 0.5;
offset /= sourceSize[0].xy;
vec3 cx = texture(source[0], texCoord - offset).xyz;
vec3 cy = texture(source[0], texCoord).xyz;
vec3 cz = vec3(5.0 * grayscale(abs(cx - cy)));
fragColor = vec4(clamp(cz, 0.0, 1.0), 1.0);
}

View File

@ -0,0 +1,4 @@
program
filter: linear
wrap: edge
fragment: edge-detection.fs

5
shaders/Makefile Normal file
View File

@ -0,0 +1,5 @@
install:
if [ -d /usr/share/higan/Video\ Shaders ]; then sudo rm -r /usr/share/higan/Video\ Shaders; fi
sudo mkdir -p /usr/share/higan/Video\ Shaders
sudo cp -r *.shader /usr/share/higan/Video\ Shaders
sudo chmod -R 777 /usr/share/higan/Video\ Shaders

View File

@ -0,0 +1,4 @@
program
filter: linear
wrap: border
fragment: scanline.fs

View File

@ -0,0 +1,20 @@
#version 150
uniform sampler2D source[];
in Vertex {
vec2 texCoord;
};
out vec4 fragColor;
void main() {
vec4 rgba = texture(source[0], texCoord);
vec4 intensity;
if(fract(gl_FragCoord.y * (0.5 * 4.0 / 3.0)) > 0.5) {
intensity = vec4(0);
} else {
intensity = smoothstep(0.2, 0.8, rgba) + normalize(rgba);
}
fragColor = intensity * -0.25 + rgba * 1.1;
}

View File

@ -15,6 +15,7 @@ ConfigurationManager::ConfigurationManager() {
video.append(video.scale, "Scale"); video.append(video.scale, "Scale");
video.append(video.aspectCorrection, "AspectCorrection"); video.append(video.aspectCorrection, "AspectCorrection");
video.append(video.filter, "Filter"); video.append(video.filter, "Filter");
video.append(video.shader, "Shader");
video.append(video.colorEmulation, "ColorEmulation"); video.append(video.colorEmulation, "ColorEmulation");
video.append(video.saturation, "Saturation"); video.append(video.saturation, "Saturation");
video.append(video.gamma, "Gamma"); video.append(video.gamma, "Gamma");

View File

@ -16,6 +16,7 @@ struct ConfigurationManager : Configuration::Document {
string scale = "Small"; string scale = "Small";
bool aspectCorrection = true; bool aspectCorrection = true;
string filter = "Blur"; string filter = "Blur";
string shader = "None";
bool colorEmulation = true; bool colorEmulation = true;
unsigned saturation = 100; unsigned saturation = 100;
unsigned gamma = 100; unsigned gamma = 100;

View File

@ -62,6 +62,7 @@ Presentation::Presentation() {
maskOverscan.setText("Mask Overscan").setChecked(config->video.overscan.mask).onToggle([&] { maskOverscan.setText("Mask Overscan").setChecked(config->video.overscan.mask).onToggle([&] {
config->video.overscan.mask = maskOverscan.checked(); config->video.overscan.mask = maskOverscan.checked();
}); });
loadShaders();
synchronizeVideo.setText("Synchronize Video").setChecked(config->video.synchronize).onToggle([&] { synchronizeVideo.setText("Synchronize Video").setChecked(config->video.synchronize).onToggle([&] {
config->video.synchronize = synchronizeVideo.checked(); config->video.synchronize = synchronizeVideo.checked();
video->set(Video::Synchronize, config->video.synchronize); video->set(Video::Synchronize, config->video.synchronize);
@ -136,56 +137,35 @@ auto Presentation::updateEmulator() -> void {
} }
auto Presentation::resizeViewport() -> void { auto Presentation::resizeViewport() -> void {
signed scale = 1; signed width = emulator ? emulator->information.width : 256;
signed 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() && !config->video.aspectCorrection) stretch = 1.0;
}
signed scale = 2;
if(config->video.scale == "Small" ) scale = 2; if(config->video.scale == "Small" ) scale = 2;
if(config->video.scale == "Medium") scale = 3; if(config->video.scale == "Medium") scale = 3;
if(config->video.scale == "Large" ) scale = 4; if(config->video.scale == "Large" ) scale = 4;
signed width = 256; signed windowWidth = 0, windowHeight = 0;
signed height = 240; if(!fullScreen()) {
if(emulator) { windowWidth = 256 * scale * (config->video.aspectCorrection ? 8.0 / 7.0 : 1.0);
width = emulator->information.width; windowHeight = 240 * scale;
height = emulator->information.height;
}
bool arc = config->video.aspectCorrection;
if(fullScreen() == false) {
signed windowWidth = 256 * scale;
signed windowHeight = 240 * scale;
if(arc) windowWidth = windowWidth * 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({windowWidth, windowHeight});
viewport.setGeometry({(windowWidth - width) / 2, (windowHeight - height) / 2, width, height});
} else { } else {
signed windowWidth = geometry().width(); windowWidth = geometry().width();
signed windowHeight = geometry().height(); windowHeight = geometry().height();
//aspect ratio correction is always enabled in fullscreen mode
//note that below algorithm yields 7:6 ratio on 2560x(1440,1600) monitors
//this is extremely close to the optimum 8:7 ratio
//it is used so that linear interpolation isn't required
//todo: we should handle other resolutions nicely as well
unsigned multiplier = windowHeight / height;
width *= 1 + multiplier;
height *= multiplier;
signed x = (windowWidth - width) / 2;
signed y = (windowHeight - height) / 2;
if(x < 0) x = 0;
if(y < 0) y = 0;
if(width > windowWidth) width = windowWidth;
if(height > windowHeight) height = windowHeight;
viewport.setGeometry({x, y, width, height});
} }
signed multiplier = min(windowWidth / (signed)(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) drawSplashScreen();
} }
@ -221,3 +201,34 @@ auto Presentation::drawSplashScreen() -> void {
video->refresh(); video->refresh();
} }
} }
auto Presentation::loadShaders() -> void {
if(config->video.driver != "OpenGL") {
videoShaderMenu.setVisible(false);
return;
}
auto pathname = locate({localpath(), "tomoko/"}, "Video Shaders/");
for(auto shader : directory::folders(pathname, "*.shader")) {
MenuRadioItem item{&videoShaderMenu};
item.setText(string{shader}.rtrim(".shader/", 1L)).onActivate([=] {
config->video.shader = {pathname, shader};
program->updateVideoFilter();
});
videoShaders.append(item);
}
videoShaderMenu.setText("Video Shaders");
videoShaderNone.setChecked().setText("None").onActivate([=] {
config->video.shader = "None";
program->updateVideoFilter();
});
for(auto object : videoShaders.objects()) {
if(auto radioItem = dynamic_cast<mMenuRadioItem*>(object.data())) {
if(config->video.shader == string{pathname, radioItem->text(), ".shader/"}) {
radioItem->setChecked();
}
}
}
}

View File

@ -4,6 +4,7 @@ struct Presentation : Window {
auto resizeViewport() -> void; auto resizeViewport() -> void;
auto toggleFullScreen() -> void; auto toggleFullScreen() -> void;
auto drawSplashScreen() -> void; auto drawSplashScreen() -> void;
auto loadShaders() -> void;
MenuBar menuBar{this}; MenuBar menuBar{this};
Menu libraryMenu{&menuBar}; Menu libraryMenu{&menuBar};
@ -31,6 +32,9 @@ struct Presentation : Window {
MenuSeparator videoFilterSeparator{&videoFilterMenu}; MenuSeparator videoFilterSeparator{&videoFilterMenu};
MenuCheckItem colorEmulation{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu};
MenuCheckItem maskOverscan{&videoFilterMenu}; MenuCheckItem maskOverscan{&videoFilterMenu};
Menu videoShaderMenu{&settingsMenu};
MenuRadioItem videoShaderNone{&videoShaderMenu};
Group videoShaders{&videoShaderNone};
MenuSeparator settingsMenuSeparator1{&settingsMenu}; MenuSeparator settingsMenuSeparator1{&settingsMenu};
MenuCheckItem synchronizeVideo{&settingsMenu}; MenuCheckItem synchronizeVideo{&settingsMenu};
MenuCheckItem synchronizeAudio{&settingsMenu}; MenuCheckItem synchronizeAudio{&settingsMenu};

View File

@ -36,8 +36,13 @@ auto Program::updateStatusText() -> void {
} }
auto Program::updateVideoFilter() -> void { auto Program::updateVideoFilter() -> void {
if(config->video.filter == "None") video->set(Video::Filter, Video::FilterNearest); if(config->video.driver == "OpenGL" && config->video.shader != "None" && directory::exists(config->video.shader)) {
if(config->video.filter == "Blur") video->set(Video::Filter, Video::FilterLinear); video->set(Video::Filter, Video::FilterNearest);
video->set(Video::Shader, (string)config->video.shader);
} else {
video->set(Video::Filter, config->video.filter == "Blur" ? Video::FilterLinear : Video::FilterNearest);
video->set(Video::Shader, (string)"");
}
} }
auto Program::updateVideoPalette() -> void { auto Program::updateVideoPalette() -> void {