Merge pull request #298 from snes9xgit/master

Sync to upstream
This commit is contained in:
ds22x 2024-08-20 01:38:48 +02:00 committed by GitHub
commit 229933ea5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
127 changed files with 1299 additions and 722 deletions

View File

@ -1,4 +1,4 @@
version: 1.62.3-{build}
version: 1.63-{build}
image: Visual Studio 2022

View File

@ -518,10 +518,14 @@ int S9xModifyCheatGroup(uint32 num, const std::string &name, const std::string &
if (num >= Cheat.group.size())
return -1;
bool enabled = Cheat.group[num].enabled;
S9xDisableCheatGroup(num);
Cheat.group[num] = S9xCreateCheatGroup(name, cheat);
if (enabled)
S9xEnableCheatGroup(num);
return num;
}

View File

@ -11,7 +11,7 @@
#include <map>
#include "glsl.h"
#include "shader_helpers.h"
#include "../vulkan/slang_helpers.hpp"
#include "common/video/vulkan/slang_helpers.hpp"
#include "shader_platform.h"
#ifndef _MSC_VER
#include <unistd.h>

View File

@ -8,7 +8,7 @@
#define __GLSL_H
#include "snes9x.h"
#include "../vulkan/slang_preset_ini.hpp"
#include "common/video/vulkan/slang_preset_ini.hpp"
#include "shader_platform.h"
#include <deque>
#include <limits.h>

View File

@ -8,7 +8,7 @@
#define __WAYLAND_EGL_CONTEXT_H
#include "opengl_context.hpp"
#include "wayland_surface.hpp"
#include "common/video/wayland/wayland_surface.hpp"
#include "glad/egl.h"
#include <memory>

View File

@ -1,5 +1,5 @@
#include "slang_preset.hpp"
#include "../external/SPIRV-Cross/spirv.hpp"
#include "external/SPIRV-Cross/spirv.hpp"
#include "slang_helpers.hpp"
#include "slang_preset_ini.hpp"
@ -12,8 +12,8 @@
#include <fstream>
#include <filesystem>
#include <future>
#include "../external/SPIRV-Cross/spirv_cross.hpp"
#include "../external/SPIRV-Cross/spirv_glsl.hpp"
#include "external/SPIRV-Cross/spirv_cross.hpp"
#include "external/SPIRV-Cross/spirv_glsl.hpp"
#include "slang_shader.hpp"
using std::string;

View File

@ -7,9 +7,9 @@
#include <sstream>
#include <vector>
#include <fstream>
#include "../external/glslang/glslang/Public/ShaderLang.h"
#include "../external/glslang/SPIRV/GlslangToSpv.h"
#include "../external/glslang/glslang/Public/ResourceLimits.h"
#include "external/glslang/glslang/Public/ShaderLang.h"
#include "external/glslang/SPIRV/GlslangToSpv.h"
#include "external/glslang/glslang/Public/ResourceLimits.h"
using std::string;
using std::vector;

View File

