Qt port.
|
@ -41,7 +41,7 @@ void S9xLandSamples (void);
|
|||
void S9xClearSamples (void);
|
||||
bool8 S9xMixSamples (uint8 *, int);
|
||||
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_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;
|
||||
}
|
||||
|
||||
S9xPortAudioSoundDriver::~S9xPortAudioSoundDriver()
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
|
||||
void S9xPortAudioSoundDriver::init()
|
||||
{
|
||||
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)
|
||||
{
|
||||
PaStreamParameters param;
|
||||
|
||||
const PaDeviceInfo *device_info;
|
||||
const PaHostApiInfo *hostapi_info;
|
||||
PaError err = paNoError;
|
||||
|
@ -74,84 +134,31 @@ bool S9xPortAudioSoundDriver::open_device(int playback_rate, int buffer_size_ms)
|
|||
|
||||
if (err != paNoError)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"Couldn't reset audio stream.\nError: %s\n",
|
||||
Pa_GetErrorText(err));
|
||||
fprintf(stderr, "Couldn't reset audio stream.\nError: %s\n", Pa_GetErrorText(err));
|
||||
return true;
|
||||
}
|
||||
|
||||
audio_stream = NULL;
|
||||
}
|
||||
|
||||
param.channelCount = 2;
|
||||
param.sampleFormat = paInt16;
|
||||
param.hostApiSpecificStreamInfo = NULL;
|
||||
this->playback_rate = playback_rate;
|
||||
this->buffer_size_ms = buffer_size_ms;
|
||||
|
||||
printf("PortAudio sound driver initializing...\n");
|
||||
|
||||
int host = 2; //Pa_GetDefaultHostApi();
|
||||
if (tryHostAPI(host))
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < Pa_GetHostApiCount(); i++)
|
||||
{
|
||||
printf(" --> ");
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (Pa_GetDefaultHostApi() != i)
|
||||
if (tryHostAPI(i))
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Couldn't initialize sound\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int S9xPortAudioSoundDriver::space_free()
|
||||
|
|
|
@ -16,6 +16,7 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
|
|||
{
|
||||
public:
|
||||
S9xPortAudioSoundDriver();
|
||||
~S9xPortAudioSoundDriver();
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
bool open_device(int playback_rate, int buffer_size) override;
|
||||
|
@ -25,9 +26,12 @@ class S9xPortAudioSoundDriver : public S9xSoundDriver
|
|||
int space_free() override;
|
||||
std::pair<int, int> buffer_level() override;
|
||||
void samples_available();
|
||||
bool tryHostAPI(int index);
|
||||
|
||||
private:
|
||||
PaStream *audio_stream;
|
||||
int playback_rate;
|
||||
int buffer_size_ms;
|
||||
int output_buffer_size;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,11 @@ S9xSDLSoundDriver::S9xSDLSoundDriver()
|
|||
{
|
||||
}
|
||||
|
||||
S9xSDLSoundDriver::~S9xSDLSoundDriver()
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
|
||||
void S9xSDLSoundDriver::init()
|
||||
{
|
||||
SDL_InitSubSystem(SDL_INIT_AUDIO);
|
||||
|
|
|
@ -22,6 +22,7 @@ class S9xSDLSoundDriver : public S9xSoundDriver
|
|||
{
|
||||
public:
|
||||
S9xSDLSoundDriver();
|
||||
~S9xSDLSoundDriver();
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
bool open_device(int playback_rate, int buffer_size) override;
|
||||
|
|
|
@ -94,6 +94,8 @@ bool GTKGLXContext::create_context()
|
|||
return false;
|
||||
}
|
||||
|
||||
resize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ bool WaylandEGLContext::create_context()
|
|||
EGL_RED_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, 0,
|
||||
EGL_NONE
|
||||
};
|
||||
|
||||
|
|
|
@ -158,6 +158,7 @@ void WaylandSurface::resize(Metrics m)
|
|||
{
|
||||
metrics = m;
|
||||
auto [w, h] = get_size();
|
||||
|
||||
wl_subsurface_set_position(subsurface, m.x, m.y);
|
||||
|
||||
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
|
||||
|
||||
#if !defined(AMD_VULKAN_MEMORY_ALLOCATOR_H)
|
||||
#include <vk_mem_alloc.h>
|
||||
#include "vk_mem_alloc.h"
|
||||
#endif
|
||||
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
|
|
@ -40,7 +40,7 @@ add_compile_definitions(HAVE_LIBPNG
|
|||
SNES9XLOCALEDIR=\"${LOCALEDIR}\")
|
||||
set(INCLUDES ../apu/bapu ../ src)
|
||||
set(SOURCES)
|
||||
set(ARGS -Wall -Wno-unused-parameter)
|
||||
set(ARGS -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-nullability-completeness)
|
||||
set(LIBS)
|
||||
set(DEFINES)
|
||||
|
||||
|
|
1
memmap.h
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
struct CMemory
|
||||
{
|
||||
|
|
1
port.h
|
@ -32,7 +32,6 @@
|
|||
#define RIGHTSHIFT_int16_IS_SAR
|
||||
#define RIGHTSHIFT_int32_IS_SAR
|
||||
#ifndef __LIBRETRO__
|
||||
#define SNES_JOY_READ_CALLBACKS
|
||||
#endif //__LIBRETRO__
|
||||
#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 <limits.h>
|
||||
#include <vector>
|
||||
#include <unistd.h>
|
||||
|
||||
static const unsigned int glsl_max_passes = 20;
|
||||
|
||||
|
|
|
@ -9,25 +9,27 @@
|
|||
|
||||
#include "port.h"
|
||||
|
||||
#ifdef SNES9X_GTK
|
||||
#include <glad/gl.h>
|
||||
#include <glad/glx.h>
|
||||
#endif
|
||||
#if defined(SNES9X_QT)
|
||||
#include <glad/gl.h>
|
||||
#if defined(_WIN32)
|
||||
#define realpath(src, resolved) _fullpath(resolved, src, PATH_MAX)
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include "gl_core_3_1.h"
|
||||
#include <direct.h>
|
||||
|
||||
#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
|
||||
#elif defined(SNES9X_GTK)
|
||||
#include <glad/gl.h>
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <stdlib.h>
|
||||
#include "gl_core_3_1.h"
|
||||
#include <direct.h>
|
||||
|
||||
#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 /* __SHADER_PLATFORM_H */
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#include <exception>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "vulkan_context.hpp"
|
||||
#include "slang_shader.hpp"
|
||||
#include "vulkan/vulkan.hpp"
|
||||
|
||||
namespace Vulkan
|
||||
{
|
||||
|
@ -56,7 +57,7 @@ static vk::UniqueInstance create_instance_preamble(const char *wsi_extension)
|
|||
{
|
||||
load_loader();
|
||||
if (!dl || !dl->success())
|
||||
return vk::UniqueInstance();
|
||||
return {};
|
||||
|
||||
std::vector<const char *> extensions = { wsi_extension, VK_KHR_SURFACE_EXTENSION_NAME };
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
{
|
||||
|
||||
init_device(preferred_device);
|
||||
init_vma();
|
||||
init_command_pool();
|
||||
|
@ -179,12 +199,13 @@ bool Context::init_device(int preferred_device)
|
|||
return device_list[0];
|
||||
};
|
||||
|
||||
if (preferred_device > 0)
|
||||
if (preferred_device > -1 && preferred_device < device_list.size())
|
||||
physical_device = device_list[preferred_device];
|
||||
else
|
||||
physical_device = find_device();
|
||||
|
||||
physical_device.getProperties(&physical_device_props);
|
||||
printf("Vulkan: Using device \"%s\"\n", (const char *)physical_device_props.deviceName);
|
||||
|
||||
graphics_queue_family_index = UINT32_MAX;
|
||||
auto queue_props = physical_device.getQueueFamilyProperties();
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
#endif
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include "vk_mem_alloc.hpp"
|
||||
#include "vulkan/vulkan.hpp"
|
||||
#include "../external/VulkanMemoryAllocator-Hpp/include/vk_mem_alloc.hpp"
|
||||
#include "vulkan_swapchain.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
@ -24,21 +24,22 @@ class Context
|
|||
Context();
|
||||
~Context();
|
||||
#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
|
||||
#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
|
||||
#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
|
||||
bool init(int preferred_device = 0);
|
||||
bool init(int preferred_device = -1);
|
||||
bool create_swapchain(int width = -1, int height = -1);
|
||||
bool recreate_swapchain(int width = -1, int height = -1);
|
||||
void wait_idle();
|
||||
vk::CommandBuffer begin_cmd_buffer();
|
||||
void end_cmd_buffer();
|
||||
void hard_barrier(vk::CommandBuffer cmd);
|
||||
static std::vector<std::string> get_device_list();
|
||||
|
||||
vma::Allocator allocator;
|
||||
vk::Device device;
|
||||
|
|
|
@ -251,7 +251,8 @@ void Texture::create(int width, int height, vk::Format fmt, vk::SamplerAddressMo
|
|||
.setUsage(vk::BufferUsageFlagBits::eTransferSrc);
|
||||
|
||||
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);
|
||||
|
||||
|
|