Qt port.
|
@ -41,7 +41,7 @@ void S9xLandSamples (void);
|
||||||
void S9xClearSamples (void);
|
void S9xClearSamples (void);
|
||||||
bool8 S9xMixSamples (uint8 *, int);
|
bool8 S9xMixSamples (uint8 *, int);
|
||||||
void S9xSetSamplesAvailableCallback (apu_callback, void *);
|
void S9xSetSamplesAvailableCallback (apu_callback, void *);
|
||||||
void S9xUpdateDynamicRate (int, int);
|
void S9xUpdateDynamicRate (int empty = 1, int buffer_size = 2);
|
||||||
|
|
||||||
#define DSP_INTERPOLATION_NONE 0
|
#define DSP_INTERPOLATION_NONE 0
|
||||||
#define DSP_INTERPOLATION_LINEAR 1
|
#define DSP_INTERPOLATION_LINEAR 1
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*****************************************************************************\
|
||||||
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
||||||
|
This file is licensed under the Snes9x License.
|
||||||
|
For further information, consult the LICENSE file in the root directory.
|
||||||
|
\*****************************************************************************/
|
||||||
|
|
||||||
|
#include "s9x_sound_driver_cubeb.hpp"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
void S9xCubebSoundDriver::write_samples(int16_t *data, int samples)
|
||||||
|
{
|
||||||
|
if (samples > buffer.space_empty())
|
||||||
|
samples = buffer.space_empty();
|
||||||
|
buffer.push(data, samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
S9xCubebSoundDriver::S9xCubebSoundDriver()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
S9xCubebSoundDriver::~S9xCubebSoundDriver()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xCubebSoundDriver::init()
|
||||||
|
{
|
||||||
|
if (!context)
|
||||||
|
cubeb_init(&context, "Snes9x", nullptr);
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xCubebSoundDriver::deinit()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
if (stream)
|
||||||
|
{
|
||||||
|
cubeb_stream_destroy(stream);
|
||||||
|
stream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context)
|
||||||
|
{
|
||||||
|
cubeb_destroy(context);
|
||||||
|
context = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xCubebSoundDriver::start()
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
cubeb_stream_start(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xCubebSoundDriver::stop()
|
||||||
|
{
|
||||||
|
if (stream)
|
||||||
|
cubeb_stream_stop(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void state_callback(cubeb_stream *stream, void *user_ptr, cubeb_state state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
long data_callback(cubeb_stream *stream, void *user_ptr,
|
||||||
|
void const *input_buffer,
|
||||||
|
void *output_buffer, long nframes)
|
||||||
|
{
|
||||||
|
return ((S9xCubebSoundDriver *)user_ptr)->data_callback(stream, input_buffer, output_buffer, nframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
long S9xCubebSoundDriver::data_callback(cubeb_stream *stream, void const *input_buffer, void *output_buffer, long nframes)
|
||||||
|
{
|
||||||
|
buffer.read((int16_t *)output_buffer, nframes * 2);
|
||||||
|
return nframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool S9xCubebSoundDriver::open_device(int playback_rate, int buffer_size)
|
||||||
|
{
|
||||||
|
cubeb_stream_params params{};
|
||||||
|
params.channels = 2;
|
||||||
|
params.format = CUBEB_SAMPLE_S16LE;
|
||||||
|
params.layout = CUBEB_LAYOUT_UNDEFINED;
|
||||||
|
params.rate = playback_rate;
|
||||||
|
params.prefs = CUBEB_STREAM_PREF_NONE;
|
||||||
|
|
||||||
|
uint32_t min_latency;
|
||||||
|
cubeb_get_min_latency(context, ¶ms, &min_latency);
|
||||||
|
|
||||||
|
auto retval = cubeb_stream_init(context, &stream, "Snes9x",
|
||||||
|
nullptr, nullptr,
|
||||||
|
nullptr, ¶ms,
|
||||||
|
min_latency,
|
||||||
|
&::data_callback,
|
||||||
|
&state_callback,
|
||||||
|
this);
|
||||||
|
|
||||||
|
if (retval != CUBEB_OK)
|
||||||
|
{
|
||||||
|
printf("Failed to start stream. Error: %d!\n", retval);
|
||||||
|
stream = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.resize(2 * buffer_size * playback_rate / 1000);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int S9xCubebSoundDriver::space_free()
|
||||||
|
{
|
||||||
|
return buffer.space_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> S9xCubebSoundDriver::buffer_level()
|
||||||
|
{
|
||||||
|
return { buffer.space_empty(), buffer.buffer_size };
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*****************************************************************************\
|
||||||
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
||||||
|
This file is licensed under the Snes9x License.
|
||||||
|
For further information, consult the LICENSE file in the root directory.
|
||||||
|
\*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __S9X_SOUND_DRIVER_CUBEB_HPP
|
||||||
|
#define __S9X_SOUND_DRIVER_CUBEB_HPP
|
||||||
|
|
||||||
|
#include "s9x_sound_driver.hpp"
|
||||||
|
#include <cstdint>
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "../../apu/resampler.h"
|
||||||
|
|
||||||
|
class S9xCubebSoundDriver : public S9xSoundDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
S9xCubebSoundDriver();
|
||||||
|
~S9xCubebSoundDriver();
|
||||||
|
void init() override;
|
||||||
|
void deinit() override;
|
||||||
|
bool open_device(int playback_rate, int buffer_size) override;
|
||||||
|
void start() override;
|
||||||
|
void stop() override;
|
||||||
|
long data_callback(cubeb_stream *stream, void const *input_buffer, void *output_buffer, long nframes);
|
||||||
|
void write_samples(int16_t *data, int samples) override;
|
||||||
|
int space_free() override;
|
||||||
|
std::pair<int, int> buffer_level() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Resampler buffer;
|
||||||
|
cubeb *context = nullptr;
|
||||||
|
cubeb_stream *stream = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* __S9X_SOUND_DRIVER_SDL_HPP */
|
|
@ -15,6 +15,11 @@ S9xPortAudioSoundDriver::S9xPortAudioSoundDriver()
|
||||||
audio_stream = NULL;
|
audio_stream = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
S9xPortAudioSoundDriver::~S9xPortAudioSoundDriver()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
void S9xPortAudioSoundDriver::init()
|
void S9xPortAudioSoundDriver::init()
|
||||||
{
|
{
|
||||||
PaError err;
|
PaError err;
|
||||||
|
@ -60,9 +65,64 @@ void S9xPortAudioSoundDriver::stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool S9xPortAudioSoundDriver::tryHostAPI(int index)
|
||||||
|
{
|
||||||
|
auto hostapi_info = Pa_GetHostApiInfo(index);
|
||||||
|
if (!hostapi_info)
|
||||||
|
{
|
||||||
|
printf("Host API #%d has no info\n", index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
printf("Attempting API: %s\n", hostapi_info->name);
|
||||||
|
|
||||||
|
auto device_info = Pa_GetDeviceInfo(hostapi_info->defaultOutputDevice);
|
||||||
|
if (!device_info)
|
||||||
|
{
|
||||||
|
printf("(%s)...No device info available.\n", hostapi_info->name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaStreamParameters param{};
|
||||||
|
param.device = hostapi_info->defaultOutputDevice;
|
||||||
|
param.suggestedLatency = buffer_size_ms * 0.001;
|
||||||
|
param.channelCount = 2;
|
||||||
|
param.sampleFormat = paInt16;
|
||||||
|
param.hostApiSpecificStreamInfo = NULL;
|
||||||
|
|
||||||
|
printf("(%s : %s, latency %dms)...\n",
|
||||||
|
hostapi_info->name,
|
||||||
|
device_info->name,
|
||||||
|
(int)(param.suggestedLatency * 1000.0));
|
||||||
|
|
||||||
|
auto err = Pa_OpenStream(&audio_stream,
|
||||||
|
NULL,
|
||||||
|
¶m,
|
||||||
|
playback_rate,
|
||||||
|
0,
|
||||||
|
paNoFlag,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
int frames = playback_rate * buffer_size_ms / 1000;
|
||||||
|
//int frames = Pa_GetStreamWriteAvailable(audio_stream);
|
||||||
|
printf("PortAudio set buffer size to %d frames.\n", frames);
|
||||||
|
output_buffer_size = frames;
|
||||||
|
|
||||||
|
if (err == paNoError)
|
||||||
|
{
|
||||||
|
printf("OK\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Failed (%s)\n", Pa_GetErrorText(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool S9xPortAudioSoundDriver::open_device(int playback_rate, int buffer_size_ms)
|
bool S9xPortAudioSoundDriver::open_device(int playback_rate, int buffer_size_ms)
|
||||||
{
|
{
|
||||||
PaStreamParameters param;
|
|
||||||
const PaDeviceInfo *device_info;
|
const PaDeviceInfo *device_info;
|
||||||
const PaHostApiInfo *hostapi_info;
|
const PaHostApiInfo *hostapi_info;
|
||||||
PaError err = paNoError;
|
PaError err = paNoError;
|
||||||
|
@ -74,84 +134,31 @@ bool S9xPortAudioSoundDriver::open_device(int playback_rate, int buffer_size_ms)
|
||||||
|
|
||||||
if (err != paNoError)
|
if (err != paNoError)
|
||||||
{
|
{
|
||||||
fprintf(stderr,
|
fprintf(stderr, "Couldn't reset audio stream.\nError: %s\n", Pa_GetErrorText(err));
|
||||||
"Couldn't reset audio stream.\nError: %s\n",
|
|
||||||
Pa_GetErrorText(err));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
audio_stream = NULL;
|
audio_stream = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
param.channelCount = 2;
|
this->playback_rate = playback_rate;
|
||||||
param.sampleFormat = paInt16;
|
this->buffer_size_ms = buffer_size_ms;
|
||||||
param.hostApiSpecificStreamInfo = NULL;
|
|
||||||
|
|
||||||
printf("PortAudio sound driver initializing...\n");
|
printf("PortAudio sound driver initializing...\n");
|
||||||
|
|
||||||
|
int host = 2; //Pa_GetDefaultHostApi();
|
||||||
|
if (tryHostAPI(host))
|
||||||
|
return true;
|
||||||
|
|
||||||
for (int i = 0; i < Pa_GetHostApiCount(); i++)
|
for (int i = 0; i < Pa_GetHostApiCount(); i++)
|
||||||
{
|
{
|
||||||
printf(" --> ");
|
if (Pa_GetDefaultHostApi() != i)
|
||||||
|
if (tryHostAPI(i))
|
||||||
hostapi_info = Pa_GetHostApiInfo(i);
|
|
||||||
if (!hostapi_info)
|
|
||||||
{
|
|
||||||
printf("Host API #%d has no info\n", i);
|
|
||||||
err = paNotInitialized;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
device_info = Pa_GetDeviceInfo(hostapi_info->defaultOutputDevice);
|
|
||||||
if (!device_info)
|
|
||||||
{
|
|
||||||
printf("(%s)...No device info available.\n", hostapi_info->name);
|
|
||||||
err = paNotInitialized;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
param.device = hostapi_info->defaultOutputDevice;
|
|
||||||
param.suggestedLatency = buffer_size_ms * 0.001;
|
|
||||||
|
|
||||||
printf("(%s : %s, latency %dms)...\n",
|
|
||||||
hostapi_info->name,
|
|
||||||
device_info->name,
|
|
||||||
(int)(param.suggestedLatency * 1000.0));
|
|
||||||
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
err = Pa_OpenStream(&audio_stream,
|
|
||||||
NULL,
|
|
||||||
¶m,
|
|
||||||
playback_rate,
|
|
||||||
0,
|
|
||||||
paNoFlag,
|
|
||||||
NULL,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
int frames = Pa_GetStreamWriteAvailable(audio_stream);
|
|
||||||
printf ("PortAudio set buffer size to %d frames.\n", frames);
|
|
||||||
output_buffer_size = frames;
|
|
||||||
|
|
||||||
if (err == paNoError)
|
|
||||||
{
|
|
||||||
printf("OK\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("Failed (%s)\n",
|
|
||||||
Pa_GetErrorText(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err != paNoError || Pa_GetHostApiCount() < 1)
|
|
||||||
{
|
|
||||||
fprintf(stderr,
|
|
||||||
"Couldn't initialize sound\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Couldn't initialize sound\n");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int S9xPortAudioSoundDriver::space_free()
|
int S9xPortAudioSoundDriver::space_free()
|
||||||
|
|
|
@ -16,6 +16,7 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
S9xPortAudioSoundDriver();
|
S9xPortAudioSoundDriver();
|
||||||
|
~S9xPortAudioSoundDriver();
|
||||||
void init() override;
|
void init() override;
|
||||||
void deinit() override;
|
void deinit() override;
|
||||||
bool open_device(int playback_rate, int buffer_size) override;
|
bool open_device(int playback_rate, int buffer_size) override;
|
||||||
|
@ -25,9 +26,12 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
|
||||||
int space_free() override;
|
int space_free() override;
|
||||||
std::pair<int, int> buffer_level() override;
|
std::pair<int, int> buffer_level() override;
|
||||||
void samples_available();
|
void samples_available();
|
||||||
|
bool tryHostAPI(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PaStream *audio_stream;
|
PaStream *audio_stream;
|
||||||
|
int playback_rate;
|
||||||
|
int buffer_size_ms;
|
||||||
int output_buffer_size;
|
int output_buffer_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,11 @@ S9xSDLSoundDriver::S9xSDLSoundDriver()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
S9xSDLSoundDriver::~S9xSDLSoundDriver()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
void S9xSDLSoundDriver::init()
|
void S9xSDLSoundDriver::init()
|
||||||
{
|
{
|
||||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||||
|
|
|
@ -22,6 +22,7 @@ class S9xSDLSoundDriver : public S9xSoundDriver
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
S9xSDLSoundDriver();
|
S9xSDLSoundDriver();
|
||||||
|
~S9xSDLSoundDriver();
|
||||||
void init() override;
|
void init() override;
|
||||||
void deinit() override;
|
void deinit() override;
|
||||||
bool open_device(int playback_rate, int buffer_size) override;
|
bool open_device(int playback_rate, int buffer_size) override;
|
||||||
|
|
|
@ -94,6 +94,8 @@ bool GTKGLXContext::create_context()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resize();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ bool WaylandEGLContext::create_context()
|
||||||
EGL_RED_SIZE, 8,
|
EGL_RED_SIZE, 8,
|
||||||
EGL_BLUE_SIZE, 8,
|
EGL_BLUE_SIZE, 8,
|
||||||
EGL_GREEN_SIZE, 8,
|
EGL_GREEN_SIZE, 8,
|
||||||
|
EGL_ALPHA_SIZE, 0,
|
||||||
EGL_NONE
|
EGL_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -158,6 +158,7 @@ void WaylandSurface::resize(Metrics m)
|
||||||
{
|
{
|
||||||
metrics = m;
|
metrics = m;
|
||||||
auto [w, h] = get_size();
|
auto [w, h] = get_size();
|
||||||
|
|
||||||
wl_subsurface_set_position(subsurface, m.x, m.y);
|
wl_subsurface_set_position(subsurface, m.x, m.y);
|
||||||
|
|
||||||
if (!viewport)
|
if (!viewport)
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*****************************************************************************\
|
||||||
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
||||||
|
This file is licensed under the Snes9x License.
|
||||||
|
For further information, consult the LICENSE file in the root directory.
|
||||||
|
\*****************************************************************************/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#include "wgl_context.hpp"
|
||||||
|
|
||||||
|
WGLContext::WGLContext()
|
||||||
|
{
|
||||||
|
hwnd = nullptr;
|
||||||
|
hdc = nullptr;
|
||||||
|
hglrc = nullptr;
|
||||||
|
version_major = -1;
|
||||||
|
version_minor = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
WGLContext::~WGLContext()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WGLContext::deinit()
|
||||||
|
{
|
||||||
|
if (wglMakeCurrent)
|
||||||
|
wglMakeCurrent(nullptr, nullptr);
|
||||||
|
if (hglrc)
|
||||||
|
wglDeleteContext(hglrc);
|
||||||
|
if (hdc)
|
||||||
|
ReleaseDC(hwnd, hdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WGLContext::attach(HWND target)
|
||||||
|
{
|
||||||
|
hwnd = target;
|
||||||
|
hdc = GetDC(hwnd);
|
||||||
|
|
||||||
|
PIXELFORMATDESCRIPTOR pfd{};
|
||||||
|
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
|
||||||
|
pfd.nVersion = 1;
|
||||||
|
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||||
|
pfd.iPixelType = PFD_TYPE_RGBA;
|
||||||
|
pfd.cColorBits = 32;
|
||||||
|
pfd.cDepthBits = 16;
|
||||||
|
pfd.iLayerType = PFD_MAIN_PLANE;
|
||||||
|
|
||||||
|
auto pfdIndex = ChoosePixelFormat(hdc, &pfd);
|
||||||
|
if (!pfdIndex)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!SetPixelFormat(hdc, pfdIndex, &pfd))
|
||||||
|
{
|
||||||
|
// Shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WGLContext::create_context()
|
||||||
|
{
|
||||||
|
hglrc = wglCreateContext(hdc);
|
||||||
|
if (!hglrc)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!wglMakeCurrent(hdc, hglrc))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
gladLoaderLoadWGL(hdc);
|
||||||
|
|
||||||
|
resize();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WGLContext::resize()
|
||||||
|
{
|
||||||
|
RECT rect;
|
||||||
|
GetClientRect(hwnd, &rect);
|
||||||
|
this->width = rect.right - rect.left;
|
||||||
|
this->height = rect.bottom - rect.top;
|
||||||
|
make_current();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WGLContext::swap_buffers()
|
||||||
|
{
|
||||||
|
SwapBuffers(hdc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WGLContext::ready()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WGLContext::make_current()
|
||||||
|
{
|
||||||
|
wglMakeCurrent(hdc, hglrc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WGLContext::swap_interval(int frames)
|
||||||
|
{
|
||||||
|
wglSwapIntervalEXT(frames);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*****************************************************************************\
|
||||||
|
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
|
||||||
|
This file is licensed under the Snes9x License.
|
||||||
|
For further information, consult the LICENSE file in the root directory.
|
||||||
|
\*****************************************************************************/
|
||||||
|
|
||||||
|
#ifndef __WGL_CONTEXT_HPP
|
||||||
|
#define __WGL_CONTEXT_HPP
|
||||||
|
|
||||||
|
#include "opengl_context.hpp"
|
||||||
|
|
||||||
|
#include <glad/wgl.h>
|
||||||
|
|
||||||
|
class WGLContext : public OpenGLContext
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WGLContext();
|
||||||
|
~WGLContext();
|
||||||
|
bool attach(HWND xid);
|
||||||
|
bool create_context() override;
|
||||||
|
void resize() override;
|
||||||
|
void swap_buffers() override;
|
||||||
|
void swap_interval(int frames) override;
|
||||||
|
void make_current() override;
|
||||||
|
bool ready();
|
||||||
|
void deinit();
|
||||||
|
|
||||||
|
HWND hwnd;
|
||||||
|
HDC hdc;
|
||||||
|
HGLRC hglrc;
|
||||||
|
|
||||||
|
int version_major;
|
||||||
|
int version_minor;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -2,7 +2,7 @@
|
||||||
#define VULKAN_MEMORY_ALLOCATOR_HPP
|
#define VULKAN_MEMORY_ALLOCATOR_HPP
|
||||||
|
|
||||||
#if !defined(AMD_VULKAN_MEMORY_ALLOCATOR_H)
|
#if !defined(AMD_VULKAN_MEMORY_ALLOCATOR_H)
|
||||||
#include <vk_mem_alloc.h>
|
#include "vk_mem_alloc.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
|
@ -40,7 +40,7 @@ add_compile_definitions(HAVE_LIBPNG
|
||||||
SNES9XLOCALEDIR=\"${LOCALEDIR}\")
|
SNES9XLOCALEDIR=\"${LOCALEDIR}\")
|
||||||
set(INCLUDES ../apu/bapu ../ src)
|
set(INCLUDES ../apu/bapu ../ src)
|
||||||
set(SOURCES)
|
set(SOURCES)
|
||||||
set(ARGS -Wall -Wno-unused-parameter)
|
set(ARGS -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-nullability-completeness)
|
||||||
set(LIBS)
|
set(LIBS)
|
||||||
set(DEFINES)
|
set(DEFINES)
|
||||||
|
|
||||||
|
|
1
memmap.h
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
struct CMemory
|
struct CMemory
|
||||||
{
|
{
|
||||||
|
|
1
port.h
|
@ -32,7 +32,6 @@
|
||||||
#define RIGHTSHIFT_int16_IS_SAR
|
#define RIGHTSHIFT_int16_IS_SAR
|
||||||
#define RIGHTSHIFT_int32_IS_SAR
|
#define RIGHTSHIFT_int32_IS_SAR
|
||||||
#ifndef __LIBRETRO__
|
#ifndef __LIBRETRO__
|
||||||
#define SNES_JOY_READ_CALLBACKS
|
|
||||||
#endif //__LIBRETRO__
|
#endif //__LIBRETRO__
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
project(snes9x-qt VERSION 1.61)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_GLOBAL_AUTOGEN_TARGET ON)
|
||||||
|
|
||||||
|
set(DEFINES SNES9X_QT)
|
||||||
|
set(SNES9X_CORE_SOURCES
|
||||||
|
../fxinst.cpp
|
||||||
|
../fxemu.cpp
|
||||||
|
../fxdbg.cpp
|
||||||
|
../c4.cpp
|
||||||
|
../c4emu.cpp
|
||||||
|
../apu/apu.cpp
|
||||||
|
../apu/bapu/dsp/sdsp.cpp
|
||||||
|
../apu/bapu/smp/smp.cpp
|
||||||
|
../apu/bapu/smp/smp_state.cpp
|
||||||
|
../msu1.cpp
|
||||||
|
../msu1.h
|
||||||
|
../dsp.cpp
|
||||||
|
../dsp1.cpp
|
||||||
|
../dsp2.cpp
|
||||||
|
../dsp3.cpp
|
||||||
|
../dsp4.cpp
|
||||||
|
../spc7110.cpp
|
||||||
|
../obc1.cpp
|
||||||
|
../seta.cpp
|
||||||
|
../seta010.cpp
|
||||||
|
../seta011.cpp
|
||||||
|
../seta018.cpp
|
||||||
|
../controls.cpp
|
||||||
|
../crosshairs.cpp
|
||||||
|
../cpu.cpp
|
||||||
|
../sa1.cpp
|
||||||
|
../debug.cpp
|
||||||
|
../sdd1.cpp
|
||||||
|
../tile.cpp
|
||||||
|
../tileimpl-n1x1.cpp
|
||||||
|
../tileimpl-n2x1.cpp
|
||||||
|
../tileimpl-h2x1.cpp
|
||||||
|
../srtc.cpp
|
||||||
|
../gfx.cpp
|
||||||
|
../memmap.cpp
|
||||||
|
../clip.cpp
|
||||||
|
../ppu.cpp
|
||||||
|
../dma.cpp
|
||||||
|
../snes9x.cpp
|
||||||
|
../globals.cpp
|
||||||
|
../stream.cpp
|
||||||
|
../conffile.cpp
|
||||||
|
../bsx.cpp
|
||||||
|
../snapshot.cpp
|
||||||
|
../screenshot.cpp
|
||||||
|
../movie.cpp
|
||||||
|
../statemanager.cpp
|
||||||
|
../sha256.cpp
|
||||||
|
../bml.cpp
|
||||||
|
../cpuops.cpp
|
||||||
|
../cpuexec.cpp
|
||||||
|
../sa1cpu.cpp
|
||||||
|
../cheats.cpp
|
||||||
|
../cheats2.cpp
|
||||||
|
../sdd1emu.cpp
|
||||||
|
../netplay.cpp
|
||||||
|
../server.cpp
|
||||||
|
../loadzip.cpp
|
||||||
|
../fscompat.cpp)
|
||||||
|
add_library(snes9x-core ${SNES9X_CORE_SOURCES})
|
||||||
|
target_include_directories(snes9x-core PRIVATE ../)
|
||||||
|
target_compile_definitions(snes9x-core PRIVATE ZLIB HAVE_STDINT_H ALLOW_CPU_OVERCLOCK)
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Gui)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(SDL REQUIRED sdl2)
|
||||||
|
pkg_check_modules(ZLIB REQUIRED zlib)
|
||||||
|
pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0)
|
||||||
|
|
||||||
|
list(APPEND LIBS Qt6::Widgets Qt6::Gui ${SDL_LIBRARIES} ${ZLIB_LIBRARIES} ${PORTAUDIO_LIBRARIES})
|
||||||
|
list(APPEND INCLUDES ${SDL_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS} ${PORTAUDIO_INCLUDE_DIRS} ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
|
list(APPEND FLAGS ${SDL_COMPILE_FLAGS} ${ZLIB_COMPILE_FLAGS} ${PORTAUDIO_COMPILE_FLAGS})
|
||||||
|
|
||||||
|
set(QT_GUI_SOURCES
|
||||||
|
src/main.cpp
|
||||||
|
src/EmuApplication.cpp
|
||||||
|
src/EmuMainWindow.cpp
|
||||||
|
src/Snes9xController.cpp
|
||||||
|
src/EmuSettingsWindow.cpp
|
||||||
|
src/EmuConfig.cpp
|
||||||
|
src/EmuInputPanel.cpp
|
||||||
|
src/EmuBinding.cpp
|
||||||
|
src/EmuCanvas.cpp
|
||||||
|
src/BindingPanel.cpp
|
||||||
|
src/ControllerPanel.cpp
|
||||||
|
src/DisplayPanel.cpp
|
||||||
|
src/SoundPanel.cpp
|
||||||
|
src/EmulationPanel.cpp
|
||||||
|
src/ShortcutsPanel.cpp
|
||||||
|
src/GeneralPanel.cpp
|
||||||
|
src/FoldersPanel.cpp
|
||||||
|
src/SDLInputManager.cpp
|
||||||
|
src/ShaderParametersDialog.cpp
|
||||||
|
src/SoftwareScalers.cpp
|
||||||
|
src/EmuCanvasQt.cpp
|
||||||
|
src/EmuCanvasOpenGL.cpp
|
||||||
|
src/EmuCanvasVulkan.cpp
|
||||||
|
../external/glad/src/gl.c
|
||||||
|
../common/audio/s9x_sound_driver_sdl.cpp
|
||||||
|
../common/audio/s9x_sound_driver_sdl.hpp
|
||||||
|
../common/audio/s9x_sound_driver_portaudio.cpp
|
||||||
|
../common/audio/s9x_sound_driver_portaudio.hpp
|
||||||
|
../common/audio/s9x_sound_driver_cubeb.cpp
|
||||||
|
../common/audio/s9x_sound_driver_cubeb.hpp
|
||||||
|
../filter/2xsai.cpp
|
||||||
|
../filter/2xsai.h
|
||||||
|
../filter/epx.cpp
|
||||||
|
../filter/epx.h
|
||||||
|
../filter/snes_ntsc_config.h
|
||||||
|
../filter/snes_ntsc.h
|
||||||
|
../filter/snes_ntsc_impl.h
|
||||||
|
../filter/snes_ntsc.c)
|
||||||
|
|
||||||
|
if(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||||
|
pkg_check_modules(WAYLAND REQUIRED wayland-client wayland-egl)
|
||||||
|
|
||||||
|
include(FindX11)
|
||||||
|
if(NOT X11_FOUND)
|
||||||
|
error()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND INCLUDES ${WAYLAND_INCLUDE_DIRS} ${X11_INCLUDE_DIRS})
|
||||||
|
list(APPEND LIBS ${WAYLAND_LIBRARIES} ${X11_LIBRARIES})
|
||||||
|
list(APPEND FLAGS ${WAYLAND_CFLAGS})
|
||||||
|
|
||||||
|
set(PLATFORM_SOURCES
|
||||||
|
../common/video/glx_context.cpp
|
||||||
|
../common/video/wayland_egl_context.cpp
|
||||||
|
../common/video/wayland_surface.cpp
|
||||||
|
../common/video/fractional-scale-v1.c
|
||||||
|
../common/video/viewporter-client-protocol.c
|
||||||
|
../common/video/wayland-idle-inhibit-unstable-v1.c
|
||||||
|
../external/glad/src/glx.c
|
||||||
|
../external/glad/src/egl.c)
|
||||||
|
else()
|
||||||
|
set(PLATFORM_SOURCES
|
||||||
|
../common/video/wgl_context.cpp
|
||||||
|
../external/glad/src/wgl.c)
|
||||||
|
list(APPEND LIBS opengl32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(QT_UI_FILES
|
||||||
|
src/GeneralPanel.ui
|
||||||
|
src/ControllerPanel.ui
|
||||||
|
src/EmuSettingsWindow.ui
|
||||||
|
src/DisplayPanel.ui
|
||||||
|
src/SoundPanel.ui
|
||||||
|
src/EmulationPanel.ui
|
||||||
|
src/ShortcutsPanel.ui
|
||||||
|
src/FoldersPanel.ui)
|
||||||
|
|
||||||
|
set(USE_SANITIZERS CACHE BOOL OFF)
|
||||||
|
set(BUILD_TESTS CACHE BOOL OFF)
|
||||||
|
set(BUILD_TOOLS CACHE BOOL OFF)
|
||||||
|
add_subdirectory("../external/cubeb" "cubeb" EXCLUDE_FROM_ALL)
|
||||||
|
list(APPEND LIBS cubeb)
|
||||||
|
list(APPEND INCLUDES "../external/cubeb/include")
|
||||||
|
|
||||||
|
set(BUILD_TESTING CACHE BOOL OFF)
|
||||||
|
add_subdirectory("../external/glslang" "glslang" EXCLUDE_FROM_ALL)
|
||||||
|
list(APPEND LIBS
|
||||||
|
glslang
|
||||||
|
OGLCompiler
|
||||||
|
HLSL
|
||||||
|
OSDependent
|
||||||
|
SPIRV
|
||||||
|
glslang-default-resource-limits)
|
||||||
|
list(APPEND INCLUDES "../external/glslang")
|
||||||
|
|
||||||
|
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS CACHE BOOL ON)
|
||||||
|
add_subdirectory("../external/SPIRV-Cross" "SPIRV-Cross" EXCLUDE_FROM_ALL)
|
||||||
|
list(APPEND LIBS
|
||||||
|
spirv-cross-core
|
||||||
|
spirv-cross-glsl
|
||||||
|
spirv-cross-reflect
|
||||||
|
spirv-cross-cpp)
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||||
|
list(APPEND DEFINES "VK_USE_PLATFORM_WIN32_KHR")
|
||||||
|
else()
|
||||||
|
list(APPEND DEFINES
|
||||||
|
"VK_USE_PLATFORM_XLIB_KHR"
|
||||||
|
"VK_USE_PLATFORM_WAYLAND_KHR")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(APPEND DEFINES
|
||||||
|
"VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1"
|
||||||
|
"VMA_DYNAMIC_VULKAN_FUNCTIONS=1"
|
||||||
|
"VMA_STATIC_VULKAN_FUNCTIONS=0"
|
||||||
|
"USE_SLANG")
|
||||||
|
list(APPEND INCLUDES
|
||||||
|
../external/vulkan-headers/include
|
||||||
|
../external/VulkanMemoryAllocator-Hpp/include
|
||||||
|
../external/stb
|
||||||
|
"../external/glad/include")
|
||||||
|
list(APPEND SOURCES
|
||||||
|
../vulkan/slang_shader.cpp
|
||||||
|
../vulkan/slang_shader.hpp
|
||||||
|
../vulkan/slang_preset.cpp
|
||||||
|
../vulkan/slang_preset.hpp
|
||||||
|
../vulkan/vulkan_hpp_storage.cpp
|
||||||
|
../vulkan/vk_mem_alloc_implementation.cpp
|
||||||
|
../vulkan/vulkan_context.cpp
|
||||||
|
../vulkan/vulkan_context.hpp
|
||||||
|
../vulkan/vulkan_texture.cpp
|
||||||
|
../vulkan/vulkan_texture.hpp
|
||||||
|
../vulkan/vulkan_swapchain.cpp
|
||||||
|
../vulkan/vulkan_swapchain.hpp
|
||||||
|
../vulkan/vulkan_slang_pipeline.cpp
|
||||||
|
../vulkan/vulkan_slang_pipeline.hpp
|
||||||
|
../vulkan/vulkan_pipeline_image.cpp
|
||||||
|
../vulkan/vulkan_pipeline_image.hpp
|
||||||
|
../vulkan/vulkan_shader_chain.cpp
|
||||||
|
../vulkan/vulkan_shader_chain.hpp
|
||||||
|
../vulkan/vulkan_simple_output.hpp
|
||||||
|
../vulkan/vulkan_simple_output.cpp
|
||||||
|
../vulkan/std_chrono_throttle.cpp
|
||||||
|
../vulkan/std_chrono_throttle.hpp
|
||||||
|
../vulkan/slang_helpers.cpp
|
||||||
|
../vulkan/slang_helpers.hpp
|
||||||
|
../vulkan/slang_preset_ini.cpp
|
||||||
|
../vulkan/slang_preset_ini.hpp
|
||||||
|
../external/stb/stb_image_implementation.cpp
|
||||||
|
../shaders/glsl.cpp
|
||||||
|
../shaders/slang.cpp
|
||||||
|
../shaders/shader_helpers.cpp)
|
||||||
|
|
||||||
|
list(APPEND DEFINES "IMGUI_IMPL_VULKAN_NO_PROTOTYPES")
|
||||||
|
list(APPEND SOURCES ../external/imgui/imgui.cpp
|
||||||
|
../external/imgui/imgui_demo.cpp
|
||||||
|
../external/imgui/imgui_draw.cpp
|
||||||
|
../external/imgui/imgui_tables.cpp
|
||||||
|
../external/imgui/imgui_widgets.cpp
|
||||||
|
../external/imgui/imgui_impl_opengl3.cpp
|
||||||
|
../external/imgui/imgui_impl_vulkan.cpp
|
||||||
|
../external/imgui/snes9x_imgui.cpp)
|
||||||
|
list(APPEND INCLUDES ../external/imgui)
|
||||||
|
|
||||||
|
add_executable(snes9x-qt ${QT_GUI_SOURCES} ${SOURCES} ${PLATFORM_SOURCES} src/resources/snes9x.qrc)
|
||||||
|
target_link_libraries(snes9x-qt snes9x-core ${LIBS})
|
||||||
|
target_compile_definitions(snes9x-qt PRIVATE ${DEFINES})
|
||||||
|
target_compile_options(snes9x-qt PRIVATE ${FLAGS})
|
||||||
|
target_include_directories(snes9x-qt PRIVATE "../" ${INCLUDES})
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
#include "BindingPanel.hpp"
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
BindingPanel::BindingPanel(EmuApplication *app)
|
||||||
|
: app(app)
|
||||||
|
{
|
||||||
|
binding_table_widget = nullptr;
|
||||||
|
joypads_changed = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::setTableWidget(QTableWidget *bindingTableWidget, EmuBinding *binding, int width, int height)
|
||||||
|
{
|
||||||
|
keyboard_icon.addFile(":/icons/blackicons/key.svg");
|
||||||
|
joypad_icon.addFile(":/icons/blackicons/joypad.svg");
|
||||||
|
this->binding_table_widget = bindingTableWidget;
|
||||||
|
this->binding = binding;
|
||||||
|
table_width = width;
|
||||||
|
table_height = height;
|
||||||
|
|
||||||
|
connect(bindingTableWidget, &QTableWidget::cellActivated, [&](int row, int column) {
|
||||||
|
cellActivated(row, column);
|
||||||
|
});
|
||||||
|
connect(bindingTableWidget, &QTableWidget::cellPressed, [&](int row, int column) {
|
||||||
|
cellActivated(row, column);
|
||||||
|
});
|
||||||
|
|
||||||
|
fillTable();
|
||||||
|
cell_column = -1;
|
||||||
|
cell_row = -1;
|
||||||
|
awaiting_binding = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BindingPanel::~BindingPanel()
|
||||||
|
{
|
||||||
|
app->qtapp->removeEventFilter(this);
|
||||||
|
timer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::hideEvent(QHideEvent *event)
|
||||||
|
{
|
||||||
|
awaiting_binding = false;
|
||||||
|
setRedirectInput(false);
|
||||||
|
QWidget::hideEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::setRedirectInput(bool redirect)
|
||||||
|
{
|
||||||
|
if (redirect)
|
||||||
|
{
|
||||||
|
app->binding_callback = [&](EmuBinding b)
|
||||||
|
{
|
||||||
|
finalizeCurrentBinding(b);
|
||||||
|
};
|
||||||
|
|
||||||
|
app->joypads_changed_callback = [&] {
|
||||||
|
if (joypads_changed)
|
||||||
|
joypads_changed();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app->binding_callback = nullptr;
|
||||||
|
app->joypads_changed_callback = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::updateCellFromBinding(int row, int column)
|
||||||
|
{
|
||||||
|
EmuBinding &b = binding[row * table_width + column];
|
||||||
|
auto table_item = binding_table_widget->item(row, column);
|
||||||
|
if (!table_item)
|
||||||
|
{
|
||||||
|
table_item = new QTableWidgetItem();
|
||||||
|
binding_table_widget->setItem(row, column, table_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
table_item->setText(b.to_string().c_str());;
|
||||||
|
table_item->setIcon(b.type == EmuBinding::Keyboard ? keyboard_icon :
|
||||||
|
b.type == EmuBinding::Joystick ? joypad_icon :
|
||||||
|
QIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::fillTable()
|
||||||
|
{
|
||||||
|
for (int column = 0; column < table_width; column++)
|
||||||
|
for (int row = 0; row < table_height; row++)
|
||||||
|
updateCellFromBinding(row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::cellActivated(int row, int column)
|
||||||
|
{
|
||||||
|
if (awaiting_binding)
|
||||||
|
{
|
||||||
|
updateCellFromBinding(cell_row, cell_column);
|
||||||
|
}
|
||||||
|
cell_column = column;
|
||||||
|
cell_row = row;
|
||||||
|
|
||||||
|
auto table_item = binding_table_widget->item(row, column);
|
||||||
|
|
||||||
|
if (!table_item)
|
||||||
|
{
|
||||||
|
table_item = new QTableWidgetItem();
|
||||||
|
binding_table_widget->setItem(row, column, table_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
table_item->setText("...");
|
||||||
|
|
||||||
|
setRedirectInput(true);
|
||||||
|
awaiting_binding = true;
|
||||||
|
accept_return = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::finalizeCurrentBinding(EmuBinding b)
|
||||||
|
{
|
||||||
|
if (!awaiting_binding)
|
||||||
|
return;
|
||||||
|
auto &slot = binding[cell_row * this->table_width + cell_column];
|
||||||
|
slot = b;
|
||||||
|
if (b.type == EmuBinding::Keyboard && b.keycode == Qt::Key_Escape)
|
||||||
|
slot = {};
|
||||||
|
|
||||||
|
if (b.type == EmuBinding::Keyboard && b.keycode == Qt::Key_Return && !accept_return)
|
||||||
|
{
|
||||||
|
accept_return = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCellFromBinding(cell_row, cell_column);
|
||||||
|
setRedirectInput(false);
|
||||||
|
awaiting_binding = false;
|
||||||
|
app->updateBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindingPanel::onJoypadsChanged(std::function<void()> func)
|
||||||
|
{
|
||||||
|
joypads_changed = func;
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QtEvents>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
class BindingPanel : public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BindingPanel(EmuApplication *app);
|
||||||
|
~BindingPanel();
|
||||||
|
void setTableWidget(QTableWidget *bindingTableWidget, EmuBinding *binding, int width, int height);
|
||||||
|
void cellActivated(int row, int column);
|
||||||
|
void handleKeyPressEvent(QKeyEvent *event);
|
||||||
|
void updateCellFromBinding(int row, int column);
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void hideEvent(QHideEvent *event) override;
|
||||||
|
void fillTable();
|
||||||
|
void checkJoypadInput();
|
||||||
|
void finalizeCurrentBinding(EmuBinding b);
|
||||||
|
void setRedirectInput(bool redirect);
|
||||||
|
void onJoypadsChanged(std::function<void()> func);
|
||||||
|
|
||||||
|
bool awaiting_binding;
|
||||||
|
bool accept_return;
|
||||||
|
int table_width;
|
||||||
|
int table_height;
|
||||||
|
int cell_row;
|
||||||
|
int cell_column;
|
||||||
|
QIcon keyboard_icon;
|
||||||
|
QIcon joypad_icon;
|
||||||
|
std::unique_ptr<QTimer> timer;
|
||||||
|
EmuApplication *app;
|
||||||
|
QTableWidget *binding_table_widget;
|
||||||
|
EmuBinding *binding;
|
||||||
|
std::function<void()> joypads_changed;
|
||||||
|
};
|
|
@ -0,0 +1,153 @@
|
||||||
|
#include "ControllerPanel.hpp"
|
||||||
|
#include "SDLInputManager.hpp"
|
||||||
|
#include "SDL_gamecontroller.h"
|
||||||
|
#include <optional>
|
||||||
|
#include <QtEvents>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
ControllerPanel::ControllerPanel(EmuApplication *app)
|
||||||
|
: BindingPanel(app)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
QObject::connect(controllerComboBox, &QComboBox::currentIndexChanged, [&](int index) {
|
||||||
|
BindingPanel::binding = this->app->config->binding.controller[index].buttons;
|
||||||
|
fillTable();
|
||||||
|
awaiting_binding = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
BindingPanel::setTableWidget(tableWidget_controller,
|
||||||
|
app->config->binding.controller[0].buttons,
|
||||||
|
app->config->allowed_bindings,
|
||||||
|
app->config->num_controller_bindings);
|
||||||
|
|
||||||
|
auto action = edit_menu.addAction(QObject::tr("Clear Current Controller"));
|
||||||
|
action->connect(action, &QAction::triggered, [&](bool checked) {
|
||||||
|
clearCurrentController();
|
||||||
|
});
|
||||||
|
|
||||||
|
action = edit_menu.addAction(QObject::tr("Clear All Controllers"));
|
||||||
|
action->connect(action, &QAction::triggered, [&](bool checked) {
|
||||||
|
clearAllControllers();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto swap_menu = edit_menu.addMenu(QObject::tr("Swap With"));
|
||||||
|
for (auto i = 0; i < 5; i++)
|
||||||
|
{
|
||||||
|
action = swap_menu->addAction(QObject::tr("Controller %1").arg(i + 1));
|
||||||
|
action->connect(action, &QAction::triggered, [&, i](bool) {
|
||||||
|
auto current_index = controllerComboBox->currentIndex();
|
||||||
|
if (current_index == i)
|
||||||
|
return;
|
||||||
|
swapControllers(i, current_index);
|
||||||
|
fillTable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
editToolButton->setMenu(&edit_menu);
|
||||||
|
editToolButton->setPopupMode(QToolButton::InstantPopup);
|
||||||
|
|
||||||
|
recreateAutoAssignMenu();
|
||||||
|
onJoypadsChanged([&]{ recreateAutoAssignMenu(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::recreateAutoAssignMenu()
|
||||||
|
{
|
||||||
|
auto_assign_menu.clear();
|
||||||
|
auto controller_list = app->input_manager->getXInputControllers();
|
||||||
|
|
||||||
|
for (int i = 0; i < app->config->allowed_bindings; i++)
|
||||||
|
{
|
||||||
|
auto slot_menu = auto_assign_menu.addMenu(tr("Slot %1").arg(i));
|
||||||
|
auto default_keyboard = slot_menu->addAction(tr("Default Keyboard"));
|
||||||
|
default_keyboard->connect(default_keyboard, &QAction::triggered, [&, slot = i](bool) {
|
||||||
|
autoPopulateWithKeyboard(slot);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (auto c : controller_list)
|
||||||
|
{
|
||||||
|
auto controller_item = slot_menu->addAction(c.second.c_str());
|
||||||
|
controller_item->connect(controller_item, &QAction::triggered, [&, id = c.first, slot = i](bool) {
|
||||||
|
autoPopulateWithJoystick(id, slot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
autoAssignToolButton->setMenu(&auto_assign_menu);
|
||||||
|
autoAssignToolButton->setPopupMode(QToolButton::InstantPopup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::autoPopulateWithKeyboard(int slot)
|
||||||
|
{
|
||||||
|
auto &buttons = app->config->binding.controller[controllerComboBox->currentIndex()].buttons;
|
||||||
|
const char *button_list[] = { "Up", "Down", "Left", "Right", "d", "c", "s", "x", "z", "a", "Return", "Space" };
|
||||||
|
|
||||||
|
for (int i = 0; i < std::size(button_list); i++)
|
||||||
|
buttons[app->config->allowed_bindings * i + slot] = EmuBinding::keyboard(QKeySequence::fromString(button_list[i])[0].key());
|
||||||
|
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::autoPopulateWithJoystick(int joystick_id, int slot)
|
||||||
|
{
|
||||||
|
auto &device = app->input_manager->devices[joystick_id];
|
||||||
|
auto sdl_controller = device.controller;
|
||||||
|
auto &buttons = app->config->binding.controller[controllerComboBox->currentIndex()].buttons;
|
||||||
|
const SDL_GameControllerButton list[] = { SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||||
|
SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||||
|
SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||||
|
SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||||
|
// B, A and X, Y are inverted on XInput vs SNES
|
||||||
|
SDL_CONTROLLER_BUTTON_B,
|
||||||
|
SDL_CONTROLLER_BUTTON_A,
|
||||||
|
SDL_CONTROLLER_BUTTON_Y,
|
||||||
|
SDL_CONTROLLER_BUTTON_X,
|
||||||
|
SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||||
|
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||||
|
SDL_CONTROLLER_BUTTON_START,
|
||||||
|
SDL_CONTROLLER_BUTTON_BACK };
|
||||||
|
for (auto i = 0; i < std::size(list); i++)
|
||||||
|
{
|
||||||
|
auto sdl_binding = SDL_GameControllerGetBindForButton(sdl_controller, list[i]);
|
||||||
|
if (SDL_CONTROLLER_BINDTYPE_BUTTON == sdl_binding.bindType)
|
||||||
|
buttons[4 * i + slot] = EmuBinding::joystick_button(device.index, sdl_binding.value.button);
|
||||||
|
else if (SDL_CONTROLLER_BINDTYPE_HAT == sdl_binding.bindType)
|
||||||
|
buttons[4 * i + slot] = EmuBinding::joystick_hat(device.index, sdl_binding.value.hat.hat, sdl_binding.value.hat.hat_mask);
|
||||||
|
else if (SDL_CONTROLLER_BINDTYPE_AXIS == sdl_binding.bindType)
|
||||||
|
buttons[4 * i + slot] = EmuBinding::joystick_axis(device.index, sdl_binding.value.axis, sdl_binding.value.axis);
|
||||||
|
}
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::swapControllers(int first, int second)
|
||||||
|
{
|
||||||
|
auto &a = app->config->binding.controller[first].buttons;
|
||||||
|
auto &b = app->config->binding.controller[second].buttons;
|
||||||
|
|
||||||
|
int count = std::size(a);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
EmuBinding swap = b[i];
|
||||||
|
b[i] = a[i];
|
||||||
|
a[i] = swap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::clearCurrentController()
|
||||||
|
{
|
||||||
|
auto &c = app->config->binding.controller[controllerComboBox->currentIndex()];
|
||||||
|
for (auto &b : c.buttons)
|
||||||
|
b = {};
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControllerPanel::clearAllControllers()
|
||||||
|
{
|
||||||
|
for (auto &c : app->config->binding.controller)
|
||||||
|
for (auto &b : c.buttons)
|
||||||
|
b = {};
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerPanel::~ControllerPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_ControllerPanel.h"
|
||||||
|
#include "BindingPanel.hpp"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
class ControllerPanel :
|
||||||
|
public Ui::ControllerPanel,
|
||||||
|
public BindingPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ControllerPanel(EmuApplication *app);
|
||||||
|
~ControllerPanel();
|
||||||
|
void clearAllControllers();
|
||||||
|
void clearCurrentController();
|
||||||
|
void autoPopulateWithKeyboard(int slot);
|
||||||
|
void autoPopulateWithJoystick(int joystick_id, int slot);
|
||||||
|
void swapControllers(int first, int second);
|
||||||
|
void recreateAutoAssignMenu();
|
||||||
|
|
||||||
|
QMenu edit_menu;
|
||||||
|
QMenu auto_assign_menu;
|
||||||
|
};
|
|
@ -0,0 +1,325 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ControllerPanel</class>
|
||||||
|
<widget class="QWidget" name="ControllerPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>674</width>
|
||||||
|
<height>632</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="controllerComboBox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SNES Controller 1</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SNES Controller 2</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SNES Controller 3 (Multitap)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SNES Controller 4 (Multitap)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>SNES Controller 5 (Multitap)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="editToolButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>90</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Swap or clear groups of bindings</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Edit</string>
|
||||||
|
</property>
|
||||||
|
<property name="popupMode">
|
||||||
|
<enum>QToolButton::DelayedPopup</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="autoAssignToolButton">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>90</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Automatically assign a controller's buttons to a slot</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Auto-Assign</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="tableWidget_controller">
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<attribute name="verticalHeaderCascadingSectionResizes">
|
||||||
|
<bool>true</bool>
|
||||||
|
</attribute>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Up</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/up.svg</normaloff>:/icons/blackicons/up.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Down</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/down.svg</normaloff>:/icons/blackicons/down.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Left</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/left.svg</normaloff>:/icons/blackicons/left.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Right</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/right.svg</normaloff>:/icons/blackicons/right.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>A</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/a.svg</normaloff>:/icons/blackicons/a.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>B</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/b.svg</normaloff>:/icons/blackicons/b.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>X</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/x.svg</normaloff>:/icons/blackicons/x.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Y</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/y.svg</normaloff>:/icons/blackicons/y.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>L</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/l.svg</normaloff>:/icons/blackicons/l.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>R</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/r.svg</normaloff>:/icons/blackicons/r.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/start.svg</normaloff>:/icons/blackicons/start.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/select.svg</normaloff>:/icons/blackicons/select.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo A</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/a.svg</normaloff>:/icons/blackicons/a.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo B</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/b.svg</normaloff>:/icons/blackicons/b.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo X</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/x.svg</normaloff>:/icons/blackicons/x.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo Y</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/y.svg</normaloff>:/icons/blackicons/y.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo L</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/l.svg</normaloff>:/icons/blackicons/l.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Turbo R</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/r.svg</normaloff>:/icons/blackicons/r.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #2</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #3</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #4</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources/snes9x.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,181 @@
|
||||||
|
#include "DisplayPanel.hpp"
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
DisplayPanel::DisplayPanel(EmuApplication *app_)
|
||||||
|
: app(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
QObject::connect(comboBox_driver, &QComboBox::activated, [&](int index) {
|
||||||
|
if (driver_list.empty() || index < 0 || index >= driver_list.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto display_driver = driver_list[index].second;
|
||||||
|
|
||||||
|
if (display_driver != app->config->display_driver)
|
||||||
|
{
|
||||||
|
app->config->display_driver = display_driver;
|
||||||
|
app->window->recreateCanvas();
|
||||||
|
populateDevices();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(comboBox_device, &QComboBox::activated, [&](int index) {
|
||||||
|
if (app->config->display_device_index != index)
|
||||||
|
{
|
||||||
|
app->config->display_device_index = index;
|
||||||
|
app->window->recreateCanvas();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_use_shader, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->use_shader = checked;
|
||||||
|
app->window->canvas->shaderChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(pushButton_browse_shader, &QPushButton::clicked, [&] {
|
||||||
|
selectShaderDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_vsync, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->enable_vsync = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_reduce_input_lag, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->reduce_input_lag = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_bilinear_filter, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->bilinear_filter = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_adjust_for_vrr, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->adjust_for_vrr = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
QObject::connect(checkBox_maintain_aspect_ratio, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->maintain_aspect_ratio = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_integer_scaling, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->use_integer_scaling = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(checkBox_overscan, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->show_overscan = checked;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(comboBox_aspect_ratio, &QComboBox::activated, [&](int index) {
|
||||||
|
auto &num = app->config->aspect_ratio_numerator;
|
||||||
|
auto &den = app->config->aspect_ratio_denominator;
|
||||||
|
if (index == 0) { num = 4, den = 3; }
|
||||||
|
if (index == 1) { num = 64, den = 49; }
|
||||||
|
if (index == 2) { num = 8, den = 7; }
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(comboBox_high_resolution_mode, &QComboBox::currentIndexChanged, [&](int index) {
|
||||||
|
app->config->high_resolution_effect = index;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(comboBox_messages, &QComboBox::currentIndexChanged, [&](int index) {
|
||||||
|
bool restart = (app->config->display_messages == EmuConfig::eOnscreen || index == EmuConfig::eOnscreen);
|
||||||
|
|
||||||
|
app->config->display_messages = index;
|
||||||
|
app->updateSettings();
|
||||||
|
if (restart)
|
||||||
|
app->window->recreateCanvas();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(spinBox_osd_size, &QSpinBox::valueChanged, [&](int value) {
|
||||||
|
bool restart = (app->config->osd_size != value && app->config->display_messages == EmuConfig::eOnscreen);
|
||||||
|
app->config->osd_size = value;
|
||||||
|
if (restart)
|
||||||
|
app->window->recreateCanvas();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayPanel::~DisplayPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPanel::selectShaderDialog()
|
||||||
|
{
|
||||||
|
QFileDialog dialog(this, tr("Select a Folder"));
|
||||||
|
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
|
dialog.setNameFilter(tr("Shader Presets (*.slangp *.glslp)"));
|
||||||
|
if (!app->config->last_shader_folder.empty())
|
||||||
|
dialog.setDirectory(app->config->last_shader_folder.c_str());
|
||||||
|
|
||||||
|
if (!dialog.exec())
|
||||||
|
return;
|
||||||
|
|
||||||
|
app->config->shader = dialog.selectedFiles().at(0).toUtf8();
|
||||||
|
app->config->last_shader_folder = dialog.directory().absolutePath().toStdString();
|
||||||
|
lineEdit_shader->setText(app->config->shader.c_str());
|
||||||
|
app->window->canvas->shaderChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPanel::populateDevices()
|
||||||
|
{
|
||||||
|
comboBox_device->clear();
|
||||||
|
auto device_list = app->window->getDisplayDeviceList();
|
||||||
|
for (auto &d : device_list)
|
||||||
|
comboBox_device->addItem(d.c_str());
|
||||||
|
comboBox_device->setCurrentIndex(app->config->display_device_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
auto &config = app->config;
|
||||||
|
|
||||||
|
comboBox_driver->clear();
|
||||||
|
comboBox_driver->addItem("Qt Software");
|
||||||
|
comboBox_driver->addItem("OpenGL");
|
||||||
|
comboBox_driver->addItem("Vulkan");
|
||||||
|
|
||||||
|
driver_list.clear();
|
||||||
|
driver_list.push_back({ driver_list.size(), "qt" });
|
||||||
|
driver_list.push_back({ driver_list.size(), "opengl" });
|
||||||
|
driver_list.push_back({ driver_list.size(), "vulkan" });
|
||||||
|
|
||||||
|
for (auto &i : driver_list)
|
||||||
|
if (config->display_driver == i.second)
|
||||||
|
{
|
||||||
|
comboBox_driver->setCurrentIndex(i.first);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
populateDevices();
|
||||||
|
|
||||||
|
checkBox_use_shader->setChecked(config->use_shader);
|
||||||
|
lineEdit_shader->setText(config->shader.c_str());
|
||||||
|
|
||||||
|
checkBox_vsync->setChecked(config->enable_vsync);
|
||||||
|
checkBox_reduce_input_lag->setChecked(config->reduce_input_lag);
|
||||||
|
checkBox_bilinear_filter->setChecked(config->bilinear_filter);
|
||||||
|
checkBox_adjust_for_vrr->setChecked(config->adjust_for_vrr);
|
||||||
|
|
||||||
|
checkBox_maintain_aspect_ratio->setChecked(config->maintain_aspect_ratio);
|
||||||
|
checkBox_integer_scaling->setChecked(config->use_integer_scaling);
|
||||||
|
checkBox_overscan->setChecked(config->show_overscan);
|
||||||
|
|
||||||
|
if (config->aspect_ratio_numerator == 4)
|
||||||
|
comboBox_aspect_ratio->setCurrentIndex(0);
|
||||||
|
else if (config->aspect_ratio_numerator == 64)
|
||||||
|
comboBox_aspect_ratio->setCurrentIndex(1);
|
||||||
|
else if (config->aspect_ratio_numerator == 8)
|
||||||
|
comboBox_aspect_ratio->setCurrentIndex(2);
|
||||||
|
|
||||||
|
comboBox_high_resolution_mode->setCurrentIndex(config->high_resolution_effect);
|
||||||
|
|
||||||
|
comboBox_messages->setCurrentIndex(config->display_messages);
|
||||||
|
spinBox_osd_size->setValue(config->osd_size);
|
||||||
|
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_DisplayPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
class DisplayPanel :
|
||||||
|
public Ui::DisplayPanel,
|
||||||
|
public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DisplayPanel(EmuApplication *app);
|
||||||
|
~DisplayPanel();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void populateDevices();
|
||||||
|
void selectShaderDialog();
|
||||||
|
|
||||||
|
std::vector<std::pair<int, std::string>> driver_list;
|
||||||
|
bool updating = true;
|
||||||
|
|
||||||
|
EmuApplication *app;
|
||||||
|
};
|
|
@ -0,0 +1,389 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>DisplayPanel</class>
|
||||||
|
<widget class="QWidget" name="DisplayPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>669</width>
|
||||||
|
<height>654</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Display Output</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_driver">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Driver:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_device">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Choose a device to render output. If you have no integrated graphics, there will be only one choice.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_driver">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select the output driver.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Device:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_3">
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_bilinear_filter">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Smooth screen output.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Bilinear filter</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_reduce_input_lag">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Prevent the display driver from getting too far ahead in order to reduce lag.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Reduce input lag</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_adjust_for_vrr">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>When entering fullscreen mode, temporarily change other settings to use VRR (G-Sync or FreeSync) correctly.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Adjust settings for VRR in fullscreen mode</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_vsync">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Sync the display to vertical retrace to eliminate tearing.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable vsync</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_use_shader">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Use a selected .slangp or .glslp shader file.
|
||||||
|
.slangp is supported by Vulkan and OpenGL.
|
||||||
|
.glslp is supported by OpenGL.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use a hardware shader:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="lineEdit_shader"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_browse_shader">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Scaling and Aspect Ratio</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_maintain_aspect_ratio">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Keep the screen at the requested proportions for width and height.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Maintain aspect ratio</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_integer_scaling">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>When scaling up, only use integer multiples of the original height.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Use integer scaling</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_overscan">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Show the areas on the top and bottom of the screen that are normally black and hidden by the TV. Some games will draw in these areas.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Show overscan area</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>High-resolution mode:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_aspect_ratio">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>4:3 - Classic display aspect</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>64:49 - NTSC aspect</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>8:7 - Square pixels</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Aspect ratio:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_high_resolution_mode">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>This affects the rarely used 512-pixels-wide mode used by the SNES.
|
||||||
|
For games like Kirby 3 and Jurassic Park, choose the "merge fields" option.
|
||||||
|
For games like Seiken Densetsu 3 and Marvelous, choose the "scale up" option.
|
||||||
|
Output directly will cause the screen to change between the two modes and look weird.</string>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Output directly</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Merge the fields of the high-resolution lines</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Scale normal resolution screens up</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>Software Filters</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_4">
|
||||||
|
<property name="title">
|
||||||
|
<string>Messages</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_5">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Display messages:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_messages">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Onscreen - High resolution</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Inside the screen - Low resolution</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Don't display messages</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="spinBox_osd_size">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string>pt</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>8</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>256</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>24</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Onscreen display size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,352 @@
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include "common/audio/s9x_sound_driver_sdl.hpp"
|
||||||
|
#include "common/audio/s9x_sound_driver_portaudio.hpp"
|
||||||
|
#include "common/audio/s9x_sound_driver_cubeb.hpp"
|
||||||
|
#include <QTimer>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
EmuApplication::EmuApplication()
|
||||||
|
{
|
||||||
|
core = Snes9xController::get();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuApplication::~EmuApplication()
|
||||||
|
{
|
||||||
|
core->deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::restartAudio()
|
||||||
|
{
|
||||||
|
sound_driver.reset();
|
||||||
|
core->sound_output_function = nullptr;
|
||||||
|
|
||||||
|
if (config->sound_driver == "portaudio")
|
||||||
|
sound_driver = std::make_unique<S9xPortAudioSoundDriver>();
|
||||||
|
else if (config->sound_driver == "cubeb")
|
||||||
|
sound_driver = std::make_unique<S9xCubebSoundDriver>();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
config->sound_driver = "sdl";
|
||||||
|
sound_driver = std::make_unique<S9xSDLSoundDriver>();
|
||||||
|
}
|
||||||
|
|
||||||
|
sound_driver->init();
|
||||||
|
if (sound_driver->open_device(config->playback_rate, config->audio_buffer_size_ms))
|
||||||
|
sound_driver->start();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("Couldn't initialize sound driver: %s\n", config->sound_driver.c_str());
|
||||||
|
sound_driver.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sound_driver)
|
||||||
|
core->sound_output_function = [&](int16_t *data, int samples) {
|
||||||
|
writeSamples(data, samples);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::writeSamples(int16_t *data, int samples)
|
||||||
|
{
|
||||||
|
if (config->speed_sync_method == EmuConfig::eSoundSync && !core->isAbnormalSpeed())
|
||||||
|
{
|
||||||
|
int iterations = 0;
|
||||||
|
while (sound_driver->space_free() < samples && iterations < 100)
|
||||||
|
{
|
||||||
|
iterations++;
|
||||||
|
std::this_thread::sleep_for(50us);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sound_driver->write_samples(data, samples);
|
||||||
|
auto buffer_level = sound_driver->buffer_level();
|
||||||
|
core->updateSoundBufferLevel(buffer_level.first, buffer_level.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::startGame()
|
||||||
|
{
|
||||||
|
if (!sound_driver)
|
||||||
|
restartAudio();
|
||||||
|
|
||||||
|
core->screen_output_function = [&](uint16_t *data, int width, int height, int stride_bytes, double frame_rate) {
|
||||||
|
if (window->canvas)
|
||||||
|
window->canvas->output((uint8_t *)data, width, height, QImage::Format_RGB16, stride_bytes, frame_rate);
|
||||||
|
};
|
||||||
|
|
||||||
|
core->updateSettings(config.get());
|
||||||
|
updateBindings();
|
||||||
|
|
||||||
|
startIdleLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuApplication::isPaused()
|
||||||
|
{
|
||||||
|
return (pause_count != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::pause()
|
||||||
|
{
|
||||||
|
pause_count++;
|
||||||
|
if (pause_count > 0)
|
||||||
|
{
|
||||||
|
core->setPaused(true);
|
||||||
|
if (sound_driver)
|
||||||
|
sound_driver->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::stopIdleLoop()
|
||||||
|
{
|
||||||
|
idle_loop->stop();
|
||||||
|
pause_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::unpause()
|
||||||
|
{
|
||||||
|
pause_count--;
|
||||||
|
if (pause_count < 0)
|
||||||
|
pause_count = 0;
|
||||||
|
if (pause_count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
core->setPaused(false);
|
||||||
|
if (core->active && sound_driver)
|
||||||
|
sound_driver->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::startIdleLoop()
|
||||||
|
{
|
||||||
|
if (!idle_loop)
|
||||||
|
{
|
||||||
|
idle_loop = std::make_unique<QTimer>();
|
||||||
|
idle_loop->setTimerType(Qt::TimerType::PreciseTimer);
|
||||||
|
idle_loop->setInterval(0);
|
||||||
|
idle_loop->setSingleShot(false);
|
||||||
|
idle_loop->callOnTimeout([&]{ idleLoop(); });
|
||||||
|
pause_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
idle_loop->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::idleLoop()
|
||||||
|
{
|
||||||
|
if (core->active && pause_count == 0)
|
||||||
|
{
|
||||||
|
idle_loop->setInterval(0);
|
||||||
|
pollJoysticks();
|
||||||
|
bool muted = config->mute_audio || (config->mute_audio_during_alternate_speed && core->isAbnormalSpeed());
|
||||||
|
core->mute(muted);
|
||||||
|
core->mainLoop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pollJoysticks();
|
||||||
|
idle_loop->setInterval(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuApplication::openFile(std::string filename)
|
||||||
|
{
|
||||||
|
auto result = core->openFile(filename);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::reportBinding(EmuBinding b, bool active)
|
||||||
|
{
|
||||||
|
if (binding_callback && active)
|
||||||
|
{
|
||||||
|
binding_callback(b);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = bindings.find(b.hash());
|
||||||
|
if (it == bindings.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (it->second.second == UI)
|
||||||
|
{
|
||||||
|
handleBinding(it->second.first, active);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
core->reportBinding(b, active);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::updateBindings()
|
||||||
|
{
|
||||||
|
bindings.clear();
|
||||||
|
for (auto i = 0; i < EmuConfig::num_shortcuts; i++)
|
||||||
|
{
|
||||||
|
auto name = EmuConfig::getShortcutNames()[i];
|
||||||
|
|
||||||
|
for (auto b = 0; b < EmuConfig::allowed_bindings; b++)
|
||||||
|
{
|
||||||
|
auto &binding = config->binding.shortcuts[i * EmuConfig::allowed_bindings + b];
|
||||||
|
|
||||||
|
if (binding.type != EmuBinding::None)
|
||||||
|
{
|
||||||
|
auto handler = core->acceptsCommand(name) ? Core : UI;
|
||||||
|
bindings.insert({ binding.hash(), { name, handler } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < EmuConfig::num_controller_bindings; i++)
|
||||||
|
{
|
||||||
|
for (int c = 0; c < 5; c++)
|
||||||
|
{
|
||||||
|
for (int b = 0; b < EmuConfig::allowed_bindings; b++)
|
||||||
|
{
|
||||||
|
auto binding = config->binding.controller[c].buttons[i * EmuConfig::allowed_bindings + b];
|
||||||
|
if (binding.hash() != 0)
|
||||||
|
bindings.insert({ binding.hash(), { "Snes9x", Core } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core->updateBindings(config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::handleBinding(std::string name, bool pressed)
|
||||||
|
{
|
||||||
|
if (core->active)
|
||||||
|
{
|
||||||
|
if (name == "Rewind")
|
||||||
|
{
|
||||||
|
core->rewinding = pressed;
|
||||||
|
}
|
||||||
|
else if (pressed) // Only activate with core active and on button down
|
||||||
|
{
|
||||||
|
if (name == "PauseContinue")
|
||||||
|
{
|
||||||
|
window->pauseContinue();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (name == "IncreaseSlot")
|
||||||
|
{
|
||||||
|
save_slot++;
|
||||||
|
if (save_slot > 999)
|
||||||
|
save_slot = 0;
|
||||||
|
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||||
|
}
|
||||||
|
else if (name == "DecreaseSlot")
|
||||||
|
{
|
||||||
|
save_slot--;
|
||||||
|
if (save_slot < 0)
|
||||||
|
save_slot = 999;
|
||||||
|
core->setMessage("Current slot: " + std::to_string(save_slot));
|
||||||
|
}
|
||||||
|
else if (name == "SaveState")
|
||||||
|
{
|
||||||
|
saveState(save_slot);
|
||||||
|
}
|
||||||
|
else if (name == "LoadState")
|
||||||
|
{
|
||||||
|
loadState(save_slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "ToggleFullscreen" && !pressed)
|
||||||
|
{
|
||||||
|
window->toggleFullscreen();
|
||||||
|
}
|
||||||
|
else if (name == "OpenROM" && pressed)
|
||||||
|
{
|
||||||
|
window->openFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuApplication::isBound(EmuBinding b)
|
||||||
|
{
|
||||||
|
if (bindings.find(b.hash()) != bindings.end())
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::updateSettings()
|
||||||
|
{
|
||||||
|
core->updateSettings(config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::pollJoysticks()
|
||||||
|
{
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
auto event = input_manager->ProcessEvent();
|
||||||
|
if (!event)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (event->type)
|
||||||
|
{
|
||||||
|
case SDL_JOYDEVICEADDED:
|
||||||
|
case SDL_JOYDEVICEREMOVED:
|
||||||
|
if (joypads_changed_callback)
|
||||||
|
joypads_changed_callback();
|
||||||
|
break;
|
||||||
|
case SDL_JOYAXISMOTION: {
|
||||||
|
auto axis_event = input_manager->DiscretizeJoyAxisEvent(event.value());
|
||||||
|
if (axis_event)
|
||||||
|
{
|
||||||
|
auto binding = EmuBinding::joystick_axis(
|
||||||
|
axis_event->joystick_num,
|
||||||
|
axis_event->axis,
|
||||||
|
axis_event->direction);
|
||||||
|
|
||||||
|
reportBinding(binding, axis_event->pressed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDL_JOYBUTTONDOWN:
|
||||||
|
case SDL_JOYBUTTONUP:
|
||||||
|
reportBinding(EmuBinding::joystick_button(
|
||||||
|
input_manager->devices[event->jbutton.which].index,
|
||||||
|
event->jbutton.button), event->jbutton.state == 1);
|
||||||
|
break;
|
||||||
|
case SDL_JOYHATMOTION:
|
||||||
|
auto hat_event = input_manager->DiscretizeHatEvent(event.value());
|
||||||
|
if (hat_event)
|
||||||
|
{
|
||||||
|
reportBinding(EmuBinding::joystick_hat(hat_event->joystick_num,
|
||||||
|
hat_event->hat,
|
||||||
|
hat_event->direction),
|
||||||
|
hat_event->pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::loadState(int slot)
|
||||||
|
{
|
||||||
|
core->loadState(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::loadState(std::string filename)
|
||||||
|
{
|
||||||
|
core->loadState(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::saveState(int slot)
|
||||||
|
{
|
||||||
|
core->saveState(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::saveState(std::string filename)
|
||||||
|
{
|
||||||
|
core->saveState(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::reset()
|
||||||
|
{
|
||||||
|
core->softReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuApplication::powerCycle()
|
||||||
|
{
|
||||||
|
core->reset();
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "EmuMainWindow.hpp"
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
#include "SDLInputManager.hpp"
|
||||||
|
#include "Snes9xController.hpp"
|
||||||
|
#include "common/audio/s9x_sound_driver.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
struct EmuApplication
|
||||||
|
{
|
||||||
|
std::unique_ptr<QApplication> qtapp;
|
||||||
|
std::unique_ptr<EmuConfig> config;
|
||||||
|
std::unique_ptr<SDLInputManager> input_manager;
|
||||||
|
std::unique_ptr<EmuMainWindow> window;
|
||||||
|
std::unique_ptr<S9xSoundDriver> sound_driver;
|
||||||
|
Snes9xController *core;
|
||||||
|
|
||||||
|
EmuApplication();
|
||||||
|
~EmuApplication();
|
||||||
|
bool openFile(std::string filename);
|
||||||
|
void handleBinding(std::string name, bool pressed);
|
||||||
|
void updateSettings();
|
||||||
|
void updateBindings();
|
||||||
|
bool isBound(EmuBinding b);
|
||||||
|
void reportBinding(EmuBinding b, bool active);
|
||||||
|
void pollJoysticks();
|
||||||
|
void restartAudio();
|
||||||
|
void writeSamples(int16_t *data, int samples);
|
||||||
|
void pause();
|
||||||
|
void reset();
|
||||||
|
void powerCycle();
|
||||||
|
bool isPaused();
|
||||||
|
void unpause();
|
||||||
|
void loadState(int slot);
|
||||||
|
void loadState(std::string filename);
|
||||||
|
void saveState(int slot);
|
||||||
|
void saveState(std::string filename);
|
||||||
|
void startGame();
|
||||||
|
void startIdleLoop();
|
||||||
|
void stopIdleLoop();
|
||||||
|
void idleLoop();
|
||||||
|
|
||||||
|
enum Handler
|
||||||
|
{
|
||||||
|
Core = 0,
|
||||||
|
UI = 1
|
||||||
|
};
|
||||||
|
std::map<uint32_t, std::pair<std::string, Handler>> bindings;
|
||||||
|
std::unique_ptr<QTimer> idle_loop;
|
||||||
|
std::function<void(EmuBinding)> binding_callback = nullptr;
|
||||||
|
std::function<void()> joypads_changed_callback = nullptr;
|
||||||
|
int save_slot = 0;
|
||||||
|
int pause_count = 0;
|
||||||
|
};
|
|
@ -0,0 +1,226 @@
|
||||||
|
#include "EmuBinding.hpp"
|
||||||
|
#include "SDL_joystick.h"
|
||||||
|
#include <QString>
|
||||||
|
#include <QKeySequence>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// Hash format:
|
||||||
|
//
|
||||||
|
// Bit 31-30: Joystick or Keyboard bit
|
||||||
|
//
|
||||||
|
// Keyboard:
|
||||||
|
// Bit 30: Alt
|
||||||
|
// Bit 29: Ctrl
|
||||||
|
// Bit 28: Super
|
||||||
|
// Bit 27: Shift
|
||||||
|
// Bits 15-0: keycode
|
||||||
|
//
|
||||||
|
// Joystick:
|
||||||
|
// Bits 29-28: Type:
|
||||||
|
// 00 Button
|
||||||
|
// 01 Axis
|
||||||
|
// 10 Hat
|
||||||
|
// Bit 27: If axis or hat, positive or negative
|
||||||
|
// Bits 26-19: Which button/hat/axis
|
||||||
|
// Bits 15-8: Hat direction
|
||||||
|
// Bits 7-0: Device identifier
|
||||||
|
uint32_t EmuBinding::hash() const
|
||||||
|
{
|
||||||
|
uint32_t hash = 0;
|
||||||
|
|
||||||
|
hash |= type << 30;
|
||||||
|
if (type == Keyboard)
|
||||||
|
{
|
||||||
|
hash |= alt << 29;
|
||||||
|
hash |= ctrl << 28;
|
||||||
|
hash |= super << 27;
|
||||||
|
hash |= shift << 26;
|
||||||
|
hash |= keycode & 0xfffffff;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hash |= (input_type & 0x3) << 28;
|
||||||
|
hash |= (threshold < 0 ? 1 : 0) << 27;
|
||||||
|
hash |= (button & 0xff) << 19;
|
||||||
|
hash |= (input_type == Hat) ? direction << 8 : 0;
|
||||||
|
hash |= (guid & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuBinding::operator==(const EmuBinding &other)
|
||||||
|
{
|
||||||
|
return other.hash() == hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuBinding EmuBinding::joystick_axis(int device, int axis, int threshold)
|
||||||
|
{
|
||||||
|
EmuBinding binding;
|
||||||
|
binding.type = Joystick;
|
||||||
|
binding.input_type = Axis;
|
||||||
|
binding.guid = device;
|
||||||
|
binding.axis = axis;
|
||||||
|
binding.threshold = threshold;
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuBinding EmuBinding::joystick_hat(int device, int hat, uint8_t direction)
|
||||||
|
{
|
||||||
|
EmuBinding binding{};
|
||||||
|
binding.type = Joystick;
|
||||||
|
binding.input_type = Hat;
|
||||||
|
binding.guid = device;
|
||||||
|
binding.hat = hat;
|
||||||
|
binding.direction = direction;
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuBinding EmuBinding::joystick_button(int device, int button)
|
||||||
|
{
|
||||||
|
EmuBinding binding{};
|
||||||
|
binding.type = Joystick;
|
||||||
|
binding.input_type = Button;
|
||||||
|
binding.guid = device;
|
||||||
|
binding.button = button;
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuBinding EmuBinding::keyboard(int keycode, bool shift, bool alt, bool ctrl, bool super)
|
||||||
|
{
|
||||||
|
EmuBinding binding{};
|
||||||
|
binding.type = Keyboard;
|
||||||
|
binding.alt = alt;
|
||||||
|
binding.ctrl = ctrl;
|
||||||
|
binding.shift = shift;
|
||||||
|
binding.super = super;
|
||||||
|
binding.keycode = keycode;
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuBinding EmuBinding::from_config_string(std::string string)
|
||||||
|
{
|
||||||
|
for (auto &c : string)
|
||||||
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
c += 32;
|
||||||
|
|
||||||
|
if (string.compare(0, 9, "keyboard ") == 0)
|
||||||
|
{
|
||||||
|
EmuBinding b{};
|
||||||
|
b.type = Keyboard;
|
||||||
|
|
||||||
|
QString qstr(string.substr(9).c_str());
|
||||||
|
auto seq = QKeySequence::fromString(qstr);
|
||||||
|
if (seq.count())
|
||||||
|
{
|
||||||
|
b.keycode = seq[0].key();
|
||||||
|
b.alt = seq[0].keyboardModifiers().testAnyFlag(Qt::AltModifier);
|
||||||
|
b.ctrl = seq[0].keyboardModifiers().testAnyFlag(Qt::ControlModifier);
|
||||||
|
b.super = seq[0].keyboardModifiers().testAnyFlag(Qt::MetaModifier);
|
||||||
|
b.shift = seq[0].keyboardModifiers().testAnyFlag(Qt::ShiftModifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
else if (string.compare(0, 8, "joystick") == 0)
|
||||||
|
{
|
||||||
|
auto substr = string.substr(8);
|
||||||
|
unsigned int axis;
|
||||||
|
unsigned int button;
|
||||||
|
unsigned int percent;
|
||||||
|
unsigned int device;
|
||||||
|
char direction_string[6]{};
|
||||||
|
char posneg;
|
||||||
|
|
||||||
|
if (sscanf(substr.c_str(), "%u axis %u %c %u", &device, &axis, &posneg, &percent) == 4)
|
||||||
|
{
|
||||||
|
int sign = posneg == '-' ? -1 : 1;
|
||||||
|
return joystick_axis(device, axis, sign * percent);
|
||||||
|
}
|
||||||
|
else if (sscanf(substr.c_str(), "%u button %u", &device, &button) == 2)
|
||||||
|
{
|
||||||
|
return joystick_button(device, button);
|
||||||
|
}
|
||||||
|
else if (sscanf(substr.c_str(), "%u hat %u %5s", &device, &axis, direction_string))
|
||||||
|
{
|
||||||
|
uint8_t direction;
|
||||||
|
if (!strcmp(direction_string, "up"))
|
||||||
|
direction = SDL_HAT_UP;
|
||||||
|
else if (!strcmp(direction_string, "down"))
|
||||||
|
direction = SDL_HAT_DOWN;
|
||||||
|
else if (!strcmp(direction_string, "left"))
|
||||||
|
direction = SDL_HAT_LEFT;
|
||||||
|
else if (!strcmp(direction_string, "right"))
|
||||||
|
direction = SDL_HAT_RIGHT;
|
||||||
|
|
||||||
|
return joystick_hat(device, axis, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EmuBinding::to_config_string()
|
||||||
|
{
|
||||||
|
return to_string(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EmuBinding::to_string(bool config)
|
||||||
|
{
|
||||||
|
std::string rep;
|
||||||
|
if (type == Keyboard)
|
||||||
|
{
|
||||||
|
if (config)
|
||||||
|
rep += "Keyboard ";
|
||||||
|
|
||||||
|
if (ctrl)
|
||||||
|
rep += "Ctrl+";
|
||||||
|
if (alt)
|
||||||
|
rep += "Alt+";
|
||||||
|
if (shift)
|
||||||
|
rep += "Shift+";
|
||||||
|
if (super)
|
||||||
|
rep += "Super+";
|
||||||
|
|
||||||
|
QKeySequence seq(keycode);
|
||||||
|
rep += seq.toString().toStdString();
|
||||||
|
}
|
||||||
|
else if (type == Joystick)
|
||||||
|
{
|
||||||
|
if (config)
|
||||||
|
rep += "joystick " + std::to_string(guid) + " ";
|
||||||
|
else
|
||||||
|
rep += "J" + std::to_string(guid) + " ";
|
||||||
|
|
||||||
|
if (input_type == Button)
|
||||||
|
{
|
||||||
|
rep += "Button ";
|
||||||
|
rep += std::to_string(button);
|
||||||
|
}
|
||||||
|
if (input_type == Axis)
|
||||||
|
{
|
||||||
|
rep += "Axis ";
|
||||||
|
rep += std::to_string(axis) + " ";
|
||||||
|
rep += std::to_string(threshold) + "%";
|
||||||
|
}
|
||||||
|
if (input_type == Hat)
|
||||||
|
{
|
||||||
|
rep += "Hat ";
|
||||||
|
rep += std::to_string(hat) + " ";
|
||||||
|
if (direction == SDL_HAT_UP)
|
||||||
|
rep += "Up";
|
||||||
|
else if (direction == SDL_HAT_DOWN)
|
||||||
|
rep += "Down";
|
||||||
|
else if (direction == SDL_HAT_LEFT)
|
||||||
|
rep += "Left";
|
||||||
|
else if (direction == SDL_HAT_RIGHT)
|
||||||
|
rep += "Right";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rep = "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
return rep;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#ifndef __EMU_BINDING_HPP
|
||||||
|
#define __EMU_BINDING_HPP
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct EmuBinding
|
||||||
|
{
|
||||||
|
uint32_t hash() const;
|
||||||
|
std::string to_string(bool config = false);
|
||||||
|
static EmuBinding joystick_axis(int device, int axis, int threshold);
|
||||||
|
static EmuBinding joystick_hat(int device, int hat, uint8_t direction);
|
||||||
|
static EmuBinding joystick_button(int device, int button);
|
||||||
|
static EmuBinding keyboard(int keycode, bool shift = false, bool alt = false, bool ctrl = false, bool super = false);
|
||||||
|
static EmuBinding from_config_string(std::string str);
|
||||||
|
std::string to_config_string();
|
||||||
|
bool operator==(const EmuBinding &);
|
||||||
|
|
||||||
|
enum Type
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Keyboard = 1,
|
||||||
|
Joystick = 2
|
||||||
|
};
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
enum JoystickInputType
|
||||||
|
{
|
||||||
|
Button = 0,
|
||||||
|
Axis = 1,
|
||||||
|
Hat = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
bool alt;
|
||||||
|
bool ctrl;
|
||||||
|
bool super;
|
||||||
|
bool shift;
|
||||||
|
int keycode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
JoystickInputType input_type;
|
||||||
|
int guid;
|
||||||
|
union {
|
||||||
|
int button;
|
||||||
|
int hat;
|
||||||
|
int axis;
|
||||||
|
};
|
||||||
|
int threshold;
|
||||||
|
uint8_t direction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,107 @@
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
#include <qnamespace.h>
|
||||||
|
#include <qwidget.h>
|
||||||
|
|
||||||
|
EmuCanvas::EmuCanvas(EmuConfig *config, QWidget *parent, QWidget *main_window)
|
||||||
|
: QWidget(parent)
|
||||||
|
{
|
||||||
|
setFocus();
|
||||||
|
setFocusPolicy(Qt::StrongFocus);
|
||||||
|
setMouseTracking(true);
|
||||||
|
|
||||||
|
output_data.buffer = nullptr;
|
||||||
|
output_data.ready = false;
|
||||||
|
this->config = config;
|
||||||
|
this->parent = parent;
|
||||||
|
this->main_window = main_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuCanvas::~EmuCanvas()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvas::output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate)
|
||||||
|
{
|
||||||
|
output_data.buffer = buffer;
|
||||||
|
output_data.width = width;
|
||||||
|
output_data.height = height;
|
||||||
|
output_data.format = format;
|
||||||
|
output_data.bytes_per_line = bytes_per_line;
|
||||||
|
output_data.frame_rate = frame_rate;
|
||||||
|
output_data.ready = true;
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvas::throttle()
|
||||||
|
{
|
||||||
|
if (config->speed_sync_method != EmuConfig::eTimer && config->speed_sync_method != EmuConfig::eTimerWithFrameskip)
|
||||||
|
return;
|
||||||
|
|
||||||
|
throttle_object.set_frame_rate(config->fixed_frame_rate == 0.0 ? output_data.frame_rate : config->fixed_frame_rate);
|
||||||
|
throttle_object.wait_for_frame_and_rebase_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect EmuCanvas::applyAspect(const QRect &viewport)
|
||||||
|
{
|
||||||
|
if (!config->scale_image)
|
||||||
|
{
|
||||||
|
return QRect((viewport.width() - output_data.width) / 2,
|
||||||
|
(viewport.height() - output_data.height) / 2,
|
||||||
|
output_data.width,
|
||||||
|
output_data.height);
|
||||||
|
}
|
||||||
|
if (!config->maintain_aspect_ratio)
|
||||||
|
return viewport;
|
||||||
|
|
||||||
|
int num = config->aspect_ratio_numerator;
|
||||||
|
int den = config->aspect_ratio_denominator;
|
||||||
|
if (config->show_overscan)
|
||||||
|
{
|
||||||
|
num *= 224;
|
||||||
|
den *= 239;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->use_integer_scaling)
|
||||||
|
{
|
||||||
|
int max_scale = 1;
|
||||||
|
|
||||||
|
for (int i = 2; i < 20; i++)
|
||||||
|
{
|
||||||
|
int scaled_height = output_data.height * i;
|
||||||
|
int scaled_width = scaled_height * num / den;
|
||||||
|
if (scaled_width <= viewport.width() && scaled_height <= viewport.height())
|
||||||
|
max_scale = i;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_height = output_data.height * max_scale;
|
||||||
|
int new_width = new_height * num / den;
|
||||||
|
return QRect((viewport.width() - new_width) / 2,
|
||||||
|
(viewport.height() - new_height) / 2,
|
||||||
|
new_width,
|
||||||
|
new_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
double canvas_aspect = (double)viewport.width() / viewport.height();
|
||||||
|
double new_aspect = (double)num / den;
|
||||||
|
|
||||||
|
if (canvas_aspect > new_aspect)
|
||||||
|
{
|
||||||
|
int new_width = viewport.height() * num / den;
|
||||||
|
int new_x = (viewport.width() - new_width) / 2;
|
||||||
|
|
||||||
|
return { new_x,
|
||||||
|
viewport.y(),
|
||||||
|
new_width,
|
||||||
|
viewport.height() };
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_height = viewport.width() * den / num;
|
||||||
|
int new_y = (viewport.height() - new_height) / 2;
|
||||||
|
|
||||||
|
return { viewport.x(),
|
||||||
|
new_y,
|
||||||
|
viewport.width(),
|
||||||
|
new_height };
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QImage>
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
#include "../../vulkan/std_chrono_throttle.hpp"
|
||||||
|
|
||||||
|
class EmuCanvas : public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuCanvas(EmuConfig *config, QWidget *parent, QWidget *main_window);
|
||||||
|
~EmuCanvas();
|
||||||
|
|
||||||
|
virtual void deinit() = 0;
|
||||||
|
virtual void draw() = 0;
|
||||||
|
void output(uint8_t *buffer, int width, int height, QImage::Format format, int bytes_per_line, double frame_rate);
|
||||||
|
void throttle();
|
||||||
|
|
||||||
|
virtual std::vector<std::string> getDeviceList()
|
||||||
|
{
|
||||||
|
return std::vector<std::string>{ "Default" };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ready()
|
||||||
|
{
|
||||||
|
return output_data.ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect applyAspect(const QRect &viewport);
|
||||||
|
|
||||||
|
struct Parameter
|
||||||
|
{
|
||||||
|
bool operator==(const Parameter &other)
|
||||||
|
{
|
||||||
|
if (name == other.name &&
|
||||||
|
id == other.id &&
|
||||||
|
min == other.min &&
|
||||||
|
max == other.max &&
|
||||||
|
val == other.val &&
|
||||||
|
step == other.step &&
|
||||||
|
significant_digits == other.significant_digits)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
std::string id;
|
||||||
|
float min;
|
||||||
|
float max;
|
||||||
|
float val;
|
||||||
|
float step;
|
||||||
|
int significant_digits;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void showParametersDialog() {};
|
||||||
|
virtual void shaderChanged() {};
|
||||||
|
virtual void saveParameters(std::string filename) {};
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
bool ready;
|
||||||
|
uint8_t *buffer;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
QImage::Format format;
|
||||||
|
int bytes_per_line;
|
||||||
|
double frame_rate;
|
||||||
|
} output_data;
|
||||||
|
|
||||||
|
QWidget *parent{};
|
||||||
|
QWidget *main_window{};
|
||||||
|
EmuConfig *config{};
|
||||||
|
Throttle throttle_object;
|
||||||
|
};
|
|
@ -0,0 +1,103 @@
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
|
||||||
|
EmuCanvas::EmuCanvas()
|
||||||
|
{
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuCanvas::~EmuCanvas()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
struct drawing_area : QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
Window xid = 0;
|
||||||
|
bool ready = false;
|
||||||
|
XSetWindowAttributes xattr;
|
||||||
|
Visual *visual;
|
||||||
|
unsigned int xflags = 0;
|
||||||
|
QWindow *wrapper_window = nullptr;
|
||||||
|
QWidget *wrapper = nullptr;
|
||||||
|
|
||||||
|
drawing_area()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~drawing_area()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void recreateWindow()
|
||||||
|
{
|
||||||
|
if (xid)
|
||||||
|
{
|
||||||
|
XUnmapWindow(QX11Info::display(), xid);
|
||||||
|
XDestroyWindow(QX11Info::display(), xid);
|
||||||
|
xid = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
XSetErrorHandler([](Display *dpy, XErrorEvent *event) -> int{
|
||||||
|
char text[4096];
|
||||||
|
XGetErrorText(QX11Info::display(), event->error_code, text, 4096);
|
||||||
|
printf("%s\n", text);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
createWinId();
|
||||||
|
|
||||||
|
int xwidth = width() * devicePixelRatio();
|
||||||
|
int xheight = height() * devicePixelRatio();
|
||||||
|
|
||||||
|
printf ("%d %d to %d %d %f\n", width(), height(), xwidth, xheight, devicePixelRatioFScale());
|
||||||
|
|
||||||
|
memset(&xattr, 0, sizeof(XSetWindowAttributes));
|
||||||
|
xattr.background_pixel = 0;
|
||||||
|
xattr.backing_store = 0;
|
||||||
|
xattr.event_mask = ExposureMask;
|
||||||
|
xattr.border_pixel = 0;
|
||||||
|
xflags = CWWidth | CWHeight | CWEventMask | CWBackPixel | CWBorderPixel | CWBackingStore;
|
||||||
|
xid = XCreateWindow(QX11Info::display(), winId(), 0, 0, xwidth, xheight, 0, CopyFromParent, InputOutput, CopyFromParent, xflags, &xattr);
|
||||||
|
XMapWindow(QX11Info::display(), xid);
|
||||||
|
/*wrapper_window = QWindow::fromWinId((WId)xid);
|
||||||
|
wrapper = QWidget::createWindowContainer(wrapper_window, this); */
|
||||||
|
}
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *event) override
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto id = winId();
|
||||||
|
|
||||||
|
XGCValues gcvalues {};
|
||||||
|
gcvalues.background = 0x00ff0000;
|
||||||
|
|
||||||
|
gcvalues.foreground = 0x00ff0000;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
QPainter paint(this);
|
||||||
|
QImage image((const uchar *)snes9x->GetScreen(), 256, 224, 1024, QImage::Format_RGB16);
|
||||||
|
paint.drawImage(0, 0, image, 0, 0, 256, 224);
|
||||||
|
paint.drawImage(QRect(0, 0, width(), height()), image, QRect(0, 0, 256, 224));
|
||||||
|
paint.end();
|
||||||
|
ready = false; */
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw()
|
||||||
|
{
|
||||||
|
ready = true;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resizeEvent(QResizeEvent *event) override
|
||||||
|
{
|
||||||
|
recreateWindow();
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,378 @@
|
||||||
|
#include "EmuCanvasOpenGL.hpp"
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include "common/video/opengl_context.hpp"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include "common/video/glx_context.hpp"
|
||||||
|
#include "common/video/wayland_egl_context.hpp"
|
||||||
|
using namespace QNativeInterface;
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#else
|
||||||
|
#include "common/video/wgl_context.hpp"
|
||||||
|
#endif
|
||||||
|
#include "shaders/glsl.h"
|
||||||
|
#include "EmuMainWindow.hpp"
|
||||||
|
#include "snes9x_imgui.h"
|
||||||
|
#include "imgui_impl_opengl3.h"
|
||||||
|
|
||||||
|
static const char *stock_vertex_shader_140 = R"(
|
||||||
|
#version 140
|
||||||
|
|
||||||
|
in vec2 in_position;
|
||||||
|
in vec2 in_texcoord;
|
||||||
|
out vec2 texcoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = vec4(in_position, 0.0, 1.0);
|
||||||
|
texcoord = in_texcoord;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char *stock_fragment_shader_140 = R"(
|
||||||
|
#version 140
|
||||||
|
|
||||||
|
uniform sampler2D texmap;
|
||||||
|
out vec4 fragcolor;
|
||||||
|
in vec2 texcoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
fragcolor = texture(texmap, texcoord);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
EmuCanvasOpenGL::EmuCanvasOpenGL(EmuConfig *config, QWidget *parent, QWidget *main_window)
|
||||||
|
: EmuCanvas(config, parent, main_window)
|
||||||
|
{
|
||||||
|
setMinimumSize(256, 224);
|
||||||
|
setUpdatesEnabled(false);
|
||||||
|
setAutoFillBackground(false);
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
createWinId();
|
||||||
|
|
||||||
|
auto timer = new QTimer(this);
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
timer->callOnTimeout([&]{ createContext(); });
|
||||||
|
timer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuCanvasOpenGL::~EmuCanvasOpenGL()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::createStockShaders()
|
||||||
|
{
|
||||||
|
stock_program = glCreateProgram();
|
||||||
|
|
||||||
|
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
glShaderSource(vertex_shader, 1, &stock_vertex_shader_140, NULL);
|
||||||
|
glShaderSource(fragment_shader, 1, &stock_fragment_shader_140, NULL);
|
||||||
|
|
||||||
|
glCompileShader(vertex_shader);
|
||||||
|
glAttachShader(stock_program, vertex_shader);
|
||||||
|
glCompileShader(fragment_shader);
|
||||||
|
glAttachShader(stock_program, fragment_shader);
|
||||||
|
|
||||||
|
glBindAttribLocation(stock_program, 0, "in_position");
|
||||||
|
glBindAttribLocation(stock_program, 1, "in_texcoord");
|
||||||
|
|
||||||
|
glLinkProgram(stock_program);
|
||||||
|
glUseProgram(stock_program);
|
||||||
|
|
||||||
|
glDeleteShader(vertex_shader);
|
||||||
|
glDeleteShader(fragment_shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::stockShaderDraw()
|
||||||
|
{
|
||||||
|
auto viewport = applyAspect(QRect(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio()));
|
||||||
|
glViewport(viewport.x(), viewport.y(), viewport.width(), viewport.height());
|
||||||
|
|
||||||
|
GLint texture_uniform = glGetUniformLocation(stock_program, "texmap");
|
||||||
|
glUniform1i(texture_uniform, 0);
|
||||||
|
|
||||||
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QRect g_viewport;
|
||||||
|
static void S9xViewportCallback(int src_width, int src_height,
|
||||||
|
int viewport_x, int viewport_y,
|
||||||
|
int viewport_width, int viewport_height,
|
||||||
|
int *out_x, int *out_y,
|
||||||
|
int *out_width, int *out_height)
|
||||||
|
{
|
||||||
|
*out_x = g_viewport.x();
|
||||||
|
*out_y = g_viewport.y();
|
||||||
|
*out_width = g_viewport.width();
|
||||||
|
*out_height = g_viewport.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::customShaderDraw()
|
||||||
|
{
|
||||||
|
auto viewport = applyAspect(QRect(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio()));
|
||||||
|
glViewport(viewport.x(), viewport.y(), viewport.width(), viewport.height());
|
||||||
|
g_viewport = viewport;
|
||||||
|
|
||||||
|
shader->render(texture, output_data.width, output_data.height, viewport.x(), viewport.y(), viewport.width(), viewport.height(), S9xViewportCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::createContext()
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto platform = QGuiApplication::platformName();
|
||||||
|
auto pni = QGuiApplication::platformNativeInterface();
|
||||||
|
QGuiApplication::sync();
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (platform == "wayland")
|
||||||
|
{
|
||||||
|
auto display = (wl_display *)pni->nativeResourceForWindow("display", windowHandle());
|
||||||
|
auto surface = (wl_surface *)pni->nativeResourceForWindow("surface", main_window->windowHandle());
|
||||||
|
auto wayland_egl_context = new WaylandEGLContext();
|
||||||
|
int s = devicePixelRatio();
|
||||||
|
|
||||||
|
if (!wayland_egl_context->attach(display, surface, { parent->x(), parent->y(), parent->width(), parent->height(), s }))
|
||||||
|
printf("Couldn't attach context to wayland surface.\n");
|
||||||
|
|
||||||
|
context.reset(wayland_egl_context);
|
||||||
|
}
|
||||||
|
else if (platform == "xcb")
|
||||||
|
{
|
||||||
|
auto display = (Display *)pni->nativeResourceForWindow("display", windowHandle());
|
||||||
|
auto xid = (Window)winId();
|
||||||
|
|
||||||
|
auto glx_context = new GTKGLXContext();
|
||||||
|
if (!glx_context->attach(display, xid))
|
||||||
|
printf("Couldn't attach to X11 window.\n");
|
||||||
|
|
||||||
|
context.reset(glx_context);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
auto hwnd = winId();
|
||||||
|
auto wgl_context = new WGLContext();
|
||||||
|
if (!wgl_context->attach((HWND)hwnd))
|
||||||
|
{
|
||||||
|
printf("Couldn't attach to context\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.reset(wgl_context);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!context->create_context())
|
||||||
|
{
|
||||||
|
printf("Couldn't create OpenGL context.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
context->make_current();
|
||||||
|
gladLoaderLoadGL();
|
||||||
|
|
||||||
|
if (config->display_messages == EmuConfig::eOnscreen)
|
||||||
|
{
|
||||||
|
auto defaults = S9xImGuiGetDefaults();
|
||||||
|
defaults.font_size = config->osd_size;
|
||||||
|
defaults.spacing = defaults.font_size / 2.4;
|
||||||
|
S9xImGuiInit(&defaults);
|
||||||
|
ImGui_ImplOpenGL3_Init();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadShaders();
|
||||||
|
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
|
||||||
|
GLuint vao;
|
||||||
|
glGenVertexArrays(1, &vao);
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
|
||||||
|
glGenBuffers(1, &stock_coord_buffer);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, stock_coord_buffer);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 16, coords, GL_STATIC_DRAW);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
|
||||||
|
context->swap_interval(config->enable_vsync ? 1 : 0);
|
||||||
|
QGuiApplication::sync();
|
||||||
|
paintEvent(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::loadShaders()
|
||||||
|
{
|
||||||
|
auto endswith = [&](std::string ext) ->bool {
|
||||||
|
return config->shader.rfind(ext) == config->shader.length() - ext.length();
|
||||||
|
};
|
||||||
|
using_shader = true;
|
||||||
|
if (!config->use_shader ||
|
||||||
|
!(endswith(".glslp") || endswith(".slangp")))
|
||||||
|
using_shader = false;
|
||||||
|
|
||||||
|
if (!using_shader)
|
||||||
|
{
|
||||||
|
createStockShaders();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setlocale(LC_NUMERIC, "C");
|
||||||
|
shader = std::make_unique<GLSLShader>();
|
||||||
|
if (!shader->load_shader(config->shader.c_str()))
|
||||||
|
{
|
||||||
|
shader.reset();
|
||||||
|
using_shader = false;
|
||||||
|
createStockShaders();
|
||||||
|
}
|
||||||
|
setlocale(LC_NUMERIC, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::uploadTexture()
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
GLuint filter = config->bilinear_filter ? GL_LINEAR : GL_NEAREST;
|
||||||
|
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, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, output_data.bytes_per_line / 2);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, output_data.width, output_data.height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, output_data.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::draw()
|
||||||
|
{
|
||||||
|
if (!isVisible() || !context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
context->make_current();
|
||||||
|
|
||||||
|
uploadTexture();
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, stock_coord_buffer);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (const void *)32);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
if (!using_shader)
|
||||||
|
stockShaderDraw();
|
||||||
|
else
|
||||||
|
customShaderDraw();
|
||||||
|
|
||||||
|
if (S9xImGuiRunning())
|
||||||
|
{
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
if (context->width <= 0)
|
||||||
|
context->width = width() * devicePixelRatioF();
|
||||||
|
if (context->height <= 0)
|
||||||
|
context->height = height() * devicePixelRatioF();
|
||||||
|
if (S9xImGuiDraw(context->width, context->height))
|
||||||
|
{
|
||||||
|
auto *draw_data = ImGui::GetDrawData();
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(draw_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->speed_sync_method == EmuConfig::eTimer || config->speed_sync_method == EmuConfig::eTimerWithFrameskip)
|
||||||
|
throttle();
|
||||||
|
|
||||||
|
context->swap_buffers();
|
||||||
|
|
||||||
|
if (config->reduce_input_lag)
|
||||||
|
glFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
if (!context) return;
|
||||||
|
|
||||||
|
auto g = parent->geometry();
|
||||||
|
int s = devicePixelRatio();
|
||||||
|
auto platform = QGuiApplication::platformName();
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (QGuiApplication::platformName() == "wayland")
|
||||||
|
((WaylandEGLContext *)context.get())->resize({ g.x(), g.y(), g.width(), g.height(), s });
|
||||||
|
else if (platform == "xcb")
|
||||||
|
((GTKGLXContext *)context.get())->resize();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
// TODO: If emu not running
|
||||||
|
if (!context || !isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (output_data.ready)
|
||||||
|
{
|
||||||
|
if (!static_cast<EmuMainWindow *>(main_window)->isActivelyDrawing())
|
||||||
|
draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
context->swap_buffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::deinit()
|
||||||
|
{
|
||||||
|
shader_parameters_dialog.reset();
|
||||||
|
context.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::shaderChanged()
|
||||||
|
{
|
||||||
|
shader_parameters_dialog.reset();
|
||||||
|
|
||||||
|
if (shader)
|
||||||
|
shader.reset();
|
||||||
|
else
|
||||||
|
glDeleteProgram(stock_program);
|
||||||
|
|
||||||
|
loadShaders();
|
||||||
|
paintEvent(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::showParametersDialog()
|
||||||
|
{
|
||||||
|
if (!using_shader)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("OpenGL Driver"), tr("The driver isn't using a specialized shader preset right now."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shader && shader->param.empty())
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("OpenGL Driver"), tr("This shader preset doesn't offer any configurable parameters."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parameters = reinterpret_cast<std::vector<EmuCanvas::Parameter> *>(&shader->param);
|
||||||
|
|
||||||
|
if (!shader_parameters_dialog)
|
||||||
|
shader_parameters_dialog =
|
||||||
|
std::make_unique<ShaderParametersDialog>(this, parameters);
|
||||||
|
|
||||||
|
shader_parameters_dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasOpenGL::saveParameters(std::string filename)
|
||||||
|
{
|
||||||
|
if (shader)
|
||||||
|
shader->save(filename.c_str());
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
#ifndef __EMU_CANVAS_OPENGL_HPP
|
||||||
|
#define __EMU_CANVAS_OPENGL_HPP
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
#include "ShaderParametersDialog.hpp"
|
||||||
|
|
||||||
|
class OpenGLContext;
|
||||||
|
class GLSLShader;
|
||||||
|
|
||||||
|
class EmuCanvasOpenGL : public EmuCanvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuCanvasOpenGL(EmuConfig *config, QWidget *parent, QWidget *main_window);
|
||||||
|
~EmuCanvasOpenGL();
|
||||||
|
|
||||||
|
void deinit() override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
QPaintEngine * paintEngine() const override { return nullptr; }
|
||||||
|
void draw() override;
|
||||||
|
void shaderChanged() override;
|
||||||
|
void showParametersDialog() override;
|
||||||
|
void saveParameters(std::string filename) override;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void resizeTexture(int width, int height);
|
||||||
|
void createContext();
|
||||||
|
void createStockShaders();
|
||||||
|
void stockShaderDraw();
|
||||||
|
void customShaderDraw();
|
||||||
|
void uploadTexture();
|
||||||
|
void loadShaders();
|
||||||
|
|
||||||
|
unsigned int stock_program;
|
||||||
|
unsigned int texture;
|
||||||
|
unsigned stock_coord_buffer;
|
||||||
|
std::unique_ptr<OpenGLContext> context;
|
||||||
|
bool using_shader;
|
||||||
|
std::unique_ptr<GLSLShader> shader;
|
||||||
|
std::unique_ptr<ShaderParametersDialog> shader_parameters_dialog;
|
||||||
|
|
||||||
|
// The first 8 values are vertices for a triangle strip, the second are texture
|
||||||
|
// coordinates for a stock NPOT texture.
|
||||||
|
const float coords[16] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
|
||||||
|
0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include "EmuCanvasQt.hpp"
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QtEvents>
|
||||||
|
|
||||||
|
EmuCanvasQt::EmuCanvasQt(EmuConfig *config, QWidget *parent, QWidget *main_window)
|
||||||
|
: EmuCanvas(config, parent, main_window)
|
||||||
|
{
|
||||||
|
setMinimumSize(256, 224);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuCanvasQt::~EmuCanvasQt()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasQt::deinit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasQt::draw()
|
||||||
|
{
|
||||||
|
QWidget::repaint(0, 0, width(), height());
|
||||||
|
throttle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasQt::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
// TODO: If emu not running
|
||||||
|
if (!output_data.ready)
|
||||||
|
{
|
||||||
|
QPainter paint(this);
|
||||||
|
paint.fillRect(QRect(0, 0, width(), height()), QBrush(QColor(0, 0, 0)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainter paint(this);
|
||||||
|
QImage image((const uchar *)output_data.buffer, output_data.width, output_data.height, output_data.bytes_per_line, output_data.format);
|
||||||
|
paint.setRenderHint(QPainter::SmoothPixmapTransform, config->bilinear_filter);
|
||||||
|
QRect dest = { 0, 0, width(), height() };
|
||||||
|
if (config->maintain_aspect_ratio)
|
||||||
|
{
|
||||||
|
paint.fillRect(QRect(0, 0, width(), height()), QBrush(QColor(0, 0, 0)));
|
||||||
|
dest = applyAspect(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
paint.drawImage(dest, image, QRect(0, 0, output_data.width, output_data.height));
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef __EMU_CANVAS_QT_HPP
|
||||||
|
#define __EMU_CANVAS_QT_HPP
|
||||||
|
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
class EmuCanvasQt : public EmuCanvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuCanvasQt(EmuConfig *config, QWidget *parent, QWidget *main_window);
|
||||||
|
~EmuCanvasQt();
|
||||||
|
|
||||||
|
virtual void deinit() override;
|
||||||
|
virtual void draw() override;
|
||||||
|
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,305 @@
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QtEvents>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include "EmuCanvasVulkan.hpp"
|
||||||
|
#include "src/ShaderParametersDialog.hpp"
|
||||||
|
#include "EmuMainWindow.hpp"
|
||||||
|
|
||||||
|
#include "snes9x_imgui.h"
|
||||||
|
#include "imgui_impl_vulkan.h"
|
||||||
|
|
||||||
|
using namespace QNativeInterface;
|
||||||
|
|
||||||
|
EmuCanvasVulkan::EmuCanvasVulkan(EmuConfig *config, QWidget *parent, QWidget *main_window)
|
||||||
|
: EmuCanvas(config, parent, main_window)
|
||||||
|
{
|
||||||
|
setMinimumSize(256, 224);
|
||||||
|
setUpdatesEnabled(false);
|
||||||
|
setAutoFillBackground(false);
|
||||||
|
setAttribute(Qt::WA_NoSystemBackground, true);
|
||||||
|
setAttribute(Qt::WA_NativeWindow, true);
|
||||||
|
setAttribute(Qt::WA_PaintOnScreen, true);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
createWinId();
|
||||||
|
window = windowHandle();
|
||||||
|
|
||||||
|
auto timer = new QTimer(this);
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
timer->callOnTimeout([&]{ createContext(); });
|
||||||
|
timer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuCanvasVulkan::~EmuCanvasVulkan()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuCanvasVulkan::initImGui()
|
||||||
|
{
|
||||||
|
auto defaults = S9xImGuiGetDefaults();
|
||||||
|
defaults.font_size = config->osd_size;
|
||||||
|
defaults.spacing = defaults.font_size / 2.4;
|
||||||
|
S9xImGuiInit(&defaults);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_LoadFunctions([](const char *function, void *instance) {
|
||||||
|
return VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr(*((VkInstance *)instance), function);
|
||||||
|
}, &context->instance.get());
|
||||||
|
|
||||||
|
vk::DescriptorPoolSize pool_sizes[] =
|
||||||
|
{
|
||||||
|
{ vk::DescriptorType::eCombinedImageSampler, 1000 },
|
||||||
|
{ vk::DescriptorType::eUniformBuffer, 1000 }
|
||||||
|
};
|
||||||
|
auto descriptor_pool_create_info = vk::DescriptorPoolCreateInfo{}
|
||||||
|
.setPoolSizes(pool_sizes)
|
||||||
|
.setMaxSets(1000)
|
||||||
|
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
||||||
|
imgui_descriptor_pool = context->device.createDescriptorPoolUnique(descriptor_pool_create_info);
|
||||||
|
|
||||||
|
ImGui_ImplVulkan_InitInfo init_info{};
|
||||||
|
init_info.Instance = context->instance.get();
|
||||||
|
init_info.PhysicalDevice = context->physical_device;
|
||||||
|
init_info.Device = context->device;;
|
||||||
|
init_info.QueueFamily = context->graphics_queue_family_index;
|
||||||
|
init_info.Queue = context->queue;
|
||||||
|
init_info.DescriptorPool = imgui_descriptor_pool.get();
|
||||||
|
init_info.Subpass = 0;
|
||||||
|
init_info.MinImageCount = context->swapchain->get_num_frames();
|
||||||
|
init_info.ImageCount = context->swapchain->get_num_frames();
|
||||||
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
ImGui_ImplVulkan_Init(&init_info, context->swapchain->get_render_pass());
|
||||||
|
|
||||||
|
auto cmd = context->begin_cmd_buffer();
|
||||||
|
ImGui_ImplVulkan_CreateFontsTexture(cmd);
|
||||||
|
context->end_cmd_buffer();
|
||||||
|
context->wait_idle();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::createContext()
|
||||||
|
{
|
||||||
|
if (simple_output)
|
||||||
|
return;
|
||||||
|
|
||||||
|
platform = QGuiApplication::platformName();
|
||||||
|
auto pni = QGuiApplication::platformNativeInterface();
|
||||||
|
setVisible(true);
|
||||||
|
QGuiApplication::sync();
|
||||||
|
|
||||||
|
context = std::make_unique<Vulkan::Context>();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
auto hwnd = (HWND)winId();
|
||||||
|
context->init_win32(nullptr, hwnd, config->display_device_index);
|
||||||
|
#else
|
||||||
|
if (platform == "wayland")
|
||||||
|
{
|
||||||
|
wayland_surface = std::make_unique<WaylandSurface>();
|
||||||
|
auto display = (wl_display *)pni->nativeResourceForWindow("display", window);
|
||||||
|
auto surface = (wl_surface *)pni->nativeResourceForWindow("surface", main_window->windowHandle());
|
||||||
|
wayland_surface->attach(display, surface, { parent->x(), parent->y(), width(), height(), static_cast<int>(devicePixelRatio()) });
|
||||||
|
auto [scaled_width, scaled_height] = wayland_surface->get_size();
|
||||||
|
context->init_wayland(display, wayland_surface->child, scaled_width, scaled_height, config->display_device_index);
|
||||||
|
}
|
||||||
|
else if (platform == "xcb")
|
||||||
|
{
|
||||||
|
auto display = (Display *)pni->nativeResourceForWindow("display", window);
|
||||||
|
auto xid = (Window)winId();
|
||||||
|
|
||||||
|
context->init_Xlib(display, xid, config->display_device_index);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (config->display_messages == EmuConfig::eOnscreen)
|
||||||
|
initImGui();
|
||||||
|
|
||||||
|
tryLoadShader();
|
||||||
|
|
||||||
|
QGuiApplication::sync();
|
||||||
|
paintEvent(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::tryLoadShader()
|
||||||
|
{
|
||||||
|
simple_output.reset();
|
||||||
|
shader_chain.reset();
|
||||||
|
shader_parameters_dialog.reset();
|
||||||
|
|
||||||
|
if (config->use_shader && !config->shader.empty())
|
||||||
|
{
|
||||||
|
shader_chain = std::make_unique<Vulkan::ShaderChain>(context.get());
|
||||||
|
setlocale(LC_NUMERIC, "C");
|
||||||
|
current_shader = config->shader;
|
||||||
|
if (!shader_chain->load_shader_preset(config->shader))
|
||||||
|
{
|
||||||
|
printf("Couldn't load shader preset: %s\n", config->shader.c_str());
|
||||||
|
shader_chain.reset();
|
||||||
|
}
|
||||||
|
setlocale(LC_NUMERIC, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shader_chain)
|
||||||
|
simple_output = std::make_unique<Vulkan::SimpleOutput>(context.get(), vk::Format::eR5G6B5UnormPack16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::shaderChanged()
|
||||||
|
{
|
||||||
|
if (!config->use_shader)
|
||||||
|
current_shader.clear();
|
||||||
|
|
||||||
|
if ((!config->use_shader && shader_chain) ||
|
||||||
|
(config->use_shader && current_shader != config->shader))
|
||||||
|
tryLoadShader();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::draw()
|
||||||
|
{
|
||||||
|
if (!context)
|
||||||
|
return;
|
||||||
|
if (!window->isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (S9xImGuiDraw(width() * devicePixelRatioF(), height() * devicePixelRatioF()))
|
||||||
|
{
|
||||||
|
auto draw_data = ImGui::GetDrawData();
|
||||||
|
context->swapchain->on_render_pass_end([&, draw_data] {
|
||||||
|
ImGui_ImplVulkan_RenderDrawData(draw_data, context->swapchain->get_cmd());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto viewport = applyAspect(QRect(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio()));
|
||||||
|
|
||||||
|
bool retval = false;
|
||||||
|
if (shader_chain)
|
||||||
|
{
|
||||||
|
retval = shader_chain->do_frame_without_swap(output_data.buffer, output_data.width, output_data.height, output_data.bytes_per_line, vk::Format::eR5G6B5UnormPack16, viewport.x(), viewport.y(), viewport.width(), viewport.height());
|
||||||
|
}
|
||||||
|
else if (simple_output)
|
||||||
|
{
|
||||||
|
simple_output->set_filter(config->bilinear_filter);
|
||||||
|
retval = simple_output->do_frame_without_swap(output_data.buffer, output_data.width, output_data.height, output_data.bytes_per_line, viewport.x(), viewport.y(), viewport.width(), viewport.height());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retval)
|
||||||
|
{
|
||||||
|
throttle();
|
||||||
|
context->swapchain->swap();
|
||||||
|
if (config->reduce_input_lag)
|
||||||
|
context->wait_idle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::resizeEvent(QResizeEvent *event)
|
||||||
|
{
|
||||||
|
QWidget::resizeEvent(event);
|
||||||
|
|
||||||
|
if (!context)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int width = event->size().width();
|
||||||
|
int height = event->size().height();
|
||||||
|
|
||||||
|
context->swapchain->set_vsync(config->enable_vsync);
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (platform == "wayland")
|
||||||
|
{
|
||||||
|
wayland_surface->resize({ parent->x(), parent->y(), width, height, (int)devicePixelRatio() });
|
||||||
|
std::tie(width, height) = wayland_surface->get_size();
|
||||||
|
// On Wayland, Vulkan WSI provides the buffer for the subsurface,
|
||||||
|
// so we have to specify a width and height instead of polling the parent.
|
||||||
|
context->recreate_swapchain(width, height);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
context->recreate_swapchain(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::paintEvent(QPaintEvent *event)
|
||||||
|
{
|
||||||
|
// TODO: If emu not running
|
||||||
|
if (!context || !isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto window = (EmuMainWindow *)main_window;
|
||||||
|
if (output_data.ready)
|
||||||
|
{
|
||||||
|
if (!window->isActivelyDrawing())
|
||||||
|
draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear to black
|
||||||
|
uint8_t buffer[] = { 0, 0, 0, 0 };
|
||||||
|
if (shader_chain)
|
||||||
|
shader_chain->do_frame(buffer, 1, 1, 1, vk::Format::eR5G6B5UnormPack16, 0, 0, width(), height());
|
||||||
|
if (simple_output)
|
||||||
|
simple_output->do_frame(buffer, 1, 1, 1, 0, 0, width(), height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::deinit()
|
||||||
|
{
|
||||||
|
shader_parameters_dialog.reset();
|
||||||
|
|
||||||
|
if (ImGui::GetCurrentContext())
|
||||||
|
{
|
||||||
|
context->wait_idle();
|
||||||
|
imgui_descriptor_pool.reset();
|
||||||
|
imgui_render_pass.reset();
|
||||||
|
ImGui_ImplVulkan_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_output.reset();
|
||||||
|
shader_chain.reset();
|
||||||
|
context.reset();
|
||||||
|
#ifndef _WIN32
|
||||||
|
wayland_surface.reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> EmuCanvasVulkan::getDeviceList()
|
||||||
|
{
|
||||||
|
return Vulkan::Context::get_device_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::showParametersDialog()
|
||||||
|
{
|
||||||
|
if (!context)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Vulkan Driver"), tr("The vulkan display driver hasn't properly loaded."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shader_chain)
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, tr("Vulkan Driver"), tr("The driver isn't using a specialized shader preset right now."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shader_chain && shader_chain->preset->parameters.empty())
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("Vulkan Driver"), tr("This shader preset doesn't offer any configurable parameters."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parameters = reinterpret_cast<std::vector<EmuCanvas::Parameter> *>(&shader_chain->preset->parameters);
|
||||||
|
|
||||||
|
if (!shader_parameters_dialog)
|
||||||
|
shader_parameters_dialog =
|
||||||
|
std::make_unique<ShaderParametersDialog>(this, parameters);
|
||||||
|
|
||||||
|
shader_parameters_dialog->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuCanvasVulkan::saveParameters(std::string filename)
|
||||||
|
{
|
||||||
|
if (shader_chain && shader_chain->preset)
|
||||||
|
shader_chain->preset->save_to_file(filename);
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
#include "ShaderParametersDialog.hpp"
|
||||||
|
#include "../../vulkan/vulkan_simple_output.hpp"
|
||||||
|
#include "../../vulkan/vulkan_shader_chain.hpp"
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include "common/video/wayland_surface.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class EmuCanvasVulkan : public EmuCanvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuCanvasVulkan(EmuConfig *config, QWidget *parent, QWidget *main_window);
|
||||||
|
~EmuCanvasVulkan();
|
||||||
|
|
||||||
|
void deinit() override;
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
|
QPaintEngine *paintEngine() const override { return nullptr; }
|
||||||
|
|
||||||
|
std::vector<std::string> getDeviceList() override;
|
||||||
|
void shaderChanged() override;
|
||||||
|
void showParametersDialog() override;
|
||||||
|
void saveParameters(std::string filename) override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
|
||||||
|
bool initImGui();
|
||||||
|
vk::UniqueRenderPass imgui_render_pass;
|
||||||
|
vk::UniqueDescriptorPool imgui_descriptor_pool;
|
||||||
|
|
||||||
|
std::unique_ptr<Vulkan::Context> context;
|
||||||
|
std::unique_ptr<Vulkan::SimpleOutput> simple_output;
|
||||||
|
std::unique_ptr<Vulkan::ShaderChain> shader_chain;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createContext();
|
||||||
|
void tryLoadShader();
|
||||||
|
std::string current_shader;
|
||||||
|
QWindow *window = nullptr;
|
||||||
|
std::unique_ptr<ShaderParametersDialog> shader_parameters_dialog = nullptr;
|
||||||
|
QString platform;
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
std::unique_ptr<WaylandSurface> wayland_surface;
|
||||||
|
#endif
|
||||||
|
};
|
|
@ -0,0 +1,560 @@
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#define TOML_LARGE_FILES 1
|
||||||
|
#define TOML_IMPLEMENTATION 1
|
||||||
|
#include <fstream>
|
||||||
|
#include "toml.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
#include "EmuBinding.hpp"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
static const char *shortcut_names[] =
|
||||||
|
{
|
||||||
|
"OpenROM",
|
||||||
|
"EmuTurbo",
|
||||||
|
"ToggleEmuTurbo",
|
||||||
|
"PauseContinue",
|
||||||
|
"SoftReset",
|
||||||
|
"Reset",
|
||||||
|
"Quit",
|
||||||
|
"ToggleFullscreen",
|
||||||
|
"Screenshot",
|
||||||
|
"SaveSPC",
|
||||||
|
"SaveState",
|
||||||
|
"LoadState",
|
||||||
|
"IncreaseSlot",
|
||||||
|
"DecreaseSlot",
|
||||||
|
"QuickSave000",
|
||||||
|
"QuickSave001",
|
||||||
|
"QuickSave002",
|
||||||
|
"QuickSave003",
|
||||||
|
"QuickSave004",
|
||||||
|
"QuickSave005",
|
||||||
|
"QuickSave006",
|
||||||
|
"QuickSave007",
|
||||||
|
"QuickSave008",
|
||||||
|
"QuickSave009",
|
||||||
|
"QuickLoad000",
|
||||||
|
"QuickLoad001",
|
||||||
|
"QuickLoad002",
|
||||||
|
"QuickLoad003",
|
||||||
|
"QuickLoad004",
|
||||||
|
"QuickLoad005",
|
||||||
|
"QuickLoad006",
|
||||||
|
"QuickLoad007",
|
||||||
|
"QuickLoad008",
|
||||||
|
"QuickLoad009",
|
||||||
|
"Rewind",
|
||||||
|
"GrabMouse",
|
||||||
|
"SwapControllers1and2",
|
||||||
|
"ToggleBG0",
|
||||||
|
"ToggleBG1",
|
||||||
|
"ToggleBG2",
|
||||||
|
"ToggleBG3",
|
||||||
|
"ToggleSprites",
|
||||||
|
"ToggleBackdrop",
|
||||||
|
"SoundChannel0",
|
||||||
|
"SoundChannel1",
|
||||||
|
"SoundChannel2",
|
||||||
|
"SoundChannel3",
|
||||||
|
"SoundChannel4",
|
||||||
|
"SoundChannel5",
|
||||||
|
"SoundChannel6",
|
||||||
|
"SoundChannel7",
|
||||||
|
"SoundChannelsOn",
|
||||||
|
"BeginRecordingMovie",
|
||||||
|
"EndRecordingMovie",
|
||||||
|
"SeekToFrame",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *default_controller_keys[] =
|
||||||
|
{
|
||||||
|
"Keyboard Ctrl+o", // eOpenROM
|
||||||
|
"Keyboard Tab", // eFastForward
|
||||||
|
"Keyboard `", // eToggleFastForward
|
||||||
|
"Keyboard p", // ePauseContinue
|
||||||
|
"Keyboard Ctrl+r", // eSoftReset
|
||||||
|
"", // ePowerCycle
|
||||||
|
"Keyboard Ctrl+q", // eQuit
|
||||||
|
"Keyboard F11", // eToggleFullscreen
|
||||||
|
"", // eSaveScreenshot
|
||||||
|
"", // eSaveSPC
|
||||||
|
"Keyboard F2", // eSaveState
|
||||||
|
"Keyboard F4", // eLoadState
|
||||||
|
"Keyboard F6", // eIncreaseSlot
|
||||||
|
"Keyboard F5", // eDecreaseSlot
|
||||||
|
"Keyboard 0", // eSaveState0
|
||||||
|
"Keyboard 1", // eSaveState1
|
||||||
|
"Keyboard 2", // eSaveState2
|
||||||
|
"Keyboard 3", // eSaveState3
|
||||||
|
"Keyboard 4", // eSaveState4
|
||||||
|
"Keyboard 5", // eSaveState5
|
||||||
|
"Keyboard 6", // eSaveState6
|
||||||
|
"Keyboard 7", // eSaveState7
|
||||||
|
"Keyboard 8", // eSaveState8
|
||||||
|
"Keyboard 9", // eSaveState9
|
||||||
|
"Keyboard Ctrl+0", // eLoadState0
|
||||||
|
"Keyboard Ctrl+1", // eLoadState1
|
||||||
|
"Keyboard Ctrl+2", // eLoadState2
|
||||||
|
"Keyboard Ctrl+3", // eLoadState3
|
||||||
|
"Keyboard Ctrl+4", // eLoadState4
|
||||||
|
"Keyboard Ctrl+5", // eLoadState5
|
||||||
|
"Keyboard Ctrl+6", // eLoadState6
|
||||||
|
"Keyboard Ctrl+7", // eLoadState7
|
||||||
|
"Keyboard Ctrl+8", // eLoadState8
|
||||||
|
"Keyboard Ctrl+9", // eLoadState9
|
||||||
|
"", // eRewind
|
||||||
|
"Keyboard Ctrl+g", // eGrabMouse
|
||||||
|
"", // eSwapControllers1and2
|
||||||
|
"", // eToggleBG0
|
||||||
|
"", // eToggleBG1
|
||||||
|
"", // eToggleBG2
|
||||||
|
"", // eToggleBG3
|
||||||
|
"", // eToggleSprites
|
||||||
|
"", // eChangeBackdrop
|
||||||
|
"", // eToggleSoundChannel1
|
||||||
|
"", // eToggleSoundChannel2
|
||||||
|
"", // eToggleSoundChannel3
|
||||||
|
"", // eToggleSoundChannel4
|
||||||
|
"", // eToggleSoundChannel5
|
||||||
|
"", // eToggleSoundChannel6
|
||||||
|
"", // eToggleSoundChannel7
|
||||||
|
"", // eToggleSoundChannel8
|
||||||
|
"", // eToggleAllSoundChannels
|
||||||
|
"", // eStartRecording
|
||||||
|
"", // eStopRecording
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
const char **EmuConfig::getDefaultShortcutKeys()
|
||||||
|
{
|
||||||
|
return default_controller_keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char **EmuConfig::getShortcutNames()
|
||||||
|
{
|
||||||
|
return shortcut_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EmuConfig::findConfigDir()
|
||||||
|
{
|
||||||
|
char *dir;
|
||||||
|
fs::path path;
|
||||||
|
|
||||||
|
if ((dir = getenv("XDG_CONFIG_HOME")))
|
||||||
|
{
|
||||||
|
path = dir;
|
||||||
|
path /= "snes9x";
|
||||||
|
}
|
||||||
|
else if ((dir = getenv("HOME")))
|
||||||
|
{
|
||||||
|
path = dir;
|
||||||
|
path /= ".config/snes9x";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = "./.snes9x";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs::exists(path))
|
||||||
|
fs::create_directory(path);
|
||||||
|
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EmuConfig::findConfigFile()
|
||||||
|
{
|
||||||
|
fs::path path(findConfigDir());
|
||||||
|
path /= "snes9x-qt.conf";
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuConfig::setDefaults(int section)
|
||||||
|
{
|
||||||
|
if (section == -1 || section == 0)
|
||||||
|
{
|
||||||
|
// General
|
||||||
|
fullscreen_on_open = false;
|
||||||
|
disable_screensaver = true;
|
||||||
|
pause_emulation_when_unfocused = true;
|
||||||
|
|
||||||
|
show_frame_rate = false;
|
||||||
|
show_indicators = true;
|
||||||
|
show_pressed_keys = false;
|
||||||
|
show_time = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 1)
|
||||||
|
{
|
||||||
|
// Display
|
||||||
|
display_driver = {};
|
||||||
|
display_device_index = 0;
|
||||||
|
enable_vsync = true;
|
||||||
|
;
|
||||||
|
bilinear_filter = true;
|
||||||
|
;
|
||||||
|
reduce_input_lag = true;
|
||||||
|
adjust_for_vrr = false;
|
||||||
|
use_shader = false;
|
||||||
|
shader = {};
|
||||||
|
last_shader_folder = {};
|
||||||
|
|
||||||
|
scale_image = true;
|
||||||
|
;
|
||||||
|
maintain_aspect_ratio = true;
|
||||||
|
use_integer_scaling = false;
|
||||||
|
aspect_ratio_numerator = 4;
|
||||||
|
aspect_ratio_denominator = 3;
|
||||||
|
show_overscan = false;
|
||||||
|
high_resolution_effect = eLeaveAlone;
|
||||||
|
|
||||||
|
software_filter = {};
|
||||||
|
|
||||||
|
display_messages = eOnscreen;
|
||||||
|
osd_size = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 2)
|
||||||
|
{
|
||||||
|
// Sound
|
||||||
|
sound_driver = {};
|
||||||
|
sound_device = {};
|
||||||
|
playback_rate = 48000;
|
||||||
|
audio_buffer_size_ms = 64;
|
||||||
|
|
||||||
|
adjust_input_rate_automatically = true;
|
||||||
|
input_rate = 31979;
|
||||||
|
dynamic_rate_control = false;
|
||||||
|
dynamic_rate_limit = 0.005;
|
||||||
|
mute_audio = false;
|
||||||
|
mute_audio_during_alternate_speed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 3)
|
||||||
|
{
|
||||||
|
speed_sync_method = eTimer;
|
||||||
|
fixed_frame_rate = 0.0;
|
||||||
|
fast_forward_skip_frames = 9;
|
||||||
|
|
||||||
|
rewind_buffer_size = 0;
|
||||||
|
rewind_frame_interval = 5;
|
||||||
|
|
||||||
|
allow_invalid_vram_access = false;
|
||||||
|
allow_opposing_dpad_directions = false;
|
||||||
|
overclock = false;
|
||||||
|
remove_sprite_limit = false;
|
||||||
|
enable_shadow_buffer = false;
|
||||||
|
superfx_clock_multiplier = 100;
|
||||||
|
sound_filter = eGaussian;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 4)
|
||||||
|
{
|
||||||
|
// Controllers
|
||||||
|
memset(binding.controller, 0, sizeof(binding.controller));
|
||||||
|
|
||||||
|
const char *button_list[] = { "Up", "Down", "Left", "Right", "d", "c", "s", "x", "z", "a", "Return", "Space" };
|
||||||
|
for (int i = 0; i < std::size(button_list); i++)
|
||||||
|
{
|
||||||
|
binding.controller[0].buttons[i * 4] = EmuBinding::from_config_string("Keyboard " + std::string(button_list[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 5)
|
||||||
|
{
|
||||||
|
// Shortcuts
|
||||||
|
memset(binding.shortcuts, 0, sizeof(binding.shortcuts));
|
||||||
|
for (auto i = 0; i < num_shortcuts; i++)
|
||||||
|
{
|
||||||
|
binding.shortcuts[i * 4] = EmuBinding::from_config_string(getDefaultShortcutKeys()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == -1 || section == 6)
|
||||||
|
{
|
||||||
|
// Files
|
||||||
|
sram_folder = {};
|
||||||
|
state_folder = {};
|
||||||
|
cheat_folder = {};
|
||||||
|
patch_folder = {};
|
||||||
|
export_folder = {};
|
||||||
|
|
||||||
|
sram_location = eROMDirectory;
|
||||||
|
state_location = eROMDirectory;
|
||||||
|
cheat_location = eROMDirectory;
|
||||||
|
patch_location = eROMDirectory;
|
||||||
|
export_location = eROMDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuConfig::config(std::string filename, bool write)
|
||||||
|
{
|
||||||
|
toml::table root;
|
||||||
|
toml::table *table = nullptr;
|
||||||
|
std::string section;
|
||||||
|
|
||||||
|
std::function<void(std::string, bool &)> Bool;
|
||||||
|
std::function<void(std::string, int &)> Int;
|
||||||
|
std::function<void(std::string, std::string &)> String;
|
||||||
|
std::function<void(std::string, int &, std::vector<const char *>)> Enum;
|
||||||
|
std::function<void(std::string, double &)> Double;
|
||||||
|
std::function<void(std::string, EmuBinding &)> Binding;
|
||||||
|
std::function<void(std::string)> BeginSection;
|
||||||
|
std::function<void()> EndSection;
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
{
|
||||||
|
Bool = [&](std::string key, bool &value) {
|
||||||
|
table->insert_or_assign(key, value);
|
||||||
|
};
|
||||||
|
Int = [&](std::string key, int &value) {
|
||||||
|
table->insert_or_assign(key, value);
|
||||||
|
};
|
||||||
|
String = [&](std::string key, std::string &value) {
|
||||||
|
table->insert_or_assign(key, value);
|
||||||
|
};
|
||||||
|
Enum = [&](std::string key, int &value, std::vector<const char *> map) {
|
||||||
|
table->insert_or_assign(key, map[value]);
|
||||||
|
};
|
||||||
|
Double = [&](std::string key, double &value) {
|
||||||
|
table->insert_or_assign(key, value);
|
||||||
|
};
|
||||||
|
Binding = [&](std::string key, EmuBinding &binding) {
|
||||||
|
table->insert_or_assign(key, binding.to_config_string());
|
||||||
|
};
|
||||||
|
BeginSection = [&](std::string str) {
|
||||||
|
section = str;
|
||||||
|
table = new toml::table;
|
||||||
|
};
|
||||||
|
|
||||||
|
EndSection = [&]() {
|
||||||
|
root.insert_or_assign(section, *table);
|
||||||
|
delete table;
|
||||||
|
};
|
||||||
|
|
||||||
|
root.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Bool = [&](std::string key, bool &value) {
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_boolean())
|
||||||
|
value = table->get(key)->as_boolean()->get();
|
||||||
|
};
|
||||||
|
Int = [&](std::string key, int &value) {
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_integer())
|
||||||
|
value = table->get(key)->as_integer()->get();
|
||||||
|
};
|
||||||
|
String = [&](std::string key, std::string &value) {
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_string())
|
||||||
|
value = table->get(key)->as_string()->get();
|
||||||
|
};
|
||||||
|
Binding = [&](std::string key, EmuBinding &binding) {
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_string())
|
||||||
|
binding = EmuBinding::from_config_string(table->get(key)->as_string()->get());
|
||||||
|
};
|
||||||
|
Double = [&](std::string key, double &value) {
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_floating_point())
|
||||||
|
value = table->get(key)->as_floating_point()->get();
|
||||||
|
};
|
||||||
|
Enum = [&](std::string key, int &value, std::vector<const char *> map) {
|
||||||
|
std::string entry;
|
||||||
|
|
||||||
|
if (table && table->contains(key) && table->get(key)->is_string())
|
||||||
|
entry = table->get(key)->as_string()->get();
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto tolower = [](std::string str) -> std::string {
|
||||||
|
for (auto &c : str)
|
||||||
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
c += ('a' - 'A');
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
entry = tolower(entry);
|
||||||
|
for (size_t i = 0; i < map.size(); i++)
|
||||||
|
{
|
||||||
|
if (tolower(map[i]) == entry)
|
||||||
|
{
|
||||||
|
value = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
BeginSection = [&](std::string str) {
|
||||||
|
section = str;
|
||||||
|
auto root_section = root.get(section);
|
||||||
|
if (root_section)
|
||||||
|
table = root_section->as_table();
|
||||||
|
else
|
||||||
|
table = nullptr;
|
||||||
|
};
|
||||||
|
EndSection = [&]() {
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_result = toml::parse_file(filename);
|
||||||
|
if (parse_result.failed())
|
||||||
|
return;
|
||||||
|
root = std::move(parse_result.table());
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginSection("Operational");
|
||||||
|
String("LastROMFolder", last_rom_folder);
|
||||||
|
Int("MainWindowWidth", main_window_width);
|
||||||
|
Int("MainWindowHeight", main_window_height);
|
||||||
|
int recent_count = recently_used.size();
|
||||||
|
Int("RecentlyUsedEntries", recent_count);
|
||||||
|
if (!write)
|
||||||
|
recently_used.resize(recent_count);
|
||||||
|
for (int i = 0; i < recent_count; i++)
|
||||||
|
{
|
||||||
|
String("RecentlyUsed" + std::to_string(i), recently_used[i]);
|
||||||
|
}
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
BeginSection("General");
|
||||||
|
Bool("FullscreenOnOpen", fullscreen_on_open);
|
||||||
|
Bool("DisableScreensaver", disable_screensaver);
|
||||||
|
Bool("PauseEmulationWhenUnfocused", pause_emulation_when_unfocused);
|
||||||
|
|
||||||
|
Bool("ShowFrameRate", show_frame_rate);
|
||||||
|
Bool("ShowIndicators", show_indicators);
|
||||||
|
Bool("ShowPressedKeys", show_pressed_keys);
|
||||||
|
Bool("ShowTime", show_time);
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
BeginSection("Display");
|
||||||
|
String("DisplayDriver", display_driver);
|
||||||
|
Int("DisplayDevice", display_device_index);
|
||||||
|
Bool("VSync", enable_vsync);
|
||||||
|
Bool("ReduceInputLag", reduce_input_lag);
|
||||||
|
Bool("BilinearFilter", bilinear_filter);
|
||||||
|
Bool("AdjustForVRR", adjust_for_vrr);
|
||||||
|
Bool("UseShader", use_shader);
|
||||||
|
String("Shader", shader);
|
||||||
|
String("LastShaderFolder", last_shader_folder);
|
||||||
|
|
||||||
|
Bool("ScaleImage", scale_image);
|
||||||
|
Bool("MaintainAspectRatio", maintain_aspect_ratio);
|
||||||
|
Bool("UseIntegerScaling", use_integer_scaling);
|
||||||
|
Int("AspectRatioNumerator", aspect_ratio_numerator);
|
||||||
|
Int("AspectRatioDenominator", aspect_ratio_denominator);
|
||||||
|
Bool("ShowOverscan", show_overscan);
|
||||||
|
Enum("HighResolutionEffect", high_resolution_effect, { "LeaveAlone", "ScaleDown", "ScaleUp" });
|
||||||
|
|
||||||
|
String("SoftwareFilter", software_filter);
|
||||||
|
|
||||||
|
Enum("DisplayMessages", display_messages, { "Onscreen", "Inscreen", "None" });
|
||||||
|
Int("OSDSize", osd_size);
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
BeginSection("Sound");
|
||||||
|
String("SoundDriver", sound_driver);
|
||||||
|
String("SoundDevice", sound_device);
|
||||||
|
Int("PlaybackRate", playback_rate);
|
||||||
|
Int("BufferSize", audio_buffer_size_ms);
|
||||||
|
Bool("AdjustInputRateAutomatically", adjust_input_rate_automatically);
|
||||||
|
Int("InputRate", input_rate);
|
||||||
|
Bool("DynamicRateControl", dynamic_rate_control);
|
||||||
|
Double("DynamicRateLimit", dynamic_rate_limit);
|
||||||
|
Bool("Mute", mute_audio);
|
||||||
|
Bool("MuteAudioDuringAlternateSpeed", mute_audio_during_alternate_speed);
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
BeginSection("Emulation");
|
||||||
|
Enum("SpeedSyncMethod", speed_sync_method, { "Timer", "TimerFrameskip", "SoundSync", "Unlimited" });
|
||||||
|
Double("FixedFrameRate", fixed_frame_rate);
|
||||||
|
Int("FastForwardSkipFrames", fast_forward_skip_frames);
|
||||||
|
Int("RewindBufferSize", rewind_buffer_size);
|
||||||
|
Int("RewindFrameInterval", rewind_frame_interval);
|
||||||
|
Bool("AllowInvalidVRAMAccess", allow_invalid_vram_access);
|
||||||
|
Bool("AllowOpposingDpadDirections", allow_opposing_dpad_directions);
|
||||||
|
Bool("Overclock", overclock);
|
||||||
|
Bool("RemoveSpriteLimit", remove_sprite_limit);
|
||||||
|
Bool("EnableShadowBuffer", enable_shadow_buffer);
|
||||||
|
Int("SuperFXClockMultiplier", superfx_clock_multiplier);
|
||||||
|
Enum("SoundFilter", sound_filter, { "Gaussian", "Nearest", "Linear", "Cubic", "Sinc" });
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
const char *names[] = { "Up", "Down", "Left", "Right", "A", "B", "X", "Y", "L", "R", "Start", "Select", "Turbo A", "Turbo B", "Turbo X", "Turbo Y", "Turbo L", "Turbo R" };
|
||||||
|
for (int c = 0; c < 5; c++)
|
||||||
|
{
|
||||||
|
BeginSection("Controller " + std::to_string(c));
|
||||||
|
|
||||||
|
for (int y = 0; y < num_controller_bindings; y++)
|
||||||
|
for (int x = 0; x < allowed_bindings; x++)
|
||||||
|
{
|
||||||
|
std::string keyname = names[y] + std::to_string(x);
|
||||||
|
Binding(keyname, binding.controller[c].buttons[y * allowed_bindings + x]);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginSection("Shortcuts");
|
||||||
|
for (int i = 0; i < num_shortcuts; i++)
|
||||||
|
{
|
||||||
|
Binding(getShortcutNames()[i] + std::to_string(0), binding.shortcuts[i * 4]);
|
||||||
|
Binding(getShortcutNames()[i] + std::to_string(1), binding.shortcuts[i * 4 + 1]);
|
||||||
|
Binding(getShortcutNames()[i] + std::to_string(2), binding.shortcuts[i * 4 + 2]);
|
||||||
|
Binding(getShortcutNames()[i] + std::to_string(3), binding.shortcuts[i * 4 + 3]);
|
||||||
|
}
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
BeginSection("Files");
|
||||||
|
Enum("SRAMLocation", sram_location, { "ROMDirectory", "ConfigDirectory", "Custom" });
|
||||||
|
Enum("StateLocation", state_location, { "ROMDirectory", "ConfigDirectory", "Custom" });
|
||||||
|
Enum("CheatLocation", cheat_location, { "ROMDirectory", "ConfigDirectory", "Custom" });
|
||||||
|
Enum("PatchLocation", patch_location, { "ROMDirectory", "ConfigDirectory", "Custom" });
|
||||||
|
Enum("ExportLocation", export_location, { "ROMDirectory", "ConfigDirectory", "Custom" });
|
||||||
|
|
||||||
|
String("SRAMFolder", sram_folder);
|
||||||
|
String("StateFolder", state_folder);
|
||||||
|
String("CheatFolder", cheat_folder);
|
||||||
|
String("PatchFolder", patch_folder);
|
||||||
|
String("ExportFolder", export_folder);
|
||||||
|
|
||||||
|
Int("SRAMSaveInterval", sram_save_interval);
|
||||||
|
EndSection();
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
{
|
||||||
|
std::ofstream ofs(filename);
|
||||||
|
ofs << root;
|
||||||
|
ofs.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuConfig::setVRRConfig(bool enable)
|
||||||
|
{
|
||||||
|
if (enable == vrr_enabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!adjust_for_vrr && enable)
|
||||||
|
return;
|
||||||
|
|
||||||
|
vrr_enabled = enable;
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
{
|
||||||
|
saved_fixed_frame_rate = fixed_frame_rate;
|
||||||
|
saved_input_rate = input_rate;
|
||||||
|
saved_speed_sync_method = speed_sync_method;
|
||||||
|
saved_enable_vsync = enable_vsync;
|
||||||
|
|
||||||
|
fixed_frame_rate = 0.0;
|
||||||
|
input_rate = 32040;
|
||||||
|
enable_vsync = true;
|
||||||
|
speed_sync_method = eTimer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fixed_frame_rate = saved_fixed_frame_rate;
|
||||||
|
input_rate = saved_input_rate;
|
||||||
|
speed_sync_method = saved_speed_sync_method;
|
||||||
|
enable_vsync = saved_enable_vsync;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
#ifndef __EMU_CONFIG_HPP
|
||||||
|
#define __EMU_CONFIG_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "EmuBinding.hpp"
|
||||||
|
|
||||||
|
struct EmuConfig
|
||||||
|
{
|
||||||
|
static std::string findConfigFile();
|
||||||
|
static std::string findConfigDir();
|
||||||
|
void setDefaults(int section = -1);
|
||||||
|
void config(std::string filename, bool write);
|
||||||
|
void loadFile(std::string filename)
|
||||||
|
{
|
||||||
|
config(filename, false);
|
||||||
|
}
|
||||||
|
void saveFile(std::string filename)
|
||||||
|
{
|
||||||
|
config(filename, true);
|
||||||
|
}
|
||||||
|
void setVRRConfig(bool enable = true);
|
||||||
|
bool vrr_enabled = false;
|
||||||
|
int saved_input_rate = 0;
|
||||||
|
double saved_fixed_frame_rate = 0.0;
|
||||||
|
int saved_speed_sync_method = 0;
|
||||||
|
bool saved_enable_vsync = false;
|
||||||
|
|
||||||
|
// Operational
|
||||||
|
std::string last_rom_folder;
|
||||||
|
int main_window_width = 0;
|
||||||
|
int main_window_height = 0;
|
||||||
|
std::vector<std::string> recently_used;
|
||||||
|
|
||||||
|
// General
|
||||||
|
bool fullscreen_on_open;
|
||||||
|
bool disable_screensaver;
|
||||||
|
bool pause_emulation_when_unfocused;
|
||||||
|
|
||||||
|
bool show_frame_rate;
|
||||||
|
bool show_indicators;
|
||||||
|
bool show_pressed_keys;
|
||||||
|
bool show_time;
|
||||||
|
|
||||||
|
// Display
|
||||||
|
std::string display_driver;
|
||||||
|
int display_device_index;
|
||||||
|
bool enable_vsync;
|
||||||
|
bool bilinear_filter;
|
||||||
|
bool reduce_input_lag;
|
||||||
|
bool adjust_for_vrr;
|
||||||
|
bool use_shader;
|
||||||
|
std::string shader;
|
||||||
|
std::string last_shader_folder;
|
||||||
|
|
||||||
|
bool scale_image;
|
||||||
|
bool maintain_aspect_ratio;
|
||||||
|
bool use_integer_scaling;
|
||||||
|
int aspect_ratio_numerator;
|
||||||
|
int aspect_ratio_denominator;
|
||||||
|
bool show_overscan;
|
||||||
|
enum HighResolutionEffect
|
||||||
|
{
|
||||||
|
eLeaveAlone = 0,
|
||||||
|
eScaleDown = 1,
|
||||||
|
eScaleUp = 2
|
||||||
|
};
|
||||||
|
int high_resolution_effect;
|
||||||
|
|
||||||
|
std::string software_filter;
|
||||||
|
|
||||||
|
enum DisplayMessages
|
||||||
|
{
|
||||||
|
eOnscreen = 0,
|
||||||
|
eInscreen = 1,
|
||||||
|
eNone = 2
|
||||||
|
};
|
||||||
|
int display_messages;
|
||||||
|
int osd_size;
|
||||||
|
|
||||||
|
// Sound
|
||||||
|
std::string sound_driver;
|
||||||
|
std::string sound_device;
|
||||||
|
int playback_rate;
|
||||||
|
int audio_buffer_size_ms;
|
||||||
|
|
||||||
|
bool adjust_input_rate_automatically;
|
||||||
|
int input_rate;
|
||||||
|
bool dynamic_rate_control;
|
||||||
|
double dynamic_rate_limit;
|
||||||
|
bool mute_audio;
|
||||||
|
bool mute_audio_during_alternate_speed;
|
||||||
|
|
||||||
|
// Emulation
|
||||||
|
|
||||||
|
enum SpeedSyncMethod
|
||||||
|
{
|
||||||
|
eTimer = 0,
|
||||||
|
eTimerWithFrameskip = 1,
|
||||||
|
eSoundSync = 2,
|
||||||
|
eUnlimited = 3
|
||||||
|
};
|
||||||
|
int speed_sync_method;
|
||||||
|
double fixed_frame_rate;
|
||||||
|
int fast_forward_skip_frames;
|
||||||
|
|
||||||
|
int rewind_buffer_size;
|
||||||
|
int rewind_frame_interval;
|
||||||
|
|
||||||
|
// Emulation/Hacks
|
||||||
|
|
||||||
|
bool allow_invalid_vram_access;
|
||||||
|
bool allow_opposing_dpad_directions;
|
||||||
|
bool overclock;
|
||||||
|
bool remove_sprite_limit;
|
||||||
|
bool enable_shadow_buffer;
|
||||||
|
int superfx_clock_multiplier;
|
||||||
|
enum SoundFilter
|
||||||
|
{
|
||||||
|
eNearest = 0,
|
||||||
|
eLinear = 1,
|
||||||
|
eGaussian = 2,
|
||||||
|
eCubic = 3,
|
||||||
|
eSinc = 4
|
||||||
|
};
|
||||||
|
int sound_filter;
|
||||||
|
|
||||||
|
// Files
|
||||||
|
enum FileLocation
|
||||||
|
{
|
||||||
|
eROMDirectory = 0,
|
||||||
|
eConfigDirectory = 1,
|
||||||
|
eCustomDirectory = 2
|
||||||
|
};
|
||||||
|
int sram_location;
|
||||||
|
int state_location;
|
||||||
|
int cheat_location;
|
||||||
|
int patch_location;
|
||||||
|
int export_location;
|
||||||
|
std::string sram_folder;
|
||||||
|
std::string state_folder;
|
||||||
|
std::string cheat_folder;
|
||||||
|
std::string patch_folder;
|
||||||
|
std::string export_folder;
|
||||||
|
|
||||||
|
int sram_save_interval;
|
||||||
|
|
||||||
|
static const int allowed_bindings = 4;
|
||||||
|
static const int num_controller_bindings = 18;
|
||||||
|
static const int num_shortcuts = 55;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
EmuBinding buttons[num_controller_bindings * allowed_bindings];
|
||||||
|
} controller[5];
|
||||||
|
|
||||||
|
EmuBinding shortcuts[num_shortcuts * allowed_bindings];
|
||||||
|
} binding;
|
||||||
|
|
||||||
|
static const char **getDefaultShortcutKeys();
|
||||||
|
static const char **getShortcutNames();
|
||||||
|
|
||||||
|
enum Shortcut
|
||||||
|
{
|
||||||
|
eOpenROM = 0,
|
||||||
|
eFastForward,
|
||||||
|
eToggleFastForward,
|
||||||
|
ePauseContinue,
|
||||||
|
eSoftReset,
|
||||||
|
ePowerCycle,
|
||||||
|
eQuit,
|
||||||
|
eToggleFullscreen,
|
||||||
|
eSaveScreenshot,
|
||||||
|
eSaveSPC,
|
||||||
|
eSaveState,
|
||||||
|
eLoadState,
|
||||||
|
eIncreaseSlot,
|
||||||
|
eDecreaseSlot,
|
||||||
|
eSaveState0,
|
||||||
|
eSaveState1,
|
||||||
|
eSaveState2,
|
||||||
|
eSaveState3,
|
||||||
|
eSaveState4,
|
||||||
|
eSaveState5,
|
||||||
|
eSaveState6,
|
||||||
|
eSaveState7,
|
||||||
|
eSaveState8,
|
||||||
|
eSaveState9,
|
||||||
|
eLoadState0,
|
||||||
|
eLoadState1,
|
||||||
|
eLoadState2,
|
||||||
|
eLoadState3,
|
||||||
|
eLoadState4,
|
||||||
|
eLoadState5,
|
||||||
|
eLoadState6,
|
||||||
|
eLoadState7,
|
||||||
|
eLoadState8,
|
||||||
|
eLoadState9,
|
||||||
|
eRewind,
|
||||||
|
eGrabMouse,
|
||||||
|
eSwapControllers1and2,
|
||||||
|
eToggleBG0,
|
||||||
|
eToggleBG1,
|
||||||
|
eToggleBG2,
|
||||||
|
eToggleBG3,
|
||||||
|
eToggleSprites,
|
||||||
|
eChangeBackdrop,
|
||||||
|
eToggleSoundChannel1,
|
||||||
|
eToggleSoundChannel2,
|
||||||
|
eToggleSoundChannel3,
|
||||||
|
eToggleSoundChannel4,
|
||||||
|
eToggleSoundChannel5,
|
||||||
|
eToggleSoundChannel6,
|
||||||
|
eToggleSoundChannel7,
|
||||||
|
eToggleSoundChannel8,
|
||||||
|
eToggleAllSoundChannels,
|
||||||
|
eStartRecording,
|
||||||
|
eStopRecording,
|
||||||
|
eSeekToFrame,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,137 @@
|
||||||
|
#include "EmuInputPanel.hpp"
|
||||||
|
#include <QFrame>
|
||||||
|
#include <QtEvents>
|
||||||
|
|
||||||
|
EmuInputBinder::EmuInputBinder(const QString &text, std::vector<EmuBinding> *bindings, int min_label_width)
|
||||||
|
{
|
||||||
|
layout = new QHBoxLayout;
|
||||||
|
setLayout(layout);
|
||||||
|
//layout->setMargin(0);
|
||||||
|
|
||||||
|
label = new QLabel(text);
|
||||||
|
layout->addWidget(label);
|
||||||
|
label->setMinimumWidth(min_label_width);
|
||||||
|
|
||||||
|
this->bindings = bindings;
|
||||||
|
|
||||||
|
remove_icon = QIcon::fromTheme("remove");
|
||||||
|
add_icon = QIcon::fromTheme("add");
|
||||||
|
|
||||||
|
auto frame = new QFrame;
|
||||||
|
auto sublayout = new QHBoxLayout;
|
||||||
|
//sublayout->setMargin(2);
|
||||||
|
frame->setContentsMargins(0, 0, 0, 0);
|
||||||
|
frame->setLayout(sublayout);
|
||||||
|
frame->setFrameShape(QFrame::Shape::StyledPanel);
|
||||||
|
frame->setFrameShadow(QFrame::Shadow::Sunken);
|
||||||
|
|
||||||
|
layout->addWidget(frame);
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
buttons[i] = new QPushButton(this);
|
||||||
|
sublayout->addWidget(buttons[i]);
|
||||||
|
buttons[i]->connect(buttons[i], &QPushButton::clicked, [&, i] {
|
||||||
|
this->bindings->erase(this->bindings->begin() + i);
|
||||||
|
updateDisplay();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_button = new QPushButton(add_icon, "");
|
||||||
|
add_button->setCheckable(true);
|
||||||
|
add_button->connect(add_button, &QPushButton::toggled, [&](bool checked) {
|
||||||
|
if (checked)
|
||||||
|
{
|
||||||
|
grabKeyboard();
|
||||||
|
if (add_button_func)
|
||||||
|
add_button_func(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sublayout->addWidget(add_button);
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
add_button_func = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInputBinder::reset(const QString &text, std::vector<EmuBinding> *bindings)
|
||||||
|
{
|
||||||
|
label->setText(text);
|
||||||
|
this->bindings = bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInputBinder::keyPressEvent(QKeyEvent *event)
|
||||||
|
{
|
||||||
|
releaseKeyboard();
|
||||||
|
|
||||||
|
if (add_button->isChecked() && bindings->size() < 3)
|
||||||
|
{
|
||||||
|
auto key = EmuBinding::keyboard(
|
||||||
|
event->key(),
|
||||||
|
event->modifiers().testFlag(Qt::KeyboardModifier::ShiftModifier),
|
||||||
|
event->modifiers().testFlag(Qt::KeyboardModifier::AltModifier),
|
||||||
|
event->modifiers().testFlag(Qt::KeyboardModifier::ControlModifier),
|
||||||
|
event->modifiers().testFlag(Qt::KeyboardModifier::MetaModifier));
|
||||||
|
|
||||||
|
bool skip = false;
|
||||||
|
for (auto &b : *bindings)
|
||||||
|
{
|
||||||
|
if (b == key)
|
||||||
|
{
|
||||||
|
skip = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event->key() == Qt::Key_Escape)
|
||||||
|
skip = true;
|
||||||
|
|
||||||
|
if (!skip)
|
||||||
|
{
|
||||||
|
bindings->push_back(key);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
add_button->setChecked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInputBinder::untoggleAddButton()
|
||||||
|
{
|
||||||
|
add_button->setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInputBinder::updateDisplay()
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < bindings->size(); i++)
|
||||||
|
{
|
||||||
|
QPushButton &b = *buttons[i];
|
||||||
|
b.setIcon(QIcon::fromTheme("remove"));
|
||||||
|
b.setText((*bindings)[i].to_string().c_str());
|
||||||
|
b.show();
|
||||||
|
}
|
||||||
|
for (; i < 3; i++)
|
||||||
|
{
|
||||||
|
buttons[i]->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bindings->size() >= 3)
|
||||||
|
{
|
||||||
|
add_button->hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
add_button->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInputBinder::connectAddButton(std::function<void (EmuInputBinder *)> func)
|
||||||
|
{
|
||||||
|
add_button_func = func;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuInputBinder::~EmuInputBinder()
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef __EMU_INPUT_PANEL_HPP
|
||||||
|
#define __EMU_INPUT_PANEL_HPP
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
|
||||||
|
#include "EmuBinding.hpp"
|
||||||
|
|
||||||
|
class EmuInputBinder : public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuInputBinder(const QString &text, std::vector<EmuBinding> *bindings, int min_label_width = 0);
|
||||||
|
~EmuInputBinder();
|
||||||
|
void updateDisplay();
|
||||||
|
std::vector<EmuBinding> *bindings;
|
||||||
|
void connectAddButton(std::function<void(EmuInputBinder *)>);
|
||||||
|
void untoggleAddButton();
|
||||||
|
void reset(const QString &text, std::vector<EmuBinding> *bindings);
|
||||||
|
|
||||||
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QLabel *label;
|
||||||
|
QHBoxLayout *layout;
|
||||||
|
QPushButton *buttons[3];
|
||||||
|
QPushButton *add_button;
|
||||||
|
std::function<void(EmuInputBinder *)> add_button_func;
|
||||||
|
QIcon remove_icon;
|
||||||
|
QIcon add_icon;
|
||||||
|
};
|
||||||
|
|
||||||
|
class EmuInputPanel : QWidget
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,566 @@
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QtWidgets>
|
||||||
|
#include <QtEvents>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QStackedWidget>
|
||||||
|
#include <qpa/qplatformnativeinterface.h>
|
||||||
|
|
||||||
|
#include "EmuMainWindow.hpp"
|
||||||
|
#include "EmuSettingsWindow.hpp"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include "EmuCanvasVulkan.hpp"
|
||||||
|
#include "EmuCanvasOpenGL.hpp"
|
||||||
|
#include "EmuCanvasQt.hpp"
|
||||||
|
#include "src/ShaderParametersDialog.hpp"
|
||||||
|
#undef KeyPress
|
||||||
|
|
||||||
|
static EmuSettingsWindow *g_emu_settings_window = nullptr;
|
||||||
|
|
||||||
|
EmuMainWindow::EmuMainWindow(EmuApplication *app)
|
||||||
|
: app(app)
|
||||||
|
{
|
||||||
|
createWidgets();
|
||||||
|
recreateCanvas();
|
||||||
|
setMouseTracking(true);
|
||||||
|
|
||||||
|
app->qtapp->installEventFilter(this);
|
||||||
|
mouse_timer.setTimerType(Qt::CoarseTimer);
|
||||||
|
mouse_timer.setInterval(1000);
|
||||||
|
mouse_timer.callOnTimeout([&] {
|
||||||
|
if (cursor_visible && isActivelyDrawing())
|
||||||
|
{
|
||||||
|
setCursor(QCursor(Qt::BlankCursor));
|
||||||
|
cursor_visible = false;
|
||||||
|
mouse_timer.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuMainWindow::~EmuMainWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::destroyCanvas()
|
||||||
|
{
|
||||||
|
auto central_widget = centralWidget();
|
||||||
|
if (!central_widget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (using_stacked_widget)
|
||||||
|
{
|
||||||
|
auto stackwidget = (QStackedWidget *)central_widget;
|
||||||
|
EmuCanvas *widget = (EmuCanvas *)stackwidget->widget(0);
|
||||||
|
if (widget)
|
||||||
|
{
|
||||||
|
widget->deinit();
|
||||||
|
stackwidget->removeWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete takeCentralWidget();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmuCanvas *widget = (EmuCanvas *)takeCentralWidget();
|
||||||
|
widget->deinit();
|
||||||
|
delete widget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::createCanvas()
|
||||||
|
{
|
||||||
|
if (app->config->display_driver != "vulkan" &&
|
||||||
|
app->config->display_driver != "opengl" &&
|
||||||
|
app->config->display_driver != "qt")
|
||||||
|
app->config->display_driver = "qt";
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (QGuiApplication::platformName() == "wayland" && app->config->display_driver != "qt")
|
||||||
|
{
|
||||||
|
auto central_widget = new QStackedWidget();
|
||||||
|
|
||||||
|
if (app->config->display_driver == "vulkan")
|
||||||
|
canvas = new EmuCanvasVulkan(app->config.get(), central_widget, this);
|
||||||
|
else if (app->config->display_driver == "opengl")
|
||||||
|
canvas = new EmuCanvasOpenGL(app->config.get(), central_widget, this);
|
||||||
|
|
||||||
|
central_widget->addWidget(canvas);
|
||||||
|
central_widget->setCurrentWidget(canvas);
|
||||||
|
setCentralWidget(central_widget);
|
||||||
|
using_stacked_widget = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (app->config->display_driver == "vulkan")
|
||||||
|
canvas = new EmuCanvasVulkan(app->config.get(), this, this);
|
||||||
|
else if (app->config->display_driver == "opengl")
|
||||||
|
canvas = new EmuCanvasOpenGL(app->config.get(), this, this);
|
||||||
|
else
|
||||||
|
canvas = new EmuCanvasQt(app->config.get(), this, this);
|
||||||
|
|
||||||
|
setCentralWidget(canvas);
|
||||||
|
using_stacked_widget = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::recreateCanvas()
|
||||||
|
{
|
||||||
|
destroyCanvas();
|
||||||
|
createCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::setCoreActionsEnabled(bool enable)
|
||||||
|
{
|
||||||
|
for (auto &a : core_actions)
|
||||||
|
a->setEnabled(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::createWidgets()
|
||||||
|
{
|
||||||
|
setWindowTitle("Snes9x");
|
||||||
|
setWindowIcon(QIcon::fromTheme("snes9x"));
|
||||||
|
|
||||||
|
// File menu
|
||||||
|
auto file_menu = new QMenu(tr("&File"));
|
||||||
|
auto open_item = file_menu->addAction(QIcon::fromTheme("document-open"), tr("&Open File..."));
|
||||||
|
open_item->connect(open_item, &QAction::triggered, this, [&] {
|
||||||
|
openFile();
|
||||||
|
});
|
||||||
|
// File->Recent Files submenu
|
||||||
|
recent_menu = new QMenu("Recent Files");
|
||||||
|
file_menu->addMenu(recent_menu);
|
||||||
|
populateRecentlyUsed();
|
||||||
|
|
||||||
|
file_menu->addSeparator();
|
||||||
|
|
||||||
|
// File->Load/Save State submenus
|
||||||
|
load_state_menu = new QMenu(tr("&Load State"));
|
||||||
|
save_state_menu = new QMenu(tr("&Save State"));
|
||||||
|
for (size_t i = 0; i < state_items_size; i++)
|
||||||
|
{
|
||||||
|
auto action = load_state_menu->addAction(tr("Slot &%1").arg(i));
|
||||||
|
connect(action, &QAction::triggered, [&, i] {
|
||||||
|
app->loadState(i);
|
||||||
|
});
|
||||||
|
core_actions.push_back(action);
|
||||||
|
|
||||||
|
action = save_state_menu->addAction(tr("Slot &%1").arg(i));
|
||||||
|
connect(action, &QAction::triggered, [&, i] {
|
||||||
|
app->saveState(i);
|
||||||
|
});
|
||||||
|
core_actions.push_back(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
load_state_menu->addSeparator();
|
||||||
|
|
||||||
|
auto load_state_file_item = load_state_menu->addAction(QIcon::fromTheme("document-open"), tr("From &File..."));
|
||||||
|
connect(load_state_file_item, &QAction::triggered, [&] {
|
||||||
|
this->chooseState(false);
|
||||||
|
});
|
||||||
|
core_actions.push_back(load_state_file_item);
|
||||||
|
|
||||||
|
load_state_menu->addSeparator();
|
||||||
|
|
||||||
|
auto load_state_undo_item = load_state_menu->addAction(QIcon::fromTheme("edit-undo"), tr("&Undo Load State"));
|
||||||
|
connect(load_state_undo_item, &QAction::triggered, [&] {
|
||||||
|
app->core->loadUndoState();
|
||||||
|
});
|
||||||
|
core_actions.push_back(load_state_undo_item);
|
||||||
|
|
||||||
|
file_menu->addMenu(load_state_menu);
|
||||||
|
|
||||||
|
save_state_menu->addSeparator();
|
||||||
|
auto save_state_file_item = save_state_menu->addAction(QIcon::fromTheme("document-save"), tr("To &File..."));
|
||||||
|
connect(save_state_file_item, &QAction::triggered, [&] {
|
||||||
|
this->chooseState(true);
|
||||||
|
});
|
||||||
|
core_actions.push_back(save_state_file_item);
|
||||||
|
file_menu->addMenu(save_state_menu);
|
||||||
|
|
||||||
|
auto exit_item = new QAction(QIcon::fromTheme("application-exit"), tr("E&xit"));
|
||||||
|
exit_item->connect(exit_item, &QAction::triggered, this, [&](bool checked) {
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
file_menu->addAction(exit_item);
|
||||||
|
menuBar()->addMenu(file_menu);
|
||||||
|
|
||||||
|
// Emulation Menu
|
||||||
|
auto emulation_menu = new QMenu(tr("&Emulation"));
|
||||||
|
|
||||||
|
auto run_item = emulation_menu->addAction(tr("&Run"));
|
||||||
|
connect(run_item, &QAction::triggered, [&] {
|
||||||
|
if (manual_pause)
|
||||||
|
{
|
||||||
|
manual_pause = false;
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core_actions.push_back(run_item);
|
||||||
|
|
||||||
|
auto pause_item = emulation_menu->addAction(QIcon::fromTheme("media-playback-pause"), tr("&Pause"));
|
||||||
|
connect(pause_item, &QAction::triggered, [&] {
|
||||||
|
if (!manual_pause)
|
||||||
|
{
|
||||||
|
manual_pause = true;
|
||||||
|
app->pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core_actions.push_back(pause_item);
|
||||||
|
|
||||||
|
emulation_menu->addSeparator();
|
||||||
|
|
||||||
|
auto reset_item = emulation_menu->addAction(QIcon::fromTheme("view-refresh"), tr("Rese&t"));
|
||||||
|
connect(reset_item, &QAction::triggered, [&] {
|
||||||
|
app->reset();
|
||||||
|
if (manual_pause)
|
||||||
|
{
|
||||||
|
manual_pause = false;
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core_actions.push_back(reset_item);
|
||||||
|
|
||||||
|
auto hard_reset_item = emulation_menu->addAction(QIcon::fromTheme("process-stop"), tr("&Hard Reset"));
|
||||||
|
connect(hard_reset_item, &QAction::triggered, [&] {
|
||||||
|
app->powerCycle();
|
||||||
|
if (manual_pause)
|
||||||
|
{
|
||||||
|
manual_pause = false;
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core_actions.push_back(hard_reset_item);
|
||||||
|
|
||||||
|
menuBar()->addMenu(emulation_menu);
|
||||||
|
|
||||||
|
// View Menu
|
||||||
|
auto view_menu = new QMenu(tr("&View"));
|
||||||
|
|
||||||
|
// Set Size Menu
|
||||||
|
auto set_size_menu = new QMenu(tr("&Set Size"));
|
||||||
|
for (size_t i = 1; i <= 5; i++)
|
||||||
|
{
|
||||||
|
auto item = new QAction(tr("&%1x").arg(i));
|
||||||
|
set_size_menu->addAction(item);
|
||||||
|
item->connect(item, &QAction::triggered, this, [&, i](bool checked) {
|
||||||
|
resizeToMultiple(i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
view_menu->addMenu(set_size_menu);
|
||||||
|
|
||||||
|
view_menu->addSeparator();
|
||||||
|
|
||||||
|
auto fullscreen_item = new QAction(QIcon::fromTheme("view-fullscreen"), tr("&Fullscreen"));
|
||||||
|
view_menu->addAction(fullscreen_item);
|
||||||
|
fullscreen_item->connect(fullscreen_item, &QAction::triggered, [&](bool checked) {
|
||||||
|
toggleFullscreen();
|
||||||
|
});
|
||||||
|
|
||||||
|
menuBar()->addMenu(view_menu);
|
||||||
|
|
||||||
|
// Options Menu
|
||||||
|
auto options_menu = new QMenu(tr("&Options"));
|
||||||
|
|
||||||
|
std::array<QString, 7> setting_panels = { tr("&General..."),
|
||||||
|
tr("&Display..."),
|
||||||
|
tr("&Sound..."),
|
||||||
|
tr("&Emulation..."),
|
||||||
|
tr("&Controllers..."),
|
||||||
|
tr("Shortcu&ts..."),
|
||||||
|
tr("&Files...") };
|
||||||
|
const char *setting_icons[] = { ":/icons/blackicons/settings.svg",
|
||||||
|
":/icons/blackicons/display.svg",
|
||||||
|
":/icons/blackicons/sound.svg",
|
||||||
|
":/icons/blackicons/emulation.svg",
|
||||||
|
":/icons/blackicons/joypad.svg",
|
||||||
|
":/icons/blackicons/keyboard.svg",
|
||||||
|
":/icons/blackicons/folders.svg" };
|
||||||
|
|
||||||
|
for (int i = 0; i < setting_panels.size(); i++)
|
||||||
|
{
|
||||||
|
auto action = options_menu->addAction(QIcon(setting_icons[i]), setting_panels[i]);
|
||||||
|
QObject::connect(action, &QAction::triggered, [&, i] {
|
||||||
|
if (!g_emu_settings_window)
|
||||||
|
g_emu_settings_window = new EmuSettingsWindow(this, app);
|
||||||
|
g_emu_settings_window->show(i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
options_menu->addSeparator();
|
||||||
|
auto shader_settings_item = new QAction(QIcon(":/icons/blackicons/shader.svg"), tr("S&hader Settings..."));
|
||||||
|
QObject::connect(shader_settings_item, &QAction::triggered, [&] {
|
||||||
|
if (canvas)
|
||||||
|
canvas->showParametersDialog();
|
||||||
|
});
|
||||||
|
options_menu->addAction(shader_settings_item);
|
||||||
|
|
||||||
|
menuBar()->addMenu(options_menu);
|
||||||
|
|
||||||
|
setCoreActionsEnabled(false);
|
||||||
|
|
||||||
|
if (app->config->main_window_width != 0 && app->config->main_window_height != 0)
|
||||||
|
resize(app->config->main_window_width, app->config->main_window_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::resizeToMultiple(int multiple)
|
||||||
|
{
|
||||||
|
resize((224 * multiple) * app->config->aspect_ratio_numerator / app->config->aspect_ratio_denominator, (224 * multiple) + menuBar()->height());
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::setBypassCompositor(bool bypass)
|
||||||
|
{
|
||||||
|
#ifndef _WIN32
|
||||||
|
if (QGuiApplication::platformName() == "xcb")
|
||||||
|
{
|
||||||
|
auto pni = QGuiApplication::platformNativeInterface();
|
||||||
|
|
||||||
|
uint32_t value = bypass;
|
||||||
|
auto display = (Display *)pni->nativeResourceForWindow("display", windowHandle());
|
||||||
|
auto xid = winId();
|
||||||
|
Atom net_wm_bypass_compositor = XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False);
|
||||||
|
XChangeProperty(display, xid, net_wm_bypass_compositor, 6, 32, PropModeReplace, (unsigned char *)&value, 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::chooseState(bool save)
|
||||||
|
{
|
||||||
|
app->pause();
|
||||||
|
|
||||||
|
QFileDialog dialog(this, tr("Choose a State File"));
|
||||||
|
|
||||||
|
dialog.setDirectory(QString::fromStdString(app->core->getStateFolder()));
|
||||||
|
dialog.setNameFilters({ tr("Save States (*.sst *.oops *.undo *.0?? *.1?? *.2?? *.3?? *.4?? *.5?? *.6?? *.7?? *.8?? *.9*)"), tr("All Files (*)") });
|
||||||
|
|
||||||
|
if (!save)
|
||||||
|
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dialog.setFileMode(QFileDialog::AnyFile);
|
||||||
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dialog.exec() || dialog.selectedFiles().empty())
|
||||||
|
{
|
||||||
|
app->unpause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filename = dialog.selectedFiles()[0];
|
||||||
|
|
||||||
|
if (!save)
|
||||||
|
app->loadState(filename.toStdString());
|
||||||
|
else
|
||||||
|
app->saveState(filename.toStdString());
|
||||||
|
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::openFile()
|
||||||
|
{
|
||||||
|
app->pause();
|
||||||
|
QFileDialog dialog(this, tr("Open a ROM File"));
|
||||||
|
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||||
|
dialog.setDirectory(QString::fromStdString(app->config->last_rom_folder));
|
||||||
|
dialog.setNameFilters({ tr("ROM Files (*.sfc *.smc *.bin *.fig *.msu *.zip)"), tr("All Files (*)") });
|
||||||
|
|
||||||
|
if (!dialog.exec() || dialog.selectedFiles().empty())
|
||||||
|
{
|
||||||
|
app->unpause();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto filename = dialog.selectedFiles()[0];
|
||||||
|
app->config->last_rom_folder = dialog.directory().canonicalPath().toStdString();
|
||||||
|
|
||||||
|
openFile(filename.toStdString());
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuMainWindow::openFile(std::string filename)
|
||||||
|
{
|
||||||
|
if (app->openFile(filename))
|
||||||
|
{
|
||||||
|
auto &ru = app->config->recently_used;
|
||||||
|
auto it = std::find(ru.begin(), ru.end(), filename);
|
||||||
|
if (it != ru.end())
|
||||||
|
ru.erase(it);
|
||||||
|
ru.insert(ru.begin(), filename);
|
||||||
|
populateRecentlyUsed();
|
||||||
|
setCoreActionsEnabled(true);
|
||||||
|
if (!isFullScreen() && app->config->fullscreen_on_open)
|
||||||
|
toggleFullscreen();
|
||||||
|
app->startGame();
|
||||||
|
mouse_timer.start();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EmuMainWindow::populateRecentlyUsed()
|
||||||
|
{
|
||||||
|
recent_menu->clear();
|
||||||
|
|
||||||
|
if (app->config->recently_used.empty())
|
||||||
|
{
|
||||||
|
auto action = recent_menu->addAction(tr("No recent files"));
|
||||||
|
action->setDisabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (app->config->recently_used.size() > 10)
|
||||||
|
app->config->recently_used.pop_back();
|
||||||
|
|
||||||
|
for (int i = 0; i < app->config->recently_used.size(); i++)
|
||||||
|
{
|
||||||
|
auto &string = app->config->recently_used[i];
|
||||||
|
auto action = recent_menu->addAction(QString("&%1: %2").arg(i).arg(QString::fromStdString(string)));
|
||||||
|
connect(action, &QAction::triggered, [&, string] {
|
||||||
|
openFile(string);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recent_menu->addSeparator();
|
||||||
|
auto action = recent_menu->addAction(tr("Clear Recent Files"));
|
||||||
|
connect(action, &QAction::triggered, [&] {
|
||||||
|
app->config->recently_used.clear();
|
||||||
|
populateRecentlyUsed();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef KeyPress
|
||||||
|
#undef KeyRelease
|
||||||
|
bool EmuMainWindow::event(QEvent *event)
|
||||||
|
{
|
||||||
|
switch (event->type())
|
||||||
|
{
|
||||||
|
case QEvent::Close:
|
||||||
|
if (isFullScreen())
|
||||||
|
{
|
||||||
|
toggleFullscreen();
|
||||||
|
}
|
||||||
|
if (canvas)
|
||||||
|
canvas->deinit();
|
||||||
|
QGuiApplication::sync();
|
||||||
|
event->accept();
|
||||||
|
break;
|
||||||
|
case QEvent::Resize:
|
||||||
|
if (!isFullScreen() && !isMaximized())
|
||||||
|
{
|
||||||
|
app->config->main_window_width = ((QResizeEvent *)event)->size().width();
|
||||||
|
app->config->main_window_height = ((QResizeEvent *)event)->size().height();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QEvent::WindowActivate:
|
||||||
|
if (focus_pause)
|
||||||
|
{
|
||||||
|
focus_pause = false;
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QEvent::WindowDeactivate:
|
||||||
|
if (app->config->pause_emulation_when_unfocused && !focus_pause)
|
||||||
|
{
|
||||||
|
focus_pause = true;
|
||||||
|
app->pause();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QEvent::MouseMove:
|
||||||
|
if (!cursor_visible)
|
||||||
|
{
|
||||||
|
setCursor(QCursor(Qt::ArrowCursor));
|
||||||
|
cursor_visible = true;
|
||||||
|
mouse_timer.start();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QMainWindow::event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::toggleFullscreen()
|
||||||
|
{
|
||||||
|
if (isFullScreen())
|
||||||
|
{
|
||||||
|
app->config->setVRRConfig(false);
|
||||||
|
app->updateSettings();
|
||||||
|
setBypassCompositor(false);
|
||||||
|
showNormal();
|
||||||
|
menuBar()->setVisible(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (app->config->adjust_for_vrr)
|
||||||
|
{
|
||||||
|
app->config->setVRRConfig(true);
|
||||||
|
app->updateSettings();
|
||||||
|
}
|
||||||
|
showFullScreen();
|
||||||
|
menuBar()->setVisible(false);
|
||||||
|
setBypassCompositor(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuMainWindow::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() != QEvent::KeyPress && event->type() != QEvent::KeyRelease)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (watched != this && watched != canvas && !app->binding_callback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto key_event = (QKeyEvent *)event;
|
||||||
|
|
||||||
|
if (isFullScreen() && key_event->key() == Qt::Key_Escape)
|
||||||
|
{
|
||||||
|
toggleFullscreen();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto binding = EmuBinding::keyboard(key_event->key(),
|
||||||
|
key_event->modifiers().testFlag(Qt::ShiftModifier),
|
||||||
|
key_event->modifiers().testFlag(Qt::AltModifier),
|
||||||
|
key_event->modifiers().testFlag(Qt::ControlModifier),
|
||||||
|
key_event->modifiers().testFlag(Qt::MetaModifier));
|
||||||
|
|
||||||
|
if ((app->isBound(binding) || app->binding_callback) && !key_event->isAutoRepeat())
|
||||||
|
{
|
||||||
|
app->reportBinding(binding, event->type() == QEvent::KeyPress);
|
||||||
|
event->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> EmuMainWindow::getDisplayDeviceList()
|
||||||
|
{
|
||||||
|
if (!canvas)
|
||||||
|
return { "Default" };
|
||||||
|
return canvas->getDeviceList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuMainWindow::pauseContinue()
|
||||||
|
{
|
||||||
|
if (manual_pause)
|
||||||
|
{
|
||||||
|
manual_pause = false;
|
||||||
|
app->unpause();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
manual_pause = true;
|
||||||
|
app->pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EmuMainWindow::isActivelyDrawing()
|
||||||
|
{
|
||||||
|
return (!app->isPaused() && app->core->active);
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
#ifndef __EMU_MAIN_WINDOW_HPP
|
||||||
|
#define __EMU_MAIN_WINDOW_HPP
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QTimer>
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
|
||||||
|
class EmuApplication;
|
||||||
|
|
||||||
|
class EmuMainWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuMainWindow(EmuApplication *app);
|
||||||
|
~EmuMainWindow();
|
||||||
|
|
||||||
|
void toggleFullscreen();
|
||||||
|
void createCanvas();
|
||||||
|
void destroyCanvas();
|
||||||
|
void recreateCanvas();
|
||||||
|
void setBypassCompositor(bool);
|
||||||
|
void setCoreActionsEnabled(bool);
|
||||||
|
bool event(QEvent *event) override;
|
||||||
|
bool eventFilter(QObject *, QEvent *event) override;
|
||||||
|
void resizeToMultiple(int multiple);
|
||||||
|
void populateRecentlyUsed();
|
||||||
|
void chooseState(bool save);
|
||||||
|
void pauseContinue();
|
||||||
|
bool isActivelyDrawing();
|
||||||
|
void openFile();
|
||||||
|
bool openFile(std::string filename);
|
||||||
|
std::vector<std::string> getDisplayDeviceList();
|
||||||
|
EmuApplication *app;
|
||||||
|
EmuCanvas *canvas;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void idle();
|
||||||
|
void createWidgets();
|
||||||
|
|
||||||
|
static const size_t recent_menu_size = 10;
|
||||||
|
static const size_t state_items_size = 10;
|
||||||
|
|
||||||
|
bool manual_pause = false;
|
||||||
|
bool focus_pause = false;
|
||||||
|
bool using_stacked_widget = false;
|
||||||
|
QMenu *load_state_menu;
|
||||||
|
QMenu *save_state_menu;
|
||||||
|
QMenu *recent_menu;
|
||||||
|
QTimer mouse_timer;
|
||||||
|
bool cursor_visible = true;
|
||||||
|
QAction *shader_settings_item;
|
||||||
|
std::vector<QAction *> core_actions;
|
||||||
|
std::vector<QAction *> recent_menu_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,57 @@
|
||||||
|
#include "EmuSettingsWindow.hpp"
|
||||||
|
#include "src/EmuMainWindow.hpp"
|
||||||
|
|
||||||
|
#include <QScrollArea>
|
||||||
|
|
||||||
|
EmuSettingsWindow::EmuSettingsWindow(QWidget *parent, EmuApplication *app)
|
||||||
|
: QDialog(parent), app(app)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
general_panel = new GeneralPanel(app);
|
||||||
|
stackedWidget->addWidget(general_panel);
|
||||||
|
|
||||||
|
QScrollArea *area = new QScrollArea(stackedWidget);
|
||||||
|
area->setWidgetResizable(true);
|
||||||
|
area->setFrameStyle(0);
|
||||||
|
display_panel = new DisplayPanel(app);
|
||||||
|
area->setWidget(display_panel);
|
||||||
|
stackedWidget->addWidget(area);
|
||||||
|
|
||||||
|
sound_panel = new SoundPanel(app);
|
||||||
|
stackedWidget->addWidget(sound_panel);
|
||||||
|
|
||||||
|
emulation_panel = new EmulationPanel(app);
|
||||||
|
stackedWidget->addWidget(emulation_panel);
|
||||||
|
|
||||||
|
controller_panel = new ControllerPanel(app);
|
||||||
|
stackedWidget->addWidget(controller_panel);
|
||||||
|
|
||||||
|
shortcuts_panel = new ShortcutsPanel(app);
|
||||||
|
stackedWidget->addWidget(shortcuts_panel);
|
||||||
|
|
||||||
|
folders_panel = new FoldersPanel(app);
|
||||||
|
stackedWidget->addWidget(folders_panel);
|
||||||
|
|
||||||
|
stackedWidget->setCurrentIndex(0);
|
||||||
|
|
||||||
|
closeButton->connect(closeButton, &QPushButton::clicked, [&](bool) {
|
||||||
|
this->close();
|
||||||
|
});
|
||||||
|
|
||||||
|
panelList->connect(panelList, &QListWidget::currentItemChanged, [&](QListWidgetItem *prev, QListWidgetItem *cur) {
|
||||||
|
stackedWidget->setCurrentIndex(panelList->currentRow());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
EmuSettingsWindow::~EmuSettingsWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuSettingsWindow::show(int page)
|
||||||
|
{
|
||||||
|
panelList->setCurrentRow(page);
|
||||||
|
stackedWidget->setCurrentIndex(page);
|
||||||
|
if (!isVisible())
|
||||||
|
open();
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui_EmuSettingsWindow.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include "GeneralPanel.hpp"
|
||||||
|
#include "DisplayPanel.hpp"
|
||||||
|
#include "SoundPanel.hpp"
|
||||||
|
#include "EmulationPanel.hpp"
|
||||||
|
#include "ControllerPanel.hpp"
|
||||||
|
#include "FoldersPanel.hpp"
|
||||||
|
#include "ShortcutsPanel.hpp"
|
||||||
|
|
||||||
|
class EmuSettingsWindow
|
||||||
|
: public QDialog,
|
||||||
|
public Ui::EmuSettingsWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmuSettingsWindow(QWidget *parent, EmuApplication *app);
|
||||||
|
~EmuSettingsWindow();
|
||||||
|
void show(int page);
|
||||||
|
|
||||||
|
EmuApplication *app;
|
||||||
|
GeneralPanel *general_panel;
|
||||||
|
DisplayPanel *display_panel;
|
||||||
|
SoundPanel *sound_panel;
|
||||||
|
EmulationPanel *emulation_panel;
|
||||||
|
ControllerPanel *controller_panel;
|
||||||
|
ShortcutsPanel *shortcuts_panel;
|
||||||
|
FoldersPanel *folders_panel;
|
||||||
|
};
|
|
@ -0,0 +1,158 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>EmuSettingsWindow</class>
|
||||||
|
<widget class="QDialog" name="EmuSettingsWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>933</width>
|
||||||
|
<height>719</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="focusPolicy">
|
||||||
|
<enum>Qt::NoFocus</enum>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="panelList">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="sizeAdjustPolicy">
|
||||||
|
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||||
|
</property>
|
||||||
|
<property name="resizeMode">
|
||||||
|
<enum>QListView::Fixed</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>General</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/settings.svg</normaloff>:/icons/blackicons/settings.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Display</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/display.svg</normaloff>:/icons/blackicons/display.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Sound</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/sound.svg</normaloff>:/icons/blackicons/sound.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Emulation</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/emulation.svg</normaloff>:/icons/blackicons/emulation.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Controllers</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/joypad.svg</normaloff>:/icons/blackicons/joypad.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Shortcuts</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/keyboard.svg</normaloff>:/icons/blackicons/keyboard.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Files</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="resources/snes9x.qrc">
|
||||||
|
<normaloff>:/icons/blackicons/folders.svg</normaloff>:/icons/blackicons/folders.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="defaultsButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Restore Defaults</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="view-refresh">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="closeButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Close</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="window-close">
|
||||||
|
<normaloff>.</normaloff>.</iconset>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="resources/snes9x.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,67 @@
|
||||||
|
#include "EmulationPanel.hpp"
|
||||||
|
EmulationPanel::EmulationPanel(EmuApplication *app_)
|
||||||
|
: app(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
auto connect_checkbox = [&](QCheckBox *box, bool *config) {
|
||||||
|
QObject::connect(box, &QCheckBox::clicked, [&, box, config](bool is_checked) {
|
||||||
|
*config = is_checked;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
auto connect_spin = [&](QSpinBox *box, int *config) {
|
||||||
|
QObject::connect(box, &QSpinBox::valueChanged, [&, box, config](int value) {
|
||||||
|
*config = value;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
auto connect_combo = [&](QComboBox *box, int *config) {
|
||||||
|
QObject::connect(box, &QComboBox::activated, [&, box, config](int index) {
|
||||||
|
*config = index;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
connect_combo(comboBox_speed_control_method, &app->config->speed_sync_method);
|
||||||
|
QObject::connect(doubleSpinBox_frame_rate, &QDoubleSpinBox::valueChanged, [&](double value) {
|
||||||
|
app->config->fixed_frame_rate = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect_spin(spinBox_rewind_buffer_size, &app->config->rewind_buffer_size);
|
||||||
|
connect_spin(spinBox_rewind_frames, &app->config->rewind_frame_interval);
|
||||||
|
connect_spin(spinBox_fast_forward_skip_frames, &app->config->fast_forward_skip_frames);
|
||||||
|
|
||||||
|
connect_checkbox(checkBox_allow_invalid_vram_access, &app->config->allow_invalid_vram_access);
|
||||||
|
connect_checkbox(checkBox_allow_opposing_dpad_directions, &app->config->allow_opposing_dpad_directions);
|
||||||
|
connect_checkbox(checkBox_overclock, &app->config->overclock);
|
||||||
|
connect_checkbox(checkBox_remove_sprite_limit, &app->config->remove_sprite_limit);
|
||||||
|
connect_checkbox(checkBox_use_shadow_echo_buffer, &app->config->enable_shadow_buffer);
|
||||||
|
connect_spin(spinBox_superfx_clock_speed, &app->config->superfx_clock_multiplier);
|
||||||
|
connect_combo(comboBox_sound_filter, &app->config->sound_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmulationPanel::~EmulationPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmulationPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
auto &config = app->config;
|
||||||
|
comboBox_speed_control_method->setCurrentIndex(config->speed_sync_method);
|
||||||
|
doubleSpinBox_frame_rate->setValue(config->fixed_frame_rate);
|
||||||
|
spinBox_fast_forward_skip_frames->setValue(config->fast_forward_skip_frames);
|
||||||
|
|
||||||
|
spinBox_rewind_buffer_size->setValue(config->rewind_buffer_size);
|
||||||
|
spinBox_rewind_frames->setValue(config->rewind_frame_interval);
|
||||||
|
|
||||||
|
checkBox_allow_invalid_vram_access->setChecked(config->allow_invalid_vram_access);
|
||||||
|
checkBox_allow_opposing_dpad_directions->setChecked(config->allow_opposing_dpad_directions);
|
||||||
|
checkBox_overclock->setChecked(config->overclock);
|
||||||
|
checkBox_remove_sprite_limit->setChecked(config->remove_sprite_limit);
|
||||||
|
checkBox_use_shadow_echo_buffer->setChecked(config->enable_shadow_buffer);
|
||||||
|
spinBox_superfx_clock_speed->setValue(config->superfx_clock_multiplier);
|
||||||
|
comboBox_sound_filter->setCurrentIndex(config->sound_filter);
|
||||||
|
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_EmulationPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
class EmulationPanel :
|
||||||
|
public Ui::EmulationPanel,
|
||||||
|
public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EmulationPanel(EmuApplication *app);
|
||||||
|
~EmulationPanel();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
EmuApplication *app;
|
||||||
|
};
|
|
@ -0,0 +1,378 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>EmulationPanel</class>
|
||||||
|
<widget class="QWidget" name="EmulationPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>862</width>
|
||||||
|
<height>756</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Emulation Speed</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Speed control method:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_speed_control_method">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Timer - Match the frame rate configured below</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Timer with Frame Skipping - Skip frames if needed</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Sound Synchronization - Wait on the sound buffer</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>None - Run unthrottled, unless vsync is turned on</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Frame rate:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDoubleSpinBox" name="doubleSpinBox_frame_rate">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> fps</string>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>4</number>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<double>0.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>120.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.000000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Set frame rate to 0 to automatically change based on PAL/NTSC and with interlacing on and off.</string>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::PlainText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fast-forward frame skip:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_fast_forward_skip_frames">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> frames</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_6">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Rewind</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rewind buffer size (0 to disable):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="spinBox_rewind_frames"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Frames between steps</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QSpinBox" name="spinBox_rewind_buffer_size">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> MB</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>Hacks</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_allow_invalid_vram_access">
|
||||||
|
<property name="text">
|
||||||
|
<string>Allow invalid VRAM access</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_overclock">
|
||||||
|
<property name="text">
|
||||||
|
<string>Overclock the CPU</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="checkBox_use_shadow_echo_buffer">
|
||||||
|
<property name="text">
|
||||||
|
<string>Use a shadow echo buffer</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_allow_opposing_dpad_directions">
|
||||||
|
<property name="text">
|
||||||
|
<string>Allow opposite D-pad directions simultaneously</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QCheckBox" name="checkBox_remove_sprite_limit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Remove the sprite limit</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>SuperFX clock speed:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_superfx_clock_speed">
|
||||||
|
<property name="suffix">
|
||||||
|
<string>%</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>50</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<number>10</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_4">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_7">
|
||||||
|
<property name="text">
|
||||||
|
<string>Alternate sound filter:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBox_sound_filter">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Nearest</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Linear</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Gaussian</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Cubic</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Sinc</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_5">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "FoldersPanel.hpp"
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
FoldersPanel::FoldersPanel(EmuApplication *app_)
|
||||||
|
: app(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
connectEntry(comboBox_sram, lineEdit_sram, pushButton_sram, &app->config->sram_location, &app->config->sram_folder);
|
||||||
|
connectEntry(comboBox_state, lineEdit_state, pushButton_state, &app->config->state_location, &app->config->state_folder);
|
||||||
|
connectEntry(comboBox_cheat, lineEdit_cheat, pushButton_cheat, &app->config->cheat_location, &app->config->cheat_folder);
|
||||||
|
connectEntry(comboBox_patch, lineEdit_patch, pushButton_patch, &app->config->patch_location, &app->config->patch_folder);
|
||||||
|
connectEntry(comboBox_export, lineEdit_export, pushButton_export, &app->config->export_location, &app->config->export_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
FoldersPanel::~FoldersPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldersPanel::connectEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButton *browse, int *location, std::string *folder)
|
||||||
|
{
|
||||||
|
auto config = app->config.get();
|
||||||
|
|
||||||
|
QObject::connect(combo, &QComboBox::activated, [=](int index) {
|
||||||
|
*location = index;
|
||||||
|
refreshEntry(combo, lineEdit, browse, location, folder);
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(browse, &QPushButton::pressed, [=] {
|
||||||
|
QFileDialog dialog(this, tr("Select a Folder"));
|
||||||
|
dialog.setFileMode(QFileDialog::Directory);
|
||||||
|
dialog.setDirectory(QString::fromUtf8(*folder));
|
||||||
|
if (!dialog.exec())
|
||||||
|
return;
|
||||||
|
*folder = dialog.selectedFiles().at(0).toUtf8();
|
||||||
|
lineEdit->setText(QString::fromUtf8(*folder));
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldersPanel::refreshData()
|
||||||
|
{
|
||||||
|
refreshEntry(comboBox_sram, lineEdit_sram, pushButton_sram, &app->config->sram_location, &app->config->sram_folder);
|
||||||
|
refreshEntry(comboBox_state, lineEdit_state, pushButton_state, &app->config->state_location, &app->config->state_folder);
|
||||||
|
refreshEntry(comboBox_cheat, lineEdit_cheat, pushButton_cheat, &app->config->cheat_location, &app->config->cheat_folder);
|
||||||
|
refreshEntry(comboBox_patch, lineEdit_patch, pushButton_patch, &app->config->patch_location, &app->config->patch_folder);
|
||||||
|
refreshEntry(comboBox_export, lineEdit_export, pushButton_export, &app->config->export_location, &app->config->export_folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldersPanel::refreshEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButton *browse, int *location, std::string *folder)
|
||||||
|
{
|
||||||
|
bool custom = (*location == EmuConfig::eCustomDirectory);
|
||||||
|
combo->setCurrentIndex(*location);
|
||||||
|
lineEdit->setText(custom ? QString::fromUtf8(*folder) : "");
|
||||||
|
lineEdit->setEnabled(custom);
|
||||||
|
browse->setEnabled(custom);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldersPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
refreshData();
|
||||||
|
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_FoldersPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
class FoldersPanel :
|
||||||
|
public Ui::FoldersPanel,
|
||||||
|
public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FoldersPanel(EmuApplication *app);
|
||||||
|
~FoldersPanel();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void connectEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButton *browse, int *location, std::string *folder);
|
||||||
|
void refreshEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButton *browse, int *location, std::string *folder);
|
||||||
|
void refreshData();
|
||||||
|
EmuApplication *app;
|
||||||
|
};
|
|
@ -0,0 +1,282 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FoldersPanel</class>
|
||||||
|
<widget class="QWidget" name="FoldersPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>779</width>
|
||||||
|
<height>673</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Data Locations</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="5" column="2">
|
||||||
|
<widget class="QComboBox" name="comboBox_export">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ROM Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Config Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QComboBox" name="comboBox_state">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ROM Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Config Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="2">
|
||||||
|
<widget class="QComboBox" name="comboBox_sram">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ROM Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Config Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save states</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="4">
|
||||||
|
<widget class="QPushButton" name="pushButton_sram">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0" colspan="2">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>SRAM</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="3">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_sram">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="2">
|
||||||
|
<widget class="QComboBox" name="comboBox_patch">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ROM Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Config Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="2">
|
||||||
|
<widget class="QComboBox" name="comboBox_cheat">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>ROM Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Config Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Custom Folder</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cheats</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Patches</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Exports</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_state">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="3">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_cheat">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="3">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_patch">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="3">
|
||||||
|
<widget class="QLineEdit" name="lineEdit_export">
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="4">
|
||||||
|
<widget class="QPushButton" name="pushButton_state">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="4">
|
||||||
|
<widget class="QPushButton" name="pushButton_cheat">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="4">
|
||||||
|
<widget class="QPushButton" name="pushButton_patch">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="4">
|
||||||
|
<widget class="QPushButton" name="pushButton_export">
|
||||||
|
<property name="text">
|
||||||
|
<string>Browse...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>SRAM</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save SRAM to disk every:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QSpinBox" name="spinBox_sram_interval">
|
||||||
|
<property name="suffix">
|
||||||
|
<string> seconds</string>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include "GeneralPanel.hpp"
|
||||||
|
|
||||||
|
GeneralPanel::GeneralPanel(EmuApplication *app_)
|
||||||
|
: app(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
auto connectCheckbox = [&](QCheckBox *box, bool *config)
|
||||||
|
{
|
||||||
|
QObject::connect(box, &QCheckBox::clicked, [&, config](bool checked)
|
||||||
|
{
|
||||||
|
*config = checked;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
connectCheckbox(checkBox_fullscreen_on_open, &app->config->fullscreen_on_open);
|
||||||
|
connectCheckbox(checkBox_disable_screensaver, &app->config->disable_screensaver);
|
||||||
|
connectCheckbox(checkBox_pause_when_unfocused, &app->config->pause_emulation_when_unfocused);
|
||||||
|
connectCheckbox(checkBox_show_frame_rate, &app->config->show_frame_rate);
|
||||||
|
connectCheckbox(checkBox_show_indicators, &app->config->show_indicators);
|
||||||
|
connectCheckbox(checkBox_show_pressed_keys, &app->config->show_pressed_keys);
|
||||||
|
connectCheckbox(checkBox_show_time, &app->config->show_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
GeneralPanel::~GeneralPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeneralPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
auto &config = app->config;
|
||||||
|
checkBox_fullscreen_on_open->setChecked(config->fullscreen_on_open);
|
||||||
|
checkBox_disable_screensaver->setChecked(config->disable_screensaver);
|
||||||
|
checkBox_disable_screensaver->setVisible(false);
|
||||||
|
checkBox_pause_when_unfocused->setChecked(config->pause_emulation_when_unfocused);
|
||||||
|
checkBox_show_frame_rate->setChecked(config->show_frame_rate);
|
||||||
|
checkBox_show_indicators->setChecked(config->show_indicators);
|
||||||
|
checkBox_show_pressed_keys->setChecked(config->show_pressed_keys);
|
||||||
|
checkBox_show_time->setChecked(config->show_time);
|
||||||
|
|
||||||
|
QWidget::showEvent(event);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_GeneralPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
class GeneralPanel :
|
||||||
|
public Ui::GeneralPanel,
|
||||||
|
public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GeneralPanel(EmuApplication *app);
|
||||||
|
~GeneralPanel();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
|
||||||
|
EmuApplication *app;
|
||||||
|
};
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>GeneralPanel</class>
|
||||||
|
<widget class="QWidget" name="GeneralPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>752</width>
|
||||||
|
<height>615</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>General</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_fullscreen_on_open">
|
||||||
|
<property name="text">
|
||||||
|
<string>Switch to fullscreen after opening a ROM</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_disable_screensaver">
|
||||||
|
<property name="text">
|
||||||
|
<string>Disable screensaver</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_pause_when_unfocused">
|
||||||
|
<property name="text">
|
||||||
|
<string>Pause emulation when window is not focused</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Overlays</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_show_frame_rate">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show frame rate</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_show_indicators">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show fast-forward and pause icons</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_show_pressed_keys">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show pressed keys</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_show_time">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show the time</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,229 @@
|
||||||
|
#include "SDLInputManager.hpp"
|
||||||
|
#include "SDL.h"
|
||||||
|
#include "SDL_events.h"
|
||||||
|
#include "SDL_gamecontroller.h"
|
||||||
|
#include "SDL_joystick.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
SDLInputManager::SDLInputManager()
|
||||||
|
{
|
||||||
|
SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLInputManager::~SDLInputManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInputManager::AddDevice(int device_index)
|
||||||
|
{
|
||||||
|
SDLInputDevice d;
|
||||||
|
if (!d.open(device_index))
|
||||||
|
return;
|
||||||
|
d.index = FindFirstOpenIndex();
|
||||||
|
|
||||||
|
printf("Slot %d: %s: ", d.index, SDL_JoystickName(d.joystick));
|
||||||
|
printf("%zu axes, %zu buttons, %zu hats, %s API\n", d.axes.size(), d.buttons.size(), d.hats.size(), d.is_controller ? "Controller" : "Joystick");
|
||||||
|
|
||||||
|
devices.insert({ d.instance_id, d });
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInputManager::RemoveDevice(int instance_id)
|
||||||
|
{
|
||||||
|
auto iter = devices.find(instance_id);
|
||||||
|
if (iter == devices.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto &d = iter->second;
|
||||||
|
|
||||||
|
if (d.is_controller)
|
||||||
|
SDL_GameControllerClose(d.controller);
|
||||||
|
else
|
||||||
|
SDL_JoystickClose(d.joystick);
|
||||||
|
|
||||||
|
devices.erase(iter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInputManager::ClearEvents()
|
||||||
|
{
|
||||||
|
std::optional<SDL_Event> event;
|
||||||
|
|
||||||
|
while ((event = ProcessEvent()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<SDLInputManager::DiscreteHatEvent>
|
||||||
|
SDLInputManager::DiscretizeHatEvent(SDL_Event &event)
|
||||||
|
{
|
||||||
|
auto &device = devices.at(event.jhat.which);
|
||||||
|
auto &hat = event.jhat.hat;
|
||||||
|
auto new_state = event.jhat.value;
|
||||||
|
auto &old_state = device.hats[hat].state;
|
||||||
|
|
||||||
|
if (old_state == new_state)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
DiscreteHatEvent dhe{};
|
||||||
|
dhe.hat = hat;
|
||||||
|
dhe.joystick_num = device.index;
|
||||||
|
|
||||||
|
for (auto &s : { SDL_HAT_UP, SDL_HAT_DOWN, SDL_HAT_LEFT, SDL_HAT_RIGHT })
|
||||||
|
if ((old_state & s) != (new_state & s))
|
||||||
|
{
|
||||||
|
printf(" old: %d, new: %d\n", old_state, new_state);
|
||||||
|
dhe.direction = s;
|
||||||
|
dhe.pressed = (new_state & s);
|
||||||
|
old_state = new_state;
|
||||||
|
return dhe;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SDLInputManager::DiscreteAxisEvent>
|
||||||
|
SDLInputManager::DiscretizeJoyAxisEvent(SDL_Event &event)
|
||||||
|
{
|
||||||
|
auto &device = devices.at(event.jaxis.which);
|
||||||
|
auto &axis = event.jaxis.axis;
|
||||||
|
auto now = event.jaxis.value;
|
||||||
|
auto &then = device.axes[axis].last;
|
||||||
|
auto center = device.axes[axis].initial;
|
||||||
|
|
||||||
|
int offset = now - center;
|
||||||
|
|
||||||
|
auto pressed = [&](int axis) -> int {
|
||||||
|
if (axis > (center + (32767 - center) / 3)) // TODO threshold
|
||||||
|
return 1;
|
||||||
|
if (axis < (center - (center + 32768) / 3)) // TODO threshold
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto was_pressed_then = pressed(then);
|
||||||
|
auto is_pressed_now = pressed(now);
|
||||||
|
|
||||||
|
if (was_pressed_then == is_pressed_now)
|
||||||
|
{
|
||||||
|
then = now;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscreteAxisEvent dae;
|
||||||
|
dae.axis = axis;
|
||||||
|
dae.direction = is_pressed_now ? is_pressed_now : was_pressed_then;
|
||||||
|
dae.pressed = (is_pressed_now != 0);
|
||||||
|
dae.joystick_num = device.index;
|
||||||
|
then = now;
|
||||||
|
|
||||||
|
return dae;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<SDL_Event> SDLInputManager::ProcessEvent()
|
||||||
|
{
|
||||||
|
SDL_Event event{};
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case SDL_JOYAXISMOTION:
|
||||||
|
return event;
|
||||||
|
case SDL_JOYHATMOTION:
|
||||||
|
return event;
|
||||||
|
case SDL_JOYBUTTONUP:
|
||||||
|
case SDL_JOYBUTTONDOWN:
|
||||||
|
return event;
|
||||||
|
case SDL_JOYDEVICEADDED:
|
||||||
|
AddDevice(event.jdevice.which);
|
||||||
|
return event;
|
||||||
|
case SDL_JOYDEVICEREMOVED:
|
||||||
|
RemoveDevice(event.jdevice.which);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInputManager::PrintDevices()
|
||||||
|
{
|
||||||
|
for (auto &pair : devices)
|
||||||
|
{
|
||||||
|
auto &d = pair.second;
|
||||||
|
printf("%s: \n", SDL_JoystickName(d.joystick));
|
||||||
|
printf(" Index: %d\n"
|
||||||
|
" Instance ID: %d\n"
|
||||||
|
" Controller %s\n"
|
||||||
|
" SDL Joystick Number: %d\n",
|
||||||
|
d.index,
|
||||||
|
d.instance_id,
|
||||||
|
d.is_controller ? "yes" : "no",
|
||||||
|
d.sdl_joystick_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDLInputManager::FindFirstOpenIndex()
|
||||||
|
{
|
||||||
|
for (int i = 0;; i++)
|
||||||
|
{
|
||||||
|
if (std::none_of(devices.begin(), devices.end(), [i](auto &d) -> bool {
|
||||||
|
return (d.second.index == i);
|
||||||
|
}))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLInputDevice::open(int joystick_num)
|
||||||
|
{
|
||||||
|
sdl_joystick_number = joystick_num;
|
||||||
|
is_controller = SDL_IsGameController(joystick_num);
|
||||||
|
|
||||||
|
if (is_controller)
|
||||||
|
{
|
||||||
|
controller = SDL_GameControllerOpen(joystick_num);
|
||||||
|
joystick = SDL_GameControllerGetJoystick(controller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
joystick = SDL_JoystickOpen(joystick_num);
|
||||||
|
controller = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!joystick)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto num_axes = SDL_JoystickNumAxes(joystick);
|
||||||
|
axes.resize(num_axes);
|
||||||
|
for (int i = 0; i < num_axes; i++)
|
||||||
|
{
|
||||||
|
SDL_JoystickGetAxisInitialState(joystick, i, &axes[i].initial);
|
||||||
|
axes[i].last = axes[i].initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.resize(SDL_JoystickNumButtons(joystick));
|
||||||
|
hats.resize(SDL_JoystickNumHats(joystick));
|
||||||
|
instance_id = SDL_JoystickInstanceID(joystick);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<int, std::string>> SDLInputManager::getXInputControllers()
|
||||||
|
{
|
||||||
|
std::vector<std::pair<int, std::string>> list;
|
||||||
|
|
||||||
|
for (auto &d : devices)
|
||||||
|
{
|
||||||
|
if (!d.second.is_controller)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
list.push_back(std::pair<int, std::string>(d.first, SDL_JoystickName(d.second.joystick)));
|
||||||
|
auto bind = SDL_GameControllerGetBindForButton(d.second.controller, SDL_CONTROLLER_BUTTON_A);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
struct SDLInputDevice
|
||||||
|
{
|
||||||
|
bool open(int joystick_num);
|
||||||
|
|
||||||
|
int index;
|
||||||
|
int sdl_joystick_number;
|
||||||
|
bool is_controller;
|
||||||
|
SDL_GameController *controller = nullptr;
|
||||||
|
SDL_Joystick *joystick = nullptr;
|
||||||
|
SDL_JoystickID instance_id;
|
||||||
|
|
||||||
|
struct Axis
|
||||||
|
{
|
||||||
|
int16_t initial;
|
||||||
|
int last;
|
||||||
|
};
|
||||||
|
std::vector<Axis> axes;
|
||||||
|
|
||||||
|
struct Hat
|
||||||
|
{
|
||||||
|
uint8_t state;
|
||||||
|
};
|
||||||
|
std::vector<Hat> hats;
|
||||||
|
|
||||||
|
std::vector<bool> buttons;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SDLInputManager
|
||||||
|
{
|
||||||
|
SDLInputManager();
|
||||||
|
~SDLInputManager();
|
||||||
|
|
||||||
|
std::optional<SDL_Event> ProcessEvent();
|
||||||
|
std::vector<std::pair<int, std::string>> getXInputControllers();
|
||||||
|
void ClearEvents();
|
||||||
|
void AddDevice(int i);
|
||||||
|
void RemoveDevice(int i);
|
||||||
|
void PrintDevices();
|
||||||
|
int FindFirstOpenIndex();
|
||||||
|
|
||||||
|
struct DiscreteAxisEvent
|
||||||
|
{
|
||||||
|
int joystick_num;
|
||||||
|
int axis;
|
||||||
|
int direction;
|
||||||
|
int pressed;
|
||||||
|
};
|
||||||
|
std::optional<DiscreteAxisEvent> DiscretizeJoyAxisEvent(SDL_Event &event);
|
||||||
|
|
||||||
|
struct DiscreteHatEvent
|
||||||
|
{
|
||||||
|
int joystick_num;
|
||||||
|
int hat;
|
||||||
|
int direction;
|
||||||
|
bool pressed;
|
||||||
|
};
|
||||||
|
std::optional<DiscreteHatEvent> DiscretizeHatEvent(SDL_Event &event);
|
||||||
|
|
||||||
|
std::map<SDL_JoystickID, SDLInputDevice> devices;
|
||||||
|
};
|
|
@ -0,0 +1,201 @@
|
||||||
|
#include "ShaderParametersDialog.hpp"
|
||||||
|
#include <QLayout>
|
||||||
|
#include <QTableWidget>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QSizePolicy>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSpacerItem>
|
||||||
|
#include <QScrollArea>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
static bool is_simple(const EmuCanvas::Parameter &p)
|
||||||
|
{
|
||||||
|
return (p.min == 0.0 && p.max == 1.0 && p.step == 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_pointless(const EmuCanvas::Parameter &p)
|
||||||
|
{
|
||||||
|
return (p.min == p.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderParametersDialog::ShaderParametersDialog(EmuCanvas *parent_, std::vector<EmuCanvas::Parameter> *parameters_)
|
||||||
|
: QDialog(parent_), canvas(parent_), config(parent_->config), parameters(parameters_)
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Shader Parameters"));
|
||||||
|
setMinimumSize(600, 200);
|
||||||
|
auto layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
auto scroll_area = new QScrollArea(this);
|
||||||
|
scroll_area->setFrameShape(QFrame::Shape::StyledPanel);
|
||||||
|
scroll_area->setWidgetResizable(true);
|
||||||
|
auto scroll_area_widget_contents = new QWidget();
|
||||||
|
scroll_area_widget_contents->setGeometry(0, 0, 400, 300);
|
||||||
|
|
||||||
|
auto grid = new QGridLayout(scroll_area_widget_contents);
|
||||||
|
scroll_area->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||||
|
|
||||||
|
auto buttonbox = new QHBoxLayout();
|
||||||
|
|
||||||
|
for (int i = 0; i < parameters->size(); i++)
|
||||||
|
{
|
||||||
|
auto &p = (*parameters)[i];
|
||||||
|
QSlider *slider = nullptr;
|
||||||
|
QDoubleSpinBox *spinbox = nullptr;
|
||||||
|
QCheckBox *checkbox = nullptr;
|
||||||
|
|
||||||
|
auto label = new QLabel(p.name.c_str(), scroll_area_widget_contents);
|
||||||
|
grid->addWidget(label, i, 0, 1, 1);
|
||||||
|
|
||||||
|
if (is_pointless(p))
|
||||||
|
{
|
||||||
|
widgets.push_back({});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_simple(p))
|
||||||
|
{
|
||||||
|
checkbox = new QCheckBox(scroll_area_widget_contents);
|
||||||
|
checkbox->setChecked(p.val == 1.0);
|
||||||
|
grid->addWidget(checkbox, i, 1, 1, 2);
|
||||||
|
QObject::connect(checkbox, &QCheckBox::clicked, [&, i](bool checked) {
|
||||||
|
(*parameters)[i].val = checked ? 1.0 : 0.0;
|
||||||
|
});
|
||||||
|
widgets.push_back({ slider, spinbox, checkbox });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
slider = new QSlider(scroll_area_widget_contents);
|
||||||
|
grid->addWidget(slider, i, 1, 1, 1);
|
||||||
|
slider->setOrientation(Qt::Horizontal);
|
||||||
|
slider->setTickInterval(1);
|
||||||
|
slider->setRange(0, (p.max - p.min) / p.step);
|
||||||
|
slider->setValue(p.val / p.step);
|
||||||
|
|
||||||
|
spinbox = new QDoubleSpinBox(scroll_area_widget_contents);
|
||||||
|
grid->addWidget(spinbox, i, 2, 1, 1);
|
||||||
|
spinbox->setDecimals(p.significant_digits > 5 ? 5 : p.significant_digits);
|
||||||
|
spinbox->setRange(p.min, p.max);
|
||||||
|
spinbox->setSingleStep(p.step);
|
||||||
|
spinbox->setValue(p.val);
|
||||||
|
|
||||||
|
QObject::connect(slider, &QSlider::valueChanged, [&, i, slider, spinbox](int value) {
|
||||||
|
auto &p = (*parameters)[i];
|
||||||
|
double new_value = value * p.step + p.min;
|
||||||
|
spinbox->blockSignals(true);
|
||||||
|
spinbox->setValue(new_value);
|
||||||
|
spinbox->blockSignals(false);
|
||||||
|
p.val = new_value;
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(spinbox, &QDoubleSpinBox::valueChanged, [&, i, slider, spinbox](double value) {
|
||||||
|
auto &p = (*parameters)[i];
|
||||||
|
int steps = round((value - p.min) / p.step);
|
||||||
|
p.val = steps * p.step + p.min;
|
||||||
|
|
||||||
|
slider->blockSignals(true);
|
||||||
|
slider->setValue(steps);
|
||||||
|
slider->blockSignals(false);
|
||||||
|
|
||||||
|
spinbox->blockSignals(true);
|
||||||
|
spinbox->setValue(p.val);
|
||||||
|
spinbox->blockSignals(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
widgets.push_back({ slider, spinbox, checkbox });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto reset = new QPushButton(tr("&Reset"), this);
|
||||||
|
QObject::connect(reset, &QPushButton::clicked, [&] {
|
||||||
|
*parameters = saved_parameters;
|
||||||
|
refreshWidgets();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonbox->addWidget(reset);
|
||||||
|
|
||||||
|
buttonbox->addStretch(1);
|
||||||
|
|
||||||
|
auto saveas = new QPushButton(tr("Save &As"), this);
|
||||||
|
buttonbox->addWidget(saveas);
|
||||||
|
connect(saveas, &QPushButton::clicked, [&] {
|
||||||
|
saveAs();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto closebutton = new QPushButton(tr("&Save"), this);
|
||||||
|
connect(closebutton, &QPushButton::clicked, [&] {
|
||||||
|
save();
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonbox->addWidget(closebutton);
|
||||||
|
scroll_area->setWidget(scroll_area_widget_contents);
|
||||||
|
layout->addWidget(scroll_area);
|
||||||
|
layout->addLayout(buttonbox, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderParametersDialog::save()
|
||||||
|
{
|
||||||
|
if (std::equal(parameters->begin(), parameters->end(), saved_parameters.begin()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString shadername(config->shader.c_str());
|
||||||
|
std::string extension;
|
||||||
|
if (shadername.endsWith("slangp", Qt::CaseInsensitive))
|
||||||
|
extension = ".slangp";
|
||||||
|
else if (shadername.endsWith("glslp", Qt::CaseInsensitive))
|
||||||
|
extension = ".glslp";
|
||||||
|
|
||||||
|
saved_parameters = *parameters;
|
||||||
|
|
||||||
|
auto filename = config->findConfigDir() + "/customized_shader" + extension;
|
||||||
|
canvas->saveParameters(filename);
|
||||||
|
config->shader = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderParametersDialog::saveAs()
|
||||||
|
{
|
||||||
|
auto folder = config->last_shader_folder;
|
||||||
|
auto filename = QFileDialog::getSaveFileName(this, tr("Save Shader Preset As"), folder.c_str());
|
||||||
|
canvas->saveParameters(filename.toStdString());
|
||||||
|
config->shader = filename.toStdString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderParametersDialog::refreshWidgets()
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < widgets.size(); i++)
|
||||||
|
{
|
||||||
|
auto &p = (*parameters)[i];
|
||||||
|
|
||||||
|
if (is_pointless(p))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto [slider, spinbox, checkbox] = widgets[i];
|
||||||
|
|
||||||
|
if (is_simple(p))
|
||||||
|
{
|
||||||
|
checkbox->setChecked(p.val == 1.0 ? true : false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spinbox->blockSignals(true);
|
||||||
|
spinbox->setValue(p.val);
|
||||||
|
spinbox->blockSignals(false);
|
||||||
|
|
||||||
|
slider->blockSignals(true);
|
||||||
|
slider->setValue((p.val - p.min) / p.step);
|
||||||
|
slider->blockSignals(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderParametersDialog::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
refreshWidgets();
|
||||||
|
saved_parameters = *parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderParametersDialog::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
*parameters = saved_parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderParametersDialog::~ShaderParametersDialog()
|
||||||
|
{
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
#include <QCheckBox>
|
||||||
|
|
||||||
|
#include "EmuCanvas.hpp"
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
|
||||||
|
class ShaderParametersDialog : public QDialog
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShaderParametersDialog(EmuCanvas *parent, std::vector<EmuCanvas::Parameter> *parameters);
|
||||||
|
~ShaderParametersDialog();
|
||||||
|
|
||||||
|
void refreshWidgets();
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
void save();
|
||||||
|
void saveAs();
|
||||||
|
|
||||||
|
std::vector<std::tuple<QSlider *, QDoubleSpinBox *, QCheckBox *>> widgets;
|
||||||
|
std::vector<EmuCanvas::Parameter> saved_parameters;
|
||||||
|
std::vector<EmuCanvas::Parameter> *parameters;
|
||||||
|
|
||||||
|
EmuCanvas *canvas = nullptr;
|
||||||
|
EmuConfig *config = nullptr;
|
||||||
|
};
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "ShortcutsPanel.hpp"
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
|
||||||
|
ShortcutsPanel::ShortcutsPanel(EmuApplication *app_)
|
||||||
|
: BindingPanel(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
setTableWidget(tableWidget_shortcuts,
|
||||||
|
app->config->binding.shortcuts,
|
||||||
|
app->config->allowed_bindings,
|
||||||
|
app->config->num_shortcuts);
|
||||||
|
|
||||||
|
toolButton_reset_to_default->setPopupMode(QToolButton::InstantPopup);
|
||||||
|
|
||||||
|
for (auto slot = 0; slot < app->config->allowed_bindings; slot++)
|
||||||
|
{
|
||||||
|
auto action = reset_to_default_menu.addAction(tr("Slot %1").arg(slot));
|
||||||
|
QObject::connect(action, &QAction::triggered, [&, slot](bool checked) {
|
||||||
|
setDefaultKeys(slot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toolButton_reset_to_default->setMenu(&reset_to_default_menu);
|
||||||
|
|
||||||
|
connect(pushButton_clear_all, &QAbstractButton::clicked, [&](bool) {
|
||||||
|
for (auto &b : app->config->binding.shortcuts)
|
||||||
|
b = {};
|
||||||
|
fillTable();
|
||||||
|
});
|
||||||
|
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortcutsPanel::~ShortcutsPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShortcutsPanel::setDefaultKeys(int slot)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < app->config->num_shortcuts; i++)
|
||||||
|
{
|
||||||
|
std::string str = EmuConfig::getDefaultShortcutKeys()[i];
|
||||||
|
if (!str.empty())
|
||||||
|
app->config->binding.shortcuts[i * 4 + slot] = EmuBinding::from_config_string(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
fillTable();
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_ShortcutsPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include "BindingPanel.hpp"
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
class ShortcutsPanel :
|
||||||
|
public Ui::ShortcutsPanel,
|
||||||
|
public BindingPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ShortcutsPanel(EmuApplication *app);
|
||||||
|
~ShortcutsPanel();
|
||||||
|
|
||||||
|
void setDefaultKeys(int slot);
|
||||||
|
QMenu reset_to_default_menu;
|
||||||
|
};
|
|
@ -0,0 +1,357 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ShortcutsPanel</class>
|
||||||
|
<widget class="QWidget" name="ShortcutsPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>797</width>
|
||||||
|
<height>716</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="toolButton_reset_to_default">
|
||||||
|
<property name="text">
|
||||||
|
<string>Reset to Default</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="pushButton_clear_all">
|
||||||
|
<property name="text">
|
||||||
|
<string>Clear All</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="tableWidget_shortcuts">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Open ROM</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Enable Fast-Forward</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Fast-Forward</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Pause/Continue</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Reset</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Power Cycle Console</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quit</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Fullscreen</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save Screenshot</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save SPC</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save State to Current Slot</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Load State from Current Slot</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Increase Current Save Slot</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Decrease Current Save Slot</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 0</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 1</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 2</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 3</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 4</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 5</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 6</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 7</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 8</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Save Slot 9</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 0</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 1</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 2</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 3</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 4</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 5</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 6</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 7</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 8</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Quick Load Slot 9</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Rewind</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Grab Mouse</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Swap Controllers 1 and 2</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle BG Layer 0</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle BG Layer 1</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle BG Layer 2</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle BG Layer 3</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sprites</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Change Backdrop Color</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 1</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 2</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 3</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 4</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 5</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 6</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 7</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle Sound Channel 8</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Toggle All Sound Channels</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start Recording</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Stop Recording</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Seek to Frame</string>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #2</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #3</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #4</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,749 @@
|
||||||
|
#include "Snes9xController.hpp"
|
||||||
|
#include "SoftwareScalers.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include <filesystem>
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
#include "snes9x.h"
|
||||||
|
#include "memmap.h"
|
||||||
|
#include "srtc.h"
|
||||||
|
#include "apu/apu.h"
|
||||||
|
#include "apu/bapu/snes/snes.hpp"
|
||||||
|
#include "gfx.h"
|
||||||
|
#include "snapshot.h"
|
||||||
|
#include "controls.h"
|
||||||
|
#include "cheats.h"
|
||||||
|
#include "movie.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "conffile.h"
|
||||||
|
#include "statemanager.h"
|
||||||
|
|
||||||
|
Snes9xController *g_snes9xcontroller = nullptr;
|
||||||
|
StateManager g_state_manager;
|
||||||
|
|
||||||
|
Snes9xController::Snes9xController()
|
||||||
|
{
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
Snes9xController::~Snes9xController()
|
||||||
|
{
|
||||||
|
deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Snes9xController *Snes9xController::get()
|
||||||
|
{
|
||||||
|
if (!g_snes9xcontroller)
|
||||||
|
{
|
||||||
|
g_snes9xcontroller = new Snes9xController();
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_snes9xcontroller;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::init()
|
||||||
|
{
|
||||||
|
Settings.MouseMaster = true;
|
||||||
|
Settings.SuperScopeMaster = true;
|
||||||
|
Settings.JustifierMaster = true;
|
||||||
|
Settings.MultiPlayer5Master = true;
|
||||||
|
Settings.Transparency = true;
|
||||||
|
Settings.Stereo = true;
|
||||||
|
Settings.ReverseStereo = false;
|
||||||
|
Settings.SixteenBitSound = true;
|
||||||
|
Settings.StopEmulation = true;
|
||||||
|
Settings.HDMATimingHack = 100;
|
||||||
|
Settings.SkipFrames = 0;
|
||||||
|
Settings.TurboSkipFrames = 9;
|
||||||
|
Settings.NetPlay = false;
|
||||||
|
|
||||||
|
Settings.UpAndDown = false;
|
||||||
|
Settings.InterpolationMethod = DSP_INTERPOLATION_GAUSSIAN;
|
||||||
|
Settings.FrameTime = 16639;
|
||||||
|
Settings.FrameTimeNTSC = 16639;
|
||||||
|
Settings.FrameTimePAL = 20000;
|
||||||
|
Settings.DisplayFrameRate = false;
|
||||||
|
Settings.DisplayTime = false;
|
||||||
|
Settings.DisplayFrameRate = false;
|
||||||
|
Settings.DisplayPressedKeys = false;
|
||||||
|
Settings.DisplayIndicators = true;
|
||||||
|
Settings.SoundPlaybackRate = 48000;
|
||||||
|
Settings.SoundInputRate = 32040;
|
||||||
|
Settings.BlockInvalidVRAMAccess = true;
|
||||||
|
Settings.SoundSync = false;
|
||||||
|
Settings.Mute = false;
|
||||||
|
Settings.DynamicRateControl = false;
|
||||||
|
Settings.DynamicRateLimit = 5;
|
||||||
|
Settings.SuperFXClockMultiplier = 100;
|
||||||
|
Settings.MaxSpriteTilesPerLine = 34;
|
||||||
|
Settings.OneClockCycle = 6;
|
||||||
|
Settings.OneSlowClockCycle = 8;
|
||||||
|
Settings.TwoClockCycles = 12;
|
||||||
|
Settings.ShowOverscan = false;
|
||||||
|
Settings.InitialInfoStringTimeout = 120;
|
||||||
|
|
||||||
|
CPU.Flags = 0;
|
||||||
|
|
||||||
|
rewind_buffer_size = 0;
|
||||||
|
rewind_frame_interval = 5;
|
||||||
|
|
||||||
|
Memory.Init();
|
||||||
|
S9xInitAPU();
|
||||||
|
S9xInitSound(0);
|
||||||
|
S9xSetSamplesAvailableCallback([](void *data) {
|
||||||
|
((Snes9xController *)data)->SamplesAvailable();
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
S9xGraphicsInit();
|
||||||
|
S9xInitInputDevices();
|
||||||
|
S9xUnmapAllControls();
|
||||||
|
S9xCheatsEnable();
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::deinit()
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
S9xAutoSaveSRAM();
|
||||||
|
S9xGraphicsDeinit();
|
||||||
|
S9xDeinitAPU();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::updateSettings(const EmuConfig * const config)
|
||||||
|
{
|
||||||
|
Settings.UpAndDown = config->allow_opposing_dpad_directions;
|
||||||
|
|
||||||
|
Settings.InterpolationMethod = config->sound_filter;
|
||||||
|
|
||||||
|
if (config->fixed_frame_rate == 0.0)
|
||||||
|
{
|
||||||
|
Settings.FrameTimeNTSC = 16639;
|
||||||
|
Settings.FrameTimePAL = 20000;
|
||||||
|
Settings.FrameTime = Settings.FrameTimeNTSC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Settings.FrameTimeNTSC = Settings.FrameTimePAL = Settings.FrameTime =
|
||||||
|
1000000 / config->fixed_frame_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.TurboSkipFrames = config->fast_forward_skip_frames;
|
||||||
|
|
||||||
|
Settings.DisplayTime = config->show_time;
|
||||||
|
|
||||||
|
if (config->display_messages == EmuConfig::eInscreen)
|
||||||
|
Settings.AutoDisplayMessages = true;
|
||||||
|
else
|
||||||
|
Settings.AutoDisplayMessages = false;
|
||||||
|
|
||||||
|
Settings.DisplayFrameRate = config->show_frame_rate;
|
||||||
|
|
||||||
|
Settings.DisplayPressedKeys = config->show_pressed_keys;
|
||||||
|
|
||||||
|
Settings.DisplayIndicators = config->show_indicators;
|
||||||
|
|
||||||
|
if (Settings.SoundPlaybackRate != config->playback_rate || Settings.SoundInputRate != config->input_rate)
|
||||||
|
{
|
||||||
|
Settings.SoundInputRate = config->input_rate;
|
||||||
|
Settings.SoundPlaybackRate = config->playback_rate;
|
||||||
|
S9xUpdateDynamicRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.BlockInvalidVRAMAccess = !config->allow_invalid_vram_access;
|
||||||
|
|
||||||
|
Settings.SoundSync = config->speed_sync_method == EmuConfig::eSoundSync;
|
||||||
|
|
||||||
|
Settings.Mute = config->mute_audio;
|
||||||
|
|
||||||
|
Settings.DynamicRateControl = config->dynamic_rate_control;
|
||||||
|
|
||||||
|
Settings.DynamicRateLimit = config->dynamic_rate_limit * 1000;
|
||||||
|
|
||||||
|
Settings.SuperFXClockMultiplier = config->superfx_clock_multiplier;
|
||||||
|
|
||||||
|
if (rewind_buffer_size != config->rewind_buffer_size && active)
|
||||||
|
{
|
||||||
|
g_state_manager.init(config->rewind_buffer_size * 1048576);
|
||||||
|
}
|
||||||
|
rewind_buffer_size = config->rewind_buffer_size;
|
||||||
|
rewind_frame_interval = config->rewind_frame_interval;
|
||||||
|
|
||||||
|
if (config->remove_sprite_limit)
|
||||||
|
Settings.MaxSpriteTilesPerLine = 128;
|
||||||
|
else
|
||||||
|
Settings.MaxSpriteTilesPerLine = 34;
|
||||||
|
|
||||||
|
if (!config->overclock)
|
||||||
|
{
|
||||||
|
Settings.OneClockCycle = 6;
|
||||||
|
Settings.OneSlowClockCycle = 8;
|
||||||
|
Settings.TwoClockCycles = 12;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Settings.OneClockCycle = 2;
|
||||||
|
Settings.OneSlowClockCycle = 3;
|
||||||
|
Settings.TwoClockCycles = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.ShowOverscan = config->show_overscan;
|
||||||
|
|
||||||
|
high_resolution_effect = config->high_resolution_effect;
|
||||||
|
|
||||||
|
config_folder = config->findConfigDir();
|
||||||
|
|
||||||
|
auto doFolder = [&](int location, std::string &dest, const std::string &src, const char *subfolder_name)
|
||||||
|
{
|
||||||
|
if (location == EmuConfig::eROMDirectory)
|
||||||
|
dest = "";
|
||||||
|
else if (location == EmuConfig::eConfigDirectory)
|
||||||
|
dest = config_folder + "/" + subfolder_name;
|
||||||
|
else
|
||||||
|
dest = src;
|
||||||
|
};
|
||||||
|
|
||||||
|
doFolder(config->sram_location, sram_folder, config->sram_folder, "sram");
|
||||||
|
doFolder(config->state_location, state_folder, config->state_folder, "state");
|
||||||
|
doFolder(config->cheat_location, cheat_folder, config->cheat_folder, "cheat");
|
||||||
|
doFolder(config->patch_location, patch_folder, config->patch_folder, "patch");
|
||||||
|
doFolder(config->export_location, export_folder, config->export_folder, "export");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::openFile(std::string filename)
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
S9xAutoSaveSRAM();
|
||||||
|
active = false;
|
||||||
|
auto result = Memory.LoadROM(filename.c_str());
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
active = true;
|
||||||
|
Memory.LoadSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str());
|
||||||
|
}
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::mainLoop()
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rewind_buffer_size > 0)
|
||||||
|
{
|
||||||
|
if (rewinding)
|
||||||
|
{
|
||||||
|
uint16 joypads[8];
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
joypads[i] = MovieGetJoypad(i);
|
||||||
|
|
||||||
|
rewinding = g_state_manager.pop();
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
MovieSetJoypad(i, joypads[i]);
|
||||||
|
}
|
||||||
|
else if (IPPU.TotalEmulatedFrames % rewind_frame_interval == 0)
|
||||||
|
g_state_manager.push();
|
||||||
|
|
||||||
|
if (rewinding)
|
||||||
|
Settings.Mute |= 0x80;
|
||||||
|
else
|
||||||
|
Settings.Mute &= ~0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
S9xMainLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::setPaused(bool paused)
|
||||||
|
{
|
||||||
|
Settings.Paused = paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::updateSoundBufferLevel(int empty, int total)
|
||||||
|
{
|
||||||
|
S9xUpdateDynamicRate(empty, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 S9xDeinitUpdate(int width, int height)
|
||||||
|
{
|
||||||
|
static int last_width = 0;
|
||||||
|
static int last_height = 0;
|
||||||
|
int yoffset = 0;
|
||||||
|
|
||||||
|
auto &display = Snes9xController::get()->screen_output_function;
|
||||||
|
if (display == nullptr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (width < 256 || height < 224)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (last_height > height)
|
||||||
|
memset(GFX.Screen + GFX.RealPPL * height, 0, GFX.Pitch * (last_height - height));
|
||||||
|
|
||||||
|
last_width = width;
|
||||||
|
last_height = height;
|
||||||
|
|
||||||
|
if (Settings.ShowOverscan)
|
||||||
|
{
|
||||||
|
if (height == SNES_HEIGHT)
|
||||||
|
{
|
||||||
|
yoffset = -8;
|
||||||
|
height = SNES_HEIGHT_EXTENDED;
|
||||||
|
}
|
||||||
|
if (height == SNES_HEIGHT * 2)
|
||||||
|
{
|
||||||
|
yoffset = -16;
|
||||||
|
height = SNES_HEIGHT_EXTENDED * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (height == SNES_HEIGHT_EXTENDED)
|
||||||
|
{
|
||||||
|
yoffset = 7;
|
||||||
|
height = SNES_HEIGHT;
|
||||||
|
}
|
||||||
|
if (height == SNES_HEIGHT_EXTENDED * 2)
|
||||||
|
{
|
||||||
|
yoffset = 14;
|
||||||
|
height = SNES_HEIGHT * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t *screen_view = GFX.Screen + (yoffset * (int)GFX.RealPPL);
|
||||||
|
|
||||||
|
auto hires_effect = Snes9xController::get()->high_resolution_effect;
|
||||||
|
if (!Settings.Paused)
|
||||||
|
{
|
||||||
|
if (hires_effect == EmuConfig::eScaleUp)
|
||||||
|
{
|
||||||
|
S9xForceHires(screen_view, GFX.Pitch, width, height);
|
||||||
|
last_width = width;
|
||||||
|
}
|
||||||
|
else if (hires_effect == EmuConfig::eScaleDown)
|
||||||
|
{
|
||||||
|
S9xMergeHires(screen_view, GFX.Pitch, width, height);
|
||||||
|
last_width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display(screen_view, width, height, GFX.Pitch, Settings.PAL ? 50.0 : 60.098813);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 S9xContinueUpdate(int width, int height)
|
||||||
|
{
|
||||||
|
return S9xDeinitUpdate(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xSyncSpeed()
|
||||||
|
{
|
||||||
|
if (Settings.TurboMode)
|
||||||
|
{
|
||||||
|
IPPU.FrameSkip++;
|
||||||
|
if ((IPPU.FrameSkip > Settings.TurboSkipFrames) && !Settings.HighSpeedSeek)
|
||||||
|
{
|
||||||
|
IPPU.FrameSkip = 0;
|
||||||
|
IPPU.SkippedFrames = 0;
|
||||||
|
IPPU.RenderThisFrame = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IPPU.SkippedFrames++;
|
||||||
|
IPPU.RenderThisFrame = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPPU.RenderThisFrame = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xParsePortConfig(ConfigFile&, int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string S9xGetDirectory(s9x_getdirtype dirtype)
|
||||||
|
{
|
||||||
|
std::string dirname;
|
||||||
|
auto c = Snes9xController::get();
|
||||||
|
|
||||||
|
switch (dirtype)
|
||||||
|
{
|
||||||
|
case HOME_DIR:
|
||||||
|
dirname = c->config_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SNAPSHOT_DIR:
|
||||||
|
dirname = c->state_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PATCH_DIR:
|
||||||
|
dirname = c->patch_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CHEAT_DIR:
|
||||||
|
dirname = c->cheat_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SRAM_DIR:
|
||||||
|
dirname = c->sram_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCREENSHOT_DIR:
|
||||||
|
case SPC_DIR:
|
||||||
|
dirname = c->export_folder;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dirname = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if directory exists, make it and/or set correct permissions */
|
||||||
|
if (dirtype != HOME_DIR && dirname != "")
|
||||||
|
{
|
||||||
|
fs::path path(dirname);
|
||||||
|
|
||||||
|
if (!fs::exists(path))
|
||||||
|
{
|
||||||
|
fs::create_directory(path);
|
||||||
|
}
|
||||||
|
else if ((fs::status(path).permissions() & fs::perms::owner_write) == fs::perms::none)
|
||||||
|
{
|
||||||
|
fs::permissions(path, fs::perms::owner_write, fs::perm_options::add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anything else, use ROM filename path */
|
||||||
|
if (dirname == "" && !Memory.ROMFilename.empty())
|
||||||
|
{
|
||||||
|
fs::path path(Memory.ROMFilename);
|
||||||
|
|
||||||
|
path.remove_filename();
|
||||||
|
|
||||||
|
if (!fs::is_directory(path))
|
||||||
|
dirname = fs::current_path().u8string();
|
||||||
|
else
|
||||||
|
dirname = path.u8string();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xInitInputDevices()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xHandlePortCommand(s9xcommand_t, short, short)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool S9xPollButton(unsigned int, bool *)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xToggleSoundChannel(int c)
|
||||||
|
{
|
||||||
|
static int sound_switch = 255;
|
||||||
|
|
||||||
|
if (c == 8)
|
||||||
|
sound_switch = 255;
|
||||||
|
else
|
||||||
|
sound_switch ^= 1 << c;
|
||||||
|
|
||||||
|
S9xSetSoundControl(sound_switch);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string S9xGetFilenameInc(std::string e, enum s9x_getdirtype dirtype)
|
||||||
|
{
|
||||||
|
fs::path rom_filename(Memory.ROMFilename);
|
||||||
|
|
||||||
|
fs::path filename_base(S9xGetDirectory(dirtype));
|
||||||
|
filename_base /= rom_filename.filename();
|
||||||
|
|
||||||
|
fs::path new_filename;
|
||||||
|
|
||||||
|
if (e[0] != '.')
|
||||||
|
e = "." + e;
|
||||||
|
int i = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
std::string new_extension = std::to_string(i);
|
||||||
|
while (new_extension.length() < 3)
|
||||||
|
new_extension = "0" + new_extension;
|
||||||
|
new_extension += e;
|
||||||
|
|
||||||
|
new_filename = filename_base;
|
||||||
|
new_filename.replace_extension(new_extension);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
} while (fs::exists(new_filename));
|
||||||
|
|
||||||
|
return new_filename.u8string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 S9xInitUpdate()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xExtraUsage()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 S9xOpenSoundDevice()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool S9xPollAxis(unsigned int axis, short *value)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xParseArg(char *argv[], int &index, int argc)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xExit()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool S9xPollPointer(unsigned int, short *, short *)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::SamplesAvailable()
|
||||||
|
{
|
||||||
|
static std::vector<int16_t> data;
|
||||||
|
if (sound_output_function)
|
||||||
|
{
|
||||||
|
int samples = S9xGetSampleCount();
|
||||||
|
if (data.size() < samples)
|
||||||
|
data.resize(samples);
|
||||||
|
S9xMixSamples((uint8_t *)data.data(), samples);
|
||||||
|
sound_output_function(data.data(), samples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
S9xClearSamples();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xMessage(int message_class, int type, const char *message)
|
||||||
|
{
|
||||||
|
S9xSetInfoString(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *S9xStringInput(const char *prompt)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool8 S9xOpenSnapshotFile(const char *filename, bool8 read_only, STREAM *file)
|
||||||
|
{
|
||||||
|
if (read_only)
|
||||||
|
{
|
||||||
|
if ((*file = OPEN_STREAM(filename, "rb")))
|
||||||
|
return (true);
|
||||||
|
else
|
||||||
|
fprintf(stderr, "Failed to open file stream for reading.\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((*file = OPEN_STREAM(filename, "wb")))
|
||||||
|
{
|
||||||
|
return (true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Couldn't open stream with zlib.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Couldn't open snapshot file:\n%s\n", filename);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xCloseSnapshotFile(STREAM file)
|
||||||
|
{
|
||||||
|
CLOSE_STREAM(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void S9xAutoSaveSRAM()
|
||||||
|
{
|
||||||
|
Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str());
|
||||||
|
S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::acceptsCommand(const char *command)
|
||||||
|
{
|
||||||
|
auto cmd = S9xGetCommandT(command);
|
||||||
|
return !(cmd.type == S9xNoMapping || cmd.type == S9xBadMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::updateBindings(const EmuConfig *const config)
|
||||||
|
{
|
||||||
|
const char *snes9x_names[] = {
|
||||||
|
"Up",
|
||||||
|
"Down",
|
||||||
|
"Left",
|
||||||
|
"Right",
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"X",
|
||||||
|
"Y",
|
||||||
|
"L",
|
||||||
|
"R",
|
||||||
|
"Start",
|
||||||
|
"Select",
|
||||||
|
"Turbo A",
|
||||||
|
"Turbo B",
|
||||||
|
"Turbo X",
|
||||||
|
"Turbo Y",
|
||||||
|
"Turbo L",
|
||||||
|
"Turbo R",
|
||||||
|
};
|
||||||
|
|
||||||
|
S9xUnmapAllControls();
|
||||||
|
|
||||||
|
S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
|
||||||
|
for (int controller_number = 0; controller_number < 5; controller_number++)
|
||||||
|
{
|
||||||
|
auto &controller = config->binding.controller[controller_number];
|
||||||
|
for (int i = 0; i < config->num_controller_bindings; i++)
|
||||||
|
{
|
||||||
|
for (int b = 0; b < config->allowed_bindings; b++)
|
||||||
|
{
|
||||||
|
auto binding = controller.buttons[i * config->allowed_bindings + b];
|
||||||
|
if (binding.hash() == 0)
|
||||||
|
continue;
|
||||||
|
std::string name = "Joypad" +
|
||||||
|
std::to_string(controller_number + 1) + " " +
|
||||||
|
snes9x_names[i];
|
||||||
|
auto cmd = S9xGetCommandT(name.c_str());
|
||||||
|
S9xMapButton(binding.hash(), cmd, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < config->num_shortcuts; i++)
|
||||||
|
{
|
||||||
|
auto command = S9xGetCommandT(EmuConfig::getShortcutNames()[i]);
|
||||||
|
if (command.type == S9xNoMapping)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (int b = 0; b < 4; b++)
|
||||||
|
{
|
||||||
|
auto binding = config->binding.shortcuts[i * 4 + b];
|
||||||
|
if (binding.type != 0)
|
||||||
|
S9xMapButton(binding.hash(), command, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::reportBinding(EmuBinding b, bool active)
|
||||||
|
{
|
||||||
|
S9xReportButton(b.hash(), active);
|
||||||
|
}
|
||||||
|
|
||||||
|
static fs::path save_slot_path(int slot)
|
||||||
|
{
|
||||||
|
std::string extension = std::to_string(slot);
|
||||||
|
while (extension.length() < 3)
|
||||||
|
extension = "0" + extension;
|
||||||
|
fs::path path(S9xGetDirectory(SNAPSHOT_DIR));
|
||||||
|
path /= fs::path(Memory.ROMFilename).filename();
|
||||||
|
path.replace_extension(extension);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::loadUndoState()
|
||||||
|
{
|
||||||
|
S9xUnfreezeGame(S9xGetFilename(".undo", SNAPSHOT_DIR).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Snes9xController::getStateFolder()
|
||||||
|
{
|
||||||
|
return S9xGetDirectory(SNAPSHOT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::loadState(int slot)
|
||||||
|
{
|
||||||
|
return loadState(save_slot_path(slot).u8string());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::loadState(std::string filename)
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
S9xFreezeGame(S9xGetFilename(".undo", SNAPSHOT_DIR).c_str());
|
||||||
|
|
||||||
|
if (S9xUnfreezeGame(filename.c_str()))
|
||||||
|
{
|
||||||
|
auto info_string = filename + " loaded";
|
||||||
|
S9xSetInfoString(info_string.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Failed to load state file: %s\n", filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::saveState(std::string filename)
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (S9xFreezeGame(filename.c_str()))
|
||||||
|
{
|
||||||
|
auto info_string = filename + " saved";
|
||||||
|
S9xSetInfoString(info_string.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Couldn't save state file: %s\n", filename.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::mute(bool muted)
|
||||||
|
{
|
||||||
|
Settings.Mute = muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::isAbnormalSpeed()
|
||||||
|
{
|
||||||
|
return (Settings.TurboMode || rewinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::reset()
|
||||||
|
{
|
||||||
|
S9xReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::softReset()
|
||||||
|
{
|
||||||
|
S9xSoftReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Snes9xController::saveState(int slot)
|
||||||
|
{
|
||||||
|
return saveState(save_slot_path(slot).u8string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Snes9xController::setMessage(std::string message)
|
||||||
|
{
|
||||||
|
S9xSetInfoString(message.c_str());
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
#ifndef __SNES9X_CONTROLLER_HPP
|
||||||
|
#define __SNES9X_CONTROLLER_HPP
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "EmuConfig.hpp"
|
||||||
|
|
||||||
|
class Snes9xController
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static Snes9xController *get();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void deinit();
|
||||||
|
void mainLoop();
|
||||||
|
bool openFile(std::string filename);
|
||||||
|
bool loadState(std::string filename);
|
||||||
|
bool loadState(int slot);
|
||||||
|
void loadUndoState();
|
||||||
|
bool saveState(std::string filename);
|
||||||
|
bool saveState(int slot);
|
||||||
|
void increaseSaveSlot();
|
||||||
|
void decreaseSaveSlot();
|
||||||
|
void updateSettings(const EmuConfig * const config);
|
||||||
|
void updateBindings(const EmuConfig * const config);
|
||||||
|
void reportBinding(EmuBinding b, bool active);
|
||||||
|
void updateSoundBufferLevel(int, int);
|
||||||
|
bool acceptsCommand(const char *command);
|
||||||
|
bool isAbnormalSpeed();
|
||||||
|
void mute(bool muted);
|
||||||
|
void reset();
|
||||||
|
void softReset();
|
||||||
|
void setPaused(bool paused);
|
||||||
|
void setMessage(std::string message);
|
||||||
|
std::string getStateFolder();
|
||||||
|
std::string config_folder;
|
||||||
|
std::string sram_folder;
|
||||||
|
std::string state_folder;
|
||||||
|
std::string cheat_folder;
|
||||||
|
std::string patch_folder;
|
||||||
|
std::string export_folder;
|
||||||
|
int high_resolution_effect;
|
||||||
|
int rewind_buffer_size;
|
||||||
|
int rewind_frame_interval;
|
||||||
|
bool rewinding = false;
|
||||||
|
|
||||||
|
std::function<void(uint16_t *, int, int, int, double)> screen_output_function = nullptr;
|
||||||
|
std::function<void(int16_t *, int)> sound_output_function = nullptr;
|
||||||
|
|
||||||
|
bool active = false;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Snes9xController();
|
||||||
|
~Snes9xController();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SamplesAvailable();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,43 @@
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
void S9xForceHires(void *buffer, int pitch, int &width, int &height)
|
||||||
|
{
|
||||||
|
if (width <= 256)
|
||||||
|
{
|
||||||
|
for (int y = (height)-1; y >= 0; y--)
|
||||||
|
{
|
||||||
|
uint16_t *line = (uint16_t *)((uint8_t *)buffer + y * pitch);
|
||||||
|
|
||||||
|
for (int x = (width * 2) - 1; x >= 0; x--)
|
||||||
|
{
|
||||||
|
*(line + x) = *(line + (x >> 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t average_565(uint16_t colora, uint16_t colorb)
|
||||||
|
{
|
||||||
|
return (((colora) & (colorb)) + ((((colora) ^ (colorb)) & 0xF7DE) >> 1));
|
||||||
|
}
|
||||||
|
void S9xMergeHires(void *buffer, int pitch, int &width, int &height)
|
||||||
|
{
|
||||||
|
if (width < 512)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
uint16_t *input = (uint16_t *)((uint8_t *)buffer + y * pitch);
|
||||||
|
uint16_t *output = input;
|
||||||
|
|
||||||
|
for (int x = 0; x < (width >> 1); x++)
|
||||||
|
{
|
||||||
|
*output++ = average_565(input[0], input[1]);
|
||||||
|
input += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width >>= 1;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
void S9xForceHires(void *buffer, int pitch, int &width, int &height);
|
||||||
|
void S9xMergeHires(void *buffer, int pitch, int &width, int &height);
|
|
@ -0,0 +1,131 @@
|
||||||
|
#include "SoundPanel.hpp"
|
||||||
|
#include <QScreen>
|
||||||
|
|
||||||
|
static const int playback_rates[] = { 96000, 48000, 44100 };
|
||||||
|
|
||||||
|
SoundPanel::SoundPanel(EmuApplication *app_)
|
||||||
|
: app(app_)
|
||||||
|
{
|
||||||
|
setupUi(this);
|
||||||
|
|
||||||
|
connect(comboBox_driver, &QComboBox::activated, [&](int index){
|
||||||
|
if (app->config->sound_driver != driver_list[index])
|
||||||
|
{
|
||||||
|
app->config->sound_driver = driver_list[index];
|
||||||
|
app->restartAudio();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(comboBox_playback_rate, &QComboBox::activated, [&](int index)
|
||||||
|
{
|
||||||
|
if (index < 3)
|
||||||
|
{
|
||||||
|
if (playback_rates[index] != app->config->playback_rate)
|
||||||
|
{
|
||||||
|
app->config->playback_rate = playback_rates[index];
|
||||||
|
app->restartAudio();
|
||||||
|
app->updateSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(spinBox_buffer_size, &QSpinBox::valueChanged, [&](int value) {
|
||||||
|
app->config->audio_buffer_size_ms = value;
|
||||||
|
app->restartAudio();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(checkBox_adjust_input_rate, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->adjust_input_rate_automatically = checked;
|
||||||
|
if (checked)
|
||||||
|
{
|
||||||
|
int calculated = screen()->refreshRate() / 60.09881 * 32040;
|
||||||
|
horizontalSlider_input_rate->setValue(calculated);
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontalSlider_input_rate->setDisabled(checked);
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(horizontalSlider_input_rate, &QSlider::valueChanged, [&](int value) {
|
||||||
|
app->config->input_rate = value;
|
||||||
|
setInputRateText(value);
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(checkBox_dynamic_rate_control, &QCheckBox::clicked, [&](bool checked) {
|
||||||
|
app->config->dynamic_rate_control = checked;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(doubleSpinBox_dynamic_rate_limit, &QDoubleSpinBox::valueChanged, [&](double value) {
|
||||||
|
app->config->dynamic_rate_limit = value;
|
||||||
|
app->updateSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(checkBox_mute_sound, &QCheckBox::toggled, [&](bool checked) {
|
||||||
|
app->config->mute_audio = checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(checkBox_mute_during_alt_speed, &QCheckBox::toggled, [&](bool checked) {
|
||||||
|
app->config->mute_audio_during_alternate_speed = checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundPanel::~SoundPanel()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPanel::setInputRateText(int value)
|
||||||
|
{
|
||||||
|
double hz = value / 32040.0 * 60.09881;
|
||||||
|
label_input_rate->setText(QString("%1\n%2 Hz").arg(value).arg(hz, 6, 'g', 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SoundPanel::showEvent(QShowEvent *event)
|
||||||
|
{
|
||||||
|
auto &config = app->config;
|
||||||
|
|
||||||
|
comboBox_driver->clear();
|
||||||
|
comboBox_driver->addItem("SDL");
|
||||||
|
comboBox_driver->addItem("PortAudio");
|
||||||
|
comboBox_driver->addItem("Cubeb");
|
||||||
|
|
||||||
|
driver_list.clear();
|
||||||
|
driver_list.push_back("sdl");
|
||||||
|
driver_list.push_back("portaudio");
|
||||||
|
driver_list.push_back("cubeb");
|
||||||
|
|
||||||
|
for (int i = 0; i < driver_list.size(); i++)
|
||||||
|
{
|
||||||
|
if (driver_list[i] == config->sound_driver)
|
||||||
|
{
|
||||||
|
comboBox_driver->setCurrentIndex(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comboBox_device->clear();
|
||||||
|
comboBox_device->addItem("Default");
|
||||||
|
|
||||||
|
comboBox_playback_rate->clear();
|
||||||
|
comboBox_playback_rate->addItem("96000Hz");
|
||||||
|
comboBox_playback_rate->addItem("48000Hz");
|
||||||
|
comboBox_playback_rate->addItem("44100Hz");
|
||||||
|
int pbr_index = 1;
|
||||||
|
if (config->playback_rate == 96000)
|
||||||
|
pbr_index = 0;
|
||||||
|
else if (config->playback_rate == 44100)
|
||||||
|
pbr_index = 2;
|
||||||
|
|
||||||
|
comboBox_playback_rate->setCurrentIndex(pbr_index);
|
||||||
|
spinBox_buffer_size->setValue(config->audio_buffer_size_ms);
|
||||||
|
|
||||||
|
checkBox_adjust_input_rate->setChecked(config->adjust_input_rate_automatically);
|
||||||
|
setInputRateText(config->input_rate);
|
||||||
|
checkBox_dynamic_rate_control->setChecked(config->dynamic_rate_control);
|
||||||
|
doubleSpinBox_dynamic_rate_limit->setValue(config->dynamic_rate_limit);
|
||||||
|
|
||||||
|
checkBox_mute_sound->setChecked(config->mute_audio);
|
||||||
|
checkBox_mute_during_alt_speed->setChecked(config->mute_audio_during_alternate_speed);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include "ui_SoundPanel.h"
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
class SoundPanel :
|
||||||
|
public Ui::SoundPanel,
|
||||||
|
public QWidget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SoundPanel(EmuApplication *app);
|
||||||
|
~SoundPanel();
|
||||||
|
EmuApplication *app;
|
||||||
|
void showEvent(QShowEvent *event) override;
|
||||||
|
void setInputRateText(int value);
|
||||||
|
|
||||||
|
std::vector<std::string> driver_list;
|
||||||
|
};
|
|
@ -0,0 +1,302 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>SoundPanel</class>
|
||||||
|
<widget class="QWidget" name="SoundPanel">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>705</width>
|
||||||
|
<height>596</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Sound Output</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayout" rowminimumheight="0,0,0,0">
|
||||||
|
<item row="3" column="1" colspan="2">
|
||||||
|
<widget class="QSpinBox" name="spinBox_buffer_size">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string> ms</string>
|
||||||
|
</property>
|
||||||
|
<property name="minimum">
|
||||||
|
<number>16</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>256</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>32</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_playback_rate">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>48000 Hz</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>44100 Hz</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
|
<widget class="QLabel" name="label_5">
|
||||||
|
<property name="text">
|
||||||
|
<string>Buffer size:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Device:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_device">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Choose a device to render output. If you have no integrated graphics, there will be only one choice.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Driver:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="comboBox_driver">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select the output driver.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Playback rate:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_2">
|
||||||
|
<property name="title">
|
||||||
|
<string>Sound Stretching</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_dynamic_rate_control">
|
||||||
|
<property name="text">
|
||||||
|
<string>Dynamic rate control</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_adjust_input_rate">
|
||||||
|
<property name="text">
|
||||||
|
<string>Adjust input rate to display rate automatically</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_4">
|
||||||
|
<property name="text">
|
||||||
|
<string>Input rate:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_input_rate">
|
||||||
|
<property name="text">
|
||||||
|
<string>31987</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QSlider" name="horizontalSlider_input_rate">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>31800</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>32200</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>31987</number>
|
||||||
|
</property>
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="tickPosition">
|
||||||
|
<enum>QSlider::NoTicks</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Dynamic rate limit:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDoubleSpinBox" name="doubleSpinBox_dynamic_rate_limit">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="decimals">
|
||||||
|
<number>3</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<double>0.050000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="singleStep">
|
||||||
|
<double>0.001000000000000</double>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<double>0.005000000000000</double>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox_3">
|
||||||
|
<property name="title">
|
||||||
|
<string>Mute</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mute_sound">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mute all sound</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="checkBox_mute_during_alt_speed">
|
||||||
|
<property name="text">
|
||||||
|
<string>Mute sound during turbo or rewind</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include "EmuApplication.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
EmuApplication emu;
|
||||||
|
emu.qtapp = std::make_unique<QApplication>(argc, argv);
|
||||||
|
|
||||||
|
emu.config = std::make_unique<EmuConfig>();
|
||||||
|
emu.config->setDefaults();
|
||||||
|
emu.config->loadFile(EmuConfig::findConfigFile());
|
||||||
|
|
||||||
|
emu.input_manager = std::make_unique<SDLInputManager>();
|
||||||
|
emu.window = std::make_unique<EmuMainWindow>(&emu);
|
||||||
|
emu.window->show();
|
||||||
|
|
||||||
|
emu.updateBindings();
|
||||||
|
emu.startIdleLoop();
|
||||||
|
emu.qtapp->exec();
|
||||||
|
|
||||||
|
emu.config->saveFile(EmuConfig::findConfigFile());
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="a.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview4430"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="4.611414"
|
||||||
|
inkscape:cy="8.2637591"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4436" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4425" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495"
|
||||||
|
cx="8"
|
||||||
|
cy="3"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-3"
|
||||||
|
cx="3"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-6"
|
||||||
|
cx="8"
|
||||||
|
cy="13"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-7"
|
||||||
|
cx="13"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="b.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview4430"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="4.611414"
|
||||||
|
inkscape:cy="8.2637591"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4436" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4425" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495"
|
||||||
|
cx="8"
|
||||||
|
cy="3"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-3"
|
||||||
|
cx="3"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-6"
|
||||||
|
cx="8"
|
||||||
|
cy="13"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-7"
|
||||||
|
cx="13"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M300 696h60V536h-60v50h-60v60h60v50Zm100-50h320v-60H400v60Zm200-110h60v-50h60v-60h-60v-50h-60v160Zm-360-50h320v-60H240v60Zm80 450v-80H160q-33 0-56.5-23.5T80 776V296q0-33 23.5-56.5T160 216h640q33 0 56.5 23.5T880 296v480q0 33-23.5 56.5T800 856H640v80H320ZM160 776h640V296H160v480Zm0 0V296v480Z"/></svg>
|
After Width: | Height: | Size: 395 B |
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="down.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="41.7193"
|
||||||
|
inkscape:cx="3.3917156"
|
||||||
|
inkscape:cy="7.5624471"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9"
|
||||||
|
spacingx="0.5"
|
||||||
|
spacingy="0.5"
|
||||||
|
empspacing="10" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.5,0.5 v 5 h -5 v 5 h 5 v 5 h 5 v -5 h 5 v -5 h -5 v -5 z"
|
||||||
|
id="path117"
|
||||||
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.88976;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||||
|
id="rect947"
|
||||||
|
width="4"
|
||||||
|
height="5"
|
||||||
|
x="6"
|
||||||
|
y="10" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M80 896V256h800v640H80Zm80-80h640V416H160v400Zm140-40-56-56 103-104-104-104 57-56 160 160-160 160Zm180 0v-80h240v80H480Z"/></svg>
|
After Width: | Height: | Size: 224 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M120 936q-33 0-56.5-23.5T40 856V336h80v520h680v80H120Zm160-160q-33 0-56.5-23.5T200 696V256q0-33 23.5-56.5T280 176h200l80 80h280q33 0 56.5 23.5T920 336v360q0 33-23.5 56.5T840 776H280Zm0-80h560V336H527l-80-80H280v440Zm0 0V256v440Z"/></svg>
|
After Width: | Height: | Size: 332 B |
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="joypad.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview6"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="41.7193"
|
||||||
|
inkscape:cx="7.7542049"
|
||||||
|
inkscape:cy="8.4972662"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid941" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<path
|
||||||
|
d="M 4,5 C 3.966482,4.9992664 3.932975,5.00195 3.9,5.008 2.3557709,5.051666 1.0228084,6.1025927 0.62000004,7.594 0.21166956,9.1238633 0.87975114,10.737506 2.25,11.531 c 1.3592384,0.782703 3.0731398,0.566167 4.195,-0.53 H 9.5550001 C 10.67686,12.097167 12.390762,12.313703 13.75,11.531 15.120249,10.737506 15.78833,9.1238633 15.38,7.594 14.976552,6.1005143 13.640475,5.0490552 12.094,5.008 12.06301,5.00223 12.03152,4.99955 12,5 12,5 3.9291169,5 4,5 Z m 0,1 h 8 c 1.134,0 2.123,0.758 2.416,1.854 0.293689,1.0932733 -0.184847,2.247341 -1.166,2.812 -0.920496,0.530147 -2.076001,0.41769 -2.877,-0.28 C 10.243,10.257 10.114,9.9960003 9.8790001,10 H 6.12 c -0.234,-0.003 -0.394,0.268 -0.492,0.385 -0.8,0.693 -1.95,0.817 -2.879,0.281 C 1.7683339,10.100978 1.290585,8.9468293 1.585,7.854 1.8759471,6.7595374 2.8675267,5.9982999 4,6 Z M 3.5,7 v 1 h -1 v 1.0000003 h 1 V 10 h 1 V 9.0000003 h 1 V 8 h -1 V 7 Z M 12,7 c -0.666666,0 -0.666666,1 0,1 0.666666,0 0.666666,-1 0,-1 z M 11,8 C 10.333333,8 10.333333,9.0000003 11,9.0000003 11.666667,9.0000003 11.666667,8 11,8 Z m 2,0 C 12.333333,8 12.333333,9.0000003 13,9.0000003 13.666667,9.0000003 13.666667,8 13,8 Z M 12,9.0000003 C 11.333334,9.0000003 11.333334,10 12,10 c 0.666666,0 0.666666,-0.9999997 0,-0.9999997 z"
|
||||||
|
fill="gray"
|
||||||
|
font-family="sans-serif"
|
||||||
|
font-weight="400"
|
||||||
|
overflow="visible"
|
||||||
|
style="font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;fill:#000000;fill-opacity:1"
|
||||||
|
white-space="normal"
|
||||||
|
id="path2"
|
||||||
|
sodipodi:nodetypes="ccccccccccccsccccccccccccccccccccccssssssssssss" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,87 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="key.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="20.85965"
|
||||||
|
inkscape:cx="11.817073"
|
||||||
|
inkscape:cy="8.9886455"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid132"
|
||||||
|
spacingx="0.5"
|
||||||
|
spacingy="0.5" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.2;stroke-dasharray:none"
|
||||||
|
id="rect1845"
|
||||||
|
width="10.5"
|
||||||
|
height="9"
|
||||||
|
x="2.75"
|
||||||
|
y="2"
|
||||||
|
rx="1.4999999"
|
||||||
|
ry="1.5000001" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;fill-opacity:0.17;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.2;stroke-dasharray:none"
|
||||||
|
id="rect1877"
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
x="1"
|
||||||
|
y="1"
|
||||||
|
rx="1.5"
|
||||||
|
ry="1.5000001" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.3;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;opacity:1"
|
||||||
|
d="M 2.6610169,9.5423728 1.5,14.5"
|
||||||
|
id="path1937"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.3;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;opacity:1"
|
||||||
|
d="M 13.322033,9.542373 14.5,14.5 v 0 0"
|
||||||
|
id="path3492"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.3;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;opacity:1"
|
||||||
|
d="m 1.5,1.5 1,1 v 0 L 3,3"
|
||||||
|
id="path3494"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.3;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;opacity:1"
|
||||||
|
d="M 14.5,1.5 13,3"
|
||||||
|
id="path3496"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M80 856V296h800v560H80Zm80-80h640V376H160v400Zm160-40h320v-80H320v80ZM200 616h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80ZM200 496h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80Zm120 0h80v-80h-80v80ZM160 776V376v400Z"/></svg>
|
After Width: | Height: | Size: 405 B |
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="l.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="29.5"
|
||||||
|
inkscape:cx="5.4237288"
|
||||||
|
inkscape:cy="9.2711864"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid132" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient1721">
|
||||||
|
<stop
|
||||||
|
style="stop-color:currentColor;stop-opacity:0.5;"
|
||||||
|
offset="0"
|
||||||
|
id="stop1717" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:currentColor;stop-opacity:0;"
|
||||||
|
offset="1"
|
||||||
|
id="stop1719" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1721"
|
||||||
|
id="linearGradient1723"
|
||||||
|
x1="1.000001"
|
||||||
|
y1="7.5"
|
||||||
|
x2="14.999999"
|
||||||
|
y2="7.5"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
fill="url(#linearGradient1723)"
|
||||||
|
stroke="currentColor">
|
||||||
|
<rect
|
||||||
|
style="fill:url(#linearGradient1723);fill-opacity:1;stroke:currentColor;stroke-width:0.999995;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect236"
|
||||||
|
width="13.000003"
|
||||||
|
height="4.0000029"
|
||||||
|
x="1.4999985"
|
||||||
|
y="5.4999986"
|
||||||
|
rx="1.5"
|
||||||
|
ry="1.5000001" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="left.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="41.7193"
|
||||||
|
inkscape:cx="3.3917156"
|
||||||
|
inkscape:cy="7.5624471"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9"
|
||||||
|
spacingx="0.5"
|
||||||
|
spacingy="0.5"
|
||||||
|
empspacing="10" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.5,0.5 v 5 h -5 v 5 h 5 v 5 h 5 v -5 h 5 v -5 h -5 v -5 z"
|
||||||
|
id="path117"
|
||||||
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.88976;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||||
|
id="rect947"
|
||||||
|
width="4"
|
||||||
|
height="5"
|
||||||
|
x="-10"
|
||||||
|
y="1"
|
||||||
|
transform="rotate(-90)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="r.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="29.5"
|
||||||
|
inkscape:cx="5.4237288"
|
||||||
|
inkscape:cy="9.2711864"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid132" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
id="linearGradient1721">
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:0;"
|
||||||
|
offset="0"
|
||||||
|
id="stop1719" />
|
||||||
|
<stop
|
||||||
|
style="stop-color:#000000;stop-opacity:0.5;"
|
||||||
|
offset="1"
|
||||||
|
id="stop1717" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
inkscape:collect="always"
|
||||||
|
xlink:href="#linearGradient1721"
|
||||||
|
id="linearGradient1723"
|
||||||
|
x1="1.000001"
|
||||||
|
y1="7.5"
|
||||||
|
x2="14.999999"
|
||||||
|
y2="7.5"
|
||||||
|
gradientUnits="userSpaceOnUse" />
|
||||||
|
</defs>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:url(#linearGradient1723);fill-opacity:1;stroke:#000000;stroke-width:0.999995;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect236"
|
||||||
|
width="13.000003"
|
||||||
|
height="4.0000029"
|
||||||
|
x="1.4999985"
|
||||||
|
y="5.4999986"
|
||||||
|
rx="1.5"
|
||||||
|
ry="1.5000001" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="right.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="41.7193"
|
||||||
|
inkscape:cx="3.3917156"
|
||||||
|
inkscape:cy="7.5624471"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9"
|
||||||
|
spacingx="0.5"
|
||||||
|
spacingy="0.5"
|
||||||
|
empspacing="10" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.5,0.5 v 5 h -5 v 5 h 5 v 5 h 5 v -5 h 5 v -5 h -5 v -5 z"
|
||||||
|
id="path117"
|
||||||
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.88976;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||||
|
id="rect947"
|
||||||
|
width="4"
|
||||||
|
height="5"
|
||||||
|
x="-10"
|
||||||
|
y="10"
|
||||||
|
transform="rotate(-90)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="select.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview9265"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="6.5295521"
|
||||||
|
inkscape:cy="8.2112073"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9271" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs9260" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect9280-5"
|
||||||
|
width="6"
|
||||||
|
height="2"
|
||||||
|
x="0.66721022"
|
||||||
|
y="12.520413"
|
||||||
|
rx="0.5"
|
||||||
|
ry="0.5"
|
||||||
|
transform="rotate(-40)" />
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;fill-opacity:0.49454543;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect9280-5-3"
|
||||||
|
width="6"
|
||||||
|
height="2"
|
||||||
|
x="-4.6951008"
|
||||||
|
y="8.0208998"
|
||||||
|
rx="0.5"
|
||||||
|
ry="0.5"
|
||||||
|
transform="rotate(-40)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm112-260q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm0-80q-25 0-42.5-17.5T422-480q0-25 17.5-42.5T482-540q25 0 42.5 17.5T542-480q0 25-17.5 42.5T482-420Zm-2-60Zm-40 320h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Z"/></svg>
|
After Width: | Height: | Size: 853 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-440v-80h80v80h-80Zm-80 80v-80h80v80h-80Zm160 0v-80h80v80h-80Zm80-80v-80h80v80h-80Zm-320 0v-80h80v80h-80Zm-80 320q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm80-80h80v-80h-80v80Zm160 0h80v-80h-80v80Zm320 0v-80 80Zm-560-80h80v-80h80v80h80v-80h80v80h80v-80h80v80h80v-80h-80v-80h80v-320H200v320h80v80h-80v80Zm0 80v-560 560Zm560-240v80-80ZM600-280v80h80v-80h-80Z"/></svg>
|
After Width: | Height: | Size: 535 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 96 960 960" width="24"><path d="M560 925v-82q90-26 145-100t55-168q0-94-55-168T560 307v-82q124 28 202 125.5T840 575q0 127-78 224.5T560 925ZM120 696V456h160l200-200v640L280 696H120Zm440 40V414q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560 736ZM400 450l-86 86H200v80h114l86 86V450ZM300 576Z"/></svg>
|
After Width: | Height: | Size: 354 B |
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="start.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview9265"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="6.5295521"
|
||||||
|
inkscape:cy="8.2112073"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9271" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs9260" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<rect
|
||||||
|
style="fill:#000000;fill-opacity:0.49454543;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect9280-5"
|
||||||
|
width="6"
|
||||||
|
height="2"
|
||||||
|
x="0.66721022"
|
||||||
|
y="12.520413"
|
||||||
|
rx="0.5"
|
||||||
|
ry="0.5"
|
||||||
|
transform="rotate(-40)" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;fill-opacity:0.49454543;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="rect9280-5-3"
|
||||||
|
width="6"
|
||||||
|
height="2"
|
||||||
|
x="-4.6951008"
|
||||||
|
y="8.0208998"
|
||||||
|
rx="0.5"
|
||||||
|
ry="0.5"
|
||||||
|
transform="rotate(-40)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,62 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="up.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="41.7193"
|
||||||
|
inkscape:cx="3.3917156"
|
||||||
|
inkscape:cy="7.5624471"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid9"
|
||||||
|
spacingx="0.5"
|
||||||
|
spacingy="0.5"
|
||||||
|
empspacing="10" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 5.5,0.5 v 5 h -5 v 5 h 5 v 5 h 5 v -5 h 5 v -5 h -5 v -5 z"
|
||||||
|
id="path117"
|
||||||
|
sodipodi:nodetypes="ccccccccccccc" />
|
||||||
|
<rect
|
||||||
|
style="opacity:0.5;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.88976;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
|
||||||
|
id="rect947"
|
||||||
|
width="4"
|
||||||
|
height="5"
|
||||||
|
x="6"
|
||||||
|
y="1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="x.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview4430"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="4.611414"
|
||||||
|
inkscape:cy="8.2637591"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4436" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4425" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:#000000;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495"
|
||||||
|
cx="8"
|
||||||
|
cy="3"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-3"
|
||||||
|
cx="3"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-6"
|
||||||
|
cx="8"
|
||||||
|
cy="13"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-7"
|
||||||
|
cx="13"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="16.0px"
|
||||||
|
height="16.0px"
|
||||||
|
viewBox="0 0 16.0 16.0"
|
||||||
|
version="1.1"
|
||||||
|
id="SVGRoot"
|
||||||
|
sodipodi:docname="y.svg"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview4430"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="38.057741"
|
||||||
|
inkscape:cx="4.611414"
|
||||||
|
inkscape:cy="8.2637591"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1021"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid4436" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs4425" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<circle
|
||||||
|
style="opacity:1;fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495"
|
||||||
|
cx="8"
|
||||||
|
cy="3"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:#000000;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-3"
|
||||||
|
cx="3"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-6"
|
||||||
|
cx="8"
|
||||||
|
cy="13"
|
||||||
|
r="2.5" />
|
||||||
|
<circle
|
||||||
|
style="fill:none;fill-opacity:0.5;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4495-7"
|
||||||
|
cx="13"
|
||||||
|
cy="8"
|
||||||
|
r="2.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="icons">
|
||||||
|
<file>blackicons/settings.svg</file>
|
||||||
|
<file>blackicons/folders.svg</file>
|
||||||
|
<file>blackicons/emulation.svg</file>
|
||||||
|
<file>blackicons/sound.svg</file>
|
||||||
|
<file>blackicons/keyboard.svg</file>
|
||||||
|
<file>blackicons/display.svg</file>
|
||||||
|
<file>blackicons/joypad.svg</file>
|
||||||
|
<file>blackicons/key.svg</file>
|
||||||
|
<file>blackicons/a.svg</file>
|
||||||
|
<file>blackicons/b.svg</file>
|
||||||
|
<file>blackicons/l.svg</file>
|
||||||
|
<file>blackicons/left.svg</file>
|
||||||
|
<file>blackicons/down.svg</file>
|
||||||
|
<file>blackicons/r.svg</file>
|
||||||
|
<file>blackicons/right.svg</file>
|
||||||
|
<file>blackicons/select.svg</file>
|
||||||
|
<file>blackicons/start.svg</file>
|
||||||
|
<file>blackicons/up.svg</file>
|
||||||
|
<file>blackicons/x.svg</file>
|
||||||
|
<file>blackicons/y.svg</file>
|
||||||
|
<file>blackicons/shader.svg</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
|
@ -0,0 +1,175 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>895</width>
|
||||||
|
<height>651</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="tableWidget">
|
||||||
|
<property name="showGrid">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="gridStyle">
|
||||||
|
<enum>Qt::SolidLine</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sortingEnabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="cornerButtonEnabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Up</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/up.svg</normaloff>:/icons/icons/up.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Down</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/down.svg</normaloff>:/icons/icons/down.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Left</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/left.svg</normaloff>:/icons/icons/left.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Right</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/right.svg</normaloff>:/icons/icons/right.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>A</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/a.svg</normaloff>:/icons/icons/a.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>B</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/b.svg</normaloff>:/icons/icons/b.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>X</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/x.svg</normaloff>:/icons/icons/x.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Y</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/y.svg</normaloff>:/icons/icons/y.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>L</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/l.svg</normaloff>:/icons/icons/l.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>R</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/r.svg</normaloff>:/icons/icons/r.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Start</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/start.svg</normaloff>:/icons/icons/start.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<row>
|
||||||
|
<property name="text">
|
||||||
|
<string>Select</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset resource="snes9x.qrc">
|
||||||
|
<normaloff>:/icons/icons/select.svg</normaloff>:/icons/icons/select.svg</iconset>
|
||||||
|
</property>
|
||||||
|
</row>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #2</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #3</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Binding #4</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<property name="text">
|
||||||
|
<string>Up</string>
|
||||||
|
</property>
|
||||||
|
<property name="icon">
|
||||||
|
<iconset theme="input-gaming"/>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="snes9x.qrc"/>
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -13,6 +13,7 @@
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
static const unsigned int glsl_max_passes = 20;
|
static const unsigned int glsl_max_passes = 20;
|
||||||
|
|
||||||
|
|
|
@ -9,25 +9,27 @@
|
||||||
|
|
||||||
#include "port.h"
|
#include "port.h"
|
||||||
|
|
||||||
#ifdef SNES9X_GTK
|
#if defined(SNES9X_QT)
|
||||||
#include <glad/gl.h>
|
#include <glad/gl.h>
|
||||||
#include <glad/glx.h>
|
#if defined(_WIN32)
|
||||||
#endif
|
#define realpath(src, resolved) _fullpath(resolved, src, PATH_MAX)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef _WIN32
|
#elif defined(SNES9X_GTK)
|
||||||
#include <windows.h>
|
#include <glad/gl.h>
|
||||||
#include <stdlib.h>
|
#elif defined(_WIN32)
|
||||||
#include "gl_core_3_1.h"
|
#include <windows.h>
|
||||||
#include <direct.h>
|
#include <stdlib.h>
|
||||||
|
#include "gl_core_3_1.h"
|
||||||
#ifdef UNICODE
|
#include <direct.h>
|
||||||
#define chdir(dir) _wchdir(Utf8ToWide(dir))
|
|
||||||
#define realpath(src, resolved) _twfullpath(resolved, src, PATH_MAX)
|
|
||||||
#else
|
|
||||||
#define chdir(dir) _chdir(dir)
|
|
||||||
#define realpath(src, resolved) _fullpath(resolved, src, PATH_MAX)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
#ifdef UNICODE
|
||||||
|
#define chdir(dir) _wchdir(Utf8ToWide(dir))
|
||||||
|
#define realpath(src, resolved) _twfullpath(resolved, src, PATH_MAX)
|
||||||
|
#else
|
||||||
|
#define chdir(dir) _chdir(dir)
|
||||||
|
#define realpath(src, resolved) _fullpath(resolved, src, PATH_MAX)
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* __SHADER_PLATFORM_H */
|
#endif /* __SHADER_PLATFORM_H */
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
#include "vulkan_context.hpp"
|
#include "vulkan_context.hpp"
|
||||||
#include "slang_shader.hpp"
|
#include "slang_shader.hpp"
|
||||||
#include "vulkan/vulkan.hpp"
|
|
||||||
|
|
||||||
namespace Vulkan
|
namespace Vulkan
|
||||||
{
|
{
|
||||||
|
@ -56,7 +57,7 @@ static vk::UniqueInstance create_instance_preamble(const char *wsi_extension)
|
||||||
{
|
{
|
||||||
load_loader();
|
load_loader();
|
||||||
if (!dl || !dl->success())
|
if (!dl || !dl->success())
|
||||||
return vk::UniqueInstance();
|
return {};
|
||||||
|
|
||||||
std::vector<const char *> extensions = { wsi_extension, VK_KHR_SURFACE_EXTENSION_NAME };
|
std::vector<const char *> extensions = { wsi_extension, VK_KHR_SURFACE_EXTENSION_NAME };
|
||||||
vk::ApplicationInfo application_info({}, {}, {}, {}, VK_API_VERSION_1_0);
|
vk::ApplicationInfo application_info({}, {}, {}, {}, VK_API_VERSION_1_0);
|
||||||
|
@ -69,6 +70,26 @@ static vk::UniqueInstance create_instance_preamble(const char *wsi_extension)
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> Vulkan::Context::get_device_list()
|
||||||
|
{
|
||||||
|
std::vector<std::string> device_names;
|
||||||
|
auto instance = create_instance_preamble(VK_KHR_SURFACE_EXTENSION_NAME);
|
||||||
|
if (!instance)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto device_list = instance->enumeratePhysicalDevices();
|
||||||
|
for (auto &d : device_list)
|
||||||
|
{
|
||||||
|
auto props = d.getProperties();
|
||||||
|
std::string device_name((const char *)props.deviceName);
|
||||||
|
|
||||||
|
device_name += " (" + vk::to_string(props.deviceType) + ")";
|
||||||
|
device_names.push_back(device_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return device_names;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||||
bool Context::init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device)
|
bool Context::init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device)
|
||||||
{
|
{
|
||||||
|
@ -127,7 +148,6 @@ bool Context::init_wayland(wl_display *dpy, wl_surface *parent, int initial_widt
|
||||||
|
|
||||||
bool Context::init(int preferred_device)
|
bool Context::init(int preferred_device)
|
||||||
{
|
{
|
||||||
|
|
||||||
init_device(preferred_device);
|
init_device(preferred_device);
|
||||||
init_vma();
|
init_vma();
|
||||||
init_command_pool();
|
init_command_pool();
|
||||||
|
@ -179,12 +199,13 @@ bool Context::init_device(int preferred_device)
|
||||||
return device_list[0];
|
return device_list[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preferred_device > 0)
|
if (preferred_device > -1 && preferred_device < device_list.size())
|
||||||
physical_device = device_list[preferred_device];
|
physical_device = device_list[preferred_device];
|
||||||
else
|
else
|
||||||
physical_device = find_device();
|
physical_device = find_device();
|
||||||
|
|
||||||
physical_device.getProperties(&physical_device_props);
|
physical_device.getProperties(&physical_device_props);
|
||||||
|
printf("Vulkan: Using device \"%s\"\n", (const char *)physical_device_props.deviceName);
|
||||||
|
|
||||||
graphics_queue_family_index = UINT32_MAX;
|
graphics_queue_family_index = UINT32_MAX;
|
||||||
auto queue_props = physical_device.getQueueFamilyProperties();
|
auto queue_props = physical_device.getQueueFamilyProperties();
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
#endif
|
#endif
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "vk_mem_alloc.hpp"
|
|
||||||
#include "vulkan/vulkan.hpp"
|
#include "vulkan/vulkan.hpp"
|
||||||
|
#include "../external/VulkanMemoryAllocator-Hpp/include/vk_mem_alloc.hpp"
|
||||||
#include "vulkan_swapchain.hpp"
|
#include "vulkan_swapchain.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -24,21 +24,22 @@ class Context
|
||||||
Context();
|
Context();
|
||||||
~Context();
|
~Context();
|
||||||
#ifdef VK_USE_PLATFORM_XLIB_KHR
|
#ifdef VK_USE_PLATFORM_XLIB_KHR
|
||||||
bool init_Xlib(Display *dpy, Window xid, int preferred_device = 0);
|
bool init_Xlib(Display *dpy, Window xid, int preferred_device = -1);
|
||||||
#endif
|
#endif
|
||||||
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
|
||||||
bool init_wayland(wl_display *dpy, wl_surface *parent, int width, int height, int preferred_device = 0);
|
bool init_wayland(wl_display *dpy, wl_surface *parent, int width, int height, int preferred_device = -1);
|
||||||
#endif
|
#endif
|
||||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||||
bool init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device = 0);
|
bool init_win32(HINSTANCE hinstance, HWND hwnd, int preferred_device = -1);
|
||||||
#endif
|
#endif
|
||||||
bool init(int preferred_device = 0);
|
bool init(int preferred_device = -1);
|
||||||
bool create_swapchain(int width = -1, int height = -1);
|
bool create_swapchain(int width = -1, int height = -1);
|
||||||
bool recreate_swapchain(int width = -1, int height = -1);
|
bool recreate_swapchain(int width = -1, int height = -1);
|
||||||
void wait_idle();
|
void wait_idle();
|
||||||
vk::CommandBuffer begin_cmd_buffer();
|
vk::CommandBuffer begin_cmd_buffer();
|
||||||
void end_cmd_buffer();
|
void end_cmd_buffer();
|
||||||
void hard_barrier(vk::CommandBuffer cmd);
|
void hard_barrier(vk::CommandBuffer cmd);
|
||||||
|
static std::vector<std::string> get_device_list();
|
||||||
|
|
||||||
vma::Allocator allocator;
|
vma::Allocator allocator;
|
||||||
vk::Device device;
|
vk::Device device;
|
||||||
|
|
|
@ -251,7 +251,8 @@ void Texture::create(int width, int height, vk::Format fmt, vk::SamplerAddressMo
|
||||||
.setUsage(vk::BufferUsageFlagBits::eTransferSrc);
|
.setUsage(vk::BufferUsageFlagBits::eTransferSrc);
|
||||||
|
|
||||||
aci.setRequiredFlags(vk::MemoryPropertyFlagBits::eHostVisible)
|
aci.setRequiredFlags(vk::MemoryPropertyFlagBits::eHostVisible)
|
||||||
.setFlags(vma::AllocationCreateFlagBits::eHostAccessSequentialWrite);
|
.setFlags(vma::AllocationCreateFlagBits::eHostAccessSequentialWrite)
|
||||||
|
.setUsage(vma::MemoryUsage::eAutoPreferHost);
|
||||||
|
|
||||||
std::tie(buffer, buffer_allocation) = allocator.createBuffer(bci, aci);
|
std::tie(buffer, buffer_allocation) = allocator.createBuffer(bci, aci);
|
||||||
|
|
||||||
|
|