@ -183,69 +183,88 @@ bool Context::init_command_pool()
return true;
}
bool Context::init_device(int preferred_device)
static bool find_extension(std::vector<vk::ExtensionProperties> &props, const char *extension_string)
{
const char *required_extensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
// VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME
};
auto check_extensions = [&](vk::PhysicalDevice &device) -> bool {
auto [retval, props] = device.enumerateDeviceExtensionProperties();
for (const auto &extension : required_extensions) {
auto found = std::find_if(
props.begin(), props.end(), [&](vk::ExtensionProperties &ext) {
return (std::string(ext.extensionName.data()) == extension);
});
return found != props.end();
}
return true;
};
return std::find_if(props.begin(),
props.end(),
[&](vk::ExtensionProperties &ext) {
return (std::string(ext.extensionName.data()) == extension_string);
}) != props.end();
};
auto device_list = instance->enumeratePhysicalDevices().value;
if (preferred_device > -1 &&
preferred_device < device_list.size() &&
check_extensions(device_list[preferred_device]))
{
physical_device = device_list[preferred_device];
}
else
{
for (auto &device : device_list)
if (check_extensions(device))
{
physical_device = device;
break;
}
}
physical_device.getProperties(&physical_device_props);
graphics_queue_family_index = UINT32_MAX;
auto queue_props = physical_device.getQueueFamilyProperties();
static uint32_t find_graphics_queue(vk::PhysicalDevice &device)
{
auto queue_props = device.getQueueFamilyProperties();
for (size_t i = 0; i < queue_props.size(); i++)
{
if (queue_props[i].queueFlags & vk::QueueFlagBits::eGraphics)
{
graphics_queue_family_index = i;
break;
return i;
}
}
return UINT32_MAX;
}
static bool check_extensions(std::vector<const char *> &required_extensions, vk::PhysicalDevice &device)
{
auto props = device.enumerateDeviceExtensionProperties().value;
for (const auto &extension : required_extensions)
{
if (!find_extension(props, extension))
return false;
}
return true;
};
bool Context::init_device(int preferred_device)
{
std::vector<const char *> required_extensions = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
auto device_list = instance->enumeratePhysicalDevices().value;
physical_device = nullptr;
if (preferred_device > -1 &&
(size_t)preferred_device < device_list.size() &&
check_extensions(required_extensions, device_list[preferred_device]))
{
physical_device = device_list[preferred_device];
}
if (physical_device == nullptr)
{
for (auto &device : device_list)
{
if (check_extensions(required_extensions, device))
{
physical_device = device;
break;
}
}
}
auto extension_properties = physical_device.enumerateDeviceExtensionProperties().value;
physical_device.getProperties(&physical_device_props);
graphics_queue_family_index = find_graphics_queue(physical_device);
if (graphics_queue_family_index == UINT32_MAX)
return false;
vk::DeviceQueueCreateInfo dqci({}, graphics_queue_family_index, 1);
vk::DeviceCreateInfo dci({}, dqci, {}, required_extensions, {});
std::vector<float> priorities = { 1.0f };
vk::DeviceQueueCreateInfo dqci({}, graphics_queue_family_index, priorities);
vk::DeviceCreateInfo dci({}, dqci, {}, required_extensions);
device = physical_device.createDevice(dci).value;
queue = device.getQueue(graphics_queue_family_index, 0);
auto surface_formats = physical_device.getSurfaceFormatsKHR(surface.get()).value;
auto format = std::find_if(surface_formats.begin(), surface_formats.end(), [](vk::SurfaceFormatKHR &f) {
return (f.format == vk::Format::eB8G8R8A8Unorm);
});
if (format == surface_formats.end())
if (std::find_if(surface_formats.begin(),
surface_formats.end(),
[](vk::SurfaceFormatKHR &f) {
return (f.format == vk::Format::eB8G8R8A8Unorm);
}) == surface_formats.end())
return false;
return true;

View File

@ -8,8 +8,8 @@
#define WINVER 0x599
#endif
#include <cstdint>
#include "vulkan/vulkan_hpp_wrapper.hpp"
#include "../external/VulkanMemoryAllocator-Hpp/include/vk_mem_alloc.hpp"
#include "vulkan_hpp_wrapper.hpp"
#include "external/VulkanMemoryAllocator-Hpp/include/vk_mem_alloc.hpp"
#include "vulkan_swapchain.hpp"
#include <memory>

View File

@ -1,5 +1,5 @@
#pragma once
#include "vulkan/vulkan_hpp_wrapper.hpp"
#include "vulkan_hpp_wrapper.hpp"
#include "slang_shader.hpp"
#include "vulkan_context.hpp"
#include "vulkan_pipeline_image.hpp"

View File

@ -1,5 +1,4 @@
#include "vulkan_swapchain.hpp"
#include <thread>
namespace Vulkan
{
@ -19,13 +18,9 @@ Swapchain::~Swapchain()
{
}
bool Swapchain::set_vsync(bool new_setting)
void Swapchain::set_vsync(bool new_setting)
{
if (new_setting == vsync)
return false;
vsync = new_setting;
return true;
}
void Swapchain::on_render_pass_end(std::function<void ()> function)
@ -80,13 +75,40 @@ void Swapchain::create_render_pass()
bool Swapchain::recreate(int new_width, int new_height)
{
device.waitIdle();
if (swapchain_object)
{
device.waitIdle();
wait_on_frames();
}
return create(num_swapchain_images, new_width, new_height);
}
vk::Image Swapchain::get_image()
{
return imageviewfbs[current_swapchain_image].image;
return image_data[current_swapchain_image].image;
}
template<typename T>
static bool vector_find(std::vector<T> haystack, T&& needle)
{
for (auto &elem : haystack)
if (elem == needle)
return true;
return false;
}
vk::PresentModeKHR Swapchain::get_present_mode() {
auto present_mode = vk::PresentModeKHR::eFifo;
if (!vsync) {
if (supports_mailbox)
present_mode = vk::PresentModeKHR::eMailbox;
if (supports_immediate)
present_mode = vk::PresentModeKHR::eImmediate;
}
return present_mode;
}
bool Swapchain::check_and_resize(int width, int height)
@ -115,7 +137,7 @@ bool Swapchain::check_and_resize(int width, int height)
bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width, int new_height)
{
frames.clear();
imageviewfbs.clear();
image_data.clear();
auto surface_capabilities = physical_device.getSurfaceCapabilitiesKHR(surface).value;
@ -126,7 +148,7 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
// If extents aren't reported (Wayland), we have to rely on Wayland to report
// the size, so keep current extent.
if (surface_capabilities.currentExtent.width != -1)
if (surface_capabilities.currentExtent.width != UINT32_MAX)
extents = surface_capabilities.currentExtent;
uint32_t graphics_queue_index = 0;
@ -164,11 +186,12 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
extents.height = surface_capabilities.minImageExtent.height;
auto present_modes = physical_device.getSurfacePresentModesKHR(surface).value;
auto tearing_present_mode = vk::PresentModeKHR::eFifo;
if (std::find(present_modes.begin(), present_modes.end(), vk::PresentModeKHR::eImmediate) != present_modes.end())
tearing_present_mode = vk::PresentModeKHR::eImmediate;
if (std::find(present_modes.begin(), present_modes.end(), vk::PresentModeKHR::eMailbox) != present_modes.end())
tearing_present_mode = vk::PresentModeKHR::eMailbox;
supports_mailbox = vector_find(present_modes, vk::PresentModeKHR::eMailbox);
supports_immediate = vector_find(present_modes, vk::PresentModeKHR::eImmediate);
supports_relaxed = vector_find(present_modes, vk::PresentModeKHR::eFifoRelaxed);
auto swapchain_maintenance_info = vk::SwapchainPresentModesCreateInfoEXT{}
.setPresentModes(present_modes);
auto swapchain_create_info = vk::SwapchainCreateInfoKHR{}
.setMinImageCount(num_swapchain_images)
@ -179,33 +202,38 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc)
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
.setClipped(true)
.setPresentMode(vsync ? vk::PresentModeKHR::eFifo : tearing_present_mode)
.setPresentMode(get_present_mode())
.setSurface(surface)
.setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity)
.setImageArrayLayers(1)
.setQueueFamilyIndices(graphics_queue_index);
.setQueueFamilyIndices(graphics_queue_index)
.setPNext(&swapchain_maintenance_info);
if (swapchain_object)
swapchain_create_info.setOldSwapchain(swapchain_object.get());
try {
swapchain_object = device.createSwapchainKHRUnique(swapchain_create_info).value;
} catch (std::exception &e) {
swapchain_object.reset();
auto resval = device.createSwapchainKHRUnique(swapchain_create_info);
if (resval.result != vk::Result::eSuccess && resval.result != vk::Result::eSuboptimalKHR)
{
swapchain_object.reset();
}
if (!swapchain_object)
return false;
}
swapchain_object = std::move(resval.value);
create_resources();
return true;
}
bool Swapchain::create_resources()
{
auto swapchain_images = device.getSwapchainImagesKHR(swapchain_object.get()).value;
vk::CommandBufferAllocateInfo command_buffer_allocate_info(command_pool, vk::CommandBufferLevel::ePrimary, swapchain_images.size());
if (swapchain_images.size() > num_swapchain_images)
num_swapchain_images = swapchain_images.size();
vk::CommandBufferAllocateInfo command_buffer_allocate_info(command_pool, vk::CommandBufferLevel::ePrimary, num_swapchain_images);
auto command_buffers = device.allocateCommandBuffersUnique(command_buffer_allocate_info).value;
if (imageviewfbs.size() > num_swapchain_images)
num_swapchain_images = imageviewfbs.size();
frames.resize(num_swapchain_images);
imageviewfbs.resize(num_swapchain_images);
image_data.resize(num_swapchain_images);
vk::FenceCreateInfo fence_create_info(vk::FenceCreateFlagBits::eSignaled);
@ -218,12 +246,11 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
frame.acquire = device.createSemaphoreUnique({}).value;
frame.complete = device.createSemaphoreUnique({}).value;
}
current_frame = 0;
for (unsigned int i = 0; i < num_swapchain_images; i++)
{
// Create resources associated with swapchain images
auto &image = imageviewfbs[i];
auto &image = image_data[i];
image.image = swapchain_images[i];
auto image_view_create_info = vk::ImageViewCreateInfo{}
.setImage(swapchain_images[i])
@ -240,11 +267,12 @@ bool Swapchain::create(unsigned int desired_num_swapchain_images, int new_width,
.setLayers(1)
.setRenderPass(render_pass.get());
image.framebuffer = device.createFramebufferUnique(framebuffer_create_info).value;
image.fence = device.createFenceUnique(fence_create_info).value;
}
device.waitIdle();
current_swapchain_image = 0;
current_frame = 0;
return true;
}
@ -259,7 +287,7 @@ bool Swapchain::begin_frame()
auto &frame = frames[current_frame];
auto result = device.waitForFences(frame.fence.get(), true, 33333333);
auto result = device.waitForFences({ frame.fence.get() }, true, 33333333);
if (result != vk::Result::eSuccess)
{
printf("Timed out waiting for fence.\n");
@ -268,7 +296,7 @@ bool Swapchain::begin_frame()
vk::ResultValue<uint32_t> result_value(vk::Result::eSuccess, 0);
result_value = device.acquireNextImageKHR(swapchain_object.get(), UINT64_MAX, frame.acquire.get());
if (result_value.result == vk::Result::eErrorOutOfDateKHR ||
result_value.result == vk::Result::eSuboptimalKHR)
{
@ -320,13 +348,22 @@ bool Swapchain::swap()
.setSwapchains(swapchain_object.get())
.setImageIndices(current_swapchain_image);
vk::Result result = vk::Result::eSuccess;
result = queue.presentKHR(present_info);
vk::SwapchainPresentModeInfoEXT present_mode_info;
auto present_mode = get_present_mode();
present_mode_info.setPresentModes(present_mode);
present_info.setPNext(&present_mode_info);
auto &present_fence = image_data[current_swapchain_image].fence.get();
device.resetFences(present_fence);
vk::SwapchainPresentFenceInfoEXT present_fence_info(present_fence);
present_mode_info.setPNext(&present_fence_info);
vk::Result result = queue.presentKHR(present_info);
if (result == vk::Result::eErrorOutOfDateKHR)
{
// NVIDIA binary drivers will set OutOfDate between acquire and
// present. Ignore this and fix it on the next swapchain acquire.
}
}
current_frame = (current_frame + 1) % num_swapchain_images;
@ -343,7 +380,7 @@ bool Swapchain::end_frame()
vk::Framebuffer Swapchain::get_framebuffer()
{
return imageviewfbs[current_swapchain_image].framebuffer.get();
return image_data[current_swapchain_image].framebuffer.get();
}
vk::CommandBuffer &Swapchain::get_cmd()
@ -360,7 +397,7 @@ void Swapchain::begin_render_pass()
auto render_pass_begin_info = vk::RenderPassBeginInfo{}
.setRenderPass(render_pass.get())
.setFramebuffer(imageviewfbs[current_swapchain_image].framebuffer.get())
.setFramebuffer(image_data[current_swapchain_image].framebuffer.get())
.setRenderArea(vk::Rect2D({}, extents))
.setClearValues(value);
get_cmd().beginRenderPass(render_pass_begin_info, vk::SubpassContents::eInline);
@ -379,10 +416,16 @@ void Swapchain::end_render_pass()
bool Swapchain::wait_on_frame(int frame_num)
{
auto result = device.waitForFences(frames[frame_num].fence.get(), true, 33000000);
auto result = device.waitForFences({ image_data[frame_num].fence.get() }, true, 33000000);
return (result == vk::Result::eSuccess);
}
void Swapchain::wait_on_frames()
{
for (auto i = 0; i < image_data.size(); i++)
wait_on_frame(i);
}
vk::Extent2D Swapchain::get_extents()
{
return extents;

View File

@ -1,6 +1,6 @@
#pragma once
#include "vulkan/vulkan_hpp_wrapper.hpp"
#include "vulkan_hpp_wrapper.hpp"
#include <functional>
namespace Vulkan
@ -17,6 +17,7 @@ class Swapchain
~Swapchain();
bool create(unsigned int num_frames, int width = -1, int height = -1);
bool recreate(int width = -1, int height = -1);
bool create_resources();
bool check_and_resize(int width = -1, int height = -1);
bool begin_frame();
void begin_render_pass();
@ -25,10 +26,11 @@ class Swapchain
bool end_frame();
void end_frame_without_swap();
bool swap();
// Returns true if vsync setting was changed, false if it was the same
bool set_vsync(bool on);
void wait_on_frames();
void set_vsync(bool on);
void on_render_pass_end(std::function<void()> function);
int get_num_frames() { return num_swapchain_images; }
vk::PresentModeKHR get_present_mode();
vk::Image get_image();
vk::Framebuffer get_framebuffer();
@ -48,9 +50,10 @@ class Swapchain
vk::UniqueCommandBuffer command_buffer;
};
struct ImageViewFB
struct ImageData
{
vk::Image image;
vk::UniqueFence fence;
vk::UniqueImageView image_view;
vk::UniqueFramebuffer framebuffer;
};
@ -64,8 +67,11 @@ class Swapchain
unsigned int current_swapchain_image = 0;
unsigned int num_swapchain_images = 0;
bool vsync = true;
bool supports_immediate = false;
bool supports_mailbox = false;
bool supports_relaxed = false;
std::vector<Frame> frames;
std::vector<ImageViewFB> imageviewfbs;
std::vector<ImageData> image_data;
vk::Device device;
vk::SurfaceKHR surface;

View File

@ -1,3 +1,33 @@
Snes9x 1.63
General:
- Added a shortcut to change the backdrop color for sprite extraction.
- Fixed QuickSave 0-9 slot shortcuts not working.
- Allow "Address:byte" form for cheat inputs.
- Fixed ZIP files not being closed after patch search.
- Various memmap fixes to allow unofficial mappings.
- Added usage of ImGui to draw things on top of the screen instead of inside.
Win32:
- Fixed AVI not recording audio.
- Fixed framerate throttling in turbo mode (now works during AVI recording).
- Fixed interlaced output speed being double.
- Fixed command line arguments not working.
- Fixed WaveOut device name display for names longer than 31 characters.
- Fixed Bank+/- hotkey saving.
- Added hotkeys for aspect ratio, cheat edit/search.
- Added multiselect for cheat edit dialog.
Gtk:
- Fixed config file location to never put files directly in $HOME and obey
$XDG_CONFIG_HOME.
- Updated translations from JakeSmarter and StanleyKid-22.
Mac:
- Added a new cheat finder.
- Added MultiCart support back.
- Create a blank window when starting the program, so the global menu change
doesn't go unnoticed.
Snes9x 1.62
- Fixed SA1 division with negative dividend again. (Atari2)
- Fixed timing on several instructions. (pi1541)

View File

@ -12,6 +12,7 @@
#include "movie.h"
#include "gfx.h"
#include "ppu.h"
#include "cheats.h"
namespace
{
@ -115,6 +116,42 @@ static void ImGui_DrawPressedKeys(int spacing)
}
}
static void ImGui_GetWatchesText(std::string& osd_text)
{
for (unsigned int i = 0; i < sizeof(watches) / sizeof(watches[0]); i++)
{
if (!watches[i].on)
break;
int32 displayNumber = 0;
char buf[64];
for (int r = 0; r < watches[i].size; r++)
displayNumber += (Cheat.CWatchRAM[(watches[i].address - 0x7E0000) + r]) << (8 * r);
if (watches[i].format == 1)
sprintf(buf, "%s,%du = %u", watches[i].desc, watches[i].size, (unsigned int)displayNumber);
else
if (watches[i].format == 3)
sprintf(buf, "%s,%dx = %X", watches[i].desc, watches[i].size, (unsigned int)displayNumber);
else // signed
{
if (watches[i].size == 1)
displayNumber = (int32)((int8)displayNumber);
else if (watches[i].size == 2)
displayNumber = (int32)((int16)displayNumber);
else if (watches[i].size == 3)
if (displayNumber >= 8388608)
displayNumber -= 16777216;
sprintf(buf, "%s,%ds = %d", watches[i].desc, watches[i].size, (int)displayNumber);
}
osd_text += buf;
osd_text += '\n';
}
}
static void ImGui_DrawTextOverlay(const char *text,
int x, int y,
int padding,
@ -246,9 +283,29 @@ bool S9xImGuiDraw(int width, int height)
}
}
std::string utf8_message;
if (Settings.DisplayWatchedAddresses)
{
ImGui_GetWatchesText(utf8_message);
}
if (!GFX.InfoString.empty())
{
auto utf8_message = sjis_to_utf8(GFX.InfoString);
utf8_message += sjis_to_utf8(GFX.InfoString);
}
if (Settings.DisplayMovieFrame && S9xMovieActive())
{
// move movie frame count into its own line if info message is active and not already a newline at end
if (!utf8_message.empty() && utf8_message.back() != '\n')
{
utf8_message += '\n';
}
utf8_message += GFX.FrameDisplayString;
}
if (!utf8_message.empty())
{
ImGui_DrawTextOverlay(utf8_message.c_str(),
settings.spacing,
height - settings.spacing,

View File

@ -1,4 +1,4 @@
/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb
/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk
Do this:
@ -48,6 +48,9 @@ LICENSE
RECENT REVISION HISTORY:
2.30 (2024-05-31) avoid erroneous gcc warning
2.29 (2023-05-xx) optimizations
2.28 (2023-01-29) many error fixes, security errors, just tons of stuff
2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes
2.26 (2020-07-13) many minor fixes
2.25 (2020-02-02) fix warnings
@ -108,7 +111,7 @@ RECENT REVISION HISTORY:
Cass Everitt Ryamond Barbiero github:grim210
Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw
Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus
Josh Tobin Matthew Gregan github:poppolopoppo
Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo
Julian Raschke Gregory Mullen Christian Floisand github:darealshinji
Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007
Brad Weinberger Matvey Cherevko github:mosra
@ -140,7 +143,7 @@ RECENT REVISION HISTORY:
// // ... x = width, y = height, n = # 8-bit components per pixel ...
// // ... replace '0' with '1'..'4' to force that many components per pixel
// // ... but 'n' will always be the number that it would have been if you said 0
// stbi_image_free(data)
// stbi_image_free(data);
//
// Standard parameters:
// int *x -- outputs image width in pixels
@ -635,7 +638,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch
#endif
#endif
#ifdef _MSC_VER
#if defined(_MSC_VER) || defined(__SYMBIAN32__)
typedef unsigned short stbi__uint16;
typedef signed short stbi__int16;
typedef unsigned int stbi__uint32;
@ -1063,6 +1066,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
}
#endif
// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow.
static int stbi__addints_valid(int a, int b)
{
if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow
if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0.
return a <= INT_MAX - b;
}
// returns 1 if the product of two ints fits in a signed short, 0 on overflow.
static int stbi__mul2shorts_valid(int a, int b)
{
if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow
if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid
if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN
return a >= SHRT_MIN / b;
}
// stbi__err - error
// stbi__errpf - error returning pointer to float
// stbi__errpuc - error returning pointer to unsigned char
@ -1985,9 +2005,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count)
int i,j,k=0;
unsigned int code;
// build size list for each symbol (from JPEG spec)
for (i=0; i < 16; ++i)
for (j=0; j < count[i]; ++j)
for (i=0; i < 16; ++i) {
for (j=0; j < count[i]; ++j) {
h->size[k++] = (stbi_uc) (i+1);
if(k >= 257) return stbi__err("bad size list","Corrupt JPEG");
}
}
h->size[k] = 0;
// compute actual symbols (from jpeg spec)
@ -2112,6 +2135,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
// convert the huffman code to the symbol id
c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
if(c < 0 || c >= 256) // symbol id out of bounds!
return -1;
STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]);
// convert the id to a symbol
@ -2130,6 +2155,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
unsigned int k;
int sgn;
if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative)
k = stbi_lrot(j->code_buffer, n);
@ -2144,6 +2170,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n)
{
unsigned int k;
if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing
k = stbi_lrot(j->code_buffer, n);
j->code_buffer = k & ~stbi__bmask[n];
k &= stbi__bmask[n];
@ -2155,6 +2182,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j)
{
unsigned int k;
if (j->code_bits < 1) stbi__grow_buffer_unsafe(j);
if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing
k = j->code_buffer;
j->code_buffer <<= 1;
--j->code_bits;
@ -2192,8 +2220,10 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman
memset(data,0,64*sizeof(data[0]));
diff = t ? stbi__extend_receive(j, t) : 0;
if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG");
dc = j->img_comp[b].dc_pred + diff;
j->img_comp[b].dc_pred = dc;
if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
data[0] = (short) (dc * dequant[0]);
// decode AC components, see JPEG spec
@ -2207,6 +2237,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman
if (r) { // fast-AC path
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
j->code_buffer <<= s;
j->code_bits -= s;
// decode into unzigzag'd location
@ -2246,8 +2277,10 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__
if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
diff = t ? stbi__extend_receive(j, t) : 0;
if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG");
dc = j->img_comp[b].dc_pred + diff;
j->img_comp[b].dc_pred = dc;
if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
data[0] = (short) (dc * (1 << j->succ_low));
} else {
// refinement scan for DC coefficient
@ -2282,6 +2315,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__
if (r) { // fast-AC path
k += (r >> 4) & 15; // run
s = r & 15; // combined length
if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available");
j->code_buffer <<= s;
j->code_bits -= s;
zig = stbi__jpeg_dezigzag[k++];
@ -3102,6 +3136,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m)
sizes[i] = stbi__get8(z->s);
n += sizes[i];
}
if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values!
L -= 17;
if (tc == 0) {
if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
@ -3351,6 +3386,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
return 1;
}
static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j)
{
// some JPEGs have junk at end, skip over it but if we find what looks
// like a valid marker, resume there
while (!stbi__at_eof(j->s)) {
stbi_uc x = stbi__get8(j->s);
while (x == 0xff) { // might be a marker
if (stbi__at_eof(j->s)) return STBI__MARKER_none;
x = stbi__get8(j->s);
if (x != 0x00 && x != 0xff) {
// not a stuffed zero or lead-in to another marker, looks
// like an actual marker, return it
return x;
}
// stuffed zero has x=0 now which ends the loop, meaning we go
// back to regular scan loop.
// repeated 0xff keeps trying to read the next byte of the marker.
}
}
return STBI__MARKER_none;
}
// decode image to YCbCr format
static int stbi__decode_jpeg_image(stbi__jpeg *j)
{
@ -3367,25 +3424,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j)
if (!stbi__process_scan_header(j)) return 0;
if (!stbi__parse_entropy_coded_data(j)) return 0;
if (j->marker == STBI__MARKER_none ) {
// handle 0s at the end of image data from IP Kamera 9060
while (!stbi__at_eof(j->s)) {
int x = stbi__get8(j->s);
if (x == 255) {
j->marker = stbi__get8(j->s);
break;
}
}
j->marker = stbi__skip_jpeg_junk_at_end(j);
// if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
}
m = stbi__get_marker(j);
if (STBI__RESTART(m))
m = stbi__get_marker(j);
} else if (stbi__DNL(m)) {
int Ld = stbi__get16be(j->s);
stbi__uint32 NL = stbi__get16be(j->s);
if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
m = stbi__get_marker(j);
} else {
if (!stbi__process_marker(j, m)) return 0;
if (!stbi__process_marker(j, m)) return 1;
m = stbi__get_marker(j);
}
m = stbi__get_marker(j);
}
if (j->progressive)
stbi__jpeg_finish(j);
@ -3976,6 +4030,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re
unsigned char* result;
stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
if (!j) return stbi__errpuc("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
STBI_NOTUSED(ri);
j->s = s;
stbi__setup_jpeg(j);
@ -3989,6 +4044,7 @@ static int stbi__jpeg_test(stbi__context *s)
int r;
stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
if (!j) return stbi__err("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
j->s = s;
stbi__setup_jpeg(j);
r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
@ -4014,6 +4070,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
int result;
stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
if (!j) return stbi__err("outofmem", "Out of memory");
memset(j, 0, sizeof(stbi__jpeg));
j->s = s;
result = stbi__jpeg_info_raw(j, x, y, comp);
STBI_FREE(j);
@ -4121,6 +4178,7 @@ typedef struct
{
stbi_uc *zbuffer, *zbuffer_end;
int num_bits;
int hit_zeof_once;
stbi__uint32 code_buffer;
char *zout;
@ -4187,9 +4245,20 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
int b,s;
if (a->num_bits < 16) {
if (stbi__zeof(a)) {
return -1; /* report error for unexpected end of data. */
if (!a->hit_zeof_once) {
// This is the first time we hit eof, insert 16 extra padding btis
// to allow us to keep going; if we actually consume any of them
// though, that is invalid data. This is caught later.
a->hit_zeof_once = 1;
a->num_bits += 16; // add 16 implicit zero bits
} else {
// We already inserted our extra 16 padding bits and are again
// out, this stream is actually prematurely terminated.
return -1;
}
} else {
stbi__fill_bits(a);
}
stbi__fill_bits(a);
}
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
if (b) {
@ -4254,17 +4323,25 @@ static int stbi__parse_huffman_block(stbi__zbuf *a)
int len,dist;
if (z == 256) {
a->zout = zout;
if (a->hit_zeof_once && a->num_bits < 16) {
// The first time we hit zeof, we inserted 16 extra zero bits into our bit
// buffer so the decoder can just do its speculative decoding. But if we
// actually consumed any of those bits (which is the case when num_bits < 16),
// the stream actually read past the end so it is malformed.
return stbi__err("unexpected end","Corrupt PNG");
}
return 1;
}
if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data
z -= 257;
len = stbi__zlength_base[z];
if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]);
z = stbi__zhuffman_decode(a, &a->z_distance);
if (z < 0) return stbi__err("bad huffman code","Corrupt PNG");
if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data
dist = stbi__zdist_base[z];
if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]);
if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG");
if (zout + len > a->zout_end) {
if (len > a->zout_end - zout) {
if (!stbi__zexpand(a, zout, len)) return 0;
zout = a->zout;
}
@ -4408,6 +4485,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
if (!stbi__parse_zlib_header(a)) return 0;
a->num_bits = 0;
a->code_buffer = 0;
a->hit_zeof_once = 0;
do {
final = stbi__zreceive(a,1);
type = stbi__zreceive(a,2);
@ -4563,9 +4641,8 @@ enum {
STBI__F_up=2,
STBI__F_avg=3,
STBI__F_paeth=4,
// synthetic filters used for first scanline to avoid needing a dummy row of 0s
STBI__F_avg_first,
STBI__F_paeth_first
// synthetic filter used for first scanline to avoid needing a dummy row of 0s
STBI__F_avg_first
};
static stbi_uc first_row_filter[5] =
@ -4574,29 +4651,56 @@ static stbi_uc first_row_filter[5] =
STBI__F_sub,
STBI__F_none,
STBI__F_avg_first,
STBI__F_paeth_first
STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub
};
static int stbi__paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = abs(p-a);
int pb = abs(p-b);
int pc = abs(p-c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
// This formulation looks very different from the reference in the PNG spec, but is
// actually equivalent and has favorable data dependencies and admits straightforward
// generation of branch-free code, which helps performance significantly.
int thresh = c*3 - (a + b);
int lo = a < b ? a : b;
int hi = a < b ? b : a;
int t0 = (hi <= thresh) ? lo : c;
int t1 = (thresh <= lo) ? hi : t0;
return t1;
}
static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
// adds an extra all-255 alpha channel
// dest == src is legal
// img_n must be 1 or 3
static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n)
{
int i;
// must process data backwards since we allow dest==src
if (img_n == 1) {
for (i=x-1; i >= 0; --i) {
dest[i*2+1] = 255;
dest[i*2+0] = src[i];
}
} else {
STBI_ASSERT(img_n == 3);
for (i=x-1; i >= 0; --i) {
dest[i*4+3] = 255;
dest[i*4+2] = src[i*3+2];
dest[i*4+1] = src[i*3+1];
dest[i*4+0] = src[i*3+0];
}
}
}
// create the png data from post-deflated data
static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
{
int bytes = (depth == 16? 2 : 1);
int bytes = (depth == 16 ? 2 : 1);
stbi__context *s = a->s;
stbi__uint32 i,j,stride = x*out_n*bytes;
stbi__uint32 img_len, img_width_bytes;
stbi_uc *filter_buf;
int all_ok = 1;
int k;
int img_n = s->img_n; // copy it into a local for later
@ -4608,8 +4712,11 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
if (!a->out) return stbi__err("outofmem", "Out of memory");
// note: error exits here don't need to clean up a->out individually,
// stbi__do_png always does on error.
if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
img_width_bytes = (((img_n * x * depth) + 7) >> 3);
if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG");
img_len = (img_width_bytes + 1) * y;
// we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
@ -4617,189 +4724,137 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
// so just check for raw_len < img_len always.
if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
// Allocate two scan lines worth of filter workspace buffer.
filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0);
if (!filter_buf) return stbi__err("outofmem", "Out of memory");
// Filtering for low-bit-depth images
if (depth < 8) {
filter_bytes = 1;
width = img_width_bytes;
}
for (j=0; j < y; ++j) {
stbi_uc *cur = a->out + stride*j;
stbi_uc *prior;
// cur/prior filter buffers alternate
stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes;
stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes;
stbi_uc *dest = a->out + stride*j;
int nk = width * filter_bytes;
int filter = *raw++;
if (filter > 4)
return stbi__err("invalid filter","Corrupt PNG");
if (depth < 8) {
if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
filter_bytes = 1;
width = img_width_bytes;
// check filter type
if (filter > 4) {
all_ok = stbi__err("invalid filter","Corrupt PNG");
break;
}
prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
// if first row, use special filter that doesn't sample previous row
if (j == 0) filter = first_row_filter[filter];
// handle first byte explicitly
for (k=0; k < filter_bytes; ++k) {
switch (filter) {
case STBI__F_none : cur[k] = raw[k]; break;
case STBI__F_sub : cur[k] = raw[k]; break;
case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break;
case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break;
case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break;
case STBI__F_avg_first : cur[k] = raw[k]; break;
case STBI__F_paeth_first: cur[k] = raw[k]; break;
}
// perform actual filtering
switch (filter) {
case STBI__F_none:
memcpy(cur, raw, nk);
break;
case STBI__F_sub:
memcpy(cur, raw, filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]);
break;
case STBI__F_up:
for (k = 0; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + prior[k]);
break;
case STBI__F_avg:
for (k = 0; k < filter_bytes; ++k)
cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1));
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1));
break;
case STBI__F_paeth:
for (k = 0; k < filter_bytes; ++k)
cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0)
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes]));
break;
case STBI__F_avg_first:
memcpy(cur, raw, filter_bytes);
for (k = filter_bytes; k < nk; ++k)
cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1));
break;
}
if (depth == 8) {
if (img_n != out_n)
cur[img_n] = 255; // first pixel
raw += img_n;
cur += out_n;
prior += out_n;
} else if (depth == 16) {
if (img_n != out_n) {
cur[filter_bytes] = 255; // first pixel top byte
cur[filter_bytes+1] = 255; // first pixel bottom byte
}
raw += filter_bytes;
cur += output_bytes;
prior += output_bytes;
} else {
raw += 1;
cur += 1;
prior += 1;
}
raw += nk;
// this is a little gross, so that we don't switch per-pixel or per-component
if (depth < 8 || img_n == out_n) {
int nk = (width - 1)*filter_bytes;
#define STBI__CASE(f) \
case f: \
for (k=0; k < nk; ++k)
switch (filter) {
// "none" filter turns into a memcpy here; make that explicit.
case STBI__F_none: memcpy(cur, raw, nk); break;
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
}
#undef STBI__CASE
raw += nk;
} else {
STBI_ASSERT(img_n+1 == out_n);
#define STBI__CASE(f) \
case f: \
for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
for (k=0; k < filter_bytes; ++k)
switch (filter) {
STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break;
STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
}
#undef STBI__CASE
// the loop above sets the high byte of the pixels' alpha, but for
// 16 bit png files we also need the low byte set. we'll do that here.
if (depth == 16) {
cur = a->out + stride*j; // start at the beginning of the row again
for (i=0; i < x; ++i,cur+=output_bytes) {
cur[filter_bytes+1] = 255;
}
}
}
}
// we make a separate pass to expand bits to pixels; for performance,
// this could run two scanlines behind the above code, so it won't
// intefere with filtering but will still be in the cache.
if (depth < 8) {
for (j=0; j < y; ++j) {
stbi_uc *cur = a->out + stride*j;
stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes;
// unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit
// png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop
// expand decoded bits in cur to dest, also adding an extra alpha channel if desired
if (depth < 8) {
stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range
stbi_uc *in = cur;
stbi_uc *out = dest;
stbi_uc inb = 0;
stbi__uint32 nsmp = x*img_n;
// note that the final byte might overshoot and write more data than desired.
// we can allocate enough data that this never writes out of memory, but it
// could also overwrite the next scanline. can it overwrite non-empty data
// on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel.
// so we need to explicitly clamp the final ones
// expand bits to bytes first
if (depth == 4) {
for (k=x*img_n; k >= 2; k-=2, ++in) {
*cur++ = scale * ((*in >> 4) );
*cur++ = scale * ((*in ) & 0x0f);
for (i=0; i < nsmp; ++i) {
if ((i & 1) == 0) inb = *in++;
*out++ = scale * (inb >> 4);
inb <<= 4;
}
if (k > 0) *cur++ = scale * ((*in >> 4) );
} else if (depth == 2) {
for (k=x*img_n; k >= 4; k-=4, ++in) {
*cur++ = scale * ((*in >> 6) );
*cur++ = scale * ((*in >> 4) & 0x03);
*cur++ = scale * ((*in >> 2) & 0x03);
*cur++ = scale * ((*in ) & 0x03);
for (i=0; i < nsmp; ++i) {
if ((i & 3) == 0) inb = *in++;
*out++ = scale * (inb >> 6);
inb <<= 2;
}
if (k > 0) *cur++ = scale * ((*in >> 6) );
if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03);
if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03);
} else if (depth == 1) {
for (k=x*img_n; k >= 8; k-=8, ++in) {
*cur++ = scale * ((*in >> 7) );
*cur++ = scale * ((*in >> 6) & 0x01);
*cur++ = scale * ((*in >> 5) & 0x01);
*cur++ = scale * ((*in >> 4) & 0x01);
*cur++ = scale * ((*in >> 3) & 0x01);
*cur++ = scale * ((*in >> 2) & 0x01);
*cur++ = scale * ((*in >> 1) & 0x01);
*cur++ = scale * ((*in ) & 0x01);
} else {
STBI_ASSERT(depth == 1);
for (i=0; i < nsmp; ++i) {
if ((i & 7) == 0) inb = *in++;
*out++ = scale * (inb >> 7);
inb <<= 1;
}
if (k > 0) *cur++ = scale * ((*in >> 7) );
if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01);
if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01);
if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01);
if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01);
if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01);
if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01);
}
if (img_n != out_n) {
int q;
// insert alpha = 255
cur = a->out + stride*j;
// insert alpha=255 values if desired
if (img_n != out_n)
stbi__create_png_alpha_expand8(dest, dest, x, img_n);
} else if (depth == 8) {
if (img_n == out_n)
memcpy(dest, cur, x*img_n);
else
stbi__create_png_alpha_expand8(dest, cur, x, img_n);
} else if (depth == 16) {
// convert the image data from big-endian to platform-native
stbi__uint16 *dest16 = (stbi__uint16*)dest;
stbi__uint32 nsmp = x*img_n;
if (img_n == out_n) {
for (i = 0; i < nsmp; ++i, ++dest16, cur += 2)
*dest16 = (cur[0] << 8) | cur[1];
} else {
STBI_ASSERT(img_n+1 == out_n);
if (img_n == 1) {
for (q=x-1; q >= 0; --q) {
cur[q*2+1] = 255;
cur[q*2+0] = cur[q];
for (i = 0; i < x; ++i, dest16 += 2, cur += 2) {
dest16[0] = (cur[0] << 8) | cur[1];
dest16[1] = 0xffff;
}
} else {
STBI_ASSERT(img_n == 3);
for (q=x-1; q >= 0; --q) {
cur[q*4+3] = 255;
cur[q*4+2] = cur[q*3+2];
cur[q*4+1] = cur[q*3+1];
cur[q*4+0] = cur[q*3+0];
for (i = 0; i < x; ++i, dest16 += 4, cur += 6) {
dest16[0] = (cur[0] << 8) | cur[1];
dest16[1] = (cur[2] << 8) | cur[3];
dest16[2] = (cur[4] << 8) | cur[5];
dest16[3] = 0xffff;
}
}
}
}
} else if (depth == 16) {
// force the image data from big-endian to platform-native.
// this is done in a separate pass due to the decoding relying
// on the data being untouched, but could probably be done
// per-line during decode if care is taken.
stbi_uc *cur = a->out;
stbi__uint16 *cur16 = (stbi__uint16*)cur;
for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
*cur16 = (cur[0] << 8) | cur[1];
}
}
STBI_FREE(filter_buf);
if (!all_ok) return 0;
return 1;
}
@ -4955,7 +5010,7 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set;
static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set;
STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply)
{
stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply;
stbi__unpremultiply_on_load_set = 1;
@ -5064,14 +5119,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (!pal_img_n) {
s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
if (scan == STBI__SCAN_header) return 1;
} else {
// if paletted, then pal_n is our final components, and
// img_n is # components to decompress/filter.
s->img_n = 1;
if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG");
// if SCAN_header, have to scan to see if we have a tRNS
}
// even with SCAN_header, have to scan to see if we have a tRNS
break;
}
@ -5103,10 +5157,14 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG");
if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG");
has_trans = 1;
// non-paletted with tRNS = constant alpha. if header-scanning, we can stop now.
if (scan == STBI__SCAN_header) { ++s->img_n; return 1; }
if (z->depth == 16) {
for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning
tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
} else {
for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
for (k = 0; k < s->img_n && k < 3; ++k)
tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
}
}
break;
@ -5115,7 +5173,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
case STBI__PNG_TYPE('I','D','A','T'): {
if (first) return stbi__err("first not IHDR", "Corrupt PNG");
if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
if (scan == STBI__SCAN_header) {
// header scan definitely stops at first IDAT
if (pal_img_n)
s->img_n = pal_img_n;
return 1;
}
if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes");
if ((int)(ioff + c.length) < (int)ioff) return 0;
if (ioff + c.length > idata_limit) {
stbi__uint32 idata_limit_old = idata_limit;
@ -5498,8 +5562,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
psize = (info.offset - info.extra_read - info.hsz) >> 2;
}
if (psize == 0) {
if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) {
return stbi__errpuc("bad offset", "Corrupt BMP");
// accept some number of extra bytes after the header, but if the offset points either to before
// the header ends or implies a large amount of extra data, reject the file as malformed
int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original);
int header_limit = 1024; // max we actually read is below 256 bytes currently.
int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size.
if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) {
return stbi__errpuc("bad header", "Corrupt BMP");
}
// we established that bytes_read_so_far is positive and sensible.
// the first half of this test rejects offsets that are either too small positives, or
// negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn
// ensures the number computed in the second half of the test can't overflow.
if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) {
return stbi__errpuc("bad offset", "Corrupt BMP");
} else {
stbi__skip(s, info.offset - bytes_read_so_far);
}
}
@ -7187,12 +7265,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re
// Run
value = stbi__get8(s);
count -= 128;
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = value;
} else {
// Dump
if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
for (z = 0; z < count; ++z)
scanline[i++ * 4 + k] = stbi__get8(s);
}
@ -7446,10 +7524,17 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req
out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0);
if (!out) return stbi__errpuc("outofmem", "Out of memory");
stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8));
if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) {
STBI_FREE(out);
return stbi__errpuc("bad PNM", "PNM file truncated");
}
if (req_comp && req_comp != s->img_n) {
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
if (ri->bits_per_channel == 16) {
out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y);
} else {
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
}
if (out == NULL) return out; // stbi__convert_format frees input on failure
}
return out;
@ -7486,6 +7571,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c)
while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
value = value*10 + (*c - '0');
*c = (char) stbi__get8(s);
if((value > 214748364) || (value == 214748364 && *c > '7'))
return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int");
}
return value;
@ -7516,9 +7603,13 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
stbi__pnm_skip_whitespace(s, &c);
*x = stbi__pnm_getinteger(s, &c); // read width
if(*x == 0)
return stbi__err("invalid width", "PPM image header had zero or overflowing width");
stbi__pnm_skip_whitespace(s, &c);
*y = stbi__pnm_getinteger(s, &c); // read height
if (*y == 0)
return stbi__err("invalid width", "PPM image header had zero or overflowing width");
stbi__pnm_skip_whitespace(s, &c);
maxv = stbi__pnm_getinteger(s, &c); // read max value

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.18)
project(snes9x-gtk VERSION 1.62.3)
project(snes9x-gtk VERSION 1.63)
option(USE_SLANG "Build support for Vulkan output and .slangp shaders" ON)
option(USE_XV "Build support for XVideo output" ON)
@ -68,23 +68,23 @@ list(APPEND ARGS ${SDL2_CFLAGS} ${GTK_CFLAGS} ${XRANDR_CFLAGS})
list(APPEND LIBS ${X11} ${XEXT} ${CMAKE_DL_LIBS} ${SDL2_LIBRARIES} ${GTK_LIBRARIES} ${XRANDR_LIBRARIES})
list(APPEND SOURCES src/gtk_display_driver_opengl.cpp
../common/video/glx_context.cpp
../shaders/glsl.cpp
../shaders/shader_helpers.cpp
../vulkan/slang_helpers.cpp
../vulkan/slang_helpers.hpp
../vulkan/slang_preset_ini.cpp
../vulkan/slang_preset_ini.hpp
../common/video/opengl/glx_context.cpp
../common/video/opengl/shaders/glsl.cpp
../common/video/opengl/shaders/shader_helpers.cpp
../common/video/vulkan/slang_helpers.cpp
../common/video/vulkan/slang_helpers.hpp
../common/video/vulkan/slang_preset_ini.cpp
../common/video/vulkan/slang_preset_ini.hpp
../external/glad/src/glx.c
../external/glad/src/egl.c
../external/glad/src/gl.c
src/gtk_shader_parameters.cpp
../vulkan/std_chrono_throttle.cpp
../vulkan/std_chrono_throttle.hpp)
../common/video/std_chrono_throttle.cpp
../common/video/std_chrono_throttle.hpp)
list(APPEND INCLUDES ../external/glad/include)
if(USE_SLANG)
list(APPEND SOURCES ../shaders/slang.cpp)
list(APPEND SOURCES ../common/video/opengl/shaders/slang.cpp)
list(APPEND INCLUDES ../external/glslang)
set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS CACHE BOOL ON)
@ -116,26 +116,26 @@ if(USE_SLANG)
list(APPEND INCLUDES ../external/VulkanMemoryAllocator-Hpp/include)
list(APPEND INCLUDES ../external/stb)
list(APPEND SOURCES ../external/stb/stb_image_implementation.cpp)
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
list(APPEND SOURCES ../common/video/vulkan/slang_shader.cpp
../common/video/vulkan/slang_shader.hpp
../common/video/vulkan/slang_preset.cpp
../common/video/vulkan/slang_preset.hpp
../common/video/vulkan/vulkan_hpp_storage.cpp
../common/video/vulkan/vk_mem_alloc_implementation.cpp
../common/video/vulkan/vulkan_context.cpp
../common/video/vulkan/vulkan_context.hpp
../common/video/vulkan/vulkan_texture.cpp
../common/video/vulkan/vulkan_texture.hpp
../common/video/vulkan/vulkan_swapchain.cpp
../common/video/vulkan/vulkan_swapchain.hpp
../common/video/vulkan/vulkan_slang_pipeline.cpp
../common/video/vulkan/vulkan_slang_pipeline.hpp
../common/video/vulkan/vulkan_pipeline_image.cpp
../common/video/vulkan/vulkan_pipeline_image.hpp
../common/video/vulkan/vulkan_shader_chain.cpp
../common/video/vulkan/vulkan_shader_chain.hpp
../common/video/vulkan/vulkan_simple_output.hpp
../common/video/vulkan/vulkan_simple_output.cpp
src/gtk_display_driver_vulkan.cpp
src/gtk_display_driver_vulkan.h
../external/imgui/imgui_impl_vulkan.cpp)
@ -154,13 +154,13 @@ list(APPEND INCLUDES ../external/imgui)
if(USE_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED wayland-client wayland-egl)
list(APPEND DEFINES "USE_WAYLAND")
list(APPEND SOURCES ../common/video/wayland_egl_context.cpp
../common/video/wayland_egl_context.hpp
../common/video/wayland_surface.cpp
../common/video/wayland_surface.hpp
../common/video/wayland-idle-inhibit-unstable-v1.c
../common/video/viewporter-client-protocol.c
../common/video/fractional-scale-v1.c)
list(APPEND SOURCES ../common/video/opengl/wayland_egl_context.cpp
../common/video/opengl/wayland_egl_context.hpp
../common/video/wayland/wayland_surface.cpp
../common/video/wayland/wayland_surface.hpp
../common/video/wayland/wayland-idle-inhibit-unstable-v1.c
../common/video/wayland/viewporter-client-protocol.c
../common/video/wayland/fractional-scale-v1.c)
list(APPEND ARGS ${WAYLAND_CFLAGS})
list(APPEND LIBS ${WAYLAND_LIBRARIES})
endif()
@ -388,16 +388,16 @@ target_compile_definitions(snes9x-gtk PRIVATE ${DEFINES})
if(USE_SLANG)
add_executable(slang_test EXCLUDE_FROM_ALL
../vulkan/slang_helpers.cpp
../vulkan/slang_helpers.hpp
../vulkan/slang_shader.cpp
../vulkan/slang_shader.hpp
../vulkan/slang_preset.cpp
../vulkan/slang_preset.hpp
../vulkan/slang_preset_ini.cpp
../vulkan/slang_preset_ini.hpp
../vulkan/vulkan_hpp_storage.cpp
../vulkan/slang_preset_test.cpp
../common/video/vulkan/slang_helpers.cpp
../common/video/vulkan/slang_helpers.hpp
../common/video/vulkan/slang_shader.cpp
../common/video/vulkan/slang_shader.hpp
../common/video/vulkan/slang_preset.cpp
../common/video/vulkan/slang_preset.hpp
../common/video/vulkan/slang_preset_ini.cpp
../common/video/vulkan/slang_preset_ini.hpp
../common/video/vulkan/vulkan_hpp_storage.cpp
../common/video/vulkan/slang_preset_test.cpp
../conffile.cpp
../stream.cpp)

View File

@ -5,7 +5,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Snes9x 1.62.3\n"
"Project-Id-Version: Snes9x 1.63\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-29 23:56+0100\n"
"PO-Revision-Date: 2023-10-30 12:00+0100\n"

View File

@ -2,7 +2,7 @@
# Stanley Kid <stanley.udr.kid@gmail.com>, 2024.
msgid ""
msgstr ""
"Project-Id-Version: 1.62.3\n"
"Project-Id-Version: 1.63\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-29 23:56+0100\n"
"PO-Revision-Date: 2024-02-14 15:43+0200\n"

View File

@ -33,22 +33,21 @@ std::string get_config_dir()
return std::string{".snes9x"};
}
fs::path config = env_home;
fs::path legacy = config;
fs::path config;
// If XDG_CONFIG_HOME is set, use that, otherwise guess default
if (!env_xdg_config_home)
{
config /= ".config/snes9x";
legacy /= ".snes9x";
}
else
if (env_xdg_config_home)
{
config = env_xdg_config_home;
config /= "snes9x";
}
if (fs::exists(legacy) && !fs::exists(config))
return legacy;
else
{
config = env_home;
config /= ".config/snes9x";
}
if (!fs::exists(config))
fs::create_directories(config);
return config;
}

View File

@ -5,8 +5,10 @@
\*****************************************************************************/
#include <fcntl.h>
#include <filesystem>
#include "SDL_joystick.h"
#include "fscompat.h"
#include "gtk_s9x.h"
#include "gtk_config.h"
#include "gtk_control.h"
@ -195,7 +197,15 @@ static void change_slot(int difference)
if (!gui_config->rom_loaded)
return;
auto info_string = "State Slot: " + std::to_string(gui_config->current_save_slot);
char extension_string[5];
snprintf(extension_string, 5, ".%03d", gui_config->current_save_slot);
auto filename = S9xGetFilename(extension_string, SNAPSHOT_DIR);
struct stat info;
std::string exists = "empty";
if (stat(filename.c_str(), &info) == 0)
exists = "used";
auto info_string = "State Slot: " + std::to_string(gui_config->current_save_slot) + " [" + exists + "]";
S9xSetInfoString(info_string.c_str());
GFX.InfoStringTimeout = 60;
}

View File

@ -12,18 +12,18 @@
#include <glad/gl.h>
#include "common/video/opengl_context.hpp"
#include "common/video/opengl/opengl_context.hpp"
#include "gtk_compat.h"
#ifdef GDK_WINDOWING_X11
#include "common/video/glx_context.hpp"
#include "common/video/opengl/glx_context.hpp"
#endif
#ifdef GDK_WINDOWING_WAYLAND
#include "common/video/wayland_egl_context.hpp"
#include "common/video/opengl/wayland_egl_context.hpp"
#endif
#include "shaders/glsl.h"
#include "vulkan/std_chrono_throttle.hpp"
#include "common/video/opengl/shaders/glsl.h"
#include "common/video/std_chrono_throttle.hpp"
#define BUFFER_OFFSET(i) ((char *)(i))

View File

@ -66,12 +66,12 @@ bool S9xVulkanDisplayDriver::init_imgui()
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.DescriptorPool = static_cast<VkDescriptorPool>(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());
ImGui_ImplVulkan_Init(&init_info, static_cast<VkRenderPass>(context->swapchain->get_render_pass()));
auto cmd = context->begin_cmd_buffer();
ImGui_ImplVulkan_CreateFontsTexture(cmd);
@ -222,6 +222,7 @@ void S9xVulkanDisplayDriver::update(uint16_t *buffer, int width, int height, int
throttle.wait_for_frame_and_rebase_time();
}
context->swapchain->set_vsync(gui_config->sync_to_vblank);
context->swapchain->swap();
if (gui_config->reduce_input_lag)

View File

@ -7,13 +7,13 @@
#pragma once
#include "gtk_s9x.h"
#include "gtk_display_driver.h"
#include "vulkan/vulkan_context.hpp"
#include "vulkan/vulkan_shader_chain.hpp"
#include "vulkan/vulkan_simple_output.hpp"
#include "vulkan/std_chrono_throttle.hpp"
#include "common/video/vulkan/vulkan_context.hpp"
#include "common/video/vulkan/vulkan_shader_chain.hpp"
#include "common/video/vulkan/vulkan_simple_output.hpp"
#include "common/video/std_chrono_throttle.hpp"
#ifdef VK_USE_PLATFORM_WAYLAND_KHR
#include "common/video/wayland_surface.hpp"
#include "common/video/wayland/wayland_surface.hpp"
#endif
class S9xVulkanDisplayDriver : public S9xDisplayDriver

View File

@ -11,7 +11,7 @@
#include "gtk_s9x.h"
#include "gtk_display.h"
#include "gtk_shader_parameters.h"
#include "shaders/glsl.h"
#include "common/video/opengl/shaders/glsl.h"
#include "gfx.h"

View File

@ -1247,6 +1247,8 @@ bool retro_load_game(const struct retro_game_info *game)
if (!rom_loaded && log_cb)
log_cb(RETRO_LOG_ERROR, "ROM loading failed...\n");
Memory.ClearSRAM();
return rom_loaded;
}

View File

@ -162,7 +162,7 @@
<key>CFBundleExecutable</key>
<string>Snes9x (i386)</string>
<key>CFBundleGetInfoString</key>
<string>Snes9x 1.62.3, Copyright 1996-2023 Snes9x developers.</string>
<string>Snes9x 1.63, Copyright 1996-2023 Snes9x developers.</string>
<key>CFBundleHelpBookFolder</key>
<string>Snes9x Help</string>
<key>CFBundleHelpBookName</key>

View File

@ -45,7 +45,6 @@ NSWindowFrameAutosaveName const kCheatFinderWindowIdentifier = @"s9xCheatFinderW
gameWindow.contentView.layer.backgroundColor = NSColor.blackColor.CGColor;
gameWindow.title = @"Snes9x";
gameWindow.restorationClass = [self class];
gameWindow.frameAutosaveName = kMainWindowIdentifier;
gameWindow.releasedWhenClosed = NO;
gameWindow.backgroundColor = NSColor.clearColor;
@ -660,7 +659,6 @@ NSWindowFrameAutosaveName const kCheatFinderWindowIdentifier = @"s9xCheatFinderW
window = self.cheatsWindowController.window;
window.title = NSLocalizedString(@"Cheats", nil);
window.restorationClass = self.class;
window.frameAutosaveName = kCheatsWindowIdentifier;
window.releasedWhenClosed = NO;
@ -695,7 +693,6 @@ NSWindowFrameAutosaveName const kCheatFinderWindowIdentifier = @"s9xCheatFinderW
window = self.cheatFinderWindowController.window;
window.title = NSLocalizedString(@"Cheat Finder", nil);
window.restorationClass = self.class;
window.frameAutosaveName = kCheatFinderWindowIdentifier;
window.releasedWhenClosed = NO;

View File

@ -1,5 +1,5 @@
/* Localized versions of Info.plist keys */
CFBundleName = "Snes9x";
CFBundleShortVersionString = "1.62.3";
CFBundleGetInfoString = "Snes9x 1.62.3, Copyright 1996-2023 Snes9x developers.";
CFBundleShortVersionString = "1.63";
CFBundleGetInfoString = "Snes9x 1.63, Copyright 1996-2023 Snes9x developers.";

View File

@ -131,7 +131,7 @@ bool SetButtonCodeForJoypadControl(uint32 vendorID, uint32 productID, uint32 ind
void ClearButtonCodeForJoypad(uint32 vendorID, uint32 productID, uint32 index, S9xButtonCode buttonCode);
void ClearJoypad(uint32 vendorID, uint32 productID, uint32 index);
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index);
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJoypadButtons(uint32 vendorID, uint32 productID, uint32 index);
std::string LabelForInput(uint32 vendorID, uint32 productID, uint32 cookie, int32 value);

View File

@ -684,7 +684,7 @@ void ClearJoypad(uint32 vendorID, uint32 productID, uint32 index)
}
}
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJuypadButtons(uint32 vendorID, uint32 productID, uint32 index)
std::unordered_map<struct JoypadInput, S9xButtonCode> GetJoypadButtons(uint32 vendorID, uint32 productID, uint32 index)
{
struct JoypadDevice device;
device.vendorID = vendorID;
@ -712,7 +712,50 @@ void SetUpHID (void)
{
IOHIDManagerRegisterInputValueCallback(hidManager, gamepadAction, NULL);
IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
IOHIDManagerSetDeviceMatching(hidManager, NULL);
CFMutableArrayRef matching = CFArrayCreateMutable(kCFAllocatorDefault, 4, &kCFTypeArrayCallBacks);
uint32 page = kHIDPage_GenericDesktop;
uint32 usage = kHIDUsage_GD_Joystick;
CFNumberRef pageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
CFNumberRef usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
CFMutableDictionaryRef entry = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsagePageKey), (void *)pageRef);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsageKey), (void *)usageRef);
CFArrayAppendValue(matching, entry);
CFRelease(usageRef);
CFRelease(entry);
usage = kHIDUsage_GD_GamePad;
usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
entry = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsagePageKey), (void *)pageRef);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsageKey), (void *)usageRef);
CFArrayAppendValue(matching, entry);
CFRelease(usageRef);
CFRelease(entry);
usage = kHIDUsage_GD_MultiAxisController;
usageRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
entry = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsagePageKey), (void *)pageRef);
CFDictionarySetValue(entry, CFSTR(kIOHIDDeviceUsageKey), (void *)usageRef);
CFArrayAppendValue(matching, entry);
CFRelease(usageRef);
CFRelease(pageRef);
CFRelease(entry);
uint32 vendor = 0x28DE; // Valve, apparently
CFNumberRef vendorRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vendor);
entry = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(entry, CFSTR(kIOHIDVendorIDKey), (void *)pageRef);
CFArrayAppendValue(matching, entry);
CFRelease(vendorRef);
CFRelease(entry);
IOHIDManagerSetDeviceMatchingMultiple(hidManager, matching);
CFRelease(matching);
ParseDefaults();

View File

@ -3286,7 +3286,7 @@ void QuitWithFatalError ( NSString *message)
{
pthread_mutex_lock(&keyLock);
NSMutableArray<S9xJoypadInput *> *inputs = [NSMutableArray new];
std::unordered_map<struct JoypadInput, S9xButtonCode> buttonCodeMap = GetJuypadButtons(vendorID, productID, index);
std::unordered_map<struct JoypadInput, S9xButtonCode> buttonCodeMap = GetJoypadButtons(vendorID, productID, index);
for (auto it = buttonCodeMap.begin(); it != buttonCodeMap.end(); ++it)
{
S9xJoypadInput *input = [S9xJoypadInput new];

View File

@ -1501,7 +1501,7 @@
INFOPLIST_FILE = Snes9x/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 1.62.3;
MARKETING_VERSION = 1.63;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.snes9x.macos.snes9x;
@ -1548,7 +1548,7 @@
INFOPLIST_FILE = Snes9x/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.12;
MARKETING_VERSION = 1.62.3;
MARKETING_VERSION = 1.63;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.snes9x.macos.snes9x;

View File

@ -3011,7 +3011,7 @@ void CMemory::Map_SA1LoROMMap (void)
// SA-1 Banks 40->4f
for (int c = 0x400; c < 0x500; c++)
SA1.Map[c] = SA1.WriteMap[c] = (uint8*)MAP_HIROM_SRAM;
SA1.Map[c] = SA1.WriteMap[c] = (uint8*) MAP_SA1RAM;
// SA-1 Banks 60->6f
for (int c = 0x600; c < 0x700; c++)

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(snes9x-qt VERSION 1.62)
project(snes9x-qt VERSION 1.63)
include(GNUInstallDirs)
@ -115,7 +115,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
list(APPEND LIBS libSDL2.a libz.a opengl32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32 dinput8)
list(APPEND DEFINES SDL_MAIN_HANDLED)
list(APPEND PLATFORM_SOURCES
../common/video/wgl_context.cpp
../common/video/opengl/wgl_context.cpp
../external/glad/src/wgl.c
src/resources/snes9x_win32.rc)
else()
@ -139,12 +139,18 @@ else()
endif()
list(APPEND 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
../common/video/opengl/glx_context.cpp
../common/video/opengl/glx_context.hpp
../common/video/opengl/wayland_egl_context.cpp
../common/video/opengl/wayland_egl_context.hpp
../common/video/wayland/wayland_surface.cpp
../common/video/wayland/wayland_surface.hpp
../common/video/wayland/fractional-scale-v1.c
../common/video/wayland/fractional-scale-v1.h
../common/video/wayland/viewporter-client-protocol.c
../common/video/wayland/viewporter-client-protocol.h
../common/video/wayland/wayland-idle-inhibit-unstable-v1.c
../common/video/wayland/wayland-idle-inhibit-unstable-v1.h
../external/glad/src/glx.c
../external/glad/src/egl.c)
endif()
@ -248,36 +254,36 @@ list(APPEND INCLUDES
../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
../common/video/vulkan/slang_shader.cpp
../common/video/vulkan/slang_shader.hpp
../common/video/vulkan/slang_preset.cpp
../common/video/vulkan/slang_preset.hpp
../common/video/vulkan/vulkan_hpp_storage.cpp
../common/video/vulkan/vk_mem_alloc_implementation.cpp
../common/video/vulkan/vulkan_context.cpp
../common/video/vulkan/vulkan_context.hpp
../common/video/vulkan/vulkan_texture.cpp
../common/video/vulkan/vulkan_texture.hpp
../common/video/vulkan/vulkan_swapchain.cpp
../common/video/vulkan/vulkan_swapchain.hpp
../common/video/vulkan/vulkan_slang_pipeline.cpp
../common/video/vulkan/vulkan_slang_pipeline.hpp
../common/video/vulkan/vulkan_pipeline_image.cpp
../common/video/vulkan/vulkan_pipeline_image.hpp
../common/video/vulkan/vulkan_shader_chain.cpp
../common/video/vulkan/vulkan_shader_chain.hpp
../common/video/vulkan/vulkan_simple_output.hpp
../common/video/vulkan/vulkan_simple_output.cpp
../common/video/std_chrono_throttle.cpp
../common/video/std_chrono_throttle.hpp
../common/video/vulkan/slang_helpers.cpp
../common/video/vulkan/slang_helpers.hpp
../common/video/vulkan/slang_preset_ini.cpp
../common/video/vulkan/slang_preset_ini.hpp
../external/stb/stb_image_implementation.cpp
../shaders/glsl.cpp
../shaders/slang.cpp
../shaders/shader_helpers.cpp)
../common/video/opengl/shaders/glsl.cpp
../common/video/opengl/shaders/slang.cpp
../common/video/opengl/shaders/shader_helpers.cpp)
list(APPEND DEFINES "IMGUI_IMPL_VULKAN_NO_PROTOTYPES")
list(APPEND SOURCES ../external/imgui/imgui.cpp

View File

@ -63,7 +63,7 @@ void CheatsDialog::addCode()
if (description.empty())
description = tr("No description").toStdString();
if (app->addCheat(description, code))
if (!app->addCheat(description, code))
{
QMessageBox::information(this, tr("Invalid Cheat"), tr("The cheat you entered was not valid."));
return;

View File

@ -98,6 +98,8 @@ DisplayPanel::DisplayPanel(EmuApplication *app_)
if (recreate)
app->window->recreateUIAssets();
});
groupBox_software_filters->hide();
}
DisplayPanel::~DisplayPanel()

View File

@ -311,7 +311,7 @@ Output directly will cause the screen to change between the two modes and look w
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<widget class="QGroupBox" name="groupBox_software_filters">
<property name="title">
<string>Software Filters</string>
</property>

View File

@ -335,22 +335,21 @@ void EmuApplication::handleBinding(std::string name, bool pressed)
window->pauseContinue();
}
else if (name == "IncreaseSlot")
else if (name == "IncreaseSlot" || name == "DecreaseSlot")
{
save_slot++;
if (name == "IncreaseSlot")
save_slot++;
else
save_slot--;
if (save_slot > 999)
save_slot = 0;
emu_thread->runOnThread([&] {
core->setMessage("Current slot: " + std::to_string(save_slot));
});
}
else if (name == "DecreaseSlot")
{
save_slot--;
if (save_slot < 0)
save_slot = 999;
emu_thread->runOnThread([&] {
core->setMessage("Current slot: " + std::to_string(save_slot));
emu_thread->runOnThread([&, slot = this->save_slot] {
std::string status = core->slotUsed(slot) ? " [used]" : " [empty]";
core->setMessage("Current slot: " + std::to_string(save_slot) + status);
});
}
else if (name == "SaveState")
@ -643,6 +642,11 @@ QString EmuApplication::iconPrefix()
return blackicons;
}
std::string EmuApplication::getContentFolder()
{
return core->getContentFolder();
}
void EmuThread::runOnThread(std::function<void()> func, bool blocking)
{
if (QThread::currentThread() != this)

View File

@ -85,6 +85,7 @@ struct EmuApplication
void stopThread();
bool isCoreActive();
QString iconPrefix();
std::string getContentFolder();
std::vector<std::tuple<bool, std::string, std::string>> getCheatList();
void disableAllCheats();

View File

@ -1,7 +1,7 @@
#pragma once
#include <QWidget>
#include <QImage>
#include "../../vulkan/std_chrono_throttle.hpp"
#include "common/video/std_chrono_throttle.hpp"
class EmuConfig;

View File

@ -3,17 +3,17 @@
#include <qpa/qplatformnativeinterface.h>
#include <QTimer>
#include <QMessageBox>
#include "common/video/opengl_context.hpp"
#include "common/video/opengl/opengl_context.hpp"
#ifndef _WIN32
#include "common/video/glx_context.hpp"
#include "common/video/wayland_egl_context.hpp"
#include "common/video/opengl/glx_context.hpp"
#include "common/video/opengl/wayland_egl_context.hpp"
using namespace QNativeInterface;
#include <X11/Xlib.h>
#else
#include "common/video/wgl_context.hpp"
#include "common/video/opengl/wgl_context.hpp"
#endif
#include "shaders/glsl.h"
#include "common/video/opengl/shaders/glsl.h"
#include "EmuMainWindow.hpp"
#include "snes9x_imgui.h"
#include "imgui_impl_opengl3.h"
@ -303,7 +303,7 @@ void EmuCanvasOpenGL::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
if (!context)
if (!context)
return;
auto g = parent->geometry();

View File

@ -1,10 +1,9 @@
#include <QtGui/QGuiApplication>
#include <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"
@ -178,9 +177,6 @@ void EmuCanvasVulkan::draw()
if (!window->isVisible())
return;
if (context->swapchain->set_vsync(config->enable_vsync))
context->recreate_swapchain();
if (S9xImGuiDraw(width() * devicePixelRatioF(), height() * devicePixelRatioF()))
{
auto draw_data = ImGui::GetDrawData();
@ -205,10 +201,11 @@ void EmuCanvasVulkan::draw()
if (retval)
{
throttle();
context->swapchain->set_vsync(config->enable_vsync);
context->swapchain->swap();
if (config->reduce_input_lag)
{
context->wait_idle();
context->swapchain->wait_on_frames();
}
}
}

View File

@ -3,11 +3,11 @@
#include "EmuCanvas.hpp"
#include "ShaderParametersDialog.hpp"
#include "../../vulkan/vulkan_simple_output.hpp"
#include "../../vulkan/vulkan_shader_chain.hpp"
#include "common/video/vulkan/vulkan_simple_output.hpp"
#include "common/video/vulkan/vulkan_shader_chain.hpp"
#ifndef _WIN32
#include "common/video/wayland_surface.hpp"
#include "common/video/wayland/wayland_surface.hpp"
#endif
class EmuCanvasVulkan : public EmuCanvas

View File

@ -144,7 +144,7 @@ std::string EmuConfig::findConfigDir()
fs::path path;
auto app_dir_path = QGuiApplication::applicationDirPath();
auto config_file = QDir(app_dir_path).absoluteFilePath("snes9x.conf");
auto config_file = QDir(app_dir_path).absoluteFilePath("snes9x-qt.conf");
if (QFile::exists(config_file))
return app_dir_path.toStdString();
@ -164,12 +164,12 @@ std::string EmuConfig::findConfigDir()
path = "./.snes9x";
}
#else
if ((dir = getenv("LOCALAPPDATA")))
if ((dir = getenv("APPDATA")))
{
path = dir;
path /= "Snes9x";
}
else if ((dir = getenv("APPDATA")))
else if ((dir = getenv("LOCALAPPDATA")))
{
path = dir;
path /= "Snes9x";

View File

@ -22,6 +22,27 @@
static EmuSettingsWindow *g_emu_settings_window = nullptr;
class DefaultBackground
: public QWidget
{
public:
DefaultBackground(QWidget *parent)
: QWidget(parent)
{
}
void paintEvent(QPaintEvent *event) override
{
QPainter paint(this);
QLinearGradient gradient(0.0, 0.0, 0.0, event->rect().toRectF().height());
gradient.setColorAt(0.0, QColor(0, 0, 128));
gradient.setColorAt(1.0, QColor(0, 0, 0));
paint.setBrush(QBrush(gradient));
paint.drawRect(0, 0, event->rect().width(), event->rect().height());
}
};
EmuMainWindow::EmuMainWindow(EmuApplication *app)
: app(app)
{
@ -33,12 +54,11 @@ EmuMainWindow::EmuMainWindow(EmuApplication *app)
mouse_timer.setTimerType(Qt::CoarseTimer);
mouse_timer.setInterval(1000);
mouse_timer.callOnTimeout([&] {
if (cursor_visible && isActivelyDrawing())
{
if (canvas)
canvas->setCursor(QCursor(Qt::BlankCursor));
cursor_visible = false;
mouse_timer.stop();
if (cursor_visible && isActivelyDrawing()) {
if (canvas)
canvas->setCursor(QCursor(Qt::BlankCursor));
cursor_visible = false;
mouse_timer.stop();
}
});
}
@ -71,10 +91,20 @@ void EmuMainWindow::destroyCanvas()
widget->deinit();
delete widget;
}
canvas = nullptr;
}
bool EmuMainWindow::createCanvas()
{
auto fallback = [this]() -> bool {
QMessageBox::warning(
this, tr("Unable to Start Display Driver"),
tr("Unable to create a %1 context. Attempting to use qt.")
.arg(QString::fromUtf8(app->config->display_driver)));
app->config->display_driver = "qt";
return createCanvas();
};
if (app->config->display_driver != "vulkan" &&
app->config->display_driver != "opengl" &&
app->config->display_driver != "qt")
@ -94,7 +124,7 @@ bool EmuMainWindow::createCanvas()
if (!canvas->createContext())
{
delete canvas;
return false;
return fallback();
}
}
else if (app->config->display_driver == "opengl")
@ -121,7 +151,7 @@ bool EmuMainWindow::createCanvas()
if (!canvas->createContext())
{
delete canvas;
return false;
return fallback();
}
}
else if (app->config->display_driver == "opengl")
@ -141,17 +171,12 @@ bool EmuMainWindow::createCanvas()
void EmuMainWindow::recreateCanvas()
{
if (!canvas)
return;
app->suspendThread();
destroyCanvas();
if (!createCanvas())
{
QMessageBox::warning(this,
tr("Unable to Start Display Driver"),
tr("Unable to create a %1 context. Attempting to use qt.").arg(QString::fromUtf8(app->config->display_driver)));
app->config->display_driver = "qt";
createCanvas();
}
createCanvas();
app->unsuspendThread();
}
@ -167,9 +192,11 @@ void EmuMainWindow::createWidgets()
setWindowTitle("Snes9x");
setWindowIcon(QIcon(":/icons/snes9x.svg"));
auto iconset = app->iconPrefix();
// File menu
auto file_menu = new QMenu(tr("&File"));
auto open_item = file_menu->addAction(QIcon::fromTheme("document-open"), tr("&Open File..."));
auto open_item = file_menu->addAction(QIcon(iconset + "open.svg"), tr("&Open File..."));
open_item->connect(open_item, &QAction::triggered, this, [&] {
openFile();
});
@ -200,7 +227,7 @@ void EmuMainWindow::createWidgets()
load_state_menu->addSeparator();
auto load_state_file_item = load_state_menu->addAction(QIcon::fromTheme("document-open"), tr("From &File..."));
auto load_state_file_item = load_state_menu->addAction(QIcon(iconset + "open.svg"), tr("From &File..."));
connect(load_state_file_item, &QAction::triggered, [&] {
this->chooseState(false);
});
@ -208,7 +235,7 @@ void EmuMainWindow::createWidgets()
load_state_menu->addSeparator();
auto load_state_undo_item = load_state_menu->addAction(QIcon::fromTheme("edit-undo"), tr("&Undo Load State"));
auto load_state_undo_item = load_state_menu->addAction(QIcon(iconset + "refresh.svg"), tr("&Undo Load State"));
connect(load_state_undo_item, &QAction::triggered, [&] {
app->loadUndoState();
});
@ -217,14 +244,14 @@ void EmuMainWindow::createWidgets()
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..."));
auto save_state_file_item = save_state_menu->addAction(QIcon(iconset + "save.svg"), 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"));
auto exit_item = new QAction(QIcon(iconset + "exit.svg"), tr("E&xit"));
exit_item->connect(exit_item, &QAction::triggered, this, [&](bool checked) {
close();
});
@ -245,7 +272,7 @@ void EmuMainWindow::createWidgets()
});
core_actions.push_back(run_item);
auto pause_item = emulation_menu->addAction(QIcon::fromTheme("media-playback-pause"), tr("&Pause"));
auto pause_item = emulation_menu->addAction(QIcon(iconset + "pause.svg"), tr("&Pause"));
connect(pause_item, &QAction::triggered, [&] {
if (!manual_pause)
{
@ -257,7 +284,7 @@ void EmuMainWindow::createWidgets()
emulation_menu->addSeparator();
auto reset_item = emulation_menu->addAction(QIcon::fromTheme("view-refresh"), tr("Rese&t"));
auto reset_item = emulation_menu->addAction(QIcon(iconset + "refresh.svg"), tr("Rese&t"));
connect(reset_item, &QAction::triggered, [&] {
app->reset();
if (manual_pause)
@ -268,7 +295,7 @@ void EmuMainWindow::createWidgets()
});
core_actions.push_back(reset_item);
auto hard_reset_item = emulation_menu->addAction(QIcon::fromTheme("process-stop"), tr("&Hard Reset"));
auto hard_reset_item = emulation_menu->addAction(QIcon(iconset + "reset.svg"), tr("&Hard Reset"));
connect(hard_reset_item, &QAction::triggered, [&] {
app->powerCycle();
if (manual_pause)
@ -308,7 +335,7 @@ void EmuMainWindow::createWidgets()
view_menu->addSeparator();
auto fullscreen_item = new QAction(QIcon::fromTheme("view-fullscreen"), tr("&Fullscreen"));
auto fullscreen_item = new QAction(QIcon(iconset + "fullscreen.svg"), tr("&Fullscreen"));
view_menu->addAction(fullscreen_item);
fullscreen_item->connect(fullscreen_item, &QAction::triggered, [&](bool checked) {
toggleFullscreen();
@ -328,7 +355,6 @@ void EmuMainWindow::createWidgets()
tr("&Controllers..."),
tr("Shortcu&ts..."),
tr("&Files...") };
QString iconset = app->iconPrefix();
const char *setting_icons[] = { "settings.svg",
"display.svg",
"sound.svg",
@ -361,6 +387,8 @@ void EmuMainWindow::createWidgets()
if (app->config->main_window_width != 0 && app->config->main_window_height != 0)
resize(app->config->main_window_width, app->config->main_window_height);
setCentralWidget(new DefaultBackground(this));
}
void EmuMainWindow::resizeToMultiple(int multiple)
@ -452,6 +480,12 @@ bool EmuMainWindow::openFile(std::string filename)
setCoreActionsEnabled(true);
if (!isFullScreen() && app->config->fullscreen_on_open)
toggleFullscreen();
if (!canvas)
if (!createCanvas())
return false;
QApplication::sync();
app->startGame();
mouse_timer.start();
return true;

View File

@ -39,8 +39,8 @@ class EmuMainWindow : public QMainWindow
void gameChanging();
void toggleMouseGrab();
std::vector<std::string> getDisplayDeviceList();
EmuApplication *app;
EmuCanvas *canvas;
EmuApplication *app = nullptr;
EmuCanvas *canvas = nullptr;
private:
void idle();

View File

@ -3,6 +3,7 @@
#include "EmuConfig.hpp"
#include <QSpinBox>
#include <QFileDialog>
#include <QDesktopServices>
FoldersPanel::FoldersPanel(EmuApplication *app_)
: app(app_)
@ -29,17 +30,6 @@ void FoldersPanel::connectEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButt
this->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()
@ -53,18 +43,56 @@ void FoldersPanel::refreshData()
void FoldersPanel::refreshEntry(QComboBox *combo, QLineEdit *lineEdit, QPushButton *browse, int *location, std::string *folder)
{
QString rom_dir;
bool custom = (*location == EmuConfig::eCustomDirectory);
combo->setCurrentIndex(*location);
if (custom)
{
lineEdit->setText(QString::fromUtf8(*folder));
}
else if (*location == EmuConfig::eConfigDirectory)
{
lineEdit->setText(tr("Config folder is %1").arg(app->config->findConfigDir().c_str()));
else
lineEdit->clear();
} else
{
rom_dir = QString::fromStdString(app->getContentFolder());
if (rom_dir.isEmpty())
rom_dir = QString::fromStdString(app->config->last_rom_folder);
lineEdit->setText("ROM Folder: " + rom_dir);
}
lineEdit->setEnabled(custom);
browse->setEnabled(custom);
browse->disconnect();
if (custom)
{
browse->setText(tr("Browse..."));
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::fromStdString(*folder));
app->updateSettings();
});
}
else
{
QString dir{};
if (*location == EmuConfig::eConfigDirectory)
dir = app->config->findConfigDir().c_str();
else if (*location == EmuConfig::eROMDirectory)
dir = rom_dir;
QObject::connect(browse, &QPushButton::pressed, [dir] {
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
});
lineEdit->setEnabled(custom);
browse->setText(tr("Open Folder..."));
}
}
void FoldersPanel::showEvent(QShowEvent *event)

View File

@ -74,7 +74,6 @@ SDLInputManager::DiscretizeHatEvent(SDL_Event &event)
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;
@ -226,4 +225,4 @@ std::vector<std::pair<int, std::string>> SDLInputManager::getXInputControllers()
}
return list;
}
}

View File

@ -1,6 +1,7 @@
#include "Snes9xController.hpp"
#include "EmuConfig.hpp"
#include "SoftwareScalers.hpp"
#include "fscompat.h"
#include <filesystem>
namespace fs = std::filesystem;
@ -531,7 +532,10 @@ void Snes9xController::clearSoundBuffer()
void S9xMessage(int message_class, int type, const char *message)
{
S9xSetInfoString(message);
if (type == S9X_ROM_INFO)
S9xSetInfoString(Memory.GetMultilineROMInfo().c_str());
printf("%s\n", message);
}
const char *S9xStringInput(const char *prompt)
@ -718,6 +722,11 @@ std::string Snes9xController::getStateFolder()
return S9xGetDirectory(SNAPSHOT_DIR);
}
bool Snes9xController::slotUsed(int slot)
{
return fs::exists(save_slot_path(slot));
}
bool Snes9xController::loadState(int slot)
{
return loadState(save_slot_path(slot).u8string());
@ -850,3 +859,8 @@ int Snes9xController::modifyCheat(int index, std::string name, std::string code)
{
return S9xModifyCheatGroup(index, name, code);
}
std::string Snes9xController::getContentFolder()
{
return S9xGetDirectory(ROMFILENAME_DIR);
}

View File

@ -16,13 +16,12 @@ class Snes9xController
void deinit();
void mainLoop();
bool openFile(std::string filename);
bool slotUsed(int slot);
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);
@ -47,6 +46,7 @@ class Snes9xController
int tryImportCheats(std::string filename);
std::string validateCheat(std::string code);
int modifyCheat(int index, std::string name, std::string code);
std::string getContentFolder();
std::string getStateFolder();
std::string config_folder;

View File

@ -6,6 +6,7 @@
#include <clocale>
#include <qnamespace.h>
#include <QStyle>
#include <QStyleHints>
#ifndef _WIN32
#include <csignal>
@ -28,35 +29,42 @@ int main(int argc, char *argv[])
if (emu.qtapp->platformName() == "windows")
{
emu.qtapp->setStyle("fusion");
if (emu.qtapp->styleHints()->colorScheme() == Qt::ColorScheme::Dark)
{
emu.qtapp->setStyle("fusion");
const QColor darkGray(53, 53, 53);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
const QColor blue(198, 238, 255);
const QColor blue2(0, 88, 208);
const QColor darkGray(53, 53, 53);
const QColor gray(128, 128, 128);
const QColor black(25, 25, 25);
const QColor blue(198, 238, 255);
const QColor blue2(0, 88, 208);
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, darkGray);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, black);
darkPalette.setColor(QPalette::AlternateBase, darkGray);
darkPalette.setColor(QPalette::ToolTipBase, blue2);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, darkGray);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, blue);
darkPalette.setColor(QPalette::Highlight, blue2);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
QPalette darkPalette;
darkPalette.setColor(QPalette::Window, darkGray);
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Base, black);
darkPalette.setColor(QPalette::AlternateBase, darkGray);
darkPalette.setColor(QPalette::ToolTipBase, blue2);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Button, darkGray);
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Link, blue);
darkPalette.setColor(QPalette::Highlight, blue2);
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::PlaceholderText, QColor(Qt::white).darker());
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
emu.qtapp->setPalette(darkPalette);
darkPalette.setColor(QPalette::Active, QPalette::Button, darkGray);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
emu.qtapp->setPalette(darkPalette);
}
else
{
emu.qtapp->setStyle("windowsvista");
}
}
#ifndef _WIN32

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M17,8l-1.41,1.41L17.17,11H9v2h8.17l-1.58,1.58L17,16l4-4L17,8z M5,5h7V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h7v-2H5V5z"/></g></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M15,22H6c-1.1,0-2-0.9-2-2V4c0-1.1,0.9-2,2-2h8l6,6v6h-2V9h-5V4H6v16h9V22z M19,21.66l0-2.24l2.95,2.95l1.41-1.41L20.41,18 h2.24v-2H17v5.66H19z"/></g></svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>

After

Width:  |  Height:  |  Size: 190 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M8 5v14l11-7z"/></svg>

After

Width:  |  Height:  |  Size: 170 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><g><path d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z M20,13c0-4.42-3.58-8-8-8c-0.06,0-0.12,0.01-0.18,0.01l1.09-1.09L11.5,2.5L8,6l3.5,3.5l1.41-1.41 l-1.08-1.08C11.89,7.01,11.95,7,12,7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02C16.95,20.44,20,17.08,20,13z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 539 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm2 16H5V5h11.17L19 7.83V19zm-7-7c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3zM6 6h9v4H6z"/></svg>

After

Width:  |  Height:  |  Size: 323 B

View File

@ -21,6 +21,14 @@
<file>whiteicons/up.svg</file>
<file>whiteicons/x.svg</file>
<file>whiteicons/y.svg</file>
<file>whiteicons/open.svg</file>
<file>whiteicons/pause.svg</file>
<file>whiteicons/play.svg</file>
<file>whiteicons/refresh.svg</file>
<file>whiteicons/reset.svg</file>
<file>whiteicons/save.svg</file>
<file>whiteicons/exit.svg</file>
<file>whiteicons/fullscreen.svg</file>
<file>snes9x.svg</file>
<file>blackicons/settings.svg</file>
<file>blackicons/folders.svg</file>
@ -43,5 +51,13 @@
<file>blackicons/x.svg</file>
<file>blackicons/y.svg</file>
<file>blackicons/shader.svg</file>
<file>blackicons/open.svg</file>
<file>blackicons/pause.svg</file>
<file>blackicons/play.svg</file>
<file>blackicons/refresh.svg</file>
<file>blackicons/reset.svg</file>
<file>blackicons/save.svg</file>
<file>blackicons/exit.svg</file>
<file>blackicons/fullscreen.svg</file>
</qresource>
</RCC>

View File

@ -13,12 +13,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "http://www.snes9x.com"
VALUE "FileDescription", "Snes9x"
VALUE "FileVersion", "1.62.3"
VALUE "FileVersion", "1.63"
VALUE "InternalName", "Snes9x"
VALUE "LegalCopyright", "Copyright (C) 1996-2023"
VALUE "OriginalFilename", "snes9x-qt.exe"
VALUE "ProductName", "Snes9x Qt Interface"
VALUE "ProductVersion", "1.62.3"
VALUE "ProductVersion", "1.63"
END
END

Some files were not shown because too many files have changed in this diff Show More